feat: add API example (#317)

pull/343/head
Lucas Fernandes Nogueira 2 years ago committed by GitHub
parent be1c775b8d
commit 5015132ece
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,6 +1,6 @@
[workspace]
members = ["plugins/*"]
exclude = ["plugins/fs", "plugins/http"]
members = ["plugins/*", "examples/*/src-tauri"]
exclude = ["plugins/fs", "plugins/http", "examples/api/src-tauri"]
resolver = "2"
[workspace.dependencies]

@ -0,0 +1,4 @@
/node_modules/
/.vscode/
.DS_Store
.cargo

@ -0,0 +1,5 @@
#!/usr/bin/env bash
export ICONS_VOLUME="$(realpath icons)"
export DIST_VOLUME="$(realpath dist)"
export ISOLATION_VOLUME="$(realpath isolation-dist)"

@ -0,0 +1,3 @@
src-tauri/locales/
src-tauri/Cross.toml
src-tauri/.gitignore

@ -0,0 +1,28 @@
# API example
This example demonstrates Tauri's API capabilities using the plugins from this repository. 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.
![App screenshot](./screenshot.png?raw=true)
## Running the example
- Install dependencies and build packages (Run inside of the repository root)
```bash
$ pnpm install
$ pnpm build
```
- 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
$ ./src-tauri/target/release/app
```

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en" theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte + Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

@ -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>

@ -0,0 +1,7 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
window.__TAURI_ISOLATION_HOOK__ = (payload) => {
return payload;
};

@ -0,0 +1,34 @@
{
"compilerOptions": {
"moduleResolution": "node",
"target": "esnext",
"module": "esnext",
/**
* svelte-preprocess cannot figure out whether you have
* a value or a type, so tell TypeScript to enforce using
* `import type` instead of `import` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
* To have warnings / errors of the Svelte compiler at the
* correct position, enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
/**
* Typecheck JS in `.svelte` and `.js` files by default.
* Disable this if you'd like to use dynamic types.
*/
"checkJs": true
},
/**
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}

@ -0,0 +1,32 @@
{
"name": "svelte-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite --clearScreen false",
"build": "vite build",
"serve": "vite preview"
},
"dependencies": {
"@tauri-apps/api": "2.0.0-alpha.3",
"@tauri-apps/cli": "2.0.0-alpha.8",
"@zerodevx/svelte-json-view": "0.2.1",
"tauri-plugin-cli-api": "0.0.0",
"tauri-plugin-clipboard-api": "0.0.0",
"tauri-plugin-dialog-api": "0.0.0",
"tauri-plugin-fs-api": "0.0.0",
"tauri-plugin-global-shortcut-api": "0.0.0",
"tauri-plugin-http-api": "0.0.0",
"tauri-plugin-notification-api": "0.0.0",
"tauri-plugin-shell-api": "0.0.0"
},
"devDependencies": {
"@iconify-json/codicon": "^1.1.10",
"@iconify-json/ph": "^1.1.1",
"@sveltejs/vite-plugin-svelte": "^1.0.1",
"internal-ip": "^7.0.0",
"svelte": "^3.49.0",
"unocss": "^0.39.3",
"vite": "^3.0.9"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

@ -0,0 +1,6 @@
# Generated by Cargo
# will have compiled files and executables
/target/
# cargo-mobile
.cargo/

@ -0,0 +1 @@
tauri-plugin-sample/

File diff suppressed because it is too large Load Diff

@ -0,0 +1,60 @@
[package]
name = "api"
version = "0.1.0"
description = "An example Tauri Application showcasing the api"
edition = "2021"
rust-version = "1.64"
license = "Apache-2.0 OR MIT"
[lib]
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2.0.0-alpha.4", features = ["codegen", "isolation"] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = [ "derive" ] }
tiny_http = "0.11"
log = "0.4"
tauri-plugin-log = { path = "../../../plugins/log" }
tauri-plugin-fs = { path = "../../../plugins/fs" }
tauri-plugin-clipboard = { path = "../../../plugins/clipboard" }
tauri-plugin-dialog = { path = "../../../plugins/dialog" }
tauri-plugin-http = { path = "../../../plugins/http", features = [ "http-multipart" ] }
tauri-plugin-notification = { path = "../../../plugins/notification", features = [ "windows7-compat" ] }
tauri-plugin-shell = { path = "../../../plugins/shell" }
[patch.crates-io]
tauri = { git = "https://github.com/tauri-apps/tauri", branch = "next" }
tauri-build = { git = "https://github.com/tauri-apps/tauri", branch = "next" }
[dependencies.tauri]
version = "2.0.0-alpha.8"
features = [
"api-all",
"icon-ico",
"icon-png",
"isolation",
"macos-private-api",
"system-tray",
"updater"
]
[target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\", target_os = \"dragonfly\", target_os = \"freebsd\", target_os = \"openbsd\", target_os = \"netbsd\"))".dependencies]
tauri-plugin-cli = { path = "../../../plugins/cli" }
tauri-plugin-global-shortcut = { path = "../../../plugins/global-shortcut" }
[target."cfg(target_os = \"windows\")".dependencies]
window-shadows = "0.2"
[features]
custom-protocol = [ "tauri/custom-protocol" ]
# default to small, optimized release binaries
[profile.release]
panic = "abort"
codegen-units = 1
lto = true
incremental = false
opt-level = "s"

@ -0,0 +1,11 @@
[build.env]
# must set ICONS_VOLUME, DIST_VOLUME and ISOLATION_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
# this can be done running `$ . .setup-cross.sh` in the examples/api folder
volumes = ["ICONS_VOLUME", "DIST_VOLUME", "ISOLATION_VOLUME"]
[target.aarch64-unknown-linux-gnu]
image = "aarch64-unknown-linux-gnu:latest"
#image = "ghcr.io/tauri-apps/tauri/aarch64-unknown-linux-gnu:latest"

@ -0,0 +1,10 @@
<?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>
</dict>
</plist>

@ -0,0 +1,12 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
fn main() {
let mut codegen = tauri_build::CodegenContext::new();
if !cfg!(feature = "custom-protocol") {
codegen = codegen.dev();
}
codegen.build();
tauri_build::build();
}

@ -0,0 +1,12 @@
# EditorConfig is awesome: https://EditorConfig.org
# top-most EditorConfig file
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = false

@ -0,0 +1,18 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
build
/captures
.externalNativeBuild
.cxx
local.properties
/.tauri
/tauri.settings.gradle

@ -0,0 +1,4 @@
/src/main/java/com/tauri/api/generated
/src/main/jniLibs/**/*.so
/tauri.build.gradle.kts
/proguard-tauri.pro

@ -0,0 +1,113 @@
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("rustPlugin")
}
android {
compileSdk = 33
defaultConfig {
manifestPlaceholders["usesCleartextTraffic"] = "false"
applicationId = "com.tauri.api"
minSdk = 24
targetSdk = 33
versionCode = 1
versionName = "1.0"
}
sourceSets.getByName("main") {
// Vulkan validation layers
val ndkHome = System.getenv("NDK_HOME")
jniLibs.srcDir("${ndkHome}/sources/third_party/vulkan/src/build-android/jniLibs")
}
buildTypes {
getByName("debug") {
manifestPlaceholders["usesCleartextTraffic"] = "true"
isDebuggable = true
isJniDebuggable = true
isMinifyEnabled = false
packagingOptions { jniLibs.keepDebugSymbols.add("*/arm64-v8a/*.so")
jniLibs.keepDebugSymbols.add("*/armeabi-v7a/*.so")
jniLibs.keepDebugSymbols.add("*/x86/*.so")
jniLibs.keepDebugSymbols.add("*/x86_64/*.so")
}
}
getByName("release") {
isMinifyEnabled = true
val proguards = fileTree(".") {
include("*.pro")
}
proguardFiles(*proguards.toList().toTypedArray())
}
}
flavorDimensions.add("abi")
productFlavors {
create("universal") {
dimension = "abi"
ndk {
abiFilters += (findProperty("abiList") as? String)?.split(",") ?: listOf( "arm64-v8a", "armeabi-v7a", "x86", "x86_64",
)
}
}
create("arm64") {
dimension = "abi"
ndk {
abiFilters += listOf("arm64-v8a")
}
}
create("arm") {
dimension = "abi"
ndk {
abiFilters += listOf("armeabi-v7a")
}
}
create("x86") {
dimension = "abi"
ndk {
abiFilters += listOf("x86")
}
}
create("x86_64") {
dimension = "abi"
ndk {
abiFilters += listOf("x86_64")
}
}
}
assetPacks += mutableSetOf()
namespace = "com.tauri.api"
}
rust {
rootDirRel = "../../../../"
targets = (findProperty("targetList") as? String)?.split(",") ?: listOf("aarch64", "armv7", "i686", "x86_64")
arches = (findProperty("archList") as? String)?.split(",") ?: listOf("arm64", "arm", "x86", "x86_64")
}
dependencies {
implementation("androidx.webkit:webkit:1.5.0")
implementation("androidx.appcompat:appcompat:1.5.1")
implementation("com.google.android.material:material:1.7.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.4")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.0")
implementation(project(":tauri-android"))
}
apply(from = "tauri.build.gradle.kts")
afterEvaluate {
android.applicationVariants.all {
tasks["mergeUniversalReleaseJniLibFolders"].dependsOn(tasks["rustBuildRelease"])
tasks["mergeUniversalDebugJniLibFolders"].dependsOn(tasks["rustBuildDebug"])
if (findProperty("targetList") == null) {
productFlavors.filter{ it.name != "universal" }.forEach { _ ->
val archAndBuildType = name.capitalize()
tasks["merge${archAndBuildType}JniLibFolders"].dependsOn(tasks["rustBuild${archAndBuildType}"])
}
}
}
}

@ -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,32 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.api"
android:usesCleartextTraffic="${usesCleartextTraffic}">
<activity
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
android:launchMode="singleTask"
android:label="@string/main_activity_title"
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
</manifest>

@ -0,0 +1,7 @@
package com.tauri.api
import app.tauri.plugin.PluginManager
class MainActivity : TauriActivity() {
var pluginManager: PluginManager = PluginManager(this)
}

@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="85.84757"
android:endY="92.4963"
android:startX="42.9492"
android:startY="49.59793"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#00000000"
android:pathData="M9,0L9,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,0L19,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,0L29,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,0L39,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,0L49,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,0L59,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,0L69,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,0L79,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M89,0L89,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M99,0L99,108"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,9L108,9"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,19L108,19"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,29L108,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,39L108,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,49L108,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,59L108,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,69L108,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,79L108,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,89L108,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M0,99L108,99"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,29L89,29"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,39L89,39"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,49L89,49"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,59L89,59"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,69L89,69"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M19,79L89,79"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M29,19L29,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M39,19L39,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M49,19L49,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M59,19L59,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M69,19L69,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
<path
android:fillColor="#00000000"
android:pathData="M79,19L79,89"
android:strokeWidth="0.8"
android:strokeColor="#33FFFFFF" />
</vector>

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.api" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

@ -0,0 +1,4 @@
<resources>
<string name="app_name">Tauri API</string>
<string name="main_activity_title">Tauri API</string>
</resources>

@ -0,0 +1,16 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.api" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="." />
<cache-path name="my_cache_images" path="." />
</paths>

@ -0,0 +1,25 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
tasks.register("clean").configure {
delete("build")
}

@ -0,0 +1,23 @@
plugins {
`kotlin-dsl`
}
gradlePlugin {
plugins {
create("pluginsForCoolKids") {
id = "rustPlugin"
implementationClass = "com.tauri.RustPlugin"
}
}
}
repositories {
google()
mavenCentral()
}
dependencies {
compileOnly(gradleApi())
implementation("com.android.tools.build:gradle:7.3.1")
}

@ -0,0 +1,58 @@
package com.tauri
import java.io.File
import org.apache.tools.ant.taskdefs.condition.Os
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.logging.LogLevel
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction
open class BuildTask : DefaultTask() {
@InputDirectory
@PathSensitive(PathSensitivity.RELATIVE)
var rootDirRel: File? = null
@Input
var target: String? = null
@Input
var release: Boolean? = null
@TaskAction
fun build() {
val executable = """yarn""";
try {
runTauriCli(executable)
} catch (e: Exception) {
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
runTauriCli("$executable.cmd")
} else {
throw e;
}
}
}
fun runTauriCli(executable: String) {
val rootDirRel = rootDirRel ?: throw GradleException("rootDirRel cannot be null")
val target = target ?: throw GradleException("target cannot be null")
val release = release ?: throw GradleException("release cannot be null")
val args = listOf("tauri", "android", "android-studio-script");
project.exec {
workingDir(File(project.projectDir, rootDirRel.path))
executable(executable)
args(args)
if (project.logger.isEnabled(LogLevel.DEBUG)) {
args("-vv")
} else if (project.logger.isEnabled(LogLevel.INFO)) {
args("-v")
}
if (release) {
args("--release")
}
args(listOf("--target", target))
}.assertNormalExitValue()
}
}

@ -0,0 +1,59 @@
package com.tauri
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import java.io.File
import java.util.*
const val TASK_GROUP = "rust"
open class Config {
var rootDirRel: String? = null
var targets: List<String>? = null
var arches: List<String>? = null
}
open class RustPlugin : Plugin<Project> {
private lateinit var config: Config
override fun apply(project: Project) {
config = project.extensions.create("rust", Config::class.java)
project.afterEvaluate {
if (config.targets == null) {
throw GradleException("targets cannot be null")
}
if (config.arches == null) {
throw GradleException("arches cannot be null")
}
for (profile in listOf("debug", "release")) {
val profileCapitalized = profile.capitalize(Locale.ROOT)
val buildTask = project.tasks.maybeCreate(
"rustBuild$profileCapitalized",
DefaultTask::class.java
).apply {
group = TASK_GROUP
description = "Build dynamic library in $profile mode for all targets"
}
for (targetPair in config.targets!!.withIndex()) {
val targetName = targetPair.value
val targetArch = config.arches!![targetPair.index]
val targetArchCapitalized = targetArch.capitalize(Locale.ROOT)
val targetBuildTask = project.tasks.maybeCreate(
"rustBuild$targetArchCapitalized$profileCapitalized",
BuildTask::class.java
).apply {
group = TASK_GROUP
description = "Build dynamic library in $profile mode for $targetArch"
rootDirRel = config.rootDirRel?.let { File(it) }
target = targetName
release = profile == "release"
}
buildTask.dependsOn(targetBuildTask)
project.tasks.findByName("preBuild")?.mustRunAfter(targetBuildTask)
}
}
}
}
}

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

@ -0,0 +1,6 @@
#Tue May 10 19:22:52 CST 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -0,0 +1,6 @@
include ':app'
include ':tauri-android'
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
apply from: 'tauri.settings.gradle'

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1015 B

@ -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>

@ -0,0 +1,24 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use serde::Deserialize;
use tauri::command;
#[derive(Debug, Deserialize)]
#[allow(unused)]
pub struct RequestBody {
id: i32,
name: String,
}
#[command]
pub fn log_operation(event: String, payload: Option<String>) {
log::info!("{} {:?}", event, payload);
}
#[command]
pub fn perform_request(endpoint: String, body: RequestBody) -> String {
println!("{} {:?}", endpoint, body);
"message response".into()
}

@ -0,0 +1,139 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
mod cmd;
#[cfg(desktop)]
mod tray;
use serde::Serialize;
use tauri::{window::WindowBuilder, App, AppHandle, RunEvent, WindowUrl};
#[derive(Clone, Serialize)]
struct Reply {
data: String,
}
pub type SetupHook = Box<dyn FnOnce(&mut App) -> Result<(), Box<dyn std::error::Error>> + Send>;
pub type OnEvent = Box<dyn FnMut(&AppHandle, RunEvent)>;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
#[allow(unused_mut)]
let mut builder = tauri::Builder::default()
.plugin(
tauri_plugin_log::Builder::default()
.level(log::LevelFilter::Info)
.build(),
)
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_clipboard::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_http::init())
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_shell::init())
.setup(move |app| {
#[cfg(desktop)]
{
tray::create_tray(app)?;
app.handle().plugin(tauri_plugin_cli::init())?;
app.handle()
.plugin(tauri_plugin_global_shortcut::Builder::new().build())?;
}
let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default());
#[cfg(desktop)]
{
window_builder = window_builder
.user_agent("Tauri API")
.title("Tauri API Validation")
.inner_size(1000., 800.)
.min_inner_size(600., 400.)
.content_protected(true);
}
#[cfg(target_os = "windows")]
{
window_builder = window_builder
.transparent(true)
.shadow(true)
.decorations(false);
}
let window = window_builder.build().unwrap();
#[cfg(debug_assertions)]
window.open_devtools();
#[cfg(desktop)]
std::thread::spawn(|| {
let server = match tiny_http::Server::http("localhost:3003") {
Ok(s) => s,
Err(e) => {
eprintln!("{}", e);
std::process::exit(1);
}
};
loop {
if let Ok(mut request) = server.recv() {
let mut body = Vec::new();
let _ = request.as_reader().read_to_end(&mut body);
let response = tiny_http::Response::new(
tiny_http::StatusCode(200),
request.headers().to_vec(),
std::io::Cursor::new(body),
request.body_length(),
None,
);
let _ = request.respond(response);
}
}
});
Ok(())
})
.on_page_load(|window, _| {
let window_ = window.clone();
window.listen("js-event", move |event| {
println!("got js-event with message '{:?}'", event.payload());
let reply = Reply {
data: "something else".to_string(),
};
window_
.emit("rust-event", Some(reply))
.expect("failed to emit");
});
});
#[cfg(target_os = "macos")]
{
builder = builder.menu(tauri::Menu::os_default("Tauri API Validation"));
}
#[allow(unused_mut)]
let mut app = builder
.invoke_handler(tauri::generate_handler![
cmd::log_operation,
cmd::perform_request,
])
.build(tauri::tauri_build_context!())
.expect("error while building tauri application");
#[cfg(target_os = "macos")]
app.set_activation_policy(tauri::ActivationPolicy::Regular);
app.run(move |_app_handle, _event| {
#[cfg(desktop)]
if let RunEvent::ExitRequested { api, .. } = &_event {
// Keep the event loop running even if all windows are closed
// This allow us to catch system tray events when there is no window
api.prevent_exit();
}
})
}

@ -0,0 +1,11 @@
// Copyright 2019-2023 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() {
#[cfg(desktop)]
api::run();
}

@ -0,0 +1,143 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use std::sync::atomic::{AtomicBool, Ordering};
use tauri::{
CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, WindowBuilder, WindowUrl,
};
use tauri_plugin_dialog::DialogExt;
use tauri_plugin_shell::ShellExt;
pub fn create_tray(app: &tauri::App) -> tauri::Result<()> {
let mut tray_menu1 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("icon_1", "Tray Icon 1"))
.add_item(CustomMenuItem::new("icon_2", "Tray Icon 2"));
#[cfg(target_os = "macos")]
{
tray_menu1 = tray_menu1.add_item(CustomMenuItem::new("set_title", "Set Title"));
}
tray_menu1 = tray_menu1
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
.add_item(CustomMenuItem::new("about", "About"))
.add_item(CustomMenuItem::new("exit_app", "Quit"))
.add_item(CustomMenuItem::new("destroy", "Destroy"));
let tray_menu2 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
.add_item(CustomMenuItem::new("new", "New window"))
.add_item(CustomMenuItem::new("switch_menu", "Switch Menu"))
.add_item(CustomMenuItem::new("about", "About"))
.add_item(CustomMenuItem::new("exit_app", "Quit"))
.add_item(CustomMenuItem::new("destroy", "Destroy"));
let is_menu1 = AtomicBool::new(true);
let handle = app.handle();
let tray_id = "my-tray".to_string();
SystemTray::new()
.with_id(&tray_id)
.with_menu(tray_menu1.clone())
.with_tooltip("Tauri")
.on_event(move |event| {
let tray_handle = handle.tray_handle_by_id(&tray_id).unwrap();
match event {
SystemTrayEvent::LeftClick {
position: _,
size: _,
..
} => {
let window = handle.get_window("main").unwrap();
window.show().unwrap();
window.set_focus().unwrap();
}
SystemTrayEvent::MenuItemClick { id, .. } => {
let item_handle = tray_handle.get_item(&id);
match id.as_str() {
"exit_app" => {
// exit the app
handle.exit(0);
}
"destroy" => {
tray_handle.destroy().unwrap();
}
"toggle" => {
let window = handle.get_window("main").unwrap();
let new_title = if window.is_visible().unwrap() {
window.hide().unwrap();
"Show"
} else {
window.show().unwrap();
"Hide"
};
item_handle.set_title(new_title).unwrap();
}
"new" => {
WindowBuilder::new(&handle, "new", WindowUrl::App("index.html".into()))
.title("Tauri")
.build()
.unwrap();
}
"set_title" => {
#[cfg(target_os = "macos")]
tray_handle.set_title("Tauri").unwrap();
}
"icon_1" => {
#[cfg(target_os = "macos")]
tray_handle.set_icon_as_template(true).unwrap();
tray_handle
.set_icon(tauri::Icon::Raw(
include_bytes!("../icons/tray_icon_with_transparency.png")
.to_vec(),
))
.unwrap();
}
"icon_2" => {
#[cfg(target_os = "macos")]
tray_handle.set_icon_as_template(true).unwrap();
tray_handle
.set_icon(tauri::Icon::Raw(
include_bytes!("../icons/icon.ico").to_vec(),
))
.unwrap();
}
"switch_menu" => {
let flag = is_menu1.load(Ordering::Relaxed);
let (menu, tooltip) = if flag {
(tray_menu2.clone(), "Menu 2")
} else {
(tray_menu1.clone(), "Tauri")
};
tray_handle.set_menu(menu).unwrap();
tray_handle.set_tooltip(tooltip).unwrap();
is_menu1.store(!flag, Ordering::Relaxed);
}
"about" => {
let window = handle.get_window("main").unwrap();
window
.dialog()
.message("Tauri demo app")
.title("About app")
.parent(&window)
.ok_button_label("Homepage")
.cancel_button_label("Cancel")
.show(move |ok| {
if ok {
window.shell().open("https://tauri.app/", None).unwrap();
}
});
}
_ => {}
}
}
_ => {}
}
})
.build(app)
.map(|_| ())
}

@ -0,0 +1,147 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"build": {
"distDir": "../dist",
"devPath": "http://localhost:5173",
"beforeDevCommand": "yarn dev",
"beforeBuildCommand": "yarn build",
"withGlobalTauri": true
},
"package": {
"productName": "Tauri API",
"version": "2.0.0"
},
"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"
}
]
}
}
}
},
"tauri": {
"pattern": {
"use": "isolation",
"options": {
"dir": "../isolation-dist/"
}
},
"macOSPrivateApi": true,
"bundle": {
"active": true,
"identifier": "com.tauri.api",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"windows": {
"wix": {
"language": {
"en-US": {},
"pt-BR": {
"localePath": "locales/pt-BR.wxl"
}
}
}
}
},
"updater": {
"active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDE5QzMxNjYwNTM5OEUwNTgKUldSWTRKaFRZQmJER1h4d1ZMYVA3dnluSjdpN2RmMldJR09hUFFlZDY0SlFqckkvRUJhZDJVZXAK",
"endpoints": [
"https://tauri-update-server.vercel.app/update/{{target}}/{{current_version}}"
]
},
"allowlist": {
"all": true,
"fs": {
"scope": {
"allow": ["$APPDATA/db/**", "$DOWNLOAD/**", "$RESOURCE/**"],
"deny": ["$APPDATA/db/*.stronghold"]
}
},
"shell": {
"open": true,
"scope": [
{
"name": "sh",
"cmd": "sh",
"args": [
"-c",
{
"validator": "\\S+"
}
]
},
{
"name": "cmd",
"cmd": "cmd",
"args": [
"/C",
{
"validator": "\\S+"
}
]
}
]
},
"protocol": {
"asset": true,
"assetScope": {
"allow": ["$APPDATA/db/**", "$RESOURCE/**"],
"deny": ["$APPDATA/db/*.stronghold"]
}
},
"http": {
"scope": ["http://localhost:3003"]
}
},
"windows": [],
"security": {
"csp": {
"default-src": "'self' customprotocol: asset:",
"font-src": ["https://fonts.gstatic.com"],
"img-src": "'self' asset: https://asset.localhost blob: data:",
"style-src": "'unsafe-inline' 'self' https://fonts.googleapis.com"
},
"freezePrototype": true
},
"systemTray": {
"iconPath": "icons/tray_icon_with_transparency.png",
"iconAsTemplate": true,
"menuOnLeftClick": false
}
}
}

@ -0,0 +1,485 @@
<script>
import { writable } from 'svelte/store'
import { open } from 'tauri-plugin-shell-api'
import { appWindow, getCurrent } from '@tauri-apps/api/window'
import * as os from '@tauri-apps/api/os'
import Welcome from './views/Welcome.svelte'
import Cli from './views/Cli.svelte'
import Communication from './views/Communication.svelte'
import Dialog from './views/Dialog.svelte'
import FileSystem from './views/FileSystem.svelte'
import Http from './views/Http.svelte'
import Notifications from './views/Notifications.svelte'
import Window from './views/Window.svelte'
import Shortcuts from './views/Shortcuts.svelte'
import Shell from './views/Shell.svelte'
import Updater from './views/Updater.svelte'
import Clipboard from './views/Clipboard.svelte'
import WebRTC from './views/WebRTC.svelte'
import App from './views/App.svelte'
import { onMount } from 'svelte'
import { listen } from '@tauri-apps/api/event'
import { ask } from 'tauri-plugin-dialog-api'
if (appWindow.label !== 'main') {
appWindow.onCloseRequested(async (event) => {
const confirmed = await confirm('Are you sure?')
if (!confirmed) {
// user did not confirm closing the window; let's prevent it
event.preventDefault()
}
})
}
appWindow.onFileDropEvent((event) => {
onMessage(`File drop: ${JSON.stringify(event.payload)}`)
})
const userAgent = navigator.userAgent.toLowerCase()
const isMobile = userAgent.includes('android') || userAgent.includes('iphone')
const views = [
{
label: 'Welcome',
component: Welcome,
icon: 'i-ph-hand-waving'
},
{
label: 'Communication',
component: Communication,
icon: 'i-codicon-radio-tower'
},
!isMobile && {
label: 'CLI',
component: Cli,
icon: 'i-codicon-terminal'
},
!isMobile && {
label: 'Dialog',
component: Dialog,
icon: 'i-codicon-multiple-windows'
},
{
label: 'File system',
component: FileSystem,
icon: 'i-codicon-files'
},
{
label: 'HTTP',
component: Http,
icon: 'i-ph-globe-hemisphere-west'
},
!isMobile && {
label: 'Notifications',
component: Notifications,
icon: 'i-codicon-bell-dot'
},
!isMobile && {
label: 'App',
component: App,
icon: 'i-codicon-hubot'
},
!isMobile && {
label: 'Window',
component: Window,
icon: 'i-codicon-window'
},
!isMobile && {
label: 'Shortcuts',
component: Shortcuts,
icon: 'i-codicon-record-keys'
},
{
label: 'Shell',
component: Shell,
icon: 'i-codicon-terminal-bash'
},
!isMobile && {
label: 'Updater',
component: Updater,
icon: 'i-codicon-cloud-download'
},
!isMobile && {
label: 'Clipboard',
component: Clipboard,
icon: 'i-codicon-clippy'
},
{
label: 'WebRTC',
component: WebRTC,
icon: 'i-ph-broadcast'
}
]
let selected = views[0]
function select(view) {
selected = view
}
// Window controls
let isWindowMaximized
onMount(async () => {
const window = getCurrent()
isWindowMaximized = await window.isMaximized()
listen('tauri://resize', async () => {
isWindowMaximized = await window.isMaximized()
})
})
function minimize() {
getCurrent().minimize()
}
async function toggleMaximize() {
const window = getCurrent()
;(await window.isMaximized()) ? window.unmaximize() : window.maximize()
}
let confirmed_close = false
async function close() {
if (!confirmed_close) {
confirmed_close = await ask(
'Are you sure that you want to close this window?',
{
title: 'Tauri API'
}
)
if (confirmed_close) {
getCurrent().close()
}
}
}
// dark/light
let isDark
onMount(() => {
isDark = localStorage && localStorage.getItem('theme') == 'dark'
applyTheme(isDark)
})
function applyTheme(isDark) {
const html = document.querySelector('html')
isDark ? html.classList.add('dark') : html.classList.remove('dark')
localStorage && localStorage.setItem('theme', isDark ? 'dark' : '')
}
function toggleDark() {
isDark = !isDark
applyTheme(isDark)
}
// Console
let messages = writable([])
function onMessage(value) {
messages.update((r) => [
{
html:
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
(typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
'</pre>'
},
...r
])
}
// this function is renders HTML without sanitizing it so it's insecure
// we only use it with our own input data
function insecureRenderHtml(html) {
messages.update((r) => [
{
html:
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
html +
'</pre>'
},
...r
])
}
function clear() {
messages.update(() => [])
}
let consoleEl, consoleH, cStartY
let minConsoleHeight = 50
function startResizingConsole(e) {
cStartY = e.clientY
const styles = window.getComputedStyle(consoleEl)
consoleH = parseInt(styles.height, 10)
const moveHandler = (e) => {
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)
}
let isWindows
onMount(async () => {
isWindows = (await os.platform()) === 'win32'
})
// mobile
let isSideBarOpen = false
let sidebar
let sidebarToggle
let isDraggingSideBar = false
let draggingStartPosX = 0
let draggingEndPosX = 0
const clamp = (min, num, max) => Math.min(Math.max(num, min), max)
function toggleSidebar(sidebar, isSideBarOpen) {
sidebar.style.setProperty(
'--translate-x',
`${isSideBarOpen ? '0' : '-18.75'}rem`
)
}
onMount(() => {
sidebar = document.querySelector('#sidebar')
sidebarToggle = document.querySelector('#sidebarToggle')
document.addEventListener('click', (e) => {
if (sidebarToggle.contains(e.target)) {
isSideBarOpen = !isSideBarOpen
} else if (isSideBarOpen && !sidebar.contains(e.target)) {
isSideBarOpen = false
}
})
document.addEventListener('touchstart', (e) => {
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
})
})
$: {
const sidebar = document.querySelector('#sidebar')
if (sidebar) {
toggleSidebar(sidebar, isSideBarOpen)
}
}
</script>
<!-- custom titlebar for Windows -->
{#if isWindows}
<div
class="w-screen select-none h-8 pl-2 flex justify-between items-center absolute text-primaryText dark:text-darkPrimaryText"
data-tauri-drag-region
>
<span class="lt-sm:pl-10 text-darkPrimaryText">Tauri API Validation</span>
<span
class="
h-100%
children:h-100% children:w-12 children:inline-flex
children:items-center children:justify-center"
>
<span
title={isDark ? 'Switch to Light mode' : 'Switch to Dark mode'}
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={toggleDark}
>
{#if isDark}
<div class="i-ph-sun" />
{:else}
<div class="i-ph-moon" />
{/if}
</span>
<span
title="Minimize"
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={minimize}
>
<div class="i-codicon-chrome-minimize" />
</span>
<span
title={isWindowMaximized ? 'Restore' : 'Maximize'}
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={toggleMaximize}
>
{#if isWindowMaximized}
<div class="i-codicon-chrome-restore" />
{:else}
<div class="i-codicon-chrome-maximize" />
{/if}
</span>
<span
title="Close"
class="hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText "
on:click={close}
>
<div class="i-codicon-chrome-close" />
</span>
</span>
</div>
{/if}
<!-- Sidebar toggle, only visible on small screens -->
<div
id="sidebarToggle"
class="z-2000 display-none lt-sm:flex justify-center items-center absolute top-2 left-2 w-8 h-8 rd-8
bg-accent dark:bg-darkAccent active:bg-accentDark dark:active:bg-darkAccentDark"
>
{#if isSideBarOpen}
<span class="i-codicon-close animate-duration-300ms animate-fade-in" />
{:else}
<span class="i-codicon-menu animate-duration-300ms animate-fade-in" />
{/if}
</div>
<div
class="flex h-screen w-screen overflow-hidden children-pt8 children-pb-2 text-primaryText dark:text-darkPrimaryText"
>
<aside
id="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 select-none px-2"
>
<img
on:click={() => open('https://tauri.app/')}
class="self-center p-7 cursor-pointer"
src="tauri_logo.png"
alt="Tauri logo"
/>
{#if !isWindows}
<a href="##" class="nv justify-between h-8" on:click={toggleDark}>
{#if isDark}
Switch to Light mode
<div class="i-ph-sun" />
{:else}
Switch to Dark mode
<div class="i-ph-moon" />
{/if}
</a>
<br />
<div class="bg-white/5 h-2px" />
<br />
{/if}
<a
class="nv justify-between h-8"
target="_blank"
href="https://tauri.app/v1/guides/"
>
Documentation
<span class="i-codicon-link-external" />
</a>
<a
class="nv justify-between h-8"
target="_blank"
href="https://github.com/tauri-apps/tauri"
>
GitHub
<span class="i-codicon-link-external" />
</a>
<a
class="nv justify-between h-8"
target="_blank"
href="https://github.com/tauri-apps/tauri/tree/dev/examples/api"
>
Source
<span class="i-codicon-link-external" />
</a>
<br />
<div class="bg-white/5 h-2px" />
<br />
<div
class="flex flex-col overflow-y-auto children-h-10 children-flex-none gap-1"
>
{#each views as view}
{#if view}
<a
href="##"
class="nv {selected === view ? 'nv_selected' : ''}"
on:click={() => {
select(view)
isSideBarOpen = false
}}
>
<div class="{view.icon} mr-2" />
<p>{view.label}</p></a
>
{/if}
{/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">
<svelte:component
this={selected.component}
{onMessage}
{insecureRenderHtml}
/>
</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
on:mousedown={startResizingConsole}
class="bg-black/20 h-2px cursor-ns-resize"
/>
<div class="flex justify-between items-center px-2">
<p class="font-semibold">Console</p>
<div
class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
"
on:click={clear}
>
<div class="i-codicon-clear-all" />
</div>
</div>
<div class="px-2 overflow-y-auto all:font-mono code-block all:text-xs">
{#each $messages as r}
{@html r.html}
{/each}
</div>
</div>
</main>
</div>

@ -0,0 +1,41 @@
*: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;
}
@media screen and (max-width: 640px) {
#sidebar {
--translate-x: -18.75rem;
transform: translateX(var(--translate-x));
}
}

@ -0,0 +1,13 @@
// Copyright 2019-2023 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";
const app = new App({
target: document.querySelector("#app"),
});
export default app;

@ -0,0 +1,33 @@
<script>
import { show, hide } from '@tauri-apps/api/app'
export let onMessage
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)
}
</script>
<div>
<button
class="btn"
id="show"
title="Hides and shows the app after 2 seconds"
on:click={showApp}>Show</button
>
<button class="btn" id="hide" on:click={hideApp}>Hide</button>
</div>

@ -0,0 +1,29 @@
<script>
import { getMatches } from "tauri-plugin-cli-api";
export let onMessage;
function cliMatches() {
getMatches().then(onMessage).catch(onMessage);
}
</script>
<p>
This binary can be run from the terminal and takes the following arguments:
<code class="code-block flex flex-wrap my-2">
<pre>
--config &lt;PATH&gt;
--theme &lt;light|dark|system&gt;
--verbose</pre>
</code>
Additionally, it has a <code>update --background</code> subcommand.
</p>
<br />
<div class="note">
Note that the arguments are only parsed, not implemented.
</div>
<br />
<br />
<button class="btn" id="cli-matches" on:click={cliMatches}>
Get matches
</button>

@ -0,0 +1,32 @@
<script>
import { writeText, readText } from 'tauri-plugin-clipboard-api'
export let onMessage
let text = 'clipboard message'
function write() {
writeText(text)
.then(() => {
onMessage('Wrote to the clipboard')
})
.catch(onMessage)
}
function read() {
readText()
.then((contents) => {
onMessage(`Clipboard contents: ${contents}`)
})
.catch(onMessage)
}
</script>
<div class="flex gap-1">
<input
class="grow input"
placeholder="Text to write to the clipboard"
bind:value={text}
/>
<button class="btn" type="button" on:click={write}>Write</button>
<button class="btn" type="button" on:click={read}>Read</button>
</div>

@ -0,0 +1,50 @@
<script>
import { listen, emit } from '@tauri-apps/api/event'
import { invoke } from '@tauri-apps/api/tauri'
import { onMount, onDestroy } from 'svelte'
export let onMessage
let unlisten
onMount(async () => {
unlisten = await listen('rust-event', onMessage)
})
onDestroy(() => {
if (unlisten) {
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 emitEvent() {
emit('js-event', 'this is the payload string')
}
</script>
<div>
<button class="btn" id="log" on:click={log}>Call Log API</button>
<button class="btn" id="request" on:click={performRequest}>
Call Request (async) API
</button>
<button class="btn" id="event" on:click={emitEvent}>
Send event to Rust
</button>
</div>

@ -0,0 +1,118 @@
<script>
import { open, save } from 'tauri-plugin-dialog-api'
import { readBinaryFile } from 'tauri-plugin-fs-api'
export let onMessage
export let insecureRenderHtml
let defaultPath = null
let filter = null
let multiple = false
let directory = false
function arrayBufferToBase64(buffer, callback) {
var blob = new Blob([buffer], {
type: 'application/octet-binary'
})
var reader = new FileReader()
reader.onload = function (evt) {
var dataurl = evt.target.result
callback(dataurl.substr(dataurl.indexOf(',') + 1))
}
reader.readAsDataURL(blob)
}
function openDialog() {
open({
title: 'My wonderful open dialog',
defaultPath,
filters: filter
? [
{
name: 'Tauri Example',
extensions: filter.split(',').map((f) => f.trim())
}
]
: [],
multiple,
directory
})
.then(function (res) {
if (Array.isArray(res)) {
onMessage(res)
} else {
var pathToRead = typeof res === 'string' ? res : res.path
var isFile = pathToRead.match(/\S+\.\S+$/g)
readBinaryFile(pathToRead)
.then(function (response) {
if (isFile) {
if (
pathToRead.includes('.png') ||
pathToRead.includes('.jpg')
) {
arrayBufferToBase64(
new Uint8Array(response),
function (base64) {
var src = 'data:image/png;base64,' + base64
insecureRenderHtml('<img src="' + src + '"></img>')
}
)
} else {
onMessage(res)
}
} else {
onMessage(res)
}
})
.catch(onMessage(res))
}
})
.catch(onMessage)
}
function saveDialog() {
save({
title: 'My wonderful save dialog',
defaultPath,
filters: filter
? [
{
name: 'Tauri Example',
extensions: filter.split(',').map((f) => f.trim())
}
]
: []
})
.then(onMessage)
.catch(onMessage)
}
</script>
<div class="flex gap-2 children:grow">
<input
class="input"
id="dialog-default-path"
placeholder="Default path"
bind:value={defaultPath}
/>
<input
class="input"
id="dialog-filter"
placeholder="Extensions filter, comma-separated"
bind:value={filter}
/>
</div>
<br />
<div>
<input type="checkbox" id="dialog-multiple" bind:checked={multiple} />
<label for="dialog-multiple">Multiple</label>
</div>
<div>
<input type="checkbox" id="dialog-directory" bind:checked={directory} />
<label for="dialog-directory">Directory</label>
</div>
<br />
<button class="btn" id="open-dialog" on:click={openDialog}>Open dialog</button>
<button class="btn" id="save-dialog" on:click={saveDialog}
>Open save dialog</button
>

@ -0,0 +1,106 @@
<script>
import {
readBinaryFile,
writeTextFile,
readDir,
Dir
} from 'tauri-plugin-fs-api'
import { convertFileSrc } from '@tauri-apps/api/tauri'
export let onMessage
export let insecureRenderHtml
let pathToRead = ''
let img
function getDir() {
const dirSelect = document.getElementById('dir')
return dirSelect.value ? parseInt(dir.value) : null
}
function arrayBufferToBase64(buffer, callback) {
const blob = new Blob([buffer], {
type: 'application/octet-binary'
})
const reader = new FileReader()
reader.onload = function (evt) {
const dataurl = evt.target.result
callback(dataurl.substr(dataurl.indexOf(',') + 1))
}
reader.readAsDataURL(blob)
}
const DirOptions = Object.keys(Dir)
.filter((key) => isNaN(parseInt(key)))
.map((dir) => [dir, Dir[dir]])
function read() {
const isFile = pathToRead.match(/\S+\.\S+$/g)
const opts = {
dir: getDir()
}
const promise = isFile
? readBinaryFile(pathToRead, opts)
: readDir(pathToRead, opts)
promise
.then(function (response) {
if (isFile) {
if (pathToRead.includes('.png') || pathToRead.includes('.jpg')) {
arrayBufferToBase64(new Uint8Array(response), function (base64) {
const src = 'data:image/png;base64,' + base64
insecureRenderHtml('<img src="' + src + '"></img>')
})
} else {
const value = String.fromCharCode.apply(null, response)
insecureRenderHtml(
'<textarea id="file-response"></textarea><button id="file-save">Save</button>'
)
setTimeout(() => {
const fileInput = document.getElementById('file-response')
fileInput.value = value
document
.getElementById('file-save')
.addEventListener('click', function () {
writeTextFile(pathToRead, fileInput.value, {
dir: getDir()
}).catch(onMessage)
})
})
}
} else {
onMessage(response)
}
})
.catch(onMessage)
}
function setSrc() {
img.src = convertFileSrc(pathToRead)
}
</script>
<form class="flex flex-col" on:submit|preventDefault={read}>
<div class="flex gap-1">
<select class="input" id="dir">
<option value="">None</option>
{#each DirOptions as dir}
<option value={dir[1]}>{dir[0]}</option>
{/each}
</select>
<input
class="input grow"
id="path-to-read"
placeholder="Type the path to read..."
bind:value={pathToRead}
/>
</div>
<br />
<div>
<button class="btn" id="read">Read</button>
<button class="btn" type="button" on:click={setSrc}>Use as img src</button>
</div>
</form>
<br />
<img alt="" bind:this={img} />

@ -0,0 +1,99 @@
<script>
import { getClient, Body, ResponseType } from 'tauri-plugin-http-api'
import { JsonView } from '@zerodevx/svelte-json-view'
let httpMethod = 'GET'
let httpBody = ''
export let onMessage
async function makeHttpRequest() {
const client = await getClient().catch((e) => {
onMessage(e)
throw e
})
let method = httpMethod || 'GET'
const options = {
url: 'http://localhost:3003',
method: method || 'GET'
}
if (
(httpBody.startsWith('{') && httpBody.endsWith('}')) ||
(httpBody.startsWith('[') && httpBody.endsWith(']'))
) {
options.body = Body.json(JSON.parse(httpBody))
} else if (httpBody !== '') {
options.body = Body.text(httpBody)
}
client.request(options).then(onMessage).catch(onMessage)
}
/// http form
let foo = 'baz'
let bar = 'qux'
let result = null
let multipart = true
async function doPost() {
const client = await getClient().catch((e) => {
onMessage(e)
throw e
})
result = await client.request({
url: 'http://localhost:3003',
method: 'POST',
body: Body.form({
foo,
bar
}),
headers: multipart
? { 'Content-Type': 'multipart/form-data' }
: undefined,
responseType: ResponseType.Text
})
}
</script>
<form on:submit|preventDefault={makeHttpRequest}>
<select class="input" id="request-method" bind:value={httpMethod}>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="PATCH">PATCH</option>
<option value="DELETE">DELETE</option>
</select>
<br />
<textarea
class="input h-auto w-100%"
id="request-body"
placeholder="Request body"
rows="5"
bind:value={httpBody}
/>
<br />
<button class="btn" id="make-request"> Make request </button>
</form>
<br />
<h3>HTTP Form</h3>
<div class="flex gap-2 children:grow">
<input class="input" bind:value={foo} />
<input class="input" bind:value={bar} />
</div>
<br />
<label>
<input type="checkbox" bind:checked={multipart} />
Multipart
</label>
<br />
<br />
<button class="btn" type="button" on:click={doPost}> Post it</button>
<br />
<br />
<JsonView json={result} />

@ -0,0 +1,34 @@
<script>
export let onMessage
// send the notification directly
// the backend is responsible for checking the permission
function _sendNotification() {
new Notification('Notification title', {
body: 'This is the notification body'
})
}
// alternatively, check the permission ourselves
function sendNotification() {
if (Notification.permission === 'default') {
Notification.requestPermission()
.then(function (response) {
if (response === 'granted') {
_sendNotification()
} else {
onMessage('Permission is ' + response)
}
})
.catch(onMessage)
} else if (Notification.permission === 'granted') {
_sendNotification()
} else {
onMessage('Permission is denied')
}
}
</script>
<button class="btn" id="notification" on:click={_sendNotification}>
Send test notification
</button>

@ -0,0 +1,100 @@
<script>
import { Command } from 'tauri-plugin-shell-api'
const windows = navigator.userAgent.includes('Windows')
let cmd = windows ? 'cmd' : 'sh'
let args = windows ? ['/C'] : ['-c']
export let onMessage
let script = 'echo "hello world"'
let cwd = null
let env = 'SOMETHING=value ANOTHER=2'
let encoding = ''
let stdin = ''
let child
function _getEnv() {
return env.split(' ').reduce((env, clause) => {
let [key, value] = clause.split('=')
return {
...env,
[key]: value
}
}, {})
}
function spawn() {
child = null
const command = Command.create(cmd, [...args, script], {
cwd: cwd || null,
env: _getEnv(),
encoding: encoding || undefined,
})
command.on('close', (data) => {
onMessage(
`command finished with code ${data.code} and signal ${data.signal}`
)
child = null
})
command.on('error', (error) => onMessage(`command error: "${error}"`))
command.stdout.on('data', (line) => onMessage(`command stdout: "${line}"`))
command.stderr.on('data', (line) => onMessage(`command stderr: "${line}"`))
command
.spawn()
.then((c) => {
child = c
})
.catch(onMessage)
}
function kill() {
child
.kill()
.then(() => onMessage('killed child process'))
.catch(onMessage)
}
function writeToStdin() {
child.write(stdin).catch(onMessage)
}
</script>
<div class="flex flex-col childre:grow gap-1">
<div class="flex items-center gap-1">
Script:
<input class="grow input" bind:value={script} />
</div>
<div class="flex items-center gap-1">
Encoding:
<input class="grow input" bind:value={encoding} />
</div>
<div class="flex items-center gap-1">
Working directory:
<input
class="grow input"
bind:value={cwd}
placeholder="Working directory"
/>
</div>
<div class="flex items-center gap-1">
Arguments:
<input
class="grow input"
bind:value={env}
placeholder="Environment variables"
/>
</div>
<div class="flex children:grow gap-1">
<button class="btn" on:click={spawn}>Run</button>
<button class="btn" on:click={kill}>Kill</button>
</div>
{#if child}
<br />
<input class="input" placeholder="write to stdin" bind:value={stdin} />
<button class="btn" on:click={writeToStdin}>Write</button>
{/if}
</div>

@ -0,0 +1,73 @@
<script>
import { writable } from 'svelte/store'
import {
register as registerShortcut,
unregister as unregisterShortcut,
unregisterAll as unregisterAllShortcuts
} from 'tauri-plugin-global-shortcut-api'
export let onMessage
const shortcuts = writable([])
let shortcut = 'CmdOrControl+X'
function register() {
const shortcut_ = shortcut
registerShortcut(shortcut_, () => {
onMessage(`Shortcut ${shortcut_} triggered`)
})
.then(() => {
shortcuts.update((shortcuts_) => [...shortcuts_, shortcut_])
onMessage(`Shortcut ${shortcut_} registered successfully`)
})
.catch(onMessage)
}
function unregister(shortcut) {
const shortcut_ = shortcut
unregisterShortcut(shortcut_)
.then(() => {
shortcuts.update((shortcuts_) =>
shortcuts_.filter((s) => s !== shortcut_)
)
onMessage(`Shortcut ${shortcut_} unregistered`)
})
.catch(onMessage)
}
function unregisterAll() {
unregisterAllShortcuts()
.then(() => {
shortcuts.update(() => [])
onMessage(`Unregistered all shortcuts`)
})
.catch(onMessage)
}
</script>
<div class="flex gap-1">
<input
class="input grow"
placeholder="Type a shortcut with '+' as separator..."
bind:value={shortcut}
/>
<button class="btn" type="button" on:click={register}>Register</button>
</div>
<br />
<div class="flex flex-col gap-1">
{#each $shortcuts as savedShortcut}
<div class="flex justify-between">
{savedShortcut}
<button
class="btn"
type="button"
on:click={() => unregister(savedShortcut)}>Unregister</button
>
</div>
{/each}
{#if $shortcuts.length > 1}
<br />
<button class="btn" type="button" on:click={unregisterAll}
>Unregister all</button
>
{/if}
</div>

@ -0,0 +1,76 @@
<script>
import { onMount, onDestroy } from 'svelte'
// This example show how updater events work when dialog is disabled.
// This allow you to use custom dialog for the updater.
// This is your responsibility to restart the application after you receive the STATUS: DONE.
import { checkUpdate, installUpdate } from '@tauri-apps/api/updater'
import { listen } from '@tauri-apps/api/event'
import { relaunch } from '@tauri-apps/api/process'
export let onMessage
let unlisten
onMount(async () => {
unlisten = await listen('tauri://update-status', onMessage)
})
onDestroy(() => {
if (unlisten) {
unlisten()
}
})
let isChecking, isInstalling, newUpdate
async function check() {
isChecking = true
try {
const { shouldUpdate, manifest } = await checkUpdate()
onMessage(`Should update: ${shouldUpdate}`)
onMessage(manifest)
newUpdate = shouldUpdate
} catch (e) {
onMessage(e)
} finally {
isChecking = false
}
}
async function install() {
isInstalling = true
try {
await installUpdate()
onMessage('Installation complete, restart required.')
await relaunch()
} catch (e) {
onMessage(e)
} finally {
isInstalling = false
}
}
</script>
<div class="flex children:grow children:h10">
{#if !isChecking && !newUpdate}
<button class="btn" on:click={check}>Check update</button>
{:else if !isInstalling && newUpdate}
<button class="btn" on:click={install}>Install update</button>
{:else}
<button
class="btn text-accentText dark:text-darkAccentText flex items-center justify-center"
><div class="spinner animate-spin" /></button
>
{/if}
</div>
<style>
.spinner {
height: 1.2rem;
width: 1.2rem;
border-radius: 50rem;
color: currentColor;
border: 2px dashed currentColor;
}
</style>

@ -0,0 +1,56 @@
<script>
import { onMount, onDestroy } from 'svelte'
export let onMessage
const constraints = (window.constraints = {
audio: true,
video: true
})
function handleSuccess(stream) {
const video = document.querySelector('video')
const videoTracks = stream.getVideoTracks()
onMessage('Got stream with constraints:', constraints)
onMessage(`Using video device: ${videoTracks[0].label}`)
window.stream = stream // make variable available to browser console
video.srcObject = stream
}
function handleError(error) {
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.`
)
} 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.name}`, error)
}
onMount(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints)
handleSuccess(stream)
} catch (e) {
handleError(e)
}
})
onDestroy(() => {
window.stream.getTracks().forEach(function (track) {
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>
<track kind="captions" />
</video>
</div>

@ -0,0 +1,47 @@
<script>
import { getName, getVersion, getTauriVersion } from '@tauri-apps/api/app'
import { relaunch, exit } from '@tauri-apps/api/process'
let version = '0.0.0'
let tauriVersion = '0.0.0'
let appName = 'Unknown'
getName().then((n) => {
appName = n
})
getVersion().then((v) => {
version = v
})
getTauriVersion().then((v) => {
tauriVersion = v
})
async function closeApp() {
await exit()
}
async function relaunchApp() {
await relaunch()
}
</script>
<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>
<br />
<br />
<pre>
App name: <code>{appName}</code>
App version: <code>{version}</code>
Tauri version: <code>{tauriVersion}</code>
</pre>
<br />
<div class="flex flex-wrap gap-1 shadow-">
<button class="btn" on:click={closeApp}>Close application</button>
<button class="btn" on:click={relaunchApp}>Relaunch application</button>
</div>

@ -0,0 +1,459 @@
<script>
import {
appWindow,
WebviewWindow,
LogicalSize,
UserAttentionType,
PhysicalSize,
PhysicalPosition
} from '@tauri-apps/api/window'
import { open as openDialog } from 'tauri-plugin-dialog-api'
import { open } from 'tauri-plugin-shell-api'
let selectedWindow = appWindow.label
const windowMap = {
[appWindow.label]: appWindow
}
const cursorIconOptions = [
'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'
]
export let onMessage
let newWindowLabel
let urlValue = 'https://tauri.app'
let resizable = true
let maximized = false
let decorations = true
let alwaysOnTop = false
let contentProtected = true
let fullscreen = false
let width = null
let height = null
let minWidth = null
let minHeight = null
let maxWidth = null
let maxHeight = null
let x = null
let y = null
let scaleFactor = 1
let innerPosition = new PhysicalPosition(x, y)
let outerPosition = new PhysicalPosition(x, y)
let innerSize = new PhysicalSize(width, height)
let outerSize = new PhysicalSize(width, height)
let resizeEventUnlisten
let moveEventUnlisten
let cursorGrab = false
let cursorVisible = true
let cursorX = null
let cursorY = null
let cursorIcon = 'default'
let cursorIgnoreEvents = false
let windowTitle = 'Awesome Tauri Example!'
function openUrl() {
open(urlValue)
}
function setTitle_() {
windowMap[selectedWindow].setTitle(windowTitle)
}
function hide_() {
windowMap[selectedWindow].hide()
setTimeout(windowMap[selectedWindow].show, 2000)
}
function minimize_() {
windowMap[selectedWindow].minimize()
setTimeout(windowMap[selectedWindow].unminimize, 2000)
}
function getIcon() {
openDialog({
multiple: false
}).then((path) => {
if (typeof path === 'string') {
windowMap[selectedWindow].setIcon(path)
}
})
}
function createWindow() {
if (!newWindowLabel) return
const webview = new WebviewWindow(newWindowLabel)
windowMap[newWindowLabel] = webview
webview.once('tauri://error', function () {
onMessage('Error creating new webview')
})
}
function loadWindowSize() {
windowMap[selectedWindow].innerSize().then((response) => {
innerSize = response
width = innerSize.width
height = innerSize.height
})
windowMap[selectedWindow].outerSize().then((response) => {
outerSize = response
})
}
function loadWindowPosition() {
windowMap[selectedWindow].innerPosition().then((response) => {
innerPosition = response
})
windowMap[selectedWindow].outerPosition().then((response) => {
outerPosition = response
x = outerPosition.x
y = outerPosition.y
})
}
async function addWindowEventListeners(window) {
if (!window) return
if (resizeEventUnlisten) {
resizeEventUnlisten()
}
if (moveEventUnlisten) {
moveEventUnlisten()
}
moveEventUnlisten = await window.listen('tauri://move', loadWindowPosition)
resizeEventUnlisten = await window.listen('tauri://resize', loadWindowSize)
}
async function requestUserAttention_() {
await windowMap[selectedWindow].minimize()
await windowMap[selectedWindow].requestUserAttention(
UserAttentionType.Critical
)
await new Promise((resolve) => setTimeout(resolve, 3000))
await windowMap[selectedWindow].requestUserAttention(null)
}
$: {
windowMap[selectedWindow]
loadWindowPosition()
loadWindowSize()
}
$: windowMap[selectedWindow]?.setResizable(resizable)
$: maximized
? windowMap[selectedWindow]?.maximize()
: windowMap[selectedWindow]?.unmaximize()
$: windowMap[selectedWindow]?.setDecorations(decorations)
$: windowMap[selectedWindow]?.setAlwaysOnTop(alwaysOnTop)
$: windowMap[selectedWindow]?.setContentProtected(contentProtected)
$: windowMap[selectedWindow]?.setFullscreen(fullscreen)
$: width &&
height &&
windowMap[selectedWindow]?.setSize(new PhysicalSize(width, height))
$: minWidth && minHeight
? windowMap[selectedWindow]?.setMinSize(
new LogicalSize(minWidth, minHeight)
)
: windowMap[selectedWindow]?.setMinSize(null)
$: maxWidth > 800 && maxHeight > 400
? windowMap[selectedWindow]?.setMaxSize(
new LogicalSize(maxWidth, maxHeight)
)
: windowMap[selectedWindow]?.setMaxSize(null)
$: x !== null &&
y !== null &&
windowMap[selectedWindow]?.setPosition(new PhysicalPosition(x, y))
$: windowMap[selectedWindow]
?.scaleFactor()
.then((factor) => (scaleFactor = factor))
$: addWindowEventListeners(windowMap[selectedWindow])
$: windowMap[selectedWindow]?.setCursorGrab(cursorGrab)
$: windowMap[selectedWindow]?.setCursorVisible(cursorVisible)
$: windowMap[selectedWindow]?.setCursorIcon(cursorIcon)
$: cursorX !== null &&
cursorY !== null &&
windowMap[selectedWindow]?.setCursorPosition(
new PhysicalPosition(cursorX, cursorY)
)
$: windowMap[selectedWindow]?.setIgnoreCursorEvents(cursorIgnoreEvents)
</script>
<div class="flex flex-col children:grow gap-2">
<div class="flex gap-1">
<input
class="input grow"
type="text"
placeholder="New Window label.."
bind:value={newWindowLabel}
/>
<button class="btn" on:click={createWindow}>New window</button>
</div>
<br />
{#if Object.keys(windowMap).length >= 1}
<span class="font-700 text-sm">Selected window:</span>
<select class="input" bind:value={selectedWindow}>
<option value="" disabled selected>Choose a window...</option>
{#each Object.keys(windowMap) as label}
<option value={label}>{label}</option>
{/each}
</select>
{/if}
{#if windowMap[selectedWindow]}
<br />
<div class="flex flex-wrap gap-2">
<button
class="btn"
title="Unminimizes after 2 seconds"
on:click={() => windowMap[selectedWindow].center()}
>
Center
</button>
<button
class="btn"
title="Unminimizes after 2 seconds"
on:click={minimize_}
>
Minimize
</button>
<button
class="btn"
title="Visible again after 2 seconds"
on:click={hide_}
>
Hide
</button>
<button class="btn" on:click={getIcon}> Change icon </button>
<button
class="btn"
on:click={requestUserAttention_}
title="Minimizes the window, requests attention for 3s and then resets it"
>Request attention</button
>
</div>
<br />
<div class="flex flex-wrap gap-2">
<label>
Maximized
<input type="checkbox" bind:checked={maximized} />
</label>
<label>
Resizable
<input type="checkbox" bind:checked={resizable} />
</label>
<label>
Has decorations
<input type="checkbox" bind:checked={decorations} />
</label>
<label>
Always on top
<input type="checkbox" bind:checked={alwaysOnTop} />
</label>
<label>
Content protected
<input type="checkbox" bind:checked={contentProtected} />
</label>
<label>
Fullscreen
<input type="checkbox" bind:checked={fullscreen} />
</label>
</div>
<br />
<div class="flex flex-row gap-2 flex-wrap">
<div class="flex children:grow flex-col">
<div>
X
<input class="input" type="number" bind:value={x} min="0" />
</div>
<div>
Y
<input class="input" type="number" bind:value={y} min="0" />
</div>
</div>
<div class="flex children:grow flex-col">
<div>
Width
<input class="input" type="number" bind:value={width} min="400" />
</div>
<div>
Height
<input class="input" type="number" bind:value={height} min="400" />
</div>
</div>
<div class="flex children:grow flex-col">
<div>
Min width
<input class="input" type="number" bind:value={minWidth} />
</div>
<div>
Min height
<input class="input" type="number" bind:value={minHeight} />
</div>
</div>
<div class="flex children:grow flex-col">
<div>
Max width
<input class="input" type="number" bind:value={maxWidth} min="800" />
</div>
<div>
Max height
<input class="input" type="number" bind:value={maxHeight} min="400" />
</div>
</div>
</div>
<br />
<div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Size
</div>
<span>Width: {innerSize.width}</span>
<span>Height: {innerSize.height}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Size
</div>
<span>Width: {outerSize.width}</span>
<span>Height: {outerSize.height}</span>
</div>
</div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Logical Size
</div>
<span>Width: {innerSize.toLogical(scaleFactor).width}</span>
<span>Height: {innerSize.toLogical(scaleFactor).height}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Logical Size
</div>
<span>Width: {outerSize.toLogical(scaleFactor).width}</span>
<span>Height: {outerSize.toLogical(scaleFactor).height}</span>
</div>
</div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Position
</div>
<span>x: {innerPosition.x}</span>
<span>y: {innerPosition.y}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Position
</div>
<span>x: {outerPosition.x}</span>
<span>y: {outerPosition.y}</span>
</div>
</div>
<div class="flex">
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Inner Logical Position
</div>
<span>x: {innerPosition.toLogical(scaleFactor).x}</span>
<span>y: {innerPosition.toLogical(scaleFactor).y}</span>
</div>
<div class="grow">
<div class="text-accent dark:text-darkAccent font-700">
Outer Logical Position
</div>
<span>x: {outerPosition.toLogical(scaleFactor).x}</span>
<span>y: {outerPosition.toLogical(scaleFactor).y}</span>
</div>
</div>
</div>
<br />
<h4 class="mb-2">Cursor</h4>
<div class="flex gap-2">
<label>
<input type="checkbox" bind:checked={cursorGrab} />
Grab
</label>
<label>
<input type="checkbox" bind:checked={cursorVisible} />
Visible
</label>
<label>
<input type="checkbox" bind:checked={cursorIgnoreEvents} />
Ignore events
</label>
</div>
<div class="flex gap-2">
<label>
Icon
<select class="input" bind:value={cursorIcon}>
{#each cursorIconOptions as kind}
<option value={kind}>{kind}</option>
{/each}
</select>
</label>
<label>
X position
<input class="input" type="number" bind:value={cursorX} />
</label>
<label>
Y position
<input class="input" type="number" bind:value={cursorY} />
</label>
</div>
<br />
<div class="flex flex-col gap-1">
<form class="flex gap-1" on:submit|preventDefault={setTitle_}>
<input class="input grow" id="title" bind:value={windowTitle} />
<button class="btn" type="submit">Set title</button>
</form>
<form class="flex gap-1" on:submit|preventDefault={openUrl}>
<input class="input grow" id="url" bind:value={urlValue} />
<button class="btn" id="open-url"> Open URL </button>
</form>
</div>
{/if}
</div>

@ -0,0 +1,105 @@
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
import {
defineConfig,
presetIcons,
presetUno,
extractorSvelte,
presetWebFonts,
} from "unocss";
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:
"h-10 flex items-center outline-none border-none p-2 rd-1 shadow-md bg-primaryLighter dark:bg-darkPrimaryLighter text-primaryText dark:text-darkPrimaryText",
},
presets: [
presetUno(),
presetIcons(),
presetWebFonts({
fonts: {
sans: "Rubik",
mono: ["Fira Code", "Fira Mono:400,700"],
},
}),
],
extractors: [extractorSvelte],
});

@ -0,0 +1,43 @@
// Copyright 2019-2023 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";
import { internalIpV4 } from "internal-ip";
import process from "process";
// https://vitejs.dev/config/
export default defineConfig(async () => {
const host =
process.env.TAURI_PLATFORM === "android" ||
process.env.TAURI_PLATFORM === "ios"
? await internalIpV4()
: "localhost";
return {
plugins: [Unocss(), svelte()],
build: {
rollupOptions: {
output: {
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`,
},
},
},
server: {
host: "0.0.0.0",
port: 5173,
strictPort: true,
hmr: {
protocol: "ws",
host,
port: 5183,
},
fs: {
allow: [".", "../../tooling/api/dist"],
},
},
};
});

