From a7240eedb5a1de8e1b25da3bea772fea73119116 Mon Sep 17 00:00:00 2001 From: Qing Date: Sat, 7 Jan 2023 08:52:11 +0800 Subject: [PATCH] lots of update 2 --- lama_cleaner/app/package.json | 2 + lama_cleaner/app/src/App.tsx | 61 ++++--- .../components/FileManager/FileManager.scss | 17 ++ .../components/FileManager/FileManager.tsx | 169 ++++++++++++++---- lama_cleaner/app/src/components/Workspace.tsx | 6 +- .../app/src/components/shared/Layout.tsx | 27 +++ lama_cleaner/app/src/hooks/useAsyncMemo.tsx | 33 ++++ lama_cleaner/app/yarn.lock | 10 ++ lama_cleaner/file_manager/file_manager.py | 5 +- lama_cleaner/file_manager/utils.py | 2 +- lama_cleaner/model/sd.py | 1 + lama_cleaner/tests/test_paint_by_example.py | 15 ++ lama_cleaner/tests/test_sd_model.py | 25 +++ 13 files changed, 302 insertions(+), 71 deletions(-) create mode 100644 lama_cleaner/app/src/components/shared/Layout.tsx create mode 100644 lama_cleaner/app/src/hooks/useAsyncMemo.tsx diff --git a/lama_cleaner/app/package.json b/lama_cleaner/app/package.json index dd83c4f..76920c3 100644 --- a/lama_cleaner/app/package.json +++ b/lama_cleaner/app/package.json @@ -18,12 +18,14 @@ "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.1.2", "@testing-library/user-event": "^13.5.0", + "@types/flexsearch": "^0.7.3", "@types/jest": "^27.0.2", "@types/lodash": "^4.14.182", "@types/node": "^16.11.1", "@types/react": "^17.0.30", "@types/react-dom": "^17.0.9", "cross-env": "7.x", + "flexsearch": "0.7.21", "hacktimer": "^1.1.3", "lodash": "^4.17.21", "mitt": "^3.0.0", diff --git a/lama_cleaner/app/src/App.tsx b/lama_cleaner/app/src/App.tsx index bfe7550..1e50a7d 100644 --- a/lama_cleaner/app/src/App.tsx +++ b/lama_cleaner/app/src/App.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useMemo } from 'react' -import { useRecoilState } from 'recoil' +import { useRecoilState, useSetRecoilState } from 'recoil' import { nanoid } from 'nanoid' import useInputImage from './hooks/useInputImage' import { themeState } from './components/Header/ThemeChanger' @@ -30,14 +30,10 @@ const SUPPORTED_FILE_TYPE = [ function App() { const [file, setFile] = useRecoilState(fileState) const [theme, setTheme] = useRecoilState(themeState) - const [toastVal, setToastState] = useRecoilState(toastState) + const setToastState = useSetRecoilState(toastState) const userInputImage = useInputImage() - const [isDisableModelSwitch, setIsDisableModelSwitch] = useRecoilState( - isDisableModelSwitchState - ) - const [enableFileManager, setEnableFileManager] = useRecoilState( - enableFileManagerState - ) + const setIsDisableModelSwitch = useSetRecoilState(isDisableModelSwitchState) + const setEnableFileManager = useSetRecoilState(enableFileManagerState) // Set Input Image useEffect(() => { @@ -70,7 +66,7 @@ function App() { setEnableFileManager(isEnabled === 'true') } fetchData2() - }, []) + }, [setEnableFileManager, setIsDisableModelSwitch]) // Dark Mode Hotkey useHotKey( @@ -118,35 +114,38 @@ function App() { setIsDragging(false) }, []) - const handleDrop = React.useCallback(event => { - event.preventDefault() - event.stopPropagation() - setIsDragging(false) - if (event.dataTransfer.files && event.dataTransfer.files.length > 0) { - if (event.dataTransfer.files.length > 1) { - setToastState({ - open: true, - desc: 'Please drag and drop only one file', - state: 'error', - duration: 3000, - }) - } else { - const dragFile = event.dataTransfer.files[0] - const fileType = dragFile.type - if (SUPPORTED_FILE_TYPE.includes(fileType)) { - setFile(dragFile) - } else { + const handleDrop = React.useCallback( + event => { + event.preventDefault() + event.stopPropagation() + setIsDragging(false) + if (event.dataTransfer.files && event.dataTransfer.files.length > 0) { + if (event.dataTransfer.files.length > 1) { setToastState({ open: true, - desc: 'Please drag and drop an image file', + desc: 'Please drag and drop only one file', state: 'error', duration: 3000, }) + } else { + const dragFile = event.dataTransfer.files[0] + const fileType = dragFile.type + if (SUPPORTED_FILE_TYPE.includes(fileType)) { + setFile(dragFile) + } else { + setToastState({ + open: true, + desc: 'Please drag and drop an image file', + state: 'error', + duration: 3000, + }) + } } + event.dataTransfer.clearData() } - event.dataTransfer.clearData() - } - }, []) + }, + [setToastState, setFile] + ) const onPaste = useCallback((event: any) => { // TODO: when sd side panel open, ctrl+v not work diff --git a/lama_cleaner/app/src/components/FileManager/FileManager.scss b/lama_cleaner/app/src/components/FileManager/FileManager.scss index 4353ae7..e844108 100644 --- a/lama_cleaner/app/src/components/FileManager/FileManager.scss +++ b/lama_cleaner/app/src/components/FileManager/FileManager.scss @@ -13,6 +13,9 @@ } .react-photo-album--photo { + -moz-user-select: none; + -webkit-user-select: none; + user-select: none; border-radius: 8px; border: 1px solid transparent; @@ -82,3 +85,17 @@ .ScrollAreaCorner { background: var(--blackA8); } + +.file-search-input { + width: 250px; + padding-left: 30px; + height: 32px; + border: 1px solid var(--border-color); + border-radius: 8px; +} + +.sort-btn-inactive { + svg { + opacity: 0.5; + } +} diff --git a/lama_cleaner/app/src/components/FileManager/FileManager.tsx b/lama_cleaner/app/src/components/FileManager/FileManager.tsx index f0aaf9a..e88fbc0 100644 --- a/lama_cleaner/app/src/components/FileManager/FileManager.tsx +++ b/lama_cleaner/app/src/components/FileManager/FileManager.tsx @@ -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 }, -}) => ( -
- {alt} -
-) +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([]) const [scrollTop, setScrollTop] = useState(0) const [closeScrollTop, setCloseScrollTop] = useState(0) - const [toastVal, setToastState] = useRecoilState(toastState) + const setToastState = useSetRecoilState(toastState) + const [sortBy, setSortBy] = useState(SortBy.CTIME) + const [sortOrder, setSortOrder] = useState(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 ( + + + + ) => { + evt.preventDefault() + evt.stopPropagation() + const target = evt.target as HTMLInputElement + setSearchText(target.value) + }} + placeholder="Search by file name" + /> + + + { + if (val === SORT_BY_CREATED_TIME) { + setSortBy(SortBy.CTIME) + } else { + setSortBy(SortBy.NAME) + } + }} + chevronDirection="down" + /> +