import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import moment from 'moment';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, siteRoot, serviceURL } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import Loading from '../../components/loading';
import Activity from '../../models/activity';
import ListCreatedFileDialog from '../../components/dialog/list-created-files-dialog';
import ModalPortal from '../../components/modal-portal';
import '../../css/files-activities.css';
moment.locale(window.app.config.lang);
const contentPropTypes = {
isLoadingMore: PropTypes.bool.isRequired,
items: PropTypes.array.isRequired,
};
class FileActivitiesContent extends Component {
render() {
const isDesktop = Utils.isDesktop();
let { items, isLoadingMore } = this.props;
const desktopThead = (
{/* avatar */} |
{gettext('User')} |
{gettext('Operation')} |
{gettext('File')} / {gettext('Library')} |
{gettext('Time')} |
);
const mobileThead = (
|
|
|
);
return (
{isDesktop ? desktopThead : mobileThead}
{items.map((item, index) => {
return (
);
})}
{isLoadingMore ? : ''}
);
}
}
FileActivitiesContent.propTypes = contentPropTypes;
const activityPropTypes = {
item: PropTypes.object.isRequired,
index: PropTypes.number.isRequired,
items: PropTypes.array.isRequired,
isDesktop: PropTypes.bool.isRequired,
};
class ActivityItem extends Component {
constructor(props) {
super(props);
this.state = {
isListCreatedFiles: false,
};
}
onListCreatedFilesToggle = () => {
this.setState({
isListCreatedFiles: !this.state.isListCreatedFiles,
});
};
render() {
const isDesktop = this.props.isDesktop;
let {item, index, items} = this.props;
let op, details, moreDetails = false;
let userProfileURL = `${siteRoot}profile/${encodeURIComponent(item.author_email)}/`;
let libURL = siteRoot + 'library/' + item.repo_id + '/' + encodeURIComponent(item.repo_name) + '/';
let libLink = {item.repo_name};
let smallLibLink = {item.repo_name};
if (item.obj_type == 'repo') {
switch(item.op_type) {
case 'create':
op = gettext('Created library');
details = libLink;
break;
case 'rename':
op = gettext('Renamed library');
details = {item.old_repo_name} => {libLink};
break;
case 'delete':
op = gettext('Deleted library');
details = item.repo_name;
break;
case 'recover':
op = gettext('Restored library');
details = libLink;
break;
case 'clean-up-trash':
op = gettext('Cleaned trash');
if (item.days == 0) {
details = gettext('Removed all items from trash.');
} else {
details = gettext('Removed items older than {n} days from trash.').replace('{n}', item.days);
}
moreDetails = true;
break;
}
} else if (item.obj_type == 'draft') {
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
let fileLink = {item.name};
op = gettext('Publish draft');
details = fileLink;
moreDetails = true;
} else if (item.obj_type == 'files') {
let fileURL = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
if (item.name.endsWith('(draft).md')) {
fileURL = serviceURL + '/drafts/' + item.draft_id + '/';
}
let fileLink = `${item.name}`;
if (item.name.endsWith('(draft).md') && !item.draft_id) {
fileLink = item.name;
}
let fileCount = item.createdFilesCount - 1;
let firstLine = gettext('{file} and {n} other files')
.replace('{file}', fileLink)
.replace('{n}', fileCount);
op = gettext('Created {n} files').replace('{n}', item.createdFilesCount);
details = (
{isDesktop && }
);
moreDetails = true;
} else if (item.obj_type == 'file') {
const isDraft = item.name.endsWith('(draft).md');
const fileURL = isDraft ? serviceURL + '/drafts/' + item.draft_id + '/' :
`${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
let fileLink = {item.name};
if (isDraft && !item.draft_id) {
fileLink = item.name;
}
switch (item.op_type) {
case 'create':
op = isDraft ? gettext('Created draft') : gettext('Created file');
details = fileLink;
moreDetails = true;
break;
case 'delete':
op = isDraft ? gettext('Deleted draft') : gettext('Deleted file');
details = item.name;
moreDetails = true;
break;
case 'recover':
op = gettext('Restored file');
details = fileLink;
moreDetails = true;
break;
case 'rename':
op = gettext('Renamed file');
details = {item.old_name} => {fileLink};
moreDetails = true;
break;
case 'move':
// eslint-disable-next-line
const filePathLink = {item.path};
op = gettext('Moved file');
details = {item.old_path} => {filePathLink};
moreDetails = true;
break;
case 'edit': // update
op = isDraft ? gettext('Updated draft') : gettext('Updated file');
details = fileLink;
moreDetails = true;
break;
}
} else { // dir
let dirURL = siteRoot + 'library/' + item.repo_id + '/' + encodeURIComponent(item.repo_name) + Utils.encodePath(item.path);
let dirLink = {item.name};
switch (item.op_type) {
case 'create':
op = gettext('Created folder');
details = dirLink;
moreDetails = true;
break;
case 'delete':
op = gettext('Deleted folder');
details = item.name;
moreDetails = true;
break;
case 'recover':
op = gettext('Restored folder');
details = dirLink;
moreDetails = true;
break;
case 'rename':
op = gettext('Renamed folder');
details = {item.old_name} => {dirLink};
moreDetails = true;
break;
case 'move':
// eslint-disable-next-line
const dirPathLink = {item.path};
op = gettext('Moved folder');
details = {item.old_path} => {dirPathLink};
moreDetails = true;
break;
}
}
let isShowDate = true;
if (index > 0) {
let lastEventTime = items[index - 1].time;
isShowDate = moment(item.time).isSame(lastEventTime, 'day') ? false : true;
}
return (
{isShowDate &&
{moment(item.time).format('YYYY-MM-DD')} |
}
{isDesktop ? (
|
{item.author_name}
|
{op} |
{details}
{moreDetails && }
{moreDetails && smallLibLink}
|
|
) : (
|
{item.author_name}
{op}
{details}
|
{moreDetails && }
{moreDetails && libLink}
|
)}
{this.state.isListCreatedFiles &&
}
);
}
}
ActivityItem.propTypes = activityPropTypes;
class FilesActivities extends Component {
constructor(props) {
super(props);
this.state = {
errorMsg: '',
isFirstLoading: true,
isLoadingMore: false,
currentPage: 1,
hasMore: true,
items: [],
};
this.avatarSize = 72;
this.curPathList = [];
this.oldPathList = [];
}
componentDidMount() {
let currentPage = this.state.currentPage;
seafileAPI.listActivities(currentPage, this.avatarSize).then(res => {
// {"events":[...]}
let events = this.mergePublishEvents(res.data.events);
events = this.mergeFileCreateEvents(events);
this.setState({
items: events,
currentPage: currentPage + 1,
isFirstLoading: false,
hasMore: true,
});
if (this.state.items.length < 25) {
this.getMore();
}
}).catch(error => {
this.setState({
isFirstLoading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
mergePublishEvents = (events) => {
events.forEach((item) => {
if (item.op_type === 'publish') {
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);
} else if (events[i].op_type === 'edit' && this.curPathList.includes(events[i].path)) {
this.curPathList.splice(this.curPathList.indexOf(events[i].path), 1);
} 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);
} else {
actuallyEvents.push(events[i]);
}
} else {
actuallyEvents.push(events[i]);
}
}
return actuallyEvents;
};
mergeFileCreateEvents = (events) => {
let actuallyEvents = [];
let multiFilesActivity = null;
for (var i = 0; i < events.length; i++) {
let isFulfilCondition = events[i].obj_type === 'file' &&
events[i].op_type === 'create' &&
events[i + 1] &&
events[i + 1].obj_type === 'file' &&
events[i + 1].op_type === 'create' &&
events[i + 1].repo_name === events[i].repo_name &&
events[i + 1].author_email === events[i].author_email;
if (multiFilesActivity != null) {
multiFilesActivity.createdFilesCount++;
multiFilesActivity.createdFilesList.push(events[i]);
if (isFulfilCondition) {
continue;
} else {
actuallyEvents.push(multiFilesActivity);
multiFilesActivity = null;
}
} else {
if (isFulfilCondition) {
multiFilesActivity = new Activity(events[i]);
multiFilesActivity.obj_type = 'files';
multiFilesActivity.createdFilesCount++;
multiFilesActivity.createdFilesList.push(events[i]);
} else {
actuallyEvents.push(events[i]);
}
}
}
return actuallyEvents;
};
getMore() {
let currentPage = this.state.currentPage;
seafileAPI.listActivities(currentPage, this.avatarSize).then(res => {
// {"events":[...]}
let events = this.mergePublishEvents(res.data.events);
events = this.mergeFileCreateEvents(events);
this.setState({
isLoadingMore: false,
items: [...this.state.items, ...events],
currentPage: currentPage + 1,
hasMore: res.data.events.length === 0 ? false : true
});
if (this.state.items.length < 25 && this.state.hasMore) {
this.getMore();
}
}).catch(error => {
this.setState({
isLoadingMore: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
handleScroll = (event) => {
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();
});
}
}
};
render() {
return (
{gettext('Activities')}
{this.state.isFirstLoading &&
}
{(!this.state.isFirstLoading && this.state.errorMsg) &&
{this.state.errorMsg}
}
{!this.state.isFirstLoading &&
}
);
}
}
export default FilesActivities;