1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-02 23:48:47 +00:00

fix: metadata gallery view toggle detail keep the current visible image (#6964)

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇
2024-10-29 17:52:44 +08:00
committed by GitHub
parent 2e7e5f41f9
commit 6a91658638
5 changed files with 103 additions and 21 deletions

View File

@@ -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;

View File

@@ -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 (
<div
id={img.id}
tabIndex={1}
className={classnames('metadata-gallery-image-item', {
'metadata-gallery-image-item-selected': isSelected,
})}
style={{ width: size, height: size, background }}
onClick={onClick}
onDoubleClick={onDoubleClick}
onContextMenu={onContextMenu}
>
<img
className="metadata-gallery-grid-image"
src={img.src}
alt={img.name}
draggable="false"
onLoad={onLoad}
/>
</div>
);
};
Image.propTypes = {
isSelected: PropTypes.bool,
img: PropTypes.object,
size: PropTypes.number,
onClick: PropTypes.func,
onDoubleClick: PropTypes.func,
onContextMenu: PropTypes.func,
};
export default Image;

View File

@@ -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 = () => {
<div className={`sf-metadata-gallery-container sf-metadata-gallery-container-${mode}`} ref={containerRef} onScroll={handleScroll} >
{!isFirstLoading && (
<>
<GalleryMain
<Main
groups={groups}
size={imageSize}
columns={columns}

View File

@@ -1,9 +1,9 @@
import React, { useState, useCallback, useMemo, useRef } from 'react';
import classnames from 'classnames';
import PropTypes from 'prop-types';
import EmptyTip from '../../../components/empty-tip';
import { gettext } from '../../../utils/constants';
import { GALLERY_DATE_MODE } from '../../constants';
import Image from './image';
const GalleryMain = ({
groups,
@@ -129,20 +129,15 @@ const GalleryMain = ({
return row.children.map((img) => {
const isSelected = selectedImageIds.includes(img.id);
return (
<div
<Image
key={img.src}
id={img.id}
tabIndex={1}
className={classnames('metadata-gallery-image-item', {
'metadata-gallery-image-item-selected': isSelected,
})}
style={{ width: size, height: size, background: '#f1f1f1' }}
isSelected={isSelected}
img={img}
size={size}
onClick={(e) => onImageClick(e, img)}
onDoubleClick={(e) => onImageDoubleClick(e, img)}
onContextMenu={(e) => onImageRightClick(e, img)}
>
<img className="metadata-gallery-grid-image" src={img.src} alt={img.name} draggable="false" />
</div>
/>
);
});
})}

View File

@@ -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 {
<div className="message err-tip">{gettext('Folder does not exist.')}</div>
}
{this.state.isDirentDetailShow && (
<DetailContainer
<Detail
path={this.state.path}
repoID={this.props.repoID}
currentRepoInfo={this.state.currentRepoInfo}