snapshot of https://github.com/tauri-apps/tauri.git (dev, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Audit / audit-js (push) Failing after 0s
bench / bench (map[os:ubuntu-latest runner:xvfb-run --auto-servernum target:x86_64-unknown-linux-gnu], nightly) (push) Failing after 0s
covector version or publish / run-integration-tests (ubuntu-latest) (push) Failing after 0s
Audit / audit-rust (push) Failing after 0s
test `@tauri-apps/cli` / test (ubuntu-latest) (push) Failing after 0s
test `tauri-cli` / test (map[os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:ubuntu-latest target:x86_64-unknown-linux-gnu toolchain:1.90]) (push) Failing after 0s
test core / test (map[args:--no-default-features key:no-default], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
deploy schema worker / deploy (push) Failing after 0s
lint rust / clippy (map[cargo:cargo include: os:ubuntu-latest target:x86_64-unknown-linux-gnu]) (push) Failing after 0s
lint rust / clippy (map[cargo:cross include:--package api os:ubuntu-latest target:aarch64-linux-android]) (push) Failing after 0s
supply chain health status / check rust dependencies with cargo vet (push) Failing after 0s
test `@tauri-apps/api` e2e (mobile) / android (push) Failing after 0s
test `@tauri-apps/api` e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
test core / test (map[args:--all-features key:all], map[cargo:cross command:build os:ubuntu-latest target:aarch64-linux-android toolchain:1.90]) (push) Failing after 0s
Udeps / changes (push) Failing after 0s
Udeps / setup (push) Has been skipped
Udeps / udeps (push) Has been skipped
bench / bench (map[os:macos-latest runner: target:aarch64-apple-darwin], nightly) (push) Has been cancelled
bench / bench (map[os:windows-latest runner: target:x86_64-pc-windows-msvc], nightly) (push) Has been cancelled
covector version or publish / run-integration-tests (macos-latest) (push) Has been cancelled
covector version or publish / run-integration-tests (windows-latest) (push) Has been cancelled
covector version or publish / version-or-publish (push) Has been cancelled
test `@tauri-apps/cli` / test (macos-latest) (push) Has been cancelled
test `tauri-cli` / test (map[args:--no-default-features --features native-tls-vendored os:windows-latest restore-only:true target:aarch64-pc-windows-msvc]) (push) Has been cancelled
test `tauri-cli` / test (map[os:macos-latest target:x86_64-apple-darwin]) (push) Has been cancelled
test `tauri-cli` / test (map[os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:macos-latest target:aarch64-apple-darwin]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include: os:windows-latest target:x86_64-pc-windows-msvc]) (push) Has been cancelled
lint rust / clippy (map[cargo:cargo include:--package api os:macos-latest target:aarch64-apple-ios]) (push) Has been cancelled
test `@tauri-apps/api` e2e (mobile) / ios (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test `@tauri-apps/api` e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
test `@tauri-apps/cli` / test (windows-latest) (push) Has been cancelled
test core / test (map[args:--all-features key:all], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:build os:macos-latest target:aarch64-apple-ios toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:macos-14 target:aarch64-apple-darwin toolchain:1.90]) (push) Has been cancelled
test core / test (map[args:--no-default-features key:no-default], map[cargo:cargo command:test os:windows-latest target:x86_64-pc-windows-msvc toolchain:1.90]) (push) Has been cancelled
BIN
examples/.icons/128x128.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
examples/.icons/128x128@2x.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
examples/.icons/32x32.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
examples/.icons/AppIcon.icon/Assets/icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
35
examples/.icons/AppIcon.icon/icon.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"fill": {
|
||||
"automatic-gradient": "extended-srgb:0.00000,0.53333,1.00000,1.00000"
|
||||
},
|
||||
"groups": [
|
||||
{
|
||||
"layers": [
|
||||
{
|
||||
"blend-mode": "normal",
|
||||
"fill": "automatic",
|
||||
"glass": false,
|
||||
"hidden": false,
|
||||
"image-name": "icon.png",
|
||||
"name": "icon",
|
||||
"position": {
|
||||
"scale": 2,
|
||||
"translation-in-points": [0, 0]
|
||||
}
|
||||
}
|
||||
],
|
||||
"shadow": {
|
||||
"kind": "neutral",
|
||||
"opacity": 0.5
|
||||
},
|
||||
"translucency": {
|
||||
"enabled": true,
|
||||
"value": 0.5
|
||||
}
|
||||
}
|
||||
],
|
||||
"supported-platforms": {
|
||||
"circles": ["watchOS"],
|
||||
"squares": "shared"
|
||||
}
|
||||
}
|
||||
BIN
examples/.icons/Square107x107Logo.png
Normal file
|
After Width: | Height: | Size: 9.0 KiB |
BIN
examples/.icons/Square142x142Logo.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
examples/.icons/Square150x150Logo.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
examples/.icons/Square284x284Logo.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
examples/.icons/Square30x30Logo.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
examples/.icons/Square310x310Logo.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
examples/.icons/Square44x44Logo.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
examples/.icons/Square71x71Logo.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
examples/.icons/Square89x89Logo.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
examples/.icons/StoreLogo.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
examples/.icons/icon.ico
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
examples/.icons/icon.png
Normal file
|
After Width: | Height: | Size: 49 KiB |
BIN
examples/.icons/tray_icon.png
Normal file
|
After Width: | Height: | Size: 804 B |
BIN
examples/.icons/tray_icon_with_transparency.ico
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
examples/.icons/tray_icon_with_transparency.png
Normal file
|
After Width: | Height: | Size: 1015 B |
5
examples/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Examples
|
||||
|
||||
Following directories contain examples of tauri app functionality.
|
||||
|
||||
There are different execution steps depending on the example. See each directory `README.md` for execution method.
|
||||
2
examples/api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/dist/*
|
||||
!/dist/.gitkeep
|
||||
6
examples/api/.setup-cross.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
export ICONS_VOLUME="$(realpath ../.icons)"
|
||||
export DIST_VOLUME="$(realpath dist)"
|
||||
export ISOLATION_VOLUME="$(realpath isolation-dist)"
|
||||
export WORKSPACE_VOLUME="$(realpath ../..)"
|
||||
3
examples/api/.taurignore
Normal file
@@ -0,0 +1,3 @@
|
||||
src-tauri/locales/
|
||||
src-tauri/Cross.toml
|
||||
src-tauri/.gitignore
|
||||
29
examples/api/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# 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.
|
||||
|
||||

|
||||
|
||||
## Running the example
|
||||
|
||||
- Compile Tauri
|
||||
go to root of the Tauri repo and run:
|
||||
|
||||
```
|
||||
pnpm i
|
||||
pnpm build:debug
|
||||
```
|
||||
|
||||
- Run the app in development mode (Run inside of this folder `examples/api/`)
|
||||
|
||||
```bash
|
||||
$ pnpm tauri dev
|
||||
```
|
||||
|
||||
- Build an run the release app (Run inside of this folder `examples/api/`)
|
||||
|
||||
```bash
|
||||
$ pnpm tauri build
|
||||
$ ../../target/release/api
|
||||
```
|
||||
0
examples/api/dist/.gitkeep
vendored
Normal file
14
examples/api/index.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="dark light" />
|
||||
<title>API Example App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
10
examples/api/isolation-dist/index.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Isolation Secure Script</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
7
examples/api/isolation-dist/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
window.__TAURI_ISOLATION_HOOK__ = (payload, options) => {
|
||||
return payload
|
||||
}
|
||||
26
examples/api/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "api",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "node ../../packages/cli/tauri.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "../../packages/api/dist"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/codicon": "^1.2.49",
|
||||
"@iconify-json/ph": "^1.2.2",
|
||||
"@sveltejs/vite-plugin-svelte": "^7.2.0",
|
||||
"@types/sortablejs": "^1.15.9",
|
||||
"@unocss/extractor-svelte": "^66.7.5",
|
||||
"sortablejs": "^1.15.7",
|
||||
"svelte": "^5.56.4",
|
||||
"unocss": "^66.7.5",
|
||||
"vite": "^8.1.4"
|
||||
}
|
||||
}
|
||||
BIN
examples/api/public/tauri_logo.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
6
examples/api/src-tauri/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
||||
# cargo-mobile
|
||||
/gen
|
||||
1
examples/api/src-tauri/.taurignore
Normal file
@@ -0,0 +1 @@
|
||||
tauri-plugin-sample/
|
||||
51
examples/api/src-tauri/Cargo.toml
Normal file
@@ -0,0 +1,51 @@
|
||||
[package]
|
||||
name = "api"
|
||||
version = "0.1.0"
|
||||
description = "An example Tauri Application showcasing the api"
|
||||
edition = "2024"
|
||||
rust-version = "1.90"
|
||||
license = "Apache-2.0 OR MIT"
|
||||
|
||||
[lib]
|
||||
name = "api_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { workspace = true, default-features = true, features = [
|
||||
"codegen",
|
||||
"isolation",
|
||||
] }
|
||||
|
||||
[dependencies]
|
||||
serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
log = "0.4.21"
|
||||
tauri-plugin-sample = { path = "./tauri-plugin-sample/" }
|
||||
tauri-plugin-log = "2"
|
||||
|
||||
# WebDriver automation bridge, used by the `@tauri-apps/api` e2e suite (packages/api-e2e).
|
||||
# Desktop-only and behind the off-by-default `automation` feature so it never ships in a
|
||||
# regular build.
|
||||
[target."cfg(not(any(target_os = \"android\", target_os = \"ios\")))".dependencies]
|
||||
tauri-plugin-automation = { version = "0.2", optional = true }
|
||||
|
||||
[dependencies.tauri]
|
||||
workspace = true
|
||||
default-features = true
|
||||
features = [
|
||||
"protocol-asset",
|
||||
"image-ico",
|
||||
"image-png",
|
||||
"isolation",
|
||||
"macos-private-api",
|
||||
"tray-icon",
|
||||
]
|
||||
|
||||
[dev-dependencies.tauri]
|
||||
workspace = true
|
||||
features = ["test"]
|
||||
|
||||
[features]
|
||||
prod = ["tauri/custom-protocol"]
|
||||
# Enables the WebDriver automation bridge; set by the e2e suite's build (packages/api-e2e).
|
||||
automation = ["dep:tauri-plugin-automation"]
|
||||
17
examples/api/src-tauri/Cross.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[build.env]
|
||||
# must set ICONS_VOLUME, DIST_VOLUME, ISOLATION_VOLUME and WORKSPACE_VOLUME environment variables
|
||||
# ICONS_VOLUME: absolute path to the .icons folder
|
||||
# DIST_VOLUME: absolute path to the dist folder
|
||||
# ISOLATION_VOLUME: absolute path to the isolation dist folder
|
||||
# WORKSPACE_VOLUME: absolute path to the workspace
|
||||
# this can be done running `$ . .setup-cross.sh` in the examples/api folder
|
||||
volumes = [
|
||||
"ICONS_VOLUME",
|
||||
"DIST_VOLUME",
|
||||
"ISOLATION_VOLUME",
|
||||
"WORKSPACE_VOLUME",
|
||||
]
|
||||
|
||||
[target.aarch64-unknown-linux-gnu]
|
||||
image = "aarch64-unknown-linux-gnu:latest"
|
||||
#image = "ghcr.io/tauri-apps/tauri/aarch64-unknown-linux-gnu:latest"
|
||||
17
examples/api/src-tauri/Info.plist
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Request camera access for WebRTC</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Request microphone access for WebRTC</string>
|
||||
<key>UIApplicationSceneManifest</key>
|
||||
<dict>
|
||||
<key>UIApplicationSupportsMultipleScenes</key>
|
||||
<true/>
|
||||
<key>UISceneConfigurations</key>
|
||||
<dict/>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
56
examples/api/src-tauri/build.rs
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use tauri_build::WindowsAttributes;
|
||||
|
||||
fn main() {
|
||||
tauri_build::try_build(
|
||||
tauri_build::Attributes::new()
|
||||
.codegen(tauri_build::CodegenContext::new())
|
||||
.windows_attributes(WindowsAttributes::new_without_app_manifest())
|
||||
.plugin(
|
||||
"app-menu",
|
||||
tauri_build::InlinedPlugin::new().commands(&["toggle", "popup"]),
|
||||
)
|
||||
.app_manifest(tauri_build::AppManifest::new().commands(&[
|
||||
"log_operation",
|
||||
"perform_request",
|
||||
"echo",
|
||||
"spam",
|
||||
])),
|
||||
)
|
||||
.expect("failed to run tauri-build");
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
// workaround needed to prevent `STATUS_ENTRYPOINT_NOT_FOUND` error in tests
|
||||
// see https://github.com/tauri-apps/tauri/pull/4383#issuecomment-1212221864
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let target_env = std::env::var("CARGO_CFG_TARGET_ENV");
|
||||
let is_tauri_workspace = std::env::var("__TAURI_WORKSPACE__").is_ok_and(|v| v == "true");
|
||||
if is_tauri_workspace && target_os == "windows" && Ok("msvc") == target_env.as_deref() {
|
||||
embed_manifest_for_tests();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn embed_manifest_for_tests() {
|
||||
static WINDOWS_MANIFEST_FILE: &str = "windows-app-manifest.xml";
|
||||
|
||||
let manifest = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("../../../crates/tauri-build/src")
|
||||
.join(WINDOWS_MANIFEST_FILE);
|
||||
|
||||
println!("cargo:rerun-if-changed={}", manifest.display());
|
||||
// Embed the Windows application manifest file.
|
||||
println!("cargo:rustc-link-arg=/MANIFEST:EMBED");
|
||||
println!(
|
||||
"cargo:rustc-link-arg=/MANIFESTINPUT:{}",
|
||||
manifest.to_str().unwrap()
|
||||
);
|
||||
// Turn linker warnings into errors.
|
||||
println!("cargo:rustc-link-arg=/WX");
|
||||
}
|
||||
1
examples/api/src-tauri/capabilities/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
schemas/
|
||||
16
examples/api/src-tauri/capabilities/main.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "secondary-window",
|
||||
"description": "capability for secondary window",
|
||||
"windows": ["main-*"],
|
||||
"permissions": [
|
||||
{
|
||||
"identifier": "sample:allow-ping",
|
||||
"deny": [
|
||||
{
|
||||
"path": "tauri.app"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
75
examples/api/src-tauri/capabilities/run-app.json
Normal file
@@ -0,0 +1,75 @@
|
||||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "run-app",
|
||||
"description": "permissions to run the app",
|
||||
"windows": ["main", "main-*"],
|
||||
"permissions": [
|
||||
"core:window:allow-is-enabled",
|
||||
"core:window:allow-set-enabled",
|
||||
{
|
||||
"identifier": "allow-log-operation",
|
||||
"allow": [
|
||||
{
|
||||
"event": "tauri-click"
|
||||
}
|
||||
]
|
||||
},
|
||||
"allow-perform-request",
|
||||
"allow-echo",
|
||||
"allow-spam",
|
||||
"app-menu:default",
|
||||
"sample:allow-ping-scoped",
|
||||
"sample:allow-js-values",
|
||||
"sample:global-scope",
|
||||
"core:default",
|
||||
"core:app:allow-app-hide",
|
||||
"core:app:allow-app-show",
|
||||
"core:app:allow-set-app-theme",
|
||||
"core:app:allow-set-dock-visibility",
|
||||
"core:app:allow-default-window-icon",
|
||||
"core:app:allow-exit",
|
||||
"core:window:allow-set-theme",
|
||||
"core:window:allow-center",
|
||||
"core:window:allow-request-user-attention",
|
||||
"core:window:allow-set-resizable",
|
||||
"core:window:allow-set-maximizable",
|
||||
"core:window:allow-set-minimizable",
|
||||
"core:window:allow-set-closable",
|
||||
"core:window:allow-set-title",
|
||||
"core:window:allow-maximize",
|
||||
"core:window:allow-unmaximize",
|
||||
"core:window:allow-minimize",
|
||||
"core:window:allow-unminimize",
|
||||
"core:window:allow-show",
|
||||
"core:window:allow-hide",
|
||||
"core:window:allow-close",
|
||||
"core:window:allow-set-decorations",
|
||||
"core:window:allow-set-shadow",
|
||||
"core:window:allow-set-effects",
|
||||
"core:window:allow-set-always-on-top",
|
||||
"core:window:allow-set-always-on-bottom",
|
||||
"core:window:allow-set-content-protected",
|
||||
"core:window:allow-set-size",
|
||||
"core:window:allow-set-min-size",
|
||||
"core:window:allow-set-max-size",
|
||||
"core:window:allow-set-position",
|
||||
"core:window:allow-set-fullscreen",
|
||||
"core:window:allow-set-fullscreen-on-monitor",
|
||||
"core:window:allow-set-simple-fullscreen",
|
||||
"core:window:allow-set-focus",
|
||||
"core:window:allow-set-focusable",
|
||||
"core:window:allow-set-skip-taskbar",
|
||||
"core:window:allow-set-cursor-grab",
|
||||
"core:window:allow-set-cursor-visible",
|
||||
"core:window:allow-set-cursor-icon",
|
||||
"core:window:allow-set-cursor-position",
|
||||
"core:window:allow-set-ignore-cursor-events",
|
||||
"core:window:allow-start-dragging",
|
||||
"core:window:allow-set-progress-bar",
|
||||
"core:window:allow-set-icon",
|
||||
"core:window:allow-toggle-maximize",
|
||||
"core:webview:allow-create-webview-window",
|
||||
"core:webview:allow-print",
|
||||
"core:webview:allow-set-webview-zoom"
|
||||
]
|
||||
}
|
||||
6
examples/api/src-tauri/locales/pt-BR.wxl
Normal file
@@ -0,0 +1,6 @@
|
||||
<WixLocalization Culture="pt-BR" xmlns="http://schemas.microsoft.com/wix/2006/localization">
|
||||
<String Id="LaunchApp">Executar Tauri API</String>
|
||||
<String Id="DowngradeErrorMessage">Uma versão mais recente de Tauri API está instalada.</String>
|
||||
<String Id="PathEnvVarFeature">Adiciona o caminho do executável de Tauri API para a variável de ambiente PATH. Isso permite Tauri API ser executado pela linha de comando.</String>
|
||||
<String Id="InstallAppFeature">Instala Tauri API.</String>
|
||||
</WixLocalization>
|
||||
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"]
|
||||
11
examples/api/src-tauri/permissions/autogenerated/echo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-echo"
|
||||
description = "Enables the echo command without any pre-configured scope."
|
||||
commands.allow = ["echo"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-echo"
|
||||
description = "Denies the echo command without any pre-configured scope."
|
||||
commands.deny = ["echo"]
|
||||
@@ -0,0 +1,11 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-log-operation"
|
||||
description = "Enables the log_operation command without any pre-configured scope."
|
||||
commands.allow = ["log_operation"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-log-operation"
|
||||
description = "Denies the log_operation command without any pre-configured scope."
|
||||
commands.deny = ["log_operation"]
|
||||
@@ -0,0 +1,11 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-perform-request"
|
||||
description = "Enables the perform_request command without any pre-configured scope."
|
||||
commands.allow = ["perform_request"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-perform-request"
|
||||
description = "Denies the perform_request command without any pre-configured scope."
|
||||
commands.deny = ["perform_request"]
|
||||
11
examples/api/src-tauri/permissions/autogenerated/spam.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-spam"
|
||||
description = "Enables the spam command without any pre-configured scope."
|
||||
commands.allow = ["spam"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-spam"
|
||||
description = "Denies the spam command without any pre-configured scope."
|
||||
commands.deny = ["spam"]
|
||||
63
examples/api/src-tauri/src/cmd.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::{
|
||||
command,
|
||||
ipc::{Channel, CommandScope},
|
||||
};
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct RequestBody {
|
||||
id: i32,
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct LogScope {
|
||||
event: String,
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn log_operation(
|
||||
event: String,
|
||||
payload: Option<String>,
|
||||
command_scope: CommandScope<LogScope>,
|
||||
) -> Result<(), &'static str> {
|
||||
if command_scope.denies().iter().any(|s| s.event == event) {
|
||||
Err("denied")
|
||||
} else if !command_scope.allows().iter().any(|s| s.event == event) {
|
||||
Err("not allowed")
|
||||
} else {
|
||||
log::info!("{event} {payload:?}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct ApiResponse {
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn perform_request(endpoint: String, body: RequestBody) -> ApiResponse {
|
||||
println!("{endpoint} {body:?}");
|
||||
ApiResponse {
|
||||
message: "message response".into(),
|
||||
}
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn echo(request: tauri::ipc::Request<'_>) -> tauri::ipc::Response {
|
||||
tauri::ipc::Response::new(request.body().clone())
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn spam(channel: Channel<i32>) -> tauri::Result<()> {
|
||||
for i in 1..=1_000 {
|
||||
channel.send(i)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
231
examples/api/src-tauri/src/lib.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
mod cmd;
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
mod menu_plugin;
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
mod tray;
|
||||
|
||||
use serde::Serialize;
|
||||
use tauri::{
|
||||
App, Emitter, Listener, Runtime, WebviewUrl,
|
||||
ipc::Channel,
|
||||
webview::{PageLoadEvent, WebviewWindowBuilder},
|
||||
};
|
||||
#[allow(unused)]
|
||||
use tauri::{Manager, RunEvent};
|
||||
use tauri_plugin_sample::{PingRequest, SampleExt};
|
||||
|
||||
#[derive(Clone, Serialize)]
|
||||
struct Reply {
|
||||
data: String,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub struct AppMenu<R: Runtime>(pub std::sync::Mutex<Option<tauri::menu::Menu<R>>>);
|
||||
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
pub struct PopupMenu<R: Runtime>(tauri::menu::Menu<R>);
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
run_app(tauri::Builder::default(), |_app| {})
|
||||
}
|
||||
|
||||
pub fn run_app<R: Runtime, F: FnOnce(&App<R>) + Send + 'static>(
|
||||
builder: tauri::Builder<R>,
|
||||
setup: F,
|
||||
) {
|
||||
// WebDriver automation bridge for the `@tauri-apps/api` e2e suite (packages/api-e2e).
|
||||
// Registered as early as possible per the plugin's docs. Behind the off-by-default
|
||||
// `automation` feature, and `not(test)` so it never interferes with the mock-runtime
|
||||
// unit test below.
|
||||
#[cfg(all(desktop, feature = "automation", not(test)))]
|
||||
let builder = builder.plugin(tauri_plugin_automation::init());
|
||||
|
||||
let builder = builder
|
||||
.plugin(
|
||||
tauri_plugin_log::Builder::default()
|
||||
.level(log::LevelFilter::Info)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_sample::init())
|
||||
.setup(move |app| {
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
{
|
||||
let handle = app.handle();
|
||||
tray::create_tray(handle)?;
|
||||
handle.plugin(menu_plugin::init())?;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
app.manage(AppMenu::<R>(Default::default()));
|
||||
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
app.manage(PopupMenu(
|
||||
tauri::menu::MenuBuilder::new(app)
|
||||
.check("check", "Tauri is awesome!")
|
||||
.text("text", "Do something")
|
||||
.copy()
|
||||
.build()?,
|
||||
));
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut window_builder = WebviewWindowBuilder::new(app, "main", WebviewUrl::default())
|
||||
.disable_drag_drop_handler()
|
||||
.on_document_title_changed(|_window, title| {
|
||||
println!("document title changed: {title}");
|
||||
});
|
||||
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
{
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
let app_ = app.handle().clone();
|
||||
let created_window_count = AtomicU64::new(0);
|
||||
|
||||
window_builder = window_builder
|
||||
.title("Tauri API Validation")
|
||||
.inner_size(1000., 800.)
|
||||
.min_inner_size(600., 400.)
|
||||
.menu(tauri::menu::Menu::default(app.handle())?)
|
||||
.on_new_window(move |url, features| {
|
||||
println!("new window requested: {url:?} {features:?}");
|
||||
|
||||
let number = created_window_count.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let builder = WebviewWindowBuilder::new(
|
||||
&app_,
|
||||
format!("new-{number}"),
|
||||
tauri::WebviewUrl::External("about:blank".parse().unwrap()),
|
||||
)
|
||||
.window_features(features)
|
||||
.on_document_title_changed(|window, title| {
|
||||
window.set_title(&title).unwrap();
|
||||
})
|
||||
.title(url.as_str());
|
||||
|
||||
let window = builder.build().unwrap();
|
||||
tauri::webview::NewWindowResponse::Create { window }
|
||||
});
|
||||
|
||||
// Liquid Glass effects need a transparent window to show through
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
window_builder = window_builder.transparent(true);
|
||||
}
|
||||
}
|
||||
|
||||
let _webview = window_builder.build()?;
|
||||
|
||||
let value = Some("test".to_string());
|
||||
let response = app.sample().ping(PingRequest {
|
||||
value: value.clone(),
|
||||
on_event: Channel::new(|event| {
|
||||
println!("got channel event: {event:?}");
|
||||
Ok(())
|
||||
}),
|
||||
});
|
||||
log::info!("got response: {response:?}");
|
||||
if let Ok(res) = response {
|
||||
assert_eq!(res.value, value);
|
||||
}
|
||||
|
||||
setup(app);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.on_page_load(|webview, payload| {
|
||||
if payload.event() == PageLoadEvent::Finished {
|
||||
let webview_ = webview.clone();
|
||||
webview.listen("js-event", move |event| {
|
||||
println!("got js-event with message '{:?}'", event.payload());
|
||||
let reply = Reply {
|
||||
data: "something else".to_string(),
|
||||
};
|
||||
|
||||
webview_
|
||||
.emit("rust-event", Some(reply))
|
||||
.expect("failed to emit");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut app = builder
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
cmd::log_operation,
|
||||
cmd::perform_request,
|
||||
cmd::echo,
|
||||
cmd::spam,
|
||||
])
|
||||
.build(tauri::tauri_build_context!())
|
||||
.expect("error while building tauri application");
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
app.set_activation_policy(tauri::ActivationPolicy::Regular);
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
let mut counter = 0;
|
||||
app.run(move |_app_handle, event| {
|
||||
match event {
|
||||
// Keep the event loop running even if all windows are closed
|
||||
// This allow us to catch tray icon events when there is no window
|
||||
// if we manually requested an exit (code is Some(_)) we will let it go through
|
||||
#[cfg(all(desktop, not(test)))]
|
||||
RunEvent::ExitRequested { api, code, .. } if code.is_none() => {
|
||||
api.prevent_exit();
|
||||
}
|
||||
#[cfg(desktop)]
|
||||
RunEvent::WindowEvent {
|
||||
event: tauri::WindowEvent::CloseRequested { api, .. },
|
||||
label,
|
||||
..
|
||||
} => {
|
||||
println!("closing window...");
|
||||
// run the window destroy manually just for fun :)
|
||||
// usually you'd show a dialog here to ask for confirmation or whatever
|
||||
api.prevent_close();
|
||||
_app_handle
|
||||
.get_webview_window(&label)
|
||||
.unwrap()
|
||||
.destroy()
|
||||
.unwrap();
|
||||
}
|
||||
#[cfg(target_os = "ios")]
|
||||
RunEvent::SceneRequested { .. } => {
|
||||
counter += 1;
|
||||
WebviewWindowBuilder::new(
|
||||
_app_handle,
|
||||
format!("main-from-scene-{counter}"),
|
||||
WebviewUrl::default(),
|
||||
)
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
|
||||
RunEvent::Opened { urls } => {
|
||||
println!("opened urls: {:?}", urls);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tauri::Manager;
|
||||
|
||||
#[test]
|
||||
fn run_app() {
|
||||
super::run_app(tauri::test::mock_builder(), |app| {
|
||||
let window = app.get_webview_window("main").unwrap();
|
||||
std::thread::spawn(move || {
|
||||
std::thread::sleep(std::time::Duration::from_secs(1));
|
||||
window.close().unwrap();
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
10
examples/api/src-tauri/src/main.rs
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
fn main() {
|
||||
api_lib::run();
|
||||
}
|
||||
50
examples/api/src-tauri/src/menu_plugin.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use tauri::{
|
||||
Runtime, command,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
};
|
||||
|
||||
#[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![
|
||||
#![plugin(app_menu)]
|
||||
popup, toggle
|
||||
])
|
||||
.build()
|
||||
}
|
||||
126
examples/api/src-tauri/src/tray.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use tauri::{
|
||||
Manager, Runtime, WebviewUrl, include_image,
|
||||
menu::{Menu, MenuItem},
|
||||
tray::{MouseButton, MouseButtonState, TrayIconBuilder, TrayIconEvent},
|
||||
};
|
||||
|
||||
pub fn create_tray<R: Runtime>(app: &tauri::AppHandle<R>) -> tauri::Result<()> {
|
||||
let toggle_i = MenuItem::with_id(app, "toggle", "Toggle", true, None::<&str>)?;
|
||||
let new_window_i = MenuItem::with_id(app, "new-window", "New window", true, None::<&str>)?;
|
||||
let icon_i_1 = MenuItem::with_id(app, "icon-1", "Icon 1", true, None::<&str>)?;
|
||||
let icon_i_2 = MenuItem::with_id(app, "icon-2", "Icon 2", true, None::<&str>)?;
|
||||
#[cfg(target_os = "macos")]
|
||||
let set_title_i = MenuItem::with_id(app, "set-title", "Set Title", true, None::<&str>)?;
|
||||
let switch_i = MenuItem::with_id(app, "switch-menu", "Switch Menu", true, None::<&str>)?;
|
||||
let quit_i = MenuItem::with_id(app, "quit", "Quit", true, None::<&str>)?;
|
||||
let remove_tray_i =
|
||||
MenuItem::with_id(app, "remove-tray", "Remove Tray icon", true, None::<&str>)?;
|
||||
let menu1 = Menu::with_items(
|
||||
app,
|
||||
&[
|
||||
&toggle_i,
|
||||
&new_window_i,
|
||||
&icon_i_1,
|
||||
&icon_i_2,
|
||||
#[cfg(target_os = "macos")]
|
||||
&set_title_i,
|
||||
&switch_i,
|
||||
&quit_i,
|
||||
&remove_tray_i,
|
||||
],
|
||||
)?;
|
||||
let menu2 = Menu::with_items(
|
||||
app,
|
||||
&[&toggle_i, &new_window_i, &switch_i, &quit_i, &remove_tray_i],
|
||||
)?;
|
||||
|
||||
let is_menu1 = AtomicBool::new(true);
|
||||
|
||||
let _tray = TrayIconBuilder::with_id("tray-1")
|
||||
.tooltip("Tauri")
|
||||
.icon(app.default_window_icon().unwrap().clone())
|
||||
.menu(&menu1)
|
||||
.show_menu_on_left_click(false)
|
||||
.on_menu_event(move |app, event| match event.id.as_ref() {
|
||||
"quit" => {
|
||||
app.exit(0);
|
||||
}
|
||||
"remove-tray" => {
|
||||
app.remove_tray_by_id("tray-1");
|
||||
}
|
||||
"toggle" => {
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let new_title = if window.is_visible().unwrap_or_default() {
|
||||
let _ = window.hide();
|
||||
"Show"
|
||||
} else {
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
"Hide"
|
||||
};
|
||||
toggle_i.set_text(new_title).unwrap();
|
||||
}
|
||||
}
|
||||
"new-window" => {
|
||||
let _webview =
|
||||
tauri::WebviewWindowBuilder::new(app, "new", WebviewUrl::App("index.html".into()))
|
||||
.title("Tauri")
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
"set-title" => {
|
||||
if let Some(tray) = app.tray_by_id("tray-1") {
|
||||
let _ = tray.set_title(Some("Tauri"));
|
||||
}
|
||||
}
|
||||
i @ "icon-1" | i @ "icon-2" => {
|
||||
if let Some(tray) = app.tray_by_id("tray-1") {
|
||||
let icon = if i == "icon-1" {
|
||||
include_image!("../../.icons/icon.ico")
|
||||
} else {
|
||||
include_image!("../../.icons/tray_icon_with_transparency.png")
|
||||
};
|
||||
let _ = tray.set_icon(Some(icon));
|
||||
}
|
||||
}
|
||||
"switch-menu" => {
|
||||
let flag = is_menu1.load(Ordering::Relaxed);
|
||||
let (menu, tooltip) = if flag {
|
||||
(menu2.clone(), "Menu 2")
|
||||
} else {
|
||||
(menu1.clone(), "Tauri")
|
||||
};
|
||||
if let Some(tray) = app.tray_by_id("tray-1") {
|
||||
let _ = tray.set_menu(Some(menu));
|
||||
let _ = tray.set_tooltip(Some(tooltip));
|
||||
}
|
||||
is_menu1.store(!flag, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
_ => {}
|
||||
})
|
||||
.on_tray_icon_event(|tray, event| {
|
||||
if let TrayIconEvent::Click {
|
||||
button: MouseButton::Left,
|
||||
button_state: MouseButtonState::Up,
|
||||
..
|
||||
} = event
|
||||
{
|
||||
let app = tray.app_handle();
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.unminimize();
|
||||
let _ = window.show();
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
})
|
||||
.build(app)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
1
examples/api/src-tauri/tauri-plugin-sample/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.tauri
|
||||
18
examples/api/src-tauri/tauri-plugin-sample/Cargo.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "tauri-plugin-sample"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
links = "tauri-plugin-sample"
|
||||
|
||||
[dependencies]
|
||||
tauri = { workspace = true }
|
||||
log = "0.4"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
thiserror = "2"
|
||||
|
||||
[target.'cfg(any(target_os = "ios", target_os = "android"))'.dependencies]
|
||||
tauri = { workspace = true, features = ["wry"] }
|
||||
|
||||
[build-dependencies]
|
||||
tauri-plugin = { workspace = true, features = ["build"] }
|
||||
2
examples/api/src-tauri/tauri-plugin-sample/android/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
.tauri
|
||||
@@ -0,0 +1,40 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.plugin.sample"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 21
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
||||
implementation("com.google.android.material:material:1.7.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
implementation(project(":tauri-android"))
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,31 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
google()
|
||||
}
|
||||
resolutionStrategy {
|
||||
eachPlugin {
|
||||
switch (requested.id.id) {
|
||||
case "com.android.library":
|
||||
useVersion("8.0.2")
|
||||
break
|
||||
case "org.jetbrains.kotlin.android":
|
||||
useVersion("1.8.20")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
include ':tauri-android'
|
||||
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package com.plugin.sample
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.plugin.sample", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package com.plugin.sample
|
||||
|
||||
import android.util.Log
|
||||
|
||||
class Example {
|
||||
fun pong(value: String): String {
|
||||
Log.i("Pong", value)
|
||||
return value
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package com.plugin.sample
|
||||
|
||||
import android.app.Activity
|
||||
import app.tauri.annotation.Command
|
||||
import app.tauri.annotation.InvokeArg
|
||||
import app.tauri.annotation.TauriPlugin
|
||||
import app.tauri.plugin.Channel
|
||||
import app.tauri.plugin.JSArray
|
||||
import app.tauri.plugin.JSObject
|
||||
import app.tauri.plugin.Plugin
|
||||
import app.tauri.plugin.Invoke
|
||||
|
||||
@InvokeArg
|
||||
class PingArgs {
|
||||
var value: String? = null
|
||||
var onEvent: Channel? = null
|
||||
}
|
||||
|
||||
class JsValues(val array: JSArray, val obj: JSObject)
|
||||
|
||||
@TauriPlugin
|
||||
class ExamplePlugin(private val activity: Activity): Plugin(activity) {
|
||||
private val implementation = Example()
|
||||
|
||||
@Command
|
||||
fun ping(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(PingArgs::class.java)
|
||||
|
||||
val event = JSObject()
|
||||
event.put("kind", "ping")
|
||||
args.onEvent?.send(event)
|
||||
|
||||
val ret = JSObject()
|
||||
ret.put("value", implementation.pong(args.value ?: "default value :("))
|
||||
invoke.resolve(ret)
|
||||
}
|
||||
|
||||
// Resolves org.json values through the plugin JSON mapper (`resolveObject`),
|
||||
// both as a property of a plain object and nested in one another.
|
||||
@Command
|
||||
fun jsValues(invoke: Invoke) {
|
||||
val array = JSArray()
|
||||
array.put("a")
|
||||
array.put(1)
|
||||
array.put(true)
|
||||
|
||||
val obj = JSObject()
|
||||
obj.put("kind", "object")
|
||||
obj.put("nested", JSArray(listOf(1, 2)))
|
||||
|
||||
invoke.resolveObject(JsValues(array, obj))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package com.plugin.sample
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
7
examples/api/src-tauri/tauri-plugin-sample/api-iife.js
Normal file
@@ -0,0 +1,7 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
if ('__TAURI__' in window) {
|
||||
window.__TAURI__.sample = {}
|
||||
}
|
||||
13
examples/api/src-tauri/tauri-plugin-sample/build.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
const COMMANDS: &[&str] = &["ping", "js_values"];
|
||||
|
||||
fn main() {
|
||||
tauri_plugin::Builder::new(COMMANDS)
|
||||
.android_path("android")
|
||||
.ios_path("ios")
|
||||
.global_api_script_path("./api-iife.js")
|
||||
.build();
|
||||
}
|
||||
10
examples/api/src-tauri/tauri-plugin-sample/ios/.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
||||
DerivedData/
|
||||
.swiftpm/config/registries.json
|
||||
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
|
||||
.netrc
|
||||
Package.resolved
|
||||
35
examples/api/src-tauri/tauri-plugin-sample/ios/Package.swift
Normal file
@@ -0,0 +1,35 @@
|
||||
// swift-tools-version:5.3
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "tauri-plugin-sample",
|
||||
platforms: [
|
||||
.macOS(.v10_13),
|
||||
.iOS(.v13),
|
||||
],
|
||||
products: [
|
||||
// Products define the executables and libraries a package produces, and make them visible to other packages.
|
||||
.library(
|
||||
name: "tauri-plugin-sample",
|
||||
type: .static,
|
||||
targets: ["tauri-plugin-sample"]),
|
||||
],
|
||||
dependencies: [
|
||||
// Dependencies declare other packages that this package depends on.
|
||||
.package(name: "Tauri", path: "../../../../../crates/tauri/mobile/ios-api")
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "tauri-plugin-sample",
|
||||
dependencies: [
|
||||
.byName(name: "Tauri")
|
||||
],
|
||||
path: "Sources")
|
||||
]
|
||||
)
|
||||
3
examples/api/src-tauri/tauri-plugin-sample/ios/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Tauri Plugin sample
|
||||
|
||||
A description of this package.
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import SwiftRs
|
||||
import Tauri
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
class PingArgs: Decodable {
|
||||
let value: String?
|
||||
let onEvent: Channel?
|
||||
}
|
||||
|
||||
class ExamplePlugin: Plugin {
|
||||
@objc public func ping(_ invoke: Invoke) throws {
|
||||
let args = try invoke.parseArgs(PingArgs.self)
|
||||
try args.onEvent?.send(["kind": "ping"])
|
||||
invoke.resolve(["value": args.value ?? ""])
|
||||
}
|
||||
|
||||
@objc public func jsValues(_ invoke: Invoke) throws {
|
||||
let array: JSArray = ["a", 1, true]
|
||||
let obj: JSObject = ["kind": "object", "nested": [1, 2] as JSArray]
|
||||
invoke.resolve(["array": array, "obj": obj])
|
||||
}
|
||||
}
|
||||
|
||||
@_cdecl("init_plugin_sample")
|
||||
func initPlugin() -> Plugin {
|
||||
return ExamplePlugin()
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import XCTest
|
||||
@testable import ExamplePlugin
|
||||
|
||||
final class ExamplePluginTests: XCTestCase {
|
||||
func testExample() throws {
|
||||
let plugin = ExamplePlugin()
|
||||
}
|
||||
}
|
||||
1
examples/api/src-tauri/tauri-plugin-sample/permissions/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
schemas/
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-js-values"
|
||||
description = "Enables the js_values command without any pre-configured scope."
|
||||
commands.allow = ["js_values"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-js-values"
|
||||
description = "Denies the js_values command without any pre-configured scope."
|
||||
commands.deny = ["js_values"]
|
||||
@@ -0,0 +1,13 @@
|
||||
# Automatically generated - DO NOT EDIT!
|
||||
|
||||
"$schema" = "../../schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-ping"
|
||||
description = "Enables the ping command without any pre-configured scope."
|
||||
commands.allow = ["ping"]
|
||||
|
||||
[[permission]]
|
||||
identifier = "deny-ping"
|
||||
description = "Denies the ping command without any pre-configured scope."
|
||||
commands.deny = ["ping"]
|
||||
@@ -0,0 +1,87 @@
|
||||
## Permission Table
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>Identifier</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`sample:allow-js-values`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the js_values command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`sample:deny-js-values`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the js_values command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`sample:allow-ping`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the ping command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`sample:deny-ping`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Denies the ping command without any pre-configured scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`sample:global-scope`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Sets a global scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td>
|
||||
|
||||
`sample:allow-ping-scoped`
|
||||
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Enables the ping command with a test scope.
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -0,0 +1,7 @@
|
||||
"$schema" = "schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "global-scope"
|
||||
description = "Sets a global scope."
|
||||
[[permission.scope.allow]]
|
||||
path = "global"
|
||||
@@ -0,0 +1,8 @@
|
||||
"$schema" = "schemas/schema.json"
|
||||
|
||||
[[permission]]
|
||||
identifier = "allow-ping-scoped"
|
||||
description = "Enables the ping command with a test scope."
|
||||
commands.allow = ["ping"]
|
||||
[[permission.scope.allow]]
|
||||
path = "x"
|
||||
30
examples/api/src-tauri/tauri-plugin-sample/src/desktop.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use tauri::{AppHandle, Runtime, plugin::PluginApi};
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||
app: &AppHandle<R>,
|
||||
_api: PluginApi<R, C>,
|
||||
) -> crate::Result<Sample<R>> {
|
||||
Ok(Sample(app.clone()))
|
||||
}
|
||||
|
||||
/// A helper class to access the sample APIs.
|
||||
pub struct Sample<R: Runtime>(AppHandle<R>);
|
||||
|
||||
impl<R: Runtime> Sample<R> {
|
||||
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
|
||||
payload.on_event.send(Event {
|
||||
kind: "ping".to_string(),
|
||||
value: payload.value.clone(),
|
||||
})?;
|
||||
Ok(PingResponse {
|
||||
value: payload.value,
|
||||
})
|
||||
}
|
||||
}
|
||||
14
examples/api/src-tauri/tauri-plugin-sample/src/error.rs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[cfg(mobile)]
|
||||
#[error(transparent)]
|
||||
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
|
||||
#[error(transparent)]
|
||||
Tauri(#[from] tauri::Error),
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
101
examples/api/src-tauri/tauri-plugin-sample/src/lib.rs
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::Deserialize;
|
||||
use std::path::PathBuf;
|
||||
use tauri::{
|
||||
Manager, Runtime, State,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
};
|
||||
|
||||
pub use models::*;
|
||||
|
||||
#[cfg(desktop)]
|
||||
mod desktop;
|
||||
#[cfg(mobile)]
|
||||
mod mobile;
|
||||
|
||||
mod error;
|
||||
mod models;
|
||||
|
||||
#[cfg(desktop)]
|
||||
use desktop::Sample;
|
||||
#[cfg(mobile)]
|
||||
use mobile::Sample;
|
||||
|
||||
pub use error::*;
|
||||
|
||||
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the sample APIs.
|
||||
pub trait SampleExt<R: Runtime> {
|
||||
fn sample(&self) -> State<'_, Sample<R>>;
|
||||
}
|
||||
|
||||
impl<R: Runtime, T: Manager<R>> crate::SampleExt<R> for T {
|
||||
fn sample(&self) -> State<'_, Sample<R>> {
|
||||
self.state::<Sample<R>>()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct PingScope {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct SampleScope {
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn ping<R: tauri::Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
value: Option<String>,
|
||||
scope: tauri::ipc::CommandScope<PingScope>,
|
||||
global_scope: tauri::ipc::GlobalScope<SampleScope>,
|
||||
) -> std::result::Result<PingResponse, String> {
|
||||
println!("local scope {scope:?}");
|
||||
println!("global scope {global_scope:?}");
|
||||
app
|
||||
.sample()
|
||||
.ping(PingRequest {
|
||||
value,
|
||||
on_event: tauri::ipc::Channel::new(|_| Ok(())),
|
||||
})
|
||||
.map_err(|e| e.to_string())
|
||||
}
|
||||
|
||||
/// Resolves `JSArray` and `JSObject` values from the native (Kotlin or Swift) plugin.
|
||||
#[tauri::command]
|
||||
fn js_values<R: tauri::Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> std::result::Result<serde_json::Value, String> {
|
||||
#[cfg(mobile)]
|
||||
return app.sample().js_values().map_err(|e| e.to_string());
|
||||
#[cfg(desktop)]
|
||||
{
|
||||
let _ = app;
|
||||
Err("js_values is only implemented on mobile".into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
Builder::new("sample")
|
||||
.setup(|app, api| {
|
||||
#[cfg(mobile)]
|
||||
let sample = mobile::init(app, api)?;
|
||||
#[cfg(desktop)]
|
||||
let sample = desktop::init(app, api)?;
|
||||
app.manage(sample);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![ping, js_values])
|
||||
.on_navigation(|window, url| {
|
||||
println!("navigation {} {url}", window.label());
|
||||
true
|
||||
})
|
||||
.build()
|
||||
}
|
||||
45
examples/api/src-tauri/tauri-plugin-sample/src/mobile.rs
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use tauri::{
|
||||
AppHandle, Runtime,
|
||||
plugin::{PluginApi, PluginHandle},
|
||||
};
|
||||
|
||||
use crate::models::*;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
const PLUGIN_IDENTIFIER: &str = "com.plugin.sample";
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
tauri::ios_plugin_binding!(init_plugin_sample);
|
||||
|
||||
// initializes the Kotlin or Swift plugin classes
|
||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
||||
_app: &AppHandle<R>,
|
||||
api: PluginApi<R, C>,
|
||||
) -> crate::Result<Sample<R>> {
|
||||
#[cfg(target_os = "android")]
|
||||
let handle = api.register_android_plugin(PLUGIN_IDENTIFIER, "ExamplePlugin")?;
|
||||
#[cfg(target_os = "ios")]
|
||||
let handle = api.register_ios_plugin(init_plugin_sample)?;
|
||||
Ok(Sample(handle))
|
||||
}
|
||||
|
||||
/// A helper class to access the sample APIs.
|
||||
pub struct Sample<R: Runtime>(PluginHandle<R>);
|
||||
|
||||
impl<R: Runtime> Sample<R> {
|
||||
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
|
||||
self
|
||||
.0
|
||||
.run_mobile_plugin("ping", payload)
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn js_values(&self) -> crate::Result<serde_json::Value> {
|
||||
self.0.run_mobile_plugin("jsValues", ()).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
24
examples/api/src-tauri/tauri-plugin-sample/src/models.rs
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tauri::ipc::Channel;
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Event {
|
||||
pub kind: String,
|
||||
pub value: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PingRequest {
|
||||
pub value: Option<String>,
|
||||
pub on_event: Channel<Event>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
||||
pub struct PingResponse {
|
||||
pub value: Option<String>,
|
||||
}
|
||||
132
examples/api/src-tauri/tauri.conf.json
Normal file
@@ -0,0 +1,132 @@
|
||||
{
|
||||
"$schema": "../../../crates/tauri-schema-generator/schemas/config.schema.json",
|
||||
"productName": "Tauri API",
|
||||
"version": "1.0.0",
|
||||
"identifier": "com.tauri.api",
|
||||
"build": {
|
||||
"frontendDist": "../dist",
|
||||
"devUrl": "http://localhost:1420",
|
||||
"beforeDevCommand": "pnpm dev",
|
||||
"beforeBuildCommand": "pnpm build",
|
||||
"removeUnusedCommands": true
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": true,
|
||||
"macOSPrivateApi": true,
|
||||
"security": {
|
||||
"pattern": {
|
||||
"use": "isolation",
|
||||
"options": {
|
||||
"dir": "../isolation-dist/"
|
||||
}
|
||||
},
|
||||
"csp": {
|
||||
"default-src": "'self' customprotocol: asset:",
|
||||
"connect-src": "ipc: http://ipc.localhost",
|
||||
"font-src": ["https://fonts.gstatic.com"],
|
||||
"img-src": "'self' asset: http://asset.localhost blob: data:",
|
||||
"style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com"
|
||||
},
|
||||
"freezePrototype": true,
|
||||
"assetProtocol": {
|
||||
"enable": true,
|
||||
"scope": {
|
||||
"allow": ["$APPDATA/db/**", "$RESOURCE/**"],
|
||||
"deny": ["$APPDATA/db/*.stronghold"]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"cli": {
|
||||
"description": "Tauri API example",
|
||||
"args": [
|
||||
{
|
||||
"short": "c",
|
||||
"name": "config",
|
||||
"takesValue": true,
|
||||
"description": "Config path"
|
||||
},
|
||||
{
|
||||
"short": "t",
|
||||
"name": "theme",
|
||||
"takesValue": true,
|
||||
"description": "App theme",
|
||||
"possibleValues": ["light", "dark", "system"]
|
||||
},
|
||||
{
|
||||
"short": "v",
|
||||
"name": "verbose",
|
||||
"description": "Verbosity level"
|
||||
}
|
||||
],
|
||||
"subcommands": {
|
||||
"update": {
|
||||
"description": "Updates the app",
|
||||
"args": [
|
||||
{
|
||||
"short": "b",
|
||||
"name": "background",
|
||||
"description": "Update in background"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": ["png"],
|
||||
"mimeType": "image/png",
|
||||
"rank": "Default"
|
||||
},
|
||||
{
|
||||
"ext": ["jpg", "jpeg"],
|
||||
"mimeType": "image/jpeg",
|
||||
"rank": "Alternate"
|
||||
},
|
||||
{
|
||||
"ext": ["gif"],
|
||||
"mimeType": "image/gif",
|
||||
"rank": "Owner"
|
||||
},
|
||||
{
|
||||
"ext": ["taurijson"],
|
||||
"exportedType": {
|
||||
"identifier": "com.tauri.dev-file-associations-demo.taurijson",
|
||||
"conformsTo": ["public.json"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ext": ["taurid"],
|
||||
"exportedType": {
|
||||
"identifier": "com.tauri.dev-file-associations-demo.tauridata",
|
||||
"conformsTo": ["public.data"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"icon": [
|
||||
"../../.icons/32x32.png",
|
||||
"../../.icons/128x128.png",
|
||||
"../../.icons/128x128@2x.png",
|
||||
"../../.icons/icon.icns",
|
||||
"../../.icons/icon.ico",
|
||||
"../../.icons/AppIcon.icon"
|
||||
],
|
||||
"windows": {
|
||||
"wix": {
|
||||
"language": {
|
||||
"en-US": {},
|
||||
"pt-BR": {
|
||||
"localePath": "locales/pt-BR.wxl"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nsis": {
|
||||
"compression": "none"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
374
examples/api/src/App.svelte
Normal file
@@ -0,0 +1,374 @@
|
||||
<script module lang="ts">
|
||||
export type ViewProps = {
|
||||
onMessage: (value: unknown) => void
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { onMount, tick } from 'svelte'
|
||||
import { writable } from 'svelte/store'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { setTheme } from '@tauri-apps/api/app'
|
||||
|
||||
import Welcome from './views/Welcome.svelte'
|
||||
import Communication from './views/Communication.svelte'
|
||||
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'
|
||||
import type { Theme } from '@tauri-apps/api/window'
|
||||
import { MediaQuery } from 'svelte/reactivity'
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.ctrlKey && event.key === 'b') {
|
||||
invoke('plugin:app-menu|toggle')
|
||||
}
|
||||
})
|
||||
|
||||
const appWindow = getCurrentWebviewWindow()
|
||||
appWindow.onDragDropEvent((event) => {
|
||||
onMessage(event.payload)
|
||||
})
|
||||
|
||||
const userAgent = navigator.userAgent.toLowerCase()
|
||||
const isMobile = userAgent.includes('android') || userAgent.includes('iphone')
|
||||
|
||||
const desktopViews = [
|
||||
{
|
||||
label: 'App',
|
||||
component: App,
|
||||
icon: 'i-codicon-hubot'
|
||||
},
|
||||
{
|
||||
label: 'Window',
|
||||
component: Window,
|
||||
icon: 'i-codicon-window'
|
||||
},
|
||||
{
|
||||
label: 'Menu',
|
||||
component: Menu,
|
||||
icon: 'i-ph-list'
|
||||
},
|
||||
{
|
||||
label: 'Tray',
|
||||
component: Tray,
|
||||
icon: 'i-ph-tray'
|
||||
}
|
||||
]
|
||||
|
||||
const views = [
|
||||
{
|
||||
label: 'Welcome',
|
||||
component: Welcome,
|
||||
icon: 'i-ph-hand-waving'
|
||||
},
|
||||
{
|
||||
label: 'Communication',
|
||||
component: Communication,
|
||||
icon: 'i-codicon-radio-tower'
|
||||
},
|
||||
...(isMobile ? [] : desktopViews),
|
||||
{
|
||||
label: 'WebRTC',
|
||||
component: WebRTC,
|
||||
icon: 'i-ph-broadcast'
|
||||
}
|
||||
]
|
||||
|
||||
let selected = $state.raw(views[0])
|
||||
|
||||
// dark/light themes
|
||||
const preferDark = new MediaQuery('prefers-color-scheme: dark')
|
||||
let theme = $state<Theme | 'auto'>(
|
||||
(localStorage.getItem('theme') as Theme | null) || 'auto'
|
||||
)
|
||||
|
||||
function switchTheme() {
|
||||
switch (theme) {
|
||||
case 'dark':
|
||||
theme = 'light'
|
||||
break
|
||||
case 'light':
|
||||
theme = 'auto'
|
||||
break
|
||||
case 'auto':
|
||||
theme = 'dark'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Follows the selection, or the system when it is `auto`.
|
||||
$effect(() => {
|
||||
const isDark = theme === 'auto' ? preferDark.current : theme === 'dark'
|
||||
document.documentElement.classList.toggle('dark', isDark)
|
||||
document.documentElement.classList.toggle('light', !isDark)
|
||||
})
|
||||
|
||||
// Only the selection drives the app-wide theme. Reading `preferDark` here would
|
||||
// make a system (or externally applied) theme change run this again and reset the
|
||||
// app back to `auto`, undoing whatever just set it.
|
||||
$effect(() => {
|
||||
setTheme(theme === 'auto' ? null : theme)
|
||||
localStorage.setItem('theme', theme)
|
||||
})
|
||||
|
||||
// Console
|
||||
const messages = writable<string[]>([])
|
||||
let consoleTextEl: HTMLDivElement
|
||||
|
||||
// this function is renders HTML without sanitizing it so it's insecure
|
||||
// we only use it with our own input data
|
||||
async function insecureRenderHtml(html: string) {
|
||||
messages.update((r) => [
|
||||
...r,
|
||||
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ${html}</pre>`
|
||||
])
|
||||
await tick()
|
||||
consoleTextEl.scrollTop = consoleTextEl.scrollHeight
|
||||
}
|
||||
|
||||
async function onMessage(value: unknown) {
|
||||
const valueStr =
|
||||
typeof value === 'string'
|
||||
? value
|
||||
: JSON.stringify(
|
||||
value instanceof ArrayBuffer
|
||||
? Array.from(new Uint8Array(value))
|
||||
: value,
|
||||
null,
|
||||
1
|
||||
)
|
||||
insecureRenderHtml(valueStr)
|
||||
}
|
||||
|
||||
function clear() {
|
||||
messages.update(() => [])
|
||||
}
|
||||
|
||||
let consoleEl: HTMLDivElement
|
||||
let consoleH = 0
|
||||
let cStartY = 0
|
||||
const minConsoleHeight = 50
|
||||
function startResizingConsole(e: MouseEvent) {
|
||||
cStartY = e.clientY
|
||||
|
||||
const styles = window.getComputedStyle(consoleEl)
|
||||
consoleH = parseInt(styles.height, 10)
|
||||
|
||||
const moveHandler = (e: MouseEvent) => {
|
||||
const dy = e.clientY - cStartY
|
||||
const newH = consoleH - dy
|
||||
consoleEl.style.height = `${newH < minConsoleHeight ? minConsoleHeight : newH}px`
|
||||
}
|
||||
const upHandler = () => {
|
||||
document.removeEventListener('mouseup', upHandler)
|
||||
document.removeEventListener('mousemove', moveHandler)
|
||||
}
|
||||
document.addEventListener('mouseup', upHandler)
|
||||
document.addEventListener('mousemove', moveHandler)
|
||||
}
|
||||
|
||||
// mobile
|
||||
let isSideBarOpen = $state(false)
|
||||
let sidebar: HTMLElement
|
||||
let sidebarToggle: HTMLElement
|
||||
let isDraggingSideBar = false
|
||||
let draggingStartPosX = 0
|
||||
let draggingEndPosX = 0
|
||||
const clamp = (min: number, num: number, max: number) =>
|
||||
Math.min(Math.max(num, min), max)
|
||||
|
||||
function toggleSidebar() {
|
||||
sidebar.style.setProperty(
|
||||
'--translate-x',
|
||||
`${isSideBarOpen ? '0' : '-18.75'}rem`
|
||||
)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
document.addEventListener('click', (e) => {
|
||||
if (!(e.target instanceof Node)) return
|
||||
|
||||
if (sidebarToggle.contains(e.target)) {
|
||||
isSideBarOpen = !isSideBarOpen
|
||||
} else if (isSideBarOpen && !sidebar.contains(e.target)) {
|
||||
isSideBarOpen = false
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('touchstart', (e) => {
|
||||
if (!(e.target instanceof Node)) return
|
||||
if (sidebarToggle.contains(e.target)) return
|
||||
|
||||
const x = e.touches[0].clientX
|
||||
if ((0 < x && x < 20 && !isSideBarOpen) || isSideBarOpen) {
|
||||
isDraggingSideBar = true
|
||||
draggingStartPosX = x
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('touchmove', (e) => {
|
||||
if (isDraggingSideBar) {
|
||||
const x = e.touches[0].clientX
|
||||
draggingEndPosX = x
|
||||
const delta = (x - draggingStartPosX) / 10
|
||||
sidebar.style.setProperty(
|
||||
'--translate-x',
|
||||
`-${clamp(0, isSideBarOpen ? 0 - delta : 18.75 - delta, 18.75)}rem`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
document.addEventListener('touchend', () => {
|
||||
if (isDraggingSideBar) {
|
||||
const delta = (draggingEndPosX - draggingStartPosX) / 10
|
||||
isSideBarOpen = isSideBarOpen ? delta > -(18.75 / 2) : delta > 18.75 / 2
|
||||
}
|
||||
|
||||
isDraggingSideBar = false
|
||||
})
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
toggleSidebar()
|
||||
})
|
||||
</script>
|
||||
|
||||
<!-- Sidebar toggle, only visible on small screens -->
|
||||
<div
|
||||
id="sidebarToggle"
|
||||
bind:this={sidebarToggle}
|
||||
class="z-2000 hidden lt-sm:flex justify-center absolute items-center w-8 h-8 rd-8
|
||||
bg-accent dark:bg-darkAccent active:bg-accentDark dark:active:bg-darkAccentDark text-accentText dark:text-darkAccentText"
|
||||
>
|
||||
{#if isSideBarOpen}
|
||||
<span class="i-codicon-close animate-duration-300ms animate-fade-in"></span>
|
||||
{:else}
|
||||
<span class="i-codicon-menu animate-duration-300ms animate-fade-in"></span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex h-screen w-screen overflow-hidden text-primaryText dark:text-darkPrimaryText"
|
||||
>
|
||||
<aside
|
||||
id="sidebar"
|
||||
bind:this={sidebar}
|
||||
class="lt-sm:h-screen lt-sm:shadow-lg lt-sm:shadow lt-sm:transition-transform lt-sm:absolute lt-sm:z-1999
|
||||
bg-darkPrimaryLighter transition-colors-250 overflow-hidden grid grid-rows-[min-content_auto] content-start select-none px-2"
|
||||
>
|
||||
<img
|
||||
class="self-center p-7 cursor-pointer"
|
||||
src="tauri_logo.png"
|
||||
alt="Tauri logo"
|
||||
/>
|
||||
<a href="##" class="nv justify-between" onclick={switchTheme}>
|
||||
{#if theme === 'auto'}
|
||||
Switch to Dark mode
|
||||
<div class="i-ph-circle-half-fill"></div>
|
||||
{:else if theme === 'dark'}
|
||||
Switch to Light mode
|
||||
<div class="i-ph-moon"></div>
|
||||
{:else if theme === 'light'}
|
||||
Switch to Auto mode
|
||||
<div class="i-ph-sun"></div>
|
||||
{/if}
|
||||
</a>
|
||||
<br />
|
||||
<div class="bg-white/5 h-2px"></div>
|
||||
<br />
|
||||
|
||||
<a
|
||||
class="nv justify-between"
|
||||
target="_blank"
|
||||
href="https://v2.tauri.app/start/"
|
||||
>
|
||||
Documentation
|
||||
<span class="i-codicon-link-external"></span>
|
||||
</a>
|
||||
<a
|
||||
class="nv justify-between"
|
||||
target="_blank"
|
||||
href="https://github.com/tauri-apps/tauri"
|
||||
>
|
||||
GitHub
|
||||
<span class="i-codicon-link-external"></span>
|
||||
</a>
|
||||
<a
|
||||
class="nv justify-between"
|
||||
target="_blank"
|
||||
href="https://github.com/tauri-apps/tauri/tree/dev/examples/api"
|
||||
>
|
||||
Source
|
||||
<span class="i-codicon-link-external"></span>
|
||||
</a>
|
||||
<br />
|
||||
<div class="bg-white/5 h-2px"></div>
|
||||
<br />
|
||||
<div class="flex flex-col overflow-y-auto children-flex-none gap-1">
|
||||
{#each views as view}
|
||||
<a
|
||||
href="##"
|
||||
class="nv {selected === view ? 'nv_selected' : ''}"
|
||||
onclick={() => {
|
||||
selected = view
|
||||
isSideBarOpen = false
|
||||
}}
|
||||
>
|
||||
<div class="{view.icon} mr-2"></div>
|
||||
<p>{view.label}</p>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</aside>
|
||||
<main
|
||||
class="flex-1 bg-primary dark:bg-darkPrimary transition-transform transition-colors-250 grid grid-rows-[2fr_auto]"
|
||||
>
|
||||
<div class="px-5 overflow-hidden grid grid-rows-[auto_1fr]">
|
||||
<h1>{selected.label}</h1>
|
||||
<div class="overflow-y-auto">
|
||||
<div class="mr-2">
|
||||
<selected.component {onMessage} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
bind:this={consoleEl}
|
||||
id="console"
|
||||
class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden"
|
||||
>
|
||||
<div
|
||||
role="button"
|
||||
tabindex="0"
|
||||
onmousedown={startResizingConsole}
|
||||
class="bg-black/20 h-4px cursor-ns-resize"
|
||||
></div>
|
||||
<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
|
||||
"
|
||||
onkeypress={(e) => (e.key === 'Enter' ? clear() : {})}
|
||||
onclick={clear}
|
||||
>
|
||||
<div class="i-codicon-clear-all"></div>
|
||||
</div>
|
||||
</div>
|
||||
<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 messageHtml}
|
||||
{@html messageHtml}
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
86
examples/api/src/app.css
Normal file
@@ -0,0 +1,86 @@
|
||||
:root {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
}
|
||||
:root.light {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
*:not(h1, h2, h3, h4, h5, h6) {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
font-family: 'Rubik', sans-serif;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0.25rem;
|
||||
height: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0.05rem 0.25rem;
|
||||
}
|
||||
|
||||
code.code-block {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
width: 18.75rem;
|
||||
}
|
||||
|
||||
/* let window effects (vibrancy / Liquid Glass) show through the whole page */
|
||||
:root.window-effects #sidebar,
|
||||
:root.window-effects main {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media screen and (width < 640px) {
|
||||
#sidebar {
|
||||
--translate-x: -18.75rem;
|
||||
transform: translateX(var(--translate-x));
|
||||
}
|
||||
}
|
||||
|
||||
#sidebar,
|
||||
main {
|
||||
padding-top: calc(env(safe-area-inset-top) + 1rem);
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
padding-left: calc(env(safe-area-inset-left) + 0.5rem);
|
||||
}
|
||||
|
||||
main {
|
||||
padding-right: env(safe-area-inset-right);
|
||||
}
|
||||
|
||||
#sidebarToggle {
|
||||
margin-top: calc(env(safe-area-inset-top) + 0.5rem);
|
||||
margin-left: calc(env(safe-area-inset-left) + 0.5rem);
|
||||
}
|
||||
|
||||
#sidebar,
|
||||
#console {
|
||||
padding-bottom: calc(env(safe-area-inset-bottom) + 1.5rem);
|
||||
}
|
||||
293
examples/api/src/components/MenuBuilder.svelte
Normal file
@@ -0,0 +1,293 @@
|
||||
<script module lang="ts">
|
||||
import {
|
||||
CheckMenuItem,
|
||||
IconMenuItem,
|
||||
Menu,
|
||||
MenuItem,
|
||||
PredefinedMenuItem,
|
||||
Submenu,
|
||||
type PredefinedMenuItemOptions
|
||||
} from '@tauri-apps/api/menu'
|
||||
|
||||
export type MenuOptions = {
|
||||
id: number
|
||||
kind: MenuItemComponentKind
|
||||
text: string | undefined
|
||||
iconPath: string | undefined
|
||||
checked: boolean | undefined
|
||||
}
|
||||
|
||||
export type Item = MenuOptions & {
|
||||
menu?: MenuItems
|
||||
}
|
||||
|
||||
export type MenuItems =
|
||||
| MenuItem
|
||||
| IconMenuItem
|
||||
| CheckMenuItem
|
||||
| PredefinedMenuItem
|
||||
|
||||
export type MenuItemClickDetail = {
|
||||
id: string
|
||||
text: string
|
||||
}
|
||||
export type MenuItemClickHandler = (detail: MenuItemClickDetail) => void
|
||||
|
||||
export async function reorderMenuItems(
|
||||
menu: Menu | Submenu,
|
||||
reorderItem: Item,
|
||||
toIndex: number
|
||||
) {
|
||||
if (!reorderItem.menu) {
|
||||
return
|
||||
}
|
||||
await menu.remove(reorderItem.menu)
|
||||
menu.insert(reorderItem.menu, toIndex)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import Sortable from 'sortablejs'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import MenuItemComponent, {
|
||||
type MenuItemComponentKind
|
||||
} from './MenuItemComponent.svelte'
|
||||
|
||||
type PredefinedItem = PredefinedMenuItemOptions['item']
|
||||
|
||||
let {
|
||||
items = $bindable(),
|
||||
itemClick,
|
||||
onItemAdded,
|
||||
onItemRemoved,
|
||||
onItemMoved
|
||||
}: {
|
||||
items: Item[]
|
||||
itemClick: MenuItemClickHandler
|
||||
onItemAdded?: (item: Item) => Promise<void>
|
||||
onItemRemoved?: (item: Item) => Promise<void>
|
||||
onItemMoved?: (item: Item, toIndex: number) => void
|
||||
} = $props()
|
||||
|
||||
const predefinedOptions: PredefinedItem[] = [
|
||||
'Separator',
|
||||
'Copy',
|
||||
'Cut',
|
||||
'Paste',
|
||||
'SelectAll',
|
||||
'Undo',
|
||||
'Redo',
|
||||
'Minimize',
|
||||
'Maximize',
|
||||
'Fullscreen',
|
||||
'Hide',
|
||||
'HideOthers',
|
||||
'ShowAll',
|
||||
'CloseWindow',
|
||||
'Quit',
|
||||
'Services',
|
||||
'BringAllToFront'
|
||||
]
|
||||
|
||||
async function create(options: MenuOptions) {
|
||||
let { kind, text, iconPath, checked } = options
|
||||
text ??= ''
|
||||
|
||||
switch (kind) {
|
||||
case 'Normal': {
|
||||
return await MenuItem.new({
|
||||
text,
|
||||
action: (id) => {
|
||||
const item = items.find((item) => item.id === options.id)
|
||||
itemClick({ id, text: item?.text ?? text })
|
||||
}
|
||||
})
|
||||
}
|
||||
case 'Icon': {
|
||||
return await IconMenuItem.new({
|
||||
text,
|
||||
icon: iconPath,
|
||||
action: (id) => {
|
||||
const item = items.find((item) => item.id === options.id)
|
||||
itemClick({ id, text: item?.text ?? text })
|
||||
}
|
||||
})
|
||||
}
|
||||
case 'Check': {
|
||||
const checkItem = await CheckMenuItem.new({
|
||||
text,
|
||||
checked,
|
||||
action: async (id) => {
|
||||
const item = items.find((item) => item.id === options.id)
|
||||
itemClick({ id, text: item?.text ?? text })
|
||||
if (item) {
|
||||
item.checked = await checkItem.isChecked()
|
||||
}
|
||||
}
|
||||
})
|
||||
return checkItem
|
||||
}
|
||||
default: {
|
||||
return await PredefinedMenuItem.new({
|
||||
item: kind
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let currentId = 0
|
||||
|
||||
let sourceSortableEl1: HTMLElement
|
||||
let sourceSortableEl2: HTMLElement
|
||||
let targetSortableEl: HTMLElement
|
||||
|
||||
function makeSourceSortable(sourceSortableEl: HTMLElement) {
|
||||
return new Sortable(sourceSortableEl, {
|
||||
draggable: 'div.menu-item',
|
||||
group: {
|
||||
name: 'shared',
|
||||
pull: 'clone',
|
||||
put: false
|
||||
},
|
||||
sort: false,
|
||||
delayOnTouchOnly: true,
|
||||
delay: 500
|
||||
})
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const sourceSortable1 = makeSourceSortable(sourceSortableEl1)
|
||||
const sourceSortable2 = makeSourceSortable(sourceSortableEl2)
|
||||
const targetSortable = new Sortable(targetSortableEl, {
|
||||
dataIdAttr: 'data-id',
|
||||
group: {
|
||||
name: 'shared'
|
||||
},
|
||||
async onAdd(event) {
|
||||
const item = event.item
|
||||
const kind = item.dataset.kind as MenuItemComponentKind
|
||||
const text = item.dataset.text
|
||||
const iconPath = item.dataset.iconPath
|
||||
const checked = item.dataset.checked
|
||||
|
||||
const newItem = {
|
||||
id: currentId,
|
||||
kind,
|
||||
text,
|
||||
iconPath,
|
||||
checked: checked !== undefined ? checked === 'true' : checked
|
||||
} as Item
|
||||
currentId += 1
|
||||
|
||||
// HACK: We can't track the element created by Sortable,
|
||||
// just make a new one and delete the one from Sortable
|
||||
item.remove()
|
||||
|
||||
if (event.newIndex !== undefined) {
|
||||
items.splice(event.newIndex, 0, newItem)
|
||||
} else {
|
||||
items.push(newItem)
|
||||
}
|
||||
|
||||
newItem.menu = await create(newItem)
|
||||
|
||||
await onItemAdded?.(newItem)
|
||||
},
|
||||
onUpdate(event) {
|
||||
const [item] = items.splice(event.oldIndex!, 1)
|
||||
// Sortable's DOM manipulation doesn't work with Svelte,
|
||||
// so we recreate all the nodes on order changes...
|
||||
// This must be `toSpliced` to trigger the `#key` block re-render
|
||||
items = items.toSpliced(event.newIndex!, 0, item!)
|
||||
|
||||
onItemMoved?.(item, event.newIndex!)
|
||||
},
|
||||
delayOnTouchOnly: true,
|
||||
delay: 500,
|
||||
animation: 200
|
||||
})
|
||||
return () => {
|
||||
targetSortable.destroy()
|
||||
sourceSortable1.destroy()
|
||||
sourceSortable2.destroy()
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
for (const item of items) {
|
||||
item.menu?.close()
|
||||
}
|
||||
items = []
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="grid gap-4 max-w-5xl">
|
||||
<div class="grid gap-2">
|
||||
<div>
|
||||
<h3>Custom</h3>
|
||||
<div class="flex flex-wrap gap-2" bind:this={sourceSortableEl1}>
|
||||
<MenuItemComponent kind="Normal" text="A menu item" />
|
||||
<MenuItemComponent
|
||||
kind="Icon"
|
||||
text="Menu item with icon"
|
||||
iconPath="../../.icons/icon.png"
|
||||
/>
|
||||
<MenuItemComponent kind="Check" text="A check menu" checked={false} />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Predefined</h3>
|
||||
<div class="flex flex-wrap gap-2" bind:this={sourceSortableEl2}>
|
||||
{#each predefinedOptions as predefinedOption}
|
||||
<MenuItemComponent kind={predefinedOption} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-1px bg-neutral/20 m-inline-4"></div>
|
||||
<div
|
||||
bind:this={targetSortableEl}
|
||||
class="p-2 border border-solid border-neutral-300 dark:border-neutral-700 rounded-md min-h-24 grid items-start gap-1"
|
||||
>
|
||||
{#key items}
|
||||
{#each items as item, i (item.id)}
|
||||
<MenuItemComponent
|
||||
kind={item.kind}
|
||||
id={item.id}
|
||||
bind:text={item.text}
|
||||
bind:iconPath={item.iconPath}
|
||||
bind:checked={item.checked}
|
||||
onTextChange={item.text !== undefined
|
||||
? () => {
|
||||
item.menu?.setText(item.text ?? '')
|
||||
}
|
||||
: undefined}
|
||||
onIconPathChange={item.iconPath !== undefined
|
||||
? () => {
|
||||
;(item.menu as IconMenuItem | undefined)?.setIcon(
|
||||
item.iconPath!
|
||||
)
|
||||
}
|
||||
: undefined}
|
||||
onCheckedChange={item.checked !== undefined
|
||||
? () => {
|
||||
;(item.menu as CheckMenuItem | undefined)?.setChecked(
|
||||
item.checked!
|
||||
)
|
||||
}
|
||||
: undefined}
|
||||
onRemove={async () => {
|
||||
items.splice(i, 1)
|
||||
await onItemRemoved?.(item).finally(() => {
|
||||
item.menu?.close()
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{:else}
|
||||
<div class="place-self-center color-secondaryText">
|
||||
Drag and drop the menu items above to start building
|
||||
</div>
|
||||
{/each}
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
79
examples/api/src/components/MenuItemComponent.svelte
Normal file
@@ -0,0 +1,79 @@
|
||||
<script module lang="ts">
|
||||
import type { PredefinedMenuItemOptions } from '@tauri-apps/api/menu'
|
||||
|
||||
type PredefinedItem = PredefinedMenuItemOptions['item']
|
||||
export type MenuItemComponentKind =
|
||||
| PredefinedItem
|
||||
| 'Normal'
|
||||
| 'Icon'
|
||||
| 'Check'
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
let {
|
||||
id,
|
||||
kind,
|
||||
text = $bindable(),
|
||||
iconPath = $bindable(),
|
||||
checked = $bindable(),
|
||||
onTextChange,
|
||||
onIconPathChange,
|
||||
onCheckedChange,
|
||||
onRemove
|
||||
}: {
|
||||
id?: number
|
||||
kind: MenuItemComponentKind
|
||||
text?: string
|
||||
iconPath?: string
|
||||
checked?: boolean
|
||||
onTextChange?: () => void
|
||||
onIconPathChange?: () => void
|
||||
onCheckedChange?: () => void
|
||||
onRemove?: () => void
|
||||
} = $props()
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center gap-2 border border-solid border-neutral-200 dark:border-none bg-neutral-100 dark:bg-neutral-700 p-inline-2 p-block-1 rounded-md select-none menu-item"
|
||||
data-kind={kind}
|
||||
data-text={text}
|
||||
data-icon-path={iconPath}
|
||||
data-checked={checked}
|
||||
data-id={id}
|
||||
>
|
||||
{kind}
|
||||
{#if text !== undefined}
|
||||
<input
|
||||
type="text"
|
||||
title="text"
|
||||
placeholder="text"
|
||||
class="border border-solid dark:bg-neutral-800 dark:color-neutral-100 border-neutral-200 dark:border-neutral-700 focus-visible:border-accent outline-none p-inline-2 p-block-1 rounded-1"
|
||||
bind:value={text}
|
||||
onchange={onTextChange}
|
||||
/>
|
||||
{/if}
|
||||
{#if iconPath !== undefined}
|
||||
<input
|
||||
type="text"
|
||||
title="icon path"
|
||||
placeholder="icon path"
|
||||
class="border border-solid dark:bg-neutral-800 dark:color-neutral-100 border-neutral-200 dark:border-neutral-700 focus-visible:border-accent outline-none p-inline-2 p-block-1 rounded-1"
|
||||
bind:value={iconPath}
|
||||
onchange={onIconPathChange}
|
||||
/>
|
||||
{/if}
|
||||
{#if checked !== undefined}
|
||||
<input
|
||||
type="checkbox"
|
||||
title="checked"
|
||||
bind:checked
|
||||
onchange={onCheckedChange}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if onRemove !== undefined}
|
||||
<button onclick={onRemove} title="remove" class="btn m-l-auto">
|
||||
<div class="i-ph-trash"></div>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
14
examples/api/src/main.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import 'uno.css'
|
||||
import './app.css'
|
||||
import App from './App.svelte'
|
||||
import { mount } from 'svelte'
|
||||
|
||||
const app = mount(App, {
|
||||
target: document.querySelector('#app')!
|
||||
})
|
||||
|
||||
export default app
|
||||
43
examples/api/src/views/App.svelte
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { show, hide, setDockVisibility } from '@tauri-apps/api/app'
|
||||
import type { ViewProps } from '../App.svelte'
|
||||
|
||||
let { onMessage }: ViewProps = $props()
|
||||
let dockVisible = $state(true)
|
||||
|
||||
function showApp() {
|
||||
hideApp()
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
show()
|
||||
.then(() => onMessage('Shown app'))
|
||||
.catch(onMessage)
|
||||
}, 2000)
|
||||
})
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function hideApp() {
|
||||
return hide()
|
||||
.then(() => onMessage('Hide app'))
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
async function toggleDockVisibility() {
|
||||
await setDockVisibility(!dockVisible)
|
||||
dockVisible = !dockVisible
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button
|
||||
class="btn"
|
||||
id="show"
|
||||
title="Hides and shows the app after 2 seconds"
|
||||
onclick={showApp}>Show</button
|
||||
>
|
||||
<button class="btn" id="hide" onclick={hideApp}>Hide</button>
|
||||
<button class="btn" id="toggle-dock-visibility" onclick={toggleDockVisibility}
|
||||
>Toggle dock visibility</button
|
||||
>
|
||||
</div>
|
||||
70
examples/api/src/views/Communication.svelte
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import { Channel, invoke } from '@tauri-apps/api/core'
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import type { UnlistenFn } from '@tauri-apps/api/event'
|
||||
import type { ViewProps } from '../App.svelte'
|
||||
|
||||
let { onMessage }: ViewProps = $props()
|
||||
|
||||
const webviewWindow = getCurrentWebviewWindow()
|
||||
|
||||
let unlisten: UnlistenFn | undefined
|
||||
onMount(async () => {
|
||||
unlisten = await webviewWindow.listen('rust-event', onMessage)
|
||||
})
|
||||
onDestroy(() => {
|
||||
unlisten?.()
|
||||
})
|
||||
|
||||
function log() {
|
||||
invoke('log_operation', {
|
||||
event: 'tauri-click',
|
||||
payload: 'this payload is optional because we used Option in Rust'
|
||||
})
|
||||
}
|
||||
|
||||
function performRequest() {
|
||||
invoke('perform_request', {
|
||||
endpoint: 'dummy endpoint arg',
|
||||
body: {
|
||||
id: 5,
|
||||
name: 'test'
|
||||
}
|
||||
})
|
||||
.then(onMessage)
|
||||
.catch(onMessage)
|
||||
}
|
||||
|
||||
function echo() {
|
||||
invoke('echo', {
|
||||
message: 'Tauri JSON request!'
|
||||
})
|
||||
.then(onMessage)
|
||||
.catch(onMessage)
|
||||
|
||||
invoke('echo', [1, 2, 3]).then(onMessage).catch(onMessage)
|
||||
}
|
||||
|
||||
function spam() {
|
||||
const channel = new Channel()
|
||||
channel.onmessage = onMessage
|
||||
invoke('spam', { channel })
|
||||
}
|
||||
|
||||
function emitEvent() {
|
||||
webviewWindow.emit('js-event', 'this is the payload string')
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button class="btn" id="log" onclick={log}>Call Log API</button>
|
||||
<button class="btn" id="request" onclick={performRequest}>
|
||||
Call Request (async) API
|
||||
</button>
|
||||
<button class="btn" id="event" onclick={emitEvent}>
|
||||
Send event to Rust
|
||||
</button>
|
||||
<button class="btn" id="request" onclick={echo}>Echo</button>
|
||||
<button class="btn" id="request" onclick={spam}>Spam</button>
|
||||
</div>
|
||||
126
examples/api/src/views/Menu.svelte
Normal file
@@ -0,0 +1,126 @@
|
||||
<script lang="ts">
|
||||
import { Menu, Submenu, NativeIcon } from '@tauri-apps/api/menu'
|
||||
import MenuBuilder, {
|
||||
reorderMenuItems,
|
||||
type Item,
|
||||
type MenuItemClickDetail,
|
||||
type MenuItems
|
||||
} from '../components/MenuBuilder.svelte'
|
||||
import { defaultWindowIcon } from '@tauri-apps/api/app'
|
||||
import type { ViewProps } from '../App.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
import type { Image } from '@tauri-apps/api/image'
|
||||
|
||||
let { onMessage }: ViewProps = $props()
|
||||
let items = $state<Item[]>([])
|
||||
|
||||
let menu: Menu | undefined
|
||||
let popupMenu: Menu | undefined
|
||||
let submenu: Submenu | undefined
|
||||
|
||||
const macOS = navigator.userAgent.includes('Macintosh')
|
||||
|
||||
async function createSubmenu(): Promise<Submenu> {
|
||||
return await Submenu.new({
|
||||
text: 'app',
|
||||
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
|
||||
})
|
||||
}
|
||||
|
||||
async function createSubmenuWithNativeIcon(): Promise<Submenu> {
|
||||
return await Submenu.new({
|
||||
text: 'Submenu with NativeIcon',
|
||||
icon: NativeIcon.Folder,
|
||||
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
|
||||
})
|
||||
}
|
||||
|
||||
async function createSubmenuWithImageIcon(): Promise<Submenu> {
|
||||
let icon: Image | undefined
|
||||
try {
|
||||
icon = (await defaultWindowIcon())!
|
||||
return await Submenu.new({
|
||||
text: 'Submenu with Image',
|
||||
icon,
|
||||
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
|
||||
})
|
||||
} finally {
|
||||
icon?.close()
|
||||
}
|
||||
}
|
||||
|
||||
async function setMenu(newSubmenu: Submenu) {
|
||||
menu?.close()
|
||||
menu = undefined
|
||||
submenu?.close()
|
||||
submenu = newSubmenu
|
||||
|
||||
menu = await Menu.new({
|
||||
items: [submenu]
|
||||
})
|
||||
await (macOS ? menu.setAsAppMenu() : menu.setAsWindowMenu())
|
||||
}
|
||||
|
||||
async function create() {
|
||||
await setMenu(await createSubmenu())
|
||||
}
|
||||
|
||||
async function createWithNativeIcon() {
|
||||
await setMenu(await createSubmenuWithNativeIcon())
|
||||
}
|
||||
|
||||
async function createWithImageIcon() {
|
||||
await setMenu(await createSubmenuWithImageIcon())
|
||||
}
|
||||
|
||||
async function popup() {
|
||||
popupMenu?.close()
|
||||
popupMenu = undefined
|
||||
popupMenu = await Menu.new({
|
||||
items: items.map((i) => i.menu).filter(Boolean) as MenuItems[]
|
||||
})
|
||||
await popupMenu.popup()
|
||||
}
|
||||
|
||||
function onItemClick(detail: MenuItemClickDetail) {
|
||||
onMessage(`Item ${detail.text} clicked`)
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
menu?.close()
|
||||
submenu?.close()
|
||||
popupMenu?.close()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="grid gap-8 mb-4">
|
||||
<MenuBuilder
|
||||
bind:items
|
||||
itemClick={onItemClick}
|
||||
onItemAdded={async (item) => {
|
||||
if (item.menu) {
|
||||
await submenu?.append(item.menu)
|
||||
}
|
||||
}}
|
||||
onItemRemoved={async (item) => {
|
||||
if (item.menu) {
|
||||
await submenu?.remove(item.menu)
|
||||
}
|
||||
}}
|
||||
onItemMoved={(item, toIndex) => {
|
||||
if (submenu) {
|
||||
reorderMenuItems(submenu, item, toIndex)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<button class="btn" onclick={create}>Create menu</button>
|
||||
<button class="btn" onclick={createWithNativeIcon}
|
||||
>Create menu with NativeIcon</button
|
||||
>
|
||||
<button class="btn" onclick={createWithImageIcon}
|
||||
>Create menu with Image icon</button
|
||||
>
|
||||
<button class="btn" onclick={popup}>Popup</button>
|
||||
</div>
|
||||
</div>
|
||||
134
examples/api/src/views/Tray.svelte
Normal file
@@ -0,0 +1,134 @@
|
||||
<script lang="ts">
|
||||
import { TrayIcon } from '@tauri-apps/api/tray'
|
||||
import MenuBuilder, {
|
||||
reorderMenuItems,
|
||||
type Item,
|
||||
type MenuItemClickDetail,
|
||||
type MenuItems
|
||||
} from '../components/MenuBuilder.svelte'
|
||||
import { Menu } from '@tauri-apps/api/menu'
|
||||
import type { ViewProps } from '../App.svelte'
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
let { onMessage }: ViewProps = $props()
|
||||
|
||||
let icon = $state<string>('../../.icons/tray_icon.png')
|
||||
let tooltip = $state<string>()
|
||||
let title = $state<string>()
|
||||
let iconAsTemplate = $state(false)
|
||||
let menuOnLeftClick = $state(true)
|
||||
let menuItems = $state<Item[]>([])
|
||||
|
||||
let menu: Menu | undefined
|
||||
let tray = $state<TrayIcon | undefined>()
|
||||
|
||||
function onItemClick(detail: MenuItemClickDetail) {
|
||||
onMessage(`Item ${detail.text} clicked`)
|
||||
}
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
menu = await Menu.new({
|
||||
items: menuItems.map((i) => i.menu).filter(Boolean) as MenuItems[]
|
||||
})
|
||||
tray = await TrayIcon.new({
|
||||
icon,
|
||||
tooltip,
|
||||
title,
|
||||
iconAsTemplate,
|
||||
menuOnLeftClick,
|
||||
menu,
|
||||
action: (event) => onMessage(event)
|
||||
})
|
||||
} catch (error) {
|
||||
menu?.close()
|
||||
menu = undefined
|
||||
tray?.close()
|
||||
tray = undefined
|
||||
onMessage(error)
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
menu?.close()
|
||||
tray?.close()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="grid gap-8 mb-4">
|
||||
<MenuBuilder
|
||||
bind:items={menuItems}
|
||||
itemClick={onItemClick}
|
||||
onItemAdded={async (item) => {
|
||||
if (item.menu) {
|
||||
await menu?.append(item.menu)
|
||||
}
|
||||
}}
|
||||
onItemRemoved={async (item) => {
|
||||
if (item.menu) {
|
||||
await menu?.remove(item.menu)
|
||||
}
|
||||
}}
|
||||
onItemMoved={(item, toIndex) => {
|
||||
if (menu) {
|
||||
reorderMenuItems(menu, item, toIndex)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="flex items-center gap-8">
|
||||
<div class="grid gap-2 grid-cols-3 items-center">
|
||||
<input
|
||||
class="input grow"
|
||||
type="text"
|
||||
placeholder="Title"
|
||||
bind:value={title}
|
||||
/>
|
||||
|
||||
<input
|
||||
class="input grow"
|
||||
type="text"
|
||||
placeholder="Tooltip"
|
||||
bind:value={tooltip}
|
||||
/>
|
||||
|
||||
<label class="flex gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={menuOnLeftClick}
|
||||
/>
|
||||
Menu on left click
|
||||
</label>
|
||||
|
||||
<input
|
||||
class="input col-span-2"
|
||||
type="text"
|
||||
placeholder="Icon path"
|
||||
bind:value={icon}
|
||||
/>
|
||||
|
||||
<label class="flex gap-2">
|
||||
<input type="checkbox" class="checkbox" bind:checked={iconAsTemplate} />
|
||||
Icon as template
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
{#if tray}
|
||||
<button
|
||||
class="btn"
|
||||
onclick={() => {
|
||||
tray?.close()
|
||||
tray = undefined
|
||||
}}
|
||||
title="Remove the tray icon">Remove tray</button
|
||||
>
|
||||
{:else}
|
||||
<button class="btn" onclick={create} title="Creates the tray icon"
|
||||
>Create tray</button
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
72
examples/api/src/views/WebRTC.svelte
Normal file
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { onMount, onDestroy } from 'svelte'
|
||||
import type { ViewProps } from '../App.svelte'
|
||||
|
||||
let { onMessage }: ViewProps = $props()
|
||||
|
||||
let video: HTMLVideoElement
|
||||
let mediaStream: MediaStream | undefined
|
||||
|
||||
const constraints: MediaStreamConstraints = {
|
||||
audio: true,
|
||||
video: true
|
||||
}
|
||||
|
||||
function handleSuccess(stream: MediaStream) {
|
||||
const settings = stream.getTracks().map((track) => track.getSettings())
|
||||
onMessage(`Got streams: ${JSON.stringify(settings, null, 2)}`)
|
||||
const videoTracks = stream.getVideoTracks()
|
||||
onMessage(`Using video device: ${videoTracks[0]?.label ?? 'Unknown'}`)
|
||||
// @ts-expect-error
|
||||
window.stream = mediaStream // make variable available to browser console
|
||||
if (video) {
|
||||
video.srcObject = stream
|
||||
}
|
||||
}
|
||||
|
||||
function handleError(error: unknown) {
|
||||
if (!(error instanceof DOMException)) {
|
||||
onMessage(`getUserMedia error: ${error}`)
|
||||
return
|
||||
}
|
||||
|
||||
if (error.name === 'ConstraintNotSatisfiedError') {
|
||||
// const v = constraints.video
|
||||
// onMessage(
|
||||
// `The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`
|
||||
// )
|
||||
onMessage(
|
||||
`The constraints ${constraints} can not be satisified by your device.`
|
||||
)
|
||||
} else if (error.name === 'PermissionDeniedError') {
|
||||
onMessage(
|
||||
'Permissions have not been granted to use your camera and '
|
||||
+ 'microphone, you need to allow the page access to your devices in '
|
||||
+ 'order for the demo to work.'
|
||||
)
|
||||
}
|
||||
onMessage(`getUserMedia error: ${error}`)
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia(constraints)
|
||||
handleSuccess(stream)
|
||||
} catch (e) {
|
||||
handleError(e)
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
for (const track of mediaStream?.getTracks() ?? []) {
|
||||
track.stop()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="note-red grow">Not available for Linux</div>
|
||||
<video id="localVideo" autoplay playsinline bind:this={video}>
|
||||
<track kind="captions" />
|
||||
</video>
|
||||
</div>
|
||||
62
examples/api/src/views/Welcome.svelte
Normal file
@@ -0,0 +1,62 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import {
|
||||
getName,
|
||||
getVersion,
|
||||
getTauriVersion,
|
||||
getBundleType,
|
||||
exit
|
||||
} from '@tauri-apps/api/app'
|
||||
import type { ViewProps } from '../App.svelte'
|
||||
|
||||
let { onMessage }: ViewProps = $props()
|
||||
|
||||
let version = $state('1.0.0')
|
||||
let tauriVersion = $state('1.0.0')
|
||||
let appName = $state('Unknown')
|
||||
let bundleType = $state('Unknown')
|
||||
|
||||
getName().then((n) => {
|
||||
appName = n
|
||||
})
|
||||
getVersion().then((v) => {
|
||||
version = v
|
||||
})
|
||||
getTauriVersion().then((v) => {
|
||||
tauriVersion = v
|
||||
})
|
||||
getBundleType().then((b) => {
|
||||
if (b) {
|
||||
bundleType = b
|
||||
}
|
||||
})
|
||||
|
||||
function contextMenu() {
|
||||
invoke('plugin:app-menu|popup')
|
||||
}
|
||||
|
||||
function exitApp() {
|
||||
exit().catch(onMessage)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="grid gap-8 justify-items-start">
|
||||
<p>
|
||||
This is a demo of Tauri's API capabilities using the <code
|
||||
>@tauri-apps/api</code
|
||||
> package. It's used as the main validation app, serving as the test bed of our
|
||||
development process. In the future, this app will be used on Tauri's integration
|
||||
tests.
|
||||
</p>
|
||||
<pre>
|
||||
App name: <code>{appName}</code>
|
||||
App version: <code>{version}</code>
|
||||
Tauri version: <code>{tauriVersion}</code>
|
||||
Bundle type: <code>{bundleType}</code>
|
||||
</pre>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<button class="btn" onclick={contextMenu}>Context menu</button>
|
||||
<button class="btn" id="exit" onclick={exitApp}>Exit</button>
|
||||
</div>
|
||||
</div>
|
||||
996
examples/api/src/views/Window.svelte
Normal file
@@ -0,0 +1,996 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import {
|
||||
LogicalSize,
|
||||
UserAttentionType,
|
||||
PhysicalSize,
|
||||
PhysicalPosition,
|
||||
Effect,
|
||||
EffectState,
|
||||
ProgressBarStatus,
|
||||
availableMonitors
|
||||
} from '@tauri-apps/api/window'
|
||||
import { WebviewWindow } from '@tauri-apps/api/webviewWindow'
|
||||
import type {
|
||||
CursorIcon,
|
||||
Effects,
|
||||
Monitor,
|
||||
Theme
|
||||
} from '@tauri-apps/api/window'
|
||||
import type { UnlistenFn } from '@tauri-apps/api/event'
|
||||
import type { ViewProps } from '../App.svelte'
|
||||
|
||||
let { onMessage }: ViewProps = $props()
|
||||
|
||||
const webview = WebviewWindow.getCurrent()
|
||||
|
||||
const webviewMap = $state({
|
||||
[webview.label]: webview
|
||||
})
|
||||
let selectedWebviewLabel = $state(webview.label)
|
||||
let selectedWebview = $derived(webviewMap[selectedWebviewLabel])
|
||||
|
||||
let monitorMap = $state<Record<string, Monitor>>({})
|
||||
let selectedMonitor = $state('')
|
||||
|
||||
availableMonitors().then((monitors) => {
|
||||
monitorMap = Object.fromEntries(
|
||||
monitors.map((m, i) => [m.name ?? `Monitor ${i + 1}`, m])
|
||||
)
|
||||
})
|
||||
|
||||
let focusable = $state(true)
|
||||
|
||||
const cursorIconOptions: CursorIcon[] = [
|
||||
'default',
|
||||
'crosshair',
|
||||
'hand',
|
||||
'arrow',
|
||||
'move',
|
||||
'text',
|
||||
'wait',
|
||||
'help',
|
||||
'progress',
|
||||
// something cannot be done
|
||||
'notAllowed',
|
||||
'contextMenu',
|
||||
'cell',
|
||||
'verticalText',
|
||||
'alias',
|
||||
'copy',
|
||||
'noDrop',
|
||||
// something can be grabbed
|
||||
'grab',
|
||||
/// something is grabbed
|
||||
'grabbing',
|
||||
'allScroll',
|
||||
'zoomIn',
|
||||
'zoomOut',
|
||||
// edge is to be moved
|
||||
'eResize',
|
||||
'nResize',
|
||||
'neResize',
|
||||
'nwResize',
|
||||
'sResize',
|
||||
'seResize',
|
||||
'swResize',
|
||||
'wResize',
|
||||
'ewResize',
|
||||
'nsResize',
|
||||
'neswResize',
|
||||
'nwseResize',
|
||||
'colResize',
|
||||
'rowResize'
|
||||
]
|
||||
|
||||
const windowsEffects: Effect[] = [
|
||||
Effect.Mica,
|
||||
Effect.Blur,
|
||||
Effect.Acrylic,
|
||||
Effect.Tabbed,
|
||||
Effect.TabbedDark,
|
||||
Effect.TabbedLight
|
||||
]
|
||||
const isWindows = navigator.appVersion.includes('Windows')
|
||||
const isMacOS = navigator.appVersion.includes('Macintosh')
|
||||
const effectOptions: Effect[] = isWindows
|
||||
? windowsEffects
|
||||
: (Object.values(Effect) as Effect[]).filter(
|
||||
(e) => !windowsEffects.includes(e)
|
||||
)
|
||||
const effectStateOptions = Object.values(EffectState) as EffectState[]
|
||||
|
||||
const progressBarStatusOptions = Object.values(ProgressBarStatus)
|
||||
|
||||
let newWebviewLabel = $state<string>()
|
||||
|
||||
let resizable = $state(true)
|
||||
let maximizable = $state(true)
|
||||
let minimizable = $state(true)
|
||||
let closable = $state(true)
|
||||
let maximized = $state(false)
|
||||
let decorations = $state(true)
|
||||
let alwaysOnTop = $state(false)
|
||||
let alwaysOnBottom = $state(false)
|
||||
let contentProtected = $state(false)
|
||||
let fullscreen = $state(false)
|
||||
let simpleFullscreen = $state(false)
|
||||
let width = $state(100)
|
||||
let height = $state(100)
|
||||
let minWidth = $state<number | null>(null)
|
||||
let minHeight = $state<number | null>(null)
|
||||
let maxWidth = $state<number | null>(null)
|
||||
let maxHeight = $state<number | null>(null)
|
||||
let x = $state(0)
|
||||
let y = $state(0)
|
||||
let scaleFactor = $state(1)
|
||||
let innerPosition = $state(new PhysicalPosition(0, 0))
|
||||
let outerPosition = $state(new PhysicalPosition(0, 0))
|
||||
let innerSize = $state(new PhysicalSize(0, 0))
|
||||
let outerSize = $state(new PhysicalSize(0, 0))
|
||||
// name of the monitor containing the selected window's top-left corner
|
||||
let monitor = $derived(
|
||||
Object.entries(monitorMap).find(
|
||||
([, m]) =>
|
||||
outerPosition.x >= m.position.x
|
||||
&& outerPosition.x < m.position.x + m.size.width
|
||||
&& outerPosition.y >= m.position.y
|
||||
&& outerPosition.y < m.position.y + m.size.height
|
||||
)?.[0] ?? ''
|
||||
)
|
||||
let resizeEventUnlisten: UnlistenFn | undefined
|
||||
let moveEventUnlisten: UnlistenFn | undefined
|
||||
let cursorGrab = $state(false)
|
||||
let cursorVisible = $state(true)
|
||||
let cursorX = $state<number | null>(null)
|
||||
let cursorY = $state<number | null>(null)
|
||||
let cursorIcon = $state<CursorIcon>('default')
|
||||
let cursorIgnoreEvents = $state(false)
|
||||
let windowTitle = $state('Awesome Tauri Example!')
|
||||
|
||||
let theme = $state<Theme | 'auto'>('auto')
|
||||
|
||||
let effects = $state<Effect[]>([])
|
||||
let selectedEffect = $state<Effect>()
|
||||
let effectState = $state<EffectState>()
|
||||
let effectRadius = $state<number>()
|
||||
let effectInteractive = $state(false)
|
||||
let effectR = $state<number>(),
|
||||
effectG = $state<number>(),
|
||||
effectB = $state<number>(),
|
||||
effectA = $state<number>()
|
||||
|
||||
let selectedProgressBarStatus = $state<ProgressBarStatus>(
|
||||
ProgressBarStatus.None
|
||||
)
|
||||
let progress = $state(0)
|
||||
|
||||
let windowIconPath = $state('')
|
||||
|
||||
function setTitle() {
|
||||
selectedWebview.setTitle(windowTitle)
|
||||
}
|
||||
|
||||
async function hide() {
|
||||
let visible = await selectedWebview.isVisible()
|
||||
onMessage('window is ' + (visible ? 'visible' : 'invisible'))
|
||||
await selectedWebview.hide()
|
||||
|
||||
setTimeout(async () => {
|
||||
visible = await selectedWebview.isVisible()
|
||||
onMessage('window is ' + (visible ? 'visible' : 'invisible'))
|
||||
|
||||
await selectedWebview.show()
|
||||
visible = await selectedWebview.isVisible()
|
||||
onMessage('window is ' + (visible ? 'visible' : 'invisible'))
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
async function disable() {
|
||||
let enabled = await selectedWebview.isEnabled()
|
||||
onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
|
||||
|
||||
await selectedWebview.setEnabled(false)
|
||||
|
||||
setTimeout(async () => {
|
||||
enabled = await selectedWebview.isEnabled()
|
||||
onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
|
||||
|
||||
await selectedWebview.setEnabled(true)
|
||||
enabled = await selectedWebview.isEnabled()
|
||||
onMessage('window is ' + (enabled ? 'enabled' : 'disabled'))
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
function minimize() {
|
||||
selectedWebview.minimize()
|
||||
setTimeout(selectedWebview.unminimize, 2000)
|
||||
}
|
||||
|
||||
function changeIcon() {
|
||||
selectedWebview.setIcon(windowIconPath)
|
||||
}
|
||||
|
||||
function createWebviewWindow() {
|
||||
if (!newWebviewLabel) return
|
||||
|
||||
const label = `main-${newWebviewLabel}`
|
||||
const webview = new WebviewWindow(label)
|
||||
webviewMap[label] = webview
|
||||
webview.once('tauri://error', function (e) {
|
||||
onMessage('Error creating new webview ' + JSON.stringify(e))
|
||||
})
|
||||
webview.once('tauri://created', function () {
|
||||
onMessage('webview created')
|
||||
})
|
||||
}
|
||||
|
||||
function loadWindowSize() {
|
||||
selectedWebview.innerSize().then((response) => {
|
||||
innerSize = response
|
||||
width = innerSize.width
|
||||
height = innerSize.height
|
||||
})
|
||||
selectedWebview.outerSize().then((response) => {
|
||||
outerSize = response
|
||||
})
|
||||
}
|
||||
|
||||
function loadWindowPosition() {
|
||||
selectedWebview.innerPosition().then((response) => {
|
||||
innerPosition = response
|
||||
})
|
||||
selectedWebview.outerPosition().then((response) => {
|
||||
outerPosition = response
|
||||
x = outerPosition.x
|
||||
y = outerPosition.y
|
||||
})
|
||||
}
|
||||
|
||||
async function addWindowEventListeners(window: WebviewWindow | undefined) {
|
||||
if (!window) return
|
||||
resizeEventUnlisten?.()
|
||||
moveEventUnlisten?.()
|
||||
moveEventUnlisten = await window.listen('tauri://move', loadWindowPosition)
|
||||
resizeEventUnlisten = await window.listen('tauri://resize', loadWindowSize)
|
||||
}
|
||||
|
||||
async function requestUserAttention() {
|
||||
await selectedWebview.minimize()
|
||||
await selectedWebview.requestUserAttention(UserAttentionType.Critical)
|
||||
await new Promise((resolve) => setTimeout(resolve, 3000))
|
||||
await selectedWebview.requestUserAttention(null)
|
||||
}
|
||||
|
||||
async function switchTheme() {
|
||||
switch (theme) {
|
||||
case 'dark':
|
||||
theme = 'light'
|
||||
break
|
||||
case 'light':
|
||||
theme = 'auto'
|
||||
break
|
||||
case 'auto':
|
||||
theme = 'dark'
|
||||
break
|
||||
}
|
||||
await selectedWebview.setTheme(theme === 'auto' ? null : theme)
|
||||
}
|
||||
|
||||
async function updateProgressBar() {
|
||||
selectedWebview.setProgressBar({
|
||||
status: selectedProgressBarStatus,
|
||||
progress
|
||||
})
|
||||
}
|
||||
|
||||
async function addEffect() {
|
||||
if (selectedEffect && !effects.includes(selectedEffect)) {
|
||||
effects = [...effects, selectedEffect]
|
||||
}
|
||||
|
||||
const payload: Effects = {
|
||||
effects,
|
||||
state: effectState,
|
||||
radius: effectRadius,
|
||||
interactive: effectInteractive
|
||||
}
|
||||
if (
|
||||
Number.isInteger(effectR)
|
||||
&& Number.isInteger(effectG)
|
||||
&& Number.isInteger(effectB)
|
||||
&& Number.isInteger(effectA)
|
||||
) {
|
||||
payload.color = [effectR!, effectG!, effectB!, effectA!]
|
||||
}
|
||||
|
||||
// see `:root.window-effects` in app.css
|
||||
document.documentElement.classList.add('window-effects')
|
||||
await selectedWebview.clearEffects()
|
||||
await selectedWebview.setEffects(payload)
|
||||
}
|
||||
|
||||
async function clearEffects() {
|
||||
effects = []
|
||||
await selectedWebview.clearEffects()
|
||||
document.documentElement.classList.remove('window-effects')
|
||||
}
|
||||
|
||||
async function updatePosition() {
|
||||
selectedWebview.setPosition(new PhysicalPosition(x, y))
|
||||
}
|
||||
|
||||
async function updateSize() {
|
||||
if (width && height) {
|
||||
selectedWebview.setSize(new PhysicalSize(width, height))
|
||||
}
|
||||
}
|
||||
|
||||
function refreshSelectedWebview() {
|
||||
loadWindowPosition()
|
||||
loadWindowSize()
|
||||
selectedWebview.scaleFactor().then((factor) => (scaleFactor = factor))
|
||||
addWindowEventListeners(selectedWebview)
|
||||
}
|
||||
|
||||
function updateResizable() {
|
||||
selectedWebview.setResizable(resizable)
|
||||
}
|
||||
|
||||
function updateMaximizable() {
|
||||
selectedWebview.setMaximizable(maximizable)
|
||||
}
|
||||
|
||||
function updateMinimizable() {
|
||||
selectedWebview.setMinimizable(minimizable)
|
||||
}
|
||||
|
||||
function updateClosable() {
|
||||
selectedWebview.setClosable(closable)
|
||||
}
|
||||
|
||||
function updateMaximized() {
|
||||
maximized ? selectedWebview.maximize() : selectedWebview.unmaximize()
|
||||
}
|
||||
|
||||
function updateDecorations() {
|
||||
selectedWebview.setDecorations(decorations)
|
||||
}
|
||||
|
||||
function updateAlwaysOnTop() {
|
||||
selectedWebview.setAlwaysOnTop(alwaysOnTop)
|
||||
}
|
||||
|
||||
function updateAlwaysOnBottom() {
|
||||
selectedWebview.setAlwaysOnBottom(alwaysOnBottom)
|
||||
}
|
||||
|
||||
function updateContentProtected() {
|
||||
selectedWebview.setContentProtected(contentProtected)
|
||||
}
|
||||
|
||||
function updateFullscreen() {
|
||||
selectedWebview.setFullscreen(fullscreen)
|
||||
}
|
||||
|
||||
function updateSimpleFullscreen() {
|
||||
selectedWebview.setSimpleFullscreen(simpleFullscreen)
|
||||
}
|
||||
|
||||
async function setFullscreenOnMonitor() {
|
||||
const target = monitorMap[selectedMonitor]
|
||||
if (!target) return
|
||||
await selectedWebview.setFullscreenOnMonitor(target.position)
|
||||
fullscreen = true
|
||||
}
|
||||
|
||||
function updateMinSize() {
|
||||
minWidth && minHeight
|
||||
? selectedWebview.setMinSize(new LogicalSize(minWidth, minHeight))
|
||||
: selectedWebview.setMinSize(null)
|
||||
}
|
||||
|
||||
function updateMaxSize() {
|
||||
maxWidth && maxHeight && maxWidth > 800 && maxHeight > 400
|
||||
? selectedWebview.setMaxSize(new LogicalSize(maxWidth, maxHeight))
|
||||
: selectedWebview.setMaxSize(null)
|
||||
}
|
||||
|
||||
function updateCursorGrab() {
|
||||
selectedWebview.setCursorGrab(cursorGrab)
|
||||
}
|
||||
|
||||
function updateCursorVisible() {
|
||||
selectedWebview.setCursorVisible(cursorVisible)
|
||||
}
|
||||
|
||||
function updateCursorIgnoreEvents() {
|
||||
selectedWebview.setIgnoreCursorEvents(cursorIgnoreEvents)
|
||||
}
|
||||
|
||||
function updateCursorIcon() {
|
||||
selectedWebview.setCursorIcon(cursorIcon)
|
||||
}
|
||||
|
||||
function updateCursorPosition() {
|
||||
cursorX !== null
|
||||
&& cursorY !== null
|
||||
&& selectedWebview.setCursorPosition(
|
||||
new PhysicalPosition(cursorX, cursorY)
|
||||
)
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
selectedWebview
|
||||
refreshSelectedWebview()
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
resizeEventUnlisten?.()
|
||||
moveEventUnlisten?.()
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col *:grow gap-8 mb-4">
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-4 pb-6 border-b-solid border-b-1 border-code"
|
||||
>
|
||||
{#if Object.keys(webviewMap).length >= 1}
|
||||
<div class="grid gap-1">
|
||||
<h4 class="my-2">Selected Window</h4>
|
||||
<select
|
||||
class="input"
|
||||
bind:value={selectedWebviewLabel}
|
||||
onchange={refreshSelectedWebview}
|
||||
>
|
||||
<option value="" disabled selected>Choose a window...</option>
|
||||
{#each Object.keys(webviewMap) as label}
|
||||
<option value={label}>{label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="grid gap-1">
|
||||
<h4 class="my-2">Create New Window</h4>
|
||||
<form
|
||||
class="flex gap-2"
|
||||
onsubmit={(ev) => {
|
||||
createWebviewWindow()
|
||||
ev.preventDefault()
|
||||
}}
|
||||
>
|
||||
<input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="New window label.."
|
||||
bind:value={newWebviewLabel}
|
||||
/>
|
||||
<button class="btn" type="submit">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{#if selectedWebview}
|
||||
<div class="flex flex-wrap items-center gap-4">
|
||||
<div class="grid gap-1 grow">
|
||||
<h4 class="my-2">Change Window Icon</h4>
|
||||
<form
|
||||
class="flex gap-2"
|
||||
onsubmit={(ev) => {
|
||||
changeIcon()
|
||||
ev.preventDefault()
|
||||
}}
|
||||
>
|
||||
<input
|
||||
class="input flex-1 min-w-10"
|
||||
placeholder="Window icon path"
|
||||
bind:value={windowIconPath}
|
||||
/>
|
||||
<button class="btn" type="submit">Change</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="grid gap-1 grow">
|
||||
<h4 class="my-2">Set Window Title</h4>
|
||||
<form
|
||||
class="flex gap-2"
|
||||
onsubmit={(ev) => {
|
||||
setTitle()
|
||||
ev.preventDefault()
|
||||
}}
|
||||
>
|
||||
<input class="input flex-1 min-w-10" bind:value={windowTitle} />
|
||||
<button class="btn" type="submit">Set</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<button
|
||||
class="btn"
|
||||
title="Unminimizes after 2 seconds"
|
||||
onclick={() => selectedWebview.center()}
|
||||
>
|
||||
Center
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
title="Unminimizes after 2 seconds"
|
||||
onclick={minimize}
|
||||
>
|
||||
Minimize
|
||||
</button>
|
||||
<button class="btn" title="Visible again after 2 seconds" onclick={hide}>
|
||||
Hide
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
title="Enabled again after 2 seconds"
|
||||
onclick={disable}
|
||||
>
|
||||
Disable
|
||||
</button>
|
||||
<button
|
||||
class="btn"
|
||||
onclick={requestUserAttention}
|
||||
title="Minimizes the window, requests attention for 3s and then resets it"
|
||||
>Request attention</button
|
||||
>
|
||||
<button class="btn" onclick={switchTheme}>Switch Theme ({theme})</button>
|
||||
<button
|
||||
class="btn"
|
||||
onclick={() => {
|
||||
focusable = !focusable
|
||||
selectedWebview.setFocusable(!focusable)
|
||||
}}
|
||||
>
|
||||
Set focusable to {!focusable}
|
||||
</button>
|
||||
|
||||
<form
|
||||
class="flex gap-2"
|
||||
onsubmit={(ev) => {
|
||||
setFullscreenOnMonitor()
|
||||
ev.preventDefault()
|
||||
}}
|
||||
>
|
||||
<button class="btn" type="submit" disabled={!selectedMonitor}>
|
||||
Set Fullscreen on Monitor
|
||||
</button>
|
||||
<select class="input" bind:value={selectedMonitor}>
|
||||
<option value="" disabled>Choose a monitor...</option>
|
||||
{#each Object.keys(monitorMap) as label}
|
||||
<option value={label}>{label}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</form>
|
||||
</div>
|
||||
<div class="grid cols-[repeat(auto-fill,minmax(180px,1fr))] *:flex *:gap-2">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={resizable}
|
||||
onchange={updateResizable}
|
||||
/>
|
||||
Resizable
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={maximizable}
|
||||
onchange={updateMaximizable}
|
||||
/>
|
||||
Maximizable
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={minimizable}
|
||||
onchange={updateMinimizable}
|
||||
/>
|
||||
Minimizable
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={closable}
|
||||
onchange={updateClosable}
|
||||
/>
|
||||
Closable
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={decorations}
|
||||
onchange={updateDecorations}
|
||||
/>
|
||||
Has decorations
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={alwaysOnTop}
|
||||
onchange={updateAlwaysOnTop}
|
||||
/>
|
||||
Always on top
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={alwaysOnBottom}
|
||||
onchange={updateAlwaysOnBottom}
|
||||
/>
|
||||
Always on bottom
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={contentProtected}
|
||||
onchange={updateContentProtected}
|
||||
/>
|
||||
Content protected
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={maximized}
|
||||
onchange={updateMaximized}
|
||||
/>
|
||||
Maximized
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={fullscreen}
|
||||
onchange={updateFullscreen}
|
||||
/>
|
||||
Fullscreen
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={simpleFullscreen}
|
||||
onchange={updateSimpleFullscreen}
|
||||
/>
|
||||
Simple fullscreen
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex flex-wrap *:flex-basis-30 gap-2">
|
||||
<div class="grid gap-1 *:grid">
|
||||
<label>
|
||||
X
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={x}
|
||||
onchange={updatePosition}
|
||||
min="0"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Y
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={y}
|
||||
onchange={updatePosition}
|
||||
min="0"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid gap-1 *:grid">
|
||||
<label>
|
||||
Width
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={width}
|
||||
onchange={updateSize}
|
||||
min="400"
|
||||
/>
|
||||
</label>
|
||||
<div>
|
||||
Height
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={height}
|
||||
onchange={updateSize}
|
||||
min="400"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-1 *:grid">
|
||||
<label>
|
||||
Min width
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={minWidth}
|
||||
onchange={updateMinSize}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Min height
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={minHeight}
|
||||
onchange={updateMinSize}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid gap-1 *:grid">
|
||||
<label>
|
||||
Max width
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={maxWidth}
|
||||
onchange={updateMaxSize}
|
||||
min="800"
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Max height
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={maxHeight}
|
||||
onchange={updateMaxSize}
|
||||
min="400"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2 max-inline-2xl">
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Inner Size
|
||||
</div>
|
||||
<span>Width: {innerSize.width}</span>
|
||||
<span>Height: {innerSize.height}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Outer Size
|
||||
</div>
|
||||
<span>Width: {outerSize.width}</span>
|
||||
<span>Height: {outerSize.height}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Inner Logical Size
|
||||
</div>
|
||||
<span>Width: {innerSize.toLogical(scaleFactor).width.toFixed(3)}</span>
|
||||
<span>Height: {innerSize.toLogical(scaleFactor).height.toFixed(3)}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Outer Logical Size
|
||||
</div>
|
||||
<span>Width: {outerSize.toLogical(scaleFactor).width.toFixed(3)}</span>
|
||||
<span>Height: {outerSize.toLogical(scaleFactor).height.toFixed(3)}</span
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Inner Position
|
||||
</div>
|
||||
<span>x: {innerPosition.x}</span>
|
||||
<span>y: {innerPosition.y}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Outer Position
|
||||
</div>
|
||||
<span>x: {outerPosition.x}</span>
|
||||
<span>y: {outerPosition.y}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Inner Logical Position
|
||||
</div>
|
||||
<span>x: {innerPosition.toLogical(scaleFactor).x.toFixed(3)}</span>
|
||||
<span>y: {innerPosition.toLogical(scaleFactor).y.toFixed(3)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Outer Logical Position
|
||||
</div>
|
||||
<span>x: {outerPosition.toLogical(scaleFactor).x.toFixed(3)}</span>
|
||||
<span>y: {outerPosition.toLogical(scaleFactor).y.toFixed(3)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-accent dark:text-darkAccent font-700 m-block-1">
|
||||
Current Monitor
|
||||
</div>
|
||||
<span>{monitor}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<h4 class="my-2">Cursor</h4>
|
||||
<div class="flex gap-4 *:flex *:gap-2">
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={cursorGrab}
|
||||
onchange={updateCursorGrab}
|
||||
/>
|
||||
Grab
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={cursorVisible}
|
||||
onchange={updateCursorVisible}
|
||||
/>
|
||||
Visible
|
||||
</label>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={cursorIgnoreEvents}
|
||||
onchange={updateCursorIgnoreEvents}
|
||||
/>
|
||||
Ignore events
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<label>
|
||||
Icon
|
||||
<select
|
||||
class="input"
|
||||
bind:value={cursorIcon}
|
||||
onchange={updateCursorIcon}
|
||||
>
|
||||
{#each cursorIconOptions as kind}
|
||||
<option value={kind}>{kind}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
X position
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={cursorX}
|
||||
onchange={updateCursorPosition}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Y position
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
bind:value={cursorY}
|
||||
onchange={updateCursorPosition}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex gap-2">
|
||||
<label>
|
||||
Progress Status
|
||||
<select
|
||||
class="input"
|
||||
bind:value={selectedProgressBarStatus}
|
||||
onchange={updateProgressBar}
|
||||
>
|
||||
{#each progressBarStatusOptions as status}
|
||||
<option value={status}>{status}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Progress
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
max="100"
|
||||
bind:value={progress}
|
||||
onchange={updateProgressBar}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if isWindows || isMacOS}
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<div>
|
||||
Applied effects: {effects.length ? effects.join(', ') : 'None'}
|
||||
</div>
|
||||
|
||||
<button class="btn" onclick={clearEffects}>Clear</button>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<label>
|
||||
Effect
|
||||
<select class="input" bind:value={selectedEffect}>
|
||||
{#each effectOptions as effect}
|
||||
<option value={effect}>{effect}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
State
|
||||
<select class="input" bind:value={effectState}>
|
||||
{#each effectStateOptions as state}
|
||||
<option value={state}>{state}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
Radius
|
||||
<input class="input" type="number" bind:value={effectRadius} />
|
||||
</label>
|
||||
|
||||
<label class="flex items-center gap-1">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="checkbox"
|
||||
bind:checked={effectInteractive}
|
||||
/>
|
||||
Interactive
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<label>
|
||||
Color
|
||||
<div class="flex gap-2 *:flex-basis-30">
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="R"
|
||||
bind:value={effectR}
|
||||
/>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="G"
|
||||
bind:value={effectG}
|
||||
/>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="B"
|
||||
bind:value={effectB}
|
||||
/>
|
||||
<input
|
||||
class="input"
|
||||
type="number"
|
||||
placeholder="A"
|
||||
bind:value={effectA}
|
||||
/>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
<button class="btn" onclick={addEffect}>Add</button>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
6
examples/api/src/vite-env.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
/// <reference types="svelte" />
|
||||
/// <reference types="vite/client" />
|
||||
7
examples/api/svelte.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs#compile-time-svelte-preprocess
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess()
|
||||
}
|
||||
15
examples/api/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"verbatimModuleSyntax": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.svelte"]
|
||||
}
|
||||
101
examples/api/unocss.config.js
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { defineConfig, presetIcons, presetWind3, presetWebFonts } from 'unocss'
|
||||
import extractorSvelte from '@unocss/extractor-svelte'
|
||||
|
||||
export default defineConfig({
|
||||
theme: {
|
||||
colors: {
|
||||
primary: '#FFFFFF',
|
||||
primaryLighter: '#e9ecef',
|
||||
darkPrimary: '#1B1B1D',
|
||||
darkPrimaryLighter: '#242526',
|
||||
primaryText: '#1C1E21',
|
||||
darkPrimaryText: '#E3E3E3',
|
||||
secondaryText: '#858A91',
|
||||
darkSecondaryText: '#C2C5CA',
|
||||
accent: '#3578E5',
|
||||
accentDark: '#306cce',
|
||||
accentDarker: '#2d66c3',
|
||||
accentDarkest: '#2554a0',
|
||||
accentLight: '#538ce9',
|
||||
accentLighter: '#72a1ed',
|
||||
accentLightest: '#9abcf2',
|
||||
accentText: '#FFFFFF',
|
||||
darkAccent: '#67d6ed',
|
||||
darkAccentDark: '#49cee9',
|
||||
darkAccentDarker: '#39cae8',
|
||||
darkAccentDarkest: '#19b5d5',
|
||||
darkAccentLight: '#85def1',
|
||||
darkAccentLighter: '#95e2f2',
|
||||
darkAccentLightest: '#c2eff8',
|
||||
darkAccentText: '#1C1E21',
|
||||
code: '#d6d8da',
|
||||
codeDark: '#282a2e',
|
||||
hoverOverlay: 'rgba(0,0,0,.05)',
|
||||
hoverOverlayDarker: 'rgba(0,0,0,.1)',
|
||||
darkHoverOverlay: 'hsla(0,0%,100%,.05)',
|
||||
darkHoverOverlayDarker: 'hsla(0,0%,100%,.1)'
|
||||
}
|
||||
},
|
||||
preflights: [
|
||||
{
|
||||
getCSS: ({ theme }) => `
|
||||
::-webkit-scrollbar-thumb {
|
||||
background-color: ${theme.colors.accent};
|
||||
}
|
||||
|
||||
.dark ::-webkit-scrollbar-thumb {
|
||||
background-color: ${theme.colors.darkAccent};
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: ${theme.fontSize.xs[0]};
|
||||
font-family: ${theme.fontFamily.mono};
|
||||
border-radius: ${theme.borderRadius['DEFAULT']};
|
||||
background-color: ${theme.colors.code};
|
||||
}
|
||||
|
||||
.code-block {
|
||||
font-family: ${theme.fontFamily.mono};
|
||||
font-size: ${theme.fontSize.sm[0]};
|
||||
}
|
||||
|
||||
.dark code {
|
||||
background-color: ${theme.colors.codeDark};
|
||||
}
|
||||
`
|
||||
}
|
||||
],
|
||||
shortcuts: {
|
||||
btn: `select-none outline-none shadow-md p-2 rd-1 text-primaryText border-none font-400 dark:font-600
|
||||
bg-accent hover:bg-accentDarker active:bg-accentDarkest text-accentText
|
||||
dark:bg-darkAccent dark:hover:bg-darkAccentDarker dark:active:bg-darkAccentDarkest dark:text-darkAccentText`,
|
||||
nv: `decoration-none flex items-center relative p-2 rd-1 transition-all-125 ease
|
||||
text-darkSecondaryText
|
||||
hover:text-accent dark:hover:text-darkAccent
|
||||
hover:bg-darkHoverOverlay hover:border-l-4`,
|
||||
nv_selected: `nv bg-darkHoverOverlay text-accent dark:text-darkAccent border-l-4`,
|
||||
note: `decoration-none flex-inline items-center relative p-2 rd-1
|
||||
border-l-4 border-accent dark:border-darkAccent
|
||||
bg-accent/10 dark:bg-darkAccent/10`,
|
||||
'note-red':
|
||||
'note bg-red-700/10 dark:bg-red-700/10 after:bg-red-700 dark:after:bg-red-700',
|
||||
input:
|
||||
'flex items-center outline-none border-none py-3 px-2 rd-1 shadow-md bg-primaryLighter dark:bg-darkPrimaryLighter text-primaryText dark:text-darkPrimaryText',
|
||||
checkbox: 'accent-accent'
|
||||
},
|
||||
presets: [
|
||||
presetWind3(),
|
||||
presetIcons(),
|
||||
presetWebFonts({
|
||||
fonts: {
|
||||
sans: 'Rubik',
|
||||
mono: ['Fira Code', 'Fira Mono:400,700']
|
||||
}
|
||||
})
|
||||
],
|
||||
extractors: [extractorSvelte]
|
||||
})
|
||||
44
examples/api/vite.config.js
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import Unocss from 'unocss/vite'
|
||||
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
||||
|
||||
const host = process.env.TAURI_DEV_HOST
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [Unocss(), svelte()],
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
entryFileNames: `assets/[name].js`,
|
||||
chunkFileNames: `assets/[name].js`,
|
||||
assetFileNames: `assets/[name].[ext]`
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Vite options 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: host ?? false,
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
hmr: host
|
||||
? {
|
||||
protocol: 'ws',
|
||||
host: host,
|
||||
port: 1430
|
||||
}
|
||||
: undefined,
|
||||
fs: {
|
||||
allow: ['.', '../../packages/api/dist']
|
||||
}
|
||||
}
|
||||
})
|
||||
5
examples/commands/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Commands Example
|
||||
|
||||
A simple Tauri Application showcasing the command API.
|
||||
|
||||
To execute run the following on the root directory of the repository: `cargo run --example commands`.
|
||||
32
examples/commands/commands.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2019-2024 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use tauri::{State, command};
|
||||
|
||||
#[command]
|
||||
pub fn cmd(_argument: String) {}
|
||||
|
||||
#[command]
|
||||
pub fn invoke(_argument: String) {}
|
||||
|
||||
#[command]
|
||||
pub fn message(_argument: String) {}
|
||||
|
||||
#[command]
|
||||
pub fn resolver(_argument: String) {}
|
||||
|
||||
#[command]
|
||||
pub fn simple_command(the_argument: String) {
|
||||
println!("{the_argument}");
|
||||
}
|
||||
|
||||
#[command]
|
||||
pub fn stateful_command(the_argument: Option<String>, state: State<'_, super::MyState>) {
|
||||
println!("{:?} {:?}", the_argument, *state);
|
||||
}
|
||||
|
||||
#[command(rename = "renamed_command_in_mod_new")]
|
||||
pub fn renamed_command_in_mod() {
|
||||
println!("renamed command in mod called");
|
||||
}
|
||||