Skip to content

Commit

Permalink
feat: show metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
杨国璇 authored and 杨国璇 committed Aug 5, 2024
1 parent cfacf6d commit a6f9060
Show file tree
Hide file tree
Showing 15 changed files with 153 additions and 55 deletions.
8 changes: 4 additions & 4 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@seafile/sdoc-editor": "~1.0.40",
"@seafile/seafile-calendar": "0.0.12",
"@seafile/seafile-editor": "1.0.107",
"@seafile/sf-metadata-ui-component": "0.0.16",
"@seafile/sf-metadata-ui-component": "0.0.17-beta",
"@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/react-codemirror": "^4.19.4",
"chart.js": "2.9.4",
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/components/dirent-detail/detail-item/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,9 @@
.dirent-detail-item-value .creator-formatter {
height: 20px;
}

.dirent-detail-item-value .sf-metadata-record-cell-empty::before {
content: attr(placeholder);
color: #666;
font-size: 14px;
}
5 changes: 5 additions & 0 deletions frontend/src/components/dirent-detail/detail-item/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import { Formatter, Icon } from '@seafile/sf-metadata-ui-component';
import classnames from 'classnames';
import { CellType, COLUMNS_ICON_CONFIG } from '../../../metadata/metadata-view/_basic';
import { gettext } from '../../../utils/constants';

import './index.css';

Expand All @@ -25,6 +26,10 @@ const DetailItem = ({ field, value, valueId, valueClick, children, ...params })
);
};

DetailItem.defaultProps = {
emptyTip: gettext('Empty')
};

