Always show window controls, and open Linux settings in dialog

This commit is contained in:
Gregory Schier
2024-10-10 06:22:11 -07:00
parent 16e090b520
commit 250625fc0e
7 changed files with 101 additions and 46 deletions

View File

@@ -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>
); );
} }

View File

@@ -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',
)} )}
> >

View File

@@ -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

View File

@@ -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">

View File

@@ -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>
); );
}); });

View File

@@ -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',

View 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;
}