mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-13 22:01:06 +00:00
Ocm (#4640)
* ocm share local and inter-server api, ocm share page (#4335) * ocm share * optimize code * ocm repo (#4341) * ocm repo * hide share if not repo owner * optimize code * fix All refresh page, hide upload if is r perm * update permission check * update return status code * update code * add receive user drop share
This commit is contained in:
@@ -19,6 +19,8 @@ import ShareAdminFolders from './pages/share-admin/folders';
|
||||
import ShareAdminShareLinks from './pages/share-admin/share-links';
|
||||
import ShareAdminUploadLinks from './pages/share-admin/upload-links';
|
||||
import SharedLibraries from './pages/shared-libs/shared-libs';
|
||||
import ShareWithOCM from './pages/share-with-ocm/shared-with-ocm';
|
||||
import OCMRepoDir from './pages/share-with-ocm/remote-dir-view';
|
||||
import MyLibraries from './pages/my-libs/my-libs';
|
||||
import MyLibDeleted from './pages/my-libs/my-libs-deleted';
|
||||
import PublicSharedView from './pages/shared-with-all/public-shared-view';
|
||||
@@ -38,6 +40,7 @@ const DraftsViewWrapper = MainContentWrapper(DraftsView);
|
||||
const StarredWrapper = MainContentWrapper(Starred);
|
||||
const LinkedDevicesWrapper = MainContentWrapper(LinkedDevices);
|
||||
const SharedLibrariesWrapper = MainContentWrapper(SharedLibraries);
|
||||
const SharedWithOCMWrapper = MainContentWrapper(ShareWithOCM);
|
||||
const ShareAdminLibrariesWrapper = MainContentWrapper(ShareAdminLibraries);
|
||||
const ShareAdminFoldersWrapper = MainContentWrapper(ShareAdminFolders);
|
||||
const ShareAdminShareLinksWrapper = MainContentWrapper(ShareAdminShareLinks);
|
||||
@@ -257,9 +260,11 @@ class App extends Component {
|
||||
<ShareAdminShareLinksWrapper path={siteRoot + 'share-admin-share-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<ShareAdminUploadLinksWrapper path={siteRoot + 'share-admin-upload-links'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<SharedLibrariesWrapper path={siteRoot + 'shared-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<SharedWithOCMWrapper path={siteRoot + 'shared-with-ocm'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<MyLibraries path={siteRoot + 'my-libs'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
|
||||
<MyLibDeleted path={siteRoot + 'my-libs/deleted/'} onSearchedClick={this.onSearchedClick} />
|
||||
<LibContentView path={siteRoot + 'library/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
|
||||
<OCMRepoDir path={siteRoot + 'remote-library/:providerID/:repoID/*'} pathPrefix={this.state.pathPrefix} onMenuClick={this.onShowSidePanel} onTabNavClick={this.tabItemClick}/>
|
||||
<Groups path={siteRoot + 'groups'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick}/>
|
||||
<Group
|
||||
path={siteRoot + 'group/:groupID'}
|
||||
|
@@ -1,12 +1,13 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
|
||||
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote } from '../../utils/constants';
|
||||
import { Modal, ModalHeader, ModalBody, TabContent, TabPane, Nav, NavItem, NavLink } from 'reactstrap';
|
||||
import { gettext, username, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, additionalShareDialogNote, enableOCM } from '../../utils/constants';
|
||||
import ShareToUser from './share-to-user';
|
||||
import ShareToGroup from './share-to-group';
|
||||
import ShareToInvitePeople from './share-to-invite-people';
|
||||
import GenerateShareLink from './generate-share-link';
|
||||
import GenerateUploadLink from './generate-upload-link';
|
||||
import ShareToOtherServer from './share-to-other-server';
|
||||
import InternalLink from './internal-link';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import Loading from '../loading';
|
||||
@@ -141,6 +142,13 @@ class ShareDialog extends React.Component {
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
{enableOCM && itemType === 'library' && this.state.isRepoOwner &&
|
||||
<NavItem>
|
||||
<NavLink className={activeTab === 'shareToOtherServer' ? 'active' : ''} onClick={this.toggle.bind(this, 'shareToOtherServer')}>
|
||||
{gettext('Share to other server')}
|
||||
</NavLink>
|
||||
</NavItem>
|
||||
}
|
||||
</Nav>
|
||||
</div>
|
||||
<div className="share-dialog-main">
|
||||
@@ -190,6 +198,11 @@ class ShareDialog extends React.Component {
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
{enableOCM && itemType === 'library' && activeTab === 'shareToOtherServer' &&
|
||||
<TabPane tabId="shareToOtherServer">
|
||||
<ShareToOtherServer itemType={this.props.itemType} isGroupOwnedRepo={this.props.isGroupOwnedRepo} itemPath={this.props.itemPath} repoID={this.props.repoID} isRepoOwner={this.state.isRepoOwner} />
|
||||
</TabPane>
|
||||
}
|
||||
</TabContent>
|
||||
</div>
|
||||
</Fragment>
|
||||
|
234
frontend/src/components/dialog/share-to-other-server.js
Normal file
234
frontend/src/components/dialog/share-to-other-server.js
Normal file
@@ -0,0 +1,234 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Input } from 'reactstrap';
|
||||
import { Button } from 'reactstrap';
|
||||
import { seafileAPI } from '../../utils/seafile-api.js';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../toast';
|
||||
import SharePermissionEditor from '../select-editor/share-permission-editor';
|
||||
|
||||
class ShareItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOperationShow: false
|
||||
};
|
||||
}
|
||||
|
||||
onMouseEnter = () => {
|
||||
this.setState({isOperationShow: true});
|
||||
}
|
||||
|
||||
onMouseLeave = () => {
|
||||
this.setState({isOperationShow: false});
|
||||
}
|
||||
|
||||
deleteShareItem = () => {
|
||||
let item = this.props.item;
|
||||
this.props.deleteShareItem(item);
|
||||
}
|
||||
|
||||
render() {
|
||||
let item = this.props.item;
|
||||
return (
|
||||
<tr onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
|
||||
<td>{item.to_sever_url}</td>
|
||||
<td className="name">{item.to_user}</td>
|
||||
<td>{Utils.sharePerms(item.permission)}</td>
|
||||
{/* <td>
|
||||
<SharePermissionEditor
|
||||
isTextMode={true}
|
||||
isEditIconShow={this.state.isOperationShow}
|
||||
currentPermission={currentPermission}
|
||||
permissions={this.props.permissions}
|
||||
onPermissionChanged={this.onChangeUserPermission}
|
||||
/>
|
||||
</td> */}
|
||||
<td>
|
||||
<span
|
||||
className={`sf2-icon-x3 action-icon ${this.state.isOperationShow ? '' : 'hide'}`}
|
||||
onClick={this.deleteShareItem}
|
||||
title={gettext('Delete')}
|
||||
>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ShareList extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="share-list-container">
|
||||
<table className="table-thead-hidden">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">{gettext('Server URL')}</th>
|
||||
<th width="25%">{gettext('User Email')}</th>
|
||||
<th width="20%">{gettext('Permission')}</th>
|
||||
<th width="15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{this.props.items.map((item, index) => {
|
||||
return (
|
||||
<ShareItem
|
||||
key={index}
|
||||
item={item}
|
||||
deleteShareItem={this.props.deleteShareItem}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
isGroupOwnedRepo: PropTypes.bool,
|
||||
itemPath: PropTypes.string.isRequired,
|
||||
itemType: PropTypes.string.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
isRepoOwner: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
class ShareToOtherServer extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
selectedOption: null,
|
||||
errorMsg: [],
|
||||
permission: 'rw',
|
||||
ocmShares: [],
|
||||
toUser: '',
|
||||
toServerURL: '',
|
||||
};
|
||||
this.options = [];
|
||||
this.permissions = ['rw', 'r'];
|
||||
this.UnshareMessage = 'File was unshared';
|
||||
|
||||
}
|
||||
|
||||
handleSelectChange = (option) => {
|
||||
this.setState({selectedOption: option});
|
||||
this.options = [];
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listOCMSharesPrepare(this.props.repoID).then((res) => {
|
||||
this.setState({ocmShares: res.data.ocm_share_list});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
startOCMShare = () => {
|
||||
let { repoID, itemPath } = this.props;
|
||||
let { toServerURL, toUser, permission } = this.state;
|
||||
if (!toServerURL.endsWith('/')) {
|
||||
toServerURL += '/';
|
||||
}
|
||||
seafileAPI.addOCMSharePrepare(toUser, toServerURL, repoID, itemPath, permission).then((res) => {
|
||||
toaster.success(gettext('share success.'));
|
||||
let ocmShares = this.state.ocmShares;
|
||||
ocmShares.push(res.data);
|
||||
this.setState({ocmShares: ocmShares});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
handleToUserChange = (e) => {
|
||||
this.setState({
|
||||
toUser: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
handleURLChange = (e) => {
|
||||
this.setState({
|
||||
toServerURL: e.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
deleteShareItem = (deletedItem) => {
|
||||
let { id } = deletedItem;
|
||||
seafileAPI.deleteOCMSharePrepare(id).then((res) => {
|
||||
toaster.success(gettext('delete success.'));
|
||||
let ocmShares = this.state.ocmShares.filter(item => {
|
||||
return item.id != id;
|
||||
});
|
||||
this.setState({ocmShares: ocmShares});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
setPermission = (permission) => {
|
||||
this.setState({permission: permission});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let { ocmShares, toUser, toServerURL, permission } = this.state;
|
||||
return (
|
||||
<Fragment>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="40%">{gettext('Server URL')}</th>
|
||||
<th width="25%">{gettext('User Email')}</th>
|
||||
<th width="20%">{gettext('Permission')}</th>
|
||||
<th width="15%"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<Input
|
||||
value={toServerURL}
|
||||
onChange={this.handleURLChange}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Input
|
||||
value={toUser}
|
||||
onChange={this.handleToUserChange}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<SharePermissionEditor
|
||||
isTextMode={false}
|
||||
isEditIconShow={false}
|
||||
currentPermission={permission}
|
||||
permissions={this.permissions}
|
||||
onPermissionChanged={this.setPermission}
|
||||
/>
|
||||
</td>
|
||||
<td>
|
||||
<Button onClick={this.startOCMShare}>{gettext('Submit')}</Button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<ShareList
|
||||
items={ocmShares}
|
||||
deleteShareItem={this.deleteShareItem}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ShareToOtherServer.propTypes = propTypes;
|
||||
|
||||
export default ShareToOtherServer;
|
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import { Badge } from 'reactstrap';
|
||||
import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer } from '../utils/constants';
|
||||
import { gettext, siteRoot, canPublishRepo, canAddRepo, canGenerateShareLink, canGenerateUploadLink, canInvitePeople, dtableWebServer, enableOCM } from '../utils/constants';
|
||||
import { seafileAPI } from '../utils/seafile-api';
|
||||
import { Utils } from '../utils/utils';
|
||||
import toaster from './toast';
|
||||
@@ -216,6 +216,14 @@ class MainSideNav extends React.Component {
|
||||
</a>
|
||||
{this.renderSharedGroups()}
|
||||
</li>
|
||||
{enableOCM &&
|
||||
<li className="nav-item">
|
||||
<Link to={siteRoot + 'shared-with-ocm/'} className={`nav-link ellipsis ${this.getActiveClass('shared-with-ocm')}`} title={gettext('Shared from other servers')} onClick={(e) => this.tabItemClick(e, 'shared-with-ocm')}>
|
||||
<span className="sf2-icon-share" aria-hidden="true"></span>
|
||||
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||
</Link>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
|
||||
|
||||
|
121
frontend/src/pages/share-with-ocm/remote-dir-content.js
Normal file
121
frontend/src/pages/share-with-ocm/remote-dir-content.js
Normal file
@@ -0,0 +1,121 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import moment from 'moment';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import Loading from '../../components/loading';
|
||||
|
||||
class DirentItem extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpIconShown: false
|
||||
};
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
isOpIconShown: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
isOpIconShown: false
|
||||
});
|
||||
}
|
||||
|
||||
openFolder = () => {
|
||||
this.props.openFolder(this.props.dirent);
|
||||
}
|
||||
|
||||
downloadDirent = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.downloadDirent(this.props.dirent);
|
||||
}
|
||||
|
||||
render () {
|
||||
let { isOpIconShown } = this.state;
|
||||
let { dirent } = this.props;
|
||||
let iconUrl = Utils.getDirentIcon(dirent);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
||||
<td className="text-center"><img src={iconUrl} width="24" alt='' /></td>
|
||||
<td>
|
||||
{dirent.is_file ?
|
||||
dirent.name :
|
||||
<Link to="#" onClick={this.openFolder}>{dirent.name}</Link>
|
||||
}
|
||||
</td>
|
||||
<td>
|
||||
{isOpIconShown && dirent.is_file &&
|
||||
<a href="#" className="op-icon sf2-icon-download" title={gettext('Download')} onClick={this.downloadDirent}></a>
|
||||
}
|
||||
</td>
|
||||
<td>{Utils.bytesToSize(dirent.size)}</td>
|
||||
<td>{moment(dirent.mtime).fromNow()}</td>
|
||||
</tr>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const propTypes = {
|
||||
direntList: PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
class DirContent extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let { loading, errorMsg, direntList } = this.props;
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (errorMsg) {
|
||||
return <p className="error text-center mt-4">{errorMsg}</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<table className="table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="5%">{/*icon*/}</th>
|
||||
<th width="55%">{gettext('Name')}</th>
|
||||
<th width="10%">{/*operation*/}</th>
|
||||
<th width="15%">{gettext('Size')}</th>
|
||||
<th width="15%">{gettext('Last Update')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{direntList.map((dirent, index) => {
|
||||
return <DirentItem
|
||||
key={index}
|
||||
dirent={dirent}
|
||||
openFolder={this.props.openFolder}
|
||||
deleteDirent={this.props.deleteDirent}
|
||||
downloadDirent={this.props.downloadDirent}
|
||||
fromSystemRepo={this.props.fromSystemRepo}
|
||||
/>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirContent.propTypes = propTypes;
|
||||
|
||||
export default DirContent;
|
70
frontend/src/pages/share-with-ocm/remote-dir-path.js
Normal file
70
frontend/src/pages/share-with-ocm/remote-dir-path.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Link } from '@reach/router';
|
||||
import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
|
||||
const propTypes = {
|
||||
repoName: PropTypes.string.isRequired,
|
||||
currentPath: PropTypes.string.isRequired,
|
||||
onPathClick: PropTypes.func.isRequired,
|
||||
onTabNavClick: PropTypes.func.isRequired,
|
||||
repoID: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
class DirPath extends React.Component {
|
||||
|
||||
onPathClick = (e) => {
|
||||
let path = Utils.getEventData(e, 'path');
|
||||
this.props.onPathClick(path);
|
||||
}
|
||||
|
||||
turnPathToLink = (path) => {
|
||||
path = path[path.length - 1] === '/' ? path.slice(0, path.length - 1) : path;
|
||||
let pathList = path.split('/');
|
||||
let nodePath = '';
|
||||
let pathElem = pathList.map((item, index) => {
|
||||
if (item === '') {
|
||||
return;
|
||||
}
|
||||
if (index === (pathList.length - 1)) {
|
||||
return (
|
||||
<Fragment key={index}>
|
||||
<span className="path-split">/</span>
|
||||
<span className="path-file-name">{item}</span>
|
||||
</Fragment>
|
||||
);
|
||||
} else {
|
||||
nodePath += '/' + item;
|
||||
return (
|
||||
<Fragment key={index} >
|
||||
<span className="path-split">/</span>
|
||||
<a className="path-link" data-path={nodePath} onClick={this.onPathClick}>{item}</a>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
});
|
||||
return pathElem;
|
||||
}
|
||||
|
||||
render() {
|
||||
let { currentPath, repoName } = this.props;
|
||||
let pathElem = this.turnPathToLink(currentPath);
|
||||
|
||||
return (
|
||||
<div className="path-container">
|
||||
<Link to={siteRoot + 'shared-with-ocm/'} className="normal" onClick={(e) => this.props.onTabNavClick('shared-with-ocm')}>{gettext('All')}</Link>
|
||||
<span className="path-split">/</span>
|
||||
{(currentPath === '/' || currentPath === '') ?
|
||||
<span className="path-repo-name">{repoName}</span>:
|
||||
<a className="path-link" data-path="/" onClick={this.onPathClick}>{repoName}</a>
|
||||
}
|
||||
{pathElem}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
DirPath.propTypes = propTypes;
|
||||
|
||||
export default DirPath;
|
30
frontend/src/pages/share-with-ocm/remote-dir-topbar.js
Normal file
30
frontend/src/pages/share-with-ocm/remote-dir-topbar.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Account from '../../components/common/account';
|
||||
|
||||
const propTypes = {
|
||||
children: PropTypes.object
|
||||
};
|
||||
|
||||
class MainPanelTopbar extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={`main-panel-north ${this.props.children ? 'border-left-show' : ''}`}>
|
||||
<div className="cur-view-toolbar">
|
||||
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu"></span>
|
||||
<div className="operation">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
<div className="common-toolbar">
|
||||
<Account isAdminPanel={false} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MainPanelTopbar.propTypes = propTypes;
|
||||
|
||||
export default MainPanelTopbar;
|
188
frontend/src/pages/share-with-ocm/remote-dir-view.js
Normal file
188
frontend/src/pages/share-with-ocm/remote-dir-view.js
Normal file
@@ -0,0 +1,188 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { Button } from 'reactstrap';
|
||||
import { post } from 'axios';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { loginUrl, siteRoot, gettext } from '../../utils/constants';
|
||||
import toaster from '../../components/toast';
|
||||
import MainPanelTopbar from './remote-dir-topbar';
|
||||
import DirPathBar from './remote-dir-path';
|
||||
import DirContent from './remote-dir-content';
|
||||
|
||||
class Dirent {
|
||||
constructor(obj) {
|
||||
this.name = obj.name;
|
||||
this.mtime = obj.mtime;
|
||||
this.size = obj.size;
|
||||
this.is_file = obj.type === 'file';
|
||||
}
|
||||
|
||||
isDir() {
|
||||
return !this.is_file;
|
||||
}
|
||||
}
|
||||
|
||||
class DirView extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.fileInput = React.createRef();
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
repoName: '',
|
||||
path: '',
|
||||
direntList: [],
|
||||
isNewFolderDialogOpen: false,
|
||||
userPerm: '',
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.loadDirentList('/');
|
||||
}
|
||||
|
||||
onPathClick = (path) => {
|
||||
this.loadDirentList(path);
|
||||
}
|
||||
|
||||
openFolder = (dirent) => {
|
||||
let direntPath = Utils.joinPath(this.state.path, dirent.name);
|
||||
if (!dirent.is_file) {
|
||||
this.loadDirentList(direntPath);
|
||||
}
|
||||
}
|
||||
|
||||
loadDirentList = (path) => {
|
||||
const { providerID, repoID } = this.props;
|
||||
seafileAPI.listOCMRepoDir(providerID, repoID, path).then(res => {
|
||||
const { repo_name: repoName, dirent_list, user_perm } = res.data;
|
||||
let direntList = [];
|
||||
dirent_list.forEach(item => {
|
||||
let dirent = new Dirent(item);
|
||||
direntList.push(dirent);
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
repoName: repoName,
|
||||
direntList: direntList,
|
||||
path: path,
|
||||
userPerm: user_perm,
|
||||
}, () => {
|
||||
let url =`${siteRoot}remote-library/${providerID}/${repoID}/${repoName}${Utils.encodePath(path)}`;
|
||||
window.history.replaceState({url: url, path: path}, path, url);
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
downloadDirent = (dirent) => {
|
||||
let path = Utils.joinPath(this.state.path, dirent.name);
|
||||
seafileAPI.getOCMRepoDownloadURL(this.props.providerID, this.props.repoID, path).then(res => {
|
||||
location.href = res.data;
|
||||
}).catch((err) => {
|
||||
let errMessage = Utils.getErrorMsg(err);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
openFileInput = () => {
|
||||
this.fileInput.current.click();
|
||||
}
|
||||
|
||||
onFileInputChange = () => {
|
||||
if (!this.fileInput.current.files.length) {
|
||||
return;
|
||||
}
|
||||
const file = this.fileInput.current.files[0];
|
||||
|
||||
let { path } = this.state;
|
||||
let { providerID, repoID } = this.props;
|
||||
seafileAPI.getOCMRepoUploadURL(providerID, repoID, path).then(res => {
|
||||
let formData = new FormData();
|
||||
formData.append('parent_dir', path);
|
||||
formData.append('file', file);
|
||||
post(res.data, formData).then(res => {
|
||||
const fileObj = res.data[0];
|
||||
let newDirent = new Dirent({
|
||||
'type': 'file',
|
||||
'name': fileObj.name,
|
||||
'size': fileObj.size,
|
||||
'mtime': (new Date()).getTime()
|
||||
});
|
||||
let direntList = this.state.direntList;
|
||||
const dirs = direntList.filter(item => { return !item.is_file; });
|
||||
direntList.splice(dirs.length, 0, newDirent);
|
||||
this.setState({
|
||||
direntList: direntList
|
||||
});
|
||||
});
|
||||
}).catch((err) => {
|
||||
let errMessage = Utils.getErrorMsg(err);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg,
|
||||
repoName, direntList, path, userPerm } = this.state;
|
||||
const { repoID } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<MainPanelTopbar>
|
||||
<Fragment>
|
||||
<input className="d-none" type="file" onChange={this.onFileInputChange} ref={this.fileInput} />
|
||||
{userPerm === 'rw' &&
|
||||
<Button className="operation-item" onClick={this.openFileInput}>{gettext('Upload')}</Button>
|
||||
}
|
||||
</Fragment>
|
||||
</MainPanelTopbar>
|
||||
<div className="main-panel-center flex-row">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path align-items-center">
|
||||
<DirPathBar
|
||||
repoID={repoID}
|
||||
repoName={repoName}
|
||||
currentPath={path}
|
||||
onPathClick={this.onPathClick}
|
||||
onTabNavClick={this.props.onTabNavClick}
|
||||
/>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<DirContent
|
||||
loading={loading}
|
||||
errorMsg={errorMsg}
|
||||
direntList={direntList}
|
||||
openFolder={this.openFolder}
|
||||
deleteDirent={this.deleteDirent}
|
||||
downloadDirent={this.downloadDirent}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DirView;
|
203
frontend/src/pages/share-with-ocm/shared-with-ocm.js
Normal file
203
frontend/src/pages/share-with-ocm/shared-with-ocm.js
Normal file
@@ -0,0 +1,203 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import moment from 'moment';
|
||||
import { Link } from '@reach/router';
|
||||
import { gettext, siteRoot } from '../../utils/constants';
|
||||
import { seafileAPI } from '../../utils/seafile-api';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import toaster from '../../components/toast';
|
||||
import Loading from '../../components/loading';
|
||||
import EmptyTip from '../../components/empty-tip';
|
||||
|
||||
class Content extends Component {
|
||||
|
||||
render() {
|
||||
const { loading, errorMsg, items } = this.props;
|
||||
|
||||
const emptyTip = (
|
||||
<EmptyTip>
|
||||
<h2>{gettext('No libraries have been shared with you')}</h2>
|
||||
<p>{gettext('No libraries have been shared directly with you. You can find more shared libraries at "Shared with groups".')}</p>
|
||||
</EmptyTip>
|
||||
);
|
||||
|
||||
if (loading) {
|
||||
return <Loading />;
|
||||
} else if (errorMsg) {
|
||||
return <p className="error text-center">{errorMsg}</p>;
|
||||
} else {
|
||||
const table = (
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th width="4%"></th>
|
||||
<th width="20%">{gettext('Name')}</th>
|
||||
<th width="20%">{gettext('Shared from')}</th>
|
||||
<th width="26%">{gettext('At site')}</th>
|
||||
<th width="20%">{gettext('Time')}</th>
|
||||
<th width="10%">{/* operations */}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{items.map((item, index) => {
|
||||
return <Item
|
||||
key={index}
|
||||
item={item}
|
||||
deleteShare={this.props.deleteShare}
|
||||
/>;
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
|
||||
return items.length ? table : emptyTip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Content.propTypes = {
|
||||
loading: PropTypes.bool.isRequired,
|
||||
errorMsg: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
class Item extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
showOpIcon: false,
|
||||
isOpMenuOpen: false // for mobile
|
||||
};
|
||||
}
|
||||
|
||||
toggleOpMenu = () => {
|
||||
this.setState({
|
||||
isOpMenuOpen: !this.state.isOpMenuOpen
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOver = () => {
|
||||
this.setState({
|
||||
showOpIcon: true
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseOut = () => {
|
||||
this.setState({
|
||||
showOpIcon: false
|
||||
});
|
||||
}
|
||||
|
||||
deleteShare = () => {
|
||||
this.props.deleteShare(this.props.item);
|
||||
}
|
||||
|
||||
render() {
|
||||
const item = this.props.item;
|
||||
|
||||
item.icon_url = Utils.getLibIconUrl(item);
|
||||
item.icon_title = Utils.getLibIconTitle(item);
|
||||
item.url = `${siteRoot}#shared-libs/lib/${item.repo_id}/`;
|
||||
|
||||
let shareRepoUrl =`${siteRoot}remote-library/${this.props.item.provider_id}/${this.props.item.repo_id}/${Utils.encodePath(this.props.item.repo_name)}/`;
|
||||
let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
|
||||
let deleteIcon = `action-icon sf2-icon-x3 ${iconVisibility ? 'invisible' : ''}`;
|
||||
return (
|
||||
<Fragment>
|
||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
||||
<td><img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" /></td>
|
||||
<td><Link to={shareRepoUrl}>{item.repo_name}</Link></td>
|
||||
<td>{item.from_user}</td>
|
||||
<td>{item.from_server_url}</td>
|
||||
<td title={moment(item.last_modified).format('llll')}>{moment(item.ctime).fromNow()}</td>
|
||||
<td>
|
||||
<a href="#" className={deleteIcon} title={gettext('Remove')} onClick={this.deleteShare}></a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Item.propTypes = {
|
||||
item: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
class SharedWithOCM extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
loading: true,
|
||||
errorMsg: '',
|
||||
items: [],
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
seafileAPI.listOCMSharesReceived().then((res) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
items: res.data.ocm_share_received_list
|
||||
});
|
||||
}).catch((error) => {
|
||||
if (error.response) {
|
||||
if (error.response.status == 403) {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Permission denied')
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Error')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.setState({
|
||||
loading: false,
|
||||
errorMsg: gettext('Please check the network.')
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deleteShare = (item) => {
|
||||
let { id } = item;
|
||||
seafileAPI.deleteOCMShareReceived(id).then((res) => {
|
||||
toaster.success(gettext('delete success.'));
|
||||
let items = this.state.items.filter(item => {
|
||||
return item.id != id;
|
||||
});
|
||||
this.setState({items: items});
|
||||
}).catch(error => {
|
||||
let errMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errMessage);
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="main-panel-center">
|
||||
<div className="cur-view-container">
|
||||
<div className="cur-view-path">
|
||||
<h3 className="sf-heading m-0">{gettext('Shared from other servers')}</h3>
|
||||
</div>
|
||||
<div className="cur-view-content">
|
||||
<Content
|
||||
loading={this.state.loading}
|
||||
errorMsg={this.state.errorMsg}
|
||||
items={this.state.items}
|
||||
deleteShare={this.deleteShare}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default SharedWithOCM;
|
@@ -65,6 +65,7 @@ export const customNavItems = window.app.pageOptions.customNavItems;
|
||||
export const enableShowContactEmailWhenSearchUser = window.app.pageOptions.enableShowContactEmailWhenSearchUser;
|
||||
export const maxUploadFileSize = window.app.pageOptions.maxUploadFileSize;
|
||||
export const maxNumberOfFilesForFileupload = window.app.pageOptions.maxNumberOfFilesForFileupload;
|
||||
export const enableOCM = window.app.pageOptions.enableOCM;
|
||||
|
||||
export const curNoteMsg = window.app.pageOptions.curNoteMsg;
|
||||
export const curNoteID = window.app.pageOptions.curNoteID;
|
||||
|
Reference in New Issue
Block a user