feat: add API example (#317)
@ -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.
|
||||
|
||||

|
||||
|
||||
## 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"
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 2.7 KiB |
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/
|
@ -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>
|
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 8.9 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 40 KiB |
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'
|
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 49 KiB |
After Width: | Height: | Size: 804 B |
After Width: | Height: | Size: 4.3 KiB |
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 <PATH>
|
||||
--theme <light|dark|system>
|
||||
--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"],
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
packages:
|
||||
- plugins/*
|
||||
- plugins/*/examples/*
|
||||
- examples/*
|
||||
|