Merge pull request #4 from JonasKruckenberg/feat/window

wip
This commit is contained in:
Jonas Kruckenberg 2022-11-15 13:00:14 +01:00 committed by GitHub
commit dae8f59fd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
82 changed files with 14879 additions and 963 deletions

52
.github/workflows/test.yml vendored Normal file
View file

@ -0,0 +1,52 @@
name: Test
on:
push:
branches:
- main
paths:
- '.github/workflows/test.yml'
- 'src/**'
- 'examples/test/**'
pull_request:
branches:
- main
paths:
- '.github/workflows/test.yml'
- 'src/**'
- 'examples/test/**'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3
- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v1
- name: Install native deps
run: |
sudo apt-get update
sudo apt-get install -y webkit2gtk-4.0 at-spi2-core
- name: Install Tauri CLI
run: |
cd examples/test
wget -qO- https://github.com/tauri-apps/tauri/releases/download/cli.rs-v1.2.0/cargo-tauri-x86_64-unknown-linux-gnu.tgz | tar -xzf- -C ~/.cargo/bin
- name: Install Trunk
run: |
cd examples/test
wget -qO- https://github.com/thedodd/trunk/releases/download/v0.16.0/trunk-x86_64-unknown-linux-gnu.tar.gz | tar -xzf- -C ~/.cargo/bin
- name: Run test app
run: |
cd examples/test
export CARGO_UNSTABLE_SPARSE_REGISTRY=true
xvfb-run cargo tauri dev --exit-on-panic --config ./src-tauri/ci.tauri.conf.json

3591
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -11,19 +11,28 @@ js-sys = "0.3.59"
serde = { version = "1.0.140", features = ["derive"] }
wasm-bindgen = { version = "0.2.82", features = ["serde_json"] }
wasm-bindgen-futures = "0.4.32"
url = { version = "2.3.1", optional = true }
url = { version = "2.3.1", optional = true, features = ["serde"] }
thiserror = "1.0.37"
semver = { version = "1.0.14", optional = true }
semver = { version = "1.0.14", optional = true, features = ["serde"] }
[dev-dependencies]
wasm-bindgen-test = "0.3.33"
tauri-sys = { path = ".", features = ["all"] }
# tauri = "1.1.1"
[package.metadata.docs.rs]
all-features = true
[features]
all = ["app", "clipboard", "dialog", "event", "mocks", "tauri"]
all = ["app", "clipboard", "event", "mocks", "tauri", "window", "process", "dialog"]
app = ["dep:semver"]
clipboard = []
dialog = []
event = []
mocks = []
tauri = ["dep:url"]
window = []
process = []
[workspace]
members = ["examples/test", "examples/test/src-tauri"]

View file

@ -1,31 +0,0 @@
use std::process::Command;
fn main() {
/* Shared arguments */
let sargs: [&str; 8] = [
"--outdir=dist",
"--format=esm",
"--bundle",
"tauri/tooling/api/src/app.ts",
"tauri/tooling/api/src/clipboard.ts",
"tauri/tooling/api/src/tauri.ts",
"tauri/tooling/api/src/event.ts",
"tauri/tooling/api/src/mocks.ts",
];
if cfg!(windows) {
/* Use cmd if the target is windows */
Command::new("cmd")
.args(&["/C", "esbuild"])
.args(&sargs)
.output()
.unwrap();
} else if cfg!(unix) {
Command::new("esbuild")
.args(&sargs)
.output()
.unwrap();
} else {
panic!("Unsupported build target");
}
}

View file

@ -1,3 +0,0 @@
/dist/
/target/
/Cargo.lock

View file

@ -1,16 +0,0 @@
[build]
target = "./index.html"
[watch]
ignore = ["./src-tauri"]
[serve]
address = "127.0.0.1"
port = 1420
open = false
# [[hooks]]
# # Runs SSG on production builds
# stage = "post_build"
# command = "bash"
# command_arguments = ["-c", "if [[ $TRUNK_PROFILE == \"release\" ]]; then cargo run --release --features ssg -- $TRUNK_STAGING_DIR; fi"]

View file

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Tauri + Yew App</title>
<link data-trunk rel="css" href="style.css" />
<link data-trunk rel="copy-dir" href="public" />
</head>
<body>
<!--app-html-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,9 +0,0 @@
[package]
name = "shared"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0.140", features = ["derive"] }

View file

@ -1,12 +0,0 @@
use serde::{Serialize, Deserialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Reply<'a> {
pub data: &'a str,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RequestBody<'a> {
pub id: i32,
pub name: &'a str,
}

View file

@ -1,37 +0,0 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use shared::{Reply, RequestBody};
#[tauri::command]
fn log_operation(event: String, payload: Option<String>) {
println!("{} {:?}", event, payload);
}
#[tauri::command]
fn perform_request(endpoint: String, body: RequestBody) -> String {
println!("{} {:?}", endpoint, body);
"message response".into()
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![log_operation, perform_request])
.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",
};
window_
.emit("rust-event", Some(reply))
.expect("failed to emit");
});
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View file

@ -1,79 +0,0 @@
mod views;
use sycamore::prelude::*;
#[cfg(not(feature = "ssg"))]
use sycamore_router::{Router, HistoryIntegration};
#[component]
fn Header<G: Html>(cx: Scope) -> View<G> {
view! { cx,
header(style="display: flex; gap: 1em; margin-bottom: 1em;") {
a(href="/") {
"Welcome"
}
a(href="/app") {
"App"
}
a(href="/clipboard") {
"Clipboard"
}
a(href="/communication") {
"Communication"
}
}
}
}
#[cfg(all(not(debug_assertions), not(feature = "ssg")))]
fn main() {
wasm_logger::init(wasm_logger::Config::default());
sycamore::hydrate(|cx| view! { cx,
Header
Router(
integration=HistoryIntegration::new(),
view=views::switch
)
});
}
#[cfg(all(debug_assertions, not(feature = "ssg")))]
fn main() {
use sycamore::view;
wasm_logger::init(wasm_logger::Config::default());
sycamore::render(|cx| view! { cx,
Header
Router(
integration=HistoryIntegration::new(),
view=views::switch
)
});
}
#[cfg(feature = "ssg")]
fn main() {
use sycamore_router::StaticRouter;
let out_dir = std::env::args().nth(1).unwrap();
println!("out_dir {}", out_dir);
let template = std::fs::read_to_string(format!("{}/index.html", out_dir)).unwrap();
let html = sycamore::render_to_string(|cx| view! { cx,
Header
StaticRouter(
route=route.clone(),
view=views::switch
)
});
let html = template.replace("<!--app-html-->\n", &html);
let path = format!("{}/index.html", out_dir);
println!("Writing html to file \"{}\"", path);
std::fs::write(path, html).unwrap();
}

View file

@ -1,78 +0,0 @@
use gloo_timers::callback::Timeout;
use sycamore::prelude::*;
use tauri_sys::app;
#[component]
pub fn App<G: Html>(cx: Scope) -> View<G> {
let show_app = |_| {
sycamore::futures::spawn_local(async move {
let res = app::hide().await;
log::debug!("app hide res {:?}", res);
let timeout = Timeout::new(2_000, move || {
sycamore::futures::spawn_local(async move {
let res = app::show().await;
log::debug!("app show res {:?}", res);
});
});
timeout.forget();
});
};
let hide_app = |_| {
sycamore::futures::spawn_local(async move {
let res = app::hide().await;
log::debug!("app hide res {:?}", res);
});
};
let get_name = |_| {
sycamore::futures::spawn_local(async move {
let res = app::get_name().await;
log::debug!("app name {:?}", res);
});
};
let get_version = |_| {
sycamore::futures::spawn_local(async move {
let res = app::get_version().await;
log::debug!("app version {:?}", res);
});
};
let get_tauri_version = |_| {
sycamore::futures::spawn_local(async move {
let res = app::get_tauri_version().await;
log::debug!("tauri version {:?}", res);
});
};
view! { cx,
div {
button(class="btn",id="get_name",on:click=get_name) {
"Get App Name"
}
button(class="btn",id="get_version",on:click=get_version) {
"Get App Version"
}
button(class="btn",id="get_tauri_version",on:click=get_tauri_version) {
"Get Tauri Version"
}
}
div {
button(class="btn",id="show",title="Hides and shows the app after 2 seconds",on:click=show_app) {
"Show"
}
button(class="btn",id="hide",on:click=hide_app) {
"Hide"
}
}
}
}

View file

@ -1,42 +0,0 @@
use sycamore::prelude::*;
use tauri_sys::clipboard::{read_text, write_text};
#[component]
pub fn Clipboard<G: Html>(cx: Scope) -> View<G> {
let text = create_signal(cx, "clipboard message".to_string());
let write = move |_| {
sycamore::futures::spawn_local_scoped(cx, async move {
write_text(&text.get()).await
// .then(() => {
// onMessage('Wrote to the clipboard')
// })
// .catch(onMessage)
});
};
let read = |_| {
sycamore::futures::spawn_local(async move {
let text = read_text().await;
log::info!("Read text from clipboard {:?}", text);
// readText()
// .then((contents) => {
// onMessage(`Clipboard contents: ${contents}`)
// })
// .catch(onMessage)
});
};
view! { cx,
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(class="btn",type="button",on:click=read) {
"Read"
}
}
}
}

View file

@ -1,90 +0,0 @@
use serde::{Deserialize, Serialize};
use sycamore::prelude::*;
use tauri_sys::event::{emit, listen};
use tauri_sys::tauri::invoke;
use shared::RequestBody;
#[component]
pub fn Communication<'a, G: Html>(cx: Scope<'a>) -> View<G> {
let unlisten = create_signal::<Option<Box<&dyn FnOnce()>>>(cx, None);
// on_mount(cx, move || {
// sycamore::futures::spawn_local_scoped(cx, async move {
// let unlisten_raw = listen::<Reply>("rust-event", &|reply| log::debug!("got reply {:?}", reply)).await;
// unlisten.set(Some(Box::new(&unlisten_raw)));
// });
// });
// on_cleanup(cx, || {
// if let Some(unlisten) = unlisten .take().as_deref() {
// (unlisten)()
// }
// });
let log = |_| {
#[derive(Serialize)]
struct Payload<'a> {
event: &'a str,
payload: &'a str,
}
sycamore::futures::spawn_local(async move {
let res = invoke::<_, ()>(
"log_operation",
&Payload {
event: "tauri-click",
payload: "this payload is optional because we used Option in Rust",
},
)
.await;
log::debug!("Emitted event, response {:?}", res);
});
};
let perform_request = |_| {
sycamore::futures::spawn_local(async move {
#[derive(Serialize)]
struct Payload<'a> {
endpoint: &'a str,
body: RequestBody<'a>
}
let res = invoke::<_, String>(
"perform_request",
&Payload {
endpoint: "dummy endpoint arg",
body: RequestBody {
id: 5,
name: "test",
},
},
)
.await;
log::debug!("Got reply {:?}", res);
});
};
let emit_event = |_| {
sycamore::futures::spawn_local(async move {
emit("js-event", &"this is the payload string").await;
});
};
view! { cx,
div {
button(class="btn",id="log",on:click=log) {
"Call Log API"
}
button(class="btn",mid="request",on:click=perform_request) {
"Call Request (async) API"
}
button(class="btn",id="event",on:click=emit_event) {
"Send event to Rust"
}
}
}
}

View file

@ -1,29 +0,0 @@
mod app;
mod clipboard;
mod communication;
mod welcome;
use sycamore::view::View;
use sycamore_router::Route;
use sycamore::prelude::*;
#[derive(Debug, Clone, Route)]
pub enum Page {
#[to("/app")]
App,
#[to("/clipboard")]
Clipboard,
#[to("/communication")]
Communication,
#[not_found]
NotFound
}
pub fn switch<G: Html>(cx: Scope, route: &ReadSignal<Page>) -> View<G> {
match route.get().as_ref() {
Page::App => app::App(cx),
Page::Clipboard => clipboard::Clipboard(cx),
Page::Communication => communication::Communication(cx),
Page::NotFound => welcome::Welcome(cx)
}
}

View file

@ -1,10 +0,0 @@
use sycamore::prelude::*;
#[component]
pub fn Welcome<G: Html>(cx: Scope) -> View<G> {
view! { cx,
h1 {
"Welcome"
}
}
}

View file

