mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-27 15:54:39 +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:
89
frontend/src/components/toolbar/gallery-files-toolbar.js
Normal file
89
frontend/src/components/toolbar/gallery-files-toolbar.js
Normal 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;
|
@@ -1,21 +1,38 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TAGS_MODE } from '../dir-view-mode/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 TagFilesToolbar from './tag-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) {
|
||||
const isAllTagsView = path.split('/').pop() === ALL_TAGS_ID;
|
||||
if (isAllTagsView) return <AllTagsToolbar />;
|
||||
|
||||
return <TagFilesToolbar currentRepoInfo={repoInfo} />;
|
||||
}
|
||||
return (
|
||||
<TableFilesToolbar repoID={repoID} />
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
MetadataPathToolbar.propTypes = {
|
||||
|
@@ -42,6 +42,7 @@ export const EVENT_BUS_TYPE = {
|
||||
SELECT_RECORDS: 'select_records',
|
||||
TOGGLE_MOVE_DIALOG: 'toggle_move_dialog',
|
||||
MOVE_RECORD: 'move_record',
|
||||
DUPLICATE_RECORD: 'duplicate_record',
|
||||
DELETE_RECORDS: 'delete_records',
|
||||
UPDATE_RECORD_DETAILS: 'update_record_details',
|
||||
UPDATE_FACE_RECOGNITION: 'update_face_recognition',
|
||||
|
@@ -18,3 +18,12 @@ export const 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 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'
|
||||
};
|
||||
|
@@ -633,6 +633,7 @@ export const MetadataViewProvider = ({
|
||||
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 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 unsubscribeUpdateDetails = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_RECORD_DETAILS, updateRecordDetails);
|
||||
const unsubscribeUpdateFaceRecognition = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_FACE_RECOGNITION, updateFaceRecognition);
|
||||
@@ -661,6 +662,7 @@ export const MetadataViewProvider = ({
|
||||
unsubscribeLocalColumnChanged();
|
||||
unsubscribeUpdateSelectedRecordIds();
|
||||
unsubscribeMoveRecord();
|
||||
unsubscribeDuplicateRecord();
|
||||
unsubscribeDeleteRecords();
|
||||
unsubscribeUpdateDetails();
|
||||
unsubscribeUpdateFaceRecognition();
|
||||
|
@@ -26,6 +26,7 @@ const Content = ({
|
||||
|
||||
const [isSelecting, setIsSelecting] = useState(false);
|
||||
const [selectionStart, setSelectionStart] = useState(null);
|
||||
const [selectionEnd, setSelectionEnd] = useState(null);
|
||||
|
||||
const selectedImageIds = useMemo(() => selectedImages.map(img => img.id), [selectedImages]);
|
||||
|
||||
@@ -50,6 +51,7 @@ const Content = ({
|
||||
|
||||
const selectionEnd = { x: e.clientX, y: e.clientY };
|
||||
const selected = [];
|
||||
setSelectionEnd(selectionEnd);
|
||||
|
||||
groups.forEach(group => {
|
||||
group.children.forEach((row) => {
|
||||
@@ -80,6 +82,7 @@ const Content = ({
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setIsSelecting(false);
|
||||
setSelectionEnd(null);
|
||||
}, []);
|
||||
|
||||
const renderDisplayGroup = useCallback((group) => {
|
||||
@@ -185,12 +188,30 @@ const Content = ({
|
||||
);
|
||||
}, [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')}/>);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="metadata-gallery-main"
|
||||
className="metadata-gallery-main position-relative"
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseUp={handleMouseUp}
|
||||
@@ -198,6 +219,7 @@ const Content = ({
|
||||
{groups.map((group) => {
|
||||
return renderDisplayGroup(group);
|
||||
})}
|
||||
{renderSelectionBox()}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@@ -6,15 +6,7 @@ import PeoplesDialog from '../../../components/dialog/peoples-dialog';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { Dirent } from '../../../../models';
|
||||
import { useFileOperations } from '../../../../hooks/file-operations';
|
||||
|
||||
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',
|
||||
};
|
||||
import { GALLERY_OPERATION_KEYS } from '../../../constants';
|
||||
|
||||
const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveImage, onAddImage, onSetPeoplePhoto }) => {
|
||||
const [isPeoplesDialogShow, setPeoplesDialogShow] = useState(false);
|
||||
@@ -28,21 +20,21 @@ const GalleryContextMenu = ({ selectedImages, onDelete, onDuplicate, onRemoveIma
|
||||
const canSetPeoplePhoto = window.sfMetadataContext.canSetPeoplePhoto();
|
||||
|
||||
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) {
|
||||
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) {
|
||||
validOptions.push({ value: CONTEXT_MENU_KEY.DUPLICATE, label: gettext('Duplicate') });
|
||||
validOptions.push({ value: GALLERY_OPERATION_KEYS.DUPLICATE, label: gettext('Copy') });
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
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;
|
||||
}, [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 => {
|
||||
switch (option.value) {
|
||||
case CONTEXT_MENU_KEY.DOWNLOAD:
|
||||
case GALLERY_OPERATION_KEYS.DOWNLOAD:
|
||||
handleDownload();
|
||||
break;
|
||||
case CONTEXT_MENU_KEY.DELETE:
|
||||
case GALLERY_OPERATION_KEYS.DELETE:
|
||||
onDelete(selectedImages);
|
||||
break;
|
||||
case CONTEXT_MENU_KEY.DUPLICATE:
|
||||
case GALLERY_OPERATION_KEYS.DUPLICATE:
|
||||
handleCopy();
|
||||
break;
|
||||
case CONTEXT_MENU_KEY.REMOVE:
|
||||
case GALLERY_OPERATION_KEYS.REMOVE:
|
||||
onRemoveImage(selectedImages);
|
||||
break;
|
||||
case CONTEXT_MENU_KEY.ADD_PHOTO_TO_GROUPS:
|
||||
case GALLERY_OPERATION_KEYS.ADD_PHOTO_TO_GROUPS:
|
||||
setPeoplesDialogShow(true);
|
||||
break;
|
||||
case CONTEXT_MENU_KEY.SET_PEOPLE_PHOTO:
|
||||
case GALLERY_OPERATION_KEYS.SET_PEOPLE_PHOTO:
|
||||
onSetPeoplePhoto(selectedImages[0]);
|
||||
break;
|
||||
default:
|
||||
|
@@ -99,3 +99,9 @@
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.selection-box {
|
||||
position: absolute;
|
||||
background-color: rgba(0, 120, 215, 0.3);
|
||||
border: 1px solid rgba(0, 120, 215, 0.8);
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
const scrollContainer = useRef(null);
|
||||
const lastState = useRef({ scrollPos: 0 });
|
||||
|
||||
const { repoID, updateCurrentDirent } = useMetadataView();
|
||||
const { repoID, updateCurrentDirent, updateSelectedRecordIds } = useMetadataView();
|
||||
const repoInfo = window.sfMetadataContext.getSetting('repoInfo');
|
||||
const canPreview = window.sfMetadataContext.canPreview();
|
||||
|
||||
@@ -191,12 +191,19 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
});
|
||||
}, [metadata, updateCurrentDirent]);
|
||||
|
||||
const updateSelectedImages = useCallback((selectedImages) => {
|
||||
const ids = selectedImages.map(item => item.id);
|
||||
updateSelectedRecordIds(ids);
|
||||
}, [updateSelectedRecordIds]);
|
||||
|
||||
const handleClick = useCallback((event, image) => {
|
||||
if (event.metaKey || event.ctrlKey) {
|
||||
setSelectedImages(prev =>
|
||||
prev.includes(image) ? prev.filter(img => img !== image) : [...prev, image]
|
||||
);
|
||||
const updatedSelectedImages = selectedImages.includes(image)
|
||||
? selectedImages.filter(img => img !== image)
|
||||
: [...selectedImages, image];
|
||||
setSelectedImages(updatedSelectedImages);
|
||||
updateSelectedImage(image);
|
||||
updateSelectedImages(updatedSelectedImages);
|
||||
return;
|
||||
}
|
||||
if (event.shiftKey && lastSelectedImage) {
|
||||
@@ -205,14 +212,18 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
const start = Math.min(lastSelectedIndex, currentIndex);
|
||||
const end = Math.max(lastSelectedIndex, currentIndex);
|
||||
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);
|
||||
updateSelectedImages(updatedSelectedImages);
|
||||
return;
|
||||
}
|
||||
setSelectedImages([image]);
|
||||
const updatedSelectedImages = [image];
|
||||
setSelectedImages(updatedSelectedImages);
|
||||
updateSelectedImage(image);
|
||||
setLastSelectedImage(image);
|
||||
}, [images, updateSelectedImage, lastSelectedImage]);
|
||||
updateSelectedImages(updatedSelectedImages);
|
||||
}, [images, selectedImages, updateSelectedImage, lastSelectedImage, updateSelectedImages]);
|
||||
|
||||
const handleDoubleClick = useCallback((event, image) => {
|
||||
event.preventDefault();
|
||||
@@ -231,28 +242,40 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
const index = images.findIndex(item => item.id === image.id);
|
||||
if (isNaN(index) || index === -1) return;
|
||||
|
||||
setSelectedImages(prev => prev.length < 2 ? [image] : [...prev]);
|
||||
}, [images]);
|
||||
const updatedSelectedImages = selectedImages.length < 2 ? [image] : [...selectedImages];
|
||||
setSelectedImages(updatedSelectedImages);
|
||||
updateSelectedImages(updatedSelectedImages);
|
||||
}, [images, selectedImages, updateSelectedImages]);
|
||||
|
||||
const moveToPrevImage = useCallback(() => {
|
||||
const imageItemsLength = images.length;
|
||||
const selectedImage = images[(imageIndex + imageItemsLength - 1) % imageItemsLength];
|
||||
setImageIndex((prevState) => (prevState + imageItemsLength - 1) % imageItemsLength);
|
||||
setSelectedImages([selectedImage]);
|
||||
const updatedSelectedImages = [selectedImage];
|
||||
setSelectedImages(updatedSelectedImages);
|
||||
updateSelectedImage(selectedImage);
|
||||
}, [images, imageIndex, updateSelectedImage]);
|
||||
updateSelectedImages(updatedSelectedImages);
|
||||
}, [images, imageIndex, updateSelectedImage, updateSelectedImages]);
|
||||
|
||||
const moveToNextImage = useCallback(() => {
|
||||
const imageItemsLength = images.length;
|
||||
const selectedImage = images[(imageIndex + 1) % imageItemsLength];
|
||||
setImageIndex((prevState) => (prevState + 1) % imageItemsLength);
|
||||
setSelectedImages([selectedImage]);
|
||||
const updatedSelectedImages = [selectedImage];
|
||||
setSelectedImages(updatedSelectedImages);
|
||||
updateSelectedImage(selectedImage);
|
||||
}, [images, imageIndex, updateSelectedImage]);
|
||||
updateSelectedImages(updatedSelectedImages);
|
||||
}, [images, imageIndex, updateSelectedImage, updateSelectedImages]);
|
||||
|
||||
const handleImageSelection = useCallback((selectedImages) => {
|
||||
setSelectedImages(selectedImages);
|
||||
}, []);
|
||||
updateSelectedImages(selectedImages);
|
||||
}, [updateSelectedImages]);
|
||||
|
||||
const selectNone = useCallback(() => {
|
||||
setSelectedImages([]);
|
||||
updateSelectedImages([]);
|
||||
}, [updateSelectedImages]);
|
||||
|
||||
const closeImagePopup = useCallback(() => {
|
||||
setIsImagePopupOpen(false);
|
||||
@@ -264,26 +287,29 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
success_callback: () => {
|
||||
updateCurrentDirent();
|
||||
setSelectedImages([]);
|
||||
updateSelectedImages([]);
|
||||
}
|
||||
});
|
||||
}, [onDelete, updateCurrentDirent]);
|
||||
}, [onDelete, updateCurrentDirent, updateSelectedImages]);
|
||||
|
||||
const handleRemoveSelectedImages = useCallback((selectedImages) => {
|
||||
if (!selectedImages.length) return;
|
||||
onRemoveImage && onRemoveImage(selectedImages, () => {
|
||||
updateCurrentDirent();
|
||||
setSelectedImages([]);
|
||||
updateSelectedImages([]);
|
||||
});
|
||||
}, [onRemoveImage, updateCurrentDirent]);
|
||||
}, [onRemoveImage, updateCurrentDirent, updateSelectedImages]);
|
||||
|
||||
const handleMakeSelectedAsCoverPhoto = useCallback((selectedImage) => {
|
||||
onSetPeoplePhoto(selectedImage, {
|
||||
success_callback: () => {
|
||||
updateCurrentDirent();
|
||||
setSelectedImages([]);
|
||||
updateSelectedImages([]);
|
||||
}
|
||||
});
|
||||
}, [onSetPeoplePhoto, updateCurrentDirent]);
|
||||
}, [onSetPeoplePhoto, updateCurrentDirent, updateSelectedImages]);
|
||||
|
||||
const handleClickOutside = useCallback((event) => {
|
||||
const className = getEventClassName(event);
|
||||
@@ -306,6 +332,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
|
||||
if (newImageItems.length === 0) {
|
||||
setSelectedImages([]);
|
||||
updateSelectedImages([]);
|
||||
setIsImagePopupOpen(false);
|
||||
setImageIndex(0);
|
||||
} else {
|
||||
@@ -314,9 +341,11 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
setImageIndex(newIndex);
|
||||
}
|
||||
|
||||
setSelectedImages(newSelectedImage ? [newSelectedImage] : []);
|
||||
const updatedSelectedImages = newSelectedImage ? [newSelectedImage] : [];
|
||||
setSelectedImages(updatedSelectedImages);
|
||||
updateSelectedImage(newSelectedImage);
|
||||
}, [selectedImages, images, onDelete, updateSelectedImage]);
|
||||
updateSelectedImages(updatedSelectedImages);
|
||||
}, [selectedImages, images, onDelete, updateSelectedImage, updateSelectedImages]);
|
||||
|
||||
const handleDateTagClick = useCallback((event, groupName) => {
|
||||
event.preventDefault();
|
||||
@@ -348,6 +377,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY,
|
||||
(mode) => {
|
||||
setSelectedImages([]);
|
||||
updateSelectedImages([]);
|
||||
setMode(mode);
|
||||
lastState.current = { ...lastState.current, mode };
|
||||
window.sfMetadataContext.localStorage.setItem(STORAGE_GALLERY_DATE_MODE_KEY, mode);
|
||||
@@ -378,9 +408,12 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
setZoomGear(zoomGear);
|
||||
});
|
||||
|
||||
const unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, selectNone);
|
||||
|
||||
return () => {
|
||||
container && resizeObserver.unobserve(container);
|
||||
modifyGalleryZoomGearSubscribe();
|
||||
unsubscribeSelectNone();
|
||||
switchGalleryModeSubscribe();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
@@ -2311,7 +2311,7 @@ class LibContentView extends React.Component {
|
||||
render() {
|
||||
const { repoID } = this.props;
|
||||
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) {
|
||||
return (
|
||||
@@ -2417,7 +2417,7 @@ class LibContentView extends React.Component {
|
||||
})}>
|
||||
{isDirentSelected ? (
|
||||
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
|
||||
repoID={this.props.repoID}
|
||||
|
Reference in New Issue
Block a user