From 2d970c97d0908bd1a4d02c9ef29489cbc00443d9 Mon Sep 17 00:00:00 2001 From: Lucas Fernandes Nogueira Date: Thu, 7 Sep 2023 13:05:12 -0300 Subject: [PATCH] feat: add documentation for plugins (#1412) * feat: add documentation for plugins * update prettierignore Signed-off-by: Lorenzo Lewis * update contributing guide Signed-off-by: Lorenzo Lewis * add notification guide stub Signed-off-by: Lorenzo Lewis * update guide Signed-off-by: Lorenzo Lewis * revise lifecycle events Signed-off-by: Lorenzo Lewis * some more updates and revisions Signed-off-by: Lorenzo Lewis * Formatting Signed-off-by: Lorenzo Lewis * resolve some todos * Update src/content/docs/2/guide/plugins/index.mdx * Update src/content/docs/2/guide/plugins/index.mdx * format * The big refactor * Fix broken links, add todo * rework sidebar * typos and minor fixes * fix link * Apply suggestions from code review * why?? * fill "develop an iOS plugin" section * Apply suggestions from code review * enable i18n * fix typo --------- Signed-off-by: Lorenzo Lewis Co-authored-by: Lorenzo Lewis --- .github/CONTRIBUTING.md | 14 +- .prettierignore | 1 + astro.config.mjs | 6 +- src/content/docs/2/guide/commands.mdx | 7 + .../docs/2/guide/plugins/develop-mobile.mdx | 385 ++++++++++++++++++ .../docs/2/guide/plugins/develop-plugin.mdx | 71 ---- src/content/docs/2/guide/plugins/index.mdx | 304 +++++++++++++- src/content/docs/2/guide/state-management.mdx | 28 ++ src/content/docs/2/reference/cli.mdx | 2 +- 9 files changed, 736 insertions(+), 82 deletions(-) create mode 100644 src/content/docs/2/guide/commands.mdx create mode 100644 src/content/docs/2/guide/plugins/develop-mobile.mdx delete mode 100644 src/content/docs/2/guide/plugins/develop-plugin.mdx create mode 100644 src/content/docs/2/guide/state-management.mdx diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 3ae8ddb058..576674dc06 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -52,7 +52,17 @@ While Tauri 2.0 is still in the prerelease stage people follow these guidelines ### Writing Style -Any ideas? Put them here! +**Dictionary** + +| Word | Description | +| -------- | ------------------------------------ | +| app | A Tauri app, prefer over application | +| web view | Where the UI is rendered | + +- Use an [oxford comma](https://www.grammarly.com/blog/what-is-the-oxford-comma-and-why-do-people-care-so-much-about-it/) in paragraphs, but not in headings and titles +- Use [title case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case) for headings and titles +- Make headings as succinct as possible to help the reader quickly find the content they need +- Use [simple present tense](https://www.grammarly.com/blog/simple-present/) for verbs ### Guide @@ -90,4 +100,4 @@ Topics that are around understanding something can be written as a blog post (we Thanks for your interest in helping to translate the documentation! Visit the [translation status page](https://beta.tauri.app/contribute/translate-status) to see which docs are ready for translation, need updated, or need reviewed. -Read the [Translating Guide](./TRANSLATING.md) for more information. \ No newline at end of file +Read the [Translating Guide](./TRANSLATING.md) for more information. diff --git a/.prettierignore b/.prettierignore index dc04641325..767bd7397e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -14,3 +14,4 @@ pnpm-lock.yaml # Configs .github +!.github/**.md diff --git a/astro.config.mjs b/astro.config.mjs index 986386c007..347ebafd0e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -115,7 +115,7 @@ export default defineConfig({ ], }, { - label: 'Workflow', + label: 'Guides', items: [ { label: 'Develop', @@ -137,6 +137,10 @@ export default defineConfig({ label: 'Distribute', link: '2/guide/distribute', }, + { + label: 'Plugin Development', + link: '2/guide/plugins', + }, ], }, { diff --git a/src/content/docs/2/guide/commands.mdx b/src/content/docs/2/guide/commands.mdx new file mode 100644 index 0000000000..9e9ce1fc45 --- /dev/null +++ b/src/content/docs/2/guide/commands.mdx @@ -0,0 +1,7 @@ +--- +title: Commands +--- + +import Stub from '@components/Stub.astro'; + + diff --git a/src/content/docs/2/guide/plugins/develop-mobile.mdx b/src/content/docs/2/guide/plugins/develop-mobile.mdx new file mode 100644 index 0000000000..0d6f204eaa --- /dev/null +++ b/src/content/docs/2/guide/plugins/develop-mobile.mdx @@ -0,0 +1,385 @@ +--- +title: Mobile Plugin Development +i18nReady: true +--- + +:::tip[Plugin Development] + +Be sure that you're familiar with the concepts covered in the [Plugin Development guide](/2/guide/plugins) as many concepts in this guide build on top of foundations covered there. + +::: + +Plugins can run native mobile code written in Kotlin (or Java) and Swift. The default plugin template includes an Android library project using Kotlin and a Swift package including an example mobile command showing how to trigger its execution from Rust code. + +## Initialize Plugin Project + +Follow the steps in the [Plugin Development guide](/2/guide/plugins#initialize-plugin-project) to initialize a new plugin project. + +If you have an existing plugin and would like to add Android or iOS capabilities to it, you can use `plugin android add` and `plugin ios add` to bootstrap the mobile library projects and guide you through the changes needed. + +The default plugin template splits the plugin's implementation into two separate modules: `desktop.rs` and `mobile.rs`. + +The desktop implementation uses Rust code to implement a functionality, while the mobile implementation sends a message to the native mobile code to execute a function and get a result back. If shared logic is needed across both implementations, it can be defined in `lib.rs`: + +```rust +// lib.rs +use tauri::Runtime; + +impl { + pub fn do_something(&self) { + // do something that is a shared implementation between desktop and mobile + } +} +``` + +This implementation simplifies the process of sharing an API that can be used both by commands and Rust code. + +### Develop an Android Plugin + +A Tauri plugin for Android is defined as a Kotlin class that extends `app.tauri.plugin.Plugin` and is annoted with `app.tauri.annotation.TauriPlugin`. Each method annotated with `app.tauri.annotation.Command` can be called by Rust or JavaScript. + +Tauri uses Kotlin by default for the Android plugin implementation, but you can switch to Java if you prefer. After generating a plugin, right click the Kotlin plugin class in Android Studio and select the "Convert Kotlin file to Java file" option from the menu. Android Studio will guide you through the project migration to Java. + +### Develop an iOS Plugin + +A Tauri plugin for iOS is defined as a Swift class that extends the `Plugin` class from the `Tauri` package. Each function with the `@objc` attribute and the `(_ invoke: Invoke)` parameter (for example `@objc private func download(_ invoke: Invoke) { }`) can be called by Rust or JavaScript. + +The plugin is defined as a [Swift package](https://www.swift.org/package-manager/) so that you can use its package manager to manage dependencies. + +## Plugin Configuration + +Refer to the [Plugin Configuration section](/2/guide/plugins/#plugin-configuration) of the Plugin Development guide for more details on developing plugin configurations. + +The plugin instance on mobile has a getter for the plugin configuration: + + + + +```java +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + override fun load(webView: WebView) { + val timeout = this.config.getInt("timeout", 30) + } +} +``` + + + + +```swift +class ExamplePlugin: Plugin { + @objc public override func load(webview: WKWebView) { + let timeout = self.config["timeout"] as? Int ?? 30 + } +} +``` + + + + +## Lifecycle Events + +Plugins can hook into several lifecycle events: + +- [load](#load): When the plugin is loaded into the web view +- [onNewIntent](#onnewintent): Android only, when the activity is re-launched + +There are also the additional [lifecycle events for plugins](/2/guide/plugins#lifecycle-events) in the Plugin Development guide. + +### load + +- **When**: When the plugin is loaded into the web view +- **Why**: Execute plugin initialization code + + + + + +```java +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + override fun load(webView: WebView) { + // perform plugin setup here + } +} +``` + + + + +```swift +class ExamplePlugin: Plugin { + @objc public override func load(webview: WKWebView) { + let timeout = self.config["timeout"] as? Int ?? 30 + } +} +``` + + + + +### onNewIntent + +**Note**: This is only available on Android. + +- **When**: When the activity is re-launched. See [Activity#onNewIntent]() for more information. +- **Why**: Handle application re-launch such as when a notification is clicked or a deep link is accessed. + + +```java +// import android.content.Intent + +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + override fun onNewIntent(intent: Intent) { + // handle new intent event + } +} +``` + +## Adding Mobile Commands + +There is a plugin class inside the respective mobile projects where commands can be defined that can be called by the Rust code: + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + + + + +```java +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + @Command + fun openCamera(invoke: Invoke) { + val allowEdit = invoke.getBoolean("allowEdit", false) + val quality = invoke.getInt("quality", 100) + + val ret = JSObject() + ret.put("path", "/path/to/photo.jpg") + invoke.resolve(ret) + } +} +``` + + + + +```swift +class ExamplePlugin: Plugin { + @objc public func openCamera(_ invoke: Invoke) { + let allowEdit = invoke.getBool("allowEdit", false) + let quality = invoke.getInt("quality", 100) + + invoke.resolve(["path": "/path/to/photo.jpg"]) + } +} +``` + + + + +Use the [`tauri::plugin::PluginHandle`](https://docs.rs/tauri/2.0.0-alpha/tauri/plugin/struct.PluginHandle.html) to call a mobile command from Rust: +```rust +use std::path::PathBuf; +use serde::{Deserialize, Serialize}; +use tauri::Runtime; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CameraRequest { + quality: usize, + allow_edit: bool, +} + +#[derive(Deserialize)] +pub struct Photo { + path: PathBuf, +} + + +impl { + pub fn open_camera(&self, payload: CameraRequest) -> crate::Result { + self + .0 + .run_mobile_plugin("openCamera", payload) + .map_err(Into::into) + } +} +``` + +## Permissions + +If a plugin requires permissions from the end user, Tauri simplifies the process of checking and requesting permissions. + + + + +First define the list of permissions needed and an alias to identify each group in code. This is done inside the `TauriPlugin` annotation: + +```java +@TauriPlugin( + permissions = [ + Permission(strings = [Manifest.permission.POST_NOTIFICATIONS], alias = "postNotification") + ] +) +class ExamplePlugin(private val activity: Activity): Plugin(activity) { } +``` + + + + +First override the `checkPermissions` and `requestPermissions` functions: + +```swift +class ExamplePlugin: Plugin { + @objc open func checkPermissions(_ invoke: Invoke) { + invoke.resolve(["postNotification": "prompt"]) + } + + @objc public override func requestPermissions(_ invoke: Invoke) { + // request permissions here + // then resolve the request + invoke.resolve(["postNotification": "granted"]) + } +} +``` + + + + +Tauri automatically implements two commands for the plugin: `checkPermissions` and `requestPermissions`. Those commands can be directly called from JavaScript or Rust: + +{/* TODO: PermissionState type should be exported in Tauri */} + + + + +```javascript +import { invoke } from '@tauri-apps/api/tauri' + +type PermissionState = 'granted' | 'denied' | 'prompt' | 'prompt-with-rationale' + +interface Permissions { + postNotification: PermissionState +} + +// check permission state +const permission = await invoke('plugin:|checkPermissions') + +if (permission.postNotification === 'prompt-with-rationale') { + // show information to the user about why permission is needed +} + +// request permission +if (permission.postNotification.startsWith('prompt')) { + const state = await invoke('plugin:|requestPermissions', { permissions: ['postNotification'] }) +} +``` + + + + +```rust +use serde::{Serialize, Deserialize}; +use tauri::Runtime; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct PermissionResponse { + pub post_notification: PermissionState, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct RequestPermission { + post_notification: bool, +} + +impl Notification { + pub fn request_post_notification_permission(&self) -> crate::Result { + self.0 + .run_mobile_plugin::("requestPermissions", RequestPermission { post_notification: true }) + .map(|r| r.post_notification) + .map_err(Into::into) + } + + pub fn check_permissions(&self) -> crate::Result { + self.0 + .run_mobile_plugin::("checkPermissions", ()) + .map_err(Into::into) + } +} +``` + + + + +## Plugin Events + +{/* TODO: Is this section a duplicate of Lifecycle Events above? */} + +Plugins can emit events at any point of time using the `trigger` function: + + + + +```java +@TauriPlugin +class ExamplePlugin(private val activity: Activity): Plugin(activity) { + override fun load(webView: WebView) { + trigger("load", JSObject()) + } + + override fun onNewIntent(intent: Intent) { + // handle new intent event + if (intent.action == Intent.ACTION_VIEW) { + val data = intent.data.toString() + val event = JSObject() + event.put("data", data) + trigger("newIntent", event) + } + } + + @Command + fun openCamera(invoke: Invoke) { + val payload = JSObject() + payload.put("open", true) + trigger("camera", payload) + } +} +``` + + + + +```swift +class ExamplePlugin: Plugin { + @objc public override func load(webview: WKWebView) { + trigger("load", data: [:]) + } + + @objc public func openCamera(_ invoke: Invoke) { + trigger("camera", data: ["open": true]) + } +} +``` + + + + +The helper functionss can then be called from the NPM package by using the [`addPluginListener`](/2/reference/js/core/namespacetauri/#addpluginlistener) helper function: + +```javascript +import { addPluginListener, PluginListener } from '@tauri-apps/api/tauri'; + +export async function onRequest( + handler: (url: string) => void +): Promise { + return await addPluginListener( + '', + 'event-name', + handler + ); +} +``` diff --git a/src/content/docs/2/guide/plugins/develop-plugin.mdx b/src/content/docs/2/guide/plugins/develop-plugin.mdx deleted file mode 100644 index 2bf32f5230..0000000000 --- a/src/content/docs/2/guide/plugins/develop-plugin.mdx +++ /dev/null @@ -1,71 +0,0 @@ ---- -title: Develop a Tauri Plugin ---- - -import Stub from '@components/Stub.astro'; - - - -## `plugin init` - -```sh -cargo tauri plugin init -``` - -``` -Initializes a Tauri plugin project - -Usage: cargo tauri plugin init [OPTIONS] --name - -Options: - -n, --name Name of your Tauri plugin - -v, --verbose... Enables verbose logging - --no-api Initializes a Tauri plugin without the TypeScript API - -d, --directory Set target directory for init [default: C:\Users\Fabian-Lars] - -t, --tauri-path Path of the Tauri project to use (relative to the cwd) - -a, --author Author name - -h, --help Print help - -V, --version Print version -``` - -## `plugin android` - -```sh -cargo tauri plugin android -``` - -``` -Manage the Android project for Tauri plugins - -Usage: cargo tauri plugin android [OPTIONS] - -Commands: - add Adds the Android project to an existing Tauri plugin - help Print this message or the help of the given subcommand(s) - -Options: - -v, --verbose... Enables verbose logging - -h, --help Print help - -V, --version Print version -``` - -## `plugin ios` - -```sh -cargo tauri plugin ios -``` - -``` -Manage the iOS project for Tauri plugins - -Usage: cargo tauri plugin ios [OPTIONS] - -Commands: - add Adds the iOS project to an existing Tauri plugin - help Print this message or the help of the given subcommand(s) - -Options: - -v, --verbose... Enables verbose logging - -h, --help Print help - -V, --version Print version -``` diff --git a/src/content/docs/2/guide/plugins/index.mdx b/src/content/docs/2/guide/plugins/index.mdx index b6ed5e39e8..b2503fd420 100644 --- a/src/content/docs/2/guide/plugins/index.mdx +++ b/src/content/docs/2/guide/plugins/index.mdx @@ -1,13 +1,303 @@ --- -title: Plugins +title: Plugin Development +i18nReady: true --- -import Stub from '@components/Stub.astro'; +{/* TODO: Add a CLI section */} - +import CommandTabs from '@components/CommandTabs.astro'; -- What is a Plugin? -- [Developing a Plugin](/2/guide/plugins/develop-plugin) -- Guide/Recipe for each plugin in https://github.com/tauri-apps/plugins-workspace/tree/v2 +{/* TODO: Link to windowing system, commands for sending messages, and event system */} - +:::tip[Plugin Development] + +This guide is for developing Tauri plugins. If you're looking for a list of the currently available plugins and how to use them then visit the [Features and Recipes list](/2/guide/list). + +::: + +Plugins are able to hook into the Tauri lifecycle, expose Rust code that relies on the web view APIs, handle commands with Rust, Kotlin or Swift code, and much more. + +Tauri offers a windowing system with web view functionality, a way to send messages between the Rust process and the web view, and an event system along with several tools to enhance the development experience. By design, the Tauri core does not contain features not needed by everyone. Instead it offers a mechanism to add external functionalities into a Tauri application called plugins. + +A Tauri plugin is composed of a Cargo crate and an optional NPM package that provides API bindings for its commands and events. Additionally, a plugin project can include an Android library project and a Swift package for iOS. You can learn more about developing plugins for Android and iOS in the [Mobile Plugin Development guide](/2/guide/plugins/develop-mobile). + +{/* TODO: https://github.com/tauri-apps/tauri/issues/7749 */} + +## Naming Convention + +{/* TODO: Add link to allowlist */} + +Tauri plugins have a prefix (`tauri-plugin-` prefix for the Rust crate name and `@tauri-apps/plugin-` for the NPM package) followed by the plugin name. The plugin name is specified on the plugin configuration under [`tauri.conf.json > plugin`](/2/reference/config/#pluginconfig) and on the allowlist configuration. + +By default Tauri prefixes your plugin crate with `tauri-plugin-`. This helps your plugin to be discovered by the Tauri community, but is not not a requirement. When initializing a new plugin project, you must provide its name. The generated crate name will be `tauri-plugin-{plugin-name}` and the JavaScript NPM package name will be `tauri-plugin-{plugin-name}-api` (although we recommend using an [NPM scope](https://docs.npmjs.com/about-scopes) if possible). The Tauri naming convention for NPM packages is `@scope-name/plugin-{plugin-name}`. + +## Initialize Plugin Project + +To bootstrap a new plugin project, run `plugin init`. If you do not need the NPM package, use the `--no-api` CLI flag. + + + +This will initialize the plugin and the resulting code will look like this: + +``` +. plugin-name/ +├── src/ - Rust code +│ ├── commands.rs - defines the commands the webview can use +| ├── desktop.rs - desktop implementation +│ ├── lib.rs - re-exports appropriate implementation, setup state... +│ └── mobile.rs - mobile implementation +├── android - Android library +├── ios - Swift package +├── webview-src - source code of the JavaScript API bindings +├── webview-dist - Transpiled assets from webview-src +├── Cargo.toml - Cargo crate metadata +└── package.json - NPM package metadata +``` + +{/* TODO: https://github.com/tauri-apps/tauri/issues/7749 */} + +If you have an existing plugin and would like to add Android or iOS capabilities to it, you can use `plugin android add` and `plugin ios add` to bootstrap the mobile library projects and guide you through the changes needed. + +## Mobile Plugin Development + +Plugins can run native mobile code written in Kotlin (or Java) and Swift. The default plugin template includes an Android library project using Kotlin and a Swift package. It includes an example mobile command showing how to trigger its execution from Rust code. + +Read more about developing plugins for mobile in the [Mobile Plugin Development guide](/2/guide/plugins/develop-mobile). + +## Plugin Configuration + +In the Tauri application where the plugin is used, the plugin configuration is specified on `tauri.conf.json` where `plugin-name` is the name of the plugin: + +```json +{ + "build": { ... }, + "tauri": { ... }, + "plugins": { + "plugin-name": { + "timeout": 30 + } + } +} +``` + +The plugin's configuration is set on the `Builder` and is parsed at runtime. Here is an example of the `Config` struct being used to specify the plugin configuration: + +```rust +// lib.rs + +use tauri::plugin::{Builder, Runtime, TauriPlugin}; +use serde::Deserialize; + +// Define the plugin config +#[derive(Deserialize)] +struct Config { + timeout: usize, +} + +pub fn init() -> TauriPlugin { + // Make the plugin config optional + // by using `Builder::>` instead + Builder::::new("") + .setup(|app, api| { + let timeout = api.config.timeout; + Ok(()) + }) + .build() +} +``` + +## Lifecycle Events + +Plugins can hook into several lifecycle events: + +- [setup](#setup): Plugin is being initialized +- [on_navigation](#on_navigation): Web view is attempting to perform navigation +- [on_webview_ready](#on_webview_ready): New window is being created +- [on_event](#on_event): Event loop events +- [on_drop](#on_drop): Plugin is being deconstructed + +There are additional [lifecycle events for mobile plugins](/2/guide/plugins/develop-mobile#lifecycle-events). + +### setup + +- **When**: Plugin is being initialized +- **Why**: Register mobile plugins, manage state, run background tasks + +```rust +use tauri::{Manager, plugin::Builder}; +use std::{collections::HashMap, sync::Mutex, time::Duration}; + +struct DummyStore(Mutex>); + +Builder::new("") + .setup(|app, api| { + app.manage(DummyStore(Default::default())); + + let app_ = app.clone(); + std::thread::spawn(move || { + loop { + app_.emit("tick", ()); + std::thread::sleep(Duration::from_secs(1)); + } + }); + + Ok(()) + }) +``` + +### on_navigation + +- **When**: Web view is attempting to perform navigation +- **Why**: Validate the navigation or track URL changes + +Returning `false` cancels the navigation. + +```rust +use tauri::plugin::Builder; + +Builder::new("") + .on_navigation(|window, url| { + println!("window {} is navigating to {}", window.label(), url); + // Cancels the navigation if forbidden + url.scheme() != "forbidden" + }) +``` + +### on_webview_ready + +- **When**: New window has been created +- **Why**: Execute an initialization script for every window + +```rust +use tauri::plugin::Builder; + +Builder::new("") + .on_webview_ready(|window| { + window.listen("content-loaded", |event| { + println!("webview content has been loaded"); + }); + }) +``` + +### on_event + +- **When**: Event loop events +- **Why**: Handle core events such as window events, menu events and application exit requested + +With this lifecycle hook you can be notified of any event loop [events](https://docs.rs/tauri/2.0.0-alpha/tauri/enum.RunEvent.html). + +```rust +use std::{collections::HashMap, fs::write, sync::Mutex}; +use tauri::{plugin::Builder, Manager, RunEvent}; + +struct DummyStore(Mutex>); + +Builder::new("") + .setup(|app, _api| { + app.manage(DummyStore(Default::default())); + Ok(()) + }) + .on_event(|app, event| { + match event { + RunEvent::ExitRequested { api, .. } => { + // user requested a window to be closed and there's no windows left + + // we can prevent the app from exiting: + api.prevent_exit(); + } + RunEvent::Exit => { + // app is going to exit, you can cleanup here + + let store = app.state::(); + write( + app.path().app_local_data_dir().unwrap().join("store.json"), + serde_json::to_string(&*store.0.lock().unwrap()).unwrap(), + ) + .unwrap(); + } + _ => {} + } + }) +``` + +### on_drop + +- **When**: Plugin is being deconstructed +- **Why**: Execute code when the plugin has been destroyed + +See [`Drop`](https://doc.rust-lang.org/std/ops/trait.Drop.html) for more information. + +```rust +use tauri::plugin::Builder; + +Builder::new("") + .on_drop(|app| { + // plugin has been destroyed... + }) +``` + +## Exposing Rust APIs + +The plugin APIs defined in the project's `desktop.rs` and `mobile.rs` are exported to the user as a struct with the same name as the plugin (in pascal case). When the plugin is setup, an instance of this struct is created and managed as a state so that users can retrieve it at any point in time with a `Manager` instance (such as `AppHandle`, `App`, or` Window`) through the extension trait defined in the plugin. + +For example, the [`global-shortcut plugin`](/2/guide/global-shortcut) defines a `GlobalShortcut` struct that can be read by using the `global_shortcut` method of the `GlobalShortcutExt` trait: + +```rust +use tauri_plugin_global_shortcut::GlobalShortcutExt; + +tauri::Builder::default() + .plugin(tauri_plugin_global_shortcut::init()) + .setup(|app| { + app.global_shortcut().register(...); + Ok(()) + }) +``` + +## Adding Commands + + +Commands are defined in the `commands.rs` file. They are regular Tauri applications commands. They can access the AppHandle and Window instances directly, access state, and take input the same way as application commands. Read the [Commands guide](/2/guide/commands) for more details on Tauri commands. + +This command shows how to get access to the `AppHandle` and `Window` instance via dependency injection, and takes two input parameters (`on_progress` and `url`): + +```rust +use tauri::{command, ipc::Channel, AppHandle, Runtime, Window}; + +#[command] +async fn upload(app: AppHandle, window: Window, on_progress: Channel, url: String) { + // implement command logic here + on_progress.send(100).unwrap(); +} +``` + +To expose the command to the webview, you must hook into the `invoke_handler()` call in `lib.rs`: + +```rust +// lib.rs +Builder::new("") + .invoke_handler(tauri::generate_handler![commands::upload]) +``` + +Define a binding function in `webview-src/index.ts` so that plugin users can easily call the command in JavaScript: + +```js +// webview-src/index.ts +import { invoke, Channel } from '@tauri-apps/api/tauri' + +export async function upload(url: string, onProgressHandler: (progress: number) => void): Promise { + const onProgress = new Channel() + onProgress.onmessage = onProgressHandler + await invoke('plugin:|upload', { url, onProgress }) +} +``` + +Be sure to build the TypeScript code prior to testing it. + +## Managing State + +A plugin can manage state in the same way a Tauri application does. Read the [State Management guide](/2/guide/state-management) for more information. diff --git a/src/content/docs/2/guide/state-management.mdx b/src/content/docs/2/guide/state-management.mdx new file mode 100644 index 0000000000..f08342fcfc --- /dev/null +++ b/src/content/docs/2/guide/state-management.mdx @@ -0,0 +1,28 @@ +--- +title: State Management +--- + +import Stub from '@components/Stub.astro'; + + + +Define a state struct and let Tauri manage it: + +```rust +use std::{collections::HashMap, sync::Mutex}; +use tauri::{Manager, plugin::Builder}; + +#[derive(Default)] +struct Store(Mutex>); + +Builder::new("") + .setup(|app, api| { + app.manage(Store::default()); + + // retrieve the store later + let store = app.state::(); + Ok(()) + }) +``` + + diff --git a/src/content/docs/2/reference/cli.mdx b/src/content/docs/2/reference/cli.mdx index 1c767583a0..2cf46a62de 100644 --- a/src/content/docs/2/reference/cli.mdx +++ b/src/content/docs/2/reference/cli.mdx @@ -19,7 +19,7 @@ You can add the Tauri CLI to your current project using your package manager of :::tip[Developing a Plugin] -For CLI commands related to developing plugins visit the [Develop a Tauri Plugin](/2/guide/plugins/develop-plugin) guide. +For CLI commands related to developing plugins visit the [Develop a Tauri Plugin guide](/2/guide/plugins). :::