@ -1,120 +0,0 @@
.logo.yew:hover {
filter: drop-shadow(0 0 2em #20a88a);
}
.logo.sycamore {
color: #0000;
font-size: 3rem;
line-height: 1;
font-weight: 800;
-webkit-background-clip: text;
background-clip: text;
background-image: linear-gradient(to right,#fdba74, #f87171);
font-family: Inter,system-ui,sans-serif;
}
.logo.sycamore:hover {
filter: drop-shadow(0 0 2em #f87171);
}
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
.container {
margin: 0;
padding-top: 10vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
align-items: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
}

2
examples/test/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target
dist

3894
examples/test/Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,21 +1,19 @@
[package]
name = "tauri-app-ui"
name = "tauri-sys-test-ui"
version = "0.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tauri-sys = { path = "../../", features = ["all"] }
serde = { version = "1.0.140", features = ["derive"] }
sycamore = { git = "https://github.com/sycamore-rs/sycamore", rev = "abd556cbc02047042dad2ebd04405e455a9b11b2", features = ["suspense", "hydrate"] }
sycamore-router = { git = "https://github.com/sycamore-rs/sycamore", rev = "abd556cbc02047042dad2ebd04405e455a9b11b2" }
log = "0.4.17"
sycamore = { git = "https://github.com/sycamore-rs/sycamore", rev = "abd556cbc02047042dad2ebd04405e455a9b11b2", features = ["suspense"] }
anyhow = "1.0.66"
console_error_panic_hook = "0.1.7"
wasm-bindgen-futures = "0.4.32"
futures-util = "0.3.25"
serde = { version = "1.0.147", features = ["derive"] }
wasm-logger = "0.2.0"
gloo-timers = "0.2.4"
shared = { path = "shared" }
log = "0.4.17"
[features]
ssg = ["sycamore/ssr"]
[workspace]
members = ["src-tauri", "shared"]
ci = []

10
examples/test/Trunk.toml Normal file
View file

@ -0,0 +1,10 @@
[build]
target = "./index.html"
[watch]
ignore = ["./src-tauri"]
[serve]
address = "127.0.0.1"
port = 1420
open = false

7
examples/test/index.html Normal file
View file

@ -0,0 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Tauri + Yew App</title>
</head>
</html>

View file

@ -1,5 +1,5 @@
[package]
name = "tauri-app"
name = "tauri-sys-test"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
@ -11,13 +11,12 @@ rust-version = "1.57"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { git = "https://github.com/tauri-apps/tauri", features = [] }
tauri-build = { version = "1.2", features = [] }
[dependencies]
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
tauri = { git = "https://github.com/tauri-apps/tauri", features = ["api-all"] }
shared = { path = "../shared" }
tauri = { version = "1.2", features = ["api-all"] }
[features]
# by default Tauri runs in production mode

View file

@ -0,0 +1,6 @@
{
"build": {
"beforeDevCommand": "trunk serve --features ci",
"beforeBuildCommand": "trunk build --release --features ci"
}
}

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View file

Before

Width:  |  Height:  |  Size: 974 B

After

Width:  |  Height:  |  Size: 974 B

View file

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

View file

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

Before

Width:  |  Height:  |  Size: 903 B

After

Width:  |  Height:  |  Size: 903 B

View file

Before

Width:  |  Height:  |  Size: 8.4 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 85 KiB

View file

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,41 @@
#![cfg_attr(
all(not(debug_assertions), target_os = "windows"),
windows_subsystem = "windows"
)]
use std::sync::atomic::{AtomicBool, Ordering};
use tauri::{Manager, State};
struct Received(AtomicBool);
// Learn more about Tauri commands at https://tauri.app/v1/guides/features/command
#[tauri::command]
fn verify_receive(emitted: State<Received>) -> bool {
emitted.0.load(Ordering::Relaxed)
}
#[tauri::command]
fn exit_with_error(e: &str) -> bool {
eprintln!("{}", e);
std::process::exit(1);
}
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![verify_receive, exit_with_error])
.setup(|app| {
app.manage(Received(AtomicBool::new(false)));
let app_handle = app.handle();
app.listen_global("foo", move |_| {
app_handle
.state::<Received>()
.0
.store(true, Ordering::Relaxed);
});
Ok(())
})
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

View file

@ -7,7 +7,7 @@
"withGlobalTauri": true
},
"package": {
"productName": "tauri-app",
"productName": "tauri-sys-test",
"version": "0.0.0"
},
"tauri": {

34
examples/test/src/app.rs Normal file
View file

@ -0,0 +1,34 @@
use anyhow::ensure;
use tauri_sys::app;
pub async fn get_name() -> anyhow::Result<()> {
let name = app::get_name().await?;
ensure!(name == "tauri-sys-test");
Ok(())
}
pub async fn get_version() -> anyhow::Result<()> {
let version = app::get_version().await?;
ensure!(version.major == 0);
ensure!(version.minor == 0);
ensure!(version.patch == 0);
ensure!(version.build.is_empty());
ensure!(version.pre.is_empty());
Ok(())
}
pub async fn get_tauri_version() -> anyhow::Result<()> {
let version = app::get_tauri_version().await?;
ensure!(version.major == 1);
ensure!(version.minor == 2);
ensure!(version.patch == 0);
ensure!(version.build.is_empty());
ensure!(version.pre.is_empty());
Ok(())
}

View file

@ -0,0 +1,12 @@
use anyhow::ensure;
use tauri_sys::clipboard;
pub async fn test() -> anyhow::Result<()> {
clipboard::write_text("foobar").await?;
let text = clipboard::read_text().await?;
ensure!(text == "foobar".to_string());
Ok(())
}

View file

@ -0,0 +1,97 @@
use anyhow::ensure;
use tauri_sys::dialog::{FileDialogBuilder, MessageDialogBuilder, MessageDialogType};
pub async fn ask() -> anyhow::Result<()> {
let mut builder = MessageDialogBuilder::new();
builder.set_title("Tauri");
builder.set_type(MessageDialogType::Warning);
let works = builder
.ask("Does this work? \n Click Yes to mark this test as passing")
.await?;
ensure!(works);
Ok(())
}
pub async fn confirm() -> anyhow::Result<()> {
let mut builder = MessageDialogBuilder::new();
builder.set_title("Tauri");
builder.set_type(MessageDialogType::Warning);
let works = builder
.confirm("Does this work? \n Click Ok to mark this test as passing")
.await?;
ensure!(works);
Ok(())
}
pub async fn message() -> anyhow::Result<()> {
let mut builder = MessageDialogBuilder::new();
builder.set_title("Tauri");
builder.set_type(MessageDialogType::Warning);
builder.message("This is a message just for you!").await?;
Ok(())
}
pub async fn pick_file() -> anyhow::Result<()> {
let mut builder = FileDialogBuilder::new();
builder.set_title("Select a file to mark this test as passing");
let file = builder.pick_file().await?;
ensure!(file.is_some());
Ok(())
}
pub async fn pick_files() -> anyhow::Result<()> {
let mut builder = FileDialogBuilder::new();
builder.set_title("Select a multiple files to mark this test as passing");
let file = builder.pick_files().await?;
ensure!(file.is_some());
ensure!(file.unwrap().len() > 1);
Ok(())
}
pub async fn pick_folder() -> anyhow::Result<()> {
let mut builder = FileDialogBuilder::new();
builder.set_title("Select a folder to mark this test as passing");
let file = builder.pick_folder().await?;
ensure!(file.is_some());
Ok(())
}
pub async fn pick_folders() -> anyhow::Result<()> {
let mut builder = FileDialogBuilder::new();
builder.set_title("Select a multiple folders to mark this test as passing");
let file = builder.pick_folders().await?;
ensure!(file.is_some());
ensure!(file.unwrap().len() > 1);
Ok(())
}
pub async fn save() -> anyhow::Result<()> {
let mut builder = FileDialogBuilder::new();
builder.set_title("Select a file to mark this test as passing");
let file = builder.save().await?;
ensure!(file.is_some());
Ok(())
}

View file

@ -0,0 +1,10 @@
use anyhow::ensure;
use tauri_sys::{event, tauri};
pub async fn emit() -> anyhow::Result<()> {
event::emit("foo", &"bar").await?;
ensure!(tauri::invoke::<_, bool>("verify_receive", &()).await?);
Ok(())
}

165
examples/test/src/main.rs Normal file
View file

@ -0,0 +1,165 @@
mod app;
mod clipboard;
mod event;
mod window;
mod dialog;
extern crate console_error_panic_hook;
use std::future::Future;
use std::panic;
use sycamore::prelude::*;
use sycamore::suspense::Suspense;
#[cfg(feature = "ci")]
async fn exit_with_error(e: String) {
use serde::Serialize;
#[derive(Serialize)]
struct Args {
e: String,
}
tauri_sys::tauri::invoke::<_, ()>("exit_with_error", &Args { e })
.await
.unwrap();
}
#[derive(Props)]
pub struct TestProps<'a, F>
where
F: Future<Output = anyhow::Result<()>> + 'a,
{
name: &'a str,
test: F,
}
#[component]
pub async fn Test<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
where
F: Future<Output = anyhow::Result<()>> + 'a,
{
let res = props.test.await;
view! { cx,
tr {
td { code { (props.name.to_string()) } }
td { (if let Err(e) = &res {
#[cfg(feature = "ci")]
{
wasm_bindgen_futures::spawn_local(exit_with_error(e.to_string()));
unreachable!()
}
#[cfg(not(feature = "ci"))]
format!("{:?}", e)
} else {
format!("")
})
}
}
}
}
#[cfg(not(feature = "ci"))]
#[component]
pub async fn InteractiveTest<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
where
F: Future<Output = anyhow::Result<()>> + 'a,
{
let mut test = Some(props.test);
let render_test = create_signal(cx, false);
let run_test = |_| {
render_test.set(true);
};
view! { cx,
(if *render_test.get() {
let test = test.take().unwrap();
let fallback = view! { cx,
tr {
td { code { (props.name.to_string()) } }
td {
"Running Test..."
}
}
};
view! { cx,
Suspense(fallback=fallback) {
Test(name=props.name, test=test)
}
}
} else {
view! { cx,
tr {
td { code { (props.name.to_string()) } }
td {
button(on:click=run_test) { "Run Interactive Test"}
}
}
}
})
}
}
#[cfg(feature = "ci")]
#[component]
pub async fn InteractiveTest<'a, G: Html, F>(cx: Scope<'a>, _props: TestProps<'a, F>) -> View<G>
where
F: Future<Output = anyhow::Result<()>> + 'a,
{
view! { cx, "Interactive tests are not run in CI mode" }
}
#[component]
pub async fn Terminate<'a, G: Html>(cx: Scope<'a>) -> View<G> {
#[cfg(feature = "ci")]
sycamore::suspense::await_suspense(cx, async {
tauri_sys::process::exit(0).await;
})
.await;
view! {
cx,
}
}
fn main() {
wasm_logger::init(wasm_logger::Config::default());
panic::set_hook(Box::new(|info| {
console_error_panic_hook::hook(info);
#[cfg(feature = "ci")]
wasm_bindgen_futures::spawn_local(exit_with_error(format!("{}", info)));
}));
sycamore::render(|cx| {
view! { cx,
table {
tbody {
Suspense(fallback=view!{ cx, "Running Tests..." }) {
Test(name="app::get_name",test=app::get_name())
Test(name="app::get_version",test=app::get_version())
Test(name="app::get_tauri_version",test=app::get_tauri_version())
Test(name="clipboard::read_text | clipboard::write_text",test=clipboard::test())
Test(name="event::emit",test=event::emit())
InteractiveTest(name="dialog::message",test=dialog::message())
InteractiveTest(name="dialog::ask",test=dialog::ask())
InteractiveTest(name="dialog::confirm",test=dialog::confirm())
InteractiveTest(name="dialog::pick_file",test=dialog::pick_file())
InteractiveTest(name="dialog::pick_files",test=dialog::pick_files())
InteractiveTest(name="dialog::pick_folder",test=dialog::pick_folder())
InteractiveTest(name="dialog::pick_folders",test=dialog::pick_folders())
InteractiveTest(name="dialog::save",test=dialog::save())
// Test(name="window::WebviewWindow::new",test=window::create_window())
Terminate
}
}
}
}
});
}

View file

@ -0,0 +1,13 @@
use anyhow::ensure;
use tauri_sys::window;
pub async fn create_window() -> anyhow::Result<()> {
let win = window::WebviewWindow::new("foo", ());
ensure!(win.is_visible().await?);
// ensure!(win.label() == "foo".to_string());
win.close().await?;
Ok(())
}

15
package.json Normal file
View file

@ -0,0 +1,15 @@
{
"name": "tauri-sys",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "esbuild --outdir=src --format=esm --bundle tauri/tooling/api/src/*.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"esbuild": "^0.15.13"
}
}

237
pnpm-lock.yaml generated Normal file
View file

