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:
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 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 = {
|
||||||
|
@@ -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',
|
||||||
|
@@ -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'
|
||||||
|
};
|
||||||
|
@@ -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();
|
||||||
|
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@@ -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:
|
||||||
|
@@ -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);
|
||||||
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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}
|
||||||
|
Reference in New Issue
Block a user