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:
@@ -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;
|
51
frontend/src/metadata/views/gallery/image.js
Normal file
51
frontend/src/metadata/views/gallery/image.js
Normal 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;
|
@@ -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}
|
||||
|
@@ -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>
|
||||
/>
|
||||
);
|
||||
});
|
||||
})}
|
@@ -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}
|
||||
|
Reference in New Issue
Block a user