@ -0,0 +1,237 @@
lockfileVersion: 5.4
specifiers:
esbuild: ^0.15.13
devDependencies:
esbuild: 0.15.13
packages:
/@esbuild/android-arm/0.15.13:
resolution: {integrity: sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==}
engines: {node: '>=12'}
cpu: [arm]
os: [android]
requiresBuild: true
dev: true
optional: true
/@esbuild/linux-loong64/0.15.13:
resolution: {integrity: sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==}
engines: {node: '>=12'}
cpu: [loong64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-android-64/0.15.13:
resolution: {integrity: sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==}
engines: {node: '>=12'}
cpu: [x64]
os: [android]
requiresBuild: true
dev: true
optional: true
/esbuild-android-arm64/0.15.13:
resolution: {integrity: sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==}
engines: {node: '>=12'}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-64/0.15.13:
resolution: {integrity: sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==}
engines: {node: '>=12'}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-arm64/0.15.13:
resolution: {integrity: sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==}
engines: {node: '>=12'}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-64/0.15.13:
resolution: {integrity: sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==}
engines: {node: '>=12'}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-arm64/0.15.13:
resolution: {integrity: sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==}
engines: {node: '>=12'}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-32/0.15.13:
resolution: {integrity: sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==}
engines: {node: '>=12'}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-64/0.15.13:
resolution: {integrity: sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==}
engines: {node: '>=12'}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm/0.15.13:
resolution: {integrity: sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==}
engines: {node: '>=12'}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm64/0.15.13:
resolution: {integrity: sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==}
engines: {node: '>=12'}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-mips64le/0.15.13:
resolution: {integrity: sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==}
engines: {node: '>=12'}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-ppc64le/0.15.13:
resolution: {integrity: sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==}
engines: {node: '>=12'}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-riscv64/0.15.13:
resolution: {integrity: sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==}
engines: {node: '>=12'}
cpu: [riscv64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-s390x/0.15.13:
resolution: {integrity: sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==}
engines: {node: '>=12'}
cpu: [s390x]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-netbsd-64/0.15.13:
resolution: {integrity: sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-openbsd-64/0.15.13:
resolution: {integrity: sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==}
engines: {node: '>=12'}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-sunos-64/0.15.13:
resolution: {integrity: sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==}
engines: {node: '>=12'}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-32/0.15.13:
resolution: {integrity: sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==}
engines: {node: '>=12'}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-64/0.15.13:
resolution: {integrity: sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==}
engines: {node: '>=12'}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-arm64/0.15.13:
resolution: {integrity: sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==}
engines: {node: '>=12'}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild/0.15.13:
resolution: {integrity: sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==}
engines: {node: '>=12'}
hasBin: true
requiresBuild: true
optionalDependencies:
'@esbuild/android-arm': 0.15.13
'@esbuild/linux-loong64': 0.15.13
esbuild-android-64: 0.15.13
esbuild-android-arm64: 0.15.13
esbuild-darwin-64: 0.15.13
esbuild-darwin-arm64: 0.15.13
esbuild-freebsd-64: 0.15.13
esbuild-freebsd-arm64: 0.15.13
esbuild-linux-32: 0.15.13
esbuild-linux-64: 0.15.13
esbuild-linux-arm: 0.15.13
esbuild-linux-arm64: 0.15.13
esbuild-linux-mips64le: 0.15.13
esbuild-linux-ppc64le: 0.15.13
esbuild-linux-riscv64: 0.15.13
esbuild-linux-s390x: 0.15.13
esbuild-netbsd-64: 0.15.13
esbuild-openbsd-64: 0.15.13
esbuild-sunos-64: 0.15.13
esbuild-windows-32: 0.15.13
esbuild-windows-64: 0.15.13
esbuild-windows-arm64: 0.15.13
dev: true

View file

@ -9,8 +9,10 @@ use semver::Version;
/// const appName = await getName();
/// ```
#[inline(always)]
pub async fn get_name() -> String {
inner::getName().await.as_string().unwrap()
pub async fn get_name() -> crate::Result<String> {
let js_val = inner::getName().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
/// Gets the application version.
@ -23,8 +25,10 @@ pub async fn get_name() -> String {
/// let version = get_version().await;
/// ```
#[inline(always)]
pub async fn get_version() -> Version {
Version::parse(&inner::getVersion().await.as_string().unwrap()).unwrap()
pub async fn get_version() -> crate::Result<Version> {
let js_val = inner::getVersion().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
/// Gets the Tauri version.
@ -37,8 +41,10 @@ pub async fn get_version() -> Version {
/// let version = get_tauri_version().await;
/// ```
#[inline(always)]
pub async fn get_tauri_version() -> Version {
Version::parse(&inner::getTauriVersion().await.as_string().unwrap()).unwrap()
pub async fn get_tauri_version() -> crate::Result<Version> {
let js_val = inner::getTauriVersion().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
/// Shows the application on macOS. This function does not automatically focuses any app window.
@ -51,8 +57,8 @@ pub async fn get_tauri_version() -> Version {
/// show().await;
/// ```
#[inline(always)]
pub async fn show() {
inner::show().await;
pub async fn show() -> crate::Result<()> {
Ok(inner::show().await?)
}
/// Hides the application on macOS.
@ -65,19 +71,24 @@ pub async fn show() {
/// hide().await;
/// ```
#[inline(always)]
pub async fn hide() {
inner::hide().await;
pub async fn hide() -> crate::Result<()> {
Ok(inner::hide().await?)
}
mod inner {
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[wasm_bindgen(module = "/dist/app.js")]
#[wasm_bindgen(module = "/src/app.js")]
extern "C" {
pub async fn getName() -> JsValue;
pub async fn getTauriVersion() -> JsValue;
pub async fn getVersion() -> JsValue;
pub async fn hide();
pub async fn show();
#[wasm_bindgen(catch)]
pub async fn getName() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn getTauriVersion() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn getVersion() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn hide() -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
pub async fn show() -> Result<(), JsValue>;
}
}

55
src/cli.js Normal file
View file

@ -0,0 +1,55 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/cli.ts
async function getMatches() {
return invokeTauriCommand({
__tauriModule: "Cli",
message: {
cmd: "cliMatches"
}
});
}
export {
getMatches
};

View file

@ -1,7 +1,3 @@
use wasm_bindgen_futures::JsFuture;
use crate::Error;
/// Gets the clipboard content as plain text.
///
/// # Example
@ -12,11 +8,10 @@ use crate::Error;
/// let clipboard_text = read_text().await;
/// ```
#[inline(always)]
pub async fn read_text() -> crate::Result<Option<String>> {
JsFuture::from(inner::readText())
.await
.map(|v| v.as_string())
.map_err(Error::Other)
pub async fn read_text() -> crate::Result<String> {
let js_val = inner::readText().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
/// Writes plain text to the clipboard.
@ -33,19 +28,17 @@ pub async fn read_text() -> crate::Result<Option<String>> {
/// @returns A promise indicating the success or failure of the operation.
#[inline(always)]
pub async fn write_text(text: &str) -> crate::Result<()> {
JsFuture::from(inner::writeText(text))
.await
.map_err(Error::Other)?;
Ok(())
Ok(inner::writeText(text).await?)
}
mod inner {
use wasm_bindgen::{prelude::wasm_bindgen};
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[wasm_bindgen(module = "/dist/clipboard.js")]
#[wasm_bindgen(module = "/src/clipboard.js")]
extern "C" {
pub fn readText() -> js_sys::Promise;
pub fn writeText(text: &str) -> js_sys::Promise;
#[wasm_bindgen(catch)]
pub async fn readText() -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn writeText(text: &str) -> Result<(), JsValue>;
}
}

View file

@ -2,12 +2,12 @@
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once3 = false) {
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once3) {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
@ -41,34 +41,12 @@ async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/helpers/dialog.ts
async function ask(message, options) {
return await invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "askDialog",
message: message.toString(),
title: options?.title?.toString(),
type: options?.type
}
});
}
async function confirm(message, options) {
return await invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "confirmDialog",
message: message.toString(),
title: options?.title?.toString(),
type: options?.type
}
});
}
async function open(options) {
if(!options) {
options = {multiple: false};
// tauri/tooling/api/src/dialog.ts
async function open(options = {}) {
if (typeof options === "object") {
Object.freeze(options);
}
return await invokeTauriCommand({
return invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "openDialog",
@ -76,31 +54,11 @@ async function open(options) {
}
});
}
async function open_multiple(options) {
if(!options) {
options = {multiple: true};
async function save(options = {}) {
if (typeof options === "object") {
Object.freeze(options);
}
return await invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "openDialog",
options
}
});
}
async function message(message, options) {
await invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "messageDialog",
message: message.toString(),
title: options?.title?.toString(),
type: options?.type
}
});
}
async function save(options) {
return await invokeTauriCommand({
return invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "saveDialog",
@ -108,11 +66,46 @@ async function save(options) {
}
});
}
export {
ask,
confirm,
open,
open_multiple,
message,
save
async function message(message2, options) {
const opts = typeof options === "string" ? { title: options } : options;
return invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "messageDialog",
message: message2.toString(),
title: opts?.title?.toString(),
type: opts?.type
}
});
}
async function ask(message2, options) {
const opts = typeof options === "string" ? { title: options } : options;
return invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "askDialog",
message: message2.toString(),
title: opts?.title?.toString(),
type: opts?.type
}
});
}
async function confirm(message2, options) {
const opts = typeof options === "string" ? { title: options } : options;
return invokeTauriCommand({
__tauriModule: "Dialog",
message: {
cmd: "confirmDialog",
message: message2.toString(),
title: opts?.title?.toString(),
type: opts?.type
}
});
}
export {
ask,
confirm,
message,
open,
save
};

View file

@ -1,273 +1,604 @@
//! User interaction with the file system using dialog boxes.
//!
//! # Example
//!
//! ```rust,no_run
//! use tauri_api::dialog::open;
//!
//! let path = open(None).await;
//! ```
use serde::Serialize;
use std::path::PathBuf;
use std::path::{Path, PathBuf};
/// Extension filter for the file dialog.
///
/// # Example
///
/// ```rust,no_run
/// let filter = DialogFilter {
/// extension: vec![".jpg", ".jpeg", ".png", ".bmp"],
/// name: "images",
/// };
/// ```
#[derive(Serialize)]
pub struct DialogFilter {
/// Extensions to filter, without a `.` prefix.
pub extensions: Vec<String>,
/// Filter name
pub name: String,
#[derive(Debug, Serialize)]
struct DialogFilter<'a> {
extensions: &'a [&'a str],
name: &'a str,
}
/// Types of a [`message`] dialog.
#[derive(Serialize)]
#[derive(Debug, Default, Serialize)]
#[serde(rename = "camelCase")]
pub struct FileDialogBuilder<'a> {
default_path: Option<&'a Path>,
filters: Vec<DialogFilter<'a>>,
title: Option<&'a str>,
directory: bool,
multiple: bool,
recursive: bool,
}
impl<'a> FileDialogBuilder<'a> {
/// Gets the default file dialog builder.
pub fn new() -> Self {
Self::default()
}
/// Set starting file name or directory of the dialog.
pub fn set_default_path(&mut self, default_path: &'a Path) {
self.default_path = Some(default_path);
}
/// If directory is true, indicates that it will be read recursively later.
/// Defines whether subdirectories will be allowed on the scope or not.
///
/// # Example
///
/// ```rust
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let _builder = FileDialogBuilder::new().set_recursive(true);
/// # Ok(())
/// # }
/// ```
pub fn set_recursive(&mut self, recursive: bool) {
self.recursive = recursive;
}
/// Set the title of the dialog.
///
/// # Example
///
/// ```rust
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let _builder = FileDialogBuilder::new().set_title("Test Title");
/// # Ok(())
/// # }
/// ```
pub fn set_title(&mut self, title: &'a str) {
self.title = Some(title);
}
/// Add file extension filter. Takes in the name of the filter, and list of extensions
///
/// # Example
///
/// ```rust
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let _builder = FileDialogBuilder::new().add_filter("Image", &["png", "jpeg"]);
/// # Ok(())
/// # }
/// ```
pub fn add_filter(&mut self, name: &'a str, extensions: &'a [&'a str]) {
self.filters.push(DialogFilter { name, extensions });
}
/// Add many file extension filters.
///
/// # Example
///
/// ```rust
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let _builder = FileDialogBuilder::new().add_filters(&[("Image", &["png", "jpeg"]),("Video", &["mp4"])]);
/// # Ok(())
/// # }
/// ```
pub fn add_filters(&mut self, filters: impl IntoIterator<Item = (&'a str, &'a [&'a str])>) {
for (name, extensions) in filters.into_iter() {
self.filters.push(DialogFilter {
name: name.as_ref(),
extensions,
});
}
}
/// Shows the dialog to select a single file.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let file = FileDialogBuilder::new().pick_file().await?;
/// # Ok(())
/// # }
/// ```
pub async fn pick_file(self) -> crate::Result<Option<PathBuf>> {
let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?;
Ok(serde_wasm_bindgen::from_value(raw)?)
}
/// Shows the dialog to select multiple files.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let files = FileDialogBuilder::new().pick_files().await?;
/// # Ok(())
/// # }
/// ```
pub async fn pick_files(mut self) -> crate::Result<Option<Vec<PathBuf>>> {
self.multiple = true;
let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?;
Ok(serde_wasm_bindgen::from_value(raw)?)
}
/// Shows the dialog to select a single folder.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let files = FileDialogBuilder::new().pick_folder().await?;
/// # Ok(())
/// # }
/// ```
pub async fn pick_folder(mut self) -> crate::Result<Option<PathBuf>> {
self.directory = true;
let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?;
Ok(serde_wasm_bindgen::from_value(raw)?)
}
/// Shows the dialog to select multiple folders.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let files = FileDialogBuilder::new().pick_folders().await?;
/// # Ok(())
/// # }
/// ```
pub async fn pick_folders(mut self) -> crate::Result<Option<Vec<PathBuf>>> {
self.directory = true;
self.multiple = true;
let raw = inner::open(serde_wasm_bindgen::to_value(&self)?).await?;
Ok(serde_wasm_bindgen::from_value(raw)?)
}
/// Open a file/directory save dialog.
///
/// The selected path is added to the filesystem and asset protocol allowlist scopes.
/// When security is more important than the easy of use of this API, prefer writing a dedicated command instead.
///
/// Note that the allowlist scope change is not persisted, so the values are cleared when the application is restarted.
/// You can save it to the filesystem using tauri-plugin-persisted-scope.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::FileDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let file = FileDialogBuilder::new().save().await?;
/// # Ok(())
/// # }
/// ```
pub async fn save(self) -> crate::Result<Option<PathBuf>> {
let raw = inner::save(serde_wasm_bindgen::to_value(&self)?).await?;
Ok(serde_wasm_bindgen::from_value(raw)?)
}
}
#[derive(Debug, Default)]
pub enum MessageDialogType {
Error,
#[default]
Info,
Warning,
Error,
}
/// Options for the [`message`] dialog.
#[derive(Serialize)]
pub struct MessageDialogOptions {
/// The title of the dialog. Defaults to the app name.
pub title: Option<String>,
/// The type of the dialog. Defaults to MessageDialogType::Info.
#[serde(rename(serialize = "type"))]
pub kind: MessageDialogType,
}
impl MessageDialogOptions {
/// Creates a new `MessageDialogOptions` with sensible default values.
pub fn new() -> Self {
Self {
title: None,
kind: MessageDialogType::Info,
impl Serialize for MessageDialogType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self {
MessageDialogType::Info => serializer.serialize_str("info"),
MessageDialogType::Warning => serializer.serialize_str("warning"),
MessageDialogType::Error => serializer.serialize_str("error"),
}
}
}
/// Options for an [`open`] dialog.
#[derive(Serialize)]
pub struct OpenDialogOptions {
/// Initial directory or file path.
#[serde(rename(serialize = "defaultPath"))]
pub default_path: Option<PathBuf>,
/// Whether the dialog is a directory selection or not.
pub directory: bool,
/// The filters of the dialog.
pub filters: Vec<DialogFilter>,
/// Whether the dialgo allows multiple selection or not.
pub multiple: bool,
/// If `directory` is `true`, indicatees that it will be read recursivley later.
/// Defines whether subdirectories will be allowed on the scope or not.
pub recursive: bool,
/// The title of the dialog window.
pub title: Option<String>,
#[derive(Debug, Default, Serialize)]
pub struct MessageDialogBuilder<'a> {
title: Option<&'a str>,
r#type: MessageDialogType,
}
impl OpenDialogOptions {
/// Creates a new `OpenDialogOptions` with sensible default values.
impl<'a> MessageDialogBuilder<'a> {
pub fn new() -> Self {
Self {
default_path: None,
directory: false,
filters: Vec::new(),
multiple: false,
recursive: false,
title: None,
}
Self::default()
}
/// Set the title of the dialog.
///
/// # Example
///
/// ```rust
/// use tauri_sys::dialog::MessageDialogBuilder;
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let _builder = MessageDialogBuilder::new().set_title("Test Title");
/// # Ok(())
/// # }
/// ```
pub fn set_title(&mut self, title: &'a str) {
self.title = Some(title);
}
/// Set the type of the dialog.
///
/// # Example
///
/// ```rust
/// use tauri_sys::dialog::{MessageDialogBuilder,MessageDialogType};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let _builder = MessageDialogBuilder::new().set_type(MessageDialogType::Error);
/// # Ok(())
/// # }
/// ```
pub fn set_type(&mut self, r#type: MessageDialogType) {
self.r#type = r#type;
}
/// Shows a message dialog with an `Ok` button.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::MessageDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let file = MessageDialogBuilder::new().message("Tauri is awesome").await?;
/// # Ok(())
/// # }
/// ```
pub async fn message(self, message: &str) -> crate::Result<()> {
Ok(inner::message(message, serde_wasm_bindgen::to_value(&self)?).await?)
}
/// Shows a question dialog with `Yes` and `No` buttons.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::MessageDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let confirmation = MessageDialogBuilder::new().ask("Are you sure?").await?;
/// # Ok(())
/// # }
/// ```
pub async fn ask(self, message: &str) -> crate::Result<bool> {
let raw = inner::ask(message, serde_wasm_bindgen::to_value(&self)?).await?;
Ok(serde_wasm_bindgen::from_value(raw)?)
}
/// Shows a question dialog with `Ok` and `Cancel` buttons.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_sys::dialog::MessageDialogBuilder;
///
/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let confirmation = MessageDialogBuilder::new().confirm("Are you sure?").await?;
/// # Ok(())
/// # }
/// ```
pub async fn confirm(self, message: &str) -> crate::Result<bool> {
let raw = inner::confirm(message, serde_wasm_bindgen::to_value(&self)?).await?;
Ok(serde_wasm_bindgen::from_value(raw)?)
}
}
/// Options for the save dialog.
#[derive(Serialize)]
pub struct SaveDialogOptions {
/// Initial directory of the file path.
/// If it's not a directory path, the dialog interface will change to that folder.
/// If it's not an existing directory, the file name will be set to the dialog's
/// file name input and the dialog will be set to the parent folder.
#[serde(rename(serialize = "defaultPath"))]
pub default_path: Option<PathBuf>,
// //! User interaction with the file system using dialog boxes.
// //!
// //! # Example
// //!
// //! ```rust,no_run
// //! use tauri_api::dialog::open;
// //!
// //! let path = open(None).await;
// //! ```
// use serde::Serialize;
// use std::path::PathBuf;
/// The filters of the dialog.
pub filters: Vec<DialogFilter>,
// /// Extension filter for the file dialog.
// ///
// /// # Example
// ///
// /// ```rust,no_run
// /// let filter = DialogFilter {
// /// extension: vec![".jpg", ".jpeg", ".png", ".bmp"],
// /// name: "images",
// /// };
// /// ```
// #[derive(Serialize)]
// pub struct DialogFilter {
// /// Extensions to filter, without a `.` prefix.
// pub extensions: Vec<String>,
/// The title of the dialog window.
pub title: Option<String>,
}
// /// Filter name
// pub name: String,
// }
impl SaveDialogOptions {
/// Creates a new `SaveDialogOptions` with sensible default values.
pub fn new() -> Self {
Self {
default_path: None,
filters: Vec::new(),
title: None,
}
}
}
// /// Types of a [`message`] dialog.
// #[derive(Serialize)]
// pub enum MessageDialogType {
// Error,
// Info,
// Warning,
// }
/// Show a question dialog with `Yes` and `No` buttons.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_api::dialog::{ask, MessageDialogOptions};
///
/// let yes = ask("Are you sure?", None).await;
/// ```
/// @param message Message to display.
/// @param options Dialog options.
/// @returns Whether the user selected `Yes` or `No`.
#[inline(always)]
pub async fn ask(message: &str, options: Option<MessageDialogOptions>) -> Option<bool> {
inner::ask(message, serde_wasm_bindgen::to_value(&options).unwrap())
.await
.as_bool()
}
// /// Options for the [`message`] dialog.
// #[derive(Serialize)]
// pub struct MessageDialogOptions {
// /// The title of the dialog. Defaults to the app name.
// pub title: Option<String>,
/// Shows a question dialog with `Ok` and `Cancel` buttons.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_api::dialog::{confirm, MessageDialogOptions};
///
/// let confirmed = confirm("Are you sure?", None).await;
/// ```
/// @returns Whether the user selelced `Ok` or `Cancel`.
pub async fn confirm(message: &str, options: Option<MessageDialogOptions>) -> Option<bool> {
inner::confirm(message, serde_wasm_bindgen::to_value(&options).unwrap())
.await
.as_bool()
}
// /// The type of the dialog. Defaults to MessageDialogType::Info.
// #[serde(rename(serialize = "type"))]
// pub kind: MessageDialogType,
// }
/// Shows a message dialog with an `Ok` button.
///
/// # Example
///
/// ```rust,no_run
/// use tauri_api::dialog::{message, MessageDialogOptions};
///
/// message("Tauri is awesome", None).await;
/// ```
/// @param message Message to display.
/// @param options Dialog options.
/// @returns Promise resolved when user closes the dialog.
pub async fn message(message: &str, options: Option<MessageDialogOptions>) {
inner::message(message, serde_wasm_bindgen::to_value(&options).unwrap()).await
}
// impl MessageDialogOptions {
// /// Creates a new `MessageDialogOptions` with sensible default values.
// pub fn new() -> Self {
// Self {
// title: None,
// kind: MessageDialogType::Info,
// }
// }
// }
/// Opens a file/directory selection dialog for a single file.
/// `multiple` field of [`options`](OpenDialogOptions) must be `false`, if provided.
///
/// The selected paths are added to the filesystem and asset protocol allowlist scopes.
/// When security is mroe important than the ease of use of this API,
/// prefer writing a dedicated command instead.
///
/// Note that the allowlist scope change is not persisited,
/// so the values are cleared when the applicaiton is restarted.
/// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
///
/// # Example
///
/// ```rust,no_run
/// use tauri_api::dialog::{open, OpenDialogOptions};
///
/// let file = open(None).await;
///
/// let mut opts = OpenDialogOptions::new();
/// opts.directory = true;
/// let dir = open(Some(opts)).await;
/// ```
/// @param options Dialog options.
/// @returns List of file paths, or `None` if user cancelled the dialog.
pub async fn open(options: Option<OpenDialogOptions>) -> Option<PathBuf> {
let file = inner::open(serde_wasm_bindgen::to_value(&options).unwrap()).await;
serde_wasm_bindgen::from_value(file).unwrap()
}
// /// Options for an [`open`] dialog.
// #[derive(Serialize)]
// pub struct OpenDialogOptions {
// /// Initial directory or file path.
// #[serde(rename(serialize = "defaultPath"))]
// pub default_path: Option<PathBuf>,
/// Opens a file/directory selection dialog for multiple files.
/// `multiple` field of [`options`](OpenDialogOptions) must be `true`, if provided.
///
/// The selected paths are added to the filesystem and asset protocol allowlist scopes.
/// When security is mroe important than the ease of use of this API,
/// prefer writing a dedicated command instead.
///
/// Note that the allowlist scope change is not persisited,
/// so the values are cleared when the applicaiton is restarted.
/// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
///
/// # Example
///
/// ```rust,no_run
/// use tauri_api::dialog::{open, OpenDialogOptions};
///
/// let files = open_multiple(None).await;
///
/// let mut opts = OpenDialogOptions::new();
/// opts.multiple = true;
/// opts.directory = true;
/// let dirs = open(Some(opts)).await;
/// ```
/// @param options Dialog options.
/// @returns List of file paths, or `None` if user cancelled the dialog.
pub async fn open_multiple(options: Option<OpenDialogOptions>) -> Option<Vec<PathBuf>> {
let files = inner::open_multiple(serde_wasm_bindgen::to_value(&options).unwrap()).await;
serde_wasm_bindgen::from_value(files).unwrap()
}
// /// Whether the dialog is a directory selection or not.
// pub directory: bool,
/// Opens a file/directory save dialog.
///
/// The selected paths are added to the filesystem and asset protocol allowlist scopes.
/// When security is mroe important than the ease of use of this API,
/// prefer writing a dedicated command instead.
///
/// Note that the allowlist scope change is not persisited,
/// so the values are cleared when the applicaiton is restarted.
/// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
///
/// # Example
///
/// ```rust,no_run
/// use tauri_api::dialog::{save, SaveDialogOptions};
///
/// let file = save(None).await;
/// ```
/// @param options Dialog options.
/// @returns File path, or `None` if user cancelled the dialog.
pub async fn save(options: Option<SaveDialogOptions>) -> Option<PathBuf> {
let path = inner::save(serde_wasm_bindgen::to_value(&options).unwrap()).await;
serde_wasm_bindgen::from_value(path).unwrap()
}
// /// The filters of the dialog.
// pub filters: Vec<DialogFilter>,
// /// Whether the dialog allows multiple selection or not.
// pub multiple: bool,
// /// If `directory` is `true`, indicatees that it will be read recursivley later.
// /// Defines whether subdirectories will be allowed on the scope or not.
// pub recursive: bool,
// /// The title of the dialog window.
// pub title: Option<String>,
// }
// impl OpenDialogOptions {
// /// Creates a new `OpenDialogOptions` with sensible default values.
// pub fn new() -> Self {
// Self {
// default_path: None,
// directory: false,
// filters: Vec::new(),
// multiple: false,
// recursive: false,
// title: None,
// }
// }
// }
// /// Options for the save dialog.
// #[derive(Serialize)]
// pub struct SaveDialogOptions {
// /// Initial directory of the file path.
// /// If it's not a directory path, the dialog interface will change to that folder.
// /// If it's not an existing directory, the file name will be set to the dialog's
// /// file name input and the dialog will be set to the parent folder.
// #[serde(rename(serialize = "defaultPath"))]
// pub default_path: Option<PathBuf>,
// /// The filters of the dialog.
// pub filters: Vec<DialogFilter>,
// /// The title of the dialog window.
// pub title: Option<String>,
// }
// impl SaveDialogOptions {
// /// Creates a new `SaveDialogOptions` with sensible default values.
// pub fn new() -> Self {
// Self {
// default_path: None,
// filters: Vec::new(),
// title: None,
// }
// }
// }
// /// Show a question dialog with `Yes` and `No` buttons.
// ///
// /// # Example
// ///
// /// ```rust,no_run
// /// use tauri_api::dialog::{ask, MessageDialogOptions};
// ///
// /// let yes = ask("Are you sure?", None).await;
// /// ```
// /// @param message Message to display.
// /// @param options Dialog options.
// /// @returns Whether the user selected `Yes` or `No`.
// #[inline(always)]
// pub async fn ask(message: &str, options: Option<MessageDialogOptions>) -> crate::Result<bool> {
// let js_val = inner::ask(message, serde_wasm_bindgen::to_value(&options)?).await?;
// Ok(serde_wasm_bindgen::from_value(js_val)?)
// }
// /// Shows a question dialog with `Ok` and `Cancel` buttons.
// ///
// /// # Example
// ///
// /// ```rust,no_run
// /// use tauri_api::dialog::{confirm, MessageDialogOptions};
// ///
// /// let confirmed = confirm("Are you sure?", None).await;
// /// ```
// /// @returns Whether the user selelced `Ok` or `Cancel`.
// pub async fn confirm(message: &str, options: Option<MessageDialogOptions>) -> crate::Result<bool> {
// let js_val = inner::confirm(message, serde_wasm_bindgen::to_value(&options)?).await?;
// Ok(serde_wasm_bindgen::from_value(js_val)?)
// }
// /// Shows a message dialog with an `Ok` button.
// ///
// /// # Example
// ///
// /// ```rust,no_run
// /// use tauri_api::dialog::{message, MessageDialogOptions};
// ///
// /// message("Tauri is awesome", None).await;
// /// ```
// /// @param message Message to display.
// /// @param options Dialog options.
// /// @returns Promise resolved when user closes the dialog.
// pub async fn message(message: &str, options: Option<MessageDialogOptions>) -> crate::Result<()> {
// Ok(inner::message(message, serde_wasm_bindgen::to_value(&options)?).await?)
// }
// /// Opens a file/directory selection dialog for a single file.
// /// `multiple` field of [`options`](OpenDialogOptions) must be `false`, if provided.
// ///
// /// The selected paths are added to the filesystem and asset protocol allowlist scopes.
// /// When security is mroe important than the ease of use of this API,
// /// prefer writing a dedicated command instead.
// ///
// /// Note that the allowlist scope change is not persisited,
// /// so the values are cleared when the applicaiton is restarted.
// /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
// ///
// /// # Example
// ///
// /// ```rust,no_run
// /// use tauri_api::dialog::{open, OpenDialogOptions};
// ///
// /// let file = open(None).await;
// ///
// /// let mut opts = OpenDialogOptions::new();
// /// opts.directory = true;
// /// let dir = open(Some(opts)).await;
// /// ```
// /// @param options Dialog options.
// /// @returns List of file paths, or `None` if user cancelled the dialog.
// pub async fn open(options: Option<OpenDialogOptions>) -> crate::Result<Option<PathBuf>> {
// let file = inner::open(serde_wasm_bindgen::to_value(&options)?).await?;
// Ok(serde_wasm_bindgen::from_value(file)?)
// }
// /// Opens a file/directory selection dialog for multiple files.
// /// `multiple` field of [`options`](OpenDialogOptions) must be `true`, if provided.
// ///
// /// The selected paths are added to the filesystem and asset protocol allowlist scopes.
// /// When security is mroe important than the ease of use of this API,
// /// prefer writing a dedicated command instead.
// ///
// /// Note that the allowlist scope change is not persisited,
// /// so the values are cleared when the applicaiton is restarted.
// /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
// ///
// /// # Example
// ///
// /// ```rust,no_run
// /// use tauri_api::dialog::{open, OpenDialogOptions};
// ///
// /// let files = open_multiple(None).await;
// ///
// /// let mut opts = OpenDialogOptions::new();
// /// opts.multiple = true;
// /// opts.directory = true;
// /// let dirs = open(Some(opts)).await;
// /// ```
// /// @param options Dialog options.
// /// @returns List of file paths, or `None` if user cancelled the dialog.
// pub async fn open_multiple(
// options: Option<OpenDialogOptions>,
// ) -> crate::Result<Option<Vec<PathBuf>>> {
// let files = inner::open_multiple(serde_wasm_bindgen::to_value(&options)?).await?;
// Ok(serde_wasm_bindgen::from_value(files)?)
// }
// /// Opens a file/directory save dialog.
// ///
// /// The selected paths are added to the filesystem and asset protocol allowlist scopes.
// /// When security is mroe important than the ease of use of this API,
// /// prefer writing a dedicated command instead.
// ///
// /// Note that the allowlist scope change is not persisited,
// /// so the values are cleared when the applicaiton is restarted.
// /// You can save it to the filessytem using the [tauri-plugin-persisted-scope](https://github.com/tauri-apps/tauri-plugin-persisted-scope).
// ///
// /// # Example
// ///
// /// ```rust,no_run
// /// use tauri_api::dialog::{save, SaveDialogOptions};
// ///
// /// let file = save(None).await;
// /// ```
// /// @param options Dialog options.
// /// @returns File path, or `None` if user cancelled the dialog.
// pub async fn save(options: Option<SaveDialogOptions>) -> crate::Result<Option<PathBuf>> {
// let path = inner::save(serde_wasm_bindgen::to_value(&options)?).await?;
// Ok(serde_wasm_bindgen::from_value(path)?)
// }
mod inner {
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[wasm_bindgen(module = "/dist/dialog.js")]
#[wasm_bindgen(module = "/src/dialog.js")]
extern "C" {
pub async fn ask(message: &str, options: JsValue) -> JsValue;
pub async fn confirm(message: &str, options: JsValue) -> JsValue;
pub async fn open(options: JsValue) -> JsValue;
pub async fn open_multiple(options: JsValue) -> JsValue;
pub async fn message(message: &str, option: JsValue);
pub async fn save(options: JsValue) -> JsValue;
#[wasm_bindgen(catch)]
pub async fn ask(message: &str, options: JsValue) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn confirm(message: &str, options: JsValue) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn open(options: JsValue) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn message(message: &str, option: JsValue) -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
pub async fn save(options: JsValue) -> Result<JsValue, JsValue>;
}
}

View file

@ -1,10 +1,6 @@
use std::fmt::Debug;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::fmt::Debug;
use wasm_bindgen::{prelude::Closure, JsValue};
use wasm_bindgen_futures::JsFuture;
use crate::Error;
#[derive(Deserialize)]
pub struct Event<T> {
@ -49,9 +45,7 @@ impl<T: Debug> Debug for Event<T> {
/// @param event Event name. Must include only alphanumeric characters, `-`, `/`, `:` and `_`.
#[inline(always)]
pub async fn emit<T: Serialize>(event: &str, payload: &T) -> crate::Result<()> {
JsFuture::from(inner::emit(event, serde_wasm_bindgen::to_value(payload)?))
.await
.map_err(Error::Other)?;
inner::emit(event, serde_wasm_bindgen::to_value(payload)?).await?;
Ok(())
}
@ -86,9 +80,7 @@ where
(handler)(serde_wasm_bindgen::from_value(raw).unwrap())
});
let unlisten = JsFuture::from(inner::listen(event, &closure))
.await
.map_err(Error::Other)?;
let unlisten = inner::listen(event, &closure).await?;
closure.forget();
@ -133,9 +125,7 @@ where
(handler)(serde_wasm_bindgen::from_value(raw).unwrap())
});
let unlisten = JsFuture::from(inner::once(event, &closure))
.await
.map_err(Error::Other)?;
let unlisten = inner::once(event, &closure).await?;
closure.forget();
@ -151,10 +141,19 @@ mod inner {
JsValue,
};
#[wasm_bindgen(module = "/dist/event.js")]
#[wasm_bindgen(module = "/src/event.js")]
extern "C" {
pub fn emit(event: &str, payload: JsValue) -> js_sys::Promise;
pub fn listen(event: &str, handler: &Closure<dyn FnMut(JsValue)>) -> js_sys::Promise;
pub fn once(event: &str, handler: &Closure<dyn FnMut(JsValue)>) -> js_sys::Promise;
#[wasm_bindgen(catch)]
pub async fn emit(event: &str, payload: JsValue) -> Result<(), JsValue>;
#[wasm_bindgen(catch)]
pub async fn listen(
event: &str,
handler: &Closure<dyn FnMut(JsValue)>,
) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn once(
event: &str,
handler: &Closure<dyn FnMut(JsValue)>,
) -> Result<JsValue, JsValue>;
}
}

243
src/fs.js Normal file
View file

@ -0,0 +1,243 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/fs.ts
var BaseDirectory = /* @__PURE__ */ ((BaseDirectory2) => {
BaseDirectory2[BaseDirectory2["Audio"] = 1] = "Audio";
BaseDirectory2[BaseDirectory2["Cache"] = 2] = "Cache";
BaseDirectory2[BaseDirectory2["Config"] = 3] = "Config";
BaseDirectory2[BaseDirectory2["Data"] = 4] = "Data";
BaseDirectory2[BaseDirectory2["LocalData"] = 5] = "LocalData";
BaseDirectory2[BaseDirectory2["Desktop"] = 6] = "Desktop";
BaseDirectory2[BaseDirectory2["Document"] = 7] = "Document";
BaseDirectory2[BaseDirectory2["Download"] = 8] = "Download";
BaseDirectory2[BaseDirectory2["Executable"] = 9] = "Executable";
BaseDirectory2[BaseDirectory2["Font"] = 10] = "Font";
BaseDirectory2[BaseDirectory2["Home"] = 11] = "Home";
BaseDirectory2[BaseDirectory2["Picture"] = 12] = "Picture";
BaseDirectory2[BaseDirectory2["Public"] = 13] = "Public";
BaseDirectory2[BaseDirectory2["Runtime"] = 14] = "Runtime";
BaseDirectory2[BaseDirectory2["Template"] = 15] = "Template";
BaseDirectory2[BaseDirectory2["Video"] = 16] = "Video";
BaseDirectory2[BaseDirectory2["Resource"] = 17] = "Resource";
BaseDirectory2[BaseDirectory2["App"] = 18] = "App";
BaseDirectory2[BaseDirectory2["Log"] = 19] = "Log";
BaseDirectory2[BaseDirectory2["Temp"] = 20] = "Temp";
BaseDirectory2[BaseDirectory2["AppConfig"] = 21] = "AppConfig";
BaseDirectory2[BaseDirectory2["AppData"] = 22] = "AppData";
BaseDirectory2[BaseDirectory2["AppLocalData"] = 23] = "AppLocalData";
BaseDirectory2[BaseDirectory2["AppCache"] = 24] = "AppCache";
BaseDirectory2[BaseDirectory2["AppLog"] = 25] = "AppLog";
return BaseDirectory2;
})(BaseDirectory || {});
async function readTextFile(filePath, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "readTextFile",
path: filePath,
options
}
});
}
async function readBinaryFile(filePath, options = {}) {
const arr = await invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "readFile",
path: filePath,
options
}
});
return Uint8Array.from(arr);
}
async function writeTextFile(path, contents, options) {
if (typeof options === "object") {
Object.freeze(options);
}
if (typeof path === "object") {
Object.freeze(path);
}
const file = { path: "", contents: "" };
let fileOptions = options;
if (typeof path === "string") {
file.path = path;
} else {
file.path = path.path;
file.contents = path.contents;
}
if (typeof contents === "string") {
file.contents = contents ?? "";
} else {
fileOptions = contents;
}
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "writeFile",
path: file.path,
contents: Array.from(new TextEncoder().encode(file.contents)),
options: fileOptions
}
});
}
async function writeBinaryFile(path, contents, options) {
if (typeof options === "object") {
Object.freeze(options);
}
if (typeof path === "object") {
Object.freeze(path);
}
const file = { path: "", contents: [] };
let fileOptions = options;
if (typeof path === "string") {
file.path = path;
} else {
file.path = path.path;
file.contents = path.contents;
}
if (contents && "dir" in contents) {
fileOptions = contents;
} else if (typeof path === "string") {
file.contents = contents ?? [];
}
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "writeFile",
path: file.path,
contents: Array.from(
file.contents instanceof ArrayBuffer ? new Uint8Array(file.contents) : file.contents
),
options: fileOptions
}
});
}
async function readDir(dir, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "readDir",
path: dir,
options
}
});
}
async function createDir(dir, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "createDir",
path: dir,
options
}
});
}
async function removeDir(dir, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "removeDir",
path: dir,
options
}
});
}
async function copyFile(source, destination, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "copyFile",
source,
destination,
options
}
});
}
async function removeFile(file, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "removeFile",
path: file,
options
}
});
}
async function renameFile(oldPath, newPath, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "renameFile",
oldPath,
newPath,
options
}
});
}
async function exists(path, options = {}) {
return invokeTauriCommand({
__tauriModule: "Fs",
message: {
cmd: "exists",
path,
options
}
});
}
export {
BaseDirectory,
BaseDirectory as Dir,
copyFile,
createDir,
exists,
readBinaryFile,
readDir,
readTextFile,
removeDir,
removeFile,
renameFile,
writeBinaryFile,
writeTextFile as writeFile,
writeTextFile
};

