mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-03-22 01:19:13 +01:00
Allow disabling window decorations/controls (#176)
Co-authored-by: Gregory Schier <gschier1990@gmail.com>
This commit is contained in:
committed by
GitHub
parent
432b366105
commit
be82b67ed3
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE settings
|
||||
ADD COLUMN hide_window_controls BOOLEAN DEFAULT FALSE NOT NULL;
|
||||
@@ -58,7 +58,7 @@ export type ProxySetting = { "type": "enabled", disabled: boolean, http: string,
|
||||
|
||||
export type ProxySettingAuth = { user: string, password: string, };
|
||||
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, };
|
||||
export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, hideWindowControls: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, proxy: ProxySetting | null, themeDark: string, themeLight: string, updateChannel: string, editorKeymap: EditorKeymap, };
|
||||
|
||||
export type SyncState = { model: "sync_state", id: string, workspaceId: string, createdAt: string, updatedAt: string, flushedAt: string, modelId: string, checksum: string, relPath: string, syncDir: string, };
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Environment } from "./gen_models";
|
||||
import type { Folder } from "./gen_models";
|
||||
import type { GrpcRequest } from "./gen_models";
|
||||
import type { HttpRequest } from "./gen_models";
|
||||
import type { WebsocketRequest } from "./gen_models";
|
||||
import type { Workspace } from "./gen_models";
|
||||
import type { Environment } from "./gen_models.js";
|
||||
import type { Folder } from "./gen_models.js";
|
||||
import type { GrpcRequest } from "./gen_models.js";
|
||||
import type { HttpRequest } from "./gen_models.js";
|
||||
import type { WebsocketRequest } from "./gen_models.js";
|
||||
import type { Workspace } from "./gen_models.js";
|
||||
|
||||
export type BatchUpsertResult = { workspaces: Array<Workspace>, environments: Array<Environment>, folders: Array<Folder>, httpRequests: Array<HttpRequest>, grpcRequests: Array<GrpcRequest>, websocketRequests: Array<WebsocketRequest>, };
|
||||
|
||||
@@ -105,6 +105,7 @@ pub struct Settings {
|
||||
pub appearance: String,
|
||||
pub editor_font_size: i32,
|
||||
pub editor_soft_wrap: bool,
|
||||
pub hide_window_controls: bool,
|
||||
pub interface_font_size: i32,
|
||||
pub interface_scale: f32,
|
||||
pub open_workspace_new_window: Option<bool>,
|
||||
@@ -154,6 +155,7 @@ impl UpsertModelInfo for Settings {
|
||||
(EditorSoftWrap, self.editor_soft_wrap.into()),
|
||||
(InterfaceFontSize, self.interface_font_size.into()),
|
||||
(InterfaceScale, self.interface_scale.into()),
|
||||
(HideWindowControls, self.hide_window_controls.into()),
|
||||
(OpenWorkspaceNewWindow, self.open_workspace_new_window.into()),
|
||||
(ThemeDark, self.theme_dark.as_str().into()),
|
||||
(ThemeLight, self.theme_light.as_str().into()),
|
||||
@@ -171,6 +173,7 @@ impl UpsertModelInfo for Settings {
|
||||
SettingsIden::EditorSoftWrap,
|
||||
SettingsIden::InterfaceFontSize,
|
||||
SettingsIden::InterfaceScale,
|
||||
SettingsIden::HideWindowControls,
|
||||
SettingsIden::OpenWorkspaceNewWindow,
|
||||
SettingsIden::Proxy,
|
||||
SettingsIden::ThemeDark,
|
||||
@@ -200,6 +203,7 @@ impl UpsertModelInfo for Settings {
|
||||
proxy: proxy.map(|p| -> ProxySetting { serde_json::from_str(p.as_str()).unwrap() }),
|
||||
theme_dark: row.get("theme_dark")?,
|
||||
theme_light: row.get("theme_light")?,
|
||||
hide_window_controls: row.get("hide_window_controls")?,
|
||||
update_channel: row.get("update_channel")?,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ impl<'a> DbContext<'a> {
|
||||
editor_soft_wrap: true,
|
||||
interface_font_size: 15,
|
||||
interface_scale: 1.0,
|
||||
hide_window_controls: false,
|
||||
open_workspace_new_window: None,
|
||||
proxy: None,
|
||||
theme_dark: "yaak-dark".to_string(),
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import type { HTMLAttributes, ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import type { CSSProperties, HTMLAttributes, ReactNode } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
|
||||
import { HEADER_SIZE_LG, HEADER_SIZE_MD, WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
||||
import { WindowControls } from './WindowControls';
|
||||
@@ -23,27 +22,42 @@ export function HeaderSize({
|
||||
onlyXWindowControl,
|
||||
children,
|
||||
}: HeaderSizeProps) {
|
||||
const osInfo = useOsInfo();
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
const stoplightsVisible = useStoplightsVisible();
|
||||
const finalStyle = useMemo<CSSProperties>(() => {
|
||||
const s = { ...style };
|
||||
|
||||
// Set the height (use min-height because scaling font size may make it larger
|
||||
if (size === 'md') s.minHeight = HEADER_SIZE_MD;
|
||||
if (size === 'lg') s.minHeight = HEADER_SIZE_LG;
|
||||
|
||||
// Add large padding for window controls
|
||||
if (stoplightsVisible && !ignoreControlsSpacing) {
|
||||
s.paddingLeft = 72 / settings.interfaceScale;
|
||||
} else if (!stoplightsVisible && !ignoreControlsSpacing && !settings.hideWindowControls) {
|
||||
s.paddingRight = WINDOW_CONTROLS_WIDTH;
|
||||
}
|
||||
|
||||
return s;
|
||||
}, [
|
||||
ignoreControlsSpacing,
|
||||
settings.hideWindowControls,
|
||||
settings.interfaceScale,
|
||||
size,
|
||||
stoplightsVisible,
|
||||
style,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
style={{
|
||||
...style,
|
||||
// Add padding for macOS stoplights, but keep it the same width (account for the interface scale)
|
||||
paddingLeft:
|
||||
stoplightsVisible && !ignoreControlsSpacing ? 72 / settings.interfaceScale : undefined,
|
||||
...(size === 'md' ? { minHeight: HEADER_SIZE_MD } : {}),
|
||||
...(size === 'lg' ? { minHeight: HEADER_SIZE_LG } : {}),
|
||||
...(osInfo.osType === 'macos' || ignoreControlsSpacing
|
||||
? { paddingRight: '2px' }
|
||||
: { paddingLeft: '2px', paddingRight: WINDOW_CONTROLS_WIDTH }),
|
||||
}}
|
||||
style={finalStyle}
|
||||
className={classNames(
|
||||
className,
|
||||
'px-1', // Give it some space on either end
|
||||
'pt-[1px]', // Make up for bottom border
|
||||
'select-none relative',
|
||||
'pt-[1px] w-full border-b border-border-subtle min-w-0',
|
||||
'w-full border-b border-border-subtle min-w-0',
|
||||
)}
|
||||
>
|
||||
{/* NOTE: This needs display:grid or else the element shrinks (even though scrollable) */}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useSearch } from '@tanstack/react-router';
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import classNames from 'classnames';
|
||||
import React, { useState } from 'react';
|
||||
import { useKeyPressEvent } from 'react-use';
|
||||
import { useOsInfo } from '../../hooks/useOsInfo';
|
||||
import { capitalize } from '../../lib/capitalize';
|
||||
import { HStack } from '../core/Stacks';
|
||||
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
||||
@@ -27,7 +27,6 @@ const tabs = [TAB_GENERAL, TAB_APPEARANCE, TAB_PROXY, TAB_PLUGINS, TAB_LICENSE]
|
||||
export type SettingsTab = (typeof tabs)[number];
|
||||
|
||||
export default function Settings({ hide }: Props) {
|
||||
const osInfo = useOsInfo();
|
||||
const { tab: tabFromQuery } = useSearch({ from: '/workspaces/$workspaceId/settings' });
|
||||
const [tab, setTab] = useState<string | undefined>(tabFromQuery);
|
||||
|
||||
@@ -60,9 +59,7 @@ export default function Settings({ hide }: Props) {
|
||||
justifyContent="center"
|
||||
className="w-full h-full grid grid-cols-[1fr_auto] pointer-events-none"
|
||||
>
|
||||
<div className={classNames(osInfo?.osType === 'macos' ? 'text-center' : 'pl-2')}>
|
||||
Settings
|
||||
</div>
|
||||
<div className={classNames(type() === 'macos' ? 'text-center' : 'pl-2')}>Settings</div>
|
||||
</HStack>
|
||||
</HeaderSize>
|
||||
)}
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { SelectProps } from '../core/Select';
|
||||
import { Select } from '../core/Select';
|
||||
import { Separator } from '../core/Separator';
|
||||
import { HStack, VStack } from '../core/Stacks';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
|
||||
const fontSizeOptions = [
|
||||
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
|
||||
@@ -122,6 +123,15 @@ export function SettingsAppearance() {
|
||||
onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })}
|
||||
/>
|
||||
|
||||
{type() !== 'macos' && (
|
||||
<Checkbox
|
||||
checked={settings.hideWindowControls}
|
||||
title="Hide Window Controls"
|
||||
help="Hide the close/maximize/minimize controls on Windows or Linux"
|
||||
onChange={(hideWindowControls) => patchModel(settings, { hideWindowControls })}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<Select
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import { settingsAtom } from '@yaakapp-internal/models';
|
||||
import classNames from 'classnames';
|
||||
import { useAtomValue } from 'jotai';
|
||||
import React, { useState } from 'react';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import {WINDOW_CONTROLS_WIDTH} from "../lib/constants";
|
||||
import { WINDOW_CONTROLS_WIDTH } from '../lib/constants';
|
||||
import { Button } from './core/Button';
|
||||
import { HStack } from './core/Stacks';
|
||||
|
||||
@@ -14,10 +16,9 @@ interface Props {
|
||||
|
||||
export function WindowControls({ className, onlyX }: Props) {
|
||||
const [maximized, setMaximized] = useState<boolean>(false);
|
||||
const osInfo = useOsInfo();
|
||||
|
||||
// Never show controls on macOS
|
||||
if (osInfo.osType === 'macos') {
|
||||
const settings = useAtomValue(settingsAtom);
|
||||
// Never show controls on macOS or if hideWindowControls is true
|
||||
if (type() === 'macos' || settings.hideWindowControls) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,10 @@ export function Checkbox({
|
||||
<HStack
|
||||
as="label"
|
||||
alignItems="center"
|
||||
space={3}
|
||||
space={2}
|
||||
className={classNames(className, 'text-text mr-auto')}
|
||||
>
|
||||
<div className={classNames(inputWrapperClassName, 'x-theme-input', 'relative flex')}>
|
||||
<div className={classNames(inputWrapperClassName, 'x-theme-input', 'relative flex mr-0.5')}>
|
||||
<input
|
||||
aria-hidden
|
||||
className={classNames(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import type { HotkeyAction } from '../../hooks/useHotKey';
|
||||
import { useFormattedHotkey } from '../../hooks/useHotKey';
|
||||
import { useOsInfo } from '../../hooks/useOsInfo';
|
||||
import { HStack } from './Stacks';
|
||||
|
||||
interface Props {
|
||||
@@ -11,9 +10,8 @@ interface Props {
|
||||
}
|
||||
|
||||
export function HotKey({ action, className, variant }: Props) {
|
||||
const osInfo = useOsInfo();
|
||||
const labelParts = useFormattedHotkey(action);
|
||||
if (labelParts === null || osInfo == null) {
|
||||
if (labelParts === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import classNames from 'classnames';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { useOsInfo } from '../../hooks/useOsInfo';
|
||||
import type { ButtonProps } from './Button';
|
||||
import { Button } from './Button';
|
||||
import { Label } from './Label';
|
||||
import type { RadioDropdownItem } from './RadioDropdown';
|
||||
import { RadioDropdown } from './RadioDropdown';
|
||||
import { HStack } from './Stacks';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
|
||||
export interface SelectProps<T extends string> {
|
||||
name: string;
|
||||
@@ -40,7 +40,6 @@ export function Select<T extends string>({
|
||||
defaultValue,
|
||||
size = 'md',
|
||||
}: SelectProps<T>) {
|
||||
const osInfo = useOsInfo();
|
||||
const [focused, setFocused] = useState<boolean>(false);
|
||||
const id = `input-${name}`;
|
||||
const isInvalidSelection = options.find((o) => 'value' in o && o.value === value) == null;
|
||||
@@ -63,7 +62,7 @@ export function Select<T extends string>({
|
||||
<Label htmlFor={id} visuallyHidden={hideLabel} className={labelClassName}>
|
||||
{label}
|
||||
</Label>
|
||||
{osInfo?.osType === 'macos' ? (
|
||||
{type() === 'macos' ? (
|
||||
<HStack
|
||||
space={2}
|
||||
className={classNames(
|
||||
|
||||
@@ -2,7 +2,6 @@ import { type } from '@tauri-apps/plugin-os';
|
||||
import { debounce } from '@yaakapp-internal/lib';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { capitalize } from '../lib/capitalize';
|
||||
import { useOsInfo } from './useOsInfo';
|
||||
|
||||
const HOLD_KEYS = ['Shift', 'Control', 'Command', 'Alt', 'Meta'];
|
||||
|
||||
@@ -176,13 +175,12 @@ export function useHotKeyLabel(action: HotkeyAction): string {
|
||||
}
|
||||
|
||||
export function useFormattedHotkey(action: HotkeyAction | null): string[] | null {
|
||||
const osInfo = useOsInfo();
|
||||
const trigger = action != null ? (hotkeys[action]?.[0] ?? null) : null;
|
||||
if (trigger == null || osInfo == null) {
|
||||
if (trigger == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const os = osInfo.osType;
|
||||
const os = type();
|
||||
const parts = trigger.split('+');
|
||||
const labelParts: string[] = [];
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
|
||||
export function useOsInfo() {
|
||||
return { osType: type() };
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
import { useIsFullscreen } from './useIsFullscreen';
|
||||
import { useOsInfo } from './useOsInfo';
|
||||
|
||||
export function useStoplightsVisible() {
|
||||
const platform = useOsInfo();
|
||||
const fullscreen = useIsFullscreen();
|
||||
const stoplightsVisible = platform?.osType === 'macos' && !fullscreen;
|
||||
const stoplightsVisible = type() === 'macos' && !fullscreen;
|
||||
return stoplightsVisible;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
export const HEADER_SIZE_MD = '27px';
|
||||
export const HEADER_SIZE_LG = '38px';
|
||||
export const HEADER_SIZE_LG = '40px';
|
||||
|
||||
export const WINDOW_CONTROLS_WIDTH = '10.5rem';
|
||||
|
||||
@@ -11,9 +11,9 @@ import { Dialogs } from '../components/Dialogs';
|
||||
import { GlobalHooks } from '../components/GlobalHooks';
|
||||
import RouteError from '../components/RouteError';
|
||||
import { Toasts } from '../components/Toasts';
|
||||
import { useOsInfo } from '../hooks/useOsInfo';
|
||||
import { jotaiStore } from '../lib/jotai';
|
||||
import { queryClient } from '../lib/queryClient';
|
||||
import { type } from '@tauri-apps/plugin-os';
|
||||
|
||||
export const Route = createRootRoute({
|
||||
component: RouteComponent,
|
||||
@@ -21,7 +21,6 @@ export const Route = createRootRoute({
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const osInfo = useOsInfo();
|
||||
return (
|
||||
<JotaiProvider store={jotaiStore}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
@@ -36,7 +35,7 @@ function RouteComponent() {
|
||||
<div
|
||||
className={classNames(
|
||||
'w-full h-full',
|
||||
osInfo?.osType === 'linux' && 'border border-border-subtle',
|
||||
type() === 'linux' && 'border border-border-subtle',
|
||||
)}
|
||||
>
|
||||
<Outlet />
|
||||
|
||||
Reference in New Issue
Block a user