import type { CSSProperties, MouseEvent, ReactNode } from 'react' import { useEffect, useState } from 'react' import { getCurrentWindow } from '@tauri-apps/api/window' import { useDragRegion } from '../hooks/useDragRegion' import { resolveEffectiveLocale, translate, type AppLocale } from '../lib/i18n' import { shouldUseCustomWindowChrome } from '../utils/platform' import { cleanupTauriEventListener } from '../utils/tauriEventCleanup' import { LinuxMenuButton } from './LinuxMenuButton' import { Button } from './ui/button' export const LINUX_TITLEBAR_HEIGHT = 32 const RESIZE_EDGE = 6 type ResizeDirection = | 'East' | 'North' | 'NorthEast' | 'NorthWest' | 'South' | 'SouthEast' | 'SouthWest' | 'West' type LinuxTitlebarProps = { locale?: AppLocale } const RESIZE_HANDLES: ReadonlyArray<{ cursor: CSSProperties['cursor'] direction: ResizeDirection style: CSSProperties }> = [ { direction: 'North', cursor: 'ns-resize', style: { top: 0, left: RESIZE_EDGE, right: RESIZE_EDGE, height: RESIZE_EDGE } }, { direction: 'South', cursor: 'ns-resize', style: { bottom: 0, left: RESIZE_EDGE, right: RESIZE_EDGE, height: RESIZE_EDGE } }, { direction: 'West', cursor: 'ew-resize', style: { top: RESIZE_EDGE, bottom: RESIZE_EDGE, left: 0, width: RESIZE_EDGE } }, { direction: 'East', cursor: 'ew-resize', style: { top: RESIZE_EDGE, bottom: RESIZE_EDGE, right: 0, width: RESIZE_EDGE } }, { direction: 'NorthWest', cursor: 'nwse-resize', style: { top: 0, left: 0, width: RESIZE_EDGE, height: RESIZE_EDGE } }, { direction: 'NorthEast', cursor: 'nesw-resize', style: { top: 0, right: 0, width: RESIZE_EDGE, height: RESIZE_EDGE } }, { direction: 'SouthWest', cursor: 'nesw-resize', style: { bottom: 0, left: 0, width: RESIZE_EDGE, height: RESIZE_EDGE } }, { direction: 'SouthEast', cursor: 'nwse-resize', style: { bottom: 0, right: 0, width: RESIZE_EDGE, height: RESIZE_EDGE } }, ] export function LinuxTitlebar({ locale: localeOverride }: LinuxTitlebarProps = {}) { const customChromeEnabled = shouldUseCustomWindowChrome() const [documentLocale, setDocumentLocale] = useState(readDocumentLocale) const locale = localeOverride ?? documentLocale const { dragRegionRef } = useDragRegion() const maximized = useLinuxMaximizedState(customChromeEnabled) useEffect(() => { if (localeOverride || !customChromeEnabled || typeof document === 'undefined') return const syncLocale = () => setDocumentLocale(readDocumentLocale()) syncLocale() const observer = new MutationObserver(syncLocale) observer.observe(document.documentElement, { attributeFilter: ['lang'], attributes: true }) return () => observer.disconnect() }, [customChromeEnabled, localeOverride]) if (!customChromeEnabled) return null const appWindow = getCurrentWindow() return ( <>
) } function readDocumentLocale(): AppLocale { if (typeof document === 'undefined') return 'en' return resolveEffectiveLocale(document.documentElement.lang) } function useLinuxMaximizedState(enabled: boolean): boolean { const [maximized, setMaximized] = useState(false) useEffect(() => { if (!enabled) return const appWindow = getCurrentWindow() let active = true const syncMaximizeState = () => { void appWindow.isMaximized().then((value) => { if (active) setMaximized(value) }).catch(() => {}) } syncMaximizeState() const unlistenPromise = appWindow.onResized(syncMaximizeState) return () => { active = false void unlistenPromise.then(cleanupTauriEventListener).catch(() => {}) } }, [enabled]) return maximized } function ResizeHandles() { if (!shouldUseCustomWindowChrome()) return null const startResize = (direction: ResizeDirection) => (event: MouseEvent) => { if (event.button !== 0) return event.preventDefault() void getCurrentWindow().startResizeDragging(direction).catch(() => {}) } return ( <> {RESIZE_HANDLES.map(({ cursor, direction, style }) => (
))} ) } function TitlebarWindowControls({ appWindow, locale, maximized, }: { appWindow: ReturnType locale: AppLocale maximized: boolean }) { const minimizeLabel = translate(locale, 'window.minimize') const resizeLabel = translate(locale, maximized ? 'window.restore' : 'window.maximize') const closeLabel = translate(locale, 'window.close') return (
void appWindow.minimize().catch(() => {})}> void appWindow.toggleMaximize().catch(() => {})} > {maximized ? : } void appWindow.close().catch(() => {})} >
) } function TitlebarButton({ ariaLabel, children, close = false, onClick, }: { ariaLabel: string children: ReactNode close?: boolean onClick: () => void }) { return ( ) } function MinimizeIcon() { return ( ) } function MaximizeIcon() { return ( ) } function RestoreIcon() { return ( ) } function CloseIcon() { return ( ) }