1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-26 15:26:19 +00:00

Face recognition top toolbar (#8180)

* [metadata] 'face recognition' view: add a top toolbar for the selected photo(s)

* [metadata] 'face recognition' view: update for 'add photos to group'

* [metadata] 'face recognition' view: update for the top toolbar
This commit is contained in:
llj
2025-08-30 17:48:41 +08:00
committed by GitHub
parent 5fc2c9f3e6
commit fe6f814bdc
9 changed files with 229 additions and 50 deletions

View File

@@ -0,0 +1,125 @@
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu';
import { gettext } from '../../utils/constants';
import { EVENT_BUS_TYPE, GALLERY_OPERATION_KEYS } from '../../metadata/constants';
import { useFileOperations } from '../../hooks/file-operations';
const FaceRecognitionFilesToolbar = () => {
const [selectedRecordIds, setSelectedRecordIds] = useState([]);
const [selectedRecords, setSelectedRecords] = useState([]);
const [isSomeone, setIsSomeone] = useState(false);
const menuRef = useRef(null);
const { handleDownload: handleDownloadAPI } = useFileOperations();
const eventBus = window.sfMetadataContext && window.sfMetadataContext.eventBus;
const checkCanDeleteRow = window.sfMetadataContext.checkCanDeleteRow();
const canRemovePhotoFromPeople = window.sfMetadataContext.canRemovePhotoFromPeople();
const canAddPhotoToPeople = window.sfMetadataContext.canAddPhotoToPeople();
const canSetPeoplePhoto = window.sfMetadataContext.canSetPeoplePhoto();
useEffect(() => {
const unsubscribeSelectedFileIds = eventBus && eventBus.subscribe(EVENT_BUS_TYPE.SELECT_RECORDS, (ids, metadata, selectedRecords, isSomeone) => {
setSelectedRecordIds(ids);
setSelectedRecords(selectedRecords);
setIsSomeone(isSomeone);
});
return () => {
unsubscribeSelectedFileIds && unsubscribeSelectedFileIds();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
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 = selectedRecords.map(record => {
const { parentDir, name: fileName } = record || {};
const name = parentDir === '/' ? fileName : `${parentDir}/${fileName}`;
return { name };
});
handleDownloadAPI('/', list);
}, [selectedRecords, handleDownloadAPI]);
const deleteRecords = useCallback(() => {
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.DELETE_FACE_RECOGNITION_RECORDS, selectedRecords, {
success_callback: () => {
eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
}
});
}, [eventBus, selectedRecords]);
const opList = useMemo(() => {
const list = [];
if (isSomeone && canRemovePhotoFromPeople) {
list.push({
key: GALLERY_OPERATION_KEYS.REMOVE_PHOTO_FROM_CURRENT_SET,
value: gettext('Remove from this group')
});
}
if (!isSomeone && canAddPhotoToPeople) {
list.push({
key: GALLERY_OPERATION_KEYS.ADD_PHOTO_TO_GROUPS,
value: gettext('Add to groups')
});
}
if (canSetPeoplePhoto && selectedRecordIds.length == 1) {
list.push({
key: GALLERY_OPERATION_KEYS.SET_PHOTO_AS_COVER,
value: gettext('Set as cover photo')
});
}
return list;
}, [isSomeone, selectedRecordIds, canRemovePhotoFromPeople, canAddPhotoToPeople, canSetPeoplePhoto]);
const getMenuList = useCallback(() => {
return opList;
}, [opList]);
const onMenuItemClick = useCallback((operation) => {
switch (operation) {
case GALLERY_OPERATION_KEYS.REMOVE_PHOTO_FROM_CURRENT_SET:
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.REMOVE_PHOTOS_FROM_CURRENT_SET, selectedRecords);
break;
case GALLERY_OPERATION_KEYS.ADD_PHOTO_TO_GROUPS:
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.ADD_PHOTO_TO_GROUPS);
break;
case GALLERY_OPERATION_KEYS.SET_PHOTO_AS_COVER:
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.SET_PHOTO_AS_COVER, selectedRecords[0]);
break;
default:
return;
}
}, [eventBus, selectedRecords]);
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>
}
<ItemDropdownMenu
ref={menuRef}
item={{}}
toggleClass="cur-view-path-btn sf3-font-more sf3-font"
onMenuItemClick={onMenuItemClick}
getMenuList={getMenuList}
/>
</div>
);
};
export default FaceRecognitionFilesToolbar;

