refactor(examples/api): new look (#4465)

Co-authored-by: Lucas Nogueira <lucas@tauri.studio>
This commit is contained in:
Amr Bashir
2022-07-04 03:15:23 +02:00
committed by GitHub
parent 7e3ac8475c
commit 3fbaee454d
50 changed files with 2588 additions and 1881 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
ul.svelte-gbh3pt{list-style:none;margin:0;padding:0;padding-left:var(--nodePaddingLeft, 1rem);border-left:var(--nodeBorderLeft, 1px dotted #9ca3af);color:var(--nodeColor, #374151)}.hidden.svelte-gbh3pt{display:none}.bracket.svelte-gbh3pt{cursor:pointer}.bracket.svelte-gbh3pt:hover{background:var(--bracketHoverBackground, #d1d5db)}.comma.svelte-gbh3pt{color:var(--nodeColor, #374151)}.val.svelte-gbh3pt{color:var(--leafDefaultColor, #9ca3af)}.val.string.svelte-gbh3pt{color:var(--leafStringColor, #059669)}.val.number.svelte-gbh3pt{color:var(--leafNumberColor, #d97706)}.val.boolean.svelte-gbh3pt{color:var(--leafBooleanColor, #2563eb)}

File diff suppressed because one or more lines are too long

View File

@@ -1,196 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Tauri&display=swap');
* {
font-family: Tauri, Arial, Helvetica, sans-serif;
}
body {
background: rgb(24, 25, 26, 0.8);
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.logo-container {
width: 95%;
margin: 0px auto;
}
.logo-link {
font-weight: 700;
position: absolute;
top: 150px;
right: 10px;
}
.logo {
cursor: pointer;
}
#response {
height: 100%;
max-height: 100%;
margin-top: 1em;
background: rgb(36, 37, 38, 0.8);
color: #f0f4f5;
border: solid 1px rgba(255, 255, 255, 0.055);
box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%);
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
word-wrap: break-word;
padding: 0px 15px;
overflow-y: auto;
}
input,
select,
textarea {
background: rgb(53, 53, 53, 0.9);
color: #fff;
font-family: system-ui, sans-serif;
border: none !important;
border-radius: 0.25rem;
font-size: 1rem;
line-height: 1.2;
padding: 0.25rem 0.5rem;
margin: 0.25rem;
transition: 0.2s ease;
}
button:hover,
button:focus {
background: #ffe07a;
}
button:focus {
outline: 1px solid #fff;
outline-offset: -4px;
}
button:active {
transform: scale(0.99);
}
.button {
border: 0;
border-radius: 0.25rem;
background: #67d6ed;
color: rgb(0, 0, 0);
font-family: system-ui, sans-serif;
font-size: 1rem;
line-height: 1.2;
white-space: nowrap;
text-decoration: none;
padding: 0.25rem 0.5rem;
margin: 0.25rem;
cursor: pointer;
transition: 0.2s ease;
}
.dark-link {
color: white;
text-decoration: none !important;
padding: 0.5em;
background: rgb(36, 37, 38);
transition: 0.2s ease;
border: solid 1px rgba(255, 255, 255, 0.055);
box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%);
}
.dark-link:hover {
background: #3d392a;
}
.nv {
color: #fff;
cursor: pointer;
transition: 0.25s ease;
}
.nv:hover {
color: #ffe07a;
padding-left: 8px;
border-left: solid 5px #ffe07a;
}
.nv_selected {
color: #67d6ed;
padding-left: 8px;
border-left: solid 5px #67d6ed;
}
.content {
background: rgb(36, 37, 38, 0.5);
color: #f0f4f5;
padding: 20px;
width: 100%;
border: solid 1px rgba(255, 255, 255, 0.055);
box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%);
}
main {
height: 100%;
}
[type='radio']:checked~label {
background: rgb(36, 37, 38);
color: #67d6ed;
border-bottom: 1px solid transparent;
z-index: 2;
}
[type='radio']:checked~label~.content {
z-index: 1;
}
.flex {
display: flex;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
.just-around {
justify-content: space-between;
}
.hidden {
display: none;
}
.alert {
width: auto;
height: 40px;
display: flex;
justify-content: left;
align-items: center;
border-radius: 5px;
padding-left: 10px;
padding-right: 40px;
font-size: 15px;
color: #000;
margin-bottom: 10px;
margin-top: 10px;
box-shadow: rgba(0, 0, 0, 0.06) 0px 0px 10px;
border-left: 6px solid #ff0000;
background: #f0f4f5;
}
#file-response {
height: 400px;
}
span.key {
color: #fff;
}

View File

@@ -1,13 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" theme="dark">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/global.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte + Vite App</title>
<script type="module" crossorigin src="/assets/index.js"></script>
<link rel="modulepreload" href="/assets/vendor.js">
<link rel="stylesheet" href="/assets/vendor.css">
<link rel="stylesheet" href="/assets/index.css">
</head>

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,8 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" theme="dark">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/global.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Svelte + Vite App</title>
</head>

View File

@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Isolation Secure Script</title>
</head>
<body>
<script src="index.js"></script>
</body>
<head>
<meta charset="UTF-8" />
<title>Isolation Secure Script</title>
</head>
<body>
<script src="index.js"></script>
</body>
</html>

View File

@@ -1,3 +1,3 @@
window.__TAURI_ISOLATION_HOOK__= (payload) => {
window.__TAURI_ISOLATION_HOOK__ = (payload) => {
return payload
}

View File

@@ -10,12 +10,14 @@
},
"dependencies": {
"@tauri-apps/api": "../../tooling/api/dist",
"@zerodevx/svelte-json-view": "0.2.0",
"hotkeys-js": "^3.8.5"
"@zerodevx/svelte-json-view": "0.2.1"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.11",
"svelte": "3.35.0",
"vite": "^2.6.4"
"@iconify-json/codicon": "^1.1.10",
"@iconify-json/ph": "^1.1.1",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
"svelte": "^3.48.0",
"unocss": "^0.39.3",
"vite": "^2.9.12"
}
}

View File

