2018-08-30 07:10:52 +00:00
|
|
|
import React, { Component } from 'react';
|
2018-10-16 10:19:51 +00:00
|
|
|
import PropTypes from 'prop-types';
|
2018-09-21 06:16:15 +00:00
|
|
|
import { seafileAPI } from '../../utils/seafile-api';
|
2018-10-09 02:56:59 +00:00
|
|
|
import { gettext, siteRoot } from '../../utils/constants';
|
2018-08-30 07:10:52 +00:00
|
|
|
|
2018-10-16 10:19:51 +00:00
|
|
|
const contentPropTypes = {
|
|
|
|
data: PropTypes.object.isRequired,
|
|
|
|
};
|
|
|
|
|
2018-08-30 07:10:52 +00:00
|
|
|
class FileActivitiesContent extends Component {
|
|
|
|
|
|
|
|
render() {
|
2018-09-18 02:11:37 +00:00
|
|
|
let {loading, error_msg, items, has_more} = this.props.data;
|
2018-08-30 07:10:52 +00:00
|
|
|
if (loading) {
|
|
|
|
return <span className="loading-icon loading-tip"></span>;
|
|
|
|
} else if (error_msg) {
|
|
|
|
return <p className="error text-center">{error_msg}</p>;
|
|
|
|
} else {
|
|
|
|
return (
|
|
|
|
<React.Fragment>
|
|
|
|
<table className="table table-hover table-vcenter activity-table">
|
|
|
|
<thead>
|
|
|
|
<tr>
|
|
|
|
<th width="8%">{/* avatar */}</th>
|
2018-09-29 10:32:53 +00:00
|
|
|
<th width="10%">{gettext('User')}</th>
|
|
|
|
<th width="25%">{gettext('Operation')}</th>
|
|
|
|
<th width="37%">{gettext('File')} / {gettext('Library')}</th>
|
|
|
|
<th width="20%">{gettext('Time')}</th>
|
2018-08-30 07:10:52 +00:00
|
|
|
</tr>
|
|
|
|
</thead>
|
2018-09-18 02:11:37 +00:00
|
|
|
<TableBody items={items} />
|
2018-08-30 07:10:52 +00:00
|
|
|
</table>
|
2018-09-18 02:11:37 +00:00
|
|
|
{has_more ? <span className="loading-icon loading-tip"></span> : ''}
|
|
|
|
{error_msg ? <p className="error text-center">{error_msg}</p> : ''}
|
2018-08-30 07:10:52 +00:00
|
|
|
</React.Fragment>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-16 10:19:51 +00:00
|
|
|
FileActivitiesContent.propTypes = contentPropTypes;
|
|
|
|
|
|
|
|
|
|
|
|
const tablePropTypes = {
|
|
|
|
items: PropTypes.array.isRequired,
|
|
|
|
};
|
|
|
|
|
2018-08-30 07:10:52 +00:00
|
|
|
class TableBody extends Component {
|
|
|
|
|
|
|
|
encodePath(path) {
|
2018-10-16 10:19:51 +00:00
|
|
|
let path_arr = path.split('/');
|
|
|
|
let path_arr_ = [];
|
2018-08-30 07:10:52 +00:00
|
|
|
for (let i = 0, len = path_arr.length; i < len; i++) {
|
|
|
|
path_arr_.push(encodeURIComponent(path_arr[i]));
|
|
|
|
}
|
|
|
|
return path_arr_.join('/');
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
let listFilesActivities = this.props.items.map(function(item, index) {
|
|
|
|
let op, details;
|
|
|
|
let userProfileURL = `${siteRoot}profile/${encodeURIComponent(item.author_email)}/`;
|
|
|
|
|
|
|
|
let libURL = `${siteRoot}#common/lib/${item.repo_id}`;
|
|
|
|
let libLink = <a href={libURL}>{item.repo_name}</a>;
|
|
|
|
let smallLibLink = <a className="small text-secondary" href={libURL}>{item.repo_name}</a>;
|
|
|
|
|
|
|
|
if (item.obj_type == 'repo') {
|
|
|
|
switch(item.op_type) {
|
2018-10-16 10:19:51 +00:00
|
|
|
case 'create':
|
|
|
|
op = gettext('Created library');
|
|
|
|
details = <td>{libLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'rename':
|
|
|
|
op = gettext('Renamed library');
|
|
|
|
details = <td>{item.old_repo_name} => {libLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'delete':
|
|
|
|
op = gettext('Deleted library');
|
|
|
|
details = <td>{item.repo_name}</td>;
|
|
|
|
break;
|
|
|
|
case 'recover':
|
|
|
|
op = gettext('Restored library');
|
|
|
|
details = <td>{libLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'clean-up-trash':
|
|
|
|
if (item.days == 0) {
|
|
|
|
op = gettext('Removed all items from trash.');
|
|
|
|
} else {
|
|
|
|
op = gettext('Removed items older than {n} days from trash.').replace('{n}', item.days);
|
|
|
|
}
|
|
|
|
details = <td>{libLink}</td>;
|
|
|
|
break;
|
2018-08-30 07:10:52 +00:00
|
|
|
}
|
|
|
|
} else if (item.obj_type == 'file') {
|
|
|
|
let fileURL = `${siteRoot}lib/${item.repo_id}/file${this.encodePath(item.path)}`;
|
|
|
|
let fileLink = <a href={fileURL}>{item.name}</a>;
|
|
|
|
switch(item.op_type) {
|
2018-10-16 10:19:51 +00:00
|
|
|
case 'create':
|
|
|
|
op = gettext('Created file');
|
|
|
|
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'delete':
|
|
|
|
op = gettext('Deleted file');
|
|
|
|
details = <td>{item.name}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'recover':
|
|
|
|
op = gettext('Restored file');
|
|
|
|
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'rename':
|
|
|
|
op = gettext('Renamed file');
|
|
|
|
details = <td>{item.old_name} => {fileLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'move':
|
|
|
|
var filePathLink = <a href={fileURL}>{item.path}</a>;
|
|
|
|
op = gettext('Moved file');
|
|
|
|
details = <td>{item.old_path} => {filePathLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'edit': // update
|
|
|
|
op = gettext('Updated file');
|
|
|
|
details = <td>{fileLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
2018-08-30 07:10:52 +00:00
|
|
|
}
|
|
|
|
} else { // dir
|
|
|
|
let dirURL = `${siteRoot}#common/lib/${item.repo_id}${this.encodePath(item.path)}`;
|
|
|
|
let dirLink = <a href={dirURL}>{item.name}</a>;
|
|
|
|
switch(item.op_type) {
|
2018-10-16 10:19:51 +00:00
|
|
|
case 'create':
|
|
|
|
op = gettext('Created folder');
|
|
|
|
details = <td>{dirLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'delete':
|
|
|
|
op = gettext('Deleted folder');
|
|
|
|
details = <td>{item.name}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'recover':
|
|
|
|
op = gettext('Restored folder');
|
|
|
|
details = <td>{dirLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'rename':
|
|
|
|
op = gettext('Renamed folder');
|
|
|
|
details = <td>{item.old_name} => {dirLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
|
|
|
case 'move':
|
|
|
|
var dirPathLink = <a href={dirURL}>{item.path}</a>;
|
|
|
|
op = gettext('Moved folder');
|
|
|
|
details = <td>{item.old_path} => {dirPathLink}<br />{smallLibLink}</td>;
|
|
|
|
break;
|
2018-08-30 07:10:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<tr key={index}>
|
|
|
|
<td className="text-center">
|
2018-09-20 05:23:24 +00:00
|
|
|
<img src={item.avatar_url} alt="" width="36px" height="36px" className="avatar" />
|
2018-08-30 07:10:52 +00:00
|
|
|
</td>
|
|
|
|
<td>
|
|
|
|
<a href={userProfileURL}>{item.author_name}</a>
|
|
|
|
</td>
|
|
|
|
<td><span className="activity-op">{op}</span></td>
|
|
|
|
{details}
|
|
|
|
<td className="text-secondary" dangerouslySetInnerHTML={{__html:item.time_relative}}></td>
|
|
|
|
</tr>
|
|
|
|
);
|
|
|
|
}, this);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<tbody>{listFilesActivities}</tbody>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-10-16 10:19:51 +00:00
|
|
|
TableBody.propTypes = tablePropTypes;
|
|
|
|
|
2018-08-30 07:10:52 +00:00
|
|
|
class FilesActivities extends Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
loading: true,
|
|
|
|
error_msg: '',
|
2018-09-18 02:11:37 +00:00
|
|
|
events: {},
|
|
|
|
items: [],
|
|
|
|
page: 1,
|
|
|
|
has_more: false
|
2018-08-30 07:10:52 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
this.handleScroll = this.handleScroll.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
2018-09-20 05:23:24 +00:00
|
|
|
const pageNum = 1;
|
|
|
|
const avatarSize = 72;
|
2018-10-16 10:19:51 +00:00
|
|
|
seafileAPI.listActivities(pageNum, avatarSize).then(res => {
|
2018-08-30 07:10:52 +00:00
|
|
|
// not logged in
|
|
|
|
if (res.status == 403) {
|
|
|
|
this.setState({
|
|
|
|
loading: false,
|
2018-10-16 10:19:51 +00:00
|
|
|
error_msg: gettext('Permission denied')
|
2018-08-30 07:10:52 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// {"events":[...]}
|
|
|
|
this.setState({
|
|
|
|
loading: false,
|
2018-09-18 02:11:37 +00:00
|
|
|
items: res.data.events,
|
|
|
|
has_more: res.data.events.length == '0' ? false : true
|
2018-08-30 07:10:52 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getMore() {
|
2018-09-18 02:11:37 +00:00
|
|
|
const pageNum = this.state.page + 1;
|
|
|
|
this.setState({
|
|
|
|
page: pageNum
|
2018-09-29 10:32:53 +00:00
|
|
|
});
|
2018-09-18 02:11:37 +00:00
|
|
|
seafileAPI.listActivities(pageNum)
|
|
|
|
.then(res => {
|
|
|
|
if (res.status == 403) {
|
|
|
|
this.setState({
|
|
|
|
loading: false,
|
2018-09-29 10:32:53 +00:00
|
|
|
error_msg: gettext('Permission denied')
|
2018-09-18 02:11:37 +00:00
|
|
|
});
|
|
|
|
} else {
|
|
|
|
// {"events":[...]}
|
|
|
|
this.setState({
|
|
|
|
loading: false,
|
|
|
|
items: [...this.state.items, ...res.data.events],
|
|
|
|
has_more: res.data.events.length == '0' ? false : true
|
|
|
|
});
|
2018-08-30 07:10:52 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2018-09-18 02:11:37 +00:00
|
|
|
handleScroll(event) {
|
|
|
|
const clientHeight = event.target.clientHeight;
|
|
|
|
const scrollHeight = event.target.scrollHeight;
|
|
|
|
const scrollTop = event.target.scrollTop;
|
|
|
|
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
|
|
|
|
if (this.state.has_more && isBottom) { // scroll to the bottom
|
2018-08-30 07:10:52 +00:00
|
|
|
this.getMore();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
return (
|
2018-09-21 06:16:15 +00:00
|
|
|
<div className="cur-view-container" id="activities">
|
2018-08-30 07:10:52 +00:00
|
|
|
<div className="cur-view-path">
|
2018-09-29 10:32:53 +00:00
|
|
|
<h3 className="sf-heading">{gettext('Activities')}</h3>
|
2018-08-30 07:10:52 +00:00
|
|
|
</div>
|
2018-09-21 06:16:15 +00:00
|
|
|
<div className="cur-view-content" onScroll={this.handleScroll}>
|
2018-08-30 07:10:52 +00:00
|
|
|
<FileActivitiesContent data={this.state} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default FilesActivities;
|