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 (
+
+

+
+ );
+};
+
+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)}
- >
-

-
+ />
);
});
})}
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 && (
-