Add system tray

This commit is contained in:
Gregory Schier
2023-02-20 00:11:15 -08:00
parent b95429dbeb
commit 45b7bc2c84
8 changed files with 154 additions and 104 deletions

37
src-tauri/Cargo.lock generated
View File

@@ -1627,12 +1627,46 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "libappindicator"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2d3cb96d092b4824cb306c9e544c856a4cb6210c1081945187f7f1924b47e8"
dependencies = [
"glib",
"gtk",
"gtk-sys",
"libappindicator-sys",
"log",
]
[[package]]
name = "libappindicator-sys"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b3b6681973cea8cc3bce7391e6d7d5502720b80a581c9a95c9cbaf592826aa"
dependencies = [
"gtk-sys",
"libloading",
"once_cell",
]
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libloading"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"winapi",
]
[[package]]
name = "line-wrap"
version = "0.1.1"
@@ -3407,6 +3441,7 @@ dependencies = [
"core-foundation",
"core-graphics",
"crossbeam-channel",
"dirs-next",
"dispatch",
"gdk",
"gdk-pixbuf",
@@ -3420,6 +3455,7 @@ dependencies = [
"instant",
"jni",
"lazy_static",
"libappindicator",
"libc",
"log",
"ndk",
@@ -3635,6 +3671,7 @@ dependencies = [
"serde_json",
"serde_with",
"thiserror",
"toml",
"url",
"walkdir",
"windows",

View File

@@ -15,15 +15,15 @@ tauri-build = { version = "1.2", features = [] }
[dependencies]
serde_json = { version = "1.0" }
serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.2", features = ["devtools", "shell-open", "window-start-dragging"] }
tauri = { version = "1.2", features = ["config-toml", "devtools", "shell-open", "system-tray", "window-start-dragging"] }
http = { version = "0.2.8" }
reqwest = { version = "0.11.14", features = ["json"] }
tokio = { version = "1.25.0", features = ["full"] }
futures = { version = "0.3.26" }
deno_core = { version = "0.171.0" }
deno_ast = { version = "0.24.0", features = ["transpiling"] }
objc = "0.2.7"
cocoa = "0.24.1"
objc = { version = "0.2.7" }
cocoa = { version = "0.24.1" }
[features]
# by default Tauri runs in production mode

View File

@@ -11,16 +11,38 @@ mod commands;
mod runtime;
mod window_ext;
use tauri::{Manager, WindowEvent};
use tauri::{
CustomMenuItem, Manager, SystemTray, SystemTrayEvent, SystemTrayMenu, SystemTrayMenuItem,
WindowEvent,
};
use window_ext::WindowExt;
fn main() {
// here `"quit".to_string()` defines the menu item id, and the second parameter is the menu item label.
let quit = CustomMenuItem::new("quit".to_string(), "Quit");
let tray_menu = SystemTrayMenu::new().add_item(quit);
let system_tray = SystemTray::new().with_menu(tray_menu);
tauri::Builder::default()
.system_tray(system_tray)
.setup(|app| {
let win = app.get_window("main").unwrap();
win.position_traffic_lights();
Ok(())
})
.on_system_tray_event(|app, event| match event {
SystemTrayEvent::MenuItemClick { id, .. } => match id.as_str() {
"quit" => {
std::process::exit(0);
}
"hide" => {
let window = app.get_window("main").unwrap();
window.hide().unwrap();
}
_ => {}
},
_ => {}
})
.on_window_event(|e| {
let apply_offset = || {
let win = e.window();

View File

@@ -1,82 +0,0 @@
{
"build": {
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build",
"devPath": "http://localhost:1420",
"distDir": "../dist",
"withGlobalTauri": false
},
"package": {
"productName": "Twosomnia",
"version": "0.0.1"
},
"tauri": {
"allowlist": {
"all": false,
"shell": {
"all": false,
"open": true
},
"window": {
"startDragging": true
},
"fs": {
"scope": [
"$RESOURCE/*"
]
}
},
"bundle": {
"active": true,
"category": "DeveloperTool",
"copyright": "",
"deb": {
"depends": []
},
"externalBin": [],
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"identifier": "co.schier.twosomnia",
"longDescription": "",
"macOS": {
"entitlements": null,
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [
"plugins/*"
],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": ""
}
},
"security": {
"csp": null
},
"updater": {
"active": false
},
"windows": [
{
"fullscreen": false,
"height": 800,
"resizable": true,
"title": "Twosomnia",
"width": 1400,
"titleBarStyle": "Overlay",
"hiddenTitle": true
}
]
}
}

73
src-tauri/tauri.toml Normal file
View File

@@ -0,0 +1,73 @@
[build]
beforeDevCommand = "npm run dev"
beforeBuildCommand = "npm run build"
devPath = "http://localhost:1420"
distDir = "../dist"
withGlobalTauri = false
[package]
productName = "Twosomnia"
version = "0.0.1"
[tauri.allowlist]
all = false
[tauri.allowlist.shell]
all = false
open = true
[tauri.allowlist.window]
startDragging = true
[tauri.allowlist.fs]
scope = [ "$RESOURCE/*" ]
[tauri.bundle]
active = true
category = "DeveloperTool"
copyright = ""
externalBin = [ ]
icon = [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
identifier = "co.schier.twosomnia"
longDescription = ""
resources = [ "plugins/*" ]
shortDescription = ""
targets = "all"
[tauri.bundle.deb]
depends = [ ]
[tauri.bundle.macOS]
exceptionDomain = ""
frameworks = [ ]
[tauri.bundle.windows]
digestAlgorithm = "sha256"
timestampUrl = ""
[tauri.security]
[tauri.updater]
active = false
endpoints = [ ]
pubkey = ""
dialog = true
[[tauri.windows]]
fullscreen = false
height = 800
resizable = true
title = "Twosomnia"
width = 1_400
titleBarStyle = "Overlay"
hiddenTitle = true
[tauri.systemTray]
iconPath = "icons/icon.png"
iconAsTemplate = true

View File

@@ -20,7 +20,7 @@ interface Response {
function App() {
const [error, setError] = useState<string | null>(null);
const [responseBody, setResponseBody] = useState<Response | null>(null);
const [response, setResponse] = useState<Response | null>(null);
const [url, setUrl] = useState('https://go-server.schier.dev/debug');
const [loading, setLoading] = useState(false);
const [method, setMethod] = useState<string>('get');
@@ -36,14 +36,14 @@ function App() {
resp.body = resp.body.replace(/<head>/gi, `<head><base href="${resp.url}"/>`);
}
setLoading(false);
setResponseBody(resp);
setResponse(resp);
} catch (err) {
setLoading(false);
setError(`${err}`);
}
}
const contentType = responseBody?.headers['content-type']?.split(';')[0] ?? 'text/plain';
const contentType = response?.headers['content-type']?.split(';')[0] ?? 'text/plain';
return (
<>
@@ -64,12 +64,12 @@ function App() {
</DropdownMenuRadio>
</HStack>
</nav>
<div className="h-full w-full overflow-auto">
<VStack className="w-full">
<HStack as={WindowDragRegion} items="center" className="pl-4 pr-1">
<h5>Hello, Friend!</h5>
<IconButton icon="gear" className="ml-auto" size="sm" />
</HStack>
<VStack className="p-4 max-w-[40rem] mx-auto" space={3}>
<VStack className="p-4 max-w-[35rem] mx-auto" space={3}>
<HStack as="form" className="items-end" onSubmit={sendRequest} space={2}>
<DropdownMenuRadio
onValueChange={setMethod}
@@ -105,30 +105,30 @@ function App() {
</HStack>
</HStack>
{error && <div className="text-white bg-red-500 px-4 py-1 rounded">{error}</div>}
{responseBody !== null && (
{response !== null && (
<>
<div>
{responseBody?.method.toUpperCase()}
<div className="my-1 italic text-gray-500 text-sm">
{response?.method.toUpperCase()}
&nbsp;&bull;&nbsp;
{responseBody?.status}
{response?.status}
&nbsp;&bull;&nbsp;
{responseBody?.elapsed}ms &nbsp;&bull;&nbsp;
{responseBody?.elapsed2}ms
{response?.elapsed}ms &nbsp;&bull;&nbsp;
{response?.elapsed2}ms
</div>
{contentType.includes('html') ? (
<iframe
title="Response preview"
srcDoc={responseBody.body}
srcDoc={response.body}
sandbox="allow-scripts allow-same-origin"
className="h-[70vh] w-full rounded-lg"
/>
) : (
<Editor value={responseBody?.body} contentType={contentType} />
)}
) : response?.body ? (
<Editor value={response?.body} contentType={contentType} />
) : null}
</>
)}
</VStack>
</div>
</VStack>
</div>
</>
);

View File

@@ -25,7 +25,7 @@ export function Input({ label, labelClassName, hideLabel, className, name, ...pr
id={id}
className={classnames(
className,
'border-2 border-gray-100 bg-gray-50 h-10 pl-3 pr-2 rounded-md text-sm focus:outline-none',
'w-0 min-w-[100%] border-2 border-gray-100 bg-gray-50 h-10 pl-3 pr-2 rounded-md text-sm focus:outline-none',
)}
{...props}
/>

View File

@@ -51,7 +51,7 @@ export interface VStackProps extends BaseStackProps {
export function VStack({ className, space, children, ...props }: VStackProps) {
return (
<BaseStack className={classnames(className, 'h-full flex-col')} {...props}>
<BaseStack className={classnames(className, 'w-full h-full flex-col')} {...props}>
{space
? Children.toArray(children)
.filter(Boolean) // Remove null/false/undefined children