import React, { Component, Fragment } from 'react'; import PropTypes from 'prop-types'; import { Link } from '@gatsbyjs/reach-router'; import dayjs from 'dayjs'; import { DropdownItem } from 'reactstrap'; import classnames from 'classnames'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; import { isPro, gettext, siteRoot, canGenerateUploadLink } from '../../utils/constants'; import ShareLink from '../../models/share-link'; import Loading from '../../components/loading'; import toaster from '../../components/toast'; import EmptyTip from '../../components/empty-tip'; import ShareLinkPermissionSelect from '../../components/dialog/share-link-permission-select'; import ShareAdminLink from '../../components/dialog/share-admin-link'; import CommonOperationConfirmationDialog from '../../components/dialog/common-operation-confirmation-dialog'; import Selector from '../../components/single-selector'; import SingleDropdownToolbar from '../../components/toolbar/single-dropdown-toolbar'; import FixedWidthTable from '../../components/common/fixed-width-table'; import MobileItemMenu from '../../components/mobile-item-menu'; const contentPropTypes = { loading: PropTypes.bool.isRequired, isLoadingMore: 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 { constructor(props) { super(props); this.state = { isItemFreezed: false }; } toggleItemFreezed = (isFreezed) => { this.setState({ isItemFreezed: isFreezed }); }; toggleSelectAllLinks = (e) => { this.props.toggleSelectAllLinks(e.target.checked); }; render() { const { loading, errorMsg, items } = this.props; if (loading) { return ; } if (errorMsg) { return

{errorMsg}

; } if (!items.length) { return ( ); } const selectedItems = items.filter(item => item.isSelected); const isAllLinksSelected = selectedItems.length == items.length; const isDesktop = Utils.isDesktop(); // only for some columns const columnWidths = isPro ? [0.14, 0.07, 0.14] : [0.21, 0.14, 0.2]; return ( <> ) }, // checkbox { isFixed: true, width: 40 }, // icon { isFixed: false, width: 0.35, children: gettext('Name') }, { isFixed: false, width: columnWidths[0], children: gettext('Library') }, isPro ? { isFixed: false, width: 0.2, children: gettext('Permission') } : null, { isFixed: false, width: columnWidths[1], children: gettext('Visits') }, { isFixed: false, width: columnWidths[2], children: gettext('Expiration') }, { isFixed: false, width: 0.1 }, // operations ].filter(i => i) : [ { isFixed: false, width: 0.12 }, { isFixed: false, width: 0.8 }, { isFixed: false, width: 0.08 }, ]} > {items.map((item, index) => { return (); })} {this.props.isLoadingMore &&
} ); } } Content.propTypes = contentPropTypes; const itemPropTypes = { item: PropTypes.object.isRequired, isDesktop: PropTypes.bool.isRequired, onRemoveLink: PropTypes.func.isRequired, isItemFreezed: PropTypes.bool.isRequired, toggleItemFreezed: PropTypes.func.isRequired, toggleSelectLink: PropTypes.func.isRequired }; class Item extends Component { constructor(props) { super(props); this.state = { highlight: false, isOpIconShown: false, isPermSelectDialogOpen: false, // for mobile isLinkDialogOpen: false, permissionOptions: [], currentPermission: '', }; } componentDidMount() { if (isPro) { this.updatePermissionOptions(); } } updatePermissionOptions = () => { const item = this.props.item; let itemType = item.is_dir ? (item.path === '/' ? 'library' : 'dir') : 'file'; let permission = item.repo_folder_permission; let permissionOptions = Utils.getShareLinkPermissionList(itemType, permission, item.path, item.can_edit); let currentPermission = Utils.getShareLinkPermissionStr(this.props.item.permissions); this.setState({ permissionOptions: permissionOptions, currentPermission: currentPermission }); }; togglePermSelectDialog = () => { this.setState({ isPermSelectDialogOpen: !this.state.isPermSelectDialogOpen }); }; toggleLinkDialog = () => { this.setState({ isLinkDialogOpen: !this.state.isLinkDialogOpen }); }; handleMouseEnter = () => { if (!this.props.isItemFreezed) { this.setState({ isOpIconShown: true, highlight: true }); } }; handleMouseLeave = () => { if (!this.props.isItemFreezed) { this.setState({ isOpIconShown: false, highlight: false }); } }; viewLink = (e) => { e.preventDefault(); this.toggleLinkDialog(); }; removeLink = (e) => { e.preventDefault(); this.props.onRemoveLink(this.props.item); }; renderExpiration = () => { const item = this.props.item; if (!item.expire_date) { return '--'; } const expire_date = dayjs(item.expire_date).format('YYYY-MM-DD'); const expire_time = dayjs(item.expire_date).format('YYYY-MM-DD HH:mm:ss'); return ({expire_date}); }; // for 'selector' in desktop changePermission = (permOption) => { this.changePerm(permOption.value); }; changePerm = (permission) => { const item = this.props.item; const permissionDetails = Utils.getShareLinkPermissionObject(permission).permissionDetails; seafileAPI.updateShareLink(item.token, JSON.stringify(permissionDetails)).then(() => { this.setState({ currentPermission: permission }); let message = gettext('Successfully modified permission.'); toaster.success(message); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); }; onCheckboxClicked = (e) => { e.stopPropagation(); }; toggleSelectLink = (e) => { const { item } = this.props; this.props.toggleSelectLink(item, e.target.checked); }; render() { const { item } = this.props; const { isSelected = false } = item; const { highlight, currentPermission, permissionOptions, isOpIconShown, isPermSelectDialogOpen, isLinkDialogOpen } = this.state; this.permOptions = permissionOptions.map(item => { return { value: item, text: Utils.getShareLinkPermissionObject(item).text, isSelected: item == currentPermission }; }); const currentSelectedPermOption = this.permOptions.filter(item => item.isSelected)[0] || {}; let iconUrl; let objUrl; if (item.is_dir) { let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1); iconUrl = Utils.getFolderIconUrl(false); objUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`; } else { iconUrl = Utils.getFileIconUrl(item.obj_name); objUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`; } return ( {this.props.isDesktop ? {item.is_dir ? {item.obj_name} : {item.obj_name} } {item.obj_id === '' ? {gettext('(deleted)')} : null} {item.repo_name} {isPro && } {item.view_cnt} {this.renderExpiration()} {!item.is_expired && } : {item.is_dir ? {item.obj_name} : {item.obj_name} } {isPro && {Utils.getShareLinkPermissionObject(currentPermission).text}}
{item.repo_name}
{gettext('Visits')}: {item.view_cnt} {gettext('Expiration')}: {this.renderExpiration()} {(isPro && !item.is_expired) && {gettext('Permission')} } {!item.is_expired && {gettext('View')} } {gettext('Remove')} {isPermSelectDialogOpen && }
} {isLinkDialogOpen && }
); } } Item.propTypes = itemPropTypes; const PER_PAGE = 25; class ShareAdminShareLinks extends Component { constructor(props) { super(props); this.state = { loading: true, hasMore: false, isLoadingMore: false, page: 1, errorMsg: '', items: [], isCleanInvalidShareLinksDialogOpen: false, isDeleteShareLinksDialogOpen: false }; } componentDidMount() { this.listUserShareLinks(); } listUserShareLinks() { const { page } = this.state; seafileAPI.listShareLinks({ page }).then((res) => { let items = res.data.map(item => { return new ShareLink(item); }); this.setState({ loading: false, hasMore: res.data.length == PER_PAGE, items, }); }).catch((error) => { this.setState({ loading: 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(); }); } } }; getMore = () => { const { page } = this.state; seafileAPI.listShareLinks({ page: page + 1 }).then((res) => { let moreItems = res.data.map(item => { return new ShareLink(item); }); this.setState({ isLoadingMore: false, hasMore: res.data.length == PER_PAGE, page: page + 1, items: this.state.items.concat(moreItems), }); }).catch((error) => { this.setState({ isLoadingMore: false, errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403 }); }); }; onRemoveLink = (item) => { seafileAPI.deleteShareLink(item.token).then(() => { let items = this.state.items.filter(uploadItem => { return uploadItem.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); }); }; toggleCleanInvalidShareLinksDialog = () => { this.setState({ isCleanInvalidShareLinksDialogOpen: !this.state.isCleanInvalidShareLinksDialogOpen }); }; cleanInvalidShareLinks = () => { seafileAPI.cleanInvalidShareLinks().then(res => { const newItems = this.state.items.filter(item => item.obj_id !== '').filter(item => !item.is_expired); this.setState({ items: newItems }); toaster.success(gettext('Successfully cleaned invalid share links.')); }).catch(error => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); }; 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 (
0 })}> {selectedLinksLen > 0 ? (
{`${selectedLinksLen} ${gettext('selected')}`}
) : (
  • {gettext('Share Links')}
  • {canGenerateUploadLink && (
  • {gettext('Upload Links')}
  • )}
) }
{this.state.isCleanInvalidShareLinksDialogOpen && } {this.state.isDeleteShareLinksDialogOpen && ( )}
); } } export default ShareAdminShareLinks;