diff --git a/frontend/src/components/dialog/perm-select.js b/frontend/src/components/dialog/perm-select.js new file mode 100644 index 0000000000..814410f873 --- /dev/null +++ b/frontend/src/components/dialog/perm-select.js @@ -0,0 +1,65 @@ +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 PermSelect 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 ( +
+ + +
+ ); + })} +
+
+ ); + } +} + +PermSelect.propTypes = propTypes; + +export default PermSelect; diff --git a/frontend/src/components/toolbar/invitations-toolbar.js b/frontend/src/components/toolbar/invitations-toolbar.js index 63999e0c53..af5880899e 100644 --- a/frontend/src/components/toolbar/invitations-toolbar.js +++ b/frontend/src/components/toolbar/invitations-toolbar.js @@ -24,16 +24,15 @@ class InvitationsToolbar extends React.Component {
- + {window.innerWidth >= 768 ? (
-
- - - + ) : ( + + )}
diff --git a/frontend/src/css/files-activities.css b/frontend/src/css/files-activities.css index 36ce527858..31ef2e24d4 100644 --- a/frontend/src/css/files-activities.css +++ b/frontend/src/css/files-activities.css @@ -5,15 +5,6 @@ .activity-details:hover { color: #333; } -.mobile-activity-op { - display: inline-block; - margin: 0 0 .2em .8em; - padding: 0 .5em; - background: #ffbd6f; - border-radius: 2px; - color: #fff; - font-size: 0.75rem; -} .mobile-activity-time { display: inline-block; margin-bottom: .2em; diff --git a/frontend/src/models/shared-repo-info.js b/frontend/src/models/shared-repo-info.js index b4b08ddcc6..e83cb6a9f3 100644 --- a/frontend/src/models/shared-repo-info.js +++ b/frontend/src/models/shared-repo-info.js @@ -13,8 +13,8 @@ class SharedRepoInfo { this.is_admin = object.is_admin; this.user_name = object.user_name; this.user_email = object.user_email; - this.contact_email = this.contact_email; - } else if(this.share_type === 'group') { + this.contact_email = object.contact_email; + } else if (this.share_type === 'group') { this.is_admin = object.is_admin; this.group_id = object.group_id; this.group_name = object.group_name; diff --git a/frontend/src/pages/dashboard/files-activities.js b/frontend/src/pages/dashboard/files-activities.js index 7bd0cd6350..ac1f12837d 100644 --- a/frontend/src/pages/dashboard/files-activities.js +++ b/frontend/src/pages/dashboard/files-activities.js @@ -269,7 +269,7 @@ class ActivityItem extends Component { {item.author_name} - {op} + {op}
{details} diff --git a/frontend/src/pages/invitations/invitations-view.js b/frontend/src/pages/invitations/invitations-view.js index 8b2dad0dcf..1769fe2c69 100644 --- a/frontend/src/pages/invitations/invitations-view.js +++ b/frontend/src/pages/invitations/invitations-view.js @@ -1,7 +1,8 @@ import React, { Component, Fragment } from 'react'; +import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap'; import PropTypes from 'prop-types'; import moment from 'moment'; -import { gettext, siteRoot, loginUrl, canInvitePeople } from '../../utils/constants'; +import { gettext, loginUrl } from '../../utils/constants'; import { Utils } from '../../utils/utils'; import { seafileAPI } from '../../utils/seafile-api'; import InvitationsToolbar from '../../components/toolbar/invitations-toolbar'; @@ -19,10 +20,17 @@ class Item extends React.Component { super(props); this.state = { isOpIconShown: false, + isOpMenuOpen: false, // for mobile isRevokeDialogOpen: false }; } + toggleOpMenu = () => { + this.setState({ + isOpMenuOpen: !this.state.isOpMenuOpen + }); + } + onMouseEnter = () => { this.setState({ isOpIconShown: true @@ -70,32 +78,70 @@ class Item extends React.Component { return null; } - const invitationItem = this.props.invitation; - const operation = invitationItem.accept_time ? - - : - - ; + const item = this.props.invitation; + + const desktopItem = ( + + {item.accepter} + {moment(item.invite_time).format('YYYY-MM-DD')} + {moment(item.expire_time).format('YYYY-MM-DD')} + {item.accept_time && } + + {isOpIconShown && ( + item.accept_time ? + + : + + + )} + + + ); + + const mobileItem = ( + + + {item.accepter}
+ {moment(item.invite_time).format('YYYY-MM-DD')}({gettext('Invite Time')}) + {moment(item.expire_time).format('YYYY-MM-DD')}({gettext('Expiration')}) + {item.accept_time && gettext('Accepted')} + + + + +
+
+
+ {item.accept_time ? + {gettext('Revoke Access')} : + {gettext('Delete')} + } +
+
+
+ + + ); return ( - - {invitationItem.accepter} - {moment(invitationItem.invite_time).format('YYYY-MM-DD')} - {moment(invitationItem.expire_time).format('YYYY-MM-DD')} - {invitationItem.accept_time && } - {isOpIconShown && operation} - + {this.props.isDesktop ? desktopItem : mobileItem} {isRevokeDialogOpen && @@ -138,23 +184,32 @@ class Content extends Component { ); } + const isDesktop = Utils.isDesktop(); return ( - +
- - - - - - - + {isDesktop ? + + + + + + + + : + + + + + } {invitationsList.map((invitation, index) => { return ( ); })} diff --git a/frontend/src/pages/share-admin/libraries.js b/frontend/src/pages/share-admin/libraries.js index 9b67c9df23..4819f319f3 100644 --- a/frontend/src/pages/share-admin/libraries.js +++ b/frontend/src/pages/share-admin/libraries.js @@ -1,5 +1,6 @@ -import React, { Component } from 'react'; +import React, { Fragment, Component } from 'react'; import { Link } from '@reach/router'; +import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap'; import { seafileAPI } from '../../utils/seafile-api'; import { gettext, siteRoot, loginUrl, isPro } from '../../utils/constants'; import { Utils } from '../../utils/utils'; @@ -7,6 +8,7 @@ import toaster from '../../components/toast'; import EmptyTip from '../../components/empty-tip'; import SharePermissionEditor from '../../components/select-editor/share-permission-editor'; import SharedRepoInfo from '../../models/shared-repo-info'; +import PermSelect from '../../components/dialog/perm-select'; class Content extends Component { @@ -36,20 +38,34 @@ class Content extends Component { const sortByName = sortBy == 'name'; const sortIcon = sortOrder == 'asc' ? : ; + const isDesktop = Utils.isDesktop(); const table = ( -
{gettext('Email')}{gettext('Invite Time')}{gettext('Expiration')}{gettext('Accepted')}
{gettext('Email')}{gettext('Invite Time')}{gettext('Expiration')}{gettext('Accepted')}
+
- - - - - - - + {isDesktop ? ( + + + + + + + + ) : ( + + + + + + )} {items.map((item, index) => { - return (); + return (); })}
{/*icon*/}{gettext('Name')} {sortByName && sortIcon}{gettext('Share To')}{gettext('Permission')}
{/*icon*/}{gettext('Name')} {sortByName && sortIcon}{gettext('Share To')}{gettext('Permission')}
@@ -69,21 +85,40 @@ class Item extends Component { this.state = { share_permission: item.share_permission, is_admin: item.is_admin, - showOpIcon: false, + isOpIconShown: false, + isOpMenuOpen: false, // for mobile + isPermSelectDialogOpen: false, // for mobile unshared: false }; - this.permissions = ['rw', 'r']; + let permissions = ['rw', 'r']; + this.permissions = permissions; + this.showAdmin = isPro && (item.share_type !== 'public'); + if (this.showAdmin) { + permissions.push('admin'); + } if (isPro) { - this.permissions = ['rw', 'r', 'cloud-edit', 'preview']; + permissions.push('cloud-edit', 'preview'); } } + toggleOpMenu = () => { + this.setState({ + isOpMenuOpen: !this.state.isOpMenuOpen + }); + } + + togglePermSelectDialog = () => { + this.setState({ + isPermSelectDialogOpen: !this.state.isPermSelectDialogOpen + }); + } + onMouseEnter = () => { - this.setState({showOpIcon: true}); + this.setState({isOpIconShown: true}); } onMouseLeave = () => { - this.setState({showOpIcon: false}); + this.setState({isOpIconShown: false}); } changePerm = (permission) => { @@ -97,8 +132,6 @@ class Item extends Component { options.user = item.user_email; } else if (share_type == 'group') { options.group_id = item.group_id; - } else if (share_type === 'public') { - // nothing todo } seafileAPI.updateRepoSharePerm(item.repo_id, options).then(() => { @@ -106,72 +139,102 @@ class Item extends Component { share_permission: permission == 'admin' ? 'rw' : permission, is_admin: permission == 'admin', }); + toaster.success(gettext('Successfully modified permission.')); }).catch((error) => { let errMessage = Utils.getErrorMsg(error); toaster.danger(errMessage); }); } - unshare = () => { - this.props.unshareFolder(this.props.item); - } - - getRepoParams = () => { - let item = this.props.item; - - let iconUrl = Utils.getLibIconUrl(item); - let iconTitle = Utils.getLibIconTitle(item); - let repoUrl = `${siteRoot}library/${item.repo_id}/${item.repo_name}/`; - - return { iconUrl, iconTitle, repoUrl }; + unshare = (e) => { + e.preventDefault(); + this.props.unshareItem(this.props.item); } render() { - let { iconUrl, iconTitle, repoUrl } = this.getRepoParams(); let item = this.props.item; - let { share_permission, is_admin } = this.state; + let iconUrl = Utils.getLibIconUrl(item); + let iconTitle = Utils.getLibIconTitle(item); + let repoUrl = `${siteRoot}library/${item.repo_id}/${encodeURIComponent(item.repo_name)}/`; + + let { share_permission, is_admin, isOpIconShown, isPermSelectDialogOpen } = this.state; let shareTo; const shareType = item.share_type; if (shareType == 'personal') { - shareTo = {item.user_name}; + shareTo = item.user_name; } else if (shareType == 'group') { - shareTo = {item.group_name}; + shareTo = item.group_name; } else if (shareType == 'public') { - shareTo = {gettext('all members')}; + shareTo = gettext('all members'); } - // show 'admin' perm or not - let showAdmin = isPro && (item.share_type !== 'public'); - if (showAdmin && is_admin) { + if (this.showAdmin && is_admin) { share_permission = 'admin'; } - let iconVisibility = this.state.showOpIcon ? '' : ' invisible'; - let unshareIconClassName = 'unshare action-icon sf2-icon-x3' + iconVisibility; - - if (showAdmin && this.permissions.indexOf('admin') === -1) { - this.permissions.splice(2, 0, 'admin'); // add a item after 'r' permission; - } - - return ( + const desktopItem = ( {iconTitle} {item.repo_name} - {shareTo} + + {item.share_type == 'personal' ? {shareTo} : shareTo} + - + ); + + const mobileItem = ( + + + {iconTitle} + + {item.repo_name} + {Utils.sharePerms(share_permission)} +
+ {`${gettext('Share To:')} ${shareTo}`} + + + + +
+
+
+ {gettext('Permission')} + {gettext('Unshare')} +
+
+
+ + + {isPermSelectDialogOpen && + + } +
+ ); + + return this.props.isDesktop ? desktopItem : mobileItem; } } @@ -221,7 +284,7 @@ class ShareAdminLibraries extends Component { }); } - unshareFolder = (item) => { + unshareItem = (item) => { const share_type = item.share_type; let options = { 'share_type': share_type @@ -282,7 +345,7 @@ class ShareAdminLibraries extends Component { sortBy={this.state.sortBy} sortOrder={this.state.sortOrder} sortItems={this.sortItems} - unshareFolder={this.unshareFolder} + unshareItem={this.unshareItem} /> diff --git a/frontend/src/utils/utils.js b/frontend/src/utils/utils.js index c60602255d..7069f20c5c 100644 --- a/frontend/src/utils/utils.js +++ b/frontend/src/utils/utils.js @@ -33,6 +33,10 @@ export const Utils = { } }, + isDesktop: function() { + return window.innerWidth >= 768; + }, + FILEEXT_ICON_MAP: { // text file @@ -90,7 +94,7 @@ export const Utils = { }, // check if a file is an image - imageCheck: function (filename) { + imageCheck: function(filename) { // no file ext if (filename.lastIndexOf('.') == -1) { return false; @@ -119,7 +123,7 @@ export const Utils = { }, // check if a file is a video - videoCheck: function (filename) { + videoCheck: function(filename) { // no file ext if (filename.lastIndexOf('.') == -1) { return false; diff --git a/media/css/seahub_react.css b/media/css/seahub_react.css index 28e94e8803..d88ec6cb01 100644 --- a/media/css/seahub_react.css +++ b/media/css/seahub_react.css @@ -1133,6 +1133,16 @@ a.table-sort-op:focus { font-size: 12px; color: #666; } + + .item-meta-info-highlighted { + display: inline-block; + margin: 0 0 .2em .8em; + padding: 0 .5em; + background: #ffbd6f; + border-radius: 2px; + color: #fff; + font-size: 0.75rem; + } .mobile-operation-menu-bg-layer { position: fixed;