+
+
+ |
 |
{item.is_dir ?
@@ -345,13 +382,14 @@ class ShareAdminShareLinks extends Component {
constructor(props) {
super(props);
this.state = {
- isCleanInvalidShareLinksDialogOpen: false,
loading: true,
hasMore: false,
isLoadingMore: false,
page: 1,
errorMsg: '',
items: [],
+ isCleanInvalidShareLinksDialogOpen: false,
+ isDeleteShareLinksDialogOpen: false
};
}
@@ -441,25 +479,103 @@ class ShareAdminShareLinks extends Component {
});
};
+ toggleDeleteShareLinksDialog = () => {
+ this.setState({ isDeleteShareLinksDialogOpen: !this.state.isDeleteShareLinksDialogOpen });
+ };
+
+ 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: shareLinks } = this.state;
+ const tokens = shareLinks.filter(item => item.isSelected).map(link => link.token);
+ seafileAPI.deleteShareLinks(tokens).then(res => {
+ const { success, failed } = res.data;
+ if (success.length) {
+ let newShareLinkList = shareLinks.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);
+ });
+ };
+
render() {
+ const { items } = this.state;
+ const selectedLinksLen = items.filter(item => item.isSelected).length;
return (
-
-
- -
-
- {gettext('Share Links')}
-
-
-
- {canGenerateUploadLink && (
- - {gettext('Upload Links')}
- )}
-
+ 0 })}>
+ {selectedLinksLen > 0
+ ? (
+
+
+
+ {`${selectedLinksLen} ${gettext('selected')}`}
+
+
+
+
+
+ )
+ : (
+
+ -
+
+ {gettext('Share Links')}
+
+
+
+ {canGenerateUploadLink && (
+ - {gettext('Upload Links')}
+ )}
+
+ )
+ }
@@ -481,6 +599,15 @@ class ShareAdminShareLinks extends Component {
toggleDialog={this.toggleCleanInvalidShareLinksDialog}
/>
}
+ {this.state.isDeleteShareLinksDialogOpen && (
+
+ )}
);
}
diff --git a/frontend/src/pages/share-admin/upload-links.js b/frontend/src/pages/share-admin/upload-links.js
index 7baaf23c1b..e67ae7e364 100644
--- a/frontend/src/pages/share-admin/upload-links.js
+++ b/frontend/src/pages/share-admin/upload-links.js
@@ -6,6 +6,7 @@ import { DropdownItem } from 'reactstrap';
import classnames from 'classnames';
import { gettext, siteRoot, canGenerateShareLink } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
+import { repoShareAdminAPI } from '../../utils/repo-share-admin-api';
import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
import Loading from '../../components/loading';
@@ -17,15 +18,23 @@ import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-tool
import FixedWidthTable from '../../components/common/fixed-width-table';
import MobileItemMenu from '../../components/mobile-item-menu';
+import '../../css/share-admin-links.css';
+
const contentPropTypes = {
loading: PropTypes.bool.isRequired,
errorMsg: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
+ toggleSelectAllLinks: PropTypes.func.isRequired,
+ toggleSelectLink: PropTypes.func.isRequired,
onRemoveLink: PropTypes.func.isRequired
};
class Content extends Component {
+ toggleSelectAllLinks = (e) => {
+ this.props.toggleSelectAllLinks(e.target.checked);
+ };
+
render() {
const { loading, errorMsg, items } = this.props;
@@ -44,11 +53,20 @@ class Content extends Component {
);
}
+ const selectedItems = items.filter(item => item.isSelected);
+ const isAllLinksSelected = selectedItems.length == items.length;
+
const isDesktop = Utils.isDesktop();
return (
)
+ }, // checkbox
{ isFixed: true, width: 40 }, // icon
{ isFixed: false, width: 0.33, children: gettext('Name') },
{ isFixed: false, width: 0.25, children: gettext('Library') },
@@ -62,7 +80,7 @@ class Content extends Component {
]}
>
{items.map((item, index) => {
- return ( - );
+ return (
);
})}
);
@@ -74,7 +92,8 @@ Content.propTypes = contentPropTypes;
const itemPropTypes = {
isDesktop: PropTypes.bool.isRequired,
item: PropTypes.object.isRequired,
- onRemoveLink: PropTypes.func.isRequired
+ onRemoveLink: PropTypes.func.isRequired,
+ toggleSelectLink: PropTypes.func.isRequired
};
class Item extends Component {
@@ -82,6 +101,7 @@ class Item extends Component {
constructor(props) {
super(props);
this.state = {
+ highlight: false,
isOpIconShown: false,
isLinkDialogOpen: false
};
@@ -94,11 +114,17 @@ class Item extends Component {
};
handleMouseOver = () => {
- this.setState({ isOpIconShown: true });
+ this.setState({
+ highlight: true,
+ isOpIconShown: true
+ });
};
handleMouseOut = () => {
- this.setState({ isOpIconShown: false });
+ this.setState({
+ highlight: false,
+ isOpIconShown: false
+ });
};
viewLink = (e) => {
@@ -121,9 +147,19 @@ class Item extends Component {
return ({expire_date});
};
+ onCheckboxClicked = (e) => {
+ e.stopPropagation();
+ };
+
+ toggleSelectLink = (e) => {
+ const { item } = this.props;
+ this.props.toggleSelectLink(item, e.target.checked);
+ };
+
render() {
- let item = this.props.item;
- const { isOpIconShown, isLinkDialogOpen } = this.state;
+ const { item } = this.props;
+ const { isSelected = false } = item;
+ const { highlight, isOpIconShown, isLinkDialogOpen } = this.state;
const iconUrl = Utils.getFolderIconUrl(false);
const repoUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}`;
@@ -132,7 +168,24 @@ class Item extends Component {
return (
{this.props.isDesktop ?
-
+
+
+
+ |
 |
{item.obj_name}
@@ -184,10 +237,11 @@ class ShareAdminUploadLinks extends Component {
constructor(props) {
super(props);
this.state = {
- isCleanInvalidUploadLinksDialogOpen: false,
loading: true,
errorMsg: '',
- items: []
+ items: [],
+ isCleanInvalidUploadLinksDialogOpen: false,
+ isDeleteLinksDialogOpen: false
};
}
@@ -241,25 +295,103 @@ class ShareAdminUploadLinks extends Component {
});
};
+ toggleDeleteLinksDialog = () => {
+ this.setState({ isDeleteLinksDialogOpen: !this.state.isDeleteLinksDialogOpen });
+ };
+
+ 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;
+ })
+ });
+ };
+
+ deleteLinks = () => {
+ const { items } = this.state;
+ const tokens = items.filter(item => item.isSelected).map(link => link.token);
+ repoShareAdminAPI.deleteUploadLinks(tokens).then(res => {
+ const { success, failed } = res.data;
+ if (success.length) {
+ let newItems = items.filter(item => {
+ return !success.some(deletedItem => {
+ return deletedItem.token == item.token;
+ });
+ });
+ this.setState({
+ items: newItems
+ });
+ const length = success.length;
+ const msg = length == 1 ?
+ gettext('Successfully deleted 1 upload link') :
+ gettext('Successfully deleted {number_placeholder} upload 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);
+ });
+ };
+
render() {
+ const { items } = this.state;
+ const selectedLinksLen = items.filter(item => item.isSelected).length;
return (
-
-
- {canGenerateShareLink && (
- - {gettext('Share Links')}
- )}
- -
-
- {gettext('Upload Links')}
-
-
-
-
+ 0 })}>
+ {selectedLinksLen > 0
+ ? (
+
+
+
+ {`${selectedLinksLen} ${gettext('selected')}`}
+
+
+
+
+
+ )
+ : (
+
+ {canGenerateShareLink && (
+ - {gettext('Share Links')}
+ )}
+ -
+
+ {gettext('Upload Links')}
+
+
+
+
+ )
+ }
@@ -280,6 +414,15 @@ class ShareAdminUploadLinks extends Component {
toggleDialog={this.toggleCleanInvalidUploadLinksDialog}
/>
}
+ {this.state.isDeleteLinksDialogOpen && (
+
+ )}
);
}
| |