From a4f280d16399205c638033bf9beb858e478e98ff Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Dec 2021 23:20:01 -0500 Subject: [PATCH] feat: more API updates --- README.md | 14 +-- packages/core-macro/examples/inline_props.rs | 10 +- packages/core-macro/src/inlineprops.rs | 8 +- packages/core-macro/src/lib.rs | 2 +- packages/core/src/scopes.rs | 8 +- packages/core/tests/sharedstate.rs | 4 +- packages/desktop/examples/async.rs | 29 ++--- packages/hooks/src/use_shared_state.rs | 8 +- packages/liveview/src/lib.rs | 1 + packages/router/examples/simple.rs | 18 +-- packages/router/src/lib.rs | 118 ++++++++----------- packages/router/src/link.rs | 48 ++++++++ packages/router/src/platform.rs | 6 + packages/router/src/service.rs | 27 +++++ packages/router/src/userouter.rs | 36 ++++++ packages/web/examples/js_bench.rs | 2 +- packages/web/src/dom.rs | 77 ++---------- 17 files changed, 227 insertions(+), 189 deletions(-) create mode 100644 packages/router/src/link.rs create mode 100644 packages/router/src/userouter.rs diff --git a/README.md b/README.md index 3f5b5d55c..1ac03771b 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust. ```rust -fn app(cx: Scope<()>) -> Element { +fn app(cx: Scope) -> Element { let mut count = use_state(&cx, || 0); cx.render(rsx!( @@ -85,18 +85,6 @@ If you know React, then you already know Dioxus. -Available cargo features: -- default: core (macro, hooks, html) -- macro: support for `Props` and `rsx` macros -- hooks: foundational hooks like `use_state`, `use_ref`, etc. -- html: the entire namespace of `html` elements, their listeners, and attributes -- router: a cross-platform (web and desktop) solution for global app routing -- liveview: a threadpool to spawn new VirtualDoms and their handles in -- ssr: render the virtualdom to a string -- web: render the your app on the web -- desktop: render your app locally rendered with webview -- mobile: render your app on your device rendered with webview - ## Examples: | File Navigator (Desktop) | Bluetooth scanner (Desktop) | TodoMVC (All platforms) | Tailwind (Liveview) | diff --git a/packages/core-macro/examples/inline_props.rs b/packages/core-macro/examples/inline_props.rs index 5be5f575f..28602ac30 100644 --- a/packages/core-macro/examples/inline_props.rs +++ b/packages/core-macro/examples/inline_props.rs @@ -4,19 +4,19 @@ fn main() {} type Element<'a> = (); -pub struct Scope<'a, T> { +pub struct Scope<'a, T = ()> { props: &'a T, } -#[inline_props] -pub fn component( - cx: Scope, +// #[inline_props] +pub fn component<'a>( + cx: Scope<'a>, chkk: String, chkk2: String, r: u32, cat: &'a str, drd: String, e: String, -) -> Element { +) -> Element<'a> { let r = chkk.len(); } diff --git a/packages/core-macro/src/inlineprops.rs b/packages/core-macro/src/inlineprops.rs index 9b6efbf36..64dab1ee3 100644 --- a/packages/core-macro/src/inlineprops.rs +++ b/packages/core-macro/src/inlineprops.rs @@ -92,13 +92,19 @@ impl ToTokens for InlinePropsBody { quote! { #[derive(Props)] } }; + let lifetime = if generics.params.is_empty() { + quote! {} + } else { + quote! { 'a, } + }; + out_tokens.append_all(quote! { #modifiers #vis struct #struct_name #generics { #(#fields),* } - #vis fn #ident #generics (#cx_token: Scope<'a, #struct_name #generics>) #output { + #vis fn #ident #generics (#cx_token: Scope<#lifetime #struct_name #generics>) #output { let #struct_name { #(#field_names),* } = &cx.props; #block } diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 85a2fa602..cacdcdf3e 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -226,7 +226,7 @@ pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStrea /// # Example /// ``` /// #[inline_props] -/// fn app(cx: Scope<{ bob: String }>) -> Element { +/// fn app(cx: Scope, bob: String) -> Element { /// cx.render(rsx!("hello, {bob}")) /// } /// diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 67d2dba5a..a7528b3ed 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -609,7 +609,7 @@ impl ScopeState { /// /// This is a "fundamental" operation and should only be called during initialization of a hook. /// - /// For a hook that provides the same functionality, use `use_provide_state` and `use_consume_state` instead. + /// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead. /// /// When the component is dropped, so is the context. Be aware of this behavior when consuming /// the context via Rc/Weak. @@ -620,7 +620,7 @@ impl ScopeState { /// struct SharedState(&'static str); /// /// static App: Component<()> = |cx, props|{ - /// cx.use_hook(|_| cx.provide_state(SharedState("world")), |_| {}, |_| {}); + /// cx.use_hook(|_| cx.provide_context(SharedState("world")), |_| {}, |_| {}); /// rsx!(cx, Child {}) /// } /// @@ -629,7 +629,7 @@ impl ScopeState { /// rsx!(cx, div { "hello {state.0}" }) /// } /// ``` - pub fn provide_state(&self, value: T) { + pub fn provide_context(&self, value: T) { self.shared_contexts .borrow_mut() .insert(TypeId::of::(), Rc::new(value)) @@ -638,7 +638,7 @@ impl ScopeState { } /// Try to retrieve a SharedState with type T from the any parent Scope. - pub fn consume_state(&self) -> Option> { + pub fn consume_context(&self) -> Option> { if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::()) { Some(shared.clone().downcast::().unwrap()) } else { diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 846b9d7f1..51b93a0e5 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -14,12 +14,12 @@ fn shared_state_test() { struct MySharedState(&'static str); static App: Component<()> = |cx| { - cx.provide_state(MySharedState("world!")); + cx.provide_context(MySharedState("world!")); cx.render(rsx!(Child {})) }; static Child: Component<()> = |cx| { - let shared = cx.consume_state::()?; + let shared = cx.consume_context::()?; cx.render(rsx!("Hello, {shared.0}")) }; diff --git a/packages/desktop/examples/async.rs b/packages/desktop/examples/async.rs index 81b464264..2c6901f85 100644 --- a/packages/desktop/examples/async.rs +++ b/packages/desktop/examples/async.rs @@ -1,29 +1,30 @@ -//! Example: README.md showcase -//! -//! The example from the README.md. - -use std::time::Duration; - use dioxus::prelude::*; use dioxus_core as dioxus; use dioxus_core_macro::*; use dioxus_hooks::*; use dioxus_html as dioxus_elements; +use std::time::Duration; fn main() { - // simple_logger::init().unwrap(); dioxus_desktop::launch(app); } fn app(cx: Scope<()>) -> Element { - let mut count = use_state(&cx, || 0); - log::debug!("count is {:?}", count); + let count = use_state(&cx, || 0); - cx.push_future(|| async move { - tokio::time::sleep(Duration::from_millis(1000)).await; - println!("count is now {:?}", count); - count += 1; - }); + // push the futureo on initialization + cx.use_hook( + |_| { + cx.push_future({ + let count = count.for_async(); + async move { + tokio::time::sleep(Duration::from_millis(1000)).await; + *count.get_mut() += 1; + } + }); + }, + |_| {}, + ); cx.render(rsx! { div { diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index c051bdb92..82bf2bda4 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -59,11 +59,11 @@ impl ProvidedStateInner { /// /// /// -pub fn use_shared_state<'a, T: 'static>(cx: &'a ScopeState) -> Option> { +pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option> { cx.use_hook( |_| { let scope_id = cx.scope_id(); - let root = cx.consume_state::>(); + let root = cx.consume_context::>(); if let Some(root) = root.as_ref() { root.borrow_mut().consumers.insert(scope_id); @@ -175,7 +175,7 @@ where /// /// /// -pub fn use_provide_state<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) { +pub fn use_context_provider<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) { cx.use_hook( |_| { let state: ProvidedState = RefCell::new(ProvidedStateInner { @@ -183,7 +183,7 @@ pub fn use_provide_state<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> notify_any: cx.schedule_update_any(), consumers: HashSet::new(), }); - cx.provide_state(state) + cx.provide_context(state) }, |_inner| {}, ) diff --git a/packages/liveview/src/lib.rs b/packages/liveview/src/lib.rs index e69de29bb..8b1378917 100644 --- a/packages/liveview/src/lib.rs +++ b/packages/liveview/src/lib.rs @@ -0,0 +1 @@ + diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs index 5f8105223..eb5e7477f 100644 --- a/packages/router/examples/simple.rs +++ b/packages/router/examples/simple.rs @@ -2,6 +2,7 @@ use dioxus_core::prelude::*; use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use dioxus_router::*; +use serde::{Deserialize, Serialize}; fn main() { console_error_panic_hook::set_once(); @@ -10,18 +11,19 @@ fn main() { } static APP: Component<()> = |cx| { - #[derive(Clone, Debug, PartialEq)] + #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] enum Route { Home, About, NotFound, } + impl Default for Route { + fn default() -> Self { + Route::Home + } + } - let route = use_router(&cx, |s| match s { - "/" => Route::Home, - "/about" => Route::About, - _ => Route::NotFound, - }); + let route = use_router(&cx, |c| {}); cx.render(rsx! { div { @@ -31,8 +33,8 @@ static APP: Component<()> = |cx| { Route::NotFound => rsx!(h1 { "NotFound" }), }} nav { - Link { to: Route::Home, href: |_| "/".to_string() } - Link { to: Route::About, href: |_| "/about".to_string() } + Link { to: Route::Home, href: "/" } + Link { to: Route::About, href: "/about" } } } }) diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 1a61b5deb..da7feb05e 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -1,72 +1,56 @@ +//! Dioxus-Router +//! +//! A simple match-based router and router service for most routing needs. +//! +//! Dioxus-Router is not a *declarative* router. Instead it uses a simple parse-match +//! pattern which can be derived via a macro. +//! +//! ```rust +//! fn app(cx: Scope) -> Element { +//! let route = use_router(&cx, |svc, path| { +//! match path { +//! "/about" => Route::About, +//! _ => Route::Home, +//! } +//! }); +//! +//! match route { +//! Route::Home => rsx!(cx, h1 { "Home" }), +//! Route::About => rsx!(cx, h1 { "About" }), +//! } +//! } +//! +//! +//! +//! +//! +//! ``` +//! +//! +//! +//! +//! +//! +//! +//! +//! + +mod link; +mod platform; +mod service; +mod userouter; mod utils; -use std::{cell::RefCell, rc::Rc}; +pub use link::*; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +pub use service::*; +pub use userouter::*; -use dioxus::Attribute; -use dioxus_core as dioxus; - -use dioxus_core::prelude::*; -use dioxus_core_macro::{format_args_f, rsx, Props}; -use dioxus_html as dioxus_elements; -// use wasm_bindgen::{JsCast, JsValue}; - -use crate::utils::strip_slash_suffix; - -/// Initialize the app's router service and provide access to `Link` components -pub fn use_router(cx: &ScopeState, f: impl Fn(&str) -> R) -> &R { - let r = f("/"); - cx.use_hook( - |_| { - // - r - }, - |f| f, - ) +pub trait Routable: + 'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default +{ } - -pub trait Routable: 'static + Send + Clone + PartialEq {} -impl Routable for T where T: 'static + Send + Clone + PartialEq {} - -#[derive(Props)] -pub struct LinkProps<'a, R: Routable> { - to: R, - - /// The url that gets pushed to the history stack - /// - /// You can either put it your own inline method or just autoderive the route using `derive(Routable)` - /// - /// ```rust - /// - /// Link { to: Route::Home, href: |_| "home".to_string() } - /// - /// // or - /// - /// Link { to: Route::Home, href: Route::as_url } - /// - /// ``` - #[props(default, setter(strip_option))] - href: Option<&'a str>, - - #[props(default, setter(strip_option))] - class: Option<&'a str>, - - children: Element<'a>, - - #[props(default)] - attributes: Option<&'a [Attribute<'a>]>, -} - -pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element { - // let service = todo!(); - // let service: todo!() = use_router_service::(&cx)?; - let class = cx.props.class.unwrap_or(""); - cx.render(rsx! { - a { - href: "#", - class: "{class}", - {&cx.props.children} - // onclick: move |_| service.push_route(cx.props.to.clone()), - // href: format_args!("{}", (cx.props.href)(&cx.props.to)), - } - }) +impl Routable for T where + T: 'static + Send + Clone + PartialEq + Serialize + DeserializeOwned + Default +{ } diff --git a/packages/router/src/link.rs b/packages/router/src/link.rs new file mode 100644 index 000000000..818d2d283 --- /dev/null +++ b/packages/router/src/link.rs @@ -0,0 +1,48 @@ +use crate::{Routable, RouterService}; +use dioxus::Attribute; +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_core_macro::{format_args_f, rsx, Props}; +use dioxus_html as dioxus_elements; + +#[derive(Props)] +pub struct LinkProps<'a, R: Routable> { + to: R, + + /// The url that gets pushed to the history stack + /// + /// You can either put it your own inline method or just autoderive the route using `derive(Routable)` + /// + /// ```rust + /// + /// Link { to: Route::Home, href: |_| "home".to_string() } + /// + /// // or + /// + /// Link { to: Route::Home, href: Route::as_url } + /// + /// ``` + #[props(default, setter(strip_option))] + href: Option<&'a str>, + + #[props(default, setter(strip_option))] + class: Option<&'a str>, + + children: Element<'a>, + + #[props(default)] + attributes: Option<&'a [Attribute<'a>]>, +} + +#[allow(non_snake_case)] +pub fn Link<'a, R: Routable>(cx: Scope<'a, LinkProps<'a, R>>) -> Element { + let service = cx.consume_context::>()?; + cx.render(rsx! { + a { + href: "#", + class: format_args!("{}", cx.props.class.unwrap_or("")), + {&cx.props.children} + onclick: move |_| service.push_route(cx.props.to.clone()), + } + }) +} diff --git a/packages/router/src/platform.rs b/packages/router/src/platform.rs index 8b1378917..55cdceb83 100644 --- a/packages/router/src/platform.rs +++ b/packages/router/src/platform.rs @@ -1 +1,7 @@ +pub fn parse_current_route() -> String { + todo!() +} +pub(crate) fn get_base_route() -> String { + todo!() +} diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index e69de29bb..e70aaf410 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -0,0 +1,27 @@ +use crate::Routable; +use std::{cell::RefCell, rc::Rc}; + +pub struct RouterService { + pub(crate) regen_route: Rc, + pub(crate) pending_routes: RefCell>, +} + +impl RouterService { + pub fn current_path(&self) -> &str { + todo!() + } + pub fn push_route(&self, route: R) { + self.pending_routes.borrow_mut().push(route); + (self.regen_route)(); + } +} + +pub struct RouterCfg { + initial_route: String, +} + +impl RouterCfg { + pub fn new(initial_route: String) -> Self { + Self { initial_route } + } +} diff --git a/packages/router/src/userouter.rs b/packages/router/src/userouter.rs new file mode 100644 index 000000000..fad22a335 --- /dev/null +++ b/packages/router/src/userouter.rs @@ -0,0 +1,36 @@ +use std::{cell::RefCell, rc::Rc}; + +use crate::{Routable, RouterCfg, RouterService}; +use dioxus_core::ScopeState; + +/// Initialize the app's router service and provide access to `Link` components +pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, cfg: impl FnOnce(&mut RouterCfg)) -> &'a R { + cx.use_hook( + |_| { + let svc: RouterService = RouterService { + regen_route: cx.schedule_update(), + pending_routes: RefCell::new(Vec::new()), + }; + let first_path = R::default(); + cx.provide_context(svc); + UseRouterInner { + svc: cx.consume_context::>().unwrap(), + history: vec![first_path], + } + }, + |f| { + let mut pending_routes = f.svc.pending_routes.borrow_mut(); + + for route in pending_routes.drain(..) { + f.history.push(route); + } + + f.history.last().unwrap() + }, + ) +} + +struct UseRouterInner { + svc: Rc>, + history: Vec, +} diff --git a/packages/web/examples/js_bench.rs b/packages/web/examples/js_bench.rs index cc7304b40..0295ef983 100644 --- a/packages/web/examples/js_bench.rs +++ b/packages/web/examples/js_bench.rs @@ -144,7 +144,7 @@ static App: Component<()> = |cx| { {items.read().iter().enumerate().map(|(id, item)| { let [adj, col, noun] = item.labels; let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; - rsx!(tr { + rsx!(tr { class: "{is_in_danger}", key: "{id}", td { class:"col-md-1" } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 40568807b..9ac102fe9 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -246,7 +246,6 @@ impl WebsysDom { .unwrap(), }; - use smallstr; use smallstr::SmallString; use std::fmt::Write; @@ -260,7 +259,7 @@ impl WebsysDom { self.nodes[(id as usize)] = Some(el); } - fn new_event_listener(&mut self, event: &'static str, scope: ScopeId, real_id: u64) { + fn new_event_listener(&mut self, event: &'static str, _scope: ScopeId, _real_id: u64) { let event = wasm_bindgen::intern(event); // attach the correct attributes to the element @@ -271,24 +270,9 @@ impl WebsysDom { let el = self.stack.top(); let el = el.dyn_ref::().unwrap(); - // let el = el.dyn_ref::().unwrap(); - // .expect(&format!("not an element: {:?}", el)); - - // let scope_id = scope.data().as_ffi(); - // let scope_id = scope.0 as u64; - // "dioxus-event-click", - // "1.10" - // &format!("", scope_id, real_id), - // &format!("dioxus-event-{}", event), - // &format!("{}.{}", scope_id, real_id), - // &format!("dioxus-event-{}", event), - // &format!("{}.{}", scope_id, real_id), el.set_attribute("dioxus-event", event).unwrap(); - // el.set_attribute(&format!("dioxus-event"), &format!("{}", event)) - // .unwrap(); - // Register the callback to decode if let Some(entry) = self.listeners.get_mut(event) { @@ -487,20 +471,17 @@ impl Stack { } pub struct DioxusWebsysEvent(web_sys::Event); + +// safety: currently the web is not multithreaded and our VirtualDom exists on the same thread unsafe impl Send for DioxusWebsysEvent {} unsafe impl Sync for DioxusWebsysEvent {} -// trait MyTrait {} -// impl MyTrait for web_sys::Event {} - // todo: some of these events are being casted to the wrong event type. // We need tests that simulate clicks/etc and make sure every event type works. fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { use dioxus_html::on::*; use dioxus_html::KeyCode; - // event.prevent_default(); - // use dioxus_core::events::on::*; match event.type_().as_str() { "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}), "compositionend" | "compositionstart" | "compositionupdate" => { @@ -525,11 +506,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { - // - Arc::new(FocusEvent {}) - } - // "change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))), + "focus" | "blur" => Arc::new(FocusEvent {}), // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy // don't have a good solution with the serialized event problem @@ -623,7 +600,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc Arc::new(SelectionEvent {}), - "touchcancel" | "touchend" | "touchmove" | "touchstart" => { let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap(); Arc::new(TouchEvent { @@ -633,9 +609,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc Arc::new(()), - "wheel" => { let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap(); Arc::new(WheelEvent { @@ -645,7 +619,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap(); Arc::new(AnimationEvent { @@ -654,7 +627,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap(); Arc::new(TransitionEvent { @@ -663,20 +635,11 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { - // - Arc::new(MediaEvent {}) - } - - "toggle" => { - // - Arc::new(ToggleEvent {}) - } - + | "timeupdate" | "volumechange" | "waiting" => Arc::new(MediaEvent {}), + "toggle" => Arc::new(ToggleEvent {}), _ => Arc::new(()), } } @@ -684,6 +647,8 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc anyhow::Result { + use anyhow::Context; + let target = event .target() .expect("missing target") @@ -692,42 +657,16 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { let typ = event.type_(); - use anyhow::Context; - let element_id = target .get_attribute("dioxus-id") .context("Could not find element id on event target")? .parse()?; - // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system - let val: String = target - .get_attribute("dioxus-event") - .context(format!("wrong format - received {:#?}", typ))?; - // .get_attribute(&format!("dioxus-event-{}", typ)) - // .context(format!("wrong format - received {:#?}", typ))?; - - let mut fields = val.splitn(3, "."); - - // let gi_id = fields - // .next() - // .and_then(|f| f.parse::().ok()) - // .context("failed to parse gi id")?; - - // let real_id = fields - // .next() - // .and_then(|raw_id| raw_id.parse::().ok()) - // .context("failed to parse real id")?; - - // let triggered_scope = gi_id; - Ok(UserEvent { name: event_name_from_typ(&typ), data: virtual_event_from_websys_event(event.clone()), element: Some(ElementId(element_id)), scope_id: None, - // scope_id: Some(ScopeId(triggered_scope as usize)), - // element: Some(ElementId(real_id as usize)), - // scope_id: Some(ScopeId(triggered_scope as usize)), priority: dioxus_core::EventPriority::Medium, }) }