|
'use client'; |
|
import { useState, useEffect, useMemo } from 'react'; |
|
import { createGlobalState } from 'react-global-hooks'; |
|
import { Dialog, DialogBackdrop, DialogPanel } from '@headlessui/react'; |
|
|
|
export interface SampleImageModalState { |
|
imgPath: string; |
|
numSamples: number; |
|
sampleImages: string[]; |
|
} |
|
|
|
export const sampleImageModalState = createGlobalState<SampleImageModalState | null>(null); |
|
|
|
export const openSampleImage = (sampleImageProps: SampleImageModalState) => { |
|
sampleImageModalState.set(sampleImageProps); |
|
}; |
|
|
|
export default function SampleImageModal() { |
|
const [imageModal, setImageModal] = sampleImageModalState.use(); |
|
const [isOpen, setIsOpen] = useState(false); |
|
|
|
useEffect(() => { |
|
if (imageModal) { |
|
setIsOpen(true); |
|
} |
|
}, [imageModal]); |
|
|
|
useEffect(() => { |
|
if (!isOpen) { |
|
|
|
setTimeout(() => { |
|
setImageModal(null); |
|
}, 500); |
|
} |
|
}, [isOpen]); |
|
|
|
const onCancel = () => { |
|
setIsOpen(false); |
|
}; |
|
|
|
const imgInfo = useMemo(() => { |
|
const ii = { |
|
filename: '', |
|
step: 0, |
|
promptIdx: 0, |
|
}; |
|
if (imageModal?.imgPath) { |
|
const filename = imageModal.imgPath.split('/').pop(); |
|
if (!filename) return ii; |
|
|
|
ii.filename = filename as string; |
|
const parts = filename |
|
.split('.')[0] |
|
.split('_') |
|
.filter(p => p !== ''); |
|
if (parts.length === 3) { |
|
ii.step = parseInt(parts[1]); |
|
ii.promptIdx = parseInt(parts[2]); |
|
} |
|
} |
|
return ii; |
|
}, [imageModal]); |
|
|
|
const handleArrowUp = () => { |
|
if (!imageModal) return; |
|
console.log('Arrow Up pressed'); |
|
|
|
const currentIdx = imageModal.sampleImages.findIndex(img => img === imageModal.imgPath); |
|
if (currentIdx === -1) return; |
|
const nextIdx = currentIdx - imageModal.numSamples; |
|
if (nextIdx < 0) return; |
|
openSampleImage({ |
|
imgPath: imageModal.sampleImages[nextIdx], |
|
numSamples: imageModal.numSamples, |
|
sampleImages: imageModal.sampleImages, |
|
}); |
|
}; |
|
|
|
const handleArrowDown = () => { |
|
if (!imageModal) return; |
|
console.log('Arrow Down pressed'); |
|
|
|
const currentIdx = imageModal.sampleImages.findIndex(img => img === imageModal.imgPath); |
|
if (currentIdx === -1) return; |
|
const nextIdx = currentIdx + imageModal.numSamples; |
|
if (nextIdx >= imageModal.sampleImages.length) return; |
|
openSampleImage({ |
|
imgPath: imageModal.sampleImages[nextIdx], |
|
numSamples: imageModal.numSamples, |
|
sampleImages: imageModal.sampleImages, |
|
}); |
|
}; |
|
|
|
const handleArrowLeft = () => { |
|
if (!imageModal) return; |
|
if (imgInfo.promptIdx === 0) return; |
|
console.log('Arrow Left pressed'); |
|
|
|
const currentIdx = imageModal.sampleImages.findIndex(img => img === imageModal.imgPath); |
|
if (currentIdx === -1) return; |
|
const minIdx = currentIdx - imgInfo.promptIdx; |
|
const nextIdx = currentIdx - 1; |
|
if (nextIdx < minIdx) return; |
|
openSampleImage({ |
|
imgPath: imageModal.sampleImages[nextIdx], |
|
numSamples: imageModal.numSamples, |
|
sampleImages: imageModal.sampleImages, |
|
}); |
|
}; |
|
|
|
const handleArrowRight = () => { |
|
if (!imageModal) return; |
|
console.log('Arrow Right pressed'); |
|
|
|
const currentIdx = imageModal.sampleImages.findIndex(img => img === imageModal.imgPath); |
|
if (currentIdx === -1) return; |
|
const stepMinIdx = currentIdx - imgInfo.promptIdx; |
|
const maxIdx = stepMinIdx + imageModal.numSamples - 1; |
|
const nextIdx = currentIdx + 1; |
|
if (nextIdx > maxIdx) return; |
|
if (nextIdx >= imageModal.sampleImages.length) return; |
|
openSampleImage({ |
|
imgPath: imageModal.sampleImages[nextIdx], |
|
numSamples: imageModal.numSamples, |
|
sampleImages: imageModal.sampleImages, |
|
}); |
|
}; |
|
|
|
|
|
useEffect(() => { |
|
const handleKeyDown = (event: KeyboardEvent) => { |
|
if (!isOpen) return; |
|
|
|
switch (event.key) { |
|
case 'Escape': |
|
onCancel(); |
|
break; |
|
case 'ArrowUp': |
|
handleArrowUp(); |
|
break; |
|
case 'ArrowDown': |
|
handleArrowDown(); |
|
break; |
|
case 'ArrowLeft': |
|
handleArrowLeft(); |
|
break; |
|
case 'ArrowRight': |
|
handleArrowRight(); |
|
break; |
|
default: |
|
break; |
|
} |
|
}; |
|
|
|
window.addEventListener('keydown', handleKeyDown); |
|
|
|
return () => { |
|
window.removeEventListener('keydown', handleKeyDown); |
|
}; |
|
}, [isOpen, imageModal, imgInfo]); |
|
|
|
return ( |
|
<Dialog open={isOpen} onClose={onCancel} className="relative z-10"> |
|
<DialogBackdrop |
|
transition |
|
className="fixed inset-0 bg-gray-900/75 transition-opacity data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in" |
|
/> |
|
|
|
<div className="fixed inset-0 z-10 w-screen overflow-y-auto"> |
|
<div className="flex min-h-full items-center justify-center p-4 text-center"> |
|
<DialogPanel |
|
transition |
|
className="relative transform overflow-hidden rounded-lg bg-gray-800 text-left shadow-xl transition-all data-closed:translate-y-4 data-closed:opacity-0 data-enter:duration-300 data-enter:ease-out data-leave:duration-200 data-leave:ease-in max-w-[95%] max-h-[95vh] data-closed:sm:translate-y-0 data-closed:sm:scale-95" |
|
> |
|
<div className="flex justify-center items-center"> |
|
{imageModal?.imgPath && ( |
|
<img |
|
src={`/api/img/${encodeURIComponent(imageModal.imgPath)}`} |
|
alt="Sample Image" |
|
className="max-w-full max-h-[calc(95vh-2rem)] object-contain" |
|
/> |
|
)} |
|
</div> |
|
<div className="bg-gray-950 text-center text-sm p-2">step: {imgInfo.step}</div> |
|
</DialogPanel> |
|
</div> |
|
</div> |
|
</Dialog> |
|
); |
|
} |
|
|