Add menu
functionality (#59)
* Added core::Channel and menu functionality. core::Channel may leak memory. * Updated examples to v2 using Leptos.
This commit is contained in:
parent
115009d4bf
commit
ae49310ee1
30 changed files with 5712 additions and 4216 deletions
|
@ -1,34 +1,569 @@
|
|||
use anyhow::ensure;
|
||||
use tauri_sys::app;
|
||||
use futures::stream::StreamExt;
|
||||
use leptos::{ev::MouseEvent, *};
|
||||
use std::rc::Rc;
|
||||
|
||||
pub async fn get_name() -> anyhow::Result<()> {
|
||||
let name = app::get_name().await?;
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
view! {
|
||||
<main class="container">
|
||||
<div>
|
||||
<h2>"core"</h2>
|
||||
<Core/>
|
||||
</div>
|
||||
|
||||
ensure!(name == "tauri-sys-test");
|
||||
<div>
|
||||
<h2>"events"</h2>
|
||||
<Events/>
|
||||
</div>
|
||||
|
||||
Ok(())
|
||||
<div>
|
||||
<h2>"window"</h2>
|
||||
<Window/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>"menu"</h2>
|
||||
<Menu/>
|
||||
</div>
|
||||
</main>
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_version() -> anyhow::Result<()> {
|
||||
let version = app::get_version().await?;
|
||||
#[component]
|
||||
fn Core() -> impl IntoView {
|
||||
let (convert_path, set_convert_path) = create_signal("".to_string());
|
||||
let (converted_path, set_converted_path) = create_signal("".to_string());
|
||||
|
||||
ensure!(version.major == 0);
|
||||
ensure!(version.minor == 0);
|
||||
ensure!(version.patch == 0);
|
||||
ensure!(version.build.is_empty());
|
||||
ensure!(version.pre.is_empty());
|
||||
let do_convert_path = move |_| {
|
||||
let converted = tauri_sys::core::convert_file_src(convert_path());
|
||||
set_converted_path(converted);
|
||||
};
|
||||
|
||||
Ok(())
|
||||
view! {
|
||||
<div>
|
||||
<div>
|
||||
<label>
|
||||
"Convert path"
|
||||
<input
|
||||
prop:value=convert_path
|
||||
on:input=move |e| set_convert_path(event_target_value(&e))
|
||||
/>
|
||||
</label>
|
||||
<button on:click=do_convert_path>"Convert"</button>
|
||||
</div>
|
||||
<div>{converted_path}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_tauri_version() -> anyhow::Result<()> {
|
||||
let version = app::get_tauri_version().await?;
|
||||
#[component]
|
||||
fn Events() -> impl IntoView {
|
||||
let (listen_event, set_listen_event) = create_signal(None);
|
||||
let (emit_count, set_emit_count) = create_signal(0);
|
||||
|
||||
ensure!(version.major == 1);
|
||||
ensure!(version.minor == 5);
|
||||
ensure!(version.patch == 3);
|
||||
ensure!(version.build.is_empty());
|
||||
ensure!(version.pre.is_empty());
|
||||
spawn_local(async move {
|
||||
let mut listener = tauri_sys::event::listen::<i32>("event::listen")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
while let Some(event) = listener.next().await {
|
||||
tracing::debug!(?event);
|
||||
let tauri_sys::event::Event {
|
||||
event: _,
|
||||
id: _,
|
||||
payload,
|
||||
} = event;
|
||||
set_listen_event.set(Some(payload));
|
||||
}
|
||||
});
|
||||
|
||||
spawn_local(async move {
|
||||
let mut listener = tauri_sys::event::listen::<i32>("event::emit")
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
while let Some(event) = listener.next().await {
|
||||
tracing::debug!(?event);
|
||||
let tauri_sys::event::Event {
|
||||
event: _,
|
||||
id: _,
|
||||
payload,
|
||||
} = event;
|
||||
set_emit_count.set(payload);
|
||||
}
|
||||
});
|
||||
|
||||
let trigger_listen_events = move |_| {
|
||||
spawn_local(async move {
|
||||
tauri_sys::core::invoke::<()>("trigger_listen_events", &()).await;
|
||||
});
|
||||
};
|
||||
|
||||
let trigger_emit_event = move |_| {
|
||||
spawn_local(async move {
|
||||
tauri_sys::event::emit("event::emit", &emit_count.with_untracked(|n| n + 1))
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<div>
|
||||
<button on:click=trigger_listen_events>"Trigger listen events"</button>
|
||||
<div>
|
||||
<strong>"Last listen event: "</strong>
|
||||
{move || listen_event()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button on:click=trigger_emit_event>"Trigger emit event"</button>
|
||||
<div>
|
||||
<strong>"Events emitted: "</strong>
|
||||
{move || emit_count()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Window() -> impl IntoView {
|
||||
view! {
|
||||
<div>
|
||||
<div>
|
||||
<h3>"Windows"</h3>
|
||||
<WindowWindows/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>"Monitors"</h3>
|
||||
<WindowMonitors/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>"Events"</h3>
|
||||
<WindowEvents/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WindowWindows() -> impl IntoView {
|
||||
let current_window = create_action(|_| async move { tauri_sys::window::get_current() });
|
||||
let all_windows = create_action(|_| async move { tauri_sys::window::get_all() });
|
||||
|
||||
let refresh = move |_| {
|
||||
current_window.dispatch(());
|
||||
all_windows.dispatch(());
|
||||
};
|
||||
|
||||
current_window.dispatch(());
|
||||
all_windows.dispatch(());
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<div style="display: flex; justify-content: center; gap: 10px;">
|
||||
<div>"Current window:"</div>
|
||||
{move || {
|
||||
current_window
|
||||
.value()
|
||||
.with(|window| match window {
|
||||
None => "Loading".to_string(),
|
||||
Some(window) => window.label().clone(),
|
||||
})
|
||||
}}
|
||||
|
||||
</div>
|
||||
<div style="display: flex; justify-content: center; gap: 10px;">
|
||||
<div>"All windows:"</div>
|
||||
{move || {
|
||||
all_windows
|
||||
.value()
|
||||
.with(|windows| match windows {
|
||||
None => "Loading".to_string(),
|
||||
Some(windows) => {
|
||||
let out = windows
|
||||
.iter()
|
||||
.map(|window| { window.label().clone() })
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
format!("[{out}]")
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
||||
</div>
|
||||
<button on:click=refresh>"Refresh"</button>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WindowMonitors() -> impl IntoView {
|
||||
let current_monitor =
|
||||
create_action(|_| async move { tauri_sys::window::current_monitor().await });
|
||||
|
||||
let primary_monitor =
|
||||
create_action(|_| async move { tauri_sys::window::primary_monitor().await });
|
||||
|
||||
let available_monitors =
|
||||
create_action(|_| async move { tauri_sys::window::available_monitors().await });
|
||||
|
||||
let monitor_from_point = create_action(|(x, y): &(isize, isize)| {
|
||||
let x = x.clone();
|
||||
let y = y.clone();
|
||||
async move { tauri_sys::window::monitor_from_point(x, y).await }
|
||||
});
|
||||
|
||||
// let cursor_position =
|
||||
// create_action(|_| async move { tauri_sys::window::cursor_position().await });
|
||||
|
||||
let refresh = move |_| {
|
||||
current_monitor.dispatch(());
|
||||
primary_monitor.dispatch(());
|
||||
available_monitors.dispatch(());
|
||||
};
|
||||
|
||||
let oninput_monitor_from_point = move |e| {
|
||||
let value = event_target_value(&e);
|
||||
let Some((x, y)) = value.split_once(',') else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(x) = x.parse::<isize>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Ok(y) = y.parse::<isize>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
monitor_from_point.dispatch((x, y));
|
||||
};
|
||||
|
||||
current_monitor.dispatch(());
|
||||
primary_monitor.dispatch(());
|
||||
available_monitors.dispatch(());
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<div>
|
||||
<div style="display: flex; justify-content: center; gap: 10px;">
|
||||
<div>"Current monitor:"</div>
|
||||
{move || {
|
||||
current_monitor
|
||||
.value()
|
||||
.with(|monitor| match monitor {
|
||||
None => "Loading".into_view(),
|
||||
Some(Some(monitor)) => view! { <Monitor monitor/> }.into_view(),
|
||||
Some(None) => "Could not detect monitor.".into_view(),
|
||||
})
|
||||
}}
|
||||
|
||||
</div>
|
||||
<div style="display: flex; justify-content: center; gap: 10px;">
|
||||
<div>"Primary monitor:"</div>
|
||||
{move || {
|
||||
primary_monitor
|
||||
.value()
|
||||
.with(|monitor| match monitor {
|
||||
None => "Loading".into_view(),
|
||||
Some(Some(monitor)) => view! { <Monitor monitor/> }.into_view(),
|
||||
Some(None) => "Could not detect monitor.".into_view(),
|
||||
})
|
||||
}}
|
||||
|
||||
</div>
|
||||
<div style="display: flex; justify-content: center; gap: 10px;">
|
||||
<div>"Available monitors:"</div>
|
||||
{move || {
|
||||
available_monitors
|
||||
.value()
|
||||
.with(|monitors| match monitors {
|
||||
None => "Loading".into_view(),
|
||||
Some(monitors) => {
|
||||
view! {
|
||||
{monitors
|
||||
.iter()
|
||||
.map(|monitor| view! { <Monitor monitor/> })
|
||||
.collect::<Vec<_>>()}
|
||||
}
|
||||
.into_view()
|
||||
}
|
||||
})
|
||||
}}
|
||||
|
||||
</div>
|
||||
<button on:click=refresh>"Refresh"</button>
|
||||
</div>
|
||||
<div>
|
||||
<label>"Monitor from point" <input on:input=oninput_monitor_from_point/></label>
|
||||
<div style="margin: 0 auto;">
|
||||
{move || {
|
||||
monitor_from_point
|
||||
.value()
|
||||
.with(|monitor| match monitor {
|
||||
None => "Enter an `x, y` coordinate.".into_view(),
|
||||
Some(Some(monitor)) => view! { <Monitor monitor/> }.into_view(),
|
||||
Some(None) => "Could not detect monitor.".into_view(),
|
||||
})
|
||||
}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
// {move || {
|
||||
// cursor_position
|
||||
// .value()
|
||||
// .with(|position| {
|
||||
// position
|
||||
// .as_ref()
|
||||
// .map(|position| {
|
||||
// view! {
|
||||
// {position.x()}
|
||||
// ", "
|
||||
// {position.y()}
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// }}
|
||||
<div>"Cursor position: "</div>
|
||||
<div style="width: 50vw; height: 30vh; margin: 0 auto; border: 2px solid black; border-radius: 5px;">
|
||||
// on:mousemove=move |_| cursor_position.dispatch(())
|
||||
"TODO (See https://github.com/tauri-apps/tauri/issues/10340)"
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn WindowEvents() -> impl IntoView {
|
||||
use tauri_sys::window::{DragDropEvent, DragDropPayload, DragOverPayload};
|
||||
|
||||
let (count, set_count) = create_signal(0);
|
||||
let increment_count = create_action(|count: &usize| {
|
||||
let count = count.clone();
|
||||
let window = tauri_sys::window::get_current();
|
||||
async move {
|
||||
web_sys::console::debug_1(&"0".into());
|
||||
window.emit("count", count).await.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
let (drag_drop, set_drag_drop) = create_signal(().into_view());
|
||||
|
||||
spawn_local(async move {
|
||||
let mut window = tauri_sys::window::get_current();
|
||||
let mut listener = window.listen::<usize>("count").await.unwrap();
|
||||
while let Some(event) = listener.next().await {
|
||||
set_count(event.payload);
|
||||
}
|
||||
});
|
||||
|
||||
spawn_local(async move {
|
||||
let window = tauri_sys::window::get_current();
|
||||
let mut listener = window.on_drag_drop_event().await.unwrap();
|
||||
while let Some(event) = listener.next().await {
|
||||
match event.payload {
|
||||
DragDropEvent::Enter(payload) => {
|
||||
let out = view! {
|
||||
<div>
|
||||
<strong>"Enter"</strong>
|
||||
<div>
|
||||
"Paths: ["
|
||||
{payload
|
||||
.paths()
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")} "]"
|
||||
</div>
|
||||
<div>
|
||||
"Position: " {payload.position().x()} ", " {payload.position().y()}
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
set_drag_drop(out.into_view());
|
||||
}
|
||||
DragDropEvent::Over(payload) => {
|
||||
let out = view! {
|
||||
<div>
|
||||
<strong>"Over"</strong>
|
||||
<div>
|
||||
"Position: " {payload.position().x()} ", " {payload.position().y()}
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
set_drag_drop(out.into_view());
|
||||
}
|
||||
DragDropEvent::Drop(payload) => {
|
||||
let out = view! {
|
||||
<div>
|
||||
<strong>"Drop"</strong>
|
||||
<div>
|
||||
"Paths: ["
|
||||
{payload
|
||||
.paths()
|
||||
.iter()
|
||||
.map(|path| path.to_string_lossy().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")} "]"
|
||||
</div>
|
||||
<div>
|
||||
"Position: " {payload.position().x()} ", " {payload.position().y()}
|
||||
</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
set_drag_drop(out.into_view());
|
||||
}
|
||||
DragDropEvent::Leave => {
|
||||
let out = view! { <strong>"Leave"</strong> };
|
||||
set_drag_drop(out.into_view());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
view! {
|
||||
<div>
|
||||
<div>
|
||||
"Count: " {count}
|
||||
<button on:click=move |_| increment_count.dispatch(count() + 1)>"+"</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3>"Drag drop event"</h3>
|
||||
<div>{drag_drop}</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Monitor<'a>(monitor: &'a tauri_sys::window::Monitor) -> impl IntoView {
|
||||
view! {
|
||||
<div style="display: inline-block; text-align: left;">
|
||||
<div>"Name: " {monitor.name().clone()}</div>
|
||||
<div>"Size: " {monitor.size().width()} " x " {monitor.size().height()}</div>
|
||||
<div>"Position: " {monitor.position().x()} ", " {monitor.position().y()}</div>
|
||||
<div>"Scale: " {monitor.scale_factor()}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Menu() -> impl IntoView {
|
||||
let (event_manual, set_event_manual) = create_signal::<Option<String>>(None);
|
||||
let (event_with_items, set_event_with_items) = create_signal::<Option<String>>(None);
|
||||
|
||||
let default_menu = move |e: MouseEvent| {
|
||||
spawn_local(async move {
|
||||
let menu = tauri_sys::menu::Menu::default().await;
|
||||
});
|
||||
};
|
||||
|
||||
let menu_manual = create_local_resource(
|
||||
|| (),
|
||||
move |_| async move {
|
||||
let menu = tauri_sys::menu::Menu::with_id("tauri-sys-menu").await;
|
||||
let mut item_open =
|
||||
tauri_sys::menu::item::MenuItem::with_id("Item 1 - Manual", "manual-item_1").await;
|
||||
let mut item_close =
|
||||
tauri_sys::menu::item::MenuItem::with_id("Item 2 - Manual", "manual-item_2").await;
|
||||
menu.append_item(&item_open).await.unwrap();
|
||||
menu.append_item(&item_close).await.unwrap();
|
||||
|
||||
spawn_local(async move {
|
||||
let mut listener_item_open = item_open.listen().fuse();
|
||||
let mut listener_item_close = item_close.listen().fuse();
|
||||
loop {
|
||||
futures::select! {
|
||||
event = listener_item_open.next() => match event{
|
||||
None => continue,
|
||||
Some(event) => set_event_manual(Some(event.clone())),
|
||||
},
|
||||
event = listener_item_close.next() => match event{
|
||||
None => continue,
|
||||
Some(event) => set_event_manual(Some(event.clone())),
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Rc::new(menu)
|
||||
},
|
||||
);
|
||||
|
||||
let menu_with_items = create_local_resource(
|
||||
|| (),
|
||||
move |_| async move {
|
||||
let mut item_open = tauri_sys::menu::item::MenuItemOptions::new("Item 1 - w/ items");
|
||||
item_open.set_id("w_items-item_1");
|
||||
let mut item_close = tauri_sys::menu::item::MenuItemOptions::new("Item 2 - w/ items");
|
||||
item_close.set_id("w_items-item_2");
|
||||
let items = vec![item_open.into(), item_close.into()];
|
||||
|
||||
let (menu, mut listeners) =
|
||||
tauri_sys::menu::Menu::with_id_and_items("tauri-sys_menu_w_items", items).await;
|
||||
let mut listener_item_open = listeners.remove(0).unwrap().fuse();
|
||||
let mut listener_item_close = listeners.remove(0).unwrap().fuse();
|
||||
|
||||
spawn_local(async move {
|
||||
loop {
|
||||
futures::select! {
|
||||
event = listener_item_open.next() => match event{
|
||||
None => continue,
|
||||
Some(event) => {
|
||||
set_event_with_items(Some(event.clone()))
|
||||
},
|
||||
},
|
||||
event = listener_item_close.next() => match event{
|
||||
None => continue,
|
||||
Some(event) => set_event_with_items(Some(event.clone())),
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Rc::new(menu)
|
||||
},
|
||||
);
|
||||
|
||||
let open_menu_manual = move |_e: MouseEvent| {
|
||||
let menu = menu_manual.get().unwrap();
|
||||
spawn_local(async move {
|
||||
menu.popup().await.unwrap();
|
||||
});
|
||||
};
|
||||
|
||||
let open_menu_with_items = move |_e: MouseEvent| {
|
||||
let menu = menu_with_items.get().unwrap();
|
||||
spawn_local(async move {
|
||||
menu.popup().await.unwrap();
|
||||
});
|
||||
};
|
||||
|
||||
view! {
|
||||
<div
|
||||
on:mousedown=open_menu_manual
|
||||
style="margin: 0 auto 2em; width: 50vw; height: 10em; border: 1px black solid; border-radius: 5px;"
|
||||
>
|
||||
{event_manual}
|
||||
</div>
|
||||
|
||||
<div
|
||||
on:mousedown=open_menu_with_items
|
||||
style="margin: auto; width: 50vw; height: 10em; border: 1px black solid; border-radius: 5px;"
|
||||
>
|
||||
{event_with_items}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
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(())
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
use anyhow::ensure;
|
||||
use tauri_sys::dialog::{FileDialogBuilder, MessageDialogBuilder, MessageDialogKind};
|
||||
|
||||
pub async fn ask() -> anyhow::Result<()> {
|
||||
let works = MessageDialogBuilder::new()
|
||||
.set_title("Tauri")
|
||||
.set_kind(MessageDialogKind::Warning)
|
||||
.ask("Does this work? \n Click Yes to mark this test as passing")
|
||||
.await?;
|
||||
|
||||
ensure!(works);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn confirm() -> anyhow::Result<()> {
|
||||
let works = MessageDialogBuilder::new()
|
||||
.set_title("Tauri")
|
||||
.set_kind(MessageDialogKind::Warning)
|
||||
.confirm("Does this work? \n Click Ok to mark this test as passing")
|
||||
.await?;
|
||||
|
||||
ensure!(works);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn message() -> anyhow::Result<()> {
|
||||
MessageDialogBuilder::new()
|
||||
.set_title("Tauri")
|
||||
.set_kind(MessageDialogKind::Warning)
|
||||
.message("This is a message just for you!")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn pick_file() -> anyhow::Result<()> {
|
||||
let file = FileDialogBuilder::new()
|
||||
.set_title("Select a file to mark this test as passing")
|
||||
.pick_file()
|
||||
.await?;
|
||||
|
||||
ensure!(file.is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn pick_files() -> anyhow::Result<()> {
|
||||
let file = FileDialogBuilder::new()
|
||||
.set_title("Select a multiple files to mark this test as passing")
|
||||
.pick_files()
|
||||
.await?;
|
||||
|
||||
ensure!(file.is_some());
|
||||
ensure!(file.unwrap().count() > 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn pick_folder() -> anyhow::Result<()> {
|
||||
let file = FileDialogBuilder::new()
|
||||
.set_title("Select a folder to mark this test as passing")
|
||||
.pick_folder()
|
||||
.await?;
|
||||
|
||||
ensure!(file.is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn pick_folders() -> anyhow::Result<()> {
|
||||
let file = FileDialogBuilder::new()
|
||||
.set_title("Select a multiple folders to mark this test as passing")
|
||||
.pick_folders()
|
||||
.await?;
|
||||
|
||||
ensure!(file.is_some());
|
||||
ensure!(file.unwrap().count() > 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn save() -> anyhow::Result<()> {
|
||||
let file = FileDialogBuilder::new()
|
||||
.set_title("Select a file to mark this test as passing")
|
||||
.save()
|
||||
.await?;
|
||||
|
||||
ensure!(file.is_some());
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
use anyhow::ensure;
|
||||
use futures::StreamExt;
|
||||
use tauri_sys::{event, tauri};
|
||||
|
||||
pub async fn emit() -> anyhow::Result<()> {
|
||||
event::emit("javascript-event", &"bar").await?;
|
||||
|
||||
ensure!(tauri::invoke::<_, bool>("verify_receive", &()).await?);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn listen() -> anyhow::Result<()> {
|
||||
let events = event::listen::<u32>("rust-event-listen").await?;
|
||||
tauri::invoke::<_, ()>("emit_event_5_times", &()).await?;
|
||||
|
||||
let events: Vec<u32> = events
|
||||
.take(5)
|
||||
.map(|e| e.payload)
|
||||
.collect()
|
||||
.await;
|
||||
|
||||
ensure!(events == vec![0, 1, 2, 3, 4]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn once() -> anyhow::Result<()> {
|
||||
// this causes enough delay for `once` to register it's event listener before the event gets triggered
|
||||
wasm_bindgen_futures::spawn_local(async {
|
||||
tauri::invoke::<_, ()>("emit_event", &()).await.unwrap();
|
||||
});
|
||||
let event = event::once::<String>("rust-event-once").await?;
|
||||
|
||||
ensure!(event.payload == "Hello World from Rust!");
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use futures::StreamExt;
|
||||
use tauri_sys::global_shortcut;
|
||||
|
||||
pub async fn register_all() -> anyhow::Result<()> {
|
||||
let task = async {
|
||||
let shortcuts = ["CommandOrControl+Shift+C", "Ctrl+Alt+F12"];
|
||||
|
||||
let streams = futures::future::try_join_all(shortcuts.map(|s| async move {
|
||||
let stream = global_shortcut::register(s).await?;
|
||||
|
||||
anyhow::Ok(stream.map(move |_| s))
|
||||
}))
|
||||
.await?;
|
||||
|
||||
let mut events = futures::stream::select_all(streams);
|
||||
|
||||
while let Some(shortcut) = events.next().await {
|
||||
log::debug!("Shortcut {} triggered", shortcut)
|
||||
}
|
||||
|
||||
anyhow::Ok(())
|
||||
};
|
||||
|
||||
let timeout = gloo_timers::future::sleep(Duration::from_secs(20));
|
||||
|
||||
futures::future::select(Box::pin(task), timeout).await;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,196 +1,33 @@
|
|||
mod app;
|
||||
mod clipboard;
|
||||
mod dialog;
|
||||
mod event;
|
||||
mod notification;
|
||||
mod os;
|
||||
mod tauri_log;
|
||||
mod window;
|
||||
mod global_shortcut;
|
||||
|
||||
extern crate console_error_panic_hook;
|
||||
use log::LevelFilter;
|
||||
use std::future::Future;
|
||||
use std::panic;
|
||||
use sycamore::prelude::*;
|
||||
use sycamore::suspense::Suspense;
|
||||
use tauri_log::TauriLogger;
|
||||
|
||||
#[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 TestInner<'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!("✅")
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Test<'a, G: Html, F>(cx: Scope<'a>, props: TestProps<'a, F>) -> View<G>
|
||||
where
|
||||
F: Future<Output = anyhow::Result<()>> + 'a,
|
||||
{
|
||||
let fallback = view! { cx,
|
||||
tr {
|
||||
td { code { (props.name.to_string()) } }
|
||||
td {
|
||||
span(class="loader") { "⏳" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
view! { cx,
|
||||
Suspense(fallback=fallback) {
|
||||
TestInner(name=props.name, test=props.test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ci"))]
|
||||
#[component]
|
||||
pub 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();
|
||||
|
||||
view! { cx,
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
static LOGGER: TauriLogger = TauriLogger;
|
||||
use app::*;
|
||||
use leptos::*;
|
||||
|
||||
fn main() {
|
||||
log::set_logger(&LOGGER)
|
||||
.map(|()| log::set_max_level(LevelFilter::Trace))
|
||||
.unwrap();
|
||||
|
||||
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())
|
||||
Test(name="event::listen",test=event::listen())
|
||||
Test(name="event::once",test=event::once())
|
||||
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="os::arch",test=os::arch())
|
||||
Test(name="os::platform",test=os::platform())
|
||||
Test(name="os::tempdir",test=os::tempdir())
|
||||
Test(name="os::kind",test=os::kind())
|
||||
Test(name="os::version",test=os::version())
|
||||
Test(name="notification::is_permission_granted",test=notification::is_permission_granted())
|
||||
Test(name="notification::request_permission",test=notification::request_permission())
|
||||
InteractiveTest(name="notification::show_notification",test=notification::show_notification())
|
||||
InteractiveTest(name="global_shortcut::register_all",test=global_shortcut::register_all())
|
||||
|
||||
Test(name="window::WebviewWindow::new",test=window::create_window())
|
||||
|
||||
Terminate
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
#[cfg(debug_assertions)]
|
||||
tracing::enable();
|
||||
console_error_panic_hook::set_once();
|
||||
mount_to_body(|| {
|
||||
view! { <App/> }
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
mod tracing {
|
||||
use tracing::level_filters::LevelFilter;
|
||||
use tracing_subscriber::prelude::*;
|
||||
use tracing_web::MakeConsoleWriter;
|
||||
|
||||
const MAX_LOG_LEVEL: LevelFilter = LevelFilter::DEBUG;
|
||||
|
||||
pub fn enable() {
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(false) // Only partially supported across browsers
|
||||
.pretty()
|
||||
.without_time()
|
||||
.with_writer(MakeConsoleWriter) // write events to the console
|
||||
.with_filter(MAX_LOG_LEVEL);
|
||||
|
||||
tracing_subscriber::registry().with(fmt_layer).init();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
use anyhow::ensure;
|
||||
use tauri_sys::notification::{self, Permission};
|
||||
|
||||
pub async fn is_permission_granted() -> anyhow::Result<()> {
|
||||
let granted = notification::is_permission_granted().await?;
|
||||
|
||||
ensure!(granted);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn request_permission() -> anyhow::Result<()> {
|
||||
let permission = notification::request_permission().await?;
|
||||
|
||||
ensure!(permission == Permission::Granted);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn show_notification() -> anyhow::Result<()> {
|
||||
let mut n = notification::Notification::default();
|
||||
n.set_title("TAURI");
|
||||
n.set_body("Tauri is awesome!");
|
||||
|
||||
n.show()?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
use tauri_sys::os;
|
||||
|
||||
pub async fn arch() -> anyhow::Result<()> {
|
||||
let arch = os::arch().await?;
|
||||
|
||||
log::debug!("{:?}", arch);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn platform() -> anyhow::Result<()> {
|
||||
let platform = os::platform().await?;
|
||||
|
||||
log::debug!("{:?}", platform);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn tempdir() -> anyhow::Result<()> {
|
||||
let tempdir = os::tempdir().await?;
|
||||
|
||||
log::info!("{:?}", tempdir);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn kind() -> anyhow::Result<()> {
|
||||
let kind = os::kind().await?;
|
||||
|
||||
log::debug!("{:?}", kind);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn version() -> anyhow::Result<()> {
|
||||
let version = os::version().await?;
|
||||
|
||||
log::debug!("{:?}", version);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
use log::{Metadata, Record};
|
||||
use serde::Serialize;
|
||||
use tauri_sys::tauri;
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct LogArgs {
|
||||
level: Level,
|
||||
message: String,
|
||||
location: String,
|
||||
file: Option<String>,
|
||||
line: Option<u32>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Level {
|
||||
Trace,
|
||||
Debug,
|
||||
Info,
|
||||
Warn,
|
||||
Error,
|
||||
}
|
||||
|
||||
impl From<log::Level> for Level {
|
||||
fn from(l: log::Level) -> Self {
|
||||
match l {
|
||||
log::Level::Error => Level::Error,
|
||||
log::Level::Warn => Level::Warn,
|
||||
log::Level::Info => Level::Info,
|
||||
log::Level::Debug => Level::Debug,
|
||||
log::Level::Trace => Level::Trace,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Level {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_u8(match self {
|
||||
Level::Trace => 1,
|
||||
Level::Debug => 2,
|
||||
Level::Info => 3,
|
||||
Level::Warn => 4,
|
||||
Level::Error => 5,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TauriLogger;
|
||||
impl log::Log for TauriLogger {
|
||||
fn enabled(&self, metadata: &Metadata) -> bool {
|
||||
metadata.level() <= log::Level::Trace
|
||||
}
|
||||
|
||||
fn log(&self, record: &Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
let args = LogArgs {
|
||||
level: record.level().into(),
|
||||
location: record.target().to_string(),
|
||||
message: format!("{}", record.args()),
|
||||
file: record.file().map(ToString::to_string),
|
||||
line: record.line(),
|
||||
};
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
tauri::invoke::<_, ()>("plugin:log|log", &args)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
use anyhow::ensure;
|
||||
use tauri_sys::window;
|
||||
|
||||
pub async fn create_window() -> anyhow::Result<()> {
|
||||
let win = window::WebviewWindowBuilder::new("foo-win")
|
||||
.set_url("/")
|
||||
.build()
|
||||
.await?;
|
||||
|
||||
ensure!(win.is_visible().await?);
|
||||
|
||||
win.close().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue