diff --git a/frontend/src/components/dirent-detail/detail-container.js b/frontend/src/components/dirent-detail/index.js similarity index 92% rename from frontend/src/components/dirent-detail/detail-container.js rename to frontend/src/components/dirent-detail/index.js index 0e8dcb0c75..8fabb6c6f9 100644 --- a/frontend/src/components/dirent-detail/detail-container.js +++ b/frontend/src/components/dirent-detail/index.js @@ -7,7 +7,7 @@ import ObjectUtils from '../../metadata/utils/object-utils'; import { MetadataContext } from '../../metadata'; import { PRIVATE_FILE_TYPE } from '../../constants'; -const DetailContainer = React.memo(({ repoID, path, dirent, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => { +const Detail = React.memo(({ repoID, path, dirent, currentRepoInfo, repoTags, fileTags, onClose, onFileTagChanged }) => { const isView = useMemo(() => path.startsWith('/' + PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES), [path]); useEffect(() => { @@ -61,7 +61,7 @@ const DetailContainer = React.memo(({ repoID, path, dirent, currentRepoInfo, rep return !isChanged; }); -DetailContainer.propTypes = { +Detail.propTypes = { repoID: PropTypes.string, path: PropTypes.string, dirent: PropTypes.object, @@ -72,4 +72,4 @@ DetailContainer.propTypes = { onFileTagChanged: PropTypes.func, }; -export default DetailContainer; +export default Detail; diff --git a/frontend/src/metadata/views/gallery/image.js b/frontend/src/metadata/views/gallery/image.js new file mode 100644 index 0000000000..671680733f --- /dev/null +++ b/frontend/src/metadata/views/gallery/image.js @@ -0,0 +1,51 @@ +import React, { useCallback, useState } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; + +const Image = ({ + isSelected, + img, + size, + onClick, + onDoubleClick, + onContextMenu, +}) => { + const [background, setBackground] = useState('#f1f1f1'); + + const onLoad = useCallback(() => { + setBackground('unset'); + }, []); + + return ( +
+ {img.name} +
+ ); +}; + +Image.propTypes = { + isSelected: PropTypes.bool, + img: PropTypes.object, + size: PropTypes.number, + onClick: PropTypes.func, + onDoubleClick: PropTypes.func, + onContextMenu: PropTypes.func, +}; + +export default Image; diff --git a/frontend/src/metadata/views/gallery/index.js b/frontend/src/metadata/views/gallery/index.js index 269b964148..d27e44e453 100644 --- a/frontend/src/metadata/views/gallery/index.js +++ b/frontend/src/metadata/views/gallery/index.js @@ -3,7 +3,7 @@ import { CenteredLoading } from '@seafile/sf-metadata-ui-component'; import metadataAPI from '../../api'; import URLDecorator from '../../../utils/url-decorator'; import toaster from '../../../components/toast'; -import GalleryMain from './gallery-main'; +import Main from './main'; import ContextMenu from './context-menu'; import ImageDialog from '../../../components/dialog/image-dialog'; import ZipDownloadDialog from '../../../components/dialog/zip-download-dialog'; @@ -32,6 +32,7 @@ const Gallery = () => { const containerRef = useRef(null); const renderMoreTimer = useRef(null); + const lastState = useRef({ visibleAreaFirstImage: { groupIndex: 0, rowIndex: 0 } }); const { metadata, store, updateCurrentDirent } = useMetadataView(); const repoID = window.sfMetadataContext.getSetting('repoID'); @@ -97,6 +98,7 @@ const Gallery = () => { let _groups = []; const imageHeight = imageSize + GALLERY_IMAGE_GAP; + const paddingTop = mode === GALLERY_DATE_MODE.ALL ? 0 : DATE_TAG_HEIGHT; init.forEach((_init, index) => { const { children, ...__init } = _init; let top = 0; @@ -108,11 +110,12 @@ const Gallery = () => { } children.forEach((child, childIndex) => { const rowIndex = ~~(childIndex / columns); - if (!rows[rowIndex]) rows[rowIndex] = { top: top + rowIndex * imageHeight, children: [] }; + if (!rows[rowIndex]) rows[rowIndex] = { top: paddingTop + top + rowIndex * imageHeight, children: [] }; + child.groupIndex = index; + child.rowIndex = rowIndex; rows[rowIndex].children.push(child); }); - const paddingTop = mode === GALLERY_DATE_MODE.ALL ? 0 : DATE_TAG_HEIGHT; const height = rows.length * imageHeight + paddingTop; _groups.push({ ...__init, @@ -192,6 +195,22 @@ const Gallery = () => { }; }, []); + useEffect(() => { + if (!imageSize || imageSize < 0) return; + if (imageSize === lastState.current.imageSize) return; + const perImageOffset = imageSize - lastState.current.imageSize; + const { groupIndex, rowIndex } = lastState.current.visibleAreaFirstImage; + const rowOffset = groups.reduce((previousValue, current, currentIndex) => { + if (currentIndex < groupIndex) { + return previousValue + current.children.length; + } + return previousValue; + }, 0) + rowIndex; + const topOffset = rowOffset * perImageOffset + groupIndex * (mode === GALLERY_DATE_MODE.ALL ? 0 : DATE_TAG_HEIGHT); + containerRef.current.scrollTop = containerRef.current.scrollTop + topOffset; + lastState.current = { ...lastState.current, imageSize }; + }, [imageSize, groups, mode]); + const handleScroll = useCallback(() => { if (!containerRef.current) return; const { scrollTop, scrollHeight, clientHeight } = containerRef.current; @@ -203,11 +222,28 @@ const Gallery = () => { const { scrollTop, clientHeight } = containerRef.current; const overScanTop = Math.max(0, scrollTop - (imageSize + GALLERY_IMAGE_GAP) * 3); const overScanBottom = scrollTop + clientHeight + (imageSize + GALLERY_IMAGE_GAP) * 3; + let groupIndex = 0; + let rowIndex = 0; + let flag = false; + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + for (let j = 0; j < group.children.length; j++) { + const row = group.children[j]; + if (row.top >= scrollTop) { + groupIndex = i; + rowIndex = j; + flag = true; + } + if (flag) break; + } + if (flag) break; + } + lastState.current = { ...lastState.current, visibleAreaFirstImage: { groupIndex, rowIndex } }; setOverScan({ top: overScanTop, bottom: overScanBottom }); renderMoreTimer.current = null; }, 200); } - }, [imageSize, loadMore, renderMoreTimer]); + }, [imageSize, loadMore, renderMoreTimer, groups]); const imageItems = useMemo(() => { return groups.flatMap(group => group.children.flatMap(row => row.children)); @@ -351,7 +387,7 @@ const Gallery = () => {
{!isFirstLoading && ( <> - { const isSelected = selectedImageIds.includes(img.id); return ( -
onImageClick(e, img)} onDoubleClick={(e) => onImageDoubleClick(e, img)} onContextMenu={(e) => onImageRightClick(e, img)} - > - {img.name} -
+ /> ); }); })} diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index f46c499668..3eb676522c 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -26,7 +26,7 @@ import { MetadataProvider, CollaboratorsProvider } from '../../metadata/hooks'; import { LIST_MODE, METADATA_MODE, FACE_RECOGNITION_MODE, DIRENT_DETAIL_MODE } from '../../components/dir-view-mode/constants'; import CurDirPath from '../../components/cur-dir-path'; import DirTool from '../../components/cur-dir-path/dir-tool'; -import DetailContainer from '../../components/dirent-detail/detail-container'; +import Detail from '../../components/dirent-detail'; import DirColumnView from '../../components/dir-view-mode/dir-column-view'; import SelectedDirentsToolbar from '../../components/toolbar/selected-dirents-toolbar'; import { VIEW_TYPE } from '../../metadata/constants'; @@ -2427,7 +2427,7 @@ class LibContentView extends React.Component {
{gettext('Folder does not exist.')}
} {this.state.isDirentDetailShow && ( -