1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-27 07:44:50 +00:00

Gallery image selection (#8144)

* [metadata] 'Gallery' view: display a transparent blue rectangle to show the selected area when use a mouse to select items

* [metadata] 'Gallery' view: added a top toolbar for the selected images(it offers 'unselect', 'download', 'delete' & 'copy' in the toolbar)

* [metadata] 'Gallery' view: fixup for 'moving the mouse to select images'

* [metadata] 'Gallery' view: code cleanup
This commit is contained in:
llj
2025-08-19 18:29:27 +08:00
committed by GitHub
parent 10c2f53cb1
commit 83572b20cc
10 changed files with 219 additions and 48 deletions

View File

@@ -0,0 +1,89 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { gettext } from '../../utils/constants';
import { EVENT_BUS_TYPE } from '../../metadata/constants';
import RowUtils from '../../metadata/views/table/utils/row-utils';
import { Dirent } from '../../models';
import { useFileOperations } from '../../hooks/file-operations';
const GalleryFilesToolbar = () => {
const [selectedRecordIds, setSelectedRecordIds] = useState([]);
const metadataRef = useRef([]);
const { handleDownload: handleDownloadAPI, handleCopy: handleCopyAPI } = useFileOperations();
const eventBus = window.sfMetadataContext && window.sfMetadataContext.eventBus;
const checkCanDeleteRow = window.sfMetadataContext.checkCanDeleteRow();
const canDuplicateRow = window.sfMetadataContext.canDuplicateRow();
useEffect(() => {
const unsubscribeSelectedFileIds = eventBus && eventBus.subscribe(EVENT_BUS_TYPE.SELECT_RECORDS, (ids, metadata) => {
metadataRef.current = metadata || [];
setSelectedRecordIds(ids);
});
return () => {
unsubscribeSelectedFileIds && unsubscribeSelectedFileIds();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const records = useMemo(() => selectedRecordIds.map(id => RowUtils.getRecordById(id, metadataRef.current)).filter(Boolean) || [], [selectedRecordIds]);
const unSelect = useCallback(() => {
setSelectedRecordIds([]);
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SELECTED_RECORD_IDS, []);
eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
}, [eventBus]);
const handleDownload = useCallback(() => {
const list = records.map(record => {
const { _parent_dir: parentDir, _name: fileName } = record || {};
const name = parentDir === '/' ? fileName : `${parentDir}/${fileName}`;
return { name };
});
handleDownloadAPI('/', list);
}, [handleDownloadAPI, records]);
const deleteRecords = useCallback(() => {
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.DELETE_RECORDS, selectedRecordIds, {
success_callback: () => {
eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
}
});
}, [eventBus, selectedRecordIds]);
const handleDuplicate = useCallback((destRepo, dirent, destPath, nodeParentPath, isByDialog) => {
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.DUPLICATE_RECORD, selectedRecordIds[0],
destRepo, dirent, destPath, nodeParentPath, isByDialog);
}, [eventBus, selectedRecordIds]);
const handleCopy = useCallback(() => {
const { _parent_dir: parentDir, _name: fileName } = records[0] || {};
const dirent = new Dirent({ name: fileName });
handleCopyAPI(parentDir, dirent, false, handleDuplicate);
}, [records, handleCopyAPI, handleDuplicate]);
const length = selectedRecordIds.length;
return (
<div className="selected-dirents-toolbar">
<span className="cur-view-path-btn px-2" onClick={unSelect}>
<span className="sf3-font-x-01 sf3-font mr-2" aria-label={gettext('Unselect')} title={gettext('Unselect')}></span>
<span>{length}{' '}{gettext('selected')}</span>
</span>
<span className="cur-view-path-btn" onClick={handleDownload}>
<span className="sf3-font-download1 sf3-font" aria-label={gettext('Download')} title={gettext('Download')}></span>
</span>
{checkCanDeleteRow &&
<span className="cur-view-path-btn" onClick={deleteRecords}>
<span className="sf3-font-delete1 sf3-font" aria-label={gettext('Delete')} title={gettext('Delete')}></span>
</span>
}
{(canDuplicateRow && length === 1) &&
<span className="cur-view-path-btn" onClick={handleCopy}>
<span className="sf3-font-copy1 sf3-font" aria-label={gettext('Copy')} title={gettext('Copy')}></span>
</span>
}
</div>
);
};
export default GalleryFilesToolbar;

View File

@@ -1,21 +1,38 @@
import React from 'react'; import React, { useMemo } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { TAGS_MODE } from '../dir-view-mode/constants'; import { TAGS_MODE } from '../dir-view-mode/constants';
import { ALL_TAGS_ID } from '../../tag/constants'; import { ALL_TAGS_ID } from '../../tag/constants';
import { useMetadata } from '../../metadata/hooks';
import { VIEW_TYPE } from '../../metadata/constants';
import AllTagsToolbar from './all-tags-toolbar'; import AllTagsToolbar from './all-tags-toolbar';
import TagFilesToolbar from './tag-files-toolbar'; import TagFilesToolbar from './tag-files-toolbar';
import TableFilesToolbar from './table-files-toolbar'; import TableFilesToolbar from './table-files-toolbar';
import GalleryFilesToolbar from './gallery-files-toolbar';
const MetadataPathToolbar = ({ repoID, repoInfo, mode, path, viewId }) => {
const { idViewMap } = useMetadata();
const view = useMemo(() => idViewMap[viewId], [viewId, idViewMap]);
const type = view?.type;
if (type === VIEW_TYPE.GALLERY) {
return (
<GalleryFilesToolbar />
);
}
if (type === VIEW_TYPE.TABLE) {
return (
<TableFilesToolbar repoID={repoID} />
);
}
const MetadataPathToolbar = ({ repoID, repoInfo, mode, path }) => {
if (mode === TAGS_MODE) { if (mode === TAGS_MODE) {
const isAllTagsView = path.split('/').pop() === ALL_TAGS_ID; const isAllTagsView = path.split('/').pop() === ALL_TAGS_ID;
if (isAllTagsView) return <AllTagsToolbar />; if (isAllTagsView) return <AllTagsToolbar />;
return <TagFilesToolbar currentRepoInfo={repoInfo} />; return <TagFilesToolbar currentRepoInfo={repoInfo} />;
} }
return (
<TableFilesToolbar repoID={repoID} />
);
}; };
MetadataPathToolbar.propTypes = { MetadataPathToolbar.propTypes = {

View File

@@ -42,6 +42,7 @@ export const EVENT_BUS_TYPE = {
SELECT_RECORDS: 'select_records', SELECT_RECORDS: 'select_records',
TOGGLE_MOVE_DIALOG: 'toggle_move_dialog', TOGGLE_MOVE_DIALOG: 'toggle_move_dialog',
MOVE_RECORD: 'move_record', MOVE_RECORD: 'move_record',
DUPLICATE_RECORD: 'duplicate_record',
DELETE_RECORDS: 'delete_records', DELETE_RECORDS: 'delete_records',
UPDATE_RECORD_DETAILS: 'update_record_details', UPDATE_RECORD_DETAILS: 'update_record_details',
UPDATE_FACE_RECOGNITION: 'update_face_recognition', UPDATE_FACE_RECOGNITION: 'update_face_recognition',

View File

@@ -18,3 +18,12 @@ export const GALLERY_DATE_MODE = {
export const STORAGE_GALLERY_DATE_MODE_KEY = 'gallery_date_mode'; export const STORAGE_GALLERY_DATE_MODE_KEY = 'gallery_date_mode';
export const STORAGE_GALLERY_ZOOM_GEAR_KEY = 'gallery_zoom_gear'; export const STORAGE_GALLERY_ZOOM_GEAR_KEY = 'gallery_zoom_gear';
export const GALLERY_OPERATION_KEYS = {
DOWNLOAD: 'download',
DELETE: 'delete',
DUPLICATE: 'duplicate',
REMOVE: 'remove',
SET_PEOPLE_PHOTO: 'set_people_photo',
ADD_PHOTO_TO_GROUPS: 'add_photo_to_groups'
};

View File

@@ -633,6 +633,7 @@ export const MetadataViewProvider = ({
const unsubscribeLocalColumnChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_COLUMN_DATA_CHANGED, updateLocalColumnData); const unsubscribeLocalColumnChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_COLUMN_DATA_CHANGED, updateLocalColumnData);
const unsubscribeUpdateSelectedRecordIds = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SELECTED_RECORD_IDS, updateSelectedRecordIds); const unsubscribeUpdateSelectedRecordIds = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SELECTED_RECORD_IDS, updateSelectedRecordIds);
const unsubscribeMoveRecord = eventBus.subscribe(EVENT_BUS_TYPE.MOVE_RECORD, moveRecord); const unsubscribeMoveRecord = eventBus.subscribe(EVENT_BUS_TYPE.MOVE_RECORD, moveRecord);
const unsubscribeDuplicateRecord = eventBus.subscribe(EVENT_BUS_TYPE.DUPLICATE_RECORD, duplicateRecord);
const unsubscribeDeleteRecords = eventBus.subscribe(EVENT_BUS_TYPE.DELETE_RECORDS, deleteRecords); const unsubscribeDeleteRecords = eventBus.subscribe(EVENT_BUS_TYPE.DELETE_RECORDS, deleteRecords);
const unsubscribeUpdateDetails = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_RECORD_DETAILS, updateRecordDetails); const unsubscribeUpdateDetails = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_RECORD_DETAILS, updateRecordDetails);
const unsubscribeUpdateFaceRecognition = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_FACE_RECOGNITION, updateFaceRecognition); const unsubscribeUpdateFaceRecognition = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_FACE_RECOGNITION, updateFaceRecognition);
@@ -661,6 +662,7 @@ export const MetadataViewProvider = ({
unsubscribeLocalColumnChanged(); unsubscribeLocalColumnChanged();
unsubscribeUpdateSelectedRecordIds(); unsubscribeUpdateSelectedRecordIds();
unsubscribeMoveRecord(); unsubscribeMoveRecord();
unsubscribeDuplicateRecord();
unsubscribeDeleteRecords(); unsubscribeDeleteRecords();
unsubscribeUpdateDetails(); unsubscribeUpdateDetails();
unsubscribeUpdateFaceRecognition(); unsubscribeUpdateFaceRecognition();

View File

@@ -26,6 +26,7 @@ const Content = ({
const [isSelecting, setIsSelecting] = useState(false); const [isSelecting, setIsSelecting] = useState(false);
const [selectionStart, setSelectionStart] = useState(null); const [selectionStart, setSelectionStart] = useState(null);
const [selectionEnd, setSelectionEnd] = useState(null);
const selectedImageIds = useMemo(() => selectedImages.map(img => img.id), [selectedImages]); const selectedImageIds = useMemo(() => selectedImages.map(img => img.id), [selectedImages]);
@@ -50,6 +51,7 @@ const Content = ({
const selectionEnd = { x: e.clientX, y: e.clientY }; const selectionEnd = { x: e.clientX, y: e.clientY };
const selected = []; const selected = [];
setSelectionEnd(selectionEnd);
groups.forEach(group => { groups.forEach(group => {
group.children.forEach((row) => { group.children.forEach((row) => {
@@ -80,6 +82,7 @@ const Content = ({
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setIsSelecting(false); setIsSelecting(false);
setSelectionEnd(null);
}, []); }, []);
const renderDisplayGroup = useCallback((group) => { const renderDisplayGroup = useCallback((group) => {
@@ -185,12 +188,30 @@ const Content = ({
); );
}, [overScan, mode, columns, rowHeight, onImageClick, onImageDoubleClick, onContextMenu, size, selectedImageIds, onDateTagClick]); }, [overScan, mode, columns, rowHeight, onImageClick, onImageDoubleClick, onContextMenu, size, selectedImageIds, onDateTagClick]);
const renderSelectionBox = useCallback(() => {
if (!isSelecting) return null;
if (!selectionEnd) return null;
const containerBounds = containerRef.current.getBoundingClientRect();
const left = Math.min(selectionStart.x, selectionEnd.x) - containerBounds.left;
const top = Math.min(selectionStart.y, selectionEnd.y) - containerBounds.top;
const width = Math.abs(selectionStart.x - selectionEnd.x);
const height = Math.abs(selectionStart.y - selectionEnd.y);
return (
<div
className="selection-box"
style={{ left, top, width, height }}
>
</div>
);
}, [isSelecting, selectionStart, selectionEnd]);
if (!Array.isArray(groups) || groups.length === 0) return (<EmptyTip text={gettext('No record')}/>); if (!Array.isArray(groups) || groups.length === 0) return (<EmptyTip text={gettext('No record')}/>);
return ( return (
<div <div
ref={containerRef} ref={containerRef}
className="metadata-gallery-main" className="metadata-gallery-main position-relative"
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove} onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp} onMouseUp={handleMouseUp}
@@ -198,6 +219,7 @@ const Content = ({
{groups.map((group) => { {groups.map((group) => {
return renderDisplayGroup(group); return renderDisplayGroup(group);
})} })}
{renderSelectionBox()}
</div> </div>
); );
}; };

View File

@@ -6,15 +6,7 @@ import PeoplesDialog from '../../../components/dialog/peoples-dialog';
import { gettext } from '../../../../utils/constants'; import { gettext } from '../../../../utils/constants';
import { Dirent } from '../../../../models'; import { Dirent } from '../../../../models';
import { useFileOperations } from '../../../../hooks/file-operations'; import { useFileOperations } from '../../../../hooks/file-operations';
import { GALLERY_OPERATION_KEYS } from '../../../constants';
const CONTEXT_MENU_KEY = {
DOWNLOAD: 'download',
DELETE: 'delete',
DUPLICATE: 'duplicate',
REMOVE: 'remove',
SET_PEOPLE_PHOTO: 'set_people_photo',
ADD_PHOTO_TO_GROUPS: 'add_photo_to_groups',
};
const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveImage, onAddImage, onSetPeoplePhoto }) => { const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveImage, onAddImage, onSetPeoplePhoto }) => {
const [isPeoplesDialogShow, setPeoplesDialogShow] = useState(false); const [isPeoplesDialogShow, setPeoplesDialogShow] = useState(false);
@@ -28,21 +20,21 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveIma
const canSetPeoplePhoto = window.sfMetadataContext.canSetPeoplePhoto(); const canSetPeoplePhoto = window.sfMetadataContext.canSetPeoplePhoto();
const options = useMemo(() => { const options = useMemo(() => {
let validOptions = [{ value: CONTEXT_MENU_KEY.DOWNLOAD, label: gettext('Download') }]; let validOptions = [{ value: GALLERY_OPERATION_KEYS.DOWNLOAD, label: gettext('Download') }];
if (onDelete && checkCanDeleteRow) { if (onDelete && checkCanDeleteRow) {
validOptions.push({ value: CONTEXT_MENU_KEY.DELETE, label: selectedImages.length > 1 ? gettext('Delete') : gettext('Delete file') }); validOptions.push({ value: GALLERY_OPERATION_KEYS.DELETE, label: gettext('Delete') });
} }
if (onDuplicate && canDuplicateRow && selectedImages.length === 1) { if (onDuplicate && canDuplicateRow && selectedImages.length === 1) {
validOptions.push({ value: CONTEXT_MENU_KEY.DUPLICATE, label: gettext('Duplicate') }); validOptions.push({ value: GALLERY_OPERATION_KEYS.DUPLICATE, label: gettext('Copy') });
} }
if (onRemoveImage && canRemovePhotoFromPeople) { if (onRemoveImage && canRemovePhotoFromPeople) {
validOptions.push({ value: CONTEXT_MENU_KEY.REMOVE, label: gettext('Remove from this group') }); validOptions.push({ value: GALLERY_OPERATION_KEYS.REMOVE, label: gettext('Remove from this group') });
} }
if (onAddImage && canAddPhotoToPeople) { if (onAddImage && canAddPhotoToPeople) {
validOptions.push({ value: CONTEXT_MENU_KEY.ADD_PHOTO_TO_GROUPS, label: gettext('Add to groups') }); validOptions.push({ value: GALLERY_OPERATION_KEYS.ADD_PHOTO_TO_GROUPS, label: gettext('Add to groups') });
} }
if (onSetPeoplePhoto && canSetPeoplePhoto) { if (onSetPeoplePhoto && canSetPeoplePhoto) {
validOptions.push({ value: CONTEXT_MENU_KEY.SET_PEOPLE_PHOTO, label: gettext('Set as cover photo') }); validOptions.push({ value: GALLERY_OPERATION_KEYS.SET_PEOPLE_PHOTO, label: gettext('Set as cover photo') });
} }
return validOptions; return validOptions;
}, [checkCanDeleteRow, canDuplicateRow, canRemovePhotoFromPeople, canAddPhotoToPeople, selectedImages, onDuplicate, onDelete, onRemoveImage, onAddImage, canSetPeoplePhoto, onSetPeoplePhoto]); }, [checkCanDeleteRow, canDuplicateRow, canRemovePhotoFromPeople, canAddPhotoToPeople, selectedImages, onDuplicate, onDelete, onRemoveImage, onAddImage, canSetPeoplePhoto, onSetPeoplePhoto]);
@@ -70,22 +62,22 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveIma
const handleOptionClick = useCallback(option => { const handleOptionClick = useCallback(option => {
switch (option.value) { switch (option.value) {
case CONTEXT_MENU_KEY.DOWNLOAD: case GALLERY_OPERATION_KEYS.DOWNLOAD:
handleDownload(); handleDownload();
break; break;
case CONTEXT_MENU_KEY.DELETE: case GALLERY_OPERATION_KEYS.DELETE:
onDelete(selectedImages); onDelete(selectedImages);
break; break;
case CONTEXT_MENU_KEY.DUPLICATE: case GALLERY_OPERATION_KEYS.DUPLICATE:
handleCopy(); handleCopy();
break; break;
case CONTEXT_MENU_KEY.REMOVE: case GALLERY_OPERATION_KEYS.REMOVE:
onRemoveImage(selectedImages); onRemoveImage(selectedImages);
break; break;
case CONTEXT_MENU_KEY.ADD_PHOTO_TO_GROUPS: case GALLERY_OPERATION_KEYS.ADD_PHOTO_TO_GROUPS:
setPeoplesDialogShow(true); setPeoplesDialogShow(true);
break; break;
case CONTEXT_MENU_KEY.SET_PEOPLE_PHOTO: case GALLERY_OPERATION_KEYS.SET_PEOPLE_PHOTO:
onSetPeoplePhoto(selectedImages[0]); onSetPeoplePhoto(selectedImages[0]);
break; break;
default: default:

View File

@@ -99,3 +99,9 @@
justify-content: center; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
} }
.selection-box {
position: absolute;
background-color: rgba(0, 120, 215, 0.3);
border: 1px solid rgba(0, 120, 215, 0.8);
}

View File

@@ -35,7 +35,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
const scrollContainer = useRef(null); const scrollContainer = useRef(null);
const lastState = useRef({ scrollPos: 0 }); const lastState = useRef({ scrollPos: 0 });
const { repoID, updateCurrentDirent } = useMetadataView(); const { repoID, updateCurrentDirent, updateSelectedRecordIds } = useMetadataView();
const repoInfo = window.sfMetadataContext.getSetting('repoInfo'); const repoInfo = window.sfMetadataContext.getSetting('repoInfo');
const canPreview = window.sfMetadataContext.canPreview(); const canPreview = window.sfMetadataContext.canPreview();
@@ -191,12 +191,19 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
}); });
}, [metadata, updateCurrentDirent]); }, [metadata, updateCurrentDirent]);
const updateSelectedImages = useCallback((selectedImages) => {
const ids = selectedImages.map(item => item.id);
updateSelectedRecordIds(ids);
}, [updateSelectedRecordIds]);
const handleClick = useCallback((event, image) => { const handleClick = useCallback((event, image) => {
if (event.metaKey || event.ctrlKey) { if (event.metaKey || event.ctrlKey) {
setSelectedImages(prev => const updatedSelectedImages = selectedImages.includes(image)
prev.includes(image) ? prev.filter(img => img !== image) : [...prev, image] ? selectedImages.filter(img => img !== image)
); : [...selectedImages, image];
setSelectedImages(updatedSelectedImages);
updateSelectedImage(image); updateSelectedImage(image);
updateSelectedImages(updatedSelectedImages);
return; return;
} }
if (event.shiftKey && lastSelectedImage) { if (event.shiftKey && lastSelectedImage) {
@@ -205,14 +212,18 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
const start = Math.min(lastSelectedIndex, currentIndex); const start = Math.min(lastSelectedIndex, currentIndex);
const end = Math.max(lastSelectedIndex, currentIndex); const end = Math.max(lastSelectedIndex, currentIndex);
const range = images.slice(start, end + 1); const range = images.slice(start, end + 1);
setSelectedImages(prev => Array.from(new Set([...prev, ...range]))); const updatedSelectedImages = Array.from(new Set([...selectedImages, ...range]));
setSelectedImages(updatedSelectedImages);
updateSelectedImage(null); updateSelectedImage(null);
updateSelectedImages(updatedSelectedImages);
return; return;
} }
setSelectedImages([image]); const updatedSelectedImages = [image];
setSelectedImages(updatedSelectedImages);
updateSelectedImage(image); updateSelectedImage(image);
setLastSelectedImage(image); setLastSelectedImage(image);
}, [images, updateSelectedImage, lastSelectedImage]); updateSelectedImages(updatedSelectedImages);
}, [images, selectedImages, updateSelectedImage, lastSelectedImage, updateSelectedImages]);
const handleDoubleClick = useCallback((event, image) => { const handleDoubleClick = useCallback((event, image) => {
event.preventDefault(); event.preventDefault();
@@ -231,28 +242,40 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
const index = images.findIndex(item => item.id === image.id); const index = images.findIndex(item => item.id === image.id);
if (isNaN(index) || index === -1) return; if (isNaN(index) || index === -1) return;
setSelectedImages(prev => prev.length < 2 ? [image] : [...prev]); const updatedSelectedImages = selectedImages.length < 2 ? [image] : [...selectedImages];
}, [images]); setSelectedImages(updatedSelectedImages);
updateSelectedImages(updatedSelectedImages);
}, [images, selectedImages, updateSelectedImages]);
const moveToPrevImage = useCallback(() => { const moveToPrevImage = useCallback(() => {
const imageItemsLength = images.length; const imageItemsLength = images.length;
const selectedImage = images[(imageIndex + imageItemsLength - 1) % imageItemsLength]; const selectedImage = images[(imageIndex + imageItemsLength - 1) % imageItemsLength];
setImageIndex((prevState) => (prevState + imageItemsLength - 1) % imageItemsLength); setImageIndex((prevState) => (prevState + imageItemsLength - 1) % imageItemsLength);
setSelectedImages([selectedImage]); const updatedSelectedImages = [selectedImage];
setSelectedImages(updatedSelectedImages);
updateSelectedImage(selectedImage); updateSelectedImage(selectedImage);
}, [images, imageIndex, updateSelectedImage]); updateSelectedImages(updatedSelectedImages);
}, [images, imageIndex, updateSelectedImage, updateSelectedImages]);
const moveToNextImage = useCallback(() => { const moveToNextImage = useCallback(() => {
const imageItemsLength = images.length; const imageItemsLength = images.length;
const selectedImage = images[(imageIndex + 1) % imageItemsLength]; const selectedImage = images[(imageIndex + 1) % imageItemsLength];
setImageIndex((prevState) => (prevState + 1) % imageItemsLength); setImageIndex((prevState) => (prevState + 1) % imageItemsLength);
setSelectedImages([selectedImage]); const updatedSelectedImages = [selectedImage];
setSelectedImages(updatedSelectedImages);
updateSelectedImage(selectedImage); updateSelectedImage(selectedImage);
}, [images, imageIndex, updateSelectedImage]); updateSelectedImages(updatedSelectedImages);
}, [images, imageIndex, updateSelectedImage, updateSelectedImages]);
const handleImageSelection = useCallback((selectedImages) => { const handleImageSelection = useCallback((selectedImages) => {
setSelectedImages(selectedImages); setSelectedImages(selectedImages);
}, []); updateSelectedImages(selectedImages);
}, [updateSelectedImages]);
const selectNone = useCallback(() => {
setSelectedImages([]);
updateSelectedImages([]);
}, [updateSelectedImages]);
const closeImagePopup = useCallback(() => { const closeImagePopup = useCallback(() => {
setIsImagePopupOpen(false); setIsImagePopupOpen(false);
@@ -264,26 +287,29 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
success_callback: () => { success_callback: () => {
updateCurrentDirent(); updateCurrentDirent();
setSelectedImages([]); setSelectedImages([]);
updateSelectedImages([]);
} }
}); });
}, [onDelete, updateCurrentDirent]); }, [onDelete, updateCurrentDirent, updateSelectedImages]);
const handleRemoveSelectedImages = useCallback((selectedImages) => { const handleRemoveSelectedImages = useCallback((selectedImages) => {
if (!selectedImages.length) return; if (!selectedImages.length) return;
onRemoveImage && onRemoveImage(selectedImages, () => { onRemoveImage && onRemoveImage(selectedImages, () => {
updateCurrentDirent(); updateCurrentDirent();
setSelectedImages([]); setSelectedImages([]);
updateSelectedImages([]);
}); });
}, [onRemoveImage, updateCurrentDirent]); }, [onRemoveImage, updateCurrentDirent, updateSelectedImages]);
const handleMakeSelectedAsCoverPhoto = useCallback((selectedImage) => { const handleMakeSelectedAsCoverPhoto = useCallback((selectedImage) => {
onSetPeoplePhoto(selectedImage, { onSetPeoplePhoto(selectedImage, {
success_callback: () => { success_callback: () => {
updateCurrentDirent(); updateCurrentDirent();
setSelectedImages([]); setSelectedImages([]);
updateSelectedImages([]);
} }
}); });
}, [onSetPeoplePhoto, updateCurrentDirent]); }, [onSetPeoplePhoto, updateCurrentDirent, updateSelectedImages]);
const handleClickOutside = useCallback((event) => { const handleClickOutside = useCallback((event) => {
const className = getEventClassName(event); const className = getEventClassName(event);
@@ -306,6 +332,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
if (newImageItems.length === 0) { if (newImageItems.length === 0) {
setSelectedImages([]); setSelectedImages([]);
updateSelectedImages([]);
setIsImagePopupOpen(false); setIsImagePopupOpen(false);
setImageIndex(0); setImageIndex(0);
} else { } else {
@@ -314,9 +341,11 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
setImageIndex(newIndex); setImageIndex(newIndex);
} }
setSelectedImages(newSelectedImage ? [newSelectedImage] : []); const updatedSelectedImages = newSelectedImage ? [newSelectedImage] : [];
setSelectedImages(updatedSelectedImages);
updateSelectedImage(newSelectedImage); updateSelectedImage(newSelectedImage);
}, [selectedImages, images, onDelete, updateSelectedImage]); updateSelectedImages(updatedSelectedImages);
}, [selectedImages, images, onDelete, updateSelectedImage, updateSelectedImages]);
const handleDateTagClick = useCallback((event, groupName) => { const handleDateTagClick = useCallback((event, groupName) => {
event.preventDefault(); event.preventDefault();
@@ -348,6 +377,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY, EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY,
(mode) => { (mode) => {
setSelectedImages([]); setSelectedImages([]);
updateSelectedImages([]);
setMode(mode); setMode(mode);
lastState.current = { ...lastState.current, mode }; lastState.current = { ...lastState.current, mode };
window.sfMetadataContext.localStorage.setItem(STORAGE_GALLERY_DATE_MODE_KEY, mode); window.sfMetadataContext.localStorage.setItem(STORAGE_GALLERY_DATE_MODE_KEY, mode);
@@ -378,9 +408,12 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
setZoomGear(zoomGear); setZoomGear(zoomGear);
}); });
const unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, selectNone);
return () => { return () => {
container && resizeObserver.unobserve(container); container && resizeObserver.unobserve(container);
modifyGalleryZoomGearSubscribe(); modifyGalleryZoomGearSubscribe();
unsubscribeSelectNone();
switchGalleryModeSubscribe(); switchGalleryModeSubscribe();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps

View File

@@ -2311,7 +2311,7 @@ class LibContentView extends React.Component {
render() { render() {
const { repoID } = this.props; const { repoID } = this.props;
let { currentRepoInfo, userPerm, isCopyMoveProgressDialogShow, isDeleteFolderDialogOpen, errorMsg, let { currentRepoInfo, userPerm, isCopyMoveProgressDialogShow, isDeleteFolderDialogOpen, errorMsg,
path, usedRepoTags, isDirentSelected, currentMode, currentNode } = this.state; path, usedRepoTags, isDirentSelected, currentMode, currentNode, viewId } = this.state;
if (this.state.libNeedDecrypt) { if (this.state.libNeedDecrypt) {
return ( return (
@@ -2417,7 +2417,7 @@ class LibContentView extends React.Component {
})}> })}>
{isDirentSelected ? ( {isDirentSelected ? (
currentMode === TAGS_MODE || currentMode === METADATA_MODE ? ( currentMode === TAGS_MODE || currentMode === METADATA_MODE ? (
<MetadataPathToolbar repoID={repoID} repoInfo={currentRepoInfo} mode={currentMode} path={path} /> <MetadataPathToolbar repoID={repoID} repoInfo={currentRepoInfo} mode={currentMode} path={path} viewId={viewId} />
) : ( ) : (
<SelectedDirentsToolbar <SelectedDirentsToolbar
repoID={this.props.repoID} repoID={this.props.repoID}