|
|
import { useState, useEffect, useRef, useReducer } from 'react' |
|
|
import { debounce } from 'lodash' |
|
|
import axios from 'axios' |
|
|
import ReactModal from 'react-modal' |
|
|
import Image from "./Image" |
|
|
|
|
|
export default function GetImages() { |
|
|
const [render, setRender] = useState(false); |
|
|
const [images, setImages] = useState([]) |
|
|
const [searchTerm, setSearchTerm] = useState('') |
|
|
const [loading, setLoading] = useState(true) |
|
|
const [error, setError] = useState(false) |
|
|
const [sort, setSort] = useState('modified') |
|
|
const [type, setType] = useState('lora') |
|
|
|
|
|
const [renderMore, setRenderMore] = useState(false); |
|
|
const [modalIsOpen, setIsOpen] = useState(false) |
|
|
const [moreImages, setMoreImages] = useState([]) |
|
|
const [moreLoading, setMoreLoading] = useState(true) |
|
|
const [moreError, setMoreError] = useState(false) |
|
|
const [moreIndex, setMoreIndex] = useState(null) |
|
|
const [moreDocument, setMoreDocument] = useState(null) |
|
|
|
|
|
const inputReference = useRef(null) |
|
|
|
|
|
ReactModal.setAppElement('#root') |
|
|
|
|
|
const customModalStyle = { |
|
|
overlay: { |
|
|
backgroundColor: 'rgba(0,0,0,0.97)', |
|
|
}, |
|
|
content: { |
|
|
maxHeight: '90%', |
|
|
maxWidth: '95%', |
|
|
top: '50%', |
|
|
left: '50%', |
|
|
right: 'auto', |
|
|
bottom: 'auto', |
|
|
marginRight: '-50%', |
|
|
transform: 'translate(-50%, -50%)', |
|
|
backgroundColor: 'rgba(0,0,0,0.25)', |
|
|
border: 'none', |
|
|
scrollbarWidth: 'thin', |
|
|
scrollbarColor: '#141414 #000' |
|
|
}, |
|
|
} |
|
|
|
|
|
const sortImages = (dataArray) => { |
|
|
if (sort == "random") { |
|
|
for (let i = dataArray.length - 1; i > 0; i--) { |
|
|
const j = Math.floor(Math.random() * (i + 1)); |
|
|
[dataArray[i], dataArray[j]] = [dataArray[j], dataArray[i]]; |
|
|
} |
|
|
} else { |
|
|
dataArray.sort(function(a, b){ |
|
|
if (sort == "modified") { |
|
|
return b.mtimeMs - a.mtimeMs |
|
|
} else { |
|
|
return a.filename - b.filename |
|
|
} |
|
|
}) |
|
|
} |
|
|
return dataArray |
|
|
} |
|
|
|
|
|
const fetchImages = () => { |
|
|
setLoading(true) |
|
|
setMoreLoading(true) |
|
|
setImages([]) |
|
|
setMoreImages([]) |
|
|
axios.get(`http://localhost:3000/images?type=${type}&search=${searchTerm}`) |
|
|
.then(response => { |
|
|
if (sort != "name") { |
|
|
setImages(sortImages(response.data)) |
|
|
} else { |
|
|
setImages(response.data) |
|
|
} |
|
|
setLoading(false) |
|
|
setError(false) |
|
|
}) |
|
|
.catch(err => { |
|
|
console.log(err) |
|
|
setLoading(false) |
|
|
setError(true) |
|
|
}) |
|
|
} |
|
|
|
|
|
const fetchMoreImages = () => { |
|
|
setMoreLoading(true) |
|
|
setMoreImages([]) |
|
|
|
|
|
const image = images[moreIndex] |
|
|
const query = encodeURIComponent(image.path + image.filename) |
|
|
|
|
|
axios.get(`http://localhost:3000/moreImages?type=${type}&search=${query}`) |
|
|
.then(response => { |
|
|
setMoreLoading(false) |
|
|
setMoreError(false) |
|
|
if (sort != "name") { |
|
|
setMoreImages(sortImages(response.data)) |
|
|
} else { |
|
|
setMoreImages(response.data) |
|
|
} |
|
|
openModal() |
|
|
}) |
|
|
.catch(err => { |
|
|
console.log(err) |
|
|
setMoreLoading(false) |
|
|
setMoreError(true) |
|
|
}) |
|
|
} |
|
|
|
|
|
const handleSearchInputChange = debounce((e) => { |
|
|
setSearchTerm(e.target.value) |
|
|
}, 500) |
|
|
|
|
|
const handleSortChange = (sortOption) => { |
|
|
setSort(sortOption) |
|
|
|
|
|
setRender(!render) |
|
|
} |
|
|
|
|
|
const handleTypeChange = (typeOption) => { |
|
|
setType(typeOption) |
|
|
|
|
|
setMoreIndex(null) |
|
|
} |
|
|
|
|
|
const openModal = () => { |
|
|
setIsOpen(true) |
|
|
} |
|
|
|
|
|
const closeModal = () => { |
|
|
setIsOpen(false) |
|
|
} |
|
|
|
|
|
const handleFolderClick = (index) => { |
|
|
if (moreIndex != null && moreIndex == index && !moreDocument) { |
|
|
openModal() |
|
|
} else { |
|
|
setMoreDocument(null) |
|
|
setMoreLoading(true) |
|
|
setMoreIndex(index) |
|
|
setRenderMore(!renderMore) |
|
|
openModal() |
|
|
} |
|
|
} |
|
|
|
|
|
const handleDocumentClick = (index) => { |
|
|
setMoreLoading(true) |
|
|
setMoreImages([]) |
|
|
setMoreDocument(null) |
|
|
|
|
|
const image = images[index] |
|
|
const file = (type == "poses") ? |
|
|
'readme.txt' |
|
|
: encodeURIComponent(image.filename.substring(0, image.filename.lastIndexOf('.')) + '.txt') |
|
|
|
|
|
axios.get(`http://localhost:3000/${image.path.replaceAll('#','%23')}${file}`) |
|
|
.then(response => { |
|
|
setMoreLoading(false) |
|
|
setMoreError(false) |
|
|
setMoreDocument(response.data) |
|
|
setMoreIndex(index) |
|
|
openModal() |
|
|
}) |
|
|
.catch(err => { |
|
|
if (err.response.status == "404") { |
|
|
setMoreLoading(false) |
|
|
setMoreError(false) |
|
|
setMoreDocument('No additional information found.') |
|
|
setMoreIndex(index) |
|
|
openModal() |
|
|
} else { |
|
|
console.log(err) |
|
|
setMoreLoading(false) |
|
|
setMoreError(true) |
|
|
openModal() |
|
|
} |
|
|
}) |
|
|
} |
|
|
|
|
|
const handleModalKeys = (event) => { |
|
|
event.stopPropagation() |
|
|
if (event.key == "ArrowLeft") { |
|
|
if (moreIndex > 0) { |
|
|
setMoreLoading(true) |
|
|
setMoreDocument(null) |
|
|
setMoreIndex(moreIndex - 1) |
|
|
setRenderMore(!renderMore) |
|
|
} |
|
|
} else if (event.key == "ArrowRight") { |
|
|
if (moreIndex+1 < images.length) { |
|
|
setMoreLoading(true) |
|
|
setMoreDocument(null) |
|
|
setMoreIndex(moreIndex + 1) |
|
|
setRenderMore(!renderMore) |
|
|
} |
|
|
} else if (event.key == "ArrowDown") { |
|
|
handleDocumentClick(moreIndex) |
|
|
} else if (event.key == "ArrowUp") { |
|
|
handleFolderClick(moreIndex) |
|
|
} else if (event.key == "Escape") { |
|
|
closeModal() |
|
|
} |
|
|
} |
|
|
|
|
|
useEffect(() => { |
|
|
fetchImages() |
|
|
}, [searchTerm, type, render]) |
|
|
|
|
|
useEffect(() => { |
|
|
if (moreIndex != null) { |
|
|
fetchMoreImages() |
|
|
} |
|
|
}, [renderMore]) |
|
|
|
|
|
return ( |
|
|
<> |
|
|
<div> |
|
|
<div id="search"> |
|
|
<div id="typeOptions"> |
|
|
<span title="Lora" className={`typeOption ${type == "lora" ? "highlight" : ""}`} onClick={() => handleTypeChange("lora")}>L</span> |
|
|
<span title="Style" className={`typeOption ${type == "styles" ? "highlight" : ""}`} onClick={() => handleTypeChange("styles")}>S</span> |
|
|
<span title="Embedding" className={`typeOption ${type == "embeddings" ? "highlight" : ""}`} onClick={() => handleTypeChange("embeddings")}>E</span> |
|
|
<span title="HyperNetwork" className={`typeOption ${type == "hypernets" ? "highlight" : ""}`} onClick={() => handleTypeChange("hypernets")}>H</span> |
|
|
<span title="Checkpoint" className={`typeOption ${type == "checkpoints" ? "highlight" : ""}`} onClick={() => handleTypeChange("checkpoints")}>C</span> |
|
|
<span title="Poses" className={`typeOption ${type == "Poses" ? "highlight" : ""}`} onClick={() => handleTypeChange("poses")}>P</span> |
|
|
<span title="Gallery" className={`typeOption ${type == "gallery" ? "highlight" : ""}`} onClick={() => handleTypeChange("gallery")}>G</span> |
|
|
</div> |
|
|
<input id="imgSearch" icon='search' placeholder='Search...' |
|
|
onChange={handleSearchInputChange} |
|
|
/> |
|
|
<div id="sortOptions"> |
|
|
<span title="Modified" className={`sortOption ${sort == "modified" ? "highlight" : ""}`} onClick={() => handleSortChange("modified")}>M</span> |
|
|
<span title="Name" className={`sortOption ${sort == "name" ? "highlight" : ""}`} onClick={() => handleSortChange("name")}>N</span> |
|
|
<span title="Random" className={`sortOption ${sort == "random" ? "highlight" : ""}`} onClick={() => handleSortChange("random")}>R</span> |
|
|
</div> |
|
|
</div> |
|
|
<div id="imgGrid"> |
|
|
{loading && <div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>} |
|
|
{error && <h2>There was an error loading the images.</h2>} |
|
|
{!loading && !error && images.map((image, index) => ( |
|
|
<Image key={index} index={index} {...image} onFolderClick={handleFolderClick} onDocumentClick={handleDocumentClick} /> |
|
|
))} |
|
|
</div> |
|
|
<ReactModal |
|
|
isOpen={modalIsOpen} |
|
|
onAfterOpen={() => { inputReference.current.focus() }} |
|
|
onRequestClose={closeModal} |
|
|
preventScroll={true} |
|
|
style={customModalStyle} |
|
|
> |
|
|
<div |
|
|
ref={inputReference} |
|
|
tabIndex="-1" // Enables key handlers on div |
|
|
onKeyDown={handleModalKeys} |
|
|
className="modalContent" |
|
|
> |
|
|
{moreLoading && <div className="lds-ellipsis"><div></div><div></div><div></div><div></div></div>} |
|
|
{moreError && <h2>There was an error during loading.</h2>} |
|
|
{!moreLoading && !moreError && images[moreIndex] && moreImages[0] && !moreDocument && <div className="modalHeader" style={{width: `calc(224px * ${moreImages.length})`} /*adjust header size for # of images until max width*/}>{images[moreIndex].path + images[moreIndex].filename}</div>} |
|
|
<div id="modalImgGrid"> |
|
|
{!moreLoading && !moreError && images[moreIndex] && moreImages[0] && !moreDocument && moreImages.map((image, index) => ( |
|
|
<div key={index+1000000} className="imgCard" onClick={() => {navigator.clipboard.writeText(images[moreIndex].prompt)}}> |
|
|
<img width="224" height="336" |
|
|
src={"http://localhost:3000/" + image.path.replaceAll('#','%23') + encodeURIComponent(image.filename)} |
|
|
loading={index <= 60 ? "eager" : "lazy"} |
|
|
title={image.filename} |
|
|
/> |
|
|
</div> |
|
|
))} |
|
|
{!moreLoading && !moreError && moreDocument && images[moreIndex] && |
|
|
<div className="moreDocument"> |
|
|
<pre> |
|
|
File: {images[moreIndex].filename}{<br />} |
|
|
{type != "styles" && `Keywords: ${images[moreIndex].keywords}\n`} |
|
|
{type != "styles" && `Weight: ${images[moreIndex].weight}`} |
|
|
{type == "styles" && `Prompt: ${images[moreIndex].prompt}`} |
|
|
</pre> |
|
|
<pre>{moreDocument}</pre> |
|
|
</div> |
|
|
} |
|
|
</div> |
|
|
</div> |
|
|
</ReactModal> |
|
|
</div> |
|
|
</> |
|
|
) |
|
|
} |