View File

@@ -8,6 +8,7 @@ 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'; import GalleryFilesToolbar from './gallery-files-toolbar';
import FaceRecognitionFilesToolbar from './face-recognition-files-toolbar';
const MetadataPathToolbar = ({ repoID, repoInfo, mode, path, viewId }) => { const MetadataPathToolbar = ({ repoID, repoInfo, mode, path, viewId }) => {
const { idViewMap } = useMetadata(); const { idViewMap } = useMetadata();
@@ -20,6 +21,10 @@ const MetadataPathToolbar = ({ repoID, repoInfo, mode, path, viewId }) => {
); );
} }
if (type === VIEW_TYPE.FACE_RECOGNITION) {
return <FaceRecognitionFilesToolbar />;
}
if (type === VIEW_TYPE.TABLE) { if (type === VIEW_TYPE.TABLE) {
return ( return (
<TableFilesToolbar repoID={repoID} /> <TableFilesToolbar repoID={repoID} />

View File

@@ -85,6 +85,12 @@ export const EVENT_BUS_TYPE = {
MODIFY_GALLERY_ZOOM_GEAR: 'modify_gallery_zoom_gear', MODIFY_GALLERY_ZOOM_GEAR: 'modify_gallery_zoom_gear',
SWITCH_GALLERY_GROUP_BY: 'switch_gallery_group_by', SWITCH_GALLERY_GROUP_BY: 'switch_gallery_group_by',
// face recognition
DELETE_FACE_RECOGNITION_RECORDS: 'delete_face_recognition_records',
REMOVE_PHOTOS_FROM_CURRENT_SET: 'remove_photos_from_current_set',
SET_PHOTO_AS_COVER: 'set_photo_as_cover',
ADD_PHOTO_TO_GROUPS: 'add_photo_to_groups',
// kanban // kanban
TOGGLE_KANBAN_SETTINGS: 'toggle_kanban_settings', TOGGLE_KANBAN_SETTINGS: 'toggle_kanban_settings',
OPEN_KANBAN_SETTINGS: 'open_kanban_settings', OPEN_KANBAN_SETTINGS: 'open_kanban_settings',

View File

@@ -22,8 +22,8 @@ export const STORAGE_GALLERY_ZOOM_GEAR_KEY = 'gallery_zoom_gear';
export const GALLERY_OPERATION_KEYS = { export const GALLERY_OPERATION_KEYS = {
DOWNLOAD: 'download', DOWNLOAD: 'download',
DELETE: 'delete', DELETE: 'delete',
DUPLICATE: 'duplicate', COPY: 'copy',
REMOVE: 'remove', REMOVE_PHOTO_FROM_CURRENT_SET: 'remove_photo_from_current_set',
SET_PEOPLE_PHOTO: 'set_people_photo', SET_PHOTO_AS_COVER: 'set_photo_as_cover',
ADD_PHOTO_TO_GROUPS: 'add_photo_to_groups' ADD_PHOTO_TO_GROUPS: 'add_photo_to_groups'
}; };

View File

@@ -296,10 +296,14 @@ export const MetadataViewProvider = ({
storeRef.current.updateFileTags(data); storeRef.current.updateFileTags(data);
}, [storeRef, modifyLocalFileTags]); }, [storeRef, modifyLocalFileTags]);
const updateSelectedRecordIds = useCallback((ids) => { const updateSelectedRecordIds = useCallback((ids, records, isSomeone) => {
toggleShowDirentToolbar(ids.length > 0); toggleShowDirentToolbar(ids.length > 0);
setTimeout(() => { setTimeout(() => {
if (records != undefined) {
window.sfMetadataContext && window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_RECORDS, ids, metadata, records, isSomeone);
} else {
window.sfMetadataContext && window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_RECORDS, ids, metadata); window.sfMetadataContext && window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_RECORDS, ids, metadata);
}
}, 0); }, 0);
}, [metadata, toggleShowDirentToolbar]); }, [metadata, toggleShowDirentToolbar]);

View File

