import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import { CenteredLoading } from '@seafile/sf-metadata-ui-component';
import toaster from '../../../components/toast';
import { Utils } from '../../../utils/utils';
import metadataAPI from '../../api';
import FaceGroup from './face-group';
import ImageDialog from '../../../components/dialog/image-dialog';
import ModalPortal from '../../../components/modal-portal';
import { siteRoot, gettext, thumbnailSizeForOriginal, thumbnailDefaultSize } from '../../../utils/constants';
import './index.css';
const LIMIT = 1000;
const FaceRecognition = ({ repoID }) => {
const [loading, setLoading] = useState(true);
const [faceOriginData, setFaceOriginData] = useState([]);
const [isLoadingMore, setLoadingMore] = useState(false);
const [isImagePopupOpen, setIsImagePopupOpen] = useState(false);
const [imageIndex, setImageIndex] = useState(-1);
const containerRef = useRef(null);
const hasMore = useRef(true);
const faceData = useMemo(() => {
if (!Array.isArray(faceOriginData) || faceOriginData.length === 0) return [];
const data = faceOriginData.map(dataItem => {
const { record_id, link_photos } = dataItem;
const linkPhotos = link_photos || [];
const name = dataItem.name || gettext('Person Image');
return {
record_id: record_id,
name: name || gettext('Person Image'),
photos: linkPhotos.map(photo => {
const { path } = photo;
return {
...photo,
name: photo.file_name,
url: `${siteRoot}lib/${repoID}/file${path}`,
default_url: `${siteRoot}thumbnail/${repoID}/${thumbnailDefaultSize}${path}`,
src: `${siteRoot}thumbnail/${repoID}/${thumbnailDefaultSize}${path}`,
thumbnail: `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${path}`,
};
}),
};
});
return data;
}, [repoID, faceOriginData]);
const imageItems = useMemo(() => {
return faceData.map(group => group.photos).flat();
}, [faceData]);
useEffect(() => {
setLoading(true);
metadataAPI.getFaceData(repoID, 0, LIMIT).then(res => {
const faceOriginData = res.data.results || [];
if (faceOriginData.length < LIMIT) {
hasMore.current = false;
}
setFaceOriginData(faceOriginData);
setLoading(false);
}).catch(error => {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const loadMore = useCallback(() => {
if (!hasMore.current) return;
setLoadingMore(true);
metadataAPI.getFaceData(repoID, faceOriginData.length, LIMIT).then(res => {
const newFaceData = res.data.results || [];
if (newFaceData.length < LIMIT) {
hasMore.current = false;
}
setFaceOriginData([...faceOriginData, ...newFaceData]);
setLoadingMore(false);
}).catch(error => {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
setLoadingMore(false);
});
}, [repoID, faceOriginData]);
const handleScroll = useCallback(() => {
if (!containerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
if (scrollTop + clientHeight >= scrollHeight - 10) {
loadMore();
}
}, [loadMore]);
const onPhotoClick = useCallback((photo) => {
let imageIndex = imageItems.findIndex(item => item.url === photo.url);
if (imageIndex < 0) imageIndex = 0;
setImageIndex(imageIndex);
setIsImagePopupOpen(true);
}, [imageItems]);
const closeImagePopup = useCallback(() => {
setIsImagePopupOpen(false);
setImageIndex(-1);
}, []);
const moveToPrevImage = useCallback(() => {
let prevImageIndex = imageIndex - 1;
if (prevImageIndex < 0) prevImageIndex = imageItems.length - 1;
setImageIndex(prevImageIndex);
}, [imageIndex, imageItems]);
const moveToNextImage = useCallback(() => {
let nextImageIndex = imageIndex + 1;
if (nextImageIndex > imageItems.length - 1) nextImageIndex = 0;
setImageIndex(nextImageIndex);
}, [imageIndex, imageItems]);
if (loading) {
return (