mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Call handlers from context of a runtime and scope
This commit is contained in:
parent
bc857bf339
commit
8323e45970
10 changed files with 270 additions and 282 deletions
|
@ -31,28 +31,33 @@ fn main() {
|
|||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
use_asset_handler(cx, move |request: &AssetRequest| {
|
||||
let request = request.clone();
|
||||
async move {
|
||||
use_asset_handler(cx, "videos", move |request, responder| {
|
||||
// Using dioxus::spawn works, but is slower than a dedicated thread
|
||||
tokio::task::spawn(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
|
||||
}
|
||||
|
||||
match get_stream_response(&mut file, &request).await {
|
||||
Ok(response) => responder.respond(response),
|
||||
Err(err) => eprintln!("Error: {}", err),
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
render! {
|
||||
div { video { src: "test_video.mp4", autoplay: true, controls: true, width: 640, height: 480 } }
|
||||
div {
|
||||
video {
|
||||
src: "/videos/test_video.mp4",
|
||||
autoplay: true,
|
||||
controls: true,
|
||||
width: 640,
|
||||
height: 480
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This was taken from wry's example
|
||||
async fn get_stream_response(
|
||||
asset: &mut (impl tokio::io::AsyncSeek + tokio::io::AsyncRead + Unpin + Send + Sync),
|
||||
request: &AssetRequest,
|
||||
|
|
|
@ -87,6 +87,16 @@ impl Runtime {
|
|||
self.scope_stack.borrow().last().copied()
|
||||
}
|
||||
|
||||
/// Call this function with the current scope set to the given scope
|
||||
///
|
||||
/// Useful in a limited number of scenarios, not public.
|
||||
pub(crate) fn with_scope<O>(&self, id: ScopeId, f: impl FnOnce() -> O) -> O {
|
||||
self.scope_stack.borrow_mut().push(id);
|
||||
let o = f();
|
||||
self.scope_stack.borrow_mut().pop();
|
||||
o
|
||||
}
|
||||
|
||||
/// Get the context for any scope given its ID
|
||||
///
|
||||
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
|
||||
|
@ -137,6 +147,17 @@ impl RuntimeGuard {
|
|||
push_runtime(runtime.clone());
|
||||
Self(runtime)
|
||||
}
|
||||
|
||||
/// Run a function with a given runtime and scope in context
|
||||
pub fn with<O>(runtime: Rc<Runtime>, scope: Option<ScopeId>, f: impl FnOnce() -> O) -> O {
|
||||
let guard = Self::new(runtime.clone());
|
||||
let o = match scope {
|
||||
Some(scope) => Runtime::with_scope(&runtime, scope, f),
|
||||
None => f(),
|
||||
};
|
||||
drop(guard);
|
||||
o
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RuntimeGuard {
|
||||
|
|
|
@ -318,11 +318,16 @@ pub fn spawn(fut: impl Future<Output = ()> + 'static) {
|
|||
with_current_scope(|cx| cx.spawn(fut));
|
||||
}
|
||||
|
||||
/// Spawn a future on a component given its [`ScopeId`].
|
||||
pub fn spawn_at(fut: impl Future<Output = ()> + 'static, scope_id: ScopeId) -> Option<TaskId> {
|
||||
with_runtime(|rt| rt.get_context(scope_id).unwrap().push_future(fut))
|
||||
}
|
||||
|
||||
/// Spawn a future that Dioxus won't clean up when this component is unmounted
|
||||
///
|
||||
/// This is good for tasks that need to be run after the component has been dropped.
|
||||
pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
|
||||
with_current_scope(|cx| cx.spawn_forever(fut))
|
||||
spawn_at(fut, ScopeId(0))
|
||||
}
|
||||
|
||||
/// Informs the scheduler that this task is no longer needed and should be removed.
|
||||
|
|
|
@ -320,6 +320,7 @@ impl<P: 'static> App<P> {
|
|||
let Some(view) = self.webviews.get_mut(&id) else {
|
||||
return;
|
||||
};
|
||||
println!("poll_vdom");
|
||||
|
||||
view.poll_vdom();
|
||||
}
|
||||
|
|
|
@ -1,135 +1,61 @@
|
|||
use crate::DesktopContext;
|
||||
use slab::Slab;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
future::Future,
|
||||
ops::Deref,
|
||||
path::{Path, PathBuf},
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::{
|
||||
runtime::Handle,
|
||||
sync::{OnceCell, RwLock},
|
||||
};
|
||||
use wry::http::{Request, Response};
|
||||
use dioxus_core::prelude::{Runtime, RuntimeGuard, ScopeId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use wry::{http::Request, RequestAsyncResponder};
|
||||
|
||||
/// An arbitrary asset is an HTTP response containing a binary body.
|
||||
pub type AssetResponse = Response<Cow<'static, [u8]>>;
|
||||
///
|
||||
pub type AssetRequest = Request<Vec<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 {
|
||||
pub(crate) path: PathBuf,
|
||||
pub(crate) request: Arc<Request<Vec<u8>>>,
|
||||
pub struct AssetHandler {
|
||||
f: Box<dyn Fn(AssetRequest, RequestAsyncResponder) + 'static>,
|
||||
scope: ScopeId,
|
||||
}
|
||||
|
||||
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 UserAssetHandler =
|
||||
Box<dyn Fn(&AssetRequest) -> Pin<Box<dyn AssetFuture>> + Send + Sync + 'static>;
|
||||
|
||||
type AssetHandlerRegistryInner = Slab<UserAssetHandler>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AssetHandlerRegistry(Arc<RwLock<AssetHandlerRegistryInner>>);
|
||||
pub struct AssetHandlerRegistry {
|
||||
dom_rt: Rc<Runtime>,
|
||||
handlers: Rc<RefCell<FxHashMap<String, AssetHandler>>>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
pub fn new(dom_rt: Rc<Runtime>) -> Self {
|
||||
AssetHandlerRegistry {
|
||||
dom_rt,
|
||||
handlers: Default::default(),
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle to a registered asset handler.
|
||||
pub struct AssetHandlerHandle {
|
||||
pub(crate) desktop: DesktopContext,
|
||||
pub(crate) 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;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn has_handler(&self, name: &str) -> bool {
|
||||
self.handlers.borrow().contains_key(name)
|
||||
}
|
||||
|
||||
pub fn handle_request(
|
||||
&self,
|
||||
name: &str,
|
||||
request: AssetRequest,
|
||||
responder: RequestAsyncResponder,
|
||||
) {
|
||||
if let Some(handler) = self.handlers.borrow().get(name) {
|
||||
// make sure the runtime is alive for the duration of the handler
|
||||
// We should do this for all the things - not just asset handlers
|
||||
RuntimeGuard::with(self.dom_rt.clone(), Some(handler.scope), || {
|
||||
(handler.f)(request, responder)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_handler(
|
||||
&self,
|
||||
name: String,
|
||||
f: Box<dyn Fn(AssetRequest, RequestAsyncResponder) + 'static>,
|
||||
scope: ScopeId,
|
||||
) {
|
||||
self.handlers
|
||||
.borrow_mut()
|
||||
.insert(name, AssetHandler { f, scope });
|
||||
}
|
||||
|
||||
pub fn remove_handler(&self, name: &str) -> Option<AssetHandler> {
|
||||
self.handlers.borrow_mut().remove(name)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
use crate::{
|
||||
app::SharedContext,
|
||||
assets::{AssetFuture, AssetHandlerRegistry},
|
||||
assets::AssetHandlerRegistry,
|
||||
edits::EditQueue,
|
||||
ipc::{EventData, UserWindowEvent},
|
||||
query::QueryEngine,
|
||||
shortcut::{HotKey, ShortcutId, ShortcutRegistryError},
|
||||
webview::WebviewInstance,
|
||||
AssetHandler, Config,
|
||||
AssetRequest, Config,
|
||||
};
|
||||
use dioxus_core::{
|
||||
prelude::{current_scope_id, ScopeId},
|
||||
Mutations, VirtualDom,
|
||||
};
|
||||
use dioxus_core::{Mutations, VirtualDom};
|
||||
use dioxus_interpreter_js::binary_protocol::Channel;
|
||||
use rustc_hash::FxHashMap;
|
||||
use slab::Slab;
|
||||
|
@ -18,7 +21,7 @@ use tao::{
|
|||
event_loop::EventLoopWindowTarget,
|
||||
window::{Fullscreen as WryFullscreen, Window, WindowId},
|
||||
};
|
||||
use wry::WebView;
|
||||
use wry::{RequestAsyncResponder, WebView};
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
use tao::platform::ios::WindowExtIOS;
|
||||
|
@ -244,17 +247,30 @@ impl DesktopService {
|
|||
}
|
||||
|
||||
/// Provide a callback to handle asset loading yourself.
|
||||
/// If the ScopeId isn't provided, defaults to a global handler.
|
||||
/// Note that the handler is namespaced by name, not ScopeId.
|
||||
///
|
||||
/// When the component is dropped, the handler is removed.
|
||||
///
|
||||
/// 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
|
||||
pub fn register_asset_handler(
|
||||
&self,
|
||||
name: String,
|
||||
f: Box<dyn Fn(AssetRequest, RequestAsyncResponder) + 'static>,
|
||||
scope: Option<ScopeId>,
|
||||
) {
|
||||
self.asset_handlers.register_handler(
|
||||
name,
|
||||
f,
|
||||
scope.unwrap_or(current_scope_id().unwrap_or(ScopeId(0))),
|
||||
)
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub fn remove_asset_handler(&self, name: &str) -> Option<()> {
|
||||
self.asset_handlers.remove_handler(name).map(|_| ())
|
||||
}
|
||||
|
||||
/// Push an objc view to the window
|
||||
|
|
|
@ -3,8 +3,8 @@ use crate::{
|
|||
ShortcutHandle, ShortcutRegistryError, WryEventHandler,
|
||||
};
|
||||
use dioxus_core::ScopeState;
|
||||
use std::rc::Rc;
|
||||
use tao::{event::Event, event_loop::EventLoopWindowTarget};
|
||||
use wry::RequestAsyncResponder;
|
||||
|
||||
/// Get an imperative handle to the current window
|
||||
pub fn use_window(cx: &ScopeState) -> &DesktopContext {
|
||||
|
@ -34,24 +34,28 @@ pub fn use_wry_event_handler(
|
|||
///
|
||||
/// 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>(
|
||||
pub fn use_asset_handler(
|
||||
cx: &ScopeState,
|
||||
handler: impl AssetHandler<F>,
|
||||
) -> &AssetHandlerHandle {
|
||||
name: &str,
|
||||
handler: impl Fn(AssetRequest, RequestAsyncResponder) + 'static,
|
||||
) {
|
||||
cx.use_hook(|| {
|
||||
let desktop = crate::window();
|
||||
let handler_id = Rc::new(tokio::sync::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,
|
||||
crate::window().asset_handlers.register_handler(
|
||||
name.to_string(),
|
||||
Box::new(handler),
|
||||
cx.scope_id(),
|
||||
);
|
||||
|
||||
Handler(name.to_string())
|
||||
});
|
||||
|
||||
// todo: can we just put ondrop in core?
|
||||
struct Handler(String);
|
||||
impl Drop for Handler {
|
||||
fn drop(&mut self) {
|
||||
_ = crate::window().asset_handlers.remove_handler(&self.0);
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a closure that executes any JavaScript in the WebView context.
|
||||
|
|
|
@ -38,7 +38,7 @@ pub use tao::window::WindowBuilder;
|
|||
pub use wry;
|
||||
|
||||
// Public exports
|
||||
pub use assets::{AssetFuture, AssetHandler, AssetRequest, AssetResponse};
|
||||
pub use assets::AssetRequest;
|
||||
pub use cfg::{Config, WindowCloseBehaviour};
|
||||
pub use desktop_context::{
|
||||
window, DesktopContext, DesktopService, WryEventHandler, WryEventHandlerId,
|
||||
|
@ -46,3 +46,4 @@ pub use desktop_context::{
|
|||
pub use hooks::{use_asset_handler, use_global_shortcut, use_window, use_wry_event_handler};
|
||||
pub use menubar::build_default_menu_bar;
|
||||
pub use shortcut::{ShortcutHandle, ShortcutId, ShortcutRegistryError};
|
||||
pub use wry::RequestAsyncResponder;
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
use crate::{assets::*, edits::EditQueue};
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
use wry::{
|
||||
http::{status::StatusCode, Request, Response},
|
||||
RequestAsyncResponder, Result,
|
||||
|
@ -11,69 +8,6 @@ use wry::{
|
|||
static MINIFIED: &str = include_str!("./minified.js");
|
||||
static DEFAULT_INDEX: &str = include_str!("./index.html");
|
||||
|
||||
// todo: clean this up a bit
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(super) async fn desktop_handler(
|
||||
request: Request<Vec<u8>>,
|
||||
custom_head: Option<String>,
|
||||
custom_index: Option<String>,
|
||||
root_name: &str,
|
||||
asset_handlers: &AssetHandlerRegistry,
|
||||
edit_queue: &EditQueue,
|
||||
headless: bool,
|
||||
responder: RequestAsyncResponder,
|
||||
) {
|
||||
let request = AssetRequest::from(request);
|
||||
|
||||
// If the request is for the root, we'll serve the index.html file.
|
||||
if request.uri().path() == "/" {
|
||||
match build_index_file(custom_index, custom_head, root_name, headless) {
|
||||
Ok(response) => return responder.respond(response),
|
||||
Err(err) => return tracing::error!("error building response: {}", err),
|
||||
}
|
||||
}
|
||||
|
||||
// If the request is asking for edits (ie binary protocol streaming, do that)
|
||||
if request.uri().path().trim_matches('/') == "edits" {
|
||||
return edit_queue.handle_request(responder);
|
||||
}
|
||||
|
||||
// If the user provided a custom asset handler, then call it and return the response if the request was handled.
|
||||
// todo(jon): I dont want this function to be async - we can probably just use a drop handler on the responder
|
||||
if let Some(response) = asset_handlers.try_handlers(&request).await {
|
||||
return responder.respond(response);
|
||||
}
|
||||
|
||||
// Else, try to serve a file from the filesystem.
|
||||
match serve_from_fs(request) {
|
||||
Ok(res) => responder.respond(res),
|
||||
Err(e) => tracing::error!("Error serving request from filesystem {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn serve_from_fs(request: AssetRequest) -> Result<AssetResponse> {
|
||||
// If the path is relative, we'll try to serve it from the assets directory.
|
||||
let mut asset = get_asset_root_or_default().join(&request.path);
|
||||
|
||||
// If we can't find it, make it absolute and try again
|
||||
if !asset.exists() {
|
||||
asset = PathBuf::from("/").join(request.path);
|
||||
}
|
||||
|
||||
if asset.exists() {
|
||||
let content_type = get_mime_from_path(&asset)?;
|
||||
let asset = std::fs::read(asset)?;
|
||||
|
||||
Ok(Response::builder()
|
||||
.header("Content-Type", content_type)
|
||||
.body(Cow::from(asset))?)
|
||||
} else {
|
||||
Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(Cow::from(String::from("Not Found").into_bytes()))?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the index.html file we use for bootstrapping a new app
|
||||
///
|
||||
/// We use wry/webview by building a special index.html that forms a bridge between the webview and your rust code
|
||||
|
@ -83,12 +17,18 @@ fn serve_from_fs(request: AssetRequest) -> Result<AssetResponse> {
|
|||
/// mess with UI elements. We make this decision since other renderers like LiveView are very separate and can
|
||||
/// never properly bridge the gap. Eventually of course, the idea is to build a custom CSS/HTML renderer where you
|
||||
/// *do* have native control over elements, but that still won't work with liveview.
|
||||
fn build_index_file(
|
||||
custom_index: Option<String>,
|
||||
pub(super) fn index_request(
|
||||
request: &Request<Vec<u8>>,
|
||||
custom_head: Option<String>,
|
||||
custom_index: Option<String>,
|
||||
root_name: &str,
|
||||
headless: bool,
|
||||
) -> std::result::Result<Response<Vec<u8>>, wry::http::Error> {
|
||||
) -> Option<Response<Vec<u8>>> {
|
||||
// If the request is for the root, we'll serve the index.html file.
|
||||
if request.uri().path() != "/" {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Load a custom index file if provided
|
||||
let mut index = custom_index.unwrap_or_else(|| DEFAULT_INDEX.to_string());
|
||||
|
||||
|
@ -110,6 +50,68 @@ fn build_index_file(
|
|||
.header("Content-Type", "text/html")
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.body(index.into())
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Handle a request from the webview
|
||||
///
|
||||
/// - Tries to stream edits if they're requested.
|
||||
/// - If that doesn't match, tries a user provided asset handler
|
||||
/// - If that doesn't match, tries to serve a file from the filesystem
|
||||
pub(super) fn desktop_handler(
|
||||
request: Request<Vec<u8>>,
|
||||
asset_handlers: AssetHandlerRegistry,
|
||||
edit_queue: &EditQueue,
|
||||
responder: RequestAsyncResponder,
|
||||
) {
|
||||
// If the request is asking for edits (ie binary protocol streaming, do that)
|
||||
if request.uri().path().trim_matches('/') == "edits" {
|
||||
return edit_queue.handle_request(responder);
|
||||
}
|
||||
|
||||
// If the user provided a custom asset handler, then call it and return the response if the request was handled.
|
||||
// The path is the first part of the URI, so we need to trim the leading slash.
|
||||
let path = PathBuf::from(
|
||||
urlencoding::decode(request.uri().path().trim_start_matches('/'))
|
||||
.expect("expected URL to be UTF-8 encoded")
|
||||
.as_ref(),
|
||||
);
|
||||
|
||||
let Some(name) = path.parent() else {
|
||||
return tracing::error!("Asset request has no root {path:?}");
|
||||
};
|
||||
|
||||
if let Some(name) = name.to_str() {
|
||||
if asset_handlers.has_handler(name) {
|
||||
return asset_handlers.handle_request(name, request, responder);
|
||||
}
|
||||
}
|
||||
|
||||
// Else, try to serve a file from the filesystem.
|
||||
match serve_from_fs(path) {
|
||||
Ok(res) => responder.respond(res),
|
||||
Err(e) => tracing::error!("Error serving request from filesystem {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
fn serve_from_fs(path: PathBuf) -> Result<Response<Vec<u8>>> {
|
||||
// If the path is relative, we'll try to serve it from the assets directory.
|
||||
let mut asset = get_asset_root_or_default().join(&path);
|
||||
|
||||
// If we can't find it, make it absolute and try again
|
||||
if !asset.exists() {
|
||||
asset = PathBuf::from("/").join(path);
|
||||
}
|
||||
|
||||
if !asset.exists() {
|
||||
return Ok(Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(String::from("Not Found").into_bytes())?);
|
||||
}
|
||||
|
||||
Ok(Response::builder()
|
||||
.header("Content-Type", get_mime_from_path(&asset)?)
|
||||
.body(std::fs::read(asset)?)?)
|
||||
}
|
||||
|
||||
/// Construct the inline script that boots up the page and bridges the webview with rust code.
|
||||
|
|
|
@ -11,7 +11,7 @@ use crate::{
|
|||
use dioxus_core::VirtualDom;
|
||||
use futures_util::{pin_mut, FutureExt};
|
||||
use std::{rc::Rc, task::Waker};
|
||||
use wry::{WebContext, WebViewBuilder};
|
||||
use wry::{RequestAsyncResponder, WebContext, WebViewBuilder};
|
||||
|
||||
pub struct WebviewInstance {
|
||||
pub dom: VirtualDom,
|
||||
|
@ -33,12 +33,6 @@ impl WebviewInstance {
|
|||
build_menu_bar(build_default_menu_bar(), &window);
|
||||
}
|
||||
|
||||
let window_id = window.id();
|
||||
let file_handler = cfg.file_drop_handler.take();
|
||||
let custom_head = cfg.custom_head.clone();
|
||||
let index_file = cfg.custom_index.clone();
|
||||
let root_name = cfg.root_name.clone();
|
||||
|
||||
// We assume that if the icon is None in cfg, then the user just didnt set it
|
||||
if cfg.window.window.window_icon.is_none() {
|
||||
window.set_window_icon(Some(
|
||||
|
@ -53,52 +47,65 @@ impl WebviewInstance {
|
|||
|
||||
let mut web_context = WebContext::new(cfg.data_dir.clone());
|
||||
let edit_queue = EditQueue::default();
|
||||
let asset_handlers = AssetHandlerRegistry::new(dom.runtime());
|
||||
let headless = !cfg.window.window.visible;
|
||||
let asset_handlers = AssetHandlerRegistry::new();
|
||||
let asset_handlers_ref = asset_handlers.clone();
|
||||
|
||||
// Rust :(
|
||||
let window_id = window.id();
|
||||
let file_handler = cfg.file_drop_handler.take();
|
||||
let custom_head = cfg.custom_head.clone();
|
||||
let index_file = cfg.custom_index.clone();
|
||||
let root_name = cfg.root_name.clone();
|
||||
let asset_handlers_ = asset_handlers.clone();
|
||||
let edit_queue_ = edit_queue.clone();
|
||||
let proxy_ = shared.proxy.clone();
|
||||
|
||||
let request_handler = move |request, responder: RequestAsyncResponder| {
|
||||
// Try to serve the index file first
|
||||
let index_bytes = protocol::index_request(
|
||||
&request,
|
||||
custom_head.clone(),
|
||||
index_file.clone(),
|
||||
&root_name,
|
||||
headless,
|
||||
);
|
||||
|
||||
// Otherwise, try to serve an asset, either from the user or the filesystem
|
||||
match index_bytes {
|
||||
Some(body) => return responder.respond(body),
|
||||
None => {
|
||||
// we need to do this in the context of the dioxus runtime since the user gave us these closures
|
||||
protocol::desktop_handler(
|
||||
request,
|
||||
asset_handlers_.clone(),
|
||||
&edit_queue_,
|
||||
responder,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let ipc_handler = move |payload: String| {
|
||||
// defer the event to the main thread
|
||||
if let Ok(message) = serde_json::from_str(&payload) {
|
||||
_ = proxy_.send_event(UserWindowEvent(EventData::Ipc(message), window_id));
|
||||
}
|
||||
};
|
||||
|
||||
let file_drop_handler = move |event| {
|
||||
file_handler
|
||||
.as_ref()
|
||||
.map(|handler| handler(window_id, event))
|
||||
.unwrap_or_default()
|
||||
};
|
||||
|
||||
let mut webview = WebViewBuilder::new(&window)
|
||||
.with_transparent(cfg.window.window.transparent)
|
||||
.with_url("dioxus://index.html/")
|
||||
.unwrap()
|
||||
.with_ipc_handler({
|
||||
let proxy = shared.proxy.clone();
|
||||
move |payload: String| {
|
||||
// defer the event to the main thread
|
||||
if let Ok(message) = serde_json::from_str(&payload) {
|
||||
_ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window_id));
|
||||
}
|
||||
}
|
||||
})
|
||||
.with_asynchronous_custom_protocol(String::from("dioxus"), {
|
||||
let edit_queue = edit_queue.clone();
|
||||
move |request, responder| {
|
||||
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();
|
||||
let edit_queue = edit_queue.clone();
|
||||
tokio::spawn(async move {
|
||||
protocol::desktop_handler(
|
||||
request,
|
||||
custom_head.clone(),
|
||||
index_file.clone(),
|
||||
&root_name,
|
||||
&asset_handlers_ref,
|
||||
&edit_queue,
|
||||
headless,
|
||||
responder,
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
})
|
||||
.with_file_drop_handler(move |event| {
|
||||
file_handler
|
||||
.as_ref()
|
||||
.map(|handler| handler(window_id, event))
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.with_ipc_handler(ipc_handler)
|
||||
.with_asynchronous_custom_protocol(String::from("dioxus"), request_handler)
|
||||
.with_file_drop_handler(file_drop_handler)
|
||||
.with_web_context(&mut web_context);
|
||||
|
||||
#[cfg(windows)]
|
||||
|
|
Loading…
Reference in a new issue