diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index ea288093..c1146743 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -3502,10 +3502,12 @@ dependencies = [ name = "tauri-app" version = "0.0.0" dependencies = [ + "cocoa", "deno_ast", "deno_core", "futures", "http", + "objc", "reqwest", "serde", "serde_json", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index e808151e..48c5041e 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,6 +22,8 @@ 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" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 28010031..63bd18dc 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -46,8 +46,16 @@ pub async fn send_request( let req = client .request(m, abs_url.to_string()) .headers(headers) - .build() - .unwrap(); + .build(); + + let req = match req { + Ok(v) => v, + Err(e) => { + println!("Error: {}", e); + return Err(e.to_string()); + } + }; + let resp = client.execute(req).await; let elapsed = start.elapsed().as_millis(); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 1281ae67..85fcbb46 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -3,11 +3,36 @@ windows_subsystem = "windows" )] +#[cfg(target_os = "macos")] +#[macro_use] +extern crate objc; + mod commands; mod runtime; +mod window_ext; + +use tauri::{Manager, WindowEvent}; +use window_ext::WindowExt; fn main() { tauri::Builder::default() + .setup(|app| { + let win = app.get_window("main").unwrap(); + win.position_traffic_lights(); + Ok(()) + }) + .on_window_event(|e| { + let apply_offset = || { + let win = e.window(); + win.position_traffic_lights(); + }; + + match e.event() { + WindowEvent::Resized(..) => apply_offset(), + WindowEvent::ThemeChanged(..) => apply_offset(), + _ => {} + } + }) .invoke_handler(tauri::generate_handler![ commands::send_request, commands::greet diff --git a/src-tauri/src/window_ext.rs b/src-tauri/src/window_ext.rs new file mode 100644 index 00000000..5cad9bff --- /dev/null +++ b/src-tauri/src/window_ext.rs @@ -0,0 +1,49 @@ +use tauri::{Runtime, Window}; + +const TRAFFIC_LIGHT_OFFSET_X: f64 = 15.0; +const TRAFFIC_LIGHT_OFFSET_Y: f64 = 20.0; + +pub trait WindowExt { + #[cfg(target_os = "macos")] + fn position_traffic_lights(&self); +} + +impl WindowExt for Window { + #[cfg(target_os = "macos")] + fn position_traffic_lights(&self) { + use cocoa::appkit::{NSView, NSWindow, NSWindowButton}; + use cocoa::foundation::NSRect; + + let window = self.ns_window().unwrap() as cocoa::base::id; + + let x = TRAFFIC_LIGHT_OFFSET_X; + let y = TRAFFIC_LIGHT_OFFSET_Y; + + unsafe { + let close = window.standardWindowButton_(NSWindowButton::NSWindowCloseButton); + let miniaturize = + window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton); + let zoom = window.standardWindowButton_(NSWindowButton::NSWindowZoomButton); + + let title_bar_container_view = close.superview().superview(); + + let close_rect: NSRect = msg_send![close, frame]; + let button_height = close_rect.size.height; + + let title_bar_frame_height = button_height + y; + let mut title_bar_rect = NSView::frame(title_bar_container_view); + title_bar_rect.size.height = title_bar_frame_height; + title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height; + let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect]; + + let window_buttons = vec![close, miniaturize, zoom]; + let space_between = NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x; + + for (i, button) in window_buttons.into_iter().enumerate() { + let mut rect: NSRect = NSView::frame(button); + rect.origin.x = x + (i as f64 * space_between); + button.setFrameOrigin(rect.origin); + } + } + } +} diff --git a/src-web/App.tsx b/src-web/App.tsx index 93ba0296..315cdf14 100644 --- a/src-web/App.tsx +++ b/src-web/App.tsx @@ -1,12 +1,12 @@ import { FormEvent, useState } from 'react'; -import { Helmet } from 'react-helmet-async'; import { invoke } from '@tauri-apps/api/tauri'; import Editor from './components/Editor/Editor'; import { Input } from './components/Input'; -import { Stacks } from './components/Stacks'; +import { HStack, VStack } from './components/Stacks'; import { Button } from './components/Button'; -import { Grid } from './components/Grid'; import { DropdownMenuRadio } from './components/Dropdown'; +import { WindowDragRegion } from './components/WindowDragRegion'; +import { IconButton } from './components/IconButton'; interface Response { url: string; @@ -19,6 +19,7 @@ interface Response { } function App() { + const [error, setError] = useState(null); const [responseBody, setResponseBody] = useState(null); const [url, setUrl] = useState('https://go-server.schier.dev/debug'); const [loading, setLoading] = useState(false); @@ -27,74 +28,107 @@ function App() { async function sendRequest(e: FormEvent) { e.preventDefault(); setLoading(true); - const resp = (await invoke('send_request', { method, url })) as Response; - console.log('RESP', resp); - if (resp.body.includes('')) { - resp.body = resp.body.replace(//gi, ``); + setError(null); + + try { + const resp = (await invoke('send_request', { method, url })) as Response; + if (resp.body.includes('')) { + resp.body = resp.body.replace(//gi, ``); + } + setLoading(false); + setResponseBody(resp); + } catch (err) { + setLoading(false); + setError(`${err}`); } - setLoading(false); - setResponseBody(resp); } const contentType = responseBody?.headers['content-type']?.split(';')[0] ?? 'text/plain'; return ( <> - - - -
-
- - - - - setUrl(e.currentTarget.value)} - value={url} - placeholder="Enter a URL..." - /> - - - {responseBody !== null && ( - <> -
- {responseBody?.method.toUpperCase()} -  •  - {responseBody?.status} -  •  - {responseBody?.elapsed}ms  •  - {responseBody?.elapsed2}ms -
- - -
-