mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-01-31 06:42:03 +00:00
Merge pull request #535 from stefandevo/v0.12.0rc
feat: add font customization and 8 new themes
This commit is contained in:
@@ -1,8 +1,20 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Palette, Moon, Sun } from 'lucide-react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Palette, Moon, Sun, Type } from 'lucide-react';
|
||||
import { darkThemes, lightThemes, type Theme } from '@/config/theme-options';
|
||||
import {
|
||||
UI_SANS_FONT_OPTIONS,
|
||||
UI_MONO_FONT_OPTIONS,
|
||||
DEFAULT_FONT_VALUE,
|
||||
} from '@/config/ui-font-options';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import type { Project } from '@/lib/electron';
|
||||
@@ -12,29 +24,125 @@ interface ProjectThemeSectionProps {
|
||||
}
|
||||
|
||||
export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
||||
const { theme: globalTheme, setProjectTheme } = useAppStore();
|
||||
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
|
||||
const {
|
||||
theme: globalTheme,
|
||||
fontFamilySans: globalFontSans,
|
||||
fontFamilyMono: globalFontMono,
|
||||
setProjectTheme,
|
||||
setProjectFontSans,
|
||||
setProjectFontMono,
|
||||
} = useAppStore();
|
||||
|
||||
// Theme state
|
||||
const projectTheme = project.theme as Theme | undefined;
|
||||
const hasCustomTheme = projectTheme !== undefined;
|
||||
const effectiveTheme = projectTheme || globalTheme;
|
||||
|
||||
// Determine if current theme is light or dark
|
||||
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
|
||||
const [activeTab, setActiveTab] = useState<'dark' | 'light'>(isLightTheme ? 'light' : 'dark');
|
||||
|
||||
// Helper to validate fonts against available options
|
||||
const isValidSansFont = (font?: string): boolean =>
|
||||
!!font && UI_SANS_FONT_OPTIONS.some((opt) => opt.value === font);
|
||||
const isValidMonoFont = (font?: string): boolean =>
|
||||
!!font && UI_MONO_FONT_OPTIONS.some((opt) => opt.value === font);
|
||||
|
||||
// Helper to get initial font value with validation
|
||||
const getInitialFontValue = (font: string | undefined, validator: (f?: string) => boolean) =>
|
||||
font && validator(font) ? font : DEFAULT_FONT_VALUE;
|
||||
|
||||
// Font local state - tracks what's selected when using custom fonts
|
||||
// Falls back to default if stored font is not in available options
|
||||
const [fontSansLocal, setFontSansLocal] = useState<string>(() =>
|
||||
getInitialFontValue(project.fontFamilySans, isValidSansFont)
|
||||
);
|
||||
const [fontMonoLocal, setFontMonoLocal] = useState<string>(() =>
|
||||
getInitialFontValue(project.fontFamilyMono, isValidMonoFont)
|
||||
);
|
||||
|
||||
// Sync state when project changes
|
||||
useEffect(() => {
|
||||
setFontSansLocal(getInitialFontValue(project.fontFamilySans, isValidSansFont));
|
||||
setFontMonoLocal(getInitialFontValue(project.fontFamilyMono, isValidMonoFont));
|
||||
// Also sync the active tab based on current theme
|
||||
const currentIsLight = lightThemes.some((t) => t.value === (project.theme || globalTheme));
|
||||
setActiveTab(currentIsLight ? 'light' : 'dark');
|
||||
}, [project, globalTheme]);
|
||||
|
||||
// Font state - check if project has custom fonts set
|
||||
const hasCustomFontSans = project.fontFamilySans !== undefined;
|
||||
const hasCustomFontMono = project.fontFamilyMono !== undefined;
|
||||
|
||||
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
||||
|
||||
// Theme handlers
|
||||
const handleThemeChange = (theme: Theme) => {
|
||||
setProjectTheme(project.id, theme);
|
||||
};
|
||||
|
||||
const handleUseGlobalTheme = (checked: boolean) => {
|
||||
if (checked) {
|
||||
// Clear project theme to use global
|
||||
setProjectTheme(project.id, null);
|
||||
} else {
|
||||
// Set project theme to current global theme
|
||||
setProjectTheme(project.id, globalTheme);
|
||||
}
|
||||
};
|
||||
|
||||
// Font handlers
|
||||
const handleUseGlobalFontSans = (checked: boolean) => {
|
||||
if (checked) {
|
||||
// Clear project font to use global
|
||||
setProjectFontSans(project.id, null);
|
||||
setFontSansLocal(DEFAULT_FONT_VALUE);
|
||||
} else {
|
||||
// Set explicit project override - use 'default' value to indicate explicit default choice
|
||||
const fontToSet = globalFontSans || DEFAULT_FONT_VALUE;
|
||||
setFontSansLocal(fontToSet);
|
||||
// Store the actual value (including 'default') so hasCustomFontSans stays true
|
||||
setProjectFontSans(project.id, fontToSet);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUseGlobalFontMono = (checked: boolean) => {
|
||||
if (checked) {
|
||||
// Clear project font to use global
|
||||
setProjectFontMono(project.id, null);
|
||||
setFontMonoLocal(DEFAULT_FONT_VALUE);
|
||||
} else {
|
||||
// Set explicit project override - use 'default' value to indicate explicit default choice
|
||||
const fontToSet = globalFontMono || DEFAULT_FONT_VALUE;
|
||||
setFontMonoLocal(fontToSet);
|
||||
// Store the actual value (including 'default') so hasCustomFontMono stays true
|
||||
setProjectFontMono(project.id, fontToSet);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFontSansChange = (value: string) => {
|
||||
setFontSansLocal(value);
|
||||
// Store the actual value (including 'default') - only null clears to use global
|
||||
setProjectFontSans(project.id, value);
|
||||
};
|
||||
|
||||
const handleFontMonoChange = (value: string) => {
|
||||
setFontMonoLocal(value);
|
||||
// Store the actual value (including 'default') - only null clears to use global
|
||||
setProjectFontMono(project.id, value);
|
||||
};
|
||||
|
||||
// Get display label for global font
|
||||
const getGlobalFontSansLabel = () => {
|
||||
if (!globalFontSans) return 'Default (Geist Sans)';
|
||||
const option = UI_SANS_FONT_OPTIONS.find((o) => o.value === globalFontSans);
|
||||
return option?.label || globalFontSans;
|
||||
};
|
||||
|
||||
const getGlobalFontMonoLabel = () => {
|
||||
if (!globalFontMono) return 'Default (Geist Mono)';
|
||||
const option = UI_MONO_FONT_OPTIONS.find((o) => o.value === globalFontMono);
|
||||
return option?.label || globalFontMono;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -49,10 +157,10 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
||||
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-brand-500/20 to-brand-600/10 flex items-center justify-center border border-brand-500/20">
|
||||
<Palette className="w-5 h-5 text-brand-500" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">Theme</h2>
|
||||
<h2 className="text-lg font-semibold text-foreground tracking-tight">Theme & Fonts</h2>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground/80 ml-12">
|
||||
Customize the theme for this project.
|
||||
Customize the appearance for this project.
|
||||
</p>
|
||||
</div>
|
||||
<div className="p-6 space-y-6">
|
||||
@@ -158,6 +266,122 @@ export function ProjectThemeSection({ project }: ProjectThemeSectionProps) {
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Fonts Section */}
|
||||
<div className="space-y-4 pt-6 border-t border-border/50">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Type className="w-4 h-4 text-muted-foreground" />
|
||||
<Label className="text-foreground font-medium">Fonts</Label>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{/* UI Font */}
|
||||
<div className="space-y-3">
|
||||
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||
<Checkbox
|
||||
id="use-global-font-sans"
|
||||
checked={!hasCustomFontSans}
|
||||
onCheckedChange={handleUseGlobalFontSans}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="flex-1 space-y-1.5">
|
||||
<Label
|
||||
htmlFor="use-global-font-sans"
|
||||
className="text-foreground cursor-pointer font-medium"
|
||||
>
|
||||
Use Global UI Font
|
||||
</Label>
|
||||
{!hasCustomFontSans && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Currently using:{' '}
|
||||
<span className="font-medium">{getGlobalFontSansLabel()}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasCustomFontSans && (
|
||||
<div className="ml-6 space-y-2">
|
||||
<Label htmlFor="ui-font-select" className="text-sm">
|
||||
Project UI Font
|
||||
</Label>
|
||||
<Select value={fontSansLocal} onValueChange={handleFontSansChange}>
|
||||
<SelectTrigger id="ui-font-select" className="w-full">
|
||||
<SelectValue placeholder="Default (Geist Sans)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{UI_SANS_FONT_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily:
|
||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Code Font */}
|
||||
<div className="space-y-3">
|
||||
<div className="group flex items-start space-x-3 p-3 rounded-xl hover:bg-accent/30 transition-colors duration-200 -mx-3">
|
||||
<Checkbox
|
||||
id="use-global-font-mono"
|
||||
checked={!hasCustomFontMono}
|
||||
onCheckedChange={handleUseGlobalFontMono}
|
||||
className="mt-1"
|
||||
/>
|
||||
<div className="flex-1 space-y-1.5">
|
||||
<Label
|
||||
htmlFor="use-global-font-mono"
|
||||
className="text-foreground cursor-pointer font-medium"
|
||||
>
|
||||
Use Global Code Font
|
||||
</Label>
|
||||
{!hasCustomFontMono && (
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Currently using:{' '}
|
||||
<span className="font-medium">{getGlobalFontMonoLabel()}</span>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{hasCustomFontMono && (
|
||||
<div className="ml-6 space-y-2">
|
||||
<Label htmlFor="code-font-select" className="text-sm">
|
||||
Project Code Font
|
||||
</Label>
|
||||
<Select value={fontMonoLocal} onValueChange={handleFontMonoChange}>
|
||||
<SelectTrigger id="code-font-select" className="w-full">
|
||||
<SelectValue placeholder="Default (Geist Mono)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{UI_MONO_FONT_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily:
|
||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import { useState } from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Palette, Moon, Sun } from 'lucide-react';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { Palette, Moon, Sun, Type } from 'lucide-react';
|
||||
import { darkThemes, lightThemes } from '@/config/theme-options';
|
||||
import {
|
||||
UI_SANS_FONT_OPTIONS,
|
||||
UI_MONO_FONT_OPTIONS,
|
||||
DEFAULT_FONT_VALUE,
|
||||
} from '@/config/ui-font-options';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import type { Theme } from '../shared/types';
|
||||
|
||||
interface AppearanceSectionProps {
|
||||
@@ -11,10 +24,43 @@ interface AppearanceSectionProps {
|
||||
}
|
||||
|
||||
export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceSectionProps) {
|
||||
const [activeTab, setActiveTab] = useState<'dark' | 'light'>('dark');
|
||||
const { fontFamilySans, fontFamilyMono, setFontSans, setFontMono } = useAppStore();
|
||||
|
||||
// Determine if current theme is light or dark
|
||||
const isLightTheme = lightThemes.some((t) => t.value === effectiveTheme);
|
||||
const [activeTab, setActiveTab] = useState<'dark' | 'light'>(isLightTheme ? 'light' : 'dark');
|
||||
|
||||
// Sync active tab when theme changes
|
||||
useEffect(() => {
|
||||
const currentIsLight = lightThemes.some((t) => t.value === effectiveTheme);
|
||||
setActiveTab(currentIsLight ? 'light' : 'dark');
|
||||
}, [effectiveTheme]);
|
||||
|
||||
const themesToShow = activeTab === 'dark' ? darkThemes : lightThemes;
|
||||
|
||||
// Convert null to 'default' for Select component
|
||||
// Also fallback to default if the stored font is not in the available options
|
||||
const isValidSansFont = (font: string | null): boolean => {
|
||||
if (!font) return false;
|
||||
return UI_SANS_FONT_OPTIONS.some((opt) => opt.value === font);
|
||||
};
|
||||
const isValidMonoFont = (font: string | null): boolean => {
|
||||
if (!font) return false;
|
||||
return UI_MONO_FONT_OPTIONS.some((opt) => opt.value === font);
|
||||
};
|
||||
const fontSansValue =
|
||||
fontFamilySans && isValidSansFont(fontFamilySans) ? fontFamilySans : DEFAULT_FONT_VALUE;
|
||||
const fontMonoValue =
|
||||
fontFamilyMono && isValidMonoFont(fontFamilyMono) ? fontFamilyMono : DEFAULT_FONT_VALUE;
|
||||
|
||||
const handleFontSansChange = (value: string) => {
|
||||
setFontSans(value === DEFAULT_FONT_VALUE ? null : value);
|
||||
};
|
||||
|
||||
const handleFontMonoChange = (value: string) => {
|
||||
setFontMono(value === DEFAULT_FONT_VALUE ? null : value);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -102,6 +148,77 @@ export function AppearanceSection({ effectiveTheme, onThemeChange }: AppearanceS
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Fonts Section */}
|
||||
<div className="space-y-4 pt-6 border-t border-border/50">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
<Type className="w-4 h-4 text-muted-foreground" />
|
||||
<Label className="text-foreground font-medium">Fonts</Label>
|
||||
</div>
|
||||
<p className="text-xs text-muted-foreground -mt-2 mb-4">
|
||||
Set default fonts for all projects. Individual projects can override these settings.
|
||||
</p>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{/* UI Font Selector */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="global-ui-font-select" className="text-sm">
|
||||
UI Font
|
||||
</Label>
|
||||
<Select value={fontSansValue} onValueChange={handleFontSansChange}>
|
||||
<SelectTrigger id="global-ui-font-select" className="w-full">
|
||||
<SelectValue placeholder="Default (Geist Sans)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{UI_SANS_FONT_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily:
|
||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Used for headings, labels, and UI text
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Code Font Selector */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="global-code-font-select" className="text-sm">
|
||||
Code Font
|
||||
</Label>
|
||||
<Select value={fontMonoValue} onValueChange={handleFontMonoChange}>
|
||||
<SelectTrigger id="global-code-font-select" className="w-full">
|
||||
<SelectValue placeholder="Default (Geist Mono)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{UI_MONO_FONT_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily:
|
||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Used for code blocks and monospaced text
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface Project {
|
||||
name: string;
|
||||
path: string;
|
||||
theme?: string;
|
||||
fontFamilySans?: string;
|
||||
fontFamilyMono?: string;
|
||||
icon?: string;
|
||||
customIconPath?: string;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,19 @@ import { Label } from '@/components/ui/label';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { SquareTerminal } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useAppStore } from '@/store/app-store';
|
||||
import { toast } from 'sonner';
|
||||
import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes';
|
||||
import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options';
|
||||
|
||||
export function TerminalSection() {
|
||||
const {
|
||||
@@ -53,27 +61,32 @@ export function TerminalSection() {
|
||||
{/* Font Family */}
|
||||
<div className="space-y-3">
|
||||
<Label className="text-foreground font-medium">Font Family</Label>
|
||||
<select
|
||||
value={fontFamily}
|
||||
onChange={(e) => {
|
||||
setTerminalFontFamily(e.target.value);
|
||||
<Select
|
||||
value={fontFamily || DEFAULT_FONT_VALUE}
|
||||
onValueChange={(value) => {
|
||||
setTerminalFontFamily(value);
|
||||
toast.info('Font family changed', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
className={cn(
|
||||
'w-full px-3 py-2 rounded-lg',
|
||||
'bg-accent/30 border border-border/50',
|
||||
'text-foreground text-sm',
|
||||
'focus:outline-none focus:ring-2 focus:ring-green-500/30'
|
||||
)}
|
||||
>
|
||||
{TERMINAL_FONT_OPTIONS.map((font) => (
|
||||
<option key={font.value} value={font.value}>
|
||||
{font.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="w-full">
|
||||
<SelectValue placeholder="Default (Menlo / Monaco)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TERMINAL_FONT_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily: option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Default Font Size */}
|
||||
|
||||
@@ -25,8 +25,17 @@ import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { TERMINAL_FONT_OPTIONS } from '@/config/terminal-themes';
|
||||
import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options';
|
||||
import { toast } from 'sonner';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
import { TerminalPanel } from './terminal-view/terminal-panel';
|
||||
@@ -232,6 +241,8 @@ export function TerminalView() {
|
||||
setTerminalDefaultRunScript,
|
||||
setTerminalFontFamily,
|
||||
setTerminalLineHeight,
|
||||
setTerminalScrollbackLines,
|
||||
setTerminalScreenReaderMode,
|
||||
updateTerminalPanelSizes,
|
||||
} = useAppStore();
|
||||
|
||||
@@ -1457,9 +1468,9 @@ export function TerminalView() {
|
||||
<Settings className="h-4 w-4" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80" align="end">
|
||||
<PopoverContent className="w-72" align="end">
|
||||
<div className="space-y-4">
|
||||
<div className="space-y-2">
|
||||
<div className="space-y-1">
|
||||
<h4 className="font-medium text-sm">Terminal Settings</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Configure global terminal appearance
|
||||
@@ -1469,15 +1480,15 @@ export function TerminalView() {
|
||||
{/* Default Font Size */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm">Default Font Size</Label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<Label className="text-xs font-medium">Default Font Size</Label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{terminalState.defaultFontSize}px
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[terminalState.defaultFontSize]}
|
||||
min={8}
|
||||
max={24}
|
||||
max={32}
|
||||
step={1}
|
||||
onValueChange={([value]) => setTerminalDefaultFontSize(value)}
|
||||
onValueCommit={() => {
|
||||
@@ -1488,37 +1499,79 @@ export function TerminalView() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Default Run Script */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium">Run on New Terminal</Label>
|
||||
<Input
|
||||
value={terminalState.defaultRunScript}
|
||||
onChange={(e) => setTerminalDefaultRunScript(e.target.value)}
|
||||
placeholder="e.g., claude"
|
||||
className="h-7 text-xs"
|
||||
/>
|
||||
<p className="text-[10px] text-muted-foreground">
|
||||
Command to run when creating a new terminal
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Font Family */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm">Font Family</Label>
|
||||
<select
|
||||
value={terminalState.fontFamily}
|
||||
onChange={(e) => {
|
||||
setTerminalFontFamily(e.target.value);
|
||||
<Label className="text-xs font-medium">Font Family</Label>
|
||||
<Select
|
||||
value={terminalState.fontFamily || DEFAULT_FONT_VALUE}
|
||||
onValueChange={(value) => {
|
||||
setTerminalFontFamily(value);
|
||||
toast.info('Font family changed', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
className={cn(
|
||||
'w-full px-2 py-1.5 rounded-md text-sm',
|
||||
'bg-accent/50 border border-border',
|
||||
'text-foreground',
|
||||
'focus:outline-none focus:ring-2 focus:ring-ring'
|
||||
)}
|
||||
>
|
||||
{TERMINAL_FONT_OPTIONS.map((font) => (
|
||||
<option key={font.value} value={font.value}>
|
||||
{font.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="w-full h-8 text-xs">
|
||||
<SelectValue placeholder="Default (Menlo / Monaco)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TERMINAL_FONT_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily:
|
||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* Scrollback */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-xs font-medium">Scrollback</Label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{(terminalState.scrollbackLines / 1000).toFixed(0)}k lines
|
||||
</span>
|
||||
</div>
|
||||
<Slider
|
||||
value={[terminalState.scrollbackLines]}
|
||||
min={1000}
|
||||
max={100000}
|
||||
step={1000}
|
||||
onValueChange={([value]) => setTerminalScrollbackLines(value)}
|
||||
onValueCommit={() => {
|
||||
toast.info('Scrollback changed', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Line Height */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label className="text-sm">Line Height</Label>
|
||||
<span className="text-sm text-muted-foreground">
|
||||
<Label className="text-xs font-medium">Line Height</Label>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{terminalState.lineHeight.toFixed(1)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1536,18 +1589,21 @@ export function TerminalView() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Default Run Script */}
|
||||
<div className="space-y-2">
|
||||
<Label className="text-sm">Default Run Script</Label>
|
||||
<Input
|
||||
value={terminalState.defaultRunScript}
|
||||
onChange={(e) => setTerminalDefaultRunScript(e.target.value)}
|
||||
placeholder="e.g., claude, npm run dev"
|
||||
className="h-8 text-sm"
|
||||
{/* Screen Reader */}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="space-y-0.5">
|
||||
<Label className="text-xs font-medium">Screen Reader</Label>
|
||||
<p className="text-[10px] text-muted-foreground">Enable accessibility mode</p>
|
||||
</div>
|
||||
<Switch
|
||||
checked={terminalState.screenReaderMode}
|
||||
onCheckedChange={(checked) => {
|
||||
setTerminalScreenReaderMode(checked);
|
||||
toast.info(checked ? 'Screen reader enabled' : 'Screen reader disabled', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Command to run when opening new terminals
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -30,6 +30,13 @@ import { Slider } from '@/components/ui/slider';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useDraggable, useDroppable } from '@dnd-kit/core';
|
||||
import { useAppStore, DEFAULT_KEYBOARD_SHORTCUTS, type KeyboardShortcuts } from '@/store/app-store';
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
@@ -38,7 +45,9 @@ import {
|
||||
getTerminalTheme,
|
||||
TERMINAL_FONT_OPTIONS,
|
||||
DEFAULT_TERMINAL_FONT,
|
||||
getTerminalFontFamily,
|
||||
} from '@/config/terminal-themes';
|
||||
import { DEFAULT_FONT_VALUE } from '@/config/ui-font-options';
|
||||
import { toast } from 'sonner';
|
||||
import { getElectronAPI } from '@/lib/electron';
|
||||
import { getApiKey, getSessionToken, getServerUrlSync } from '@/lib/http-api-client';
|
||||
@@ -567,7 +576,7 @@ export function TerminalPanel({
|
||||
// Get settings from store (read at initialization time)
|
||||
const terminalSettings = useAppStore.getState().terminalState;
|
||||
const screenReaderEnabled = terminalSettings.screenReaderMode;
|
||||
const terminalFontFamily = terminalSettings.fontFamily || DEFAULT_TERMINAL_FONT;
|
||||
const terminalFontFamily = getTerminalFontFamily(terminalSettings.fontFamily);
|
||||
const terminalScrollback = terminalSettings.scrollbackLines || 5000;
|
||||
const terminalLineHeight = terminalSettings.lineHeight || 1.0;
|
||||
|
||||
@@ -1269,7 +1278,7 @@ export function TerminalPanel({
|
||||
|
||||
useEffect(() => {
|
||||
if (xtermRef.current && isTerminalReady) {
|
||||
xtermRef.current.options.fontFamily = fontFamily;
|
||||
xtermRef.current.options.fontFamily = getTerminalFontFamily(fontFamily);
|
||||
fitAddonRef.current?.fit();
|
||||
}
|
||||
}, [fontFamily, isTerminalReady]);
|
||||
@@ -1902,22 +1911,33 @@ export function TerminalPanel({
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label className="text-xs font-medium">Font Family</Label>
|
||||
<select
|
||||
value={fontFamily}
|
||||
onChange={(e) => {
|
||||
setTerminalFontFamily(e.target.value);
|
||||
<Select
|
||||
value={fontFamily || DEFAULT_FONT_VALUE}
|
||||
onValueChange={(value) => {
|
||||
setTerminalFontFamily(value);
|
||||
toast.info('Font family changed', {
|
||||
description: 'Restart terminal for changes to take effect',
|
||||
});
|
||||
}}
|
||||
className="w-full h-7 text-xs bg-background border border-input rounded-md px-2"
|
||||
>
|
||||
{TERMINAL_FONT_OPTIONS.map((font) => (
|
||||
<option key={font.value} value={font.value}>
|
||||
{font.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<SelectTrigger className="w-full h-8 text-xs">
|
||||
<SelectValue placeholder="Default (Menlo / Monaco)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{TERMINAL_FONT_OPTIONS.map((option) => (
|
||||
<SelectItem key={option.value} value={option.value}>
|
||||
<span
|
||||
style={{
|
||||
fontFamily:
|
||||
option.value === DEFAULT_FONT_VALUE ? undefined : option.value,
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
|
||||
Reference in New Issue
Block a user