1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-08-12 04:12:16 +00:00
seahub/frontend/src/components/dialog/repo-share-admin/share-links.js
Jerry Ren 890880a5c8
refactor(metadata): remove ui-component (#7492)
1. ModalPortal
2. Icon
3. IconBtn
4. Loading
5. CenteredLoading
6. ClickOutside
7. SearchInput
8. Switch
9. CustomizeAddTool
10. SfCalendar
11. SfFilterCalendar
12. CustomizeSelect
13. CustomizePopover
14. FieldDisplaySettings
15. Formatters
16. remove duplicate codes
2025-03-01 10:12:48 +08:00

350 lines
11 KiB
JavaScript

import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import dayjs from 'dayjs';
import classnames from 'classnames';
import { Link } from '@gatsbyjs/reach-router';
import { Utils } from '../../../utils/utils';
import { seafileAPI } from '../../../utils/seafile-api';
import { repoShareAdminAPI } from '../../../utils/repo-share-admin-api';
import { gettext, siteRoot } from '../../../utils/constants';
import Loading from '../../loading';
import toaster from '../../../components/toast';
import EmptyTip from '../../../components/empty-tip';
import CommonOperationConfirmationDialog from '../../../components/dialog/common-operation-confirmation-dialog';
const itemPropTypes = {
item: PropTypes.object.isRequired,
deleteItem: PropTypes.func.isRequired,
toggleSelectLink: PropTypes.func.isRequired
};
class Item extends Component {
constructor(props) {
super(props);
this.state = {
isOperationShow: false
};
}
onMouseEnter = () => {
this.setState({ isOperationShow: true });
};
onMouseLeave = () => {
this.setState({ isOperationShow: false });
};
onDeleteLink = () => {
this.props.deleteItem(this.props.item);
};
cutLink = (link) => {
let length = link.length;
return link.slice(0, 9) + '...' + link.slice(length - 5);
};
toggleSelectLink = (e) => {
const { item } = this.props;
this.props.toggleSelectLink(item, e.target.checked);
};
render() {
let objUrl;
let item = this.props.item;
let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1);
if (item.is_dir) {
objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`;
} else {
objUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`;
}
return (
<tr
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
onFocus={this.onMouseEnter}
className={classnames({ 'tr-highlight': item.isSelected })}
>
<td className="text-center">
<input
type="checkbox"
checked={item.isSelected || false}
className="vam"
onChange={this.toggleSelectLink}
/>
</td>
<td>
<img src={item.creator_avatar} alt={item.creator_name} width="24" className="rounded-circle mr-2" />
<a href={`${siteRoot}profile/${encodeURIComponent(item.creator_email)}/`} target="_blank" className="align-middle" rel="noreferrer">{item.creator_name}</a>
</td>
<td>
{item.is_dir ?
<Link to={objUrl}>{item.obj_name}</Link>
:
<a href={objUrl} target="_blank" rel="noreferrer">{item.obj_name}</a>
}
</td>
<td>
<a href={item.link} target="_blank" rel="noreferrer">
{this.cutLink(item.link)}
</a>
</td>
<td>
{item.expire_date ? dayjs(item.expire_date).format('YYYY-MM-DD HH:mm') : '--'}
</td>
<td>{item.view_cnt}</td>
<td>
<i
tabIndex="0"
role="button"
className={`sf3-font-delete1 sf3-font op-icon ${this.state.isOperationShow ? '' : 'invisible'}`}
onClick={this.onDeleteLink}
onKeyDown={Utils.onKeyDown}
title={gettext('Delete')}
aria-label={gettext('Delete')}
>
</i>
</td>
</tr>
);
}
}
Item.propTypes = itemPropTypes;
const propTypes = {
repo: PropTypes.object.isRequired,
};
const PER_PAGE = 25;
class RepoShareAdminShareLinks extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
hasMore: false,
isLoadingMore: false,
page: 1,
errorMsg: '',
items: [],
isDeleteShareLinksDialogOpen: false
};
}
componentDidMount() {
const { repo } = this.props;
const { page } = this.state;
repoShareAdminAPI.listRepoShareLinks(repo.repo_id, page).then((res) => {
this.setState({
loading: false,
hasMore: res.data.length == PER_PAGE,
items: res.data,
});
}).catch((error) => {
this.setState({
loading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
deleteItem = (item) => {
seafileAPI.deleteRepoShareLink(this.props.repo.repo_id, item.token).then(() => {
let items = this.state.items.filter(linkItem => {
return linkItem.token !== item.token;
});
this.setState({ items: items });
let message = gettext('Successfully deleted 1 item');
toaster.success(message);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
toggleDeleteShareLinksDialog = () => {
this.setState({ isDeleteShareLinksDialogOpen: !this.state.isDeleteShareLinksDialogOpen });
};
toggleSelectAllLinks = (e) => {
this._toggleSelectAllLinks(e.target.checked);
};
cancelSelectAllLinks = () => {
this._toggleSelectAllLinks(false);
};
_toggleSelectAllLinks = (isSelected) => {
const { items: links } = this.state;
this.setState({
items: links.map(item => {
item.isSelected = isSelected;
return item;
})
});
};
toggleSelectLink = (link, isSelected) => {
const { items: links } = this.state;
this.setState({
items: links.map(item => {
if (item.token == link.token) {
item.isSelected = isSelected;
}
return item;
})
});
};
deleteShareLinks = () => {
const { items } = this.state;
const tokens = items.filter(item => item.isSelected).map(link => link.token);
seafileAPI.deleteShareLinks(tokens).then(res => {
const { success, failed } = res.data;
if (success.length) {
let newShareLinkList = items.filter(shareLink => {
return !success.some(deletedShareLink => {
return deletedShareLink.token == shareLink.token;
});
});
this.setState({
items: newShareLinkList
});
const length = success.length;
const msg = length == 1 ?
gettext('Successfully deleted 1 share link') :
gettext('Successfully deleted {number_placeholder} share links')
.replace('{number_placeholder}', length);
toaster.success(msg);
}
failed.forEach(item => {
const msg = `${item.token}: ${item.error_msg}`;
toaster.danger(msg);
});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
getTheadContent = (withCheckbox, isAllLinksSelected) => {
return (
<tr>
<th width="5%" className="text-center">
{withCheckbox && <input type="checkbox" checked={isAllLinksSelected} className="vam" onChange={this.toggleSelectAllLinks} />}
</th>
<th width="20%">{gettext('Creator')}</th>
<th width="24%">{gettext('Name')}</th>
<th width="19%">{gettext('Link')}</th>
<th width="17%">{gettext('Expiration')}</th>
<th width="8%">{gettext('Visits')}</th>
<th width="7%"></th>
</tr>
);
};
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();
});
}
}
};
getMore = () => {
const { page, items } = this.state;
const { repo } = this.props;
repoShareAdminAPI.listRepoShareLinks(repo.repo_id, page + 1).then((res) => {
this.setState({
isLoadingMore: false,
hasMore: res.data.length == PER_PAGE,
page: page + 1,
items: items.concat(res.data)
});
}).catch(error => {
this.setState({
isLoadingMore: false
});
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
render() {
const { loading, isLoadingMore, errorMsg, items, isDeleteShareLinksDialogOpen } = this.state;
const selectedLinks = items.filter(item => item.isSelected);
const isAllLinksSelected = items.length == selectedLinks.length;
return (
<Fragment>
<div className="d-flex justify-content-between align-items-center pb-2 mt-1 pr-1 border-bottom">
<h6 className="font-weight-normal m-0">{gettext('Share Links')}</h6>
<div className="d-flex">
{selectedLinks.length > 0 && (
<>
<button className="btn btn-sm btn-secondary mr-2" onClick={this.cancelSelectAllLinks}>{gettext('Cancel')}</button>
<button className="btn btn-sm btn-secondary mr-2" onClick={this.toggleDeleteShareLinksDialog}>{gettext('Delete')}</button>
</>
)}
</div>
</div>
{loading && <Loading />}
{!loading && errorMsg && <p className="error text-center mt-8">{errorMsg}</p>}
{!loading && !errorMsg && !items.length &&
<EmptyTip text={gettext('No share links')}/>
}
{!loading && !errorMsg && items.length > 0 && (
<>
<table>
<thead>{this.getTheadContent(true, isAllLinksSelected)}</thead>
<tbody></tbody>
</table>
<div className='table-real-container' onScroll={this.handleScroll}>
<table className="table-hover table-thead-hidden">
<thead>{this.getTheadContent(false)}</thead>
<tbody>
{items.map((item, index) => {
return (
<Item
key={index}
item={item}
deleteItem={this.deleteItem}
toggleSelectLink={this.toggleSelectLink}
/>
);
})}
</tbody>
</table>
{isLoadingMore && <Loading />}
</div>
</>
)}
{isDeleteShareLinksDialogOpen && (
<CommonOperationConfirmationDialog
title={gettext('Delete share links')}
message={gettext('Are you sure you want to delete the selected share link(s) ?')}
executeOperation={this.deleteShareLinks}
confirmBtnText={gettext('Delete')}
toggleDialog={this.toggleDeleteShareLinksDialog}
/>
)}
</Fragment>
);
}
}
RepoShareAdminShareLinks.propTypes = propTypes;
export default RepoShareAdminShareLinks;