Skip to content

Commit 1b1efa2

Browse files
committed
Function: Add settings page
1 parent 451a701 commit 1b1efa2

File tree

1 file changed

+167
-154
lines changed

1 file changed

+167
-154
lines changed

src/components/pages/ImageGenerationPage.tsx

Lines changed: 167 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState, useEffect, useCallback } from "react";
1+
import { useState, useEffect, useCallback, useRef } from "react";
22
import {
33
SettingsService,
44
SETTINGS_CHANGE_EVENT,
@@ -18,6 +18,7 @@ export const ImageGenerationPage = () => {
1818
const [error, setError] = useState<Error | null>(null);
1919
const [isApiKeyMissing, setIsApiKeyMissing] = useState(true);
2020
const [aspectRatio, setAspectRatio] = useState("1:1");
21+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2122
const [imageCount, setImageCount] = useState(1);
2223
const [randomSeed, setRandomSeed] = useState(
2324
Math.floor(Math.random() * 1000000).toString()
@@ -27,6 +28,10 @@ export const ImageGenerationPage = () => {
2728
);
2829
const [historyResults, setHistoryResults] = useState<ImageGenerationResult[]>([]);
2930
const [isLoadingHistory, setIsLoadingHistory] = useState(true);
31+
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
32+
33+
const settingsButtonRef = useRef<HTMLButtonElement>(null);
34+
const settingsPopupRef = useRef<HTMLDivElement>(null);
3035

3136
// Load image generation history from database
3237
const refreshImageHistory = useCallback(async () => {
@@ -90,6 +95,25 @@ export const ImageGenerationPage = () => {
9095
};
9196
}, []);
9297

98+
// Handle clicks outside the settings popup
99+
useEffect(() => {
100+
const handleClickOutside = (event: MouseEvent) => {
101+
if (
102+
settingsPopupRef.current &&
103+
!settingsPopupRef.current.contains(event.target as Node) &&
104+
settingsButtonRef.current &&
105+
!settingsButtonRef.current.contains(event.target as Node)
106+
) {
107+
setIsSettingsOpen(false);
108+
}
109+
};
110+
111+
document.addEventListener('mousedown', handleClickOutside);
112+
return () => {
113+
document.removeEventListener('mousedown', handleClickOutside);
114+
};
115+
}, []);
116+
93117
// Handle generating an image using OpenAI's DALL-E 3
94118
const handleGenerateImage = async () => {
95119
if (!prompt.trim()) return;
@@ -196,6 +220,11 @@ export const ImageGenerationPage = () => {
196220
);
197221
};
198222

223+
// Toggle settings popup
224+
const toggleSettings = () => {
225+
setIsSettingsOpen(!isSettingsOpen);
226+
};
227+
199228
return (
200229
<div className="flex flex-col w-full h-full bg-white">
201230
{isApiKeyMissing && (
@@ -223,8 +252,8 @@ export const ImageGenerationPage = () => {
223252

224253
<div className="flex flex-row gap-2 p-2 border border-gray-300 rounded-lg shadow-sm">
225254
<button
226-
onClick={handleGenerateImage}
227-
disabled={!prompt.trim() || isApiKeyMissing || isAnyImageGenerating()}
255+
ref={settingsButtonRef}
256+
onClick={toggleSettings}
228257
className="px-4 py-2.5 text-nowrap flex flex-row gap-1 text-white text-center confirm-btn"
229258
>
230259
<Settings></Settings>
@@ -292,167 +321,151 @@ export const ImageGenerationPage = () => {
292321
</div>
293322
</div>
294323

295-
{/* Right side - Results */}
296-
<div className="hidden w-[420px] h-full p-6 overflow-y-auto">
297-
{/* Provider selection */}
298-
<div className="mb-6">
299-
<label className="block mb-2 text-sm font-medium text-gray-700">
300-
{t("imageGeneration.provider")}
301-
</label>
302-
<div className="relative">
303-
<button
304-
className="flex items-center justify-between w-full p-3 text-left input-box"
305-
disabled={true}
306-
>
307-
<span>OpenAI</span>
308-
<ChevronDown size={18} className="text-gray-500" />
309-
</button>
310-
</div>
311-
</div>
312-
313-
{/* Model selection */}
314-
<div className="mb-6">
315-
<label className="block mb-2 text-sm font-medium text-gray-700">
316-
{t("imageGeneration.model")}
317-
</label>
318-
<div className="relative">
319-
<button
320-
className="flex items-center justify-between w-full p-3 text-left input-box"
321-
disabled={true}
322-
>
323-
<span>DALL-E 3</span>
324-
<ChevronDown size={18} className="text-gray-500" />
325-
</button>
324+
{/* Settings popup */}
325+
{isSettingsOpen && (
326+
<div
327+
ref={settingsPopupRef}
328+
className="absolute z-10 p-4 bg-white border border-gray-300 rounded-lg shadow-lg image-generation-settings-popup"
329+
style={{
330+
top: settingsButtonRef.current ?
331+
settingsButtonRef.current.getBoundingClientRect().top + 5 : 100,
332+
left: settingsButtonRef.current ?
333+
settingsButtonRef.current.getBoundingClientRect().left - 130 : 100,
334+
width: '320px'
335+
}}
336+
>
337+
<div className="mb-4">
338+
<h3 className="mb-2 text-base font-medium text-gray-800">
339+
{t("imageGeneration.imageSize")}
340+
</h3>
341+
<div className="grid grid-cols-3 gap-2">
342+
<button
343+
onClick={() => setAspectRatio("1:1")}
344+
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
345+
aspectRatio === "1:1" ? "active" : ""
346+
}`}
347+
>
348+
<div className="flex justify-center mb-1">
349+
<div className="w-6 h-6 border border-gray-500 rounded-sm"></div>
350+
</div>
351+
<span className="text-xs">1:1</span>
352+
</button>
353+
<button
354+
onClick={() => setAspectRatio("3:2")}
355+
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
356+
aspectRatio === "3:2" ? "active" : ""
357+
}`}
358+
>
359+
<div className="flex justify-center mb-1">
360+
<div className="w-6 h-4 border border-gray-500 rounded-sm"></div>
361+
</div>
362+
<span className="text-xs">3:2</span>
363+
</button>
364+
<button
365+
onClick={() => setAspectRatio("16:9")}
366+
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
367+
aspectRatio === "16:9" ? "active" : ""
368+
}`}
369+
>
370+
<div className="flex justify-center mb-1">
371+
<div className="w-6 h-3.5 border border-gray-500 rounded-sm"></div>
372+
</div>
373+
<span className="text-xs">16:9</span>
374+
</button>
375+
<button
376+
onClick={() => setAspectRatio("1:2")}
377+
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
378+
aspectRatio === "1:2" ? "active" : ""
379+
}`}
380+
>
381+
<div className="flex justify-center mb-1">
382+
<div className="w-4 h-6 border border-gray-500 rounded-sm"></div>
383+
</div>
384+
<span className="text-xs">1:2</span>
385+
</button>
386+
<button
387+
onClick={() => setAspectRatio("3:4")}
388+
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
389+
aspectRatio === "3:4" ? "active" : ""
390+
}`}
391+
>
392+
<div className="flex justify-center mb-1">
393+
<div className="w-[1rem] h-[1.5rem] border border-gray-500 rounded-sm"></div>
394+
</div>
395+
<span className="text-xs">3:4</span>
396+
</button>
397+
<button
398+
onClick={() => setAspectRatio("9:16")}
399+
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
400+
aspectRatio === "9:16" ? "active" : ""
401+
}`}
402+
>
403+
<div className="flex justify-center mb-1">
404+
<div className="w-3.5 h-6 border border-gray-500 rounded-sm"></div>
405+
</div>
406+
<span className="text-xs">9:16</span>
407+
</button>
408+
</div>
326409
</div>
327-
</div>
328410

329-
{/* Aspect ratio selection */}
330-
<div className="mb-6">
331-
<label className="block mb-2 text-sm font-medium text-gray-700">
332-
{t("imageGeneration.imageSize")}
333-
</label>
334-
<div className="grid grid-cols-6 gap-1">
335-
<button
336-
onClick={() => setAspectRatio("1:1")}
337-
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
338-
aspectRatio === "1:1" ? "active" : ""
339-
}`}
340-
>
341-
<div className="flex justify-center mb-1">
342-
<div className="w-6 h-6 border border-gray-500 rounded-sm"></div>
343-
</div>
344-
<span className="text-xs">1:1</span>
345-
</button>
346-
<button
347-
onClick={() => setAspectRatio("1:2")}
348-
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
349-
aspectRatio === "1:2" ? "active" : ""
350-
}`}
351-
>
352-
<div className="flex justify-center mb-1">
353-
<div className="w-4 h-6 border border-gray-500 rounded-sm"></div>
354-
</div>
355-
<span className="text-xs">1:2</span>
356-
</button>
357-
<button
358-
onClick={() => setAspectRatio("3:2")}
359-
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
360-
aspectRatio === "3:2" ? "active" : ""
361-
}`}
362-
>
363-
<div className="flex justify-center mb-1">
364-
<div className="w-6 h-4 border border-gray-500 rounded-sm"></div>
365-
</div>
366-
<span className="text-xs">3:2</span>
367-
</button>
368-
<button
369-
onClick={() => setAspectRatio("3:4")}
370-
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
371-
aspectRatio === "3:4" ? "active" : ""
372-
}`}
373-
>
374-
<div className="flex justify-center mb-1">
375-
<div className="w-[1rem] h-[1.5rem] border border-gray-500 rounded-sm"></div>
376-
</div>
377-
<span className="text-xs">3:4</span>
378-
</button>
379-
<button
380-
onClick={() => setAspectRatio("16:9")}
381-
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
382-
aspectRatio === "16:9" ? "active" : ""
383-
}`}
384-
>
385-
<div className="flex justify-center mb-1">
386-
<div className="w-6 h-3.5 border border-gray-500 rounded-sm"></div>
387-
</div>
388-
<span className="text-xs">16:9</span>
389-
</button>
390-
<button
391-
onClick={() => setAspectRatio("9:16")}
392-
className={`p-2 text-center border rounded-lg aspect-ratio-button ${
393-
aspectRatio === "9:16" ? "active" : ""
394-
}`}
395-
>
396-
<div className="flex justify-center mb-1">
397-
<div className="w-3.5 h-6 border border-gray-500 rounded-sm"></div>
411+
<div className="mb-4">
412+
<label className="flex items-center block mb-2 text-sm font-medium text-gray-700">
413+
{t("imageGeneration.randomSeed")}
414+
<div
415+
className="flex items-center justify-center w-4 h-4 ml-1 text-xs text-gray-500 bg-gray-200 rounded-full cursor-help"
416+
title={t("imageGeneration.seedHelp")}
417+
>
418+
?
398419
</div>
399-
<span className="text-xs">9:16</span>
400-
</button>
420+
</label>
421+
<div className="flex">
422+
<input
423+
type="text"
424+
value={randomSeed}
425+
onChange={(e) => setRandomSeed(e.target.value)}
426+
className="flex-grow p-3 mr-2 input-box"
427+
/>
428+
<button
429+
onClick={generateNewSeed}
430+
className="p-2 rounded-lg image-generation-refresh-button"
431+
title={t("imageGeneration.randomSeed")}
432+
>
433+
<RefreshCw size={20} />
434+
</button>
435+
</div>
401436
</div>
402-
</div>
403437

404-
{/* Image count */}
405-
<div className="mb-6">
406-
<label className="flex items-center block mb-2 text-sm font-medium text-gray-700">
407-
{t("imageGeneration.generationCount")}
408-
<div
409-
className="flex items-center justify-center w-4 h-4 ml-1 text-xs text-gray-500 bg-gray-200 rounded-full cursor-help"
410-
title={t("imageGeneration.generationCount")}
411-
>
412-
?
438+
<div className="mb-4">
439+
<label className="flex items-center block mb-2 text-sm font-medium text-gray-700">
440+
{t("imageGeneration.provider")}
441+
</label>
442+
<div className="relative">
443+
<button
444+
className="flex items-center justify-between w-full p-3 text-left input-box"
445+
disabled={true}
446+
>
447+
<span>OpenAI</span>
448+
<ChevronDown size={18} className="text-gray-500" />
449+
</button>
413450
</div>
414-
</label>
415-
<input
416-
type="number"
417-
value={imageCount}
418-
onChange={(e) => setImageCount(parseInt(e.target.value) || 1)}
419-
min="1"
420-
max="4"
421-
className="w-full p-3 input-box"
422-
disabled={true}
423-
/>
424-
</div>
451+
</div>
425452

426-
{/* Random seed */}
427-
<div className="mb-6">
428-
<label className="flex items-center block mb-2 text-sm font-medium text-gray-700">
429-
{t("imageGeneration.randomSeed")}
430-
<div
431-
className="flex items-center justify-center w-4 h-4 ml-1 text-xs text-gray-500 bg-gray-200 rounded-full cursor-help"
432-
title={t("imageGeneration.seedHelp")}
433-
>
434-
?
453+
<div className="mb-4">
454+
<label className="flex items-center block mb-2 text-sm font-medium text-gray-700">
455+
{t("imageGeneration.model")}
456+
</label>
457+
<div className="relative">
458+
<button
459+
className="flex items-center justify-between w-full p-3 text-left input-box"
460+
disabled={true}
461+
>
462+
<span>DALL-E 3</span>
463+
<ChevronDown size={18} className="text-gray-500" />
464+
</button>
435465
</div>
436-
</label>
437-
<div className="flex">
438-
<input
439-
type="text"
440-
value={randomSeed}
441-
onChange={(e) => setRandomSeed(e.target.value)}
442-
className="flex-grow p-3 mr-2 input-box"
443-
disabled={true}
444-
/>
445-
<button
446-
onClick={generateNewSeed}
447-
className="p-2 rounded-lg image-generation-refresh-button"
448-
title={t("imageGeneration.randomSeed")}
449-
disabled={true}
450-
>
451-
<RefreshCw size={20} />
452-
</button>
453466
</div>
454467
</div>
455-
</div>
468+
)}
456469
</div>
457470
</div>
458471
);

0 commit comments

Comments
 (0)