mirror of
https://github.com/mountain-loop/yaak.git
synced 2025-12-05 19:17:44 -06:00
Compare commits
6 Commits
095af8cf4b
...
7b78fac24e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b78fac24e | ||
|
|
6534b3f622 | ||
|
|
daba21fbca | ||
|
|
3b99ea1cad | ||
|
|
937d7aa72a | ||
|
|
5bf7278479 |
@@ -7,7 +7,7 @@
|
||||
"scripts": {
|
||||
"build": "run-s build:*",
|
||||
"build:1-build": "yaakcli build",
|
||||
"build:2-cpywasm": "cp \"../../node_modules/@1password/sdk-core/nodejs/core_bg.wasm\" build/",
|
||||
"build:2-cpywasm": "cpx \"../../node_modules/@1password/sdk-core/nodejs/core_bg.wasm\" build/",
|
||||
"dev": "yaakcli dev"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -7,7 +7,7 @@ if (version.startsWith('wasm-pack ')) {
|
||||
}
|
||||
|
||||
console.log('Installing wasm-pack via cargo...');
|
||||
execSync('cargo install wasm-pack', { stdio: 'inherit' });
|
||||
execSync('cargo install wasm-pack --locked', { stdio: 'inherit' });
|
||||
|
||||
function tryExecSync(cmd) {
|
||||
try {
|
||||
|
||||
@@ -31,6 +31,7 @@ use tokio::time;
|
||||
use yaak_common::window::WorkspaceWindowTrait;
|
||||
use yaak_grpc::manager::GrpcHandle;
|
||||
use yaak_grpc::{Code, ServiceDefinition, serialize_message};
|
||||
use yaak_mac_window::AppHandleMacWindowExt;
|
||||
use yaak_models::models::{
|
||||
AnyModel, CookieJar, Environment, GrpcConnection, GrpcConnectionState, GrpcEvent,
|
||||
GrpcEventType, GrpcRequest, HttpRequest, HttpResponse, HttpResponseState, Plugin, Workspace,
|
||||
@@ -1321,7 +1322,13 @@ pub fn run() {
|
||||
}))
|
||||
.plugin(tauri_plugin_clipboard_manager::init())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
// Don't restore StateFlags::DECORATIONS because we want to be able to toggle them on/off on a restart
|
||||
// We could* make this work if we toggled them in the frontend before the window closes, but, this is nicer.
|
||||
.plugin(
|
||||
tauri_plugin_window_state::Builder::new()
|
||||
.with_state_flags(StateFlags::all() - StateFlags::DECORATIONS)
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
@@ -1391,6 +1398,10 @@ pub fn run() {
|
||||
let grpc_handle = GrpcHandle::new(&app.app_handle());
|
||||
app.manage(Mutex::new(grpc_handle));
|
||||
|
||||
// Specific settings
|
||||
let settings = app.db().get_settings();
|
||||
app.app_handle().set_native_titlebar(settings.use_native_titlebar);
|
||||
|
||||
monitor_plugin_events(&app.app_handle().clone());
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::history::get_or_upsert_launch_info;
|
||||
use chrono::{DateTime, Utc};
|
||||
use log::{debug, info};
|
||||
use reqwest::Method;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::time::Instant;
|
||||
use tauri::{AppHandle, Emitter, Manager, Runtime, WebviewWindow};
|
||||
use ts_rs::TS;
|
||||
use yaak_common::api_client::yaak_api_client;
|
||||
@@ -21,7 +20,7 @@ const KV_KEY: &str = "seen";
|
||||
|
||||
// Create updater struct
|
||||
pub struct YaakNotifier {
|
||||
last_check: SystemTime,
|
||||
last_check: Option<Instant>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, TS)]
|
||||
@@ -47,9 +46,7 @@ pub struct YaakNotificationAction {
|
||||
|
||||
impl YaakNotifier {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_check: SystemTime::UNIX_EPOCH,
|
||||
}
|
||||
Self { last_check: None }
|
||||
}
|
||||
|
||||
pub async fn seen<R: Runtime>(&mut self, window: &WebviewWindow<R>, id: &str) -> Result<()> {
|
||||
@@ -69,13 +66,13 @@ impl YaakNotifier {
|
||||
|
||||
pub async fn maybe_check<R: Runtime>(&mut self, window: &WebviewWindow<R>) -> Result<()> {
|
||||
let app_handle = window.app_handle();
|
||||
let ignore_check = self.last_check.elapsed().unwrap().as_secs() < MAX_UPDATE_CHECK_SECONDS;
|
||||
|
||||
if ignore_check {
|
||||
if let Some(i) = self.last_check
|
||||
&& i.elapsed().as_secs() < MAX_UPDATE_CHECK_SECONDS
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.last_check = SystemTime::now();
|
||||
self.last_check = Some(Instant::now());
|
||||
|
||||
if !app_handle.db().get_settings().check_notifications {
|
||||
info!("Notifications are disabled. Skipping check.");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::path::PathBuf;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::error::Result;
|
||||
use log::{debug, error, info, warn};
|
||||
@@ -24,7 +24,7 @@ const MAX_UPDATE_CHECK_HOURS_ALPHA: u64 = 1;
|
||||
|
||||
// Create updater struct
|
||||
pub struct YaakUpdater {
|
||||
last_update_check: SystemTime,
|
||||
last_check: Option<Instant>,
|
||||
}
|
||||
|
||||
pub enum UpdateMode {
|
||||
@@ -62,9 +62,7 @@ pub enum UpdateTrigger {
|
||||
|
||||
impl YaakUpdater {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
last_update_check: SystemTime::UNIX_EPOCH,
|
||||
}
|
||||
Self { last_check: None }
|
||||
}
|
||||
|
||||
pub async fn check_now<R: Runtime>(
|
||||
@@ -84,7 +82,7 @@ impl YaakUpdater {
|
||||
|
||||
let settings = window.db().get_settings();
|
||||
let update_key = format!("{:x}", md5::compute(settings.id));
|
||||
self.last_update_check = SystemTime::now();
|
||||
self.last_check = Some(Instant::now());
|
||||
|
||||
info!("Checking for updates mode={} autodl={}", mode, auto_download);
|
||||
|
||||
@@ -176,9 +174,10 @@ impl YaakUpdater {
|
||||
UpdateMode::Beta => MAX_UPDATE_CHECK_HOURS_BETA,
|
||||
UpdateMode::Alpha => MAX_UPDATE_CHECK_HOURS_ALPHA,
|
||||
} * (60 * 60);
|
||||
let seconds_since_last_check = self.last_update_check.elapsed().unwrap().as_secs();
|
||||
let ignore_check = seconds_since_last_check < update_period_seconds;
|
||||
if ignore_check {
|
||||
|
||||
if let Some(i) = self.last_check
|
||||
&& i.elapsed().as_secs() < update_period_seconds
|
||||
{
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ use tauri::{
|
||||
};
|
||||
use tauri_plugin_opener::OpenerExt;
|
||||
use tokio::sync::mpsc;
|
||||
use yaak_models::query_manager::QueryManagerExt;
|
||||
|
||||
const DEFAULT_WINDOW_WIDTH: f64 = 1100.0;
|
||||
const DEFAULT_WINDOW_HEIGHT: f64 = 600.0;
|
||||
@@ -94,7 +95,8 @@ pub(crate) fn create_window<R: Runtime>(
|
||||
});
|
||||
}
|
||||
|
||||
if config.hide_titlebar {
|
||||
let settings = handle.db().get_settings();
|
||||
if config.hide_titlebar && !settings.use_native_titlebar {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
use tauri::TitleBarStyle;
|
||||
@@ -102,7 +104,6 @@ pub(crate) fn create_window<R: Runtime>(
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
// Doesn't seem to work from Rust, here, so we do it in main.tsx
|
||||
win_builder = win_builder.decorations(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ pub fn app_menu<R: Runtime>(app_handle: &AppHandle<R>) -> tauri::Result<Menu<R>>
|
||||
],
|
||||
)?;
|
||||
|
||||
#[cfg(target_os = "macos")] {
|
||||
window_menu.set_as_windows_menu_for_nsapp()?;
|
||||
}
|
||||
|
||||
let help_menu = Submenu::with_id_and_items(
|
||||
app_handle,
|
||||
HELP_SUBMENU_ID,
|
||||
@@ -44,6 +48,10 @@ pub fn app_menu<R: Runtime>(app_handle: &AppHandle<R>) -> tauri::Result<Menu<R>>
|
||||
],
|
||||
)?;
|
||||
|
||||
#[cfg(target_os = "macos")] {
|
||||
help_menu.set_as_windows_menu_for_nsapp()?;
|
||||
}
|
||||
|
||||
let menu = Menu::with_items(
|
||||
app_handle,
|
||||
&[
|
||||
|
||||
@@ -1,16 +1,38 @@
|
||||
use crate::error::Error::GitNotFound;
|
||||
use crate::error::Result;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
use crate::error::Result;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
use std::os::windows::process::CommandExt;
|
||||
use crate::error::Error::GitNotFound;
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
pub(crate) fn new_binary_command(dir: &Path) -> Result<Command> {
|
||||
let status = Command::new("git").arg("--version").status();
|
||||
// 1. Probe that `git` exists and is runnable
|
||||
let mut probe = Command::new("git");
|
||||
probe.arg("--version").stdin(Stdio::null()).stdout(Stdio::null()).stderr(Stdio::null());
|
||||
|
||||
if let Err(_) = status {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
probe.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
let status = probe.status().map_err(|_| GitNotFound)?;
|
||||
|
||||
if !status.success() {
|
||||
return Err(GitNotFound);
|
||||
}
|
||||
|
||||
// 2. Build the reusable git command
|
||||
let mut cmd = Command::new("git");
|
||||
cmd.arg("-C").arg(dir);
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
|
||||
Ok(cmd)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,32 @@ mod commands;
|
||||
mod mac;
|
||||
|
||||
use crate::commands::{set_theme, set_title};
|
||||
use tauri::{
|
||||
Runtime, generate_handler,
|
||||
plugin::{Builder, TauriPlugin},
|
||||
};
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use tauri::{generate_handler, plugin, plugin::TauriPlugin, Manager, Runtime};
|
||||
|
||||
pub trait AppHandleMacWindowExt {
|
||||
/// Sets whether to use the native titlebar
|
||||
fn set_native_titlebar(&self, enable: bool);
|
||||
}
|
||||
|
||||
impl<R: Runtime> AppHandleMacWindowExt for tauri::AppHandle<R> {
|
||||
fn set_native_titlebar(&self, enable: bool) {
|
||||
self.state::<PluginState>().native_titlebar.store(enable, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PluginState {
|
||||
native_titlebar: AtomicBool,
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
#[allow(unused)]
|
||||
Builder::new("yaak-mac-window")
|
||||
plugin::Builder::new("yaak-mac-window")
|
||||
.setup(move |app, _| {
|
||||
app.manage(PluginState { native_titlebar: AtomicBool::new(false) });
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(generate_handler![set_title, set_theme])
|
||||
.on_window_ready(|window| {
|
||||
.on_window_ready(move |window| {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
mac::setup_traffic_light_positioner(&window);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#![allow(deprecated)]
|
||||
use crate::PluginState;
|
||||
use csscolorparser::Color;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use tauri::{Emitter, Runtime, Window};
|
||||
use tauri::{Emitter, Manager, Runtime, State, Window};
|
||||
|
||||
struct UnsafeWindowHandle(*mut std::ffi::c_void);
|
||||
|
||||
@@ -16,6 +17,8 @@ const MAIN_WINDOW_PREFIX: &str = "main_";
|
||||
pub(crate) fn update_window_title<R: Runtime>(window: Window<R>, title: String) {
|
||||
use cocoa::{appkit::NSWindow, base::nil, foundation::NSString};
|
||||
|
||||
let state: State<PluginState> = window.state();
|
||||
let native_titlebar = state.native_titlebar.load(std::sync::atomic::Ordering::Relaxed);
|
||||
unsafe {
|
||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
||||
|
||||
@@ -25,12 +28,16 @@ pub(crate) fn update_window_title<R: Runtime>(window: Window<R>, title: String)
|
||||
let win_title = NSString::alloc(nil).init_str(&title);
|
||||
let handle = window_handle;
|
||||
NSWindow::setTitle_(handle.0 as cocoa::base::id, win_title);
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
if !native_titlebar {
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(
|
||||
window2.ns_window().expect("Failed to create window handle"),
|
||||
),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -42,6 +49,8 @@ pub(crate) fn update_window_theme<R: Runtime>(window: Window<R>, color: Color) {
|
||||
|
||||
let brightness = (color.r as f64 + color.g as f64 + color.b as f64) / 3.0;
|
||||
let label = window.label().to_string();
|
||||
let state: State<PluginState> = window.state();
|
||||
let native_titlebar = state.native_titlebar.load(std::sync::atomic::Ordering::Relaxed);
|
||||
|
||||
unsafe {
|
||||
let window_handle = UnsafeWindowHandle(window.ns_window().unwrap());
|
||||
@@ -56,12 +65,16 @@ pub(crate) fn update_window_theme<R: Runtime>(window: Window<R>, color: Color) {
|
||||
};
|
||||
|
||||
NSWindow::setAppearance(handle.0 as cocoa::base::id, selected_appearance);
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window2.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
if !native_titlebar {
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(
|
||||
window2.ns_window().expect("Failed to create window handle"),
|
||||
),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
label,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -119,6 +132,11 @@ pub fn setup_traffic_light_positioner<R: Runtime>(window: &Window<R>) {
|
||||
use rand::distr::Alphanumeric;
|
||||
use std::ffi::c_void;
|
||||
|
||||
let state: State<PluginState> = window.state();
|
||||
if state.native_titlebar.load(std::sync::atomic::Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(window.ns_window().expect("Failed to create window handle")),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
|
||||
@@ -62,7 +62,7 @@ export type ProxySetting = { "type": "enabled", http: string, https: string, aut
|
||||
|
||||
export type ProxySettingAuth = { user: string, password: string, };
|
||||
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, };
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, coloredMethods: boolean, editorFont: string | null, editorFontSize: number, editorKeymap: EditorKeymap, editorSoftWrap: boolean, hideWindowControls: boolean, useNativeTitlebar: boolean, interfaceFont: string | null, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, hideLicenseBadge: boolean, autoupdate: boolean, autoDownloadUpdates: boolean, checkNotifications: boolean, };
|
||||
|
||||
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add a setting to force native window title bar / controls
|
||||
ALTER TABLE settings
|
||||
ADD COLUMN use_native_titlebar BOOLEAN DEFAULT FALSE NOT NULL;
|
||||
@@ -112,6 +112,8 @@ pub struct Settings {
|
||||
pub editor_keymap: EditorKeymap,
|
||||
pub editor_soft_wrap: bool,
|
||||
pub hide_window_controls: bool,
|
||||
// When true (primarily on Windows/Linux), use the native OS window title bar and controls
|
||||
pub use_native_titlebar: bool,
|
||||
pub interface_font: Option<String>,
|
||||
pub interface_font_size: i32,
|
||||
pub interface_scale: f32,
|
||||
@@ -168,6 +170,7 @@ impl UpsertModelInfo for Settings {
|
||||
(InterfaceFontSize, self.interface_font_size.into()),
|
||||
(InterfaceScale, self.interface_scale.into()),
|
||||
(HideWindowControls, self.hide_window_controls.into()),
|
||||
(UseNativeTitlebar, self.use_native_titlebar.into()),
|
||||
(OpenWorkspaceNewWindow, self.open_workspace_new_window.into()),
|
||||
(ThemeDark, self.theme_dark.as_str().into()),
|
||||
(ThemeLight, self.theme_light.as_str().into()),
|
||||
@@ -193,6 +196,7 @@ impl UpsertModelInfo for Settings {
|
||||
SettingsIden::InterfaceScale,
|
||||
SettingsIden::InterfaceFont,
|
||||
SettingsIden::HideWindowControls,
|
||||
SettingsIden::UseNativeTitlebar,
|
||||
SettingsIden::OpenWorkspaceNewWindow,
|
||||
SettingsIden::Proxy,
|
||||
SettingsIden::ThemeDark,
|
||||
@@ -225,6 +229,7 @@ impl UpsertModelInfo for Settings {
|
||||
interface_font_size: row.get("interface_font_size")?,
|
||||
interface_scale: row.get("interface_scale")?,
|
||||
interface_font: row.get("interface_font")?,
|
||||
use_native_titlebar: row.get("use_native_titlebar")?,
|
||||
open_workspace_new_window: row.get("open_workspace_new_window")?,
|
||||
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
|
||||
theme_dark: row.get("theme_dark")?,
|
||||
|
||||
@@ -26,6 +26,7 @@ impl<'a> DbContext<'a> {
|
||||
interface_scale: 1.0,
|
||||
interface_font: None,
|
||||
hide_window_controls: false,
|
||||
use_native_titlebar: false,
|
||||
open_workspace_new_window: None,
|
||||
proxy: None,
|
||||
theme_dark: "yaak-dark".to_string(),
|
||||
|
||||
@@ -27,6 +27,7 @@ export function HeaderSize({
|
||||
}: HeaderSizeProps) {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const isFullscreen = useIsFullscreen();
|
||||
const nativeTitlebar = settings.useNativeTitlebar;
|
||||
const finalStyle = useMemo<CSSProperties>(() => {
|
||||
const s = { ...style };
|
||||
|
||||
@@ -34,7 +35,9 @@ export function HeaderSize({
|
||||
if (size === 'md') s.minHeight = HEADER_SIZE_MD;
|
||||
if (size === 'lg') s.minHeight = HEADER_SIZE_LG;
|
||||
|
||||
if (type() === 'macos') {
|
||||
if (nativeTitlebar) {
|
||||
// No style updates when using native titlebar
|
||||
} else if (type() === 'macos') {
|
||||
if (!isFullscreen) {
|
||||
// Add large padding for window controls
|
||||
s.paddingLeft = 72 / settings.interfaceScale;
|
||||
@@ -51,6 +54,7 @@ export function HeaderSize({
|
||||
settings.interfaceScale,
|
||||
size,
|
||||
style,
|
||||
nativeTitlebar,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -73,7 +77,7 @@ export function HeaderSize({
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{!hideControls && <WindowControls onlyX={onlyXWindowControl} />}
|
||||
{!hideControls && !nativeTitlebar && <WindowControls onlyX={onlyXWindowControl} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,14 @@ import { useLicense } from '@yaakapp-internal/license';
|
||||
import type { EditorKeymap, Settings } from '@yaakapp-internal/models';
|
||||
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
|
||||
import { clamp } from '../../lib/clamp';
|
||||
import { showConfirm } from '../../lib/confirm';
|
||||
import { invokeCmd } from '../../lib/tauri';
|
||||
import { CargoFeature } from '../CargoFeature';
|
||||
import { Button } from '../core/Button';
|
||||
import { Checkbox } from '../core/Checkbox';
|
||||
import { Icon } from '../core/Icon';
|
||||
import { Link } from '../core/Link';
|
||||
@@ -154,6 +157,8 @@ export function SettingsInterface() {
|
||||
<LicenseSettings settings={settings} />
|
||||
</CargoFeature>
|
||||
|
||||
<NativeTitlebarSetting settings={settings} />
|
||||
|
||||
{type() !== 'macos' && (
|
||||
<Checkbox
|
||||
checked={settings.hideWindowControls}
|
||||
@@ -165,6 +170,33 @@ export function SettingsInterface() {
|
||||
</VStack>
|
||||
);
|
||||
}
|
||||
|
||||
function NativeTitlebarSetting({ settings }: { settings: Settings }) {
|
||||
const [nativeTitlebar, setNativeTitlebar] = useState(settings.useNativeTitlebar);
|
||||
return (
|
||||
<div className="flex gap-1 overflow-hidden h-2xs">
|
||||
<Checkbox
|
||||
checked={nativeTitlebar}
|
||||
title="Native title bar"
|
||||
help="Use the operating system's standard title bar and window controls"
|
||||
onChange={setNativeTitlebar}
|
||||
/>
|
||||
{settings.useNativeTitlebar !== nativeTitlebar && (
|
||||
<Button
|
||||
color="primary"
|
||||
size="2xs"
|
||||
onClick={async () => {
|
||||
await patchModel(settings, { useNativeTitlebar: nativeTitlebar });
|
||||
await invokeCmd('cmd_restart');
|
||||
}}
|
||||
>
|
||||
Apply and Restart
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LicenseSettings({ settings }: { settings: Settings }) {
|
||||
const license = useLicense();
|
||||
if (license.check.data?.type !== 'personal_use') {
|
||||
|
||||
@@ -18,7 +18,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
||||
const [maximized, setMaximized] = useState<boolean>(false);
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
// Never show controls on macOS or if hideWindowControls is true
|
||||
if (type() === 'macos' || settings.hideWindowControls) {
|
||||
if (type() === 'macos' || settings.hideWindowControls || settings.useNativeTitlebar) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import './main.css';
|
||||
import { RouterProvider } from '@tanstack/react-router';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import { changeModelStoreWorkspace, initModelStore } from '@yaakapp-internal/models';
|
||||
import { StrictMode } from 'react';
|
||||
@@ -10,12 +9,7 @@ import { initGlobalListeners } from './lib/initGlobalListeners';
|
||||
import { jotaiStore } from './lib/jotai';
|
||||
import { router } from './lib/router';
|
||||
|
||||
// Hide decorations here because it doesn't work in Rust for some reason (bug?)
|
||||
const osType = type();
|
||||
if (osType !== 'macos') {
|
||||
await getCurrentWebviewWindow().setDecorations(false);
|
||||
}
|
||||
|
||||
document.documentElement.setAttribute('data-platform', osType);
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
|
||||
Reference in New Issue
Block a user