mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-09 10:50:24 +00:00
Monitor shared repos (#5375)
* [shared with me] added 'watch/unwatch file changes' for libraries * [groups, group] added 'watch/unwatch file changes' for libraries * [shared with me] only libraries shared with 'r', 'rw' permissions can be monitored * [groups, group] only libraries shared with 'r', 'rw' permissions can be monitored * [shared with me] improved the interaction for library items * [groups, group] cleaned up the code
This commit is contained in:
33
frontend/src/components/repo-monitored-icon.js
Normal file
33
frontend/src/components/repo-monitored-icon.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import React, { Fragment } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { UncontrolledTooltip } from 'reactstrap';
|
||||||
|
import Icon from '../components/icon';
|
||||||
|
import { gettext } from '../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
repoID: PropTypes.string.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class RepoMonitoredIcon extends React.Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { repoID } = this.props;
|
||||||
|
return (
|
||||||
|
<Fragment>
|
||||||
|
<span id={`watching-${repoID}`} className="ml-1">
|
||||||
|
<Icon symbol='monitor' />
|
||||||
|
</span>
|
||||||
|
<UncontrolledTooltip
|
||||||
|
placement="bottom"
|
||||||
|
target={`#watching-${repoID}`}
|
||||||
|
>
|
||||||
|
{gettext('You are watching file changes of this library.')}
|
||||||
|
</UncontrolledTooltip>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RepoMonitoredIcon.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default RepoMonitoredIcon;
|
@@ -17,6 +17,7 @@ import LibHistorySettingDialog from '../dialog/lib-history-setting-dialog';
|
|||||||
import toaster from '../toast';
|
import toaster from '../toast';
|
||||||
import RepoAPITokenDialog from '../dialog/repo-api-token-dialog';
|
import RepoAPITokenDialog from '../dialog/repo-api-token-dialog';
|
||||||
import RepoShareUploadLinksDialog from '../dialog/repo-share-upload-links-dialog';
|
import RepoShareUploadLinksDialog from '../dialog/repo-share-upload-links-dialog';
|
||||||
|
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
currentGroup: PropTypes.object,
|
currentGroup: PropTypes.object,
|
||||||
@@ -29,6 +30,7 @@ const propTypes = {
|
|||||||
onItemDetails: PropTypes.func,
|
onItemDetails: PropTypes.func,
|
||||||
onItemRename: PropTypes.func,
|
onItemRename: PropTypes.func,
|
||||||
onItemDelete: PropTypes.func,
|
onItemDelete: PropTypes.func,
|
||||||
|
onMonitorRepo: PropTypes.func
|
||||||
};
|
};
|
||||||
|
|
||||||
class SharedRepoListItem extends React.Component {
|
class SharedRepoListItem extends React.Component {
|
||||||
@@ -163,11 +165,36 @@ class SharedRepoListItem extends React.Component {
|
|||||||
case 'Reset Password':
|
case 'Reset Password':
|
||||||
this.onResetPasswordToggle();
|
this.onResetPasswordToggle();
|
||||||
break;
|
break;
|
||||||
default:
|
case 'Watch File Changes':
|
||||||
|
this.watchFileChanges();
|
||||||
break;
|
break;
|
||||||
|
case 'Unwatch File Changes':
|
||||||
|
this.unwatchFileChanges();
|
||||||
|
break;
|
||||||
|
// no default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchFileChanges = () => {
|
||||||
|
const { repo } = this.props;
|
||||||
|
seafileAPI.monitorRepo(repo.repo_id).then(() => {
|
||||||
|
this.props.onMonitorRepo(repo, true);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unwatchFileChanges = () => {
|
||||||
|
const { repo } = this.props;
|
||||||
|
seafileAPI.unMonitorRepo(repo.repo_id).then(() => {
|
||||||
|
this.props.onMonitorRepo(repo, false);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
onItemRenameToggle = () => {
|
onItemRenameToggle = () => {
|
||||||
this.props.onFreezedItem();
|
this.props.onFreezedItem();
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -294,6 +321,12 @@ class SharedRepoListItem extends React.Component {
|
|||||||
case 'Reset Password':
|
case 'Reset Password':
|
||||||
translateResult = gettext('Reset Password');
|
translateResult = gettext('Reset Password');
|
||||||
break;
|
break;
|
||||||
|
case 'Watch File Changes':
|
||||||
|
translateResult = gettext('Watch File Changes');
|
||||||
|
break;
|
||||||
|
case 'Unwatch File Changes':
|
||||||
|
translateResult = gettext('Unwatch File Changes');
|
||||||
|
break;
|
||||||
case 'API Token':
|
case 'API Token':
|
||||||
translateResult = 'API Token'; // translation is not needed here
|
translateResult = 'API Token'; // translation is not needed here
|
||||||
break;
|
break;
|
||||||
@@ -339,6 +372,10 @@ class SharedRepoListItem extends React.Component {
|
|||||||
operations.push('Unshare');
|
operations.push('Unshare');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (repo.permission == 'r' || repo.permission == 'rw') {
|
||||||
|
const monitorOp = repo.monitored ? 'Unwatch File Changes' : 'Watch File Changes';
|
||||||
|
operations.push(monitorOp);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isRepoOwner) {
|
if (isRepoOwner) {
|
||||||
operations.push('Share');
|
operations.push('Share');
|
||||||
@@ -400,8 +437,6 @@ class SharedRepoListItem extends React.Component {
|
|||||||
operations.push('Unshare');
|
operations.push('Unshare');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// scene one: (Share, Delete, itemToggle and other operations);
|
|
||||||
// scene two: (Share, Unshare), (Share), (Unshare)
|
|
||||||
operations = this.generatorOperations();
|
operations = this.generatorOperations();
|
||||||
}
|
}
|
||||||
const shareOperation = <a href="#" className="op-icon sf2-icon-share" title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.onItemShare}></a>;
|
const shareOperation = <a href="#" className="op-icon sf2-icon-share" title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.onItemShare}></a>;
|
||||||
@@ -433,23 +468,41 @@ class SharedRepoListItem extends React.Component {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (operations.length == 2) {
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{shareOperation}
|
{operations.map(item => {
|
||||||
{unshareOperation}
|
switch (item) {
|
||||||
|
case 'Share':
|
||||||
|
return <Fragment key={item}>{shareOperation}</Fragment>;
|
||||||
|
case 'Unshare':
|
||||||
|
return <Fragment key={item}>{unshareOperation}</Fragment>;
|
||||||
|
case 'Watch File Changes':
|
||||||
|
case 'Unwatch File Changes':
|
||||||
|
return (
|
||||||
|
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu} key={item}>
|
||||||
|
<DropdownToggle
|
||||||
|
className="sf-dropdown-toggle sf2-icon-caret-down border-0 p-0"
|
||||||
|
title={gettext('More Operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={this.state.isItemMenuShow}
|
||||||
|
aria-haspopup={true}
|
||||||
|
style={{'minWidth': '0'}}
|
||||||
|
onClick={this.clickOperationMenuToggle}
|
||||||
|
onKeyDown={this.onDropdownToggleKeyDown}
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
{[item].map((item, index) => {
|
||||||
|
return <DropdownItem key={index} data-toggle={item} onClick={this.onMenuItemClick} onKeyDown={this.onMenuItemKeyDown}>{this.translateMenuItem(item)}</DropdownItem>;
|
||||||
|
})}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
// no default
|
||||||
|
}
|
||||||
|
})}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (operations.length == 1 && operations[0] === 'Share') {
|
|
||||||
return shareOperation;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operations.length == 1 && operations[0] === 'Unshare') {
|
|
||||||
return unshareOperation;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onToggleStarRepo = (e) => {
|
onToggleStarRepo = (e) => {
|
||||||
@@ -493,7 +546,10 @@ class SharedRepoListItem extends React.Component {
|
|||||||
<td>
|
<td>
|
||||||
{this.state.isRenaming ?
|
{this.state.isRenaming ?
|
||||||
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
|
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel}/> :
|
||||||
|
<Fragment>
|
||||||
<Link to={libPath}>{repo.repo_name}</Link>
|
<Link to={libPath}>{repo.repo_name}</Link>
|
||||||
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
||||||
|
</Fragment>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>{this.state.isOperationShow && this.generatorPCMenu()}</td>
|
<td>{this.state.isOperationShow && this.generatorPCMenu()}</td>
|
||||||
@@ -522,7 +578,10 @@ class SharedRepoListItem extends React.Component {
|
|||||||
<td onClick={this.visitRepo}>
|
<td onClick={this.visitRepo}>
|
||||||
{this.state.isRenaming ?
|
{this.state.isRenaming ?
|
||||||
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
<Rename name={repo.repo_name} onRenameConfirm={this.onRenameConfirm} onRenameCancel={this.onRenameCancel} /> :
|
||||||
|
<Fragment>
|
||||||
<Link to={libPath}>{repo.repo_name}</Link>
|
<Link to={libPath}>{repo.repo_name}</Link>
|
||||||
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
||||||
|
</Fragment>
|
||||||
}
|
}
|
||||||
<br />
|
<br />
|
||||||
<span className="item-meta-info" title={repo.owner_contact_email}>{repo.owner_name}</span>
|
<span className="item-meta-info" title={repo.owner_contact_email}>{repo.owner_name}</span>
|
||||||
|
@@ -99,6 +99,7 @@ class SharedRepoListView extends React.Component {
|
|||||||
onItemDelete={this.props.onItemDelete}
|
onItemDelete={this.props.onItemDelete}
|
||||||
onItemDetails={this.props.onItemDetails}
|
onItemDetails={this.props.onItemDetails}
|
||||||
onItemRename={this.props.onItemRename}
|
onItemRename={this.props.onItemRename}
|
||||||
|
onMonitorRepo={this.props.onMonitorRepo}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
@@ -252,6 +252,16 @@ class GroupView extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMonitorRepo = (repo, monitored) => {
|
||||||
|
let repoList = this.state.repoList.map(item => {
|
||||||
|
if (item.repo_id === repo.repo_id) {
|
||||||
|
item.monitored = monitored;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.setState({repoList: repoList});
|
||||||
|
}
|
||||||
|
|
||||||
onTabNavClick = (tabName) => {
|
onTabNavClick = (tabName) => {
|
||||||
this.props.onTabNavClick(tabName);
|
this.props.onTabNavClick(tabName);
|
||||||
}
|
}
|
||||||
@@ -561,6 +571,7 @@ class GroupView extends React.Component {
|
|||||||
onItemDelete={this.onItemDelete}
|
onItemDelete={this.onItemDelete}
|
||||||
onItemDetails={this.onItemDetails}
|
onItemDetails={this.onItemDetails}
|
||||||
onItemRename={this.onItemRename}
|
onItemRename={this.onItemRename}
|
||||||
|
onMonitorRepo={this.onMonitorRepo}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -75,6 +75,16 @@ class RepoListViewPanel extends React.Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMonitorRepo = (repo, monitored) => {
|
||||||
|
let repoList = this.state.repoList.map(item => {
|
||||||
|
if (item.repo_id === repo.repo_id) {
|
||||||
|
item.monitored = monitored;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.setState({repoList: repoList});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let group = this.props.group;
|
let group = this.props.group;
|
||||||
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
|
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
|
||||||
@@ -94,6 +104,7 @@ class RepoListViewPanel extends React.Component {
|
|||||||
onItemDelete={this.onItemDelete}
|
onItemDelete={this.onItemDelete}
|
||||||
onItemDetails={this.props.onItemDetails}
|
onItemDetails={this.props.onItemDetails}
|
||||||
onItemRename={this.onItemRename}
|
onItemRename={this.onItemRename}
|
||||||
|
onMonitorRepo={this.onMonitorRepo}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,7 +3,6 @@ import PropTypes from 'prop-types';
|
|||||||
import MediaQuery from 'react-responsive';
|
import MediaQuery from 'react-responsive';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Link, navigate } from '@gatsbyjs/reach-router';
|
import { Link, navigate } from '@gatsbyjs/reach-router';
|
||||||
import { UncontrolledTooltip } from 'reactstrap';
|
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { gettext, siteRoot, storages } from '../../utils/constants';
|
import { gettext, siteRoot, storages } from '../../utils/constants';
|
||||||
@@ -22,7 +21,7 @@ import MylibRepoMenu from './mylib-repo-menu';
|
|||||||
import RepoAPITokenDialog from '../../components/dialog/repo-api-token-dialog';
|
import RepoAPITokenDialog from '../../components/dialog/repo-api-token-dialog';
|
||||||
import RepoShareUploadLinksDialog from '../../components/dialog/repo-share-upload-links-dialog';
|
import RepoShareUploadLinksDialog from '../../components/dialog/repo-share-upload-links-dialog';
|
||||||
import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog';
|
import LibOldFilesAutoDelDialog from '../../components/dialog/lib-old-files-auto-del-dialog';
|
||||||
import Icon from '../../components/icon';
|
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
repo: PropTypes.object.isRequired,
|
repo: PropTypes.object.isRequired,
|
||||||
@@ -337,19 +336,7 @@ class MylibRepoListItem extends React.Component {
|
|||||||
{!this.state.isRenaming && repo.repo_name && (
|
{!this.state.isRenaming && repo.repo_name && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Link to={repoURL}>{repo.repo_name}</Link>
|
<Link to={repoURL}>{repo.repo_name}</Link>
|
||||||
{repo.monitored && (
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
||||||
<Fragment>
|
|
||||||
<span id={`watching-${repo.repo_id}`} className="ml-1">
|
|
||||||
<Icon symbol='monitor' />
|
|
||||||
</span>
|
|
||||||
<UncontrolledTooltip
|
|
||||||
placement="bottom"
|
|
||||||
target={`#watching-${repo.repo_id}`}
|
|
||||||
>
|
|
||||||
{gettext('You are watching file changes of this library.')}
|
|
||||||
</UncontrolledTooltip>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{!this.state.isRenaming && !repo.repo_name &&
|
{!this.state.isRenaming && !repo.repo_name &&
|
||||||
@@ -398,19 +385,7 @@ class MylibRepoListItem extends React.Component {
|
|||||||
{!this.state.isRenaming && repo.repo_name && (
|
{!this.state.isRenaming && repo.repo_name && (
|
||||||
<div>
|
<div>
|
||||||
<Link to={repoURL}>{repo.repo_name}</Link>
|
<Link to={repoURL}>{repo.repo_name}</Link>
|
||||||
{repo.monitored && (
|
{repo.monitored && <RepoMonitoredIcon repoID={repo.repo_id} />}
|
||||||
<Fragment>
|
|
||||||
<span id={`watching-${repo.repo_id}`} className="ml-1">
|
|
||||||
<Icon symbol='monitor' />
|
|
||||||
</span>
|
|
||||||
<UncontrolledTooltip
|
|
||||||
placement="bottom"
|
|
||||||
target={`#watching-${repo.repo_id}`}
|
|
||||||
>
|
|
||||||
{gettext('You are watching file changes of this library.')}
|
|
||||||
</UncontrolledTooltip>
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!this.state.isRenaming && !repo.repo_name &&
|
{!this.state.isRenaming && !repo.repo_name &&
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component, Fragment } from 'react';
|
||||||
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import cookie from 'react-cookies';
|
import cookie from 'react-cookies';
|
||||||
@@ -15,9 +15,23 @@ import LibsMobileThead from '../../components/libs-mobile-thead';
|
|||||||
import ModalPotal from '../../components/modal-portal';
|
import ModalPotal from '../../components/modal-portal';
|
||||||
import ShareDialog from '../../components/dialog/share-dialog';
|
import ShareDialog from '../../components/dialog/share-dialog';
|
||||||
import SortOptionsDialog from '../../components/dialog/sort-options';
|
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||||
|
import RepoMonitoredIcon from '../../components/repo-monitored-icon';
|
||||||
|
|
||||||
class Content extends Component {
|
class Content extends Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isItemFreezed: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
freezeItem = (freezed) => {
|
||||||
|
this.setState({
|
||||||
|
isItemFreezed: freezed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
sortByName = (e) => {
|
sortByName = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const sortBy = 'name';
|
const sortBy = 'name';
|
||||||
@@ -80,7 +94,14 @@ class Content extends Component {
|
|||||||
{isDesktop ? desktopThead : <LibsMobileThead />}
|
{isDesktop ? desktopThead : <LibsMobileThead />}
|
||||||
<tbody>
|
<tbody>
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
return <Item key={index} data={item} isDesktop={isDesktop} />;
|
return <Item
|
||||||
|
key={index}
|
||||||
|
data={item}
|
||||||
|
isDesktop={isDesktop}
|
||||||
|
isItemFreezed={this.state.isItemFreezed}
|
||||||
|
freezeItem={this.freezeItem}
|
||||||
|
onMonitorRepo={this.props.onMonitorRepo}
|
||||||
|
/>;
|
||||||
})}
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -97,7 +118,8 @@ Content.propTypes = {
|
|||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
sortBy: PropTypes.string.isRequired,
|
sortBy: PropTypes.string.isRequired,
|
||||||
sortOrder: PropTypes.string.isRequired,
|
sortOrder: PropTypes.string.isRequired,
|
||||||
sortItems: PropTypes.func.isRequired
|
sortItems: PropTypes.func.isRequired,
|
||||||
|
onMonitorRepo: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
class Item extends Component {
|
class Item extends Component {
|
||||||
@@ -105,31 +127,40 @@ class Item extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
highlight: false,
|
||||||
showOpIcon: false,
|
showOpIcon: false,
|
||||||
unshared: false,
|
unshared: false,
|
||||||
isShowSharedDialog: false,
|
isShowSharedDialog: false,
|
||||||
isStarred: this.props.data.starred,
|
isStarred: this.props.data.starred,
|
||||||
isOpMenuOpen: false // for mobile
|
isOpMenuOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleOpMenu = () => {
|
toggleOpMenu = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
isOpMenuOpen: !this.state.isOpMenuOpen
|
isOpMenuOpen: !this.state.isOpMenuOpen
|
||||||
|
}, () => {
|
||||||
|
this.props.freezeItem(this.state.isOpMenuOpen);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOver = () => {
|
handleMouseOver = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
highlight: true,
|
||||||
showOpIcon: true
|
showOpIcon: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleMouseOut = () => {
|
handleMouseOut = () => {
|
||||||
|
if (!this.props.isItemFreezed) {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
highlight: false,
|
||||||
showOpIcon: false
|
showOpIcon: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
share = (e) => {
|
share = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -199,6 +230,26 @@ class Item extends Component {
|
|||||||
navigate(this.repoURL);
|
navigate(this.repoURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
watchFileChanges = () => {
|
||||||
|
const { data: repo } = this.props;
|
||||||
|
seafileAPI.monitorRepo(repo.repo_id).then(() => {
|
||||||
|
this.props.onMonitorRepo(repo, true);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unwatchFileChanges = () => {
|
||||||
|
const { data: repo } = this.props;
|
||||||
|
seafileAPI.unMonitorRepo(repo.repo_id).then(() => {
|
||||||
|
this.props.onMonitorRepo(repo, false);
|
||||||
|
}).catch(error => {
|
||||||
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
|
toaster.danger(errMessage);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if (this.state.unshared) {
|
if (this.state.unshared) {
|
||||||
return null;
|
return null;
|
||||||
@@ -213,21 +264,47 @@ class Item extends Component {
|
|||||||
let shareIconClassName = 'op-icon sf2-icon-share repo-share-btn' + iconVisibility;
|
let shareIconClassName = 'op-icon sf2-icon-share repo-share-btn' + iconVisibility;
|
||||||
let leaveShareIconClassName = 'op-icon sf2-icon-x3' + iconVisibility;
|
let leaveShareIconClassName = 'op-icon sf2-icon-x3' + iconVisibility;
|
||||||
let shareRepoUrl = this.repoURL = `${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`;
|
let shareRepoUrl = this.repoURL = `${siteRoot}library/${data.repo_id}/${Utils.encodePath(data.repo_name)}/`;
|
||||||
|
|
||||||
|
// at present, only repo shared with 'r', 'rw' can be monitored.(Fri Feb 10 16:24:49 CST 2023)
|
||||||
|
const enableMonitorRepo = isPro && (data.permission == 'r' || data.permission == 'rw');
|
||||||
|
|
||||||
const desktopItem = (
|
const desktopItem = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
||||||
<td className="text-center">
|
<td className="text-center">
|
||||||
<a href="#" role="button" aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')} onClick={this.onToggleStarRepo}>
|
<a href="#" role="button" aria-label={this.state.isStarred ? gettext('Unstar') : gettext('Star')} onClick={this.onToggleStarRepo}>
|
||||||
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
<i className={`fa-star ${this.state.isStarred ? 'fas' : 'far star-empty'}`}></i>
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
<td><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||||
<td><Link to={shareRepoUrl}>{data.repo_name}</Link></td>
|
<td>
|
||||||
|
<Fragment>
|
||||||
|
<Link to={shareRepoUrl}>{data.repo_name}</Link>
|
||||||
|
{data.monitored && <RepoMonitoredIcon repoID={data.repo_id} />}
|
||||||
|
</Fragment>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{(isPro && data.is_admin) &&
|
{(isPro && data.is_admin) &&
|
||||||
<a href="#" className={shareIconClassName} title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.share}></a>
|
<a href="#" className={shareIconClassName} title={gettext('Share')} role="button" aria-label={gettext('Share')} onClick={this.share}></a>
|
||||||
}
|
}
|
||||||
<a href="#" className={leaveShareIconClassName} title={gettext('Leave Share')} role="button" aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a>
|
<a href="#" className={leaveShareIconClassName} title={gettext('Leave Share')} role="button" aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a>
|
||||||
|
{enableMonitorRepo &&
|
||||||
|
<Dropdown isOpen={this.state.isOpMenuOpen} toggle={this.toggleOpMenu}>
|
||||||
|
<DropdownToggle
|
||||||
|
tag="i"
|
||||||
|
role="button"
|
||||||
|
tabIndex="0"
|
||||||
|
className={`sf-dropdown-toggle sf2-icon-caret-down${iconVisibility}`}
|
||||||
|
title={gettext('More Operations')}
|
||||||
|
aria-label={gettext('More Operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={this.state.isOpMenuOpen}
|
||||||
|
/>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownItem onClick={data.monitored ? this.unwatchFileChanges : this.watchFileChanges}>{data.monitored ? gettext('Unwatch File Changes') : gettext('Watch File Changes')}</DropdownItem>
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>{data.size}</td>
|
<td>{data.size}</td>
|
||||||
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
|
<td title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</td>
|
||||||
@@ -256,7 +333,9 @@ class Item extends Component {
|
|||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||||
<td onClick={this.visitRepo}><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
<td onClick={this.visitRepo}><img src={data.icon_url} title={data.icon_title} alt={data.icon_title} width="24" /></td>
|
||||||
<td onClick={this.visitRepo}>
|
<td onClick={this.visitRepo}>
|
||||||
<Link to={shareRepoUrl}>{data.repo_name}</Link><br />
|
<Link to={shareRepoUrl}>{data.repo_name}</Link>
|
||||||
|
{data.monitored && <RepoMonitoredIcon repoID={data.repo_id} />}
|
||||||
|
<br />
|
||||||
<span className="item-meta-info" title={data.owner_contact_email}>{data.owner_name}</span>
|
<span className="item-meta-info" title={data.owner_contact_email}>{data.owner_name}</span>
|
||||||
<span className="item-meta-info">{data.size}</span>
|
<span className="item-meta-info">{data.size}</span>
|
||||||
<span className="item-meta-info" title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</span>
|
<span className="item-meta-info" title={moment(data.last_modified).format('llll')}>{moment(data.last_modified).fromNow()}</span>
|
||||||
@@ -274,10 +353,9 @@ class Item extends Component {
|
|||||||
<div className="mobile-operation-menu-bg-layer"></div>
|
<div className="mobile-operation-menu-bg-layer"></div>
|
||||||
<div className="mobile-operation-menu">
|
<div className="mobile-operation-menu">
|
||||||
<DropdownItem className="mobile-menu-item" onClick={this.onToggleStarRepo}>{this.state.isStarred ? gettext('Unstar') : gettext('Star')}</DropdownItem>
|
<DropdownItem className="mobile-menu-item" onClick={this.onToggleStarRepo}>{this.state.isStarred ? gettext('Unstar') : gettext('Star')}</DropdownItem>
|
||||||
{(isPro && data.is_admin) &&
|
{(isPro && data.is_admin) && <DropdownItem className="mobile-menu-item" onClick={this.share}>{gettext('Share')}</DropdownItem>}
|
||||||
<DropdownItem className="mobile-menu-item" onClick={this.share}>{gettext('Share')}</DropdownItem>
|
|
||||||
}
|
|
||||||
<DropdownItem className="mobile-menu-item" onClick={this.leaveShare}>{gettext('Leave Share')}</DropdownItem>
|
<DropdownItem className="mobile-menu-item" onClick={this.leaveShare}>{gettext('Leave Share')}</DropdownItem>
|
||||||
|
{enableMonitorRepo && <DropdownItem className="mobile-menu-item" onClick={data.monitored ? this.unwatchFileChanges : this.watchFileChanges}>{data.monitored ? gettext('Unwatch File Changes') : gettext('Watch File Changes')}</DropdownItem>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
@@ -307,7 +385,10 @@ class Item extends Component {
|
|||||||
|
|
||||||
Item.propTypes = {
|
Item.propTypes = {
|
||||||
isDesktop: PropTypes.bool.isRequired,
|
isDesktop: PropTypes.bool.isRequired,
|
||||||
data: PropTypes.object.isRequired
|
data: PropTypes.object.isRequired,
|
||||||
|
isItemFreezed: PropTypes.bool.isRequired,
|
||||||
|
freezeItem: PropTypes.func.isRequired,
|
||||||
|
onMonitorRepo: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
class SharedLibraries extends Component {
|
class SharedLibraries extends Component {
|
||||||
@@ -356,6 +437,16 @@ class SharedLibraries extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMonitorRepo = (repo, monitored) => {
|
||||||
|
let items = this.state.items.map(item => {
|
||||||
|
if (item.repo_id === repo.repo_id) {
|
||||||
|
item.monitored = monitored;
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
this.setState({items: items});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
@@ -373,6 +464,7 @@ class SharedLibraries extends Component {
|
|||||||
sortBy={this.state.sortBy}
|
sortBy={this.state.sortBy}
|
||||||
sortOrder={this.state.sortOrder}
|
sortOrder={this.state.sortOrder}
|
||||||
sortItems={this.sortItems}
|
sortItems={this.sortItems}
|
||||||
|
onMonitorRepo={this.onMonitorRepo}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
Reference in New Issue
Block a user