feat: add tray and menu javascript APIs and events, closes #6617 (#7709)

Co-authored-by: Lucas Fernandes Nogueira <lucas@tauri.app>
Co-authored-by: Fabian-Lars <fabianlars@fabianlars.de>
Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Amr Bashir 2023-11-19 13:38:24 +02:00 committed by GitHub
parent 92b50a3a39
commit f93148eac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 3900 additions and 552 deletions

View File

@ -0,0 +1,5 @@
---
'@tauri-apps/api': 'minor:feat'
---
Add `tray` and `menu` modules to create and manage tray icons and menus from Javascript.

View File

@ -49,6 +49,9 @@ jobs:
- name: install deps via yarn
working-directory: ./tooling/api/
run: yarn
- name: run ts:check
working-directory: ./tooling/api/
run: yarn ts:check
- name: run lint
working-directory: ./tooling/api/
run: yarn lint

View File

@ -11,7 +11,7 @@ if [ -z "$(git diff --name-only tooling/api)" ]; then
else
cd tooling/api
yarn format
yarn lint-fix
yarn lint:fix
cd ../..
fi

View File

@ -13,6 +13,8 @@ SPDX-License-Identifier: Apache-2.0
SPDX-License-Identifier: MIT`
const bundlerLicense =
'// Copyright 2016-2019 Cargo-Bundle developers <https://github.com/burtonageo/cargo-bundle>'
const denoLicense =
'// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.'
const extensions = ['.rs', '.js', '.ts', '.yml', '.swift', '.kt']
const ignore = [
@ -43,7 +45,8 @@ async function checkFile(file) {
line.length === 0 ||
line.startsWith('#!') ||
line.startsWith('// swift-tools-version:') ||
line === bundlerLicense
line === bundlerLicense ||
line === denoLicense
) {
continue
}

View File

@ -16,6 +16,7 @@ use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
mod command;
mod menu;
mod mobile;
mod runtime;
@ -89,3 +90,64 @@ pub fn default_runtime(attributes: TokenStream, input: TokenStream) -> TokenStre
let input = parse_macro_input!(input as DeriveInput);
runtime::default_runtime(attributes, input).into()
}
/// Accepts a closure-like syntax to call arbitrary code on a menu item
/// after matching against `kind` and retrieving it from `resources_table` using `rid`.
///
/// You can optionally pass a third parameter to select which item kinds
/// to match against, by providing a `|` separated list of item kinds
/// ```ignore
/// do_menu_item!(|i| i.set_text(text), Check | Submenu);
/// ```
/// You could also provide a negated list
/// ```ignore
/// do_menu_item!(|i| i.set_text(text), !Check);
/// do_menu_item!(|i| i.set_text(text), !Check | !Submenu);
/// ```
/// but you can't have mixed negations and positive kinds.
/// ```ignore
/// do_menu_item!(|i| i.set_text(text), !Check | Submeun);
/// ```
///
/// #### Example
///
/// ```ignore
/// let rid = 23;
/// let kind = ItemKind::Check;
/// let resources_table = app.manager.resources_table();
/// do_menu_item!(|i| i.set_text(text))
/// ```
/// which will expand into:
/// ```ignore
/// let rid = 23;
/// let kind = ItemKind::Check;
/// let resources_table = app.manager.resources_table();
/// match kind {
/// ItemKind::Submenu => {
/// let i = resources_table.get::<Submenu<R>>(rid)?;
/// i.set_text(text)
/// }
/// ItemKind::MenuItem => {
/// let i = resources_table.get::<MenuItem<R>>(rid)?;
/// i.set_text(text)
/// }
/// ItemKind::Predefined => {
/// let i = resources_table.get::<PredefinedMenuItem<R>>(rid)?;
/// i.set_text(text)
/// }
/// ItemKind::Check => {
/// let i = resources_table.get::<CheckMenuItem<R>>(rid)?;
/// i.set_text(text)
/// }
/// ItemKind::Icon => {
/// let i = resources_table.get::<IconMenuItem<R>>(rid)?;
/// i.set_text(text)
/// }
/// _ => unreachable!(),
/// }
/// ```
#[proc_macro]
pub fn do_menu_item(input: TokenStream) -> TokenStream {
let tokens = parse_macro_input!(input as menu::DoMenuItemInput);
menu::do_menu_item(tokens).into()
}

View File

@ -0,0 +1,118 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Expr, Token,
};
pub struct DoMenuItemInput {
resources_table: Ident,
rid: Ident,
kind: Ident,
var: Ident,
expr: Expr,
kinds: Vec<NegatedIdent>,
}
#[derive(Clone)]
struct NegatedIdent(bool, Ident);
impl Parse for NegatedIdent {
fn parse(input: ParseStream) -> syn::Result<Self> {
let t = input.parse::<Token![!]>();
let i: Ident = input.parse()?;
Ok(NegatedIdent(t.is_ok(), i))
}
}
impl Parse for DoMenuItemInput {
fn parse(input: ParseStream) -> syn::Result<Self> {
let resources_table: Ident = input.parse()?;
let _: Token![,] = input.parse()?;
let rid: Ident = input.parse()?;
let _: Token![,] = input.parse()?;
let kind: Ident = input.parse()?;
let _: Token![,] = input.parse()?;
let _: Token![|] = input.parse()?;
let var: Ident = input.parse()?;
let _: Token![|] = input.parse()?;
let expr: Expr = input.parse()?;
let _: syn::Result<Token![,]> = input.parse();
let kinds = Punctuated::<NegatedIdent, Token![|]>::parse_terminated(input)?;
Ok(Self {
resources_table,
rid,
kind,
var,
expr,
kinds: kinds.into_iter().collect(),
})
}
}
pub fn do_menu_item(input: DoMenuItemInput) -> TokenStream {
let DoMenuItemInput {
rid,
resources_table,
kind,
expr,
var,
mut kinds,
} = input;
let defaults = vec![
NegatedIdent(false, Ident::new("Submenu", Span::call_site())),
NegatedIdent(false, Ident::new("MenuItem", Span::call_site())),
NegatedIdent(false, Ident::new("Predefined", Span::call_site())),
NegatedIdent(false, Ident::new("Check", Span::call_site())),
NegatedIdent(false, Ident::new("Icon", Span::call_site())),
];
if kinds.is_empty() {
kinds.extend(defaults.clone());
}
let has_negated = kinds.iter().any(|n| n.0);
if has_negated {
kinds.extend(defaults);
kinds.sort_by(|a, b| a.1.cmp(&b.1));
kinds.dedup_by(|a, b| a.1 == b.1);
}
let (kinds, types): (Vec<Ident>, Vec<Ident>) = kinds
.into_iter()
.filter_map(|nident| {
if nident.0 {
None
} else {
match nident.1 {
i if i == "MenuItem" => Some((i, Ident::new("MenuItem", Span::call_site()))),
i if i == "Submenu" => Some((i, Ident::new("Submenu", Span::call_site()))),
i if i == "Predefined" => Some((i, Ident::new("PredefinedMenuItem", Span::call_site()))),
i if i == "Check" => Some((i, Ident::new("CheckMenuItem", Span::call_site()))),
i if i == "Icon" => Some((i, Ident::new("IconMenuItem", Span::call_site()))),
_ => None,
}
}
})
.unzip();
quote! {
match #kind {
#(
ItemKind::#kinds => {
let #var = #resources_table.get::<#types<R>>(#rid)?;
#expr
}
)*
_ => unreachable!(),
}
}
}

View File

@ -73,8 +73,8 @@ ico = { version = "0.3.0", optional = true }
http-range = { version = "0.1.5", optional = true }
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\", target_os = \"windows\", target_os = \"macos\"))".dependencies]
muda = { version = "0.10", default-features = false }
tray-icon = { version = "0.10", default-features = false, optional = true }
muda = { version = "0.10", default-features = false, features = [ "serde" ] }
tray-icon = { version = "0.10", default-features = false, features = [ "serde" ], optional = true }
[target."cfg(any(target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
gtk = { version = "0.18", features = [ "v3_24" ] }

File diff suppressed because one or more lines are too long

View File

@ -772,7 +772,8 @@ macro_rules! shared_app_impl {
/// **You should always exit the tauri app immediately after this function returns and not use any tauri-related APIs.**
pub fn cleanup_before_exit(&self) {
#[cfg(all(desktop, feature = "tray-icon"))]
self.manager.tray.icons.lock().unwrap().clear()
self.manager.tray.icons.lock().unwrap().clear();
self.manager.resources_table().clear();
}
}
};
@ -787,6 +788,11 @@ impl<R: Runtime> App<R> {
self.handle.plugin(crate::event::plugin::init())?;
self.handle.plugin(crate::window::plugin::init())?;
self.handle.plugin(crate::app::plugin::init())?;
self.handle.plugin(crate::resources::plugin::init())?;
#[cfg(desktop)]
self.handle.plugin(crate::menu::plugin::init())?;
#[cfg(all(desktop, feature = "tray-icon"))]
self.handle.plugin(crate::tray::plugin::init())?;
Ok(())
}

View File

@ -130,6 +130,12 @@ pub enum Error {
/// window not found.
#[error("window not found")]
WindowNotFound,
/// The resource id is invalid.
#[error("The resource id {0} is invalid.")]
BadResourceId(crate::resources::ResourceId),
/// The anyhow crate error.
#[error(transparent)]
Anyhow(#[from] anyhow::Error),
}
/// `Result<T, ::tauri::Error>`

View File

@ -4,13 +4,14 @@
use std::{
collections::HashMap,
str::FromStr,
sync::{
atomic::{AtomicU32, Ordering},
Arc, Mutex,
},
};
use serde::{Deserialize, Serialize, Serializer};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::{
command,
@ -50,6 +51,62 @@ impl Serialize for Channel {
}
}
/// The ID of a channel that was defined on the JavaScript layer.
///
/// Useful when expecting [`Channel`] as part of a JSON object instead of a top-level command argument.
///
/// # Examples
///
/// ```rust
/// use tauri::{ipc::JavaScriptChannelId, Runtime, Window};
///
/// #[derive(serde::Deserialize)]
/// #[serde(rename_all = "camelCase")]
/// struct Button {
/// label: String,
/// on_click: JavaScriptChannelId,
/// }
///
/// #[tauri::command]
/// fn add_button<R: Runtime>(window: Window<R>, button: Button) {
/// let channel = button.on_click.channel_on(window);
/// channel.send("clicked").unwrap();
/// }
/// ```
pub struct JavaScriptChannelId(CallbackFn);
impl FromStr for JavaScriptChannelId {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
s.split_once(IPC_PAYLOAD_PREFIX)
.ok_or("invalid channel string")
.and_then(|(_prefix, id)| id.parse().map_err(|_| "invalid channel ID"))
.map(|id| Self(CallbackFn(id)))
}
}
impl JavaScriptChannelId {
/// Gets a [`Channel`] for this channel ID on the given [`Window`].
pub fn channel_on<R: Runtime>(&self, window: Window<R>) -> Channel {
Channel::from_callback_fn(window, self.0)
}
}
impl<'de> Deserialize<'de> for JavaScriptChannelId {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: String = Deserialize::deserialize(deserializer)?;
Self::from_str(&value).map_err(|_| {
serde::de::Error::custom(format!(
"invalid channel value `{value}`, expected a string in the `{IPC_PAYLOAD_PREFIX}ID` format"
))
})
}
}
impl Channel {
/// Creates a new channel with the given message handler.
pub fn new<F: Fn(InvokeBody) -> crate::Result<()> + Send + Sync + 'static>(
@ -58,7 +115,7 @@ impl Channel {
Self::new_with_id(CHANNEL_COUNTER.fetch_add(1, Ordering::Relaxed), on_message)
}
pub(crate) fn new_with_id<F: Fn(InvokeBody) -> crate::Result<()> + Send + Sync + 'static>(
fn new_with_id<F: Fn(InvokeBody) -> crate::Result<()> + Send + Sync + 'static>(
id: u32,
on_message: F,
) -> Self {
@ -74,7 +131,7 @@ impl Channel {
channel
}
pub(crate) fn from_ipc<R: Runtime>(window: Window<R>, callback: CallbackFn) -> Self {
pub(crate) fn from_callback_fn<R: Runtime>(window: Window<R>, callback: CallbackFn) -> Self {
Channel::new_with_id(callback.0, move |body| {
let data_id = CHANNEL_DATA_COUNTER.fetch_add(1, Ordering::Relaxed);
window
@ -90,23 +147,12 @@ impl Channel {
})
}
pub(crate) fn load_from_ipc<R: Runtime>(
window: Window<R>,
value: impl AsRef<str>,
) -> Option<Self> {
value
.as_ref()
.split_once(IPC_PAYLOAD_PREFIX)
.and_then(|(_prefix, id)| id.parse().ok())
.map(|callback_id| Self::from_ipc(window, CallbackFn(callback_id)))
}
/// The channel identifier.
pub fn id(&self) -> u32 {
self.id
}
/// Sends the given data through the channel.
/// Sends the given data through the channel.
pub fn send<T: IpcResponse>(&self, data: T) -> crate::Result<()> {
let body = data.body()?;
(self.on_message)(body)
@ -121,11 +167,13 @@ impl<'de, R: Runtime> CommandArg<'de, R> for Channel {
let window = command.message.window();
let value: String =
Deserialize::deserialize(command).map_err(|e| crate::Error::InvalidArgs(name, arg, e))?;
Channel::load_from_ipc(window, &value).ok_or_else(|| {
InvokeError::from_anyhow(anyhow::anyhow!(
JavaScriptChannelId::from_str(&value)
.map(|id| id.channel_on(window))
.map_err(|_| {
InvokeError::from_anyhow(anyhow::anyhow!(
"invalid channel value `{value}`, expected a string in the `{IPC_PAYLOAD_PREFIX}ID` format"
))
})
})
}
}

View File

@ -25,7 +25,7 @@ pub(crate) mod channel;
pub(crate) mod format_callback;
pub(crate) mod protocol;
pub use channel::Channel;
pub use channel::{Channel, JavaScriptChannelId};
/// A closure that is run every time Tauri receives a message it doesn't explicitly handle.
pub type InvokeHandler<R> = dyn Fn(Invoke<R>) -> bool + Send + Sync + 'static;

View File

@ -245,7 +245,7 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
if !(cfg!(target_os = "macos") || cfg!(target_os = "ios"))
&& matches!(v, JsonValue::Object(_) | JsonValue::Array(_))
{
let _ = Channel::from_ipc(window, callback).send(v);
let _ = Channel::from_callback_fn(window, callback).send(v);
} else {
responder_eval(
&window,
@ -262,7 +262,8 @@ fn handle_ipc_message<R: Runtime>(message: String, manager: &AppManager<R>, labe
error,
);
} else {
let _ = Channel::from_ipc(window, callback).send(InvokeBody::Raw(v.clone()));
let _ =
Channel::from_callback_fn(window, callback).send(InvokeBody::Raw(v.clone()));
}
}
InvokeResponse::Err(e) => responder_eval(

View File

@ -82,6 +82,7 @@ mod manager;
mod pattern;
pub mod plugin;
pub(crate) mod protocol;
mod resources;
mod vibrancy;
pub mod window;
use tauri_runtime as runtime;
@ -160,7 +161,7 @@ pub use tauri_runtime_wry::wry;
/// A task to run on the main thread.
pub type SyncTask = Box<dyn FnOnce() + Send>;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fmt::{self, Debug},
@ -903,6 +904,40 @@ mod tests {
}
}
#[derive(Deserialize)]
#[serde(untagged)]
pub(crate) enum IconDto {
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
File(std::path::PathBuf),
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
Raw(Vec<u8>),
Rgba {
rgba: Vec<u8>,
width: u32,
height: u32,
},
}
impl From<IconDto> for Icon {
fn from(icon: IconDto) -> Self {
match icon {
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
IconDto::File(path) => Self::File(path),
#[cfg(any(feature = "icon-png", feature = "icon-ico"))]
IconDto::Raw(raw) => Self::Raw(raw),
IconDto::Rgba {
rgba,
width,
height,
} => Self::Rgba {
rgba,
width,
height,
},
}
}
}
#[allow(unused)]
macro_rules! run_main_thread {
($self:ident, $ex:expr) => {{

View File

@ -6,7 +6,7 @@ use std::{
borrow::Cow,
collections::HashMap,
fmt,
sync::{Arc, Mutex},
sync::{Arc, Mutex, MutexGuard},
};
use serde::Serialize;
@ -20,7 +20,6 @@ use tauri_utils::{
html::{SCRIPT_NONCE_TOKEN, STYLE_NONCE_TOKEN},
};
use crate::event::EmitArgs;
use crate::{
app::{AppHandle, GlobalWindowEventListener, OnPageLoad},
event::{assert_event_name_is_valid, Event, EventId, Listeners},
@ -33,6 +32,7 @@ use crate::{
},
Context, Pattern, Runtime, StateManager, Window,
};
use crate::{event::EmitArgs, resources::ResourceTable};
#[cfg(desktop)]
mod menu;
@ -196,6 +196,9 @@ pub struct AppManager<R: Runtime> {
/// Application pattern.
pub pattern: Arc<Pattern>,
/// Application Resources Table
pub(crate) resources_table: Arc<Mutex<ResourceTable>>,
}
impl<R: Runtime> fmt::Debug for AppManager<R> {
@ -274,6 +277,7 @@ impl<R: Runtime> AppManager<R> {
app_icon: context.app_icon,
package_info: context.package_info,
pattern: Arc::new(context.pattern),
resources_table: Arc::default(),
}
}
@ -535,6 +539,14 @@ impl<R: Runtime> AppManager<R> {
pub fn windows(&self) -> HashMap<String, Window<R>> {
self.window.windows_lock().clone()
}
/// Resources table managed by the application.
pub(crate) fn resources_table(&self) -> MutexGuard<'_, ResourceTable> {
self
.resources_table
.lock()
.expect("poisoned window manager")
}
}
#[cfg(desktop)]

View File

@ -2,9 +2,11 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime};
use crate::{menu::MenuId, resources::Resource, run_main_thread, AppHandle, Manager, Runtime};
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
/// A menu item inside a [`Menu`] or [`Submenu`]
/// and usually contains a text and a check mark or a similar toggle
/// that corresponds to a checked and unchecked states.
///
/// [`Menu`]: super::Menu
/// [`Submenu`]: super::Submenu
@ -31,7 +33,7 @@ unsafe impl<R: Runtime> Sync for CheckMenuItem<R> {}
unsafe impl<R: Runtime> Send for CheckMenuItem<R> {}
impl<R: Runtime> super::sealed::IsMenuItemBase for CheckMenuItem<R> {
fn inner(&self) -> &dyn muda::IsMenuItem {
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
&self.inner
}
}
@ -146,3 +148,5 @@ impl<R: Runtime> CheckMenuItem<R> {
run_main_thread!(self, |self_: Self| self_.inner.set_checked(checked))
}
}
impl<R: Runtime> Resource for CheckMenuItem<R> {}

View File

@ -3,9 +3,12 @@
// SPDX-License-Identifier: MIT
use super::NativeIcon;
use crate::{menu::MenuId, run_main_thread, AppHandle, Icon, Manager, Runtime};
use crate::{
menu::MenuId, resources::Resource, run_main_thread, AppHandle, Icon, Manager, Runtime,
};
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
/// A menu item inside a [`Menu`] or [`Submenu`]
/// and usually contains an icon and a text.
///
/// [`Menu`]: super::Menu
/// [`Submenu`]: super::Submenu
@ -32,7 +35,7 @@ unsafe impl<R: Runtime> Sync for IconMenuItem<R> {}
unsafe impl<R: Runtime> Send for IconMenuItem<R> {}
impl<R: Runtime> super::sealed::IsMenuItemBase for IconMenuItem<R> {
fn inner(&self) -> &dyn muda::IsMenuItem {
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
&self.inner
}
}
@ -214,3 +217,5 @@ impl<R: Runtime> IconMenuItem<R> {
Ok(())
}
}
impl<R: Runtime> Resource for IconMenuItem<R> {}

View File

@ -4,6 +4,7 @@
use super::sealed::ContextMenuBase;
use super::{AboutMetadata, IsMenuItem, MenuItemKind, PredefinedMenuItem, Submenu};
use crate::resources::Resource;
use crate::Window;
use crate::{run_main_thread, AppHandle, Manager, Position, Runtime};
use muda::ContextMenu;
@ -84,12 +85,12 @@ impl<R: Runtime> ContextMenuBase for Menu<R> {
}
})
}
fn inner(&self) -> &dyn muda::ContextMenu {
fn inner_context(&self) -> &dyn muda::ContextMenu {
&self.inner
}
fn inner_owned(&self) -> Box<dyn muda::ContextMenu> {
Box::new(self.clone().inner)
fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {
Box::new(self.inner.clone())
}
}
@ -261,8 +262,10 @@ impl<R: Runtime> Menu<R> {
/// [`Submenu`]: super::Submenu
pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| self_.inner.append(kind.inner().inner()))?
.map_err(Into::into)
run_main_thread!(self, |self_: Self| self_
.inner
.append(kind.inner().inner_muda()))?
.map_err(Into::into)
}
/// Add menu items to the end of this menu. It calls [`Menu::append`] in a loop internally.
@ -291,7 +294,7 @@ impl<R: Runtime> Menu<R> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| self_
.inner
.prepend(kind.inner().inner()))?
.prepend(kind.inner().inner_muda()))?
.map_err(Into::into)
}
@ -317,7 +320,7 @@ impl<R: Runtime> Menu<R> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| self_
.inner
.insert(kind.inner().inner(), position))?
.insert(kind.inner().inner_muda(), position))?
.map_err(Into::into)
}
@ -339,8 +342,18 @@ impl<R: Runtime> Menu<R> {
/// Remove a menu item from this menu.
pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| self_.inner.remove(kind.inner().inner()))?
.map_err(Into::into)
run_main_thread!(self, |self_: Self| self_
.inner
.remove(kind.inner().inner_muda()))?
.map_err(Into::into)
}
/// Remove the menu item at the specified position from this menu and returns it.
pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {
run_main_thread!(self, |self_: Self| self_
.inner
.remove_at(position)
.map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i)))
}
/// Retrieves the menu item matching the given identifier.
@ -358,40 +371,27 @@ impl<R: Runtime> Menu<R> {
/// Returns a list of menu items that has been added to this menu.
pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
let handle = self.app_handle.clone();
run_main_thread!(self, |self_: Self| self_
.inner
.items()
.into_iter()
.map(|i| match i {
muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
muda::MenuItemKind::Predefined(i) => {
super::MenuItemKind::Predefined(super::PredefinedMenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
})
}
muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
})
.map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i))
.collect::<Vec<_>>())
}
/// Set this menu as the application menu.
///
/// This is an alias for [`AppHandle::set_menu`].
pub fn set_as_app_menu(&self) -> crate::Result<Option<Menu<R>>> {
self.app_handle.set_menu(self.clone())
}
/// Set this menu as the window menu.
///
/// This is an alias for [`Window::set_menu`].
pub fn set_as_window_menu(&self, window: &Window<R>) -> crate::Result<Option<Menu<R>>> {
window.set_menu(self.clone())
}
}
impl<R: Runtime> Resource for Menu<R> {}

