diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index a3e828977a..7d0e2157d1 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -14,12 +14,12 @@
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@gatsbyjs/reach-router": "2.0.1",
- "@seafile/react-image-lightbox": "^4.0.1",
+ "@seafile/react-image-lightbox": "4.0.2",
"@seafile/resumablejs": "1.1.16",
"@seafile/sdoc-editor": "^2.0.8",
"@seafile/seafile-calendar": "0.0.28",
"@seafile/seafile-editor": "2.0.0",
- "@seafile/sf-metadata-ui-component": "^1.0.1",
+ "@seafile/sf-metadata-ui-component": "^1.0.2",
"@seafile/stldraw-editor": "1.0.0",
"@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/codemirror-themes": "^4.23.5",
@@ -5213,10 +5213,9 @@
"license": "MIT"
},
"node_modules/@seafile/react-image-lightbox": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/@seafile/react-image-lightbox/-/react-image-lightbox-4.0.1.tgz",
- "integrity": "sha512-4m1fMJWVertsvn4o7WqEfvMT8dW2hlm2Qp0CIxDPchRirIMcLQlHRWkMygWmU9m+FDOAw58YZr+9GJ3xbGn/4w==",
- "license": "MIT",
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@seafile/react-image-lightbox/-/react-image-lightbox-4.0.2.tgz",
+ "integrity": "sha512-rQy2X1JDltLE9hLcOQIee6dxW3UwUtWhjgbcOt/aq1BkqWG3mWzGdnHBUmFGfJMvwdbMgEPbDD3yDErBYy6P3w==",
"dependencies": {
"prop-types": "^15.8.1",
"react-modal": "^3.16.1"
@@ -5569,9 +5568,9 @@
}
},
"node_modules/@seafile/sf-metadata-ui-component": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-1.0.1.tgz",
- "integrity": "sha512-8uIfcVJMXz1pMe3dKcQOjlN5GB3gGX+dreTDgmLBJ8PyJgkLEGV0n3KdiQJMKLoLsHAj4uer8xZpSoKuf3LEiw==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-1.0.2.tgz",
+ "integrity": "sha512-soyDV9z1E1wAeW1qFcLmSIRZucX60b1czFcth9yZX5TwPXYA51y07xeXBEPS6URQPQ+0Mx7fQ4eSCkssa/ZNkg==",
"dependencies": {
"@seafile/seafile-calendar": "0.0.28",
"@seafile/seafile-editor": "2.0.0",
diff --git a/frontend/package.json b/frontend/package.json
index 95f48b9c51..bf68c69bbc 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -9,12 +9,12 @@
"@emoji-mart/data": "^1.2.1",
"@emoji-mart/react": "^1.1.1",
"@gatsbyjs/reach-router": "2.0.1",
- "@seafile/react-image-lightbox": "4.0.1",
+ "@seafile/react-image-lightbox": "4.0.2",
"@seafile/resumablejs": "1.1.16",
"@seafile/sdoc-editor": "^2.0.8",
"@seafile/seafile-calendar": "0.0.28",
"@seafile/seafile-editor": "2.0.0",
- "@seafile/sf-metadata-ui-component": "^1.0.1",
+ "@seafile/sf-metadata-ui-component": "^1.0.2",
"@seafile/stldraw-editor": "1.0.0",
"@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/codemirror-themes": "^4.23.5",
diff --git a/frontend/src/assets/icons/left_arrow.svg b/frontend/src/assets/icons/left_arrow.svg
new file mode 100644
index 0000000000..a1f6f436a0
--- /dev/null
+++ b/frontend/src/assets/icons/left_arrow.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/frontend/src/assets/icons/right_arrow.svg b/frontend/src/assets/icons/right_arrow.svg
new file mode 100644
index 0000000000..ea25f85e22
--- /dev/null
+++ b/frontend/src/assets/icons/right_arrow.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/frontend/src/components/dialog/image-dialog/index.css b/frontend/src/components/dialog/image-dialog/index.css
new file mode 100644
index 0000000000..daf9c99574
--- /dev/null
+++ b/frontend/src/components/dialog/image-dialog/index.css
@@ -0,0 +1,101 @@
+.lightbox-side-panel {
+ width: 10px;
+ height: calc(100% - 100px);
+ display: flex;
+ flex-direction: column;
+ top: 50px;
+ right: 0;
+ position: absolute;
+ font-size: 1rem;
+ color: #fff;
+ background-color: #333;
+ border: 1px solid #666;
+ transition: width 0.3s ease;
+}
+
+.lightbox-side-panel .cur-view-detail {
+ background-color: inherit;
+ border: none;
+}
+
+.lightbox-side-panel .side-panel-controller {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ top: 30px;
+ left: -40px;
+ width: 40px;
+ height: 48px;
+ background-color: #333;
+ color: #fff;
+ border: 1px solid #666;
+ border-right: none;
+ border-top-left-radius: 50%;
+ border-bottom-left-radius: 50%;
+ margin-right: -1px;
+ cursor: pointer;
+}
+
+.lightbox-side-panel .side-panel-controller .expand-button {
+ width: 32px;
+ height: 32px;
+ opacity: 0.7;
+ border: none;
+}
+
+.lightbox-side-panel .side-panel-controller:hover .expand-button {
+ opacity: 1;
+}
+
+.lightbox-side-panel .detail-header {
+ width: 100%;
+ height: fit-content;
+ display: flex;
+ padding: 26px 16px;
+ border: none;
+ font-size: 1rem;
+}
+
+.lightbox-side-panel .detail-header .detail-title .name,
+.lightbox-side-panel .file-details-collapse .file-details-collapse-header .file-details-collapse-header-title,
+.lightbox-side-panel .dirent-detail-item .dirent-detail-item-name,
+.lightbox-side-panel .sf-metadata-number-property-detail-editor {
+ color: #fff;
+}
+
+.lightbox-side-panel .detail-body {
+ scrollbar-color: #666 #333;
+ padding: 0 16px 8px;
+}
+
+.lightbox-side-panel .file-details-collapse+.dirent-detail-people {
+ margin-top: 20px;
+}
+
+.lightbox-side-panel .sf-metadata-ui.collaborator-item,
+.lightbox-side-panel .sf-metadata-text-property-detail-editor:not(.formatter),
+.lightbox-side-panel .sf-metadata-number-property-detail-editor:focus {
+ color: #212529;
+}
+
+.lightbox-side-panel .sf-metadata-text-property-detail-editor:not(.formatter) {
+ cursor: text;
+}
+
+.lightbox-side-panel .dirent-detail-item .dirent-detail-item-value:not(.editable) .sf-metadata-record-cell-empty:empty::before,
+.lightbox-side-panel .sf-metadata-property-detail-editor:empty::before,
+.lightbox-side-panel .sf-metadata-property-detail-capture-information-item .dirent-detail-item-value:empty::before,
+.lightbox-side-panel .file-details-collapse .file-details-collapse-header .sf3-font-down,
+.lightbox-side-panel .sf-metadata-number-property-detail-editor::placeholder {
+ color: #999;
+}
+
+.lightbox-side-panel .file-details-collapse .file-details-collapse-header .file-details-collapse-header-operation:hover,
+.lightbox-side-panel .dirent-detail-item .dirent-detail-item-value:hover {
+ background-color: #666;
+}
+
+.lightbox-side-panel .dirent-detail-people {
+ border-top: 1px solid #999;
+}
diff --git a/frontend/src/components/dialog/image-dialog.js b/frontend/src/components/dialog/image-dialog/index.js
similarity index 63%
rename from frontend/src/components/dialog/image-dialog.js
rename to frontend/src/components/dialog/image-dialog/index.js
index 5c44fd903f..a5a7b4eb3b 100644
--- a/frontend/src/components/dialog/image-dialog.js
+++ b/frontend/src/components/dialog/image-dialog/index.js
@@ -1,13 +1,22 @@
-import React, { useCallback } from 'react';
+import React, { useCallback, useState } from 'react';
import PropTypes from 'prop-types';
-import { gettext } from '../../utils/constants';
+import { gettext } from '../../../utils/constants';
import Lightbox from '@seafile/react-image-lightbox';
-import { useMetadataAIOperations } from '../../hooks/metadata-ai-operation';
-import { SYSTEM_FOLDERS } from '../../constants';
+import { useMetadataAIOperations } from '../../../hooks/metadata-ai-operation';
+import EmbeddedFileDetails from '../../dirent-detail/embedded-file-details';
+import { SYSTEM_FOLDERS } from '../../../constants';
+import { Utils } from '../../../utils/utils';
+import Icon from '../../icon';
import '@seafile/react-image-lightbox/style.css';
+import './index.css';
+
+const SIDE_PANEL_COLLAPSED_WIDTH = 10;
+const SIDE_PANEL_EXPANDED_WIDTH = 300;
+
+const ImageDialog = ({ repoID, repoInfo, enableRotate: oldEnableRotate = true, imageItems, imageIndex, closeImagePopup, moveToPrevImage, moveToNextImage, onDeleteImage, onRotateImage, isCustomPermission }) => {
+ const [expanded, setExpanded] = useState(false);
-const ImageDialog = ({ enableRotate: oldEnableRotate = true, imageItems, imageIndex, closeImagePopup, moveToPrevImage, moveToNextImage, onDeleteImage, onRotateImage }) => {
const { enableOCR, enableMetadata, canModify, onOCR: onOCRAPI, OCRSuccessCallBack } = useMetadataAIOperations();
const downloadImage = useCallback((url) => {
@@ -18,8 +27,13 @@ const ImageDialog = ({ enableRotate: oldEnableRotate = true, imageItems, imageIn
window.open(imageItems[imageIndex].url, '_blank');
}, [imageItems, imageIndex]);
+ const onToggleSidePanel = useCallback(() => {
+ setExpanded(!expanded);
+ }, [expanded]);
+
const imageItemsLength = imageItems.length;
if (imageItemsLength === 0) return null;
+ const id = imageItems[imageIndex].id;
const name = imageItems[imageIndex].name;
const mainImg = imageItems[imageIndex];
const nextImg = imageItems[(imageIndex + 1) % imageItemsLength];
@@ -39,6 +53,25 @@ const ImageDialog = ({ enableRotate: oldEnableRotate = true, imageItems, imageIn
onOCR = () => onOCRAPI({ parentDir: mainImg.parentDir, fileName: mainImg.name }, { success_callback: OCRSuccessCallBack });
}
+ const renderSidePanel = () => {
+ const dirent = { id, name, type: 'file' };
+ const path = Utils.joinPath(mainImg.parentDir, name);
+
+ return (
+
+
+
+
+ {expanded && (
)}
+
+
+ );
+ };
+
return (
onRotateImage(imageIndex, angle) : null}
onOCR={onOCR}
OCRLabel={gettext('OCR')}
+ sidePanel={!isCustomPermission ? {
+ render: renderSidePanel,
+ width: expanded ? SIDE_PANEL_EXPANDED_WIDTH : SIDE_PANEL_COLLAPSED_WIDTH,
+ } : null}
/>
);
};
diff --git a/frontend/src/components/dialog/lib-settings.js b/frontend/src/components/dialog/lib-settings.js
index e1d1e4670a..bc2b3e53c7 100644
--- a/frontend/src/components/dialog/lib-settings.js
+++ b/frontend/src/components/dialog/lib-settings.js
@@ -46,8 +46,8 @@ const LibSettingsDialog = ({ repoID, currentRepoInfo, toggleDialog, tab }) => {
const { encrypted, is_admin } = currentRepoInfo;
const { enableMetadataManagement } = window.app.pageOptions;
- const { enableFaceRecognition, updateEnableFaceRecognition } = useMetadata();
- const { enableMetadata, updateEnableMetadata, enableTags, tagsLang, updateEnableTags, enableOCR, updateEnableOCR } = useMetadataStatus();
+ const { updateEnableFaceRecognition } = useMetadata();
+ const { enableMetadata, updateEnableMetadata, enableTags, tagsLang, updateEnableTags, enableOCR, updateEnableOCR, enableFaceRecognition } = useMetadataStatus();
const enableHistorySetting = is_admin; // repo owner, admin of the department which the repo belongs to, and ...
const enableAutoDelSetting = is_admin && enableRepoAutoDel;
const enableExtendedPropertiesSetting = !encrypted && is_admin && enableMetadataManagement;
diff --git a/frontend/src/components/dir-view-mode/dir-files.js b/frontend/src/components/dir-view-mode/dir-files.js
index 8b576ad094..f6a57ba119 100644
--- a/frontend/src/components/dir-view-mode/dir-files.js
+++ b/frontend/src/components/dir-view-mode/dir-files.js
@@ -257,6 +257,7 @@ class DirFiles extends React.Component {
thumbnail = `${siteRoot}thumbnail/${repoID}/${thumbnailSizeForOriginal}${path}`;
}
return {
+ id: item.object.id,
name,
parentDir: node.parentNode.path,
src,
@@ -364,12 +365,11 @@ class DirFiles extends React.Component {
render() {
const { repoID, currentRepoInfo, userPerm } = this.props;
const { encrypted: repoEncrypted } = currentRepoInfo;
-
+ const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm);
let canModifyFile = false;
if (['rw', 'cloud-edit'].indexOf(userPerm) != -1) {
canModifyFile = true;
} else {
- const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm);
if (isCustomPermission) {
const { modify } = customPermission.permission;
canModifyFile = modify;
@@ -461,6 +461,8 @@ class DirFiles extends React.Component {
{this.state.isNodeImagePopupOpen && (
)}
diff --git a/frontend/src/components/dirent-detail/detail/header/index.js b/frontend/src/components/dirent-detail/detail/header/index.js
index 1ff83603a8..499db364f5 100644
--- a/frontend/src/components/dirent-detail/detail/header/index.js
+++ b/frontend/src/components/dirent-detail/detail/header/index.js
@@ -10,12 +10,16 @@ const Header = ({ title, icon, iconSize = 32, onClose, children, component = {}
return (
-
- {children}
-
- {closeIcon ? closeIcon :
}
+ {(children || onClose) && (
+
+ {children}
+ {onClose && (
+
+ {closeIcon ? closeIcon : }
+
+ )}
-
+ )}
);
};
@@ -26,7 +30,7 @@ Header.propTypes = {
iconSize: PropTypes.number,
component: PropTypes.object,
children: PropTypes.any,
- onClose: PropTypes.func.isRequired,
+ onClose: PropTypes.func,
};
export default Header;
diff --git a/frontend/src/components/dirent-detail/dirent-details/file-details/file-tag.js b/frontend/src/components/dirent-detail/dirent-details/file-details/file-tag.js
new file mode 100644
index 0000000000..2d4c256ec6
--- /dev/null
+++ b/frontend/src/components/dirent-detail/dirent-details/file-details/file-tag.js
@@ -0,0 +1,61 @@
+import React, { useCallback, useMemo, useState } from 'react';
+import PropTypes from 'prop-types';
+import { v4 as uuidV4 } from 'uuid';
+import classnames from 'classnames';
+import { getDirentPath } from '../utils';
+import { gettext } from '../../../../utils/constants';
+import EditFileTagPopover from '../../../popover/edit-filetag-popover';
+import FileTagList from '../../../file-tag-list';
+
+const FileTag = ({ repoID, dirent, path, repoTags, fileTagList, onFileTagChanged }) => {
+ const [isEditFileTagShow, setEditFileTagShow] = useState(false);
+ const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
+ const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []);
+
+ const onEditFileTagToggle = useCallback(() => {
+ setEditFileTagShow(!isEditFileTagShow);
+ }, [isEditFileTagShow]);
+
+ const fileTagChanged = useCallback(() => {
+ onFileTagChanged(dirent, direntPath);
+ }, [dirent, direntPath, onFileTagChanged]);
+
+ return (
+ <>
+
+ {Array.isArray(fileTagList) && fileTagList.length > 0 ? (
+
+ ) : (
+ {gettext('Empty')}
+ )}
+
+ {isEditFileTagShow &&
+
+ }
+ >
+ );
+};
+
+FileTag.propTypes = {
+ repoID: PropTypes.string,
+ dirent: PropTypes.object,
+ path: PropTypes.string,
+ direntDetail: PropTypes.object,
+ repoTags: PropTypes.array,
+ fileTagList: PropTypes.array,
+ onFileTagChanged: PropTypes.func,
+};
+
+export default FileTag;
diff --git a/frontend/src/components/dirent-detail/dirent-details/file-details/index.js b/frontend/src/components/dirent-detail/dirent-details/file-details/index.js
index 0e1e433dca..ebdbb466c6 100644
--- a/frontend/src/components/dirent-detail/dirent-details/file-details/index.js
+++ b/frontend/src/components/dirent-detail/dirent-details/file-details/index.js
@@ -1,14 +1,9 @@
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
-import { v4 as uuidV4 } from 'uuid';
import { Formatter } from '@seafile/sf-metadata-ui-component';
-import classnames from 'classnames';
-import { getDirentPath } from '../utils';
import DetailItem from '../../detail-item';
import { CellType, PRIVATE_COLUMN_KEY } from '../../../../metadata/constants';
import { gettext } from '../../../../utils/constants';
-import EditFileTagPopover from '../../../popover/edit-filetag-popover';
-import FileTagList from '../../../file-tag-list';
import { Utils } from '../../../../utils/utils';
import { MetadataDetails, useMetadataDetails } from '../../../../metadata';
import ObjectUtils from '../../../../metadata/utils/object-utils';
@@ -16,6 +11,8 @@ import { getCellValueByColumn, getDateDisplayString, decimalToExposureTime } fro
import Collapse from './collapse';
import { useMetadataStatus } from '../../../../hooks';
import { CAPTURE_INFO_SHOW_KEY } from '../../../../constants';
+import People from '../../people';
+import FileTag from './file-tag';
import './index.css';
@@ -58,14 +55,11 @@ const getImageInfoValue = (key, value) => {
}
};
-const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => {
- const [isEditFileTagShow, setEditFileTagShow] = useState(false);
+const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, isShowRepoTags = true, repoTags, fileTagList, onFileTagChanged }) => {
const [isCaptureInfoShow, setCaptureInfoShow] = useState(false);
- const { enableMetadataManagement, enableMetadata } = useMetadataStatus();
+ const { enableFaceRecognition, enableMetadata } = useMetadataStatus();
const { record } = useMetadataDetails();
- const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
- const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []);
const sizeField = useMemo(() => ({ type: 'size', name: gettext('Size') }), []);
const lastModifierField = useMemo(() => ({ type: CellType.LAST_MODIFIER, name: gettext('Last modifier') }), []);
const lastModifiedTimeField = useMemo(() => ({ type: CellType.MTIME, name: gettext('Last modified time') }), []);
@@ -76,14 +70,6 @@ const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, onFileTagC
setCaptureInfoShow(savedValue);
}, []);
- const onEditFileTagToggle = useCallback(() => {
- setEditFileTagShow(!isEditFileTagShow);
- }, [isEditFileTagShow]);
-
- const fileTagChanged = useCallback(() => {
- onFileTagChanged(dirent, direntPath);
- }, [dirent, direntPath, onFileTagChanged]);
-
const dom = (
<>
@@ -104,22 +90,12 @@ const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, onFileTagC
- {window.app.pageOptions.enableFileTags && !enableMetadata && (
+ {isShowRepoTags && window.app.pageOptions.enableFileTags && !enableMetadata && (
-
- {Array.isArray(fileTagList) && fileTagList.length > 0 ? (
-
- ) : (
- {gettext('Empty')}
- )}
-
+
)}
- {enableMetadataManagement && enableMetadata && (
+ {enableMetadata && (
)}
>
@@ -154,22 +130,15 @@ const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, onFileTagC
return (
<>
{component}
- {isEditFileTagShow &&
-
- }
+ {enableFaceRecognition && Utils.imageCheck(dirent.name) && (
+
+ )}
>
);
}, (props, nextProps) => {
- const { repoID, repoInfo, dirent, path, direntDetail, repoTags, fileTagList } = props;
+ const { repoID, repoInfo, dirent, path, direntDetail, isShowRepoTags, repoTags, fileTagList } = props;
const isChanged = (
+ isShowRepoTags !== nextProps.isShowRepoTags ||
repoID !== nextProps.repoID ||
path !== nextProps.path ||
!ObjectUtils.isSameObject(repoInfo, nextProps.repoInfo) ||
@@ -182,11 +151,14 @@ const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, onFileTagC
});
FileDetails.propTypes = {
+ isShowRepoTags: PropTypes.bool,
repoID: PropTypes.string,
repoInfo: PropTypes.object,
dirent: PropTypes.object,
path: PropTypes.string,
direntDetail: PropTypes.object,
+ repoTags: PropTypes.array,
+ fileTagList: PropTypes.array,
onFileTagChanged: PropTypes.func,
};
diff --git a/frontend/src/components/dirent-detail/embedded-file-details/file-details.js b/frontend/src/components/dirent-detail/embedded-file-details/file-details.js
deleted file mode 100644
index 2ed2ae5894..0000000000
--- a/frontend/src/components/dirent-detail/embedded-file-details/file-details.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, { useMemo } from 'react';
-import PropTypes from 'prop-types';
-import { Formatter } from '@seafile/sf-metadata-ui-component';
-import DetailItem from '../detail-item';
-import { CellType } from '../../../metadata/constants';
-import { gettext } from '../../../utils/constants';
-import { Utils } from '../../../utils/utils';
-import { MetadataDetails } from '../../../metadata';
-import { useMetadataStatus } from '../../../hooks';
-
-const FileDetails = ({ direntDetail }) => {
- const { enableMetadata } = useMetadataStatus();
-
- const sizeField = useMemo(() => ({ type: 'size', name: gettext('Size') }), []);
- const lastModifierField = useMemo(() => ({ type: CellType.LAST_MODIFIER, name: gettext('Last modifier') }), []);
- const lastModifiedTimeField = useMemo(() => ({ type: CellType.MTIME, name: gettext('Last modified time') }), []);
-
- return (
- <>
-
-
-
-
-
-
-
-
-
- {enableMetadata && (
-
- )}
- >
- );
-};
-
-FileDetails.propTypes = {
- direntDetail: PropTypes.object,
-};
-
-export default FileDetails;
diff --git a/frontend/src/components/dirent-detail/embedded-file-details/index.js b/frontend/src/components/dirent-detail/embedded-file-details/index.js
index 26dcdc6d4f..c42a48e9fb 100644
--- a/frontend/src/components/dirent-detail/embedded-file-details/index.js
+++ b/frontend/src/components/dirent-detail/embedded-file-details/index.js
@@ -1,11 +1,11 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, useMemo } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { seafileAPI } from '../../../utils/seafile-api';
import { Utils } from '../../../utils/utils';
import toaster from '../../toast';
import { Header, Body } from '../detail';
-import FileDetails from './file-details';
+import FileDetails from '../dirent-details/file-details';
import { MetadataContext } from '../../../metadata';
import { MetadataDetailsProvider } from '../../../metadata/hooks';
import { AI, Settings } from '../../../metadata/components/metadata-details';
@@ -16,20 +16,38 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width =
const { headerComponent } = component;
const [direntDetail, setDirentDetail] = useState('');
+ const isView = useMemo(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.has('view');
+ }, []);
+
+ const isTag = useMemo(() => {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.has('tag');
+ }, []);
+
useEffect(() => {
- // init context
- const context = new MetadataContext();
- window.sfMetadataContext = context;
- window.sfMetadataContext.init({ repoID, repoInfo });
seafileAPI.getFileInfo(repoID, path).then(res => {
setDirentDetail(res.data);
}).catch(error => {
const errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
+ }, [repoID, path]);
+
+ useEffect(() => {
+ if (isView || isTag) return;
+
+ let isExistContext = true;
+ if (!window.sfMetadataContext) {
+ const context = new MetadataContext();
+ window.sfMetadataContext = context;
+ window.sfMetadataContext.init({ repoID, repoInfo });
+ isExistContext = false;
+ }
return () => {
- if (window.sfMetadataContext) {
+ if (window.sfMetadataContext && !isExistContext) {
window.sfMetadataContext.destroy();
delete window['sfMetadataContext'];
}
@@ -53,14 +71,18 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width =
})}
style={{ width }}
>
-
-
-
+
+ {onClose && (
+ <>
+
+
+ >
+ )}
{dirent && direntDetail && (
-
+
)}
@@ -75,7 +97,7 @@ EmbeddedFileDetails.propTypes = {
path: PropTypes.string.isRequired,
repoInfo: PropTypes.object.isRequired,
component: PropTypes.object,
- onClose: PropTypes.func.isRequired,
+ onClose: PropTypes.func,
};
export default EmbeddedFileDetails;
diff --git a/frontend/src/components/dirent-detail/people/index.css b/frontend/src/components/dirent-detail/people/index.css
new file mode 100644
index 0000000000..f6807ae5a9
--- /dev/null
+++ b/frontend/src/components/dirent-detail/people/index.css
@@ -0,0 +1,15 @@
+.dirent-detail-people {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 12px;
+ margin-bottom: 20px;
+ border-top: 1px solid #eee;
+ padding-top: 20px;
+}
+
+.dirent-detail-people .dirent-detail-people-item {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ overflow: hidden;
+}
diff --git a/frontend/src/components/dirent-detail/people/index.js b/frontend/src/components/dirent-detail/people/index.js
new file mode 100644
index 0000000000..7ae590d7ee
--- /dev/null
+++ b/frontend/src/components/dirent-detail/people/index.js
@@ -0,0 +1,36 @@
+import React, { useCallback, useMemo } from 'react';
+import { PRIVATE_COLUMN_KEY } from '../../../metadata/constants';
+import { gettext, mediaUrl, siteRoot, thumbnailDefaultSize } from '../../../utils/constants';
+import { getCellValueByColumn } from '../../../metadata/utils/cell';
+
+import './index.css';
+
+const People = ({ repoID, record }) => {
+ const images = useMemo(() => {
+ if (!record) return [];
+ const faceLinks = getCellValueByColumn(record, { key: PRIVATE_COLUMN_KEY.FACE_LINKS });
+ if (!faceLinks) return [];
+ return faceLinks.map(item => ({
+ ...item,
+ url: `${siteRoot}thumbnail/${repoID}/${thumbnailDefaultSize}/_Internal/Faces/${item.row_id}.jpg`
+ }));
+ }, [repoID, record]);
+
+ const onImgLoadError = useCallback((e) => {
+ e.target.src = `${mediaUrl}avatars/default.png`;
+ }, []);
+
+ if (!images.length) return null;
+
+ return (
+
+ {images.map(img => (
+
+

+
+ ))}
+
+ );
+};
+
+export default People;
diff --git a/frontend/src/components/dirent-grid-view/dirent-grid-view.js b/frontend/src/components/dirent-grid-view/dirent-grid-view.js
index 75e26fad5c..85ffbaaa7d 100644
--- a/frontend/src/components/dirent-grid-view/dirent-grid-view.js
+++ b/frontend/src/components/dirent-grid-view/dirent-grid-view.js
@@ -6,7 +6,7 @@ import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
import Loading from '../loading';
import ModalPortal from '../modal-portal';
-import ImageDialog from '../../components/dialog/image-dialog';
+import ImageDialog from '../dialog/image-dialog';
import DirentGridItem from '../../components/dirent-grid-view/dirent-grid-item';
import ContextMenu from '../context-menu/context-menu';
import { hideMenu, showMenu } from '../context-menu/actions';
@@ -618,6 +618,7 @@ class DirentGridView extends React.Component {
}
return {
+ id: item.id,
name,
thumbnail,
src,
@@ -860,11 +861,11 @@ class DirentGridView extends React.Component {
let canModifyFile = false;
let canDeleteFile = false;
+ const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm);
if (['rw', 'cloud-edit'].indexOf(userPerm) != -1) {
canModifyFile = true;
canDeleteFile = true;
} else {
- const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm);
if (isCustomPermission) {
const { modify, delete: canDelete } = customPermission.permission;
canModifyFile = modify;
@@ -1042,6 +1043,8 @@ class DirentGridView extends React.Component {
{this.state.isImagePopupOpen && this.state.imageItems.length && (
)}
diff --git a/frontend/src/components/dirent-list-view/dirent-list-view.js b/frontend/src/components/dirent-list-view/dirent-list-view.js
index fdbb4827ff..b63c1a574f 100644
--- a/frontend/src/components/dirent-list-view/dirent-list-view.js
+++ b/frontend/src/components/dirent-list-view/dirent-list-view.js
@@ -205,6 +205,7 @@ class DirentListView extends React.Component {
}
return {
+ id: item.id,
name,
thumbnail,
src,
@@ -765,11 +766,11 @@ class DirentListView extends React.Component {
let canModifyFile = false;
let canDeleteFile = false;
+ const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm);
if (['rw', 'cloud-edit'].indexOf(userPerm) != -1) {
canModifyFile = true;
canDeleteFile = true;
} else {
- const { isCustomPermission, customPermission } = Utils.getUserPermission(userPerm);
if (isCustomPermission) {
const { modify, delete: canDelete } = customPermission.permission;
canModifyFile = modify;
@@ -872,6 +873,8 @@ class DirentListView extends React.Component {
{this.state.isImagePopupOpen && (
)}
diff --git a/frontend/src/hooks/metadata-status.js b/frontend/src/hooks/metadata-status.js
index 16458cd8a4..a7dce0a5a0 100644
--- a/frontend/src/hooks/metadata-status.js
+++ b/frontend/src/hooks/metadata-status.js
@@ -21,6 +21,7 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, chi
const [enableOCR, setEnableOCR] = useState(false);
const [detailsSettings, setDetailsSettings] = useState({});
const [isBeingBuilt, setIsBeingBuilt] = useState(false);
+ const [enableFaceRecognition, setEnableFaceRecognition] = useState(false);
const cancelMetadataURL = useCallback((isSetRoot = false) => {
// If attribute extension is turned off, unmark the URL
@@ -39,6 +40,7 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, chi
setEnableMetadata(false);
setEnableTags(false);
setEnableOCR(false);
+ setEnableFaceRecognition(false);
setDetailsSettings({});
setIsBeingBuilt(false);
if (!enableMetadataManagement) {
@@ -52,7 +54,8 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, chi
tags_enabled: enableTags,
tags_lang: tagsLang,
details_settings: detailsSettings,
- ocr_enabled: enableOCR
+ ocr_enabled: enableOCR,
+ face_recognition_enabled: enableFaceRecognition,
} = res.data;
if (!enableMetadata) {
cancelMetadataURL();
@@ -61,6 +64,7 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, chi
setTagsLang(tagsLang || 'en');
setDetailsSettings(JSON.parse(detailsSettings));
setEnableOCR(enableOCR);
+ setEnableFaceRecognition(enableFaceRecognition);
setEnableMetadata(enableMetadata);
setLoading(false);
}).catch(error => {
@@ -97,6 +101,11 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, chi
setEnableOCR(newValue);
}, [enableOCR]);
+ const updateEnableFaceRecognition = useCallback((newValue) => {
+ if (newValue === enableFaceRecognition) return;
+ setEnableFaceRecognition(newValue);
+ }, [enableFaceRecognition]);
+
const modifyDetailsSettings = useCallback((update) => {
metadataAPI.modifyMetadataDetailsSettings(repoID, update).then(res => {
const newDetailsSettings = { ...detailsSettings, ...update };
@@ -122,6 +131,8 @@ export const MetadataStatusProvider = ({ repoID, repoInfo, hideMetadataView, chi
modifyDetailsSettings,
enableOCR,
updateEnableOCR,
+ enableFaceRecognition,
+ updateEnableFaceRecognition,
}}
>
{!isLoading && (
diff --git a/frontend/src/metadata/components/cell-editors/long-text-editor/index.css b/frontend/src/metadata/components/cell-editors/long-text-editor/index.css
deleted file mode 100644
index 739dd34b72..0000000000
--- a/frontend/src/metadata/components/cell-editors/long-text-editor/index.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.sf-metadata-long-text-editor-dialog {
- z-index: 1049 !important;
-}
diff --git a/frontend/src/metadata/components/cell-editors/long-text-editor/index.js b/frontend/src/metadata/components/cell-editors/long-text-editor/index.js
index dafacbdf70..1b9e6f5c75 100644
--- a/frontend/src/metadata/components/cell-editors/long-text-editor/index.js
+++ b/frontend/src/metadata/components/cell-editors/long-text-editor/index.js
@@ -9,8 +9,6 @@ import { lang, serviceURL } from '../../../../utils/constants';
import { LONG_TEXT_EXCEED_LIMIT_MESSAGE, LONG_TEXT_EXCEED_LIMIT_SUGGEST } from '../../../constants';
import i18n from '../../../../_i18n/i18n-seafile-editor';
-import './index.css';
-
class LongTextEditor extends React.PureComponent {
constructor(props) {
diff --git a/frontend/src/metadata/components/cell-formatter/image-previewer.js b/frontend/src/metadata/components/cell-formatter/image-previewer.js
index 0c3145f77c..ed64e89bca 100644
--- a/frontend/src/metadata/components/cell-formatter/image-previewer.js
+++ b/frontend/src/metadata/components/cell-formatter/image-previewer.js
@@ -98,6 +98,8 @@ const ImagePreviewer = ({ record, table, repoID, repoInfo, closeImagePopup, dele
return (
{
const errorMsg = Utils.getErrorMsg(error);
diff --git a/frontend/src/metadata/hooks/metadata.js b/frontend/src/metadata/hooks/metadata.js
index a295d3e04f..9a5a867391 100644
--- a/frontend/src/metadata/hooks/metadata.js
+++ b/frontend/src/metadata/hooks/metadata.js
@@ -17,14 +17,13 @@ const MetadataContext = React.createContext(null);
export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadataView, children }) => {
const [isLoading, setLoading] = useState(true);
- const [enableFaceRecognition, setEnableFaceRecognition] = useState(false);
const [navigation, setNavigation] = useState([]);
const [idViewMap, setIdViewMap] = useState({});
const collapsedFoldersIds = useRef([]);
const originalTitleRef = useRef(document.title);
- const { enableMetadata, isBeingBuilt, setIsBeingBuilt } = useMetadataStatus();
+ const { enableMetadata, enableFaceRecognition, isBeingBuilt, setIsBeingBuilt, updateEnableFaceRecognition: updateEnableFaceRecognitionAPI } = useMetadataStatus();
const getCollapsedFolders = useCallback(() => {
const strFoldedFolders = window.localStorage.getItem(`${CACHED_COLLAPSED_FOLDERS_PREFIX}-${repoID}`);
@@ -70,27 +69,12 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata
});
return;
}
- setEnableFaceRecognition(false);
setNavigation([]);
setIdViewMap({});
setLoading(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enableMetadata]);
- useEffect(() => {
- if (!enableMetadata) {
- setEnableFaceRecognition(false);
- return;
- }
- metadataAPI.getFaceRecognitionStatus(repoID).then(res => {
- setEnableFaceRecognition(res.data.enabled);
- }).catch(error => {
- const errorMsg = Utils.getErrorMsg(error);
- toaster.danger(errorMsg);
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [enableMetadata]);
-
const getFirstView = useCallback(() => {
const firstViewNav = navigation.find(item => item.type === VIEWS_TYPE_VIEW);
const firstView = firstViewNav ? idViewMap[firstViewNav._id] : null;
@@ -394,8 +378,8 @@ export const MetadataProvider = ({ repoID, currentPath, repoInfo, selectMetadata
deleteView({ folderId, viewId: FACE_RECOGNITION_VIEW_ID, isSelected });
}
}
- setEnableFaceRecognition(newValue);
- }, [enableFaceRecognition, currentPath, idViewMap, navigation, addView, deleteView]);
+ updateEnableFaceRecognitionAPI(newValue);
+ }, [enableFaceRecognition, currentPath, idViewMap, navigation, addView, deleteView, updateEnableFaceRecognitionAPI]);
const modifyViewType = useCallback((viewId, update) => {
metadataAPI.modifyView(repoID, viewId, update).then(res => {
diff --git a/frontend/src/metadata/views/gallery/main.js b/frontend/src/metadata/views/gallery/main.js
index 15f7b6fcd8..df185df183 100644
--- a/frontend/src/metadata/views/gallery/main.js
+++ b/frontend/src/metadata/views/gallery/main.js
@@ -35,6 +35,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
const lastState = useRef({ scrollPos: 0 });
const { repoID, updateCurrentDirent } = useMetadataView();
+ const repoInfo = window.sfMetadataContext.getSetting('repoInfo');
const images = useMemo(() => {
if (isFirstLoading) return [];
@@ -424,6 +425,8 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
{isImagePopupOpen && (
}
diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py
index a7d29c349f..bc6b5e6a3a 100644
--- a/seahub/repo_metadata/apis.py
+++ b/seahub/repo_metadata/apis.py
@@ -52,6 +52,7 @@ class MetadataManage(APIView):
tags_lang = ''
details_settings = '{}'
is_ocr_enabled = False
+ face_recognition_enabled = False
try:
record = RepoMetadata.objects.filter(repo_id=repo_id).first()
@@ -65,6 +66,8 @@ class MetadataManage(APIView):
tags_lang = record.tags_lang
if record.ocr_enabled:
is_ocr_enabled = True
+ if record.face_recognition_enabled:
+ face_recognition_enabled = True
except Exception as e:
logger.error(e)
error_msg = 'Internal Server Error'
@@ -73,9 +76,10 @@ class MetadataManage(APIView):
return Response({
'enabled': is_enabled,
'tags_enabled': is_tags_enabled,
+ 'ocr_enabled': is_ocr_enabled,
+ 'face_recognition_enabled': face_recognition_enabled,
'tags_lang': tags_lang,
'details_settings': details_settings,
- 'ocr_enabled': is_ocr_enabled
})
def put(self, request, repo_id):