@@ -237,12 +237,14 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onAddPeople
const eventBus = window?.sfMetadataContext?.eventBus; const eventBus = window?.sfMetadataContext?.eventBus;
if (!eventBus) return; if (!eventBus) return;
const unsubscribeViewChange = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, onViewChange); const unsubscribeViewChange = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, onViewChange);
const unsubscribeDeleteRecords = eventBus.subscribe(EVENT_BUS_TYPE.DELETE_FACE_RECOGNITION_RECORDS, handleDelete);
const localRecordChangedSubscribe = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, onRecordChange); const localRecordChangedSubscribe = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, onRecordChange);
return () => { return () => {
unsubscribeViewChange && unsubscribeViewChange(); unsubscribeViewChange && unsubscribeViewChange();
unsubscribeDeleteRecords && unsubscribeDeleteRecords();
localRecordChangedSubscribe && localRecordChangedSubscribe(); localRecordChangedSubscribe && localRecordChangedSubscribe();
}; };
}, [onViewChange, onRecordChange]); }, [onViewChange, handleDelete, onRecordChange]);
if (isLoading) return (<CenteredLoading />); if (isLoading) return (<CenteredLoading />);
@@ -256,6 +258,7 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onAddPeople
onRemoveImage={people._is_someone ? handleRemove : null} onRemoveImage={people._is_someone ? handleRemove : null}
onAddImage={!people._is_someone ? handleAdd : null} onAddImage={!people._is_someone ? handleAdd : null}
onSetPeoplePhoto={handleSetPeoplePhoto} onSetPeoplePhoto={handleSetPeoplePhoto}
isSomeone={people._is_someone}
/> />
</div> </div>
); );

View File

