From 16e550ec1503765158cdc3bb2a20e70ec710e981 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Fri, 16 Feb 2024 13:07:39 +0200 Subject: [PATCH] refactor(core): add webview events (#8844) * refactor(core): add webview events * license header * clippy * fix doctests * more doctests * fix JS `listen` with `EventTarget::Any` * typo * update module import * clippy * remove console.log * fix api example * fix documentation for emiTo [skip ci] * actually add RunEvent::WebviewEvent * update migration * lint --------- Co-authored-by: Lucas Nogueira --- .changes/api-tauri-event-file-drop-rename.md | 9 + .changes/api-webview-window-new-methods.md | 5 + .changes/api-webview-window.md | 5 + .changes/api-window-on-filedrop.md | 5 + .changes/core-js-event-anytarget.md | 5 + .changes/tauri-runtime-webview-events.md | 6 + .changes/tauri-webview-events.md | 9 + .github/workflows/test-core.yml | 2 +- core/tauri-runtime-wry/src/lib.rs | 249 +++++++++++-------- core/tauri-runtime/src/lib.rs | 13 +- core/tauri-runtime/src/window.rs | 7 + core/tauri/scripts/bundle.global.js | 2 +- core/tauri/src/app.rs | 60 ++++- core/tauri/src/event/listener.rs | 31 ++- core/tauri/src/event/mod.rs | 2 +- core/tauri/src/lib.rs | 8 +- core/tauri/src/manager/mod.rs | 25 +- core/tauri/src/manager/webview.rs | 65 ++++- core/tauri/src/manager/window.rs | 54 ++-- core/tauri/src/test/mock_runtime.rs | 13 + core/tauri/src/webview/mod.rs | 20 +- core/tauri/src/webview/webview_window.rs | 2 +- core/tauri/src/window/mod.rs | 10 +- examples/api/src/views/Window.svelte | 2 +- tooling/api/src/event.ts | 10 +- tooling/api/src/index.ts | 14 +- tooling/api/src/webview.ts | 208 +--------------- tooling/api/src/webviewWindow.ts | 234 +++++++++++++++++ tooling/api/src/window.ts | 79 +++++- tooling/cli/src/interface/rust.rs | 2 +- tooling/cli/src/migrate/frontend.rs | 4 + 31 files changed, 779 insertions(+), 381 deletions(-) create mode 100644 .changes/api-tauri-event-file-drop-rename.md create mode 100644 .changes/api-webview-window-new-methods.md create mode 100644 .changes/api-webview-window.md create mode 100644 .changes/api-window-on-filedrop.md create mode 100644 .changes/core-js-event-anytarget.md create mode 100644 .changes/tauri-runtime-webview-events.md create mode 100644 .changes/tauri-webview-events.md create mode 100644 tooling/api/src/webviewWindow.ts diff --git a/.changes/api-tauri-event-file-drop-rename.md b/.changes/api-tauri-event-file-drop-rename.md new file mode 100644 index 000000000..f7e9d4f80 --- /dev/null +++ b/.changes/api-tauri-event-file-drop-rename.md @@ -0,0 +1,9 @@ +--- +'@tauri-apps/api': 'patch:breaking' +--- + +Renamed the following enum variants of `TauriEvent` enum: + +- `TauriEvent.WEBVIEW_FILE_DROP` -> `TauriEvent.FILE_DROP` +- `TauriEvent.WEBVIEW_FILE_DROP_HOVER` -> `TauriEvent.FILE_DROP_HOVER` +- `TauriEvent.WEBVIEW_FILE_DROP_CANCELLED` -> `TauriEvent.FILE_DROP_CANCELLED` diff --git a/.changes/api-webview-window-new-methods.md b/.changes/api-webview-window-new-methods.md new file mode 100644 index 000000000..063c15f7a --- /dev/null +++ b/.changes/api-webview-window-new-methods.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'patch:feat' +--- + +Add a new `webviewWindow` module that exports `WebviewWindow` class and related methods such as `getCurrent` and `getAll`. diff --git a/.changes/api-webview-window.md b/.changes/api-webview-window.md new file mode 100644 index 000000000..d2fd15e5e --- /dev/null +++ b/.changes/api-webview-window.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'patch:breaking' +--- + +Move `WebviewWindow` class from `webview` module to a new `webviewWindow` module. diff --git a/.changes/api-window-on-filedrop.md b/.changes/api-window-on-filedrop.md new file mode 100644 index 000000000..12ff6b77a --- /dev/null +++ b/.changes/api-window-on-filedrop.md @@ -0,0 +1,5 @@ +--- +'@tauri-apps/api': 'patch:feat' +--- + +Add `Window.onFileDropEvent` method. diff --git a/.changes/core-js-event-anytarget.md b/.changes/core-js-event-anytarget.md new file mode 100644 index 000000000..3d55b2c6a --- /dev/null +++ b/.changes/core-js-event-anytarget.md @@ -0,0 +1,5 @@ +--- +'tauri': 'patch:bug' +--- + +Fix JS event listeners registered using JS `listen` api with `EventTarget::Any` never fired. diff --git a/.changes/tauri-runtime-webview-events.md b/.changes/tauri-runtime-webview-events.md new file mode 100644 index 000000000..b021ab1d1 --- /dev/null +++ b/.changes/tauri-runtime-webview-events.md @@ -0,0 +1,6 @@ +--- +'tauri-runtime': 'patch' +'tauri-runtime-wry': 'patch' +--- + +Add `WebviewEvent`, `RunEvent::WebviewEvent` and `WebviewDispatch::on_webview_event`. diff --git a/.changes/tauri-webview-events.md b/.changes/tauri-webview-events.md new file mode 100644 index 000000000..7a4aa10ef --- /dev/null +++ b/.changes/tauri-webview-events.md @@ -0,0 +1,9 @@ +--- +'tauri': 'patch:feat' +--- + +Add webview-specific events for multi-webview windows: + +- Add `WebviewEvent` enum +- Add `RunEvent::WebviewEvent` variant. +- Add `Builder::on_webview_event` and `Webview::on_webview_event` methods. diff --git a/.github/workflows/test-core.yml b/.github/workflows/test-core.yml index fb34ec63d..e8e2613a8 100644 --- a/.github/workflows/test-core.yml +++ b/.github/workflows/test-core.yml @@ -94,7 +94,7 @@ jobs: - name: test (using cross) if: ${{ matrix.platform.cross }} run: | - cargo install cross --git https://github.com/cross-rs/cross + cargo install cross --git https://github.com/cross-rs/cross --locked cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} ${{ matrix.features.args }} - name: test (using cargo) diff --git a/core/tauri-runtime-wry/src/lib.rs b/core/tauri-runtime-wry/src/lib.rs index de7ded2ad..3278228cb 100644 --- a/core/tauri-runtime-wry/src/lib.rs +++ b/core/tauri-runtime-wry/src/lib.rs @@ -17,12 +17,12 @@ use tauri_runtime::{ webview::{DetachedWebview, DownloadEvent, PendingWebview, WebviewIpcHandler}, window::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, RawWindow, WindowBuilder, - WindowBuilderBase, WindowEvent, WindowId, + CursorIcon, DetachedWindow, FileDropEvent, PendingWindow, RawWindow, WebviewEvent, + WindowBuilder, WindowBuilderBase, WindowEvent, WindowId, }, DeviceEventFilter, Error, EventLoopProxy, ExitRequestedEventAction, Icon, Result, RunEvent, Runtime, RuntimeHandle, RuntimeInitArgs, UserAttentionType, UserEvent, WebviewDispatch, - WindowDispatch, WindowEventId, + WebviewEventId, WindowDispatch, WindowEventId, }; #[cfg(target_os = "macos")] @@ -121,6 +121,8 @@ pub type WebContextStore = Arc, WebContext>>>; // window pub type WindowEventHandler = Box; pub type WindowEventListeners = Arc>>; +pub type WebviewEventHandler = Box; +pub type WebviewEventListeners = Arc>>; #[derive(Debug, Clone, Default)] pub struct WindowIdStore(Arc>>); @@ -172,7 +174,7 @@ pub(crate) fn send_user_message( &context.main_thread.window_target, message, UserMessageContext { - webview_id_map: context.webview_id_map.clone(), + window_id_map: context.window_id_map.clone(), windows: context.main_thread.windows.clone(), }, ); @@ -187,7 +189,7 @@ pub(crate) fn send_user_message( #[derive(Clone)] pub struct Context { - pub webview_id_map: WindowIdStore, + pub window_id_map: WindowIdStore, main_thread_id: ThreadId, pub proxy: TaoEventLoopProxy>, main_thread: DispatcherMainThreadContext, @@ -195,6 +197,7 @@ pub struct Context { next_window_id: Arc, next_webview_id: Arc, next_window_event_id: Arc, + next_webview_event_id: Arc, next_webcontext_id: Arc, } @@ -222,6 +225,10 @@ impl Context { self.next_window_event_id.fetch_add(1, Ordering::Relaxed) } + fn next_webview_event_id(&self) -> u32 { + self.next_webview_event_id.fetch_add(1, Ordering::Relaxed) + } + fn next_webcontext_id(&self) -> u32 { self.next_webcontext_id.fetch_add(1, Ordering::Relaxed) } @@ -463,16 +470,6 @@ impl<'a> From<&TaoWindowEvent<'a>> for WindowEventWrapper { } } -impl From for WindowEventWrapper { - fn from(event: WebviewEvent) -> Self { - let event = match event { - WebviewEvent::Focused(focused) => WindowEvent::Focused(focused), - WebviewEvent::FileDrop(event) => WindowEvent::FileDrop(event), - }; - Self(Some(event)) - } -} - pub struct MonitorHandleWrapper(pub MonitorHandle); impl From for Monitor { @@ -994,53 +991,6 @@ impl WindowBuilder for WindowBuilderWrapper { } } -pub struct FileDropEventWrapper(WryFileDropEvent); - -// on Linux, the paths are percent-encoded -#[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -))] -fn decode_path(path: PathBuf) -> PathBuf { - percent_encoding::percent_decode(path.display().to_string().as_bytes()) - .decode_utf8_lossy() - .into_owned() - .into() -} - -// on Windows and macOS, we do not need to decode the path -#[cfg(not(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "openbsd" -)))] -fn decode_path(path: PathBuf) -> PathBuf { - path -} - -impl From for FileDropEvent { - fn from(event: FileDropEventWrapper) -> Self { - match event.0 { - WryFileDropEvent::Hovered { paths, position } => FileDropEvent::Hovered { - paths: paths.into_iter().map(decode_path).collect(), - position: PhysicalPosition::new(position.0 as f64, position.1 as f64), - }, - WryFileDropEvent::Dropped { paths, position } => FileDropEvent::Dropped { - paths: paths.into_iter().map(decode_path).collect(), - position: PhysicalPosition::new(position.0 as f64, position.1 as f64), - }, - // default to cancelled - // FIXME(maybe): Add `FileDropEvent::Unknown` event? - _ => FileDropEvent::Cancelled, - } - } -} - #[cfg(any( target_os = "linux", target_os = "dragonfly", @@ -1168,13 +1118,30 @@ pub enum WindowMessage { RequestRedraw, } +#[derive(Debug, Clone)] +pub enum SynthesizedWindowEvent { + Focused(bool), + FileDrop(FileDropEvent), +} + +impl From for WindowEventWrapper { + fn from(event: SynthesizedWindowEvent) -> Self { + let event = match event { + SynthesizedWindowEvent::Focused(focused) => WindowEvent::Focused(focused), + SynthesizedWindowEvent::FileDrop(event) => WindowEvent::FileDrop(event), + }; + Self(Some(event)) + } +} + pub enum WebviewMessage { + AddEventListener(WebviewEventId, Box), #[cfg(not(all(feature = "tracing", not(target_os = "android"))))] EvaluateScript(String), #[cfg(all(feature = "tracing", not(target_os = "android")))] EvaluateScript(String, Sender<()>, tracing::Span), - #[allow(dead_code)] WebviewEvent(WebviewEvent), + SynthesizedWindowEvent(SynthesizedWindowEvent), Navigate(Url), Print, Close, @@ -1195,13 +1162,6 @@ pub enum WebviewMessage { IsDevToolsOpen(Sender), } -#[allow(dead_code)] -#[derive(Debug, Clone)] -pub enum WebviewEvent { - FileDrop(FileDropEvent), - Focused(bool), -} - pub type CreateWindowClosure = Box>) -> Result + Send>; @@ -1250,6 +1210,16 @@ impl WebviewDispatch for WryWebviewDispatcher { send_user_message(&self.context, Message::Task(Box::new(f))) } + fn on_webview_event(&self, f: F) -> WindowEventId { + let id = self.context.next_webview_event_id(); + let _ = self.context.proxy.send_event(Message::Webview( + self.window_id, + self.webview_id, + WebviewMessage::AddEventListener(id, Box::new(f)), + )); + id + } + fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { send_user_message( &self.context, @@ -1853,9 +1823,11 @@ impl WindowDispatch for WryWindowDispatcher { #[derive(Clone)] pub struct WebviewWrapper { + label: String, id: WebviewId, inner: Rc, context_store: WebContextStore, + webview_event_listeners: WebviewEventListeners, // the key of the WebContext if it's not shared context_key: Option, bounds: Option>>, @@ -1977,7 +1949,7 @@ impl WryHandle { pub fn window_id(&self, window_id: TaoWindowId) -> WindowId { *self .context - .webview_id_map + .window_id_map .0 .lock() .unwrap() @@ -2132,10 +2104,10 @@ impl Wry { let web_context = WebContextStore::default(); let windows = Rc::new(RefCell::new(HashMap::default())); - let webview_id_map = WindowIdStore::default(); + let window_id_map = WindowIdStore::default(); let context = Context { - webview_id_map, + window_id_map, main_thread_id, proxy: event_loop.create_proxy(), main_thread: DispatcherMainThreadContext { @@ -2149,6 +2121,7 @@ impl Wry { next_window_id: Default::default(), next_webview_id: Default::default(), next_window_event_id: Default::default(), + next_webview_event_id: Default::default(), next_webcontext_id: Default::default(), }; @@ -2347,7 +2320,7 @@ impl Runtime for Wry { fn run_iteration)>(&mut self, mut callback: F) { use tao::platform::run_return::EventLoopExtRunReturn; let windows = self.context.main_thread.windows.clone(); - let webview_id_map = self.context.webview_id_map.clone(); + let window_id_map = self.context.window_id_map.clone(); let web_context = &self.context.main_thread.web_context; let plugins = self.context.plugins.clone(); @@ -2372,7 +2345,7 @@ impl Runtime for Wry { control_flow, EventLoopIterationContext { callback: &mut callback, - webview_id_map: webview_id_map.clone(), + window_id_map: window_id_map.clone(), windows: windows.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), @@ -2391,7 +2364,7 @@ impl Runtime for Wry { EventLoopIterationContext { callback: &mut callback, windows: windows.clone(), - webview_id_map: webview_id_map.clone(), + window_id_map: window_id_map.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), }, @@ -2401,7 +2374,7 @@ impl Runtime for Wry { fn run) + 'static>(self, mut callback: F) { let windows = self.context.main_thread.windows.clone(); - let webview_id_map = self.context.webview_id_map.clone(); + let window_id_map = self.context.window_id_map.clone(); let web_context = self.context.main_thread.web_context; let plugins = self.context.plugins.clone(); @@ -2418,7 +2391,7 @@ impl Runtime for Wry { control_flow, EventLoopIterationContext { callback: &mut callback, - webview_id_map: webview_id_map.clone(), + window_id_map: window_id_map.clone(), windows: windows.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), @@ -2435,7 +2408,7 @@ impl Runtime for Wry { control_flow, EventLoopIterationContext { callback: &mut callback, - webview_id_map: webview_id_map.clone(), + window_id_map: window_id_map.clone(), windows: windows.clone(), #[cfg(feature = "tracing")] active_tracing_spans: active_tracing_spans.clone(), @@ -2447,7 +2420,7 @@ impl Runtime for Wry { pub struct EventLoopIterationContext<'a, T: UserEvent> { pub callback: &'a mut (dyn FnMut(RunEvent)), - pub webview_id_map: WindowIdStore, + pub window_id_map: WindowIdStore, pub windows: Rc>>, #[cfg(feature = "tracing")] pub active_tracing_spans: ActiveTraceSpanStore, @@ -2455,7 +2428,7 @@ pub struct EventLoopIterationContext<'a, T: UserEvent> { struct UserMessageContext { windows: Rc>>, - webview_id_map: WindowIdStore, + window_id_map: WindowIdStore, } fn handle_user_message( @@ -2464,7 +2437,7 @@ fn handle_user_message( context: UserMessageContext, ) { let UserMessageContext { - webview_id_map, + window_id_map, windows, } = context; match message { @@ -2684,6 +2657,17 @@ fn handle_user_message( }); if let Some((Some(window), Some(webview))) = webview_handle { match webview_message { + WebviewMessage::WebviewEvent(_) => { /* already handled */ } + WebviewMessage::SynthesizedWindowEvent(_) => { /* already handled */ } + + WebviewMessage::AddEventListener(id, listener) => { + webview + .webview_event_listeners + .lock() + .unwrap() + .insert(id, listener); + } + #[cfg(all(feature = "tracing", not(target_os = "android")))] WebviewMessage::EvaluateScript(script, tx, span) => { let _span = span.entered(); @@ -2743,7 +2727,6 @@ fn handle_user_message( WebviewMessage::SetFocus => { webview.focus(); } - WebviewMessage::WebviewEvent(_event) => { /* already handled */ } WebviewMessage::WithWebview(f) => { #[cfg(any( target_os = "linux", @@ -2850,7 +2833,7 @@ fn handle_user_message( let (label, builder) = handler(); let is_window_transparent = builder.window.transparent; if let Ok(window) = builder.build(event_loop) { - webview_id_map.insert(window.id(), window_id); + window_id_map.insert(window.id(), window_id); let window = Arc::new(window); @@ -2901,7 +2884,7 @@ fn handle_event_loop( ) { let EventLoopIterationContext { callback, - webview_id_map, + window_id_map, windows, #[cfg(feature = "tracing")] active_tracing_spans, @@ -2930,7 +2913,7 @@ fn handle_event_loop( #[cfg(any(feature = "tracing", windows))] Event::RedrawRequested(id) => { #[cfg(windows)] - if let Some(window_id) = webview_id_map.get(&id) { + if let Some(window_id) = window_id_map.get(&id) { let mut windows_ref = windows.borrow_mut(); if let Some(window) = windows_ref.get_mut(&window_id) { if window.is_window_transparent { @@ -2949,19 +2932,50 @@ fn handle_event_loop( Event::UserEvent(Message::Webview( window_id, - _webview_id, + webview_id, WebviewMessage::WebviewEvent(event), + )) => { + let windows_ref = windows.borrow(); + if let Some(window) = windows_ref.get(&window_id) { + if let Some(webview) = window.webviews.iter().find(|w| w.id == webview_id) { + let label = webview.label.clone(); + let webview_event_listeners = webview.webview_event_listeners.clone(); + + drop(windows_ref); + + callback(RunEvent::WebviewEvent { + label, + event: event.clone(), + }); + let listeners = webview_event_listeners.lock().unwrap(); + let handlers = listeners.values(); + for handler in handlers { + handler(&event); + } + } + } + } + + Event::UserEvent(Message::Webview( + window_id, + _webview_id, + WebviewMessage::SynthesizedWindowEvent(event), )) => { if let Some(event) = WindowEventWrapper::from(event).0 { - let windows = windows.borrow(); - let window = windows.get(&window_id); + let windows_ref = windows.borrow(); + let window = windows_ref.get(&window_id); if let Some(window) = window { + let label = window.label.clone(); + let window_event_listeners = window.window_event_listeners.clone(); + + drop(windows_ref); + callback(RunEvent::WindowEvent { - label: window.label.clone(), + label, event: event.clone(), }); - let listeners = window.window_event_listeners.lock().unwrap(); + let listeners = window_event_listeners.lock().unwrap(); let handlers = listeners.values(); for handler in handlers { handler(&event); @@ -2973,7 +2987,7 @@ fn handle_event_loop( Event::WindowEvent { event, window_id, .. } => { - if let Some(window_id) = webview_id_map.get(&window_id) { + if let Some(window_id) = window_id_map.get(&window_id) { { let windows_ref = windows.borrow(); if let Some(window) = windows_ref.get(&window_id) { @@ -3076,7 +3090,7 @@ fn handle_event_loop( event_loop, message, UserMessageContext { - webview_id_map, + window_id_map, windows, }, ); @@ -3216,7 +3230,7 @@ fn create_window( }); } - context.webview_id_map.insert(window.id(), window_id); + context.window_id_map.insert(window.id(), window_id); if window_builder.center { let _ = center_window(&window, window.inner_size()); @@ -3290,7 +3304,8 @@ fn create_window( }) } -// the kind of the webview +/// the kind of the webview +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)] enum WebviewKind { // webview is the entire window content WindowContent, @@ -3375,12 +3390,32 @@ fn create_webview( if webview_attributes.file_drop_handler_enabled { let proxy = context.proxy.clone(); webview_builder = webview_builder.with_file_drop_handler(move |event| { - let event: FileDropEvent = FileDropEventWrapper(event).into(); - let _ = proxy.send_event(Message::Webview( - window_id, - id, - WebviewMessage::WebviewEvent(WebviewEvent::FileDrop(event)), - )); + let event = match event { + WryFileDropEvent::Hovered { + paths, + position: (x, y), + } => FileDropEvent::Hovered { + paths, + position: PhysicalPosition::new(x as _, y as _), + }, + WryFileDropEvent::Dropped { + paths, + position: (x, y), + } => FileDropEvent::Dropped { + paths, + position: PhysicalPosition::new(x as _, y as _), + }, + WryFileDropEvent::Cancelled => FileDropEvent::Cancelled, + _ => unimplemented!(), + }; + + let message = if kind == WebviewKind::WindowContent { + WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::FileDrop(event)) + } else { + WebviewMessage::WebviewEvent(WebviewEvent::FileDrop(event)) + }; + + let _ = proxy.send_event(Message::Webview(window_id, id, message)); true }); } @@ -3566,7 +3601,7 @@ fn create_webview( .map_err(|e| Error::CreateWebview(Box::new(e)))?; #[cfg(windows)] - { + if kind == WebviewKind::WindowContent { let controller = webview.controller(); let proxy = context.proxy.clone(); let proxy_ = proxy.clone(); @@ -3574,10 +3609,10 @@ fn create_webview( unsafe { controller.add_GotFocus( &FocusChangedEventHandler::create(Box::new(move |_, _| { - let _ = proxy.send_event(Message::Webview( + let _ = proxy_.send_event(Message::Webview( window_id, id, - WebviewMessage::WebviewEvent(WebviewEvent::Focused(true)), + WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(true)), )); Ok(()) })), @@ -3588,10 +3623,10 @@ fn create_webview( unsafe { controller.add_LostFocus( &FocusChangedEventHandler::create(Box::new(move |_, _| { - let _ = proxy_.send_event(Message::Webview( + let _ = proxy.send_event(Message::Webview( window_id, id, - WebviewMessage::WebviewEvent(WebviewEvent::Focused(false)), + WebviewMessage::SynthesizedWindowEvent(SynthesizedWindowEvent::Focused(false)), )); Ok(()) })), @@ -3602,9 +3637,11 @@ fn create_webview( } Ok(WebviewWrapper { + label, id, inner: Rc::new(webview), context_store: context.main_thread.web_context.clone(), + webview_event_listeners: Default::default(), context_key: if automation_enabled { None } else { diff --git a/core/tauri-runtime/src/lib.rs b/core/tauri-runtime/src/lib.rs index cad68dae9..0caff2215 100644 --- a/core/tauri-runtime/src/lib.rs +++ b/core/tauri-runtime/src/lib.rs @@ -27,7 +27,7 @@ pub mod window; use monitor::Monitor; use window::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - CursorIcon, DetachedWindow, PendingWindow, RawWindow, WindowEvent, + CursorIcon, DetachedWindow, PendingWindow, RawWindow, WebviewEvent, WindowEvent, }; use window::{WindowBuilder, WindowId}; @@ -38,6 +38,7 @@ use http::{ }; pub type WindowEventId = u32; +pub type WebviewEventId = u32; /// Type of user attention requested on a window. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] @@ -166,6 +167,13 @@ pub enum RunEvent { /// The detailed event. event: WindowEvent, }, + /// An event associated with a webview. + WebviewEvent { + /// The webview label. + label: String, + /// The detailed event. + event: WebviewEvent, + }, /// Application ready. Ready, /// Sent if the event loop is being resumed. @@ -362,6 +370,9 @@ pub trait WebviewDispatch: Debug + Clone + Send + Sync + Sized + ' /// Run a task on the main thread. fn run_on_main_thread(&self, f: F) -> Result<()>; + /// Registers a webview event handler. + fn on_webview_event(&self, f: F) -> WebviewEventId; + /// Runs a closure with the platform webview object as argument. fn with_webview) + Send + 'static>(&self, f: F) -> Result<()>; diff --git a/core/tauri-runtime/src/window.rs b/core/tauri-runtime/src/window.rs index 10b3e34a5..df6b2ccbd 100644 --- a/core/tauri-runtime/src/window.rs +++ b/core/tauri-runtime/src/window.rs @@ -65,6 +65,13 @@ pub enum WindowEvent { ThemeChanged(Theme), } +/// An event from a window. +#[derive(Debug, Clone)] +pub enum WebviewEvent { + /// An event associated with the file drop action. + FileDrop(FileDropEvent), +} + /// The file drop event payload. #[derive(Debug, Clone)] #[non_exhaustive] diff --git a/core/tauri/scripts/bundle.global.js b/core/tauri/scripts/bundle.global.js index 3531a725c..73acfd4f3 100644 --- a/core/tauri/scripts/bundle.global.js +++ b/core/tauri/scripts/bundle.global.js @@ -1 +1 @@ -var __TAURI_IIFE__=function(e){"use strict";function t(e,t,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(e):i?i.value:t.get(e)}function n(e,t,n,i,r){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?r.call(e,n):r?r.value=n:t.set(e,n),n}var i,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}"function"==typeof SuppressedError&&SuppressedError;class s{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),this.id=a((e=>{t(this,i,"f").call(this,e)}))}set onmessage(e){n(this,i,e,"f")}get onmessage(){return t(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}i=new WeakMap;class l{constructor(e,t,n){this.plugin=e,this.event=t,this.channelId=n}async unregister(){return o(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function o(e,t={},n){return window.__TAURI_INTERNALS__.invoke(e,t,n)}class u{get rid(){return t(this,r,"f")}constructor(e){r.set(this,void 0),n(this,r,e,"f")}async close(){return o("plugin:resources|close",{rid:this.rid})}}r=new WeakMap;var c=Object.freeze({__proto__:null,Channel:s,PluginListener:l,Resource:u,addPluginListener:async function(e,t,n){const i=new s;return i.onmessage=n,o(`plugin:${e}|register_listener`,{event:t,handler:i}).then((()=>new l(e,t,i.id)))},convertFileSrc:function(e,t="asset"){return window.__TAURI_INTERNALS__.convertFileSrc(e,t)},invoke:o,transformCallback:a});var d,p=Object.freeze({__proto__:null,getName:async function(){return o("plugin:app|name")},getTauriVersion:async function(){return o("plugin:app|tauri_version")},getVersion:async function(){return o("plugin:app|version")},hide:async function(){return o("plugin:app|app_hide")},show:async function(){return o("plugin:app|app_show")}});async function h(e,t){await o("plugin:event|unlisten",{event:e,eventId:t})}async function y(e,t,n){const i="string"==typeof n?.target?{kind:"AnyLabel",label:n.target}:n?.target??{kind:"Any"};return o("plugin:event|listen",{event:e,target:i,handler:a(t)}).then((t=>async()=>h(e,t)))}async function w(e,t,n){return y(e,(n=>{t(n),h(e,n.id).catch((()=>{}))}),n)}async function g(e,t){await o("plugin:event|emit",{event:e,payload:t})}async function b(e,t,n){const i="string"==typeof e?{kind:"AnyLabel",label:e}:e;await o("plugin:event|emit_to",{target:i,event:t,payload:n})}!function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WEBVIEW_CREATED="tauri://webview-created",e.WEBVIEW_FILE_DROP="tauri://file-drop",e.WEBVIEW_FILE_DROP_HOVER="tauri://file-drop-hover",e.WEBVIEW_FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(d||(d={}));var _=Object.freeze({__proto__:null,get TauriEvent(){return d},emit:g,emitTo:b,listen:y,once:w});class m{constructor(e,t){this.type="Logical",this.width=e,this.height=t}}class f{constructor(e,t){this.type="Physical",this.width=e,this.height=t}toLogical(e){return new m(this.width/e,this.height/e)}}class v{constructor(e,t){this.type="Logical",this.x=e,this.y=t}}class k{constructor(e,t){this.type="Physical",this.x=e,this.y=t}toLogical(e){return new v(this.x/e,this.y/e)}}var E,A,D=Object.freeze({__proto__:null,LogicalPosition:v,LogicalSize:m,PhysicalPosition:k,PhysicalSize:f});!function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"}(E||(E={}));class P{constructor(e){this._preventDefault=!1,this.event=e.event,this.id=e.id}preventDefault(){this._preventDefault=!0}isPreventDefault(){return this._preventDefault}}function L(){return new T(window.__TAURI_INTERNALS__.metadata.currentWindow.label,{skip:!0})}function I(){return window.__TAURI_INTERNALS__.metadata.windows.map((e=>new T(e.label,{skip:!0})))}!function(e){e.None="none",e.Normal="normal",e.Indeterminate="indeterminate",e.Paused="paused",e.Error="error"}(A||(A={}));const S=["tauri://created","tauri://error"];class T{constructor(e,t={}){this.label=e,this.listeners=Object.create(null),t?.skip||o("plugin:window|create",{options:{...t,parent:"string"==typeof t.parent?t.parent:t.parent?.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static getByLabel(e){return I().find((t=>t.label===e))??null}static getCurrent(){return L()}static getAll(){return I()}static async getFocusedWindow(){for(const e of I())if(await e.isFocused())return e;return null}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):y(e,t,{target:{kind:"Window",label:this.label}})}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):w(e,t,{target:{kind:"Window",label:this.label}})}async emit(e,t){if(S.includes(e)){for(const n of this.listeners[e]||[])n({event:e,id:-1,payload:t});return Promise.resolve()}return g(e,t)}async emitTo(e,t,n){if(S.includes(t)){for(const e of this.listeners[t]||[])e({event:t,id:-1,payload:n});return Promise.resolve()}return b(e,t,n)}_handleTauriEvent(e,t){return!!S.includes(e)&&(e in this.listeners?this.listeners[e].push(t):this.listeners[e]=[t],!0)}async scaleFactor(){return o("plugin:window|scale_factor",{label:this.label})}async innerPosition(){return o("plugin:window|inner_position",{label:this.label}).then((({x:e,y:t})=>new k(e,t)))}async outerPosition(){return o("plugin:window|outer_position",{label:this.label}).then((({x:e,y:t})=>new k(e,t)))}async innerSize(){return o("plugin:window|inner_size",{label:this.label}).then((({width:e,height:t})=>new f(e,t)))}async outerSize(){return o("plugin:window|outer_size",{label:this.label}).then((({width:e,height:t})=>new f(e,t)))}async isFullscreen(){return o("plugin:window|is_fullscreen",{label:this.label})}async isMinimized(){return o("plugin:window|is_minimized",{label:this.label})}async isMaximized(){return o("plugin:window|is_maximized",{label:this.label})}async isFocused(){return o("plugin:window|is_focused",{label:this.label})}async isDecorated(){return o("plugin:window|is_decorated",{label:this.label})}async isResizable(){return o("plugin:window|is_resizable",{label:this.label})}async isMaximizable(){return o("plugin:window|is_maximizable",{label:this.label})}async isMinimizable(){return o("plugin:window|is_minimizable",{label:this.label})}async isClosable(){return o("plugin:window|is_closable",{label:this.label})}async isVisible(){return o("plugin:window|is_visible",{label:this.label})}async title(){return o("plugin:window|title",{label:this.label})}async theme(){return o("plugin:window|theme",{label:this.label})}async center(){return o("plugin:window|center",{label:this.label})}async requestUserAttention(e){let t=null;return e&&(t=e===E.Critical?{type:"Critical"}:{type:"Informational"}),o("plugin:window|request_user_attention",{label:this.label,value:t})}async setResizable(e){return o("plugin:window|set_resizable",{label:this.label,value:e})}async setMaximizable(e){return o("plugin:window|set_maximizable",{label:this.label,value:e})}async setMinimizable(e){return o("plugin:window|set_minimizable",{label:this.label,value:e})}async setClosable(e){return o("plugin:window|set_closable",{label:this.label,value:e})}async setTitle(e){return o("plugin:window|set_title",{label:this.label,value:e})}async maximize(){return o("plugin:window|maximize",{label:this.label})}async unmaximize(){return o("plugin:window|unmaximize",{label:this.label})}async toggleMaximize(){return o("plugin:window|toggle_maximize",{label:this.label})}async minimize(){return o("plugin:window|minimize",{label:this.label})}async unminimize(){return o("plugin:window|unminimize",{label:this.label})}async show(){return o("plugin:window|show",{label:this.label})}async hide(){return o("plugin:window|hide",{label:this.label})}async close(){return o("plugin:window|close",{label:this.label})}async destroy(){return o("plugin:window|destroy",{label:this.label})}async setDecorations(e){return o("plugin:window|set_decorations",{label:this.label,value:e})}async setShadow(e){return o("plugin:window|set_shadow",{label:this.label,value:e})}async setEffects(e){return o("plugin:window|set_effects",{label:this.label,value:e})}async clearEffects(){return o("plugin:window|set_effects",{label:this.label,value:null})}async setAlwaysOnTop(e){return o("plugin:window|set_always_on_top",{label:this.label,value:e})}async setAlwaysOnBottom(e){return o("plugin:window|set_always_on_bottom",{label:this.label,value:e})}async setContentProtected(e){return o("plugin:window|set_content_protected",{label:this.label,value:e})}async setSize(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:window|set_size",{label:this.label,value:{type:e.type,data:{width:e.width,height:e.height}}})}async setMinSize(e){if(e&&"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:window|set_min_size",{label:this.label,value:e?{type:e.type,data:{width:e.width,height:e.height}}:null})}async setMaxSize(e){if(e&&"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:window|set_max_size",{label:this.label,value:e?{type:e.type,data:{width:e.width,height:e.height}}:null})}async setPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return o("plugin:window|set_position",{label:this.label,value:{type:e.type,data:{x:e.x,y:e.y}}})}async setFullscreen(e){return o("plugin:window|set_fullscreen",{label:this.label,value:e})}async setFocus(){return o("plugin:window|set_focus",{label:this.label})}async setIcon(e){return o("plugin:window|set_icon",{label:this.label,value:"string"==typeof e?e:Array.from(e)})}async setSkipTaskbar(e){return o("plugin:window|set_skip_taskbar",{label:this.label,value:e})}async setCursorGrab(e){return o("plugin:window|set_cursor_grab",{label:this.label,value:e})}async setCursorVisible(e){return o("plugin:window|set_cursor_visible",{label:this.label,value:e})}async setCursorIcon(e){return o("plugin:window|set_cursor_icon",{label:this.label,value:e})}async setCursorPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return o("plugin:window|set_cursor_position",{label:this.label,value:{type:e.type,data:{x:e.x,y:e.y}}})}async setIgnoreCursorEvents(e){return o("plugin:window|set_ignore_cursor_events",{label:this.label,value:e})}async startDragging(){return o("plugin:window|start_dragging",{label:this.label})}async startResizeDragging(e){return o("plugin:window|start_resize_dragging",{label:this.label,value:e})}async setProgressBar(e){return o("plugin:window|set_progress_bar",{label:this.label,value:e})}async setVisibleOnAllWorkspaces(e){return o("plugin:window|set_visible_on_all_workspaces",{label:this.label,value:e})}async onResized(e){return this.listen(d.WINDOW_RESIZED,(t=>{t.payload=R(t.payload),e(t)}))}async onMoved(e){return this.listen(d.WINDOW_MOVED,(t=>{t.payload=W(t.payload),e(t)}))}async onCloseRequested(e){return this.listen(d.WINDOW_CLOSE_REQUESTED,(t=>{const n=new P(t);Promise.resolve(e(n)).then((()=>{if(!n.isPreventDefault())return this.destroy()}))}))}async onFocusChanged(e){const t=await this.listen(d.WINDOW_FOCUS,(t=>{e({...t,payload:!0})})),n=await this.listen(d.WINDOW_BLUR,(t=>{e({...t,payload:!1})}));return()=>{t(),n()}}async onScaleChanged(e){return this.listen(d.WINDOW_SCALE_FACTOR_CHANGED,e)}async onThemeChanged(e){return this.listen(d.WINDOW_THEME_CHANGED,e)}}var C,x;function z(e){return null===e?null:{name:e.name,scaleFactor:e.scaleFactor,position:W(e.position),size:R(e.size)}}function W(e){return new k(e.x,e.y)}function R(e){return new f(e.width,e.height)}!function(e){e.AppearanceBased="appearanceBased",e.Light="light",e.Dark="dark",e.MediumLight="mediumLight",e.UltraDark="ultraDark",e.Titlebar="titlebar",e.Selection="selection",e.Menu="menu",e.Popover="popover",e.Sidebar="sidebar",e.HeaderView="headerView",e.Sheet="sheet",e.WindowBackground="windowBackground",e.HudWindow="hudWindow",e.FullScreenUI="fullScreenUI",e.Tooltip="tooltip",e.ContentBackground="contentBackground",e.UnderWindowBackground="underWindowBackground",e.UnderPageBackground="underPageBackground",e.Mica="mica",e.Blur="blur",e.Acrylic="acrylic",e.Tabbed="tabbed",e.TabbedDark="tabbedDark",e.TabbedLight="tabbedLight"}(C||(C={})),function(e){e.FollowsWindowActiveState="followsWindowActiveState",e.Active="active",e.Inactive="inactive"}(x||(x={}));var F=Object.freeze({__proto__:null,CloseRequestedEvent:P,get Effect(){return C},get EffectState(){return x},LogicalPosition:v,LogicalSize:m,PhysicalPosition:k,PhysicalSize:f,get ProgressBarStatus(){return A},get UserAttentionType(){return E},Window:T,availableMonitors:async function(){return o("plugin:window|available_monitors").then((e=>e.map(z)))},currentMonitor:async function(){return o("plugin:window|current_monitor").then(z)},getAll:I,getCurrent:L,primaryMonitor:async function(){return o("plugin:window|primary_monitor").then(z)}});function O(){return new U(L(),window.__TAURI_INTERNALS__.metadata.currentWebview.label,{skip:!0})}function N(){return window.__TAURI_INTERNALS__.metadata.webviews.map((e=>new U(T.getByLabel(e.windowLabel),e.label,{skip:!0})))}const M=["tauri://created","tauri://error"];class U{constructor(e,t,n){this.window=e,this.label=t,this.listeners=Object.create(null),n?.skip||o("plugin:webview|create_webview",{windowLabel:e.label,label:t,options:n}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static getByLabel(e){return N().find((t=>t.label===e))??null}static getCurrent(){return O()}static getAll(){return N()}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):y(e,t,{target:{kind:"Webview",label:this.label}})}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):w(e,t,{target:{kind:"Webview",label:this.label}})}async emit(e,t){if(M.includes(e)){for(const n of this.listeners[e]||[])n({event:e,id:-1,payload:t});return Promise.resolve()}return g(e,t)}async emitTo(e,t,n){if(M.includes(t)){for(const e of this.listeners[t]||[])e({event:t,id:-1,payload:n});return Promise.resolve()}return b(e,t,n)}_handleTauriEvent(e,t){return!!M.includes(e)&&(e in this.listeners?this.listeners[e].push(t):this.listeners[e]=[t],!0)}async position(){return o("plugin:webview|webview_position",{label:this.label}).then((({x:e,y:t})=>new k(e,t)))}async size(){return o("plugin:webview|webview_size",{label:this.label}).then((({width:e,height:t})=>new f(e,t)))}async close(){return o("plugin:webview|close",{label:this.label})}async setSize(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:webview|set_webview_size",{label:this.label,value:{type:e.type,data:{width:e.width,height:e.height}}})}async setPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return o("plugin:webview|set_webview_position",{label:this.label,value:{type:e.type,data:{x:e.x,y:e.y}}})}async setFocus(){return o("plugin:webview|set_webview_focus",{label:this.label})}async onFileDropEvent(e){const t=await this.listen(d.WEBVIEW_FILE_DROP,(t=>{e({...t,payload:{type:"drop",paths:t.payload.paths,position:B(t.payload.position)}})})),n=await this.listen(d.WEBVIEW_FILE_DROP_HOVER,(t=>{e({...t,payload:{type:"hover",paths:t.payload.paths,position:B(t.payload.position)}})})),i=await this.listen(d.WEBVIEW_FILE_DROP_CANCELLED,(t=>{e({...t,payload:{type:"cancel"}})}));return()=>{t(),n(),i()}}}function B(e){return new k(e.x,e.y)}class V{constructor(e,t={}){this.label=e,this.listeners=Object.create(null),t?.skip||o("plugin:webview|create_webview_window",{options:{...t,parent:"string"==typeof t.parent?t.parent:t.parent?.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static getByLabel(e){const t=N().find((t=>t.label===e))??null;return t?new V(t.label,{skip:!0}):null}static getCurrent(){const e=O();return new V(e.label,{skip:!0})}static getAll(){return N().map((e=>new V(e.label,{skip:!0})))}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):y(e,t,{target:{kind:"WebviewWindow",label:this.label}})}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):w(e,t,{target:{kind:"WebviewWindow",label:this.label}})}}var j,H;j=V,H=[T,U],(Array.isArray(H)?H:[H]).forEach((e=>{Object.getOwnPropertyNames(e.prototype).forEach((t=>{"object"==typeof j.prototype&&j.prototype&&t in j.prototype||Object.defineProperty(j.prototype,t,Object.getOwnPropertyDescriptor(e.prototype,t)??Object.create(null))}))}));var G,q=Object.freeze({__proto__:null,Webview:U,WebviewWindow:V,getAll:N,getCurrent:O});!function(e){e[e.Audio=1]="Audio",e[e.Cache=2]="Cache",e[e.Config=3]="Config",e[e.Data=4]="Data",e[e.LocalData=5]="LocalData",e[e.Document=6]="Document",e[e.Download=7]="Download",e[e.Picture=8]="Picture",e[e.Public=9]="Public",e[e.Video=10]="Video",e[e.Resource=11]="Resource",e[e.Temp=12]="Temp",e[e.AppConfig=13]="AppConfig",e[e.AppData=14]="AppData",e[e.AppLocalData=15]="AppLocalData",e[e.AppCache=16]="AppCache",e[e.AppLog=17]="AppLog",e[e.Desktop=18]="Desktop",e[e.Executable=19]="Executable",e[e.Font=20]="Font",e[e.Home=21]="Home",e[e.Runtime=22]="Runtime",e[e.Template=23]="Template"}(G||(G={}));var Q=Object.freeze({__proto__:null,get BaseDirectory(){return G},appCacheDir:async function(){return o("plugin:path|resolve_directory",{directory:G.AppCache})},appConfigDir:async function(){return o("plugin:path|resolve_directory",{directory:G.AppConfig})},appDataDir:async function(){return o("plugin:path|resolve_directory",{directory:G.AppData})},appLocalDataDir:async function(){return o("plugin:path|resolve_directory",{directory:G.AppLocalData})},appLogDir:async function(){return o("plugin:path|resolve_directory",{directory:G.AppLog})},audioDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Audio})},basename:async function(e,t){return o("plugin:path|basename",{path:e,ext:t})},cacheDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Cache})},configDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Config})},dataDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Data})},delimiter:function(){return window.__TAURI_INTERNALS__.plugins.path.delimiter},desktopDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Desktop})},dirname:async function(e){return o("plugin:path|dirname",{path:e})},documentDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Document})},downloadDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Download})},executableDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Executable})},extname:async function(e){return o("plugin:path|extname",{path:e})},fontDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Font})},homeDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Home})},isAbsolute:async function(e){return o("plugin:path|isAbsolute",{path:e})},join:async function(...e){return o("plugin:path|join",{paths:e})},localDataDir:async function(){return o("plugin:path|resolve_directory",{directory:G.LocalData})},normalize:async function(e){return o("plugin:path|normalize",{path:e})},pictureDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Picture})},publicDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Public})},resolve:async function(...e){return o("plugin:path|resolve",{paths:e})},resolveResource:async function(e){return o("plugin:path|resolve_directory",{directory:G.Resource,path:e})},resourceDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Resource})},runtimeDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Runtime})},sep:function(){return window.__TAURI_INTERNALS__.plugins.path.sep},tempDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Temp})},templateDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Template})},videoDir:async function(){return o("plugin:path|resolve_directory",{directory:G.Video})}});class $ extends u{constructor(e,t){super(e),this.id=t}static async new(e){e?.menu&&(e.menu=[e.menu.rid,e.menu.kind]),e?.icon&&(e.icon="string"==typeof e.icon?e.icon:Array.from(e.icon));const t=new s;return e?.action&&(t.onmessage=e.action,delete e.action),o("plugin:tray|new",{options:e??{},handler:t}).then((([e,t])=>new $(e,t)))}async setIcon(e){let t=null;return e&&(t="string"==typeof e?e:Array.from(e)),o("plugin:tray|set_icon",{rid:this.rid,icon:t})}async setMenu(e){return e&&(e=[e.rid,e.kind]),o("plugin:tray|set_menu",{rid:this.rid,menu:e})}async setTooltip(e){return o("plugin:tray|set_tooltip",{rid:this.rid,tooltip:e})}async setTitle(e){return o("plugin:tray|set_title",{rid:this.rid,title:e})}async setVisible(e){return o("plugin:tray|set_visible",{rid:this.rid,visible:e})}async setTempDirPath(e){return o("plugin:tray|set_temp_dir_path",{rid:this.rid,path:e})}async setIconAsTemplate(e){return o("plugin:tray|set_icon_as_template",{rid:this.rid,asTemplate:e})}async setMenuOnLeftClick(e){return o("plugin:tray|set_show_menu_on_left_click",{rid:this.rid,onLeft:e})}}var Z,J,K,Y=Object.freeze({__proto__:null,TrayIcon:$});function X(e){if("items"in e)e.items=e.items?.map((e=>"rid"in e?e:X(e)));else if("action"in e&&e.action){const t=new s;return t.onmessage=e.action,delete e.action,{...e,handler:t}}return e}async function ee(e,t){const n=new s;let i=null;return t&&"object"==typeof t&&("action"in t&&t.action&&(n.onmessage=t.action,delete t.action),"items"in t&&t.items&&(i=t.items.map((e=>"rid"in e?[e.rid,e.kind]:X(e))))),o("plugin:menu|new",{kind:e,options:t?{...t,items:i}:void 0,handler:n})}class te extends u{get id(){return t(this,Z,"f")}get kind(){return t(this,J,"f")}constructor(e,t,i){super(e),Z.set(this,void 0),J.set(this,void 0),n(this,Z,t,"f"),n(this,J,i,"f")}}Z=new WeakMap,J=new WeakMap;class ne extends te{constructor(e,t){super(e,t,"MenuItem")}static async new(e){return ee("MenuItem",e).then((([e,t])=>new ne(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return o("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}}class ie extends te{constructor(e,t){super(e,t,"Check")}static async new(e){return ee("Check",e).then((([e,t])=>new ie(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return o("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async isChecked(){return o("plugin:menu|is_checked",{rid:this.rid})}async setChecked(e){return o("plugin:menu|set_checked",{rid:this.rid,checked:e})}}!function(e){e.Add="Add",e.Advanced="Advanced",e.Bluetooth="Bluetooth",e.Bookmarks="Bookmarks",e.Caution="Caution",e.ColorPanel="ColorPanel",e.ColumnView="ColumnView",e.Computer="Computer",e.EnterFullScreen="EnterFullScreen",e.Everyone="Everyone",e.ExitFullScreen="ExitFullScreen",e.FlowView="FlowView",e.Folder="Folder",e.FolderBurnable="FolderBurnable",e.FolderSmart="FolderSmart",e.FollowLinkFreestanding="FollowLinkFreestanding",e.FontPanel="FontPanel",e.GoLeft="GoLeft",e.GoRight="GoRight",e.Home="Home",e.IChatTheater="IChatTheater",e.IconView="IconView",e.Info="Info",e.InvalidDataFreestanding="InvalidDataFreestanding",e.LeftFacingTriangle="LeftFacingTriangle",e.ListView="ListView",e.LockLocked="LockLocked",e.LockUnlocked="LockUnlocked",e.MenuMixedState="MenuMixedState",e.MenuOnState="MenuOnState",e.MobileMe="MobileMe",e.MultipleDocuments="MultipleDocuments",e.Network="Network",e.Path="Path",e.PreferencesGeneral="PreferencesGeneral",e.QuickLook="QuickLook",e.RefreshFreestanding="RefreshFreestanding",e.Refresh="Refresh",e.Remove="Remove",e.RevealFreestanding="RevealFreestanding",e.RightFacingTriangle="RightFacingTriangle",e.Share="Share",e.Slideshow="Slideshow",e.SmartBadge="SmartBadge",e.StatusAvailable="StatusAvailable",e.StatusNone="StatusNone",e.StatusPartiallyAvailable="StatusPartiallyAvailable",e.StatusUnavailable="StatusUnavailable",e.StopProgressFreestanding="StopProgressFreestanding",e.StopProgress="StopProgress",e.TrashEmpty="TrashEmpty",e.TrashFull="TrashFull",e.User="User",e.UserAccounts="UserAccounts",e.UserGroup="UserGroup",e.UserGuest="UserGuest"}(K||(K={}));class re extends te{constructor(e,t){super(e,t,"Icon")}static async new(e){return ee("Icon",e).then((([e,t])=>new re(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return o("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async setIcon(e){return o("plugin:menu|set_icon",{rid:this.rid,icon:e})}}class ae extends te{constructor(e,t){super(e,t,"Predefined")}static async new(e){return ee("Predefined",e).then((([e,t])=>new ae(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}}function se([e,t,n]){switch(n){case"Submenu":return new le(e,t);case"Predefined":return new ae(e,t);case"Check":return new ie(e,t);case"Icon":return new re(e,t);default:return new ne(e,t)}}class le extends te{constructor(e,t){super(e,t,"Submenu")}static async new(e){return ee("Submenu",e).then((([e,t])=>new le(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async append(e){return o("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return o("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,t){return o("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:t})}async remove(e){return o("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return o("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(se)}async items(){return o("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(se)))}async get(e){return o("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?se(e):null))}async popup(e,t){let n=null;return e&&(n={type:e instanceof k?"Physical":"Logical",data:e}),o("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:t?.label??null,at:n})}async setAsWindowsMenuForNSApp(){return o("plugin:menu|set_as_windows_menu_for_nsapp",{rid:this.rid})}async setAsHelpMenuForNSApp(){return o("plugin:menu|set_as_help_menu_for_nsapp",{rid:this.rid})}}function oe([e,t,n]){switch(n){case"Submenu":return new le(e,t);case"Predefined":return new ae(e,t);case"Check":return new ie(e,t);case"Icon":return new re(e,t);default:return new ne(e,t)}}class ue extends te{constructor(e,t){super(e,t,"Menu")}static async new(e){return ee("Menu",e).then((([e,t])=>new ue(e,t)))}static async default(){return o("plugin:menu|create_default").then((([e,t])=>new ue(e,t)))}async append(e){return o("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return o("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,t){return o("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:t})}async remove(e){return o("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return o("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(oe)}async items(){return o("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(oe)))}async get(e){return o("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?oe(e):null))}async popup(e,t){let n=null;return e&&(n={type:e instanceof k?"Physical":"Logical",data:e}),o("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:t?.label??null,at:n})}async setAsAppMenu(){return o("plugin:menu|set_as_app_menu",{rid:this.rid}).then((e=>e?new ue(e[0],e[1]):null))}async setAsWindowMenu(e){return o("plugin:menu|set_as_window_menu",{rid:this.rid,window:e?.label??null}).then((e=>e?new ue(e[0],e[1]):null))}}var ce=Object.freeze({__proto__:null,CheckMenuItem:ie,IconMenuItem:re,Menu:ue,MenuItem:ne,get NativeIcon(){return K},PredefinedMenuItem:ae,Submenu:le});return e.app=p,e.core=c,e.dpi=D,e.event=_,e.menu=ce,e.path=Q,e.tray=Y,e.webview=q,e.window=F,e}({});window.__TAURI__=__TAURI_IIFE__; +var __TAURI_IIFE__=function(e){"use strict";function t(e,t,n,i){if("a"===n&&!i)throw new TypeError("Private accessor was defined without a getter");if("function"==typeof t?e!==t||!i:!t.has(e))throw new TypeError("Cannot read private member from an object whose class did not declare it");return"m"===n?i:"a"===n?i.call(e):i?i.value:t.get(e)}function n(e,t,n,i,r){if("m"===i)throw new TypeError("Private method is not writable");if("a"===i&&!r)throw new TypeError("Private accessor was defined without a setter");if("function"==typeof t?e!==t||!r:!t.has(e))throw new TypeError("Cannot write private member to an object whose class did not declare it");return"a"===i?r.call(e,n):r?r.value=n:t.set(e,n),n}var i,r;function a(e,t=!1){return window.__TAURI_INTERNALS__.transformCallback(e,t)}"function"==typeof SuppressedError&&SuppressedError;class s{constructor(){this.__TAURI_CHANNEL_MARKER__=!0,i.set(this,(()=>{})),this.id=a((e=>{t(this,i,"f").call(this,e)}))}set onmessage(e){n(this,i,e,"f")}get onmessage(){return t(this,i,"f")}toJSON(){return`__CHANNEL__:${this.id}`}}i=new WeakMap;class l{constructor(e,t,n){this.plugin=e,this.event=t,this.channelId=n}async unregister(){return o(`plugin:${this.plugin}|remove_listener`,{event:this.event,channelId:this.channelId})}}async function o(e,t={},n){return window.__TAURI_INTERNALS__.invoke(e,t,n)}class u{get rid(){return t(this,r,"f")}constructor(e){r.set(this,void 0),n(this,r,e,"f")}async close(){return o("plugin:resources|close",{rid:this.rid})}}r=new WeakMap;var c=Object.freeze({__proto__:null,Channel:s,PluginListener:l,Resource:u,addPluginListener:async function(e,t,n){const i=new s;return i.onmessage=n,o(`plugin:${e}|register_listener`,{event:t,handler:i}).then((()=>new l(e,t,i.id)))},convertFileSrc:function(e,t="asset"){return window.__TAURI_INTERNALS__.convertFileSrc(e,t)},invoke:o,transformCallback:a});var d,p=Object.freeze({__proto__:null,getName:async function(){return o("plugin:app|name")},getTauriVersion:async function(){return o("plugin:app|tauri_version")},getVersion:async function(){return o("plugin:app|version")},hide:async function(){return o("plugin:app|app_hide")},show:async function(){return o("plugin:app|app_show")}});async function h(e,t){await o("plugin:event|unlisten",{event:e,eventId:t})}async function y(e,t,n){const i="string"==typeof n?.target?{kind:"AnyLabel",label:n.target}:n?.target??{kind:"Any"};return o("plugin:event|listen",{event:e,target:i,handler:a(t)}).then((t=>async()=>h(e,t)))}async function w(e,t,n){return y(e,(n=>{t(n),h(e,n.id).catch((()=>{}))}),n)}async function g(e,t){await o("plugin:event|emit",{event:e,payload:t})}async function b(e,t,n){const i="string"==typeof e?{kind:"AnyLabel",label:e}:e;await o("plugin:event|emit_to",{target:i,event:t,payload:n})}!function(e){e.WINDOW_RESIZED="tauri://resize",e.WINDOW_MOVED="tauri://move",e.WINDOW_CLOSE_REQUESTED="tauri://close-requested",e.WINDOW_DESTROYED="tauri://destroyed",e.WINDOW_FOCUS="tauri://focus",e.WINDOW_BLUR="tauri://blur",e.WINDOW_SCALE_FACTOR_CHANGED="tauri://scale-change",e.WINDOW_THEME_CHANGED="tauri://theme-changed",e.WEBVIEW_CREATED="tauri://webview-created",e.FILE_DROP="tauri://file-drop",e.FILE_DROP_HOVER="tauri://file-drop-hover",e.FILE_DROP_CANCELLED="tauri://file-drop-cancelled"}(d||(d={}));var _=Object.freeze({__proto__:null,get TauriEvent(){return d},emit:g,emitTo:b,listen:y,once:w});class m{constructor(e,t){this.type="Logical",this.width=e,this.height=t}}class f{constructor(e,t){this.type="Physical",this.width=e,this.height=t}toLogical(e){return new m(this.width/e,this.height/e)}}class v{constructor(e,t){this.type="Logical",this.x=e,this.y=t}}class k{constructor(e,t){this.type="Physical",this.x=e,this.y=t}toLogical(e){return new v(this.x/e,this.y/e)}}var A,E,D=Object.freeze({__proto__:null,LogicalPosition:v,LogicalSize:m,PhysicalPosition:k,PhysicalSize:f});!function(e){e[e.Critical=1]="Critical",e[e.Informational=2]="Informational"}(A||(A={}));class L{constructor(e){this._preventDefault=!1,this.event=e.event,this.id=e.id}preventDefault(){this._preventDefault=!0}isPreventDefault(){return this._preventDefault}}function P(){return new T(window.__TAURI_INTERNALS__.metadata.currentWindow.label,{skip:!0})}function I(){return window.__TAURI_INTERNALS__.metadata.windows.map((e=>new T(e.label,{skip:!0})))}!function(e){e.None="none",e.Normal="normal",e.Indeterminate="indeterminate",e.Paused="paused",e.Error="error"}(E||(E={}));const S=["tauri://created","tauri://error"];class T{constructor(e,t={}){this.label=e,this.listeners=Object.create(null),t?.skip||o("plugin:window|create",{options:{...t,parent:"string"==typeof t.parent?t.parent:t.parent?.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static getByLabel(e){return I().find((t=>t.label===e))??null}static getCurrent(){return P()}static getAll(){return I()}static async getFocusedWindow(){for(const e of I())if(await e.isFocused())return e;return null}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):y(e,t,{target:{kind:"Window",label:this.label}})}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):w(e,t,{target:{kind:"Window",label:this.label}})}async emit(e,t){if(S.includes(e)){for(const n of this.listeners[e]||[])n({event:e,id:-1,payload:t});return Promise.resolve()}return g(e,t)}async emitTo(e,t,n){if(S.includes(t)){for(const e of this.listeners[t]||[])e({event:t,id:-1,payload:n});return Promise.resolve()}return b(e,t,n)}_handleTauriEvent(e,t){return!!S.includes(e)&&(e in this.listeners?this.listeners[e].push(t):this.listeners[e]=[t],!0)}async scaleFactor(){return o("plugin:window|scale_factor",{label:this.label})}async innerPosition(){return o("plugin:window|inner_position",{label:this.label}).then((({x:e,y:t})=>new k(e,t)))}async outerPosition(){return o("plugin:window|outer_position",{label:this.label}).then((({x:e,y:t})=>new k(e,t)))}async innerSize(){return o("plugin:window|inner_size",{label:this.label}).then((({width:e,height:t})=>new f(e,t)))}async outerSize(){return o("plugin:window|outer_size",{label:this.label}).then((({width:e,height:t})=>new f(e,t)))}async isFullscreen(){return o("plugin:window|is_fullscreen",{label:this.label})}async isMinimized(){return o("plugin:window|is_minimized",{label:this.label})}async isMaximized(){return o("plugin:window|is_maximized",{label:this.label})}async isFocused(){return o("plugin:window|is_focused",{label:this.label})}async isDecorated(){return o("plugin:window|is_decorated",{label:this.label})}async isResizable(){return o("plugin:window|is_resizable",{label:this.label})}async isMaximizable(){return o("plugin:window|is_maximizable",{label:this.label})}async isMinimizable(){return o("plugin:window|is_minimizable",{label:this.label})}async isClosable(){return o("plugin:window|is_closable",{label:this.label})}async isVisible(){return o("plugin:window|is_visible",{label:this.label})}async title(){return o("plugin:window|title",{label:this.label})}async theme(){return o("plugin:window|theme",{label:this.label})}async center(){return o("plugin:window|center",{label:this.label})}async requestUserAttention(e){let t=null;return e&&(t=e===A.Critical?{type:"Critical"}:{type:"Informational"}),o("plugin:window|request_user_attention",{label:this.label,value:t})}async setResizable(e){return o("plugin:window|set_resizable",{label:this.label,value:e})}async setMaximizable(e){return o("plugin:window|set_maximizable",{label:this.label,value:e})}async setMinimizable(e){return o("plugin:window|set_minimizable",{label:this.label,value:e})}async setClosable(e){return o("plugin:window|set_closable",{label:this.label,value:e})}async setTitle(e){return o("plugin:window|set_title",{label:this.label,value:e})}async maximize(){return o("plugin:window|maximize",{label:this.label})}async unmaximize(){return o("plugin:window|unmaximize",{label:this.label})}async toggleMaximize(){return o("plugin:window|toggle_maximize",{label:this.label})}async minimize(){return o("plugin:window|minimize",{label:this.label})}async unminimize(){return o("plugin:window|unminimize",{label:this.label})}async show(){return o("plugin:window|show",{label:this.label})}async hide(){return o("plugin:window|hide",{label:this.label})}async close(){return o("plugin:window|close",{label:this.label})}async destroy(){return o("plugin:window|destroy",{label:this.label})}async setDecorations(e){return o("plugin:window|set_decorations",{label:this.label,value:e})}async setShadow(e){return o("plugin:window|set_shadow",{label:this.label,value:e})}async setEffects(e){return o("plugin:window|set_effects",{label:this.label,value:e})}async clearEffects(){return o("plugin:window|set_effects",{label:this.label,value:null})}async setAlwaysOnTop(e){return o("plugin:window|set_always_on_top",{label:this.label,value:e})}async setAlwaysOnBottom(e){return o("plugin:window|set_always_on_bottom",{label:this.label,value:e})}async setContentProtected(e){return o("plugin:window|set_content_protected",{label:this.label,value:e})}async setSize(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:window|set_size",{label:this.label,value:{type:e.type,data:{width:e.width,height:e.height}}})}async setMinSize(e){if(e&&"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:window|set_min_size",{label:this.label,value:e?{type:e.type,data:{width:e.width,height:e.height}}:null})}async setMaxSize(e){if(e&&"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:window|set_max_size",{label:this.label,value:e?{type:e.type,data:{width:e.width,height:e.height}}:null})}async setPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return o("plugin:window|set_position",{label:this.label,value:{type:e.type,data:{x:e.x,y:e.y}}})}async setFullscreen(e){return o("plugin:window|set_fullscreen",{label:this.label,value:e})}async setFocus(){return o("plugin:window|set_focus",{label:this.label})}async setIcon(e){return o("plugin:window|set_icon",{label:this.label,value:"string"==typeof e?e:Array.from(e)})}async setSkipTaskbar(e){return o("plugin:window|set_skip_taskbar",{label:this.label,value:e})}async setCursorGrab(e){return o("plugin:window|set_cursor_grab",{label:this.label,value:e})}async setCursorVisible(e){return o("plugin:window|set_cursor_visible",{label:this.label,value:e})}async setCursorIcon(e){return o("plugin:window|set_cursor_icon",{label:this.label,value:e})}async setCursorPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return o("plugin:window|set_cursor_position",{label:this.label,value:{type:e.type,data:{x:e.x,y:e.y}}})}async setIgnoreCursorEvents(e){return o("plugin:window|set_ignore_cursor_events",{label:this.label,value:e})}async startDragging(){return o("plugin:window|start_dragging",{label:this.label})}async startResizeDragging(e){return o("plugin:window|start_resize_dragging",{label:this.label,value:e})}async setProgressBar(e){return o("plugin:window|set_progress_bar",{label:this.label,value:e})}async setVisibleOnAllWorkspaces(e){return o("plugin:window|set_visible_on_all_workspaces",{label:this.label,value:e})}async onResized(e){return this.listen(d.WINDOW_RESIZED,(t=>{t.payload=F(t.payload),e(t)}))}async onMoved(e){return this.listen(d.WINDOW_MOVED,(t=>{t.payload=z(t.payload),e(t)}))}async onCloseRequested(e){return this.listen(d.WINDOW_CLOSE_REQUESTED,(t=>{const n=new L(t);Promise.resolve(e(n)).then((()=>{if(!n.isPreventDefault())return this.destroy()}))}))}async onFileDropEvent(e){const t=await this.listen(d.FILE_DROP,(t=>{e({...t,payload:{type:"drop",paths:t.payload.paths,position:z(t.payload.position)}})})),n=await this.listen(d.FILE_DROP_HOVER,(t=>{e({...t,payload:{type:"hover",paths:t.payload.paths,position:z(t.payload.position)}})})),i=await this.listen(d.FILE_DROP_CANCELLED,(t=>{e({...t,payload:{type:"cancel"}})}));return()=>{t(),n(),i()}}async onFocusChanged(e){const t=await this.listen(d.WINDOW_FOCUS,(t=>{e({...t,payload:!0})})),n=await this.listen(d.WINDOW_BLUR,(t=>{e({...t,payload:!1})}));return()=>{t(),n()}}async onScaleChanged(e){return this.listen(d.WINDOW_SCALE_FACTOR_CHANGED,e)}async onThemeChanged(e){return this.listen(d.WINDOW_THEME_CHANGED,e)}}var C,x;function R(e){return null===e?null:{name:e.name,scaleFactor:e.scaleFactor,position:z(e.position),size:F(e.size)}}function z(e){return new k(e.x,e.y)}function F(e){return new f(e.width,e.height)}!function(e){e.AppearanceBased="appearanceBased",e.Light="light",e.Dark="dark",e.MediumLight="mediumLight",e.UltraDark="ultraDark",e.Titlebar="titlebar",e.Selection="selection",e.Menu="menu",e.Popover="popover",e.Sidebar="sidebar",e.HeaderView="headerView",e.Sheet="sheet",e.WindowBackground="windowBackground",e.HudWindow="hudWindow",e.FullScreenUI="fullScreenUI",e.Tooltip="tooltip",e.ContentBackground="contentBackground",e.UnderWindowBackground="underWindowBackground",e.UnderPageBackground="underPageBackground",e.Mica="mica",e.Blur="blur",e.Acrylic="acrylic",e.Tabbed="tabbed",e.TabbedDark="tabbedDark",e.TabbedLight="tabbedLight"}(C||(C={})),function(e){e.FollowsWindowActiveState="followsWindowActiveState",e.Active="active",e.Inactive="inactive"}(x||(x={}));var O=Object.freeze({__proto__:null,CloseRequestedEvent:L,get Effect(){return C},get EffectState(){return x},LogicalPosition:v,LogicalSize:m,PhysicalPosition:k,PhysicalSize:f,get ProgressBarStatus(){return E},get UserAttentionType(){return A},Window:T,availableMonitors:async function(){return o("plugin:window|available_monitors").then((e=>e.map(R)))},currentMonitor:async function(){return o("plugin:window|current_monitor").then(R)},getAll:I,getCurrent:P,primaryMonitor:async function(){return o("plugin:window|primary_monitor").then(R)}});function W(){return new U(P(),window.__TAURI_INTERNALS__.metadata.currentWebview.label,{skip:!0})}function N(){return window.__TAURI_INTERNALS__.metadata.webviews.map((e=>new U(T.getByLabel(e.windowLabel),e.label,{skip:!0})))}const M=["tauri://created","tauri://error"];class U{constructor(e,t,n){this.window=e,this.label=t,this.listeners=Object.create(null),n?.skip||o("plugin:webview|create_webview",{windowLabel:e.label,label:t,options:n}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static getByLabel(e){return N().find((t=>t.label===e))??null}static getCurrent(){return W()}static getAll(){return N()}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):y(e,t,{target:{kind:"Webview",label:this.label}})}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):w(e,t,{target:{kind:"Webview",label:this.label}})}async emit(e,t){if(M.includes(e)){for(const n of this.listeners[e]||[])n({event:e,id:-1,payload:t});return Promise.resolve()}return g(e,t)}async emitTo(e,t,n){if(M.includes(t)){for(const e of this.listeners[t]||[])e({event:t,id:-1,payload:n});return Promise.resolve()}return b(e,t,n)}_handleTauriEvent(e,t){return!!M.includes(e)&&(e in this.listeners?this.listeners[e].push(t):this.listeners[e]=[t],!0)}async position(){return o("plugin:webview|webview_position",{label:this.label}).then((({x:e,y:t})=>new k(e,t)))}async size(){return o("plugin:webview|webview_size",{label:this.label}).then((({width:e,height:t})=>new f(e,t)))}async close(){return o("plugin:webview|close",{label:this.label})}async setSize(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `size` argument must be either a LogicalSize or a PhysicalSize instance");return o("plugin:webview|set_webview_size",{label:this.label,value:{type:e.type,data:{width:e.width,height:e.height}}})}async setPosition(e){if(!e||"Logical"!==e.type&&"Physical"!==e.type)throw new Error("the `position` argument must be either a LogicalPosition or a PhysicalPosition instance");return o("plugin:webview|set_webview_position",{label:this.label,value:{type:e.type,data:{x:e.x,y:e.y}}})}async setFocus(){return o("plugin:webview|set_webview_focus",{label:this.label})}async onFileDropEvent(e){const t=await this.listen(d.FILE_DROP,(t=>{e({...t,payload:{type:"drop",paths:t.payload.paths,position:B(t.payload.position)}})})),n=await this.listen(d.FILE_DROP_HOVER,(t=>{e({...t,payload:{type:"hover",paths:t.payload.paths,position:B(t.payload.position)}})})),i=await this.listen(d.FILE_DROP_CANCELLED,(t=>{e({...t,payload:{type:"cancel"}})}));return()=>{t(),n(),i()}}}function B(e){return new k(e.x,e.y)}var V,j,H=Object.freeze({__proto__:null,Webview:U,getAll:N,getCurrent:W});function G(){const e=W();return new Q(e.label,{skip:!0})}function q(){return window.__TAURI_INTERNALS__.metadata.webviews.map((e=>new Q(e.label,{skip:!0})))}class Q{constructor(e,t={}){this.label=e,this.listeners=Object.create(null),t?.skip||o("plugin:webview|create_webview_window",{options:{...t,parent:"string"==typeof t.parent?t.parent:t.parent?.label,label:e}}).then((async()=>this.emit("tauri://created"))).catch((async e=>this.emit("tauri://error",e)))}static getByLabel(e){const t=q().find((t=>t.label===e))??null;return t?new Q(t.label,{skip:!0}):null}static getCurrent(){return G()}static getAll(){return q().map((e=>new Q(e.label,{skip:!0})))}async listen(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):y(e,t,{target:{kind:"WebviewWindow",label:this.label}})}async once(e,t){return this._handleTauriEvent(e,t)?Promise.resolve((()=>{const n=this.listeners[e];n.splice(n.indexOf(t),1)})):w(e,t,{target:{kind:"WebviewWindow",label:this.label}})}}V=Q,j=[T,U],(Array.isArray(j)?j:[j]).forEach((e=>{Object.getOwnPropertyNames(e.prototype).forEach((t=>{"object"==typeof V.prototype&&V.prototype&&t in V.prototype||Object.defineProperty(V.prototype,t,Object.getOwnPropertyDescriptor(e.prototype,t)??Object.create(null))}))}));var $,Z=Object.freeze({__proto__:null,WebviewWindow:Q,getAll:q,getCurrent:G});!function(e){e[e.Audio=1]="Audio",e[e.Cache=2]="Cache",e[e.Config=3]="Config",e[e.Data=4]="Data",e[e.LocalData=5]="LocalData",e[e.Document=6]="Document",e[e.Download=7]="Download",e[e.Picture=8]="Picture",e[e.Public=9]="Public",e[e.Video=10]="Video",e[e.Resource=11]="Resource",e[e.Temp=12]="Temp",e[e.AppConfig=13]="AppConfig",e[e.AppData=14]="AppData",e[e.AppLocalData=15]="AppLocalData",e[e.AppCache=16]="AppCache",e[e.AppLog=17]="AppLog",e[e.Desktop=18]="Desktop",e[e.Executable=19]="Executable",e[e.Font=20]="Font",e[e.Home=21]="Home",e[e.Runtime=22]="Runtime",e[e.Template=23]="Template"}($||($={}));var J=Object.freeze({__proto__:null,get BaseDirectory(){return $},appCacheDir:async function(){return o("plugin:path|resolve_directory",{directory:$.AppCache})},appConfigDir:async function(){return o("plugin:path|resolve_directory",{directory:$.AppConfig})},appDataDir:async function(){return o("plugin:path|resolve_directory",{directory:$.AppData})},appLocalDataDir:async function(){return o("plugin:path|resolve_directory",{directory:$.AppLocalData})},appLogDir:async function(){return o("plugin:path|resolve_directory",{directory:$.AppLog})},audioDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Audio})},basename:async function(e,t){return o("plugin:path|basename",{path:e,ext:t})},cacheDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Cache})},configDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Config})},dataDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Data})},delimiter:function(){return window.__TAURI_INTERNALS__.plugins.path.delimiter},desktopDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Desktop})},dirname:async function(e){return o("plugin:path|dirname",{path:e})},documentDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Document})},downloadDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Download})},executableDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Executable})},extname:async function(e){return o("plugin:path|extname",{path:e})},fontDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Font})},homeDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Home})},isAbsolute:async function(e){return o("plugin:path|isAbsolute",{path:e})},join:async function(...e){return o("plugin:path|join",{paths:e})},localDataDir:async function(){return o("plugin:path|resolve_directory",{directory:$.LocalData})},normalize:async function(e){return o("plugin:path|normalize",{path:e})},pictureDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Picture})},publicDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Public})},resolve:async function(...e){return o("plugin:path|resolve",{paths:e})},resolveResource:async function(e){return o("plugin:path|resolve_directory",{directory:$.Resource,path:e})},resourceDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Resource})},runtimeDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Runtime})},sep:function(){return window.__TAURI_INTERNALS__.plugins.path.sep},tempDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Temp})},templateDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Template})},videoDir:async function(){return o("plugin:path|resolve_directory",{directory:$.Video})}});class K extends u{constructor(e,t){super(e),this.id=t}static async new(e){e?.menu&&(e.menu=[e.menu.rid,e.menu.kind]),e?.icon&&(e.icon="string"==typeof e.icon?e.icon:Array.from(e.icon));const t=new s;return e?.action&&(t.onmessage=e.action,delete e.action),o("plugin:tray|new",{options:e??{},handler:t}).then((([e,t])=>new K(e,t)))}async setIcon(e){let t=null;return e&&(t="string"==typeof e?e:Array.from(e)),o("plugin:tray|set_icon",{rid:this.rid,icon:t})}async setMenu(e){return e&&(e=[e.rid,e.kind]),o("plugin:tray|set_menu",{rid:this.rid,menu:e})}async setTooltip(e){return o("plugin:tray|set_tooltip",{rid:this.rid,tooltip:e})}async setTitle(e){return o("plugin:tray|set_title",{rid:this.rid,title:e})}async setVisible(e){return o("plugin:tray|set_visible",{rid:this.rid,visible:e})}async setTempDirPath(e){return o("plugin:tray|set_temp_dir_path",{rid:this.rid,path:e})}async setIconAsTemplate(e){return o("plugin:tray|set_icon_as_template",{rid:this.rid,asTemplate:e})}async setMenuOnLeftClick(e){return o("plugin:tray|set_show_menu_on_left_click",{rid:this.rid,onLeft:e})}}var Y,X,ee,te=Object.freeze({__proto__:null,TrayIcon:K});function ne(e){if("items"in e)e.items=e.items?.map((e=>"rid"in e?e:ne(e)));else if("action"in e&&e.action){const t=new s;return t.onmessage=e.action,delete e.action,{...e,handler:t}}return e}async function ie(e,t){const n=new s;let i=null;return t&&"object"==typeof t&&("action"in t&&t.action&&(n.onmessage=t.action,delete t.action),"items"in t&&t.items&&(i=t.items.map((e=>"rid"in e?[e.rid,e.kind]:ne(e))))),o("plugin:menu|new",{kind:e,options:t?{...t,items:i}:void 0,handler:n})}class re extends u{get id(){return t(this,Y,"f")}get kind(){return t(this,X,"f")}constructor(e,t,i){super(e),Y.set(this,void 0),X.set(this,void 0),n(this,Y,t,"f"),n(this,X,i,"f")}}Y=new WeakMap,X=new WeakMap;class ae extends re{constructor(e,t){super(e,t,"MenuItem")}static async new(e){return ie("MenuItem",e).then((([e,t])=>new ae(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return o("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}}class se extends re{constructor(e,t){super(e,t,"Check")}static async new(e){return ie("Check",e).then((([e,t])=>new se(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return o("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async isChecked(){return o("plugin:menu|is_checked",{rid:this.rid})}async setChecked(e){return o("plugin:menu|set_checked",{rid:this.rid,checked:e})}}!function(e){e.Add="Add",e.Advanced="Advanced",e.Bluetooth="Bluetooth",e.Bookmarks="Bookmarks",e.Caution="Caution",e.ColorPanel="ColorPanel",e.ColumnView="ColumnView",e.Computer="Computer",e.EnterFullScreen="EnterFullScreen",e.Everyone="Everyone",e.ExitFullScreen="ExitFullScreen",e.FlowView="FlowView",e.Folder="Folder",e.FolderBurnable="FolderBurnable",e.FolderSmart="FolderSmart",e.FollowLinkFreestanding="FollowLinkFreestanding",e.FontPanel="FontPanel",e.GoLeft="GoLeft",e.GoRight="GoRight",e.Home="Home",e.IChatTheater="IChatTheater",e.IconView="IconView",e.Info="Info",e.InvalidDataFreestanding="InvalidDataFreestanding",e.LeftFacingTriangle="LeftFacingTriangle",e.ListView="ListView",e.LockLocked="LockLocked",e.LockUnlocked="LockUnlocked",e.MenuMixedState="MenuMixedState",e.MenuOnState="MenuOnState",e.MobileMe="MobileMe",e.MultipleDocuments="MultipleDocuments",e.Network="Network",e.Path="Path",e.PreferencesGeneral="PreferencesGeneral",e.QuickLook="QuickLook",e.RefreshFreestanding="RefreshFreestanding",e.Refresh="Refresh",e.Remove="Remove",e.RevealFreestanding="RevealFreestanding",e.RightFacingTriangle="RightFacingTriangle",e.Share="Share",e.Slideshow="Slideshow",e.SmartBadge="SmartBadge",e.StatusAvailable="StatusAvailable",e.StatusNone="StatusNone",e.StatusPartiallyAvailable="StatusPartiallyAvailable",e.StatusUnavailable="StatusUnavailable",e.StopProgressFreestanding="StopProgressFreestanding",e.StopProgress="StopProgress",e.TrashEmpty="TrashEmpty",e.TrashFull="TrashFull",e.User="User",e.UserAccounts="UserAccounts",e.UserGroup="UserGroup",e.UserGuest="UserGuest"}(ee||(ee={}));class le extends re{constructor(e,t){super(e,t,"Icon")}static async new(e){return ie("Icon",e).then((([e,t])=>new le(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async setAccelerator(e){return o("plugin:menu|set_accelerator",{rid:this.rid,kind:this.kind,accelerator:e})}async setIcon(e){return o("plugin:menu|set_icon",{rid:this.rid,icon:e})}}class oe extends re{constructor(e,t){super(e,t,"Predefined")}static async new(e){return ie("Predefined",e).then((([e,t])=>new oe(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}}function ue([e,t,n]){switch(n){case"Submenu":return new ce(e,t);case"Predefined":return new oe(e,t);case"Check":return new se(e,t);case"Icon":return new le(e,t);default:return new ae(e,t)}}class ce extends re{constructor(e,t){super(e,t,"Submenu")}static async new(e){return ie("Submenu",e).then((([e,t])=>new ce(e,t)))}async text(){return o("plugin:menu|text",{rid:this.rid,kind:this.kind})}async setText(e){return o("plugin:menu|set_text",{rid:this.rid,kind:this.kind,text:e})}async isEnabled(){return o("plugin:menu|is_enabled",{rid:this.rid,kind:this.kind})}async setEnabled(e){return o("plugin:menu|set_enabled",{rid:this.rid,kind:this.kind,enabled:e})}async append(e){return o("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return o("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,t){return o("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:t})}async remove(e){return o("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return o("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(ue)}async items(){return o("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(ue)))}async get(e){return o("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?ue(e):null))}async popup(e,t){let n=null;return e&&(n={type:e instanceof k?"Physical":"Logical",data:e}),o("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:t?.label??null,at:n})}async setAsWindowsMenuForNSApp(){return o("plugin:menu|set_as_windows_menu_for_nsapp",{rid:this.rid})}async setAsHelpMenuForNSApp(){return o("plugin:menu|set_as_help_menu_for_nsapp",{rid:this.rid})}}function de([e,t,n]){switch(n){case"Submenu":return new ce(e,t);case"Predefined":return new oe(e,t);case"Check":return new se(e,t);case"Icon":return new le(e,t);default:return new ae(e,t)}}class pe extends re{constructor(e,t){super(e,t,"Menu")}static async new(e){return ie("Menu",e).then((([e,t])=>new pe(e,t)))}static async default(){return o("plugin:menu|create_default").then((([e,t])=>new pe(e,t)))}async append(e){return o("plugin:menu|append",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async prepend(e){return o("plugin:menu|prepend",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e))})}async insert(e,t){return o("plugin:menu|insert",{rid:this.rid,kind:this.kind,items:(Array.isArray(e)?e:[e]).map((e=>"rid"in e?[e.rid,e.kind]:e)),position:t})}async remove(e){return o("plugin:menu|remove",{rid:this.rid,kind:this.kind,item:[e.rid,e.kind]})}async removeAt(e){return o("plugin:menu|remove_at",{rid:this.rid,kind:this.kind,position:e}).then(de)}async items(){return o("plugin:menu|items",{rid:this.rid,kind:this.kind}).then((e=>e.map(de)))}async get(e){return o("plugin:menu|get",{rid:this.rid,kind:this.kind,id:e}).then((e=>e?de(e):null))}async popup(e,t){let n=null;return e&&(n={type:e instanceof k?"Physical":"Logical",data:e}),o("plugin:menu|popup",{rid:this.rid,kind:this.kind,window:t?.label??null,at:n})}async setAsAppMenu(){return o("plugin:menu|set_as_app_menu",{rid:this.rid}).then((e=>e?new pe(e[0],e[1]):null))}async setAsWindowMenu(e){return o("plugin:menu|set_as_window_menu",{rid:this.rid,window:e?.label??null}).then((e=>e?new pe(e[0],e[1]):null))}}var he=Object.freeze({__proto__:null,CheckMenuItem:se,IconMenuItem:le,Menu:pe,MenuItem:ae,get NativeIcon(){return ee},PredefinedMenuItem:oe,Submenu:ce});return e.app=p,e.core=c,e.dpi=D,e.event=_,e.menu=he,e.path=J,e.tray=te,e.webview=H,e.webviewWindow=Z,e.window=O,e}({});window.__TAURI__=__TAURI_IIFE__; diff --git a/core/tauri/src/app.rs b/core/tauri/src/app.rs index 05101cff3..0fd20ae3d 100644 --- a/core/tauri/src/app.rs +++ b/core/tauri/src/app.rs @@ -13,8 +13,8 @@ use crate::{ }, plugin::{Plugin, PluginStore}, runtime::{ - window::WindowEvent as RuntimeWindowEvent, ExitRequestedEventAction, - RunEvent as RuntimeRunEvent, + window::{WebviewEvent as RuntimeWebviewEvent, WindowEvent as RuntimeWindowEvent}, + ExitRequestedEventAction, RunEvent as RuntimeRunEvent, }, sealed::{ManagerBase, RuntimeOrDispatch}, utils::config::Config, @@ -62,6 +62,8 @@ pub(crate) type GlobalMenuEventListener = Box = Box; pub(crate) type GlobalWindowEventListener = Box, &WindowEvent) + Send + Sync>; +pub(crate) type GlobalWebviewEventListener = + Box, &WebviewEvent) + Send + Sync>; /// A closure that is run when the Tauri application is setting up. pub type SetupHook = Box) -> Result<(), Box> + Send>; @@ -164,6 +166,22 @@ impl From for WindowEvent { } } +/// An event from a window. +#[derive(Debug, Clone)] +#[non_exhaustive] +pub enum WebviewEvent { + /// An event associated with the file drop action. + FileDrop(FileDropEvent), +} + +impl From for WebviewEvent { + fn from(event: RuntimeWebviewEvent) -> Self { + match event { + RuntimeWebviewEvent::FileDrop(e) => Self::FileDrop(e), + } + } +} + /// An application event, triggered from the event loop. /// /// See [`App::run`](crate::App#method.run) for usage examples. @@ -190,6 +208,14 @@ pub enum RunEvent { /// The detailed event. event: WindowEvent, }, + /// An event associated with a webview. + #[non_exhaustive] + WebviewEvent { + /// The window label. + label: String, + /// The detailed event. + event: WebviewEvent, + }, /// Application ready. Ready, /// Sent if the event loop is being resumed. @@ -1043,6 +1069,9 @@ pub struct Builder { /// Window event handlers that listens to all windows. window_event_listeners: Vec>, + /// Webview event handlers that listens to all webviews. + webview_event_listeners: Vec>, + /// The device event filter. device_event_filter: DeviceEventFilter, } @@ -1101,6 +1130,7 @@ impl Builder { menu: None, enable_macos_default_menu: true, window_event_listeners: Vec::new(), + webview_event_listeners: Vec::new(), device_event_filter: Default::default(), } } @@ -1400,6 +1430,27 @@ tauri::Builder::default() self } + /// Registers a webview event handler for all webviews. + /// + /// # Examples + /// ``` + /// tauri::Builder::default() + /// .on_webview_event(|window, event| match event { + /// tauri::WebviewEvent::FileDrop(event) => { + /// println!("{:?}", event); + /// } + /// _ => {} + /// }); + /// ``` + #[must_use] + pub fn on_webview_event, &WebviewEvent) + Send + Sync + 'static>( + mut self, + handler: F, + ) -> Self { + self.webview_event_listeners.push(Box::new(handler)); + self + } + /// Registers a URI scheme protocol available to all webviews. /// Leverages [setURLSchemeHandler](https://developer.apple.com/documentation/webkit/wkwebviewconfiguration/2875766-seturlschemehandler) on macOS, /// [AddWebResourceRequestedFilter](https://docs.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.addwebresourcerequestedfilter?view=webview2-dotnet-1.0.774.44) on Windows @@ -1544,6 +1595,7 @@ tauri::Builder::default() self.uri_scheme_protocols, self.state, self.window_event_listeners, + self.webview_event_listeners, #[cfg(desktop)] HashMap::new(), (self.invoke_responder, self.invoke_initialization_script), @@ -1800,6 +1852,10 @@ fn on_event_loop_event( label, event: event.into(), }, + RuntimeRunEvent::WebviewEvent { label, event } => RunEvent::WebviewEvent { + label, + event: event.into(), + }, RuntimeRunEvent::Ready => { // set the app icon in development #[cfg(all(dev, target_os = "macos"))] diff --git a/core/tauri/src/event/listener.rs b/core/tauri/src/event/listener.rs index 640d5f4f7..f342483d7 100644 --- a/core/tauri/src/event/listener.rs +++ b/core/tauri/src/event/listener.rs @@ -285,28 +285,49 @@ impl Listeners { }) } - pub(crate) fn try_for_each_js<'a, R, I, F>( + pub(crate) fn emit_js_filter<'a, R, I, F>( &self, - event: &str, mut webviews: I, - callback: F, + event: &str, + emit_args: &EmitArgs, + filter: Option<&F>, ) -> crate::Result<()> where R: Runtime, I: Iterator>, - F: Fn(&Webview, &EventTarget) -> crate::Result<()>, + F: Fn(&EventTarget) -> bool, { let listeners = self.inner.js_event_listeners.lock().unwrap(); webviews.try_for_each(|webview| { if let Some(handlers) = listeners.get(webview.label()).and_then(|s| s.get(event)) { for JsHandler { target, .. } in handlers { - callback(webview, target)?; + if *target == EventTarget::Any || filter.as_ref().map(|f| f(target)).unwrap_or(false) { + webview.emit_js(emit_args, target)?; + } } } Ok(()) }) } + + pub(crate) fn emit_js<'a, R, I>( + &self, + webviews: I, + event: &str, + emit_args: &EmitArgs, + ) -> crate::Result<()> + where + R: Runtime, + I: Iterator>, + { + self.emit_js_filter( + webviews, + event, + emit_args, + None::<&&dyn Fn(&EventTarget) -> bool>, + ) + } } #[cfg(test)] diff --git a/core/tauri/src/event/mod.rs b/core/tauri/src/event/mod.rs index d165581c4..2473c9746 100644 --- a/core/tauri/src/event/mod.rs +++ b/core/tauri/src/event/mod.rs @@ -238,7 +238,7 @@ pub fn event_initialization_script(function: &str, listeners: &str) -> String { const listeners = (window['{listeners}'] && window['{listeners}'][eventData.event]) || [] for (let i = listeners.length - 1; i >= 0; i--) {{ const listener = listeners[i] - if ((listener.target.kind === 'Global' && target.kind === 'Global') || (listener.target.kind === target.kind && listener.target.label === target.label)) {{ + if (listener.target.kind === 'Any' || (listener.target.kind === target.kind && listener.target.label === target.label)) {{ eventData.id = listener.id listener.handler(eventData) }} diff --git a/core/tauri/src/lib.rs b/core/tauri/src/lib.rs index 0f915f280..bf203ddb3 100644 --- a/core/tauri/src/lib.rs +++ b/core/tauri/src/lib.rs @@ -206,7 +206,9 @@ pub use self::utils::TitleBarStyle; pub use self::event::{Event, EventId, EventTarget}; pub use { - self::app::{App, AppHandle, AssetResolver, Builder, CloseRequestApi, RunEvent, WindowEvent}, + self::app::{ + App, AppHandle, AssetResolver, Builder, CloseRequestApi, RunEvent, WebviewEvent, WindowEvent, + }, self::manager::Asset, self::runtime::{ webview::WebviewAttributes, @@ -800,7 +802,7 @@ pub trait Manager: sealed::ManagerBase { /// Fetch a single webview window from the manager. fn get_webview_window(&self, label: &str) -> Option> { self.manager().get_webview(label).and_then(|webview| { - if webview.window().webview_window { + if webview.window().is_webview_window { Some(WebviewWindow { webview }) } else { None @@ -815,7 +817,7 @@ pub trait Manager: sealed::ManagerBase { .webviews() .into_iter() .filter_map(|(label, webview)| { - if webview.window().webview_window { + if webview.window().is_webview_window { Some((label, WebviewWindow { webview })) } else { None diff --git a/core/tauri/src/manager/mod.rs b/core/tauri/src/manager/mod.rs index b2da64f96..2ecd1b8f9 100644 --- a/core/tauri/src/manager/mod.rs +++ b/core/tauri/src/manager/mod.rs @@ -21,7 +21,7 @@ use tauri_utils::{ }; use crate::{ - app::{AppHandle, GlobalWindowEventListener, OnPageLoad}, + app::{AppHandle, GlobalWebviewEventListener, GlobalWindowEventListener, OnPageLoad}, event::{assert_event_name_is_valid, Event, EventId, EventTarget, Listeners}, ipc::{Invoke, InvokeHandler, InvokeResponder, RuntimeAuthority}, plugin::PluginStore, @@ -231,6 +231,7 @@ impl AppManager { uri_scheme_protocols: HashMap>>, state: StateManager, window_event_listeners: Vec>, + webiew_event_listeners: Vec>, #[cfg(desktop)] window_menu_event_listeners: HashMap< String, crate::app::GlobalMenuEventListener>, @@ -255,6 +256,7 @@ impl AppManager { invoke_handler, on_page_load, uri_scheme_protocols: Mutex::new(uri_scheme_protocols), + event_listeners: Arc::new(webiew_event_listeners), invoke_responder, invoke_initialization_script, }, @@ -485,16 +487,11 @@ impl AppManager { let listeners = self.listeners(); - listeners.try_for_each_js( - event, + listeners.emit_js_filter( self.webview.webviews_lock().values(), - |webview, target| { - if filter(target) { - webview.emit_js(&emit_args, target) - } else { - Ok(()) - } - }, + event, + &emit_args, + Some(&filter), )?; listeners.emit_filter(emit_args, Some(filter))?; @@ -511,12 +508,7 @@ impl AppManager { let listeners = self.listeners(); - listeners.try_for_each_js( - event, - self.webview.webviews_lock().values(), - |webview, target| webview.emit_js(&emit_args, target), - )?; - + listeners.emit_js(self.webview.webviews_lock().values(), event, &emit_args)?; listeners.emit(emit_args)?; Ok(()) @@ -647,6 +639,7 @@ mod test { StateManager::new(), Default::default(), Default::default(), + Default::default(), (None, "".into()), ); diff --git a/core/tauri/src/manager/webview.rs b/core/tauri/src/manager/webview.rs index 92e3e0a22..80bb783bd 100644 --- a/core/tauri/src/manager/webview.rs +++ b/core/tauri/src/manager/webview.rs @@ -12,20 +12,26 @@ use std::{ use serde::Serialize; use serialize_to_javascript::{default_template, DefaultTemplate, Template}; -use tauri_runtime::webview::{DetachedWebview, PendingWebview}; +use tauri_runtime::{ + webview::{DetachedWebview, PendingWebview}, + window::FileDropEvent, +}; use tauri_utils::config::WebviewUrl; use url::Url; use crate::{ - app::{OnPageLoad, UriSchemeResponder}, + app::{GlobalWebviewEventListener, OnPageLoad, UriSchemeResponder, WebviewEvent}, ipc::{InvokeHandler, InvokeResponder}, pattern::PatternJavascript, sealed::ManagerBase, webview::PageLoadPayload, - AppHandle, EventLoopMessage, Manager, Runtime, Webview, Window, + AppHandle, EventLoopMessage, EventTarget, Manager, Runtime, Scopes, Webview, Window, }; -use super::AppManager; +use super::{ + window::{FileDropPayload, DROP_CANCELLED_EVENT, DROP_EVENT, DROP_HOVER_EVENT}, + AppManager, +}; // we need to proxy the dev server on mobile because we can't use `localhost`, so we use the local IP address // and we do not get a secure context without the custom protocol that proxies to the dev server @@ -73,6 +79,8 @@ pub struct WebviewManager { pub on_page_load: Option>>, /// The webview protocols available to all webviews. pub uri_scheme_protocols: Mutex>>>, + /// Webview event listeners to all webviews. + pub event_listeners: Arc>>, /// Responder for invoke calls. pub invoke_responder: Option>>, @@ -557,6 +565,15 @@ impl WebviewManager { ) -> Webview { let webview = Webview::new(window, webview); + let webview_event_listeners = self.event_listeners.clone(); + let webview_ = webview.clone(); + webview.on_webview_event(move |event| { + let _ = on_webview_event(&webview_, event); + for handler in webview_event_listeners.iter() { + handler(&webview_, event); + } + }); + // insert the webview into our manager { self @@ -600,3 +617,43 @@ impl WebviewManager { self.webviews_lock().keys().cloned().collect() } } + +impl Webview { + /// Emits event to [`EventTarget::Window`] and [`EventTarget::WebviewWindow`] + fn emit_to_webview(&self, event: &str, payload: S) -> crate::Result<()> { + let window_label = self.label(); + self.emit_filter(event, payload, |target| match target { + EventTarget::Webview { label } | EventTarget::WebviewWindow { label } => { + label == window_label + } + _ => false, + }) + } +} + +fn on_webview_event(webview: &Webview, event: &WebviewEvent) -> crate::Result<()> { + match event { + WebviewEvent::FileDrop(event) => match event { + FileDropEvent::Hovered { paths, position } => { + let payload = FileDropPayload { paths, position }; + webview.emit_to_webview(DROP_HOVER_EVENT, payload)? + } + FileDropEvent::Dropped { paths, position } => { + let scopes = webview.state::(); + for path in paths { + if path.is_file() { + let _ = scopes.allow_file(path); + } else { + let _ = scopes.allow_directory(path, false); + } + } + let payload = FileDropPayload { paths, position }; + webview.emit_to_webview(DROP_EVENT, payload)? + } + FileDropEvent::Cancelled => webview.emit_to_webview(DROP_CANCELLED_EVENT, ())?, + _ => unimplemented!(), + }, + } + + Ok(()) +} diff --git a/core/tauri/src/manager/window.rs b/core/tauri/src/manager/window.rs index bf5066fd2..9f0d79fc0 100644 --- a/core/tauri/src/manager/window.rs +++ b/core/tauri/src/manager/window.rs @@ -23,8 +23,6 @@ use crate::{ Icon, Manager, Runtime, Scopes, Window, WindowEvent, }; -use super::AppManager; - const WINDOW_RESIZED_EVENT: &str = "tauri://resize"; const WINDOW_MOVED_EVENT: &str = "tauri://move"; const WINDOW_CLOSE_REQUESTED_EVENT: &str = "tauri://close-requested"; @@ -33,9 +31,9 @@ const WINDOW_FOCUS_EVENT: &str = "tauri://focus"; const WINDOW_BLUR_EVENT: &str = "tauri://blur"; const WINDOW_SCALE_FACTOR_CHANGED_EVENT: &str = "tauri://scale-change"; const WINDOW_THEME_CHANGED: &str = "tauri://theme-changed"; -const WINDOW_FILE_DROP_EVENT: &str = "tauri://file-drop"; -const WINDOW_FILE_DROP_HOVER_EVENT: &str = "tauri://file-drop-hover"; -const WINDOW_FILE_DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled"; +pub const DROP_EVENT: &str = "tauri://file-drop"; +pub const DROP_HOVER_EVENT: &str = "tauri://file-drop-hover"; +pub const DROP_CANCELLED_EVENT: &str = "tauri://file-drop-cancelled"; pub struct WindowManager { pub windows: Mutex>>, @@ -95,9 +93,8 @@ impl WindowManager { let window_ = window.clone(); let window_event_listeners = self.event_listeners.clone(); - let manager = window.manager.clone(); window.on_window_event(move |event| { - let _ = on_window_event(&window_, &manager, event); + let _ = on_window_event(&window_, event); for handler in window_event_listeners.iter() { handler(&window_, event); } @@ -152,16 +149,12 @@ impl Window { } #[derive(Serialize, Clone)] -struct FileDropPayload<'a> { - paths: &'a Vec, - position: &'a PhysicalPosition, +pub struct FileDropPayload<'a> { + pub paths: &'a Vec, + pub position: &'a PhysicalPosition, } -fn on_window_event( - window: &Window, - manager: &AppManager, - event: &WindowEvent, -) -> crate::Result<()> { +fn on_window_event(window: &Window, event: &WindowEvent) -> crate::Result<()> { match event { WindowEvent::Resized(size) => window.emit_to_window(WINDOW_RESIZED_EVENT, size)?, WindowEvent::Moved(position) => window.emit_to_window(WINDOW_MOVED_EVENT, position)?, @@ -174,7 +167,7 @@ fn on_window_event( WindowEvent::Destroyed => { window.emit_to_window(WINDOW_DESTROYED_EVENT, ())?; let label = window.label(); - let webviews_map = manager.webview.webviews_lock(); + let webviews_map = window.manager().webview.webviews_lock(); let webviews = webviews_map.values(); for webview in webviews { webview.eval(&format!( @@ -204,7 +197,15 @@ fn on_window_event( WindowEvent::FileDrop(event) => match event { FileDropEvent::Hovered { paths, position } => { let payload = FileDropPayload { paths, position }; - window.emit_to_window(WINDOW_FILE_DROP_HOVER_EVENT, payload)? + if window.is_webview_window { + window.emit_to( + EventTarget::labeled(window.label()), + DROP_HOVER_EVENT, + payload, + )? + } else { + window.emit_to_window(DROP_HOVER_EVENT, payload)? + } } FileDropEvent::Dropped { paths, position } => { let scopes = window.state::(); @@ -216,9 +217,24 @@ fn on_window_event( } } let payload = FileDropPayload { paths, position }; - window.emit_to_window(WINDOW_FILE_DROP_EVENT, payload)? + + if window.is_webview_window { + window.emit_to(EventTarget::labeled(window.label()), DROP_EVENT, payload)? + } else { + window.emit_to_window(DROP_EVENT, payload)? + } + } + FileDropEvent::Cancelled => { + if window.is_webview_window { + window.emit_to( + EventTarget::labeled(window.label()), + DROP_CANCELLED_EVENT, + (), + )? + } else { + window.emit_to_window(DROP_CANCELLED_EVENT, ())? + } } - FileDropEvent::Cancelled => window.emit_to_window(WINDOW_FILE_DROP_CANCELLED_EVENT, ())?, _ => unimplemented!(), }, WindowEvent::ThemeChanged(theme) => { diff --git a/core/tauri/src/test/mock_runtime.rs b/core/tauri/src/test/mock_runtime.rs index 891e6d341..926371ff5 100644 --- a/core/tauri/src/test/mock_runtime.rs +++ b/core/tauri/src/test/mock_runtime.rs @@ -61,6 +61,7 @@ pub struct RuntimeContext { next_window_id: Arc, next_webview_id: Arc, next_window_event_id: Arc, + next_webview_event_id: Arc, } // SAFETY: we ensure this type is only used on the main thread. @@ -100,6 +101,10 @@ impl RuntimeContext { fn next_window_event_id(&self) -> WindowEventId { self.next_window_event_id.fetch_add(1, Ordering::Relaxed) } + + fn next_webview_event_id(&self) -> WindowEventId { + self.next_webview_event_id.fetch_add(1, Ordering::Relaxed) + } } impl fmt::Debug for RuntimeContext { @@ -460,6 +465,13 @@ impl WebviewDispatch for MockWebviewDispatcher { self.context.send_message(Message::Task(Box::new(f))) } + fn on_webview_event( + &self, + f: F, + ) -> tauri_runtime::WebviewEventId { + self.context.next_window_event_id() + } + fn with_webview) + Send + 'static>(&self, f: F) -> Result<()> { Ok(()) } @@ -922,6 +934,7 @@ impl MockRuntime { next_window_id: Default::default(), next_webview_id: Default::default(), next_window_event_id: Default::default(), + next_webview_event_id: Default::default(), }; Self { is_running, diff --git a/core/tauri/src/webview/mod.rs b/core/tauri/src/webview/mod.rs index a62546ffd..b64840420 100644 --- a/core/tauri/src/webview/mod.rs +++ b/core/tauri/src/webview/mod.rs @@ -26,7 +26,7 @@ use tauri_utils::config::{WebviewUrl, WindowConfig}; pub use url::Url; use crate::{ - app::UriSchemeResponder, + app::{UriSchemeResponder, WebviewEvent}, event::{EmitArgs, EventTarget}, ipc::{ CallbackFn, CommandArg, CommandItem, Invoke, InvokeBody, InvokeError, InvokeMessage, @@ -861,6 +861,14 @@ impl Webview { pub fn label(&self) -> &str { &self.webview.label } + + /// Registers a window event listener. + pub fn on_webview_event(&self, f: F) { + self + .webview + .dispatcher + .on_webview_event(move |event| f(&event.clone().into())); + } } /// Desktop webview setters and actions. @@ -875,7 +883,7 @@ impl Webview { /// Closes this webview. pub fn close(&self) -> crate::Result<()> { - if self.window.webview_window { + if self.window.is_webview_window { self.window.close() } else { self.webview.dispatcher.close()?; @@ -886,7 +894,7 @@ impl Webview { /// Resizes this webview. pub fn set_size>(&self, size: S) -> crate::Result<()> { - if self.window.webview_window { + if self.window.is_webview_window { self.window.set_size(size.into()) } else { self @@ -899,7 +907,7 @@ impl Webview { /// Sets this webviews's position. pub fn set_position>(&self, position: Pos) -> crate::Result<()> { - if self.window.webview_window { + if self.window.is_webview_window { self.window.set_position(position.into()) } else { self @@ -920,7 +928,7 @@ impl Webview { /// - For child webviews, returns the position of the top-left hand corner of the webviews's client area relative to the top-left hand corner of the parent window. /// - For webview window, returns the inner position of the window. pub fn position(&self) -> crate::Result> { - if self.window.webview_window { + if self.window.is_webview_window { self.window.inner_position() } else { self.webview.dispatcher.position().map_err(Into::into) @@ -929,7 +937,7 @@ impl Webview { /// Returns the physical size of the webviews's client area. pub fn size(&self) -> crate::Result> { - if self.window.webview_window { + if self.window.is_webview_window { self.window.inner_size() } else { self.webview.dispatcher.size().map_err(Into::into) diff --git a/core/tauri/src/webview/webview_window.rs b/core/tauri/src/webview/webview_window.rs index 69f66eba6..8ca8432f8 100644 --- a/core/tauri/src/webview/webview_window.rs +++ b/core/tauri/src/webview/webview_window.rs @@ -876,7 +876,7 @@ impl<'de, R: Runtime> CommandArg<'de, R> for WebviewWindow { /// Grabs the [`Window`] from the [`CommandItem`]. This will never fail. fn from_command(command: CommandItem<'de, R>) -> Result { let webview = command.message.webview(); - if webview.window().webview_window { + if webview.window().is_webview_window { Ok(Self { webview }) } else { Err(InvokeError::from_anyhow(anyhow::anyhow!( diff --git a/core/tauri/src/window/mod.rs b/core/tauri/src/window/mod.rs index fcfe93001..60855a158 100644 --- a/core/tauri/src/window/mod.rs +++ b/core/tauri/src/window/mod.rs @@ -863,7 +863,7 @@ pub struct Window { #[cfg(desktop)] pub(crate) menu: Arc>>>, /// Whether this window is a Webview window (hosts only a single webview) or a container for multiple webviews - pub(crate) webview_window: bool, + pub(crate) is_webview_window: bool, } impl std::fmt::Debug for Window { @@ -872,7 +872,7 @@ impl std::fmt::Debug for Window { .field("window", &self.window) .field("manager", &self.manager) .field("app_handle", &self.app_handle) - .field("webview_window", &self.webview_window) + .field("is_webview_window", &self.is_webview_window) .finish() } } @@ -893,7 +893,7 @@ impl Clone for Window { app_handle: self.app_handle.clone(), #[cfg(desktop)] menu: self.menu.clone(), - webview_window: self.webview_window, + is_webview_window: self.is_webview_window, } } } @@ -948,7 +948,7 @@ impl Window { window: DetachedWindow, app_handle: AppHandle, #[cfg(desktop)] menu: Option>, - webview_window: bool, + is_webview_window: bool, ) -> Self { Self { window, @@ -956,7 +956,7 @@ impl Window { app_handle, #[cfg(desktop)] menu: Arc::new(std::sync::Mutex::new(menu)), - webview_window, + is_webview_window, } } diff --git a/examples/api/src/views/Window.svelte b/examples/api/src/views/Window.svelte index 891417599..28ed5c33a 100644 --- a/examples/api/src/views/Window.svelte +++ b/examples/api/src/views/Window.svelte @@ -8,7 +8,7 @@ EffectState, ProgressBarStatus } from '@tauri-apps/api/window' - import { WebviewWindow } from '@tauri-apps/api/webview' + import { WebviewWindow } from '@tauri-apps/api/webviewWindow' const webview = WebviewWindow.getCurrent() diff --git a/tooling/api/src/event.ts b/tooling/api/src/event.ts index b51d325bd..b5b7d554c 100644 --- a/tooling/api/src/event.ts +++ b/tooling/api/src/event.ts @@ -56,9 +56,9 @@ enum TauriEvent { WINDOW_SCALE_FACTOR_CHANGED = 'tauri://scale-change', WINDOW_THEME_CHANGED = 'tauri://theme-changed', WEBVIEW_CREATED = 'tauri://webview-created', - WEBVIEW_FILE_DROP = 'tauri://file-drop', - WEBVIEW_FILE_DROP_HOVER = 'tauri://file-drop-hover', - WEBVIEW_FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled' + FILE_DROP = 'tauri://file-drop', + FILE_DROP_HOVER = 'tauri://file-drop-hover', + FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled' } /** @@ -183,8 +183,8 @@ async function emit(event: string, payload?: unknown): Promise { * * @example * ```typescript - * import { emit } from '@tauri-apps/api/event'; - * await emit('frontend-loaded', { loggedIn: true, token: 'authToken' }); + * import { emitTo } from '@tauri-apps/api/event'; + * await emitTo('main', 'frontend-loaded', { loggedIn: true, token: 'authToken' }); * ``` * * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object. diff --git a/tooling/api/src/index.ts b/tooling/api/src/index.ts index 91e0333e9..578a7458b 100644 --- a/tooling/api/src/index.ts +++ b/tooling/api/src/index.ts @@ -18,9 +18,21 @@ import * as event from './event' import * as core from './core' import * as window from './window' import * as webview from './webview' +import * as webviewWindow from './webviewWindow' import * as path from './path' import * as dpi from './dpi' import * as tray from './tray' import * as menu from './menu' -export { app, dpi, event, path, core, window, webview, tray, menu } +export { + app, + dpi, + event, + path, + core, + window, + webview, + webviewWindow, + tray, + menu +} diff --git a/tooling/api/src/webview.ts b/tooling/api/src/webview.ts index 603804ec9..72d9edf50 100644 --- a/tooling/api/src/webview.ts +++ b/tooling/api/src/webview.ts @@ -31,7 +31,6 @@ import { } from './event' import { invoke } from './core' import { Window, getCurrent as getCurrentWindow } from './window' -import type { WindowOptions } from './window' interface FileDropPayload { paths: string[] @@ -47,7 +46,7 @@ type FileDropEvent = /** * Get an instance of `Webview` for the current webview. * - * @since 1.0.0 + * @since 2.0.0 */ function getCurrent(): Webview { return new Webview( @@ -63,7 +62,7 @@ function getCurrent(): Webview { /** * Gets a list of instances of `Webview` for all available webviews. * - * @since 1.0.0 + * @since 2.0.0 */ function getAll(): Webview[] { return window.__TAURI_INTERNALS__.metadata.webviews.map( @@ -298,7 +297,7 @@ class Webview { * @example * ```typescript * import { getCurrent } from '@tauri-apps/api/webview'; - * await getCurrent().emit('webview-loaded', { loggedIn: true, token: 'authToken' }); + * await getCurrent().emitTo('main', 'webview-loaded', { loggedIn: true, token: 'authToken' }); * ``` * * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object. @@ -506,7 +505,7 @@ class Webview { handler: EventCallback ): Promise { const unlistenFileDrop = await this.listen( - TauriEvent.WEBVIEW_FILE_DROP, + TauriEvent.FILE_DROP, (event) => { handler({ ...event, @@ -520,7 +519,7 @@ class Webview { ) const unlistenFileHover = await this.listen( - TauriEvent.WEBVIEW_FILE_DROP_HOVER, + TauriEvent.FILE_DROP_HOVER, (event) => { handler({ ...event, @@ -534,7 +533,7 @@ class Webview { ) const unlistenCancel = await this.listen( - TauriEvent.WEBVIEW_FILE_DROP_CANCELLED, + TauriEvent.FILE_DROP_CANCELLED, (event) => { handler({ ...event, payload: { type: 'cancel' } }) } @@ -552,199 +551,10 @@ function mapPhysicalPosition(m: PhysicalPosition): PhysicalPosition { return new PhysicalPosition(m.x, m.y) } -// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging -interface WebviewWindow extends Webview, Window {} - -// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging -class WebviewWindow { - label: string - /** Local event listeners. */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - listeners: Record>> - - /** - * Creates a new {@link Window} hosting a {@link Webview}. - * @example - * ```typescript - * import { WebviewWindow } from '@tauri-apps/api/webview' - * const webview = new WebviewWindow('my-label', { - * url: 'https://github.com/tauri-apps/tauri' - * }); - * webview.once('tauri://created', function () { - * // webview successfully created - * }); - * webview.once('tauri://error', function (e) { - * // an error happened creating the webview - * }); - * ``` - * - * @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`. - * @returns The {@link WebviewWindow} instance to communicate with the window and webview. - */ - constructor( - label: WebviewLabel, - options: Omit & - WindowOptions = {} - ) { - this.label = label - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - this.listeners = Object.create(null) - - // @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions - if (!options?.skip) { - invoke('plugin:webview|create_webview_window', { - options: { - ...options, - parent: - typeof options.parent === 'string' - ? options.parent - : options.parent?.label, - label - } - }) - .then(async () => this.emit('tauri://created')) - .catch(async (e: string) => this.emit('tauri://error', e)) - } - } - - /** - * Gets the Webview for the webview associated with the given label. - * @example - * ```typescript - * import { Webview } from '@tauri-apps/api/webview'; - * const mainWebview = Webview.getByLabel('main'); - * ``` - * - * @param label The webview label. - * @returns The Webview instance to communicate with the webview or null if the webview doesn't exist. - */ - static getByLabel(label: string): WebviewWindow | null { - const webview = getAll().find((w) => w.label === label) ?? null - if (webview) { - // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor - return new WebviewWindow(webview.label, { skip: true }) - } - return null - } - - /** - * Get an instance of `Webview` for the current webview. - */ - static getCurrent(): WebviewWindow { - const webview = getCurrent() - // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor - return new WebviewWindow(webview.label, { skip: true }) - } - - /** - * Gets a list of instances of `Webview` for all available webviews. - */ - static getAll(): WebviewWindow[] { - // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor - return getAll().map((w) => new WebviewWindow(w.label, { skip: true })) - } - - /** - * Listen to an emitted event on this webivew window. - * - * @example - * ```typescript - * import { WebviewWindow } from '@tauri-apps/api/webview'; - * const unlisten = await WebviewWindow.getCurrent().listen('state-changed', (event) => { - * console.log(`Got error: ${payload}`); - * }); - * - * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted - * unlisten(); - * ``` - * - * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. - * @param handler Event handler. - * @returns A promise resolving to a function to unlisten to the event. - * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. - */ - async listen( - event: EventName, - handler: EventCallback - ): Promise { - if (this._handleTauriEvent(event, handler)) { - return Promise.resolve(() => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, security/detect-object-injection - const listeners = this.listeners[event] - listeners.splice(listeners.indexOf(handler), 1) - }) - } - return listen(event, handler, { - target: { kind: 'WebviewWindow', label: this.label } - }) - } - - /** - * Listen to an emitted event on this webivew window only once. - * - * @example - * ```typescript - * import { WebviewWindow } from '@tauri-apps/api/webview'; - * const unlisten = await WebviewWindow.getCurrent().once('initialized', (event) => { - * console.log(`Webview initialized!`); - * }); - * - * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted - * unlisten(); - * ``` - * - * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. - * @param handler Event handler. - * @returns A promise resolving to a function to unlisten to the event. - * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. - */ - async once(event: string, handler: EventCallback): Promise { - if (this._handleTauriEvent(event, handler)) { - return Promise.resolve(() => { - // eslint-disable-next-line security/detect-object-injection - const listeners = this.listeners[event] - listeners.splice(listeners.indexOf(handler), 1) - }) - } - return once(event, handler, { - target: { kind: 'WebviewWindow', label: this.label } - }) - } -} - -// Order matters, we use window APIs by default -applyMixins(WebviewWindow, [Window, Webview]) - -/** Extends a base class by other specifed classes, wihtout overriding existing properties */ -function applyMixins( - baseClass: { prototype: unknown }, - extendedClasses: unknown -): void { - ;(Array.isArray(extendedClasses) - ? extendedClasses - : [extendedClasses] - ).forEach((extendedClass: { prototype: unknown }) => { - Object.getOwnPropertyNames(extendedClass.prototype).forEach((name) => { - if ( - typeof baseClass.prototype === 'object' && - baseClass.prototype && - name in baseClass.prototype - ) - return - Object.defineProperty( - baseClass.prototype, - name, - // eslint-disable-next-line - Object.getOwnPropertyDescriptor(extendedClass.prototype, name) ?? - Object.create(null) - ) - }) - }) -} /** * Configuration for the webview to create. * - * @since 1.0.0 + * @since 2.0.0 */ interface WebviewOptions { /** @@ -803,6 +613,6 @@ interface WebviewOptions { proxyUrl?: string } -export { Webview, WebviewWindow, getCurrent, getAll } +export { Webview, getCurrent, getAll } -export type { FileDropEvent, WebviewOptions } +export type { FileDropEvent, FileDropPayload, WebviewOptions } diff --git a/tooling/api/src/webviewWindow.ts b/tooling/api/src/webviewWindow.ts new file mode 100644 index 000000000..01d12f523 --- /dev/null +++ b/tooling/api/src/webviewWindow.ts @@ -0,0 +1,234 @@ +// Copyright 2019-2023 Tauri Programme within The Commons Conservancy +// SPDX-License-Identifier: Apache-2.0 +// SPDX-License-Identifier: MIT + +import { + getCurrent as getCurrentWebview, + Webview, + WebviewLabel, + WebviewOptions +} from './webview' +import type { WindowOptions } from './window' +import { Window } from './window' +import { listen, once } from './event' +import type { EventName, EventCallback, UnlistenFn } from './event' +import { invoke } from './core' +import type { FileDropEvent, FileDropPayload } from './webview' + +/** + * Get an instance of `Webview` for the current webview window. + * + * @since 2.0.0 + */ +function getCurrent(): WebviewWindow { + const webview = getCurrentWebview() + // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor + return new WebviewWindow(webview.label, { skip: true }) +} + +/** + * Gets a list of instances of `Webview` for all available webview windows. + * + * @since 2.0.0 + */ +function getAll(): WebviewWindow[] { + return window.__TAURI_INTERNALS__.metadata.webviews.map( + (w) => + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + new WebviewWindow(w.label, { + // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor + skip: true + }) + ) +} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +interface WebviewWindow extends Webview, Window {} + +// eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging +class WebviewWindow { + label: string + /** Local event listeners. */ + // eslint-disable-next-line @typescript-eslint/no-explicit-any + listeners: Record>> + + /** + * Creates a new {@link Window} hosting a {@link Webview}. + * @example + * ```typescript + * import { WebviewWindow } from '@tauri-apps/api/webviewWindow' + * const webview = new WebviewWindow('my-label', { + * url: 'https://github.com/tauri-apps/tauri' + * }); + * webview.once('tauri://created', function () { + * // webview successfully created + * }); + * webview.once('tauri://error', function (e) { + * // an error happened creating the webview + * }); + * ``` + * + * @param label The unique webview label. Must be alphanumeric: `a-zA-Z-/:_`. + * @returns The {@link WebviewWindow} instance to communicate with the window and webview. + */ + constructor( + label: WebviewLabel, + options: Omit & + WindowOptions = {} + ) { + this.label = label + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + this.listeners = Object.create(null) + + // @ts-expect-error `skip` is not a public API so it is not defined in WebviewOptions + if (!options?.skip) { + invoke('plugin:webview|create_webview_window', { + options: { + ...options, + parent: + typeof options.parent === 'string' + ? options.parent + : options.parent?.label, + label + } + }) + .then(async () => this.emit('tauri://created')) + .catch(async (e: string) => this.emit('tauri://error', e)) + } + } + + /** + * Gets the Webview for the webview associated with the given label. + * @example + * ```typescript + * import { Webview } from '@tauri-apps/api/webviewWindow'; + * const mainWebview = Webview.getByLabel('main'); + * ``` + * + * @param label The webview label. + * @returns The Webview instance to communicate with the webview or null if the webview doesn't exist. + */ + static getByLabel(label: string): WebviewWindow | null { + const webview = getAll().find((w) => w.label === label) ?? null + if (webview) { + // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor + return new WebviewWindow(webview.label, { skip: true }) + } + return null + } + + /** + * Get an instance of `Webview` for the current webview. + */ + static getCurrent(): WebviewWindow { + return getCurrent() + } + + /** + * Gets a list of instances of `Webview` for all available webviews. + */ + static getAll(): WebviewWindow[] { + // @ts-expect-error `skip` is not defined in the public API but it is handled by the constructor + return getAll().map((w) => new WebviewWindow(w.label, { skip: true })) + } + + /** + * Listen to an emitted event on this webivew window. + * + * @example + * ```typescript + * import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; + * const unlisten = await WebviewWindow.getCurrent().listen('state-changed', (event) => { + * console.log(`Got error: ${payload}`); + * }); + * + * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted + * unlisten(); + * ``` + * + * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. + * @param handler Event handler. + * @returns A promise resolving to a function to unlisten to the event. + * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. + */ + async listen( + event: EventName, + handler: EventCallback + ): Promise { + if (this._handleTauriEvent(event, handler)) { + return Promise.resolve(() => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, security/detect-object-injection + const listeners = this.listeners[event] + listeners.splice(listeners.indexOf(handler), 1) + }) + } + return listen(event, handler, { + target: { kind: 'WebviewWindow', label: this.label } + }) + } + + /** + * Listen to an emitted event on this webivew window only once. + * + * @example + * ```typescript + * import { WebviewWindow } from '@tauri-apps/api/webviewWindow'; + * const unlisten = await WebviewWindow.getCurrent().once('initialized', (event) => { + * console.log(`Webview initialized!`); + * }); + * + * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted + * unlisten(); + * ``` + * + * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. + * @param handler Event handler. + * @returns A promise resolving to a function to unlisten to the event. + * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. + */ + async once(event: string, handler: EventCallback): Promise { + if (this._handleTauriEvent(event, handler)) { + return Promise.resolve(() => { + // eslint-disable-next-line security/detect-object-injection + const listeners = this.listeners[event] + listeners.splice(listeners.indexOf(handler), 1) + }) + } + return once(event, handler, { + target: { kind: 'WebviewWindow', label: this.label } + }) + } +} + +// Order matters, we use window APIs by default +applyMixins(WebviewWindow, [Window, Webview]) + +/** Extends a base class by other specifed classes, wihtout overriding existing properties */ +function applyMixins( + baseClass: { prototype: unknown }, + extendedClasses: unknown +): void { + ;(Array.isArray(extendedClasses) + ? extendedClasses + : [extendedClasses] + ).forEach((extendedClass: { prototype: unknown }) => { + Object.getOwnPropertyNames(extendedClass.prototype).forEach((name) => { + if ( + typeof baseClass.prototype === 'object' && + baseClass.prototype && + name in baseClass.prototype + ) + return + Object.defineProperty( + baseClass.prototype, + name, + // eslint-disable-next-line + Object.getOwnPropertyDescriptor(extendedClass.prototype, name) ?? + Object.create(null) + ) + }) + }) +} + +export { WebviewWindow, getCurrent, getAll } +export type { FileDropEvent, FileDropPayload } diff --git a/tooling/api/src/window.ts b/tooling/api/src/window.ts index 58e658be8..0b902e7c2 100644 --- a/tooling/api/src/window.ts +++ b/tooling/api/src/window.ts @@ -34,7 +34,8 @@ import { once } from './event' import { invoke } from './core' -import { WebviewWindow } from './webview' +import { WebviewWindow } from './webviewWindow' +import type { FileDropEvent, FileDropPayload } from './webview' /** * Allows you to retrieve information about a given monitor. @@ -458,7 +459,7 @@ class Window { * @example * ```typescript * import { getCurrent } from '@tauri-apps/api/window'; - * await getCurrent().emit('window-loaded', { loggedIn: true, token: 'authToken' }); + * await getCurrent().emit('main', 'window-loaded', { loggedIn: true, token: 'authToken' }); * ``` * @param target Label of the target Window/Webview/WebviewWindow or raw {@link EventTarget} object. * @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`. @@ -1716,6 +1717,76 @@ class Window { } /* eslint-enable */ + /** + * Listen to a file drop event. + * The listener is triggered when the user hovers the selected files on the webview, + * drops the files or cancels the operation. + * + * @example + * ```typescript + * import { getCurrent } from "@tauri-apps/api/webview"; + * const unlisten = await getCurrent().onFileDropEvent((event) => { + * if (event.payload.type === 'hover') { + * console.log('User hovering', event.payload.paths); + * } else if (event.payload.type === 'drop') { + * console.log('User dropped', event.payload.paths); + * } else { + * console.log('File drop cancelled'); + * } + * }); + * + * // you need to call unlisten if your handler goes out of scope e.g. the component is unmounted + * unlisten(); + * ``` + * + * @returns A promise resolving to a function to unlisten to the event. + * Note that removing the listener is required if your listener goes out of scope e.g. the component is unmounted. + */ + async onFileDropEvent( + handler: EventCallback + ): Promise { + const unlistenFileDrop = await this.listen( + TauriEvent.FILE_DROP, + (event) => { + handler({ + ...event, + payload: { + type: 'drop', + paths: event.payload.paths, + position: mapPhysicalPosition(event.payload.position) + } + }) + } + ) + + const unlistenFileHover = await this.listen( + TauriEvent.FILE_DROP_HOVER, + (event) => { + handler({ + ...event, + payload: { + type: 'hover', + paths: event.payload.paths, + position: mapPhysicalPosition(event.payload.position) + } + }) + } + ) + + const unlistenCancel = await this.listen( + TauriEvent.FILE_DROP_CANCELLED, + (event) => { + handler({ ...event, payload: { type: 'cancel' } }) + } + ) + + return () => { + unlistenFileDrop() + unlistenFileHover() + unlistenCancel() + } + } + /** * Listen to window focus change. * @@ -2200,5 +2271,7 @@ export type { TitleBarStyle, ScaleFactorChanged, WindowOptions, - Color + Color, + FileDropEvent, + FileDropPayload } diff --git a/tooling/cli/src/interface/rust.rs b/tooling/cli/src/interface/rust.rs index 256939546..0ac25cc5c 100644 --- a/tooling/cli/src/interface/rust.rs +++ b/tooling/cli/src/interface/rust.rs @@ -302,7 +302,7 @@ fn build_ignore_matcher(dir: &Path) -> IgnoreMatcher { for line in crate::dev::TAURI_CLI_BUILTIN_WATCHER_IGNORE_FILE .lines() - .flatten() + .map_while(Result::ok) { let _ = ignore_builder.add_line(None, &line); } diff --git a/tooling/cli/src/migrate/frontend.rs b/tooling/cli/src/migrate/frontend.rs index 4d21ecbc6..33d4f5693 100644 --- a/tooling/cli/src/migrate/frontend.rs +++ b/tooling/cli/src/migrate/frontend.rs @@ -43,6 +43,10 @@ pub fn migrate(app_dir: &Path, tauri_dir: &Path) -> Result<()> { let new = "@tauri-apps/api/core".to_string(); log::info!("Replacing `{original}` with `{new}` on {}", path.display()); new + } else if module == "window" { + let new = "@tauri-apps/api/webviewWindow".to_string(); + log::info!("Replacing `{original}` with `{new}` on {}", path.display()); + new } else if CORE_API_MODULES.contains(&module) { original.to_string() } else {