View File

@ -4,7 +4,7 @@
#![cfg(desktop)]
//! Menu types and utility functions
//! Menu types and utilities.
// TODO(muda-migration): figure out js events
@ -14,6 +14,7 @@ mod icon;
#[allow(clippy::module_inception)]
mod menu;
mod normal;
pub(crate) mod plugin;
mod predefined;
mod submenu;
pub use builders::*;
@ -22,13 +23,14 @@ pub use icon::IconMenuItem;
pub use menu::{Menu, HELP_SUBMENU_ID, WINDOW_SUBMENU_ID};
pub use normal::MenuItem;
pub use predefined::PredefinedMenuItem;
use serde::{Deserialize, Serialize};
pub use submenu::Submenu;
use crate::{Icon, Runtime};
use crate::{AppHandle, Icon, Runtime};
pub use muda::MenuId;
/// Describes a menu event emitted when a menu item is activated
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct MenuEvent {
/// Id of the menu item which triggered this event
pub id: MenuId,
@ -233,7 +235,7 @@ impl From<AboutMetadata> for muda::AboutMetadata {
/// ## Platform-specific:
///
/// - **Windows / Linux**: Unsupported.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
pub enum NativeIcon {
/// An add item template image.
Add,
@ -449,6 +451,36 @@ impl<R: Runtime> MenuItemKind<R> {
}
}
pub(crate) fn from_muda(app_handle: AppHandle<R>, i: muda::MenuItemKind) -> Self {
match i {
muda::MenuItemKind::MenuItem(i) => Self::MenuItem(MenuItem {
id: i.id().clone(),
inner: i,
app_handle,
}),
muda::MenuItemKind::Submenu(i) => Self::Submenu(Submenu {
id: i.id().clone(),
inner: i,
app_handle,
}),
muda::MenuItemKind::Predefined(i) => Self::Predefined(PredefinedMenuItem {
id: i.id().clone(),
inner: i,
app_handle,
}),
muda::MenuItemKind::Check(i) => Self::Check(CheckMenuItem {
id: i.id().clone(),
inner: i,
app_handle,
}),
muda::MenuItemKind::Icon(i) => Self::Icon(IconMenuItem {
id: i.id().clone(),
inner: i,
app_handle,
}),
}
}
/// Casts this item to a [`MenuItem`], and returns `None` if it wasn't.
pub fn as_menuitem(&self) -> Option<&MenuItem<R>> {
match self {
@ -543,8 +575,8 @@ impl<R: Runtime> Clone for MenuItemKind<R> {
}
impl<R: Runtime> sealed::IsMenuItemBase for MenuItemKind<R> {
fn inner(&self) -> &dyn muda::IsMenuItem {
self.inner().inner()
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
self.inner().inner_muda()
}
}
@ -593,12 +625,12 @@ pub trait ContextMenu: sealed::ContextMenuBase + Send + Sync {
pub(crate) mod sealed {
pub trait IsMenuItemBase {
fn inner(&self) -> &dyn muda::IsMenuItem;
fn inner_muda(&self) -> &dyn muda::IsMenuItem;
}
pub trait ContextMenuBase {
fn inner(&self) -> &dyn muda::ContextMenu;
fn inner_owned(&self) -> Box<dyn muda::ContextMenu>;
fn inner_context(&self) -> &dyn muda::ContextMenu;
fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu>;
fn popup_inner<R: crate::Runtime, P: Into<crate::Position>>(
&self,
window: crate::Window<R>,

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime};
use crate::{menu::MenuId, resources::Resource, run_main_thread, AppHandle, Manager, Runtime};
/// A menu item inside a [`Menu`] or [`Submenu`] and contains only text.
///
@ -31,7 +31,7 @@ unsafe impl<R: Runtime> Sync for MenuItem<R> {}
unsafe impl<R: Runtime> Send for MenuItem<R> {}
impl<R: Runtime> super::sealed::IsMenuItemBase for MenuItem<R> {
fn inner(&self) -> &dyn muda::IsMenuItem {
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
&self.inner
}
}
@ -132,3 +132,5 @@ impl<R: Runtime> MenuItem<R> {
run_main_thread!(self, |self_: Self| self_.inner.set_accelerator(accel))?.map_err(Into::into)
}
}
impl<R: Runtime> Resource for MenuItem<R> {}

View File

@ -0,0 +1,876 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::{
collections::HashMap,
sync::{Mutex, MutexGuard},
};
use serde::{Deserialize, Serialize};
use tauri_runtime::window::dpi::Position;
use super::{sealed::ContextMenuBase, *};
use crate::{
command,
ipc::{channel::JavaScriptChannelId, Channel},
plugin::{Builder, TauriPlugin},
resources::{ResourceId, ResourceTable},
AppHandle, IconDto, Manager, RunEvent, Runtime, State, Window,
};
use tauri_macros::do_menu_item;
#[derive(Deserialize, Serialize)]
pub(crate) enum ItemKind {
Menu,
MenuItem,
Predefined,
Submenu,
Check,
Icon,
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct AboutMetadata {
pub name: Option<String>,
pub version: Option<String>,
pub short_version: Option<String>,
pub authors: Option<Vec<String>>,
pub comments: Option<String>,
pub copyright: Option<String>,
pub license: Option<String>,
pub website: Option<String>,
pub website_label: Option<String>,
pub credits: Option<String>,
pub icon: Option<IconDto>,
}
impl From<AboutMetadata> for super::AboutMetadata {
fn from(value: AboutMetadata) -> Self {
Self {
name: value.name,
version: value.version,
short_version: value.short_version,
authors: value.authors,
comments: value.comments,
copyright: value.copyright,
license: value.license,
website: value.website,
website_label: value.website_label,
credits: value.credits,
icon: value.icon.map(Into::into),
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Deserialize)]
enum Predefined {
Separator,
Copy,
Cut,
Paste,
SelectAll,
Undo,
Redo,
Minimize,
Maximize,
Fullscreen,
Hide,
HideOthers,
ShowAll,
CloseWindow,
Quit,
About(Option<AboutMetadata>),
Services,
}
#[derive(Deserialize)]
struct SubmenuPayload {
id: Option<MenuId>,
text: String,
enabled: Option<bool>,
items: Vec<MenuItemPayloadKind>,
}
impl SubmenuPayload {
pub fn create_item<R: Runtime>(
self,
window: &Window<R>,
resources_table: &MutexGuard<'_, ResourceTable>,
) -> crate::Result<Submenu<R>> {
let mut builder = if let Some(id) = self.id {
SubmenuBuilder::with_id(window, id, self.text)
} else {
SubmenuBuilder::new(window, self.text)
};
if let Some(enabled) = self.enabled {
builder = builder.enabled(enabled);
}
for item in self.items {
builder = item.with_item(window, resources_table, |i| Ok(builder.item(i)))?;
}
builder.build()
}
}
#[derive(Deserialize)]
struct CheckMenuItemPayload {
handler: Option<JavaScriptChannelId>,
id: Option<MenuId>,
text: String,
checked: bool,
enabled: Option<bool>,
accelerator: Option<String>,
}
impl CheckMenuItemPayload {
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> CheckMenuItem<R> {
let mut builder = if let Some(id) = self.id {
CheckMenuItemBuilder::with_id(id, self.text)
} else {
CheckMenuItemBuilder::new(self.text)
};
if let Some(accelerator) = self.accelerator {
builder = builder.accelerator(accelerator);
}
if let Some(enabled) = self.enabled {
builder = builder.enabled(enabled);
}
let item = builder.checked(self.checked).build(window);
if let Some(handler) = self.handler {
let handler = handler.channel_on(window.clone());
window
.state::<MenuChannels>()
.0
.lock()
.unwrap()
.insert(item.id().clone(), handler);
}
item
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum Icon {
Native(NativeIcon),
Icon(IconDto),
}
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct IconMenuItemPayload {
handler: Option<JavaScriptChannelId>,
id: Option<MenuId>,
text: String,
icon: Icon,
enabled: Option<bool>,
accelerator: Option<String>,
}
impl IconMenuItemPayload {
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> IconMenuItem<R> {
let mut builder = if let Some(id) = self.id {
IconMenuItemBuilder::with_id(id, self.text)
} else {
IconMenuItemBuilder::new(self.text)
};
if let Some(accelerator) = self.accelerator {
builder = builder.accelerator(accelerator);
}
if let Some(enabled) = self.enabled {
builder = builder.enabled(enabled);
}
builder = match self.icon {
Icon::Native(native_icon) => builder.native_icon(native_icon),
Icon::Icon(icon) => builder.icon(icon.into()),
};
let item = builder.build(window);
if let Some(handler) = self.handler {
let handler = handler.channel_on(window.clone());
window
.state::<MenuChannels>()
.0
.lock()
.unwrap()
.insert(item.id().clone(), handler);
}
item
}
}
#[derive(Deserialize)]
struct MenuItemPayload {
handler: Option<JavaScriptChannelId>,
id: Option<MenuId>,
text: String,
enabled: Option<bool>,
accelerator: Option<String>,
}
impl MenuItemPayload {
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> MenuItem<R> {
let mut builder = if let Some(id) = self.id {
MenuItemBuilder::with_id(id, self.text)
} else {
MenuItemBuilder::new(self.text)
};
if let Some(accelerator) = self.accelerator {
builder = builder.accelerator(accelerator);
}
if let Some(enabled) = self.enabled {
builder = builder.enabled(enabled);
}
let item = builder.build(window);
if let Some(handler) = self.handler {
let handler = handler.channel_on(window.clone());
window
.state::<MenuChannels>()
.0
.lock()
.unwrap()
.insert(item.id().clone(), handler);
}
item
}
}
#[derive(Deserialize)]
struct PredefinedMenuItemPayload {
item: Predefined,
text: Option<String>,
}
impl PredefinedMenuItemPayload {
pub fn create_item<R: Runtime>(self, window: &Window<R>) -> PredefinedMenuItem<R> {
match self.item {
Predefined::Separator => PredefinedMenuItem::separator(window),
Predefined::Copy => PredefinedMenuItem::copy(window, self.text.as_deref()),
Predefined::Cut => PredefinedMenuItem::cut(window, self.text.as_deref()),
Predefined::Paste => PredefinedMenuItem::paste(window, self.text.as_deref()),
Predefined::SelectAll => PredefinedMenuItem::select_all(window, self.text.as_deref()),
Predefined::Undo => PredefinedMenuItem::undo(window, self.text.as_deref()),
Predefined::Redo => PredefinedMenuItem::redo(window, self.text.as_deref()),
Predefined::Minimize => PredefinedMenuItem::minimize(window, self.text.as_deref()),
Predefined::Maximize => PredefinedMenuItem::maximize(window, self.text.as_deref()),
Predefined::Fullscreen => PredefinedMenuItem::fullscreen(window, self.text.as_deref()),
Predefined::Hide => PredefinedMenuItem::hide(window, self.text.as_deref()),
Predefined::HideOthers => PredefinedMenuItem::hide_others(window, self.text.as_deref()),
Predefined::ShowAll => PredefinedMenuItem::show_all(window, self.text.as_deref()),
Predefined::CloseWindow => PredefinedMenuItem::close_window(window, self.text.as_deref()),
Predefined::Quit => PredefinedMenuItem::quit(window, self.text.as_deref()),
Predefined::About(metadata) => {
PredefinedMenuItem::about(window, self.text.as_deref(), metadata.map(Into::into))
}
Predefined::Services => PredefinedMenuItem::services(window, self.text.as_deref()),
}
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum MenuItemPayloadKind {
ExistingItem((ResourceId, ItemKind)),
Predefined(PredefinedMenuItemPayload),
Check(CheckMenuItemPayload),
Submenu(SubmenuPayload),
Icon(IconMenuItemPayload),
MenuItem(MenuItemPayload),
}
impl MenuItemPayloadKind {
pub fn with_item<T, R: Runtime, F: FnOnce(&dyn IsMenuItem<R>) -> crate::Result<T>>(
self,
window: &Window<R>,
resources_table: &MutexGuard<'_, ResourceTable>,
f: F,
) -> crate::Result<T> {
match self {
Self::ExistingItem((rid, kind)) => {
do_menu_item!(resources_table, rid, kind, |i| f(&*i))
}
Self::Submenu(i) => f(&i.create_item(window, resources_table)?),
Self::Predefined(i) => f(&i.create_item(window)),
Self::Check(i) => f(&i.create_item(window)),
Self::Icon(i) => f(&i.create_item(window)),
Self::MenuItem(i) => f(&i.create_item(window)),
}
}
}
#[derive(Deserialize, Default)]
#[serde(rename_all = "camelCase")]
struct NewOptions {
id: Option<MenuId>,
text: Option<String>,
enabled: Option<bool>,
checked: Option<bool>,
accelerator: Option<String>,
#[serde(rename = "item")]
predefined_item: Option<Predefined>,
icon: Option<Icon>,
items: Option<Vec<MenuItemPayloadKind>>,
}
#[command(root = "crate")]
fn new<R: Runtime>(
app: AppHandle<R>,
window: Window<R>,
kind: ItemKind,
options: Option<NewOptions>,
channels: State<'_, MenuChannels>,
handler: Channel,
) -> crate::Result<(ResourceId, MenuId)> {
let options = options.unwrap_or_default();
let mut resources_table = app.manager.resources_table();
let (rid, id) = match kind {
ItemKind::Menu => {
let mut builder = MenuBuilder::new(&app);
if let Some(id) = options.id {
builder = builder.id(id);
}
if let Some(items) = options.items {
for item in items {
builder = item.with_item(&window, &resources_table, |i| Ok(builder.item(i)))?;
}
}
let menu = builder.build()?;
let id = menu.id().clone();
let rid = resources_table.add(menu);
(rid, id)
}
ItemKind::Submenu => {
let submenu = SubmenuPayload {
id: options.id,
text: options.text.unwrap_or_default(),
enabled: options.enabled,
items: options.items.unwrap_or_default(),
}
.create_item(&window, &resources_table)?;
let id = submenu.id().clone();
let rid = resources_table.add(submenu);
(rid, id)
}
ItemKind::MenuItem => {
let item = MenuItemPayload {
// handler managed in this function instead
handler: None,
id: options.id,
text: options.text.unwrap_or_default(),
enabled: options.enabled,
accelerator: options.accelerator,
}
.create_item(&window);
let id = item.id().clone();
let rid = resources_table.add(item);
(rid, id)
}
ItemKind::Predefined => {
let item = PredefinedMenuItemPayload {
item: options.predefined_item.unwrap(),
text: options.text,
}
.create_item(&window);
let id = item.id().clone();
let rid = resources_table.add(item);
(rid, id)
}
ItemKind::Check => {
let item = CheckMenuItemPayload {
// handler managed in this function instead
handler: None,
id: options.id,
text: options.text.unwrap_or_default(),
checked: options.checked.unwrap_or_default(),
enabled: options.enabled,
accelerator: options.accelerator,
}
.create_item(&window);
let id = item.id().clone();
let rid = resources_table.add(item);
(rid, id)
}
ItemKind::Icon => {
let item = IconMenuItemPayload {
// handler managed in this function instead
handler: None,
id: options.id,
text: options.text.unwrap_or_default(),
icon: options.icon.unwrap_or(Icon::Native(NativeIcon::User)),
enabled: options.enabled,
accelerator: options.accelerator,
}
.create_item(&window);
let id = item.id().clone();
let rid = resources_table.add(item);
(rid, id)
}
};
channels.0.lock().unwrap().insert(id.clone(), handler);
Ok((rid, id))
}
#[command(root = "crate")]
fn append<R: Runtime>(
window: Window<R>,
rid: ResourceId,
kind: ItemKind,
items: Vec<MenuItemPayloadKind>,
) -> crate::Result<()> {
let resources_table = window.manager.resources_table();
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
for item in items {
item.with_item(&window, &resources_table, |i| menu.append(i))?;
}
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
for item in items {
item.with_item(&window, &resources_table, |i| submenu.append(i))?;
}
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
Ok(())
}
#[command(root = "crate")]
fn prepend<R: Runtime>(
window: Window<R>,
rid: ResourceId,
kind: ItemKind,
items: Vec<MenuItemPayloadKind>,
) -> crate::Result<()> {
let resources_table = window.manager.resources_table();
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
for item in items {
item.with_item(&window, &resources_table, |i| menu.prepend(i))?;
}
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
for item in items {
item.with_item(&window, &resources_table, |i| submenu.prepend(i))?;
}
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
Ok(())
}
#[command(root = "crate")]
fn insert<R: Runtime>(
window: Window<R>,
rid: ResourceId,
kind: ItemKind,
items: Vec<MenuItemPayloadKind>,
mut position: usize,
) -> crate::Result<()> {
let resources_table = window.manager.resources_table();
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
for item in items {
item.with_item(&window, &resources_table, |i| menu.insert(i, position))?;
position += 1
}
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
for item in items {
item.with_item(&window, &resources_table, |i| submenu.insert(i, position))?;
position += 1
}
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
Ok(())
}
#[command(root = "crate")]
fn remove<R: Runtime>(
app: AppHandle<R>,
menu_rid: ResourceId,
menu_kind: ItemKind,
item: (ResourceId, ItemKind),
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let (rid, kind) = item;
match menu_kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(menu_rid)?;
do_menu_item!(resources_table, rid, kind, |i| menu.remove(&*i))?;
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(menu_rid)?;
do_menu_item!(resources_table, rid, kind, |i| submenu.remove(&*i))?;
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
Ok(())
}
macro_rules! make_item_resource {
($resources_table:ident, $item:ident) => {{
let id = $item.id().clone();
let (rid, kind) = match $item {
MenuItemKind::MenuItem(i) => ($resources_table.add(i), ItemKind::MenuItem),
MenuItemKind::Submenu(i) => ($resources_table.add(i), ItemKind::Submenu),
MenuItemKind::Predefined(i) => ($resources_table.add(i), ItemKind::Predefined),
MenuItemKind::Check(i) => ($resources_table.add(i), ItemKind::Check),
MenuItemKind::Icon(i) => ($resources_table.add(i), ItemKind::Icon),
};
(rid, id, kind)
}};
}
#[command(root = "crate")]
fn remove_at<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
kind: ItemKind,
position: usize,
) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {
let mut resources_table = app.manager.resources_table();
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
if let Some(item) = menu.remove_at(position)? {
return Ok(Some(make_item_resource!(resources_table, item)));
}
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
if let Some(item) = submenu.remove_at(position)? {
return Ok(Some(make_item_resource!(resources_table, item)));
}
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
Ok(None)
}
#[command(root = "crate")]
fn items<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
kind: ItemKind,
) -> crate::Result<Vec<(ResourceId, MenuId, ItemKind)>> {
let mut resources_table = app.manager.resources_table();
let items = match kind {
ItemKind::Menu => resources_table.get::<Menu<R>>(rid)?.items()?,
ItemKind::Submenu => resources_table.get::<Submenu<R>>(rid)?.items()?,
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
Ok(
items
.into_iter()
.map(|i| make_item_resource!(resources_table, i))
.collect::<Vec<_>>(),
)
}
#[command(root = "crate")]
fn get<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
kind: ItemKind,
id: MenuId,
) -> crate::Result<Option<(ResourceId, MenuId, ItemKind)>> {
let mut resources_table = app.manager.resources_table();
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
if let Some(item) = menu.get(&id) {
return Ok(Some(make_item_resource!(resources_table, item)));
}
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
if let Some(item) = submenu.get(&id) {
return Ok(Some(make_item_resource!(resources_table, item)));
}
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
Ok(None)
}
#[command(root = "crate")]
async fn popup<R: Runtime>(
app: AppHandle<R>,
current_window: Window<R>,
rid: ResourceId,
kind: ItemKind,
window: Option<String>,
at: Option<Position>,
) -> crate::Result<()> {
let window = window
.map(|w| app.get_window(&w))
.unwrap_or(Some(current_window));
if let Some(window) = window {
let resources_table = app.manager.resources_table();
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
menu.popup_inner(window, at)?;
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
submenu.popup_inner(window, at)?;
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
}
Ok(())
}
#[command(root = "crate")]
fn default<R: Runtime>(app: AppHandle<R>) -> crate::Result<(ResourceId, MenuId)> {
let mut resources_table = app.manager.resources_table();
let menu = Menu::default(&app)?;
let id = menu.id().clone();
let rid = resources_table.add(menu);
Ok((rid, id))
}
#[command(root = "crate")]
async fn set_as_app_menu<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
) -> crate::Result<Option<(ResourceId, MenuId)>> {
let mut resources_table = app.manager.resources_table();
let menu = resources_table.get::<Menu<R>>(rid)?;
if let Some(menu) = menu.set_as_app_menu()? {
let id = menu.id().clone();
let rid = resources_table.add(menu);
return Ok(Some((rid, id)));
}
Ok(None)
}
#[command(root = "crate")]
async fn set_as_window_menu<R: Runtime>(
app: AppHandle<R>,
current_window: Window<R>,
rid: ResourceId,
window: Option<String>,
) -> crate::Result<Option<(ResourceId, MenuId)>> {
let window = window
.map(|w| app.get_window(&w))
.unwrap_or(Some(current_window));
if let Some(window) = window {
let mut resources_table = app.manager.resources_table();
let menu = resources_table.get::<Menu<R>>(rid)?;
if let Some(menu) = menu.set_as_window_menu(&window)? {
let id = menu.id().clone();
let rid = resources_table.add(menu);
return Ok(Some((rid, id)));
}
}
Ok(None)
}
#[command(root = "crate")]
fn text<R: Runtime>(app: AppHandle<R>, rid: ResourceId, kind: ItemKind) -> crate::Result<String> {
let resources_table = app.manager.resources_table();
do_menu_item!(resources_table, rid, kind, |i| i.text())
}
#[command(root = "crate")]
fn set_text<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
kind: ItemKind,
text: String,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
do_menu_item!(resources_table, rid, kind, |i| i.set_text(text))
}
#[command(root = "crate")]
fn is_enabled<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
kind: ItemKind,
) -> crate::Result<bool> {
let resources_table = app.manager.resources_table();
do_menu_item!(resources_table, rid, kind, |i| i.is_enabled(), !Predefined)
}
#[command(root = "crate")]
fn set_enabled<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
kind: ItemKind,
enabled: bool,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
do_menu_item!(
resources_table,
rid,
kind,
|i| i.set_enabled(enabled),
!Predefined
)
}
#[command(root = "crate")]
fn set_accelerator<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
kind: ItemKind,
accelerator: Option<String>,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
do_menu_item!(
resources_table,
rid,
kind,
|i| i.set_accelerator(accelerator),
!Predefined | !Submenu
)
}
#[command(root = "crate")]
fn set_as_windows_menu_for_nsapp<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
) -> crate::Result<()> {
#[cfg(target_os = "macos")]
{
let resources_table = app.manager.resources_table();
let submenu = resources_table.get::<Submenu<R>>(rid)?;
submenu.set_as_help_menu_for_nsapp()?;
}
let _ = rid;
let _ = app;
Ok(())
}
#[command(root = "crate")]
fn set_as_help_menu_for_nsapp<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<()> {
#[cfg(target_os = "macos")]
{
let resources_table = app.manager.resources_table();
let submenu = resources_table.get::<Submenu<R>>(rid)?;
submenu.set_as_help_menu_for_nsapp()?;
}
let _ = rid;
let _ = app;
Ok(())
}
#[command(root = "crate")]
fn is_checked<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<bool> {
let resources_table = app.manager.resources_table();
let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;
check_item.is_checked()
}
#[command(root = "crate")]
fn set_checked<R: Runtime>(app: AppHandle<R>, rid: ResourceId, checked: bool) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let check_item = resources_table.get::<CheckMenuItem<R>>(rid)?;
check_item.set_checked(checked)
}
#[command(root = "crate")]
fn set_icon<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
icon: Option<Icon>,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let icon_item = resources_table.get::<IconMenuItem<R>>(rid)?;
match icon {
Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
Some(Icon::Icon(icon)) => icon_item.set_icon(Some(icon.into())),
None => {
icon_item.set_icon(None)?;
icon_item.set_native_icon(None)?;
Ok(())
}
}
}
struct MenuChannels(Mutex<HashMap<MenuId, Channel>>);
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("menu")
.setup(|app, _api| {
app.manage(MenuChannels(Mutex::default()));
Ok(())
})
.on_event(|app, e| {
if let RunEvent::MenuEvent(e) = e {
if let Some(channel) = app.state::<MenuChannels>().0.lock().unwrap().get(&e.id) {
let _ = channel.send(&e.id);
}
}
})
.invoke_handler(crate::generate_handler![
new,
append,
prepend,
insert,
remove,
remove_at,
items,
get,
popup,
default,
set_as_app_menu,
set_as_window_menu,
text,
set_text,
is_enabled,
set_enabled,
set_accelerator,
set_as_windows_menu_for_nsapp,
set_as_help_menu_for_nsapp,
is_checked,
set_checked,
set_icon,
])
.build()
}

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
use super::AboutMetadata;
use crate::{menu::MenuId, run_main_thread, AppHandle, Manager, Runtime};
use crate::{menu::MenuId, resources::Resource, run_main_thread, AppHandle, Manager, Runtime};
/// A predefined (native) menu item which has a predfined behavior by the OS or by this crate.
pub struct PredefinedMenuItem<R: Runtime> {
@ -29,7 +29,7 @@ unsafe impl<R: Runtime> Sync for PredefinedMenuItem<R> {}
unsafe impl<R: Runtime> Send for PredefinedMenuItem<R> {}
impl<R: Runtime> super::sealed::IsMenuItemBase for PredefinedMenuItem<R> {
fn inner(&self) -> &dyn muda::IsMenuItem {
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
&self.inner
}
}
@ -285,3 +285,5 @@ impl<R: Runtime> PredefinedMenuItem<R> {
&self.app_handle
}
}
impl<R: Runtime> Resource for PredefinedMenuItem<R> {}

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: MIT
use super::{sealed::ContextMenuBase, IsMenuItem, MenuItemKind};
use crate::{run_main_thread, AppHandle, Manager, Position, Runtime, Window};
use crate::{resources::Resource, run_main_thread, AppHandle, Manager, Position, Runtime, Window};
use muda::{ContextMenu, MenuId};
/// A type that is a submenu inside a [`Menu`] or [`Submenu`]
@ -33,7 +33,7 @@ impl<R: Runtime> Clone for Submenu<R> {
}
impl<R: Runtime> super::sealed::IsMenuItemBase for Submenu<R> {
fn inner(&self) -> &dyn muda::IsMenuItem {
fn inner_muda(&self) -> &dyn muda::IsMenuItem {
&self.inner
}
}
@ -95,11 +95,11 @@ impl<R: Runtime> ContextMenuBase for Submenu<R> {
})
}
fn inner(&self) -> &dyn muda::ContextMenu {
fn inner_context(&self) -> &dyn muda::ContextMenu {
&self.inner
}
fn inner_owned(&self) -> Box<dyn muda::ContextMenu> {
fn inner_context_owned(&self) -> Box<dyn muda::ContextMenu> {
Box::new(self.clone().inner)
}
}
@ -173,8 +173,10 @@ impl<R: Runtime> Submenu<R> {
/// Add a menu item to the end of this submenu.
pub fn append(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| self_.inner.append(kind.inner().inner()))?
.map_err(Into::into)
run_main_thread!(self, |self_: Self| self_
.inner
.append(kind.inner().inner_muda()))?
.map_err(Into::into)
}
/// Add menu items to the end of this submenu. It calls [`Submenu::append`] in a loop internally.
@ -190,7 +192,7 @@ impl<R: Runtime> Submenu<R> {
pub fn prepend(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| {
self_.inner.prepend(kind.inner().inner())
self_.inner.prepend(kind.inner().inner_muda())
})?
.map_err(Into::into)
}
@ -204,7 +206,7 @@ impl<R: Runtime> Submenu<R> {
pub fn insert(&self, item: &dyn IsMenuItem<R>, position: usize) -> crate::Result<()> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| {
self_.inner.insert(kind.inner().inner(), position)
self_.inner.insert(kind.inner().inner_muda(), position)
})?
.map_err(Into::into)
}
@ -221,47 +223,41 @@ impl<R: Runtime> Submenu<R> {
/// Remove a menu item from this submenu.
pub fn remove(&self, item: &dyn IsMenuItem<R>) -> crate::Result<()> {
let kind = item.kind();
run_main_thread!(self, |self_: Self| self_.inner.remove(kind.inner().inner()))?
.map_err(Into::into)
run_main_thread!(self, |self_: Self| self_
.inner
.remove(kind.inner().inner_muda()))?
.map_err(Into::into)
}
/// Remove the menu item at the specified position from this submenu and returns it.
pub fn remove_at(&self, position: usize) -> crate::Result<Option<MenuItemKind<R>>> {
run_main_thread!(self, |self_: Self| self_
.inner
.remove_at(position)
.map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i)))
}
/// Retrieves the menu item matching the given identifier.
pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>
where
I: ?Sized,
MenuId: PartialEq<&'a I>,
{
self
.items()
.unwrap_or_default()
.into_iter()
.find(|i| i.id() == &id)
}
/// Returns a list of menu items that has been added to this submenu.
pub fn items(&self) -> crate::Result<Vec<MenuItemKind<R>>> {
let handle = self.app_handle.clone();
run_main_thread!(self, |self_: Self| {
self_
.inner
.items()
.into_iter()
.map(|i| match i {
muda::MenuItemKind::MenuItem(i) => super::MenuItemKind::MenuItem(super::MenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
muda::MenuItemKind::Submenu(i) => super::MenuItemKind::Submenu(super::Submenu {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
muda::MenuItemKind::Predefined(i) => {
super::MenuItemKind::Predefined(super::PredefinedMenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
})
}
muda::MenuItemKind::Check(i) => super::MenuItemKind::Check(super::CheckMenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
muda::MenuItemKind::Icon(i) => super::MenuItemKind::Icon(super::IconMenuItem {
id: i.id().clone(),
inner: i,
app_handle: handle.clone(),
}),
})
.map(|i| MenuItemKind::from_muda(self_.app_handle.clone(), i))
.collect::<Vec<_>>()
})
}
@ -307,22 +303,11 @@ impl<R: Runtime> Submenu<R> {
///
/// If no menu is set as the Help menu, macOS will automatically use any menu
/// which has a title matching the localized word "Help".
#[cfg(target_os = "macos")]
pub fn set_as_help_menu_for_nsapp(&self) -> crate::Result<()> {
#[cfg(target_os = "macos")]
run_main_thread!(self, |self_: Self| self_.inner.set_as_help_menu_for_nsapp())?;
Ok(())
}
/// Retrieves the menu item matching the given identifier.
pub fn get<'a, I>(&self, id: &'a I) -> Option<MenuItemKind<R>>
where
I: ?Sized,
MenuId: PartialEq<&'a I>,
{
self
.items()
.unwrap_or_default()
.into_iter()
.find(|i| i.id() == &id)
}
}
impl<R: Runtime> Resource for Submenu<R> {}

View File

@ -0,0 +1,143 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
// a modified version of https://github.com/denoland/deno/blob/0ae83847f498a2886ae32172e50fd5bdbab2f524/core/resources.rs#L220
pub(crate) mod plugin;
use crate::error::Error;
use std::{
any::{type_name, Any, TypeId},
borrow::Cow,
collections::BTreeMap,
sync::Arc,
};
/// Resources are Rust objects that are stored in [ResourceTable] and managed by tauri.
/// They are identified in JS by a numeric ID (the resource ID, or rid).
/// Resources can be created in commands. Resources can also be retrieved in commands by
/// their rid. Resources are thread-safe.
///
/// Resources are reference counted in Rust. This means that they can be
/// cloned and passed around. When the last reference is dropped, the resource
/// is automatically closed. As long as the resource exists in the resource
/// table, the reference count is at least 1.
pub(crate) trait Resource: Any + 'static + Send + Sync {
/// Returns a string representation of the resource. The default implementation
/// returns the Rust type name, but specific resource types may override this
/// trait method.
fn name(&self) -> Cow<'_, str> {
type_name::<Self>().into()
}
/// Resources may implement the `close()` trait method if they need to do
/// resource specific clean-ups, such as cancelling pending futures, after a
/// resource has been removed from the resource table.
fn close(self: Arc<Self>) {}
}
impl dyn Resource {
#[inline(always)]
fn is<T: Resource>(&self) -> bool {
self.type_id() == TypeId::of::<T>()
}
#[inline(always)]
pub(crate) fn downcast_arc<'a, T: Resource>(self: &'a Arc<Self>) -> Option<&'a Arc<T>> {
if self.is::<T>() {
// A resource is stored as `Arc<T>` in a BTreeMap
// and is safe to cast to `Arc<T>` because of the runtime
// check done in `self.is::<T>()`
let ptr = self as *const Arc<_> as *const Arc<T>;
Some(unsafe { &*ptr })
} else {
None
}
}
}
/// A `ResourceId` is an integer value referencing a resource. It could be
/// considered to be the tauri equivalent of a `file descriptor` in POSIX like
/// operating systems. Elsewhere in the code base it is commonly abbreviated
/// to `rid`.
pub(crate) type ResourceId = u32;
/// Map-like data structure storing Tauri's resources (equivalent to file
/// descriptors).
///
/// Provides basic methods for element access. A resource can be of any type.
/// Different types of resources can be stored in the same map, and provided
/// with a name for description.
///
/// Each resource is identified through a _resource ID (rid)_, which acts as
/// the key in the map.
#[derive(Default)]
pub(crate) struct ResourceTable {
pub(crate) index: BTreeMap<ResourceId, Arc<dyn Resource>>,
pub(crate) next_rid: ResourceId,
}
impl ResourceTable {
/// Inserts resource into the resource table, which takes ownership of it.
///
/// The resource type is erased at runtime and must be statically known
/// when retrieving it through `get()`.
///
/// Returns a unique resource ID, which acts as a key for this resource.
pub(crate) fn add<T: Resource>(&mut self, resource: T) -> ResourceId {
self.add_arc(Arc::new(resource))
}
/// Inserts a `Arc`-wrapped resource into the resource table.
///
/// The resource type is erased at runtime and must be statically known
/// when retrieving it through `get()`.
///
/// Returns a unique resource ID, which acts as a key for this resource.
pub(crate) fn add_arc<T: Resource>(&mut self, resource: Arc<T>) -> ResourceId {
let resource = resource as Arc<dyn Resource>;
self.add_arc_dyn(resource)
}
pub(crate) fn add_arc_dyn(&mut self, resource: Arc<dyn Resource>) -> ResourceId {
let rid = self.next_rid;
let removed_resource = self.index.insert(rid, resource);
// TODO: add replace method if needed
assert!(removed_resource.is_none());
self.next_rid += 1;
rid
}
/// Returns a reference counted pointer to the resource of type `T` with the
/// given `rid`. If `rid` is not present or has a type different than `T`,
/// this function returns [`Error::BadResourceId`].
pub(crate) fn get<T: Resource>(&self, rid: ResourceId) -> Result<Arc<T>, Error> {
self
.index
.get(&rid)
.and_then(|rc| rc.downcast_arc::<T>())
.map(Clone::clone)
.ok_or_else(|| Error::BadResourceId(rid))
}
/// Removes the resource with the given `rid` from the resource table. If the
/// only reference to this resource existed in the resource table, this will
/// cause the resource to be dropped. However, since resources are reference
/// counted, therefore pending ops are not automatically cancelled. A resource
/// may implement the `close()` method to perform clean-ups such as canceling
/// ops.
pub(crate) fn close(&mut self, rid: ResourceId) -> Result<(), Error> {
self
.index
.remove(&rid)
.ok_or_else(|| Error::BadResourceId(rid))
.map(|resource| resource.close())
}
/// Removes and frees all resources stored.
pub(crate) fn clear(&mut self) {
self.index.clear()
}
}