97
src/globalShortcut.js Normal file
View file

@ -0,0 +1,97 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/globalShortcut.ts
async function register(shortcut, handler) {
return invokeTauriCommand({
__tauriModule: "GlobalShortcut",
message: {
cmd: "register",
shortcut,
handler: transformCallback(handler)
}
});
}
async function registerAll(shortcuts, handler) {
return invokeTauriCommand({
__tauriModule: "GlobalShortcut",
message: {
cmd: "registerAll",
shortcuts,
handler: transformCallback(handler)
}
});
}
async function isRegistered(shortcut) {
return invokeTauriCommand({
__tauriModule: "GlobalShortcut",
message: {
cmd: "isRegistered",
shortcut
}
});
}
async function unregister(shortcut) {
return invokeTauriCommand({
__tauriModule: "GlobalShortcut",
message: {
cmd: "unregister",
shortcut
}
});
}
async function unregisterAll() {
return invokeTauriCommand({
__tauriModule: "GlobalShortcut",
message: {
cmd: "unregisterAll"
}
});
}
export {
isRegistered,
register,
registerAll,
unregister,
unregisterAll
};

219
src/http.js Normal file
View file

@ -0,0 +1,219 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/http.ts
var ResponseType = /* @__PURE__ */ ((ResponseType2) => {
ResponseType2[ResponseType2["JSON"] = 1] = "JSON";
ResponseType2[ResponseType2["Text"] = 2] = "Text";
ResponseType2[ResponseType2["Binary"] = 3] = "Binary";
return ResponseType2;
})(ResponseType || {});
var Body = class {
constructor(type, payload) {
this.type = type;
this.payload = payload;
}
static form(data) {
const form = {};
const append = (key, v) => {
if (v !== null) {
let r;
if (typeof v === "string") {
r = v;
} else if (v instanceof Uint8Array || Array.isArray(v)) {
r = Array.from(v);
} else if (v instanceof File) {
r = { file: v.name, mime: v.type, fileName: v.name };
} else if (typeof v.file === "string") {
r = { file: v.file, mime: v.mime, fileName: v.fileName };
} else {
r = { file: Array.from(v.file), mime: v.mime, fileName: v.fileName };
}
form[String(key)] = r;
}
};
if (data instanceof FormData) {
for (const [key, value] of data) {
append(key, value);
}
} else {
for (const [key, value] of Object.entries(data)) {
append(key, value);
}
}
return new Body("Form", form);
}
static json(data) {
return new Body("Json", data);
}
static text(value) {
return new Body("Text", value);
}
static bytes(bytes) {
return new Body(
"Bytes",
Array.from(bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes)
);
}
};
var Response = class {
constructor(response) {
this.url = response.url;
this.status = response.status;
this.ok = this.status >= 200 && this.status < 300;
this.headers = response.headers;
this.rawHeaders = response.rawHeaders;
this.data = response.data;
}
};
var Client = class {
constructor(id) {
this.id = id;
}
async drop() {
return invokeTauriCommand({
__tauriModule: "Http",
message: {
cmd: "dropClient",
client: this.id
}
});
}
async request(options) {
const jsonResponse = !options.responseType || options.responseType === 1 /* JSON */;
if (jsonResponse) {
options.responseType = 2 /* Text */;
}
return invokeTauriCommand({
__tauriModule: "Http",
message: {
cmd: "httpRequest",
client: this.id,
options
}
}).then((res) => {
const response = new Response(res);
if (jsonResponse) {
try {
response.data = JSON.parse(response.data);
} catch (e) {
if (response.ok && response.data === "") {
response.data = {};
} else if (response.ok) {
throw Error(
`Failed to parse response \`${response.data}\` as JSON: ${e};
try setting the \`responseType\` option to \`ResponseType.Text\` or \`ResponseType.Binary\` if the API does not return a JSON response.`
);
}
}
return response;
}
return response;
});
}
async get(url, options) {
return this.request({
method: "GET",
url,
...options
});
}
async post(url, body, options) {
return this.request({
method: "POST",
url,
body,
...options
});
}
async put(url, body, options) {
return this.request({
method: "PUT",
url,
body,
...options
});
}
async patch(url, options) {
return this.request({
method: "PATCH",
url,
...options
});
}
async delete(url, options) {
return this.request({
method: "DELETE",
url,
...options
});
}
};
async function getClient(options) {
return invokeTauriCommand({
__tauriModule: "Http",
message: {
cmd: "createClient",
options
}
}).then((id) => new Client(id));
}
var defaultClient = null;
async function fetch(url, options) {
if (defaultClient === null) {
defaultClient = await getClient();
}
return defaultClient.request({
url,
method: options?.method ?? "GET",
...options
});
}
export {
Body,
Client,
Response,
ResponseType,
fetch,
getClient
};

