mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-08-16 19:30:27 +03:00
feat(api): add Image
class (#9042)
* feat(api): add `Image` class * clippy * license headers * small cleanup * fixes * code review * readd from_png_bytes and from_ico_bytes --------- Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
parent
2ca9afb576
commit
77b9a508a4
@ -1,5 +1,6 @@
|
|||||||
---
|
---
|
||||||
'tauri': 'minor:feat'
|
'tauri': 'minor:feat'
|
||||||
|
'@tauri-apps/api': 'minor:feat'
|
||||||
---
|
---
|
||||||
|
|
||||||
Add `Image` type.
|
Add a new `Image` type in Rust and JS.
|
||||||
|
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -3418,12 +3418,6 @@ dependencies = [
|
|||||||
"loom",
|
"loom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "static_assertions"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@ -3630,7 +3624,6 @@ dependencies = [
|
|||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serialize-to-javascript",
|
"serialize-to-javascript",
|
||||||
"state",
|
"state",
|
||||||
"static_assertions",
|
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
|
@ -72,7 +72,6 @@ png = { version = "0.17", optional = true }
|
|||||||
ico = { version = "0.3.0", optional = true }
|
ico = { version = "0.3.0", optional = true }
|
||||||
http-range = { version = "0.1.5", optional = true }
|
http-range = { version = "0.1.5", optional = true }
|
||||||
tracing = { version = "0.1", optional = true }
|
tracing = { version = "0.1", optional = true }
|
||||||
static_assertions = "1"
|
|
||||||
heck = "0.4"
|
heck = "0.4"
|
||||||
|
|
||||||
[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]
|
[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]
|
||||||
|
@ -137,6 +137,19 @@ const PLUGINS: &[(&str, &[(&str, bool)])] = &[
|
|||||||
("app_hide", false),
|
("app_hide", false),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"image",
|
||||||
|
&[
|
||||||
|
("new", true),
|
||||||
|
("from_bytes", true),
|
||||||
|
("from_png_bytes", true),
|
||||||
|
("from_ico_bytes", true),
|
||||||
|
("from_path", true),
|
||||||
|
("rgba", true),
|
||||||
|
("width", true),
|
||||||
|
("height", true),
|
||||||
|
],
|
||||||
|
),
|
||||||
("resources", &[("close", true)]),
|
("resources", &[("close", true)]),
|
||||||
(
|
(
|
||||||
"menu",
|
"menu",
|
||||||
|
19
core/tauri/permissions/image/autogenerated/reference.md
Normal file
19
core/tauri/permissions/image/autogenerated/reference.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
| Permission | Description |
|
||||||
|
|------|-----|
|
||||||
|
|`allow-from-bytes`|Enables the from_bytes command without any pre-configured scope.|
|
||||||
|
|`deny-from-bytes`|Denies the from_bytes command without any pre-configured scope.|
|
||||||
|
|`allow-from-ico-bytes`|Enables the from_ico_bytes command without any pre-configured scope.|
|
||||||
|
|`deny-from-ico-bytes`|Denies the from_ico_bytes command without any pre-configured scope.|
|
||||||
|
|`allow-from-path`|Enables the from_path command without any pre-configured scope.|
|
||||||
|
|`deny-from-path`|Denies the from_path command without any pre-configured scope.|
|
||||||
|
|`allow-from-png-bytes`|Enables the from_png_bytes command without any pre-configured scope.|
|
||||||
|
|`deny-from-png-bytes`|Denies the from_png_bytes command without any pre-configured scope.|
|
||||||
|
|`allow-height`|Enables the height command without any pre-configured scope.|
|
||||||
|
|`deny-height`|Denies the height command without any pre-configured scope.|
|
||||||
|
|`allow-new`|Enables the new command without any pre-configured scope.|
|
||||||
|
|`deny-new`|Denies the new command without any pre-configured scope.|
|
||||||
|
|`allow-rgba`|Enables the rgba command without any pre-configured scope.|
|
||||||
|
|`deny-rgba`|Denies the rgba command without any pre-configured scope.|
|
||||||
|
|`allow-width`|Enables the width command without any pre-configured scope.|
|
||||||
|
|`deny-width`|Denies the width command without any pre-configured scope.|
|
||||||
|
|`default`|Default permissions for the plugin.|
|
File diff suppressed because one or more lines are too long
@ -870,6 +870,7 @@ impl<R: Runtime> App<R> {
|
|||||||
self.handle.plugin(crate::webview::plugin::init())?;
|
self.handle.plugin(crate::webview::plugin::init())?;
|
||||||
self.handle.plugin(crate::app::plugin::init())?;
|
self.handle.plugin(crate::app::plugin::init())?;
|
||||||
self.handle.plugin(crate::resources::plugin::init())?;
|
self.handle.plugin(crate::resources::plugin::init())?;
|
||||||
|
self.handle.plugin(crate::image::plugin::init())?;
|
||||||
#[cfg(desktop)]
|
#[cfg(desktop)]
|
||||||
self.handle.plugin(crate::menu::plugin::init())?;
|
self.handle.plugin(crate::menu::plugin::init())?;
|
||||||
#[cfg(all(desktop, feature = "tray-icon"))]
|
#[cfg(all(desktop, feature = "tray-icon"))]
|
||||||
|
@ -2,8 +2,13 @@
|
|||||||
// SPDX-License-Identifier: Apache-2.0
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
pub mod plugin;
|
||||||
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{Manager, Resource, ResourceId, Runtime};
|
||||||
|
|
||||||
/// An RGBA Image in row-major order from top to bottom.
|
/// An RGBA Image in row-major order from top to bottom.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -13,6 +18,21 @@ pub struct Image<'a> {
|
|||||||
height: u32,
|
height: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Resource for Image<'static> {}
|
||||||
|
|
||||||
|
impl Image<'static> {
|
||||||
|
/// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.
|
||||||
|
///
|
||||||
|
/// Similar to [`Self::new`] but avoids cloning the rgba data to get an owned Image.
|
||||||
|
pub const fn new_owned(rgba: Vec<u8>, width: u32, height: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
rgba: Cow::Owned(rgba),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Image<'a> {
|
impl<'a> Image<'a> {
|
||||||
/// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.
|
/// Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height.
|
||||||
pub const fn new(rgba: &'a [u8], width: u32, height: u32) -> Self {
|
pub const fn new(rgba: &'a [u8], width: u32, height: u32) -> Self {
|
||||||
@ -123,6 +143,19 @@ impl<'a> Image<'a> {
|
|||||||
pub fn height(&self) -> u32 {
|
pub fn height(&self) -> u32 {
|
||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert into a 'static owned [`Image`].
|
||||||
|
/// This will allocate.
|
||||||
|
pub fn to_owned(self) -> Image<'static> {
|
||||||
|
Image {
|
||||||
|
rgba: match self.rgba {
|
||||||
|
Cow::Owned(v) => Cow::Owned(v),
|
||||||
|
Cow::Borrowed(v) => Cow::Owned(v.to_vec()),
|
||||||
|
},
|
||||||
|
height: self.height,
|
||||||
|
width: self.width,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<Image<'a>> for crate::runtime::Icon<'a> {
|
impl<'a> From<Image<'a>> for crate::runtime::Icon<'a> {
|
||||||
@ -153,12 +186,12 @@ impl TryFrom<Image<'_>> for tray_icon::Icon {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(desktop)]
|
|
||||||
#[derive(serde::Deserialize)]
|
#[derive(serde::Deserialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum JsIcon<'a> {
|
pub enum JsImage<'a> {
|
||||||
Path(std::path::PathBuf),
|
Path(std::path::PathBuf),
|
||||||
Bytes(&'a [u8]),
|
Bytes(&'a [u8]),
|
||||||
|
Resource(ResourceId),
|
||||||
Rgba {
|
Rgba {
|
||||||
rgba: &'a [u8],
|
rgba: &'a [u8],
|
||||||
width: u32,
|
width: u32,
|
||||||
@ -166,23 +199,24 @@ pub enum JsIcon<'a> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(desktop)]
|
impl<'a> JsImage<'a> {
|
||||||
impl<'a> TryFrom<JsIcon<'a>> for Image<'a> {
|
pub fn into_img<R: Runtime, M: Manager<R>>(self, app: &M) -> crate::Result<Arc<Image<'a>>> {
|
||||||
type Error = crate::Error;
|
match self {
|
||||||
|
Self::Resource(rid) => {
|
||||||
fn try_from(img: JsIcon<'a>) -> Result<Self, Self::Error> {
|
let resources_table = app.resources_table();
|
||||||
match img {
|
resources_table.get::<Image<'static>>(rid)
|
||||||
|
}
|
||||||
#[cfg(any(feature = "image-ico", feature = "image-png"))]
|
#[cfg(any(feature = "image-ico", feature = "image-png"))]
|
||||||
JsIcon::Path(path) => Self::from_path(path).map_err(Into::into),
|
Self::Path(path) => Image::from_path(path).map(Arc::new).map_err(Into::into),
|
||||||
|
|
||||||
#[cfg(any(feature = "image-ico", feature = "image-png"))]
|
#[cfg(any(feature = "image-ico", feature = "image-png"))]
|
||||||
JsIcon::Bytes(bytes) => Self::from_bytes(bytes).map_err(Into::into),
|
Self::Bytes(bytes) => Image::from_bytes(bytes).map(Arc::new).map_err(Into::into),
|
||||||
|
|
||||||
JsIcon::Rgba {
|
Self::Rgba {
|
||||||
rgba,
|
rgba,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
} => Ok(Self::new(rgba, width, height)),
|
} => Ok(Arc::new(Image::new(rgba, width, height))),
|
||||||
|
|
||||||
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
|
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
|
||||||
_ => Err(
|
_ => Err(
|
||||||
@ -190,9 +224,9 @@ impl<'a> TryFrom<JsIcon<'a>> for Image<'a> {
|
|||||||
ErrorKind::InvalidInput,
|
ErrorKind::InvalidInput,
|
||||||
format!(
|
format!(
|
||||||
"expected RGBA image data, found {}",
|
"expected RGBA image data, found {}",
|
||||||
match img {
|
match self {
|
||||||
JsIcon::Path(_) => "a file path",
|
JsImage::Path(_) => "a file path",
|
||||||
JsIcon::Bytes(_) => "raw bytes",
|
JsImage::Bytes(_) => "raw bytes",
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
),
|
),
|
116
core/tauri/src/image/plugin.rs
Normal file
116
core/tauri/src/image/plugin.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
use crate::plugin::{Builder, TauriPlugin};
|
||||||
|
use crate::{command, AppHandle, Image, Manager, ResourceId, Runtime};
|
||||||
|
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn new<R: Runtime>(
|
||||||
|
app: AppHandle<R>,
|
||||||
|
rgba: Vec<u8>,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
) -> crate::Result<ResourceId> {
|
||||||
|
let image = Image::new_owned(rgba, width, height);
|
||||||
|
let mut resources_table = app.resources_table();
|
||||||
|
let rid = resources_table.add(image);
|
||||||
|
Ok(rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image-ico", feature = "image-png"))]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
|
||||||
|
let image = Image::from_bytes(&bytes)?.to_owned();
|
||||||
|
let mut resources_table = app.resources_table();
|
||||||
|
let rid = resources_table.add(image);
|
||||||
|
Ok(rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_bytes() -> std::result::Result<(), &'static str> {
|
||||||
|
Err("from_bytes is only supported if the `image-ico` or `image-png` Cargo features are enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image-ico")]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_ico_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
|
||||||
|
let image = Image::from_ico_bytes(&bytes)?.to_owned();
|
||||||
|
let mut resources_table = app.resources_table();
|
||||||
|
let rid = resources_table.add(image);
|
||||||
|
Ok(rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "image-ico"))]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_ico_bytes() -> std::result::Result<(), &'static str> {
|
||||||
|
Err("from_ico_bytes is only supported if the `image-ico` Cargo feature is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "image-png")]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_png_bytes<R: Runtime>(app: AppHandle<R>, bytes: Vec<u8>) -> crate::Result<ResourceId> {
|
||||||
|
let image = Image::from_png_bytes(&bytes)?.to_owned();
|
||||||
|
let mut resources_table = app.resources_table();
|
||||||
|
let rid = resources_table.add(image);
|
||||||
|
Ok(rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "image-png"))]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_png_bytes() -> std::result::Result<(), &'static str> {
|
||||||
|
Err("from_png_bytes is only supported if the `image-ico` Cargo feature is enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "image-ico", feature = "image-png"))]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_path<R: Runtime>(app: AppHandle<R>, path: std::path::PathBuf) -> crate::Result<ResourceId> {
|
||||||
|
let image = Image::from_path(path)?.to_owned();
|
||||||
|
let mut resources_table = app.resources_table();
|
||||||
|
let rid = resources_table.add(image);
|
||||||
|
Ok(rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(feature = "image-ico", feature = "image-png")))]
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn from_path() -> std::result::Result<(), &'static str> {
|
||||||
|
Err("from_path is only supported if the `image-ico` or `image-png` Cargo features are enabled")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn rgba<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<Vec<u8>> {
|
||||||
|
let resources_table = app.resources_table();
|
||||||
|
let image = resources_table.get::<Image<'_>>(rid)?;
|
||||||
|
Ok(image.rgba().to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn width<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<u32> {
|
||||||
|
let resources_table = app.resources_table();
|
||||||
|
let image = resources_table.get::<Image<'_>>(rid)?;
|
||||||
|
Ok(image.width())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[command(root = "crate")]
|
||||||
|
fn height<R: Runtime>(app: AppHandle<R>, rid: ResourceId) -> crate::Result<u32> {
|
||||||
|
let resources_table = app.resources_table();
|
||||||
|
let image = resources_table.get::<Image<'_>>(rid)?;
|
||||||
|
Ok(image.height())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initializes the plugin.
|
||||||
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||||
|
Builder::new("image")
|
||||||
|
.invoke_handler(crate::generate_handler![
|
||||||
|
new,
|
||||||
|
from_bytes,
|
||||||
|
from_ico_bytes,
|
||||||
|
from_png_bytes,
|
||||||
|
from_path,
|
||||||
|
rgba,
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
])
|
||||||
|
.build()
|
||||||
|
}
|
@ -13,7 +13,7 @@ use tauri_runtime::window::dpi::Position;
|
|||||||
use super::{sealed::ContextMenuBase, *};
|
use super::{sealed::ContextMenuBase, *};
|
||||||
use crate::{
|
use crate::{
|
||||||
command,
|
command,
|
||||||
image::JsIcon,
|
image::JsImage,
|
||||||
ipc::{channel::JavaScriptChannelId, Channel},
|
ipc::{channel::JavaScriptChannelId, Channel},
|
||||||
plugin::{Builder, TauriPlugin},
|
plugin::{Builder, TauriPlugin},
|
||||||
resources::{ResourceId, ResourceTable},
|
resources::{ResourceId, ResourceTable},
|
||||||
@ -46,29 +46,30 @@ pub(crate) struct AboutMetadata<'a> {
|
|||||||
pub website_label: Option<String>,
|
pub website_label: Option<String>,
|
||||||
pub credits: Option<String>,
|
pub credits: Option<String>,
|
||||||
#[serde(borrow)]
|
#[serde(borrow)]
|
||||||
pub icon: Option<JsIcon<'a>>,
|
pub icon: Option<JsImage<'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TryFrom<AboutMetadata<'a>> for super::AboutMetadata<'a> {
|
impl<'a> AboutMetadata<'a> {
|
||||||
type Error = crate::Error;
|
pub fn into_metdata<R: Runtime, M: Manager<R>>(
|
||||||
|
self,
|
||||||
fn try_from(value: AboutMetadata<'a>) -> Result<Self, Self::Error> {
|
app: &M,
|
||||||
let icon = match value.icon {
|
) -> crate::Result<super::AboutMetadata<'a>> {
|
||||||
Some(i) => Some(i.try_into()?),
|
let icon = match self.icon {
|
||||||
|
Some(i) => Some(i.into_img(app)?.as_ref().clone()),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(super::AboutMetadata {
|
||||||
name: value.name,
|
name: self.name,
|
||||||
version: value.version,
|
version: self.version,
|
||||||
short_version: value.short_version,
|
short_version: self.short_version,
|
||||||
authors: value.authors,
|
authors: self.authors,
|
||||||
comments: value.comments,
|
comments: self.comments,
|
||||||
copyright: value.copyright,
|
copyright: self.copyright,
|
||||||
license: value.license,
|
license: self.license,
|
||||||
website: value.website,
|
website: self.website,
|
||||||
website_label: value.website_label,
|
website_label: self.website_label,
|
||||||
credits: value.credits,
|
credits: self.credits,
|
||||||
icon,
|
icon,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -173,7 +174,7 @@ impl CheckMenuItemPayload {
|
|||||||
enum Icon<'a> {
|
enum Icon<'a> {
|
||||||
Native(NativeIcon),
|
Native(NativeIcon),
|
||||||
#[serde(borrow)]
|
#[serde(borrow)]
|
||||||
Icon(JsIcon<'a>),
|
Icon(JsImage<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -203,7 +204,7 @@ impl<'a> IconMenuItemPayload<'a> {
|
|||||||
}
|
}
|
||||||
builder = match self.icon {
|
builder = match self.icon {
|
||||||
Icon::Native(native_icon) => builder.native_icon(native_icon),
|
Icon::Native(native_icon) => builder.native_icon(native_icon),
|
||||||
Icon::Icon(icon) => builder.icon(icon.try_into()?),
|
Icon::Icon(icon) => builder.icon(icon.into_img(webview)?.as_ref().clone()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let item = builder.build(webview)?;
|
let item = builder.build(webview)?;
|
||||||
@ -291,7 +292,7 @@ impl<'a> PredefinedMenuItemPayload<'a> {
|
|||||||
Predefined::Quit => PredefinedMenuItem::quit(webview, self.text.as_deref()),
|
Predefined::Quit => PredefinedMenuItem::quit(webview, self.text.as_deref()),
|
||||||
Predefined::About(metadata) => {
|
Predefined::About(metadata) => {
|
||||||
let metadata = match metadata {
|
let metadata = match metadata {
|
||||||
Some(m) => Some(m.try_into()?),
|
Some(m) => Some(m.into_metdata(webview)?),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
PredefinedMenuItem::about(webview, self.text.as_deref(), metadata)
|
PredefinedMenuItem::about(webview, self.text.as_deref(), metadata)
|
||||||
@ -852,7 +853,7 @@ fn set_icon<R: Runtime>(
|
|||||||
|
|
||||||
match icon {
|
match icon {
|
||||||
Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
|
Some(Icon::Native(icon)) => icon_item.set_native_icon(Some(icon)),
|
||||||
Some(Icon::Icon(icon)) => icon_item.set_icon(Some(icon.try_into()?)),
|
Some(Icon::Icon(icon)) => icon_item.set_icon(Some(icon.into_img(&app)?.as_ref().clone())),
|
||||||
None => {
|
None => {
|
||||||
icon_item.set_icon(None)?;
|
icon_item.set_icon(None)?;
|
||||||
icon_item.set_native_icon(None)?;
|
icon_item.set_native_icon(None)?;
|
||||||
|
@ -8,7 +8,7 @@ use serde::Deserialize;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
command,
|
command,
|
||||||
image::JsIcon,
|
image::JsImage,
|
||||||
ipc::Channel,
|
ipc::Channel,
|
||||||
menu::{plugin::ItemKind, Menu, Submenu},
|
menu::{plugin::ItemKind, Menu, Submenu},
|
||||||
plugin::{Builder, TauriPlugin},
|
plugin::{Builder, TauriPlugin},
|
||||||
@ -25,7 +25,7 @@ struct TrayIconOptions<'a> {
|
|||||||
id: Option<String>,
|
id: Option<String>,
|
||||||
menu: Option<(ResourceId, ItemKind)>,
|
menu: Option<(ResourceId, ItemKind)>,
|
||||||
#[serde(borrow)]
|
#[serde(borrow)]
|
||||||
icon: Option<JsIcon<'a>>,
|
icon: Option<JsImage<'a>>,
|
||||||
tooltip: Option<String>,
|
tooltip: Option<String>,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
temp_dir_path: Option<PathBuf>,
|
temp_dir_path: Option<PathBuf>,
|
||||||
@ -65,7 +65,7 @@ fn new<R: Runtime>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if let Some(icon) = options.icon {
|
if let Some(icon) = options.icon {
|
||||||
builder = builder.icon(icon.try_into()?);
|
builder = builder.icon(icon.into_img(&app)?.as_ref().clone());
|
||||||
}
|
}
|
||||||
if let Some(tooltip) = options.tooltip {
|
if let Some(tooltip) = options.tooltip {
|
||||||
builder = builder.tooltip(tooltip);
|
builder = builder.tooltip(tooltip);
|
||||||
@ -94,12 +94,12 @@ fn new<R: Runtime>(
|
|||||||
fn set_icon<R: Runtime>(
|
fn set_icon<R: Runtime>(
|
||||||
app: AppHandle<R>,
|
app: AppHandle<R>,
|
||||||
rid: ResourceId,
|
rid: ResourceId,
|
||||||
icon: Option<JsIcon<'_>>,
|
icon: Option<JsImage<'_>>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let resources_table = app.resources_table();
|
let resources_table = app.resources_table();
|
||||||
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
|
let tray = resources_table.get::<TrayIcon<R>>(rid)?;
|
||||||
let icon = match icon {
|
let icon = match icon {
|
||||||
Some(i) => Some(i.try_into()?),
|
Some(i) => Some(i.into_img(&app)?.as_ref().clone()),
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
tray.set_icon(icon)
|
tray.set_icon(icon)
|
||||||
|
@ -134,10 +134,11 @@ mod desktop_commands {
|
|||||||
pub async fn set_icon<R: Runtime>(
|
pub async fn set_icon<R: Runtime>(
|
||||||
window: Window<R>,
|
window: Window<R>,
|
||||||
label: Option<String>,
|
label: Option<String>,
|
||||||
value: crate::image::JsIcon<'_>,
|
value: crate::image::JsImage<'_>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
get_window(window, label)?
|
let window = get_window(window, label)?;
|
||||||
.set_icon(value.try_into()?)
|
window
|
||||||
|
.set_icon(value.into_img(&window)?.as_ref().clone())
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
7
examples/api/src-tauri/Cargo.lock
generated
7
examples/api/src-tauri/Cargo.lock
generated
@ -2975,12 +2975,6 @@ dependencies = [
|
|||||||
"loom",
|
"loom",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "static_assertions"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "string_cache"
|
name = "string_cache"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@ -3182,7 +3176,6 @@ dependencies = [
|
|||||||
"serde_repr",
|
"serde_repr",
|
||||||
"serialize-to-javascript",
|
"serialize-to-javascript",
|
||||||
"state",
|
"state",
|
||||||
"static_assertions",
|
|
||||||
"swift-rs",
|
"swift-rs",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-macros",
|
"tauri-macros",
|
||||||
|
@ -2,10 +2,7 @@
|
|||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
"$schema": "../gen/schemas/desktop-schema.json",
|
||||||
"identifier": "run-app",
|
"identifier": "run-app",
|
||||||
"description": "permissions to run the app",
|
"description": "permissions to run the app",
|
||||||
"windows": [
|
"windows": ["main", "main-*"],
|
||||||
"main",
|
|
||||||
"main-*"
|
|
||||||
],
|
|
||||||
"permissions": [
|
"permissions": [
|
||||||
{
|
{
|
||||||
"identifier": "allow-log-operation",
|
"identifier": "allow-log-operation",
|
||||||
@ -24,6 +21,7 @@
|
|||||||
"window:default",
|
"window:default",
|
||||||
"app:default",
|
"app:default",
|
||||||
"resources:default",
|
"resources:default",
|
||||||
|
"image:default",
|
||||||
"menu:default",
|
"menu:default",
|
||||||
"tray:default",
|
"tray:default",
|
||||||
"app:allow-app-hide",
|
"app:allow-app-hide",
|
||||||
@ -98,4 +96,4 @@
|
|||||||
"tray:allow-set-icon-as-template",
|
"tray:allow-set-icon-as-template",
|
||||||
"tray:allow-set-show-menu-on-left-click"
|
"tray:allow-set-show-menu-on-left-click"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
141
tooling/api/src/image.ts
Normal file
141
tooling/api/src/image.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
import { Resource, invoke } from './core'
|
||||||
|
|
||||||
|
/** An RGBA Image in row-major order from top to bottom. */
|
||||||
|
export class Image extends Resource {
|
||||||
|
private constructor(rid: number) {
|
||||||
|
super(rid)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new Image using RGBA data, in row-major order from top to bottom, and with specified width and height. */
|
||||||
|
static async new(
|
||||||
|
rgba: number[] | Uint8Array | ArrayBuffer,
|
||||||
|
width: number,
|
||||||
|
height: number
|
||||||
|
): Promise<Image> {
|
||||||
|
return invoke<number>('plugin:image|new', {
|
||||||
|
rgba: transformImage(rgba),
|
||||||
|
width,
|
||||||
|
height
|
||||||
|
}).then((rid) => new Image(rid))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new image using the provided bytes by inferring the file format.
|
||||||
|
* If the format is known, prefer [@link Image.fromPngBytes] or [@link Image.fromIcoBytes].
|
||||||
|
*
|
||||||
|
* Only `ico` and `png` are supported (based on activated feature flag).
|
||||||
|
*
|
||||||
|
* Note that you need the `image-ico` or `image-png` Cargo features to use this API.
|
||||||
|
* To enable it, change your Cargo.toml file:
|
||||||
|
* ```toml
|
||||||
|
* [dependencies]
|
||||||
|
* tauri = { version = "...", features = ["...", "image-png"] }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static async fromBytes(
|
||||||
|
bytes: number[] | Uint8Array | ArrayBuffer
|
||||||
|
): Promise<Image> {
|
||||||
|
return invoke<number>('plugin:image|from_bytes', {
|
||||||
|
bytes: transformImage(bytes)
|
||||||
|
}).then((rid) => new Image(rid))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new image using the provided png bytes.
|
||||||
|
*
|
||||||
|
* Note that you need the `image-png` Cargo features to use this API.
|
||||||
|
* To enable it, change your Cargo.toml file:
|
||||||
|
* ```toml
|
||||||
|
* [dependencies]
|
||||||
|
* tauri = { version = "...", features = ["...", "image-png"] }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static async fromPngBytes(
|
||||||
|
bytes: number[] | Uint8Array | ArrayBuffer
|
||||||
|
): Promise<Image> {
|
||||||
|
return invoke<number>('plugin:image|from_png_bytes', {
|
||||||
|
bytes: transformImage(bytes)
|
||||||
|
}).then((rid) => new Image(rid))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new image using the provided ico bytes.
|
||||||
|
*
|
||||||
|
* Note that you need the `image-ico` Cargo features to use this API.
|
||||||
|
* To enable it, change your Cargo.toml file:
|
||||||
|
* ```toml
|
||||||
|
* [dependencies]
|
||||||
|
* tauri = { version = "...", features = ["...", "image-ico"] }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static async fromIcoBytes(
|
||||||
|
bytes: number[] | Uint8Array | ArrayBuffer
|
||||||
|
): Promise<Image> {
|
||||||
|
return invoke<number>('plugin:image|from_ico_bytes', {
|
||||||
|
bytes: transformImage(bytes)
|
||||||
|
}).then((rid) => new Image(rid))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new image using the provided path.
|
||||||
|
*
|
||||||
|
* Only `ico` and `png` are supported (based on activated feature flag).
|
||||||
|
*
|
||||||
|
* Note that you need the `image-ico` or `image-png` Cargo features to use this API.
|
||||||
|
* To enable it, change your Cargo.toml file:
|
||||||
|
* ```toml
|
||||||
|
* [dependencies]
|
||||||
|
* tauri = { version = "...", features = ["...", "image-png"] }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
static async fromPath(path: string): Promise<Image> {
|
||||||
|
return invoke<number>('plugin:image|from_path', { path }).then(
|
||||||
|
(rid) => new Image(rid)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the RGBA data for this image, in row-major order from top to bottom. */
|
||||||
|
async rgba(): Promise<ArrayBuffer | number[]> {
|
||||||
|
return invoke<ArrayBuffer | number[]>('plugin:image|rgba', {
|
||||||
|
rid: this.rid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the width of this image. */
|
||||||
|
async width() {
|
||||||
|
return invoke<number>('plugin:image|width', { rid: this.rid })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the height of this image. */
|
||||||
|
async height() {
|
||||||
|
return invoke<number>('plugin:image|height', { rid: this.rid })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transforms image from various types into a type acceptable by Rust. Intended for internal use only.
|
||||||
|
*
|
||||||
|
* @ignore
|
||||||
|
*/
|
||||||
|
export function transformImage<T>(
|
||||||
|
image: string | Image | Uint8Array | ArrayBuffer | number[] | null
|
||||||
|
): T {
|
||||||
|
const ret =
|
||||||
|
image == null
|
||||||
|
? null
|
||||||
|
: typeof image === 'string'
|
||||||
|
? image
|
||||||
|
: image instanceof Uint8Array
|
||||||
|
? Array.from(image)
|
||||||
|
: image instanceof ArrayBuffer
|
||||||
|
? Array.from(new Uint8Array(image))
|
||||||
|
: image instanceof Image
|
||||||
|
? image.rid
|
||||||
|
: image
|
||||||
|
|
||||||
|
return ret as T
|
||||||
|
}
|
@ -23,6 +23,7 @@ import * as path from './path'
|
|||||||
import * as dpi from './dpi'
|
import * as dpi from './dpi'
|
||||||
import * as tray from './tray'
|
import * as tray from './tray'
|
||||||
import * as menu from './menu'
|
import * as menu from './menu'
|
||||||
|
import * as image from './image'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
app,
|
app,
|
||||||
@ -34,5 +35,6 @@ export {
|
|||||||
webview,
|
webview,
|
||||||
webviewWindow,
|
webviewWindow,
|
||||||
tray,
|
tray,
|
||||||
menu
|
menu,
|
||||||
|
image
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
import { Channel, invoke, Resource } from '../core'
|
import { Channel, invoke, Resource } from '../core'
|
||||||
|
import { transformImage } from '../image'
|
||||||
import { CheckMenuItemOptions } from './checkMenuItem'
|
import { CheckMenuItemOptions } from './checkMenuItem'
|
||||||
import { IconMenuItemOptions } from './iconMenuItem'
|
import { IconMenuItemOptions } from './iconMenuItem'
|
||||||
import { MenuItemOptions } from './menuItem'
|
import { MenuItemOptions } from './menuItem'
|
||||||
@ -76,6 +77,15 @@ export async function newMenu(
|
|||||||
if ('rid' in i) {
|
if ('rid' in i) {
|
||||||
return [i.rid, i.kind]
|
return [i.rid, i.kind]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ('item' in i && typeof i.item === 'object' && i.item.About?.icon) {
|
||||||
|
i.item.About.icon = transformImage(i.item.About.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('icon' in i && i.icon) {
|
||||||
|
i.icon = transformImage(i.icon)
|
||||||
|
}
|
||||||
|
|
||||||
return injectChannel(i)
|
return injectChannel(i)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
import { MenuItemBase, newMenu } from './base'
|
import { MenuItemBase, newMenu } from './base'
|
||||||
import { type MenuItemOptions } from '../menu'
|
import { type MenuItemOptions } from '../menu'
|
||||||
import { invoke } from '../core'
|
import { invoke } from '../core'
|
||||||
|
import { Image, transformImage } from '../image'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A native Icon to be used for the menu item
|
* A native Icon to be used for the menu item
|
||||||
@ -133,7 +134,7 @@ export interface IconMenuItemOptions extends MenuItemOptions {
|
|||||||
/**
|
/**
|
||||||
* Icon to be used for the new icon menu item.
|
* Icon to be used for the new icon menu item.
|
||||||
*/
|
*/
|
||||||
icon?: NativeIcon | string | Uint8Array
|
icon?: NativeIcon | string | Image | Uint8Array | ArrayBuffer | number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -189,7 +190,19 @@ export class IconMenuItem extends MenuItemBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Sets an icon for this icon menu item */
|
/** Sets an icon for this icon menu item */
|
||||||
async setIcon(icon: NativeIcon | string | Uint8Array | null): Promise<void> {
|
async setIcon(
|
||||||
return invoke('plugin:menu|set_icon', { rid: this.rid, icon })
|
icon:
|
||||||
|
| NativeIcon
|
||||||
|
| string
|
||||||
|
| Image
|
||||||
|
| Uint8Array
|
||||||
|
| ArrayBuffer
|
||||||
|
| number[]
|
||||||
|
| null
|
||||||
|
): Promise<void> {
|
||||||
|
return invoke('plugin:menu|set_icon', {
|
||||||
|
rid: this.rid,
|
||||||
|
icon: transformImage(icon)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import { MenuItemBase, newMenu } from './base'
|
import { MenuItemBase, newMenu } from './base'
|
||||||
import { invoke } from '../core'
|
import { invoke } from '../core'
|
||||||
|
import { Image } from '../image'
|
||||||
|
|
||||||
/** A metadata for the about predefined menu item. */
|
/** A metadata for the about predefined menu item. */
|
||||||
export interface AboutMetadata {
|
export interface AboutMetadata {
|
||||||
@ -76,7 +77,7 @@ export interface AboutMetadata {
|
|||||||
*
|
*
|
||||||
* - **Windows:** Unsupported.
|
* - **Windows:** Unsupported.
|
||||||
*/
|
*/
|
||||||
icon?: string | Uint8Array
|
icon?: string | Uint8Array | ArrayBuffer | number[] | Image
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Options for creating a new predefined menu item. */
|
/** Options for creating a new predefined menu item. */
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
import type { Menu, Submenu } from './menu'
|
import type { Menu, Submenu } from './menu'
|
||||||
import { Channel, invoke, Resource } from './core'
|
import { Channel, invoke, Resource } from './core'
|
||||||
|
import { Image, transformImage } from './image'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes a tray event emitted when a tray icon is clicked
|
* Describes a tray event emitted when a tray icon is clicked
|
||||||
@ -58,7 +59,7 @@ export interface TrayIconOptions {
|
|||||||
* tauri = { version = "...", features = ["...", "image-png"] }
|
* tauri = { version = "...", features = ["...", "image-png"] }
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
icon?: string | Uint8Array | number[]
|
icon?: string | Uint8Array | ArrayBuffer | number[] | Image
|
||||||
/** The tray icon tooltip */
|
/** The tray icon tooltip */
|
||||||
tooltip?: string
|
tooltip?: string
|
||||||
/**
|
/**
|
||||||
@ -132,10 +133,7 @@ export class TrayIcon extends Resource {
|
|||||||
options.menu = [options.menu.rid, options.menu.kind]
|
options.menu = [options.menu.rid, options.menu.kind]
|
||||||
}
|
}
|
||||||
if (options?.icon) {
|
if (options?.icon) {
|
||||||
options.icon =
|
options.icon = transformImage(options.icon)
|
||||||
typeof options.icon === 'string'
|
|
||||||
? options.icon
|
|
||||||
: Array.from(options.icon)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handler = new Channel<TrayIconEvent>()
|
const handler = new Channel<TrayIconEvent>()
|
||||||
@ -150,11 +148,22 @@ export class TrayIcon extends Resource {
|
|||||||
}).then(([rid, id]) => new TrayIcon(rid, id))
|
}).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> {
|
* Sets a new tray icon. If `null` is provided, it will remove the icon.
|
||||||
|
*
|
||||||
|
* Note that you need the `image-ico` or `image-png` Cargo features to use this API.
|
||||||
|
* To enable it, change your Cargo.toml file:
|
||||||
|
* ```toml
|
||||||
|
* [dependencies]
|
||||||
|
* tauri = { version = "...", features = ["...", "image-png"] }
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
async setIcon(
|
||||||
|
icon: string | Image | Uint8Array | ArrayBuffer | number[] | null
|
||||||
|
): Promise<void> {
|
||||||
let trayIcon = null
|
let trayIcon = null
|
||||||
if (icon) {
|
if (icon) {
|
||||||
trayIcon = typeof icon === 'string' ? icon : Array.from(icon)
|
trayIcon = transformImage(icon)
|
||||||
}
|
}
|
||||||
return invoke('plugin:tray|set_icon', { rid: this.rid, icon: trayIcon })
|
return invoke('plugin:tray|set_icon', { rid: this.rid, icon: trayIcon })
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ import {
|
|||||||
import { invoke } from './core'
|
import { invoke } from './core'
|
||||||
import { WebviewWindow } from './webviewWindow'
|
import { WebviewWindow } from './webviewWindow'
|
||||||
import type { FileDropEvent, FileDropPayload } from './webview'
|
import type { FileDropEvent, FileDropPayload } from './webview'
|
||||||
|
import { Image, transformImage } from './image'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows you to retrieve information about a given monitor.
|
* Allows you to retrieve information about a given monitor.
|
||||||
@ -1393,10 +1394,12 @@ class Window {
|
|||||||
* @param icon Icon bytes or path to the icon file.
|
* @param icon Icon bytes or path to the icon file.
|
||||||
* @returns A promise indicating the success or failure of the operation.
|
* @returns A promise indicating the success or failure of the operation.
|
||||||
*/
|
*/
|
||||||
async setIcon(icon: string | Uint8Array): Promise<void> {
|
async setIcon(
|
||||||
|
icon: string | Image | Uint8Array | ArrayBuffer | number[]
|
||||||
|
): Promise<void> {
|
||||||
return invoke('plugin:window|set_icon', {
|
return invoke('plugin:window|set_icon', {
|
||||||
label: this.label,
|
label: this.label,
|
||||||
value: typeof icon === 'string' ? icon : Array.from(icon)
|
value: transformImage(icon)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
"webview:default",
|
"webview:default",
|
||||||
"app:default",
|
"app:default",
|
||||||
"resources:default",
|
"resources:default",
|
||||||
|
"image:default",
|
||||||
"menu:default",
|
"menu:default",
|
||||||
"tray:default"
|
"tray:default"
|
||||||
]
|
]
|
||||||
|
Loading…
Reference in New Issue
Block a user