View File

@ -0,0 +1,22 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use crate::{
command,
plugin::{Builder, TauriPlugin},
AppHandle, Runtime,
};
use super::ResourceId;
#[command(root = "crate")]
fn close<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<()> {
app.manager.resources_table().close(rid)
}
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("resources")
.invoke_handler(crate::generate_handler![close])
.build()
}

View File

@ -4,19 +4,21 @@
#![cfg(all(desktop, feature = "tray-icon"))]
//! Tray icon types and utility functions
//! Tray icon types and utilities.
pub(crate) mod plugin;
use crate::app::{GlobalMenuEventListener, GlobalTrayIconEventListener};
use crate::menu::ContextMenu;
use crate::menu::MenuEvent;
use crate::resources::Resource;
use crate::{run_main_thread, AppHandle, Icon, Manager, Runtime};
use serde::Serialize;
use std::path::Path;
pub use tray_icon::TrayIconId;
// TODO(muda-migration): figure out js events
/// Describes a rectangle including position (x - y axis) and size.
#[derive(Debug, PartialEq, Clone, Copy, Default)]
#[derive(Debug, PartialEq, Clone, Copy, Default, Serialize)]
pub struct Rectangle {
/// The x-coordinate of the upper-left corner of the rectangle.
pub left: f64,
@ -29,7 +31,7 @@ pub struct Rectangle {
}
/// Describes the click type that triggered this tray icon event.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize)]
pub enum ClickType {
/// Left mouse click.
Left,
@ -51,7 +53,8 @@ impl Default for ClickType {
///
/// - **Linux**: Unsupported. The event is not emmited even though the icon is shown,
/// the icon will still show a context menu on right click.
#[derive(Debug, Clone, Default)]
#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TrayIconEvent {
/// Id of the tray icon which triggered this event.
pub id: TrayIconId,
@ -146,7 +149,7 @@ impl<R: Runtime> TrayIconBuilder<R> {
///
/// - **Linux**: once a menu is set, it cannot be removed or replaced but you can change its content.
pub fn menu<M: ContextMenu>(mut self, menu: &M) -> Self {
self.inner = self.inner.with_menu(menu.inner_owned());
self.inner = self.inner.with_menu(menu.inner_context_owned());
self
}
@ -360,13 +363,13 @@ impl<R: Runtime> TrayIcon<R> {
&self.id
}
/// Set new tray icon. If `None` is provided, it will remove the icon.
/// Sets a new tray icon. If `None` is provided, it will remove the icon.
pub fn set_icon(&self, icon: Option<Icon>) -> crate::Result<()> {
let icon = icon.and_then(|i| i.try_into().ok());
run_main_thread!(self, |self_: Self| self_.inner.set_icon(icon))?.map_err(Into::into)
}
/// Set new tray menu.
/// Sets a new tray menu.
///
/// ## Platform-specific:
///
@ -374,7 +377,7 @@ impl<R: Runtime> TrayIcon<R> {
pub fn set_menu<M: ContextMenu + 'static>(&self, menu: Option<M>) -> crate::Result<()> {
run_main_thread!(self, |self_: Self| self_
.inner
.set_menu(menu.map(|m| m.inner_owned())))
.set_menu(menu.map(|m| m.inner_context_owned())))
}
/// Sets the tooltip for this tray icon.
@ -387,7 +390,7 @@ impl<R: Runtime> TrayIcon<R> {
run_main_thread!(self, |self_: Self| self_.inner.set_tooltip(s))?.map_err(Into::into)
}
/// Sets the tooltip for this tray icon.
/// Sets the title for this tray icon.
///
/// ## Platform-specific:
///
@ -402,7 +405,7 @@ impl<R: Runtime> TrayIcon<R> {
run_main_thread!(self, |self_: Self| self_.inner.set_title(s))
}
/// Show or hide this tray icon
/// Show or hide this tray icon.
pub fn set_visible(&self, visible: bool) -> crate::Result<()> {
run_main_thread!(self, |self_: Self| self_.inner.set_visible(visible))?.map_err(Into::into)
}
@ -419,7 +422,7 @@ impl<R: Runtime> TrayIcon<R> {
Ok(())
}
/// Set the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
/// Sets the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
pub fn set_icon_as_template(&self, #[allow(unused)] is_template: bool) -> crate::Result<()> {
#[cfg(target_os = "macos")]
run_main_thread!(self, |self_: Self| self_
@ -446,3 +449,9 @@ impl TryFrom<Icon> for tray_icon::Icon {
tray_icon::Icon::from_rgba(value.rgba, value.width, value.height).map_err(Into::into)
}
}
impl<R: Runtime> Resource for TrayIcon<R> {
fn close(self: std::sync::Arc<Self>) {
self.app_handle.remove_tray_by_id(&self.id);
}
}

View File

@ -0,0 +1,204 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::path::PathBuf;
use serde::Deserialize;
use crate::{
command,
ipc::Channel,
menu::{plugin::ItemKind, Menu, Submenu},
plugin::{Builder, TauriPlugin},
resources::ResourceId,
tray::TrayIconBuilder,
AppHandle, IconDto, Runtime,
};
use super::TrayIcon;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct TrayIconOptions {
id: Option<String>,
menu: Option<(ResourceId, ItemKind)>,
icon: Option<IconDto>,
tooltip: Option<String>,
title: Option<String>,
temp_dir_path: Option<PathBuf>,
icon_as_template: Option<bool>,
menu_on_left_click: Option<bool>,
}
#[command(root = "crate")]
fn new<R: Runtime>(
app: AppHandle<R>,
options: TrayIconOptions,
handler: Channel,
) -> crate::Result<(ResourceId, String)> {
let mut builder = if let Some(id) = options.id {
TrayIconBuilder::<R>::with_id(id)
} else {
TrayIconBuilder::<R>::new()
};
builder = builder.on_tray_icon_event(move |_tray, e| {
let _ = handler.send(e);
});
let mut resources_table = app.manager.resources_table();
if let Some((rid, kind)) = options.menu {
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
builder = builder.menu(&*menu);
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
builder = builder.menu(&*submenu);
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
}
if let Some(icon) = options.icon {
builder = builder.icon(icon.into());
}
if let Some(tooltip) = options.tooltip {
builder = builder.tooltip(tooltip);
}
if let Some(title) = options.title {
builder = builder.title(title);
}
if let Some(temp_dir_path) = options.temp_dir_path {
builder = builder.temp_dir_path(temp_dir_path);
}
if let Some(icon_as_template) = options.icon_as_template {
builder = builder.icon_as_template(icon_as_template);
}
if let Some(menu_on_left_click) = options.menu_on_left_click {
builder = builder.menu_on_left_click(menu_on_left_click);
}
let tray = builder.build(&app)?;
let id = tray.id().as_ref().to_string();
let rid = resources_table.add(tray);
Ok((rid, id))
}
#[command(root = "crate")]
fn set_icon<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
icon: Option<IconDto>,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
tray.set_icon(icon.map(Into::into))
}
#[command(root = "crate")]
fn set_menu<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
menu: Option<(ResourceId, ItemKind)>,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
if let Some((rid, kind)) = menu {
match kind {
ItemKind::Menu => {
let menu = resources_table.get::<Menu<R>>(rid)?;
tray.set_menu(Some((*menu).clone()))?;
}
ItemKind::Submenu => {
let submenu = resources_table.get::<Submenu<R>>(rid)?;
tray.set_menu(Some((*submenu).clone()))?;
}
_ => return Err(anyhow::anyhow!("unexpected menu item kind").into()),
};
} else {
tray.set_menu(None::<Menu<R>>)?;
}
Ok(())
}
#[command(root = "crate")]
fn set_tooltip<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
tooltip: Option<String>,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
tray.set_tooltip(tooltip)
}
#[command(root = "crate")]
fn set_title<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
title: Option<String>,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
tray.set_title(title)
}
#[command(root = "crate")]
fn set_visible<R: Runtime>(app: AppHandle<R>, rid: ResourceId, visible: bool) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
tray.set_visible(visible)
}
#[command(root = "crate")]
fn set_temp_dir_path<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
path: Option<PathBuf>,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
tray.set_temp_dir_path(path)
}
#[command(root = "crate")]
fn set_icon_as_template<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
as_template: bool,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
tray.set_icon_as_template(as_template)
}
#[command(root = "crate")]
fn set_show_menu_on_left_click<R: Runtime>(
app: AppHandle<R>,
rid: ResourceId,
on_left: bool,
) -> crate::Result<()> {
let resources_table = app.manager.resources_table();
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
tray.set_show_menu_on_left_click(on_left)
}
pub(crate) fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("tray")
.invoke_handler(crate::generate_handler![
new,
set_icon,
set_menu,
set_tooltip,
set_title,
set_visible,
set_temp_dir_path,
set_icon_as_template,
set_show_menu_on_left_click,
])
.build()
}