2414
src/index.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -10,15 +10,37 @@ pub mod dialog;
pub mod event;
#[cfg(feature = "mocks")]
pub mod mocks;
#[cfg(feature = "process")]
pub mod process;
#[cfg(feature = "tauri")]
pub mod tauri;
#[cfg(feature = "window")]
pub mod window;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(transparent)]
Serde(#[from] serde_wasm_bindgen::Error),
#[error("{0:?}")]
Other(JsValue),
#[error("{0}")]
Serde(String),
#[error("Unknown Theme \"{0}\". Expected one of \"light\",\"dark\"")]
UnknownTheme(String),
#[error("Invalid Url {0}")]
InvalidUrl(#[from] url::ParseError),
#[error("Invalid Version {0}")]
InvalidVersion(#[from] semver::Error),
#[error("{0}")]
Other(String),
}
impl From<serde_wasm_bindgen::Error> for Error {
fn from(e: serde_wasm_bindgen::Error) -> Self {
Self::Serde(format!("{:?}", e))
}
}
impl From<JsValue> for Error {
fn from(e: JsValue) -> Self {
Self::Serde(format!("{:?}", e))
}
}
pub(crate) type Result<T> = std::result::Result<T, Error>;

View file

@ -55,7 +55,7 @@ mod inner {
JsValue,
};
#[wasm_bindgen(module = "/dist/mocks.js")]
#[wasm_bindgen(module = "/src/mocks.js")]
extern "C" {
#[wasm_bindgen(variadic)]
pub fn mockWindows(current: &str, rest: JsValue);

70
src/notification.js Normal file
View file

@ -0,0 +1,70 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/notification.ts
async function isPermissionGranted() {
if (window.Notification.permission !== "default") {
return Promise.resolve(window.Notification.permission === "granted");
}
return invokeTauriCommand({
__tauriModule: "Notification",
message: {
cmd: "isNotificationPermissionGranted"
}
});
}
async function requestPermission() {
return window.Notification.requestPermission();
}
function sendNotification(options) {
if (typeof options === "string") {
new window.Notification(options);
} else {
new window.Notification(options.title, options);
}
}
export {
isPermissionGranted,
requestPermission,
sendNotification
};

98
src/os.js Normal file
View file

@ -0,0 +1,98 @@
// tauri/tooling/api/src/helpers/os-check.ts
function isWindows() {
return navigator.appVersion.includes("Win");
}
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/os.ts
var EOL = isWindows() ? "\r\n" : "\n";
async function platform() {
return invokeTauriCommand({
__tauriModule: "Os",
message: {
cmd: "platform"
}
});
}
async function version() {
return invokeTauriCommand({
__tauriModule: "Os",
message: {
cmd: "version"
}
});
}
async function type() {
return invokeTauriCommand({
__tauriModule: "Os",
message: {
cmd: "osType"
}
});
}
async function arch() {
return invokeTauriCommand({
__tauriModule: "Os",
message: {
cmd: "arch"
}
});
}
async function tempdir() {
return invokeTauriCommand({
__tauriModule: "Os",
message: {
cmd: "tempdir"
}
});
}
export {
EOL,
arch,
platform,
tempdir,
type,
version
};

418
src/path.js Normal file
View file

@ -0,0 +1,418 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve2, reject) => {
const callback = transformCallback((e) => {
resolve2(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/fs.ts
var BaseDirectory = /* @__PURE__ */ ((BaseDirectory2) => {
BaseDirectory2[BaseDirectory2["Audio"] = 1] = "Audio";
BaseDirectory2[BaseDirectory2["Cache"] = 2] = "Cache";
BaseDirectory2[BaseDirectory2["Config"] = 3] = "Config";
BaseDirectory2[BaseDirectory2["Data"] = 4] = "Data";
BaseDirectory2[BaseDirectory2["LocalData"] = 5] = "LocalData";
BaseDirectory2[BaseDirectory2["Desktop"] = 6] = "Desktop";
BaseDirectory2[BaseDirectory2["Document"] = 7] = "Document";
BaseDirectory2[BaseDirectory2["Download"] = 8] = "Download";
BaseDirectory2[BaseDirectory2["Executable"] = 9] = "Executable";
BaseDirectory2[BaseDirectory2["Font"] = 10] = "Font";
BaseDirectory2[BaseDirectory2["Home"] = 11] = "Home";
BaseDirectory2[BaseDirectory2["Picture"] = 12] = "Picture";
BaseDirectory2[BaseDirectory2["Public"] = 13] = "Public";
BaseDirectory2[BaseDirectory2["Runtime"] = 14] = "Runtime";
BaseDirectory2[BaseDirectory2["Template"] = 15] = "Template";
BaseDirectory2[BaseDirectory2["Video"] = 16] = "Video";
BaseDirectory2[BaseDirectory2["Resource"] = 17] = "Resource";
BaseDirectory2[BaseDirectory2["App"] = 18] = "App";
BaseDirectory2[BaseDirectory2["Log"] = 19] = "Log";
BaseDirectory2[BaseDirectory2["Temp"] = 20] = "Temp";
BaseDirectory2[BaseDirectory2["AppConfig"] = 21] = "AppConfig";
BaseDirectory2[BaseDirectory2["AppData"] = 22] = "AppData";
BaseDirectory2[BaseDirectory2["AppLocalData"] = 23] = "AppLocalData";
BaseDirectory2[BaseDirectory2["AppCache"] = 24] = "AppCache";
BaseDirectory2[BaseDirectory2["AppLog"] = 25] = "AppLog";
return BaseDirectory2;
})(BaseDirectory || {});
// tauri/tooling/api/src/helpers/os-check.ts
function isWindows() {
return navigator.appVersion.includes("Win");
}
// tauri/tooling/api/src/path.ts
async function appDir() {
return appConfigDir();
}
async function appConfigDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 21 /* AppConfig */
}
});
}
async function appDataDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 22 /* AppData */
}
});
}
async function appLocalDataDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 23 /* AppLocalData */
}
});
}
async function appCacheDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 24 /* AppCache */
}
});
}
async function audioDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 1 /* Audio */
}
});
}
async function cacheDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 2 /* Cache */
}
});
}
async function configDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 3 /* Config */
}
});
}
async function dataDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 4 /* Data */
}
});
}
async function desktopDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 6 /* Desktop */
}
});
}
async function documentDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 7 /* Document */
}
});
}
async function downloadDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 8 /* Download */
}
});
}
async function executableDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 9 /* Executable */
}
});
}
async function fontDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 10 /* Font */
}
});
}
async function homeDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 11 /* Home */
}
});
}
async function localDataDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 5 /* LocalData */
}
});
}
async function pictureDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 12 /* Picture */
}
});
}
async function publicDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 13 /* Public */
}
});
}
async function resourceDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 17 /* Resource */
}
});
}
async function resolveResource(resourcePath) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: resourcePath,
directory: 17 /* Resource */
}
});
}
async function runtimeDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 14 /* Runtime */
}
});
}
async function templateDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 15 /* Template */
}
});
}
async function videoDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 16 /* Video */
}
});
}
async function logDir() {
return appLogDir();
}
async function appLogDir() {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolvePath",
path: "",
directory: 25 /* AppLog */
}
});
}
var sep = isWindows() ? "\\" : "/";
var delimiter = isWindows() ? ";" : ":";
async function resolve(...paths) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "resolve",
paths
}
});
}
async function normalize(path) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "normalize",
path
}
});
}
async function join(...paths) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "join",
paths
}
});
}
async function dirname(path) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "dirname",
path
}
});
}
async function extname(path) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "extname",
path
}
});
}
async function basename(path, ext) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "basename",
path,
ext
}
});
}
async function isAbsolute(path) {
return invokeTauriCommand({
__tauriModule: "Path",
message: {
cmd: "isAbsolute",
path
}
});
}
export {
BaseDirectory,
appCacheDir,
appConfigDir,
appDataDir,
appDir,
appLocalDataDir,
appLogDir,
audioDir,
basename,
cacheDir,
configDir,
dataDir,
delimiter,
desktopDir,
dirname,
documentDir,
downloadDir,
executableDir,
extname,
fontDir,
homeDir,
isAbsolute,
join,
localDataDir,
logDir,
normalize,
pictureDir,
publicDir,
resolve,
resolveResource,
resourceDir,
runtimeDir,
sep,
templateDir,
videoDir
};

