Skip to content

Commit

Permalink
Support mouse drag for multiple selection, improve interactivity (#6823)
Browse files Browse the repository at this point in the history
  • Loading branch information
Aries-0331 authored Sep 27, 2024
1 parent ed915c1 commit be1509c
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 28 deletions.
2 changes: 2 additions & 0 deletions frontend/src/metadata/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ export const GALLERY_ZOOM_GEAR_MIN = -2;

export const GALLERY_ZOOM_GEAR_MAX = 2;

export const GALLERY_IMAGE_GAP = 2;

export const GALLERY_DATE_MODE = {
YEAR: 'year',
MONTH: 'month',
Expand Down
101 changes: 94 additions & 7 deletions frontend/src/metadata/views/gallery/gallery-main.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,85 @@
import React, { useCallback, useMemo, useRef } from 'react';
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';

const GalleryMain = ({ groups, overScan, columns, size, gap, selectedImages, onImageClick, onImageDoubleClick, onImageRightClick }) => {
const GalleryMain = ({
groups,
overScan,
columns,
size,
gap,
selectedImages,
setSelectedImages,
onImageClick,
onImageDoubleClick,
onImageRightClick
}) => {
const containerRef = useRef(null);
const imageRef = useRef(null);
const animationFrameRef = useRef(null);

const [isSelecting, setIsSelecting] = useState(false);
const [selectionStart, setSelectionStart] = useState(null);

const imageHeight = useMemo(() => size + gap, [size, gap]);

const handleMouseDown = useCallback((e) => {
if (e.button !== 0) return;
if (e.ctrlKey || e.metaKey || e.shiftKey) return;

setIsSelecting(true);
setSelectionStart({ x: e.clientX, y: e.clientY });
setSelectedImages([]);

}, [setSelectedImages]);

const handleMouseMove = useCallback((e) => {
if (!isSelecting) return;

if (animationFrameRef.current) {
cancelAnimationFrame(animationFrameRef.current);
}

animationFrameRef.current = requestAnimationFrame(() => {
e.preventDefault();
e.stopPropagation();

const selectionEnd = { x: e.clientX, y: e.clientY };
const selected = [];

groups.forEach(group => {
group.children.forEach((row) => {
row.children.forEach((img) => {
const imgElement = document.getElementById(img.id);
if (imgElement) {
const rect = imgElement.getBoundingClientRect();
if (
rect.left < Math.max(selectionStart.x, selectionEnd.x) &&
rect.right > Math.min(selectionStart.x, selectionEnd.x) &&
rect.top < Math.max(selectionStart.y, selectionEnd.y) &&
rect.bottom > Math.min(selectionStart.y, selectionEnd.y)
) {
selected.push(img);
}
}
});
});
});

setSelectedImages(selected);
});
}, [groups, isSelecting, selectionStart, setSelectedImages]);

const handleMouseUp = useCallback((e) => {
if (e.button !== 0) return;

e.preventDefault();
e.stopPropagation();
setIsSelecting(false);
}, []);

const renderDisplayGroup = useCallback((group) => {
const { top: overScanTop, bottom: overScanBottom } = overScan;
const { name, children, height, top, paddingTop } = group;
Expand Down Expand Up @@ -36,7 +107,12 @@ const GalleryMain = ({ groups, overScan, columns, size, gap, selectedImages, onI
}

return (
<div key={name} className="metadata-gallery-date-group w-100" style={{ height, paddingTop }}>
<div
key={name}
className="metadata-gallery-date-group"
style={{ height, paddingTop }}

>
{childrenStartIndex === 0 && (<div className="metadata-gallery-date-tag">{name}</div>)}
<div
ref={imageRef}
Expand All @@ -53,6 +129,7 @@ const GalleryMain = ({ groups, overScan, columns, size, gap, selectedImages, onI
return (
<div
key={img.src}
id={img.id}
tabIndex={1}
className={classnames('metadata-gallery-image-item', {
'metadata-gallery-image-item-selected': isSelected,
Expand All @@ -62,7 +139,7 @@ const GalleryMain = ({ groups, overScan, columns, size, gap, selectedImages, onI
onDoubleClick={(e) => onImageDoubleClick(e, img)}
onContextMenu={(e) => onImageRightClick(e, img)}
>
<img className="metadata-gallery-grid-image" src={img.src} alt={img.name} />
<img className="metadata-gallery-grid-image" src={img.src} alt={img.name} draggable="false" />
</div>
);
});
Expand All @@ -76,9 +153,19 @@ const GalleryMain = ({ groups, overScan, columns, size, gap, selectedImages, onI
return <EmptyTip text={gettext('No record')}/>;
}

return groups.map((group, index) => {
return renderDisplayGroup(group, index);
});
return (
<div
ref={containerRef}
className='metadata-gallery-main'
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
>
{groups.map((group) => {
return renderDisplayGroup(group);
})}
</div>
);
};

GalleryMain.propTypes = {
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/metadata/views/gallery/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
}

.metadata-gallery-date-group {
width: 100%;
position: relative;
}

Expand All @@ -28,6 +29,10 @@
user-select: none;
}

.metadata-gallery-main {
flex: 1;
}

.metadata-gallery-image-list {
display: grid;
gap: 2px;
Expand All @@ -52,6 +57,7 @@
height: 100%;
object-fit: cover;
transition: transform 0.2s ease-in-out;
user-select: none;
}

.metadata-gallery-grid-image:hover {
Expand Down
28 changes: 7 additions & 21 deletions frontend/src/metadata/views/gallery/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,10 @@ import { useMetadataView } from '../../hooks/metadata-view';
import { Utils } from '../../../utils/utils';
import { getDateDisplayString } from '../../utils/cell';
import { siteRoot, fileServerRoot, useGoFileserver, gettext, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY, GALLERY_DATE_MODE, DATE_TAG_HEIGHT } from '../../constants';
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER, PRIVATE_COLUMN_KEY, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants';

import './index.css';

const IMAGE_GAP = 2;

const Gallery = () => {
const [isFirstLoading, setFirstLoading] = useState(true);
const [isLoadingMore, setLoadingMore] = useState(false);
Expand Down Expand Up @@ -90,7 +88,7 @@ const Gallery = () => {
}, []);

let _groups = [];
const imageHeight = imageSize + IMAGE_GAP;
const imageHeight = imageSize + GALLERY_IMAGE_GAP;
init.forEach((_init, index) => {
const { children, ...__init } = _init;
let top = 0;
Expand Down Expand Up @@ -160,7 +158,7 @@ const Gallery = () => {
// Calculate initial overScan information
const columns = 8 - gear;
const imageSize = (offsetWidth - columns * 2 - 2) / columns;
setOverScan({ top: 0, bottom: clientHeight + (imageSize + IMAGE_GAP) * 2 });
setOverScan({ top: 0, bottom: clientHeight + (imageSize + GALLERY_IMAGE_GAP) * 2 });
}
setFirstLoading(false);

Expand All @@ -186,18 +184,6 @@ const Gallery = () => {
};
}, []);

useEffect(() => {
const handleClickOutside = (e) => {
if (containerRef.current && !containerRef.current.contains(e.target) || e.target.tagName.toLowerCase() !== 'img') {
setSelectedImages([]);
}
};
document.addEventListener('click', handleClickOutside);
return () => {
document.removeEventListener('click', handleClickOutside);
};
}, []);

const handleScroll = useCallback(() => {
if (!containerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
Expand All @@ -207,8 +193,8 @@ const Gallery = () => {
renderMoreTimer.current && clearTimeout(renderMoreTimer.current);
renderMoreTimer.current = setTimeout(() => {
const { scrollTop, clientHeight } = containerRef.current;
const overScanTop = Math.max(0, scrollTop - (imageSize + IMAGE_GAP) * 3);
const overScanBottom = scrollTop + clientHeight + (imageSize + IMAGE_GAP) * 3;
const overScanTop = Math.max(0, scrollTop - (imageSize + GALLERY_IMAGE_GAP) * 3);
const overScanBottom = scrollTop + clientHeight + (imageSize + GALLERY_IMAGE_GAP) * 3;
setOverScan({ top: overScanTop, bottom: overScanBottom });
renderMoreTimer.current = null;
}, 200);
Expand Down Expand Up @@ -241,7 +227,6 @@ const Gallery = () => {
setIsImagePopupOpen(true);
}, [imageItems]);


const handleRightClick = useCallback((event, image) => {
event.preventDefault();
const index = imageItems.findIndex(item => item.id === image.id);
Expand Down Expand Up @@ -333,8 +318,9 @@ const Gallery = () => {
size={imageSize}
columns={columns}
overScan={overScan}
gap={IMAGE_GAP}
gap={GALLERY_IMAGE_GAP}
selectedImages={selectedImages}
setSelectedImages={setSelectedImages}
onImageClick={handleClick}
onImageDoubleClick={handleDoubleClick}
onImageRightClick={handleRightClick}
Expand Down

0 comments on commit be1509c

Please sign in to comment.