1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-06 09:34:00 +00:00
seahub/frontend/src/pages/dashboard/files-activities.js

334 lines
11 KiB
JavaScript
Raw Normal View History

2018-11-29 09:55:14 +00:00
import React, { Component, Fragment } from 'react';
2018-10-16 10:19:51 +00:00
import PropTypes from 'prop-types';
import moment from 'moment';
2018-09-21 06:16:15 +00:00
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot } from '../../utils/constants';
2018-12-24 04:24:16 +00:00
import { Utils } from '../../utils/utils';
2018-12-28 03:12:24 +00:00
import Loading from '../../components/loading';
import Activity from '../../models/activity';
2018-08-30 07:10:52 +00:00
moment.locale(window.app.config.lang);
2018-10-16 10:19:51 +00:00
const contentPropTypes = {
2018-12-28 03:12:24 +00:00
isLoadingMore: PropTypes.bool.isRequired,
items: PropTypes.array.isRequired,
2018-10-16 10:19:51 +00:00
};
2018-08-30 07:10:52 +00:00
class FileActivitiesContent extends Component {
render() {
2018-12-28 03:12:24 +00:00
let {items, isLoadingMore} = this.props;
return (
<Fragment>
<table width="100%" className="table table-hover table-vcenter">
<col width="8%" />
<col width="15%" />
<col width="20%" />
<col width="37%" />
<col width="20%" />
2018-12-28 03:12:24 +00:00
<TableBody items={items} />
</table>
{isLoadingMore ? <span className="loading-icon loading-tip"></span> : ''}
</Fragment>
);
2018-08-30 07:10:52 +00:00
}
}
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 {
render() {
let listFilesActivities = this.props.items.map(function(item, index) {
let op, details;
let userProfileURL = `${siteRoot}profile/${encodeURIComponent(item.author_email)}/`;
2018-12-24 04:24:16 +00:00
let libURL = siteRoot + 'library/' + item.repo_id + '/' + encodeURIComponent(item.repo_name) + '/';
2018-08-30 07:10:52 +00:00
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) {
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 == 'review') {
let fileURL = `${siteRoot}drafts/review/${item.review_id}`;
let fileLink = <a href={fileURL}>{item.name}</a>;
switch(item.op_type) {
case 'open':
op = gettext('Open review');
details = <td>{fileLink}<br />{smallLibLink}</td>;
break;
case 'closed':
op = gettext('Close review');
details = <td>{fileLink}<br />{smallLibLink}</td>;
break;
case 'finished':
op = gettext('Publish draft');
details = <td>{fileLink}<br />{smallLibLink}</td>;
break;
}
2018-08-30 07:10:52 +00:00
} else if (item.obj_type == 'file') {
2018-12-24 04:24:16 +00:00
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
2018-08-30 07:10:52 +00:00
let fileLink = <a href={fileURL}>{item.name}</a>;
switch(item.op_type) {
case 'create':
if (item.name.endsWith('(draft).md')) {
op = gettext('Created draft');
details = <td>{fileLink}<br />{smallLibLink}</td>;
break;
}
op = gettext('Created file');
details = <td>{fileLink}<br />{smallLibLink}</td>;
break;
case 'delete':
if (item.name.endsWith('(draft).md')) {
op = gettext('Deleted draft');
details = <td>{item.name}<br />{smallLibLink}</td>;
break;
}
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
if (item.name.endsWith('(draft).md')) {
op = gettext('Updated draft');
details = <td>{fileLink}<br />{smallLibLink}</td>;
break;
}
op = gettext('Updated file');
details = <td>{fileLink}<br />{smallLibLink}</td>;
break;
2018-08-30 07:10:52 +00:00
}
} else { // dir
2018-12-24 04:24:16 +00:00
let dirURL = siteRoot + 'library/' + item.repo_id + '/' + encodeURIComponent(item.repo_name) + Utils.encodePath(item.path);
2018-08-30 07:10:52 +00:00
let dirLink = <a href={dirURL}>{item.name}</a>;
switch(item.op_type) {
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
}
}
let isShowDate = true;
if (index > 0) {
let lastEventTime = this.props.items[index - 1].time;
isShowDate = moment(item.time).isSame(lastEventTime, 'day') ? false : true;
}
2018-08-30 07:10:52 +00:00
return (
<Fragment key={index}>
{ isShowDate &&
<tr>
<td colSpan='5'>{moment(item.time).format('YYYY-MM-DD')}</td>
</tr>
}
<tr>
<td className="text-center">
<img src={item.avatar_url} alt="" width="36px" height="36px" className="avatar" />
</td>
<td>
<a href={userProfileURL}>{item.author_name}</a>
</td>
<td><span className="activity-op">{op}</span></td>
{details}
<td className="text-secondary">
<time datetime={item.time} is="relative-time" title={moment(item.time).format('llll')}>{moment(item.time).fromNow()}</time>
</td>
</tr>
</Fragment>
2018-08-30 07:10:52 +00:00
);
}, 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 = {
2018-12-28 03:12:24 +00:00
errorMsg: '',
isFirstLoading: true,
isLoadingMore: false,
currentPage: 1,
hasMore: true,
items: [],
2018-08-30 07:10:52 +00:00
};
2019-01-08 06:51:13 +00:00
this.avatarSize = 72;
this.curPathList = [];
this.oldPathList = [];
2018-08-30 07:10:52 +00:00
}
componentDidMount() {
2018-12-28 03:12:24 +00:00
let currentPage = this.state.currentPage;
2019-01-08 06:51:13 +00:00
seafileAPI.listActivities(currentPage, this.avatarSize).then(res => {
2018-12-28 03:12:24 +00:00
// {"events":[...]}
this.setState({
items: this.filterSuperfluousEvents(res.data.events),
2018-12-28 03:12:24 +00:00
currentPage: currentPage + 1,
isFirstLoading: false,
hasMore: true,
});
}).catch(error => {
if (error.response.status == 403) {
2018-08-30 07:10:52 +00:00
this.setState({
2018-12-28 03:12:24 +00:00
isFirstLoading: false,
errorMsg: gettext('Permission denied')
2018-08-30 07:10:52 +00:00
});
}
});
}
filterSuperfluousEvents = (events) => {
events.map((item) => {
if (item.op_type === 'finished') {
this.curPathList.push(item.path);
this.oldPathList.push(item.old_path);
}
});
let actuallyEvents = [];
for (var i = 0; i < events.length; i++) {
if (events[i].obj_type === 'file') {
if (events[i].op_type === 'delete' && this.oldPathList.includes(events[i].path)) {
this.oldPathList.splice(this.oldPathList.indexOf(events[i].path), 1);
continue;
} else if (events[i].op_type === 'edit' && this.curPathList.includes(events[i].path)) {
this.curPathList.splice(this.curPathList.indexOf(events[i].path), 1);
continue;
} else if (events[i].op_type === 'rename' && this.oldPathList.includes(events[i].old_path)) {
this.oldPathList.splice(this.oldPathList.indexOf(events[i].old_path), 1);
continue;
} else {
let event = new Activity(events[i]);
actuallyEvents.push(event);
}
} else {
let event = new Activity(events[i]);
actuallyEvents.push(event);
}
}
return actuallyEvents;
}
2018-08-30 07:10:52 +00:00
getMore() {
2018-12-28 03:12:24 +00:00
let currentPage = this.state.currentPage;
2019-01-08 06:51:13 +00:00
seafileAPI.listActivities(currentPage, this.avatarSize).then(res => {
2018-12-28 03:12:24 +00:00
// {"events":[...]}
this.setState({
isLoadingMore: false,
items: [...this.state.items, ...this.filterSuperfluousEvents(res.data.events)],
2018-12-28 03:12:24 +00:00
currentPage: currentPage + 1,
hasMore: res.data.events.length === 0 ? false : true
});
}).catch(error => {
if (error.response.status == 403) {
2018-12-25 10:25:16 +00:00
this.setState({
2018-12-28 03:12:24 +00:00
isLoadingMore: false,
errorMsg: gettext('Permission denied')
2018-12-25 10:25:16 +00:00
});
}
});
2018-08-30 07:10:52 +00:00
}
2018-12-28 03:12:24 +00:00
handleScroll = (event) => {
2019-01-08 06:51:13 +00:00
if (!this.state.isLoadingMore && this.state.hasMore) {
const clientHeight = event.target.clientHeight;
const scrollHeight = event.target.scrollHeight;
const scrollTop = event.target.scrollTop;
const isBottom = (clientHeight + scrollTop + 1 >= scrollHeight);
if (isBottom) { // scroll to the bottom
this.setState({isLoadingMore: true}, () => {
this.getMore();
});
}
2018-08-30 07:10:52 +00:00
}
}
render() {
return (
2018-11-26 09:53:18 +00:00
<div className="main-panel-center">
<div className="cur-view-container" id="activities">
<div className="cur-view-path">
<h3 className="sf-heading">{gettext('Activities')}</h3>
</div>
<div className="cur-view-content" onScroll={this.handleScroll}>
2018-12-28 03:12:24 +00:00
{this.state.isFirstLoading && <Loading />}
{(!this.state.isFirstLoading && this.state.errorMsg) &&
<p className="error text-center">{this.state.errorMsg}</p>
}
{!this.state.isFirstLoading &&
<FileActivitiesContent items={this.state.items} isLoadingMore={this.state.isLoadingMore}/>
}
2018-11-26 06:00:32 +00:00
</div>
2018-08-30 07:10:52 +00:00
</div>
2018-11-26 09:53:18 +00:00
</div>
2018-08-30 07:10:52 +00:00
);
}
}
export default FilesActivities;