65
src/process.js Normal file
View file

@ -0,0 +1,65 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/process.ts
async function exit(exitCode = 0) {
return invokeTauriCommand({
__tauriModule: "Process",
message: {
cmd: "exit",
exitCode
}
});
}
async function relaunch() {
return invokeTauriCommand({
__tauriModule: "Process",
message: {
cmd: "relaunch"
}
});
}
export {
exit,
relaunch
};

18
src/process.rs Normal file
View file

@ -0,0 +1,18 @@
pub async fn exit(exit_code: u32) -> ! {
inner::exit(exit_code).await;
unreachable!()
}
pub fn relaunch() {
inner::relaunch();
}
mod inner {
use wasm_bindgen::prelude::*;
#[wasm_bindgen(module = "/src/process.js")]
extern "C" {
pub async fn exit(exitCode: u32);
pub fn relaunch();
}
}

230
src/shell.js Normal file
View file

@ -0,0 +1,230 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/shell.ts
async function execute(onEvent, program, args = [], options) {
if (typeof args === "object") {
Object.freeze(args);
}
return invokeTauriCommand({
__tauriModule: "Shell",
message: {
cmd: "execute",
program,
args,
options,
onEventFn: transformCallback(onEvent)
}
});
}
var EventEmitter = class {
constructor() {
this.eventListeners = /* @__PURE__ */ Object.create(null);
}
addListener(eventName, listener) {
return this.on(eventName, listener);
}
removeListener(eventName, listener) {
return this.off(eventName, listener);
}
on(eventName, listener) {
if (eventName in this.eventListeners) {
this.eventListeners[eventName].push(listener);
} else {
this.eventListeners[eventName] = [listener];
}
return this;
}
once(eventName, listener) {
const wrapper = (...args) => {
this.removeListener(eventName, wrapper);
listener(...args);
};
return this.addListener(eventName, wrapper);
}
off(eventName, listener) {
if (eventName in this.eventListeners) {
this.eventListeners[eventName] = this.eventListeners[eventName].filter(
(l) => l !== listener
);
}
return this;
}
removeAllListeners(event) {
if (event) {
delete this.eventListeners[event];
} else {
this.eventListeners = /* @__PURE__ */ Object.create(null);
}
return this;
}
emit(eventName, ...args) {
if (eventName in this.eventListeners) {
const listeners = this.eventListeners[eventName];
for (const listener of listeners)
listener(...args);
return true;
}
return false;
}
listenerCount(eventName) {
if (eventName in this.eventListeners)
return this.eventListeners[eventName].length;
return 0;
}
prependListener(eventName, listener) {
if (eventName in this.eventListeners) {
this.eventListeners[eventName].unshift(listener);
} else {
this.eventListeners[eventName] = [listener];
}
return this;
}
prependOnceListener(eventName, listener) {
const wrapper = (...args) => {
this.removeListener(eventName, wrapper);
listener(...args);
};
return this.prependListener(eventName, wrapper);
}
};
var Child = class {
constructor(pid) {
this.pid = pid;
}
async write(data) {
return invokeTauriCommand({
__tauriModule: "Shell",
message: {
cmd: "stdinWrite",
pid: this.pid,
buffer: typeof data === "string" ? data : Array.from(data)
}
});
}
async kill() {
return invokeTauriCommand({
__tauriModule: "Shell",
message: {
cmd: "killChild",
pid: this.pid
}
});
}
};
var Command = class extends EventEmitter {
constructor(program, args = [], options) {
super();
this.stdout = new EventEmitter();
this.stderr = new EventEmitter();
this.program = program;
this.args = typeof args === "string" ? [args] : args;
this.options = options ?? {};
}
static sidecar(program, args = [], options) {
const instance = new Command(program, args, options);
instance.options.sidecar = true;
return instance;
}
async spawn() {
return execute(
(event) => {
switch (event.event) {
case "Error":
this.emit("error", event.payload);
break;
case "Terminated":
this.emit("close", event.payload);
break;
case "Stdout":
this.stdout.emit("data", event.payload);
break;
case "Stderr":
this.stderr.emit("data", event.payload);
break;
}
},
this.program,
this.args,
this.options
).then((pid) => new Child(pid));
}
async execute() {
return new Promise((resolve, reject) => {
this.on("error", reject);
const stdout = [];
const stderr = [];
this.stdout.on("data", (line) => {
stdout.push(line);
});
this.stderr.on("data", (line) => {
stderr.push(line);
});
this.on("close", (payload) => {
resolve({
code: payload.code,
signal: payload.signal,
stdout: stdout.join("\n"),
stderr: stderr.join("\n")
});
});
this.spawn().catch(reject);
});
}
};
async function open(path, openWith) {
return invokeTauriCommand({
__tauriModule: "Shell",
message: {
cmd: "open",
path,
with: openWith
}
});
}
export {
Child,
Command,
EventEmitter,
open
};

View file

