mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-18 00:00:00 +00:00
feat: metadata details settings (#7145)
* feat: metadata details settings * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code * feat: optimize code --------- Co-authored-by: 杨国璇 <ygx@Hello-word.local> Co-authored-by: 杨国璇 <ygx@192.168.1.6>
This commit is contained in:
@@ -1 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1721786317709" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="22672" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 304c115.2 0 208 92.8 208 208s-92.8 208-208 208-208-92.8-208-208 92.8-208 208-208z m0 128c-44.8 0-80 35.2-80 80s35.2 80 80 80 80-35.2 80-80-35.2-80-80-80z" p-id="22673"></path><path d="M432 57.6c-9.6-19.2-32-28.8-51.2-22.4-80 19.2-150.4 60.8-208 112-12.8 16-16 35.2-9.6 54.4 6.4 12.8 9.6 25.6 9.6 41.6 0 51.2-41.6 92.8-89.6 92.8-19.2 0-38.4 12.8-41.6 32C22.4 409.6 16 460.8 16 512c0 35.2 3.2 67.2 9.6 99.2 3.2 22.4 25.6 38.4 48 35.2h9.6c51.2 0 89.6 41.6 89.6 92.8 0 22.4-9.6 41.6-22.4 57.6-16 16-12.8 41.6 3.2 60.8 57.6 60.8 131.2 105.6 214.4 131.2 22.4 6.4 48-6.4 54.4-28.8 12.8-38.4 44.8-64 86.4-64s73.6 25.6 86.4 64c6.4 22.4 32 35.2 54.4 28.8 83.2-25.6 156.8-70.4 214.4-131.2 16-16 16-41.6 3.2-60.8-12.8-16-22.4-35.2-22.4-57.6 0-51.2 41.6-92.8 89.6-92.8h9.6c22.4 3.2 44.8-12.8 48-35.2 6.4-32 9.6-67.2 9.6-99.2 0-51.2-6.4-102.4-22.4-150.4-6.4-19.2-22.4-32-41.6-32-51.2 0-89.6-41.6-89.6-92.8 0-16 3.2-28.8 9.6-41.6 9.6-16 3.2-38.4-9.6-51.2-57.6-54.4-128-92.8-204.8-115.2-19.2 0-41.6 9.6-51.2 28.8-16 28.8-44.8 51.2-80 51.2s-67.2-22.4-80-51.2zM288 262.4c0-16-3.2-28.8-6.4-41.6 32-25.6 67.2-44.8 105.6-60.8 28.8 38.4 73.6 60.8 124.8 60.8s96-22.4 124.8-60.8c38.4 12.8 73.6 35.2 105.6 60.8-3.2 12.8-6.4 28.8-6.4 41.6 0 80 57.6 150.4 134.4 163.2 6.4 28.8 9.6 57.6 9.6 86.4 0 16 0 28.8-3.2 44.8-80 9.6-140.8 80-140.8 163.2 0 25.6 6.4 51.2 16 73.6-32 28.8-67.2 51.2-105.6 67.2-28.8-48-80-76.8-137.6-76.8s-108.8 32-137.6 76.8c-38.4-16-73.6-38.4-105.6-67.2 9.6-22.4 16-48 16-73.6 0-83.2-60.8-153.6-140.8-163.2 3.2-12.8 3.2-28.8 3.2-41.6 0-28.8 3.2-57.6 9.6-86.4C230.4 412.8 288 345.6 288 262.4z" p-id="22674"></path></svg>
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1733301494152" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6527" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 320c105.6 0 192 86.4 192 192s-86.4 192-192 192-192-86.4-192-192 86.4-192 192-192z m0 96c-54.4 0-96 41.6-96 96s41.6 96 96 96 96-41.6 96-96-41.6-96-96-96z" p-id="6528"></path><path d="M432 60.8c-9.6-19.2-32-28.8-54.4-22.4-83.2 19.2-153.6 60.8-214.4 112-12.8 16-16 35.2-9.6 54.4 6.4 12.8 9.6 25.6 9.6 41.6 0 51.2-44.8 92.8-92.8 92.8-19.2 0-38.4 12.8-44.8 32-19.2 41.6-25.6 92.8-25.6 144 0 35.2 3.2 67.2 9.6 99.2 3.2 22.4 25.6 38.4 51.2 35.2h9.6c54.4 0 92.8 41.6 92.8 92.8 0 22.4-9.6 41.6-22.4 57.6-16 16-12.8 41.6 3.2 60.8 60.8 60.8 137.6 105.6 224 131.2 22.4 6.4 51.2-6.4 57.6-28.8 12.8-38.4 48-64 89.6-64s76.8 25.6 89.6 64c6.4 22.4 32 35.2 57.6 28.8 86.4-25.6 163.2-70.4 224-131.2 16-16 16-41.6 3.2-60.8-12.8-16-22.4-35.2-22.4-57.6 0-51.2 44.8-92.8 92.8-92.8h9.6c22.4 3.2 48-12.8 51.2-35.2 6.4-32 9.6-67.2 9.6-99.2 0-51.2-6.4-102.4-22.4-150.4-6.4-19.2-22.4-32-44.8-32-54.4 0-92.8-41.6-92.8-92.8 0-16 3.2-28.8 9.6-41.6 3.2-16-3.2-38.4-16-51.2-60.8-54.4-134.4-92.8-211.2-115.2-19.2 0-44.8 9.6-54.4 28.8-16 28.8-48 51.2-83.2 51.2s-70.4-22.4-83.2-51.2zM262.4 240c0-16-3.2-32-6.4-44.8 35.2-28.8 76.8-48 118.4-67.2 32 41.6 83.2 67.2 140.8 67.2 57.6 0 108.8-25.6 140.8-67.2 44.8 12.8 83.2 38.4 118.4 67.2-3.2 12.8-6.4 32-6.4 44.8 0 86.4 64 166.4 150.4 179.2 6.4 32 9.6 64 9.6 96 0 16 0 32-3.2 48-89.6 9.6-160 86.4-160 179.2 0 28.8 6.4 57.6 19.2 80-35.2 32-76.8 57.6-118.4 73.6-32-51.2-89.6-83.2-153.6-83.2s-121.6 35.2-153.6 83.2c-44.8-16-83.2-41.6-118.4-73.6 6.4-25.6 16-51.2 16-80 0-89.6-67.2-169.6-160-179.2 3.2-12.8 3.2-32 3.2-44.8 0-32 3.2-64 9.6-96 89.6-16 153.6-89.6 153.6-182.4z" p-id="6529"></path></svg>
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -9,28 +9,11 @@
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.detail-header .detail-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
width: 0; /* prevent strut flex layout */
|
||||
}
|
||||
|
||||
.detail-header .detail-title .detail-header-icon-container {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
.detail-header .detail-control-container {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-header .detail-title .name {
|
||||
margin: 0 0.5rem 0 6px;
|
||||
line-height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
font-size: 1rem;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.detail-header .detail-control {
|
||||
|
@@ -1,21 +1,20 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Icon from '../../../icon';
|
||||
import Title from './title';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const Header = ({ title, icon, iconSize = 32, onClose, component = {} }) => {
|
||||
const Header = ({ title, icon, iconSize = 32, onClose, children, component = {} }) => {
|
||||
const { closeIcon } = component;
|
||||
return (
|
||||
<div className="detail-header">
|
||||
<div className="detail-title dirent-title">
|
||||
<div className="detail-header-icon-container">
|
||||
<img src={icon} width={iconSize} height={iconSize} alt="" />
|
||||
<Title title={title} icon={icon} iconSize={iconSize} />
|
||||
<div className="detail-control-container">
|
||||
{children}
|
||||
<div className="detail-control" onClick={onClose}>
|
||||
{closeIcon ? closeIcon : <Icon symbol="close" className="detail-control-close" />}
|
||||
</div>
|
||||
<span className="name ellipsis" title={title}>{title}</span>
|
||||
</div>
|
||||
<div className="detail-control" onClick={onClose}>
|
||||
{closeIcon ? closeIcon : <Icon symbol="close" className="detail-control-close" />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -26,6 +25,7 @@ Header.propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
iconSize: PropTypes.number,
|
||||
component: PropTypes.object,
|
||||
children: PropTypes.any,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
@@ -0,0 +1,23 @@
|
||||
.detail-header .detail-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
width: 0; /* prevent strut flex layout */
|
||||
}
|
||||
|
||||
.detail-header .detail-title .detail-header-icon-container {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-header .detail-title .name {
|
||||
margin: 0 0.5rem 0 6px;
|
||||
line-height: 1.5rem;
|
||||
vertical-align: middle;
|
||||
font-size: 1rem;
|
||||
color: #212529;
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const Title = ({ icon, iconSize, title }) => {
|
||||
|
||||
return (
|
||||
<div className="detail-title dirent-title">
|
||||
<div className="detail-header-icon-container">
|
||||
<img src={icon} width={iconSize} height={iconSize} alt="" />
|
||||
</div>
|
||||
<span className="name ellipsis" title={title}>{title}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Title.propTypes = {
|
||||
icon: PropTypes.string,
|
||||
iconSize: PropTypes.number,
|
||||
title: PropTypes.string,
|
||||
};
|
||||
|
||||
export default Title;
|
@@ -1,16 +1,14 @@
|
||||
import React, { useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Formatter } from '@seafile/sf-metadata-ui-component';
|
||||
import { getDirentPath } from './utils';
|
||||
import DetailItem from '../detail-item';
|
||||
import { CellType } from '../../../metadata/constants';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { MetadataDetails } from '../../../metadata';
|
||||
import { useMetadataStatus } from '../../../hooks';
|
||||
|
||||
const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail }) => {
|
||||
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
|
||||
const { enableMetadata } = useMetadataStatus();
|
||||
const DirDetails = ({ direntDetail }) => {
|
||||
const { enableMetadata, enableMetadataManagement } = useMetadataStatus();
|
||||
const lastModifiedTimeField = useMemo(() => {
|
||||
return { type: CellType.MTIME, name: gettext('Last modified time') };
|
||||
}, []);
|
||||
@@ -20,8 +18,8 @@ const DirDetails = ({ repoID, repoInfo, dirent, path, direntDetail }) => {
|
||||
<DetailItem field={lastModifiedTimeField} className="sf-metadata-property-detail-formatter">
|
||||
<Formatter field={lastModifiedTimeField} value={direntDetail.mtime} />
|
||||
</DetailItem>
|
||||
{window.app.pageOptions.enableMetadataManagement && enableMetadata && (
|
||||
<MetadataDetails repoID={repoID} repoInfo={repoInfo} filePath={direntPath} direntType="dir" />
|
||||
{enableMetadataManagement && enableMetadata && (
|
||||
<MetadataDetails />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@@ -10,7 +10,7 @@ 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 } from '../../../../metadata';
|
||||
import { MetadataDetails, useMetadataDetails } from '../../../../metadata';
|
||||
import ObjectUtils from '../../../../metadata/utils/object-utils';
|
||||
import { getCellValueByColumn, getDateDisplayString, decimalToExposureTime } from '../../../../metadata/utils/cell';
|
||||
import Collapse from './collapse';
|
||||
@@ -57,10 +57,10 @@ const getImageInfoValue = (key, value) => {
|
||||
}
|
||||
};
|
||||
|
||||
const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => {
|
||||
const FileDetails = React.memo(({ repoID, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => {
|
||||
const [isEditFileTagShow, setEditFileTagShow] = useState(false);
|
||||
const { enableMetadata } = useMetadataStatus();
|
||||
const [record, setRecord] = useState(null);
|
||||
const { enableMetadataManagement, enableMetadata } = useMetadataStatus();
|
||||
const { record } = useMetadataDetails();
|
||||
|
||||
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
|
||||
const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []);
|
||||
@@ -77,10 +77,6 @@ const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail,
|
||||
onFileTagChanged(dirent, direntPath);
|
||||
}, [dirent, direntPath, onFileTagChanged]);
|
||||
|
||||
const updateRecord = useCallback((record) => {
|
||||
setRecord(record);
|
||||
}, []);
|
||||
|
||||
const dom = (
|
||||
<>
|
||||
<DetailItem field={sizeField} className="sf-metadata-property-detail-formatter">
|
||||
@@ -116,8 +112,8 @@ const FileDetails = React.memo(({ repoID, repoInfo, dirent, path, direntDetail,
|
||||
</div>
|
||||
</DetailItem>
|
||||
)}
|
||||
{window.app.pageOptions.enableMetadataManagement && enableMetadata && (
|
||||
<MetadataDetails repoID={repoID} filePath={direntPath} repoInfo={repoInfo} direntType="file" updateRecord={updateRecord} />
|
||||
{enableMetadataManagement && enableMetadata && (
|
||||
<MetadataDetails />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
@@ -9,6 +9,9 @@ import { Detail, Header, Body } from '../detail';
|
||||
import DirDetails from './dir-details';
|
||||
import FileDetails from './file-details';
|
||||
import ObjectUtils from '../../../metadata/utils/object-utils';
|
||||
import { MetadataDetailsProvider } from '../../../metadata/hooks';
|
||||
import Settings from '../../../metadata/components/metadata-details/settings';
|
||||
import { getDirentPath } from './utils';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@@ -95,38 +98,59 @@ class DirentDetails extends React.Component {
|
||||
|
||||
render() {
|
||||
const { dirent, direntDetail } = this.state;
|
||||
const { repoID, path, fileTags } = this.props;
|
||||
const { repoID, fileTags } = this.props;
|
||||
|
||||
if (!dirent || !direntDetail) {
|
||||
return (
|
||||
<Detail>
|
||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={this.props.onClose} />
|
||||
<Body>
|
||||
{this.renderImage()}
|
||||
</Body>
|
||||
</Detail>
|
||||
);
|
||||
}
|
||||
|
||||
let path = this.props.path;
|
||||
if (dirent?.type !== 'file') {
|
||||
path = this.props.dirent ? Utils.joinPath(path, dirent.name) : path;
|
||||
}
|
||||
|
||||
return (
|
||||
<Detail>
|
||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={this.props.onClose} />
|
||||
<Body>
|
||||
{this.renderImage()}
|
||||
{dirent && direntDetail && (
|
||||
<div className="detail-content">
|
||||
{dirent.type !== 'file' ?
|
||||
<DirDetails
|
||||
repoID={repoID}
|
||||
repoInfo={this.props.currentRepoInfo}
|
||||
dirent={dirent}
|
||||
direntDetail={direntDetail}
|
||||
path={this.props.dirent ? Utils.joinPath(path, dirent.name) : path}
|
||||
/>
|
||||
:
|
||||
<FileDetails
|
||||
repoID={repoID}
|
||||
repoInfo={this.props.currentRepoInfo}
|
||||
dirent={dirent}
|
||||
path={path}
|
||||
direntDetail={direntDetail}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTagList={dirent ? dirent.file_tags : fileTags}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</Body>
|
||||
</Detail>
|
||||
<MetadataDetailsProvider
|
||||
repoID={repoID}
|
||||
repoInfo={this.props.currentRepoInfo}
|
||||
path={getDirentPath(dirent, path)}
|
||||
dirent={dirent}
|
||||
direntDetail={direntDetail}
|
||||
direntType={dirent?.type !== 'file' ? 'dir' : 'file'}
|
||||
>
|
||||
<Detail>
|
||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={this.props.onClose} >
|
||||
<Settings />
|
||||
</Header>
|
||||
<Body>
|
||||
{this.renderImage()}
|
||||
{dirent && direntDetail && (
|
||||
<div className="detail-content">
|
||||
{dirent.type !== 'file' ? (
|
||||
<DirDetails direntDetail={direntDetail} />
|
||||
) : (
|
||||
<FileDetails
|
||||
repoID={repoID}
|
||||
dirent={dirent}
|
||||
path={path}
|
||||
direntDetail={direntDetail}
|
||||
repoTags={this.props.repoTags}
|
||||
fileTagList={dirent ? dirent.file_tags : fileTags}
|
||||
onFileTagChanged={this.props.onFileTagChanged}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Body>
|
||||
</Detail>
|
||||
</MetadataDetailsProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { Utils } from '../../../utils/utils';
|
||||
|
||||
export const getDirentPath = (dirent, path) => {
|
||||
if (Utils.isMarkdownFile(path)) return path; // column mode: view file
|
||||
return Utils.joinPath(path, dirent.name);
|
||||
return Utils.joinPath(path, dirent?.name);
|
||||
};
|
||||
|
||||
export const getFileParent = (dirent, path) => {
|
||||
|
@@ -8,7 +8,7 @@ import { Utils } from '../../../utils/utils';
|
||||
import { MetadataDetails } from '../../../metadata';
|
||||
import { useMetadataStatus } from '../../../hooks';
|
||||
|
||||
const FileDetails = ({ repoID, repoInfo, path, direntDetail }) => {
|
||||
const FileDetails = ({ direntDetail }) => {
|
||||
const { enableMetadata } = useMetadataStatus();
|
||||
|
||||
const sizeField = useMemo(() => ({ type: 'size', name: gettext('Size') }), []);
|
||||
@@ -36,16 +36,13 @@ const FileDetails = ({ repoID, repoInfo, path, direntDetail }) => {
|
||||
<Formatter field={lastModifiedTimeField} value={direntDetail.last_modified}/>
|
||||
</DetailItem>
|
||||
{enableMetadata && (
|
||||
<MetadataDetails repoID={repoID} filePath={path} repoInfo={repoInfo} direntType="file" />
|
||||
<MetadataDetails />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
FileDetails.propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
repoInfo: PropTypes.object,
|
||||
path: PropTypes.string,
|
||||
direntDetail: PropTypes.object,
|
||||
};
|
||||
|
||||
|
@@ -7,6 +7,8 @@ import toaster from '../../toast';
|
||||
import { Header, Body } from '../detail';
|
||||
import FileDetails from './file-details';
|
||||
import { MetadataContext } from '../../../metadata';
|
||||
import { MetadataDetailsProvider } from '../../../metadata/hooks';
|
||||
import Settings from '../../../metadata/components/metadata-details/settings';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@@ -36,27 +38,33 @@ const EmbeddedFileDetails = ({ repoID, repoInfo, dirent, path, onClose, width =
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classnames('cur-view-detail', className, {
|
||||
'cur-view-detail-small': width < 400,
|
||||
'cur-view-detail-large': width > 400
|
||||
})}
|
||||
style={{ width }}
|
||||
<MetadataDetailsProvider
|
||||
repoID={repoID}
|
||||
repoInfo={repoInfo}
|
||||
path={path}
|
||||
dirent={dirent}
|
||||
direntDetail={direntDetail}
|
||||
direntType="file"
|
||||
>
|
||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={onClose} component={headerComponent} />
|
||||
<Body>
|
||||
{dirent && direntDetail && (
|
||||
<div className="detail-content">
|
||||
<FileDetails
|
||||
repoID={repoID}
|
||||
repoInfo={repoInfo}
|
||||
path={path}
|
||||
direntDetail={direntDetail}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Body>
|
||||
</div>
|
||||
<div
|
||||
className={classnames('cur-view-detail', className, {
|
||||
'cur-view-detail-small': width < 400,
|
||||
'cur-view-detail-large': width > 400
|
||||
})}
|
||||
style={{ width }}
|
||||
>
|
||||
<Header title={dirent?.name || ''} icon={Utils.getDirentIcon(dirent, true)} onClose={onClose} component={headerComponent} >
|
||||
<Settings />
|
||||
</Header>
|
||||
<Body>
|
||||
{dirent && direntDetail && (
|
||||
<div className="detail-content">
|
||||
<FileDetails direntDetail={direntDetail} />
|
||||
</div>
|
||||
)}
|
||||
</Body>
|
||||
</div>
|
||||
</MetadataDetailsProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
@@ -31,13 +31,9 @@ const Detail = React.memo(({ repoID, path, dirent, currentRepoInfo, repoTags, fi
|
||||
}
|
||||
|
||||
if (path === '/' && !dirent) {
|
||||
return (
|
||||
<LibDetail
|
||||
currentRepoInfo={currentRepoInfo}
|
||||
onClose={onClose}
|
||||
/>
|
||||
);
|
||||
return (<LibDetail currentRepoInfo={currentRepoInfo} onClose={onClose} />);
|
||||
}
|
||||
|
||||
return (
|
||||
<DirentDetail
|
||||
repoID={repoID}
|
||||
|
@@ -31,7 +31,7 @@ const propTypes = {
|
||||
|
||||
const { isStarred, isLocked, lockedByMe,
|
||||
repoID, filePath, filePerm, enableWatermark, userNickName,
|
||||
fileName, repoEncrypted
|
||||
fileName, repoEncrypted, isRepoAdmin
|
||||
} = window.app.pageOptions;
|
||||
|
||||
class FileView extends React.Component {
|
||||
@@ -116,7 +116,8 @@ class FileView extends React.Component {
|
||||
const { isDetailsPanelOpen, isHeaderShown } = this.state;
|
||||
const repoInfo = {
|
||||
permission: filePerm,
|
||||
encrypted: repoEncrypted
|
||||
encrypted: repoEncrypted,
|
||||
is_admin: isRepoAdmin,
|
||||
};
|
||||
return (
|
||||
<I18nextProvider i18n={ i18n }>
|
||||
|
@@ -38,3 +38,8 @@ export const MODE_TYPE_MAP = {
|
||||
RECENTLY_USED: 'recently_used',
|
||||
SEARCH_RESULTS: 'search_results',
|
||||
};
|
||||
|
||||
export const SYSTEM_FOLDERS = [
|
||||
'/_Internal',
|
||||
'/images'
|
||||
];
|
||||
|
@@ -17,6 +17,7 @@ export const MetadataStatusProvider = ({ repoID, currentRepoInfo, hideMetadataVi
|
||||
const [enableMetadata, setEnableMetadata] = useState(false);
|
||||
const [enableTags, setEnableTags] = useState(false);
|
||||
const [tagsLang, setTagsLang] = useState('en');
|
||||
const [detailsSettings, setDetailsSettings] = useState({});
|
||||
const [isBeingBuilt, setIsBeingBuilt] = useState(false);
|
||||
|
||||
const cancelMetadataURL = useCallback(() => {
|
||||
@@ -37,12 +38,13 @@ export const MetadataStatusProvider = ({ repoID, currentRepoInfo, hideMetadataVi
|
||||
return;
|
||||
}
|
||||
metadataAPI.getMetadataStatus(repoID).then(res => {
|
||||
const { enabled: enableMetadata, tags_enabled: enableTags, tags_lang: tagsLang } = res.data;
|
||||
const { enabled: enableMetadata, tags_enabled: enableTags, tags_lang: tagsLang, details_settings: detailsSettings } = res.data;
|
||||
if (!enableMetadata) {
|
||||
cancelMetadataURL();
|
||||
}
|
||||
setEnableTags(enableTags);
|
||||
setTagsLang(tagsLang || 'en');
|
||||
setDetailsSettings(JSON.parse(detailsSettings));
|
||||
setEnableMetadata(enableMetadata);
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
@@ -60,6 +62,7 @@ export const MetadataStatusProvider = ({ repoID, currentRepoInfo, hideMetadataVi
|
||||
cancelMetadataURL();
|
||||
setEnableTags(false);
|
||||
}
|
||||
setDetailsSettings({});
|
||||
setIsBeingBuilt(newValue);
|
||||
setEnableMetadata(newValue);
|
||||
}, [enableMetadata, cancelMetadataURL]);
|
||||
@@ -74,6 +77,16 @@ export const MetadataStatusProvider = ({ repoID, currentRepoInfo, hideMetadataVi
|
||||
setTagsLang(lang);
|
||||
}, [enableTags, tagsLang, cancelMetadataURL, hideMetadataView]);
|
||||
|
||||
const modifyDetailsSettings = useCallback((update) => {
|
||||
metadataAPI.modifyMetadataDetailsSettings(repoID, update).then(res => {
|
||||
const newDetailsSettings = { ...detailsSettings, ...update };
|
||||
setDetailsSettings(newDetailsSettings);
|
||||
}).catch(error => {
|
||||
const newDetailsSettings = { ...detailsSettings, ...update };
|
||||
setDetailsSettings(newDetailsSettings);
|
||||
});
|
||||
}, [repoID, detailsSettings]);
|
||||
|
||||
return (
|
||||
<EnableMetadataContext.Provider
|
||||
value={{
|
||||
@@ -85,6 +98,8 @@ export const MetadataStatusProvider = ({ repoID, currentRepoInfo, hideMetadataVi
|
||||
enableTags,
|
||||
tagsLang,
|
||||
updateEnableTags,
|
||||
detailsSettings,
|
||||
modifyDetailsSettings,
|
||||
}}
|
||||
>
|
||||
{!isLoading && children}
|
||||
|
@@ -65,6 +65,12 @@ class MetadataManagerAPI {
|
||||
return this.req.delete(url);
|
||||
}
|
||||
|
||||
modifyMetadataDetailsSettings(repoID, settings) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/details-settings/';
|
||||
const data = { settings: settings };
|
||||
return this.req.put(url, data);
|
||||
}
|
||||
|
||||
getMetadata(repoID, params) {
|
||||
const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/records/';
|
||||
return this.req.get(url, { params: params });
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { PRIVATE_COLUMN_KEY } from '../../constants';
|
||||
import { PRIVATE_COLUMN_KEY, CellType } from '../../constants';
|
||||
|
||||
export const NOT_DISPLAY_COLUMN_KEYS = [
|
||||
PRIVATE_COLUMN_KEY.ID,
|
||||
@@ -23,11 +23,7 @@ export const NOT_DISPLAY_COLUMN_KEYS = [
|
||||
PRIVATE_COLUMN_KEY.FACE_VECTORS,
|
||||
];
|
||||
|
||||
export const SYSTEM_FOLDERS = [
|
||||
'/_Internal',
|
||||
'/images'
|
||||
];
|
||||
|
||||
export {
|
||||
PRIVATE_COLUMN_KEY,
|
||||
CellType,
|
||||
};
|
||||
|
@@ -1,159 +1,45 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import toaster from '../../../components/toast';
|
||||
import React, { useMemo } from 'react';
|
||||
import CellFormatter from '../cell-formatter';
|
||||
import DetailEditor from '../detail-editor';
|
||||
import DetailItem from '../../../components/dirent-detail/detail-item';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import metadataAPI from '../../api';
|
||||
import Column from '../../model/column';
|
||||
import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColumnOptionNameById, getFileNameFromRecord, getRecordIdFromRecord, getFileObjIdFromRecord } from '../../utils/cell';
|
||||
import { normalizeFields } from './utils';
|
||||
import { getCellValueByColumn, getFileNameFromRecord } from '../../utils/cell';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { CellType, EVENT_BUS_TYPE, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../../constants';
|
||||
import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../../utils/column';
|
||||
import { SYSTEM_FOLDERS } from './constants';
|
||||
import { PRIVATE_COLUMN_KEY } from '../../constants';
|
||||
import Location from './location';
|
||||
import { useMetadataDetails } from '../../hooks';
|
||||
import { checkIsDir } from '../../utils/row';
|
||||
import tagsAPI from '../../../tag/api';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord }) => {
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [metadata, setMetadata] = useState({ record: {}, fields: [] });
|
||||
const permission = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw', [repoInfo]);
|
||||
const MetadataDetails = () => {
|
||||
const { isLoading, canModifyRecord, record, columns, onChange, modifyColumnData, updateFileTags } = useMetadataDetails();
|
||||
|
||||
const onChange = useCallback((fieldKey, newValue) => {
|
||||
const { record, fields } = metadata;
|
||||
const field = getColumnByKey(fields, fieldKey);
|
||||
const fileName = getColumnOriginName(field);
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
const fileObjId = getFileObjIdFromRecord(record);
|
||||
let update = { [fileName]: newValue };
|
||||
if (field.type === CellType.SINGLE_SELECT) {
|
||||
update = { [fileName]: getColumnOptionNameById(field, newValue) };
|
||||
} else if (field.type === CellType.MULTIPLE_SELECT) {
|
||||
update = { [fileName]: newValue ? getColumnOptionNamesByIds(field, newValue) : [] };
|
||||
}
|
||||
metadataAPI.modifyRecord(repoID, recordId, update, fileObjId).then(res => {
|
||||
const newMetadata = { ...metadata, record: { ...record, ...update } };
|
||||
setMetadata(newMetadata);
|
||||
if (window?.sfMetadataContext?.eventBus) {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, recordId, update);
|
||||
}
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
});
|
||||
}, [repoID, metadata]);
|
||||
|
||||
const modifyColumnData = useCallback((fieldKey, newData) => {
|
||||
const { fields, record } = metadata;
|
||||
let newFields = fields.slice(0);
|
||||
let update;
|
||||
metadataAPI.modifyColumnData(repoID, fieldKey, newData).then(res => {
|
||||
const newField = new Column(res.data.column);
|
||||
const fieldIndex = fields.findIndex(f => f.key === fieldKey);
|
||||
newFields[fieldIndex] = newField;
|
||||
return newField;
|
||||
}).then((newField) => {
|
||||
const fileName = getColumnOriginName(newField);
|
||||
const options = getColumnOptions(newField);
|
||||
const newOption = options[options.length - 1];
|
||||
update = { [fileName]: newOption.id };
|
||||
if (!PREDEFINED_COLUMN_KEYS.includes(fieldKey) && newField.type === CellType.SINGLE_SELECT) {
|
||||
update = { [fileName]: getOptionName(options, newOption.id) };
|
||||
} else if (newField.type === CellType.MULTIPLE_SELECT) {
|
||||
const oldValue = getCellValueByColumn(record, newField) || [];
|
||||
update = { [fileName]: [...oldValue, newOption.name] };
|
||||
}
|
||||
return metadataAPI.modifyRecord(repoID, record._id, update, record._obj_id);
|
||||
}).then(res => {
|
||||
const newMetadata = { ...metadata, record: { ...record, ...update }, fields: newFields };
|
||||
setMetadata(newMetadata);
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
});
|
||||
}, [repoID, metadata]);
|
||||
|
||||
const localRecordChanged = useCallback((recordId, updates) => {
|
||||
if (getRecordIdFromRecord(metadata?.record) !== recordId) return;
|
||||
const newMetadata = { ...metadata, record: { ...metadata.record, ...updates } };
|
||||
setMetadata(newMetadata);
|
||||
}, [metadata]);
|
||||
|
||||
const updateFileTags = useCallback((updateRecords) => {
|
||||
const { record } = metadata;
|
||||
const { record_id, tags } = updateRecords[0];
|
||||
|
||||
tagsAPI.updateFileTags(repoID, [{ record_id, tags }]).then(res => {
|
||||
const newValue = tags ? tags.map(id => ({ row_id: id, display_value: id })) : [];
|
||||
const update = { [PRIVATE_COLUMN_KEY.TAGS]: newValue };
|
||||
const newMetadata = { ...metadata, record: { ...record, ...update } };
|
||||
setMetadata(newMetadata);
|
||||
if (window?.sfMetadataContext?.eventBus) {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, record_id, update);
|
||||
}
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
});
|
||||
}, [repoID, metadata]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
if (SYSTEM_FOLDERS.find(folderPath => filePath.startsWith(folderPath))) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const dirName = Utils.getDirName(filePath);
|
||||
const fileName = Utils.getFileName(filePath);
|
||||
let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1);
|
||||
if (!parentDir.startsWith('/')) {
|
||||
parentDir = '/' + parentDir;
|
||||
}
|
||||
metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => {
|
||||
const { results, metadata } = res.data;
|
||||
const record = Array.isArray(results) && results.length > 0 ? results[0] : {};
|
||||
let fields = normalizeFields(metadata).map(field => new Column(field));
|
||||
const isDir = checkIsDir(record);
|
||||
if (isDir) {
|
||||
fields = fields.filter(field => field.type !== CellType.TAGS);
|
||||
}
|
||||
updateRecord && updateRecord(record);
|
||||
setMetadata({ record, fields });
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
const errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [repoID, filePath, direntType, updateRecord]);
|
||||
|
||||
useEffect(() => {
|
||||
const eventBus = window?.sfMetadataContext?.eventBus;
|
||||
if (!eventBus) return;
|
||||
const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, localRecordChanged);
|
||||
return () => {
|
||||
unsubscribeLocalRecordChanged();
|
||||
};
|
||||
}, [localRecordChanged]);
|
||||
const displayColumns = useMemo(() => columns.filter(c => c.shown), [columns]);
|
||||
|
||||
if (isLoading) return null;
|
||||
const { fields, record } = metadata;
|
||||
if (!record) return null;
|
||||
if (!record._id) return null;
|
||||
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
const isImage = record && (Utils.imageCheck(fileName) || Utils.videoCheck(fileName));
|
||||
const isDir = record && checkIsDir(record);
|
||||
|
||||
return (
|
||||
<>
|
||||
{fields.map(field => {
|
||||
let canEdit = permission === 'rw' && field.editable;
|
||||
{displayColumns.map(field => {
|
||||
if (field.key === PRIVATE_COLUMN_KEY.LOCATION) {
|
||||
if (!isImage) return null;
|
||||
return (<Location key={field.key} position={getCellValueByColumn(record, field)} />);
|
||||
}
|
||||
|
||||
if (field.key === PRIVATE_COLUMN_KEY.TAGS && isDir) return null;
|
||||
|
||||
let canEdit = canModifyRecord && field.editable;
|
||||
if (!isImage && canEdit && field.key === PRIVATE_COLUMN_KEY.CAPTURE_TIME) {
|
||||
canEdit = false;
|
||||
}
|
||||
|
||||
const value = getCellValueByColumn(record, field);
|
||||
return (
|
||||
<DetailItem key={field.key} field={field} readonly={!canEdit}>
|
||||
@@ -161,7 +47,7 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
|
||||
<DetailEditor
|
||||
field={field}
|
||||
value={value}
|
||||
fields={fields}
|
||||
fields={columns}
|
||||
record={record}
|
||||
modifyColumnData={modifyColumnData}
|
||||
onChange={onChange}
|
||||
@@ -173,18 +59,8 @@ const MetadataDetails = ({ repoID, filePath, repoInfo, direntType, updateRecord
|
||||
</DetailItem>
|
||||
);
|
||||
})}
|
||||
{isImage && (<Location position={getCellValueByColumn(record, { key: PRIVATE_COLUMN_KEY.LOCATION })} />)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MetadataDetails.propTypes = {
|
||||
repoID: PropTypes.string,
|
||||
filePath: PropTypes.string,
|
||||
repoInfo: PropTypes.object,
|
||||
direntType: PropTypes.string,
|
||||
direntDetail: PropTypes.object,
|
||||
updateRecord: PropTypes.func,
|
||||
};
|
||||
|
||||
export default MetadataDetails;
|
||||
|
@@ -175,7 +175,7 @@ class Location extends React.Component {
|
||||
)}
|
||||
</DetailItem>
|
||||
{isLoading ? (<Loading />) : this.mapType && (
|
||||
<div className={classnames('dirent-detail-item-value-map', { 'd-none': !isValid })}>
|
||||
<div className={classnames('dirent-detail-item dirent-detail-item-value-map', { 'd-none': !isValid })}>
|
||||
<div className="w-100 h-100" ref={ref => this.ref = ref} id="sf-geolocation-map-container"></div>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -0,0 +1,46 @@
|
||||
import React, { useMemo, useCallback, useState } from 'react';
|
||||
import Icon from '../../../../components/icon';
|
||||
import HideColumnPopover from '../../popover/hidden-column-popover';
|
||||
import { useMetadataDetails } from '../../../hooks';
|
||||
import { useMetadataStatus } from '../../../../hooks';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const Settings = () => {
|
||||
const [isShowSetter, setShowSetter] = useState(false);
|
||||
|
||||
const { enableMetadata } = useMetadataStatus();
|
||||
const { modifyColumnOrder, modifyHiddenColumns, columns, canModifyDetails } = useMetadataDetails();
|
||||
const hiddenColumns = useMemo(() => columns.filter(c => !c.shown).map(c => c.key), [columns]);
|
||||
|
||||
const onSetterToggle = useCallback(() => {
|
||||
setShowSetter(!isShowSetter);
|
||||
}, [isShowSetter]);
|
||||
const target = useMemo(() => 'detail-control-settings-btn', []);
|
||||
|
||||
if (!enableMetadata) return null;
|
||||
if (!canModifyDetails) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="detail-control mr-2" id={target} onClick={onSetterToggle}>
|
||||
<Icon symbol="set-up" className="detail-control-close" />
|
||||
</div>
|
||||
{isShowSetter && (
|
||||
<HideColumnPopover
|
||||
readOnly={false}
|
||||
hiddenColumns={hiddenColumns}
|
||||
target={target}
|
||||
placement="bottom-end"
|
||||
columns={columns}
|
||||
hidePopover={onSetterToggle}
|
||||
onChange={modifyHiddenColumns}
|
||||
modifyColumnOrder={modifyColumnOrder}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default Settings;
|
@@ -1,10 +1,10 @@
|
||||
import { getNormalizedColumnType } from '../../utils/column';
|
||||
import { getCellValueByColumn } from '../../utils/cell';
|
||||
import { NOT_DISPLAY_COLUMN_KEYS } from './constants';
|
||||
import { NOT_DISPLAY_COLUMN_KEYS, CellType, PRIVATE_COLUMN_KEY } from './constants';
|
||||
|
||||
export const normalizeFields = (fields) => {
|
||||
if (!Array.isArray(fields) || fields.length === 0) return [];
|
||||
const validFields = fields.map((field) => {
|
||||
let validFields = fields.map((field) => {
|
||||
const { type, key, ...params } = field;
|
||||
return {
|
||||
...params,
|
||||
@@ -13,11 +13,8 @@ export const normalizeFields = (fields) => {
|
||||
width: 200,
|
||||
};
|
||||
}).filter(field => !NOT_DISPLAY_COLUMN_KEYS.includes(field.key));
|
||||
let displayFields = [];
|
||||
validFields.forEach(field => {
|
||||
displayFields.push(field);
|
||||
});
|
||||
return displayFields;
|
||||
validFields.push({ key: PRIVATE_COLUMN_KEY.LOCATION, type: CellType.GEOLOCATION, width: 200 });
|
||||
return validFields;
|
||||
};
|
||||
|
||||
export {
|
||||
|
@@ -1,110 +1,105 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classNames from 'classnames';
|
||||
import { DragSource, DropTarget } from 'react-dnd';
|
||||
import { Icon, Switch } from '@seafile/sf-metadata-ui-component';
|
||||
import { COLUMNS_ICON_CONFIG } from '../../../../constants';
|
||||
|
||||
const dragSource = {
|
||||
beginDrag: props => {
|
||||
return { key: props.column.key, column: props.column };
|
||||
},
|
||||
endDrag(props, monitor) {
|
||||
const source = monitor.getItem();
|
||||
const didDrop = monitor.didDrop();
|
||||
let target = {};
|
||||
if (!didDrop) {
|
||||
return { source, target };
|
||||
}
|
||||
},
|
||||
isDragging(props) {
|
||||
const { columnIndex, currentIndex } = props;
|
||||
return currentIndex > columnIndex;
|
||||
}
|
||||
};
|
||||
const dropTarget = {
|
||||
drop(props, monitor) {
|
||||
const source = monitor.getItem();
|
||||
const { column: targetColumn } = props;
|
||||
if (targetColumn.key !== source.key && source.column.frozen === targetColumn.frozen) {
|
||||
const target = { key: targetColumn.key };
|
||||
props.onMove(source.key, target.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const dragCollect = (connect, monitor) => ({
|
||||
connectDragSource: connect.dragSource(),
|
||||
connectDragPreview: connect.dragPreview(),
|
||||
isDragging: monitor.isDragging(),
|
||||
});
|
||||
|
||||
const dropCollect = (connect, monitor) => ({
|
||||
connectDropTarget: connect.dropTarget(),
|
||||
isOver: monitor.isOver(),
|
||||
canDrop: monitor.canDrop(),
|
||||
dragged: monitor.getItem(),
|
||||
});
|
||||
|
||||
const HideColumnItem = ({
|
||||
isOver,
|
||||
isDragging,
|
||||
canDrop,
|
||||
connectDragSource,
|
||||
connectDragPreview,
|
||||
connectDropTarget,
|
||||
readOnly,
|
||||
isHidden,
|
||||
column,
|
||||
columnIndex,
|
||||
isHidden,
|
||||
draggingColumnKey,
|
||||
draggingColumnIndex,
|
||||
dragOverColumnKey,
|
||||
updateDraggingKey,
|
||||
updateDragOverKey,
|
||||
onChange,
|
||||
onMouseEnter,
|
||||
onMouseLeave,
|
||||
onMove,
|
||||
}) => {
|
||||
const ref = useRef(null);
|
||||
|
||||
const onDragStart = useCallback((event) => {
|
||||
const dragData = JSON.stringify({ type: 'sf-metadata-filed-display-setting', column_key: column.key });
|
||||
event.dataTransfer.setDragImage(ref.current, 10, 10);
|
||||
event.dataTransfer.effectAllowed = 'move';
|
||||
event.dataTransfer.setData('application/drag-sf-metadata-filed-display-setting', dragData);
|
||||
updateDraggingKey(column.key);
|
||||
}, [column, updateDraggingKey]);
|
||||
|
||||
const onDragEnter = useCallback(() => {
|
||||
if (!draggingColumnKey) return;
|
||||
updateDragOverKey(column.key);
|
||||
}, [column, updateDragOverKey, draggingColumnKey]);
|
||||
|
||||
const onDragLeave = useCallback(() => {
|
||||
if (!draggingColumnKey) return;
|
||||
updateDragOverKey(null);
|
||||
}, [updateDragOverKey, draggingColumnKey]);
|
||||
|
||||
const onDragOver = useCallback((event) => {
|
||||
if (!draggingColumnKey) return;
|
||||
event.preventDefault();
|
||||
event.dataTransfer.dropEffect = 'move';
|
||||
updateDragOverKey(column.key);
|
||||
}, [column, updateDragOverKey, draggingColumnKey]);
|
||||
|
||||
const onDrop = useCallback((event) => {
|
||||
event.stopPropagation();
|
||||
let dragData = event.dataTransfer.getData('application/drag-sf-metadata-filed-display-setting');
|
||||
if (!dragData) return false;
|
||||
dragData = JSON.parse(dragData);
|
||||
if (dragData.type !== 'sf-metadata-filed-display-setting' || !dragData.column_key) return false;
|
||||
if (dragData.column_key !== column.key) {
|
||||
onMove && onMove(dragData.column_key, column.key);
|
||||
}
|
||||
}, [column, onMove]);
|
||||
|
||||
const onDragEnd = useCallback(() => {
|
||||
updateDraggingKey(null);
|
||||
updateDragOverKey(null);
|
||||
}, [updateDraggingKey, updateDragOverKey]);
|
||||
|
||||
const update = useCallback(() => {
|
||||
if (readOnly) return;
|
||||
onChange(column.key);
|
||||
}, [readOnly, column, onChange]);
|
||||
|
||||
const isOver = dragOverColumnKey === column.key;
|
||||
|
||||
return (
|
||||
<>
|
||||
{connectDropTarget(
|
||||
connectDragPreview(
|
||||
<div
|
||||
className={classNames('hide-column-item', {
|
||||
'disabled': readOnly,
|
||||
'hide-column-can-drop-top': isOver && canDrop && isDragging,
|
||||
'hide-column-can-drop': isOver && canDrop && !isDragging
|
||||
})}
|
||||
onMouseEnter={() => onMouseEnter(columnIndex)}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
{!readOnly && (
|
||||
<>
|
||||
{connectDragSource(
|
||||
<div className="drag-hide-column-handle">
|
||||
<Icon iconName="drag" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<Switch
|
||||
disabled={readOnly}
|
||||
checked={isHidden}
|
||||
placeholder={(
|
||||
<>
|
||||
<Icon iconName={COLUMNS_ICON_CONFIG[column.type]} />
|
||||
<span className="text-truncate">{column.name}</span>
|
||||
</>
|
||||
)}
|
||||
onChange={update}
|
||||
switchClassName="hide-column-item-switch"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
<div
|
||||
ref={ref}
|
||||
className={classNames('hide-column-item', {
|
||||
'disabled': readOnly,
|
||||
'hide-column-can-drop-top': isOver && draggingColumnIndex >= columnIndex,
|
||||
'hide-column-can-drop': isOver && draggingColumnIndex < columnIndex,
|
||||
'dragging': draggingColumnKey === column.key
|
||||
})}
|
||||
onDrop={onDrop}
|
||||
onDragEnter={onDragEnter}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
onDragEnd={onDragEnd}
|
||||
>
|
||||
{!readOnly && (
|
||||
<div className="drag-hide-column-handle" draggable="true" onDragStart={onDragStart}>
|
||||
<Icon iconName="drag" />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
<Switch
|
||||
disabled={readOnly}
|
||||
checked={isHidden}
|
||||
placeholder={(
|
||||
<>
|
||||
<Icon iconName={COLUMNS_ICON_CONFIG[column.type]} />
|
||||
<span className="text-truncate">{column.name}</span>
|
||||
</>
|
||||
)}
|
||||
onChange={update}
|
||||
switchClassName="hide-column-item-switch"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -112,14 +107,14 @@ HideColumnItem.propTypes = {
|
||||
readOnly: PropTypes.bool,
|
||||
isHidden: PropTypes.bool,
|
||||
columnIndex: PropTypes.number,
|
||||
currentIndex: PropTypes.number,
|
||||
column: PropTypes.object.isRequired,
|
||||
draggingColumnKey: PropTypes.string,
|
||||
draggingColumnIndex: PropTypes.number,
|
||||
dragOverColumnKey: PropTypes.string,
|
||||
updateDraggingKey: PropTypes.func,
|
||||
updateDragOverKey: PropTypes.func,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
onMove: PropTypes.func,
|
||||
onMouseEnter: PropTypes.func,
|
||||
onMouseLeave: PropTypes.func,
|
||||
};
|
||||
|
||||
export default DropTarget('sfMetadataHiddenColumns', dropTarget, dropCollect)(
|
||||
DragSource('sfMetadataHiddenColumns', dragSource, dragCollect)(HideColumnItem)
|
||||
);
|
||||
export default HideColumnItem;
|
||||
|
@@ -1,27 +1,29 @@
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { DropTarget } from 'react-dnd';
|
||||
import HideColumn from './hide-column';
|
||||
import html5DragDropContext from '../../../../../pages/wiki2/wiki-nav/html5DragDropContext';
|
||||
import { gettext } from '../../../../../utils/constants';
|
||||
|
||||
const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, modifyColumnOrder }) => {
|
||||
const [currentIndex, setCurrentIndex] = useState(-1);
|
||||
const [draggingColumnKey, setDraggingCellKey] = useState(null);
|
||||
const [dragOverColumnKey, setDragOverCellKey] = useState(null);
|
||||
|
||||
const isEmpty = useMemo(() => {
|
||||
if (!Array.isArray(columns) || columns.length === 0) return true;
|
||||
return false;
|
||||
}, [columns]);
|
||||
|
||||
const onMouseEnter = useCallback((columnIndex) => {
|
||||
if (currentIndex === columnIndex) return;
|
||||
setCurrentIndex(columnIndex);
|
||||
}, [currentIndex]);
|
||||
const updateDraggingKey = useCallback((cellKey) => {
|
||||
if (cellKey === draggingColumnKey) return;
|
||||
setDraggingCellKey(cellKey);
|
||||
}, [draggingColumnKey]);
|
||||
|
||||
const onMouseLeave = useCallback(() => {
|
||||
setCurrentIndex(-1);
|
||||
}, []);
|
||||
const updateDragOverKey = useCallback((cellKey) => {
|
||||
if (cellKey === dragOverColumnKey) return;
|
||||
setDragOverCellKey(cellKey);
|
||||
}, [dragOverColumnKey]);
|
||||
|
||||
const draggingColumnIndex = draggingColumnKey ? columns.findIndex(c => c.key === draggingColumnKey) : -1;
|
||||
|
||||
return (
|
||||
<div className={classnames('hide-columns-list', { 'empty-hide-columns-container': isEmpty })}>
|
||||
@@ -32,13 +34,15 @@ const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, modifyColum
|
||||
key={column.key}
|
||||
readOnly={readOnly}
|
||||
columnIndex={columnIndex}
|
||||
currentIndex={currentIndex}
|
||||
isHidden={!hiddenColumns.includes(column.key)}
|
||||
column={column}
|
||||
draggingColumnKey={draggingColumnKey}
|
||||
draggingColumnIndex={draggingColumnIndex}
|
||||
dragOverColumnKey={dragOverColumnKey}
|
||||
onChange={onChange}
|
||||
onMove={modifyColumnOrder}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
updateDraggingKey={updateDraggingKey}
|
||||
updateDragOverKey={updateDragOverKey}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
@@ -54,8 +58,4 @@ HiddenColumns.propTypes = {
|
||||
modifyColumnOrder: PropTypes.func,
|
||||
};
|
||||
|
||||
const DndHiddenColumns = DropTarget('sfMetadataHiddenColumns', {}, connect => ({
|
||||
connectDropTarget: connect.dropTarget()
|
||||
}))(HiddenColumns);
|
||||
|
||||
export default html5DragDropContext(DndHiddenColumns);
|
||||
export default HiddenColumns;
|
||||
|
@@ -145,6 +145,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sf-metadata-hide-columns-popover .hide-column-item.dragging {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop::after,
|
||||
.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop-top::before {
|
||||
content: '';
|
||||
|
@@ -67,13 +67,14 @@ const KanbanViewToolBar = ({
|
||||
className="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-setting"
|
||||
size={24}
|
||||
role="button"
|
||||
aria-label="Setting"
|
||||
aria-label={gettext('Settings')}
|
||||
title={gettext('Settings')}
|
||||
tabIndex={0}
|
||||
onClick={onToggleKanbanSetting}
|
||||
/>
|
||||
{!isCustomPermission && (
|
||||
<div className="cur-view-path-btn ml-2" onClick={toggleDetails}>
|
||||
<span className="sf3-font sf3-font-info" aria-label={gettext('Properties')} title={gettext('Properties')}></span>
|
||||
<div className="cur-view-path-btn ml-2" onClick={toggleDetails} aria-label={gettext('Properties')} title={gettext('Properties')}>
|
||||
<span className="sf3-font sf3-font-info"></span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -1,2 +1,3 @@
|
||||
export { MetadataProvider, useMetadata } from './metadata';
|
||||
export { CollaboratorsProvider, useCollaborators } from './collaborators';
|
||||
export { MetadataDetailsProvider, useMetadataDetails } from './metadata-details';
|
||||
|
211
frontend/src/metadata/hooks/metadata-details.js
Normal file
211
frontend/src/metadata/hooks/metadata-details.js
Normal file
@@ -0,0 +1,211 @@
|
||||
import React, { useContext, useEffect, useCallback, useState, useMemo, useRef } from 'react';
|
||||
import metadataAPI from '../api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import { useMetadataStatus } from '../../hooks/metadata-status';
|
||||
import { SYSTEM_FOLDERS } from '../../constants';
|
||||
import Column from '../model/column';
|
||||
import { normalizeFields } from '../components/metadata-details/utils';
|
||||
import { CellType, EVENT_BUS_TYPE, PREDEFINED_COLUMN_KEYS, PRIVATE_COLUMN_KEY } from '../constants';
|
||||
import { getCellValueByColumn, getOptionName, getColumnOptionNamesByIds, getColumnOptionNameById, getRecordIdFromRecord,
|
||||
getFileObjIdFromRecord
|
||||
} from '../utils/cell';
|
||||
import tagsAPI from '../../tag/api';
|
||||
import { getColumnByKey, getColumnOptions, getColumnOriginName } from '../utils/column';
|
||||
|
||||
const MetadataDetailsContext = React.createContext(null);
|
||||
|
||||
export const MetadataDetailsProvider = ({ repoID, repoInfo, path, dirent, direntDetail, direntType, children }) => {
|
||||
const { enableMetadata, detailsSettings, modifyDetailsSettings } = useMetadataStatus();
|
||||
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [record, setRecord] = useState(null);
|
||||
const [originColumns, setOriginColumns] = useState([]);
|
||||
|
||||
const canModifyRecord = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? false : true, [repoInfo]);
|
||||
const canModifyDetails = useMemo(() => repoInfo.is_admin, [repoInfo]);
|
||||
|
||||
const allColumnsRef = useRef([]);
|
||||
|
||||
const columns = useMemo(() => {
|
||||
const orderAndHiddenColumns = detailsSettings?.columns || [];
|
||||
if (!Array.isArray(orderAndHiddenColumns) || orderAndHiddenColumns.length === 0) {
|
||||
return originColumns.map(c => ({ ...c, shown: true }));
|
||||
}
|
||||
const oldColumnsMap = orderAndHiddenColumns.reduce((pre, cur) => {
|
||||
pre[cur.key] = true;
|
||||
return pre;
|
||||
}, {});
|
||||
const columnsMap = originColumns.reduce((pre, cur) => {
|
||||
pre[cur.key] = cur;
|
||||
return pre;
|
||||
}, {});
|
||||
const exitColumnsOrder = orderAndHiddenColumns.map(c => {
|
||||
const column = columnsMap[c.key];
|
||||
if (column) return { ...c, ...column };
|
||||
return null;
|
||||
}).filter(c => c);
|
||||
const newColumns = originColumns.filter(c => !oldColumnsMap[c.key]).map(c => ({ ...c, shown: false }));
|
||||
return [...exitColumnsOrder, ...newColumns];
|
||||
}, [originColumns, detailsSettings]);
|
||||
|
||||
const localRecordChanged = useCallback((recordId, updates) => {
|
||||
if (getRecordIdFromRecord(record) !== recordId) return;
|
||||
const newRecord = { ...record, ...updates };
|
||||
setRecord(newRecord);
|
||||
}, [record]);
|
||||
|
||||
const onChange = useCallback((fieldKey, newValue) => {
|
||||
const field = getColumnByKey(originColumns, fieldKey);
|
||||
const fileName = getColumnOriginName(field);
|
||||
const recordId = getRecordIdFromRecord(record);
|
||||
const fileObjId = getFileObjIdFromRecord(record);
|
||||
let update = { [fileName]: newValue };
|
||||
if (field.type === CellType.SINGLE_SELECT) {
|
||||
update = { [fileName]: getColumnOptionNameById(field, newValue) };
|
||||
} else if (field.type === CellType.MULTIPLE_SELECT) {
|
||||
update = { [fileName]: newValue ? getColumnOptionNamesByIds(field, newValue) : [] };
|
||||
}
|
||||
metadataAPI.modifyRecord(repoID, recordId, update, fileObjId).then(res => {
|
||||
setRecord({ ...record, ...update });
|
||||
if (window?.sfMetadataContext?.eventBus) {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, recordId, update);
|
||||
}
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
});
|
||||
}, [repoID, record, originColumns]);
|
||||
|
||||
const modifyColumnData = useCallback((fieldKey, newData) => {
|
||||
let newColumns = originColumns.slice(0);
|
||||
let update;
|
||||
metadataAPI.modifyColumnData(repoID, fieldKey, newData).then(res => {
|
||||
const newColumn = new Column(res.data.column);
|
||||
const fieldIndex = originColumns.findIndex(f => f.key === fieldKey);
|
||||
newColumns[fieldIndex] = newColumn;
|
||||
return newColumn;
|
||||
}).then((newField) => {
|
||||
const fileName = getColumnOriginName(newField);
|
||||
const options = getColumnOptions(newField);
|
||||
const newOption = options[options.length - 1];
|
||||
update = { [fileName]: newOption.id };
|
||||
if (!PREDEFINED_COLUMN_KEYS.includes(fieldKey) && newField.type === CellType.SINGLE_SELECT) {
|
||||
update = { [fileName]: getOptionName(options, newOption.id) };
|
||||
} else if (newField.type === CellType.MULTIPLE_SELECT) {
|
||||
const oldValue = getCellValueByColumn(record, newField) || [];
|
||||
update = { [fileName]: [...oldValue, newOption.name] };
|
||||
}
|
||||
return metadataAPI.modifyRecord(repoID, record._id, update, record._obj_id);
|
||||
}).then(res => {
|
||||
setOriginColumns(newColumns);
|
||||
setRecord({ ...record, ...update });
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
});
|
||||
}, [repoID, record, originColumns]);
|
||||
|
||||
const updateFileTags = useCallback((updateRecords) => {
|
||||
const { record_id, tags } = updateRecords[0];
|
||||
|
||||
tagsAPI.updateFileTags(repoID, [{ record_id, tags }]).then(res => {
|
||||
const newValue = tags ? tags.map(id => ({ row_id: id, display_value: id })) : [];
|
||||
const update = { [PRIVATE_COLUMN_KEY.TAGS]: newValue };
|
||||
setRecord({ ...record, ...update });
|
||||
if (window?.sfMetadataContext?.eventBus) {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, record_id, update);
|
||||
}
|
||||
}).catch(error => {
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
});
|
||||
}, [repoID, record]);
|
||||
|
||||
const saveColumns = useCallback((columns) => {
|
||||
modifyDetailsSettings({ columns: columns.map(c => ({ key: c.key, shown: c.shown })) });
|
||||
}, [modifyDetailsSettings]);
|
||||
|
||||
const modifyHiddenColumns = useCallback((hiddenColumns) => {
|
||||
let newColumns = columns.slice(0);
|
||||
newColumns = newColumns.map(c => ({ ...c, shown: !hiddenColumns.includes(c.key) }));
|
||||
saveColumns(newColumns);
|
||||
}, [columns, saveColumns]);
|
||||
|
||||
const modifyColumnOrder = useCallback((sourceColumnKey, targetColumnKey) => {
|
||||
const targetColumnIndex = columns.findIndex(c => c.key === targetColumnKey);
|
||||
const sourceColumn = columns.find(c => c.key === sourceColumnKey);
|
||||
let newColumns = columns.slice(0);
|
||||
newColumns = newColumns.filter(c => c.key !== sourceColumnKey);
|
||||
newColumns.splice(targetColumnIndex, 0, sourceColumn);
|
||||
saveColumns(newColumns);
|
||||
}, [columns, saveColumns]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
if (!dirent || !direntDetail || !enableMetadata || SYSTEM_FOLDERS.find(folderPath => path.startsWith(folderPath))) {
|
||||
setRecord(null);
|
||||
setOriginColumns([]);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const dirName = Utils.getDirName(path);
|
||||
const fileName = Utils.getFileName(path);
|
||||
let parentDir = direntType === 'file' ? dirName : dirName.slice(0, dirName.length - fileName.length - 1);
|
||||
|
||||
if (!parentDir.startsWith('/')) {
|
||||
parentDir = '/' + parentDir;
|
||||
}
|
||||
metadataAPI.getMetadataRecordInfo(repoID, parentDir, fileName).then(res => {
|
||||
const { results, metadata } = res.data;
|
||||
const record = Array.isArray(results) && results.length > 0 ? results[0] : {};
|
||||
const columns = normalizeFields(metadata).map(field => new Column(field));
|
||||
allColumnsRef.current = columns;
|
||||
setRecord(record);
|
||||
setOriginColumns(columns);
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
const errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
setLoading(false);
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [enableMetadata, repoID, path, direntType, dirent, direntDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
const eventBus = window?.sfMetadataContext?.eventBus;
|
||||
if (!eventBus) return;
|
||||
const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, localRecordChanged);
|
||||
return () => {
|
||||
unsubscribeLocalRecordChanged();
|
||||
};
|
||||
}, [localRecordChanged]);
|
||||
|
||||
return (
|
||||
<MetadataDetailsContext.Provider
|
||||
value={{
|
||||
isLoading,
|
||||
canModifyRecord,
|
||||
canModifyDetails,
|
||||
record,
|
||||
columns,
|
||||
onChange,
|
||||
modifyColumnData,
|
||||
updateFileTags,
|
||||
modifyHiddenColumns,
|
||||
modifyColumnOrder,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MetadataDetailsContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMetadataDetails = () => {
|
||||
const context = useContext(MetadataDetailsContext);
|
||||
if (!context) {
|
||||
throw new Error('\'MetadataDetailsContext\' is null');
|
||||
}
|
||||
return context;
|
||||
};
|
@@ -27,6 +27,7 @@ const updateTableRowsWithRowsData = (tables, tableId, recordsData = []) => {
|
||||
};
|
||||
|
||||
export const checkIsDir = (record) => {
|
||||
if (!record) return false;
|
||||
const isDir = record[PRIVATE_COLUMN_KEY.IS_DIR];
|
||||
if (typeof isDir === 'string') {
|
||||
return isDir.toUpperCase() === 'TRUE';
|
||||
|
@@ -17,7 +17,7 @@ import Dirent from '../../../../src/models/dirent';
|
||||
import '../css/header-toolbar.css';
|
||||
|
||||
const { seafileCollabServer } = window.app.config;
|
||||
const { canDownloadFile, repoID, filePath } = window.app.pageOptions;
|
||||
const { canDownloadFile, repoID, filePath, isRepoAdmin } = window.app.pageOptions;
|
||||
|
||||
const propTypes = {
|
||||
editorApi: PropTypes.object.isRequired,
|
||||
@@ -95,7 +95,7 @@ class HeaderToolbar extends React.Component {
|
||||
};
|
||||
|
||||
onArticleInfoToggle = () => {
|
||||
const repoInfo = { permission: this.currentDirent.permission };
|
||||
const repoInfo = { permission: this.currentDirent.permission, is_admin: isRepoAdmin, };
|
||||
const eventBus = EventBus.getInstance();
|
||||
|
||||
eventBus.dispatch(EXTERNAL_EVENTS.ON_ARTICLE_INFO_TOGGLE, this.isFileInfoShow ? null : {
|
||||
|
@@ -15,6 +15,16 @@
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.sdoc-content-right-panel .detail-header .seafile-multicolor-icon-set-up {
|
||||
fill: #999;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sdoc-content-right-panel .detail-header .detail-control:hover .seafile-multicolor-icon-set-up {
|
||||
fill: #5a5a5a;
|
||||
}
|
||||
|
||||
.sdoc-content-right-panel .detail-header .sdoc-sm-close {
|
||||
color: #999;
|
||||
font-weight: 700;
|
||||
|
@@ -16,7 +16,7 @@ const SdocEditor = () => {
|
||||
const [currentDirent, setCurrentDirent] = useState(null);
|
||||
const { collaborators } = useCollaborators();
|
||||
const plugins = useMemo(() => {
|
||||
const { repoID, docPath, docPerm } = window.seafile;
|
||||
const { repoID, docPath, docPerm, isRepoAdmin } = window.seafile;
|
||||
return [
|
||||
{
|
||||
name: 'sdoc-info',
|
||||
@@ -29,7 +29,7 @@ const SdocEditor = () => {
|
||||
repoID={repoID}
|
||||
path={docPath}
|
||||
dirent={currentDirent}
|
||||
repoInfo={{ permission: docPerm }}
|
||||
repoInfo={{ permission: docPerm, is_admin: isRepoAdmin }}
|
||||
width={width - 1}
|
||||
component={{
|
||||
headerComponent: {
|
||||
|
@@ -15,11 +15,12 @@ const {
|
||||
repoID, repoName, repoEncrypted, parentDir, filePerm,
|
||||
docPath, docName, docUuid, seadocAccessToken, seadocServerUrl, assetsUrl,
|
||||
isSdocRevision, isPublished, originFilename, revisionCreatedAt, originFileVersion,
|
||||
originFilePath, originDocUuid, revisionId, isFreezed, mobileLogin
|
||||
originFilePath, originDocUuid, revisionId, isFreezed, mobileLogin, isRepoAdmin
|
||||
} = window.app.pageOptions;
|
||||
|
||||
window.seafile = {
|
||||
repoID,
|
||||
isRepoAdmin,
|
||||
docPath,
|
||||
docName,
|
||||
docUuid,
|
||||
@@ -52,7 +53,7 @@ window.seafile = {
|
||||
mobileLogin,
|
||||
};
|
||||
|
||||
const repoInfo = { encrypted: repoEncrypted, permission: filePerm };
|
||||
const repoInfo = { encrypted: repoEncrypted, permission: filePerm, is_admin: isRepoAdmin };
|
||||
|
||||
ReactDom.render(
|
||||
<I18nextProvider i18n={ i18n } >
|
||||
|
Reference in New Issue
Block a user