mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-25 10:18:31 +02:00
Always show window controls, and open Linux settings in dialog
This commit is contained in:
@@ -1,27 +1,31 @@
|
|||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import type { HTMLAttributes, ReactNode } from 'react';
|
import type { HTMLAttributes, ReactNode } from 'react';
|
||||||
import { useIsFullscreen } from '../hooks/useIsFullscreen';
|
import React from 'react';
|
||||||
import { useOsInfo } from '../hooks/useOsInfo';
|
|
||||||
import { useSettings } from '../hooks/useSettings';
|
import { useSettings } from '../hooks/useSettings';
|
||||||
|
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
|
||||||
|
import { WINDOW_CONTROLS_WIDTH, WindowControls } from './WindowControls';
|
||||||
|
|
||||||
interface HeaderSizeProps extends HTMLAttributes<HTMLDivElement> {
|
interface HeaderSizeProps extends HTMLAttributes<HTMLDivElement> {
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
size: 'md' | 'lg';
|
size: 'md' | 'lg';
|
||||||
ignoreStoplights?: boolean;
|
ignoreControlsSpacing?: boolean;
|
||||||
|
onlyXWindowControl?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const HEADER_SIZE_MD = '27px';
|
||||||
|
export const HEADER_SIZE_LG = '38px';
|
||||||
|
|
||||||
export function HeaderSize({
|
export function HeaderSize({
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
size,
|
size,
|
||||||
ignoreStoplights,
|
ignoreControlsSpacing,
|
||||||
...props
|
onlyXWindowControl,
|
||||||
|
children,
|
||||||
}: HeaderSizeProps) {
|
}: HeaderSizeProps) {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const platform = useOsInfo();
|
const stoplightsVisible = useStoplightsVisible();
|
||||||
const fullscreen = useIsFullscreen();
|
|
||||||
const stoplightsVisible = platform?.osType === 'macos' && !fullscreen && !ignoreStoplights;
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
data-tauri-drag-region
|
data-tauri-drag-region
|
||||||
@@ -33,18 +37,23 @@ export function HeaderSize({
|
|||||||
...style,
|
...style,
|
||||||
// Add padding for macOS stoplights, but keep it the same width (account for the interface scale)
|
// Add padding for macOS stoplights, but keep it the same width (account for the interface scale)
|
||||||
paddingLeft: stoplightsVisible ? 72 / settings.interfaceScale : undefined,
|
paddingLeft: stoplightsVisible ? 72 / settings.interfaceScale : undefined,
|
||||||
|
...(size === 'md' ? { height: HEADER_SIZE_MD } : {}),
|
||||||
|
...(size === 'lg' ? { height: HEADER_SIZE_LG } : {}),
|
||||||
|
...(stoplightsVisible || ignoreControlsSpacing
|
||||||
|
? { paddingRight: '2px' }
|
||||||
|
: { paddingLeft: '2px', paddingRight: WINDOW_CONTROLS_WIDTH }),
|
||||||
}}
|
}}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'select-none',
|
'select-none relative',
|
||||||
'pt-[1px] w-full border-b border-border-subtle min-w-0',
|
'pt-[1px] w-full border-b border-border-subtle min-w-0',
|
||||||
stoplightsVisible ? 'pr-1' : 'pl-1',
|
|
||||||
size === 'md' && 'h-[27px]',
|
|
||||||
size === 'lg' && 'h-[38px]',
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{/* NOTE: This needs display:grid or else the element shrinks (even though scrollable) */}
|
{/* NOTE: This needs display:grid or else the element shrinks (even though scrollable) */}
|
||||||
<div className="h-full w-full overflow-x-auto hide-scrollbars grid" {...props} />
|
<div className="pointer-events-none h-full w-full overflow-x-auto hide-scrollbars grid">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
<WindowControls onlyX={onlyXWindowControl} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ export function RecentRequestsDropdown({ className }: Pick<ButtonProps, 'classNa
|
|||||||
hotkeyAction="request_switcher.toggle"
|
hotkeyAction="request_switcher.toggle"
|
||||||
className={classNames(
|
className={classNames(
|
||||||
className,
|
className,
|
||||||
'max-w-[40vw]',
|
'truncate pointer-events-auto',
|
||||||
'text truncate pointer-events-auto',
|
|
||||||
activeRequest === null && 'text-text-subtlest italic',
|
activeRequest === null && 'text-text-subtlest italic',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -7,11 +7,14 @@ import { capitalize } from '../../lib/capitalize';
|
|||||||
import { HStack } from '../core/Stacks';
|
import { HStack } from '../core/Stacks';
|
||||||
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
import { TabContent, Tabs } from '../core/Tabs/Tabs';
|
||||||
import { HeaderSize } from '../HeaderSize';
|
import { HeaderSize } from '../HeaderSize';
|
||||||
import { WindowControls } from '../WindowControls';
|
|
||||||
import { SettingsAppearance } from './SettingsAppearance';
|
import { SettingsAppearance } from './SettingsAppearance';
|
||||||
import { SettingsGeneral } from './SettingsGeneral';
|
import { SettingsGeneral } from './SettingsGeneral';
|
||||||
import { SettingsPlugins } from './SettingsPlugins';
|
import { SettingsPlugins } from './SettingsPlugins';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
hide?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
enum Tab {
|
enum Tab {
|
||||||
General = 'general',
|
General = 'general',
|
||||||
Appearance = 'appearance',
|
Appearance = 'appearance',
|
||||||
@@ -20,33 +23,45 @@ enum Tab {
|
|||||||
|
|
||||||
const tabs = [Tab.General, Tab.Appearance, Tab.Plugins];
|
const tabs = [Tab.General, Tab.Appearance, Tab.Plugins];
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings({ hide }: Props) {
|
||||||
const osInfo = useOsInfo();
|
const osInfo = useOsInfo();
|
||||||
const [tab, setTab] = useState<string>(Tab.General);
|
const [tab, setTab] = useState<string>(Tab.General);
|
||||||
|
|
||||||
// Close settings window on escape
|
// Close settings window on escape
|
||||||
// TODO: Could this be put in a better place? Eg. in Rust key listener when creating the window
|
// TODO: Could this be put in a better place? Eg. in Rust key listener when creating the window
|
||||||
useKeyPressEvent('Escape', () => getCurrentWebviewWindow().close());
|
useKeyPressEvent('Escape', async () => {
|
||||||
|
if (hide != null) {
|
||||||
|
// It's being shown in a dialog, so close the dialog
|
||||||
|
hide();
|
||||||
|
} else {
|
||||||
|
// It's being shown in a window, so close the window
|
||||||
|
await getCurrentWebviewWindow().close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames('grid grid-rows-[auto_minmax(0,1fr)] h-full')}>
|
<div className={classNames('grid grid-rows-[auto_minmax(0,1fr)] h-full')}>
|
||||||
<HeaderSize
|
{hide ? (
|
||||||
data-tauri-drag-region
|
<span />
|
||||||
ignoreStoplights
|
) : (
|
||||||
size="md"
|
<HeaderSize
|
||||||
className="x-theme-appHeader bg-surface text-text-subtle flex items-center justify-center border-b border-border-subtle text-sm font-semibold"
|
data-tauri-drag-region
|
||||||
>
|
ignoreControlsSpacing
|
||||||
<HStack
|
onlyXWindowControl
|
||||||
space={2}
|
size="md"
|
||||||
justifyContent="center"
|
className="x-theme-appHeader bg-surface text-text-subtle flex items-center justify-center border-b border-border-subtle text-sm font-semibold"
|
||||||
className="w-full h-full grid grid-cols-[1fr_auto] pointer-events-none"
|
|
||||||
>
|
>
|
||||||
<div className={classNames(osInfo?.osType === 'macos' ? 'text-center' : 'pl-2')}>
|
<HStack
|
||||||
Settings
|
space={2}
|
||||||
</div>
|
justifyContent="center"
|
||||||
<WindowControls className="ml-auto" onlyX />
|
className="w-full h-full grid grid-cols-[1fr_auto] pointer-events-none"
|
||||||
</HStack>
|
>
|
||||||
</HeaderSize>
|
<div className={classNames(osInfo?.osType === 'macos' ? 'text-center' : 'pl-2')}>
|
||||||
|
Settings
|
||||||
|
</div>
|
||||||
|
</HStack>
|
||||||
|
</HeaderSize>
|
||||||
|
)}
|
||||||
<Tabs
|
<Tabs
|
||||||
value={tab}
|
value={tab}
|
||||||
addBorders
|
addBorders
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
import { getCurrentWebviewWindow } from '@tauri-apps/api/webviewWindow';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useOsInfo } from '../hooks/useOsInfo';
|
import { useStoplightsVisible } from '../hooks/useStoplightsVisible';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import { HStack } from './core/Stacks';
|
import { HStack } from './core/Stacks';
|
||||||
|
|
||||||
@@ -10,16 +10,21 @@ interface Props {
|
|||||||
onlyX?: boolean;
|
onlyX?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const WINDOW_CONTROLS_WIDTH = '10.5rem';
|
||||||
|
|
||||||
export function WindowControls({ className, onlyX }: Props) {
|
export function WindowControls({ className, onlyX }: Props) {
|
||||||
const [maximized, setMaximized] = useState<boolean>(false);
|
const [maximized, setMaximized] = useState<boolean>(false);
|
||||||
const osInfo = useOsInfo();
|
const stoplightsVisible = useStoplightsVisible();
|
||||||
const shouldShow = osInfo?.osType === 'linux' || osInfo?.osType === 'windows';
|
if (stoplightsVisible) {
|
||||||
if (!shouldShow) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack className={classNames(className, 'ml-4 h-full')}>
|
<HStack
|
||||||
|
className={classNames(className, 'ml-4 absolute right-0 top-0 bottom-0')}
|
||||||
|
justifyContent="end"
|
||||||
|
style={{ width: WINDOW_CONTROLS_WIDTH }}
|
||||||
|
>
|
||||||
{!onlyX && (
|
{!onlyX && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@@ -57,7 +62,7 @@ export function WindowControls({ className, onlyX }: Props) {
|
|||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
color="custom"
|
color="custom"
|
||||||
className="!h-full px-4 text-text-subtle rounded-none hocus:bg-danger hocus:text"
|
className="!h-full px-4 text-text-subtle rounded-none hocus:bg-danger hocus:text-text"
|
||||||
onClick={() => getCurrentWebviewWindow().close()}
|
onClick={() => getCurrentWebviewWindow().close()}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { ImportCurlButton } from './ImportCurlButton';
|
|||||||
import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
import { RecentRequestsDropdown } from './RecentRequestsDropdown';
|
||||||
import { SettingsDropdown } from './SettingsDropdown';
|
import { SettingsDropdown } from './SettingsDropdown';
|
||||||
import { SidebarActions } from './SidebarActions';
|
import { SidebarActions } from './SidebarActions';
|
||||||
import { WindowControls } from './WindowControls';
|
|
||||||
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
import { WorkspaceActionsDropdown } from './WorkspaceActionsDropdown';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -20,17 +19,22 @@ interface Props {
|
|||||||
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
|
export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Props) {
|
||||||
const togglePalette = useToggleCommandPalette();
|
const togglePalette = useToggleCommandPalette();
|
||||||
return (
|
return (
|
||||||
<HStack space={2} justifyContent="center" className={classNames(className, 'w-full h-full')}>
|
<div
|
||||||
|
className={classNames(
|
||||||
|
className,
|
||||||
|
'grid grid-cols-[auto_minmax(0,1fr)_auto_auto] items-center w-full h-full',
|
||||||
|
)}
|
||||||
|
>
|
||||||
<HStack space={0.5} className="flex-1 pointer-events-none">
|
<HStack space={0.5} className="flex-1 pointer-events-none">
|
||||||
<SidebarActions />
|
<SidebarActions />
|
||||||
<CookieDropdown />
|
<CookieDropdown />
|
||||||
<HStack>
|
<HStack className="min-w-0">
|
||||||
<WorkspaceActionsDropdown />
|
<WorkspaceActionsDropdown />
|
||||||
<Icon icon="chevron_right" className="text-text-subtle" />
|
<Icon icon="chevron_right" className="text-text-subtle" />
|
||||||
<EnvironmentActionsDropdown className="w-auto pointer-events-auto" />
|
<EnvironmentActionsDropdown className="w-auto pointer-events-auto" />
|
||||||
</HStack>
|
</HStack>
|
||||||
</HStack>
|
</HStack>
|
||||||
<div className="pointer-events-none">
|
<div className="pointer-events-none w-full max-w-[30vw] mx-auto">
|
||||||
<RecentRequestsDropdown />
|
<RecentRequestsDropdown />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 flex gap-1 items-center h-full justify-end pointer-events-none pr-0.5">
|
<div className="flex-1 flex gap-1 items-center h-full justify-end pointer-events-none pr-0.5">
|
||||||
@@ -42,8 +46,7 @@ export const WorkspaceHeader = memo(function WorkspaceHeader({ className }: Prop
|
|||||||
onClick={togglePalette}
|
onClick={togglePalette}
|
||||||
/>
|
/>
|
||||||
<SettingsDropdown />
|
<SettingsDropdown />
|
||||||
<WindowControls />
|
|
||||||
</div>
|
</div>
|
||||||
</HStack>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +1,31 @@
|
|||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
|
import { useDialog } from '../components/DialogContext';
|
||||||
|
import Settings from '../components/Settings/Settings';
|
||||||
import { invokeCmd } from '../lib/tauri';
|
import { invokeCmd } from '../lib/tauri';
|
||||||
import { useActiveWorkspace } from './useActiveWorkspace';
|
import { useActiveWorkspace } from './useActiveWorkspace';
|
||||||
import { useAppRoutes } from './useAppRoutes';
|
import { useAppRoutes } from './useAppRoutes';
|
||||||
|
import { useOsInfo } from './useOsInfo';
|
||||||
|
|
||||||
export function useOpenSettings() {
|
export function useOpenSettings() {
|
||||||
const routes = useAppRoutes();
|
const routes = useAppRoutes();
|
||||||
const workspace = useActiveWorkspace();
|
const workspace = useActiveWorkspace();
|
||||||
|
const dialog = useDialog();
|
||||||
|
const { osType } = useOsInfo();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationKey: ['open_settings'],
|
mutationKey: ['open_settings'],
|
||||||
mutationFn: async () => {
|
mutationFn: async () => {
|
||||||
if (workspace == null) return;
|
if (workspace == null) return;
|
||||||
|
|
||||||
|
// HACK: Show settings in dialog on Linux until this is fixed: https://github.com/tauri-apps/tauri/issues/11171
|
||||||
|
if (osType === 'linux') {
|
||||||
|
dialog.show({
|
||||||
|
id: 'settings',
|
||||||
|
size: 'lg',
|
||||||
|
render: ({ hide }) => <Settings hide={hide} />,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
await invokeCmd('cmd_new_child_window', {
|
await invokeCmd('cmd_new_child_window', {
|
||||||
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
|
url: routes.paths.workspaceSettings({ workspaceId: workspace.id }),
|
||||||
label: 'settings',
|
label: 'settings',
|
||||||
9
src-web/hooks/useStoplightsVisible.ts
Normal file
9
src-web/hooks/useStoplightsVisible.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useIsFullscreen } from './useIsFullscreen';
|
||||||
|
import { useOsInfo } from './useOsInfo';
|
||||||
|
|
||||||
|
export function useStoplightsVisible() {
|
||||||
|
const platform = useOsInfo();
|
||||||
|
const fullscreen = useIsFullscreen();
|
||||||
|
const stoplightsVisible = platform?.osType === 'macos' && !fullscreen;
|
||||||
|
return stoplightsVisible;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user