@@ -1,196 +0,0 @@
@import url('https://fonts.googleapis.com/css2?family=Tauri&display=swap');
* {
font-family: Tauri, Arial, Helvetica, sans-serif;
}
body {
background: rgb(24, 25, 26, 0.8);
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.logo-container {
width: 95%;
margin: 0px auto;
}
.logo-link {
font-weight: 700;
position: absolute;
top: 150px;
right: 10px;
}
.logo {
cursor: pointer;
}
#response {
height: 100%;
max-height: 100%;
margin-top: 1em;
background: rgb(36, 37, 38, 0.8);
color: #f0f4f5;
border: solid 1px rgba(255, 255, 255, 0.055);
box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%);
font-family: 'Courier New', Courier, monospace;
font-size: 12px;
word-wrap: break-word;
padding: 0px 15px;
overflow-y: auto;
}
input,
select,
textarea {
background: rgb(53, 53, 53, 0.9);
color: #fff;
font-family: system-ui, sans-serif;
border: none !important;
border-radius: 0.25rem;
font-size: 1rem;
line-height: 1.2;
padding: 0.25rem 0.5rem;
margin: 0.25rem;
transition: 0.2s ease;
}
button:hover,
button:focus {
background: #ffe07a;
}
button:focus {
outline: 1px solid #fff;
outline-offset: -4px;
}
button:active {
transform: scale(0.99);
}
.button {
border: 0;
border-radius: 0.25rem;
background: #67d6ed;
color: rgb(0, 0, 0);
font-family: system-ui, sans-serif;
font-size: 1rem;
line-height: 1.2;
white-space: nowrap;
text-decoration: none;
padding: 0.25rem 0.5rem;
margin: 0.25rem;
cursor: pointer;
transition: 0.2s ease;
}
.dark-link {
color: white;
text-decoration: none !important;
padding: 0.5em;
background: rgb(36, 37, 38);
transition: 0.2s ease;
border: solid 1px rgba(255, 255, 255, 0.055);
box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%);
}
.dark-link:hover {
background: #3d392a;
}
.nv {
color: #fff;
cursor: pointer;
transition: 0.25s ease;
}
.nv:hover {
color: #ffe07a;
padding-left: 8px;
border-left: solid 5px #ffe07a;
}
.nv_selected {
color: #67d6ed;
padding-left: 8px;
border-left: solid 5px #67d6ed;
}
.content {
background: rgb(36, 37, 38, 0.5);
color: #f0f4f5;
padding: 20px;
width: 100%;
border: solid 1px rgba(255, 255, 255, 0.055);
box-shadow: 0 1px 5px 0 rgb(0 0 0 / 20%);
}
main {
height: 100%;
}
[type='radio']:checked~label {
background: rgb(36, 37, 38);
color: #67d6ed;
border-bottom: 1px solid transparent;
z-index: 2;
}
[type='radio']:checked~label~.content {
z-index: 1;
}
.flex {
display: flex;
}
.row {
flex-direction: row;
}
.col {
flex-direction: column;
}
.just-around {
justify-content: space-between;
}
.hidden {
display: none;
}
.alert {
width: auto;
height: 40px;
display: flex;
justify-content: left;
align-items: center;
border-radius: 5px;
padding-left: 10px;
padding-right: 40px;
font-size: 15px;
color: #000;
margin-bottom: 10px;
margin-top: 10px;
box-shadow: rgba(0, 0, 0, 0.06) 0px 0px 10px;
border-left: 6px solid #ff0000;
background: #f0f4f5;
}
#file-response {
height: 400px;
}
span.key {
color: #fff;
}

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -97,6 +97,8 @@ dependencies = [
"tauri",
"tauri-build",
"tiny_http",
"window-shadows",
"window-vibrancy",
]
[[package]]
@@ -3948,6 +3950,30 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "window-shadows"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "796156ad1a67853e927727809bb6138ddc1f19ebced0dc976c0d109d5e2576f7"
dependencies = [
"cocoa",
"objc",
"raw-window-handle",
"windows-sys",
]
[[package]]
name = "window-vibrancy"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b66a7f578d164c3f53425ecb73241246ed56a7c3383a15e741684c949a88c86"
dependencies = [
"cocoa",
"objc",
"raw-window-handle",
"windows-sys",
]
[[package]]
name = "windows"
version = "0.24.0"

View File

@@ -31,6 +31,10 @@ features = [
"updater"
]
[target."cfg(target_os = \"windows\")".dependencies]
window-vibrancy = "0.1"
window-shadows= "0.1"
[features]
default = [ "custom-protocol" ]
custom-protocol = [ "tauri/custom-protocol" ]

View File