File diff suppressed because it is too large Load Diff

@ -24,7 +24,7 @@
* @module
*/
import { invoke, transformCallback } from '@tauri-apps/api/tauri'
import { invoke, transformCallback } from "@tauri-apps/api/tauri";
/**
* Options to send a notification.
@ -35,116 +35,116 @@ interface Options {
/**
* The notification identifier to reference this object later. Must be a 32-bit integer.
*/
id?: number
id?: number;
/**
* Identifier of the {@link Channel} that deliveres this notification.
*
*
* If the channel does not exist, the notification won't fire.
* Make sure the channel exists with {@link listChannels} and {@link createChannel}.
*/
channelId?: string
channelId?: string;
/**
* Notification title.
*/
title: string
title: string;
/**
* Optional notification body.
* */
body?: string
body?: string;
/**
* Schedule this notification to fire on a later time or a fixed interval.
*/
schedule?: Schedule
schedule?: Schedule;
/**
* Multiline text.
* Changes the notification style to big text.
* Cannot be used with `inboxLines`.
*/
largeBody?: string
largeBody?: string;
/**
* Detail text for the notification with `largeBody`, `inboxLines` or `groupSummary`.
*/
summary?: string
summary?: string;
/**
* Defines an action type for this notification.
*/
actionTypeId?: string
actionTypeId?: string;
/**
* Identifier used to group multiple notifications.
*
*
* https://developer.apple.com/documentation/usernotifications/unmutablenotificationcontent/1649872-threadidentifier
*/
group?: string
group?: string;
/**
* Instructs the system that this notification is the summary of a group on Android.
*/
groupSummary?: boolean
groupSummary?: boolean;
/**
* The sound resource name. Only available on mobile.
*/
sound?: string
sound?: string;
/**
* List of lines to add to the notification.
* Changes the notification style to inbox.
* Cannot be used with `largeBody`.
*
*
* Only supports up to 5 lines.
*/
inboxLines?: string[]
inboxLines?: string[];
/**
* Notification icon.
*
*
* On Android the icon must be placed in the app's `res/drawable` folder.
*/
icon?: string
icon?: string;
/**
* Notification large icon (Android).
*
*
* The icon must be placed in the app's `res/drawable` folder.
*/
largeIcon?: string
largeIcon?: string;
/**
* Icon color on Android.
*/
iconColor?: string
iconColor?: string;
/**
* Notification attachments.
*/
attachments?: Attachment[]
attachments?: Attachment[];
/**
* Extra payload to store in the notification.
*/
extra?: { [key: string]: unknown }
extra?: { [key: string]: unknown };
/**
* If true, the notification cannot be dismissed by the user on Android.
*
*
* An application service must manage the dismissal of the notification.
* It is typically used to indicate a background task that is pending (e.g. a file download)
* or the user is engaged with (e.g. playing music).
*/
ongoing?: boolean
ongoing?: boolean;
/**
* Automatically cancel the notification when the user clicks on it.
*/
autoCancel?: boolean
autoCancel?: boolean;
/**
* Changes the notification presentation to be silent on iOS (no badge, no sound, not listed).
*/
silent?: boolean
silent?: boolean;
/**
* Notification visibility.
*/
visibility?: Visibility
visibility?: Visibility;
/**
* Sets the number of items this notification represents on Android.
*/
number?: number
number?: number;
}
type ScheduleInterval = {
year?: number
month?: number
day?: number
year?: number;
month?: number;
day?: number;
/**
* 1 - Sunday
* 2 - Monday
@ -154,61 +154,64 @@ type ScheduleInterval = {
* 6 - Friday
* 7 - Saturday
*/
weekday?: number
hour?: number
minute?: number
second?: number
}
weekday?: number;
hour?: number;
minute?: number;
second?: number;
};
enum ScheduleEvery {
Year = 'Year',
Month = 'Month',
TwoWeeks = 'TwoWeeks',
Week = 'Week',
Day = 'Day',
Hour = 'Hour',
Minute = 'Minute',
Year = "Year",
Month = "Month",
TwoWeeks = "TwoWeeks",
Week = "Week",
Day = "Day",
Hour = "Hour",
Minute = "Minute",
/**
* Not supported on iOS.
*/
Second = 'Second'
Second = "Second",
}
type ScheduleData = {
kind: 'At',
data: {
date: Date
repeating: boolean
}
} | {
kind: 'Interval',
data: ScheduleInterval
} | {
kind: 'Every',
data: {
interval: ScheduleEvery
}
}
type ScheduleData =
| {
kind: "At";
data: {
date: Date;
repeating: boolean;
};
}
| {
kind: "Interval";
data: ScheduleInterval;
}
| {
kind: "Every";
data: {
interval: ScheduleEvery;
};
};
class Schedule {
kind: string
data: unknown
kind: string;
data: unknown;
private constructor(schedule: ScheduleData) {
this.kind = schedule.kind
this.data = schedule.data
this.kind = schedule.kind;
this.data = schedule.data;
}
static at(date: Date, repeating = false) {
return new Schedule({ kind: 'At', data: { date, repeating } })
return new Schedule({ kind: "At", data: { date, repeating } });
}
static interval(interval: ScheduleInterval) {
return new Schedule({ kind: 'Interval', data: interval })
return new Schedule({ kind: "Interval", data: interval });
}
static every(kind: ScheduleEvery) {
return new Schedule({ kind: 'Every', data: { interval: kind } })
return new Schedule({ kind: "Every", data: { interval: kind } });
}
}
@ -217,58 +220,58 @@ class Schedule {
*/
interface Attachment {
/** Attachment identifier. */
id: string
id: string;
/** Attachment URL. Accepts the `asset` and `file` protocols. */
url: string
url: string;
}
interface Action {
id: string
title: string
requiresAuthentication?: boolean
foreground?: boolean
destructive?: boolean
input?: boolean
inputButtonTitle?: string
inputPlaceholder?: string
id: string;
title: string;
requiresAuthentication?: boolean;
foreground?: boolean;
destructive?: boolean;
input?: boolean;
inputButtonTitle?: string;
inputPlaceholder?: string;
}
interface ActionType {
/**
* The identifier of this action type
*/
id: string
id: string;
/**
* The list of associated actions
*/
actions: Action[]
hiddenPreviewsBodyPlaceholder?: string,
customDismissAction?: boolean,
allowInCarPlay?: boolean,
hiddenPreviewsShowTitle?: boolean,
hiddenPreviewsShowSubtitle?: boolean,
actions: Action[];
hiddenPreviewsBodyPlaceholder?: string;
customDismissAction?: boolean;
allowInCarPlay?: boolean;
hiddenPreviewsShowTitle?: boolean;
hiddenPreviewsShowSubtitle?: boolean;
}
interface PendingNotification {
id: number
title?: string
body?: string
schedule: Schedule
id: number;
title?: string;
body?: string;
schedule: Schedule;
}
interface ActiveNotification {
id: number
tag?: string
title?: string
body?: string
group?: string
groupSummary: boolean
data: Record<string, string>
extra: Record<string, unknown>
attachments: Attachment[]
actionTypeId?: string
schedule?: Schedule
sound?: string
id: number;
tag?: string;
title?: string;
body?: string;
group?: string;
groupSummary: boolean;
data: Record<string, string>;
extra: Record<string, unknown>;
attachments: Attachment[];
actionTypeId?: string;
schedule?: Schedule;
sound?: string;
}
enum Importance {
@ -276,25 +279,25 @@ enum Importance {
Min,
Low,
Default,
High
High,
}
enum Visibility {
Secret = -1,
Private,
Public
Public,
}
interface Channel {
id: string
name: string
description?: string
sound?: string
lights?: boolean
lightColor?: string
vibration?: boolean
importance?: Importance
visibility?: Visibility
id: string;
name: string;
description?: string;
sound?: string;
lights?: boolean;
lightColor?: string;
vibration?: boolean;
importance?: Importance;
visibility?: Visibility;
}
/** Possible permission values. */
@ -367,7 +370,7 @@ function sendNotification(options: Options | string): void {
/**
* Register actions that are performed when the user clicks on the notification.
*
*
* @example
* ```typescript
* import { registerActionTypes } from '@tauri-apps/api/notification';
@ -385,12 +388,12 @@ function sendNotification(options: Options | string): void {
* @since 2.0.0
*/
async function registerActionTypes(types: ActionType[]): Promise<void> {
return invoke('plugin:notification|register_action_types', { types })
return invoke("plugin:notification|register_action_types", { types });
}
/**
* Retrieves the list of pending notifications.
*
*
* @example
* ```typescript
* import { pending } from '@tauri-apps/api/notification';
@ -402,12 +405,12 @@ async function registerActionTypes(types: ActionType[]): Promise<void> {
* @since 2.0.0
*/
async function pending(): Promise<PendingNotification[]> {
return invoke('plugin:notification|get_pending')
return invoke("plugin:notification|get_pending");
}
/**
* Cancels the pending notifications with the given list of identifiers.
*
*
* @example
* ```typescript
* import { cancel } from '@tauri-apps/api/notification';
@ -419,12 +422,12 @@ async function pending(): Promise<PendingNotification[]> {
* @since 2.0.0
*/
async function cancel(notifications: number[]): Promise<void> {
return invoke('plugin:notification|cancel', { notifications })
return invoke("plugin:notification|cancel", { notifications });
}
/**
* Cancels all pending notifications.
*
*
* @example
* ```typescript
* import { cancelAll } from '@tauri-apps/api/notification';
@ -436,12 +439,12 @@ async function cancel(notifications: number[]): Promise<void> {
* @since 2.0.0
*/
async function cancelAll(): Promise<void> {
return invoke('plugin:notification|cancel')
return invoke("plugin:notification|cancel");
}
/**
* Retrieves the list of active notifications.
*
*
* @example
* ```typescript
* import { active } from '@tauri-apps/api/notification';
@ -453,12 +456,12 @@ async function cancelAll(): Promise<void> {
* @since 2.0.0
*/
async function active(): Promise<ActiveNotification[]> {
return invoke('plugin:notification|get_active')
return invoke("plugin:notification|get_active");
}
/**
* Removes the active notifications with the given list of identifiers.
*
*
* @example
* ```typescript
* import { cancel } from '@tauri-apps/api/notification';
@ -470,12 +473,12 @@ async function active(): Promise<ActiveNotification[]> {
* @since 2.0.0
*/
async function removeActive(notifications: number[]): Promise<void> {
return invoke('plugin:notification|remove_active', { notifications })
return invoke("plugin:notification|remove_active", { notifications });
}
/**
* Removes all active notifications.
*
*
* @example
* ```typescript
* import { removeAllActive } from '@tauri-apps/api/notification';
@ -487,12 +490,12 @@ async function removeActive(notifications: number[]): Promise<void> {
* @since 2.0.0
*/
async function removeAllActive(): Promise<void> {
return invoke('plugin:notification|remove_active')
return invoke("plugin:notification|remove_active");
}
/**
* Removes all active notifications.
*
*
* @example
* ```typescript
* import { createChannel, Importance, Visibility } from '@tauri-apps/api/notification';
@ -511,12 +514,12 @@ async function removeAllActive(): Promise<void> {
* @since 2.0.0
*/
async function createChannel(channel: Channel): Promise<void> {
return invoke('plugin:notification|create_channel', { ...channel })
return invoke("plugin:notification|create_channel", { ...channel });
}
/**
* Removes the channel with the given identifier.
*
*
* @example
* ```typescript
* import { removeChannel } from '@tauri-apps/api/notification';
@ -528,12 +531,12 @@ async function createChannel(channel: Channel): Promise<void> {
* @since 2.0.0
*/
async function removeChannel(id: string): Promise<void> {
return invoke('plugin:notification|delete_channel', { id })
return invoke("plugin:notification|delete_channel", { id });
}
/**
* Retrieves the list of notification channels.
*
*
* @example
* ```typescript
* import { channels } from '@tauri-apps/api/notification';
@ -545,43 +548,74 @@ async function removeChannel(id: string): Promise<void> {
* @since 2.0.0
*/
async function channels(): Promise<Channel[]> {
return invoke('plugin:notification|getActive')
return invoke("plugin:notification|getActive");
}
class EventChannel {
id: number
unregisterFn: (channel: EventChannel) => Promise<void>
constructor(id: number, unregisterFn: (channel: EventChannel) => Promise<void>) {
this.id = id
this.unregisterFn = unregisterFn
id: number;
unregisterFn: (channel: EventChannel) => Promise<void>;
constructor(
id: number,
unregisterFn: (channel: EventChannel) => Promise<void>
) {
this.id = id;
this.unregisterFn = unregisterFn;
}
toJSON(): string {
return `__CHANNEL__:${this.id}`
return `__CHANNEL__:${this.id}`;
}
async unregister(): Promise<void> {
return this.unregisterFn(this)
return this.unregisterFn(this);
}
}
// TODO: use addPluginListener API on @tauri-apps/api/tauri 2.0.0-alpha.4
async function onNotificationReceived(cb: (notification: Options) => void): Promise<EventChannel> {
const channelId = transformCallback(cb)
const handler = new EventChannel(channelId, (channel) => invoke('plugin:notification|remove_listener', { event: 'notification', channelId: channel.id }))
return invoke('plugin:notification|register_listener', { event: 'notification', handler }).then(() => handler)
async function onNotificationReceived(
cb: (notification: Options) => void
): Promise<EventChannel> {
const channelId = transformCallback(cb);
const handler = new EventChannel(channelId, (channel) =>
invoke("plugin:notification|remove_listener", {
event: "notification",
channelId: channel.id,
})
);
return invoke("plugin:notification|register_listener", {
event: "notification",
handler,
}).then(() => handler);
}
// TODO: use addPluginListener API on @tauri-apps/api/tauri 2.0.0-alpha.4
async function onAction(cb: (notification: Options) => void): Promise<EventChannel> {
const channelId = transformCallback(cb)
const handler = new EventChannel(channelId, (channel) => invoke('plugin:notification|remove_listener', { event: 'actionPerformed', channelId: channel.id }))
return invoke('plugin:notification|register_listener', { event: 'actionPerformed', handler }).then(() => handler)
async function onAction(
cb: (notification: Options) => void
): Promise<EventChannel> {
const channelId = transformCallback(cb);
const handler = new EventChannel(channelId, (channel) =>
invoke("plugin:notification|remove_listener", {
event: "actionPerformed",
channelId: channel.id,
})
);
return invoke("plugin:notification|register_listener", {
event: "actionPerformed",
handler,
}).then(() => handler);
}
export type { Attachment, Options, Permission, Action, ActionType, PendingNotification, ActiveNotification, Channel }
export type {
Attachment,
Options,
Permission,
Action,
ActionType,
PendingNotification,
ActiveNotification,
Channel,
};
export {
Importance,
@ -599,7 +633,6 @@ export {
createChannel,
removeChannel,
channels,
onNotificationReceived,
onAction
}
onAction,
};

@ -46,8 +46,8 @@ impl<R: Runtime> Shell<R> {
/// Open a (url) path with a default or specific browser opening program.
///
/// See [`crate::api::shell::open`] for how it handles security-related measures.
pub fn open(&self, path: String, with: Option<open::Program>) -> Result<()> {
open::open(&self.scope, path, with).map_err(Into::into)
pub fn open(&self, path: impl Into<String>, with: Option<open::Program>) -> Result<()> {
open::open(&self.scope, path.into(), with).map_err(Into::into)
}
}

@ -50,6 +50,64 @@ importers:
specifier: ^5.0.4
version: 5.0.4
examples/api:
dependencies:
'@tauri-apps/api':
specifier: 2.0.0-alpha.3
version: 2.0.0-alpha.3
'@tauri-apps/cli':
specifier: 2.0.0-alpha.8
version: 2.0.0-alpha.8
'@zerodevx/svelte-json-view':
specifier: 0.2.1
version: 0.2.1
tauri-plugin-cli-api:
specifier: 0.0.0
version: link:../../plugins/cli
tauri-plugin-clipboard-api:
specifier: 0.0.0
version: link:../../plugins/clipboard
tauri-plugin-dialog-api:
specifier: 0.0.0
version: link:../../plugins/dialog
tauri-plugin-fs-api:
specifier: 0.0.0
version: link:../../plugins/fs
tauri-plugin-global-shortcut-api:
specifier: 0.0.0
version: link:../../plugins/global-shortcut
tauri-plugin-http-api:
specifier: 0.0.0
version: link:../../plugins/http
tauri-plugin-notification-api:
specifier: 0.0.0
version: link:../../plugins/notification
tauri-plugin-shell-api:
specifier: 0.0.0
version: link:../../plugins/shell
devDependencies:
'@iconify-json/codicon':
specifier: ^1.1.10
version: 1.1.10
'@iconify-json/ph':
specifier: ^1.1.1
version: 1.1.1
'@sveltejs/vite-plugin-svelte':
specifier: ^1.0.1
version: 1.0.1(svelte@3.55.1)(vite@3.0.9)
internal-ip:
specifier: ^7.0.0
version: 7.0.0
svelte:
specifier: ^3.49.0
version: 3.55.1
unocss:
specifier: ^0.39.3
version: 0.39.3(vite@3.0.9)
vite:
specifier: ^3.0.9
version: 3.0.9
plugins/authenticator:
dependencies:
'@tauri-apps/api':
@ -301,6 +359,17 @@ importers:
packages:
/@antfu/install-pkg@0.1.1:
resolution: {integrity: sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==}
dependencies:
execa: 5.1.1
find-up: 5.0.0
dev: true
/@antfu/utils@0.5.2:
resolution: {integrity: sha512-CQkeV+oJxUazwjlHD0/3ZD08QWKuGQkhnrKo3e6ly5pd48VUpXbb77q0xMU4+vc2CkJnDS02Eq/M9ugyX20XZA==}
dev: true
/@esbuild/android-arm64@0.17.18:
resolution: {integrity: sha512-/iq0aK0eeHgSC3z55ucMAHO05OIqmQehiGay8eP5l/5l+iEr4EIbh4/MI8xD9qRFjqzgkc0JkX0LculNC9mXBw==}
engines: {node: '>=12'}
@ -565,6 +634,35 @@ packages:
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
dev: true
/@iconify-json/codicon@1.1.10:
resolution: {integrity: sha512-xx3nX5k4UeDQnpX9D1T6L1RCEwyLtqu3Lqk9plYK+SoBSQ/kR73bPy9WbYyDVOw2MDw50JCSpZZYlBC718k7Sg==}
dependencies:
'@iconify/types': 1.1.0
dev: true
/@iconify-json/ph@1.1.1:
resolution: {integrity: sha512-sIHTY+c1F8x29BM49IqoccJ3T8mvVXPcrE4WOpJ3GsBaip2YqFJRYU60rw64UL6GEI13vWSD7NsZKq8ytTO87g==}
dependencies:
'@iconify/types': 1.1.0
dev: true
/@iconify/types@1.1.0:
resolution: {integrity: sha512-Jh0llaK2LRXQoYsorIH8maClebsnzTcve+7U3rQUSnC11X4jtPnFuyatqFLvMxZ8MLG8dB4zfHsbPfuvxluONw==}
dev: true
/@iconify/utils@1.0.33:
resolution: {integrity: sha512-vGeAqo7aGPxOQmGdVoXFUOuyN+0V7Lcrx2EvaiRjxUD1x6Om0Tvq2bdm7E24l2Pz++4S0mWMCVFXe/17EtKImQ==}
dependencies:
'@antfu/install-pkg': 0.1.1
'@antfu/utils': 0.5.2
'@iconify/types': 1.1.0
debug: 4.3.4
kolorist: 1.7.0
local-pkg: 0.4.3
transitivePeerDependencies:
- supports-color
dev: true
/@jridgewell/gen-mapping@0.3.2:
resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
engines: {node: '>=6.0.0'}
@ -752,7 +850,7 @@ packages:
dependencies:
'@rollup/pluginutils': 4.2.1
debug: 4.3.4
deepmerge: 4.2.2
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.26.7
svelte: 3.55.1
@ -762,6 +860,29 @@ packages:
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte@1.0.1(svelte@3.55.1)(vite@3.0.9):
resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==}
engines: {node: ^14.18.0 || >= 16}
peerDependencies:
diff-match-patch: ^1.0.5
svelte: ^3.44.0
vite: ^3.0.0
peerDependenciesMeta:
diff-match-patch:
optional: true
dependencies:
'@rollup/pluginutils': 4.2.1
debug: 4.3.4
deepmerge: 4.3.1
kleur: 4.1.5
magic-string: 0.26.7
svelte: 3.55.1
svelte-hmr: 0.14.12(svelte@3.55.1)
vite: 3.0.9
transitivePeerDependencies:
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte@2.1.1(svelte@3.58.0)(vite@4.3.3):
resolution: {integrity: sha512-7YeBDt4us0FiIMNsVXxyaP4Hwyn2/v9x3oqStkHU3ZdIc5O22pGwUwH33wUqYo+7Itdmo8zxJ45Qvfm3H7UUjQ==}
engines: {node: ^14.18.0 || >= 16}
@ -786,6 +907,11 @@ packages:
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
/@tauri-apps/api@2.0.0-alpha.3:
resolution: {integrity: sha512-F6seMDlcaxeCPy4gS0zJdp6Tet+0rd1qJi/fbKrOrhLM6Y5UtkiG1aSDnMPi+1udThSfadjhUwrLHINvfMCjzQ==}
engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'}
dev: false
/@tauri-apps/cli-darwin-arm64@2.0.0-alpha.8:
resolution: {integrity: sha512-ZF9nkkYCDiAEKZFwjEbuqTcFVp+DBgem3edKjsZDYPQpWg0VcZOSYr0o3/RPC81T1/FAy1lq478mkcMe0efvEw==}
engines: {node: '>= 10'}
@ -1031,6 +1157,144 @@ packages:
eslint-visitor-keys: 3.4.0
dev: true
/@unocss/cli@0.39.3:
resolution: {integrity: sha512-h+qq76CJTkV7GYBSQ3vSJCn/jewFzBVh8owMYH3B1ROe5D1mCev2INYvHlsQsVVoyxnccBeuZ6st6OK56VyDjA==}
engines: {node: '>=14'}
hasBin: true
dependencies:
'@unocss/config': 0.39.3
'@unocss/core': 0.39.3
'@unocss/preset-uno': 0.39.3
cac: 6.7.14
chokidar: 3.5.3
colorette: 2.0.19
consola: 2.15.3
fast-glob: 3.2.12
pathe: 0.3.9
perfect-debounce: 0.1.3
dev: true
/@unocss/config@0.39.3:
resolution: {integrity: sha512-qyxjUUdi+D/vS4Snhoj0uW8ErKlfZCKdjJ+ntwnJK3c8dxAp/IuicE+6ukcLfHxT0kAw1xaRlNwamtL3MgcX/A==}
engines: {node: '>=14'}
dependencies:
'@unocss/core': 0.39.3
unconfig: 0.3.7
dev: true
/@unocss/core@0.39.3:
resolution: {integrity: sha512-8MnXKHNtp6xgsFIaFtWctnbsT60c8JSlxXA7XbGxEztOmSEhpZmLeLGe5AgmEGPH6MssqJtI0DCeTbzbbtOjfw==}
dev: true
/@unocss/inspector@0.39.3:
resolution: {integrity: sha512-j7U04I07sqK63+3cA7oju/hoGOkdN+/hAwGYkCgWGNj+HwxiU7TTEVg0qZ1FAUU/GyyI9G/c4RIpwei9dLVz9w==}
dependencies:
gzip-size: 6.0.0
sirv: 2.0.2
dev: true
/@unocss/preset-attributify@0.39.3:
resolution: {integrity: sha512-SZWWUfTTKyHHqlF9x6aZ+BFMIiwOsUTP4NXS3/rIroqzfvVDZtGS6/a7RVBl+M74wjqSWB/DDeS9kQiH2L/CIg==}
dependencies:
'@unocss/core': 0.39.3
dev: true
/@unocss/preset-icons@0.39.3:
resolution: {integrity: sha512-zMTfP3pVaN2WREWY36adsY62gEm51R0CZd7v0gHOlltEG6kT1UCeyIQwOtn48wHRCesy92f70R6RIR3rwSVaCQ==}
dependencies:
'@iconify/utils': 1.0.33
'@unocss/core': 0.39.3
ohmyfetch: 0.4.21
transitivePeerDependencies:
- supports-color
dev: true
/@unocss/preset-mini@0.39.3:
resolution: {integrity: sha512-XCxp3mwWsEpCo0cIJA3tLrWqdAL09gP3wv9iGh4H9o0fIPlYXjVTC1WtUHkv3C09LdZ+MH/9Ja/KqnVf3bNROA==}
dependencies:
'@unocss/core': 0.39.3
dev: true
/@unocss/preset-tagify@0.39.3:
resolution: {integrity: sha512-OXE47cS/tiL92ZThgLOpbSFy7MPZ4upE4ZX1m9pnCaWzX7LBzp8Gw0DM+dF3IYdIfJpmU4R6b53ME8SchofuHA==}
dependencies:
'@unocss/core': 0.39.3
dev: true
/@unocss/preset-typography@0.39.3:
resolution: {integrity: sha512-jTJOA87bEkU0RGMPSFZK3Zobr2fgkqKCYDczTjPbCiZ8UzlMJnWrpsNTN9f4UI0b6Ck8sXtMtW8sRrJsEll9jg==}
dependencies:
'@unocss/core': 0.39.3
dev: true
/@unocss/preset-uno@0.39.3:
resolution: {integrity: sha512-EADVFqx5x4te/teqwjHb025FIy/T0QXafcVDRwUijS6OOqm5rZ7fXd/hu41XYYn3B802r/g4bDC2wO+7foNVwA==}
dependencies:
'@unocss/core': 0.39.3
'@unocss/preset-mini': 0.39.3
'@unocss/preset-wind': 0.39.3
dev: true
/@unocss/preset-web-fonts@0.39.3:
resolution: {integrity: sha512-b23nmEGHbfvC/PCv0m0BGqFt2zX8J9ekwjfmEL1Bk1C0KL2voYGSdbSm0I8iO6sKb1CLy6qy71N/CuGtIE3FJA==}
dependencies:
'@unocss/core': 0.39.3
ohmyfetch: 0.4.21
dev: true
/@unocss/preset-wind@0.39.3:
resolution: {integrity: sha512-kjMgPxt4xfmiliodKTbotJDSAqAOCy25f1jdIj9CjjFjwYsUAuiYi8UgPsEi550Bj5BlBEHFn/RhcMGvinzY8A==}
dependencies:
'@unocss/core': 0.39.3
'@unocss/preset-mini': 0.39.3
dev: true
/@unocss/reset@0.39.3:
resolution: {integrity: sha512-hW3gZ3lsu6N58XEG7m1dprt15fN0xkYjAo7vSp8eT3/p7h5HE7wNgU2v9ttGBC3B2z4AWHGdspfmaH3sR8lCJw==}
dev: true
/@unocss/scope@0.39.3:
resolution: {integrity: sha512-ex2QDRgBQ5mTwBcXtCWdTDPl6/HrBv0asDWVXXv7ezjxcByJjMrHj64gMvUbAcGAoX2ic7hIEUT3Ju5i6knKFw==}
dev: true
/@unocss/transformer-compile-class@0.39.3:
resolution: {integrity: sha512-OmYP0uk+DGR5kc2T+teL6CLNj/sRxbY3SmlPx2kDbsRLc5gFccQryjj4bBk6QNOKxP5OGJpAqcw1y1JctvRgog==}
dependencies:
'@unocss/core': 0.39.3
dev: true
/@unocss/transformer-directives@0.39.3:
resolution: {integrity: sha512-E1wzZaR6rIBQNemgDi9LoljtkYcOSiKGMUTz6kRGoxVBzaYE6Ji/YKbb22lKd6vLOFnRyCxzPHdzY9qvvl5D6w==}
dependencies:
'@unocss/core': 0.39.3
css-tree: 2.3.1
dev: true
/@unocss/transformer-variant-group@0.39.3:
resolution: {integrity: sha512-YoYz87qSSEvXXUkgHbO2kz/M03dbZuedjDvvWXsBAvj20MQFpkZpbNHYf2DJ+EkO/WXd+KEF2HBwlgoANcZlaw==}
dependencies:
'@unocss/core': 0.39.3
dev: true
/@unocss/vite@0.39.3(vite@3.0.9):
resolution: {integrity: sha512-JT21v6ZwLCHPGVfjoWsOdSkMhFNiW2robhQke33WLlRGyT5U4K1SWLxNk+XPDbFdP+WZdcVJi5W5yG8Mm27WBw==}
peerDependencies:
vite: ^2.9.0
dependencies:
'@rollup/pluginutils': 4.2.1
'@unocss/config': 0.39.3
'@unocss/core': 0.39.3
'@unocss/inspector': 0.39.3
'@unocss/scope': 0.39.3
'@unocss/transformer-directives': 0.39.3
magic-string: 0.26.7
vite: 3.0.9
dev: true
/@zerodevx/svelte-json-view@0.2.1:
resolution: {integrity: sha512-yaLojLYTi08vccUKRg/XSRCCPoyzCZqrG+W8mVhJEGiOfFKAmWqNH6b+/il1gG3V1UaEe7amj2mzmo1mo4q1iA==}
dev: false
/acorn-jsx@5.3.2(acorn@8.8.1):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -1163,6 +1427,11 @@ packages:
streamsearch: 1.1.0
dev: true
/cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
dev: true
/call-bind@1.0.2:
resolution: {integrity: sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==}
dependencies:
@ -1209,6 +1478,10 @@ packages:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
dev: true
/colorette@2.0.19:
resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==}
dev: true
/commander@2.20.3:
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
dev: true
@ -1217,6 +1490,10 @@ packages:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
dev: true
/consola@2.15.3:
resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
dev: true
/cookie@0.5.0:
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
engines: {node: '>= 0.6'}
@ -1231,6 +1508,14 @@ packages:
which: 2.0.2
dev: true
/css-tree@2.3.1:
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
dependencies:
mdn-data: 2.0.30
source-map-js: 1.0.2
dev: true
/debug@3.2.7:
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
peerDependencies:
@ -1283,6 +1568,14 @@ packages:
object-keys: 1.1.1
dev: true
/defu@6.1.2:
resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==}
dev: true
/destr@1.2.2:
resolution: {integrity: sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA==}
dev: true
/detect-indent@6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
@ -1313,6 +1606,10 @@ packages:
esutils: 2.0.3
dev: true
/duplexer@0.1.2:
resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==}
dev: true
/es-abstract@1.20.5:
resolution: {integrity: sha512-7h8MM2EQhsCA7pU/Nv78qOXFpD8Rhqd12gYiSJVkrH9+e8VuA8JlPJK/hQjjlLv6pJvx/z1iRFKzYb0XT/RuAQ==}
engines: {node: '>= 0.4'}
@ -2105,6 +2402,13 @@ packages:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true
/gzip-size@6.0.0:
resolution: {integrity: sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==}
engines: {node: '>=10'}
dependencies:
duplexer: 0.1.2
dev: true
/has-bigints@1.0.2:
resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==}
dev: true
@ -2340,6 +2644,11 @@ packages:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
dev: true
/jiti@1.18.2:
resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==}
hasBin: true
dev: true
/js-sdsl@4.2.0:
resolution: {integrity: sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==}
dev: true
@ -2371,6 +2680,10 @@ packages:
engines: {node: '>=6'}
dev: true
/kolorist@1.7.0:
resolution: {integrity: sha512-ymToLHqL02udwVdbkowNpzjFd6UzozMtshPQKVi5k1EjKRqKqBrOnE9QbLEb0/pV76SAiIT13hdL8R6suc+f3g==}
dev: true
/levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@ -2379,6 +2692,11 @@ packages:
type-check: 0.4.0
dev: true
/local-pkg@0.4.3:
resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
engines: {node: '>=14'}
dev: true
/locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@ -2418,6 +2736,10 @@ packages:
'@jridgewell/sourcemap-codec': 1.4.14
dev: true
/mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
dev: true
/merge-stream@2.0.0:
resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
dev: true
@ -2506,6 +2828,10 @@ packages:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
dev: true
/node-fetch-native@0.1.8:
resolution: {integrity: sha512-ZNaury9r0NxaT2oL65GvdGDy+5PlSaHTovT6JV5tOW07k1TQmgC0olZETa4C9KZg0+6zBr99ctTYa3Utqj9P/Q==}
dev: true
/normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@ -2546,6 +2872,15 @@ packages:
es-abstract: 1.20.5
dev: true
/ohmyfetch@0.4.21:
resolution: {integrity: sha512-VG7f/JRvqvBOYvL0tHyEIEG7XHWm7OqIfAs6/HqwWwDfjiJ1g0huIpe5sFEmyb+7hpFa1EGNH2aERWR72tlClw==}
dependencies:
destr: 1.2.2
node-fetch-native: 0.1.8
ufo: 0.8.6
undici: 5.22.0
dev: true
/once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
dependencies:
@ -2635,6 +2970,14 @@ packages:
engines: {node: '>=8'}
dev: true
/pathe@0.3.9:
resolution: {integrity: sha512-6Y6s0vT112P3jD8dGfuS6r+lpa0qqNrLyHPOwvXMnyNTQaYiwgau2DP3aNDsR13xqtGj7rrPo+jFUATpU6/s+g==}
dev: true
/perfect-debounce@0.1.3:
resolution: {integrity: sha512-NOT9AcKiDGpnV/HBhI22Str++XWcErO/bALvHCuhv33owZW/CjH8KAFLZDCmu3727sihe0wTxpDhyGc6M8qacQ==}
dev: true
/picocolors@1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
@ -2747,6 +3090,14 @@ packages:
glob: 7.2.3
dev: true
/rollup@2.77.3:
resolution: {integrity: sha512-/qxNTG7FbmefJWoeeYJFbHehJ2HNWnjkAFRKzWN/45eNBBF/r8lo992CwcJXEzyVxs5FmfId+vTSTQDb+bxA+g==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/rollup@2.79.1:
resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==}
engines: {node: '>=10.0.0'}
@ -3147,6 +3498,10 @@ packages:
hasBin: true
dev: true
/ufo@0.8.6:
resolution: {integrity: sha512-fk6CmUgwKCfX79EzcDQQpSCMxrHstvbLswFChHS0Vump+kFkw7nJBfTZoC1j0bOGoY9I7R3n2DGek5ajbcYnOw==}
dev: true
/unbox-primitive@1.0.2:
resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==}
dependencies:
@ -3156,6 +3511,14 @@ packages:
which-boxed-primitive: 1.0.2
dev: true
/unconfig@0.3.7:
resolution: {integrity: sha512-1589b7oGa8ILBYpta7TndM5mLHLzHUqBfhszeZxuUBrjO/RoQ52VGVWsS3w0C0GLNxO9RPmqkf6BmIvBApaRdA==}
dependencies:
'@antfu/utils': 0.5.2
defu: 6.1.2
jiti: 1.18.2
dev: true
/undici@5.22.0:
resolution: {integrity: sha512-fR9RXCc+6Dxav4P9VV/sp5w3eFiSdOjJYsbtWfd4s5L5C4ogyuVpdKIVHeW0vV1MloM65/f7W45nR9ZxwVdyiA==}
engines: {node: '>=14.0'}
@ -3163,6 +3526,35 @@ packages:
busboy: 1.6.0
dev: true
/unocss@0.39.3(vite@3.0.9):
resolution: {integrity: sha512-+BZazovI1A+jlW0+GuSSABHQjBLpu2sQkLXriBTdZiPYZAqJJdiWHuQ6VPzF4Al5WM4VPpOgX5mUYWusJ813qw==}
engines: {node: '>=14'}
peerDependencies:
'@unocss/webpack': 0.39.3
peerDependenciesMeta:
'@unocss/webpack':
optional: true
dependencies:
'@unocss/cli': 0.39.3
'@unocss/core': 0.39.3
'@unocss/preset-attributify': 0.39.3
'@unocss/preset-icons': 0.39.3
'@unocss/preset-mini': 0.39.3
'@unocss/preset-tagify': 0.39.3
'@unocss/preset-typography': 0.39.3
'@unocss/preset-uno': 0.39.3
'@unocss/preset-web-fonts': 0.39.3
'@unocss/preset-wind': 0.39.3
'@unocss/reset': 0.39.3
'@unocss/transformer-compile-class': 0.39.3
'@unocss/transformer-directives': 0.39.3
'@unocss/transformer-variant-group': 0.39.3
'@unocss/vite': 0.39.3(vite@3.0.9)
transitivePeerDependencies:
- supports-color
- vite
dev: true
/uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies:
@ -3196,6 +3588,33 @@ packages:
fsevents: 2.3.2
dev: true
/vite@3.0.9:
resolution: {integrity: sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw==}
engines: {node: ^14.18.0 || >=16.0.0}
hasBin: true
peerDependencies:
less: '*'
sass: '*'
stylus: '*'
terser: ^5.4.0
peerDependenciesMeta:
less:
optional: true
sass:
optional: true
stylus:
optional: true
terser:
optional: true
dependencies:
esbuild: 0.14.54
postcss: 8.4.23
resolve: 1.22.1
rollup: 2.77.3
optionalDependencies:
fsevents: 2.3.2
dev: true
/vite@4.3.3:
resolution: {integrity: sha512-MwFlLBO4udZXd+VBcezo3u8mC77YQk+ik+fbc0GZWGgzfbPP+8Kf0fldhARqvSYmtIWoAJ5BXPClUbMTlqFxrA==}
engines: {node: ^14.18.0 || >=16.0.0}

@ -1,3 +1,4 @@
packages:
- plugins/*
- plugins/*/examples/*
- examples/*

Loading…
Cancel
Save