import React, { SyntheticEvent, useEffect, useState, useCallback, useRef, FormEvent, } from 'react' import _ from 'lodash' import * as Tabs from '@radix-ui/react-tabs' import { useRecoilState, useSetRecoilState } from 'recoil' import PhotoAlbum from 'react-photo-album' import { BarsArrowDownIcon, BarsArrowUpIcon } from '@heroicons/react/24/outline' import { MagnifyingGlassIcon, ViewHorizontalIcon, ViewGridIcon, } from '@radix-ui/react-icons' import { useDebounce } from 'react-use' import { Id, Index, IndexSearchResult } from 'flexsearch' import * as ScrollArea from '@radix-ui/react-scroll-area' import Modal from '../shared/Modal' import Flex from '../shared/Layout' import { fileManagerLayout, fileManagerSearchText, fileManagerSortBy, fileManagerSortOrder, SortBy, SortOrder, toastState, } from '../../store/Atoms' import { getMedias } from '../../adapters/inpainting' import Selector from '../shared/Selector' import Button from '../shared/Button' import TextInput from '../shared/Input' interface Photo { src: string height: number width: number name: string } interface Filename { name: string height: number width: number ctime: number mtime: number } const SORT_BY_NAME = 'Name' const SORT_BY_CREATED_TIME = 'Created time' const SORT_BY_MODIFIED_TIME = 'Modified time' const IMAGE_TAB = 'image' const OUTPUT_TAB = 'output' const SortByMap = { [SortBy.NAME]: SORT_BY_NAME, [SortBy.CTIME]: SORT_BY_CREATED_TIME, [SortBy.MTIME]: SORT_BY_MODIFIED_TIME, } interface Props { show: boolean onClose: () => void onPhotoClick(tab: string, filename: string): void photoWidth: number } export default function FileManager(props: Props) { const { show, onClose, onPhotoClick, photoWidth } = props const [scrollTop, setScrollTop] = useState(0) const [closeScrollTop, setCloseScrollTop] = useState(0) const setToastState = useSetRecoilState(toastState) const [sortBy, setSortBy] = useRecoilState(fileManagerSortBy) const [sortOrder, setSortOrder] = useRecoilState(fileManagerSortOrder) const [layout, setLayout] = useRecoilState(fileManagerLayout) const [debouncedSearchText, setDebouncedSearchText] = useRecoilState( fileManagerSearchText ) const ref = useRef(null) const [searchText, setSearchText] = useState(debouncedSearchText) const [tab, setTab] = useState(IMAGE_TAB) const [photos, setPhotos] = useState([]) const [, cancel] = useDebounce( () => { setDebouncedSearchText(searchText) }, 500, [searchText] ) useEffect(() => { if (!show) { setCloseScrollTop(scrollTop) } }, [show, scrollTop]) const onRefChange = useCallback( (node: HTMLDivElement) => { if (node !== null) { if (show) { setTimeout(() => { // TODO: without timeout, scrollTo not work, why? node.scrollTo({ top: closeScrollTop, left: 0 }) }, 100) } } }, [show, closeScrollTop] ) useEffect(() => { if (!show) { return } const fetchData = async () => { try { const filenames = await getMedias(tab) let filteredFilenames = filenames if (debouncedSearchText) { const index = new Index() filenames.forEach((filename: Filename, id: number) => index.add(id, filename.name) ) const results: IndexSearchResult = index.search(debouncedSearchText) filteredFilenames = results.map((id: Id) => filenames[id as number]) } filteredFilenames = _.orderBy(filteredFilenames, sortBy, sortOrder) const newPhotos = filteredFilenames.map((filename: Filename) => { const width = photoWidth const height = filename.height * (width / filename.width) const src = `/media_thumbnail/${tab}/${filename.name}?width=${width}&height=${height}` return { src, height, width, name: filename.name } }) setPhotos(newPhotos) } catch (e: any) { setToastState({ open: true, desc: e.message ? e.message : e.toString(), state: 'error', duration: 2000, }) } } fetchData() }, [ setToastState, tab, debouncedSearchText, sortBy, sortOrder, photoWidth, show, ]) const onScroll = (event: SyntheticEvent) => { setScrollTop(event.currentTarget.scrollTop) } const onClick = ({ index }: { index: number }) => { onPhotoClick(tab, photos[index].name) } const renderTitle = () => { return (
{`Images (${photos.length})`}