Merge branch 'master' into master
This commit is contained in:
128 files changed
+2793
-790
No files matched your search
@@ -124,8 +124,6 @@ jobs:
|
|||||||
}
|
}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: install stable
|
- name: install stable
|
||||||
uses: dtolnay/rust-toolchain@master
|
uses: dtolnay/rust-toolchain@master
|
||||||
with:
|
with:
|
||||||
@@ -141,6 +139,11 @@ jobs:
|
|||||||
workspaces: core -> ../target
|
workspaces: core -> ../target
|
||||||
save-if: ${{ matrix.features.key == 'all' }}
|
save-if: ${{ matrix.features.key == 'all' }}
|
||||||
|
|
||||||
|
- name: Install rustfmt
|
||||||
|
run: rustup component add rustfmt
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: test
|
- name: test
|
||||||
run: |
|
run: |
|
||||||
${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}
|
${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }}
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ jobs:
|
|||||||
|
|
||||||
# working-directory: tokio
|
# working-directory: tokio
|
||||||
env:
|
env:
|
||||||
# todo: disable memory leaks ignore
|
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields
|
||||||
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks
|
|
||||||
PROPTEST_CASES: 10
|
PROPTEST_CASES: 10
|
||||||
|
|
||||||
# Cache the global cargo directory, but NOT the local `target` directory which
|
# Cache the global cargo directory, but NOT the local `target` directory which
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
# args: --path packages/cli
|
# args: --path packages/cli
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
run: npx playwright test
|
run: npx playwright test
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: playwright-report
|
name: playwright-report
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
/dist
|
/dist
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
/examples/assets/test_video.mp4
|
||||||
|
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
|
|||||||
+4
-3
@@ -50,7 +50,7 @@ members = [
|
|||||||
exclude = ["examples/mobile_demo"]
|
exclude = ["examples/mobile_demo"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
|
|
||||||
# dependencies that are shared across packages
|
# dependencies that are shared across packages
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
@@ -77,7 +77,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" }
|
|||||||
dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
|
dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" }
|
||||||
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
|
rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" }
|
||||||
dioxus-signals = { path = "packages/signals" }
|
dioxus-signals = { path = "packages/signals" }
|
||||||
generational-box = { path = "packages/generational-box", version = "0.1.0" }
|
generational-box = { path = "packages/generational-box", version = "0.4.3" }
|
||||||
dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
|
dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" }
|
||||||
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
|
dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" }
|
||||||
dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
|
dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" }
|
||||||
@@ -99,7 +99,7 @@ prettyplease = { package = "prettier-please", version = "0.2", features = [
|
|||||||
# It is not meant to be published, but is used so "cargo run --example XYZ" works properly
|
# It is not meant to be published, but is used so "cargo run --example XYZ" works properly
|
||||||
[package]
|
[package]
|
||||||
name = "dioxus-examples"
|
name = "dioxus-examples"
|
||||||
version = "0.0.0"
|
version = "0.4.3"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Top level crate for the Dioxus repository"
|
description = "Top level crate for the Dioxus repository"
|
||||||
@@ -133,3 +133,4 @@ fern = { version = "0.6.0", features = ["colored"] }
|
|||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
simple_logger = "4.0.0"
|
simple_logger = "4.0.0"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
|
http-range = "0.1.5"
|
||||||
@@ -161,7 +161,7 @@ So... Dioxus is great, but why won't it work for me?
|
|||||||
## Contributing
|
## Contributing
|
||||||
- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.4/contributing).
|
- Check out the website [section on contributing](https://dioxuslabs.com/learn/0.4/contributing).
|
||||||
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
- Report issues on our [issue tracker](https://github.com/dioxuslabs/dioxus/issues).
|
||||||
- Join the discord and ask questions!
|
- [Join](https://discord.gg/XgGxMSkvUM) the discord and ask questions!
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
|
<a href="https://github.com/dioxuslabs/dioxus/graphs/contributors">
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cx.render(rsx! (
|
cx.render(rsx! (
|
||||||
div {
|
div { style: "{CONTAINER_STYLE}",
|
||||||
style: "{CONTAINER_STYLE}",
|
|
||||||
div {
|
div {
|
||||||
style: "{RECT_STYLE}",
|
style: "{RECT_STYLE}",
|
||||||
// focusing is necessary to catch keyboard events
|
// focusing is necessary to catch keyboard events
|
||||||
@@ -62,7 +61,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
|
|
||||||
onmousemove: move |event| log_event(Event::MouseMove(event)),
|
onmousemove: move |event| log_event(Event::MouseMove(event)),
|
||||||
onclick: move |event| log_event(Event::MouseClick(event)),
|
onclick: move |event| log_event(Event::MouseClick(event)),
|
||||||
ondblclick: move |event| log_event(Event::MouseDoubleClick(event)),
|
ondoubleclick: move |event| log_event(Event::MouseDoubleClick(event)),
|
||||||
onmousedown: move |event| log_event(Event::MouseDown(event)),
|
onmousedown: move |event| log_event(Event::MouseDown(event)),
|
||||||
onmouseup: move |event| log_event(Event::MouseUp(event)),
|
onmouseup: move |event| log_event(Event::MouseUp(event)),
|
||||||
|
|
||||||
@@ -77,9 +76,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
|
|
||||||
"Hover, click, type or scroll to see the info down below"
|
"Hover, click, type or scroll to see the info down below"
|
||||||
}
|
}
|
||||||
div {
|
div { events.read().iter().map(|event| rsx!( div { "{event:?}" } )) }
|
||||||
events.read().iter().map(|event| rsx!( div { "{event:?}" } ))
|
}
|
||||||
},
|
|
||||||
},
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_desktop::wry::http::Response;
|
||||||
|
use dioxus_desktop::{use_asset_handler, AssetRequest};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
dioxus_desktop::launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
use_asset_handler(cx, |request: &AssetRequest| {
|
||||||
|
let path = request.path().to_path_buf();
|
||||||
|
async move {
|
||||||
|
if path != Path::new("logo.png") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let image_data: &[u8] = include_bytes!("./assets/logo.png");
|
||||||
|
Some(Response::new(image_data.into()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
img {
|
||||||
|
src: "logo.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ frameworks = ["WebKit"]
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.56"
|
anyhow = "1.0.56"
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
wry = "0.28.0"
|
wry = "0.34.0"
|
||||||
dioxus = { path = "../../packages/dioxus" }
|
dioxus = { path = "../../packages/dioxus" }
|
||||||
dioxus-desktop = { path = "../../packages/desktop", features = [
|
dioxus-desktop = { path = "../../packages/desktop", features = [
|
||||||
"tokio_runtime",
|
"tokio_runtime",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "openid_auth_demo"
|
name = "openid_auth_demo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,20 @@ fn app(cx: Scope) -> Element {
|
|||||||
a: "asd".to_string(),
|
a: "asd".to_string(),
|
||||||
c: "asd".to_string(),
|
c: "asd".to_string(),
|
||||||
d: Some("asd".to_string()),
|
d: Some("asd".to_string()),
|
||||||
|
e: Some("asd".to_string()),
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
a: "asd".to_string(),
|
||||||
|
b: "asd".to_string(),
|
||||||
|
c: "asd".to_string(),
|
||||||
|
d: Some("asd".to_string()),
|
||||||
e: "asd".to_string(),
|
e: "asd".to_string(),
|
||||||
}
|
}
|
||||||
|
Button {
|
||||||
|
a: "asd".to_string(),
|
||||||
|
c: "asd".to_string(),
|
||||||
|
d: Some("asd".to_string()),
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
name = "query_segments_demo"
|
name = "query_segments_demo"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|||||||
@@ -18,4 +18,4 @@ dioxus = { path = "../../packages/dioxus" }
|
|||||||
dioxus-desktop = { path = "../../packages/desktop" }
|
dioxus-desktop = { path = "../../packages/desktop" }
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
dioxus-web = { path = "../../packages/web" }
|
dioxus-web = { path = "../../packages/web" }
|
||||||
+48
-41
@@ -48,11 +48,8 @@ pub fn app(cx: Scope<()>) -> Element {
|
|||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
section { class: "todoapp",
|
section { class: "todoapp",
|
||||||
style { include_str!("./assets/todomvc.css") }
|
style { include_str!("./assets/todomvc.css") }
|
||||||
TodoHeader {
|
TodoHeader { todos: todos }
|
||||||
todos: todos,
|
section { class: "main",
|
||||||
}
|
|
||||||
section {
|
|
||||||
class: "main",
|
|
||||||
if !todos.is_empty() {
|
if !todos.is_empty() {
|
||||||
rsx! {
|
rsx! {
|
||||||
input {
|
input {
|
||||||
@@ -103,31 +100,34 @@ pub fn TodoHeader<'a>(cx: Scope<'a, TodoHeaderProps<'a>>) -> Element {
|
|||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
header { class: "header",
|
header { class: "header",
|
||||||
h1 {"todos"}
|
h1 { "todos" }
|
||||||
input {
|
input {
|
||||||
class: "new-todo",
|
class: "new-todo",
|
||||||
placeholder: "What needs to be done?",
|
placeholder: "What needs to be done?",
|
||||||
value: "{draft}",
|
value: "{draft}",
|
||||||
autofocus: "true",
|
autofocus: "true",
|
||||||
oninput: move |evt| {
|
oninput: move |evt| {
|
||||||
draft.set(evt.value.clone());
|
draft.set(evt.value.clone());
|
||||||
},
|
},
|
||||||
onkeydown: move |evt| {
|
onkeydown: move |evt| {
|
||||||
if evt.key() == Key::Enter && !draft.is_empty() {
|
if evt.key() == Key::Enter && !draft.is_empty() {
|
||||||
cx.props.todos.make_mut().insert(
|
cx.props
|
||||||
**todo_id,
|
.todos
|
||||||
TodoItem {
|
.make_mut()
|
||||||
id: **todo_id,
|
.insert(
|
||||||
checked: false,
|
**todo_id,
|
||||||
contents: draft.to_string(),
|
TodoItem {
|
||||||
},
|
id: **todo_id,
|
||||||
);
|
checked: false,
|
||||||
*todo_id.make_mut() += 1;
|
contents: draft.to_string(),
|
||||||
draft.set("".to_string());
|
},
|
||||||
|
);
|
||||||
|
*todo_id.make_mut() += 1;
|
||||||
|
draft.set("".to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,8 +146,7 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||||||
let editing = if **is_editing { "editing" } else { "" };
|
let editing = if **is_editing { "editing" } else { "" };
|
||||||
|
|
||||||
cx.render(rsx!{
|
cx.render(rsx!{
|
||||||
li {
|
li { class: "{completed} {editing}",
|
||||||
class: "{completed} {editing}",
|
|
||||||
div { class: "view",
|
div { class: "view",
|
||||||
input {
|
input {
|
||||||
class: "toggle",
|
class: "toggle",
|
||||||
@@ -160,14 +159,16 @@ pub fn TodoEntry<'a>(cx: Scope<'a, TodoEntryProps<'a>>) -> Element {
|
|||||||
}
|
}
|
||||||
label {
|
label {
|
||||||
r#for: "cbg-{todo.id}",
|
r#for: "cbg-{todo.id}",
|
||||||
ondblclick: move |_| is_editing.set(true),
|
ondoubleclick: move |_| is_editing.set(true),
|
||||||
prevent_default: "onclick",
|
prevent_default: "onclick",
|
||||||
"{todo.contents}"
|
"{todo.contents}"
|
||||||
}
|
}
|
||||||
button {
|
button {
|
||||||
class: "destroy",
|
class: "destroy",
|
||||||
onclick: move |_| { cx.props.todos.make_mut().remove(&todo.id); },
|
onclick: move |_| {
|
||||||
prevent_default: "onclick",
|
cx.props.todos.make_mut().remove(&todo.id);
|
||||||
|
},
|
||||||
|
prevent_default: "onclick"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is_editing.then(|| rsx!{
|
is_editing.then(|| rsx!{
|
||||||
@@ -213,15 +214,15 @@ pub fn ListFooter<'a>(cx: Scope<'a, ListFooterProps<'a>>) -> Element {
|
|||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
footer { class: "footer",
|
footer { class: "footer",
|
||||||
span { class: "todo-count",
|
span { class: "todo-count",
|
||||||
strong {"{active_todo_count} "}
|
strong { "{active_todo_count} " }
|
||||||
span {"{active_todo_text} left"}
|
span { "{active_todo_text} left" }
|
||||||
}
|
}
|
||||||
ul { class: "filters",
|
ul { class: "filters",
|
||||||
for (state, state_text, url) in [
|
for (state , state_text , url) in [
|
||||||
(FilterState::All, "All", "#/"),
|
(FilterState::All, "All", "#/"),
|
||||||
(FilterState::Active, "Active", "#/active"),
|
(FilterState::Active, "Active", "#/active"),
|
||||||
(FilterState::Completed, "Completed", "#/completed"),
|
(FilterState::Completed, "Completed", "#/completed"),
|
||||||
] {
|
] {
|
||||||
li {
|
li {
|
||||||
a {
|
a {
|
||||||
href: url,
|
href: url,
|
||||||
@@ -250,8 +251,14 @@ pub fn PageFooter(cx: Scope) -> Element {
|
|||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
footer { class: "info",
|
footer { class: "info",
|
||||||
p { "Double-click to edit a todo" }
|
p { "Double-click to edit a todo" }
|
||||||
p { "Created by ", a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }}
|
p {
|
||||||
p { "Part of ", a { href: "http://todomvc.com", "TodoMVC" }}
|
"Created by "
|
||||||
|
a { href: "http://github.com/jkelleyrtp/", "jkelleyrtp" }
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
"Part of "
|
||||||
|
a { href: "http://todomvc.com", "TodoMVC" }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_desktop::wry::http;
|
||||||
|
use dioxus_desktop::wry::http::Response;
|
||||||
|
use dioxus_desktop::{use_asset_handler, AssetRequest};
|
||||||
|
use http::{header::*, response::Builder as ResponseBuilder, status::StatusCode};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::{io::SeekFrom, path::PathBuf};
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use tokio::io::AsyncSeekExt;
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
|
||||||
|
const VIDEO_PATH: &str = "./examples/assets/test_video.mp4";
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let video_file = PathBuf::from(VIDEO_PATH);
|
||||||
|
if !video_file.exists() {
|
||||||
|
tokio::runtime::Runtime::new()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async move {
|
||||||
|
println!("Downloading video file...");
|
||||||
|
let video_url =
|
||||||
|
"http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4";
|
||||||
|
let mut response = reqwest::get(video_url).await.unwrap();
|
||||||
|
let mut file = tokio::fs::File::create(&video_file).await.unwrap();
|
||||||
|
while let Some(chunk) = response.chunk().await.unwrap() {
|
||||||
|
file.write_all(&chunk).await.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dioxus_desktop::launch(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
use_asset_handler(cx, move |request: &AssetRequest| {
|
||||||
|
let request = request.clone();
|
||||||
|
async move {
|
||||||
|
let video_file = PathBuf::from(VIDEO_PATH);
|
||||||
|
let mut file = tokio::fs::File::open(&video_file).await.unwrap();
|
||||||
|
let response: Option<Response<Cow<'static, [u8]>>> =
|
||||||
|
match get_stream_response(&mut file, &request).await {
|
||||||
|
Ok(response) => Some(response.map(Cow::Owned)),
|
||||||
|
Err(err) => {
|
||||||
|
eprintln!("Error: {}", err);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
response
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
render! {
|
||||||
|
div { video { src: "test_video.mp4", autoplay: true, controls: true, width: 640, height: 480 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_stream_response(
|
||||||
|
asset: &mut (impl tokio::io::AsyncSeek + tokio::io::AsyncRead + Unpin + Send + Sync),
|
||||||
|
request: &AssetRequest,
|
||||||
|
) -> Result<Response<Vec<u8>>, Box<dyn std::error::Error>> {
|
||||||
|
// get stream length
|
||||||
|
let len = {
|
||||||
|
let old_pos = asset.stream_position().await?;
|
||||||
|
let len = asset.seek(SeekFrom::End(0)).await?;
|
||||||
|
asset.seek(SeekFrom::Start(old_pos)).await?;
|
||||||
|
len
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut resp = ResponseBuilder::new().header(CONTENT_TYPE, "video/mp4");
|
||||||
|
|
||||||
|
// if the webview sent a range header, we need to send a 206 in return
|
||||||
|
// Actually only macOS and Windows are supported. Linux will ALWAYS return empty headers.
|
||||||
|
let http_response = if let Some(range_header) = request.headers().get("range") {
|
||||||
|
let not_satisfiable = || {
|
||||||
|
ResponseBuilder::new()
|
||||||
|
.status(StatusCode::RANGE_NOT_SATISFIABLE)
|
||||||
|
.header(CONTENT_RANGE, format!("bytes */{len}"))
|
||||||
|
.body(vec![])
|
||||||
|
};
|
||||||
|
|
||||||
|
// parse range header
|
||||||
|
let ranges = if let Ok(ranges) = http_range::HttpRange::parse(range_header.to_str()?, len) {
|
||||||
|
ranges
|
||||||
|
.iter()
|
||||||
|
// map the output back to spec range <start-end>, example: 0-499
|
||||||
|
.map(|r| (r.start, r.start + r.length - 1))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
return Ok(not_satisfiable()?);
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The Maximum bytes we send in one range
|
||||||
|
const MAX_LEN: u64 = 1000 * 1024;
|
||||||
|
|
||||||
|
if ranges.len() == 1 {
|
||||||
|
let &(start, mut end) = ranges.first().unwrap();
|
||||||
|
|
||||||
|
// check if a range is not satisfiable
|
||||||
|
//
|
||||||
|
// this should be already taken care of by HttpRange::parse
|
||||||
|
// but checking here again for extra assurance
|
||||||
|
if start >= len || end >= len || end < start {
|
||||||
|
return Ok(not_satisfiable()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjust end byte for MAX_LEN
|
||||||
|
end = start + (end - start).min(len - start).min(MAX_LEN - 1);
|
||||||
|
|
||||||
|
// calculate number of bytes needed to be read
|
||||||
|
let bytes_to_read = end + 1 - start;
|
||||||
|
|
||||||
|
// allocate a buf with a suitable capacity
|
||||||
|
let mut buf = Vec::with_capacity(bytes_to_read as usize);
|
||||||
|
// seek the file to the starting byte
|
||||||
|
asset.seek(SeekFrom::Start(start)).await?;
|
||||||
|
// read the needed bytes
|
||||||
|
asset.take(bytes_to_read).read_to_end(&mut buf).await?;
|
||||||
|
|
||||||
|
resp = resp.header(CONTENT_RANGE, format!("bytes {start}-{end}/{len}"));
|
||||||
|
resp = resp.header(CONTENT_LENGTH, end + 1 - start);
|
||||||
|
resp = resp.status(StatusCode::PARTIAL_CONTENT);
|
||||||
|
resp.body(buf)
|
||||||
|
} else {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
let ranges = ranges
|
||||||
|
.iter()
|
||||||
|
.filter_map(|&(start, mut end)| {
|
||||||
|
// filter out unsatisfiable ranges
|
||||||
|
//
|
||||||
|
// this should be already taken care of by HttpRange::parse
|
||||||
|
// but checking here again for extra assurance
|
||||||
|
if start >= len || end >= len || end < start {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
// adjust end byte for MAX_LEN
|
||||||
|
end = start + (end - start).min(len - start).min(MAX_LEN - 1);
|
||||||
|
Some((start, end))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let boundary = format!("{:x}", rand::random::<u64>());
|
||||||
|
let boundary_sep = format!("\r\n--{boundary}\r\n");
|
||||||
|
let boundary_closer = format!("\r\n--{boundary}\r\n");
|
||||||
|
|
||||||
|
resp = resp.header(
|
||||||
|
CONTENT_TYPE,
|
||||||
|
format!("multipart/byteranges; boundary={boundary}"),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (end, start) in ranges {
|
||||||
|
// a new range is being written, write the range boundary
|
||||||
|
buf.write_all(boundary_sep.as_bytes()).await?;
|
||||||
|
|
||||||
|
// write the needed headers `Content-Type` and `Content-Range`
|
||||||
|
buf.write_all(format!("{CONTENT_TYPE}: video/mp4\r\n").as_bytes())
|
||||||
|
.await?;
|
||||||
|
buf.write_all(format!("{CONTENT_RANGE}: bytes {start}-{end}/{len}\r\n").as_bytes())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// write the separator to indicate the start of the range body
|
||||||
|
buf.write_all("\r\n".as_bytes()).await?;
|
||||||
|
|
||||||
|
// calculate number of bytes needed to be read
|
||||||
|
let bytes_to_read = end + 1 - start;
|
||||||
|
|
||||||
|
let mut local_buf = vec![0_u8; bytes_to_read as usize];
|
||||||
|
asset.seek(SeekFrom::Start(start)).await?;
|
||||||
|
asset.read_exact(&mut local_buf).await?;
|
||||||
|
buf.extend_from_slice(&local_buf);
|
||||||
|
}
|
||||||
|
// all ranges have been written, write the closing boundary
|
||||||
|
buf.write_all(boundary_closer.as_bytes()).await?;
|
||||||
|
|
||||||
|
resp.body(buf)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
resp = resp.header(CONTENT_LENGTH, len);
|
||||||
|
let mut buf = Vec::with_capacity(len as usize);
|
||||||
|
asset.read_to_end(&mut buf).await?;
|
||||||
|
resp.body(buf)
|
||||||
|
};
|
||||||
|
|
||||||
|
http_response.map_err(Into::into)
|
||||||
|
}
|
||||||
@@ -8,13 +8,14 @@ use std::fmt::{Result, Write};
|
|||||||
|
|
||||||
use dioxus_rsx::IfmtInput;
|
use dioxus_rsx::IfmtInput;
|
||||||
|
|
||||||
use crate::write_ifmt;
|
use crate::{indent::IndentOptions, write_ifmt};
|
||||||
|
|
||||||
/// The output buffer that tracks indent and string
|
/// The output buffer that tracks indent and string
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Buffer {
|
pub struct Buffer {
|
||||||
pub buf: String,
|
pub buf: String,
|
||||||
pub indent: usize,
|
pub indent_level: usize,
|
||||||
|
pub indent: IndentOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Buffer {
|
impl Buffer {
|
||||||
@@ -31,16 +32,16 @@ impl Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn tab(&mut self) -> Result {
|
pub fn tab(&mut self) -> Result {
|
||||||
self.write_tabs(self.indent)
|
self.write_tabs(self.indent_level)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn indented_tab(&mut self) -> Result {
|
pub fn indented_tab(&mut self) -> Result {
|
||||||
self.write_tabs(self.indent + 1)
|
self.write_tabs(self.indent_level + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result {
|
pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result {
|
||||||
for _ in 0..num {
|
for _ in 0..num {
|
||||||
write!(self.buf, " ")?
|
write!(self.buf, "{}", self.indent.indent_str())?
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ impl Writer<'_> {
|
|||||||
|
|
||||||
// check if we have a lot of attributes
|
// check if we have a lot of attributes
|
||||||
let attr_len = self.is_short_attrs(attributes);
|
let attr_len = self.is_short_attrs(attributes);
|
||||||
let is_short_attr_list = (attr_len + self.out.indent * 4) < 80;
|
let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80;
|
||||||
let children_len = self.is_short_children(children);
|
let children_len = self.is_short_children(children);
|
||||||
let is_small_children = children_len.is_some();
|
let is_small_children = children_len.is_some();
|
||||||
|
|
||||||
@@ -86,7 +86,7 @@ impl Writer<'_> {
|
|||||||
|
|
||||||
// if we have few children and few attributes, make it a one-liner
|
// if we have few children and few attributes, make it a one-liner
|
||||||
if is_short_attr_list && is_small_children {
|
if is_short_attr_list && is_small_children {
|
||||||
if children_len.unwrap() + attr_len + self.out.indent * 4 < 100 {
|
if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 {
|
||||||
opt_level = ShortOptimization::Oneliner;
|
opt_level = ShortOptimization::Oneliner;
|
||||||
} else {
|
} else {
|
||||||
opt_level = ShortOptimization::PropsOnTop;
|
opt_level = ShortOptimization::PropsOnTop;
|
||||||
@@ -185,11 +185,11 @@ impl Writer<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
while let Some(attr) = attr_iter.next() {
|
while let Some(attr) = attr_iter.next() {
|
||||||
self.out.indent += 1;
|
self.out.indent_level += 1;
|
||||||
if !sameline {
|
if !sameline {
|
||||||
self.write_comments(attr.attr.start())?;
|
self.write_comments(attr.attr.start())?;
|
||||||
}
|
}
|
||||||
self.out.indent -= 1;
|
self.out.indent_level -= 1;
|
||||||
|
|
||||||
if !sameline {
|
if !sameline {
|
||||||
self.out.indented_tabbed_line()?;
|
self.out.indented_tabbed_line()?;
|
||||||
@@ -398,14 +398,14 @@ impl Writer<'_> {
|
|||||||
for idx in start.line..end.line {
|
for idx in start.line..end.line {
|
||||||
let line = &self.src[idx];
|
let line = &self.src[idx];
|
||||||
if line.trim().starts_with("//") {
|
if line.trim().starts_with("//") {
|
||||||
for _ in 0..self.out.indent + 1 {
|
for _ in 0..self.out.indent_level + 1 {
|
||||||
write!(self.out, " ")?
|
write!(self.out, " ")?
|
||||||
}
|
}
|
||||||
writeln!(self.out, "{}", line.trim()).unwrap();
|
writeln!(self.out, "{}", line.trim()).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _ in 0..self.out.indent {
|
for _ in 0..self.out.indent_level {
|
||||||
write!(self.out, " ")?
|
write!(self.out, " ")?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ impl Writer<'_> {
|
|||||||
let first_line = &self.src[start.line - 1];
|
let first_line = &self.src[start.line - 1];
|
||||||
write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?;
|
write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?;
|
||||||
|
|
||||||
let prev_block_indent_level = crate::leading_whitespaces(first_line) / 4;
|
let prev_block_indent_level = self.out.indent.count_indents(first_line);
|
||||||
|
|
||||||
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
||||||
writeln!(self.out)?;
|
writeln!(self.out)?;
|
||||||
@@ -43,9 +43,9 @@ impl Writer<'_> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// trim the leading whitespace
|
// trim the leading whitespace
|
||||||
let previous_indent = crate::leading_whitespaces(line) / 4;
|
let previous_indent = self.out.indent.count_indents(line);
|
||||||
let offset = previous_indent.saturating_sub(prev_block_indent_level);
|
let offset = previous_indent.saturating_sub(prev_block_indent_level);
|
||||||
let required_indent = self.out.indent + offset;
|
let required_indent = self.out.indent_level + offset;
|
||||||
self.out.write_tabs(required_indent)?;
|
self.out.write_tabs(required_indent)?;
|
||||||
|
|
||||||
let line = line.trim_start();
|
let line = line.trim_start();
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
|
pub enum IndentType {
|
||||||
|
Spaces,
|
||||||
|
Tabs,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct IndentOptions {
|
||||||
|
width: usize,
|
||||||
|
indent_string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndentOptions {
|
||||||
|
pub fn new(typ: IndentType, width: usize) -> Self {
|
||||||
|
assert_ne!(width, 0, "Cannot have an indent width of 0");
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
indent_string: match typ {
|
||||||
|
IndentType::Tabs => "\t".into(),
|
||||||
|
IndentType::Spaces => " ".repeat(width),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets a string containing one indent worth of whitespace
|
||||||
|
pub fn indent_str(&self) -> &str {
|
||||||
|
&self.indent_string
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the line length in characters, counting tabs as the indent width.
|
||||||
|
pub fn line_length(&self, line: &str) -> usize {
|
||||||
|
line.chars()
|
||||||
|
.map(|ch| if ch == '\t' { self.width } else { 1 })
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Estimates how many times the line has been indented.
|
||||||
|
pub fn count_indents(&self, mut line: &str) -> usize {
|
||||||
|
let mut indent = 0;
|
||||||
|
while !line.is_empty() {
|
||||||
|
// Try to count tabs
|
||||||
|
let num_tabs = line.chars().take_while(|ch| *ch == '\t').count();
|
||||||
|
if num_tabs > 0 {
|
||||||
|
indent += num_tabs;
|
||||||
|
line = &line[num_tabs..];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to count spaces
|
||||||
|
let num_spaces = line.chars().take_while(|ch| *ch == ' ').count();
|
||||||
|
if num_spaces >= self.width {
|
||||||
|
// Intentionally floor here to take only the amount of space that matches an indent
|
||||||
|
let num_space_indents = num_spaces / self.width;
|
||||||
|
indent += num_space_indents;
|
||||||
|
line = &line[num_space_indents * self.width..];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Line starts with either non-indent characters or an unevent amount of spaces,
|
||||||
|
// so no more indent remains.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
indent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IndentOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(IndentType::Spaces, 4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn count_indents() {
|
||||||
|
assert_eq!(
|
||||||
|
IndentOptions::new(IndentType::Spaces, 4).count_indents("no indentation here!"),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\tv += 2"),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\t v += 2"),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
IndentOptions::new(IndentType::Spaces, 2).count_indents(" v += 2"),
|
||||||
|
2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-15
@@ -16,8 +16,11 @@ mod collect_macros;
|
|||||||
mod component;
|
mod component;
|
||||||
mod element;
|
mod element;
|
||||||
mod expr;
|
mod expr;
|
||||||
|
mod indent;
|
||||||
mod writer;
|
mod writer;
|
||||||
|
|
||||||
|
pub use indent::{IndentOptions, IndentType};
|
||||||
|
|
||||||
/// A modification to the original file to be applied by an IDE
|
/// A modification to the original file to be applied by an IDE
|
||||||
///
|
///
|
||||||
/// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
|
/// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes.
|
||||||
@@ -47,7 +50,7 @@ pub struct FormattedBlock {
|
|||||||
/// back to the file precisely.
|
/// back to the file precisely.
|
||||||
///
|
///
|
||||||
/// Nested blocks of RSX will be handled automatically
|
/// Nested blocks of RSX will be handled automatically
|
||||||
pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec<FormattedBlock> {
|
||||||
let mut formatted_blocks = Vec::new();
|
let mut formatted_blocks = Vec::new();
|
||||||
|
|
||||||
let parsed = syn::parse_file(contents).unwrap();
|
let parsed = syn::parse_file(contents).unwrap();
|
||||||
@@ -61,6 +64,7 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut writer = Writer::new(contents);
|
let mut writer = Writer::new(contents);
|
||||||
|
writer.out.indent = indent;
|
||||||
|
|
||||||
// Don't parse nested macros
|
// Don't parse nested macros
|
||||||
let mut end_span = LineColumn { column: 0, line: 0 };
|
let mut end_span = LineColumn { column: 0, line: 0 };
|
||||||
@@ -76,7 +80,10 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
|||||||
|
|
||||||
let rsx_start = macro_path.span().start();
|
let rsx_start = macro_path.span().start();
|
||||||
|
|
||||||
writer.out.indent = leading_whitespaces(writer.src[rsx_start.line - 1]) / 4;
|
writer.out.indent_level = writer
|
||||||
|
.out
|
||||||
|
.indent
|
||||||
|
.count_indents(writer.src[rsx_start.line - 1]);
|
||||||
|
|
||||||
write_body(&mut writer, &body);
|
write_body(&mut writer, &body);
|
||||||
|
|
||||||
@@ -159,12 +166,13 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option<String> {
|
|||||||
buf.consume()
|
buf.consume()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option<String> {
|
||||||
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).unwrap();
|
||||||
|
|
||||||
let mut buf = Writer::new(block);
|
let mut buf = Writer::new(block);
|
||||||
|
|
||||||
buf.out.indent = indent_level;
|
buf.out.indent = indent;
|
||||||
|
buf.out.indent_level = indent_level;
|
||||||
|
|
||||||
write_body(&mut buf, &body);
|
write_body(&mut buf, &body);
|
||||||
|
|
||||||
@@ -230,14 +238,3 @@ pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::f
|
|||||||
let display = DisplayIfmt(input);
|
let display = DisplayIfmt(input);
|
||||||
write!(writable, "{}", display)
|
write!(writable, "{}", display)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn leading_whitespaces(input: &str) -> usize {
|
|
||||||
input
|
|
||||||
.chars()
|
|
||||||
.map_while(|c| match c {
|
|
||||||
' ' => Some(1),
|
|
||||||
'\t' => Some(4),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
@@ -96,11 +96,11 @@ impl<'a> Writer<'a> {
|
|||||||
|
|
||||||
// Push out the indent level and write each component, line by line
|
// Push out the indent level and write each component, line by line
|
||||||
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
||||||
self.out.indent += 1;
|
self.out.indent_level += 1;
|
||||||
|
|
||||||
self.write_body_no_indent(children)?;
|
self.write_body_no_indent(children)?;
|
||||||
|
|
||||||
self.out.indent -= 1;
|
self.out.indent_level -= 1;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ macro_rules! twoway {
|
|||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
|
let src = include_str!(concat!("./samples/", stringify!($name), ".rsx"));
|
||||||
let formatted = dioxus_autofmt::fmt_file(src);
|
let formatted = dioxus_autofmt::fmt_file(src, Default::default());
|
||||||
let out = dioxus_autofmt::apply_formats(src, formatted);
|
let out = dioxus_autofmt::apply_formats(src, formatted);
|
||||||
// normalize line endings
|
// normalize line endings
|
||||||
let out = out.replace("\r", "");
|
let out = out.replace("\r", "");
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
use dioxus_autofmt::{IndentOptions, IndentType};
|
||||||
|
|
||||||
macro_rules! twoway {
|
macro_rules! twoway {
|
||||||
($val:literal => $name:ident) => {
|
($val:literal => $name:ident ($indent:expr)) => {
|
||||||
#[test]
|
#[test]
|
||||||
fn $name() {
|
fn $name() {
|
||||||
let src_right = include_str!(concat!("./wrong/", $val, ".rsx"));
|
let src_right = include_str!(concat!("./wrong/", $val, ".rsx"));
|
||||||
let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
|
let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx"));
|
||||||
let formatted = dioxus_autofmt::fmt_file(src_wrong);
|
let formatted = dioxus_autofmt::fmt_file(src_wrong, $indent);
|
||||||
let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
|
let out = dioxus_autofmt::apply_formats(src_wrong, formatted);
|
||||||
|
|
||||||
// normalize line endings
|
// normalize line endings
|
||||||
@@ -16,8 +18,11 @@ macro_rules! twoway {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
twoway!("comments" => comments);
|
twoway!("comments-4sp" => comments_4sp (IndentOptions::new(IndentType::Spaces, 4)));
|
||||||
|
twoway!("comments-tab" => comments_tab (IndentOptions::new(IndentType::Tabs, 4)));
|
||||||
|
|
||||||
twoway!("multi" => multi);
|
twoway!("multi-4sp" => multi_4sp (IndentOptions::new(IndentType::Spaces, 4)));
|
||||||
|
twoway!("multi-tab" => multi_tab (IndentOptions::new(IndentType::Tabs, 4)));
|
||||||
|
|
||||||
twoway!("multiexpr" => multiexpr);
|
twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4)));
|
||||||
|
twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4)));
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,7 @@
|
|||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
// Comments
|
||||||
|
class: "asdasd",
|
||||||
|
"hello world"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
rsx! {
|
||||||
|
div {
|
||||||
|
// Comments
|
||||||
|
class: "asdasd", "hello world" }
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,3 @@
|
|||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! { div { "hello world" } })
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {"hello world" }
|
||||||
|
})
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,8 @@
|
|||||||
|
fn ItWroks() {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
|
||||||
|
left,
|
||||||
|
right
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
fn ItWroks() {
|
||||||
|
cx.render(rsx! {
|
||||||
|
div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dioxus-cli"
|
name = "dioxus-cli"
|
||||||
version = "0.4.1"
|
version = "0.4.3"
|
||||||
authors = ["Jonathan Kelley"]
|
authors = ["Jonathan Kelley"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
description = "CLI tool for developing, testing, and publishing Dioxus apps"
|
||||||
@@ -83,6 +83,7 @@ dioxus-html = { workspace = true, features = ["hot-reload-context"] }
|
|||||||
dioxus-core = { workspace = true, features = ["serialize"] }
|
dioxus-core = { workspace = true, features = ["serialize"] }
|
||||||
dioxus-hot-reload = { workspace = true }
|
dioxus-hot-reload = { workspace = true }
|
||||||
interprocess-docfix = { version = "1.2.2" }
|
interprocess-docfix = { version = "1.2.2" }
|
||||||
|
gitignore = "1.0.8"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ It handles building, bundling, development and publishing to simplify developmen
|
|||||||
|
|
||||||
### Install the stable version (recommended)
|
### Install the stable version (recommended)
|
||||||
|
|
||||||
```
|
```shell
|
||||||
cargo install dioxus-cli
|
cargo install dioxus-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -20,7 +20,7 @@ To get the latest bug fixes and features, you can install the development versio
|
|||||||
However, this is not fully tested.
|
However, this is not fully tested.
|
||||||
That means you're probably going to have more bugs despite having the latest bug fixes.
|
That means you're probably going to have more bugs despite having the latest bug fixes.
|
||||||
|
|
||||||
```
|
```shell
|
||||||
cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli
|
cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ and install it in Cargo's global binary directory (`~/.cargo/bin/` by default).
|
|||||||
|
|
||||||
### Install from local folder
|
### Install from local folder
|
||||||
|
|
||||||
```
|
```shell
|
||||||
cargo install --path . --debug
|
cargo install --path . --debug
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ It will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxu
|
|||||||
|
|
||||||
Alternatively, you can specify the template path:
|
Alternatively, you can specify the template path:
|
||||||
|
|
||||||
```
|
```shell
|
||||||
dx create hello --template gh:dioxuslabs/dioxus-template
|
dx create hello --template gh:dioxuslabs/dioxus-template
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,18 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
|
|||||||
|
|
||||||
// [1] Build the .wasm module
|
// [1] Build the .wasm module
|
||||||
log::info!("🚅 Running build command...");
|
log::info!("🚅 Running build command...");
|
||||||
|
|
||||||
|
let wasm_check_command = std::process::Command::new("rustup")
|
||||||
|
.args(["show"])
|
||||||
|
.output()?;
|
||||||
|
let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap();
|
||||||
|
if !wasm_check_output.contains("wasm32-unknown-unknown") {
|
||||||
|
log::info!("wasm32-unknown-unknown target not detected, installing..");
|
||||||
|
let _ = std::process::Command::new("rustup")
|
||||||
|
.args(["target", "add", "wasm32-unknown-unknown"])
|
||||||
|
.output()?;
|
||||||
|
}
|
||||||
|
|
||||||
let cmd = subprocess::Exec::cmd("cargo");
|
let cmd = subprocess::Exec::cmd("cargo");
|
||||||
let cmd = cmd
|
let cmd = cmd
|
||||||
.cwd(crate_dir)
|
.cwd(crate_dir)
|
||||||
@@ -81,6 +93,8 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result<BuildResult> {
|
|||||||
cmd
|
cmd
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let cmd = cmd.args(&config.cargo_args);
|
||||||
|
|
||||||
let cmd = match executable {
|
let cmd = match executable {
|
||||||
ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
|
ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
|
||||||
ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
|
ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
|
||||||
@@ -253,7 +267,6 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
|
|||||||
let mut cmd = subprocess::Exec::cmd("cargo")
|
let mut cmd = subprocess::Exec::cmd("cargo")
|
||||||
.cwd(&config.crate_dir)
|
.cwd(&config.crate_dir)
|
||||||
.arg("build")
|
.arg("build")
|
||||||
.arg("--quiet")
|
|
||||||
.arg("--message-format=json");
|
.arg("--message-format=json");
|
||||||
|
|
||||||
if config.release {
|
if config.release {
|
||||||
@@ -261,6 +274,8 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
|
|||||||
}
|
}
|
||||||
if config.verbose {
|
if config.verbose {
|
||||||
cmd = cmd.arg("--verbose");
|
cmd = cmd.arg("--verbose");
|
||||||
|
} else {
|
||||||
|
cmd = cmd.arg("--quiet");
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.custom_profile.is_some() {
|
if config.custom_profile.is_some() {
|
||||||
@@ -273,6 +288,14 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
|
|||||||
cmd = cmd.arg("--features").arg(features_str);
|
cmd = cmd.arg("--features").arg(features_str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(target) = &config.target {
|
||||||
|
cmd = cmd.arg("--target").arg(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
let target_platform = config.target.as_deref().unwrap_or("");
|
||||||
|
|
||||||
|
cmd = cmd.args(&config.cargo_args);
|
||||||
|
|
||||||
let cmd = match &config.executable {
|
let cmd = match &config.executable {
|
||||||
crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
|
crate::ExecutableType::Binary(name) => cmd.arg("--bin").arg(name),
|
||||||
crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
|
crate::ExecutableType::Lib(name) => cmd.arg("--lib").arg(name),
|
||||||
@@ -290,12 +313,17 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result<BuildResul
|
|||||||
let mut res_path = match &config.executable {
|
let mut res_path = match &config.executable {
|
||||||
crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => {
|
crate::ExecutableType::Binary(name) | crate::ExecutableType::Lib(name) => {
|
||||||
file_name = name.clone();
|
file_name = name.clone();
|
||||||
config.target_dir.join(release_type).join(name)
|
config
|
||||||
|
.target_dir
|
||||||
|
.join(target_platform)
|
||||||
|
.join(release_type)
|
||||||
|
.join(name)
|
||||||
}
|
}
|
||||||
crate::ExecutableType::Example(name) => {
|
crate::ExecutableType::Example(name) => {
|
||||||
file_name = name.clone();
|
file_name = name.clone();
|
||||||
config
|
config
|
||||||
.target_dir
|
.target_dir
|
||||||
|
.join(target_platform)
|
||||||
.join(release_type)
|
.join(release_type)
|
||||||
.join("examples")
|
.join("examples")
|
||||||
.join(name)
|
.join(name)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use dioxus_autofmt::{IndentOptions, IndentType};
|
||||||
use futures::{stream::FuturesUnordered, StreamExt};
|
use futures::{stream::FuturesUnordered, StreamExt};
|
||||||
use std::{fs, path::Path, process::exit};
|
use std::{fs, path::Path, process::exit};
|
||||||
|
|
||||||
@@ -26,16 +27,19 @@ pub struct Autoformat {
|
|||||||
impl Autoformat {
|
impl Autoformat {
|
||||||
// Todo: autoformat the entire crate
|
// Todo: autoformat the entire crate
|
||||||
pub async fn autoformat(self) -> Result<()> {
|
pub async fn autoformat(self) -> Result<()> {
|
||||||
|
let Autoformat { check, raw, file } = self;
|
||||||
|
|
||||||
// Default to formatting the project
|
// Default to formatting the project
|
||||||
if self.raw.is_none() && self.file.is_none() {
|
if raw.is_none() && file.is_none() {
|
||||||
if let Err(e) = autoformat_project(self.check).await {
|
if let Err(e) = autoformat_project(check).await {
|
||||||
eprintln!("error formatting project: {}", e);
|
eprintln!("error formatting project: {}", e);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(raw) = self.raw {
|
if let Some(raw) = raw {
|
||||||
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0) {
|
let indent = indentation_for(".")?;
|
||||||
|
if let Some(inner) = dioxus_autofmt::fmt_block(&raw, 0, indent) {
|
||||||
println!("{}", inner);
|
println!("{}", inner);
|
||||||
} else {
|
} else {
|
||||||
// exit process with error
|
// exit process with error
|
||||||
@@ -45,43 +49,90 @@ impl Autoformat {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Format single file
|
// Format single file
|
||||||
if let Some(file) = self.file {
|
if let Some(file) = file {
|
||||||
let file_content = if file == "-" {
|
refactor_file(file)?;
|
||||||
let mut contents = String::new();
|
|
||||||
std::io::stdin().read_to_string(&mut contents)?;
|
|
||||||
Ok(contents)
|
|
||||||
} else {
|
|
||||||
fs::read_to_string(&file)
|
|
||||||
};
|
|
||||||
|
|
||||||
match file_content {
|
|
||||||
Ok(s) => {
|
|
||||||
let edits = dioxus_autofmt::fmt_file(&s);
|
|
||||||
let out = dioxus_autofmt::apply_formats(&s, edits);
|
|
||||||
if file == "-" {
|
|
||||||
print!("{}", out);
|
|
||||||
} else {
|
|
||||||
match fs::write(&file, out) {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("formatted {}", file);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("failed to write formatted content to file: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("failed to open file: {}", e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn refactor_file(file: String) -> Result<(), Error> {
|
||||||
|
let indent = indentation_for(".")?;
|
||||||
|
let file_content = if file == "-" {
|
||||||
|
let mut contents = String::new();
|
||||||
|
std::io::stdin().read_to_string(&mut contents)?;
|
||||||
|
Ok(contents)
|
||||||
|
} else {
|
||||||
|
fs::read_to_string(&file)
|
||||||
|
};
|
||||||
|
let Ok(s) = file_content else {
|
||||||
|
eprintln!("failed to open file: {}", file_content.unwrap_err());
|
||||||
|
exit(1);
|
||||||
|
};
|
||||||
|
let edits = dioxus_autofmt::fmt_file(&s, indent);
|
||||||
|
let out = dioxus_autofmt::apply_formats(&s, edits);
|
||||||
|
|
||||||
|
if file == "-" {
|
||||||
|
print!("{}", out);
|
||||||
|
} else if let Err(e) = fs::write(&file, out) {
|
||||||
|
eprintln!("failed to write formatted content to file: {e}",);
|
||||||
|
} else {
|
||||||
|
println!("formatted {}", file);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_project_files(config: &CrateConfig) -> Vec<PathBuf> {
|
||||||
|
let mut files = vec![];
|
||||||
|
|
||||||
|
let gitignore_path = config.crate_dir.join(".gitignore");
|
||||||
|
if gitignore_path.is_file() {
|
||||||
|
let gitigno = gitignore::File::new(gitignore_path.as_path()).unwrap();
|
||||||
|
if let Ok(git_files) = gitigno.included_files() {
|
||||||
|
let git_files = git_files
|
||||||
|
.into_iter()
|
||||||
|
.filter(|f| f.ends_with(".rs") && !is_target_dir(f));
|
||||||
|
files.extend(git_files)
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
collect_rs_files(&config.crate_dir, &mut files);
|
||||||
|
}
|
||||||
|
|
||||||
|
files
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_target_dir(file: &Path) -> bool {
|
||||||
|
let stripped = if let Ok(cwd) = std::env::current_dir() {
|
||||||
|
file.strip_prefix(cwd).unwrap_or(file)
|
||||||
|
} else {
|
||||||
|
file
|
||||||
|
};
|
||||||
|
if let Some(first) = stripped.components().next() {
|
||||||
|
first.as_os_str() == "target"
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn format_file(
|
||||||
|
path: impl AsRef<Path>,
|
||||||
|
indent: IndentOptions,
|
||||||
|
) -> Result<usize, tokio::io::Error> {
|
||||||
|
let contents = tokio::fs::read_to_string(&path).await?;
|
||||||
|
|
||||||
|
let edits = dioxus_autofmt::fmt_file(&contents, indent);
|
||||||
|
let len = edits.len();
|
||||||
|
|
||||||
|
if !edits.is_empty() {
|
||||||
|
let out = dioxus_autofmt::apply_formats(&contents, edits);
|
||||||
|
tokio::fs::write(path, out).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(len)
|
||||||
|
}
|
||||||
|
|
||||||
/// Read every .rs file accessible when considering the .gitignore and try to format it
|
/// Read every .rs file accessible when considering the .gitignore and try to format it
|
||||||
///
|
///
|
||||||
/// Runs using Tokio for multithreading, so it should be really really fast
|
/// Runs using Tokio for multithreading, so it should be really really fast
|
||||||
@@ -90,42 +141,27 @@ impl Autoformat {
|
|||||||
async fn autoformat_project(check: bool) -> Result<()> {
|
async fn autoformat_project(check: bool) -> Result<()> {
|
||||||
let crate_config = crate::CrateConfig::new(None)?;
|
let crate_config = crate::CrateConfig::new(None)?;
|
||||||
|
|
||||||
let mut files_to_format = vec![];
|
let files_to_format = get_project_files(&crate_config);
|
||||||
collect_rs_files(&crate_config.crate_dir, &mut files_to_format);
|
|
||||||
|
if files_to_format.is_empty() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let indent = indentation_for(&files_to_format[0])?;
|
||||||
|
|
||||||
let counts = files_to_format
|
let counts = files_to_format
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|file| {
|
|
||||||
if file.components().any(|f| f.as_os_str() == "target") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
})
|
|
||||||
.map(|path| async {
|
.map(|path| async {
|
||||||
let _path = path.clone();
|
let path_clone = path.clone();
|
||||||
let res = tokio::spawn(async move {
|
let res = tokio::spawn(format_file(path, indent.clone())).await;
|
||||||
let contents = tokio::fs::read_to_string(&path).await?;
|
|
||||||
|
|
||||||
let edits = dioxus_autofmt::fmt_file(&contents);
|
|
||||||
let len = edits.len();
|
|
||||||
|
|
||||||
if !edits.is_empty() {
|
|
||||||
let out = dioxus_autofmt::apply_formats(&contents, edits);
|
|
||||||
tokio::fs::write(&path, out).await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(len) as Result<usize, tokio::io::Error>
|
|
||||||
})
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("error formatting file: {}\n{err}", _path.display());
|
eprintln!("error formatting file: {}\n{err}", path_clone.display());
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Ok(Err(err)) => {
|
Ok(Err(err)) => {
|
||||||
eprintln!("error formatting file: {}\n{err}", _path.display());
|
eprintln!("error formatting file: {}\n{err}", path_clone.display());
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
Ok(Ok(res)) => Some(res),
|
Ok(Ok(res)) => Some(res),
|
||||||
@@ -135,13 +171,7 @@ async fn autoformat_project(check: bool) -> Result<()> {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let files_formatted: usize = counts
|
let files_formatted: usize = counts.into_iter().flatten().sum();
|
||||||
.into_iter()
|
|
||||||
.map(|f| match f {
|
|
||||||
Some(res) => res,
|
|
||||||
_ => 0,
|
|
||||||
})
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
if files_formatted > 0 && check {
|
if files_formatted > 0 && check {
|
||||||
eprintln!("{} files needed formatting", files_formatted);
|
eprintln!("{} files needed formatting", files_formatted);
|
||||||
@@ -151,26 +181,67 @@ async fn autoformat_project(check: bool) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_rs_files(folder: &Path, files: &mut Vec<PathBuf>) {
|
fn indentation_for(file_or_dir: impl AsRef<Path>) -> Result<IndentOptions> {
|
||||||
let Ok(folder) = folder.read_dir() else {
|
let out = std::process::Command::new("cargo")
|
||||||
|
.args(["fmt", "--", "--print-config", "current"])
|
||||||
|
.arg(file_or_dir.as_ref())
|
||||||
|
.stdout(std::process::Stdio::piped())
|
||||||
|
.stderr(std::process::Stdio::inherit())
|
||||||
|
.output()?;
|
||||||
|
if !out.status.success() {
|
||||||
|
return Err(Error::CargoError("cargo fmt failed".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let config = String::from_utf8_lossy(&out.stdout);
|
||||||
|
|
||||||
|
let hard_tabs = config
|
||||||
|
.lines()
|
||||||
|
.find(|line| line.starts_with("hard_tabs "))
|
||||||
|
.and_then(|line| line.split_once('='))
|
||||||
|
.map(|(_, value)| value.trim() == "true")
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::RuntimeError("Could not find hard_tabs option in rustfmt config".into())
|
||||||
|
})?;
|
||||||
|
let tab_spaces = config
|
||||||
|
.lines()
|
||||||
|
.find(|line| line.starts_with("tab_spaces "))
|
||||||
|
.and_then(|line| line.split_once('='))
|
||||||
|
.map(|(_, value)| value.trim().parse::<usize>())
|
||||||
|
.ok_or_else(|| {
|
||||||
|
Error::RuntimeError("Could not find tab_spaces option in rustfmt config".into())
|
||||||
|
})?
|
||||||
|
.map_err(|_| {
|
||||||
|
Error::RuntimeError("Could not parse tab_spaces option in rustfmt config".into())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(IndentOptions::new(
|
||||||
|
if hard_tabs {
|
||||||
|
IndentType::Tabs
|
||||||
|
} else {
|
||||||
|
IndentType::Spaces
|
||||||
|
},
|
||||||
|
tab_spaces,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_rs_files(folder: &impl AsRef<Path>, files: &mut Vec<PathBuf>) {
|
||||||
|
if is_target_dir(folder.as_ref()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let Ok(folder) = folder.as_ref().read_dir() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
// load the gitignore
|
// load the gitignore
|
||||||
|
|
||||||
for entry in folder {
|
for entry in folder {
|
||||||
let Ok(entry) = entry else {
|
let Ok(entry) = entry else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
|
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
collect_rs_files(&path, files);
|
collect_rs_files(&path, files);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ext) = path.extension() {
|
if let Some(ext) = path.extension() {
|
||||||
if ext == "rs" {
|
if ext == "rs" && !is_target_dir(&path) {
|
||||||
files.push(path);
|
files.push(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,8 +37,14 @@ impl Build {
|
|||||||
.platform
|
.platform
|
||||||
.unwrap_or(crate_config.dioxus_config.application.default_platform);
|
.unwrap_or(crate_config.dioxus_config.application.default_platform);
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
if let Some(target) = self.build.target {
|
||||||
let _ = PluginManager::on_build_start(&crate_config, &platform);
|
crate_config.set_target(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
crate_config.set_cargo_args(self.build.cargo_args);
|
||||||
|
|
||||||
|
// #[cfg(feature = "plugin")]
|
||||||
|
// let _ = PluginManager::on_build_start(&crate_config, &platform);
|
||||||
|
|
||||||
match platform {
|
match platform {
|
||||||
Platform::Web => {
|
Platform::Web => {
|
||||||
@@ -66,8 +72,8 @@ impl Build {
|
|||||||
)?;
|
)?;
|
||||||
file.write_all(temp.as_bytes())?;
|
file.write_all(temp.as_bytes())?;
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
// #[cfg(feature = "plugin")]
|
||||||
let _ = PluginManager::on_build_finish(&crate_config, &platform);
|
// let _ = PluginManager::on_build_finish(&crate_config, &platform);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,12 @@ impl Bundle {
|
|||||||
crate_config.set_profile(self.build.profile.unwrap());
|
crate_config.set_profile(self.build.profile.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(target) = &self.build.target {
|
||||||
|
crate_config.set_target(target.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
crate_config.set_cargo_args(self.build.cargo_args);
|
||||||
|
|
||||||
// build the desktop app
|
// build the desktop app
|
||||||
build_desktop(&crate_config, false)?;
|
build_desktop(&crate_config, false)?;
|
||||||
|
|
||||||
@@ -148,6 +154,11 @@ impl Bundle {
|
|||||||
.collect(),
|
.collect(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(target) = &self.build.target {
|
||||||
|
settings = settings.target(target.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
let settings = settings.build();
|
let settings = settings.build();
|
||||||
|
|
||||||
// on macos we need to set CI=true (https://github.com/tauri-apps/tauri/issues/2567)
|
// on macos we need to set CI=true (https://github.com/tauri-apps/tauri/issues/2567)
|
||||||
@@ -156,9 +167,9 @@ impl Bundle {
|
|||||||
|
|
||||||
tauri_bundler::bundle::bundle_project(settings.unwrap()).unwrap_or_else(|err|{
|
tauri_bundler::bundle::bundle_project(settings.unwrap()).unwrap_or_else(|err|{
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
panic!("Failed to bundle project: {}\nMake sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)", err);
|
panic!("Failed to bundle project: {:#?}\nMake sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)", err);
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
panic!("Failed to bundle project: {}", err);
|
panic!("Failed to bundle project: {:#?}", err);
|
||||||
});
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ use super::*;
|
|||||||
/// Config options for the build system.
|
/// Config options for the build system.
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||||
pub struct ConfigOptsBuild {
|
pub struct ConfigOptsBuild {
|
||||||
/// The index HTML file to drive the bundling process [default: index.html]
|
|
||||||
#[arg(long)]
|
|
||||||
pub target: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Build in release mode [default: false]
|
/// Build in release mode [default: false]
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -35,14 +31,18 @@ pub struct ConfigOptsBuild {
|
|||||||
/// Space separated list of features to activate
|
/// Space separated list of features to activate
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub features: Option<Vec<String>>,
|
pub features: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Rustc platform triple
|
||||||
|
#[clap(long)]
|
||||||
|
pub target: Option<String>,
|
||||||
|
|
||||||
|
/// Extra arguments passed to cargo build
|
||||||
|
#[clap(last = true)]
|
||||||
|
pub cargo_args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
#[derive(Clone, Debug, Default, Deserialize, Parser)]
|
||||||
pub struct ConfigOptsServe {
|
pub struct ConfigOptsServe {
|
||||||
/// The index HTML file to drive the bundling process [default: index.html]
|
|
||||||
#[arg(short, long)]
|
|
||||||
pub target: Option<PathBuf>,
|
|
||||||
|
|
||||||
/// Port of dev server
|
/// Port of dev server
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
#[clap(default_value_t = 8080)]
|
#[clap(default_value_t = 8080)]
|
||||||
@@ -89,6 +89,14 @@ pub struct ConfigOptsServe {
|
|||||||
/// Space separated list of features to activate
|
/// Space separated list of features to activate
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub features: Option<Vec<String>>,
|
pub features: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Rustc platform triple
|
||||||
|
#[clap(long)]
|
||||||
|
pub target: Option<String>,
|
||||||
|
|
||||||
|
/// Extra arguments passed to cargo build
|
||||||
|
#[clap(last = true)]
|
||||||
|
pub cargo_args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)]
|
||||||
@@ -129,4 +137,12 @@ pub struct ConfigOptsBundle {
|
|||||||
/// Space separated list of features to activate
|
/// Space separated list of features to activate
|
||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub features: Option<Vec<String>>,
|
pub features: Option<Vec<String>>,
|
||||||
|
|
||||||
|
/// Rustc platform triple
|
||||||
|
#[clap(long)]
|
||||||
|
pub target: Option<String>,
|
||||||
|
|
||||||
|
/// Extra arguments passed to cargo build
|
||||||
|
#[clap(last = true)]
|
||||||
|
pub cargo_args: Vec<String>,
|
||||||
}
|
}
|
||||||
@@ -34,6 +34,12 @@ impl Serve {
|
|||||||
// Subdirectories don't work with the server
|
// Subdirectories don't work with the server
|
||||||
crate_config.dioxus_config.web.app.base_path = None;
|
crate_config.dioxus_config.web.app.base_path = None;
|
||||||
|
|
||||||
|
if let Some(target) = self.serve.target {
|
||||||
|
crate_config.set_target(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
crate_config.set_cargo_args(self.serve.cargo_args);
|
||||||
|
|
||||||
let platform = self
|
let platform = self
|
||||||
.serve
|
.serve
|
||||||
.platform
|
.platform
|
||||||
|
|||||||
@@ -211,6 +211,8 @@ pub struct CrateConfig {
|
|||||||
pub verbose: bool,
|
pub verbose: bool,
|
||||||
pub custom_profile: Option<String>,
|
pub custom_profile: Option<String>,
|
||||||
pub features: Option<Vec<String>>,
|
pub features: Option<Vec<String>>,
|
||||||
|
pub target: Option<String>,
|
||||||
|
pub cargo_args: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -278,6 +280,8 @@ impl CrateConfig {
|
|||||||
let verbose = false;
|
let verbose = false;
|
||||||
let custom_profile = None;
|
let custom_profile = None;
|
||||||
let features = None;
|
let features = None;
|
||||||
|
let target = None;
|
||||||
|
let cargo_args = vec![];
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
out_dir,
|
out_dir,
|
||||||
@@ -294,6 +298,8 @@ impl CrateConfig {
|
|||||||
custom_profile,
|
custom_profile,
|
||||||
features,
|
features,
|
||||||
verbose,
|
verbose,
|
||||||
|
target,
|
||||||
|
cargo_args,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,6 +337,16 @@ impl CrateConfig {
|
|||||||
self.features = Some(features);
|
self.features = Some(features);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_target(&mut self, target: String) -> &mut Self {
|
||||||
|
self.target = Some(target);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cargo_args(&mut self, cargo_args: Vec<String>) -> &mut Self {
|
||||||
|
self.cargo_args = cargo_args;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
|
||||||
|
|||||||
@@ -28,7 +28,19 @@ pub fn set_up_logging() {
|
|||||||
message = message,
|
message = message,
|
||||||
));
|
));
|
||||||
})
|
})
|
||||||
.level(log::LevelFilter::Info)
|
.level(match std::env::var("DIOXUS_LOG") {
|
||||||
|
Ok(level) => match level.to_lowercase().as_str() {
|
||||||
|
"error" => log::LevelFilter::Error,
|
||||||
|
"warn" => log::LevelFilter::Warn,
|
||||||
|
"info" => log::LevelFilter::Info,
|
||||||
|
"debug" => log::LevelFilter::Debug,
|
||||||
|
"trace" => log::LevelFilter::Trace,
|
||||||
|
_ => {
|
||||||
|
panic!("Invalid log level: {}", level)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(_) => log::LevelFilter::Info,
|
||||||
|
})
|
||||||
.chain(std::io::stdout())
|
.chain(std::io::stdout())
|
||||||
.apply()
|
.apply()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|||||||
+16
-13
@@ -42,34 +42,36 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
set_up_logging();
|
set_up_logging();
|
||||||
|
|
||||||
let bin = get_bin(args.bin)?;
|
let bin = get_bin(args.bin);
|
||||||
|
|
||||||
let _dioxus_config = DioxusConfig::load(Some(bin.clone()))
|
if let Ok(bin) = &bin {
|
||||||
|
let _dioxus_config = DioxusConfig::load(Some(bin.clone()))
|
||||||
.map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))?
|
.map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))?
|
||||||
.unwrap_or_else(|| {
|
.unwrap_or_else(|| {
|
||||||
log::warn!("You appear to be creating a Dioxus project from scratch; we will use the default config");
|
log::warn!("You appear to be creating a Dioxus project from scratch; we will use the default config");
|
||||||
DioxusConfig::default()
|
DioxusConfig::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
PluginManager::init(_dioxus_config.plugin)
|
PluginManager::init(_dioxus_config.plugin)
|
||||||
.map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?;
|
.map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?;
|
||||||
|
}
|
||||||
|
|
||||||
match args.action {
|
match args.action {
|
||||||
Translate(opts) => opts
|
Translate(opts) => opts
|
||||||
.translate()
|
.translate()
|
||||||
.map_err(|e| anyhow!("🚫 Translation of HTML into RSX failed: {}", e)),
|
.map_err(|e| anyhow!("🚫 Translation of HTML into RSX failed: {}", e)),
|
||||||
|
|
||||||
Build(opts) => opts
|
Build(opts) if bin.is_ok() => opts
|
||||||
.build(Some(bin.clone()))
|
.build(Some(bin.unwrap().clone()))
|
||||||
.map_err(|e| anyhow!("🚫 Building project failed: {}", e)),
|
.map_err(|e| anyhow!("🚫 Building project failed: {}", e)),
|
||||||
|
|
||||||
Clean(opts) => opts
|
Clean(opts) if bin.is_ok() => opts
|
||||||
.clean(Some(bin.clone()))
|
.clean(Some(bin.unwrap().clone()))
|
||||||
.map_err(|e| anyhow!("🚫 Cleaning project failed: {}", e)),
|
.map_err(|e| anyhow!("🚫 Cleaning project failed: {}", e)),
|
||||||
|
|
||||||
Serve(opts) => opts
|
Serve(opts) if bin.is_ok() => opts
|
||||||
.serve(Some(bin.clone()))
|
.serve(Some(bin.unwrap().clone()))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| anyhow!("🚫 Serving project failed: {}", e)),
|
.map_err(|e| anyhow!("🚫 Serving project failed: {}", e)),
|
||||||
|
|
||||||
@@ -81,8 +83,8 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
.config()
|
.config()
|
||||||
.map_err(|e| anyhow!("🚫 Configuring new project failed: {}", e)),
|
.map_err(|e| anyhow!("🚫 Configuring new project failed: {}", e)),
|
||||||
|
|
||||||
Bundle(opts) => opts
|
Bundle(opts) if bin.is_ok() => opts
|
||||||
.bundle(Some(bin.clone()))
|
.bundle(Some(bin.unwrap().clone()))
|
||||||
.map_err(|e| anyhow!("🚫 Bundling project failed: {}", e)),
|
.map_err(|e| anyhow!("🚫 Bundling project failed: {}", e)),
|
||||||
|
|
||||||
#[cfg(feature = "plugin")]
|
#[cfg(feature = "plugin")]
|
||||||
@@ -107,5 +109,6 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
_ => Err(anyhow::anyhow!(bin.unwrap_err())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -43,8 +43,6 @@ pub async fn startup(config: CrateConfig) -> Result<()> {
|
|||||||
|
|
||||||
let hot_reload_tx = broadcast::channel(100).0;
|
let hot_reload_tx = broadcast::channel(100).0;
|
||||||
|
|
||||||
clear_paths();
|
|
||||||
|
|
||||||
Some(HotReloadState {
|
Some(HotReloadState {
|
||||||
messages: hot_reload_tx.clone(),
|
messages: hot_reload_tx.clone(),
|
||||||
file_map: file_map.clone(),
|
file_map: file_map.clone(),
|
||||||
@@ -73,6 +71,7 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option<HotReloadState>
|
|||||||
|
|
||||||
move || {
|
move || {
|
||||||
let mut current_child = currently_running_child.write().unwrap();
|
let mut current_child = currently_running_child.write().unwrap();
|
||||||
|
log::trace!("Killing old process");
|
||||||
current_child.kill()?;
|
current_child.kill()?;
|
||||||
let (child, result) = start_desktop(&config)?;
|
let (child, result) = start_desktop(&config)?;
|
||||||
*current_child = child;
|
*current_child = child;
|
||||||
@@ -109,7 +108,14 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option<HotReloadState>
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()> {
|
async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()> {
|
||||||
match LocalSocketListener::bind("@dioxusin") {
|
let metadata = cargo_metadata::MetadataCommand::new()
|
||||||
|
.no_deps()
|
||||||
|
.exec()
|
||||||
|
.unwrap();
|
||||||
|
let target_dir = metadata.target_directory.as_std_path();
|
||||||
|
let path = target_dir.join("dioxusin");
|
||||||
|
clear_paths(&path);
|
||||||
|
match LocalSocketListener::bind(path) {
|
||||||
Ok(local_socket_stream) => {
|
Ok(local_socket_stream) => {
|
||||||
let aborted = Arc::new(Mutex::new(false));
|
let aborted = Arc::new(Mutex::new(false));
|
||||||
// States
|
// States
|
||||||
@@ -148,7 +154,11 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
|
|||||||
println!("Connected to hot reloading 🚀");
|
println!("Connected to hot reloading 🚀");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
if err.kind() != std::io::ErrorKind::WouldBlock {
|
let error_string = err.to_string();
|
||||||
|
// Filter out any error messages about a operation that may block and an error message that triggers on some operating systems that says "Waiting for a process to open the other end of the pipe" without WouldBlock being set
|
||||||
|
let display_error = err.kind() != std::io::ErrorKind::WouldBlock
|
||||||
|
&& !error_string.contains("Waiting for a process");
|
||||||
|
if display_error {
|
||||||
println!("Error connecting to hot reloading: {} (Hot reloading is a feature of the dioxus-cli. If you are not using the CLI, this error can be ignored)", err);
|
println!("Error connecting to hot reloading: {} (Hot reloading is a feature of the dioxus-cli. If you are not using the CLI, this error can be ignored)", err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,17 +191,14 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_paths() {
|
fn clear_paths(file_socket_path: &std::path::Path) {
|
||||||
if cfg!(target_os = "macos") {
|
if cfg!(target_os = "macos") {
|
||||||
// On unix, if you force quit the application, it can leave the file socket open
|
// 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
|
// 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
|
// 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 {
|
if file_socket_path.exists() {
|
||||||
let path = std::path::PathBuf::from(path);
|
let _ = std::fs::remove_file(file_socket_path);
|
||||||
if path.exists() {
|
|
||||||
let _ = std::fs::remove_file(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,6 +219,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool {
|
|||||||
|
|
||||||
pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> {
|
pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> {
|
||||||
// Run the desktop application
|
// Run the desktop application
|
||||||
|
log::trace!("Building application");
|
||||||
let result = crate::builder::build_desktop(config, true)?;
|
let result = crate::builder::build_desktop(config, true)?;
|
||||||
|
|
||||||
match &config.executable {
|
match &config.executable {
|
||||||
@@ -222,6 +230,7 @@ pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> {
|
|||||||
if cfg!(windows) {
|
if cfg!(windows) {
|
||||||
file.set_extension("exe");
|
file.set_extension("exe");
|
||||||
}
|
}
|
||||||
|
log::trace!("Running application from {:?}", file);
|
||||||
let child = Command::new(file.to_str().unwrap()).spawn()?;
|
let child = Command::new(file.to_str().unwrap()).spawn()?;
|
||||||
|
|
||||||
Ok((child, result))
|
Ok((child, result))
|
||||||
|
|||||||
@@ -22,17 +22,20 @@ pub fn print_console_info(
|
|||||||
options: PrettierOptions,
|
options: PrettierOptions,
|
||||||
web_info: Option<WebServerInfo>,
|
web_info: Option<WebServerInfo>,
|
||||||
) {
|
) {
|
||||||
if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") {
|
// Don't clear the screen if the user has set the DIOXUS_LOG environment variable to "trace" so that we can see the logs
|
||||||
"cls"
|
if Some("trace") != std::env::var("DIOXUS_LOG").ok().as_deref() {
|
||||||
} else {
|
if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") {
|
||||||
"clear"
|
"cls"
|
||||||
})
|
} else {
|
||||||
.output()
|
"clear"
|
||||||
{
|
})
|
||||||
print!("{}", String::from_utf8_lossy(&native_clearseq.stdout));
|
.output()
|
||||||
} else {
|
{
|
||||||
// Try ANSI-Escape characters
|
print!("{}", String::from_utf8_lossy(&native_clearseq.stdout));
|
||||||
print!("\x1b[2J\x1b[H");
|
} else {
|
||||||
|
// Try ANSI-Escape characters
|
||||||
|
print!("\x1b[2J\x1b[H");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut profile = if config.release { "Release" } else { "Debug" }.to_string();
|
let mut profile = if config.release { "Release" } else { "Debug" }.to_string();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use axum::{
|
|||||||
body::{Full, HttpBody},
|
body::{Full, HttpBody},
|
||||||
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
|
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
|
||||||
http::{
|
http::{
|
||||||
|
self,
|
||||||
header::{HeaderName, HeaderValue},
|
header::{HeaderName, HeaderValue},
|
||||||
Method, Response, StatusCode,
|
Method, Response, StatusCode,
|
||||||
},
|
},
|
||||||
@@ -262,7 +263,7 @@ async fn setup_router(
|
|||||||
.override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop)
|
.override_response_header(HeaderName::from_static("cross-origin-opener-policy"), coop)
|
||||||
.and_then(
|
.and_then(
|
||||||
move |response: Response<ServeFileSystemResponseBody>| async move {
|
move |response: Response<ServeFileSystemResponseBody>| async move {
|
||||||
let response = if file_service_config
|
let mut response = if file_service_config
|
||||||
.dioxus_config
|
.dioxus_config
|
||||||
.web
|
.web
|
||||||
.watcher
|
.watcher
|
||||||
@@ -290,6 +291,13 @@ async fn setup_router(
|
|||||||
} else {
|
} else {
|
||||||
response.map(|body| body.boxed())
|
response.map(|body| body.boxed())
|
||||||
};
|
};
|
||||||
|
let headers = response.headers_mut();
|
||||||
|
headers.insert(
|
||||||
|
http::header::CACHE_CONTROL,
|
||||||
|
HeaderValue::from_static("no-cache"),
|
||||||
|
);
|
||||||
|
headers.insert(http::header::PRAGMA, HeaderValue::from_static("no-cache"));
|
||||||
|
headers.insert(http::header::EXPIRES, HeaderValue::from_static("0"));
|
||||||
Ok(response)
|
Ok(response)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -58,8 +58,11 @@ impl ToTokens for ComponentDeserializerOutput {
|
|||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let comp_fn = &self.comp_fn;
|
let comp_fn = &self.comp_fn;
|
||||||
let props_struct = &self.props_struct;
|
let props_struct = &self.props_struct;
|
||||||
|
let fn_ident = &comp_fn.sig.ident;
|
||||||
|
|
||||||
|
let doc = format!("Properties for the [`{fn_ident}`] component.");
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
|
#[doc = #doc]
|
||||||
#props_struct
|
#props_struct
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#comp_fn
|
#comp_fn
|
||||||
|
|||||||
@@ -243,10 +243,6 @@ mod field_info {
|
|||||||
}
|
}
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn type_from_inside_option(&self, check_option_name: bool) -> Option<&syn::Type> {
|
|
||||||
type_from_inside_option(self.ty, check_option_name)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Default, Clone)]
|
||||||
@@ -783,31 +779,16 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
None => quote!(),
|
None => quote!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of
|
let arg_type = field_type;
|
||||||
// nesting is different so we have to do this little dance.
|
let (arg_type, arg_expr) =
|
||||||
let arg_type = if field.builder_attr.strip_option {
|
if field.builder_attr.auto_into || field.builder_attr.strip_option {
|
||||||
field.type_from_inside_option(false).ok_or_else(|| {
|
(
|
||||||
Error::new_spanned(
|
quote!(impl ::core::convert::Into<#arg_type>),
|
||||||
field_type,
|
quote!(#field_name.into()),
|
||||||
"can't `strip_option` - field is not `Option<...>`",
|
|
||||||
)
|
)
|
||||||
})?
|
} else {
|
||||||
} else {
|
(quote!(#arg_type), quote!(#field_name))
|
||||||
field_type
|
};
|
||||||
};
|
|
||||||
let (arg_type, arg_expr) = if field.builder_attr.auto_into {
|
|
||||||
(
|
|
||||||
quote!(impl ::core::convert::Into<#arg_type>),
|
|
||||||
quote!(#field_name.into()),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(quote!(#arg_type), quote!(#field_name))
|
|
||||||
};
|
|
||||||
let arg_expr = if field.builder_attr.strip_option {
|
|
||||||
quote!(Some(#arg_expr))
|
|
||||||
} else {
|
|
||||||
arg_expr
|
|
||||||
};
|
|
||||||
|
|
||||||
let repeated_fields_error_type_name = syn::Ident::new(
|
let repeated_fields_error_type_name = syn::Ident::new(
|
||||||
&format!(
|
&format!(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::nodes::RenderReturn;
|
use crate::nodes::RenderReturn;
|
||||||
use crate::{Attribute, AttributeValue};
|
use crate::{Attribute, AttributeValue, VComponent};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::cell::{Cell, UnsafeCell};
|
use std::cell::{Cell, UnsafeCell};
|
||||||
@@ -7,7 +7,10 @@ use std::cell::{Cell, UnsafeCell};
|
|||||||
pub(crate) struct BumpFrame {
|
pub(crate) struct BumpFrame {
|
||||||
pub bump: UnsafeCell<Bump>,
|
pub bump: UnsafeCell<Bump>,
|
||||||
pub node: Cell<*const RenderReturn<'static>>,
|
pub node: Cell<*const RenderReturn<'static>>,
|
||||||
|
|
||||||
|
// The bump allocator will not call the destructor of the objects it allocated. Attributes and props need to have there destructor called, so we keep a list of them to drop before the bump allocator is reset.
|
||||||
pub(crate) attributes_to_drop_before_reset: RefCell<Vec<*const Attribute<'static>>>,
|
pub(crate) attributes_to_drop_before_reset: RefCell<Vec<*const Attribute<'static>>>,
|
||||||
|
pub(crate) props_to_drop_before_reset: RefCell<Vec<*const VComponent<'static>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BumpFrame {
|
impl BumpFrame {
|
||||||
@@ -17,6 +20,7 @@ impl BumpFrame {
|
|||||||
bump: UnsafeCell::new(bump),
|
bump: UnsafeCell::new(bump),
|
||||||
node: Cell::new(std::ptr::null()),
|
node: Cell::new(std::ptr::null()),
|
||||||
attributes_to_drop_before_reset: Default::default(),
|
attributes_to_drop_before_reset: Default::default(),
|
||||||
|
props_to_drop_before_reset: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +45,10 @@ impl BumpFrame {
|
|||||||
.push(attribute);
|
.push(attribute);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reset the bump allocator and drop all the attributes and props that were allocated in it.
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The caller must insure that no reference to anything allocated in the bump allocator is available after this function is called.
|
||||||
pub(crate) unsafe fn reset(&self) {
|
pub(crate) unsafe fn reset(&self) {
|
||||||
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
|
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
|
||||||
attributes.drain(..).for_each(|attribute| {
|
attributes.drain(..).for_each(|attribute| {
|
||||||
@@ -49,9 +57,20 @@ impl BumpFrame {
|
|||||||
_ = l.take();
|
_ = l.take();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
let mut props = self.props_to_drop_before_reset.borrow_mut();
|
||||||
|
props.drain(..).for_each(|prop| {
|
||||||
|
let prop = unsafe { &*prop };
|
||||||
|
_ = prop.props.borrow_mut().take();
|
||||||
|
});
|
||||||
unsafe {
|
unsafe {
|
||||||
let bump = &mut *self.bump.get();
|
let bump = &mut *self.bump.get();
|
||||||
bump.reset();
|
bump.reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for BumpFrame {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe { self.reset() }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -205,7 +205,7 @@ impl<'b> VirtualDom {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We write all the descndent data for this element
|
/// We write all the descendent data for this element
|
||||||
///
|
///
|
||||||
/// Elements can contain other nodes - and those nodes can be dynamic or static
|
/// Elements can contain other nodes - and those nodes can be dynamic or static
|
||||||
///
|
///
|
||||||
@@ -405,6 +405,7 @@ impl<'b> VirtualDom {
|
|||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
|
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
|
||||||
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
|
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
|
||||||
|
|
||||||
let byte_index = byte_index.parse::<usize>().unwrap();
|
let byte_index = byte_index.parse::<usize>().unwrap();
|
||||||
// First, check if we've already seen this template
|
// First, check if we've already seen this template
|
||||||
if self
|
if self
|
||||||
|
|||||||
@@ -560,7 +560,7 @@ impl<'b> VirtualDom {
|
|||||||
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
|
// If none of the old keys are reused by the new children, then we remove all the remaining old children and
|
||||||
// create the new children afresh.
|
// create the new children afresh.
|
||||||
if shared_keys.is_empty() {
|
if shared_keys.is_empty() {
|
||||||
if old.get(0).is_some() {
|
if old.first().is_some() {
|
||||||
self.remove_nodes(&old[1..]);
|
self.remove_nodes(&old[1..]);
|
||||||
self.replace(&old[0], new);
|
self.replace(&old[0], new);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use crate::{innerlude::VNode, ScopeState};
|
|||||||
|
|
||||||
/// A concrete type provider for closures that build [`VNode`] structures.
|
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||||
///
|
///
|
||||||
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
|
/// This struct wraps lazy structs that build [`VNode`] trees. Normally, we cannot perform a blanket implementation over
|
||||||
/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
|
/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -707,7 +707,7 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str {
|
|||||||
impl IntoDynNode<'_> for String {
|
impl IntoDynNode<'_> for String {
|
||||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||||
DynamicNode::Text(VText {
|
DynamicNode::Text(VText {
|
||||||
value: cx.bump().alloc(self),
|
value: cx.bump().alloc_str(&self),
|
||||||
id: Default::default(),
|
id: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -791,6 +791,12 @@ impl<'a> IntoAttributeValue<'a> for &'a str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoAttributeValue<'a> for String {
|
||||||
|
fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> {
|
||||||
|
AttributeValue::Text(cx.alloc_str(&self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> IntoAttributeValue<'a> for f64 {
|
impl<'a> IntoAttributeValue<'a> for f64 {
|
||||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||||
AttributeValue::Float(self)
|
AttributeValue::Float(self)
|
||||||
|
|||||||
@@ -230,17 +230,7 @@ impl ScopeContext {
|
|||||||
/// This is good for tasks that need to be run after the component has been dropped.
|
/// This is good for tasks that need to be run after the component has been dropped.
|
||||||
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
// The root scope will never be unmounted so we can just add the task at the top of the app
|
// The root scope will never be unmounted so we can just add the task at the top of the app
|
||||||
let id = self.tasks.spawn(ScopeId::ROOT, fut);
|
self.tasks.spawn(ScopeId::ROOT, fut)
|
||||||
|
|
||||||
// wake up the scheduler if it is sleeping
|
|
||||||
self.tasks
|
|
||||||
.sender
|
|
||||||
.unbounded_send(SchedulerMsg::TaskNotified(id))
|
|
||||||
.expect("Scheduler should exist");
|
|
||||||
|
|
||||||
self.spawned_tasks.borrow_mut().insert(id);
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Informs the scheduler that this task is no longer needed and should be removed.
|
/// Informs the scheduler that this task is no longer needed and should be removed.
|
||||||
|
|||||||
@@ -367,12 +367,17 @@ impl<'src> ScopeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut props = self.borrowed_props.borrow_mut();
|
let mut props = self.borrowed_props.borrow_mut();
|
||||||
|
let mut drop_props = self
|
||||||
|
.previous_frame()
|
||||||
|
.props_to_drop_before_reset
|
||||||
|
.borrow_mut();
|
||||||
for node in element.dynamic_nodes {
|
for node in element.dynamic_nodes {
|
||||||
if let DynamicNode::Component(comp) = node {
|
if let DynamicNode::Component(comp) = node {
|
||||||
|
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
|
||||||
if !comp.static_props {
|
if !comp.static_props {
|
||||||
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
|
|
||||||
props.push(unbounded);
|
props.push(unbounded);
|
||||||
}
|
}
|
||||||
|
drop_props.push(unbounded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ dioxus-hot-reload = { workspace = true, optional = true }
|
|||||||
serde = "1.0.136"
|
serde = "1.0.136"
|
||||||
serde_json = "1.0.79"
|
serde_json = "1.0.79"
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
wry = { version = "0.28.0", default-features = false, features = ["protocol", "file-drop"] }
|
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
|
wry = { version = "0.34.0", default-features = false, features = ["tao", "protocol", "file-drop"] }
|
||||||
futures-channel = { workspace = true }
|
futures-channel = { workspace = true }
|
||||||
tokio = { workspace = true, features = [
|
tokio = { workspace = true, features = [
|
||||||
"sync",
|
"sync",
|
||||||
@@ -37,10 +37,12 @@ slab = { workspace = true }
|
|||||||
futures-util = { workspace = true }
|
futures-util = { workspace = true }
|
||||||
urlencoding = "2.1.2"
|
urlencoding = "2.1.2"
|
||||||
async-trait = "0.1.68"
|
async-trait = "0.1.68"
|
||||||
|
crossbeam-channel = "0.5.8"
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
|
[target.'cfg(any(target_os = "windows",target_os = "macos",target_os = "linux",target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))'.dependencies]
|
||||||
rfd = "0.11.3"
|
rfd = "0.12"
|
||||||
|
global-hotkey = { git = "https://github.com/tauri-apps/global-hotkey" }
|
||||||
|
|
||||||
[target.'cfg(target_os = "ios")'.dependencies]
|
[target.'cfg(target_os = "ios")'.dependencies]
|
||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
@@ -56,9 +58,8 @@ tokio_runtime = ["tokio"]
|
|||||||
fullscreen = ["wry/fullscreen"]
|
fullscreen = ["wry/fullscreen"]
|
||||||
transparent = ["wry/transparent"]
|
transparent = ["wry/transparent"]
|
||||||
devtools = ["wry/devtools"]
|
devtools = ["wry/devtools"]
|
||||||
tray = ["wry/tray"]
|
|
||||||
dox = ["wry/dox"]
|
|
||||||
hot-reload = ["dioxus-hot-reload"]
|
hot-reload = ["dioxus-hot-reload"]
|
||||||
|
gnu = []
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
default-features = false
|
default-features = false
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
fn main() {
|
||||||
|
// WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently
|
||||||
|
if std::env::var("CARGO_CFG_WINDOWS").is_ok()
|
||||||
|
&& std::env::var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu"
|
||||||
|
&& !cfg!(feature = "gnu")
|
||||||
|
{
|
||||||
|
println!("cargo:warning=GNU windows targets have some limitations within Wry. Using the MSVC windows toolchain is recommended. If you would like to use continue using GNU, you can read https://github.com/wravery/webview2-rs#cross-compilation and disable this warning by adding the gnu feature to dioxus-desktop in your Cargo.toml")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -229,75 +229,111 @@ fn app(cx: Scope) -> Element {
|
|||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
assert!(event.data.modifiers().is_empty());
|
assert!(event.data.modifiers().is_empty());
|
||||||
assert!(event.data.held_buttons().is_empty());
|
assert!(event.data.held_buttons().is_empty());
|
||||||
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Primary));
|
assert_eq!(
|
||||||
|
event.data.trigger_button(),
|
||||||
|
Some(dioxus_html::input_data::MouseButton::Primary),
|
||||||
|
);
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
id: "mouse_move_div",
|
id: "mouse_move_div",
|
||||||
onmousemove: move |event| {
|
onmousemove: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
assert!(event.data.modifiers().is_empty());
|
assert!(event.data.modifiers().is_empty());
|
||||||
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
|
assert!(
|
||||||
|
event
|
||||||
|
.data
|
||||||
|
.held_buttons()
|
||||||
|
.contains(dioxus_html::input_data::MouseButton::Secondary),
|
||||||
|
);
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
div {
|
div {
|
||||||
id: "mouse_click_div",
|
id: "mouse_click_div",
|
||||||
onclick: move |event| {
|
onclick: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
assert!(event.data.modifiers().is_empty());
|
assert!(event.data.modifiers().is_empty());
|
||||||
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
|
assert!(
|
||||||
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
|
event
|
||||||
recieved_events.modify(|x| *x + 1)
|
.data
|
||||||
},
|
.held_buttons()
|
||||||
}
|
.contains(dioxus_html::input_data::MouseButton::Secondary),
|
||||||
div{
|
);
|
||||||
id: "mouse_dblclick_div",
|
assert_eq!(
|
||||||
ondblclick: move |event| {
|
event.data.trigger_button(),
|
||||||
println!("{:?}", event.data);
|
Some(dioxus_html::input_data::MouseButton::Secondary),
|
||||||
assert!(event.data.modifiers().is_empty());
|
);
|
||||||
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Primary));
|
|
||||||
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
|
|
||||||
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
|
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div{
|
div {
|
||||||
|
id: "mouse_dblclick_div",
|
||||||
|
ondoubleclick: move |event| {
|
||||||
|
println!("{:?}", event.data);
|
||||||
|
assert!(event.data.modifiers().is_empty());
|
||||||
|
assert!(
|
||||||
|
event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Primary),
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
event
|
||||||
|
.data
|
||||||
|
.held_buttons()
|
||||||
|
.contains(dioxus_html::input_data::MouseButton::Secondary),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
event.data.trigger_button(),
|
||||||
|
Some(dioxus_html::input_data::MouseButton::Secondary),
|
||||||
|
);
|
||||||
|
recieved_events.modify(|x| *x + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
div {
|
||||||
id: "mouse_down_div",
|
id: "mouse_down_div",
|
||||||
onmousedown: move |event| {
|
onmousedown: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
assert!(event.data.modifiers().is_empty());
|
assert!(event.data.modifiers().is_empty());
|
||||||
assert!(event.data.held_buttons().contains(dioxus_html::input_data::MouseButton::Secondary));
|
assert!(
|
||||||
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Secondary));
|
event
|
||||||
|
.data
|
||||||
|
.held_buttons()
|
||||||
|
.contains(dioxus_html::input_data::MouseButton::Secondary),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
event.data.trigger_button(),
|
||||||
|
Some(dioxus_html::input_data::MouseButton::Secondary),
|
||||||
|
);
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div{
|
div {
|
||||||
id: "mouse_up_div",
|
id: "mouse_up_div",
|
||||||
onmouseup: move |event| {
|
onmouseup: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
assert!(event.data.modifiers().is_empty());
|
assert!(event.data.modifiers().is_empty());
|
||||||
assert!(event.data.held_buttons().is_empty());
|
assert!(event.data.held_buttons().is_empty());
|
||||||
assert_eq!(event.data.trigger_button(), Some(dioxus_html::input_data::MouseButton::Primary));
|
assert_eq!(
|
||||||
|
event.data.trigger_button(),
|
||||||
|
Some(dioxus_html::input_data::MouseButton::Primary),
|
||||||
|
);
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div{
|
div {
|
||||||
id: "wheel_div",
|
id: "wheel_div",
|
||||||
width: "100px",
|
width: "100px",
|
||||||
height: "100px",
|
height: "100px",
|
||||||
background_color: "red",
|
background_color: "red",
|
||||||
onwheel: move |event| {
|
onwheel: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
let dioxus_html::geometry::WheelDelta::Pixels(delta)= event.data.delta()else{
|
let dioxus_html::geometry::WheelDelta::Pixels(delta) = event.data.delta() else {
|
||||||
panic!("Expected delta to be in pixels")
|
panic!("Expected delta to be in pixels") };
|
||||||
};
|
|
||||||
assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));
|
assert_eq!(delta, Vector3D::new(1.0, 2.0, 3.0));
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input{
|
input {
|
||||||
id: "key_down_div",
|
id: "key_down_div",
|
||||||
onkeydown: move |event| {
|
onkeydown: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
@@ -306,11 +342,10 @@ fn app(cx: Scope) -> Element {
|
|||||||
assert_eq!(event.data.code().to_string(), "KeyA");
|
assert_eq!(event.data.code().to_string(), "KeyA");
|
||||||
assert_eq!(event.data.location, 0);
|
assert_eq!(event.data.location, 0);
|
||||||
assert!(event.data.is_auto_repeating());
|
assert!(event.data.is_auto_repeating());
|
||||||
|
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input{
|
input {
|
||||||
id: "key_up_div",
|
id: "key_up_div",
|
||||||
onkeyup: move |event| {
|
onkeyup: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
@@ -319,11 +354,10 @@ fn app(cx: Scope) -> Element {
|
|||||||
assert_eq!(event.data.code().to_string(), "KeyA");
|
assert_eq!(event.data.code().to_string(), "KeyA");
|
||||||
assert_eq!(event.data.location, 0);
|
assert_eq!(event.data.location, 0);
|
||||||
assert!(!event.data.is_auto_repeating());
|
assert!(!event.data.is_auto_repeating());
|
||||||
|
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input{
|
input {
|
||||||
id: "key_press_div",
|
id: "key_press_div",
|
||||||
onkeypress: move |event| {
|
onkeypress: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
@@ -332,18 +366,17 @@ fn app(cx: Scope) -> Element {
|
|||||||
assert_eq!(event.data.code().to_string(), "KeyA");
|
assert_eq!(event.data.code().to_string(), "KeyA");
|
||||||
assert_eq!(event.data.location, 0);
|
assert_eq!(event.data.location, 0);
|
||||||
assert!(!event.data.is_auto_repeating());
|
assert!(!event.data.is_auto_repeating());
|
||||||
|
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input{
|
input {
|
||||||
id: "focus_in_div",
|
id: "focus_in_div",
|
||||||
onfocusin: move |event| {
|
onfocusin: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
recieved_events.modify(|x| *x + 1)
|
recieved_events.modify(|x| *x + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
input{
|
input {
|
||||||
id: "focus_out_div",
|
id: "focus_out_div",
|
||||||
onfocusout: move |event| {
|
onfocusout: move |event| {
|
||||||
println!("{:?}", event.data);
|
println!("{:?}", event.data);
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ use std::rc::Weak;
|
|||||||
|
|
||||||
use crate::create_new_window;
|
use crate::create_new_window;
|
||||||
use crate::events::IpcMessage;
|
use crate::events::IpcMessage;
|
||||||
|
use crate::protocol::AssetFuture;
|
||||||
|
use crate::protocol::AssetHandlerRegistry;
|
||||||
use crate::query::QueryEngine;
|
use crate::query::QueryEngine;
|
||||||
use crate::shortcut::ShortcutId;
|
use crate::shortcut::{HotKey, ShortcutId, ShortcutRegistry, ShortcutRegistryError};
|
||||||
use crate::shortcut::ShortcutRegistry;
|
use crate::AssetHandler;
|
||||||
use crate::shortcut::ShortcutRegistryError;
|
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
use crate::WebviewHandler;
|
use crate::WebviewHandler;
|
||||||
use dioxus_core::ScopeState;
|
use dioxus_core::ScopeState;
|
||||||
@@ -15,7 +16,6 @@ use dioxus_core::VirtualDom;
|
|||||||
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
#[cfg(all(feature = "hot-reload", debug_assertions))]
|
||||||
use dioxus_hot_reload::HotReloadMsg;
|
use dioxus_hot_reload::HotReloadMsg;
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use wry::application::accelerator::Accelerator;
|
|
||||||
use wry::application::event::Event;
|
use wry::application::event::Event;
|
||||||
use wry::application::event_loop::EventLoopProxy;
|
use wry::application::event_loop::EventLoopProxy;
|
||||||
use wry::application::event_loop::EventLoopWindowTarget;
|
use wry::application::event_loop::EventLoopWindowTarget;
|
||||||
@@ -67,6 +67,8 @@ pub struct DesktopService {
|
|||||||
|
|
||||||
pub(crate) shortcut_manager: ShortcutRegistry,
|
pub(crate) shortcut_manager: ShortcutRegistry,
|
||||||
|
|
||||||
|
pub(crate) asset_handlers: AssetHandlerRegistry,
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
|
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
|
||||||
}
|
}
|
||||||
@@ -91,6 +93,7 @@ impl DesktopService {
|
|||||||
webviews: WebviewQueue,
|
webviews: WebviewQueue,
|
||||||
event_handlers: WindowEventHandlers,
|
event_handlers: WindowEventHandlers,
|
||||||
shortcut_manager: ShortcutRegistry,
|
shortcut_manager: ShortcutRegistry,
|
||||||
|
asset_handlers: AssetHandlerRegistry,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
webview: Rc::new(webview),
|
webview: Rc::new(webview),
|
||||||
@@ -100,6 +103,7 @@ impl DesktopService {
|
|||||||
pending_windows: webviews,
|
pending_windows: webviews,
|
||||||
event_handlers,
|
event_handlers,
|
||||||
shortcut_manager,
|
shortcut_manager,
|
||||||
|
asset_handlers,
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
views: Default::default(),
|
views: Default::default(),
|
||||||
}
|
}
|
||||||
@@ -233,11 +237,11 @@ impl DesktopService {
|
|||||||
/// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.
|
/// Linux: Only works on x11. See [this issue](https://github.com/tauri-apps/tao/issues/331) for more information.
|
||||||
pub fn create_shortcut(
|
pub fn create_shortcut(
|
||||||
&self,
|
&self,
|
||||||
accelerator: Accelerator,
|
hotkey: HotKey,
|
||||||
callback: impl FnMut() + 'static,
|
callback: impl FnMut() + 'static,
|
||||||
) -> Result<ShortcutId, ShortcutRegistryError> {
|
) -> Result<ShortcutId, ShortcutRegistryError> {
|
||||||
self.shortcut_manager
|
self.shortcut_manager
|
||||||
.add_shortcut(accelerator, Box::new(callback))
|
.add_shortcut(hotkey, Box::new(callback))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a global shortcut
|
/// Remove a global shortcut
|
||||||
@@ -250,6 +254,20 @@ impl DesktopService {
|
|||||||
self.shortcut_manager.remove_all()
|
self.shortcut_manager.remove_all()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Provide a callback to handle asset loading yourself.
|
||||||
|
///
|
||||||
|
/// See [`use_asset_handle`](crate::use_asset_handle) for a convenient hook.
|
||||||
|
pub async fn register_asset_handler<F: AssetFuture>(&self, f: impl AssetHandler<F>) -> usize {
|
||||||
|
self.asset_handlers.register_handler(f).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes an asset handler by its identifier.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the handler did not exist.
|
||||||
|
pub async fn remove_asset_handler(&self, id: usize) -> Option<()> {
|
||||||
|
self.asset_handlers.remove_handler(id).await
|
||||||
|
}
|
||||||
|
|
||||||
/// Push an objc view to the window
|
/// Push an objc view to the window
|
||||||
#[cfg(target_os = "ios")]
|
#[cfg(target_os = "ios")]
|
||||||
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
|
pub fn push_view(&self, view: objc_id::ShareId<objc::runtime::Object>) {
|
||||||
@@ -369,17 +387,10 @@ impl WryWindowEventHandlerInner {
|
|||||||
target: &EventLoopWindowTarget<UserWindowEvent>,
|
target: &EventLoopWindowTarget<UserWindowEvent>,
|
||||||
) {
|
) {
|
||||||
// if this event does not apply to the window this listener cares about, return
|
// if this event does not apply to the window this listener cares about, return
|
||||||
match event {
|
if let Event::WindowEvent { window_id, .. } = event {
|
||||||
Event::WindowEvent { window_id, .. }
|
if *window_id != self.window_id {
|
||||||
| Event::MenuEvent {
|
return;
|
||||||
window_id: Some(window_id),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
if *window_id != self.window_id {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
(self.handler)(event, target)
|
(self.handler)(event, target)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,16 @@ mod escape;
|
|||||||
mod eval;
|
mod eval;
|
||||||
mod events;
|
mod events;
|
||||||
mod file_upload;
|
mod file_upload;
|
||||||
|
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||||
|
mod mobile_shortcut;
|
||||||
mod protocol;
|
mod protocol;
|
||||||
mod query;
|
mod query;
|
||||||
mod shortcut;
|
mod shortcut;
|
||||||
mod waker;
|
mod waker;
|
||||||
mod webview;
|
mod webview;
|
||||||
|
|
||||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
|
||||||
mod mobile_shortcut;
|
|
||||||
|
|
||||||
use crate::query::QueryResult;
|
use crate::query::QueryResult;
|
||||||
|
use crate::shortcut::GlobalHotKeyEvent;
|
||||||
pub use cfg::{Config, WindowCloseBehaviour};
|
pub use cfg::{Config, WindowCloseBehaviour};
|
||||||
pub use desktop_context::DesktopContext;
|
pub use desktop_context::DesktopContext;
|
||||||
pub use desktop_context::{
|
pub use desktop_context::{
|
||||||
@@ -32,6 +32,7 @@ use dioxus_html::{native_bind::NativeFileEngine, FormData, HtmlEvent};
|
|||||||
use element::DesktopElement;
|
use element::DesktopElement;
|
||||||
use eval::init_eval;
|
use eval::init_eval;
|
||||||
use futures_util::{pin_mut, FutureExt};
|
use futures_util::{pin_mut, FutureExt};
|
||||||
|
pub use protocol::{use_asset_handler, AssetFuture, AssetHandler, AssetRequest, AssetResponse};
|
||||||
use shortcut::ShortcutRegistry;
|
use shortcut::ShortcutRegistry;
|
||||||
pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
|
pub use shortcut::{use_global_shortcut, ShortcutHandle, ShortcutId, ShortcutRegistryError};
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
@@ -43,11 +44,12 @@ use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
|||||||
pub use tao::window::WindowBuilder;
|
pub use tao::window::WindowBuilder;
|
||||||
use tao::{
|
use tao::{
|
||||||
event::{Event, StartCause, WindowEvent},
|
event::{Event, StartCause, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::ControlFlow,
|
||||||
};
|
};
|
||||||
pub use webview::build_default_menu_bar;
|
pub use webview::build_default_menu_bar;
|
||||||
pub use wry;
|
pub use wry;
|
||||||
pub use wry::application as tao;
|
pub use wry::application as tao;
|
||||||
|
use wry::application::event_loop::EventLoopBuilder;
|
||||||
use wry::webview::WebView;
|
use wry::webview::WebView;
|
||||||
use wry::{application::window::WindowId, webview::WebContext};
|
use wry::{application::window::WindowId, webview::WebContext};
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config) {
|
pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config) {
|
||||||
let event_loop = EventLoop::<UserWindowEvent>::with_user_event();
|
let event_loop = EventLoopBuilder::<UserWindowEvent>::with_user_event().build();
|
||||||
|
|
||||||
let proxy = event_loop.create_proxy();
|
let proxy = event_loop.create_proxy();
|
||||||
|
|
||||||
@@ -158,7 +160,8 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
|||||||
|
|
||||||
let queue = WebviewQueue::default();
|
let queue = WebviewQueue::default();
|
||||||
|
|
||||||
let shortcut_manager = ShortcutRegistry::new(&event_loop);
|
let shortcut_manager = ShortcutRegistry::new();
|
||||||
|
let global_hotkey_channel = GlobalHotKeyEvent::receiver();
|
||||||
|
|
||||||
// move the props into a cell so we can pop it out later to create the first window
|
// move the props into a cell so we can pop it out later to create the first window
|
||||||
// iOS panics if we create a window before the event loop is started
|
// iOS panics if we create a window before the event loop is started
|
||||||
@@ -167,10 +170,14 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
|||||||
let mut is_visible_before_start = true;
|
let mut is_visible_before_start = true;
|
||||||
|
|
||||||
event_loop.run(move |window_event, event_loop, control_flow| {
|
event_loop.run(move |window_event, event_loop, control_flow| {
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Poll;
|
||||||
|
|
||||||
event_handlers.apply_event(&window_event, event_loop);
|
event_handlers.apply_event(&window_event, event_loop);
|
||||||
|
|
||||||
|
if let Ok(event) = global_hotkey_channel.try_recv() {
|
||||||
|
shortcut_manager.call_handlers(event);
|
||||||
|
}
|
||||||
|
|
||||||
match window_event {
|
match window_event {
|
||||||
Event::WindowEvent {
|
Event::WindowEvent {
|
||||||
event, window_id, ..
|
event, window_id, ..
|
||||||
@@ -376,7 +383,6 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config)
|
|||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
Event::GlobalShortcutEvent(id) => shortcut_manager.call_handlers(id),
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -391,7 +397,8 @@ fn create_new_window(
|
|||||||
event_handlers: &WindowEventHandlers,
|
event_handlers: &WindowEventHandlers,
|
||||||
shortcut_manager: ShortcutRegistry,
|
shortcut_manager: ShortcutRegistry,
|
||||||
) -> WebviewHandler {
|
) -> WebviewHandler {
|
||||||
let (webview, web_context) = webview::build(&mut cfg, event_loop, proxy.clone());
|
let (webview, web_context, asset_handlers) =
|
||||||
|
webview::build(&mut cfg, event_loop, proxy.clone());
|
||||||
let desktop_context = Rc::from(DesktopService::new(
|
let desktop_context = Rc::from(DesktopService::new(
|
||||||
webview,
|
webview,
|
||||||
proxy.clone(),
|
proxy.clone(),
|
||||||
@@ -399,6 +406,7 @@ fn create_new_window(
|
|||||||
queue.clone(),
|
queue.clone(),
|
||||||
event_handlers.clone(),
|
event_handlers.clone(),
|
||||||
shortcut_manager,
|
shortcut_manager,
|
||||||
|
asset_handlers,
|
||||||
));
|
));
|
||||||
|
|
||||||
let cx = dom.base_scope();
|
let cx = dom.base_scope();
|
||||||
|
|||||||
@@ -1,29 +1,51 @@
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use wry::application::accelerator::Accelerator;
|
use std::str::FromStr;
|
||||||
use wry::application::event_loop::EventLoopWindowTarget;
|
use wry::application::event_loop::EventLoopWindowTarget;
|
||||||
|
|
||||||
pub struct GlobalShortcut();
|
use dioxus_html::input_data::keyboard_types::Modifiers;
|
||||||
pub struct ShortcutManager();
|
|
||||||
|
|
||||||
impl ShortcutManager {
|
#[derive(Clone, Debug)]
|
||||||
pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
|
pub struct Accelerator;
|
||||||
Self()
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct HotKey;
|
||||||
|
|
||||||
|
impl HotKey {
|
||||||
|
pub fn new(mods: Option<Modifiers>, key: Code) -> Self {
|
||||||
|
Self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register(
|
pub fn id(&self) -> u32 {
|
||||||
&mut self,
|
0
|
||||||
accelerator: Accelerator,
|
}
|
||||||
) -> Result<GlobalShortcut, ShortcutManagerError> {
|
}
|
||||||
Ok(GlobalShortcut())
|
|
||||||
|
impl FromStr for HotKey {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(HotKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct GlobalHotKeyManager();
|
||||||
|
|
||||||
|
impl GlobalHotKeyManager {
|
||||||
|
pub fn new() -> Result<Self, HotkeyError> {
|
||||||
|
Ok(Self())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unregister(&mut self, id: ShortcutId) -> Result<(), ShortcutManagerError> {
|
pub fn register(&mut self, accelerator: HotKey) -> Result<HotKey, HotkeyError> {
|
||||||
|
Ok(HotKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unregister(&mut self, id: HotKey) -> Result<(), HotkeyError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unregister_all(&mut self) -> Result<(), ShortcutManagerError> {
|
pub fn unregister_all(&mut self, _: &[HotKey]) -> Result<(), HotkeyError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,23 +55,35 @@ use std::{error, fmt};
|
|||||||
/// An error whose cause the `ShortcutManager` to fail.
|
/// An error whose cause the `ShortcutManager` to fail.
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum ShortcutManagerError {
|
pub enum HotkeyError {
|
||||||
AcceleratorAlreadyRegistered(Accelerator),
|
AcceleratorAlreadyRegistered(Accelerator),
|
||||||
AcceleratorNotRegistered(Accelerator),
|
AcceleratorNotRegistered(Accelerator),
|
||||||
InvalidAccelerator(String),
|
HotKeyParseError(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for ShortcutManagerError {}
|
impl error::Error for HotkeyError {}
|
||||||
impl fmt::Display for ShortcutManagerError {
|
impl fmt::Display for HotkeyError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||||
match self {
|
match self {
|
||||||
ShortcutManagerError::AcceleratorAlreadyRegistered(e) => {
|
HotkeyError::AcceleratorAlreadyRegistered(e) => {
|
||||||
f.pad(&format!("hotkey already registered: {:?}", e))
|
f.pad(&format!("hotkey already registered: {:?}", e))
|
||||||
}
|
}
|
||||||
ShortcutManagerError::AcceleratorNotRegistered(e) => {
|
HotkeyError::AcceleratorNotRegistered(e) => {
|
||||||
f.pad(&format!("hotkey not registered: {:?}", e))
|
f.pad(&format!("hotkey not registered: {:?}", e))
|
||||||
}
|
}
|
||||||
ShortcutManagerError::InvalidAccelerator(e) => e.fmt(f),
|
HotkeyError::HotKeyParseError(e) => e.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct GlobalHotKeyEvent {
|
||||||
|
pub id: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GlobalHotKeyEvent {
|
||||||
|
pub fn receiver() -> crossbeam_channel::Receiver<GlobalHotKeyEvent> {
|
||||||
|
crossbeam_channel::unbounded().1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) type Code = dioxus_html::input_data::keyboard_types::Code;
|
||||||
@@ -1,13 +1,26 @@
|
|||||||
|
use dioxus_core::ScopeState;
|
||||||
use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS};
|
use dioxus_interpreter_js::{COMMON_JS, INTERPRETER_JS};
|
||||||
|
use slab::Slab;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
future::Future,
|
||||||
|
ops::Deref,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
pin::Pin,
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
use tokio::{
|
||||||
|
runtime::Handle,
|
||||||
|
sync::{OnceCell, RwLock},
|
||||||
};
|
};
|
||||||
use wry::{
|
use wry::{
|
||||||
http::{status::StatusCode, Request, Response},
|
http::{status::StatusCode, Request, Response},
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{use_window, DesktopContext};
|
||||||
|
|
||||||
fn module_loader(root_name: &str) -> String {
|
fn module_loader(root_name: &str) -> String {
|
||||||
let js = INTERPRETER_JS.replace(
|
let js = INTERPRETER_JS.replace(
|
||||||
"/*POST_HANDLE_EDITS*/",
|
"/*POST_HANDLE_EDITS*/",
|
||||||
@@ -51,12 +64,156 @@ fn module_loader(root_name: &str) -> String {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn desktop_handler(
|
/// An arbitrary asset is an HTTP response containing a binary body.
|
||||||
request: &Request<Vec<u8>>,
|
pub type AssetResponse = Response<Cow<'static, [u8]>>;
|
||||||
|
|
||||||
|
/// A future that returns an [`AssetResponse`]. This future may be spawned in a new thread,
|
||||||
|
/// so it must be [`Send`], [`Sync`], and `'static`.
|
||||||
|
pub trait AssetFuture: Future<Output = Option<AssetResponse>> + Send + Sync + 'static {}
|
||||||
|
impl<T: Future<Output = Option<AssetResponse>> + Send + Sync + 'static> AssetFuture for T {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
/// A request for an asset. This is a wrapper around [`Request<Vec<u8>>`] that provides methods specific to asset requests.
|
||||||
|
pub struct AssetRequest {
|
||||||
|
path: PathBuf,
|
||||||
|
request: Arc<Request<Vec<u8>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetRequest {
|
||||||
|
/// Get the path the asset request is for
|
||||||
|
pub fn path(&self) -> &Path {
|
||||||
|
&self.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Request<Vec<u8>>> for AssetRequest {
|
||||||
|
fn from(request: Request<Vec<u8>>) -> Self {
|
||||||
|
let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/'))
|
||||||
|
.expect("expected URL to be UTF-8 encoded");
|
||||||
|
let path = PathBuf::from(&*decoded);
|
||||||
|
Self {
|
||||||
|
request: Arc::new(request),
|
||||||
|
path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for AssetRequest {
|
||||||
|
type Target = Request<Vec<u8>>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.request
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handler that takes an [`AssetRequest`] and returns a future that either loads the asset, or returns `None`.
|
||||||
|
/// This handler is stashed indefinitely in a context object, so it must be `'static`.
|
||||||
|
pub trait AssetHandler<F: AssetFuture>: Send + Sync + 'static {
|
||||||
|
/// Handle an asset request, returning a future that either loads the asset, or returns `None`
|
||||||
|
fn handle_request(&self, request: &AssetRequest) -> F;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F: AssetFuture, T: Fn(&AssetRequest) -> F + Send + Sync + 'static> AssetHandler<F> for T {
|
||||||
|
fn handle_request(&self, request: &AssetRequest) -> F {
|
||||||
|
self(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssetHandlerRegistryInner =
|
||||||
|
Slab<Box<dyn Fn(&AssetRequest) -> Pin<Box<dyn AssetFuture>> + Send + Sync + 'static>>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AssetHandlerRegistry(Arc<RwLock<AssetHandlerRegistryInner>>);
|
||||||
|
|
||||||
|
impl AssetHandlerRegistry {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
AssetHandlerRegistry(Arc::new(RwLock::new(Slab::new())))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn register_handler<F: AssetFuture>(&self, f: impl AssetHandler<F>) -> usize {
|
||||||
|
let mut registry = self.0.write().await;
|
||||||
|
registry.insert(Box::new(move |req| Box::pin(f.handle_request(req))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn remove_handler(&self, id: usize) -> Option<()> {
|
||||||
|
let mut registry = self.0.write().await;
|
||||||
|
registry.try_remove(id).map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn try_handlers(&self, req: &AssetRequest) -> Option<AssetResponse> {
|
||||||
|
let registry = self.0.read().await;
|
||||||
|
for (_, handler) in registry.iter() {
|
||||||
|
if let Some(response) = handler(req).await {
|
||||||
|
return Some(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A handle to a registered asset handler.
|
||||||
|
pub struct AssetHandlerHandle {
|
||||||
|
desktop: DesktopContext,
|
||||||
|
handler_id: Rc<OnceCell<usize>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AssetHandlerHandle {
|
||||||
|
/// Returns the ID for this handle.
|
||||||
|
///
|
||||||
|
/// Because registering an ID is asynchronous, this may return `None` if the
|
||||||
|
/// registration has not completed yet.
|
||||||
|
pub fn handler_id(&self) -> Option<usize> {
|
||||||
|
self.handler_id.get().copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for AssetHandlerHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let cell = Rc::clone(&self.handler_id);
|
||||||
|
let desktop = Rc::clone(&self.desktop);
|
||||||
|
tokio::task::block_in_place(move || {
|
||||||
|
Handle::current().block_on(async move {
|
||||||
|
if let Some(id) = cell.get() {
|
||||||
|
desktop.asset_handlers.remove_handler(*id).await;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide a callback to handle asset loading yourself.
|
||||||
|
///
|
||||||
|
/// The callback takes a path as requested by the web view, and it should return `Some(response)`
|
||||||
|
/// if you want to load the asset, and `None` if you want to fallback on the default behavior.
|
||||||
|
pub fn use_asset_handler<F: AssetFuture>(
|
||||||
|
cx: &ScopeState,
|
||||||
|
handler: impl AssetHandler<F>,
|
||||||
|
) -> &AssetHandlerHandle {
|
||||||
|
let desktop = Rc::clone(use_window(cx));
|
||||||
|
cx.use_hook(|| {
|
||||||
|
let handler_id = Rc::new(OnceCell::new());
|
||||||
|
let handler_id_ref = Rc::clone(&handler_id);
|
||||||
|
let desktop_ref = Rc::clone(&desktop);
|
||||||
|
cx.push_future(async move {
|
||||||
|
let id = desktop.asset_handlers.register_handler(handler).await;
|
||||||
|
handler_id.set(id).unwrap();
|
||||||
|
});
|
||||||
|
AssetHandlerHandle {
|
||||||
|
desktop: desktop_ref,
|
||||||
|
handler_id: handler_id_ref,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn desktop_handler(
|
||||||
|
request: Request<Vec<u8>>,
|
||||||
custom_head: Option<String>,
|
custom_head: Option<String>,
|
||||||
custom_index: Option<String>,
|
custom_index: Option<String>,
|
||||||
root_name: &str,
|
root_name: &str,
|
||||||
) -> Result<Response<Cow<'static, [u8]>>> {
|
asset_handlers: &AssetHandlerRegistry,
|
||||||
|
) -> Result<AssetResponse> {
|
||||||
|
let request = AssetRequest::from(request);
|
||||||
|
|
||||||
// If the request is for the root, we'll serve the index.html file.
|
// If the request is for the root, we'll serve the index.html file.
|
||||||
if request.uri().path() == "/" {
|
if request.uri().path() == "/" {
|
||||||
// If a custom index is provided, just defer to that, expecting the user to know what they're doing.
|
// If a custom index is provided, just defer to that, expecting the user to know what they're doing.
|
||||||
@@ -91,18 +248,21 @@ pub(super) fn desktop_handler(
|
|||||||
.map_err(From::from);
|
.map_err(From::from);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the user provided a custom asset handler, then call it and return the response
|
||||||
|
// if the request was handled.
|
||||||
|
if let Some(response) = asset_handlers.try_handlers(&request).await {
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
// Else, try to serve a file from the filesystem.
|
// Else, try to serve a file from the filesystem.
|
||||||
let decoded = urlencoding::decode(request.uri().path().trim_start_matches('/'))
|
|
||||||
.expect("expected URL to be UTF-8 encoded");
|
|
||||||
let path = PathBuf::from(&*decoded);
|
|
||||||
|
|
||||||
// If the path is relative, we'll try to serve it from the assets directory.
|
// If the path is relative, we'll try to serve it from the assets directory.
|
||||||
let mut asset = get_asset_root()
|
let mut asset = get_asset_root()
|
||||||
.unwrap_or_else(|| Path::new(".").to_path_buf())
|
.unwrap_or_else(|| Path::new(".").to_path_buf())
|
||||||
.join(&path);
|
.join(&request.path);
|
||||||
|
|
||||||
if !asset.exists() {
|
if !asset.exists() {
|
||||||
asset = PathBuf::from("/").join(path);
|
asset = PathBuf::from("/").join(request.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
if asset.exists() {
|
if asset.exists() {
|
||||||
|
|||||||
+149
-158
@@ -3,11 +3,7 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc, str::FromStr};
|
|||||||
use dioxus_core::ScopeState;
|
use dioxus_core::ScopeState;
|
||||||
use dioxus_html::input_data::keyboard_types::Modifiers;
|
use dioxus_html::input_data::keyboard_types::Modifiers;
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use wry::application::{
|
use wry::application::keyboard::ModifiersState;
|
||||||
accelerator::{Accelerator, AcceleratorId},
|
|
||||||
event_loop::EventLoopWindowTarget,
|
|
||||||
keyboard::{KeyCode, ModifiersState},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::{desktop_context::DesktopContext, use_window};
|
use crate::{desktop_context::DesktopContext, use_window};
|
||||||
|
|
||||||
@@ -20,22 +16,25 @@ use crate::{desktop_context::DesktopContext, use_window};
|
|||||||
target_os = "netbsd",
|
target_os = "netbsd",
|
||||||
target_os = "openbsd"
|
target_os = "openbsd"
|
||||||
))]
|
))]
|
||||||
use wry::application::global_shortcut::{GlobalShortcut, ShortcutManager, ShortcutManagerError};
|
pub use global_hotkey::{
|
||||||
|
hotkey::{Code, HotKey},
|
||||||
|
Error as HotkeyError, GlobalHotKeyEvent, GlobalHotKeyManager,
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(any(target_os = "ios", target_os = "android"))]
|
#[cfg(any(target_os = "ios", target_os = "android"))]
|
||||||
pub use crate::mobile_shortcut::*;
|
pub use crate::mobile_shortcut::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub(crate) struct ShortcutRegistry {
|
pub(crate) struct ShortcutRegistry {
|
||||||
manager: Rc<RefCell<ShortcutManager>>,
|
manager: Rc<RefCell<GlobalHotKeyManager>>,
|
||||||
shortcuts: ShortcutMap,
|
shortcuts: ShortcutMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ShortcutMap = Rc<RefCell<HashMap<AcceleratorId, Shortcut>>>;
|
type ShortcutMap = Rc<RefCell<HashMap<u32, Shortcut>>>;
|
||||||
|
|
||||||
struct Shortcut {
|
struct Shortcut {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
shortcut: GlobalShortcut,
|
shortcut: HotKey,
|
||||||
callbacks: Slab<Box<dyn FnMut()>>,
|
callbacks: Slab<Box<dyn FnMut()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,15 +53,15 @@ impl Shortcut {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ShortcutRegistry {
|
impl ShortcutRegistry {
|
||||||
pub fn new<T>(target: &EventLoopWindowTarget<T>) -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
manager: Rc::new(RefCell::new(ShortcutManager::new(target))),
|
manager: Rc::new(RefCell::new(GlobalHotKeyManager::new().unwrap())),
|
||||||
shortcuts: Rc::new(RefCell::new(HashMap::new())),
|
shortcuts: Rc::new(RefCell::new(HashMap::new())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn call_handlers(&self, id: AcceleratorId) {
|
pub(crate) fn call_handlers(&self, id: GlobalHotKeyEvent) {
|
||||||
if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id) {
|
if let Some(Shortcut { callbacks, .. }) = self.shortcuts.borrow_mut().get_mut(&id.id) {
|
||||||
for (_, callback) in callbacks.iter_mut() {
|
for (_, callback) in callbacks.iter_mut() {
|
||||||
(callback)();
|
(callback)();
|
||||||
}
|
}
|
||||||
@@ -71,10 +70,10 @@ impl ShortcutRegistry {
|
|||||||
|
|
||||||
pub(crate) fn add_shortcut(
|
pub(crate) fn add_shortcut(
|
||||||
&self,
|
&self,
|
||||||
accelerator: Accelerator,
|
hotkey: HotKey,
|
||||||
callback: Box<dyn FnMut()>,
|
callback: Box<dyn FnMut()>,
|
||||||
) -> Result<ShortcutId, ShortcutRegistryError> {
|
) -> Result<ShortcutId, ShortcutRegistryError> {
|
||||||
let accelerator_id = accelerator.clone().id();
|
let accelerator_id = hotkey.clone().id();
|
||||||
let mut shortcuts = self.shortcuts.borrow_mut();
|
let mut shortcuts = self.shortcuts.borrow_mut();
|
||||||
Ok(
|
Ok(
|
||||||
if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) {
|
if let Some(callbacks) = shortcuts.get_mut(&accelerator_id) {
|
||||||
@@ -84,12 +83,12 @@ impl ShortcutRegistry {
|
|||||||
number: id,
|
number: id,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match self.manager.borrow_mut().register(accelerator) {
|
match self.manager.borrow_mut().register(hotkey) {
|
||||||
Ok(global_shortcut) => {
|
Ok(_) => {
|
||||||
let mut slab = Slab::new();
|
let mut slab = Slab::new();
|
||||||
let id = slab.insert(callback);
|
let id = slab.insert(callback);
|
||||||
let shortcut = Shortcut {
|
let shortcut = Shortcut {
|
||||||
shortcut: global_shortcut,
|
shortcut: hotkey,
|
||||||
callbacks: slab,
|
callbacks: slab,
|
||||||
};
|
};
|
||||||
shortcuts.insert(accelerator_id, shortcut);
|
shortcuts.insert(accelerator_id, shortcut);
|
||||||
@@ -98,7 +97,7 @@ impl ShortcutRegistry {
|
|||||||
number: id,
|
number: id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ShortcutManagerError::InvalidAccelerator(shortcut)) => {
|
Err(HotkeyError::HotKeyParseError(shortcut)) => {
|
||||||
return Err(ShortcutRegistryError::InvalidShortcut(shortcut))
|
return Err(ShortcutRegistryError::InvalidShortcut(shortcut))
|
||||||
}
|
}
|
||||||
Err(err) => return Err(ShortcutRegistryError::Other(Box::new(err))),
|
Err(err) => return Err(ShortcutRegistryError::Other(Box::new(err))),
|
||||||
@@ -113,15 +112,6 @@ impl ShortcutRegistry {
|
|||||||
callbacks.remove(id.number);
|
callbacks.remove(id.number);
|
||||||
if callbacks.is_empty() {
|
if callbacks.is_empty() {
|
||||||
if let Some(_shortcut) = shortcuts.remove(&id.id) {
|
if let Some(_shortcut) = shortcuts.remove(&id.id) {
|
||||||
#[cfg(any(
|
|
||||||
target_os = "windows",
|
|
||||||
target_os = "macos",
|
|
||||||
target_os = "linux",
|
|
||||||
target_os = "dragonfly",
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "openbsd"
|
|
||||||
))]
|
|
||||||
let _ = self.manager.borrow_mut().unregister(_shortcut.shortcut);
|
let _ = self.manager.borrow_mut().unregister(_shortcut.shortcut);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,8 +120,8 @@ impl ShortcutRegistry {
|
|||||||
|
|
||||||
pub(crate) fn remove_all(&self) {
|
pub(crate) fn remove_all(&self) {
|
||||||
let mut shortcuts = self.shortcuts.borrow_mut();
|
let mut shortcuts = self.shortcuts.borrow_mut();
|
||||||
shortcuts.clear();
|
let hotkeys: Vec<_> = shortcuts.drain().map(|(_, v)| v.shortcut).collect();
|
||||||
let _ = self.manager.borrow_mut().unregister_all();
|
let _ = self.manager.borrow_mut().unregister_all(&hotkeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,7 +138,7 @@ pub enum ShortcutRegistryError {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
/// An global id for a shortcut.
|
/// An global id for a shortcut.
|
||||||
pub struct ShortcutId {
|
pub struct ShortcutId {
|
||||||
id: AcceleratorId,
|
id: u32,
|
||||||
number: usize,
|
number: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,30 +150,30 @@ pub struct ShortcutHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoAccelerator {
|
pub trait IntoAccelerator {
|
||||||
fn accelerator(&self) -> Accelerator;
|
fn accelerator(&self) -> HotKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoAccelerator for (dioxus_html::KeyCode, ModifiersState) {
|
impl IntoAccelerator for (dioxus_html::KeyCode, ModifiersState) {
|
||||||
fn accelerator(&self) -> Accelerator {
|
fn accelerator(&self) -> HotKey {
|
||||||
Accelerator::new(Some(self.1), self.0.into_key_code())
|
HotKey::new(Some(self.1.into_modifiers_state()), self.0.into_key_code())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoAccelerator for (ModifiersState, dioxus_html::KeyCode) {
|
impl IntoAccelerator for (ModifiersState, dioxus_html::KeyCode) {
|
||||||
fn accelerator(&self) -> Accelerator {
|
fn accelerator(&self) -> HotKey {
|
||||||
Accelerator::new(Some(self.0), self.1.into_key_code())
|
HotKey::new(Some(self.0.into_modifiers_state()), self.1.into_key_code())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoAccelerator for dioxus_html::KeyCode {
|
impl IntoAccelerator for dioxus_html::KeyCode {
|
||||||
fn accelerator(&self) -> Accelerator {
|
fn accelerator(&self) -> HotKey {
|
||||||
Accelerator::new(None, self.into_key_code())
|
HotKey::new(None, self.into_key_code())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoAccelerator for &str {
|
impl IntoAccelerator for &str {
|
||||||
fn accelerator(&self) -> Accelerator {
|
fn accelerator(&self) -> HotKey {
|
||||||
Accelerator::from_str(self).unwrap()
|
HotKey::from_str(self).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,143 +210,144 @@ impl Drop for ShortcutHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoModifersState {
|
pub trait IntoModifersState {
|
||||||
fn into_modifiers_state(self) -> ModifiersState;
|
fn into_modifiers_state(self) -> Modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoModifersState for ModifiersState {
|
impl IntoModifersState for ModifiersState {
|
||||||
fn into_modifiers_state(self) -> ModifiersState {
|
fn into_modifiers_state(self) -> Modifiers {
|
||||||
self
|
let mut modifiers = Modifiers::default();
|
||||||
|
if self.shift_key() {
|
||||||
|
modifiers |= Modifiers::SHIFT;
|
||||||
|
}
|
||||||
|
if self.control_key() {
|
||||||
|
modifiers |= Modifiers::CONTROL;
|
||||||
|
}
|
||||||
|
if self.alt_key() {
|
||||||
|
modifiers |= Modifiers::ALT;
|
||||||
|
}
|
||||||
|
if self.super_key() {
|
||||||
|
modifiers |= Modifiers::META;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifiers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoModifersState for Modifiers {
|
impl IntoModifersState for Modifiers {
|
||||||
fn into_modifiers_state(self) -> ModifiersState {
|
fn into_modifiers_state(self) -> Modifiers {
|
||||||
let mut state = ModifiersState::empty();
|
self
|
||||||
if self.contains(Modifiers::SHIFT) {
|
|
||||||
state |= ModifiersState::SHIFT
|
|
||||||
}
|
|
||||||
if self.contains(Modifiers::CONTROL) {
|
|
||||||
state |= ModifiersState::CONTROL
|
|
||||||
}
|
|
||||||
if self.contains(Modifiers::ALT) {
|
|
||||||
state |= ModifiersState::ALT
|
|
||||||
}
|
|
||||||
if self.contains(Modifiers::META) || self.contains(Modifiers::SUPER) {
|
|
||||||
state |= ModifiersState::SUPER
|
|
||||||
}
|
|
||||||
state
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoKeyCode {
|
pub trait IntoKeyCode {
|
||||||
fn into_key_code(self) -> KeyCode;
|
fn into_key_code(self) -> Code;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoKeyCode for KeyCode {
|
impl IntoKeyCode for Code {
|
||||||
fn into_key_code(self) -> KeyCode {
|
fn into_key_code(self) -> Code {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoKeyCode for dioxus_html::KeyCode {
|
impl IntoKeyCode for dioxus_html::KeyCode {
|
||||||
fn into_key_code(self) -> KeyCode {
|
fn into_key_code(self) -> Code {
|
||||||
match self {
|
match self {
|
||||||
dioxus_html::KeyCode::Backspace => KeyCode::Backspace,
|
dioxus_html::KeyCode::Backspace => Code::Backspace,
|
||||||
dioxus_html::KeyCode::Tab => KeyCode::Tab,
|
dioxus_html::KeyCode::Tab => Code::Tab,
|
||||||
dioxus_html::KeyCode::Clear => KeyCode::NumpadClear,
|
dioxus_html::KeyCode::Clear => Code::NumpadClear,
|
||||||
dioxus_html::KeyCode::Enter => KeyCode::Enter,
|
dioxus_html::KeyCode::Enter => Code::Enter,
|
||||||
dioxus_html::KeyCode::Shift => KeyCode::ShiftLeft,
|
dioxus_html::KeyCode::Shift => Code::ShiftLeft,
|
||||||
dioxus_html::KeyCode::Ctrl => KeyCode::ControlLeft,
|
dioxus_html::KeyCode::Ctrl => Code::ControlLeft,
|
||||||
dioxus_html::KeyCode::Alt => KeyCode::AltLeft,
|
dioxus_html::KeyCode::Alt => Code::AltLeft,
|
||||||
dioxus_html::KeyCode::Pause => KeyCode::Pause,
|
dioxus_html::KeyCode::Pause => Code::Pause,
|
||||||
dioxus_html::KeyCode::CapsLock => KeyCode::CapsLock,
|
dioxus_html::KeyCode::CapsLock => Code::CapsLock,
|
||||||
dioxus_html::KeyCode::Escape => KeyCode::Escape,
|
dioxus_html::KeyCode::Escape => Code::Escape,
|
||||||
dioxus_html::KeyCode::Space => KeyCode::Space,
|
dioxus_html::KeyCode::Space => Code::Space,
|
||||||
dioxus_html::KeyCode::PageUp => KeyCode::PageUp,
|
dioxus_html::KeyCode::PageUp => Code::PageUp,
|
||||||
dioxus_html::KeyCode::PageDown => KeyCode::PageDown,
|
dioxus_html::KeyCode::PageDown => Code::PageDown,
|
||||||
dioxus_html::KeyCode::End => KeyCode::End,
|
dioxus_html::KeyCode::End => Code::End,
|
||||||
dioxus_html::KeyCode::Home => KeyCode::Home,
|
dioxus_html::KeyCode::Home => Code::Home,
|
||||||
dioxus_html::KeyCode::LeftArrow => KeyCode::ArrowLeft,
|
dioxus_html::KeyCode::LeftArrow => Code::ArrowLeft,
|
||||||
dioxus_html::KeyCode::UpArrow => KeyCode::ArrowUp,
|
dioxus_html::KeyCode::UpArrow => Code::ArrowUp,
|
||||||
dioxus_html::KeyCode::RightArrow => KeyCode::ArrowRight,
|
dioxus_html::KeyCode::RightArrow => Code::ArrowRight,
|
||||||
dioxus_html::KeyCode::DownArrow => KeyCode::ArrowDown,
|
dioxus_html::KeyCode::DownArrow => Code::ArrowDown,
|
||||||
dioxus_html::KeyCode::Insert => KeyCode::Insert,
|
dioxus_html::KeyCode::Insert => Code::Insert,
|
||||||
dioxus_html::KeyCode::Delete => KeyCode::Delete,
|
dioxus_html::KeyCode::Delete => Code::Delete,
|
||||||
dioxus_html::KeyCode::Num0 => KeyCode::Numpad0,
|
dioxus_html::KeyCode::Num0 => Code::Numpad0,
|
||||||
dioxus_html::KeyCode::Num1 => KeyCode::Numpad1,
|
dioxus_html::KeyCode::Num1 => Code::Numpad1,
|
||||||
dioxus_html::KeyCode::Num2 => KeyCode::Numpad2,
|
dioxus_html::KeyCode::Num2 => Code::Numpad2,
|
||||||
dioxus_html::KeyCode::Num3 => KeyCode::Numpad3,
|
dioxus_html::KeyCode::Num3 => Code::Numpad3,
|
||||||
dioxus_html::KeyCode::Num4 => KeyCode::Numpad4,
|
dioxus_html::KeyCode::Num4 => Code::Numpad4,
|
||||||
dioxus_html::KeyCode::Num5 => KeyCode::Numpad5,
|
dioxus_html::KeyCode::Num5 => Code::Numpad5,
|
||||||
dioxus_html::KeyCode::Num6 => KeyCode::Numpad6,
|
dioxus_html::KeyCode::Num6 => Code::Numpad6,
|
||||||
dioxus_html::KeyCode::Num7 => KeyCode::Numpad7,
|
dioxus_html::KeyCode::Num7 => Code::Numpad7,
|
||||||
dioxus_html::KeyCode::Num8 => KeyCode::Numpad8,
|
dioxus_html::KeyCode::Num8 => Code::Numpad8,
|
||||||
dioxus_html::KeyCode::Num9 => KeyCode::Numpad9,
|
dioxus_html::KeyCode::Num9 => Code::Numpad9,
|
||||||
dioxus_html::KeyCode::A => KeyCode::KeyA,
|
dioxus_html::KeyCode::A => Code::KeyA,
|
||||||
dioxus_html::KeyCode::B => KeyCode::KeyB,
|
dioxus_html::KeyCode::B => Code::KeyB,
|
||||||
dioxus_html::KeyCode::C => KeyCode::KeyC,
|
dioxus_html::KeyCode::C => Code::KeyC,
|
||||||
dioxus_html::KeyCode::D => KeyCode::KeyD,
|
dioxus_html::KeyCode::D => Code::KeyD,
|
||||||
dioxus_html::KeyCode::E => KeyCode::KeyE,
|
dioxus_html::KeyCode::E => Code::KeyE,
|
||||||
dioxus_html::KeyCode::F => KeyCode::KeyF,
|
dioxus_html::KeyCode::F => Code::KeyF,
|
||||||
dioxus_html::KeyCode::G => KeyCode::KeyG,
|
dioxus_html::KeyCode::G => Code::KeyG,
|
||||||
dioxus_html::KeyCode::H => KeyCode::KeyH,
|
dioxus_html::KeyCode::H => Code::KeyH,
|
||||||
dioxus_html::KeyCode::I => KeyCode::KeyI,
|
dioxus_html::KeyCode::I => Code::KeyI,
|
||||||
dioxus_html::KeyCode::J => KeyCode::KeyJ,
|
dioxus_html::KeyCode::J => Code::KeyJ,
|
||||||
dioxus_html::KeyCode::K => KeyCode::KeyK,
|
dioxus_html::KeyCode::K => Code::KeyK,
|
||||||
dioxus_html::KeyCode::L => KeyCode::KeyL,
|
dioxus_html::KeyCode::L => Code::KeyL,
|
||||||
dioxus_html::KeyCode::M => KeyCode::KeyM,
|
dioxus_html::KeyCode::M => Code::KeyM,
|
||||||
dioxus_html::KeyCode::N => KeyCode::KeyN,
|
dioxus_html::KeyCode::N => Code::KeyN,
|
||||||
dioxus_html::KeyCode::O => KeyCode::KeyO,
|
dioxus_html::KeyCode::O => Code::KeyO,
|
||||||
dioxus_html::KeyCode::P => KeyCode::KeyP,
|
dioxus_html::KeyCode::P => Code::KeyP,
|
||||||
dioxus_html::KeyCode::Q => KeyCode::KeyQ,
|
dioxus_html::KeyCode::Q => Code::KeyQ,
|
||||||
dioxus_html::KeyCode::R => KeyCode::KeyR,
|
dioxus_html::KeyCode::R => Code::KeyR,
|
||||||
dioxus_html::KeyCode::S => KeyCode::KeyS,
|
dioxus_html::KeyCode::S => Code::KeyS,
|
||||||
dioxus_html::KeyCode::T => KeyCode::KeyT,
|
dioxus_html::KeyCode::T => Code::KeyT,
|
||||||
dioxus_html::KeyCode::U => KeyCode::KeyU,
|
dioxus_html::KeyCode::U => Code::KeyU,
|
||||||
dioxus_html::KeyCode::V => KeyCode::KeyV,
|
dioxus_html::KeyCode::V => Code::KeyV,
|
||||||
dioxus_html::KeyCode::W => KeyCode::KeyW,
|
dioxus_html::KeyCode::W => Code::KeyW,
|
||||||
dioxus_html::KeyCode::X => KeyCode::KeyX,
|
dioxus_html::KeyCode::X => Code::KeyX,
|
||||||
dioxus_html::KeyCode::Y => KeyCode::KeyY,
|
dioxus_html::KeyCode::Y => Code::KeyY,
|
||||||
dioxus_html::KeyCode::Z => KeyCode::KeyZ,
|
dioxus_html::KeyCode::Z => Code::KeyZ,
|
||||||
dioxus_html::KeyCode::Numpad0 => KeyCode::Numpad0,
|
dioxus_html::KeyCode::Numpad0 => Code::Numpad0,
|
||||||
dioxus_html::KeyCode::Numpad1 => KeyCode::Numpad1,
|
dioxus_html::KeyCode::Numpad1 => Code::Numpad1,
|
||||||
dioxus_html::KeyCode::Numpad2 => KeyCode::Numpad2,
|
dioxus_html::KeyCode::Numpad2 => Code::Numpad2,
|
||||||
dioxus_html::KeyCode::Numpad3 => KeyCode::Numpad3,
|
dioxus_html::KeyCode::Numpad3 => Code::Numpad3,
|
||||||
dioxus_html::KeyCode::Numpad4 => KeyCode::Numpad4,
|
dioxus_html::KeyCode::Numpad4 => Code::Numpad4,
|
||||||
dioxus_html::KeyCode::Numpad5 => KeyCode::Numpad5,
|
dioxus_html::KeyCode::Numpad5 => Code::Numpad5,
|
||||||
dioxus_html::KeyCode::Numpad6 => KeyCode::Numpad6,
|
dioxus_html::KeyCode::Numpad6 => Code::Numpad6,
|
||||||
dioxus_html::KeyCode::Numpad7 => KeyCode::Numpad7,
|
dioxus_html::KeyCode::Numpad7 => Code::Numpad7,
|
||||||
dioxus_html::KeyCode::Numpad8 => KeyCode::Numpad8,
|
dioxus_html::KeyCode::Numpad8 => Code::Numpad8,
|
||||||
dioxus_html::KeyCode::Numpad9 => KeyCode::Numpad9,
|
dioxus_html::KeyCode::Numpad9 => Code::Numpad9,
|
||||||
dioxus_html::KeyCode::Multiply => KeyCode::NumpadMultiply,
|
dioxus_html::KeyCode::Multiply => Code::NumpadMultiply,
|
||||||
dioxus_html::KeyCode::Add => KeyCode::NumpadAdd,
|
dioxus_html::KeyCode::Add => Code::NumpadAdd,
|
||||||
dioxus_html::KeyCode::Subtract => KeyCode::NumpadSubtract,
|
dioxus_html::KeyCode::Subtract => Code::NumpadSubtract,
|
||||||
dioxus_html::KeyCode::DecimalPoint => KeyCode::NumpadDecimal,
|
dioxus_html::KeyCode::DecimalPoint => Code::NumpadDecimal,
|
||||||
dioxus_html::KeyCode::Divide => KeyCode::NumpadDivide,
|
dioxus_html::KeyCode::Divide => Code::NumpadDivide,
|
||||||
dioxus_html::KeyCode::F1 => KeyCode::F1,
|
dioxus_html::KeyCode::F1 => Code::F1,
|
||||||
dioxus_html::KeyCode::F2 => KeyCode::F2,
|
dioxus_html::KeyCode::F2 => Code::F2,
|
||||||
dioxus_html::KeyCode::F3 => KeyCode::F3,
|
dioxus_html::KeyCode::F3 => Code::F3,
|
||||||
dioxus_html::KeyCode::F4 => KeyCode::F4,
|
dioxus_html::KeyCode::F4 => Code::F4,
|
||||||
dioxus_html::KeyCode::F5 => KeyCode::F5,
|
dioxus_html::KeyCode::F5 => Code::F5,
|
||||||
dioxus_html::KeyCode::F6 => KeyCode::F6,
|
dioxus_html::KeyCode::F6 => Code::F6,
|
||||||
dioxus_html::KeyCode::F7 => KeyCode::F7,
|
dioxus_html::KeyCode::F7 => Code::F7,
|
||||||
dioxus_html::KeyCode::F8 => KeyCode::F8,
|
dioxus_html::KeyCode::F8 => Code::F8,
|
||||||
dioxus_html::KeyCode::F9 => KeyCode::F9,
|
dioxus_html::KeyCode::F9 => Code::F9,
|
||||||
dioxus_html::KeyCode::F10 => KeyCode::F10,
|
dioxus_html::KeyCode::F10 => Code::F10,
|
||||||
dioxus_html::KeyCode::F11 => KeyCode::F11,
|
dioxus_html::KeyCode::F11 => Code::F11,
|
||||||
dioxus_html::KeyCode::F12 => KeyCode::F12,
|
dioxus_html::KeyCode::F12 => Code::F12,
|
||||||
dioxus_html::KeyCode::NumLock => KeyCode::NumLock,
|
dioxus_html::KeyCode::NumLock => Code::NumLock,
|
||||||
dioxus_html::KeyCode::ScrollLock => KeyCode::ScrollLock,
|
dioxus_html::KeyCode::ScrollLock => Code::ScrollLock,
|
||||||
dioxus_html::KeyCode::Semicolon => KeyCode::Semicolon,
|
dioxus_html::KeyCode::Semicolon => Code::Semicolon,
|
||||||
dioxus_html::KeyCode::EqualSign => KeyCode::Equal,
|
dioxus_html::KeyCode::EqualSign => Code::Equal,
|
||||||
dioxus_html::KeyCode::Comma => KeyCode::Comma,
|
dioxus_html::KeyCode::Comma => Code::Comma,
|
||||||
dioxus_html::KeyCode::Period => KeyCode::Period,
|
dioxus_html::KeyCode::Period => Code::Period,
|
||||||
dioxus_html::KeyCode::ForwardSlash => KeyCode::Slash,
|
dioxus_html::KeyCode::ForwardSlash => Code::Slash,
|
||||||
dioxus_html::KeyCode::GraveAccent => KeyCode::Backquote,
|
dioxus_html::KeyCode::GraveAccent => Code::Backquote,
|
||||||
dioxus_html::KeyCode::OpenBracket => KeyCode::BracketLeft,
|
dioxus_html::KeyCode::OpenBracket => Code::BracketLeft,
|
||||||
dioxus_html::KeyCode::BackSlash => KeyCode::Backslash,
|
dioxus_html::KeyCode::BackSlash => Code::Backslash,
|
||||||
dioxus_html::KeyCode::CloseBraket => KeyCode::BracketRight,
|
dioxus_html::KeyCode::CloseBraket => Code::BracketRight,
|
||||||
dioxus_html::KeyCode::SingleQuote => KeyCode::Quote,
|
dioxus_html::KeyCode::SingleQuote => Code::Quote,
|
||||||
key => panic!("Failed to convert {:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly", key),
|
key => panic!("Failed to convert {:?} to tao::keyboard::KeyCode, try using tao::keyboard::KeyCode directly", key),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
use crate::desktop_context::EventData;
|
use crate::desktop_context::EventData;
|
||||||
use crate::protocol;
|
use crate::protocol::{self, AssetHandlerRegistry};
|
||||||
use crate::{desktop_context::UserWindowEvent, Config};
|
use crate::{desktop_context::UserWindowEvent, Config};
|
||||||
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
||||||
pub use wry;
|
pub use wry;
|
||||||
pub use wry::application as tao;
|
pub use wry::application as tao;
|
||||||
use wry::application::menu::{MenuBar, MenuItem};
|
use wry::application::menu::{MenuBar, MenuItem};
|
||||||
use wry::application::window::Window;
|
use wry::application::window::Window;
|
||||||
|
use wry::http::Response;
|
||||||
use wry::webview::{WebContext, WebView, WebViewBuilder};
|
use wry::webview::{WebContext, WebView, WebViewBuilder};
|
||||||
|
|
||||||
pub fn build(
|
pub fn build(
|
||||||
cfg: &mut Config,
|
cfg: &mut Config,
|
||||||
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
|
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
|
||||||
proxy: EventLoopProxy<UserWindowEvent>,
|
proxy: EventLoopProxy<UserWindowEvent>,
|
||||||
) -> (WebView, WebContext) {
|
) -> (WebView, WebContext, AssetHandlerRegistry) {
|
||||||
let mut builder = cfg.window.clone();
|
let window = builder.with_visible(false).build(event_loop).unwrap();
|
||||||
let file_handler = cfg.file_drop_handler.take();
|
let file_handler = cfg.file_drop_handler.take();
|
||||||
let custom_head = cfg.custom_head.clone();
|
let custom_head = cfg.custom_head.clone();
|
||||||
let index_file = cfg.custom_index.clone();
|
let index_file = cfg.custom_index.clone();
|
||||||
@@ -38,6 +39,8 @@ pub fn build(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
||||||
|
let asset_handlers = AssetHandlerRegistry::new();
|
||||||
|
let asset_handlers_ref = asset_handlers.clone();
|
||||||
|
|
||||||
let mut webview = WebViewBuilder::new(window)
|
let mut webview = WebViewBuilder::new(window)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
@@ -50,8 +53,29 @@ pub fn build(
|
|||||||
_ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id()));
|
_ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id()));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.with_custom_protocol(String::from("dioxus"), move |r| {
|
.with_asynchronous_custom_protocol(String::from("dioxus"), move |request, responder| {
|
||||||
protocol::desktop_handler(r, custom_head.clone(), index_file.clone(), &root_name)
|
let custom_head = custom_head.clone();
|
||||||
|
let index_file = index_file.clone();
|
||||||
|
let root_name = root_name.clone();
|
||||||
|
let asset_handlers_ref = asset_handlers_ref.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
let response_res = protocol::desktop_handler(
|
||||||
|
request,
|
||||||
|
custom_head.clone(),
|
||||||
|
index_file.clone(),
|
||||||
|
&root_name,
|
||||||
|
&asset_handlers_ref,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
let response = response_res.unwrap_or_else(|err| {
|
||||||
|
tracing::error!("Error: {}", err);
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.body(err.to_string().into_bytes().into())
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
responder.respond(response);
|
||||||
|
});
|
||||||
})
|
})
|
||||||
.with_file_drop_handler(move |window, evet| {
|
.with_file_drop_handler(move |window, evet| {
|
||||||
file_handler
|
file_handler
|
||||||
@@ -77,7 +101,16 @@ pub fn build(
|
|||||||
// .with_web_context(&mut web_context);
|
// .with_web_context(&mut web_context);
|
||||||
|
|
||||||
for (name, handler) in cfg.protocols.drain(..) {
|
for (name, handler) in cfg.protocols.drain(..) {
|
||||||
webview = webview.with_custom_protocol(name, handler)
|
webview = webview.with_custom_protocol(name, move |r| match handler(&r) {
|
||||||
|
Ok(response) => response,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::error!("Error: {}", err);
|
||||||
|
Response::builder()
|
||||||
|
.status(500)
|
||||||
|
.body(err.to_string().into_bytes().into())
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.disable_context_menu {
|
if cfg.disable_context_menu {
|
||||||
@@ -100,7 +133,7 @@ pub fn build(
|
|||||||
webview = webview.with_devtools(true);
|
webview = webview.with_devtools(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
(webview.build().unwrap(), web_context)
|
(webview.build().unwrap(), web_context, asset_handlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builds a standard menu bar depending on the users platform. It may be used as a starting point
|
/// Builds a standard menu bar depending on the users platform. It may be used as a starting point
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
// todo: remove
|
// todo: remove
|
||||||
let mut trimmed = format!("{event:?}");
|
let mut trimmed = format!("{event:?}");
|
||||||
trimmed.truncate(200);
|
trimmed.truncate(200);
|
||||||
rsx!(p { "{trimmed}" })
|
rsx!( p { "{trimmed}" } )
|
||||||
});
|
});
|
||||||
|
|
||||||
let log_event = move |event: Event| {
|
let log_event = move |event: Event| {
|
||||||
@@ -45,10 +45,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
};
|
};
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div { width: "100%", height: "100%", flex_direction: "column",
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
flex_direction: "column",
|
|
||||||
div {
|
div {
|
||||||
width: "80%",
|
width: "80%",
|
||||||
height: "50%",
|
height: "50%",
|
||||||
@@ -59,7 +56,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
|
|
||||||
onmousemove: move |event| log_event(Event::MouseMove(event.inner().clone())),
|
onmousemove: move |event| log_event(Event::MouseMove(event.inner().clone())),
|
||||||
onclick: move |event| log_event(Event::MouseClick(event.inner().clone())),
|
onclick: move |event| log_event(Event::MouseClick(event.inner().clone())),
|
||||||
ondblclick: move |event| log_event(Event::MouseDoubleClick(event.inner().clone())),
|
ondoubleclick: move |event| log_event(Event::MouseDoubleClick(event.inner().clone())),
|
||||||
onmousedown: move |event| log_event(Event::MouseDown(event.inner().clone())),
|
onmousedown: move |event| log_event(Event::MouseDown(event.inner().clone())),
|
||||||
onmouseup: move |event| log_event(Event::MouseUp(event.inner().clone())),
|
onmouseup: move |event| log_event(Event::MouseUp(event.inner().clone())),
|
||||||
|
|
||||||
@@ -73,13 +70,8 @@ fn app(cx: Scope) -> Element {
|
|||||||
onfocusout: move |event| log_event(Event::FocusOut(event.inner().clone())),
|
onfocusout: move |event| log_event(Event::FocusOut(event.inner().clone())),
|
||||||
|
|
||||||
"Hover, click, type or scroll to see the info down below"
|
"Hover, click, type or scroll to see the info down below"
|
||||||
},
|
}
|
||||||
div {
|
div { width: "80%", height: "50%", flex_direction: "column", events_rendered }
|
||||||
width: "80%",
|
}
|
||||||
height: "50%",
|
|
||||||
flex_direction: "column",
|
|
||||||
events_rendered,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -15,21 +15,21 @@ fn app(cx: Scope) -> Element {
|
|||||||
let mapping: DioxusElementToNodeId = cx.consume_context().unwrap();
|
let mapping: DioxusElementToNodeId = cx.consume_context().unwrap();
|
||||||
// disable templates so that every node has an id and can be queried
|
// disable templates so that every node has an id and can be queried
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div{
|
div {
|
||||||
width: "100%",
|
width: "100%",
|
||||||
background_color: "hsl({hue}, 70%, {brightness}%)",
|
background_color: "hsl({hue}, 70%, {brightness}%)",
|
||||||
onmousemove: move |evt| {
|
onmousemove: move |evt| {
|
||||||
if let RenderReturn::Ready(node) = cx.root_node() {
|
if let RenderReturn::Ready(node) = cx.root_node() {
|
||||||
if let Some(id) = node.root_ids.borrow().get(0).cloned() {
|
if let Some(id) = node.root_ids.borrow().first().cloned() {
|
||||||
let node = tui_query.get(mapping.get_node_id(id).unwrap());
|
let node = tui_query.get(mapping.get_node_id(id).unwrap());
|
||||||
let Size{width, height} = node.size().unwrap();
|
let Size { width, height } = node.size().unwrap();
|
||||||
let pos = evt.inner().element_coordinates();
|
let pos = evt.inner().element_coordinates();
|
||||||
hue.set((pos.x as f32/width as f32)*255.0);
|
hue.set((pos.x as f32 / width as f32) * 255.0);
|
||||||
brightness.set((pos.y as f32/height as f32)*100.0);
|
brightness.set((pos.y as f32 / height as f32) * 100.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"hsl({hue}, 70%, {brightness}%)",
|
"hsl({hue}, 70%, {brightness}%)"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,39 @@
|
|||||||
//! This file exports functions into the vscode extension
|
//! This file exports functions into the vscode extension
|
||||||
|
|
||||||
use dioxus_autofmt::FormattedBlock;
|
use dioxus_autofmt::{FormattedBlock, IndentOptions, IndentType};
|
||||||
use wasm_bindgen::prelude::*;
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn format_rsx(raw: String) -> String {
|
pub fn format_rsx(raw: String, use_tabs: bool, indent_size: usize) -> String {
|
||||||
let block = dioxus_autofmt::fmt_block(&raw, 0);
|
let block = dioxus_autofmt::fmt_block(
|
||||||
|
&raw,
|
||||||
|
0,
|
||||||
|
IndentOptions::new(
|
||||||
|
if use_tabs {
|
||||||
|
IndentType::Tabs
|
||||||
|
} else {
|
||||||
|
IndentType::Spaces
|
||||||
|
},
|
||||||
|
indent_size,
|
||||||
|
),
|
||||||
|
);
|
||||||
block.unwrap()
|
block.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn format_selection(raw: String) -> String {
|
pub fn format_selection(raw: String, use_tabs: bool, indent_size: usize) -> String {
|
||||||
let block = dioxus_autofmt::fmt_block(&raw, 0);
|
let block = dioxus_autofmt::fmt_block(
|
||||||
|
&raw,
|
||||||
|
0,
|
||||||
|
IndentOptions::new(
|
||||||
|
if use_tabs {
|
||||||
|
IndentType::Tabs
|
||||||
|
} else {
|
||||||
|
IndentType::Spaces
|
||||||
|
},
|
||||||
|
indent_size,
|
||||||
|
),
|
||||||
|
);
|
||||||
block.unwrap()
|
block.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,8 +57,18 @@ impl FormatBlockInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn format_file(contents: String) -> FormatBlockInstance {
|
pub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance {
|
||||||
let _edits = dioxus_autofmt::fmt_file(&contents);
|
let _edits = dioxus_autofmt::fmt_file(
|
||||||
|
&contents,
|
||||||
|
IndentOptions::new(
|
||||||
|
if use_tabs {
|
||||||
|
IndentType::Tabs
|
||||||
|
} else {
|
||||||
|
IndentType::Spaces
|
||||||
|
},
|
||||||
|
indent_size,
|
||||||
|
),
|
||||||
|
);
|
||||||
let out = dioxus_autofmt::apply_formats(&contents, _edits.clone());
|
let out = dioxus_autofmt::apply_formats(&contents, _edits.clone());
|
||||||
FormatBlockInstance { new: out, _edits }
|
FormatBlockInstance { new: out, _edits }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,13 @@ function fmtDocument(document: vscode.TextDocument) {
|
|||||||
if (!editor) return; // Need an editor to apply text edits.
|
if (!editor) return; // Need an editor to apply text edits.
|
||||||
|
|
||||||
const contents = editor.document.getText();
|
const contents = editor.document.getText();
|
||||||
const formatted = dioxus.format_file(contents);
|
let tabSize: number;
|
||||||
|
if (typeof editor.options.tabSize === 'number') {
|
||||||
|
tabSize = editor.options.tabSize;
|
||||||
|
} else {
|
||||||
|
tabSize = 4;
|
||||||
|
}
|
||||||
|
const formatted = dioxus.format_file(contents, !editor.options.insertSpaces, tabSize);
|
||||||
|
|
||||||
// Replace the entire text document
|
// Replace the entire text document
|
||||||
// Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using
|
// Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ mod atoms {
|
|||||||
pub use atom::*;
|
pub use atom::*;
|
||||||
pub use atomfamily::*;
|
pub use atomfamily::*;
|
||||||
pub use atomref::*;
|
pub use atomref::*;
|
||||||
pub use selector::*;
|
|
||||||
pub use selectorfamily::*;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod hooks {
|
pub mod hooks {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ tower = { version = "0.4.13", features = ["util"], optional = true }
|
|||||||
axum-macros = "0.3.7"
|
axum-macros = "0.3.7"
|
||||||
|
|
||||||
# salvo
|
# salvo
|
||||||
salvo = { version = "0.46.0", optional = true, features = ["serve-static", "websocket", "compression"] }
|
salvo = { version = "0.63.0", optional = true, features = ["serve-static", "websocket", "compression"] }
|
||||||
serde = "1.0.159"
|
serde = "1.0.159"
|
||||||
|
|
||||||
# Dioxus + SSR
|
# Dioxus + SSR
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||||||
|
|
||||||
let mut count = use_state(cx, || 0);
|
let mut count = use_state(cx, || 0);
|
||||||
let text = use_state(cx, || "...".to_string());
|
let text = use_state(cx, || "...".to_string());
|
||||||
|
let eval = use_eval(cx);
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ dioxus = { workspace = true }
|
|||||||
dioxus-fullstack = { workspace = true }
|
dioxus-fullstack = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["full"], optional = true }
|
tokio = { workspace = true, features = ["full"], optional = true }
|
||||||
serde = "1.0.159"
|
serde = "1.0.159"
|
||||||
salvo = { version = "0.37.9", optional = true }
|
salvo = { version = "0.63.0", optional = true }
|
||||||
execute = "0.2.12"
|
execute = "0.2.12"
|
||||||
reqwest = "0.11.18"
|
reqwest = "0.11.18"
|
||||||
simple_logger = "4.2.0"
|
simple_logger = "4.2.0"
|
||||||
|
|||||||
@@ -89,26 +89,26 @@ impl Service for ServerFnHandler {
|
|||||||
let parts = Arc::new(RwLock::new(parts));
|
let parts = Arc::new(RwLock::new(parts));
|
||||||
|
|
||||||
// Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime
|
// Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime
|
||||||
let (resp_tx, resp_rx) = tokio::sync::oneshot::channel();
|
|
||||||
let pool = get_local_pool();
|
let pool = get_local_pool();
|
||||||
pool.spawn_pinned({
|
let result = pool
|
||||||
let function = function.clone();
|
.spawn_pinned({
|
||||||
let mut server_context = server_context.clone();
|
let function = function.clone();
|
||||||
server_context.parts = parts;
|
let mut server_context = server_context.clone();
|
||||||
move || async move {
|
server_context.parts = parts;
|
||||||
let data = match function.encoding() {
|
move || async move {
|
||||||
Encoding::Url | Encoding::Cbor => &body,
|
let data = match function.encoding() {
|
||||||
Encoding::GetJSON | Encoding::GetCBOR => &query,
|
Encoding::Url | Encoding::Cbor => &body,
|
||||||
};
|
Encoding::GetJSON | Encoding::GetCBOR => &query,
|
||||||
let server_function_future = function.call((), data);
|
};
|
||||||
let server_function_future =
|
let server_function_future = function.call((), data);
|
||||||
ProvideServerContext::new(server_function_future, server_context.clone());
|
let server_function_future = ProvideServerContext::new(
|
||||||
let resp = server_function_future.await;
|
server_function_future,
|
||||||
|
server_context.clone(),
|
||||||
resp_tx.send(resp).unwrap();
|
);
|
||||||
}
|
server_function_future.await
|
||||||
});
|
}
|
||||||
let result = resp_rx.await.unwrap();
|
})
|
||||||
|
.await?;
|
||||||
let mut res = http::Response::builder();
|
let mut res = http::Response::builder();
|
||||||
|
|
||||||
// Set the headers from the server context
|
// Set the headers from the server context
|
||||||
|
|||||||
@@ -12,9 +12,11 @@ use serde::{de::DeserializeOwned, Serialize};
|
|||||||
/// use dioxus_fullstack::prelude::*;
|
/// use dioxus_fullstack::prelude::*;
|
||||||
///
|
///
|
||||||
/// fn app(cx: Scope) -> Element {
|
/// fn app(cx: Scope) -> Element {
|
||||||
/// let state1 = use_state(cx, || from_server(|| {
|
/// let state1 = use_state(cx, || server_cached(|| {
|
||||||
/// 1234
|
/// 1234
|
||||||
/// }));
|
/// }));
|
||||||
|
///
|
||||||
|
/// todo!()
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn server_cached<O: 'static + Serialize + DeserializeOwned>(server_fn: impl Fn() -> O) -> O {
|
pub fn server_cached<O: 'static + Serialize + DeserializeOwned>(server_fn: impl Fn() -> O) -> O {
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ use tracing_futures::Instrument;
|
|||||||
|
|
||||||
use http::{Request, Response};
|
use http::{Request, Response};
|
||||||
|
|
||||||
|
/// A layer that wraps a service. This can be used to add additional information to the request, or response on top of some other service
|
||||||
pub trait Layer: Send + Sync + 'static {
|
pub trait Layer: Send + Sync + 'static {
|
||||||
|
/// Wrap a boxed service with this layer
|
||||||
fn layer(&self, inner: BoxedService) -> BoxedService;
|
fn layer(&self, inner: BoxedService) -> BoxedService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,7 +19,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A service is a function that takes a request and returns an async response
|
||||||
pub trait Service {
|
pub trait Service {
|
||||||
|
/// Run the service and produce a future that resolves to a response
|
||||||
fn run(
|
fn run(
|
||||||
&mut self,
|
&mut self,
|
||||||
req: http::Request<hyper::body::Body>,
|
req: http::Request<hyper::body::Body>,
|
||||||
@@ -55,6 +59,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A boxed service is a type-erased service that can be used without knowing the underlying type
|
||||||
pub struct BoxedService(pub Box<dyn Service + Send>);
|
pub struct BoxedService(pub Box<dyn Service + Send>);
|
||||||
|
|
||||||
impl tower::Service<http::Request<hyper::body::Body>> for BoxedService {
|
impl tower::Service<http::Request<hyper::body::Body>> for BoxedService {
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ pub mod prelude {
|
|||||||
#[cfg(not(feature = "ssr"))]
|
#[cfg(not(feature = "ssr"))]
|
||||||
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
||||||
pub use crate::launch::LaunchBuilder;
|
pub use crate::launch::LaunchBuilder;
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
pub use crate::layer::{Layer, Service};
|
||||||
#[cfg(all(feature = "ssr", feature = "router"))]
|
#[cfg(all(feature = "ssr", feature = "router"))]
|
||||||
pub use crate::render::pre_cache_static_routes_with_props;
|
pub use crate::render::pre_cache_static_routes_with_props;
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
|
|||||||
@@ -45,6 +45,8 @@ impl SsrRendererPool {
|
|||||||
.expect("couldn't spawn runtime")
|
.expect("couldn't spawn runtime")
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
let mut vdom = VirtualDom::new_with_props(component, props);
|
let mut vdom = VirtualDom::new_with_props(component, props);
|
||||||
|
// Make sure the evaluator is initialized
|
||||||
|
dioxus_ssr::eval::init_eval(vdom.base_scope());
|
||||||
let mut to = WriteBuffer { buffer: Vec::new() };
|
let mut to = WriteBuffer { buffer: Vec::new() };
|
||||||
// before polling the future, we need to set the context
|
// before polling the future, we need to set the context
|
||||||
let prev_context =
|
let prev_context =
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "generational-box"
|
name = "generational-box"
|
||||||
authors = ["Evan Almloff"]
|
authors = ["Evan Almloff"]
|
||||||
version = "0.1.0"
|
version = "0.4.3"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
description = "A box backed by a generational runtime"
|
description = "A box backed by a generational runtime"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
@@ -18,3 +18,5 @@ rand = "0.8.5"
|
|||||||
[features]
|
[features]
|
||||||
default = ["check_generation"]
|
default = ["check_generation"]
|
||||||
check_generation = []
|
check_generation = []
|
||||||
|
debug_borrows = []
|
||||||
|
debug_ownership = []
|
||||||
@@ -11,6 +11,8 @@ Three main types manage state in Generational Box:
|
|||||||
Example:
|
Example:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
|
use generational_box::Store;
|
||||||
|
|
||||||
// Create a store for this thread
|
// Create a store for this thread
|
||||||
let store = Store::default();
|
let store = Store::default();
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
any::Any,
|
||||||
cell::{Cell, Ref, RefCell, RefMut},
|
cell::{Cell, Ref, RefCell, RefMut},
|
||||||
fmt::Debug,
|
error::Error,
|
||||||
|
fmt::{Debug, Display},
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,12 +32,12 @@ fn reused() {
|
|||||||
let first_ptr;
|
let first_ptr;
|
||||||
{
|
{
|
||||||
let owner = store.owner();
|
let owner = store.owner();
|
||||||
first_ptr = owner.insert(1).raw.data.as_ptr();
|
first_ptr = owner.insert(1).raw.0.data.as_ptr();
|
||||||
drop(owner);
|
drop(owner);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let owner = store.owner();
|
let owner = store.owner();
|
||||||
let second_ptr = owner.insert(1234).raw.data.as_ptr();
|
let second_ptr = owner.insert(1234).raw.0.data.as_ptr();
|
||||||
assert_eq!(first_ptr, second_ptr);
|
assert_eq!(first_ptr, second_ptr);
|
||||||
drop(owner);
|
drop(owner);
|
||||||
}
|
}
|
||||||
@@ -53,7 +56,10 @@ fn leaking_is_ok() {
|
|||||||
// don't drop the owner
|
// don't drop the owner
|
||||||
std::mem::forget(owner);
|
std::mem::forget(owner);
|
||||||
}
|
}
|
||||||
assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string()));
|
assert_eq!(
|
||||||
|
key.try_read().as_deref().unwrap(),
|
||||||
|
&"hello world".to_string()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -68,7 +74,7 @@ fn drops() {
|
|||||||
key = owner.insert(data);
|
key = owner.insert(data);
|
||||||
// drop the owner
|
// drop the owner
|
||||||
}
|
}
|
||||||
assert!(key.try_read().is_none());
|
assert!(key.try_read().is_err());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -129,7 +135,7 @@ fn fuzz() {
|
|||||||
println!("{:?}", path);
|
println!("{:?}", path);
|
||||||
for key in valid_keys.iter() {
|
for key in valid_keys.iter() {
|
||||||
let value = key.read();
|
let value = key.read();
|
||||||
println!("{:?}", value);
|
println!("{:?}", &*value);
|
||||||
assert!(value.starts_with("hello world"));
|
assert!(value.starts_with("hello world"));
|
||||||
}
|
}
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
@@ -153,6 +159,8 @@ pub struct GenerationalBox<T> {
|
|||||||
raw: MemoryLocation,
|
raw: MemoryLocation,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
generation: u32,
|
generation: u32,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at: &'static std::panic::Location<'static>,
|
||||||
_marker: PhantomData<T>,
|
_marker: PhantomData<T>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,7 +169,7 @@ impl<T: 'static> Debug for GenerationalBox<T> {
|
|||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
f.write_fmt(format_args!(
|
f.write_fmt(format_args!(
|
||||||
"{:?}@{:?}",
|
"{:?}@{:?}",
|
||||||
self.raw.data.as_ptr(),
|
self.raw.0.data.as_ptr(),
|
||||||
self.generation
|
self.generation
|
||||||
))?;
|
))?;
|
||||||
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
||||||
@@ -175,7 +183,7 @@ impl<T: 'static> GenerationalBox<T> {
|
|||||||
fn validate(&self) -> bool {
|
fn validate(&self) -> bool {
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
{
|
{
|
||||||
self.raw.generation.get() == self.generation
|
self.raw.0.generation.get() == self.generation
|
||||||
}
|
}
|
||||||
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
||||||
{
|
{
|
||||||
@@ -184,43 +192,51 @@ impl<T: 'static> GenerationalBox<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Try to read the value. Returns None if the value is no longer valid.
|
/// Try to read the value. Returns None if the value is no longer valid.
|
||||||
pub fn try_read(&self) -> Option<Ref<'static, T>> {
|
#[track_caller]
|
||||||
self.validate()
|
pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
|
||||||
.then(|| {
|
if !self.validate() {
|
||||||
Ref::filter_map(self.raw.data.borrow(), |any| {
|
return Err(BorrowError::Dropped(ValueDroppedError {
|
||||||
any.as_ref()?.downcast_ref::<T>()
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
})
|
created_at: self.created_at,
|
||||||
.ok()
|
}));
|
||||||
})
|
}
|
||||||
.flatten()
|
self.raw.try_borrow(
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
self.created_at,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the value. Panics if the value is no longer valid.
|
/// Read the value. Panics if the value is no longer valid.
|
||||||
pub fn read(&self) -> Ref<'static, T> {
|
#[track_caller]
|
||||||
|
pub fn read(&self) -> GenerationalRef<T> {
|
||||||
self.try_read().unwrap()
|
self.try_read().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to write the value. Returns None if the value is no longer valid.
|
/// Try to write the value. Returns None if the value is no longer valid.
|
||||||
pub fn try_write(&self) -> Option<RefMut<'static, T>> {
|
#[track_caller]
|
||||||
self.validate()
|
pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
|
||||||
.then(|| {
|
if !self.validate() {
|
||||||
RefMut::filter_map(self.raw.data.borrow_mut(), |any| {
|
return Err(BorrowMutError::Dropped(ValueDroppedError {
|
||||||
any.as_mut()?.downcast_mut::<T>()
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
})
|
created_at: self.created_at,
|
||||||
.ok()
|
}));
|
||||||
})
|
}
|
||||||
.flatten()
|
self.raw.try_borrow_mut(
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
self.created_at,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the value. Panics if the value is no longer valid.
|
/// Write the value. Panics if the value is no longer valid.
|
||||||
pub fn write(&self) -> RefMut<'static, T> {
|
#[track_caller]
|
||||||
|
pub fn write(&self) -> GenerationalRefMut<T> {
|
||||||
self.try_write().unwrap()
|
self.try_write().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the value. Panics if the value is no longer valid.
|
/// Set the value. Panics if the value is no longer valid.
|
||||||
pub fn set(&self, value: T) {
|
pub fn set(&self, value: T) {
|
||||||
self.validate().then(|| {
|
self.validate().then(|| {
|
||||||
*self.raw.data.borrow_mut() = Some(Box::new(value));
|
*self.raw.0.data.borrow_mut() = Some(Box::new(value));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,7 +244,8 @@ impl<T: 'static> GenerationalBox<T> {
|
|||||||
pub fn ptr_eq(&self, other: &Self) -> bool {
|
pub fn ptr_eq(&self, other: &Self) -> bool {
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
{
|
{
|
||||||
self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation
|
self.raw.0.data.as_ptr() == other.raw.0.data.as_ptr()
|
||||||
|
&& self.generation == other.generation
|
||||||
}
|
}
|
||||||
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
#[cfg(not(any(debug_assertions, feature = "check_generation")))]
|
||||||
{
|
{
|
||||||
@@ -246,26 +263,37 @@ impl<T> Clone for GenerationalBox<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
struct MemoryLocation {
|
struct MemoryLocation(&'static MemoryLocationInner);
|
||||||
data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
|
|
||||||
|
struct MemoryLocationInner {
|
||||||
|
data: RefCell<Option<Box<dyn std::any::Any>>>,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
generation: &'static Cell<u32>,
|
generation: Cell<u32>,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_at: RefCell<Vec<&'static std::panic::Location<'static>>>,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_mut_at: Cell<Option<&'static std::panic::Location<'static>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MemoryLocation {
|
impl MemoryLocation {
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
fn drop(&self) {
|
fn drop(&self) {
|
||||||
let old = self.data.borrow_mut().take();
|
let old = self.0.data.borrow_mut().take();
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
if old.is_some() {
|
if old.is_some() {
|
||||||
drop(old);
|
drop(old);
|
||||||
let new_generation = self.generation.get() + 1;
|
let new_generation = self.0.generation.get() + 1;
|
||||||
self.generation.set(new_generation);
|
self.0.generation.set(new_generation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace<T: 'static>(&mut self, value: T) -> GenerationalBox<T> {
|
fn replace_with_caller<T: 'static>(
|
||||||
let mut inner_mut = self.data.borrow_mut();
|
&mut self,
|
||||||
|
value: T,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
caller: &'static std::panic::Location<'static>,
|
||||||
|
) -> GenerationalBox<T> {
|
||||||
|
let mut inner_mut = self.0.data.borrow_mut();
|
||||||
|
|
||||||
let raw = Box::new(value);
|
let raw = Box::new(value);
|
||||||
let old = inner_mut.replace(raw);
|
let old = inner_mut.replace(raw);
|
||||||
@@ -273,10 +301,315 @@ impl MemoryLocation {
|
|||||||
GenerationalBox {
|
GenerationalBox {
|
||||||
raw: *self,
|
raw: *self,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
generation: self.generation.get(),
|
generation: self.0.generation.get(),
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at: caller,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn try_borrow<T: Any>(
|
||||||
|
&self,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at: &'static std::panic::Location<'static>,
|
||||||
|
) -> Result<GenerationalRef<T>, BorrowError> {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
self.0
|
||||||
|
.borrowed_at
|
||||||
|
.borrow_mut()
|
||||||
|
.push(std::panic::Location::caller());
|
||||||
|
match self.0.data.try_borrow() {
|
||||||
|
Ok(borrow) => match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::<T>()) {
|
||||||
|
Ok(reference) => Ok(GenerationalRef {
|
||||||
|
inner: reference,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow: GenerationalRefBorrowInfo {
|
||||||
|
borrowed_at: std::panic::Location::caller(),
|
||||||
|
borrowed_from: self.0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Err(_) => Err(BorrowError::Dropped(ValueDroppedError {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
Err(_) => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_mut_at: self.0.borrowed_mut_at.get().unwrap(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn try_borrow_mut<T: Any>(
|
||||||
|
&self,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at: &'static std::panic::Location<'static>,
|
||||||
|
) -> Result<GenerationalRefMut<T>, BorrowMutError> {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
{
|
||||||
|
self.0
|
||||||
|
.borrowed_mut_at
|
||||||
|
.set(Some(std::panic::Location::caller()));
|
||||||
|
}
|
||||||
|
match self.0.data.try_borrow_mut() {
|
||||||
|
Ok(borrow_mut) => {
|
||||||
|
match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::<T>()) {
|
||||||
|
Ok(reference) => Ok(GenerationalRefMut {
|
||||||
|
inner: reference,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow: GenerationalRefMutBorrowInfo {
|
||||||
|
borrowed_from: self.0,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at,
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_at: self.0.borrowed_at.borrow().clone(),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
/// An error that can occur when trying to borrow a value.
|
||||||
|
pub enum BorrowError {
|
||||||
|
/// The value was dropped.
|
||||||
|
Dropped(ValueDroppedError),
|
||||||
|
/// The value was already borrowed mutably.
|
||||||
|
AlreadyBorrowedMut(AlreadyBorrowedMutError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BorrowError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
BorrowError::Dropped(error) => Display::fmt(error, f),
|
||||||
|
BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for BorrowError {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
/// An error that can occur when trying to borrow a value mutably.
|
||||||
|
pub enum BorrowMutError {
|
||||||
|
/// The value was dropped.
|
||||||
|
Dropped(ValueDroppedError),
|
||||||
|
/// The value was already borrowed.
|
||||||
|
AlreadyBorrowed(AlreadyBorrowedError),
|
||||||
|
/// The value was already borrowed mutably.
|
||||||
|
AlreadyBorrowedMut(AlreadyBorrowedMutError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for BorrowMutError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
BorrowMutError::Dropped(error) => Display::fmt(error, f),
|
||||||
|
BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f),
|
||||||
|
BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error for BorrowMutError {}
|
||||||
|
|
||||||
|
/// An error that can occur when trying to use a value that has been dropped.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct ValueDroppedError {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at: &'static std::panic::Location<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ValueDroppedError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("Failed to borrow because the value was dropped.")?;
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
f.write_fmt(format_args!("created_at: {}", self.created_at))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for ValueDroppedError {}
|
||||||
|
|
||||||
|
/// An error that can occur when trying to borrow a value that has already been borrowed mutably.
|
||||||
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
pub struct AlreadyBorrowedMutError {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_mut_at: &'static std::panic::Location<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AlreadyBorrowedMutError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("Failed to borrow because the value was already borrowed mutably.")?;
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for AlreadyBorrowedMutError {}
|
||||||
|
|
||||||
|
/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AlreadyBorrowedError {
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_at: Vec<&'static std::panic::Location<'static>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AlreadyBorrowedError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?;
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
f.write_str("borrowed_at:")?;
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
for location in self.borrowed_at.iter() {
|
||||||
|
f.write_fmt(format_args!("\t{}", location))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for AlreadyBorrowedError {}
|
||||||
|
|
||||||
|
/// A reference to a value in a generational box.
|
||||||
|
pub struct GenerationalRef<T: 'static> {
|
||||||
|
inner: Ref<'static, T>,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow: GenerationalRefBorrowInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> GenerationalRef<T> {
|
||||||
|
/// Map one ref type to another.
|
||||||
|
pub fn map<U, F>(orig: GenerationalRef<T>, f: F) -> GenerationalRef<U>
|
||||||
|
where
|
||||||
|
F: FnOnce(&T) -> &U,
|
||||||
|
{
|
||||||
|
GenerationalRef {
|
||||||
|
inner: Ref::map(orig.inner, f),
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow: GenerationalRefBorrowInfo {
|
||||||
|
borrowed_at: orig.borrow.borrowed_at,
|
||||||
|
borrowed_from: orig.borrow.borrowed_from,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filter one ref type to another.
|
||||||
|
pub fn filter_map<U, F>(orig: GenerationalRef<T>, f: F) -> Option<GenerationalRef<U>>
|
||||||
|
where
|
||||||
|
F: FnOnce(&T) -> Option<&U>,
|
||||||
|
{
|
||||||
|
let Self {
|
||||||
|
inner,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow,
|
||||||
|
} = orig;
|
||||||
|
Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef {
|
||||||
|
inner,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow: GenerationalRefBorrowInfo {
|
||||||
|
borrowed_at: borrow.borrowed_at,
|
||||||
|
borrowed_from: borrow.borrowed_from,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Deref for GenerationalRef<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.inner.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
struct GenerationalRefBorrowInfo {
|
||||||
|
borrowed_at: &'static std::panic::Location<'static>,
|
||||||
|
borrowed_from: &'static MemoryLocationInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
impl Drop for GenerationalRefBorrowInfo {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.borrowed_from
|
||||||
|
.borrowed_at
|
||||||
|
.borrow_mut()
|
||||||
|
.retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A mutable reference to a value in a generational box.
|
||||||
|
pub struct GenerationalRefMut<T: 'static> {
|
||||||
|
inner: RefMut<'static, T>,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow: GenerationalRefMutBorrowInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> GenerationalRefMut<T> {
|
||||||
|
/// Map one ref type to another.
|
||||||
|
pub fn map<U, F>(orig: GenerationalRefMut<T>, f: F) -> GenerationalRefMut<U>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T) -> &mut U,
|
||||||
|
{
|
||||||
|
GenerationalRefMut {
|
||||||
|
inner: RefMut::map(orig.inner, f),
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow: orig.borrow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Filter one ref type to another.
|
||||||
|
pub fn filter_map<U, F>(orig: GenerationalRefMut<T>, f: F) -> Option<GenerationalRefMut<U>>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut T) -> Option<&mut U>,
|
||||||
|
{
|
||||||
|
let Self {
|
||||||
|
inner,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow,
|
||||||
|
} = orig;
|
||||||
|
RefMut::filter_map(inner, f)
|
||||||
|
.ok()
|
||||||
|
.map(|inner| GenerationalRefMut {
|
||||||
|
inner,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrow,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> Deref for GenerationalRefMut<T> {
|
||||||
|
type Target = T;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
self.inner.deref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> DerefMut for GenerationalRefMut<T> {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
self.inner.deref_mut()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
struct GenerationalRefMutBorrowInfo {
|
||||||
|
borrowed_from: &'static MemoryLocationInner,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
impl Drop for GenerationalRefMutBorrowInfo {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.borrowed_from.borrowed_mut_at.take();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
|
/// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
|
||||||
@@ -305,12 +638,16 @@ impl Store {
|
|||||||
if let Some(location) = self.recycled.borrow_mut().pop() {
|
if let Some(location) = self.recycled.borrow_mut().pop() {
|
||||||
location
|
location
|
||||||
} else {
|
} else {
|
||||||
let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None));
|
let data: &'static MemoryLocationInner = self.bump.alloc(MemoryLocationInner {
|
||||||
MemoryLocation {
|
data: RefCell::new(None),
|
||||||
data,
|
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
generation: self.bump.alloc(Cell::new(0)),
|
generation: Cell::new(0),
|
||||||
}
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_at: Default::default(),
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
borrowed_mut_at: Default::default(),
|
||||||
|
});
|
||||||
|
MemoryLocation(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,9 +668,31 @@ pub struct Owner {
|
|||||||
|
|
||||||
impl Owner {
|
impl Owner {
|
||||||
/// Insert a value into the store. The value will be dropped when the owner is dropped.
|
/// Insert a value into the store. The value will be dropped when the owner is dropped.
|
||||||
|
#[track_caller]
|
||||||
pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> {
|
pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> {
|
||||||
let mut location = self.store.claim();
|
let mut location = self.store.claim();
|
||||||
let key = location.replace(value);
|
let key = location.replace_with_caller(
|
||||||
|
value,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
std::panic::Location::caller(),
|
||||||
|
);
|
||||||
|
self.owned.borrow_mut().push(location);
|
||||||
|
key
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped.
|
||||||
|
pub fn insert_with_caller<T: 'static>(
|
||||||
|
&self,
|
||||||
|
value: T,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
caller: &'static std::panic::Location<'static>,
|
||||||
|
) -> GenerationalBox<T> {
|
||||||
|
let mut location = self.store.claim();
|
||||||
|
let key = location.replace_with_caller(
|
||||||
|
value,
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
caller,
|
||||||
|
);
|
||||||
self.owned.borrow_mut().push(location);
|
self.owned.borrow_mut().push(location);
|
||||||
key
|
key
|
||||||
}
|
}
|
||||||
@@ -341,12 +700,16 @@ impl Owner {
|
|||||||
/// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value.
|
/// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value.
|
||||||
pub fn invalid<T: 'static>(&self) -> GenerationalBox<T> {
|
pub fn invalid<T: 'static>(&self) -> GenerationalBox<T> {
|
||||||
let location = self.store.claim();
|
let location = self.store.claim();
|
||||||
GenerationalBox {
|
let key = GenerationalBox {
|
||||||
raw: location,
|
raw: location,
|
||||||
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
#[cfg(any(debug_assertions, feature = "check_generation"))]
|
||||||
generation: location.generation.get(),
|
generation: location.0.generation.get(),
|
||||||
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
|
created_at: std::panic::Location::caller(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
};
|
||||||
|
self.owned.borrow_mut().push(location);
|
||||||
|
key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
use dioxus_core::{ScopeState, TaskId};
|
use dioxus_core::{ScopeState, TaskId};
|
||||||
use std::{any::Any, cell::Cell, future::Future};
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
future::Future,
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::UseFutureDep;
|
use crate::UseFutureDep;
|
||||||
|
|
||||||
@@ -14,7 +19,7 @@ use crate::UseFutureDep;
|
|||||||
/// ## Arguments
|
/// ## Arguments
|
||||||
///
|
///
|
||||||
/// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`.
|
/// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`.
|
||||||
/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future.
|
/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. That future may return nothing or a closure that will be executed when the dependencies change to clean up the effect.
|
||||||
///
|
///
|
||||||
/// ## Examples
|
/// ## Examples
|
||||||
///
|
///
|
||||||
@@ -33,6 +38,16 @@ use crate::UseFutureDep;
|
|||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
|
/// // Only fetch the user data when the id changes.
|
||||||
|
/// use_effect(cx, (id,), |(id,)| {
|
||||||
|
/// to_owned![name];
|
||||||
|
/// async move {
|
||||||
|
/// let user = fetch_user(id).await;
|
||||||
|
/// name.set(user.name);
|
||||||
|
/// move || println!("Cleaning up from {}", id)
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
/// let name = name.get().clone().unwrap_or("Loading...".to_string());
|
/// let name = name.get().clone().unwrap_or("Loading...".to_string());
|
||||||
///
|
///
|
||||||
/// render!(
|
/// render!(
|
||||||
@@ -45,34 +60,80 @@ use crate::UseFutureDep;
|
|||||||
/// render!(Profile { id: 0 })
|
/// render!(Profile { id: 0 })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn use_effect<T, F, D>(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> F)
|
pub fn use_effect<T, R, D>(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> R)
|
||||||
where
|
where
|
||||||
T: 'static,
|
|
||||||
F: Future<Output = T> + 'static,
|
|
||||||
D: UseFutureDep,
|
D: UseFutureDep,
|
||||||
|
R: UseEffectReturn<T>,
|
||||||
{
|
{
|
||||||
struct UseEffect {
|
struct UseEffect {
|
||||||
needs_regen: bool,
|
needs_regen: bool,
|
||||||
task: Cell<Option<TaskId>>,
|
task: Cell<Option<TaskId>>,
|
||||||
dependencies: Vec<Box<dyn Any>>,
|
dependencies: Vec<Box<dyn Any>>,
|
||||||
|
cleanup: UseEffectCleanup,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for UseEffect {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(cleanup) = self.cleanup.borrow_mut().take() {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = cx.use_hook(move || UseEffect {
|
let state = cx.use_hook(move || UseEffect {
|
||||||
needs_regen: true,
|
needs_regen: true,
|
||||||
task: Cell::new(None),
|
task: Cell::new(None),
|
||||||
dependencies: Vec::new(),
|
dependencies: Vec::new(),
|
||||||
|
cleanup: Rc::new(RefCell::new(None)),
|
||||||
});
|
});
|
||||||
|
|
||||||
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen {
|
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen {
|
||||||
|
// Call the cleanup function if it exists
|
||||||
|
if let Some(cleanup) = state.cleanup.borrow_mut().take() {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
// We don't need regen anymore
|
// We don't need regen anymore
|
||||||
state.needs_regen = false;
|
state.needs_regen = false;
|
||||||
|
|
||||||
// Create the new future
|
// Create the new future
|
||||||
let fut = future(dependencies.out());
|
let return_value = future(dependencies.out());
|
||||||
|
|
||||||
state.task.set(Some(cx.push_future(async move {
|
if let Some(task) = return_value.apply(state.cleanup.clone(), cx) {
|
||||||
fut.await;
|
state.task.set(Some(task));
|
||||||
})));
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UseEffectCleanup = Rc<RefCell<Option<Box<dyn FnOnce()>>>>;
|
||||||
|
|
||||||
|
/// Something that can be returned from a `use_effect` hook.
|
||||||
|
pub trait UseEffectReturn<T> {
|
||||||
|
fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option<TaskId>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> UseEffectReturn<()> for T
|
||||||
|
where
|
||||||
|
T: Future<Output = ()> + 'static,
|
||||||
|
{
|
||||||
|
fn apply(self, _: UseEffectCleanup, cx: &ScopeState) -> Option<TaskId> {
|
||||||
|
Some(cx.push_future(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub struct CleanupFutureMarker;
|
||||||
|
impl<T, F> UseEffectReturn<CleanupFutureMarker> for T
|
||||||
|
where
|
||||||
|
T: Future<Output = F> + 'static,
|
||||||
|
F: FnOnce() + 'static,
|
||||||
|
{
|
||||||
|
fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option<TaskId> {
|
||||||
|
let task = cx.push_future(async move {
|
||||||
|
let cleanup = self.await;
|
||||||
|
*oncleanup.borrow_mut() = Some(Box::new(cleanup) as Box<dyn FnOnce()>);
|
||||||
|
});
|
||||||
|
Some(task)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ macro_rules! debug_location {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod error {
|
pub mod error {
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String {
|
fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String {
|
||||||
locations
|
locations
|
||||||
.iter()
|
.iter()
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
|
|||||||
} = cfg;
|
} = cfg;
|
||||||
|
|
||||||
if let Ok(crate_dir) = PathBuf::from_str(root_path) {
|
if let Ok(crate_dir) = PathBuf::from_str(root_path) {
|
||||||
// try to find the gitingore file
|
// try to find the gitignore file
|
||||||
let gitignore_file_path = crate_dir.join(".gitignore");
|
let gitignore_file_path = crate_dir.join(".gitignore");
|
||||||
let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path);
|
let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path);
|
||||||
|
|
||||||
@@ -152,21 +152,20 @@ pub fn init<Ctx: HotReloadingContext + Send + 'static>(cfg: Config<Ctx>) {
|
|||||||
}
|
}
|
||||||
let file_map = Arc::new(Mutex::new(file_map));
|
let file_map = Arc::new(Mutex::new(file_map));
|
||||||
|
|
||||||
|
let target_dir = crate_dir.join("target");
|
||||||
|
let hot_reload_socket_path = target_dir.join("dioxusin");
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
// On unix, if you force quit the application, it can leave the file socket open
|
// 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
|
// 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
|
// We check if the file socket is already open from an old session and then delete it
|
||||||
let paths = ["./dioxusin", "./@dioxusin"];
|
if hot_reload_socket_path.exists() {
|
||||||
for path in paths {
|
let _ = std::fs::remove_file(hot_reload_socket_path);
|
||||||
let path = PathBuf::from(path);
|
|
||||||
if path.exists() {
|
|
||||||
let _ = std::fs::remove_file(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match LocalSocketListener::bind("@dioxusin") {
|
match LocalSocketListener::bind(hot_reload_socket_path) {
|
||||||
Ok(local_socket_stream) => {
|
Ok(local_socket_stream) => {
|
||||||
let aborted = Arc::new(Mutex::new(false));
|
let aborted = Arc::new(Mutex::new(false));
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
use std::io::{BufRead, BufReader};
|
use std::{
|
||||||
|
io::{BufRead, BufReader},
|
||||||
|
path::PathBuf,
|
||||||
|
};
|
||||||
|
|
||||||
use dioxus_core::Template;
|
use dioxus_core::Template;
|
||||||
#[cfg(feature = "file_watcher")]
|
#[cfg(feature = "file_watcher")]
|
||||||
@@ -24,7 +27,8 @@ pub enum HotReloadMsg {
|
|||||||
/// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected
|
/// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected
|
||||||
pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) {
|
pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) {
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
if let Ok(socket) = LocalSocketStream::connect("@dioxusin") {
|
let path = PathBuf::from("./").join("target").join("dioxusin");
|
||||||
|
if let Ok(socket) = LocalSocketStream::connect(path) {
|
||||||
let mut buf_reader = BufReader::new(socket);
|
let mut buf_reader = BufReader::new(socket);
|
||||||
loop {
|
loop {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ keyboard-types = "0.7"
|
|||||||
async-trait = "0.1.58"
|
async-trait = "0.1.58"
|
||||||
serde-value = "0.7.0"
|
serde-value = "0.7.0"
|
||||||
tokio = { workspace = true, features = ["fs", "io-util"], optional = true }
|
tokio = { workspace = true, features = ["fs", "io-util"], optional = true }
|
||||||
rfd = { version = "0.11.3", optional = true }
|
rfd = { version = "0.12", optional = true }
|
||||||
async-channel = "1.8.0"
|
async-channel = "1.8.0"
|
||||||
serde_json = { version = "1", optional = true }
|
serde_json = { version = "1", optional = true }
|
||||||
|
|
||||||
@@ -68,3 +68,4 @@ mounted = [
|
|||||||
wasm-bind = ["web-sys", "wasm-bindgen"]
|
wasm-bind = ["web-sys", "wasm-bindgen"]
|
||||||
native-bind = ["tokio"]
|
native-bind = ["tokio"]
|
||||||
hot-reload-context = ["dioxus-rsx"]
|
hot-reload-context = ["dioxus-rsx"]
|
||||||
|
html-to-rsx = []
|
||||||
@@ -74,7 +74,26 @@ macro_rules! impl_attribute_match {
|
|||||||
$attr:ident $fil:ident: $vil:ident (in $ns:literal),
|
$attr:ident $fil:ident: $vil:ident (in $ns:literal),
|
||||||
) => {
|
) => {
|
||||||
if $attr == stringify!($fil) {
|
if $attr == stringify!($fil) {
|
||||||
return Some((stringify!(fil), Some(ns)));
|
return Some((stringify!(fil), Some($ns)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
macro_rules! impl_html_to_rsx_attribute_match {
|
||||||
|
(
|
||||||
|
$attr:ident $fil:ident $name:literal
|
||||||
|
) => {
|
||||||
|
if $attr == $name {
|
||||||
|
return Some(stringify!($fil));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$attr:ident $fil:ident $_:tt
|
||||||
|
) => {
|
||||||
|
if $attr == stringify!($fil) {
|
||||||
|
return Some(stringify!($fil));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -180,14 +199,26 @@ macro_rules! impl_element_match {
|
|||||||
};
|
};
|
||||||
|
|
||||||
(
|
(
|
||||||
$el:ident $name:ident $namespace:tt {
|
$el:ident $name:ident $namespace:literal {
|
||||||
$(
|
$(
|
||||||
$fil:ident: $vil:ident $extra:tt,
|
$fil:ident: $vil:ident $extra:tt,
|
||||||
)*
|
)*
|
||||||
}
|
}
|
||||||
) => {
|
) => {
|
||||||
if $el == stringify!($name) {
|
if $el == stringify!($name) {
|
||||||
return Some((stringify!($name), Some(stringify!($namespace))));
|
return Some((stringify!($name), Some($namespace)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$el:ident $name:ident [$_:literal, $namespace:tt] {
|
||||||
|
$(
|
||||||
|
$fil:ident: $vil:ident $extra:tt,
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
if $el == stringify!($name) {
|
||||||
|
return Some((stringify!($name), Some($namespace)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -207,6 +238,8 @@ macro_rules! impl_element_match_attributes {
|
|||||||
$attr $fil: $vil ($extra),
|
$attr $fil: $vil ($extra),
|
||||||
);
|
);
|
||||||
)*
|
)*
|
||||||
|
|
||||||
|
return impl_map_global_attributes!($el $attr $name None);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -223,10 +256,41 @@ macro_rules! impl_element_match_attributes {
|
|||||||
$attr $fil: $vil ($extra),
|
$attr $fil: $vil ($extra),
|
||||||
);
|
);
|
||||||
)*
|
)*
|
||||||
|
|
||||||
|
return impl_map_global_attributes!($el $attr $name $namespace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "hot-reload-context")]
|
||||||
|
macro_rules! impl_map_global_attributes {
|
||||||
|
(
|
||||||
|
$el:ident $attr:ident $element:ident None
|
||||||
|
) => {
|
||||||
|
map_global_attributes($attr)
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$el:ident $attr:ident $element:ident $namespace:literal
|
||||||
|
) => {
|
||||||
|
if $namespace == "http://www.w3.org/2000/svg" {
|
||||||
|
map_svg_attributes($attr)
|
||||||
|
} else {
|
||||||
|
map_global_attributes($attr)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
(
|
||||||
|
$el:ident $attr:ident $element:ident [$name:literal, $namespace:tt]
|
||||||
|
) => {
|
||||||
|
if $namespace == "http://www.w3.org/2000/svg" {
|
||||||
|
map_svg_attributes($attr)
|
||||||
|
} else {
|
||||||
|
map_global_attributes($attr)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! builder_constructors {
|
macro_rules! builder_constructors {
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
@@ -254,7 +318,7 @@ macro_rules! builder_constructors {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
)*
|
)*
|
||||||
map_global_attributes(attribute).or_else(|| map_svg_attributes(attribute))
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn map_element(element: &str) -> Option<(&'static str, Option<&'static str>)> {
|
fn map_element(element: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||||
@@ -271,6 +335,38 @@ macro_rules! builder_constructors {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
pub fn map_html_attribute_to_rsx(html: &str) -> Option<&'static str> {
|
||||||
|
$(
|
||||||
|
$(
|
||||||
|
impl_html_to_rsx_attribute_match!(
|
||||||
|
html $fil $extra
|
||||||
|
);
|
||||||
|
)*
|
||||||
|
)*
|
||||||
|
|
||||||
|
if let Some(name) = crate::map_html_global_attributes_to_rsx(html) {
|
||||||
|
return Some(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(name) = crate::map_html_svg_attributes_to_rsx(html) {
|
||||||
|
return Some(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
pub fn map_html_element_to_rsx(html: &str) -> Option<&'static str> {
|
||||||
|
$(
|
||||||
|
if html == stringify!($name) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
$(
|
$(
|
||||||
impl_element!(
|
impl_element!(
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
@@ -782,6 +878,7 @@ builder_constructors! {
|
|||||||
decoding: ImageDecoding DEFAULT,
|
decoding: ImageDecoding DEFAULT,
|
||||||
height: usize DEFAULT,
|
height: usize DEFAULT,
|
||||||
ismap: Bool DEFAULT,
|
ismap: Bool DEFAULT,
|
||||||
|
loading: String DEFAULT,
|
||||||
src: Uri DEFAULT,
|
src: Uri DEFAULT,
|
||||||
srcset: String DEFAULT, // FIXME this is much more complicated
|
srcset: String DEFAULT, // FIXME this is much more complicated
|
||||||
usemap: String DEFAULT, // FIXME should be a fragment starting with '#'
|
usemap: String DEFAULT, // FIXME should be a fragment starting with '#'
|
||||||
@@ -952,9 +1049,8 @@ builder_constructors! {
|
|||||||
src: Uri DEFAULT,
|
src: Uri DEFAULT,
|
||||||
text: String DEFAULT,
|
text: String DEFAULT,
|
||||||
|
|
||||||
// r#async: Bool,
|
r#async: Bool "async",
|
||||||
// r#type: String, // TODO could be an enum
|
r#type: String "type", // TODO could be an enum
|
||||||
r#type: String "type",
|
|
||||||
r#script: String "script",
|
r#script: String "script",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -119,10 +119,7 @@ impl_event! {
|
|||||||
/// oncontextmenu
|
/// oncontextmenu
|
||||||
oncontextmenu
|
oncontextmenu
|
||||||
|
|
||||||
/// ondoubleclick
|
#[deprecated(since = "0.5.0", note = "use ondoubleclick instead")]
|
||||||
ondoubleclick
|
|
||||||
|
|
||||||
/// ondoubleclick
|
|
||||||
ondblclick
|
ondblclick
|
||||||
|
|
||||||
/// onmousedown
|
/// onmousedown
|
||||||
@@ -149,6 +146,22 @@ impl_event! {
|
|||||||
onmouseup
|
onmouseup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ondoubleclick
|
||||||
|
#[inline]
|
||||||
|
pub fn ondoubleclick<'a, E: crate::EventReturn<T>, T>(
|
||||||
|
_cx: &'a ::dioxus_core::ScopeState,
|
||||||
|
mut _f: impl FnMut(::dioxus_core::Event<MouseData>) -> E + 'a,
|
||||||
|
) -> ::dioxus_core::Attribute<'a> {
|
||||||
|
::dioxus_core::Attribute::new(
|
||||||
|
"ondblclick",
|
||||||
|
_cx.listener(move |e: ::dioxus_core::Event<MouseData>| {
|
||||||
|
_f(e).spawn(_cx);
|
||||||
|
}),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
impl MouseData {
|
impl MouseData {
|
||||||
/// Construct MouseData with the specified properties
|
/// Construct MouseData with the specified properties
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -33,12 +33,44 @@ macro_rules! trait_method_mapping {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
macro_rules! html_to_rsx_attribute_mapping {
|
||||||
|
(
|
||||||
|
$matching:ident;
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
$name:ident;
|
||||||
|
) => {
|
||||||
|
if $matching == stringify!($name) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(
|
||||||
|
$matching:ident;
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
$name:ident: $lit:literal;
|
||||||
|
) => {
|
||||||
|
if $matching == stringify!($lit) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(
|
||||||
|
$matching:ident;
|
||||||
|
$(#[$attr:meta])*
|
||||||
|
$name:ident: $lit:literal, $ns:literal;
|
||||||
|
) => {
|
||||||
|
if $matching == stringify!($lit) {
|
||||||
|
return Some(stringify!($name));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! trait_methods {
|
macro_rules! trait_methods {
|
||||||
(
|
(
|
||||||
@base
|
@base
|
||||||
$(#[$trait_attr:meta])*
|
$(#[$trait_attr:meta])*
|
||||||
$trait:ident;
|
$trait:ident;
|
||||||
$fn:ident;
|
$fn:ident;
|
||||||
|
$fn_html_to_rsx:ident;
|
||||||
$(
|
$(
|
||||||
$(#[$attr:meta])*
|
$(#[$attr:meta])*
|
||||||
$name:ident $(: $($arg:literal),*)*;
|
$name:ident $(: $($arg:literal),*)*;
|
||||||
@@ -62,6 +94,18 @@ macro_rules! trait_methods {
|
|||||||
)*
|
)*
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
#[doc = "Converts an HTML attribute to an RSX attribute"]
|
||||||
|
pub(crate) fn $fn_html_to_rsx(html: &str) -> Option<&'static str> {
|
||||||
|
$(
|
||||||
|
html_to_rsx_attribute_mapping! {
|
||||||
|
html;
|
||||||
|
$name$(: $($arg),*)*;
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Rename the incoming ident and apply a custom namespace
|
// Rename the incoming ident and apply a custom namespace
|
||||||
@@ -79,6 +123,7 @@ trait_methods! {
|
|||||||
|
|
||||||
GlobalAttributes;
|
GlobalAttributes;
|
||||||
map_global_attributes;
|
map_global_attributes;
|
||||||
|
map_html_global_attributes_to_rsx;
|
||||||
|
|
||||||
/// Prevent the default action for this element.
|
/// Prevent the default action for this element.
|
||||||
///
|
///
|
||||||
@@ -1593,6 +1638,7 @@ trait_methods! {
|
|||||||
@base
|
@base
|
||||||
SvgAttributes;
|
SvgAttributes;
|
||||||
map_svg_attributes;
|
map_svg_attributes;
|
||||||
|
map_html_svg_attributes_to_rsx;
|
||||||
|
|
||||||
/// Prevent the default action for this element.
|
/// Prevent the default action for this element.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
mod elements;
|
mod elements;
|
||||||
#[cfg(feature = "hot-reload-context")]
|
#[cfg(feature = "hot-reload-context")]
|
||||||
pub use elements::HtmlCtx;
|
pub use elements::HtmlCtx;
|
||||||
|
#[cfg(feature = "html-to-rsx")]
|
||||||
|
pub use elements::{map_html_attribute_to_rsx, map_html_element_to_rsx};
|
||||||
pub mod events;
|
pub mod events;
|
||||||
pub mod geometry;
|
pub mod geometry;
|
||||||
mod global_attributes;
|
mod global_attributes;
|
||||||
|
|||||||
@@ -34,10 +34,14 @@ warp = { version = "0.3.3", optional = true }
|
|||||||
axum = { version = "0.6.1", optional = true, features = ["ws"] }
|
axum = { version = "0.6.1", optional = true, features = ["ws"] }
|
||||||
|
|
||||||
# salvo
|
# salvo
|
||||||
salvo = { version = "0.44.1", optional = true, features = ["ws"] }
|
salvo = { version = "0.63.0", optional = true, features = ["websocket"] }
|
||||||
once_cell = "1.17.1"
|
once_cell = "1.17.1"
|
||||||
async-trait = "0.1.71"
|
async-trait = "0.1.71"
|
||||||
|
|
||||||
|
# rocket
|
||||||
|
rocket = { version = "0.5.0", optional = true }
|
||||||
|
rocket_ws = { version = "0.1.0", optional = true }
|
||||||
|
|
||||||
# actix is ... complicated?
|
# actix is ... complicated?
|
||||||
# actix-files = { version = "0.6.2", optional = true }
|
# actix-files = { version = "0.6.2", optional = true }
|
||||||
# actix-web = { version = "4.2.1", optional = true }
|
# actix-web = { version = "4.2.1", optional = true }
|
||||||
@@ -49,13 +53,16 @@ tokio = { workspace = true, features = ["full"] }
|
|||||||
dioxus = { workspace = true }
|
dioxus = { workspace = true }
|
||||||
warp = "0.3.3"
|
warp = "0.3.3"
|
||||||
axum = { version = "0.6.1", features = ["ws"] }
|
axum = { version = "0.6.1", features = ["ws"] }
|
||||||
salvo = { version = "0.44.1", features = ["affix", "ws"] }
|
salvo = { version = "0.63.0", features = ["affix", "websocket"] }
|
||||||
|
rocket = "0.5.0"
|
||||||
|
rocket_ws = "0.1.0"
|
||||||
tower = "0.4.13"
|
tower = "0.4.13"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["hot-reload"]
|
default = ["hot-reload"]
|
||||||
# actix = ["actix-files", "actix-web", "actix-ws"]
|
# actix = ["actix-files", "actix-web", "actix-ws"]
|
||||||
hot-reload = ["dioxus-hot-reload"]
|
hot-reload = ["dioxus-hot-reload"]
|
||||||
|
rocket = ["dep:rocket", "dep:rocket_ws"]
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
@@ -68,3 +75,7 @@ required-features = ["salvo"]
|
|||||||
[[example]]
|
[[example]]
|
||||||
name = "warp"
|
name = "warp"
|
||||||
required-features = ["warp"]
|
required-features = ["warp"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "rocket"
|
||||||
|
required-features = ["rocket"]
|
||||||
@@ -28,6 +28,7 @@ The current backend frameworks supported include:
|
|||||||
- Axum
|
- Axum
|
||||||
- Warp
|
- Warp
|
||||||
- Salvo
|
- Salvo
|
||||||
|
- Rocket
|
||||||
|
|
||||||
Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework.
|
Dioxus-LiveView exports some primitives to wire up an app into an existing backend framework.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_liveview::LiveViewPool;
|
||||||
|
use rocket::response::content::RawHtml;
|
||||||
|
use rocket::{Config, Rocket, State};
|
||||||
|
use rocket_ws::{Channel, WebSocket};
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
let mut num = use_state(cx, || 0);
|
||||||
|
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
"hello Rocket! {num}"
|
||||||
|
button { onclick: move |_| num += 1, "Increment" }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_page_with_glue(glue: &str) -> RawHtml<String> {
|
||||||
|
RawHtml(format!(
|
||||||
|
r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head> <title>Dioxus LiveView with Rocket</title> </head>
|
||||||
|
<body> <div id="main"></div> </body>
|
||||||
|
{glue}
|
||||||
|
</html>
|
||||||
|
"#,
|
||||||
|
glue = glue
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/")]
|
||||||
|
async fn index(config: &Config) -> RawHtml<String> {
|
||||||
|
index_page_with_glue(&dioxus_liveview::interpreter_glue(&format!(
|
||||||
|
"ws://{addr}:{port}/ws",
|
||||||
|
addr = config.address,
|
||||||
|
port = config.port,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/as-path")]
|
||||||
|
async fn as_path() -> RawHtml<String> {
|
||||||
|
index_page_with_glue(&dioxus_liveview::interpreter_glue("/ws"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/ws")]
|
||||||
|
fn ws(ws: WebSocket, pool: &State<LiveViewPool>) -> Channel<'static> {
|
||||||
|
let pool = pool.inner().to_owned();
|
||||||
|
|
||||||
|
ws.channel(move |stream| {
|
||||||
|
Box::pin(async move {
|
||||||
|
let _ = pool
|
||||||
|
.launch(dioxus_liveview::rocket_socket(stream), app)
|
||||||
|
.await;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let view = dioxus_liveview::LiveViewPool::new();
|
||||||
|
|
||||||
|
Rocket::build()
|
||||||
|
.manage(view)
|
||||||
|
.mount("/", routes![index, as_path, ws])
|
||||||
|
.ignite()
|
||||||
|
.await
|
||||||
|
.expect("Failed to ignite rocket")
|
||||||
|
.launch()
|
||||||
|
.await
|
||||||
|
.expect("Failed to launch rocket");
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
use crate::{LiveViewError, LiveViewSocket};
|
||||||
|
use rocket::futures::{SinkExt, StreamExt};
|
||||||
|
use rocket_ws::{result::Error, stream::DuplexStream, Message};
|
||||||
|
|
||||||
|
/// Convert a rocket websocket into a LiveViewSocket
|
||||||
|
///
|
||||||
|
/// This is required to launch a LiveView app using the rocket web framework
|
||||||
|
pub fn rocket_socket(stream: DuplexStream) -> impl LiveViewSocket {
|
||||||
|
stream
|
||||||
|
.map(transform_rx)
|
||||||
|
.with(transform_tx)
|
||||||
|
.sink_map_err(|_| LiveViewError::SendingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn transform_rx(message: Result<Message, Error>) -> Result<Vec<u8>, LiveViewError> {
|
||||||
|
message
|
||||||
|
.map_err(|_| LiveViewError::SendingFailed)?
|
||||||
|
.into_text()
|
||||||
|
.map(|s| s.into_bytes())
|
||||||
|
.map_err(|_| LiveViewError::SendingFailed)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn transform_tx(message: Vec<u8>) -> Result<Message, Error> {
|
||||||
|
Ok(Message::Text(String::from_utf8_lossy(&message).to_string()))
|
||||||
|
}
|
||||||
@@ -18,6 +18,11 @@ pub mod adapters {
|
|||||||
|
|
||||||
#[cfg(feature = "salvo")]
|
#[cfg(feature = "salvo")]
|
||||||
pub use salvo_adapter::*;
|
pub use salvo_adapter::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "rocket")]
|
||||||
|
pub mod rocket_adapter;
|
||||||
|
#[cfg(feature = "rocket")]
|
||||||
|
pub use rocket_adapter::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use adapters::*;
|
pub use adapters::*;
|
||||||
|
|||||||
@@ -57,7 +57,12 @@ impl DioxusState {
|
|||||||
node.insert(ElementIdComponent(element_id));
|
node.insert(ElementIdComponent(element_id));
|
||||||
if self.node_id_mapping.len() <= element_id.0 {
|
if self.node_id_mapping.len() <= element_id.0 {
|
||||||
self.node_id_mapping.resize(element_id.0 + 1, None);
|
self.node_id_mapping.resize(element_id.0 + 1, None);
|
||||||
|
} else if let Some(mut node) =
|
||||||
|
self.node_id_mapping[element_id.0].and_then(|id| node.real_dom_mut().get_mut(id))
|
||||||
|
{
|
||||||
|
node.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.node_id_mapping[element_id.0] = Some(node_id);
|
self.node_id_mapping[element_id.0] = Some(node_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ impl State for TaffyLayout {
|
|||||||
attribute, value, ..
|
attribute, value, ..
|
||||||
} in attributes
|
} in attributes
|
||||||
{
|
{
|
||||||
if let Some(text) = value.as_text() {
|
if value.as_custom().is_none() {
|
||||||
apply_layout_attributes_cfg(
|
apply_layout_attributes_cfg(
|
||||||
&attribute.name,
|
&attribute.name,
|
||||||
text,
|
&value.to_string(),
|
||||||
&mut style,
|
&mut style,
|
||||||
&LayoutConfigeration {
|
&LayoutConfigeration {
|
||||||
border_widths: BorderWidths {
|
border_widths: BorderWidths {
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ impl Redirect {
|
|||||||
|
|
||||||
let (segments, query) = parse_route_segments(
|
let (segments, query) = parse_route_segments(
|
||||||
path.span(),
|
path.span(),
|
||||||
|
#[allow(clippy::map_identity)]
|
||||||
closure_arguments.iter().map(|(name, ty)| (name, ty)),
|
closure_arguments.iter().map(|(name, ty)| (name, ty)),
|
||||||
&path.value(),
|
&path.value(),
|
||||||
)?;
|
)?;
|
||||||
|
|||||||
Loaded 100 of 128 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user