mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:08:32 +02:00
Fix window stoplights
This commit is contained in:
@@ -69,10 +69,7 @@ mod plugin;
|
|||||||
mod render;
|
mod render;
|
||||||
mod updates;
|
mod updates;
|
||||||
mod window_menu;
|
mod window_menu;
|
||||||
#[cfg(target_os = "macos")]
|
mod tauri_plugin_traffic_light;
|
||||||
mod mac;
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
mod win;
|
|
||||||
|
|
||||||
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
|
async fn migrate_db(app_handle: &AppHandle, db: &Mutex<Pool<Sqlite>>) -> Result<(), String> {
|
||||||
let pool = &*db.lock().await;
|
let pool = &*db.lock().await;
|
||||||
@@ -1502,8 +1499,14 @@ async fn cmd_list_workspaces(w: WebviewWindow) -> Result<Vec<Workspace>, String>
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn cmd_new_window(window: WebviewWindow, url: &str) -> Result<(), String> {
|
async fn cmd_new_window(app_handle: AppHandle, url: &str) -> Result<(), String> {
|
||||||
create_window(&window.app_handle(), Some(url));
|
create_window(&app_handle, url);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn cmd_new_nested_window(window: WebviewWindow, url: &str, label: &str, title: &str) -> Result<(), String> {
|
||||||
|
create_nested_window(&window, label, url, title);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1532,12 +1535,13 @@ async fn cmd_check_for_updates(
|
|||||||
pub fn run() {
|
pub fn run() {
|
||||||
tauri::Builder::default()
|
tauri::Builder::default()
|
||||||
.plugin(tauri_plugin_clipboard_manager::init())
|
.plugin(tauri_plugin_clipboard_manager::init())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
.plugin(tauri_plugin_window_state::Builder::default().with_denylist(&["settings"]).build())
|
||||||
.plugin(tauri_plugin_shell::init())
|
.plugin(tauri_plugin_shell::init())
|
||||||
.plugin(tauri_plugin_updater::Builder::new().build())
|
.plugin(tauri_plugin_updater::Builder::new().build())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_fs::init())
|
.plugin(tauri_plugin_fs::init())
|
||||||
|
.plugin(tauri_plugin_traffic_light::init())
|
||||||
.plugin(
|
.plugin(
|
||||||
tauri_plugin_log::Builder::default()
|
tauri_plugin_log::Builder::default()
|
||||||
.targets([
|
.targets([
|
||||||
@@ -1663,6 +1667,7 @@ pub fn run() {
|
|||||||
cmd_list_http_responses,
|
cmd_list_http_responses,
|
||||||
cmd_list_workspaces,
|
cmd_list_workspaces,
|
||||||
cmd_metadata,
|
cmd_metadata,
|
||||||
|
cmd_new_nested_window,
|
||||||
cmd_new_window,
|
cmd_new_window,
|
||||||
cmd_request_to_curl,
|
cmd_request_to_curl,
|
||||||
cmd_dismiss_notification,
|
cmd_dismiss_notification,
|
||||||
@@ -1691,7 +1696,7 @@ pub fn run() {
|
|||||||
.run(|app_handle, event| {
|
.run(|app_handle, event| {
|
||||||
match event {
|
match event {
|
||||||
RunEvent::Ready => {
|
RunEvent::Ready => {
|
||||||
create_window(app_handle, None);
|
create_window(app_handle, "/");
|
||||||
let h = app_handle.clone();
|
let h = app_handle.clone();
|
||||||
tauri::async_runtime::spawn(async move {
|
tauri::async_runtime::spawn(async move {
|
||||||
let info = analytics::track_launch_event(&h).await;
|
let info = analytics::track_launch_event(&h).await;
|
||||||
@@ -1737,7 +1742,84 @@ fn is_dev() -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
|
fn create_nested_window(window: &WebviewWindow, label: &str, url: &str, title: &str) -> WebviewWindow {
|
||||||
|
info!("Create new nested window label={label}");
|
||||||
|
let pos = window.outer_position().unwrap();
|
||||||
|
let mut win_builder = tauri::WebviewWindowBuilder::new(
|
||||||
|
window,
|
||||||
|
label,
|
||||||
|
WebviewUrl::App(url.into()),
|
||||||
|
)
|
||||||
|
.resizable(true)
|
||||||
|
.fullscreen(false)
|
||||||
|
.disable_drag_drop_handler() // Required for frontend Dnd on windows
|
||||||
|
.title(title)
|
||||||
|
.parent(&window)
|
||||||
|
.unwrap()
|
||||||
|
.position(
|
||||||
|
(pos.x + 20) as f64,
|
||||||
|
(pos.y + 20) as f64,
|
||||||
|
)
|
||||||
|
.inner_size(
|
||||||
|
500.0f64,
|
||||||
|
300.0f64,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add macOS-only things
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
win_builder = win_builder
|
||||||
|
.hidden_title(true)
|
||||||
|
.title_bar_style(TitleBarStyle::Overlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add non-MacOS things
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
{
|
||||||
|
win_builder = win_builder.decorations(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let win = win_builder.build().expect("failed to build window");
|
||||||
|
|
||||||
|
// Tauri doesn't support shadows when hiding decorations, so we add our own
|
||||||
|
// #[cfg(any(windows, target_os = "macos"))]
|
||||||
|
// set_shadow(&win, true).unwrap();
|
||||||
|
|
||||||
|
let win2 = win.clone();
|
||||||
|
win.on_menu_event(move |w, event| {
|
||||||
|
if !w.is_focused().unwrap() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
match event.id().0.as_str() {
|
||||||
|
"quit" => exit(0),
|
||||||
|
"close" => _ = w.close(),
|
||||||
|
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
|
||||||
|
"zoom_in" => w.emit("zoom_in", true).unwrap(),
|
||||||
|
"zoom_out" => w.emit("zoom_out", true).unwrap(),
|
||||||
|
"settings" => w.emit("settings", true).unwrap(),
|
||||||
|
"refresh" => win2.eval("location.reload()").unwrap(),
|
||||||
|
"open_feedback" => {
|
||||||
|
_ = win2
|
||||||
|
.app_handle()
|
||||||
|
.shell()
|
||||||
|
.open("https://yaak.canny.io", None)
|
||||||
|
}
|
||||||
|
"toggle_devtools" => {
|
||||||
|
if win2.is_devtools_open() {
|
||||||
|
win2.close_devtools();
|
||||||
|
} else {
|
||||||
|
win2.open_devtools();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
win
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_window(handle: &AppHandle, url: &str) -> WebviewWindow {
|
||||||
let menu = app_menu(handle).unwrap();
|
let menu = app_menu(handle).unwrap();
|
||||||
|
|
||||||
// This causes the window to not be clickable (in AppImage), so disable on Linux
|
// This causes the window to not be clickable (in AppImage), so disable on Linux
|
||||||
@@ -1745,11 +1827,12 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
|
|||||||
handle.set_menu(menu).expect("Failed to set app menu");
|
handle.set_menu(menu).expect("Failed to set app menu");
|
||||||
|
|
||||||
let window_num = handle.webview_windows().len();
|
let window_num = handle.webview_windows().len();
|
||||||
let window_id = format!("wnd_{}", window_num);
|
let label = format!("wnd_{}", window_num);
|
||||||
|
info!("Create new window label={label}");
|
||||||
let mut win_builder = tauri::WebviewWindowBuilder::new(
|
let mut win_builder = tauri::WebviewWindowBuilder::new(
|
||||||
handle,
|
handle,
|
||||||
window_id,
|
label,
|
||||||
WebviewUrl::App(url.unwrap_or_default().into()),
|
WebviewUrl::App(url.into()),
|
||||||
)
|
)
|
||||||
.resizable(true)
|
.resizable(true)
|
||||||
.fullscreen(false)
|
.fullscreen(false)
|
||||||
@@ -1774,16 +1857,12 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
|
|||||||
// Add non-MacOS things
|
// Add non-MacOS things
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
{
|
{
|
||||||
// Doesn't seem to work from Rust, here, so we do it in JS
|
// Doesn't seem to work from Rust, here, so we do it in main.tsx
|
||||||
win_builder = win_builder.decorations(false);
|
// win_builder = win_builder.decorations(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
let win = win_builder.build().expect("failed to build window");
|
let win = win_builder.build().expect("failed to build window");
|
||||||
|
|
||||||
// Tauri doesn't support shadows when hiding decorations, so we add our own
|
|
||||||
// #[cfg(any(windows, target_os = "macos"))]
|
|
||||||
// set_shadow(&win, true).unwrap();
|
|
||||||
|
|
||||||
let win2 = win.clone();
|
let win2 = win.clone();
|
||||||
win.on_menu_event(move |w, event| {
|
win.on_menu_event(move |w, event| {
|
||||||
if !w.is_focused().unwrap() {
|
if !w.is_focused().unwrap() {
|
||||||
@@ -1792,12 +1871,11 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
|
|||||||
|
|
||||||
match event.id().0.as_str() {
|
match event.id().0.as_str() {
|
||||||
"quit" => exit(0),
|
"quit" => exit(0),
|
||||||
"close" => w.close().unwrap(),
|
"close" => _ = w.close(),
|
||||||
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
|
"zoom_reset" => w.emit("zoom_reset", true).unwrap(),
|
||||||
"zoom_in" => w.emit("zoom_in", true).unwrap(),
|
"zoom_in" => w.emit("zoom_in", true).unwrap(),
|
||||||
"zoom_out" => w.emit("zoom_out", true).unwrap(),
|
"zoom_out" => w.emit("zoom_out", true).unwrap(),
|
||||||
"settings" => w.emit("settings", true).unwrap(),
|
"settings" => w.emit("settings", true).unwrap(),
|
||||||
"duplicate_request" => w.emit("duplicate_request", true).unwrap(),
|
|
||||||
"refresh" => win2.eval("location.reload()").unwrap(),
|
"refresh" => win2.eval("location.reload()").unwrap(),
|
||||||
"open_feedback" => {
|
"open_feedback" => {
|
||||||
_ = win2
|
_ = win2
|
||||||
@@ -1816,19 +1894,6 @@ fn create_window(handle: &AppHandle, url: Option<&str>) -> WebviewWindow {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use mac::setup_mac_window;
|
|
||||||
let mut m_win = win.clone();
|
|
||||||
setup_mac_window(&mut m_win);
|
|
||||||
};
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
use win::setup_win_window;
|
|
||||||
let mut m_win = win.clone();
|
|
||||||
setup_win_window(&mut m_win);
|
|
||||||
}
|
|
||||||
|
|
||||||
win
|
win
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,89 +1,44 @@
|
|||||||
// Borrowed from our friends at Hoppscotch
|
use objc::{msg_send, sel, sel_impl};
|
||||||
// https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
|
use rand::{distributions::Alphanumeric, Rng};
|
||||||
|
use tauri::{Manager, plugin::{Builder, TauriPlugin}, Runtime, Window};
|
||||||
use hex_color::HexColor;
|
|
||||||
use tauri::{Manager, WebviewWindow};
|
|
||||||
|
|
||||||
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
|
||||||
|
|
||||||
unsafe impl Send for UnsafeWindowHandle {}
|
|
||||||
|
|
||||||
unsafe impl Sync for UnsafeWindowHandle {}
|
|
||||||
|
|
||||||
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
|
const WINDOW_CONTROL_PAD_X: f64 = 13.0;
|
||||||
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
|
const WINDOW_CONTROL_PAD_Y: f64 = 18.0;
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
||||||
fn update_window_title(window: &WebviewWindow, title: String) {
|
unsafe impl Send for UnsafeWindowHandle {}
|
||||||
use cocoa::{
|
unsafe impl Sync for UnsafeWindowHandle {}
|
||||||
appkit::NSWindow,
|
|
||||||
base::nil,
|
|
||||||
foundation::NSString,
|
|
||||||
};
|
|
||||||
|
|
||||||
unsafe {
|
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
Builder::new("traffic_light_positioner")
|
||||||
|
.on_window_ready(|window| {
|
||||||
let _ = window.run_on_main_thread(move || {
|
#[cfg(target_os = "macos")]
|
||||||
let win_title = NSString::alloc(nil).init_str(&title);
|
setup_traffic_light_positioner(window);
|
||||||
let handle = window_handle;
|
return;
|
||||||
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
|
})
|
||||||
set_window_controls_pos(handle.0 as cocoa::base::id);
|
.build()
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
fn update_window_theme(window: &WebviewWindow, color: HexColor) {
|
fn position_traffic_lights(ns_window_handle: UnsafeWindowHandle, x: f64, y: f64) {
|
||||||
use cocoa::{
|
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||||
appkit::{NSAppearance, NSAppearanceNameVibrantDark, NSAppearanceNameVibrantLight, NSWindow},
|
use cocoa::foundation::NSRect;
|
||||||
base::nil,
|
let ns_window = ns_window_handle.0 as cocoa::base::id;
|
||||||
foundation::NSString,
|
|
||||||
};
|
|
||||||
|
|
||||||
let brightness = (color.r as u64 + color.g as u64 + color.b as u64) / 3;
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
let close = ns_window.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||||
|
let miniaturize =
|
||||||
let _ = window.run_on_main_thread(move || {
|
ns_window.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||||
let handle = window_handle;
|
let zoom = ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||||
|
|
||||||
let selected_appearance = if brightness >= 128 {
|
|
||||||
NSAppearance(NSAppearanceNameVibrantLight)
|
|
||||||
} else {
|
|
||||||
NSAppearance(NSAppearanceNameVibrantDark)
|
|
||||||
};
|
|
||||||
|
|
||||||
let title = NSString::alloc(nil).init_str("My Title");
|
|
||||||
NSWindow::setTitle_(handle.0 as cocoa::base::id, title);
|
|
||||||
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
|
|
||||||
set_window_controls_pos(handle.0 as cocoa::base::id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
fn set_window_controls_pos(window: cocoa::base::id) {
|
|
||||||
use cocoa::{
|
|
||||||
appkit::{NSView, NSWindow, NSWindowButton},
|
|
||||||
foundation::NSRect,
|
|
||||||
};
|
|
||||||
|
|
||||||
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 title_bar_container_view = close.superview().superview();
|
||||||
|
|
||||||
let close_rect: NSRect = msg_send![close, frame];
|
let close_rect: NSRect = msg_send![close, frame];
|
||||||
let button_height = close_rect.size.height;
|
let button_height = close_rect.size.height;
|
||||||
|
|
||||||
let title_bar_frame_height = button_height + WINDOW_CONTROL_PAD_Y;
|
let title_bar_frame_height = button_height + y;
|
||||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||||
title_bar_rect.size.height = title_bar_frame_height;
|
title_bar_rect.size.height = title_bar_frame_height;
|
||||||
title_bar_rect.origin.y = NSView::frame(window).size.height - title_bar_frame_height;
|
title_bar_rect.origin.y = NSView::frame(ns_window).size.height - title_bar_frame_height;
|
||||||
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
let _: () = msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||||
|
|
||||||
let window_buttons = vec![close, miniaturize, zoom];
|
let window_buttons = vec![close, miniaturize, zoom];
|
||||||
@@ -91,7 +46,7 @@ fn set_window_controls_pos(window: cocoa::base::id) {
|
|||||||
|
|
||||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||||
let mut rect: NSRect = NSView::frame(button);
|
let mut rect: NSRect = NSView::frame(button);
|
||||||
rect.origin.x = WINDOW_CONTROL_PAD_X + (i as f64 * space_between);
|
rect.origin.x = x + (i as f64 * space_between);
|
||||||
button.setFrameOrigin(rect.origin);
|
button.setFrameOrigin(rect.origin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,12 +54,12 @@ fn set_window_controls_pos(window: cocoa::base::id) {
|
|||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct AppState {
|
struct WindowState<R: Runtime> {
|
||||||
window: WebviewWindow,
|
window: Window<R>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
pub fn setup_mac_window(window: &mut WebviewWindow) {
|
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
|
||||||
use cocoa::delegate;
|
use cocoa::delegate;
|
||||||
use cocoa::appkit::NSWindow;
|
use cocoa::appkit::NSWindow;
|
||||||
use cocoa::base::{BOOL, id};
|
use cocoa::base::{BOOL, id};
|
||||||
@@ -112,16 +67,30 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
|
|||||||
use objc::runtime::{Object, Sel};
|
use objc::runtime::{Object, Sel};
|
||||||
use std::ffi::c_void;
|
use std::ffi::c_void;
|
||||||
|
|
||||||
fn with_app_state<F: FnOnce(&mut AppState) -> T, T>(this: &Object, func: F) {
|
// Do the initial positioning
|
||||||
|
position_traffic_lights(
|
||||||
|
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
|
||||||
|
WINDOW_CONTROL_PAD_X,
|
||||||
|
WINDOW_CONTROL_PAD_Y,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Ensure they stay in place while resizing the window.
|
||||||
|
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
|
||||||
|
this: &Object,
|
||||||
|
func: F,
|
||||||
|
) {
|
||||||
let ptr = unsafe {
|
let ptr = unsafe {
|
||||||
let x: *mut c_void = *this.get_ivar("yaakApp");
|
let x: *mut c_void = *this.get_ivar("app_box");
|
||||||
&mut *(x as *mut AppState)
|
&mut *(x as *mut WindowState<R>)
|
||||||
};
|
};
|
||||||
func(ptr);
|
func(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let ns_win = window.ns_window().unwrap() as id;
|
let ns_win = window
|
||||||
|
.ns_window()
|
||||||
|
.expect("NS Window should exist to mount traffic light delegate.")
|
||||||
|
as id;
|
||||||
|
|
||||||
let current_delegate: id = ns_win.delegate();
|
let current_delegate: id = ns_win.delegate();
|
||||||
|
|
||||||
@@ -137,12 +106,21 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
|
|||||||
let _: () = msg_send![super_del, windowWillClose: notification];
|
let _: () = msg_send![super_del, windowWillClose: notification];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extern "C" fn on_window_did_resize(this: &Object, _cmd: Sel, notification: id) {
|
extern "C" fn on_window_did_resize<R: Runtime>(this: &Object, _cmd: Sel, notification: id) {
|
||||||
unsafe {
|
unsafe {
|
||||||
with_app_state(&*this, |state| {
|
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||||
let id = state.window.ns_window().unwrap() as id;
|
let id = state
|
||||||
|
.window
|
||||||
|
.ns_window()
|
||||||
|
.expect("NS window should exist on state to handle resize")
|
||||||
|
as id;
|
||||||
|
|
||||||
set_window_controls_pos(id);
|
#[cfg(target_os = "macos")]
|
||||||
|
position_traffic_lights(
|
||||||
|
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||||
|
WINDOW_CONTROL_PAD_X,
|
||||||
|
WINDOW_CONTROL_PAD_Y,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let super_del: id = *this.get_ivar("super_delegate");
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
@@ -222,43 +200,75 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
|
|||||||
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
|
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extern "C" fn on_window_did_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
with_app_state(&*this, |state| {
|
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||||
state.window.emit("did-enter-fullscreen", ()).unwrap();
|
state
|
||||||
|
.window
|
||||||
|
.emit("did-enter-fullscreen", ())
|
||||||
|
.expect("Failed to emit event");
|
||||||
});
|
});
|
||||||
|
|
||||||
let super_del: id = *this.get_ivar("super_delegate");
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
|
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extern "C" fn on_window_will_enter_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
with_app_state(&*this, |state| {
|
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||||
state.window.emit("will-enter-fullscreen", ()).unwrap();
|
state
|
||||||
|
.window
|
||||||
|
.emit("will-enter-fullscreen", ())
|
||||||
|
.expect("Failed to emit event");
|
||||||
});
|
});
|
||||||
|
|
||||||
let super_del: id = *this.get_ivar("super_delegate");
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
|
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extern "C" fn on_window_did_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
with_app_state(&*this, |state| {
|
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||||
state.window.emit("did-exit-fullscreen", ()).unwrap();
|
state
|
||||||
|
.window
|
||||||
|
.emit("did-exit-fullscreen", ())
|
||||||
|
.expect("Failed to emit event");
|
||||||
|
|
||||||
let id = state.window.ns_window().unwrap() as id;
|
let id = state.window.ns_window().expect("Failed to emit event") as id;
|
||||||
set_window_controls_pos(id);
|
position_traffic_lights(
|
||||||
|
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||||
|
WINDOW_CONTROL_PAD_X,
|
||||||
|
WINDOW_CONTROL_PAD_Y,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
let super_del: id = *this.get_ivar("super_delegate");
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
|
let _: () = msg_send![super_del, windowDidExitFullScreen: notification];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
extern "C" fn on_window_will_exit_full_screen(this: &Object, _cmd: Sel, notification: id) {
|
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
|
||||||
|
this: &Object,
|
||||||
|
_cmd: Sel,
|
||||||
|
notification: id,
|
||||||
|
) {
|
||||||
unsafe {
|
unsafe {
|
||||||
with_app_state(&*this, |state| {
|
with_window_state(&*this, |state: &mut WindowState<R>| {
|
||||||
state.window.emit("will-exit-fullscreen", ()).unwrap();
|
state
|
||||||
|
.window
|
||||||
|
.emit("will-exit-fullscreen", ())
|
||||||
|
.expect("Failed to emit event");
|
||||||
});
|
});
|
||||||
|
|
||||||
let super_del: id = *this.get_ivar("super_delegate");
|
let super_del: id = *this.get_ivar("super_delegate");
|
||||||
@@ -299,44 +309,29 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// extern fn on_dealloc(this: &Object, cmd: Sel) {
|
|
||||||
// unsafe {
|
|
||||||
// let super_del: id = *this.get_ivar("super_delegate");
|
|
||||||
// let _: () = msg_send![super_del, dealloc];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// extern fn on_mark_is_checking_zoomed_in(this: &Object, cmd: Sel) {
|
|
||||||
// unsafe {
|
|
||||||
// let super_del: id = *this.get_ivar("super_delegate");
|
|
||||||
// let _: () = msg_send![super_del, markIsCheckingZoomedIn];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// extern fn on_clear_is_checking_zoomed_in(this: &Object, cmd: Sel) {
|
|
||||||
// unsafe {
|
|
||||||
// let super_del: id = *this.get_ivar("super_delegate");
|
|
||||||
// let _: () = msg_send![super_del, clearIsCheckingZoomedIn];
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Are we deallocing this properly ? (I miss safe Rust :( )
|
// Are we deallocing this properly ? (I miss safe Rust :( )
|
||||||
let w = window.clone();
|
let window_label = window.label().to_string();
|
||||||
let app_state = AppState { window: w };
|
|
||||||
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
|
||||||
set_window_controls_pos(ns_win);
|
|
||||||
|
|
||||||
ns_win.setDelegate_(delegate!("MainWindowDelegate", {
|
let app_state = WindowState { window };
|
||||||
|
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
||||||
|
let random_str: String = rand::thread_rng()
|
||||||
|
.sample_iter(&Alphanumeric)
|
||||||
|
.take(20)
|
||||||
|
.map(char::from)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
|
||||||
|
// delegate with the same name.
|
||||||
|
let delegate_name = format!("windowDelegate_{}_{}", window_label, random_str);
|
||||||
|
|
||||||
|
ns_win.setDelegate_(delegate!(&delegate_name, {
|
||||||
window: id = ns_win,
|
window: id = ns_win,
|
||||||
yaakApp: *mut c_void = app_box,
|
app_box: *mut c_void = app_box,
|
||||||
toolbar: id = cocoa::base::nil,
|
toolbar: id = cocoa::base::nil,
|
||||||
super_delegate: id = current_delegate,
|
super_delegate: id = current_delegate,
|
||||||
// (dealloc) => on_dealloc as extern fn(&Object, Sel),
|
|
||||||
// (markIsCheckingZoomedIn) => on_mark_is_checking_zoomed_in as extern fn(&Object, Sel),
|
|
||||||
// (clearIsCheckingZoomedIn) => on_clear_is_checking_zoomed_in as extern fn(&Object, Sel),
|
|
||||||
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
|
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
|
||||||
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
|
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
|
||||||
(windowDidResize:) => on_window_did_resize as extern fn(&Object, Sel, id),
|
(windowDidResize:) => on_window_did_resize::<R> as extern fn(&Object, Sel, id),
|
||||||
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
|
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
|
||||||
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
|
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
|
||||||
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
|
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
|
||||||
@@ -347,36 +342,13 @@ pub fn setup_mac_window(window: &mut WebviewWindow) {
|
|||||||
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
|
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
|
||||||
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
|
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
|
||||||
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
||||||
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen as extern fn(&Object, Sel, id),
|
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||||
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen as extern fn(&Object, Sel, id),
|
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||||
(windowDidExitFullScreen:) => on_window_did_exit_full_screen as extern fn(&Object, Sel, id),
|
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||||
(windowWillExitFullScreen:) => on_window_will_exit_full_screen as extern fn(&Object, Sel, id),
|
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||||
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
|
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
|
||||||
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
|
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
|
||||||
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
|
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
let app = window.app_handle();
|
|
||||||
let window = window.clone();
|
|
||||||
|
|
||||||
// Control window theme based on app update_window
|
|
||||||
let window_for_theme = window.clone();
|
|
||||||
app.listen_any("yaak_bg_changed", move |ev| {
|
|
||||||
let payload = serde_json::from_str::<&str>(ev.payload())
|
|
||||||
.unwrap()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let color = HexColor::parse_rgb(payload).unwrap();
|
|
||||||
|
|
||||||
update_window_theme(&window_for_theme, color);
|
|
||||||
});
|
|
||||||
|
|
||||||
let window_for_title = window.clone();
|
|
||||||
app.listen_any("yaak_title_changed", move |ev| {
|
|
||||||
let title = serde_json::from_str::<&str>(ev.payload())
|
|
||||||
.unwrap()
|
|
||||||
.trim();
|
|
||||||
update_window_title(&window_for_title, title.to_string());
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
@@ -19,6 +19,7 @@ pub struct YaakUpdater {
|
|||||||
pub enum UpdateMode {
|
pub enum UpdateMode {
|
||||||
Stable,
|
Stable,
|
||||||
Beta,
|
Beta,
|
||||||
|
Alpha,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for UpdateMode {
|
impl Display for UpdateMode {
|
||||||
@@ -26,6 +27,7 @@ impl Display for UpdateMode {
|
|||||||
let s = match self {
|
let s = match self {
|
||||||
UpdateMode::Stable => "stable",
|
UpdateMode::Stable => "stable",
|
||||||
UpdateMode::Beta => "beta",
|
UpdateMode::Beta => "beta",
|
||||||
|
UpdateMode::Alpha => "alpha",
|
||||||
};
|
};
|
||||||
write!(f, "{}", s)
|
write!(f, "{}", s)
|
||||||
}
|
}
|
||||||
@@ -35,6 +37,7 @@ impl UpdateMode {
|
|||||||
pub fn new(mode: &str) -> UpdateMode {
|
pub fn new(mode: &str) -> UpdateMode {
|
||||||
match mode {
|
match mode {
|
||||||
"beta" => UpdateMode::Beta,
|
"beta" => UpdateMode::Beta,
|
||||||
|
"alpha" => UpdateMode::Alpha,
|
||||||
_ => UpdateMode::Stable,
|
_ => UpdateMode::Stable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
// Borrowed from our friends at Hoppscotch
|
|
||||||
// https://github.com/hoppscotch/hoppscotch/blob/286fcd2bb08a84f027b10308d1e18da368f95ebf/packages/hoppscotch-selfhost-desktop/src-tauri/src/mac/window.rs
|
|
||||||
use std::mem::transmute;
|
|
||||||
|
|
||||||
use hex_color::HexColor;
|
|
||||||
|
|
||||||
use windows::Win32::Foundation::BOOL;
|
|
||||||
use windows::Win32::Foundation::COLORREF;
|
|
||||||
use windows::Win32::Foundation::HWND;
|
|
||||||
use windows::Win32::Graphics::Dwm::DwmSetWindowAttribute;
|
|
||||||
use windows::Win32::Graphics::Dwm::DWMWA_CAPTION_COLOR;
|
|
||||||
use windows::Win32::Graphics::Dwm::DWMWA_USE_IMMERSIVE_DARK_MODE;
|
|
||||||
use windows::Win32::UI::Controls::{WTA_NONCLIENT, WTNCA_NODRAWICON, WTNCA_NOMIRRORHELP, WTNCA_NOSYSMENU};
|
|
||||||
use windows::Win32::UI::Controls::SetWindowThemeAttribute;
|
|
||||||
use windows::Win32::UI::Controls::WTNCA_NODRAWCAPTION;
|
|
||||||
|
|
||||||
fn hex_color_to_colorref(color: HexColor) -> COLORREF {
|
|
||||||
// TODO: Remove this unsafe, This operation doesn't need to be unsafe!
|
|
||||||
unsafe {
|
|
||||||
COLORREF(transmute::<[u8; 4], u32>([color.r, color.g, color.b, 0]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct WinThemeAttribute {
|
|
||||||
flag: u32,
|
|
||||||
mask: u32
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
fn update_bg_color(hwnd: &HWND, bg_color: HexColor) {
|
|
||||||
|
|
||||||
let use_dark_mode = BOOL::from(true);
|
|
||||||
|
|
||||||
let final_color = hex_color_to_colorref(bg_color);
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
DwmSetWindowAttribute(
|
|
||||||
HWND(hwnd.0),
|
|
||||||
DWMWA_USE_IMMERSIVE_DARK_MODE,
|
|
||||||
ptr::addr_of!(use_dark_mode) as *const c_void,
|
|
||||||
size_of::<BOOL>().try_into().unwrap()
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
DwmSetWindowAttribute(
|
|
||||||
HWND(hwnd.0),
|
|
||||||
DWMWA_CAPTION_COLOR,
|
|
||||||
ptr::addr_of!(final_color) as *const c_void,
|
|
||||||
size_of::<COLORREF>().try_into().unwrap()
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let flags = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON;
|
|
||||||
let mask = WTNCA_NODRAWCAPTION | WTNCA_NODRAWICON | WTNCA_NOSYSMENU | WTNCA_NOMIRRORHELP;
|
|
||||||
let options = WinThemeAttribute { flag: flags, mask };
|
|
||||||
|
|
||||||
SetWindowThemeAttribute(
|
|
||||||
HWND(hwnd.0),
|
|
||||||
WTA_NONCLIENT,
|
|
||||||
ptr::addr_of!(options) as *const c_void,
|
|
||||||
size_of::<WinThemeAttribute>().try_into().unwrap()
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
pub fn setup_win_window(window: &mut WebviewWindow) {
|
|
||||||
let win_handle = window.hwnd().unwrap();
|
|
||||||
let win_clone = win_handle.clone();
|
|
||||||
|
|
||||||
window.listen_global("yaak_bg_changed", move |ev| {
|
|
||||||
let payload = serde_json::from_str::<&str>(ev.payload().unwrap())
|
|
||||||
.unwrap()
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
let color = HexColor::parse_rgb(payload).unwrap();
|
|
||||||
update_bg_color(&HWND(win_clone.0), color);
|
|
||||||
});
|
|
||||||
|
|
||||||
update_bg_color(&HWND(win_handle.0), HexColor::rgb(23, 23, 23));
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,7 @@ import { routePaths, useAppRoutes } from '../hooks/useAppRoutes';
|
|||||||
import { DefaultLayout } from './DefaultLayout';
|
import { DefaultLayout } from './DefaultLayout';
|
||||||
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
|
import { RedirectToLatestWorkspace } from './RedirectToLatestWorkspace';
|
||||||
import RouteError from './RouteError';
|
import RouteError from './RouteError';
|
||||||
|
import { SettingsDialog } from './Settings/SettingsDialog';
|
||||||
import Workspace from './Workspace';
|
import Workspace from './Workspace';
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@@ -36,6 +37,12 @@ const router = createBrowserRouter([
|
|||||||
path: '/workspaces/:workspaceId/environments/:environmentId/requests/:requestId',
|
path: '/workspaces/:workspaceId/environments/:environmentId/requests/:requestId',
|
||||||
element: <RedirectLegacyEnvironmentURLs />,
|
element: <RedirectLegacyEnvironmentURLs />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: routePaths.workspaceSettings({
|
||||||
|
workspaceId: ':workspaceId',
|
||||||
|
}),
|
||||||
|
element: <SettingsDialog fullscreen />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { useRequestUpdateKey } from '../hooks/useRequestUpdateKey';
|
|||||||
import { settingsQueryKey, useSettings } from '../hooks/useSettings';
|
import { settingsQueryKey, useSettings } from '../hooks/useSettings';
|
||||||
import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument';
|
import { useSyncThemeToDocument } from '../hooks/useSyncThemeToDocument';
|
||||||
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
import { useSyncWindowTitle } from '../hooks/useSyncWindowTitle';
|
||||||
import { useUpdateSettings } from '../hooks/useUpdateSettings';
|
|
||||||
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
import { workspacesQueryKey } from '../hooks/useWorkspaces';
|
||||||
import { useZoom } from '../hooks/useZoom';
|
import { useZoom } from '../hooks/useZoom';
|
||||||
import type { Model } from '../lib/models';
|
import type { Model } from '../lib/models';
|
||||||
@@ -140,7 +139,6 @@ export function GlobalHooks() {
|
|||||||
`--editor-font-size: ${editorFontSize}px`,
|
`--editor-font-size: ${editorFontSize}px`,
|
||||||
].join('; ');
|
].join('; ');
|
||||||
}, [settings]);
|
}, [settings]);
|
||||||
const updateSettings = useUpdateSettings();
|
|
||||||
|
|
||||||
// Handle Zoom. Note, Mac handles it in app menu, so need to also handle keyboard
|
// Handle Zoom. Note, Mac handles it in app menu, so need to also handle keyboard
|
||||||
// shortcuts for Windows/Linux
|
// shortcuts for Windows/Linux
|
||||||
@@ -149,21 +147,8 @@ export function GlobalHooks() {
|
|||||||
useListenToTauriEvent('zoom_in', () => zoom.zoomIn);
|
useListenToTauriEvent('zoom_in', () => zoom.zoomIn);
|
||||||
useHotKey('app.zoom_out', () => zoom.zoomOut);
|
useHotKey('app.zoom_out', () => zoom.zoomOut);
|
||||||
useListenToTauriEvent('zoom_out', () => zoom.zoomOut);
|
useListenToTauriEvent('zoom_out', () => zoom.zoomOut);
|
||||||
useHotKey('app.zoom_out', () => zoom.zoomReset);
|
useHotKey('app.zoom_reset', () => zoom.zoomReset);
|
||||||
useListenToTauriEvent('zoom_out', () => zoom.zoomReset);
|
useListenToTauriEvent('zoom_reset', () => zoom.zoomReset);
|
||||||
|
|
||||||
useHotKey('app.zoom_out', () => {
|
|
||||||
if (!settings) return;
|
|
||||||
updateSettings.mutate({
|
|
||||||
...settings,
|
|
||||||
interfaceScale: Math.max(0.4, settings.interfaceScale * 0.9),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
useHotKey('app.zoom_reset', () => {
|
|
||||||
if (!settings) return;
|
|
||||||
updateSettings.mutate({ ...settings, interfaceScale: 1 });
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
import { useActiveWorkspace } from '../../hooks/useActiveWorkspace';
|
||||||
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
|
||||||
@@ -10,10 +9,8 @@ import { trackEvent } from '../../lib/analytics';
|
|||||||
import { clamp } from '../../lib/clamp';
|
import { clamp } from '../../lib/clamp';
|
||||||
import { isThemeDark } from '../../lib/theme/window';
|
import { isThemeDark } from '../../lib/theme/window';
|
||||||
import type { ButtonProps } from '../core/Button';
|
import type { ButtonProps } from '../core/Button';
|
||||||
import { Button } from '../core/Button';
|
|
||||||
import { Editor } from '../core/Editor';
|
import { Editor } from '../core/Editor';
|
||||||
import type { IconProps } from '../core/Icon';
|
import type { IconProps } from '../core/Icon';
|
||||||
import { Icon } from '../core/Icon';
|
|
||||||
import { IconButton } from '../core/IconButton';
|
import { IconButton } from '../core/IconButton';
|
||||||
import { PlainInput } from '../core/PlainInput';
|
import { PlainInput } from '../core/PlainInput';
|
||||||
import type { SelectOption } from '../core/Select';
|
import type { SelectOption } from '../core/Select';
|
||||||
@@ -83,6 +80,7 @@ export function SettingsAppearance() {
|
|||||||
name="interfaceFontSize"
|
name="interfaceFontSize"
|
||||||
label="Font Size"
|
label="Font Size"
|
||||||
placeholder="16"
|
placeholder="16"
|
||||||
|
step={0.5}
|
||||||
type="number"
|
type="number"
|
||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
defaultValue={`${settings.interfaceFontSize}`}
|
defaultValue={`${settings.interfaceFontSize}`}
|
||||||
@@ -99,6 +97,7 @@ export function SettingsAppearance() {
|
|||||||
name="editorFontSize"
|
name="editorFontSize"
|
||||||
label="Editor Font Size"
|
label="Editor Font Size"
|
||||||
placeholder="14"
|
placeholder="14"
|
||||||
|
step={0.5}
|
||||||
type="number"
|
type="number"
|
||||||
labelPosition="left"
|
labelPosition="left"
|
||||||
defaultValue={`${settings.editorFontSize}`}
|
defaultValue={`${settings.editorFontSize}`}
|
||||||
@@ -124,35 +123,40 @@ export function SettingsAppearance() {
|
|||||||
trackEvent('setting', 'update', { appearance });
|
trackEvent('setting', 'update', { appearance });
|
||||||
}}
|
}}
|
||||||
options={[
|
options={[
|
||||||
{ label: 'Sync with OS', value: 'system' },
|
{ label: 'Automatic', value: 'system' },
|
||||||
{ label: 'Light', value: 'light' },
|
{ label: 'Light', value: 'light' },
|
||||||
{ label: 'Dark', value: 'dark' },
|
{ label: 'Dark', value: 'dark' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<Select
|
{(settings.appearance === 'system' || settings.appearance === 'light') && (
|
||||||
name="lightTheme"
|
<Select
|
||||||
label="Light Theme"
|
name="lightTheme"
|
||||||
labelPosition="left"
|
label={settings.appearance === 'system' ? 'Light Theme' : 'Theme'}
|
||||||
size="sm"
|
labelPosition="left"
|
||||||
value={activeTheme.light.id}
|
size="sm"
|
||||||
options={lightThemes}
|
value={activeTheme.light.id}
|
||||||
onChange={async (themeLight) => {
|
options={lightThemes}
|
||||||
await updateSettings.mutateAsync({ ...settings, themeLight });
|
onChange={async (themeLight) => {
|
||||||
trackEvent('setting', 'update', { themeLight });
|
await updateSettings.mutateAsync({ ...settings, themeLight });
|
||||||
}}
|
trackEvent('setting', 'update', { themeLight });
|
||||||
/>
|
}}
|
||||||
<Select
|
/>
|
||||||
name="darkTheme"
|
)}
|
||||||
label="Dark Theme"
|
{(settings.appearance === 'system' || settings.appearance === 'dark') && (
|
||||||
labelPosition="left"
|
<Select
|
||||||
size="sm"
|
name="darkTheme"
|
||||||
value={activeTheme.dark.id}
|
label={settings.appearance === 'system' ? 'Dark Theme' : 'Theme'}
|
||||||
options={darkThemes}
|
labelPosition="left"
|
||||||
onChange={async (themeDark) => {
|
size="sm"
|
||||||
await updateSettings.mutateAsync({ ...settings, themeDark });
|
value={activeTheme.dark.id}
|
||||||
trackEvent('setting', 'update', { themeDark });
|
options={darkThemes}
|
||||||
}}
|
onChange={async (themeDark) => {
|
||||||
/>
|
await updateSettings.mutateAsync({ ...settings, themeDark });
|
||||||
|
trackEvent('setting', 'update', { themeDark });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<VStack
|
<VStack
|
||||||
space={3}
|
space={3}
|
||||||
className="mt-3 w-full bg-background p-3 border border-dashed border-background-highlight rounded overflow-x-auto"
|
className="mt-3 w-full bg-background p-3 border border-dashed border-background-highlight rounded overflow-x-auto"
|
||||||
@@ -196,18 +200,6 @@ export function SettingsAppearance() {
|
|||||||
contentType="application/javascript"
|
contentType="application/javascript"
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
<Button
|
|
||||||
color="secondary"
|
|
||||||
variant="border"
|
|
||||||
size="sm"
|
|
||||||
className="mr-auto"
|
|
||||||
rightSlot={<Icon icon="externalLink" />}
|
|
||||||
onClick={async () => {
|
|
||||||
await invoke('cmd_new_window', { url: window.location.pathname });
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Open Preview Window
|
|
||||||
</Button>
|
|
||||||
</VStack>
|
</VStack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,25 @@ enum Tab {
|
|||||||
const tabs = [Tab.General, Tab.Appearance, Tab.Design];
|
const tabs = [Tab.General, Tab.Appearance, Tab.Design];
|
||||||
const useTabState = createGlobalState<string>(tabs[0]!);
|
const useTabState = createGlobalState<string>(tabs[0]!);
|
||||||
|
|
||||||
export const SettingsDialog = () => {
|
interface Props {
|
||||||
|
fullscreen?: true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SettingsDialog = ({ fullscreen }: Props) => {
|
||||||
const [tab, setTab] = useTabState();
|
const [tab, setTab] = useTabState();
|
||||||
const appInfo = useAppInfo();
|
const appInfo = useAppInfo();
|
||||||
const isDev = appInfo?.isDev ?? false;
|
const isDev = appInfo?.isDev ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('w-[70vw] max-w-[40rem]', 'h-[80vh]')}>
|
<div className={classNames(!fullscreen && 'w-[70vw] max-w-[40rem] h-[80vh]')}>
|
||||||
|
{fullscreen && (
|
||||||
|
<div
|
||||||
|
data-tauri-drag-region
|
||||||
|
className="h-[38px] bg-background-highlight-secondary flex items-center justify-center border-b border-background-highlight"
|
||||||
|
>
|
||||||
|
Settings
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Tabs
|
<Tabs
|
||||||
value={tab}
|
value={tab}
|
||||||
addBorders
|
addBorders
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
|
import { invoke } from '@tauri-apps/api/core';
|
||||||
import { open } from '@tauri-apps/plugin-shell';
|
import { open } from '@tauri-apps/plugin-shell';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
import { useActiveWorkspaceId } from '../hooks/useActiveWorkspaceId';
|
||||||
import { useAppInfo } from '../hooks/useAppInfo';
|
import { useAppInfo } from '../hooks/useAppInfo';
|
||||||
|
import { useAppRoutes } from '../hooks/useAppRoutes';
|
||||||
import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
|
import { useCheckForUpdates } from '../hooks/useCheckForUpdates';
|
||||||
import { useExportData } from '../hooks/useExportData';
|
import { useExportData } from '../hooks/useExportData';
|
||||||
import { useImportData } from '../hooks/useImportData';
|
import { useImportData } from '../hooks/useImportData';
|
||||||
@@ -20,6 +23,8 @@ export function SettingsDropdown() {
|
|||||||
const dropdownRef = useRef<DropdownRef>(null);
|
const dropdownRef = useRef<DropdownRef>(null);
|
||||||
const dialog = useDialog();
|
const dialog = useDialog();
|
||||||
const checkForUpdates = useCheckForUpdates();
|
const checkForUpdates = useCheckForUpdates();
|
||||||
|
const routes = useAppRoutes();
|
||||||
|
const workspaceId = useActiveWorkspaceId();
|
||||||
|
|
||||||
const showSettings = () => {
|
const showSettings = () => {
|
||||||
dialog.show({
|
dialog.show({
|
||||||
@@ -58,6 +63,20 @@ export function SettingsDropdown() {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: 'foo',
|
||||||
|
label: 'Foo',
|
||||||
|
hotKeyAction: 'hotkeys.showHelp',
|
||||||
|
leftSlot: <Icon icon="keyboard" />,
|
||||||
|
onSelect: async () => {
|
||||||
|
if (!workspaceId) return;
|
||||||
|
await invoke('cmd_new_nested_window', {
|
||||||
|
url: routes.paths.workspaceSettings({ workspaceId }),
|
||||||
|
label: 'settings',
|
||||||
|
title: 'Yaak Settings',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'import-data',
|
key: 'import-data',
|
||||||
label: 'Import Data',
|
label: 'Import Data',
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { HStack } from './Stacks';
|
|||||||
|
|
||||||
export type PlainInputProps = Omit<InputProps, 'wrapLines' | 'onKeyDown' | 'type'> & {
|
export type PlainInputProps = Omit<InputProps, 'wrapLines' | 'onKeyDown' | 'type'> & {
|
||||||
type: 'text' | 'password' | 'number';
|
type: 'text' | 'password' | 'number';
|
||||||
|
step?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PlainInput = forwardRef<HTMLInputElement, PlainInputProps>(function Input(
|
export const PlainInput = forwardRef<HTMLInputElement, PlainInputProps>(function Input(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironmentId';
|
|
||||||
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
|
||||||
import { useActiveRequestId } from './useActiveRequestId';
|
|
||||||
import type { Environment } from '../lib/models';
|
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import type { Environment } from '../lib/models';
|
||||||
|
import { QUERY_ENVIRONMENT_ID } from './useActiveEnvironmentId';
|
||||||
|
import { useActiveRequestId } from './useActiveRequestId';
|
||||||
|
import { useActiveWorkspaceId } from './useActiveWorkspaceId';
|
||||||
|
|
||||||
export type RouteParamsWorkspace = {
|
export type RouteParamsWorkspace = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@@ -18,6 +18,9 @@ export const routePaths = {
|
|||||||
workspaces() {
|
workspaces() {
|
||||||
return '/workspaces';
|
return '/workspaces';
|
||||||
},
|
},
|
||||||
|
workspaceSettings({ workspaceId } = { workspaceId: ':workspaceId' } as RouteParamsWorkspace) {
|
||||||
|
return `/workspaces/${workspaceId}/settings`;
|
||||||
|
},
|
||||||
workspace(
|
workspace(
|
||||||
{ workspaceId, environmentId } = {
|
{ workspaceId, environmentId } = {
|
||||||
workspaceId: ':workspaceId',
|
workspaceId: ':workspaceId',
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ export function useSettings() {
|
|||||||
queryKey: settingsQueryKey(),
|
queryKey: settingsQueryKey(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const settings = (await invoke('cmd_get_settings')) as Settings;
|
const settings = (await invoke('cmd_get_settings')) as Settings;
|
||||||
|
console.log('SETTINGS', settings);
|
||||||
return [settings];
|
return [settings];
|
||||||
},
|
},
|
||||||
}).data?.[0] ?? undefined
|
}).data?.[0] ?? undefined
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { emit } from '@tauri-apps/api/event';
|
|
||||||
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
import { getCurrent } from '@tauri-apps/api/webviewWindow';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
@@ -12,7 +11,12 @@ export function useSyncWindowTitle() {
|
|||||||
const activeWorkspace = useActiveWorkspace();
|
const activeWorkspace = useActiveWorkspace();
|
||||||
const activeEnvironment = useActiveEnvironment();
|
const activeEnvironment = useActiveEnvironment();
|
||||||
const osInfo = useOsInfo();
|
const osInfo = useOsInfo();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (osInfo?.osType == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let newTitle = activeWorkspace ? activeWorkspace.name : 'Yaak';
|
let newTitle = activeWorkspace ? activeWorkspace.name : 'Yaak';
|
||||||
if (activeEnvironment) {
|
if (activeEnvironment) {
|
||||||
newTitle += ` [${activeEnvironment.name}]`;
|
newTitle += ` [${activeEnvironment.name}]`;
|
||||||
@@ -25,9 +29,10 @@ export function useSyncWindowTitle() {
|
|||||||
// TODO: This resets the stoplight position so we can't use it on macOS yet. Perhaps
|
// TODO: This resets the stoplight position so we can't use it on macOS yet. Perhaps
|
||||||
// we can
|
// we can
|
||||||
if (osInfo?.osType !== 'macos') {
|
if (osInfo?.osType !== 'macos') {
|
||||||
|
console.log('DO IT', osInfo?.osType);
|
||||||
getCurrent().setTitle(newTitle).catch(console.error);
|
getCurrent().setTitle(newTitle).catch(console.error);
|
||||||
} else {
|
} else {
|
||||||
emit('yaak_title_changed', newTitle).catch(console.error);
|
// emit('yaak_title_changed', newTitle).catch(console.error);
|
||||||
}
|
}
|
||||||
}, [activeEnvironment, activeRequest, activeWorkspace, osInfo?.osType]);
|
}, [activeEnvironment, activeRequest, activeWorkspace, osInfo?.osType]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { catppuccin } from './themes/catppuccin';
|
|||||||
import { github } from './themes/github';
|
import { github } from './themes/github';
|
||||||
import { hotdogStand } from './themes/hotdog-stand';
|
import { hotdogStand } from './themes/hotdog-stand';
|
||||||
import { monokaiPro } from './themes/monokai-pro';
|
import { monokaiPro } from './themes/monokai-pro';
|
||||||
import { relaxing } from './themes/relaxing';
|
|
||||||
import { rosePine } from './themes/rose-pine';
|
import { rosePine } from './themes/rose-pine';
|
||||||
import { yaak, yaakDark, yaakLight } from './themes/yaak';
|
import { yaak, yaakDark, yaakLight } from './themes/yaak';
|
||||||
|
|
||||||
@@ -12,7 +11,6 @@ export const defaultLightTheme = yaakLight;
|
|||||||
export const yaakThemes = [
|
export const yaakThemes = [
|
||||||
...yaak,
|
...yaak,
|
||||||
...catppuccin,
|
...catppuccin,
|
||||||
...relaxing,
|
|
||||||
...rosePine,
|
...rosePine,
|
||||||
...github,
|
...github,
|
||||||
...monokaiPro,
|
...monokaiPro,
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import type { YaakTheme } from '../window';
|
|||||||
|
|
||||||
const githubDark: YaakTheme = {
|
const githubDark: YaakTheme = {
|
||||||
id: 'github-dark',
|
id: 'github-dark',
|
||||||
name: 'GitHub',
|
name: 'GitHub Dark',
|
||||||
background: new Color('#0d1218', 'dark'),
|
background: new Color('#0d1218', 'dark'),
|
||||||
backgroundHighlight: new Color('#171c23', 'dark'),
|
backgroundHighlight: new Color('#171c23', 'dark'),
|
||||||
backgroundHighlightSecondary: new Color('#1c2127', 'dark'),
|
backgroundHighlightSecondary: new Color('#1c2127', 'dark'),
|
||||||
@@ -36,7 +36,7 @@ const githubDark: YaakTheme = {
|
|||||||
|
|
||||||
export const githubLight: YaakTheme = {
|
export const githubLight: YaakTheme = {
|
||||||
id: 'github-light',
|
id: 'github-light',
|
||||||
name: 'GitHub',
|
name: 'GitHub Light',
|
||||||
background: new Color('#ffffff', 'light'),
|
background: new Color('#ffffff', 'light'),
|
||||||
backgroundHighlight: new Color('hsl(210,15%,92%)', 'light'),
|
backgroundHighlight: new Color('hsl(210,15%,92%)', 'light'),
|
||||||
backgroundHighlightSecondary: new Color('hsl(210,29%,94%)', 'light'),
|
backgroundHighlightSecondary: new Color('hsl(210,29%,94%)', 'light'),
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { Color } from '../color';
|
|
||||||
import type { YaakTheme } from '../window';
|
|
||||||
|
|
||||||
const relaxingDefault: YaakTheme = {
|
|
||||||
name: 'Relaxing',
|
|
||||||
id: 'relaxing',
|
|
||||||
background: new Color('#2b1e3b', 'dark'),
|
|
||||||
foreground: new Color('#ede2f5', 'dark'),
|
|
||||||
colors: {
|
|
||||||
primary: new Color('#cba6f7', 'dark'),
|
|
||||||
secondary: new Color('#bac2de', 'dark'),
|
|
||||||
info: new Color('#89b4fa', 'dark'),
|
|
||||||
success: new Color('#a6e3a1', 'dark'),
|
|
||||||
notice: new Color('#f9e2af', 'dark'),
|
|
||||||
warning: new Color('#fab387', 'dark'),
|
|
||||||
danger: new Color('#f38ba8', 'dark'),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const relaxing = [relaxingDefault];
|
|
||||||
@@ -3,7 +3,7 @@ import type { YaakTheme } from '../window';
|
|||||||
|
|
||||||
export const yaakLight: YaakTheme = {
|
export const yaakLight: YaakTheme = {
|
||||||
id: 'yaak-light',
|
id: 'yaak-light',
|
||||||
name: 'Yaak',
|
name: 'Yaak Light',
|
||||||
background: new Color('hsl(216,24%,100%)', 'light'),
|
background: new Color('hsl(216,24%,100%)', 'light'),
|
||||||
backgroundHighlight: new Color('hsl(216,24%,93%)', 'light'),
|
backgroundHighlight: new Color('hsl(216,24%,93%)', 'light'),
|
||||||
backgroundHighlightSecondary: new Color('hsl(216,24%,87%)', 'light'),
|
backgroundHighlightSecondary: new Color('hsl(216,24%,87%)', 'light'),
|
||||||
@@ -30,7 +30,7 @@ export const yaakLight: YaakTheme = {
|
|||||||
|
|
||||||
export const yaakDark: YaakTheme = {
|
export const yaakDark: YaakTheme = {
|
||||||
id: 'yaak-dark',
|
id: 'yaak-dark',
|
||||||
name: 'Yaak',
|
name: 'Yaak Dark',
|
||||||
background: new Color('hsl(244,23%,14%)', 'dark'),
|
background: new Color('hsl(244,23%,14%)', 'dark'),
|
||||||
backgroundHighlight: new Color('hsl(244,23%,23%)', 'dark'),
|
backgroundHighlight: new Color('hsl(244,23%,23%)', 'dark'),
|
||||||
backgroundHighlightSecondary: new Color('hsl(244,23%,20%)', 'dark'),
|
backgroundHighlightSecondary: new Color('hsl(244,23%,20%)', 'dark'),
|
||||||
|
|||||||
Reference in New Issue
Block a user