xpaintdev / components /SettingsPanel.tsx
suisuyy
Rename project from xpaintai to paintai; update canvas dimensions and AI API endpoint; enhance AI features with dimensions mode
0817dc1
import React from 'react';
import { CloseIcon, FullscreenEnterIcon, FullscreenExitIcon } from './icons'; // Import fullscreen icons
import { AiImageQuality, AiDimensionsMode } from '../hooks/useAiFeatures';
interface SettingsPanelProps {
isOpen: boolean;
onClose: () => void;
showZoomSlider: boolean;
onShowZoomSliderChange: (show: boolean) => void;
aiImageQuality: AiImageQuality;
onAiImageQualityChange: (quality: AiImageQuality) => void;
aiApiEndpoint: string;
onAiApiEndpointChange: (endpoint: string) => void;
aiDimensionsMode: AiDimensionsMode;
onAiDimensionsModeChange: (mode: AiDimensionsMode) => void;
isFullscreenActive: boolean;
onToggleFullscreen: () => void;
currentCanvasWidth: number;
currentCanvasHeight: number;
onCanvasSizeChange: (width: number, height: number) => void;
}
const CANVAS_SIZE_OPTIONS = [
{ label: '1024 x 1536 px', width: 1024, height: 1536 },
{ label: '1024 x 1024 px', width: 1024, height: 1024 },
{ label: '1200 x 1200 px', width: 1200, height: 1200 },
{ label: '1600 x 1600 px', width: 1600, height: 1600 },
{ label: '2000 x 2000 px', width: 2000, height: 2000 },
{ label: '4000 x 4000 px', width: 4000, height: 4000 },
];
const AI_API_OPTIONS = [
{
label: 'Pollinations AI',
value: 'https://rpfor.deno.dev/proxy?url=https://image.pollinations.ai/prompt/{prompt}?nologo=true&model=gptimage&image={imgurl.url}&quality={quality}{size_params}'
},
{
label: 'Gemini Simple Get',
value: 'https://geminisimpleget.deno.dev/prompt/{prompt}?image={imgurl.url}'
}
];
const SettingsPanel: React.FC<SettingsPanelProps> = ({
isOpen,
onClose,
showZoomSlider,
onShowZoomSliderChange,
aiImageQuality,
onAiImageQualityChange,
aiApiEndpoint,
onAiApiEndpointChange,
aiDimensionsMode,
onAiDimensionsModeChange,
isFullscreenActive,
onToggleFullscreen,
currentCanvasWidth,
currentCanvasHeight,
onCanvasSizeChange,
}) => {
if (!isOpen) return null;
const qualityOptions: { label: string; value: AiImageQuality }[] = [
{ label: 'Low', value: 'low' },
{ label: 'Medium', value: 'medium' },
{ label: 'High (HD)', value: 'hd' },
];
const handleSizeSelection = (event: React.ChangeEvent<HTMLSelectElement>) => {
const selectedValue = event.target.value;
const [newWidth, newHeight] = selectedValue.split('x').map(Number);
if (!isNaN(newWidth) && !isNaN(newHeight)) {
onCanvasSizeChange(newWidth, newHeight);
}
};
const currentSizeValue = `${currentCanvasWidth}x${currentCanvasHeight}`;
return (
<div
className="fixed inset-0 bg-black bg-opacity-50 backdrop-blur-sm flex justify-center items-center z-[1000] p-4"
aria-modal="true"
role="dialog"
aria-labelledby="settings-panel-title"
>
<div className="bg-white rounded-lg shadow-2xl p-6 w-full max-w-md transform transition-all overflow-y-auto max-h-[90vh]">
<div className="flex justify-between items-center mb-6">
<h2 id="settings-panel-title" className="text-xl font-semibold text-slate-800">
Application Settings
</h2>
<button
onClick={onClose}
className="text-slate-400 hover:text-slate-600 transition-colors"
aria-label="Close settings panel"
>
<CloseIcon className="w-6 h-6" />
</button>
</div>
{/* Interface Settings */}
<div className="mb-6">
<h3 className="text-md font-medium text-slate-700 mb-2">Interface</h3>
<div className="space-y-3">
{/* Zoom Slider Toggle */}
<div className="flex items-center justify-between bg-slate-50 p-3 rounded-md border border-slate-200">
<label htmlFor="showZoomSliderToggle" className="text-sm text-slate-600">
Show Zoom Slider
</label>
<button
id="showZoomSliderToggle"
role="switch"
aria-checked={showZoomSlider}
onClick={() => onShowZoomSliderChange(!showZoomSlider)}
className={`relative inline-flex items-center h-6 rounded-full w-11 transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 ${
showZoomSlider ? 'bg-blue-600' : 'bg-slate-300'
}`}
>
<span className="sr-only">Toggle Zoom Slider Visibility</span>
<span
className={`inline-block w-4 h-4 transform bg-white rounded-full transition-transform duration-200 ease-in-out ${
showZoomSlider ? 'translate-x-6' : 'translate-x-1'
}`}
/>
</button>
</div>
{/* Fullscreen Toggle */}
<div className="bg-slate-50 p-3 rounded-md border border-slate-200">
<button
onClick={onToggleFullscreen}
className="w-full flex items-center justify-center px-4 py-2 text-sm font-medium text-blue-700 bg-blue-100 hover:bg-blue-200 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors"
aria-label={isFullscreenActive ? 'Exit Fullscreen' : 'Enter Fullscreen'}
>
{isFullscreenActive ? (
<FullscreenExitIcon className="w-5 h-5 mr-2" />
) : (
<FullscreenEnterIcon className="w-5 h-5 mr-2" />
)}
{isFullscreenActive ? 'Exit Fullscreen' : 'Enter Fullscreen'}
</button>
</div>
</div>
</div>
{/* Canvas Dimensions Settings */}
<div className="mb-6">
<h3 className="text-md font-medium text-slate-700 mb-2">Canvas Dimensions</h3>
<div className="bg-slate-50 p-3 rounded-md border border-slate-200">
<label htmlFor="canvasSizeSelect" className="block text-sm text-slate-600 mb-1">
Canvas Size
</label>
<select
id="canvasSizeSelect"
value={currentSizeValue}
onChange={handleSizeSelection}
className="w-full p-2 border border-slate-300 rounded-md focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
aria-describedby="canvas-size-description"
>
{CANVAS_SIZE_OPTIONS.map((option) => (
<option key={`${option.width}x${option.height}`} value={`${option.width}x${option.height}`}>
{option.label}
</option>
))}
</select>
<p id="canvas-size-description" className="text-xs text-slate-500 mt-1">
Changing size will clear the current canvas and history.
</p>
</div>
</div>
{/* AI Image Generation Settings */}
<div className="mb-6">
<h3 className="text-md font-medium text-slate-700 mb-2">AI Image Generation</h3>
<div className="bg-slate-50 p-3 rounded-md border border-slate-200 mb-4">
<label htmlFor="aiApiEndpointSelect" className="block text-sm text-slate-600 mb-1">
API Endpoint
</label>
<select
id="aiApiEndpointSelect"
value={aiApiEndpoint}
onChange={(e) => onAiApiEndpointChange(e.target.value)}
className="w-full p-2 border border-slate-300 rounded-md focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
aria-describedby="ai-api-description"
>
{AI_API_OPTIONS.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<p id="ai-api-description" className="text-xs text-slate-500 mt-1">
Select an API for image generation. Placeholders are filled automatically.
</p>
</div>
<div className="bg-slate-50 p-3 rounded-md border border-slate-200 mb-4">
<label htmlFor="aiDimensionsModeSelect" className="block text-sm text-slate-600 mb-1">
AI Image Dimensions
</label>
<select
id="aiDimensionsModeSelect"
value={aiDimensionsMode}
onChange={(e) => onAiDimensionsModeChange(e.target.value as AiDimensionsMode)}
className="w-full p-2 border border-slate-300 rounded-md focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
aria-describedby="ai-dimensions-description"
>
<option value="api_default">API Default (no size)</option>
<option value="match_canvas">Match Canvas Size</option>
<option value="fixed_1024">Fixed 1024x1024</option>
</select>
<p id="ai-dimensions-description" className="text-xs text-slate-500 mt-1">
Controls `width` & `height` params for APIs that support them (e.g., Pollinations AI).
</p>
</div>
<div className="bg-slate-50 p-3 rounded-md border border-slate-200">
<label htmlFor="aiImageQualitySelect" className="block text-sm text-slate-600 mb-1">
Image Quality
</label>
<select
id="aiImageQualitySelect"
value={aiImageQuality}
onChange={(e) => onAiImageQualityChange(e.target.value as AiImageQuality)}
className="w-full p-2 border border-slate-300 rounded-md focus:ring-1 focus:ring-blue-500 focus:border-blue-500 transition-shadow text-sm"
aria-describedby="ai-quality-description"
>
{qualityOptions.map((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
<p id="ai-quality-description" className="text-xs text-slate-500 mt-1">
Used for APIs that support a 'quality' parameter (e.g., Pollinations AI).
</p>
</div>
</div>
<div className="flex justify-end gap-3 mt-8">
<button
onClick={onClose}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors text-sm font-medium"
>
Done
</button>
</div>
</div>
</div>
);
};
export default SettingsPanel;