diff --git a/frontend/src/components/dialog/generate-share-link.js b/frontend/src/components/dialog/generate-share-link.js index bb65662c80..1f6fc7de6b 100644 --- a/frontend/src/components/dialog/generate-share-link.js +++ b/frontend/src/components/dialog/generate-share-link.js @@ -3,10 +3,10 @@ import PropTypes from 'prop-types'; import moment from 'moment'; import copy from 'copy-to-clipboard'; import { Button, Form, FormGroup, Label, Input, InputGroup, InputGroupAddon, Alert } from 'reactstrap'; -import { gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkPasswordMinLength, canSendShareLinkEmail } from '../../utils/constants'; +import { isPro, gettext, shareLinkExpireDaysMin, shareLinkExpireDaysMax, shareLinkExpireDaysDefault, shareLinkPasswordMinLength, canSendShareLinkEmail } from '../../utils/constants'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; -import SharedLinkInfo from '../../models/shared-link-info'; +import ShareLink from '../../models/share-link'; import toaster from '../toast'; import Loading from '../loading'; import SendLink from '../send-link'; @@ -43,7 +43,7 @@ class GenerateShareLink extends React.Component { 'can_edit': false, 'can_download': true }; - this.isOfficeFile = Utils.isOfficeFile(this.props.itemPath); + this.isOfficeFile = Utils.isEditableOfficeFile(this.props.itemPath); } componentDidMount() { @@ -51,7 +51,7 @@ class GenerateShareLink extends React.Component { let repoID = this.props.repoID; seafileAPI.getShareLink(repoID, path).then((res) => { if (res.data.length !== 0) { - let sharedLinkInfo = new SharedLinkInfo(res.data[0]); + let sharedLinkInfo = new ShareLink(res.data[0]); this.setState({ isLoading: false, sharedLinkInfo: sharedLinkInfo @@ -133,11 +133,10 @@ class GenerateShareLink extends React.Component { this.setState({errorInfo: ''}); let { itemPath, repoID } = this.props; let { password, isExpireChecked, expireDays } = this.state; - let permissions = this.permissions; - permissions = JSON.stringify(permissions); + let permissions = isPro ? JSON.stringify(this.permissions) : ''; const expireDaysSent = isExpireChecked ? expireDays : ''; seafileAPI.createShareLink(repoID, itemPath, password, expireDaysSent, permissions).then((res) => { - let sharedLinkInfo = new SharedLinkInfo(res.data); + let sharedLinkInfo = new ShareLink(res.data); this.setState({sharedLinkInfo: sharedLinkInfo}); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); @@ -402,6 +401,8 @@ class GenerateShareLink extends React.Component { )} + {isPro && ( + } + + )} {this.state.errorInfo && {gettext(this.state.errorInfo)}} diff --git a/frontend/src/components/dialog/share-link-permission-select.js b/frontend/src/components/dialog/share-link-permission-select.js new file mode 100644 index 0000000000..3855ee7e1a --- /dev/null +++ b/frontend/src/components/dialog/share-link-permission-select.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Modal, ModalBody } from 'reactstrap'; +import { Utils } from '../../utils/utils'; + +const propTypes = { + currentPerm: PropTypes.string.isRequired, + permissions: PropTypes.array.isRequired, + changePerm: PropTypes.func.isRequired, + toggleDialog: PropTypes.func.isRequired +}; + +class ShareLinkPermissionSelect extends React.Component { + + constructor(props) { + super(props); + + this.state = { + currentOption: this.props.currentPerm + }; + } + + switchOption = (e) => { + if (!e.target.checked) { + return; + } + + const currentOption = e.target.value; + this.setState({ + currentOption: currentOption + }); + + this.props.changePerm(currentOption); + this.props.toggleDialog(); + } + + render() { + const options = this.props.permissions; + const { currentOption } = this.state; + + return ( + + + {options.map((item, index) => { + return ( +
+ + +
+ ); + })} +
+
+ ); + } +} + +ShareLinkPermissionSelect.propTypes = propTypes; + +export default ShareLinkPermissionSelect; diff --git a/frontend/src/components/select-editor/share-links-permission-editor.js b/frontend/src/components/select-editor/share-link-permission-editor.js similarity index 66% rename from frontend/src/components/select-editor/share-links-permission-editor.js rename to frontend/src/components/select-editor/share-link-permission-editor.js index d83c2d5167..6f97855214 100644 --- a/frontend/src/components/select-editor/share-links-permission-editor.js +++ b/frontend/src/components/select-editor/share-link-permission-editor.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { gettext } from '../../utils/constants'; +import { Utils } from '../../utils/utils'; import SelectEditor from './select-editor'; const propTypes = { @@ -11,16 +11,10 @@ const propTypes = { onPermissionChanged: PropTypes.func.isRequired }; -class ShareLinksPermissionEditor extends React.Component { +class ShareLinkPermissionEditor extends React.Component { translatePermission = (permission) => { - if (permission === 'Preview only') { - return gettext('Preview only'); - } - - if (permission === 'Preview and download') { - return gettext('Preview and download'); - } + return Utils.getShareLinkPermissionObject(permission).text; } render() { @@ -37,6 +31,6 @@ class ShareLinksPermissionEditor extends React.Component { } } -ShareLinksPermissionEditor.propTypes = propTypes; +ShareLinkPermissionEditor.propTypes = propTypes; -export default ShareLinksPermissionEditor; +export default ShareLinkPermissionEditor; diff --git a/frontend/src/models/shared-link-info.js b/frontend/src/models/share-link.js similarity index 90% rename from frontend/src/models/shared-link-info.js rename to frontend/src/models/share-link.js index 9f887d3b70..47dd5afd39 100644 --- a/frontend/src/models/shared-link-info.js +++ b/frontend/src/models/share-link.js @@ -1,4 +1,4 @@ -class SharedLinkInfo { +class ShareLink { constructor(object) { this.repo_id = object.repo_id; @@ -18,4 +18,4 @@ class SharedLinkInfo { } -export default SharedLinkInfo; +export default ShareLink; diff --git a/frontend/src/pages/share-admin/share-links.js b/frontend/src/pages/share-admin/share-links.js index 6ae94bb67f..1e22395917 100644 --- a/frontend/src/pages/share-admin/share-links.js +++ b/frontend/src/pages/share-admin/share-links.js @@ -1,15 +1,18 @@ import React, { Component, Fragment } from 'react'; import { Link } from '@reach/router'; import moment from 'moment'; +import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap'; import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap'; import copy from '@seafile/seafile-editor/dist//utils/copy-to-clipboard'; import { seafileAPI } from '../../utils/seafile-api'; import { Utils } from '../../utils/utils'; -import { gettext, siteRoot, loginUrl, canGenerateUploadLink } from '../../utils/constants'; -import SharedLinkInfo from '../../models/shared-link-info'; -import ShareLinksPermissionEditor from '../../components/select-editor/share-links-permission-editor'; +import { isPro, gettext, siteRoot, loginUrl, canGenerateUploadLink } from '../../utils/constants'; +import ShareLink from '../../models/share-link'; +import ShareLinkPermissionEditor from '../../components/select-editor/share-link-permission-editor'; +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'; class Content extends Component { @@ -62,7 +65,7 @@ class Content extends Component { const { loading, errorMsg, items, sortBy, sortOrder } = this.props; if (loading) { - return ; + return ; } else if (errorMsg) { return

{errorMsg}

; } else { @@ -78,23 +81,34 @@ class Content extends Component { const sortByTime = sortBy == 'time'; const sortIcon = sortOrder == 'asc' ? : ; + const isDesktop = Utils.isDesktop(); + // only for some columns + const columnWidths = isPro ? ['14%', '7%', '14%'] : ['21%', '14%', '20%']; const table = ( - +
- - - - - - - - - + {isDesktop ? ( + + + + + {isPro && } + + + + + ) : ( + + + + + + )} {items.map((item, index) => { - return (); + return (); })}
{/*icon*/}{gettext('Name')} {sortByName && sortIcon}{gettext('Library')}{gettext('Permission')}{gettext('Visits')}{gettext('Expiration')} {sortByTime && sortIcon}{/*Operations*/}
{/*icon*/}{gettext('Name')} {sortByName && sortIcon}{gettext('Library')}{gettext('Permission')}{gettext('Visits')}{gettext('Expiration')} {sortByTime && sortIcon}{/*Operations*/}
@@ -121,20 +135,76 @@ class Item extends Component { constructor(props) { super(props); - let item = this.props.item; + const item = this.props.item; + + if (isPro) { + this.editOption = 'edit_download'; + this.permissionOptions = ['preview_download', 'preview_only']; + this.updatePermissionOptions(); + } + this.state = { - currentPermission: item.permissions.can_download ? 'Preview and download' : 'Preview only', - showOpIcon: false, + currentPermission: isPro ? this.getCurrentPermission() : '', + isOpIconShown: false, + isOpMenuOpen: false, // for mobile + isPermSelectDialogOpen: false // for mobile }; - this.permissionOptions = ['Preview only', 'Preview and download']; + } + + updatePermissionOptions = () => { + const item = this.props.item; + let options = this.permissionOptions; + + if (!Utils.isEditableOfficeFile(item.obj_name)) { + return ; + } + + if (item.permissions.can_edit) { + options.push(this.editOption); + return ; + } + + seafileAPI.getFileInfo(item.repo_id, item.path).then((res) => { + if (res.data.can_edit) { + options.push(this.editOption); + return ; + } + }).catch(error => { + return ; + }); + } + + getCurrentPermission = () => { + const options = this.permissionOptions; + const { can_edit, can_download } = this.props.item.permissions; + switch (`${can_edit} ${can_download}`) { + case 'false true': + return options[0]; + case 'false false': + return options[1]; + case 'true true': + return this.editOption; + } + } + + toggleOpMenu = () => { + this.setState({ + isOpMenuOpen: !this.state.isOpMenuOpen + }); + } + + togglePermSelectDialog = () => { + this.setState({ + isPermSelectDialogOpen: !this.state.isPermSelectDialogOpen + }); } handleMouseOver = () => { - this.setState({showOpIcon: true}); + this.setState({isOpIconShown: true}); } handleMouseOut = () => { - this.setState({showOpIcon: false}); + this.setState({isOpIconShown: false}); } viewLink = (e) => { @@ -147,22 +217,6 @@ class Item extends Component { this.props.onRemoveLink(this.props.item); } - getLinkParams = () => { - let item = this.props.item; - let iconUrl = ''; - let linkUrl = ''; - if (item.is_dir) { - let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1); - iconUrl = Utils.getFolderIconUrl(false); - linkUrl = `${siteRoot}library/${item.repo_id}/${item.repo_name}${Utils.encodePath(path)}`; - } else { - iconUrl = Utils.getFileIconUrl(item.obj_name); - linkUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`; - } - - return { iconUrl, linkUrl }; - } - renderExpriedData = () => { let item = this.props.item; if (!item.expire_date) { @@ -181,64 +235,112 @@ class Item extends Component { ); } - changePerm = (changed_to_permissions) => { + changePerm = (permission) => { const item = this.props.item; - let permissions = item.permissions; - - if (changed_to_permissions === 'Preview only') - permissions.can_download = false; - else if (changed_to_permissions === 'Preview and download') - permissions.can_download = true; - - seafileAPI.updateShareLink(item.token, JSON.stringify(permissions)).then(() => { + const permissionDetails = Utils.getShareLinkPermissionObject(permission).permissionDetails; + seafileAPI.updateShareLink(item.token, JSON.stringify(permissionDetails)).then(() => { this.setState({ - currentPermission: changed_to_permissions, + currentPermission: permission }); - let message = gettext("Successfully modified permission."); + let message = gettext('Successfully modified permission.'); toaster.success(message); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); - if (errMessage === gettext('Error')) { - errMessage = gettext("Failed to modify permission."); - } toaster.danger(errMessage); }); } render() { const item = this.props.item; - let { iconUrl, linkUrl } = this.getLinkParams(); + const { currentPermission, isOpIconShown, isPermSelectDialogOpen } = this.state; - let iconVisibility = this.state.showOpIcon ? '' : ' invisible'; - let linkIconClassName = 'sf2-icon-link action-icon' + iconVisibility; - let deleteIconClassName = 'sf2-icon-delete action-icon' + iconVisibility; - return ( + let iconUrl, linkUrl; + if (item.is_dir) { + let path = item.path === '/' ? '/' : item.path.slice(0, item.path.length - 1); + iconUrl = Utils.getFolderIconUrl(false); + linkUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}${Utils.encodePath(path)}`; + } else { + iconUrl = Utils.getFileIconUrl(item.obj_name); + linkUrl = `${siteRoot}lib/${item.repo_id}/file${Utils.encodePath(item.path)}`; + } + + const desktopItem = ( - + {item.is_dir ? {item.obj_name} : {item.obj_name} } - {item.repo_name} + {item.repo_name} + {isPro && - + } {item.view_cnt} {this.renderExpriedData()} - - + + ); + + const mobileItem = ( + + + + + {item.is_dir ? + {item.obj_name} : + {item.obj_name} + } + {isPro && {Utils.getShareLinkPermissionObject(currentPermission).text}} +
+ {item.repo_name}
+ {item.view_cnt}({gettext('Visits')}) + {this.renderExpriedData()}({gettext('Expiration')}) + + + + +
+
+
+ {(isPro && !item.is_expired) && {gettext('Permission')}} + {gettext('View')} + {gettext('Remove')} +
+
+
+ + + {isPermSelectDialogOpen && + + } +
+ ); + + return this.props.isDesktop ? desktopItem : mobileItem; } } @@ -305,9 +407,8 @@ class ShareAdminShareLinks extends Component { componentDidMount() { seafileAPI.listShareLinks().then((res) => { - // res: {data: Array(2), status: 200, statusText: "OK", headers: {…}, config: {…}, …} let items = res.data.map(item => { - return new SharedLinkInfo(item); + return new ShareLink(item); }); this.setState({ loading: false, @@ -327,7 +428,6 @@ class ShareAdminShareLinks extends Component { errorMsg: gettext('Error') }); } - } else { this.setState({ loading: false, @@ -343,13 +443,10 @@ class ShareAdminShareLinks extends Component { return uploadItem.token !== item.token; }); this.setState({items: items}); - let message = gettext("Successfully deleted share link."); + let message = gettext('Successfully deleted 1 item.'); toaster.success(message); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); - if (errMessage === gettext('Error')) { - errMessage = gettext("Failed to delete share link."); - } toaster.danger(errMessage); }); } diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index ce8ce32c48..3ed85188c6 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -108,13 +108,13 @@ export const Utils = { } }, - isOfficeFile: function(filename) { + isEditableOfficeFile: function(filename) { // no file ext if (filename.lastIndexOf('.') == -1) { return false; } var file_ext = filename.substr(filename.lastIndexOf('.') + 1).toLowerCase(); - var exts = ['doc', 'ppt', 'xls', 'docx', 'pptx', 'xlsx']; + var exts = ['docx', 'pptx', 'xlsx']; if (exts.indexOf(file_ext) != -1) { return true; } else { @@ -421,6 +421,38 @@ export const Utils = { return title; }, + getShareLinkPermissionObject: function(permission) { + switch (permission) { + case 'preview_download': + return { + value: permission, + text: gettext('Preview and download'), + permissionDetails: { + 'can_edit': false, + "can_download": true + } + }; + case 'preview_only': + return { + value: permission, + text: gettext('Preview only'), + permissionDetails: { + 'can_edit': false, + "can_download": false + } + }; + case 'edit_download': + return { + value: permission, + text: gettext('Edit on cloud and download'), + permissionDetails: { + 'can_edit': true, + "can_download": true + } + }; + } + }, + formatSize: function(options) { /* * param: {bytes, precision}