@@ -8,7 +8,6 @@
)]
mod cmd;
mod menu;
use std::sync::atomic::{AtomicBool, Ordering};
@@ -23,11 +22,6 @@ struct Reply {
data: String,
}
#[tauri::command]
async fn menu_toggle(window: tauri::Window) {
window.menu_handle().toggle().unwrap();
}
fn main() {
let tray_menu1 = SystemTrayMenu::new()
.add_item(CustomMenuItem::new("toggle", "Toggle"))
@@ -44,10 +38,30 @@ fn main() {
let is_menu1 = AtomicBool::new(true);
#[allow(unused_mut)]
let mut app = tauri::Builder::default()
let mut builder = tauri::Builder::default()
.setup(|app| {
#[allow(unused_mut)]
let mut window_builder = WindowBuilder::new(app, "main", WindowUrl::default())
.title("Tauri API Validation")
.inner_size(1000., 800.)
.min_inner_size(600., 400.);
#[cfg(target_os = "windows")]
{
window_builder = window_builder.transparent(true);
window_builder = window_builder.decorations(false);
}
let window = window_builder.build().unwrap();
#[cfg(target_os = "windows")]
{
let _ = window_shadows::set_shadow(&window, true);
let _ = window_vibrancy::apply_blur(&window, Some((0, 0, 0, 0)));
}
#[cfg(debug_assertions)]
app.get_window("main").unwrap().open_devtools();
window.open_devtools();
std::thread::spawn(|| {
let server = match tiny_http::Server::http("localhost:3003") {
@@ -88,10 +102,6 @@ fn main() {
.expect("failed to emit");
});
})
.menu(menu::get_menu())
.on_menu_event(|event| {
println!("{:?}", event.menu_item_id());
})
.system_tray(SystemTray::new().with_menu(tray_menu1.clone()))
.on_system_tray_event(move |app, event| match event {
SystemTrayEvent::LeftClick {
@@ -165,11 +175,18 @@ fn main() {
}
}
_ => {}
})
});
#[cfg(target_os = "macos")]
{
builder = builder.menu(tauri::Menu::os_default("Tauri API Validation"));
}
#[allow(unused_mut)]
let mut app = builder
.invoke_handler(tauri::generate_handler![
cmd::log_operation,
cmd::perform_request,
menu_toggle,
])
.build(tauri::generate_context!())
.expect("error while building tauri application");

View File

@@ -1,38 +0,0 @@
// Copyright 2019-2021 Tauri Programme within The Commons Conservancy
// SPDX-License-Identifier: Apache-2.0
// SPDX-License-Identifier: MIT
use tauri::{CustomMenuItem, Menu, MenuItem, Submenu};
pub fn get_menu() -> Menu {
#[allow(unused_mut)]
let mut disable_item =
CustomMenuItem::new("disable-menu", "Disable menu").accelerator("CmdOrControl+D");
#[allow(unused_mut)]
let mut test_item = CustomMenuItem::new("test", "Test").accelerator("CmdOrControl+T");
#[cfg(target_os = "macos")]
{
disable_item = disable_item.native_image(tauri::NativeImage::MenuOnState);
test_item = test_item.native_image(tauri::NativeImage::Add);
}
// create a submenu
let my_sub_menu = Menu::with_items([disable_item.into()]);
let my_app_menu = Menu::new()
.add_native_item(MenuItem::Copy)
.add_submenu(Submenu::new("Sub menu", my_sub_menu));
let test_menu = Menu::new()
.add_item(CustomMenuItem::new(
"selected/disabled",
"Selected and disabled",
))
.add_native_item(MenuItem::Separator)
.add_item(test_item);
// add all our childs to the menu (order is how they'll appear)
Menu::new()
.add_submenu(Submenu::new("My app", my_app_menu))
.add_submenu(Submenu::new("Other menu", test_menu))
}

View File

@@ -117,12 +117,7 @@
"scope": ["http://localhost:3003"]
}
},
"windows": [
{
"title": "Tauri API Validation",
"transparent": true
}
],
"windows": [],
"security": {
"csp": {
"default-src": "'self' customprotocol: asset:",

View File

@@ -1,178 +1,380 @@
<script>
import { onMount } from "svelte";
import { writable } from "svelte/store";
import hotkeys from "hotkeys-js";
import { open } from "@tauri-apps/api/shell";
import { invoke } from "@tauri-apps/api/tauri";
import { appWindow } from "@tauri-apps/api/window";
import { writable } from 'svelte/store'
import { open } from '@tauri-apps/api/shell'
import { appWindow, getCurrent } from '@tauri-apps/api/window'
import * as os from '@tauri-apps/api/os'
import Welcome from "./components/Welcome.svelte";
import Cli from "./components/Cli.svelte";
import Communication from "./components/Communication.svelte";
import Dialog from "./components/Dialog.svelte";
import FileSystem from "./components/FileSystem.svelte";
import Http from "./components/Http.svelte";
import Notifications from "./components/Notifications.svelte";
import Window from "./components/Window.svelte";
import Shortcuts from "./components/Shortcuts.svelte";
import Shell from "./components/Shell.svelte";
import Updater from "./components/Updater.svelte";
import Clipboard from "./components/Clipboard.svelte";
import WebRTC from './components/WebRTC.svelte'
import HttpForm from "./components/HttpForm.svelte";
import Welcome from './views/Welcome.svelte'
import Cli from './views/Cli.svelte'
import Communication from './views/Communication.svelte'
import Dialog from './views/Dialog.svelte'
import FileSystem from './views/FileSystem.svelte'
import Http from './views/Http.svelte'
import Notifications from './views/Notifications.svelte'
import Window from './views/Window.svelte'
import Shortcuts from './views/Shortcuts.svelte'
import Shell from './views/Shell.svelte'
import Updater from './views/Updater.svelte'
import Clipboard from './views/Clipboard.svelte'
import WebRTC from './views/WebRTC.svelte'
const MENU_TOGGLE_HOTKEY = 'ctrl+b';
onMount(() => {
hotkeys(MENU_TOGGLE_HOTKEY, () => {
invoke('menu_toggle');
});
});
import { onMount } from 'svelte'
import { listen } from '@tauri-apps/api/event'
import { ask } from '@tauri-apps/api/dialog'
appWindow.listen('tauri://file-drop', function (event) {
onMessage(`File drop: ${event.payload}`);
});
onMessage(`File drop: ${event.payload}`)
})
const views = [
{
label: "Welcome",
label: 'Welcome',
component: Welcome,
icon: 'i-ph-hand-waving'
},
{
label: "Messages",
label: 'Communication',
component: Communication,
icon: 'i-codicon-radio-tower'
},
{
label: "CLI",
label: 'CLI',
component: Cli,
icon: 'i-codicon-terminal'
},
{
label: "Dialog",
label: 'Dialog',
component: Dialog,
icon: 'i-codicon-multiple-windows'
},
{
label: "File system",
label: 'File system',
component: FileSystem,
icon: 'i-codicon-files'
},
{
label: "HTTP",
label: 'HTTP',
component: Http,
icon: 'i-ph-globe-hemisphere-west'
},
{
label: "HTTP Form",
component: HttpForm,
},
{
label: "Notifications",
label: 'Notifications',
component: Notifications,
icon: 'i-codicon-bell-dot'
},
{
label: "Window",
label: 'Window',
component: Window,
icon: 'i-codicon-window'
},
{
label: "Shortcuts",
label: 'Shortcuts',
component: Shortcuts,
icon: 'i-codicon-record-keys'
},
{
label: "Shell",
label: 'Shell',
component: Shell,
icon: 'i-codicon-terminal-bash'
},
{
label: "Updater",
label: 'Updater',
component: Updater,
icon: 'i-codicon-cloud-download'
},
{
label: "Clipboard",
label: 'Clipboard',
component: Clipboard,
icon: 'i-codicon-clippy'
},
{
label: "WebRTC",
label: 'WebRTC',
component: WebRTC,
},
];
let selected = views[0];
let responses = writable([]);
icon: 'i-ph-broadcast'
}
]
let selected = views[0]
function select(view) {
selected = view;
selected = view
}
// Window controls
let isWindowMaximized
onMount(async () => {
const window = getCurrent()
isWindowMaximized = await window.isMaximized()
listen('tauri://resize', async () => {
isWindowMaximized = await window.isMaximized()
})
})
function minimize() {
getCurrent().minimize()
}
async function toggleMaximize() {
const window = getCurrent()
;(await window.isMaximized()) ? window.unmaximize() : window.maximize()
}
let confirmed_close = false
async function close() {
if (!confirmed_close) {
confirmed_close = await ask(
'Are you sure that you want to close this window?',
{
title: 'Tauri API'
}
)
if (confirmed_close) {
getCurrent().close()
}
}
}
// dark/light
let isDark
onMount(() => {
isDark = localStorage.getItem('theme') == 'dark'
applyTheme(isDark)
})
function applyTheme(isDark) {
const html = document.querySelector('html')
isDark ? html.classList.add('dark') : html.classList.remove('dark')
localStorage.setItem('theme', isDark ? 'dark' : '')
}
function toggleDark() {
isDark = !isDark
applyTheme(isDark)
}
// Console
let messages = writable([])
function onMessage(value) {
responses.update(r => [{ text: `[${new Date().toLocaleTimeString()}]` + ': ' + (typeof value === "string" ? value : JSON.stringify(value)) }, ...r])
messages.update((r) => [
{
html:
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
(typeof value === 'string' ? value : JSON.stringify(value, null, 1)) +
'</pre>'
},
...r
])
}
// this function is renders HTML without sanitizing it so it's insecure
// we only use it with our own input data
function insecureRenderHtml(html) {
responses.update(r => [{ html }, ...r])
messages.update((r) => [
{
html:
`<pre><strong class="text-accent dark:text-darkAccent">[${new Date().toLocaleTimeString()}]:</strong> ` +
html +
'</pre>'
},
...r
])
}
function clear() {
responses.update(() => []);
messages.update(() => [])
}
function onLogoClick() {
open("https://tauri.app/");
let consoleEl, consoleH, cStartY
let minConsoleHeight = 50
function startResizingConsole(e) {
cStartY = e.clientY
const styles = window.getComputedStyle(consoleEl)
consoleH = parseInt(styles.height, 10)
const moveHandler = (e) => {
const dy = e.clientY - cStartY
const newH = consoleH - dy
consoleEl.style.height = `${
newH < minConsoleHeight ? minConsoleHeight : newH
}px`
}
const upHandler = () => {
document.removeEventListener('mouseup', upHandler)
document.removeEventListener('mousemove', moveHandler)
}
document.addEventListener('mouseup', upHandler)
document.addEventListener('mousemove', moveHandler)
}
let isWindows
onMount(async () => {
isWindows = (await os.platform()) === 'win32'
})
</script>
<main>
<div class="flex row noselect just-around container" data-tauri-drag-region>
<img class="logo" src="tauri logo.png" height="60" on:click={onLogoClick} alt="logo" />
<div>
<a class="dark-link" target="_blank" href="https://tauri.app/en/docs/get-started/intro">
Documentation
</a>
<a class="dark-link" target="_blank" href="https://github.com/tauri-apps/tauri">
Github
</a>
<a class="dark-link" target="_blank" href="https://github.com/tauri-apps/tauri/tree/dev/tauri/examples/api">
Source
</a>
</div>
{#if isWindows}
<div
class="w-screen select-none h-8 pl-2 flex justify-between items-center absolute text-primaryText dark:text-darkPrimaryText"
data-tauri-drag-region
>
<span class="text-darkPrimaryText">Tauri API Validation</span>
<span
class="
h-100%
children:h-100% children:w-12 children:inline-flex
children:items-center children:justify-center"
>
<span
title={isDark ? 'Switch to Light mode' : 'Switch to Dark mode'}
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={toggleDark}
>
{#if isDark}
<div class="i-ph-sun" />
{:else}
<div class="i-ph-moon" />
{/if}
</span>
<span
title="Minimize"
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={minimize}
>
<div class="i-codicon-chrome-minimize" />
</span>
<span
title={isWindowMaximized ? 'Restore' : 'Maximize'}
class="hover:bg-hoverOverlay active:bg-hoverOverlayDarker dark:hover:bg-darkHoverOverlay dark:active:bg-darkHoverOverlayDarker"
on:click={toggleMaximize}
>
{#if isWindowMaximized}
<div class="i-codicon-chrome-restore" />
{:else}
<div class="i-codicon-chrome-maximize" />
{/if}
</span>
<span
title="Close"
class="hover:bg-red-700 dark:hover:bg-red-700 hover:text-darkPrimaryText active:bg-red-700/90 dark:active:bg-red-700/90 active:text-darkPrimaryText "
on:click={close}
>
<div class="i-codicon-chrome-close" />
</span>
</span>
</div>
<div class="flex row">
<div class="view-container">
{/if}
<div
class="flex h-screen w-screen overflow-hidden children-pt8 children-pb-2 text-primaryText dark:text-darkPrimaryText"
>
<aside
class="w-75 {isWindows
? 'bg-darkPrimaryLighter/60'
: 'bg-darkPrimaryLighter'} transition-colors-250 overflow-hidden grid select-none px-2"
>
<img
on:click={() => open('https://tauri.app/')}
class="self-center p-7 cursor-pointer"
src="tauri_logo.png"
alt="Tauri logo"
/>
{#if !isWindows}
<a href="##" class="nv justify-between h-8" on:click={toggleDark}>
{#if isDark}
Switch to Light mode
<div class="i-ph-sun" />
{:else}
Switch to Dark mode
<div class="i-ph-moon" />
{/if}
</a>
<br />
<div class="bg-white/5 h-2px" />
<br />
{/if}
<a
class="nv justify-between h-8"
target="_blank"
href="https://tauri.app/v1/guides/"
>
Documentation
<span class="i-codicon-link-external" />
</a>
<a
class="nv justify-between h-8"
target="_blank"
href="https://github.com/tauri-apps/tauri"
>
Github
<span class="i-codicon-link-external" />
</a>
<a
class="nv justify-between h-8"
target="_blank"
href="https://github.com/tauri-apps/tauri/tree/dev/examples/api"
>
Source
<span class="i-codicon-link-external" />
</a>
<br />
<div class="bg-white/5 h-2px" />
<br />
<div
class="flex flex-col overflow-y-auto children-h-10 children-flex-none gap-1"
>
{#each views as view}
<p class="nv noselect {selected === view ? 'nv_selected' : ''}" on:click={()=> select(view)}
<a
href="##"
class="nv {selected === view ? 'nv_selected' : ''}"
on:click={() => select(view)}
>
<div class="{view.icon} mr-2" />
<p>{view.label}</p></a
>
{view.label}
</p>
{/each}
</div>
<div class="content">
<svelte:component this={selected.component} {onMessage} {insecureRenderHtml} />
</aside>
<main
class="flex-1 bg-primary dark:bg-darkPrimary transition-colors-250 grid grid-rows-[2fr_auto]"
>
<div class="px-5 overflow-hidden grid grid-rows-[auto_1fr]">
<h1>{selected.label}</h1>
<div class="overflow-y-auto">
<div class="mr-2">
<svelte:component
this={selected.component}
{onMessage}
{insecureRenderHtml}
/>
</div>
</div>
</div>
</div>
<div id="response">
<p class="flex row just-around">
<strong>Tauri Console</strong>
<span class="nv" on:click={clear}>clear</span>
</p>
{#each $responses as r}
{#if r.text}
<p>{r.text}</p>
{:else}
{@html r.html}
{/if}
{/each}
</div>
</main>
<style>
.container {
margin: 1em;
}
.view-container {
width:15em;
margin-left:0.5em;
}
#response {
white-space: pre-line;
}
</style>
<div
bind:this={consoleEl}
id="console"
class="select-none h-15rem grid grid-rows-[2px_2rem_1fr] gap-1 overflow-hidden"
>
<div
on:mousedown={startResizingConsole}
class="bg-black/20 h-2px cursor-ns-resize"
/>
<div class="flex justify-between items-center px-2">
<p class="font-semibold">Console</p>
<div
class="cursor-pointer h-85% rd-1 p-1 flex justify-center items-center
hover:bg-hoverOverlay dark:hover:bg-darkHoverOverlay
active:bg-hoverOverlay/25 dark:active:bg-darkHoverOverlay/25
"
on:click={clear}
>
<div class="i-codicon-clear-all" />
</div>
</div>
<div class="px-2 overflow-y-auto all:font-mono code-block all:text-xs">
{#each $messages as r}
{@html r.html}
{/each}
</div>
</div>
</main>
</div>

30
examples/api/src/app.css Normal file
View File

@@ -0,0 +1,30 @@
*:not(h1, h2, h3, h4, h5, h6) {
margin: 0;
padding: 0;
}
* {
box-sizing: border-box;
font-family: 'Rubik', sans-serif;
}
::-webkit-scrollbar {
width: 0.25rem;
height: 3px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
border-radius: 0.25rem;
}
code {
padding: 0.05rem 0.25rem;
}
code.code-block {
padding: 0.5rem;
}

View File

@@ -1,24 +0,0 @@
<script>
import { getMatches } from "@tauri-apps/api/cli";
export let onMessage;
function cliMatches() {
getMatches().then(onMessage).catch(onMessage);
}
</script>
<div>
This binary can be run on the terminal and takes the following arguments:
<ul>
<li>--config PATH</li>
<li>--theme light|dark|system</li>
<li>--verbose</li>
</ul>
Additionally, it has a <i>update --background</i> subcommand.
Note that the arguments are only parsed, not implemented.
<br>
<button class="button" id="cli-matches" on:click={cliMatches}>
Get matches
</button>
</div>

View File

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

View File

@@ -1,50 +0,0 @@
<script>
import { listen, emit } from "@tauri-apps/api/event";
import { invoke } from "@tauri-apps/api/tauri";
import { onMount, onDestroy } from "svelte";
export let onMessage;
let unlisten;
onMount(async () => {
unlisten = await listen("rust-event", onMessage)
})
onDestroy(() => {
if (unlisten) {
unlisten()
}
})
function log() {
invoke("log_operation", {
event: "tauri-click",
payload: "this payload is optional because we used Option in Rust",
});
}
function performRequest() {
invoke("perform_request", {
endpoint: "dummy endpoint arg",
body: {
id: 5,
name: "test",
},
})
.then(onMessage)
.catch(onMessage);
}
function emitEvent() {
emit("js-event", "this is the payload string");
}
</script>
<div>
<button class="button" id="log" on:click={log}>Call Log API</button>
<button class="button" id="request" on:click={performRequest}>
Call Request (async) API
</button>
<button class="button" id="event" on:click={emitEvent}>
Send event to Rust
</button>
</div>

View File

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

View File

@@ -1,57 +0,0 @@
<script>
import { getClient, Body } from "@tauri-apps/api/http";
let httpMethod = "GET";
let httpBody = "";
export let onMessage;
async function makeHttpRequest() {
const client = await getClient().catch(e => {
onMessage(e)
throw e
});
let method = httpMethod || "GET";
const options = {
url: "http://localhost:3003",
method: method || "GET",
};
if (
(httpBody.startsWith("{") && httpBody.endsWith("}")) ||
(httpBody.startsWith("[") && httpBody.endsWith("]"))
) {
options.body = Body.json(JSON.parse(httpBody));
} else if (httpBody !== "") {
options.body = Body.text(httpBody);
}
client.request(options).then(onMessage).catch(onMessage);
}
</script>
<form on:submit|preventDefault={makeHttpRequest}>
<select class="button" id="request-method" bind:value={httpMethod}>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="PATCH">PATCH</option>
<option value="DELETE">DELETE</option>
</select>
<br />
<textarea
id="request-body"
placeholder="Request body"
rows="5"
bind:value={httpBody}
/>
<button class="button" id="make-request"> Make request </button>
</form>
<style>
#request-body {
width:100%;
margin-right:10px;
font-size:12px;
}
</style>

View File

@@ -1,39 +0,0 @@
<script>
import { getClient, Body, ResponseType } from "@tauri-apps/api/http"
import { JsonView } from '@zerodevx/svelte-json-view'
let foo = 'baz'
let bar = 'qux'
let result = null
let multipart = true
async function doPost () {
const client = await getClient().catch(e => {
onMessage(e)
throw e
})
result = await client.request({
url: 'http://localhost:3003',
method: 'POST',
body: Body.form({
foo,
bar
}),
headers: multipart ? { 'Content-Type': 'multipart/form-data' } : undefined,
responseType: ResponseType.Text
})
}
</script>
<div>
<input bind:value={foo} />
<input bind:value={bar} />
<label>
<input type="checkbox" bind:checked={multipart} />
Multipart
</label>
<button type="button" on:click={doPost}>
Post it.
</button>
<JsonView json={result} />
</div>

View File

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

View File

@@ -1,74 +0,0 @@
<script>
import { Command } from "@tauri-apps/api/shell"
const windows = navigator.userAgent.includes('Windows')
let cmd = windows ? 'cmd' : 'sh'
let args = windows ? ['/C'] : ['-c']
export let onMessage;
let script = 'echo "hello world"'
let cwd = null
let env = 'SOMETHING=value ANOTHER=2'
let stdin = ''
let child
function _getEnv() {
return env.split(' ').reduce((env, clause) => {
let [key, value] = clause.split('=')
return {
...env,
[key]: value
}
}, {})
}
function spawn() {
child = null
const command = new Command(cmd, [...args, script], { cwd: cwd || null, env: _getEnv() })
command.on('close', data => {
onMessage(`command finished with code ${data.code} and signal ${data.signal}`)
child = null
})
command.on('error', error => onMessage(`command error: "${error}"`))
command.stdout.on('data', line => onMessage(`command stdout: "${line}"`))
command.stderr.on('data', line => onMessage(`command stderr: "${line}"`))
command.spawn()
.then(c => {
child = c
})
.catch(onMessage)
}
function kill() {
child.kill().then(() => onMessage('killed child process')).catch(onMessage)
}
function writeToStdin() {
child.write(stdin).catch(onMessage)
}
</script>
<div>
<div>
<input bind:value={script}>
<button class="button" on:click={spawn}>Run</button>
<button class="button" on:click={kill}>Kill</button>
{#if child}
<input placeholder="write to stdin" bind:value={stdin}>
<button class="button" on:click={writeToStdin}>Write</button>
{/if}
</div>
<div>
<input bind:value={cwd} placeholder="Working directory">
<input class="env-vars" bind:value={env} placeholder="Environment variables">
</div>
</div>
<style>
.env-vars {
width: 300px;
}
</style>

View File

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

View File

@@ -1,59 +0,0 @@
<script>
import { onMount, onDestroy } from "svelte";
// This example show how updater events work when dialog is disabled.
// This allow you to use custom dialog for the updater.
// This is your responsability to restart the application after you receive the STATUS: DONE.
import { checkUpdate, installUpdate } from "@tauri-apps/api/updater";
import { listen } from "@tauri-apps/api/event";
import { relaunch } from "@tauri-apps/api/process";
export let onMessage;
let unlisten;
onMount(async () => {
unlisten = await listen("tauri://update-status", onMessage)
})
onDestroy(() => {
if (unlisten) {
unlisten()
}
})
async function check() {
try {
document.getElementById("check_update").classList.add("hidden");
const {shouldUpdate, manifest} = await checkUpdate();
onMessage(`Should update: ${shouldUpdate}`);
onMessage(manifest);
if (shouldUpdate) {
document.getElementById("start_update").classList.remove("hidden");
}
} catch(e) {
onMessage(e);
}
}
async function install() {
try {
document.getElementById("start_update").classList.add("hidden");
await installUpdate();
onMessage("Installation complete, restart required.");
await relaunch();
} catch(e) {
onMessage(e);
}
}
</script>
<div>
<button class="button" id="check_update" on:click={check}>Check update</button>
<button class="button hidden" id="start_update" on:click={install}>Install update</button>
</div>

View File

@@ -1,53 +0,0 @@
<script>
import { onMount,onDestroy } from "svelte";
export let onMessage;
const constraints = window.constraints = {
audio: true,
video: true
};
function handleSuccess(stream) {
const video = document.querySelector('video');
const videoTracks = stream.getVideoTracks();
onMessage('Got stream with constraints:', constraints);
onMessage(`Using video device: ${videoTracks[0].label}`);
window.stream = stream; // make variable available to browser console
video.srcObject = stream;
}
function handleError(error) {
if (error.name === 'ConstraintNotSatisfiedError') {
const v = constraints.video;
onMessage(`The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`);
} else if (error.name === 'PermissionDeniedError') {
onMessage('Permissions have not been granted to use your camera and ' +
'microphone, you need to allow the page access to your devices in ' +
'order for the demo to work.');
}
onMessage(`getUserMedia error: ${error.name}`, error);
}
onMount(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
handleSuccess(stream);
} catch (e) {
handleError(e);
}
})
onDestroy(() => {
window.stream.getTracks().forEach(function(track) {
track.stop();
});
})
</script>
<div>
<div class="alert"><p>Not available for Linux</p></div>
<video id="localVideo" autoplay playsinline>
<track kind="captions" />
</video>
</div>

View File

@@ -1,33 +0,0 @@
<script>
import { getName, getVersion, getTauriVersion } from "@tauri-apps/api/app";
import { relaunch, exit } from "@tauri-apps/api/process";
let version = 0.0;
let tauriVersion = 0.0;
let appName = 'Unknown';
getName().then(n => { appName = n });
getVersion().then(v => { version = v });
getTauriVersion().then(v => { tauriVersion = v });
async function closeApp() {
await exit();
}
async function relaunchApp() {
await relaunch();
}
</script>
<h1>Welcome</h1>
<p>
Tauri's API capabilities using the ` @tauri-apps/api ` package. It's used as
the main validation app, serving as the testbed of our development process. In
the future, this app will be used on Tauri's integration tests.
</p>
<p>Current App version: {version}</p>
<p>Current Tauri version: {tauriVersion}</p>
<p>Current App name: {appName}</p>
<button class="button" on:click={closeApp}>Close application</button>
<button class="button" on:click={relaunchApp}>Relaunch application</button>

View File

@@ -1,410 +0,0 @@
<script>
import {
appWindow,
WebviewWindow,
LogicalSize,
UserAttentionType,
PhysicalSize,
PhysicalPosition
} from '@tauri-apps/api/window'
import { open as openDialog } from '@tauri-apps/api/dialog'
import { open } from '@tauri-apps/api/shell'
let selectedWindow = appWindow.label
const windowMap = {
[selectedWindow]: appWindow
}
const cursorIconOptions = [
'default',
'crosshair',
'hand',
'arrow',
'move',
'text',
'wait',
'help',
'progress',
// something cannot be done
'notAllowed',
'contextMenu',
'cell',
'verticalText',
'alias',
'copy',
'noDrop',
// something can be grabbed
'grab',
/// something is grabbed
'grabbing',
'allScroll',
'zoomIn',
'zoomOut',
// edge is to be moved
'eResize',
'nResize',
'neResize',
'nwResize',
'sResize',
'seResize',
'swResize',
'wResize',
'ewResize',
'nsResize',
'neswResize',
'nwseResize',
'colResize',
'rowResize'
]
export let onMessage
let urlValue = 'https://tauri.app'
let resizable = true
let maximized = false
let transparent = false
let decorations = true
let alwaysOnTop = false
let fullscreen = false
let width = 900
let height = 700
let minWidth = 600
let minHeight = 600
let maxWidth = null
let maxHeight = null
let x = 100
let y = 100
let scaleFactor = 1
let innerPosition = new PhysicalPosition(x, y)
let outerPosition = new PhysicalPosition(x, y)
let innerSize = new PhysicalSize(width, height)
let outerSize = new PhysicalSize(width, height)
let resizeEventUnlisten
let moveEventUnlisten
let cursorGrab = false
let cursorVisible = true
let cursorX = 600
let cursorY = 800
let cursorIcon = 'default'
let windowTitle = 'Awesome Tauri Example!'
function openUrl() {
open(urlValue)
}
function setTitle_() {
windowMap[selectedWindow].setTitle(windowTitle)
}
function hide_() {
windowMap[selectedWindow].hide()
setTimeout(windowMap[selectedWindow].show, 2000)
}
function minimize_() {
windowMap[selectedWindow].minimize()
setTimeout(windowMap[selectedWindow].unminimize, 2000)
}
function getIcon() {
openDialog({
multiple: false
}).then((path) => {
if (typeof path === 'string') {
windowMap[selectedWindow].setIcon(path)
}
})
}
function createWindow() {
const label = Math.random().toString().replace('.', '')
const webview = new WebviewWindow(label)
windowMap[label] = webview
webview.once('tauri://error', function () {
onMessage('Error creating new webview')
})
}
function handleWindowResize() {
windowMap[selectedWindow].innerSize().then((response) => {
innerSize = response
width = innerSize.width
height = innerSize.height
})
windowMap[selectedWindow].outerSize().then((response) => {
outerSize = response
})
}
function handleWindowMove() {
windowMap[selectedWindow].innerPosition().then((response) => {
innerPosition = response
})
windowMap[selectedWindow].outerPosition().then((response) => {
outerPosition = response
x = outerPosition.x
y = outerPosition.y
})
}
async function addWindowEventListeners(window) {
if (resizeEventUnlisten) {
resizeEventUnlisten()
}
if (moveEventUnlisten) {
moveEventUnlisten()
}
moveEventUnlisten = await window.listen('tauri://move', handleWindowMove)
resizeEventUnlisten = await window.listen(
'tauri://resize',
handleWindowResize
)
}
async function requestUserAttention_() {
await windowMap[selectedWindow].minimize()
await windowMap[selectedWindow].requestUserAttention(
UserAttentionType.Critical
)
await new Promise((resolve) => setTimeout(resolve, 3000))
await windowMap[selectedWindow].requestUserAttention(null)
}
$: windowMap[selectedWindow].setResizable(resizable)
$: maximized
? windowMap[selectedWindow].maximize()
: windowMap[selectedWindow].unmaximize()
$: windowMap[selectedWindow].setDecorations(decorations)
$: windowMap[selectedWindow].setAlwaysOnTop(alwaysOnTop)
$: windowMap[selectedWindow].setFullscreen(fullscreen)
$: windowMap[selectedWindow].setSize(new PhysicalSize(width, height))
$: minWidth && minHeight
? windowMap[selectedWindow].setMinSize(new LogicalSize(minWidth, minHeight))
: windowMap[selectedWindow].setMinSize(null)
$: maxWidth && maxHeight
? windowMap[selectedWindow].setMaxSize(new LogicalSize(maxWidth, maxHeight))
: windowMap[selectedWindow].setMaxSize(null)
$: windowMap[selectedWindow].setPosition(new PhysicalPosition(x, y))
$: windowMap[selectedWindow]
.scaleFactor()
.then((factor) => (scaleFactor = factor))
$: addWindowEventListeners(windowMap[selectedWindow])
$: windowMap[selectedWindow].setCursorGrab(cursorGrab)
$: windowMap[selectedWindow].setCursorVisible(cursorVisible)
$: windowMap[selectedWindow].setCursorIcon(cursorIcon)
$: windowMap[selectedWindow].setCursorPosition(
new PhysicalPosition(cursorX, cursorY)
)
</script>
<div class="flex col">
<select class="button" bind:value={selectedWindow}>
{#each Object.keys(windowMap) as label}
<option value={label}>{label}</option>
{/each}
</select>
<div>
<label>
<input type="checkbox" bind:checked={resizable} />
Resizable
</label>
<label>
<input type="checkbox" bind:checked={maximized} />
Maximize
</label>
<button
title="Unminimizes after 2 seconds"
on:click={() => windowMap[selectedWindow].center()}
>
Center
</button>
<button title="Unminimizes after 2 seconds" on:click={minimize_}>
Minimize
</button>
<button title="Visible again after 2 seconds" on:click={hide_}>
Hide
</button>
<label>
<input type="checkbox" bind:checked={transparent} />
Transparent
</label>
<label>
<input type="checkbox" bind:checked={decorations} />
Has decorations
</label>
<label>
<input type="checkbox" bind:checked={alwaysOnTop} />
Always on top
</label>
<label>
<input type="checkbox" bind:checked={fullscreen} />
Fullscreen
</label>
<button on:click={getIcon}> Change icon </button>
</div>
<div>
<div class="window-controls flex flex-row">
<div class="flex col grow">
<div>
X
<input type="number" bind:value={x} min="0" />
</div>
<div>
Y
<input type="number" bind:value={y} min="0" />
</div>
</div>
<div class="flex col grow">
<div>
Width
<input type="number" bind:value={width} min="400" />
</div>
<div>
Height
<input type="number" bind:value={height} min="400" />
</div>
</div>
<div class="flex col grow">
<div>
Min width
<input type="number" bind:value={minWidth} />
</div>
<div>
Min height
<input type="number" bind:value={minHeight} />
</div>
</div>
<div class="flex col grow">
<div>
Max width
<input type="number" bind:value={maxWidth} min="400" />
</div>
<div>
Max height
<input type="number" bind:value={maxHeight} min="400" />
</div>
</div>
</div>
</div>
</div>
<div>
<div class="flex">
<div class="grow window-property">
<div>Inner Size</div>
<span>Width: {innerSize.width}</span>
<span>Height: {innerSize.height}</span>
</div>
<div class="grow window-property">
<div>Outer Size</div>
<span>Width: {outerSize.width}</span>
<span>Height: {outerSize.height}</span>
</div>
</div>
<div class="flex">
<div class="grow window-property">
<div>Inner Logical Size</div>
<span>Width: {innerSize.toLogical(scaleFactor).width}</span>
<span>Height: {innerSize.toLogical(scaleFactor).height}</span>
</div>
<div class="grow window-property">
<div>Outer Logical Size</div>
<span>Width: {outerSize.toLogical(scaleFactor).width}</span>
<span>Height: {outerSize.toLogical(scaleFactor).height}</span>
</div>
</div>
<div class="flex">
<div class="grow window-property">
<div>Inner Position</div>
<span>x: {innerPosition.x}</span>
<span>y: {innerPosition.y}</span>
</div>
<div class="grow window-property">
<div>Outer Position</div>
<span>x: {outerPosition.x}</span>
<span>y: {outerPosition.y}</span>
</div>
</div>
<div class="flex">
<div class="grow window-property">
<div>Inner Logical Position</div>
<span>x: {innerPosition.toLogical(scaleFactor).x}</span>
<span>y: {innerPosition.toLogical(scaleFactor).y}</span>
</div>
<div class="grow window-property">
<div>Outer Logical Position</div>
<span>x: {outerPosition.toLogical(scaleFactor).x}</span>
<span>y: {outerPosition.toLogical(scaleFactor).y}</span>
</div>
</div>
</div>
<div>
<h4>Cursor</h4>
<label>
<input type="checkbox" bind:checked={cursorGrab} />
Grab
</label>
<label>
<input type="checkbox" bind:checked={cursorVisible} />
Visible
</label>
<select class="button" bind:value={cursorIcon}>
{#each cursorIconOptions as kind}
<option value={kind}>{kind}</option>
{/each}
</select>
<div class="flex col grow">
<div>
X position
<input type="number" bind:value={cursorX} />
</div>
<div>
Y position
<input type="number" bind:value={cursorY} />
</div>
</div>
</div>
<form on:submit|preventDefault={setTitle_}>
<input id="title" bind:value={windowTitle} />
<button class="button" type="submit">Set title</button>
</form>
<form on:submit|preventDefault={openUrl}>
<input id="url" bind:value={urlValue} />
<button class="button" id="open-url"> Open URL </button>
</form>
<button
class="button"
on:click={requestUserAttention_}
title="Minimizes the window, requests attention for 3s and then resets it"
>Request attention</button
>
<button class="button" on:click={createWindow}>New window</button>
<style>
form {
margin-top: 24px;
}
.flex-row {
flex-direction: row;
}
.grow {
flex-grow: 1;
}
.window-controls input {
width: 50px;
}
.window-property {
margin-top: 12px;
}
.window-property span {
font-size: 0.8rem;
}
</style>

View File

@@ -1,7 +1,9 @@
import 'uno.css'
import './app.css'
import App from './App.svelte'
const app = new App({
target: document.body
target: document.querySelector('#app')
})
export default app

View File

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

View File

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

View File

@@ -0,0 +1,50 @@
<script>
import { listen, emit } from '@tauri-apps/api/event'
import { invoke } from '@tauri-apps/api/tauri'
import { onMount, onDestroy } from 'svelte'
export let onMessage
let unlisten
onMount(async () => {
unlisten = await listen('rust-event', onMessage)
})
onDestroy(() => {
if (unlisten) {
unlisten()
}
})
function log() {
invoke('log_operation', {
event: 'tauri-click',
payload: 'this payload is optional because we used Option in Rust'
})
}
function performRequest() {
invoke('perform_request', {
endpoint: 'dummy endpoint arg',
body: {
id: 5,
name: 'test'
}
})
.then(onMessage)
.catch(onMessage)
}
function emitEvent() {
emit('js-event', 'this is the payload string')
}
</script>
<div>
<button class="btn" id="log" on:click={log}>Call Log API</button>
<button class="btn" id="request" on:click={performRequest}>
Call Request (async) API
</button>
<button class="btn" id="event" on:click={emitEvent}>
Send event to Rust
</button>
</div>

View File

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

View File

@@ -79,20 +79,28 @@
}
</script>
<form on:submit|preventDefault={read}>
<select class="button" id="dir">
<option value="">None</option>
{#each DirOptions as dir}
<option value={dir[1]}>{dir[0]}</option>
{/each}
</select>
<input
id="path-to-read"
placeholder="Type the path to read..."
bind:value={pathToRead}
/>
<button class="button" id="read">Read</button>
<button class="button" type="button" on:click={setSrc}>Use as img src</button>
<img alt="file" bind:this={img} />
<form class="flex flex-col" on:submit|preventDefault={read}>
<div class="flex gap-1">
<select class="input" id="dir">
<option value="">None</option>
{#each DirOptions as dir}
<option value={dir[1]}>{dir[0]}</option>
{/each}
</select>
<input
class="input grow"
id="path-to-read"
placeholder="Type the path to read..."
bind:value={pathToRead}
/>
</div>
<br />
<div>
<button class="btn" id="read">Read</button>
<button class="btn" type="button" on:click={setSrc}>Use as img src</button>
</div>
</form>
<br />
<img alt="" bind:this={img} />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
<script>
import { onMount, onDestroy } from 'svelte'
export let onMessage
const constraints = (window.constraints = {
audio: true,
video: true
})
function handleSuccess(stream) {
const video = document.querySelector('video')
const videoTracks = stream.getVideoTracks()
onMessage('Got stream with constraints:', constraints)
onMessage(`Using video device: ${videoTracks[0].label}`)
window.stream = stream // make variable available to browser console
video.srcObject = stream
}
function handleError(error) {
if (error.name === 'ConstraintNotSatisfiedError') {
const v = constraints.video
onMessage(
`The resolution ${v.width.exact}x${v.height.exact} px is not supported by your device.`
)
} else if (error.name === 'PermissionDeniedError') {
onMessage(
'Permissions have not been granted to use your camera and ' +
'microphone, you need to allow the page access to your devices in ' +
'order for the demo to work.'
)
}
onMessage(`getUserMedia error: ${error.name}`, error)
}
onMount(async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints)
handleSuccess(stream)
} catch (e) {
handleError(e)
}
})
onDestroy(() => {
window.stream.getTracks().forEach(function (track) {
track.stop()
})
})
</script>
<div class="flex flex-col gap-2">
<div class="note-red grow">Not available for Linux</div>
<video id="localVideo" autoplay playsinline>
<track kind="captions" />
</video>
</div>

View File

@@ -0,0 +1,47 @@
<script>
import { getName, getVersion, getTauriVersion } from '@tauri-apps/api/app'
import { relaunch, exit } from '@tauri-apps/api/process'
let version = '0.0.0'
let tauriVersion = '0.0.0'
let appName = 'Unknown'
getName().then((n) => {
appName = n
})
getVersion().then((v) => {
version = v
})
getTauriVersion().then((v) => {
tauriVersion = v
})
async function closeApp() {
await exit()
}
async function relaunchApp() {
await relaunch()
}
</script>
<p>
This is a demo of Tauri's API capabilities using the <code
>@tauri-apps/api</code
> package. It's used as the main validation app, serving as the test bed of our
development process. In the future, this app will be used on Tauri's integration
tests.
</p>
<br />
<br />
<pre>
App name: <code>{appName}</code>
App version: <code>{version}</code>
Tauri version: <code>{tauriVersion}</code>
</pre>
<br />
<div class="flex flex-wrap gap-1 shadow-">
<button class="btn" on:click={closeApp}>Close application</button>
<button class="btn" on:click={relaunchApp}>Relaunch application</button>
</div>

View File

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

View File

@@ -0,0 +1,98 @@
import {
defineConfig,
presetIcons,
presetUno,
extractorSvelte,
presetWebFonts,
} from 'unocss'
export default defineConfig({
theme: {
colors: {
primary: '#FFFFFF',
primaryLighter: '#e9ecef',
darkPrimary: '#1B1B1D',
darkPrimaryLighter: '#242526',
primaryText: '#1C1E21',
darkPrimaryText: '#E3E3E3',
secondaryText: '#858A91',
darkSecondaryText: '#C2C5CA',
accent: '#3578E5',
accentDark: '#306cce',
accentDarker: '#2d66c3',
accentDarkest: '#2554a0',
accentLight: '#538ce9',
accentLighter: '#72a1ed',
accentLightest: '#9abcf2',
accentText: '#FFFFFF',
darkAccent: '#67d6ed',
darkAccentDark: '#49cee9',
darkAccentDarker: '#39cae8',
darkAccentDarkest: '#19b5d5',
darkAccentLight: '#85def1',
darkAccentLighter: '#95e2f2',
darkAccentLightest: '#c2eff8',
darkAccentText: '#1C1E21',
code: '#d6d8da',
codeDark: '#282a2e',
hoverOverlay: 'rgba(0,0,0,.05)',
hoverOverlayDarker: 'rgba(0,0,0,.1)',
darkHoverOverlay: 'hsla(0,0%,100%,.05)',
darkHoverOverlayDarker: 'hsla(0,0%,100%,.1)'
}
},
preflights: [
{
getCSS: ({ theme }) => `
::-webkit-scrollbar-thumb {
background-color: ${theme.colors.accent};
}
.dark ::-webkit-scrollbar-thumb {
background-color: ${theme.colors.darkAccent};
}
code {
font-size: ${theme.fontSize.xs[0]};
font-family: ${theme.fontFamily.mono};
border-radius: ${theme.borderRadius['DEFAULT']};
background-color: ${theme.colors.code};
}
.code-block {
font-family: ${theme.fontFamily.mono};
font-size: ${theme.fontSize.sm[0]};
}
.dark code {
background-color: ${theme.colors.codeDark};
}
`
}
],
shortcuts: {
btn: `select-none outline-none shadow-md p-2 rd-1 text-primaryText border-none font-400 dark:font-600
bg-accent hover:bg-accentDarker active:bg-accentDarkest text-accentText
dark:bg-darkAccent dark:hover:bg-darkAccentDarker dark:active:bg-darkAccentDarkest dark:text-darkAccentText`,
nv: `decoration-none flex items-center relative p-2 rd-1 transition-all-125 ease
text-darkSecondaryText
hover:text-accent dark:hover:text-darkAccent
hover:bg-darkHoverOverlay hover:border-l-4`,
nv_selected: `nv bg-darkHoverOverlay text-accent dark:text-darkAccent border-l-4`,
note: `decoration-none flex-inline items-center relative p-2 rd-1
border-l-4 border-accent dark:border-darkAccent
bg-accent/10 dark:bg-darkAccent/10`,
'note-red':
'note bg-red-700/10 dark:bg-red-700/10 after:bg-red-700 dark:after:bg-red-700',
input:
'h-10 flex items-center outline-none border-none p-2 rd-1 shadow-md bg-primaryLighter dark:bg-darkPrimaryLighter text-primaryText dark:text-darkPrimaryText',
},
presets: [presetUno(), presetIcons(), presetWebFonts({
fonts: {
sans: 'Rubik',
mono: ['Fira Code', 'Fira Mono:400,700'],
}
})],
extractors: [extractorSvelte]
})

View File

@@ -1,9 +1,10 @@
import { defineConfig } from 'vite'
import Unocss from 'unocss/vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [svelte()],
plugins: [Unocss(), svelte()],
build: {
rollupOptions: {
output: {

File diff suppressed because it is too large Load Diff