DetailItem.propTypes = {
field: PropTypes.object.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/dirent-detail/detail-list-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class DetailListView extends React.Component {
this.tagListTitleID = `detail-list-view-tags-${uuidv4()}`;
}

getDirentPosition = () => {
getFileParent = () => {
let { repoInfo } = this.props;
let direntPath = this.getDirentPath();
let position = repoInfo.repo_name;
Expand Down Expand Up @@ -69,7 +69,7 @@ class DetailListView extends React.Component {

renderTags = () => {
const { direntType, direntDetail } = this.props;
const position = this.getDirentPosition();
const position = this.getFileParent();
if (direntType === 'dir') {
return (
<table className="table-thead-hidden">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import { getDirentPath, getDirentPosition } from './utils';
import { getDirentPath, getFileParent } from './utils';
import DetailItem from '../detail-item';
import { CellType } from '../../../metadata/metadata-view/_basic';
import { gettext } from '../../../utils/constants';
import { MetadataDetails } from '../../../metadata';

const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail }) => {
const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]);
const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]);
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);

return (
<>
<DetailItem field={{ type: CellType.TEXT, name: gettext('File location') }} value={position} />
<DetailItem field={{ type: CellType.TEXT, name: gettext('Parent') }} value={parent} />
<DetailItem field={{ type: 'size', name: gettext('Size') }} value={repoInfo.size} />
<DetailItem field={{ type: CellType.CREATOR, name: gettext('Creator') }} value={repoInfo.owner_email} collaborators={[{
name: repoInfo.owner_name,
Expand All @@ -20,6 +21,9 @@ const DirDetails = ({ repoID, repoInfo, dirent, direntType, path, direntDetail }
avatar_url: repoInfo.owner_avatar,
}]} />
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={direntDetail.mtime} />
{window.app.pageOptions.enableMetadataManagement && (
<MetadataDetails repoID={repoID} filePath={direntPath} direntType={direntType} direntDetail={direntDetail} />
)}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import React, { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { v4 as uuidV4 } from 'uuid';
import { getDirentPath, getDirentPosition } from './utils';
import { getDirentPath, getFileParent } from './utils';
import DetailItem from '../detail-item';
import { CellType } from '../../../metadata/metadata-view/_basic';
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';

// direntType
const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, onFileTagChanged, repoTags, fileTagList }) => {
const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, direntType, onFileTagChanged, repoTags, fileTagList }) => {
const [isEditFileTagShow, setEditFileTagShow] = useState(false);

const position = useMemo(() => getDirentPosition(repoInfo, dirent, path), [repoInfo, dirent, path]);
const parent = useMemo(() => getFileParent(repoInfo, dirent, path), [repoInfo, dirent, path]);
const direntPath = useMemo(() => getDirentPath(dirent, path), [dirent, path]);
const tagListTitleID = useMemo(() => `detail-list-view-tags-${uuidV4()}`, []);

Expand All @@ -27,7 +27,7 @@ const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, onFileTagCh

return (
<>
<DetailItem field={{ type: CellType.TEXT, name: gettext('File location') }} value={position} />
<DetailItem field={{ type: CellType.TEXT, name: gettext('Parent') }} value={parent} />
<DetailItem field={{ type: 'size', name: gettext('Size') }} value={Utils.bytesToSize(direntDetail.size)} />
<DetailItem field={{ type: CellType.LAST_MODIFIER, name: gettext('Last modifier') }} value={direntDetail.last_modifier_email} collaborators={[{
name: direntDetail.last_modifier_name,
Expand All @@ -36,13 +36,18 @@ const FileDetails = ({ repoID, repoInfo, dirent, path, direntDetail, onFileTagCh
avatar_url: direntDetail.last_modifier_avatar,
}]} />
<DetailItem field={{ type: CellType.MTIME, name: gettext('Last modified time') }} value={direntDetail.last_modified} />
<DetailItem field={{ type: CellType.SINGLE_SELECT, name: gettext('Tags') }} valueId={tagListTitleID} valueClick={onEditFileTagToggle} >
{Array.isArray(fileTagList) && fileTagList.length > 0 ? (
<FileTagList fileTagList={fileTagList} />
) : (
<span className="empty-tip-text">{gettext('Empty')}</span>
)}
</DetailItem>
{!window.app.pageOptions.enableMetadataManagement && (
<DetailItem field={{ type: CellType.SINGLE_SELECT, name: gettext('Tags') }} valueId={tagListTitleID} valueClick={onEditFileTagToggle} >
{Array.isArray(fileTagList) && fileTagList.length > 0 ? (
<FileTagList fileTagList={fileTagList} />
) : (
<span className="empty-tip-text">{gettext('Empty')}</span>
)}
</DetailItem>
)}
{window.app.pageOptions.enableMetadataManagement && (
<MetadataDetails repoID={repoID} filePath={direntPath} direntType={direntType} direntDetail={direntDetail} />
)}
{isEditFileTagShow &&
<EditFileTagPopover
repoID={repoID}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@
}

.detail-container .empty-tip-text {
color: #666
color: #666;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { Utils } from '../../../utils/utils';

export const getDirentPath = (dirent, path) => {
if (Utils.isMarkdownFile(path)) return path; // column mode: view file
if (dirent.type === 'dir') return path;
return Utils.joinPath(path, dirent.name);
};

export const getDirentPosition = (repoInfo, dirent, path) => {
export const getFileParent = (repoInfo, dirent, path) => {
const direntPath = getDirentPath(dirent, path);
const position = repoInfo.repo_name;
if (direntPath === '/') return position;
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/metadata/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import SeafileMetadata from './metadata-view';
import MetadataStatusManagementDialog from './metadata-status-manage-dialog';
import MetadataTreeView from './metadata-tree-view';
import MetadataDetails from './metadata-details';

export {
SeafileMetadata,
MetadataStatusManagementDialog,
MetadataTreeView,
MetadataDetails,
};
22 changes: 22 additions & 0 deletions frontend/src/metadata/metadata-details/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { PRIVATE_COLUMN_KEY } from '../metadata-view/_basic';

export const NOT_DISPLAY_COLUMN_KEYS = [
PRIVATE_COLUMN_KEY.ID,
PRIVATE_COLUMN_KEY.CTIME,
PRIVATE_COLUMN_KEY.MTIME,
PRIVATE_COLUMN_KEY.CREATOR,
PRIVATE_COLUMN_KEY.LAST_MODIFIER,
PRIVATE_COLUMN_KEY.FILE_CREATOR,
PRIVATE_COLUMN_KEY.FILE_CTIME,
PRIVATE_COLUMN_KEY.FILE_MODIFIER,
PRIVATE_COLUMN_KEY.FILE_MTIME,
PRIVATE_COLUMN_KEY.PARENT_DIR,
PRIVATE_COLUMN_KEY.FILE_NAME,
PRIVATE_COLUMN_KEY.IS_DIR,
PRIVATE_COLUMN_KEY.FILE_TYPE,
PRIVATE_COLUMN_KEY.LOCATION,
];

export {
PRIVATE_COLUMN_KEY,
};
56 changes: 56 additions & 0 deletions frontend/src/metadata/metadata-details/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { Utils } from '../../utils/utils';
import metadataAPI from '../api';
import Column from '../metadata-view/model/metadata/column';
import { normalizeFields, getCellValueByColumn } from './utils';
import toaster from '../../components/toast';
import DetailItem from '../../components/dirent-detail/detail-item';

const MetadataDetails = ({ repoID, filePath, direntType, direntDetail, emptyTip }) => {
const [isLoading, setLoading] = useState(true);
const [metadata, setMetadata] = useState({ record: {}, fields: [] });
const isEmptyFile = useMemo(() => {

Check warning on line 13 in frontend/src/metadata/metadata-details/index.js

View workflow job for this annotation

GitHub Actions / build (20.x)

'isEmptyFile' is assigned a value but never used
if (direntType === 'dir') return false;
const direntDetailId = direntDetail?.id || '';
return direntDetailId === '0'.repeat(direntDetailId.length);
}, [direntDetail, direntType]);

useEffect(() => {
setLoading(true);
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] : {};
const fields = normalizeFields(metadata).map(field => new Column(field));
setMetadata({ record, fields });
setLoading(false);
}).catch(error => {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
setLoading(false);
});
}, [repoID, filePath, direntType]);

if (isLoading) return null;
const { fields, record } = metadata;
if (!record._id) return null;
return fields.map(field => {
const value = getCellValueByColumn(record, field);
return (<DetailItem key={field.key} field={field} value={value} emptyTip={emptyTip}/>);
});
};

MetadataDetails.propTypes = {
repoID: PropTypes.string,
filePath: PropTypes.string,
direntType: PropTypes.string,
direntDetail: PropTypes.object,
};

export default MetadataDetails;
25 changes: 25 additions & 0 deletions frontend/src/metadata/metadata-details/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { getColumnType } from '../metadata-view/utils/column-utils';
import { getCellValueByColumn } from '../metadata-view/_basic';
import { NOT_DISPLAY_COLUMN_KEYS } from './constants';

export const normalizeFields = (fields) => {
if (!Array.isArray(fields) || fields.length === 0) return [];
const validFields = fields.map((field) => {
const { type, key, ...params } = field;
return {
...params,
key,
type: getColumnType(key, type),
width: 200,
};
}).filter(field => !NOT_DISPLAY_COLUMN_KEYS.includes(field.key));
let displayFields = [];
validFields.forEach(field => {
displayFields.push(field);
});
return displayFields;
};

export {
getCellValueByColumn,
};
2 changes: 1 addition & 1 deletion frontend/src/metadata/metadata-view/utils/column-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export const getColumnName = (key, name) => {
}
};

const getColumnType = (key, type) => {
export const getColumnType = (key, type) => {
switch (key) {
case PRIVATE_COLUMN_KEY.CTIME:
case PRIVATE_COLUMN_KEY.FILE_CTIME:
Expand Down
32 changes: 2 additions & 30 deletions seahub/api2/endpoints/metadata_manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,8 @@ def get(self, request, repo_id):
error_msg = 'name invalid'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

record = RepoMetadata.objects.filter(repo_id=repo_id).first()
if not record or not record.enabled:
metadata = RepoMetadata.objects.filter(repo_id=repo_id).first()
if not metadata or not metadata.enabled:
error_msg = f'The metadata module is disabled for repo {repo_id}.'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)

Expand Down Expand Up @@ -337,40 +337,12 @@ def get(self, request, repo_id):
error_msg = 'Internal Server Error'
return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg)

sys_columns = [
METADATA_TABLE.columns.id.key,
METADATA_TABLE.columns.file_creator.key,
METADATA_TABLE.columns.file_ctime.key,
METADATA_TABLE.columns.file_modifier.key,
METADATA_TABLE.columns.file_mtime.key,
METADATA_TABLE.columns.parent_dir.key,
METADATA_TABLE.columns.file_name.key,
METADATA_TABLE.columns.is_dir.key,
]

rows = query_result.get('results')

if not rows:
error_msg = 'Record not found'
return api_error(status.HTTP_404_NOT_FOUND, error_msg)

metadata = query_result.get('metadata')
editable_columns = []
name_to_key = {}
for col in metadata:
col_key = col.get('key')
col_name = col.get('name')
name_to_key[col_name] = col_key
if col_key in sys_columns:
continue
editable_columns.append(col.get('name'))

row = {name_to_key[name]: value for name, value in rows[0].items()}
query_result['row'] = row
query_result['editable_columns'] = editable_columns

query_result.pop('results', None)

return Response(query_result)


Expand Down

0 comments on commit a6f9060

Please sign in to comment.