mirror of
https://github.com/tauri-apps/tauri.git
synced 2024-12-25 03:33:36 +03:00
feat(build): support plugins that are defined in app crate (#8781)
* feat(build): support plugins that are defined in app crate * dx
This commit is contained in:
parent
052e8b4311
commit
edb11c138d
5
.changes/inline-plugins.md
Normal file
5
.changes/inline-plugins.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'tauri-build': patch:enhance
|
||||
---
|
||||
|
||||
Added `Attributes::plugin()` to register a plugin that is inlined in the application crate.
|
@ -24,6 +24,7 @@ use tauri_utils::{
|
||||
};
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
env::var_os,
|
||||
fs::copy,
|
||||
path::{Path, PathBuf},
|
||||
@ -331,6 +332,41 @@ impl WindowsAttributes {
|
||||
}
|
||||
}
|
||||
|
||||
/// Definition of a plugin that is part of the Tauri application instead of having its own crate.
|
||||
///
|
||||
/// By default it generates a plugin manifest that parses permissions from the `permissions/$plugin-name` directory.
|
||||
/// To change the glob pattern that is used to find permissions, use [`Self::permissions_path_pattern`].
|
||||
///
|
||||
/// To autogenerate permissions for each of the plugin commands, see [`Self::commands`].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct InlinedPlugin {
|
||||
commands: &'static [&'static str],
|
||||
permissions_path_pattern: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl InlinedPlugin {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Define a list of commands that gets permissions autogenerated in the format of `allow-$command` and `deny-$command`
|
||||
/// where $command is the command in kebab-case.
|
||||
pub fn commands(mut self, commands: &'static [&'static str]) -> Self {
|
||||
self.commands = commands;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a glob pattern that is used to find the permissions of this inlined plugin.
|
||||
///
|
||||
/// **Note:** You must emit [rerun-if-changed] instructions for the plugin permissions directory.
|
||||
///
|
||||
/// By default it is `./permissions/$plugin-name/**/*`
|
||||
pub fn permissions_path_pattern(mut self, pattern: &'static str) -> Self {
|
||||
self.permissions_path_pattern.replace(pattern);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The attributes used on the build.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Attributes {
|
||||
@ -339,6 +375,7 @@ pub struct Attributes {
|
||||
capabilities_path_pattern: Option<&'static str>,
|
||||
#[cfg(feature = "codegen")]
|
||||
codegen: Option<codegen::context::CodegenContext>,
|
||||
inlined_plugins: HashMap<&'static str, InlinedPlugin>,
|
||||
}
|
||||
|
||||
impl Attributes {
|
||||
@ -365,6 +402,14 @@ impl Attributes {
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds the given plugin to the list of inlined plugins (a plugin that is part of your application).
|
||||
///
|
||||
/// See [`InlinedPlugin`] for more information.
|
||||
pub fn plugin(mut self, name: &'static str, plugin: InlinedPlugin) -> Self {
|
||||
self.inlined_plugins.insert(name, plugin);
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(feature = "codegen")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "codegen")))]
|
||||
#[must_use]
|
||||
@ -473,7 +518,51 @@ pub fn try_build(attributes: Attributes) -> Result<()> {
|
||||
let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
||||
manifest::check(&config, &mut manifest)?;
|
||||
let plugin_manifests = acl::get_plugin_manifests()?;
|
||||
let mut plugin_manifests = acl::get_plugin_manifests()?;
|
||||
for (name, plugin) in attributes.inlined_plugins {
|
||||
let plugin_out_dir = out_dir.join("plugins").join(name);
|
||||
|
||||
let mut permission_files = if plugin.commands.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
tauri_utils::acl::build::autogenerate_command_permissions(
|
||||
&plugin_out_dir,
|
||||
plugin.commands,
|
||||
"",
|
||||
);
|
||||
tauri_utils::acl::build::define_permissions(
|
||||
&plugin_out_dir.join("*").to_string_lossy(),
|
||||
name,
|
||||
&plugin_out_dir,
|
||||
)?
|
||||
};
|
||||
|
||||
if let Some(pattern) = plugin.permissions_path_pattern {
|
||||
permission_files.extend(tauri_utils::acl::build::define_permissions(
|
||||
pattern,
|
||||
name,
|
||||
&plugin_out_dir,
|
||||
)?);
|
||||
} else {
|
||||
let default_permissions_path = Path::new("permissions").join(name);
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
default_permissions_path.display()
|
||||
);
|
||||
permission_files.extend(tauri_utils::acl::build::define_permissions(
|
||||
&default_permissions_path
|
||||
.join("**")
|
||||
.join("*")
|
||||
.to_string_lossy(),
|
||||
name,
|
||||
&plugin_out_dir,
|
||||
)?);
|
||||
}
|
||||
|
||||
let manifest = tauri_utils::acl::plugin::Manifest::new(permission_files, None);
|
||||
plugin_manifests.insert(name.into(), manifest);
|
||||
}
|
||||
|
||||
std::fs::write(
|
||||
out_dir.join(PLUGIN_MANIFESTS_FILE_NAME),
|
||||
serde_json::to_string(&plugin_manifests)?,
|
||||
|
@ -8,6 +8,9 @@ fn main() {
|
||||
codegen = codegen.dev();
|
||||
}
|
||||
|
||||
tauri_build::try_build(tauri_build::Attributes::new().codegen(codegen))
|
||||
.expect("failed to run tauri-build");
|
||||
tauri_build::try_build(tauri_build::Attributes::new().codegen(codegen).plugin(
|
||||
"app-menu",
|
||||
tauri_build::InlinedPlugin::new().commands(&["toggle", "popup"]),
|
||||
))
|
||||
.expect("failed to run tauri-build");
|
||||
}
|
||||
|
@ -4,6 +4,7 @@
|
||||
"description": "permissions to run the app",
|
||||
"windows": ["main", "main-*"],
|
||||
"permissions": [
|
||||
"app-menu:default",
|
||||
"sample:allow-ping-scoped",
|
||||
"sample:global-scope",
|
||||
"path:default",
|
||||
|
3
examples/api/src-tauri/permissions/app-menu/default.toml
Normal file
3
examples/api/src-tauri/permissions/app-menu/default.toml
Normal file
@ -0,0 +1,3 @@
|
||||
[default]
|
||||
description = "Default permissions for the plugin"
|
||||
permissions = ["allow-toggle", "allow-popup"]
|
@ -29,37 +29,3 @@ pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
|
||||
message: "message response".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(desktop, not(target_os = "macos")))]
|
||||
#[command]
|
||||
pub fn toggle_menu<R: tauri::Runtime>(window: tauri::Window<R>) {
|
||||
if window.is_menu_visible().unwrap_or_default() {
|
||||
let _ = window.hide_menu();
|
||||
} else {
|
||||
let _ = window.show_menu();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[command]
|
||||
pub fn toggle_menu<R: tauri::Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
app_menu: tauri::State<'_, crate::AppMenu<R>>,
|
||||
) {
|
||||
if let Some(menu) = app.remove_menu().unwrap() {
|
||||
app_menu.0.lock().unwrap().replace(menu);
|
||||
} else {
|
||||
app
|
||||
.set_menu(app_menu.0.lock().unwrap().clone().expect("no app menu"))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(desktop)]
|
||||
#[command]
|
||||
pub fn popup_context_menu<R: tauri::Runtime>(
|
||||
window: tauri::Window<R>,
|
||||
popup_menu: tauri::State<'_, crate::PopupMenu<R>>,
|
||||
) {
|
||||
window.popup_menu(&popup_menu.0).unwrap();
|
||||
}
|
||||
|
@ -4,6 +4,8 @@
|
||||
|
||||
mod cmd;
|
||||
#[cfg(desktop)]
|
||||
mod menu_plugin;
|
||||
#[cfg(desktop)]
|
||||
mod tray;
|
||||
|
||||
use serde::Serialize;
|
||||
@ -46,6 +48,7 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
let handle = app.handle();
|
||||
tray::create_tray(handle)?;
|
||||
handle.plugin(tauri_plugin_cli::init())?;
|
||||
handle.plugin(menu_plugin::init())?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@ -140,10 +143,6 @@ pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::log_operation,
|
||||
cmd::perform_request,
|
||||
#[cfg(desktop)]
|
||||
cmd::toggle_menu,
|
||||
#[cfg(desktop)]
|
||||
cmd::popup_context_menu
|
||||
])
|
||||
.build(tauri::tauri_build_context!())
|
||||
.expect("error while building tauri application");
|
||||
|
48
examples/api/src-tauri/src/menu_plugin.rs
Normal file
48
examples/api/src-tauri/src/menu_plugin.rs
Normal file
@ -0,0 +1,48 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use tauri::{
|
||||
command,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Runtime,
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
#[command]
|
||||
pub fn toggle<R: tauri::Runtime>(window: tauri::Window<R>) {
|
||||
if window.is_menu_visible().unwrap_or_default() {
|
||||
let _ = window.hide_menu();
|
||||
} else {
|
||||
let _ = window.show_menu();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[command]
|
||||
pub fn toggle<R: tauri::Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
app_menu: tauri::State<'_, crate::AppMenu<R>>,
|
||||
) {
|
||||
if let Some(menu) = app.remove_menu().unwrap() {
|
||||
app_menu.0.lock().unwrap().replace(menu);
|
||||
} else {
|
||||
app
|
||||
.set_menu(app_menu.0.lock().unwrap().clone().expect("no app menu"))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn popup<R: tauri::Runtime>(
|
||||
window: tauri::Window<R>,
|
||||
popup_menu: tauri::State<'_, crate::PopupMenu<R>>,
|
||||
) {
|
||||
window.popup_menu(&popup_menu.0).unwrap();
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("app-menu")
|
||||
.invoke_handler(tauri::generate_handler![popup, toggle])
|
||||
.build()
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
<script>
|
||||
import { onMount, tick } from "svelte";
|
||||
import { onMount, tick } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.ctrlKey && event.key === 'b') {
|
||||
invoke('toggle_menu')
|
||||
invoke('plugin:app-menu|toggle')
|
||||
}
|
||||
})
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
|
||||
// Console
|
||||
let messages = writable([])
|
||||
let consoleTextEl;
|
||||
let consoleTextEl
|
||||
async function onMessage(value) {
|
||||
messages.update((r) => [
|
||||
...r,
|
||||
@ -90,10 +90,10 @@
|
||||
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
|
||||
(typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
|
||||
'</pre>'
|
||||
},
|
||||
}
|
||||
])
|
||||
await tick();
|
||||
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight;
|
||||
await tick()
|
||||
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
|
||||
}
|
||||
|
||||
// this function is renders HTML without sanitizing it so it's insecure
|
||||
@ -106,10 +106,10 @@
|
||||
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
|
||||
html +
|
||||
'</pre>'
|
||||
},
|
||||
}
|
||||
])
|
||||
await tick();
|
||||
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight;
|
||||
await tick()
|
||||
if (consoleTextEl) consoleTextEl.scrollTop = consoleTextEl.scrollHeight
|
||||
}
|
||||
|
||||
function clear() {
|
||||
@ -329,13 +329,16 @@
|
||||
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
|
||||
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
|
||||
"
|
||||
on:keypress={(e) => e.key === "Enter"? clear() : {} }
|
||||
on:keypress={(e) => (e.key === 'Enter' ? clear() : {})}
|
||||
on:click={clear}
|
||||
>
|
||||
<div class="i-codicon-clear-all" />
|
||||
</div>
|
||||
</div>
|
||||
<div bind:this={consoleTextEl} class="px-2 overflow-y-auto all:font-mono code-block all:text-xs select-text mr-2">
|
||||
<div
|
||||
bind:this={consoleTextEl}
|
||||
class="px-2 overflow-y-auto all:font-mono code-block all:text-xs select-text mr-2"
|
||||
>
|
||||
{#each $messages as r}
|
||||
{@html r.html}
|
||||
{/each}
|
||||
|
@ -17,7 +17,7 @@
|
||||
})
|
||||
|
||||
function contextMenu() {
|
||||
invoke('popup_context_menu')
|
||||
invoke('plugin:app-menu|popup')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user