@@ -1,16 +1,12 @@
import React, { useMemo, useCallback, useState } from 'react'; import React, { useMemo, useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import ContextMenu from '../../../components/context-menu'; import ContextMenu from '../../../components/context-menu';
import ModalPortal from '../../../../components/modal-portal';
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'; import { GALLERY_OPERATION_KEYS } from '../../../constants';
const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveImage, onAddImage, onSetPeoplePhoto }) => { const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveImage, onAddImage, onSetPeoplePhoto }) => {
const [isPeoplesDialogShow, setPeoplesDialogShow] = useState(false);
const { handleDownload: handleDownloadAPI, handleCopy: handleCopyAPI } = useFileOperations(); const { handleDownload: handleDownloadAPI, handleCopy: handleCopyAPI } = useFileOperations();
const checkCanDeleteRow = window.sfMetadataContext.checkCanDeleteRow(); const checkCanDeleteRow = window.sfMetadataContext.checkCanDeleteRow();
@@ -22,22 +18,39 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveIma
const options = useMemo(() => { const options = useMemo(() => {
let validOptions = [{ value: GALLERY_OPERATION_KEYS.DOWNLOAD, label: gettext('Download') }]; let validOptions = [{ value: GALLERY_OPERATION_KEYS.DOWNLOAD, label: gettext('Download') }];
if (onDelete && checkCanDeleteRow) { if (onDelete && checkCanDeleteRow) {
validOptions.push({ value: GALLERY_OPERATION_KEYS.DELETE, label: gettext('Delete') }); 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: GALLERY_OPERATION_KEYS.DUPLICATE, label: gettext('Copy') }); validOptions.push({
value: GALLERY_OPERATION_KEYS.COPY,
label: gettext('Copy')
});
} }
if (onRemoveImage && canRemovePhotoFromPeople) { if (onRemoveImage && canRemovePhotoFromPeople) {
validOptions.push({ value: GALLERY_OPERATION_KEYS.REMOVE, label: gettext('Remove from this group') }); validOptions.push({
value: GALLERY_OPERATION_KEYS.REMOVE_PHOTO_FROM_CURRENT_SET,
label: gettext('Remove from this group')
});
} }
if (onAddImage && canAddPhotoToPeople) { if (onAddImage && canAddPhotoToPeople) {
validOptions.push({ value: GALLERY_OPERATION_KEYS.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 && selectedImages.length === 1) {
validOptions.push({ value: GALLERY_OPERATION_KEYS.SET_PEOPLE_PHOTO, label: gettext('Set as cover photo') }); validOptions.push({
value: GALLERY_OPERATION_KEYS.SET_PHOTO_AS_COVER,
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]);
const handleDuplicate = useCallback((destRepo, dirent, destPath, nodeParentPath, isByDialog) => { const handleDuplicate = useCallback((destRepo, dirent, destPath, nodeParentPath, isByDialog) => {
const selectedImage = selectedImages[0]; const selectedImage = selectedImages[0];
@@ -68,44 +81,29 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveIma
case GALLERY_OPERATION_KEYS.DELETE: case GALLERY_OPERATION_KEYS.DELETE:
onDelete(selectedImages); onDelete(selectedImages);
break; break;
case GALLERY_OPERATION_KEYS.DUPLICATE: case GALLERY_OPERATION_KEYS.COPY:
handleCopy(); handleCopy();
break; break;
case GALLERY_OPERATION_KEYS.REMOVE: case GALLERY_OPERATION_KEYS.REMOVE_PHOTO_FROM_CURRENT_SET:
onRemoveImage(selectedImages); onRemoveImage(selectedImages);
break; break;
case GALLERY_OPERATION_KEYS.ADD_PHOTO_TO_GROUPS: case GALLERY_OPERATION_KEYS.ADD_PHOTO_TO_GROUPS:
setPeoplesDialogShow(true); onAddImage();
break; break;
case GALLERY_OPERATION_KEYS.SET_PEOPLE_PHOTO: case GALLERY_OPERATION_KEYS.SET_PHOTO_AS_COVER:
onSetPeoplePhoto(selectedImages[0]); onSetPeoplePhoto(selectedImages[0]);
break; break;
default: default:
break; break;
} }
}, [handleDownload, onDelete, selectedImages, handleCopy, onRemoveImage, onSetPeoplePhoto]); }, [handleDownload, onDelete, selectedImages, handleCopy, onRemoveImage, onAddImage, onSetPeoplePhoto]);
const closePeoplesDialog = useCallback(() => {
setPeoplesDialogShow(false);
}, []);
const addPeople = useCallback((peopleIds, addedImages, callback) => {
onAddImage(peopleIds, addedImages, callback);
}, [onAddImage]);
return ( return (
<>
<ContextMenu <ContextMenu
options={options} options={options}
ignoredTriggerElements={['.metadata-gallery-image-item', '.metadata-gallery-grid-image']} ignoredTriggerElements={['.metadata-gallery-image-item', '.metadata-gallery-grid-image']}
onOptionClick={handleOptionClick} onOptionClick={handleOptionClick}
/> />
{isPeoplesDialogShow && (
<ModalPortal>
<PeoplesDialog selectedImages={selectedImages} onToggle={closePeoplesDialog} onSubmit={addPeople} />
</ModalPortal>
)}
</>
); );
}; };

View File

@@ -15,12 +15,13 @@ import { getEventClassName } from '../../../utils/dom';
import { getColumns, getImageSize, getRowHeight } from './utils'; import { getColumns, getImageSize, getRowHeight } from './utils';
import ObjectUtils from '../../../utils/object'; import ObjectUtils from '../../../utils/object';
import { openFile } from '../../utils/file'; import { openFile } from '../../utils/file';
import PeoplesDialog from '../../components/dialog/peoples-dialog';
import './index.css'; import './index.css';
const OVER_SCAN_ROWS = 20; const OVER_SCAN_ROWS = 20;
const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, onRemoveImage, onAddImage, onSetPeoplePhoto }) => { const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord, onRemoveImage, onAddImage, onSetPeoplePhoto, isSomeone }) => {
const [isFirstLoading, setFirstLoading] = useState(true); const [isFirstLoading, setFirstLoading] = useState(true);
const [zoomGear, setZoomGear] = useState(0); const [zoomGear, setZoomGear] = useState(0);
const [containerWidth, setContainerWidth] = useState(0); const [containerWidth, setContainerWidth] = useState(0);
@@ -30,6 +31,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
const [imageIndex, setImageIndex] = useState(0); const [imageIndex, setImageIndex] = useState(0);
const [selectedImages, setSelectedImages] = useState([]); const [selectedImages, setSelectedImages] = useState([]);
const [lastSelectedImage, setLastSelectedImage] = useState(null); const [lastSelectedImage, setLastSelectedImage] = useState(null);
const [isPeoplesDialogShow, setPeoplesDialogShow] = useState(false);
const containerRef = useRef(null); const containerRef = useRef(null);
const scrollContainer = useRef(null); const scrollContainer = useRef(null);
@@ -193,8 +195,12 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
const updateSelectedImages = useCallback((selectedImages) => { const updateSelectedImages = useCallback((selectedImages) => {
const ids = selectedImages.map(item => item.id); const ids = selectedImages.map(item => item.id);
if (isSomeone != undefined) { // 'face recognition'
updateSelectedRecordIds(ids, selectedImages, isSomeone);
} else {
updateSelectedRecordIds(ids); updateSelectedRecordIds(ids);
}, [updateSelectedRecordIds]); }
}, [isSomeone, updateSelectedRecordIds]);
const handleClick = useCallback((event, image) => { const handleClick = useCallback((event, image) => {
if (event.metaKey || event.ctrlKey) { if (event.metaKey || event.ctrlKey) {
@@ -301,7 +307,22 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
}); });
}, [onRemoveImage, updateCurrentDirent, updateSelectedImages]); }, [onRemoveImage, updateCurrentDirent, updateSelectedImages]);
const handleMakeSelectedAsCoverPhoto = useCallback((selectedImage) => { const handleAddPhotoToGroup = useCallback((selectedImages) => {
setPeoplesDialogShow(true);
}, []);
const closePeoplesDialog = useCallback(() => {
setPeoplesDialogShow(false);
}, []);
const addPhotoToGroup = useCallback((peopleIds, addedImages, callback) => {
onAddImage(peopleIds, addedImages, callback);
updateCurrentDirent();
setSelectedImages([]);
updateSelectedImages([]);
}, [onAddImage, updateCurrentDirent, updateSelectedImages]);
const setSelectedImageAsCover = useCallback((selectedImage) => {
onSetPeoplePhoto(selectedImage, { onSetPeoplePhoto(selectedImage, {
success_callback: () => { success_callback: () => {
updateCurrentDirent(); updateCurrentDirent();
@@ -409,11 +430,17 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
}); });
const unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, selectNone); const unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, selectNone);
const unsubscribeRemovePhotosFromCurrentSet = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.REMOVE_PHOTOS_FROM_CURRENT_SET, handleRemoveSelectedImages);
const unsubscribeSetPhotoAsCover = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SET_PHOTO_AS_COVER, setSelectedImageAsCover);
const unsubscribeAddPhotoToGroups = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.ADD_PHOTO_TO_GROUPS, handleAddPhotoToGroup);
return () => { return () => {
container && resizeObserver.unobserve(container); container && resizeObserver.unobserve(container);
modifyGalleryZoomGearSubscribe(); modifyGalleryZoomGearSubscribe();
unsubscribeSelectNone(); unsubscribeSelectNone();
unsubscribeRemovePhotosFromCurrentSet();
unsubscribeSetPhotoAsCover();
unsubscribeAddPhotoToGroups();
switchGalleryModeSubscribe(); switchGalleryModeSubscribe();
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -481,8 +508,8 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
onDelete={handleDeleteSelectedImages} onDelete={handleDeleteSelectedImages}
onDuplicate={duplicateRecord} onDuplicate={duplicateRecord}
onRemoveImage={onRemoveImage ? handleRemoveSelectedImages : null} onRemoveImage={onRemoveImage ? handleRemoveSelectedImages : null}
onAddImage={onAddImage} onAddImage={onAddImage ? handleAddPhotoToGroup : null}
onSetPeoplePhoto={handleMakeSelectedAsCoverPhoto} onSetPeoplePhoto={setSelectedImageAsCover}
/> />
{isImagePopupOpen && ( {isImagePopupOpen && (
<ModalPortal> <ModalPortal>
@@ -498,6 +525,11 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
/> />
</ModalPortal> </ModalPortal>
)} )}
{isPeoplesDialogShow && (
<ModalPortal>
<PeoplesDialog selectedImages={selectedImages} onToggle={closePeoplesDialog} onSubmit={addPhotoToGroup} />
</ModalPortal>
)}
</div> </div>
); );
}; };

View File

@@ -2473,7 +2473,13 @@ 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} viewId={viewId} /> <MetadataPathToolbar
repoID={repoID}
repoInfo={currentRepoInfo}
mode={currentMode}
path={path}
viewId={viewId}
/>
) : ( ) : (
<SelectedDirentsToolbar <SelectedDirentsToolbar
repoID={this.props.repoID} repoID={this.props.repoID}