lots of update 2
This commit is contained in:
@@ -4,13 +4,25 @@ import React, {
|
||||
useMemo,
|
||||
useState,
|
||||
useCallback,
|
||||
useRef,
|
||||
FormEvent,
|
||||
} from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import PhotoAlbum, { RenderPhoto } from 'react-photo-album'
|
||||
import _ from 'lodash'
|
||||
import { useSetRecoilState } from 'recoil'
|
||||
import PhotoAlbum from 'react-photo-album'
|
||||
import { BarsArrowDownIcon, BarsArrowUpIcon } from '@heroicons/react/24/outline'
|
||||
import { MagnifyingGlassIcon } 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 { toastState } from '../../store/Atoms'
|
||||
import { getMedias } from '../../adapters/inpainting'
|
||||
import Selector from '../shared/Selector'
|
||||
import Button from '../shared/Button'
|
||||
import TextInput from '../shared/Input'
|
||||
import { useAsyncMemo } from '../../hooks/useAsyncMemo'
|
||||
|
||||
interface Photo {
|
||||
src: string
|
||||
@@ -22,26 +34,26 @@ interface Filename {
|
||||
name: string
|
||||
height: number
|
||||
width: number
|
||||
ctime: number
|
||||
}
|
||||
|
||||
const renderPhoto: RenderPhoto = ({
|
||||
layout,
|
||||
layoutOptions,
|
||||
imageProps: { alt, style, ...restImageProps },
|
||||
}) => (
|
||||
<div
|
||||
style={{
|
||||
boxSizing: 'content-box',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<img
|
||||
alt={alt}
|
||||
style={{ ...style, width: '100%', padding: 0 }}
|
||||
{...restImageProps}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
enum SortOrder {
|
||||
DESCENDING = 'desc',
|
||||
ASCENDING = 'asc',
|
||||
}
|
||||
|
||||
enum SortBy {
|
||||
NAME = 'name',
|
||||
CTIME = 'ctime',
|
||||
}
|
||||
|
||||
const SORT_BY_NAME = 'Name'
|
||||
const SORT_BY_CREATED_TIME = 'Created time'
|
||||
|
||||
const SortByMap = {
|
||||
[SortBy.NAME]: SORT_BY_NAME,
|
||||
[SortBy.CTIME]: SORT_BY_CREATED_TIME,
|
||||
}
|
||||
|
||||
interface Props {
|
||||
show: boolean
|
||||
@@ -55,7 +67,20 @@ export default function FileManager(props: Props) {
|
||||
const [filenames, setFileNames] = useState<Filename[]>([])
|
||||
const [scrollTop, setScrollTop] = useState(0)
|
||||
const [closeScrollTop, setCloseScrollTop] = useState(0)
|
||||
const [toastVal, setToastState] = useRecoilState(toastState)
|
||||
const setToastState = useSetRecoilState(toastState)
|
||||
const [sortBy, setSortBy] = useState<SortBy>(SortBy.CTIME)
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>(SortOrder.DESCENDING)
|
||||
const ref = useRef(null)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const [debouncedSearchText, setDebouncedSearchText] = useState('')
|
||||
|
||||
const [, cancel] = useDebounce(
|
||||
() => {
|
||||
setDebouncedSearchText(searchText)
|
||||
},
|
||||
500,
|
||||
[searchText]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!show) {
|
||||
@@ -98,20 +123,37 @@ export default function FileManager(props: Props) {
|
||||
if (show) {
|
||||
fetchData()
|
||||
}
|
||||
}, [show])
|
||||
}, [show, setToastState])
|
||||
|
||||
const onScroll = (event: SyntheticEvent) => {
|
||||
setScrollTop(event.currentTarget.scrollTop)
|
||||
}
|
||||
|
||||
const photos = useMemo(() => {
|
||||
return filenames.map((filename: Filename) => {
|
||||
const width = photoWidth
|
||||
const height = filename.height * (width / filename.width)
|
||||
const src = `/media_thumbnail/${filename.name}?width=${width}&height=${height}`
|
||||
return { src, height, width }
|
||||
})
|
||||
}, [filenames])
|
||||
const filteredFilenames: Filename[] | undefined = useAsyncMemo(async () => {
|
||||
if (!debouncedSearchText) {
|
||||
return filenames
|
||||
}
|
||||
|
||||
const index = new Index()
|
||||
filenames.forEach((filename: Filename, id: number) =>
|
||||
index.add(id, filename.name)
|
||||
)
|
||||
const results: IndexSearchResult = await index.searchAsync(
|
||||
debouncedSearchText
|
||||
)
|
||||
return results.map((id: Id) => filenames[id as number])
|
||||
}, [filenames, debouncedSearchText])
|
||||
|
||||
const photos: Photo[] = useMemo(() => {
|
||||
return _.orderBy(filteredFilenames, sortBy, sortOrder).map(
|
||||
(filename: Filename) => {
|
||||
const width = photoWidth
|
||||
const height = filename.height * (width / filename.width)
|
||||
const src = `/media_thumbnail/${filename.name}?width=${width}&height=${height}`
|
||||
return { src, height, width }
|
||||
}
|
||||
)
|
||||
}, [filteredFilenames, photoWidth, sortBy, sortOrder])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -120,6 +162,66 @@ export default function FileManager(props: Props) {
|
||||
className="file-manager-modal"
|
||||
show={show}
|
||||
>
|
||||
<Flex style={{ justifyContent: 'end', gap: 8 }}>
|
||||
<Flex
|
||||
style={{
|
||||
position: 'relative',
|
||||
justifyContent: 'start',
|
||||
}}
|
||||
>
|
||||
<MagnifyingGlassIcon style={{ position: 'absolute', left: 8 }} />
|
||||
<TextInput
|
||||
ref={ref}
|
||||
value={searchText}
|
||||
className="file-search-input"
|
||||
tabIndex={-1}
|
||||
onInput={(evt: FormEvent<HTMLInputElement>) => {
|
||||
evt.preventDefault()
|
||||
evt.stopPropagation()
|
||||
const target = evt.target as HTMLInputElement
|
||||
setSearchText(target.value)
|
||||
}}
|
||||
placeholder="Search by file name"
|
||||
/>
|
||||
</Flex>
|
||||
<Flex style={{ gap: 8 }}>
|
||||
<Selector
|
||||
width={130}
|
||||
value={SortByMap[sortBy]}
|
||||
options={Object.values(SortByMap)}
|
||||
onChange={val => {
|
||||
if (val === SORT_BY_CREATED_TIME) {
|
||||
setSortBy(SortBy.CTIME)
|
||||
} else {
|
||||
setSortBy(SortBy.NAME)
|
||||
}
|
||||
}}
|
||||
chevronDirection="down"
|
||||
/>
|
||||
<Button
|
||||
icon={<BarsArrowDownIcon />}
|
||||
toolTip="Descending order"
|
||||
tooltipPosition="bottom"
|
||||
onClick={() => {
|
||||
setSortOrder(SortOrder.DESCENDING)
|
||||
}}
|
||||
className={
|
||||
sortOrder !== SortOrder.DESCENDING ? 'sort-btn-inactive' : ''
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon={<BarsArrowUpIcon />}
|
||||
toolTip="Ascending order"
|
||||
tooltipPosition="bottom"
|
||||
onClick={() => {
|
||||
setSortOrder(SortOrder.ASCENDING)
|
||||
}}
|
||||
className={
|
||||
sortOrder !== SortOrder.ASCENDING ? 'sort-btn-inactive' : ''
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<ScrollArea.Root className="ScrollAreaRoot">
|
||||
<ScrollArea.Viewport
|
||||
className="ScrollAreaViewport"
|
||||
@@ -129,9 +231,8 @@ export default function FileManager(props: Props) {
|
||||
<PhotoAlbum
|
||||
layout="masonry"
|
||||
photos={photos}
|
||||
renderPhoto={renderPhoto}
|
||||
spacing={8}
|
||||
padding={8}
|
||||
padding={0}
|
||||
onClick={onClick}
|
||||
/>
|
||||
</ScrollArea.Viewport>
|
||||
@@ -141,12 +242,12 @@ export default function FileManager(props: Props) {
|
||||
>
|
||||
<ScrollArea.Thumb className="ScrollAreaThumb" />
|
||||
</ScrollArea.Scrollbar>
|
||||
<ScrollArea.Scrollbar
|
||||
{/* <ScrollArea.Scrollbar
|
||||
className="ScrollAreaScrollbar"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<ScrollArea.Thumb className="ScrollAreaThumb" />
|
||||
</ScrollArea.Scrollbar>
|
||||
</ScrollArea.Scrollbar> */}
|
||||
<ScrollArea.Corner className="ScrollAreaCorner" />
|
||||
</ScrollArea.Root>
|
||||
</Modal>
|
||||
|
||||
Reference in New Issue
Block a user