@ -36,14 +36,10 @@ use url::Url;
///
/// @return the URL that can be used as source on the webview.
#[inline(always)]
pub async fn convert_file_src(file_path: &str, protocol: Option<&str>) -> Url {
Url::parse(
&inner::convertFileSrc(file_path, protocol)
.await
.as_string()
.unwrap(),
)
.unwrap()
pub async fn convert_file_src(file_path: &str, protocol: Option<&str>) -> crate::Result<Url> {
let js_val = inner::convertFileSrc(file_path, protocol).await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
/// Sends a message to the backend.
@ -66,9 +62,8 @@ pub async fn convert_file_src(file_path: &str, protocol: Option<&str>) -> Url {
/// @return A promise resolving or rejecting to the backend response.
#[inline(always)]
pub async fn invoke<A: Serialize, R: DeserializeOwned>(cmd: &str, args: &A) -> crate::Result<R> {
let res = inner::invoke(cmd, serde_wasm_bindgen::to_value(args).unwrap()).await;
let raw = inner::invoke(cmd, serde_wasm_bindgen::to_value(args)?).await?;
let raw = res.map_err(crate::Error::Other)?;
serde_wasm_bindgen::from_value(raw).map_err(Into::into)
}
@ -77,24 +72,35 @@ pub async fn invoke<A: Serialize, R: DeserializeOwned>(cmd: &str, args: &A) -> c
///
/// @return A unique identifier associated with the callback function.
#[inline(always)]
pub async fn transform_callback<T: DeserializeOwned>(callback: &dyn Fn(T), once: bool) -> f64 {
inner::transformCallback(
pub async fn transform_callback<T: DeserializeOwned>(
callback: &dyn Fn(T),
once: bool,
) -> crate::Result<f64> {
let js_val = inner::transformCallback(
&|raw| callback(serde_wasm_bindgen::from_value(raw).unwrap()),
once,
)
.await
.as_f64()
.unwrap()
.await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
mod inner {
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[wasm_bindgen(module = "/dist/tauri.js")]
#[wasm_bindgen(module = "/src/tauri.js")]
extern "C" {
pub async fn convertFileSrc(filePath: &str, protocol: Option<&str>) -> JsValue;
#[wasm_bindgen(catch)]
pub async fn convertFileSrc(
filePath: &str,
protocol: Option<&str>,
) -> Result<JsValue, JsValue>;
#[wasm_bindgen(catch)]
pub async fn invoke(cmd: &str, args: JsValue) -> Result<JsValue, JsValue>;
pub async fn transformCallback(callback: &dyn Fn(JsValue), once: bool) -> JsValue;
#[wasm_bindgen(catch)]
pub async fn transformCallback(
callback: &dyn Fn(JsValue),
once: bool,
) -> Result<JsValue, JsValue>;
}
}

185
src/updater.js Normal file
View file

@ -0,0 +1,185 @@
// tauri/tooling/api/src/tauri.ts
function uid() {
return window.crypto.getRandomValues(new Uint32Array(1))[0];
}
function transformCallback(callback, once3 = false) {
const identifier = uid();
const prop = `_${identifier}`;
Object.defineProperty(window, prop, {
value: (result) => {
if (once3) {
Reflect.deleteProperty(window, prop);
}
return callback?.(result);
},
writable: false,
configurable: true
});
return identifier;
}
async function invoke(cmd, args = {}) {
return new Promise((resolve, reject) => {
const callback = transformCallback((e) => {
resolve(e);
Reflect.deleteProperty(window, `_${error}`);
}, true);
const error = transformCallback((e) => {
reject(e);
Reflect.deleteProperty(window, `_${callback}`);
}, true);
window.__TAURI_IPC__({
cmd,
callback,
error,
...args
});
});
}
// tauri/tooling/api/src/helpers/tauri.ts
async function invokeTauriCommand(command) {
return invoke("tauri", command);
}
// tauri/tooling/api/src/helpers/event.ts
async function _unlisten(event, eventId) {
return invokeTauriCommand({
__tauriModule: "Event",
message: {
cmd: "unlisten",
event,
eventId
}
});
}
async function emit(event, windowLabel, payload) {
await invokeTauriCommand({
__tauriModule: "Event",
message: {
cmd: "emit",
event,
windowLabel,
payload
}
});
}
async function listen(event, windowLabel, handler) {
return invokeTauriCommand({
__tauriModule: "Event",
message: {
cmd: "listen",
event,
windowLabel,
handler: transformCallback(handler)
}
}).then((eventId) => {
return async () => _unlisten(event, eventId);
});
}
async function once(event, windowLabel, handler) {
return listen(event, windowLabel, (eventData) => {
handler(eventData);
_unlisten(event, eventData.id).catch(() => {
});
});
}
// tauri/tooling/api/src/event.ts
async function listen2(event, handler) {
return listen(event, null, handler);
}
async function once2(event, handler) {
return once(event, null, handler);
}
async function emit2(event, payload) {
return emit(event, void 0, payload);
}
// tauri/tooling/api/src/updater.ts
async function onUpdaterEvent(handler) {
return listen2("tauri://update-status" /* STATUS_UPDATE */, (data) => {
handler(data?.payload);
});
}
async function installUpdate() {
let unlistenerFn;
function cleanListener() {
if (unlistenerFn) {
unlistenerFn();
}
unlistenerFn = void 0;
}
return new Promise((resolve, reject) => {
function onStatusChange(statusResult) {
if (statusResult.error) {
cleanListener();
return reject(statusResult.error);
}
if (statusResult.status === "DONE") {
cleanListener();
return resolve();
}
}
onUpdaterEvent(onStatusChange).then((fn) => {
unlistenerFn = fn;
}).catch((e) => {
cleanListener();
throw e;
});
emit2("tauri://update-install" /* INSTALL_UPDATE */).catch((e) => {
cleanListener();
throw e;
});
});
}
async function checkUpdate() {
let unlistenerFn;
function cleanListener() {
if (unlistenerFn) {
unlistenerFn();
}
unlistenerFn = void 0;
}
return new Promise((resolve, reject) => {
function onUpdateAvailable(manifest) {
cleanListener();
return resolve({
manifest,
shouldUpdate: true
});
}
function onStatusChange(statusResult) {
if (statusResult.error) {
cleanListener();
return reject(statusResult.error);
}
if (statusResult.status === "UPTODATE") {
cleanListener();
return resolve({
shouldUpdate: false
});
}
}
once2("tauri://update-available" /* UPDATE_AVAILABLE */, (data) => {
onUpdateAvailable(data?.payload);
}).catch((e) => {
cleanListener();
throw e;
});
onUpdaterEvent(onStatusChange).then((fn) => {
unlistenerFn = fn;
}).catch((e) => {
cleanListener();
throw e;
});
emit2("tauri://update" /* CHECK_UPDATE */).catch((e) => {
cleanListener();
throw e;
});
});
}
export {
checkUpdate,
installUpdate,
onUpdaterEvent
};

1004
src/window.js Normal file

File diff suppressed because it is too large Load diff

825
src/window.rs Normal file
View file

@ -0,0 +1,825 @@
use crate::{event::Event, Error};
use serde::{de::DeserializeOwned, Serialize};
use std::fmt::Display;
use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
#[derive(Debug, Clone, PartialEq)]
pub enum Theme {
Light,
Dark,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TitleBarStyle {
Visible,
Transparent,
Overlay,
}
#[derive(Debug, Clone, PartialEq)]
pub enum UserAttentionType {
Critical = 1,
Informational,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Position {
Physical(PhysicalPosition),
Logical(LogicalPosition),
}
#[derive(Debug, Clone, PartialEq)]
pub enum Size {
Physical(PhysicalSize),
Logical(LogicalSize),
}
#[derive(Debug, Clone, PartialEq)]
pub enum CursorIcon {
Default,
Crosshair,
Hand,
Arrow,
Move,
Text,
Wait,
Help,
Progress,
// something cannot be done
NotAllowed,
ContextMenu,
Cell,
VerticalText,
Alias,
Copy,
NoDrop,
// something can be grabbed
Grab,
/// something is grabbed
Grabbing,
AllScroll,
ZoomIn,
ZoomOut,
// edge is to be moved
EResize,
NResize,
NeResize,
NwResize,
SResize,
SeResize,
SwResize,
WResize,
EwResize,
NsResize,
NeswResize,
NwseResize,
ColResize,
RowResize,
}
impl Display for CursorIcon {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CursorIcon::Default => write!(f, "default"),
CursorIcon::Crosshair => write!(f, "crosshair"),
CursorIcon::Hand => write!(f, "hand"),
CursorIcon::Arrow => write!(f, "arrow"),
CursorIcon::Move => write!(f, "move"),
CursorIcon::Text => write!(f, "text"),
CursorIcon::Wait => write!(f, "wait"),
CursorIcon::Help => write!(f, "help"),
CursorIcon::Progress => write!(f, "progress"),
CursorIcon::NotAllowed => write!(f, "notAllowed"),
CursorIcon::ContextMenu => write!(f, "contextMenu"),
CursorIcon::Cell => write!(f, "cell"),
CursorIcon::VerticalText => write!(f, "verticalText"),
CursorIcon::Alias => write!(f, "alias"),
CursorIcon::Copy => write!(f, "copy"),
CursorIcon::NoDrop => write!(f, "noDrop"),
CursorIcon::Grab => write!(f, "grab"),
CursorIcon::Grabbing => write!(f, "grabbing"),
CursorIcon::AllScroll => write!(f, "allScroll"),
CursorIcon::ZoomIn => write!(f, "zoomIn"),
CursorIcon::ZoomOut => write!(f, "zoomOut"),
CursorIcon::EResize => write!(f, "eResize"),
CursorIcon::NResize => write!(f, "nResize"),
CursorIcon::NeResize => write!(f, "neResize"),
CursorIcon::NwResize => write!(f, "nwResize"),
CursorIcon::SResize => write!(f, "sResize"),
CursorIcon::SeResize => write!(f, "seResize"),
CursorIcon::SwResize => write!(f, "swResize"),
CursorIcon::WResize => write!(f, "wResize"),
CursorIcon::EwResize => write!(f, "ewResize"),
CursorIcon::NsResize => write!(f, "nsResize"),
CursorIcon::NeswResize => write!(f, "neswResize"),
CursorIcon::NwseResize => write!(f, "nwseResize"),
CursorIcon::ColResize => write!(f, "colResize"),
CursorIcon::RowResize => write!(f, "rowResize"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct WebviewWindow(inner::WebviewWindow);
impl WebviewWindow {
pub fn new(label: &str, options: ()) -> Self {
Self(inner::WebviewWindow::new(label, options))
}
pub fn get_by_label(label: &str) -> Option<Self> {
inner::WebviewWindow::getByLabel(label).map(Self)
}
pub fn label(&self) -> String {
self.0.label()
}
pub async fn scale_factor(&self) -> crate::Result<f64> {
let js_val = self.0.scaleFactor().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
pub async fn inner_position(&self) -> crate::Result<PhysicalPosition> {
Ok(PhysicalPosition(
self.0.innerPosition().await?.unchecked_into(),
))
}
pub async fn outer_position(&self) -> crate::Result<PhysicalPosition> {
Ok(PhysicalPosition(
self.0.outerPosition().await?.unchecked_into(),
))
}
pub async fn inner_size(&self) -> crate::Result<PhysicalSize> {
Ok(PhysicalSize(self.0.innerSize().await?.unchecked_into()))
}
pub async fn outer_size(&self) -> crate::Result<PhysicalSize> {
Ok(PhysicalSize(self.0.outerSize().await?.unchecked_into()))
}
pub async fn is_fullscreen(&self) -> crate::Result<bool> {
let js_val = self.0.isFullscreen().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
pub async fn is_maximized(&self) -> crate::Result<bool> {
let js_val = self.0.isMaximized().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
pub async fn is_decorated(&self) -> crate::Result<bool> {
let js_val = self.0.isDecorated().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
pub async fn is_resizable(&self) -> crate::Result<bool> {
let js_val = self.0.isResizable().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
pub async fn is_visible(&self) -> crate::Result<bool> {
let js_val = self.0.isVisible().await?;
Ok(serde_wasm_bindgen::from_value(js_val)?)
}
pub async fn theme(&self) -> crate::Result<Theme> {
let js_val = self.0.theme().await?;
let str = serde_wasm_bindgen::from_value::<String>(js_val)?;
match str.as_str() {
"light" => Ok(Theme::Light),
"dark" => Ok(Theme::Dark),
_ => Err(Error::UnknownTheme(str)),
}
}
pub async fn center(&self) -> crate::Result<()> {
Ok(self.0.center().await?)
}
pub async fn request_user_attention(
&self,
request_type: UserAttentionType,
) -> crate::Result<()> {
Ok(self.0.requestUserAttention(request_type as u32).await?)
}
pub async fn set_resizable(&self, resizable: bool) -> crate::Result<()> {
Ok(self.0.setResizable(resizable).await?)
}
pub async fn set_title(&self, title: impl AsRef<str>) -> crate::Result<()> {
Ok(self.0.setTitle(title.as_ref()).await?)
}
pub async fn maximize(&self) -> crate::Result<()> {
Ok(self.0.maximize().await?)
}
pub async fn unmaximize(&self) -> crate::Result<()> {
Ok(self.0.unmaximize().await?)
}
pub async fn toggle_maximize(&self) -> crate::Result<()> {
Ok(self.0.toggleMaximize().await?)
}
pub async fn minimize(&self) -> crate::Result<()> {
Ok(self.0.minimize().await?)
}
pub async fn unminimize(&self) -> crate::Result<()> {
Ok(self.0.unminimize().await?)
}
pub async fn show(&self) -> crate::Result<()> {
Ok(self.0.show().await?)
}
pub async fn hide(&self) -> crate::Result<()> {
Ok(self.0.hide().await?)
}
pub async fn close(&self) -> crate::Result<()> {
Ok(self.0.close().await?)
}
pub async fn set_decorations(&self, decorations: bool) -> crate::Result<()> {
Ok(self.0.setDecorations(decorations).await?)
}
pub async fn set_always_on_top(&self, always_on_top: bool) -> crate::Result<()> {
Ok(self.0.setAlwaysOnTop(always_on_top).await?)
}
pub async fn set_size(&self, size: Size) -> crate::Result<()> {
match size {
Size::Physical(size) => self.0.setSizePhysical(size.0).await?,
Size::Logical(size) => self.0.setSizeLogical(size.0).await?,
}
Ok(())
}
pub async fn set_min_size(&self, size: Option<Size>) -> crate::Result<()> {
match size {
None => self.0.setMinSizePhysical(None).await?,
Some(Size::Physical(size)) => self.0.setMinSizePhysical(Some(size.0)).await?,
Some(Size::Logical(size)) => self.0.setMinSizeLogical(Some(size.0)).await?,
}
Ok(())
}
pub async fn set_max_size(&self, size: Option<Size>) -> crate::Result<()> {
match size {
None => self.0.setMaxSizePhysical(None).await?,
Some(Size::Physical(size)) => self.0.setMaxSizePhysical(Some(size.0)).await?,
Some(Size::Logical(size)) => self.0.setMaxSizeLogical(Some(size.0)).await?,
}
Ok(())
}
pub async fn set_position(&self, position: Position) -> crate::Result<()> {
match position {
Position::Physical(pos) => self.0.setPositionPhysical(pos.0).await?,
Position::Logical(pos) => self.0.setPositionLogical(pos.0).await?,
}
Ok(())
}
pub async fn set_fullscreen(&self, fullscreen: bool) -> crate::Result<()> {
Ok(self.0.setFullscreen(fullscreen).await?)
}
pub async fn set_focus(&self) -> crate::Result<()> {
Ok(self.0.setFocus().await?)
}
pub async fn set_icon(&self, icon: &[u8]) -> crate::Result<()> {
Ok(self.0.setIcon(icon).await?)
}
pub async fn set_skip_taskbar(&self, skip: bool) -> crate::Result<()> {
Ok(self.0.setSkipTaskbar(skip).await?)
}
pub async fn set_cursor_grab(&self, grab: bool) -> crate::Result<()> {
Ok(self.0.setCursorGrab(grab).await?)
}
pub async fn set_cursor_visible(&self, visible: bool) -> crate::Result<()> {
Ok(self.0.setCursorVisible(visible).await?)
}
pub async fn set_cursor_icon(&self, icon: CursorIcon) -> crate::Result<()> {
Ok(self.0.setCursorIcon(&icon.to_string()).await?)
}
pub async fn set_cursor_position(&self, position: Position) -> crate::Result<()> {
match position {
Position::Physical(pos) => self.0.setCursorPositionPhysical(pos.0).await?,
Position::Logical(pos) => self.0.setCursorPositionLogical(pos.0).await?,
}
Ok(())
}
pub async fn set_ignore_cursor_events(&self, ignore: bool) -> crate::Result<()> {
Ok(self.0.setIgnoreCursorEvents(ignore).await?)
}
pub async fn start_dragging(&self) -> crate::Result<()> {
Ok(self.0.startDragging().await?)
}
#[inline(always)]
pub async fn emit<T: Serialize>(&self, event: &str, payload: &T) -> crate::Result<()> {
self.0
.emit(event, serde_wasm_bindgen::to_value(payload).unwrap())
.await?;
Ok(())
}
#[inline(always)]
pub async fn listen<T, H>(&self, event: &str, mut handler: H) -> crate::Result<impl FnOnce()>
where
T: DeserializeOwned,
H: FnMut(Event<T>) + 'static,
{
let closure = Closure::<dyn FnMut(JsValue)>::new(move |raw| {
(handler)(serde_wasm_bindgen::from_value(raw).unwrap())
});
let unlisten = self.0.listen(event, &closure).await?;
closure.forget();
let unlisten = js_sys::Function::from(unlisten);
Ok(move || {
unlisten.call0(&wasm_bindgen::JsValue::NULL).unwrap();
})
}
#[inline(always)]
pub async fn once<T, H>(&self, event: &str, mut handler: H) -> crate::Result<impl FnOnce()>
where
T: DeserializeOwned,
H: FnMut(Event<T>) + 'static,
{
let closure = Closure::<dyn FnMut(JsValue)>::new(move |raw| {
(handler)(serde_wasm_bindgen::from_value(raw).unwrap())
});
let unlisten = self.0.once(event, &closure).await?;
closure.forget();
let unlisten = js_sys::Function::from(unlisten);
Ok(move || {
unlisten.call0(&wasm_bindgen::JsValue::NULL).unwrap();
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LogicalPosition(inner::LogicalPosition);
impl LogicalPosition {
pub fn new(x: u32, y: u32) -> Self {
Self(inner::LogicalPosition::new(x, y))
}
pub fn x(&self) -> u32 {
self.0.x()
}
pub fn set_x(&self, x: u32) {
self.0.set_x(x)
}
pub fn y(&self) -> u32 {
self.0.y()
}
pub fn set_y(&self, y: u32) {
self.0.set_y(y)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PhysicalPosition(inner::PhysicalPosition);
impl PhysicalPosition {
pub fn new(x: u32, y: u32) -> Self {
Self(inner::PhysicalPosition::new(x, y))
}
pub fn to_logical(self, scale_factor: u32) -> LogicalPosition {
LogicalPosition(self.0.toLogical(scale_factor))
}
pub fn x(&self) -> u32 {
self.0.x()
}
pub fn set_x(&self, x: u32) {
self.0.set_x(x)
}
pub fn y(&self) -> u32 {
self.0.y()
}
pub fn set_y(&self, y: u32) {
self.0.set_y(y)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LogicalSize(inner::LogicalSize);
impl LogicalSize {
pub fn new(x: u32, y: u32) -> Self {
Self(inner::LogicalSize::new(x, y))
}
pub fn width(&self) -> u32 {
self.0.width()
}
pub fn set_width(&self, x: u32) {
self.0.set_width(x)
}
pub fn height(&self) -> u32 {
self.0.height()
}
pub fn set_height(&self, y: u32) {
self.0.set_height(y)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PhysicalSize(inner::PhysicalSize);
impl PhysicalSize {
pub fn new(x: u32, y: u32) -> Self {
Self(inner::PhysicalSize::new(x, y))
}
pub fn to_logical(self, scale_factor: u32) -> LogicalSize {
LogicalSize(self.0.toLogical(scale_factor))
}
pub fn width(&self) -> u32 {
self.0.width()
}
pub fn set_width(&self, x: u32) {
self.0.set_width(x)
}
pub fn height(&self) -> u32 {
self.0.height()
}
pub fn set_height(&self, y: u32) {
self.0.set_height(y)
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct Monitor(JsValue);
impl Monitor {
pub fn name(&self) -> Option<String> {
let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("name")).unwrap();
raw.as_string()
}
pub fn size(&self) -> PhysicalSize {
let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("size")).unwrap();
PhysicalSize(raw.unchecked_into())
}
pub fn position(&self) -> PhysicalPosition {
let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("position")).unwrap();
PhysicalPosition(raw.unchecked_into())
}
pub fn scale_factor(&self) -> u32 {
let raw = js_sys::Reflect::get(&self.0, &JsValue::from_str("size"))
.unwrap()
.as_f64()
.unwrap();
raw as u32
}
}
pub fn current_window() -> WebviewWindow {
WebviewWindow(inner::getCurrent())
}
pub fn all_windows() -> Vec<WebviewWindow> {
inner::getAll().into_iter().map(WebviewWindow).collect()
}
pub async fn current_monitor() -> Monitor {
Monitor(inner::currentMonitor().await)
}
pub async fn primary_monitor() -> Monitor {
Monitor(inner::primaryMonitor().await)
}
#[derive(Debug, Clone)]
pub struct AvailableMonitors {
idx: u32,
array: js_sys::Array,
}
impl Iterator for AvailableMonitors {
type Item = Monitor;
fn next(&mut self) -> Option<Self::Item> {
let raw = self.array.get(self.idx);
if raw.is_undefined() {
None
} else {
let monitor = Monitor(raw);
self.idx += 1;
Some(monitor)
}
}
}
pub async fn available_monitors() -> AvailableMonitors {
AvailableMonitors {
idx: 0,
array: inner::availableMonitors().await.unchecked_into(),
}
}
mod inner {
use wasm_bindgen::{
prelude::{wasm_bindgen, Closure},
JsValue,
};
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[derive(Debug, Clone, PartialEq)]
pub type LogicalPosition;
#[wasm_bindgen(constructor)]
pub fn new(x: u32, y: u32) -> LogicalPosition;
#[wasm_bindgen(method, getter)]
pub fn x(this: &LogicalPosition) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_x(this: &LogicalPosition, x: u32);
#[wasm_bindgen(method, getter)]
pub fn y(this: &LogicalPosition) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_y(this: &LogicalPosition, y: u32);
}
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[derive(Debug, Clone, PartialEq)]
pub type PhysicalPosition;
#[wasm_bindgen(constructor)]
pub fn new(x: u32, y: u32) -> PhysicalPosition;
#[wasm_bindgen(method)]
pub fn toLogical(this: &PhysicalPosition, scaleFactor: u32) -> LogicalPosition;
#[wasm_bindgen(method, getter)]
pub fn x(this: &PhysicalPosition) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_x(this: &PhysicalPosition, x: u32);
#[wasm_bindgen(method, getter)]
pub fn y(this: &PhysicalPosition) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_y(this: &PhysicalPosition, y: u32);
}
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[derive(Debug, Clone, PartialEq)]
pub type LogicalSize;
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> LogicalSize;
#[wasm_bindgen(method, getter)]
pub fn width(this: &LogicalSize) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_width(this: &LogicalSize, width: u32);
#[wasm_bindgen(method, getter)]
pub fn height(this: &LogicalSize) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_height(this: &LogicalSize, height: u32);
}
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[derive(Debug, Clone, PartialEq)]
pub type PhysicalSize;
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> PhysicalSize;
#[wasm_bindgen(method)]
pub fn toLogical(this: &PhysicalSize, scaleFactor: u32) -> LogicalSize;
#[wasm_bindgen(method, getter)]
pub fn width(this: &PhysicalSize) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_width(this: &PhysicalSize, width: u32);
#[wasm_bindgen(method, getter)]
pub fn height(this: &PhysicalSize) -> u32;
#[wasm_bindgen(method, setter)]
pub fn set_height(this: &PhysicalSize, height: u32);
}
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[derive(Debug, Clone, PartialEq)]
pub type WebviewWindowHandle;
#[wasm_bindgen(constructor)]
pub fn new(label: &str) -> WebviewWindowHandle;
#[wasm_bindgen(method, catch)]
pub async fn listen(
this: &WebviewWindowHandle,
event: &str,
handler: &Closure<dyn FnMut(JsValue)>,
) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn once(
this: &WebviewWindowHandle,
event: &str,
handler: &Closure<dyn FnMut(JsValue)>,
) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn emit(
this: &WebviewWindowHandle,
event: &str,
payload: JsValue,
) -> Result<(), JsValue>;
}
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[wasm_bindgen(extends = WebviewWindowHandle)]
#[derive(Debug, Clone, PartialEq)]
pub type WindowManager;
#[wasm_bindgen(constructor)]
pub fn new(label: &str) -> WindowManager;
#[wasm_bindgen(method, getter)]
pub fn label(this: &WindowManager) -> String;
#[wasm_bindgen(method, catch)]
pub async fn scaleFactor(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn innerPosition(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn outerPosition(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn innerSize(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn outerSize(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn isFullscreen(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn isMaximized(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn isDecorated(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn isResizable(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn isVisible(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn theme(this: &WindowManager) -> Result<JsValue, JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn center(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn requestUserAttention(
this: &WindowManager,
requestType: u32,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setResizable(this: &WindowManager, resizable: bool) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setTitle(this: &WindowManager, title: &str) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn maximize(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn unmaximize(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn toggleMaximize(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn minimize(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn unminimize(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn show(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn hide(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn close(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setDecorations(this: &WindowManager, decorations: bool)
-> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setAlwaysOnTop(this: &WindowManager, alwaysOnTop: bool)
-> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setSize, catch)]
pub async fn setSizePhysical(
this: &WindowManager,
size: PhysicalSize,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setSize, catch)]
pub async fn setSizeLogical(this: &WindowManager, size: LogicalSize)
-> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setMinSize, catch)]
pub async fn setMinSizePhysical(
this: &WindowManager,
size: Option<PhysicalSize>,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setMinSize, catch)]
pub async fn setMinSizeLogical(
this: &WindowManager,
size: Option<LogicalSize>,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setMaxSize, catch)]
pub async fn setMaxSizePhysical(
this: &WindowManager,
size: Option<PhysicalSize>,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setMinSize, catch)]
pub async fn setMaxSizeLogical(
this: &WindowManager,
size: Option<LogicalSize>,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setPosition, catch)]
pub async fn setPositionPhysical(
this: &WindowManager,
position: PhysicalPosition,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setPosition, catch)]
pub async fn setPositionLogical(
this: &WindowManager,
position: LogicalPosition,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setFullscreen(this: &WindowManager, fullscreen: bool) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setFocus(this: &WindowManager) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setIcon(this: &WindowManager, icon: &[u8]) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setSkipTaskbar(this: &WindowManager, skip: bool) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setCursorGrab(this: &WindowManager, grab: bool) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setCursorVisible(this: &WindowManager, visible: bool) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setCursorIcon(this: &WindowManager, icon: &str) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setCursorPosition, catch)]
pub async fn setCursorPositionPhysical(
this: &WindowManager,
position: PhysicalPosition,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, js_name = setCursorPosition, catch)]
pub async fn setCursorPositionLogical(
this: &WindowManager,
position: LogicalPosition,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn setIgnoreCursorEvents(
this: &WindowManager,
ignore: bool,
) -> Result<(), JsValue>;
#[wasm_bindgen(method, catch)]
pub async fn startDragging(this: &WindowManager) -> Result<(), JsValue>;
}
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
#[wasm_bindgen(extends = WindowManager)]
#[derive(Debug, Clone, PartialEq)]
pub type WebviewWindow;
#[wasm_bindgen(constructor)]
pub fn new(label: &str, options: ()) -> WebviewWindow;
#[wasm_bindgen(static_method_of = WebviewWindow)]
pub fn getByLabel(label: &str) -> Option<WebviewWindow>;
}
#[wasm_bindgen(module = "/src/window.js")]
extern "C" {
pub fn getCurrent() -> WebviewWindow;
pub fn getAll() -> Vec<WebviewWindow>;
pub async fn currentMonitor() -> JsValue;
pub async fn primaryMonitor() -> JsValue;
pub async fn availableMonitors() -> JsValue;
}
}

2
tauri

@ -1 +1 @@
Subproject commit 35264b4c1801b381e0b867c1c35540f0fbb43365
Subproject commit 2e1bd04775c0f05f1c0b67605e6abec4465dbf84