View File

@ -1411,12 +1411,13 @@ impl<R: Runtime> Window<R> {
/// - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one
/// window, if you need to remove it, use [`AppHandle::remove_menu`] instead.
pub fn remove_menu(&self) -> crate::Result<Option<Menu<R>>> {
let current_menu = self.menu_lock().as_ref().map(|m| m.menu.clone());
let prev_menu = self.menu_lock().take().map(|m| m.menu);
// remove from the window
#[cfg_attr(target_os = "macos", allow(unused_variables))]
if let Some(menu) = current_menu {
if let Some(menu) = &prev_menu {
let window = self.clone();
let menu = menu.clone();
self.run_on_main_thread(move || {
#[cfg(windows)]
if let Ok(hwnd) = window.hwnd() {
@ -1435,8 +1436,6 @@ impl<R: Runtime> Window<R> {
})?;
}
let prev_menu = self.menu_lock().take().map(|m| m.menu);
self
.manager
.remove_menu_from_stash_by_id(prev_menu.as_ref().map(|m| m.id()));
@ -2287,12 +2286,13 @@ impl<R: Runtime> Window<R> {
handled = true;
fn load_channels<R: Runtime>(payload: &serde_json::Value, window: &Window<R>) {
use std::str::FromStr;
if let serde_json::Value::Object(map) = payload {
for v in map.values() {
if let serde_json::Value::String(s) = v {
if s.starts_with(crate::ipc::channel::IPC_PAYLOAD_PREFIX) {
crate::ipc::Channel::load_from_ipc(window.clone(), s);
}
crate::ipc::JavaScriptChannelId::from_str(s)
.map(|id| id.channel_on(window.clone()));
}
}
}

View File

@ -1,4 +1,5 @@
# API example
This example demonstrates Tauri's API capabilities using the `@tauri-apps/api` package. It's used as the main validation app, serving as the testbed of our development process.
In the future, this app will be used on Tauri's integration tests.
@ -7,19 +8,22 @@ In the future, this app will be used on Tauri's integration tests.
## Running the example
- Compile Tauri
go to root of the Tauri repo and run:
Linux / Mac:
go to root of the Tauri repo and run:
Linux / Mac:
```
# choose to install node cli (1)
bash .scripts/setup.sh
```
Windows:
```
./.scripts/setup.ps1
```
- Install dependencies (Run inside of this folder `examples/api/`)
```bash
# with yarn
$ yarn
@ -28,6 +32,7 @@ $ npm install
```
- Run the app in development mode (Run inside of this folder `examples/api/`)
```bash
# with yarn
$ yarn tauri dev
@ -36,6 +41,7 @@ $ npm run tauri dev
```
- Build an run the release app (Run inside of this folder `examples/api/`)
```bash
$ yarn tauri build
$ ./src-tauri/target/release/app

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte + Vite App</title>
<title>API Example App</title>
<script type="module" crossorigin src="/assets/index.js"></script>
<link rel="stylesheet" href="/assets/index.css">
</head>

View File

@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte + Vite App</title>
<title>API Example App</title>
</head>
<body>

View File

@ -1,11 +1,12 @@
{
"name": "svelte-app",
"name": "api",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
"dev": "vite",
"build": "vite build",
"serve": "vite preview",
"preview": "vite preview",
"tauri": "node ../../tooling/cli/node/tauri.js"
},
"dependencies": {

View File

@ -2091,6 +2091,7 @@ dependencies = [
"objc",
"once_cell",
"png",
"serde",
"thiserror",
"windows-sys 0.48.0",
]
@ -3884,6 +3885,7 @@ dependencies = [
"objc",
"once_cell",
"png",
"serde",
"thiserror",
"windows-sys 0.48.0",
]

View File

@ -2,11 +2,6 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod cmd;
#[cfg(desktop)]
mod tray;

View File

@ -2,7 +2,7 @@
"$schema": "../../../core/tauri-config-schema/schema.json",
"build": {
"distDir": "../dist",
"devPath": "http://localhost:5173",
"devPath": "http://localhost:1420",
"beforeDevCommand": "yarn dev",
"beforeBuildCommand": "yarn build",
"withGlobalTauri": true
@ -26,11 +26,7 @@
"name": "theme",
"takesValue": true,
"description": "App theme",
"possibleValues": [
"light",
"dark",
"system"
]
"possibleValues": ["light", "dark", "system"]
},
{
"short": "v",
@ -93,9 +89,7 @@
"csp": {
"default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: http://ipc.localhost",
"font-src": [
"https://fonts.gstatic.com"
],
"font-src": ["https://fonts.gstatic.com"],
"img-src": "'self' asset: http://asset.localhost blob: data:",
"style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com"
},
@ -103,15 +97,10 @@
"assetProtocol": {
"enable": true,
"scope": {
"allow": [
"$APPDATA/db/**",
"$RESOURCE/**"
],
"deny": [
"$APPDATA/db/*.stronghold"
]
"allow": ["$APPDATA/db/**", "$RESOURCE/**"],
"deny": ["$APPDATA/db/*.stronghold"]
}
}
}
}
}
}

View File

@ -8,7 +8,8 @@
import Window from './views/Window.svelte'
import WebRTC from './views/WebRTC.svelte'
import App from './views/App.svelte'
import Menu from './views/Menu.svelte'
import Tray from './views/Tray.svelte'
document.addEventListener('keydown', (event) => {
if (event.ctrlKey && event.key === 'b') {
@ -40,6 +41,16 @@
component: Window,
icon: 'i-codicon-window'
},
{
label: 'Menu',
component: Menu,
icon: 'i-ph-list'
},
{
label: 'Tray',
component: Tray,
icon: 'i-ph-tray'
},
{
label: 'WebRTC',
component: WebRTC,
@ -299,16 +310,21 @@
class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden"
>
<div
role="button"
tabindex="0"
on:mousedown={startResizingConsole}
class="bg-black/20 h-2px cursor-ns-resize"
/>
<div class="flex justify-between items-center px-2">
<p class="font-semibold">Console</p>
<div
role="button"
tabindex="0"
class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
"
on:keypress={(e) => e.key === "Enter"? clear() : {} }
on:click={clear}
>
<div class="i-codicon-clear-all" />

View File

@ -0,0 +1,51 @@
<script>
import { CheckMenuItem } from '@tauri-apps/api/menu'
import MenuItemBuilder from './MenuItemBuilder.svelte'
import { createEventDispatcher } from 'svelte'
export let items = []
const dispatch = createEventDispatcher()
function addItem(event) {
items = [
...items,
{ item: event.detail.item, options: event.detail.options }
]
}
function onItemClick(event) {
dispatch('itemClick', event.detail)
}
function itemIcon(item) {
if (item.options.icon) {
return 'i-ph-images-square'
}
if (item.item instanceof CheckMenuItem) {
return item.options.checked ? 'i-ph-check-duotone' : 'i-ph-square-duotone'
}
if (item.options.item) {
return 'i-ph-globe-stand'
}
return 'i-ph-chat-teardrop-text'
}
function itemToString(item) {
// icon || check|normal || predefined
return item.options.icon || item.options.text || item.options.item
}
</script>
<div class="flex flex-col children:grow gap-2">
<MenuItemBuilder on:new={addItem} on:itemClick={onItemClick} />
<div>
{#each items as item}
<div class="flex flex-row gap-1">
<div class={itemIcon(item)} />
<p>{itemToString(item)}</p>
</div>
{/each}
</div>
</div>

View File

@ -0,0 +1,158 @@
<script>
import {
IconMenuItem,
CheckMenuItem,
PredefinedMenuItem,
MenuItem
} from '@tauri-apps/api/menu'
import { createEventDispatcher } from 'svelte'
const dispatch = createEventDispatcher()
let kind = 'Normal'
let text = ''
let icon = ''
let predefinedItem = ''
let checked = true
const itemKinds = ['Normal', 'Icon', 'Check', 'Predefined']
const predefinedOptions = [
'Separator',
'Copy',
'Cut',
'Paste',
'SelectAll',
'Undo',
'Redo',
'Minimize',
'Maximize',
'Fullscreen',
'Hide',
'HideOthers',
'ShowAll',
'CloseWindow',
'Quit',
'Services'
]
function onKindChange(event) {
kind = event.currentTarget.value
}
function onPredefinedChange(event) {
predefinedItem = event.currentTarget.value
}
async function create() {
let options = null
let item = null
const t = text
switch (kind) {
case 'Normal':
options = {
text,
action: (id) => dispatch('itemClick', { id, text: t })
}
item = await MenuItem.new(options)
break
case 'Icon':
options = {
text,
icon,
action: (id) => dispatch('itemClick', { id, text: t })
}
item = await IconMenuItem.new(options)
break
case 'Check':
options = {
text,
checked,
action: (id) => dispatch('itemClick', { id, text: t })
}
item = await CheckMenuItem.new(options)
break
case 'Predefined':
options = {
item: predefinedItem
}
item = await PredefinedMenuItem.new(options)
break
}
dispatch('new', { item, options })
text = ''
predefinedItem = ''
}
</script>
<div class="flex flex-row gap-2 flex-grow-0">
<div class="flex flex-col">
{#each itemKinds as itemKind}
<div class="flex gap-1">
<input
id="{itemKind}Input"
checked={kind === itemKind}
on:change={onKindChange}
type="radio"
name="kind"
bind:value={itemKind}
/>
<label for="{itemKind}Input">{itemKind}</label>
</div>
{/each}
</div>
<div class="bg-gray/30 dark:bg-white/5 w-1px flex-shrink-0" />
<div class="flex flex-col gap-2">
{#if kind == 'Normal' || kind == 'Icon' || kind == 'Check'}
<input
class="input"
type="text"
placeholder="Text"
bind:value={text}
/>
{/if}
{#if kind == 'Icon'}
<input
class="input"
type="icon"
placeholder="Icon"
bind:value={icon}
/>
{:else if kind == 'Check'}
<div class="flex gap-1">
<input
id="checkItemCheckedInput"
type="checkbox"
bind:checked={checked}
/>
<label for="checkItemCheckedInput">Enabled</label>
</div>
{:else if kind == 'Predefined'}
<div class="flex gap-2 flex-wrap">
{#each predefinedOptions as predefinedOption}
<div class="flex gap-1">
<input
id="{predefinedOption}Input"
checked={kind === predefinedOption}
on:change={onPredefinedChange}
type="radio"
name="predefinedKind"
bind:value={predefinedOption}
/>
<label for="{predefinedOption}Input">{predefinedOption}</label>
</div>
{/each}
</div>
{/if}
</div>
<div class="grow"></div>
<div class="flex flex-col">
<button class="btn" on:click={create}>Create</button>
</div>
</div>

View File

@ -0,0 +1,47 @@
<script>
import { Menu, Submenu } from '@tauri-apps/api/menu'
import MenuBuilder from '../components/MenuBuilder.svelte'
export let onMessage
let items = []
let menu = null
let submenu = null
let menuItemCount = 0
const macOS = navigator.userAgent.includes('Macintosh')
async function createSubmenu() {
submenu = await Submenu.new({
text: 'app',
items: items.map((i) => i.item)
})
}
async function create() {
await createSubmenu()
menuItemCount = items.length
menu = await Menu.new({
items: [submenu]
})
await (macOS ? menu.setAsAppMenu() : menu.setAsWindowMenu())
}
async function popup() {
if (!submenu || menuItemCount !== items.length) {
await createSubmenu()
}
// we can't popup the same menu because it's the app menu (it crashes on macOS)
const m = await Menu.new({ items: [submenu] })
m.popup()
}
function onItemClick(event) {
onMessage(`Item ${event.detail.text} clicked`)
}
</script>
<div>
<MenuBuilder bind:items on:itemClick={onItemClick} />
<button class="btn" on:click={create}>Create menu</button>
<button class="btn" on:click={popup}>Popup</button>
</div>

View File

@ -0,0 +1,79 @@
<script>
import { TrayIcon } from '@tauri-apps/api/tray'
import MenuBuilder from '../components/MenuBuilder.svelte'
import { Menu } from '@tauri-apps/api/menu'
export let onMessage
let icon = null
let tooltip = null
let title = null
let iconAsTemplate = false
let menuOnLeftClick = true
let menuItems = []
function onItemClick(event) {
onMessage(`Item ${event.detail.text} clicked`)
}
async function create() {
TrayIcon.new({
icon,
tooltip,
title,
iconAsTemplate,
menuOnLeftClick,
menu: await Menu.new({
items: menuItems.map((i) => i.item)
}),
action: (event) => onMessage(event)
}).catch(onMessage)
}
</script>
<div class="flex flex-col children:grow gap-2">
<div class="flex gap-1">
<input
class="input grow"
type="text"
placeholder="Title"
bind:value={title}
/>
<input
class="input grow"
type="text"
placeholder="Tooltip"
bind:value={tooltip}
/>
<label>
Menu on left click
<input type="checkbox" bind:checked={menuOnLeftClick} />
</label>
</div>
<div class="flex gap-1">
<input
class="input grow"
type="text"
placeholder="Icon path"
bind:value={icon}
/>
<label>
Icon as template
<input type="checkbox" bind:checked={iconAsTemplate} />
</label>
</div>
<div class="flex children:grow">
<MenuBuilder bind:items={menuItems} on:itemClick={onItemClick} />
</div>
<div class="flex">
<button class="btn" on:click={create} title="Creates the tray icon"
>Create tray</button
>
</div>
</div>

View File

@ -42,7 +42,7 @@
})
onDestroy(() => {
window.stream.getTracks().forEach(function (track) {
window.stream?.getTracks().forEach(function (track) {
track.stop()
})
})

View File

@ -10,7 +10,6 @@
ProgressBarStatus,
Window
} from '@tauri-apps/api/window'
import { invoke } from '@tauri-apps/api/primitives'
const appWindow = getCurrent()
@ -309,9 +308,9 @@
{#if windowMap[selectedWindow]}
<br />
<div class="flex gap-1 items-center">
<label> Icon path </label>
<label for="windowIconPath"> Icon path </label>
<form class="flex gap-1 grow" on:submit|preventDefault={setTitle_}>
<input class="input grow" bind:value={windowIconPath} />
<input id="windowIconPath" class="input grow" bind:value={windowIconPath} />
<button class="btn" type="submit"> Change window icon </button>
</form>
</div>

6
examples/api/src/vite-env.d.ts vendored Normal file
View File

@ -0,0 +1,6 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
/// <reference types="svelte" />
/// <reference types="vite/client" />

View File

@ -21,15 +21,20 @@ export default defineConfig({
}
}
},
// Vite optons tailored for Tauri development and only applied in `tauri dev` or `tauri build`
// prevent vite from obscuring rust errors
clearScreen: false,
// tauri expects a fixed port, fail if that port is not available
server: {
host: mobile ? '0.0.0.0' : false,
port: 5173,
port: 1420,
strictPort: true,
hmr: mobile
? {
protocol: 'ws',
host: internalIpV4Sync(),
port: 5183
port: 1421
}
: undefined,
fs: {

View File

@ -11,18 +11,23 @@
"@jridgewell/trace-mapping" "^0.3.9"
"@antfu/install-pkg@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@antfu/install-pkg/-/install-pkg-0.1.0.tgz#8d8c61820cbc32e5c37d82d515485ad3ee9bd052"
integrity sha512-VaIJd3d1o7irZfK1U0nvBsHMyjkuyMP3HKYVV53z8DKyulkHKmjhhtccXO51WSPeeSHIeoJEoNOKavYpS7jkZw==
version "0.1.1"
resolved "https://registry.yarnpkg.com/@antfu/install-pkg/-/install-pkg-0.1.1.tgz#157bb04f0de8100b9e4c01734db1a6c77e98bbb5"
integrity sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==
dependencies:
execa "^5.1.1"
find-up "^5.0.0"
"@antfu/utils@^0.5.0", "@antfu/utils@^0.5.1":
"@antfu/utils@^0.5.0":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.5.2.tgz#8c2d931ff927be0ebe740169874a3d4004ab414b"
integrity sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==
"@antfu/utils@^0.7.5":
version "0.7.6"
resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-0.7.6.tgz#30a046419b9e1ecd276e53d41ab68fb6c558c04d"
integrity sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==
"@esbuild/android-arm64@0.18.20":
version "0.18.20"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622"
@ -139,20 +144,25 @@
integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==
"@iconify-json/codicon@^1.1.10":
version "1.1.10"
resolved "https://registry.yarnpkg.com/@iconify-json/codicon/-/codicon-1.1.10.tgz#22fee909be51afebfbcc6cd57209064b5363f202"
integrity sha512-xx3nX5k4UeDQnpX9D1T6L1RCEwyLtqu3Lqk9plYK+SoBSQ/kR73bPy9WbYyDVOw2MDw50JCSpZZYlBC718k7Sg==
version "1.1.28"
resolved "https://registry.yarnpkg.com/@iconify-json/codicon/-/codicon-1.1.28.tgz#b753f619a902e382dad6575fa8075aff8fb1d9e3"
integrity sha512-a4rcQ/Rh65QJbifXeb5eAqiXZmgen6oX0jM/BV/64Hh69EREHYVnjndTSdQq+rywCxG6ipiMJBrfBnjX4+20Sg==
dependencies:
"@iconify/types" "^1.1.0"
"@iconify/types" "*"
"@iconify-json/ph@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@iconify-json/ph/-/ph-1.1.1.tgz#17b3dee91a47055bac93d65c32b9ee33c654d56f"
integrity sha512-sIHTY+c1F8x29BM49IqoccJ3T8mvVXPcrE4WOpJ3GsBaip2YqFJRYU60rw64UL6GEI13vWSD7NsZKq8ytTO87g==
version "1.1.6"
resolved "https://registry.yarnpkg.com/@iconify-json/ph/-/ph-1.1.6.tgz#bbb7a3589199d91d8298bc084c4e893c8821ad8e"
integrity sha512-dexzEndlXQX/sbQhnEpA94Pby6JCGV2tZToSGcPPQpbilDGyk5VMd0ymusYoocRAn6+qLpGRvMoz5XFKGqP+VA==
dependencies:
"@iconify/types" "^1.0.12"
"@iconify/types" "*"
"@iconify/types@^1.0.12", "@iconify/types@^1.1.0":
"@iconify/types@*":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57"
integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==
"@iconify/types@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@iconify/types/-/types-1.1.0.tgz#dc15fc988b1b3fd558dd140a24ede7e0aac11280"
integrity sha512-Jh0llaK2LRXQoYsorIH8maClebsnzTcve+7U3rQUSnC11X4jtPnFuyatqFLvMxZ8MLG8dB4zfHsbPfuvxluONw==
@ -256,7 +266,7 @@
vitefu "^0.2.4"
"@tauri-apps/api@../../tooling/api/dist":
version "2.0.0-alpha.8"
version "2.0.0-alpha.9"
"@types/estree@*", "@types/estree@^1.0.0", "@types/estree@^1.0.1":
version "1.0.2"
@ -418,9 +428,9 @@ acorn@^8.10.0, acorn@^8.9.0:
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
anymatch@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
version "3.1.3"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
dependencies:
normalize-path "^3.0.0"
picomatch "^2.0.4"
@ -452,9 +462,9 @@ braces@^3.0.2, braces@~3.0.2:
fill-range "^7.0.1"
cac@^6.7.12:
version "6.7.12"
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.12.tgz#6fb5ea2ff50bd01490dbda497f4ae75a99415193"
integrity sha512-rM7E2ygtMkJqD9c7WnFU6fruFcN3xe4FM5yUmgxhZzIKJk4uHl9U/fhwdajGFQbQuv43FAUo1Fe8gX/oIKDeSA==
version "6.7.14"
resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959"
integrity sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==
chokidar@^3.5.3:
version "3.5.3"
@ -483,9 +493,9 @@ code-red@^1.0.3:
periscopic "^3.1.0"
colorette@^2.0.16:
version "2.0.19"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
integrity sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==
version "2.0.20"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
consola@^2.15.3:
version "2.15.3"
@ -501,15 +511,7 @@ cross-spawn@^7.0.3:
shebang-command "^2.0.0"
which "^2.0.1"
css-tree@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.1.0.tgz#170e27ccf94e7c5facb183765c25898be843d1d2"
integrity sha512-PcysZRzToBbrpoUrZ9qfblRIRf8zbEAkU0AIpQFtgkFK0vSbzOmBCvdSAx2Zg7Xx5wiYJKUKk0NMP7kxevie/A==
dependencies:
mdn-data "2.0.27"
source-map-js "^1.0.1"
css-tree@^2.3.1:
css-tree@^2.1.0, css-tree@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
@ -536,20 +538,20 @@ default-gateway@^6.0.3:
dependencies:
execa "^5.0.0"
defu@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.0.0.tgz#b397a6709a2f3202747a3d9daf9446e41ad0c5fc"
integrity sha512-t2MZGLf1V2rV4VBZbWIaXKdX/mUcYW0n2znQZoADBkGGxYL8EWqCuCZBmJPJ/Yy9fofJkyuuSuo5GSwo0XdEgw==
defu@^6.1.2:
version "6.1.2"
resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.2.tgz#1217cba167410a1765ba93893c6dbac9ed9d9e5c"
integrity sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==
dequal@^2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
destr@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/destr/-/destr-1.1.1.tgz#910457d10a2f2f247add4ca4fdb4a03adcc49079"
integrity sha512-QqkneF8LrYmwATMdnuD2MLI3GHQIcBnG6qFC2q9bSH430VTCDAVjcspPmUaKhPGtAtPAftIUFqY1obQYQuwmbg==
destr@^1.2.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/destr/-/destr-1.2.2.tgz#7ba9befcafb645a50e76b260449c63927b51e22f"
integrity sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA==
duplexer@^0.1.2:
version "0.1.2"
@ -612,9 +614,9 @@ execa@^5.0.0, execa@^5.1.1:
strip-final-newline "^2.0.0"
fast-glob@^3.2.11:
version "3.2.11"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9"
integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==
version "3.3.1"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
@ -623,9 +625,9 @@ fast-glob@^3.2.11:
micromatch "^4.0.4"
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
version "1.15.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a"
integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==
dependencies:
reusify "^1.0.4"
@ -645,9 +647,9 @@ find-up@^5.0.0:
path-exists "^4.0.0"
fsevents@~2.3.2:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
version "2.3.3"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
get-stream@^6.0.0:
version "6.0.1"
@ -689,9 +691,9 @@ ip-regex@^4.0.0:
integrity sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==
ipaddr.js@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
version "2.1.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f"
integrity sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==
is-binary-path@~2.1.0:
version "2.1.0"
@ -741,10 +743,15 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
jiti@^1.13.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.14.0.tgz#5350fff532a4d891ca4bcd700c47c1f40e6ee326"
integrity sha512-4IwstlaKQc9vCTC+qUXLM1hajy2ImiL9KnLvVYiaHOtS/v3wRjhLlGl121AmgDgx/O43uKmxownJghS5XMya2A==
jiti@^1.19.1:
version "1.19.3"
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.3.tgz#ef554f76465b3c2b222dc077834a71f0d4a37569"
integrity sha512-5eEbBDQT/jF1xg6l36P+mWGGoH9Spuy0PCdSr2dtWRDGC6ph/w9ZCL4lmESW8f8F7MwT3XKescfP0wnZWAKL9w==
jsonc-parser@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76"
integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==
kleur@^4.1.5:
version "4.1.5"
@ -752,14 +759,14 @@ kleur@^4.1.5:
integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
kolorist@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.5.1.tgz#c3d66dc4fabde4f6b7faa6efda84c00491f9e52b"
integrity sha512-lxpCM3HTvquGxKGzHeknB/sUjuVoUElLlfYnXZT73K8geR9jQbroGlSCFBax9/0mpGoD3kzcMLnOlGQPJJNyqQ==
version "1.8.0"
resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
local-pkg@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.1.tgz#e7b0d7aa0b9c498a1110a5ac5b00ba66ef38cfff"
integrity sha512-lL87ytIGP2FU5PWwNDo0w3WhIo2gopIAxPg9RxDYF7m4rr5ahuZxP22xnJHIvaLTe4Z9P6uKKY2UHiwyB4pcrw==
version "0.4.3"
resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.3.tgz#0ff361ab3ae7f1c19113d9bb97b98b905dbc4963"
integrity sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==
locate-character@^3.0.0:
version "3.0.0"
@ -774,9 +781,9 @@ locate-path@^6.0.0:
p-locate "^5.0.0"
magic-string@^0.26.2:
version "0.26.2"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.2.tgz#5331700e4158cd6befda738bb6b0c7b93c0d4432"
integrity sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==
version "0.26.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f"
integrity sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==
dependencies:
sourcemap-codec "^1.4.8"
@ -787,11 +794,6 @@ magic-string@^0.30.0, magic-string@^0.30.3:
dependencies:
"@jridgewell/sourcemap-codec" "^1.4.15"
mdn-data@2.0.27:
version "2.0.27"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.27.tgz#1710baa7b0db8176d3b3d565ccb7915fc69525ab"
integrity sha512-kwqO0I0jtWr25KcfLm9pia8vLZ8qoAKhWZuZMbneJq3jjBD3gl5nZs8l8Tu3ZBlBAHVQtDur9rdDGyvtfVraHQ==
mdn-data@2.0.30:
version "2.0.30"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
@ -820,6 +822,16 @@ mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mlly@^1.2.0, mlly@^1.4.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.4.1.tgz#7ab9cbb040bf8bd8205a0c341ce9acc3ae0c3a74"
integrity sha512-SCDs78Q2o09jiZiE2WziwVBEqXQ02XkGdUy45cbJf+BpYRIjArXRJ1Wbowxkb+NaM9DWvS3UC9GiO/6eqvQ/pg==
dependencies:
acorn "^8.10.0"
pathe "^1.1.1"
pkg-types "^1.0.3"
ufo "^1.3.0"
mrmime@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
@ -835,10 +847,10 @@ nanoid@^3.3.6:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
node-fetch-native@^0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-0.1.4.tgz#09b06754f9e298bac23848050da2352125634f89"
integrity sha512-10EKpOCQPXwZVFh3U1ptOMWBgKTbsN7Vvo6WVKt5pw4hp8zbv6ZVBZPlXw+5M6Tyi1oc1iD4/sNPd71KYA16tQ==
node-fetch-native@^0.1.8:
version "0.1.8"
resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-0.1.8.tgz#19e2eaf6d86ac14e711ebd2612f40517c3468f2a"
integrity sha512-ZNaury9r0NxaT2oL65GvdGDy+5PlSaHTovT6JV5tOW07k1TQmgC0olZETa4C9KZg0+6zBr99ctTYa3Utqj9P/Q==
normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0"
@ -853,14 +865,14 @@ npm-run-path@^4.0.1:
path-key "^3.0.0"
ohmyfetch@^0.4.18:
version "0.4.18"
resolved "https://registry.yarnpkg.com/ohmyfetch/-/ohmyfetch-0.4.18.tgz#2952e04bd52662d0618d3d2f344db0250c3eeac2"
integrity sha512-MslzNrQzBLtZHmiZBI8QMOcMpdNFlK61OJ34nFNFynZ4v+4BonfCQ7VIN4EGXvGGq5zhDzgdJoY3o9S1l2T7KQ==
version "0.4.21"
resolved "https://registry.yarnpkg.com/ohmyfetch/-/ohmyfetch-0.4.21.tgz#6850db751fc7bbf08153aa8b11ff1ef45fcfd963"
integrity sha512-VG7f/JRvqvBOYvL0tHyEIEG7XHWm7OqIfAs6/HqwWwDfjiJ1g0huIpe5sFEmyb+7hpFa1EGNH2aERWR72tlClw==
dependencies:
destr "^1.1.1"
node-fetch-native "^0.1.3"
ufo "^0.8.4"
undici "^5.2.0"
destr "^1.2.0"
node-fetch-native "^0.1.8"
ufo "^0.8.6"
undici "^5.12.0"
onetime@^5.1.2:
version "5.1.2"
@ -913,9 +925,14 @@ path-key@^3.0.0, path-key@^3.1.0:
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
pathe@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.3.0.tgz#fd95bc16208263fa6dc1c78c07b3907a528de6eb"
integrity sha512-3vUjp552BJzCw9vqKsO5sttHkbYqqsZtH0x1PNtItgqx8BXEXzoY1SYRKcL6BTyVh4lGJGLj0tM42elUDMvcYA==
version "0.3.9"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.3.9.tgz#4baff768f37f03e3d9341502865fb93116f65191"
integrity sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g==
pathe@^1.1.0, pathe@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.1.tgz#1dd31d382b974ba69809adc9a7a347e65d84829a"
integrity sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==
perfect-debounce@^0.1.3:
version "0.1.3"
@ -936,15 +953,19 @@ picocolors@^1.0.0:
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
picomatch@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
pkg-types@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868"
integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==
dependencies:
jsonc-parser "^3.2.0"
mlly "^1.2.0"
pathe "^1.1.0"
postcss@^8.4.27:
version "8.4.31"
@ -1004,20 +1025,15 @@ signal-exit@^3.0.3:
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
sirv@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.2.tgz#128b9a628d77568139cff85703ad5497c46a4760"
integrity sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==
version "2.0.3"
resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.3.tgz#ca5868b87205a74bef62a469ed0296abceccd446"
integrity sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==
dependencies:
"@polka/url" "^1.0.0-next.20"
mrmime "^1.0.0"
totalist "^3.0.0"
source-map-js@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
source-map-js@^1.0.2:
source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@ -1064,28 +1080,34 @@ to-regex-range@^5.0.1:
is-number "^7.0.0"
totalist@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==
version "3.0.1"
resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.1.tgz#ba3a3d600c915b1a97872348f79c127475f6acf8"
integrity sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==
ufo@^0.8.4:
version "0.8.4"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.8.4.tgz#23e9ed82398d2116dcb378e8fba5ced8eca2ee40"
integrity sha512-/+BmBDe8GvlB2nIflWasLLAInjYG0bC9HRnfEpNi4sw77J2AJNnEVnTDReVrehoh825+Q/evF3THXTAweyam2g==
ufo@^0.8.6:
version "0.8.6"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-0.8.6.tgz#c0ec89bc0e0c9fa59a683680feb0f28b55ec323b"
integrity sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==
ufo@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.3.0.tgz#c92f8ac209daff607c57bbd75029e190930a0019"
integrity sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==
unconfig@^0.3.4:
version "0.3.4"
resolved "https://registry.yarnpkg.com/unconfig/-/unconfig-0.3.4.tgz#f0c85584a088a434dde2215d8a3b272427d6056c"
integrity sha512-cf39F1brwQuLSuMLTYXOdWJH0O1CJee6a4QW1nYtO7SoBUfVvQCvEel6ssTNXtPfi17kop1ADmVtmC49NlFkIQ==
version "0.3.10"
resolved "https://registry.yarnpkg.com/unconfig/-/unconfig-0.3.10.tgz#2439cfc4303c8e12f7333d7cb7286917a3eb9b63"
integrity sha512-tj317lhIq2iZF/NXrJnU1t2UaGUKKz1eL1sK2t63Oq66V9BxqvZV12m55fp/fpQJ+DDmVlLgo7cnLVOZkhlO/A==
dependencies:
"@antfu/utils" "^0.5.1"
defu "^6.0.0"
jiti "^1.13.0"
"@antfu/utils" "^0.7.5"
defu "^6.1.2"
jiti "^1.19.1"
mlly "^1.4.0"
undici@^5.2.0:
version "5.26.3"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.3.tgz#ab3527b3d5bb25b12f898dfd22165d472dd71b79"
integrity sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==
undici@^5.12.0:
version "5.26.5"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.5.tgz#f6dc8c565e3cad8c4475b187f51a13e505092838"
integrity sha512-cSb4bPFd5qgR7qr2jYAi0hlX9n5YKK2ONKkLFkxl+v/9BvC0sOpZjBHDBSXc5lWAf5ty9oZdRXytBIHzgUcerw==
dependencies:
"@fastify/busboy" "^2.0.0"

View File

@ -9,7 +9,6 @@
"parser": "@typescript-eslint/parser",
"extends": [
"standard-with-typescript",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
// TODO: make this work with typescript
// "plugin:node/recommended"

View File

@ -20,10 +20,10 @@
"homepage": "https://github.com/tauri-apps/tauri#readme",
"type": "module",
"scripts": {
"build": "yarn rollup --config rollup.config.ts --configPlugin typescript",
"build": "rollup -c --configPlugin typescript",
"npm-pack": "yarn build && cd ./dist && npm pack",
"npm-publish": "yarn build && cd ./dist && yarn publish --access public --loglevel silly --tag next",
"check": "tsc",
"ts:check": "tsc -noEmit",
"lint": "eslint --ext ts \"./src/**/*.ts\"",
"lint:fix": "eslint --fix --ext ts \"./src/**/*.ts\"",
"format": "prettier --write . --config ../../.prettierrc --ignore-path .gitignore --ignore-path ../../.prettierignore",
@ -37,7 +37,6 @@
"@typescript-eslint/parser": "6.10.0",
"eslint": "8.53.0",
"eslint-config-prettier": "9.0.0",
"eslint-config-standard-with-typescript": "39.1.1",
"eslint-plugin-import": "2.29.0",
"eslint-plugin-n": "16.3.0",
"eslint-plugin-node": "11.1.0",

View File

@ -2,7 +2,7 @@
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { defineConfig, Plugin } from 'rollup'
import { defineConfig, Plugin, RollupLog } from 'rollup'
import typescript from '@rollup/plugin-typescript'
import terser from '@rollup/plugin-terser'
import fg from 'fast-glob'
@ -31,6 +31,7 @@ export default defineConfig([
format: 'esm',
dir: './dist',
preserveModules: true,
preserveModulesRoot: 'src',
entryFileNames: (chunkInfo) => {
if (chunkInfo.name.includes('node_modules')) {
return chunkInfo.name.replace('node_modules', 'external') + '.js'
@ -43,6 +44,7 @@ export default defineConfig([
format: 'cjs',
dir: './dist',
preserveModules: true,
preserveModulesRoot: 'src',
entryFileNames: (chunkInfo) => {
if (chunkInfo.name.includes('node_modules')) {
return chunkInfo.name.replace('node_modules', 'external') + '.cjs'
@ -55,11 +57,12 @@ export default defineConfig([
plugins: [
typescript({
declaration: true,
declarationDir: './dist',
declarationDir: './dist/types',
rootDir: 'src'
}),
makeFlatPackageInDist()
]
],
onwarn
},
{
@ -67,13 +70,19 @@ export default defineConfig([
output: {
format: 'iife',
name: '__TAURI_IIFE__',
file: '../../core/tauri/scripts/bundle.global.js',
footer: 'window.__TAURI__ = __TAURI_IIFE__'
footer: 'window.__TAURI__ = __TAURI_IIFE__',
file: '../../core/tauri/scripts/bundle.global.js'
},
plugins: [typescript(), terser()]
plugins: [typescript(), terser()],
onwarn
}
])
function onwarn(warning: RollupLog) {
// deny warnings by default
throw Object.assign(new Error(), warning)
}
function makeFlatPackageInDist(): Plugin {
return {
name: 'makeFlatPackageInDist',
@ -88,13 +97,17 @@ function makeFlatPackageInDist(): Plugin {
exports: Object.assign(
{},
...mods.map((mod) => {
let temp: Record<string, { import: string; require: string }> = {}
let temp: Record<
string,
{ types: string; import: string; require: string }
> = {}
let key = `./${mod}`
if (mod === 'index') {
key = '.'
}
temp[key] = {
types: `./types/${mod}.d.ts`,
import: `./${mod}.js`,
require: `./${mod}.cjs`
}

View File

@ -55,8 +55,7 @@ enum TauriEvent {
WINDOW_THEME_CHANGED = 'tauri://theme-changed',
WINDOW_FILE_DROP = 'tauri://file-drop',
WINDOW_FILE_DROP_HOVER = 'tauri://file-drop-hover',
WINDOW_FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled',
MENU = 'tauri://menu'
WINDOW_FILE_DROP_CANCELLED = 'tauri://file-drop-cancelled'
}
/**

View File

@ -19,5 +19,7 @@ import * as primitives from './primitives'
import * as window from './window'
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, primitives, window }
export { app, dpi, event, path, primitives, window, tray, menu }

View File

@ -0,0 +1,34 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { invoke } from '../primitives'
/**
* A rust-backed resource.
*
* The resource lives in the main process and does not exist
* in the Javascript world, and thus will not be cleaned up automatiacally
* except on application exit. If you want to clean it up early, call {@linkcode Resource.close}
*/
export class Resource {
readonly #rid: number
get rid(): number {
return this.#rid
}
constructor(rid: number) {
this.#rid = rid
}
/**
* Destroys and cleans up this resource from memory.
* **You should not call any method on this object anymore and should drop any reference to it.**
*/
async close(): Promise<void> {
return invoke('plugin:resources|close', {
rid: this.rid
})
}
}

17
tooling/api/src/menu.ts Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
export * from './menu/submenu'
export * from './menu/menuItem'
export * from './menu/menu'
export * from './menu/checkMenuItem'
export * from './menu/iconMenuItem'
export * from './menu/predefinedMenuItem'
/**
* Menu types and utilities.
*
* This package is also accessible with `window.__TAURI__.menu` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
* @module
*/

View File

@ -0,0 +1,114 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { Resource } from '../internal'
import { Channel, invoke } from '../primitives'
import { CheckMenuItemOptions } from './checkMenuItem'
import { IconMenuItemOptions } from './iconMenuItem'
import { MenuItemOptions } from './menuItem'
import { PredefinedMenuItemOptions } from './predefinedMenuItem'
import { SubmenuOptions } from './submenu'
export type ItemKind =
| 'MenuItem'
| 'Predefined'
| 'Check'
| 'Icon'
| 'Submenu'
| 'Menu'
function injectChannel(
i:
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
):
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| (CheckMenuItemOptions & { handler?: Channel<string> }) {
if ('items' in i) {
i.items = i.items?.map((item) =>
'rid' in item ? item : injectChannel(item)
)
} else if ('action' in i && i.action) {
const handler = new Channel<string>()
handler.onmessage = i.action
delete i.action
return { ...i, handler }
}
return i
}
export async function newMenu(
kind: ItemKind,
opts?: unknown
): Promise<[number, string]> {
const handler = new Channel<string>()
let items: null | Array<
| [number, string]
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
> = null
if (opts && typeof opts === 'object') {
if ('action' in opts && opts.action) {
handler.onmessage = opts.action as () => void
delete opts.action
}
if ('items' in opts && opts.items) {
items = (
opts.items as Array<
| { rid: number; kind: string }
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>
).map((i) => {
if ('rid' in i) {
return [i.rid, i.kind]
}
return injectChannel(i)
})
}
}
return invoke('plugin:menu|new', {
kind,
options: opts ? { ...opts, items } : undefined,
handler
})
}
export class MenuItemBase extends Resource {
/** @ignore */
readonly #id: string
/** @ignore */
readonly #kind: ItemKind
/** The id of this item. */
get id(): string {
return this.#id
}
/** @ignore */
get kind(): string {
return this.#kind
}
/** @ignore */
protected constructor(rid: number, id: string, kind: ItemKind) {
super(rid)
this.#id = id
this.#kind = kind
}
}

View File

@ -0,0 +1,82 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { MenuItemBase, newMenu } from './base'
import { invoke } from '../primitives'
import { type MenuItemOptions } from '../menu'
/** Options for creating a new check menu item. */
export interface CheckMenuItemOptions extends MenuItemOptions {
/** Whether the new check menu item is enabled or not. */
checked?: boolean
}
/**
* A check menu item inside a {@linkcode Menu} or {@linkcode Submenu}
* and usually contains a text and a check mark or a similar toggle
* that corresponds to a checked and unchecked states.
*/
export class CheckMenuItem extends MenuItemBase {
/** @ignore */
protected constructor(rid: number, id: string) {
super(rid, id, 'Check')
}
/** Create a new check menu item. */
static async new(opts: CheckMenuItemOptions): Promise<CheckMenuItem> {
return newMenu('Check', opts).then(
([rid, id]) => new CheckMenuItem(rid, id)
)
}
/** Returns the text of this check menu item. */
async text(): Promise<string> {
return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })
}
/** Sets the text for this check menu item. */
async setText(text: string): Promise<void> {
return invoke('plugin:menu|set_text', {
rid: this.rid,
kind: this.kind,
text
})
}
/** Returns whether this check menu item is enabled or not. */
async isEnabled(): Promise<boolean> {
return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })
}
/** Sets whether this check menu item is enabled or not. */
async setEnabled(enabled: boolean): Promise<void> {
return invoke('plugin:menu|set_enabled', {
rid: this.rid,
kind: this.kind,
enabled
})
}
/** Sets the accelerator for this check menu item. */
async setAccelerator(accelerator: string | null): Promise<void> {
return invoke('plugin:menu|set_accelerator', {
rid: this.rid,
kind: this.kind,
accelerator
})
}
/** Returns whether this check menu item is checked or not. */
async isChecked(): Promise<boolean> {
return invoke('plugin:menu|is_checked', { rid: this.rid })
}
/** Sets whether this check menu item is checked or not. */
async setChecked(checked: boolean): Promise<void> {
return invoke('plugin:menu|set_checked', {
rid: this.rid,
checked
})
}
}

View File

@ -0,0 +1,195 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { MenuItemBase, newMenu } from './base'
import { type MenuItemOptions } from '../menu'
import { invoke } from '../primitives'
/**
* A native Icon to be used for the menu item
*
* #### Platform-specific:
*
* - **Windows / Linux**: Unsupported.
*/
export enum NativeIcon {
/** An add item template image. */
Add = 'Add',
/** Advanced preferences toolbar icon for the preferences window. */
Advanced = 'Advanced',
/** A Bluetooth template image. */
Bluetooth = 'Bluetooth',
/** Bookmarks image suitable for a template. */
Bookmarks = 'Bookmarks',
/** A caution image. */
Caution = 'Caution',
/** A color panel toolbar icon. */
ColorPanel = 'ColorPanel',
/** A column view mode template image. */
ColumnView = 'ColumnView',
/** A computer icon. */
Computer = 'Computer',
/** An enter full-screen mode template image. */
EnterFullScreen = 'EnterFullScreen',
/** Permissions for all users. */
Everyone = 'Everyone',
/** An exit full-screen mode template image. */
ExitFullScreen = 'ExitFullScreen',
/** A cover flow view mode template image. */
FlowView = 'FlowView',
/** A folder image. */
Folder = 'Folder',
/** A burnable folder icon. */
FolderBurnable = 'FolderBurnable',
/** A smart folder icon. */
FolderSmart = 'FolderSmart',
/** A link template image. */
FollowLinkFreestanding = 'FollowLinkFreestanding',
/** A font panel toolbar icon. */
FontPanel = 'FontPanel',
/** A `go back` template image. */
GoLeft = 'GoLeft',
/** A `go forward` template image. */
GoRight = 'GoRight',
/** Home image suitable for a template. */
Home = 'Home',
/** An iChat Theater template image. */
IChatTheater = 'IChatTheater',
/** An icon view mode template image. */
IconView = 'IconView',
/** An information toolbar icon. */
Info = 'Info',
/** A template image used to denote invalid data. */
InvalidDataFreestanding = 'InvalidDataFreestanding',
/** A generic left-facing triangle template image. */
LeftFacingTriangle = 'LeftFacingTriangle',
/** A list view mode template image. */
ListView = 'ListView',
/** A locked padlock template image. */
LockLocked = 'LockLocked',
/** An unlocked padlock template image. */
LockUnlocked = 'LockUnlocked',
/** A horizontal dash, for use in menus. */
MenuMixedState = 'MenuMixedState',
/** A check mark template image, for use in menus. */
MenuOnState = 'MenuOnState',
/** A MobileMe icon. */
MobileMe = 'MobileMe',
/** A drag image for multiple items. */
MultipleDocuments = 'MultipleDocuments',
/** A network icon. */
Network = 'Network',
/** A path button template image. */
Path = 'Path',
/** General preferences toolbar icon for the preferences window. */
PreferencesGeneral = 'PreferencesGeneral',
/** A Quick Look template image. */
QuickLook = 'QuickLook',
/** A refresh template image. */
RefreshFreestanding = 'RefreshFreestanding',
/** A refresh template image. */
Refresh = 'Refresh',
/** A remove item template image. */
Remove = 'Remove',
/** A reveal contents template image. */
RevealFreestanding = 'RevealFreestanding',
/** A generic right-facing triangle template image. */
RightFacingTriangle = 'RightFacingTriangle',
/** A share view template image. */
Share = 'Share',
/** A slideshow template image. */
Slideshow = 'Slideshow',
/** A badge for a `smart` item. */
SmartBadge = 'SmartBadge',
/** Small green indicator, similar to iChats available image. */
StatusAvailable = 'StatusAvailable',
/** Small clear indicator. */
StatusNone = 'StatusNone',
/** Small yellow indicator, similar to iChats idle image. */
StatusPartiallyAvailable = 'StatusPartiallyAvailable',
/** Small red indicator, similar to iChats unavailable image. */
StatusUnavailable = 'StatusUnavailable',
/** A stop progress template image. */
StopProgressFreestanding = 'StopProgressFreestanding',
/** A stop progress button template image. */
StopProgress = 'StopProgress',
/** An image of the empty trash can. */
TrashEmpty = 'TrashEmpty',
/** An image of the full trash can. */
TrashFull = 'TrashFull',
/** Permissions for a single user. */
User = 'User',
/** User account toolbar icon for the preferences window. */
UserAccounts = 'UserAccounts',
/** Permissions for a group of users. */
UserGroup = 'UserGroup',
/** Permissions for guests. */
UserGuest = 'UserGuest'
}
/** Options for creating a new icon menu item. */
export interface IconMenuItemOptions extends MenuItemOptions {
/**
* Icon to be used for the new icon menu item.
*/
icon?: NativeIcon | string | Uint8Array
}
/**
* An icon menu item inside a {@linkcode Menu} or {@linkcode Submenu}
* and usually contains an icon and a text.
*/
export class IconMenuItem extends MenuItemBase {
/** @ignore */
protected constructor(rid: number, id: string) {
super(rid, id, 'Icon')
}
/** Create a new icon menu item. */
static async new(opts: IconMenuItemOptions): Promise<IconMenuItem> {
return newMenu('Icon', opts).then(([rid, id]) => new IconMenuItem(rid, id))
}
/** Returns the text of this icon menu item. */
async text(): Promise<string> {
return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })
}
/** Sets the text for this icon menu item. */
async setText(text: string): Promise<void> {
return invoke('plugin:menu|set_text', {
rid: this.rid,
kind: this.kind,
text
})
}
/** Returns whether this icon menu item is enabled or not. */
async isEnabled(): Promise<boolean> {
return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })
}
/** Sets whether this icon menu item is enabled or not. */
async setEnabled(enabled: boolean): Promise<void> {
return invoke('plugin:menu|set_enabled', {
rid: this.rid,
kind: this.kind,
enabled
})
}
/** Sets the accelerator for this icon menu item. */
async setAccelerator(accelerator: string | null): Promise<void> {
return invoke('plugin:menu|set_accelerator', {
rid: this.rid,
kind: this.kind,
accelerator
})
}
/** Sets an icon for this icon menu item */
async setIcon(icon: NativeIcon | string | Uint8Array | null): Promise<void> {
return invoke('plugin:menu|set_icon', { rid: this.rid, icon })
}
}

View File

@ -0,0 +1,287 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import {
MenuItemOptions,
SubmenuOptions,
IconMenuItemOptions,
PredefinedMenuItemOptions,
CheckMenuItemOptions
} from '../menu'
import { MenuItem } from './menuItem'
import { CheckMenuItem } from './checkMenuItem'
import { IconMenuItem } from './iconMenuItem'
import { PredefinedMenuItem } from './predefinedMenuItem'
import { Submenu } from './submenu'
import { type LogicalPosition, PhysicalPosition } from '../dpi'
import { type Window } from '../window'
import { invoke } from '../primitives'
import { type ItemKind, MenuItemBase, newMenu } from './base'
function itemFromKind([rid, id, kind]: [number, string, ItemKind]):
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem {
/* eslint-disable @typescript-eslint/no-unsafe-return */
switch (kind) {
case 'Submenu':
// @ts-expect-error constructor is protected for external usage only
return new Submenu(rid, id)
case 'Predefined':
// @ts-expect-error constructor is protected for external usage only
return new PredefinedMenuItem(rid, id)
case 'Check':
// @ts-expect-error constructor is protected for external usage only
return new CheckMenuItem(rid, id)
case 'Icon':
// @ts-expect-error constructor is protected for external usage only
return new IconMenuItem(rid, id)
case 'MenuItem':
default:
// @ts-expect-error constructor is protected for external usage only
return new MenuItem(rid, id)
}
/* eslint-enable @typescript-eslint/no-unsafe-return */
}
/** Options for creating a new menu. */
export interface MenuOptions {
/** Specify an id to use for the new menu. */
id?: string
/** List of items to add to the new menu. */
items?: Array<
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>
}
/** A type that is either a menu bar on the window
* on Windows and Linux or as a global menu in the menubar on macOS.
*/
export class Menu extends MenuItemBase {
/** @ignore */
protected constructor(rid: number, id: string) {
super(rid, id, 'Menu')
}
/** Create a new menu. */
static async new(opts?: MenuOptions): Promise<Menu> {
return newMenu('Menu', opts).then(([rid, id]) => new Menu(rid, id))
}
/** Create a default menu. */
static async default(): Promise<Menu> {
return invoke<[number, string]>('plugin:menu|default').then(
([rid, id]) => new Menu(rid, id)
)
}
/**
* Add a menu item to the end of this menu.
*
* ## Platform-spcific:
*
* - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
*/
async append<
T extends
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>(items: T | T[]): Promise<void> {
return invoke('plugin:menu|append', {
rid: this.rid,
kind: this.kind,
items: (Array.isArray(items) ? items : [items]).map((i) =>
'rid' in i ? [i.rid, i.kind] : i
)
})
}
/**
* Add a menu item to the beginning of this menu.
*
* ## Platform-spcific:
*
* - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
*/
async prepend<
T extends
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>(items: T | T[]): Promise<void> {
return invoke('plugin:menu|prepend', {
rid: this.rid,
kind: this.kind,
items: (Array.isArray(items) ? items : [items]).map((i) =>
'rid' in i ? [i.rid, i.kind] : i
)
})
}
/**
* Add a menu item to the specified position in this menu.
*
* ## Platform-spcific:
*
* - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
*/
async insert<
T extends
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>(items: T | T[], position: number): Promise<void> {
return invoke('plugin:menu|insert', {
rid: this.rid,
kind: this.kind,
items: (Array.isArray(items) ? items : [items]).map((i) =>
'rid' in i ? [i.rid, i.kind] : i
),
position
})
}
/** Remove a menu item from this menu. */
async remove(
item: Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
): Promise<void> {
return invoke('plugin:menu|remove', {
rid: this.rid,
kind: this.kind,
item: [item.rid, item.kind]
})
}
/** Remove a menu item from this menu at the specified position. */
async removeAt(
position: number
): Promise<
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| null
> {
return invoke<[number, string, ItemKind]>('plugin:menu|remove_at', {
rid: this.rid,
kind: this.kind,
position
}).then(itemFromKind)
}
/** Returns a list of menu items that has been added to this menu. */
async items(): Promise<
Array<
Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
>
> {
return invoke<Array<[number, string, ItemKind]>>('plugin:menu|items', {
rid: this.rid,
kind: this.kind
}).then((i) => i.map(itemFromKind))
}
/** Retrieves the menu item matching the given identifier. */
async get(
id: string
): Promise<
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| null
> {
return invoke<[number, string, ItemKind] | null>('plugin:menu|get', {
rid: this.rid,
kind: this.kind,
id
}).then((r) => (r ? itemFromKind(r) : null))
}
/**
* Popup this menu as a context menu on the specified window.
*
* If the position, is provided, it is relative to the window's top-left corner.
*/
async popup(
at?: PhysicalPosition | LogicalPosition,
window?: Window
): Promise<void> {
let atValue = null
if (at) {
atValue = {
type: at instanceof PhysicalPosition ? 'Physical' : 'Logical',
data: at
}
}
return invoke('plugin:menu|popup', {
rid: this.rid,
kind: this.kind,
window: window?.label ?? null,
at: atValue
})
}
/**
* Sets the app-wide menu and returns the previous one.
*
* If a window was not created with an explicit menu or had one set explicitly,
* this menu will be assigned to it.
*/
async setAsAppMenu(): Promise<Menu | null> {
return invoke<[number, string] | null>('plugin:menu|set_as_app_menu', {
rid: this.rid
}).then((r) => (r ? new Menu(r[0], r[1]) : null))
}
/**
* Sets the window menu and returns the previous one.
*
* #### Platform-specific:
*
* - **macOS:** Unsupported. The menu on macOS is app-wide and not specific to one
* window, if you need to set it, use {@linkcode Menu.setAsAppMenu} instead.
*/
async setAsWindowMenu(window?: Window): Promise<Menu | null> {
return invoke<[number, string] | null>('plugin:menu|set_as_window_menu', {
rid: this.rid,
window: window?.label ?? null
}).then((r) => (r ? new Menu(r[0], r[1]) : null))
}
}

View File

@ -0,0 +1,70 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { MenuItemBase, newMenu } from './base'
import { invoke } from '../primitives'
/** Options for creating a new menu item. */
export interface MenuItemOptions {
/** Specify an id to use for the new menu item. */
id?: string
/** The text of the new menu item. */
text: string
/** Whether the new menu item is enabled or not. */
enabled?: boolean
/** Specify an accelerator for the new menu item. */
accelerator?: string
/** Specify a handler to be called when this menu item is activated. */
action?: (id: string) => void
}
/** A menu item inside a {@linkcode Menu} or {@linkcode Submenu} and contains only text. */
export class MenuItem extends MenuItemBase {
/** @ignore */
protected constructor(rid: number, id: string) {
super(rid, id, 'MenuItem')
}
/** Create a new menu item. */
static async new(opts: MenuItemOptions): Promise<MenuItem> {
return newMenu('MenuItem', opts).then(([rid, id]) => new MenuItem(rid, id))
}
/** Returns the text of this menu item. */
async text(): Promise<string> {
return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })
}
/** Sets the text for this menu item. */
async setText(text: string): Promise<void> {
return invoke('plugin:menu|set_text', {
rid: this.rid,
kind: this.kind,
text
})
}
/** Returns whether this menu item is enabled or not. */
async isEnabled(): Promise<boolean> {
return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })
}
/** Sets whether this menu item is enabled or not. */
async setEnabled(enabled: boolean): Promise<void> {
return invoke('plugin:menu|set_enabled', {
rid: this.rid,
kind: this.kind,
enabled
})
}
/** Sets the accelerator for this menu item. */
async setAccelerator(accelerator: string | null): Promise<void> {
return invoke('plugin:menu|set_accelerator', {
rid: this.rid,
kind: this.kind,
accelerator
})
}
}

View File

@ -0,0 +1,138 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import { MenuItemBase, newMenu } from './base'
import { invoke } from '../primitives'
/** A metadata for the about predefined menu item. */
export interface AboutMetadata {
/** Sets the application name. */
name?: string
/** The application version. */
version?: string
/**
* The short version, e.g. "1.0".
*
* #### Platform-specific
*
* - **Windows / Linux:** Appended to the end of `version` in parentheses.
*/
shortVersion?: string
/**
* The authors of the application.
*
* #### Platform-specific
*
* - **macOS:** Unsupported.
*/
authors?: string[]
/**
* Application comments.
*
* #### Platform-specific
*
* - **macOS:** Unsupported.
*/
comments?: string
/** The copyright of the application. */
copyright?: string
/**
* The license of the application.
*
* #### Platform-specific
*
* - **macOS:** Unsupported.
*/
license?: string
/**
* The application website.
*
* #### Platform-specific
*
* - **macOS:** Unsupported.
*/
website?: string
/**
* The website label.
*
* #### Platform-specific
*
* - **macOS:** Unsupported.
*/
websiteLabel?: string
/**
* The credits.
*
* #### Platform-specific
*
* - **Windows / Linux:** Unsupported.
*/
credits?: string
/**
* The application icon.
*
* #### Platform-specific
*
* - **Windows:** Unsupported.
*/
icon?: string | Uint8Array
}
/** Options for creating a new predefined menu item. */
export interface PredefinedMenuItemOptions {
/** The text of the new predefined menu item. */
text?: string
/** The predefined item type */
item:
| 'Separator'
| 'Copy'
| 'Cut'
| 'Paste'
| 'SelectAll'
| 'Undo'
| 'Redo'
| 'Minimize'
| 'Maximize'
| 'Fullscreen'
| 'Hide'
| 'HideOthers'
| 'ShowAll'
| 'CloseWindow'
| 'Quit'
| 'Services'
| {
About: AboutMetadata | null
}
}
/** A predefined (native) menu item which has a predfined behavior by the OS or by tauri. */
export class PredefinedMenuItem extends MenuItemBase {
/** @ignore */
protected constructor(rid: number, id: string) {
super(rid, id, 'Predefined')
}
/** Create a new predefined menu item. */
static async new(
opts?: PredefinedMenuItemOptions
): Promise<PredefinedMenuItem> {
return newMenu('Predefined', opts).then(
([rid, id]) => new PredefinedMenuItem(rid, id)
)
}
/** Returns the text of this predefined menu item. */
async text(): Promise<string> {
return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })
}
/** Sets the text for this predefined menu item. */
async setText(text: string): Promise<void> {
return invoke('plugin:menu|set_text', {
rid: this.rid,
kind: this.kind,
text
})
}
}

View File

@ -0,0 +1,294 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import {
IconMenuItemOptions,
PredefinedMenuItemOptions,
CheckMenuItemOptions
} from '../menu'
import { MenuItem, type MenuItemOptions } from './menuItem'
import { CheckMenuItem } from './checkMenuItem'
import { IconMenuItem } from './iconMenuItem'
import { PredefinedMenuItem } from './predefinedMenuItem'
import { invoke } from '../primitives'
import { type LogicalPosition, PhysicalPosition, type Window } from '../window'
import { type ItemKind, MenuItemBase, newMenu } from './base'
import { type MenuOptions } from './menu'
function itemFromKind([rid, id, kind]: [number, string, ItemKind]):
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem {
/* eslint-disable @typescript-eslint/no-unsafe-return */
switch (kind) {
case 'Submenu':
// @ts-expect-error constructor is protected for external usage only, safe for us to use
return new Submenu(rid, id)
case 'Predefined':
// @ts-expect-error constructor is protected for external usage only, safe for us to use
return new PredefinedMenuItem(rid, id)
case 'Check':
// @ts-expect-error constructor is protected for external usage only, safe for us to use
return new CheckMenuItem(rid, id)
case 'Icon':
// @ts-expect-error constructor is protected for external usage only, safe for us to use
return new IconMenuItem(rid, id)
case 'MenuItem':
default:
// @ts-expect-error constructor is protected for external usage only, safe for us to use
return new MenuItem(rid, id)
}
/* eslint-enable @typescript-eslint/no-unsafe-return */
}
export type SubmenuOptions = Omit<MenuItemOptions, 'accelerator' | 'action'> &
MenuOptions
/** A type that is a submenu inside a {@linkcode Menu} or {@linkcode Submenu}. */
export class Submenu extends MenuItemBase {
/** @ignore */
protected constructor(rid: number, id: string) {
super(rid, id, 'Submenu')
}
/** Create a new submenu. */
static async new(opts: SubmenuOptions): Promise<Submenu> {
return newMenu('Submenu', opts).then(([rid, id]) => new Submenu(rid, id))
}
/** Returns the text of this submenu. */
async text(): Promise<string> {
return invoke('plugin:menu|text', { rid: this.rid, kind: this.kind })
}
/** Sets the text for this submenu. */
async setText(text: string): Promise<void> {
return invoke('plugin:menu|set_text', {
rid: this.rid,
kind: this.kind,
text
})
}
/** Returns whether this submenu is enabled or not. */
async isEnabled(): Promise<boolean> {
return invoke('plugin:menu|is_enabled', { rid: this.rid, kind: this.kind })
}
/** Sets whether this submenu is enabled or not. */
async setEnabled(enabled: boolean): Promise<void> {
return invoke('plugin:menu|set_enabled', {
rid: this.rid,
kind: this.kind,
enabled
})
}
/**
* Add a menu item to the end of this submenu.
*
* ## Platform-spcific:
*
* - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
*/
async append<
T extends
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>(items: T | T[]): Promise<void> {
return invoke('plugin:menu|append', {
rid: this.rid,
kind: this.kind,
items: (Array.isArray(items) ? items : [items]).map((i) =>
'rid' in i ? [i.rid, i.kind] : i
)
})
}
/**
* Add a menu item to the beginning of this submenu.
*
* ## Platform-spcific:
*
* - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
*/
async prepend<
T extends
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>(items: T | T[]): Promise<void> {
return invoke('plugin:menu|prepend', {
rid: this.rid,
kind: this.kind,
items: (Array.isArray(items) ? items : [items]).map((i) =>
'rid' in i ? [i.rid, i.kind] : i
)
})
}
/**
* Add a menu item to the specified position in this submenu.
*
* ## Platform-spcific:
*
* - **macOS:** Only {@linkcode Submenu}s can be added to a {@linkcode Menu}.
*/
async insert<
T extends
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| MenuItemOptions
| SubmenuOptions
| IconMenuItemOptions
| PredefinedMenuItemOptions
| CheckMenuItemOptions
>(items: T | T[], position: number): Promise<void> {
return invoke('plugin:menu|insert', {
rid: this.rid,
kind: this.kind,
items: (Array.isArray(items) ? items : [items]).map((i) =>
'rid' in i ? [i.rid, i.kind] : i
),
position
})
}
/** Remove a menu item from this submenu. */
async remove(
item: Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
): Promise<void> {
return invoke('plugin:menu|remove', {
rid: this.rid,
kind: this.kind,
item: [item.rid, item.kind]
})
}
/** Remove a menu item from this submenu at the specified position. */
async removeAt(
position: number
): Promise<
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| null
> {
return invoke<[number, string, ItemKind]>('plugin:menu|remove_at', {
rid: this.rid,
kind: this.kind,
position
}).then(itemFromKind)
}
/** Returns a list of menu items that has been added to this submenu. */
async items(): Promise<
Array<
Submenu | MenuItem | PredefinedMenuItem | CheckMenuItem | IconMenuItem
>
> {
return invoke<Array<[number, string, ItemKind]>>('plugin:menu|items', {
rid: this.rid,
kind: this.kind
}).then((i) => i.map(itemFromKind))
}
/** Retrieves the menu item matching the given identifier. */
async get(
id: string
): Promise<
| Submenu
| MenuItem
| PredefinedMenuItem
| CheckMenuItem
| IconMenuItem
| null
> {
return invoke<[number, string, ItemKind] | null>('plugin:menu|get', {
rid: this.rid,
kind: this.kind,
id
}).then((r) => (r ? itemFromKind(r) : null))
}
/**
* Popup this submenu as a context menu on the specified window.
*
* If the position, is provided, it is relative to the window's top-left corner.
*/
async popup(
at?: PhysicalPosition | LogicalPosition,
window?: Window
): Promise<void> {
let atValue = null
if (at) {
atValue = {
type: at instanceof PhysicalPosition ? 'Physical' : 'Logical',
data: at
}
}
return invoke('plugin:menu|popup', {
rid: this.rid,
kind: this.kind,
window: window?.label ?? null,
at: atValue
})
}
/**
* Set this submenu as the Window menu for the application on macOS.
*
* This will cause macOS to automatically add window-switching items and
* certain other items to the menu.
*
* #### Platform-specific:
*
* - **Windows / Linux**: Unsupported.
*/
async setAsWindowsMenuForNSApp(): Promise<void> {
return invoke('plugin:menu|set_as_windows_menu_for_nsapp', {
rid: this.rid
})
}
/**
* Set this submenu as the Help menu for the application on macOS.
*
* This will cause macOS to automatically add a search box to the menu.
*
* If no menu is set as the Help menu, macOS will automatically use any menu
* which has a title matching the localized word "Help".
*
* #### Platform-specific:
*
* - **Windows / Linux**: Unsupported.
*/
async setAsHelpMenuForNSApp(): Promise<void> {
return invoke('plugin:menu|set_as_help_menu_for_nsapp', {
rid: this.rid
})
}
}

View File

@ -533,7 +533,7 @@ async function appLogDir(): Promise<string> {
*
* @since 2.0.0
*/
async function tempDir(path: string): Promise<string> {
async function tempDir(): Promise<string> {
return invoke('plugin:path|resolve_directory', {
directory: BaseDirectory.Temp
})

235
tooling/api/src/tray.ts Normal file
View File

@ -0,0 +1,235 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import type { Menu, Submenu } from './menu'
import { Resource } from './internal'
import { Channel, invoke } from './primitives'
/**
* Describes a tray event emitted when a tray icon is clicked
*
* #### Platform-specific:
*
* - **Linux**: Unsupported. The event is not emmited even though the icon is shown,
* the icon will still show a context menu on right click.
*/
export interface TrayIconEvent {
/** Id of the tray icon which triggered this event. */
id: string
/** Physical X Position of the click the triggered this event. */
x: number
/** Physical Y Position of the click the triggered this event. */
y: number
/** Position and size of the tray icon. */
iconRect: {
/** The x-coordinate of the upper-left corner of the rectangle. */
left: number
/** The y-coordinate of the upper-left corner of the rectangle. */
top: number
/** The x-coordinate of the lower-right corner of the rectangle. */
right: number
/** The y-coordinate of the lower-right corner of the rectangle. */
bottom: number
}
/** The click type that triggered this event. */
clickType: 'Left' | 'Right' | 'Double'
}
/**
* Tray icon types and utilities.
*
* This package is also accessible with `window.__TAURI__.tray` when [`build.withGlobalTauri`](https://tauri.app/v1/api/config/#buildconfig.withglobaltauri) in `tauri.conf.json` is set to `true`.
* @module
*/
/** {@link TrayIcon.new|`TrayIcon`} creation options */
export interface TrayIconOptions {
/** The tray icon id. If undefined, a random one will be assigend */
id?: string
/** The tray icon menu */
menu?: Menu | Submenu
/**
* The tray icon which could be icon bytes or path to the icon file.
*
* Note that you need the `icon-ico` or `icon-png` Cargo features to use this API.
* To enable it, change your Cargo.toml file:
* ```toml
* [dependencies]
* tauri = { version = "...", features = ["...", "icon-png"] }
* ```
*/
icon?: string | Uint8Array | number[]
/** The tray icon tooltip */
tooltip?: string
/**
* The tray title
*
* #### Platform-specific
*
* - **Linux:** The title will not be shown unless there is an icon
* as well. The title is useful for numerical and other frequently
* updated information. In general, it shouldn't be shown unless a
* user requests it as it can take up a significant amount of space
* on the user's panel. This may not be shown in all visualizations.
* - **Windows:** Unsupported.
*/
title?: string
/**
* The tray icon temp dir path. **Linux only**.
*
* On Linux, we need to write the icon to the disk and usually it will
* be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
*/
tempDirPath?: string
/**
* Use the icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only**.
*/
iconAsTemplate?: boolean
/** Whether to show the tray menu on left click or not, default is `true`. **macOS only**. */
menuOnLeftClick?: boolean
/** A handler for an event on the tray icon. */
action?: (event: TrayIconEvent) => void
}
/**
* Tray icon class and associated methods. This type constructor is private,
* instead, you should use the static method {@linkcode TrayIcon.new}.
*
* #### Warning
*
* Unlike Rust, javascript does not have any way to run cleanup code
* when an object is being removed by garbage collection, but this tray icon
* will be cleaned up when the tauri app exists, however if you want to cleanup
* this object early, you need to call {@linkcode TrayIcon.close}.
*
* @example
* ```ts
* import { TrayIcon } from '@tauri-apps/api/tray';
* const tray = await TrayIcon.new({ tooltip: 'awesome tray tooltip' });
* tray.set_tooltip('new tooltip');
* ```
*/
export class TrayIcon extends Resource {
/** The id associated with this tray icon. */
public id: string
private constructor(rid: number, id: string) {
super(rid)
this.id = id
}
/**
* Creates a new {@linkcode TrayIcon}
*
* #### Platform-specific:
*
* - **Linux:** Sometimes the icon won't be visible unless a menu is set.
* Setting an empty {@linkcode Menu} is enough.
*/
static async new(options?: TrayIconOptions): Promise<TrayIcon> {
if (options?.menu) {
// @ts-expect-error we only need the rid and kind
options.menu = [options.menu.rid, options.menu.kind]
}
if (options?.icon) {
options.icon =
typeof options.icon === 'string'
? options.icon
: Array.from(options.icon)
}
const handler = new Channel<TrayIconEvent>()
if (options?.action) {
handler.onmessage = options.action
delete options.action
}
return invoke<[number, string]>('plugin:tray|new', {
options: options ?? {},
handler
}).then(([rid, id]) => new TrayIcon(rid, id))
}
/** Sets a new tray icon. If `null` is provided, it will remove the icon. */
async setIcon(icon: string | Uint8Array | null): Promise<void> {
let trayIcon = null
if (icon) {
trayIcon = typeof icon === 'string' ? icon : Array.from(icon)
}
return invoke('plugin:tray|set_icon', { rid: this.rid, icon: trayIcon })
}
/**
* Sets a new tray menu.
*
* #### Platform-specific:
*
* - **Linux**: once a menu is set it cannot be removed so `null` has no effect
*/
async setMenu(menu: Menu | Submenu | null): Promise<void> {
if (menu) {
// @ts-expect-error we only need the rid and kind
menu = [menu.rid, menu.kind]
}
return invoke('plugin:tray|set_menu', { rid: this.rid, menu })
}
/**
* Sets the tooltip for this tray icon.
*
* ## Platform-specific:
*
* - **Linux:** Unsupported
*/
async setTooltip(tooltip: string | null): Promise<void> {
return invoke('plugin:tray|set_tooltip', { rid: this.rid, tooltip })
}
/**
* Sets the tooltip for this tray icon.
*
* ## Platform-specific:
*
* - **Linux:** The title will not be shown unless there is an icon
* as well. The title is useful for numerical and other frequently
* updated information. In general, it shouldn't be shown unless a
* user requests it as it can take up a significant amount of space
* on the user's panel. This may not be shown in all visualizations.
* - **Windows:** Unsupported
*/
async setTitle(title: string | null): Promise<void> {
return invoke('plugin:tray|set_title', { rid: this.rid, title })
}
/** Show or hide this tray icon. */
async setVisible(visible: boolean): Promise<void> {
return invoke('plugin:tray|set_visible', { rid: this.rid, visible })
}
/**
* Sets the tray icon temp dir path. **Linux only**.
*
* On Linux, we need to write the icon to the disk and usually it will
* be `$XDG_RUNTIME_DIR/tray-icon` or `$TEMP/tray-icon`.
*/
async setTempDirPath(path: string | null): Promise<void> {
return invoke('plugin:tray|set_temp_dir_path', { rid: this.rid, path })
}
/** Sets the current icon as a [template](https://developer.apple.com/documentation/appkit/nsimage/1520017-template?language=objc). **macOS only** */
async setIconAsTemplate(asTemplate: boolean): Promise<void> {
return invoke('plugin:tray|set_icon_as_template', {
rid: this.rid,
asTemplate
})
}
/** Disable or enable showing the tray menu on left click. **macOS only**. */
async setMenuOnLeftClick(onLeft: boolean): Promise<void> {
return invoke('plugin:tray|set_show_menu_on_left_click', {
rid: this.rid,
onLeft
})
}
}

View File

@ -1686,27 +1686,6 @@ class Window {
)
}
/**
* Listen to the window menu item click. The payload is the item id.
*
* @example
* ```typescript
* import { getCurrent } from "@tauri-apps/api/window";
* const unlisten = await getCurrent().onMenuClicked(({ payload: menuId }) => {
* console.log('Menu clicked: ' + menuId);
* });
*
* // 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 onMenuClicked(handler: EventCallback<string>): Promise<UnlistenFn> {
return this.listen<string>(TauriEvent.MENU, handler)
}
/**
* Listen to a file drop event.
* The listener is triggered when the user hovers the selected files on the window,

View File

@ -15,9 +15,9 @@
eslint-visitor-keys "^3.3.0"
"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.0", "@eslint-community/regexpp@^4.6.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.9.1.tgz#449dfa81a57a1d755b09aa58d826c1262e4283b4"
integrity sha512-Y27x+MBLjXa+0JWDhykM3+JE+il3kHKAEqabfEWq3SDhZjLYb6/BHL/JKFnH3fe207JaXkyDo685Oc2Glt6ifA==
version "4.10.0"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63"
integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==
"@eslint/eslintrc@^2.1.3":
version "2.1.3"
@ -91,9 +91,9 @@
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.9":
version "0.3.19"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
version "0.3.20"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
@ -206,14 +206,14 @@
integrity sha512-dMvGV8p92GQ8jhNlGIKpyhVZPzJlT258pPrM5q2F8lKcc9Iv9BbfdnhX1OfinYWnb9ms5zLw6MlaMnqLfUkKnQ==
"@types/estree@^1.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453"
integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
"@types/json-schema@^7.0.12":
version "7.0.14"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.14.tgz#74a97a5573980802f32c8e47b663530ab3b6b7d1"
integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/json5@^0.0.29":
version "0.0.29"
@ -228,9 +228,9 @@
undici-types "~5.26.4"
"@types/semver@^7.5.0":
version "7.5.4"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff"
integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==
version "7.5.5"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.5.tgz#deed5ab7019756c9c90ea86139106b0346223f35"
integrity sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==
"@typescript-eslint/eslint-plugin@6.10.0":
version "6.10.0"
@ -260,17 +260,6 @@
"@typescript-eslint/visitor-keys" "6.10.0"
debug "^4.3.4"
"@typescript-eslint/parser@^6.4.0":
version "6.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.9.0.tgz#2b402cadeadd3f211c25820e5433413347b27391"
integrity sha512-GZmjMh4AJ/5gaH4XF2eXA8tMnHWP+Pm1mjQR2QN4Iz+j/zO04b9TOvJYOX2sCNIQHtRStKTxRY1FX7LhpJT4Gw==
dependencies:
"@typescript-eslint/scope-manager" "6.9.0"
"@typescript-eslint/types" "6.9.0"
"@typescript-eslint/typescript-estree" "6.9.0"
"@typescript-eslint/visitor-keys" "6.9.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@6.10.0":
version "6.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz#b0276118b13d16f72809e3cecc86a72c93708540"
@ -279,14 +268,6 @@
"@typescript-eslint/types" "6.10.0"
"@typescript-eslint/visitor-keys" "6.10.0"
"@typescript-eslint/scope-manager@6.9.0":
version "6.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.9.0.tgz#2626e9a7fe0e004c3e25f3b986c75f584431134e"
integrity sha512-1R8A9Mc39n4pCCz9o79qRO31HGNDvC7UhPhv26TovDsWPBDx+Sg3rOZdCELIA3ZmNoWAuxaMOT7aWtGRSYkQxw==
dependencies:
"@typescript-eslint/types" "6.9.0"
"@typescript-eslint/visitor-keys" "6.9.0"
"@typescript-eslint/type-utils@6.10.0":
version "6.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz#1007faede067c78bdbcef2e8abb31437e163e2e1"
@ -302,11 +283,6 @@
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.10.0.tgz#f4f0a84aeb2ac546f21a66c6e0da92420e921367"
integrity sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==
"@typescript-eslint/types@6.9.0":
version "6.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.9.0.tgz#86a0cbe7ac46c0761429f928467ff3d92f841098"
integrity sha512-+KB0lbkpxBkBSiVCuQvduqMJy+I1FyDbdwSpM3IoBS7APl4Bu15lStPjgBIdykdRqQNYqYNMa8Kuidax6phaEw==
"@typescript-eslint/typescript-estree@6.10.0":
version "6.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz#667381eed6f723a1a8ad7590a31f312e31e07697"
@ -320,19 +296,6 @@
semver "^7.5.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/typescript-estree@6.9.0":
version "6.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.0.tgz#d0601b245be873d8fe49f3737f93f8662c8693d4"
integrity sha512-NJM2BnJFZBEAbCfBP00zONKXvMqihZCrmwCaik0UhLr0vAgb6oguXxLX1k00oQyD+vZZ+CJn3kocvv2yxm4awQ==
dependencies:
"@typescript-eslint/types" "6.9.0"
"@typescript-eslint/visitor-keys" "6.9.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.5.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/utils@6.10.0":
version "6.10.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.10.0.tgz#4d76062d94413c30e402c9b0df8c14aef8d77336"
@ -354,14 +317,6 @@
"@typescript-eslint/types" "6.10.0"
eslint-visitor-keys "^3.4.1"
"@typescript-eslint/visitor-keys@6.9.0":
version "6.9.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.0.tgz#cc69421c10c4ac997ed34f453027245988164e80"
integrity sha512-dGtAfqjV6RFOtIP8I0B4ZTBRrlTT8NHHlZZSchQx3qReaoDeXhYM++M4So2AgFK9ZB0emRPA6JI1HkafzA2Ibg==
dependencies:
"@typescript-eslint/types" "6.9.0"
eslint-visitor-keys "^3.4.1"
"@ungap/structured-clone@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
@ -373,9 +328,9 @@ acorn-jsx@^5.3.2:
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.8.2, acorn@^8.9.0:
version "8.10.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
version "8.11.2"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.2.tgz#ca0d78b51895be5390a5903c5b3bdcdaf78ae40b"
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
ajv@^6.12.4:
version "6.12.6"
@ -509,13 +464,14 @@ builtins@^5.0.1:
dependencies:
semver "^7.0.0"
call-bind@^1.0.0, call-bind@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513"
integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==
dependencies:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
function-bind "^1.1.2"
get-intrinsic "^1.2.1"
set-function-length "^1.1.1"
callsites@^3.0.0:
version "3.1.0"
@ -580,7 +536,7 @@ deep-is@^0.1.3:
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
define-data-property@^1.0.1:
define-data-property@^1.0.1, define-data-property@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.1.tgz#c35f7cd0ab09883480d12ac5cb213715587800b3"
integrity sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==
@ -620,25 +576,25 @@ doctrine@^3.0.0:
esutils "^2.0.2"
es-abstract@^1.22.1:
version "1.22.2"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.2.tgz#90f7282d91d0ad577f505e423e52d4c1d93c1b8a"
integrity sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==
version "1.22.3"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32"
integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==
dependencies:
array-buffer-byte-length "^1.0.0"
arraybuffer.prototype.slice "^1.0.2"
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
call-bind "^1.0.5"
es-set-tostringtag "^2.0.1"
es-to-primitive "^1.2.1"
function.prototype.name "^1.1.6"
get-intrinsic "^1.2.1"
get-intrinsic "^1.2.2"
get-symbol-description "^1.0.0"
globalthis "^1.0.3"
gopd "^1.0.1"
has "^1.0.3"
has-property-descriptors "^1.0.0"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
internal-slot "^1.0.5"
is-array-buffer "^3.0.2"
is-callable "^1.2.7"
@ -648,7 +604,7 @@ es-abstract@^1.22.1:
is-string "^1.0.7"
is-typed-array "^1.1.12"
is-weakref "^1.0.2"
object-inspect "^1.12.3"
object-inspect "^1.13.1"
object-keys "^1.1.1"
object.assign "^4.1.4"
regexp.prototype.flags "^1.5.1"
@ -662,23 +618,23 @@ es-abstract@^1.22.1:
typed-array-byte-offset "^1.0.0"
typed-array-length "^1.0.4"
unbox-primitive "^1.0.2"
which-typed-array "^1.1.11"
which-typed-array "^1.1.13"
es-set-tostringtag@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
version "2.0.2"
resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz#11f7cc9f63376930a5f20be4915834f4bc74f9c9"
integrity sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==
dependencies:
get-intrinsic "^1.1.3"
has "^1.0.3"
get-intrinsic "^1.2.2"
has-tostringtag "^1.0.0"
hasown "^2.0.0"
es-shim-unscopables@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz#702e632193201e3edf8713635d083d378e510241"
integrity sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==
version "1.0.2"
resolved "https://registry.yarnpkg.com/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz#1f6942e71ecc7835ed1c8a83006d8771a63a3763"
integrity sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==
dependencies:
has "^1.0.3"
hasown "^2.0.0"
es-to-primitive@^1.2.1:
version "1.2.1"
@ -699,19 +655,6 @@ eslint-config-prettier@9.0.0:
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz#eb25485946dd0c66cd216a46232dc05451518d1f"
integrity sha512-IcJsTkJae2S35pRsRAwoCE+925rJJStOdkKnLVgtE+tEpqU0EVVM7OqrwxqgptKdX29NUwC82I5pXsGFIgSevw==
eslint-config-standard-with-typescript@39.1.1:
version "39.1.1"
resolved "https://registry.yarnpkg.com/eslint-config-standard-with-typescript/-/eslint-config-standard-with-typescript-39.1.1.tgz#d682bd1fc8f1ee996940f85c9b0a833d7cfa5fee"
integrity sha512-t6B5Ep8E4I18uuoYeYxINyqcXb2UbC0SOOTxRtBSt2JUs+EzeXbfe2oaiPs71AIdnoWhXDO2fYOHz8df3kV84A==
dependencies:
"@typescript-eslint/parser" "^6.4.0"
eslint-config-standard "17.1.0"
eslint-config-standard@17.1.0:
version "17.1.0"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-17.1.0.tgz#40ffb8595d47a6b242e07cbfd49dc211ed128975"
integrity sha512-IwHwmaBNtDK4zDHQukFDW5u/aTb8+meQWZvNFWkiGmbWjD6bqyuSSBxxXKkCftCUzc1zwCH2m/baCNDLGmuO5Q==
eslint-import-resolver-node@^0.3.9:
version "0.3.9"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac"
@ -729,9 +672,9 @@ eslint-module-utils@^2.8.0:
debug "^3.2.7"
eslint-plugin-es-x@^7.1.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-es-x/-/eslint-plugin-es-x-7.2.0.tgz#5779d742ad31f8fd780b9481331481e142b72311"
integrity sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==
version "7.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-es-x/-/eslint-plugin-es-x-7.3.0.tgz#c699280ad35cd315720c3cccf0fe503092c08788"
integrity sha512-W9zIs+k00I/I13+Bdkl/zG1MEO07G97XjUSQuH117w620SJ6bHtLUmoMvkGA2oYnI/gNdr+G7BONLyYnFaLLEQ==
dependencies:
"@eslint-community/eslint-utils" "^4.1.2"
"@eslint-community/regexpp" "^4.6.0"
@ -918,7 +861,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@3.3.2:
fast-glob@3.3.2, fast-glob@^3.2.9:
version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@ -929,17 +872,6 @@ fast-glob@3.3.2:
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-glob@^3.2.9:
version "3.3.1"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.1.tgz#784b4e897340f3dbbef17413b3f11acf03c874c4"
integrity sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==
dependencies:
"@nodelib/fs.stat" "^2.0.2"
"@nodelib/fs.walk" "^1.2.3"
glob-parent "^5.1.2"
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
@ -980,9 +912,9 @@ find-up@^5.0.0:
path-exists "^4.0.0"
flat-cache@^3.0.4:
version "3.1.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.1.tgz#a02a15fdec25a8f844ff7cc658f03dd99eb4609b"
integrity sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==
version "3.2.0"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee"
integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
dependencies:
flatted "^3.2.9"
keyv "^4.5.3"
@ -1010,7 +942,7 @@ fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.1, function-bind@^1.1.2:
function-bind@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
@ -1030,15 +962,15 @@ functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b"
integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==
dependencies:
function-bind "^1.1.1"
has "^1.0.3"
function-bind "^1.1.2"
has-proto "^1.0.1"
has-symbols "^1.0.3"
hasown "^2.0.0"
get-symbol-description@^1.0.0:
version "1.0.0"
@ -1130,11 +1062,11 @@ has-flag@^4.0.0:
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-property-descriptors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz#52ba30b6c5ec87fd89fa574bc1c39125c6f65340"
integrity sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==
dependencies:
get-intrinsic "^1.1.1"
get-intrinsic "^1.2.2"
has-proto@^1.0.1:
version "1.0.1"
@ -1153,11 +1085,6 @@ has-tostringtag@^1.0.0:
dependencies:
has-symbols "^1.0.2"
has@^1.0.3:
version "1.0.4"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6"
integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==
hasown@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c"
@ -1197,12 +1124,12 @@ inherits@2:
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
internal-slot@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
version "1.0.6"
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930"
integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==
dependencies:
get-intrinsic "^1.2.0"
has "^1.0.3"
get-intrinsic "^1.2.2"
hasown "^2.0.0"
side-channel "^1.0.4"
is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
@ -1234,14 +1161,7 @@ is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-core-module@^2.12.1, is-core-module@^2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==
dependencies:
has "^1.0.3"
is-core-module@^2.13.1:
is-core-module@^2.12.1, is-core-module@^2.13.0, is-core-module@^2.13.1:
version "2.13.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384"
integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==
@ -1445,10 +1365,10 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
object-inspect@^1.12.3, object-inspect@^1.9.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.0.tgz#42695d3879e1cd5bda6df5062164d80c996e23e2"
integrity sha512-HQ4J+ic8hKrgIt3mqk6cVOVrW2ozL4KdvHlqpBv9vDYWx9ysAgENAdvy4FoGF+KFdhR7nQTNm5J0ctAeOwn+3g==
object-inspect@^1.13.1, object-inspect@^1.9.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2"
integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==
object-keys@^1.1.1:
version "1.1.1"
@ -1574,9 +1494,9 @@ prettier@3.0.3:
integrity sha512-L/4pUDMxcNa8R/EthV08Zt42WBO4h1rarVtK0K+QJG0X187OLo7l699jWw0GKuwzkPQ//jMFA/8Xm6Fh3J/DAg==
punycode@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
version "2.3.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
queue-microtask@^1.2.2:
version "1.2.3"
@ -1716,6 +1636,16 @@ serialize-javascript@^6.0.1:
dependencies:
randombytes "^2.1.0"
set-function-length@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.1.1.tgz#4bc39fafb0307224a33e106a7d35ca1218d659ed"
integrity sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==
dependencies:
define-data-property "^1.1.1"
get-intrinsic "^1.2.1"
gopd "^1.0.1"
has-property-descriptors "^1.0.0"
set-function-name@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-function-name/-/set-function-name-2.0.1.tgz#12ce38b7954310b9f61faa12701620a0c882793a"
@ -1826,9 +1756,9 @@ supports-preserve-symlinks-flag@^1.0.0:
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
terser@^5.17.4:
version "5.22.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.22.0.tgz#4f18103f84c5c9437aafb7a14918273310a8a49d"
integrity sha512-hHZVLgRA2z4NWcN6aS5rQDc+7Dcy58HOf2zbYwmFcQ+ua3h6eEFf5lIDKTzbWwlazPyOZsFQO8V80/IjVNExEw==
version "5.24.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.24.0.tgz#4ae50302977bca4831ccc7b4fef63a3c04228364"
integrity sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==
dependencies:
"@jridgewell/source-map" "^0.3.3"
acorn "^8.8.2"
@ -1956,13 +1886,13 @@ which-boxed-primitive@^1.0.2:
is-string "^1.0.5"
is-symbol "^1.0.3"
which-typed-array@^1.1.11:
version "1.1.11"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.11.tgz#99d691f23c72aab6768680805a271b69761ed61a"
integrity sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==
which-typed-array@^1.1.11, which-typed-array@^1.1.13:
version "1.1.13"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36"
integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==
dependencies:
available-typed-arrays "^1.0.5"
call-bind "^1.0.2"
call-bind "^1.0.4"
for-each "^0.3.3"
gopd "^1.0.1"
has-tostringtag "^1.0.0"