mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-25 14:50:29 +00:00
Shared from other servers (#8099)
* [Shared from other servers('shared-with-ocm/')] added nav items for it in the side nav panel; added content for it in the 'Files' page; a new version of its independent page) * [Shared from other servers] side panel: highlight the current nav item for the page * [Shared from other servers('shared-with-ocm/')] added 'sort' & 'star/unstar' * [Shared from other servers('shared-with-ocm/')] removed 'star/unstar' * [Shared from other servers('shared-with-ocm/')] update for 'sort' and the side panel * [Shared from other servers('shared-with-ocm/')] dir view: modified the text for the root element of the path * [Shared from other servers('shared-with-ocm/')] dir view: fixed the icons for the file items * [Shared from other servers('shared-with-ocm/')] dir view: improved 'download' & 'hover', removed the code for 'delete'(not supported) * [Shared from other servers('shared-with-ocm/')] dir view: added 'upload'(as a dropdown menu); redesigned the path bar * [Shared from other servers('/ocm-via-webdav/')] redesigned it * [Shared from other servers('shared-with-ocm/')] update * [Shared from other servers('shared-with-ocm/')] i18n: changed some text to the existing text as required
This commit is contained in:
@@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from '@gatsbyjs/reach-router';
|
import { Link } from '@gatsbyjs/reach-router';
|
||||||
import {
|
import {
|
||||||
gettext, siteRoot, canAddRepo, canViewOrg
|
gettext, siteRoot, canAddRepo, canViewOrg, enableOCM, enableOCMViaWebdav
|
||||||
} from '../utils/constants';
|
} from '../utils/constants';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
@@ -72,6 +72,32 @@ class FilesSubNav extends React.Component {
|
|||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
{enableOCM &&
|
||||||
|
<li className={`nav-item ${this.getActiveClass('shared-with-ocm')}`}>
|
||||||
|
<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="sf3-font-share-with-me sf3-font nav-icon" aria-hidden="true"></span>
|
||||||
|
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
{enableOCMViaWebdav &&
|
||||||
|
<li className={`nav-item ${this.getActiveClass('ocm-via-webdav')}`}>
|
||||||
|
<Link
|
||||||
|
to={siteRoot + 'ocm-via-webdav/'}
|
||||||
|
className={`nav-link ellipsis ${this.getActiveClass('ocm-via-webdav')}`}
|
||||||
|
title={gettext('Shared from other servers')}
|
||||||
|
onClick={(e) => this.tabItemClick(e, 'ocm-via-webdav')}
|
||||||
|
>
|
||||||
|
<span className="sf3-font-share-with-me sf3-font nav-icon" aria-hidden="true"></span>
|
||||||
|
<span className="nav-text">{gettext('Shared from other servers')}</span>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
{this.renderSharedGroups()}
|
{this.renderSharedGroups()}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@@ -6,7 +6,8 @@ import {
|
|||||||
gettext, siteRoot, canAddGroup, canAddRepo, canShareRepo,
|
gettext, siteRoot, canAddGroup, canAddRepo, canShareRepo,
|
||||||
canGenerateShareLink, canGenerateUploadLink, canInvitePeople,
|
canGenerateShareLink, canGenerateUploadLink, canInvitePeople,
|
||||||
enableTC, sideNavFooterCustomHtml, enableShowAbout, showWechatSupportGroup,
|
enableTC, sideNavFooterCustomHtml, enableShowAbout, showWechatSupportGroup,
|
||||||
canViewOrg, isPro, isDBSqlite3, customNavItems, mediaUrl
|
canViewOrg, enableOCM, enableOCMViaWebdav,
|
||||||
|
isPro, isDBSqlite3, customNavItems, mediaUrl
|
||||||
} from '../utils/constants';
|
} from '../utils/constants';
|
||||||
import { seafileAPI } from '../utils/seafile-api';
|
import { seafileAPI } from '../utils/seafile-api';
|
||||||
import { Utils } from '../utils/utils';
|
import { Utils } from '../utils/utils';
|
||||||
@@ -62,7 +63,7 @@ class MainSideNav extends React.Component {
|
|||||||
return group;
|
return group;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.filesNavHeight = (groupList.length + (canAddGroup ? 1 : 0) + (canAddRepo ? 1 : 0) + (canViewOrg ? 1 : 0) + 1) * SUB_NAV_ITEM_HEIGHT;
|
this.filesNavHeight = (groupList.length + (canAddGroup ? 1 : 0) + (canAddRepo ? 1 : 0) + (canViewOrg ? 1 : 0) + (enableOCM ? 1 : 0) + (enableOCMViaWebdav ? 1 : 0) + 1) * SUB_NAV_ITEM_HEIGHT;
|
||||||
this.setState({
|
this.setState({
|
||||||
groupItems: groupList.sort((a, b) => {
|
groupItems: groupList.sort((a, b) => {
|
||||||
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
|
||||||
|
@@ -13,7 +13,7 @@ class SortMenu extends React.Component {
|
|||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.sortOptions = [
|
this.sortOptions = this.props.sortOptions || [
|
||||||
{ value: 'name-asc', text: gettext('By name ascending') },
|
{ value: 'name-asc', text: gettext('By name ascending') },
|
||||||
{ value: 'name-desc', text: gettext('By name descending') },
|
{ value: 'name-desc', text: gettext('By name descending') },
|
||||||
{ value: 'size-asc', text: gettext('By size ascending') },
|
{ value: 'size-asc', text: gettext('By size ascending') },
|
||||||
|
@@ -2,7 +2,7 @@ import React, { Component } from 'react';
|
|||||||
import cookie from 'react-cookies';
|
import cookie from 'react-cookies';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { gettext, canAddRepo, canViewOrg } from '../../utils/constants';
|
import { gettext, canAddRepo, canViewOrg, enableOCM } from '../../utils/constants';
|
||||||
import Repo from '../../models/repo';
|
import Repo from '../../models/repo';
|
||||||
import Group from '../../models/group';
|
import Group from '../../models/group';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
@@ -16,6 +16,7 @@ import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
|
|||||||
import MylibRepoListView from '../../pages/my-libs/mylib-repo-list-view';
|
import MylibRepoListView from '../../pages/my-libs/mylib-repo-list-view';
|
||||||
import SharedLibraries from '../../pages/shared-libs';
|
import SharedLibraries from '../../pages/shared-libs';
|
||||||
import SharedWithAll from '../../pages/shared-with-all';
|
import SharedWithAll from '../../pages/shared-with-all';
|
||||||
|
import SharedWithOCM from '../../pages/share-with-ocm/shared-with-ocm';
|
||||||
import GroupItem from '../../pages/groups/group-item';
|
import GroupItem from '../../pages/groups/group-item';
|
||||||
import { GroupsReposManager } from './groups-repos-manager';
|
import { GroupsReposManager } from './groups-repos-manager';
|
||||||
import EventBus from '../../components/common/event-bus';
|
import EventBus from '../../components/common/event-bus';
|
||||||
@@ -62,7 +63,6 @@ class Libraries extends Component {
|
|||||||
this.unsubscribeUnsharedRepoToGroup();
|
this.unsubscribeUnsharedRepoToGroup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
initLibraries = () => {
|
initLibraries = () => {
|
||||||
const promiseListRepos = seafileAPI.listRepos({ 'type': ['mine', 'shared', 'public'] });
|
const promiseListRepos = seafileAPI.listRepos({ 'type': ['mine', 'shared', 'public'] });
|
||||||
const promiseListGroups = seafileAPI.listGroups(true);
|
const promiseListGroups = seafileAPI.listGroups(true);
|
||||||
@@ -515,6 +515,17 @@ class Libraries extends Component {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{enableOCM &&
|
||||||
|
<div className="pb-3">
|
||||||
|
<SharedWithOCM
|
||||||
|
inAllLibs={true}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
|
sortBy={this.state.sortBy}
|
||||||
|
sortOrder={this.state.sortOrder}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
{groupList.length > 0 && groupList.map((group) => {
|
{groupList.length > 0 && groupList.map((group) => {
|
||||||
return (
|
return (
|
||||||
<GroupItem
|
<GroupItem
|
||||||
|
@@ -9,6 +9,8 @@ import toaster from '../../components/toast';
|
|||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
import EmptyTip from '../../components/empty-tip';
|
import EmptyTip from '../../components/empty-tip';
|
||||||
|
|
||||||
|
import '../../css/lib-content-view.css';
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
class OCMViaWebdav extends Component {
|
class OCMViaWebdav extends Component {
|
||||||
@@ -31,11 +33,12 @@ class OCMViaWebdav extends Component {
|
|||||||
getAllReceivedShares = () => {
|
getAllReceivedShares = () => {
|
||||||
const url = seafileAPI.server + '/ocm-via-webdav/received-shares/';
|
const url = seafileAPI.server + '/ocm-via-webdav/received-shares/';
|
||||||
seafileAPI.req.get(url).then((res) => {
|
seafileAPI.req.get(url).then((res) => {
|
||||||
|
const { received_share_list } = res.data;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
shareID: '',
|
shareID: '',
|
||||||
path: '',
|
path: '',
|
||||||
items: res.data.received_share_list,
|
items: this.sortItems(received_share_list)
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
@@ -59,18 +62,18 @@ class OCMViaWebdav extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
openFolder = (item) => {
|
openFolder = (item) => {
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = seafileAPI.server + '/ocm-via-webdav/received-shares/' + item.id + '/?path=' + item.path;
|
const url = seafileAPI.server + '/ocm-via-webdav/received-shares/' + item.id + '/?path=' + item.path;
|
||||||
seafileAPI.req.get(url).then((res) => {
|
seafileAPI.req.get(url).then((res) => {
|
||||||
|
const { received_share_list, parent_dir } = res.data;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
shareID: item.id,
|
shareID: item.id,
|
||||||
path: res.data.parent_dir,
|
path: parent_dir,
|
||||||
items: res.data.received_share_list,
|
items: this.sortItems(received_share_list)
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
@@ -79,17 +82,17 @@ class OCMViaWebdav extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onPathClick = (path) => {
|
onPathClick = (path) => {
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: true,
|
loading: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = seafileAPI.server + '/ocm-via-webdav/received-shares/' + this.state.shareID + '/?path=' + path;
|
const url = seafileAPI.server + '/ocm-via-webdav/received-shares/' + this.state.shareID + '/?path=' + path;
|
||||||
seafileAPI.req.get(url).then((res) => {
|
seafileAPI.req.get(url).then((res) => {
|
||||||
|
const { received_share_list, parent_dir } = res.data;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
items: res.data.received_share_list,
|
path: parent_dir,
|
||||||
path: res.data.parent_dir,
|
items: this.sortItems(received_share_list)
|
||||||
});
|
});
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
let errMessage = Utils.getErrorMsg(error);
|
let errMessage = Utils.getErrorMsg(error);
|
||||||
@@ -97,25 +100,33 @@ class OCMViaWebdav extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sortItems = (items) => {
|
||||||
|
return items.sort((a, b) => {
|
||||||
|
return a.is_dir ? -1 : 1;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { loading, errorMsg, items, shareID, path } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="main-panel-center">
|
<div className="main-panel-center">
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<div className="cur-view-path align-items-center">
|
<div className="cur-view-path align-items-center">
|
||||||
<DirPath
|
<DirPath
|
||||||
shareID={this.state.shareID}
|
shareID={shareID}
|
||||||
currentPath={this.state.path}
|
currentPath={path}
|
||||||
onPathClick={this.onPathClick}
|
onPathClick={this.onPathClick}
|
||||||
getAllReceivedShares={this.getAllReceivedShares}
|
getAllReceivedShares={this.getAllReceivedShares}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
<div className="cur-view-content">
|
||||||
<Content
|
<Content
|
||||||
loading={this.state.loading}
|
loading={loading}
|
||||||
errorMsg={this.state.errorMsg}
|
errorMsg={errorMsg}
|
||||||
items={this.state.items}
|
items={items}
|
||||||
path={this.state.path}
|
path={path}
|
||||||
leaveShare={this.leaveShare}
|
leaveShare={this.leaveShare}
|
||||||
openFolder={this.openFolder}
|
openFolder={this.openFolder}
|
||||||
/>
|
/>
|
||||||
@@ -143,29 +154,29 @@ class Content extends Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, errorMsg, items, path } = this.props;
|
const { loading, errorMsg, items, path } = this.props;
|
||||||
const emptyTip = (
|
|
||||||
<EmptyTip
|
|
||||||
title={gettext('No libraries have been shared with you')}
|
|
||||||
text={gettext('No libraries have been shared with you from other servers.')}
|
|
||||||
>
|
|
||||||
</EmptyTip>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
} else if (errorMsg) {
|
} else if (errorMsg) {
|
||||||
return <p className="error text-center">{errorMsg}</p>;
|
return <p className="error text-center">{errorMsg}</p>;
|
||||||
} else {
|
} else {
|
||||||
|
const emptyTip = (
|
||||||
|
<EmptyTip
|
||||||
|
title={gettext('No files or folders have been shared with you')}
|
||||||
|
text={gettext('No files or folders have been shared with you from other servers.')}
|
||||||
|
>
|
||||||
|
</EmptyTip>
|
||||||
|
);
|
||||||
|
|
||||||
const table = (
|
const table = (
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="5%"></th>
|
<th width="5%"></th>
|
||||||
<th width="30%">{gettext('Name')}</th>
|
<th width="40%">{gettext('Name')}</th>
|
||||||
<th width="35%">{gettext('Shared By')}</th>
|
<th width="10%">{/* operations */}</th>
|
||||||
<th width="20%">{gettext('Time')}</th>
|
<th width="30%">{gettext('Shared By')}</th>
|
||||||
<th width="5%">{/* operations */}</th>
|
<th width="15%">{gettext('Sharing Time')}</th>
|
||||||
<th width="5%">{/* operations */}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -188,7 +199,6 @@ class Content extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Content.propTypes = {
|
Content.propTypes = {
|
||||||
data: PropTypes.object.isRequired,
|
|
||||||
loading: PropTypes.bool.isRequired,
|
loading: PropTypes.bool.isRequired,
|
||||||
errorMsg: PropTypes.string.isRequired,
|
errorMsg: PropTypes.string.isRequired,
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
@@ -206,18 +216,21 @@ class Item extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isHighlighted: false,
|
||||||
isOpIconShown: false
|
isOpIconShown: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOver = () => {
|
handleMouseOver = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isHighlighted: true,
|
||||||
isOpIconShown: true
|
isOpIconShown: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMouseOut = () => {
|
handleMouseOut = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isHighlighted: false,
|
||||||
isOpIconShown: false
|
isOpIconShown: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -227,8 +240,7 @@ class Item extends Component {
|
|||||||
window.location.href = downloadUrl;
|
window.location.href = downloadUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
leaveShare = (e) => {
|
leaveShare = () => {
|
||||||
e.preventDefault();
|
|
||||||
this.props.leaveShare(this.props.item);
|
this.props.leaveShare(this.props.item);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -238,8 +250,8 @@ class Item extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const item = this.props.item;
|
const { item, path } = this.props;
|
||||||
const { isOpIconShown } = this.state;
|
const { isHighlighted, isOpIconShown } = this.state;
|
||||||
|
|
||||||
if (item.is_dir) {
|
if (item.is_dir) {
|
||||||
item.icon_url = Utils.getFolderIconUrl();
|
item.icon_url = Utils.getFolderIconUrl();
|
||||||
@@ -247,17 +259,27 @@ class Item extends Component {
|
|||||||
item.icon_url = Utils.getFileIconUrl(item.name);
|
item.icon_url = Utils.getFileIconUrl(item.name);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}>
|
<tr
|
||||||
<td><img src={item.icon_url} width="24" alt="" /></td>
|
className={isHighlighted ? 'tr-highlight' : ''}
|
||||||
|
onMouseOver={this.handleMouseOver}
|
||||||
|
onMouseOut={this.handleMouseOut}
|
||||||
|
onFocus={this.handleMouseOver}
|
||||||
|
>
|
||||||
|
<td className="text-center">
|
||||||
|
<img src={item.icon_url} width="24" alt="" />
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{item.is_dir ? <a href="#" onClick={this.openFolder}>{item.name}</a> : item.name}
|
{item.is_dir
|
||||||
|
? <a href="#" onClick={this.openFolder}>{item.name}</a>
|
||||||
|
: item.name
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{item.is_dir ? '' : <i className={`op-icon sf3-font sf3-font-download1 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Download')} onClick={this.downloadFile}></i>}
|
||||||
|
{path ? '' : <i className={`op-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Leave Share')} onClick={this.leaveShare}></i>}
|
||||||
</td>
|
</td>
|
||||||
<td>{item.shared_by}</td>
|
<td>{item.shared_by}</td>
|
||||||
<td title={dayjs(item.last_modified).format('dddd, MMMM D, YYYY h:mm:ss A')}>{dayjs(item.ctime).fromNow()}</td>
|
<td title={dayjs(item.ctime).format('dddd, MMMM D, YYYY h:mm:ss A')}>{dayjs(item.ctime).fromNow()}</td>
|
||||||
<td>{item.is_dir ? '' : <a href="#" className={`action-icon sf2-icon-download ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Download')} onClick={this.downloadFile}></a>}
|
|
||||||
</td>
|
|
||||||
<td>{this.props.path ? '' : <a href="#" className={`action-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Leave Share')} onClick={this.leaveShare}></a>}
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -298,7 +320,7 @@ class DirPath extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
<span className="path-split">/</span>
|
<span className="path-split">/</span>
|
||||||
<span className="path-file-name">{item}</span>
|
<span className="last-path-item" title={item}>{item}</span>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -310,7 +332,14 @@ class DirPath extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Fragment key={index} >
|
<Fragment key={index} >
|
||||||
<span className="path-split">/</span>
|
<span className="path-split">/</span>
|
||||||
<a className="path-link" data-path={nodePath} onClick={this.onPathClick}>{item}</a>
|
<span
|
||||||
|
className="path-item"
|
||||||
|
data-path={nodePath}
|
||||||
|
onClick={this.onPathClick}
|
||||||
|
title={item}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</span>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -319,11 +348,17 @@ class DirPath extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let pathElem = this.turnPathToLink(this.props.currentPath);
|
const { currentPath } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="path-container">
|
<div className="path-container dir-view-path">
|
||||||
<a href="#" onClick={this.props.getAllReceivedShares}>{gettext('All')}</a>
|
<span
|
||||||
{pathElem}
|
className="path-item mw-100"
|
||||||
|
onClick={this.props.getAllReceivedShares}
|
||||||
|
title={gettext('Shared from other servers')}
|
||||||
|
>
|
||||||
|
{gettext('Shared from other servers')}
|
||||||
|
</span>
|
||||||
|
{currentPath && this.turnPathToLink(currentPath)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
87
frontend/src/pages/share-with-ocm/last-path-item-wrapper.js
Normal file
87
frontend/src/pages/share-with-ocm/last-path-item-wrapper.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
|
||||||
|
import { gettext } from '../../utils/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
userPerm: PropTypes.string.isRequired,
|
||||||
|
openFileInput: PropTypes.func.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
class LastPathItemWrapper extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
isDesktopMenuOpen: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDesktopOpMenu = () => {
|
||||||
|
this.setState({ isDesktopMenuOpen: !this.state.isDesktopMenuOpen });
|
||||||
|
};
|
||||||
|
|
||||||
|
onDropdownToggleKeyDown = (e) => {
|
||||||
|
if (e.key == 'Enter' || e.key == 'Space') {
|
||||||
|
this.toggleDesktopOpMenu();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMenuItemKeyDown = (item, e) => {
|
||||||
|
if (e.key == 'Enter' || e.key == 'Space') {
|
||||||
|
item.onClick();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { userPerm } = this.props;
|
||||||
|
let dropdownMenu = null;
|
||||||
|
if (userPerm == 'rw') {
|
||||||
|
const opList = [
|
||||||
|
{
|
||||||
|
'icon': 'upload-files',
|
||||||
|
'text': gettext('Upload'),
|
||||||
|
'onClick': this.props.openFileInput
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
dropdownMenu = (
|
||||||
|
<Dropdown isOpen={this.state.isDesktopMenuOpen} toggle={this.toggleDesktopOpMenu}>
|
||||||
|
<DropdownToggle
|
||||||
|
tag="div"
|
||||||
|
role="button"
|
||||||
|
className="path-item"
|
||||||
|
onClick={this.toggleDesktopOpMenu}
|
||||||
|
onKeyDown={this.onDropdownToggleKeyDown}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
>
|
||||||
|
<i className="sf3-font-new sf3-font"></i>
|
||||||
|
<i className="sf3-font-down sf3-font path-item-dropdown-toggle"></i>
|
||||||
|
</DropdownToggle>
|
||||||
|
<DropdownMenu positionFixed={true}>
|
||||||
|
{opList.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<DropdownItem key={index} onClick={item.onClick} onKeyDown={this.onMenuItemKeyDown.bind(this, item)}>
|
||||||
|
<i className={`sf3-font-${item.icon} sf3-font mr-2 dropdown-item-icon`}></i>
|
||||||
|
{item.text}
|
||||||
|
</DropdownItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</DropdownMenu>
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="dir-operation">
|
||||||
|
{this.props.children}
|
||||||
|
{userPerm == 'rw' && dropdownMenu}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LastPathItemWrapper.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default LastPathItemWrapper;
|
@@ -1,6 +1,7 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Link } from '@gatsbyjs/reach-router';
|
import { Link } from '@gatsbyjs/reach-router';
|
||||||
|
import classnames from 'classnames';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import { gettext } from '../../utils/constants';
|
import { gettext } from '../../utils/constants';
|
||||||
@@ -14,18 +15,21 @@ class DirentItem extends React.Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
isHighlighted: false,
|
||||||
isOpIconShown: false
|
isOpIconShown: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOver = () => {
|
handleMouseOver = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isHighlighted: true,
|
||||||
isOpIconShown: true
|
isOpIconShown: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMouseOut = () => {
|
handleMouseOut = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isHighlighted: false,
|
||||||
isOpIconShown: false
|
isOpIconShown: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -34,19 +38,22 @@ class DirentItem extends React.Component {
|
|||||||
this.props.openFolder(this.props.dirent);
|
this.props.openFolder(this.props.dirent);
|
||||||
};
|
};
|
||||||
|
|
||||||
downloadDirent = (e) => {
|
downloadDirent = () => {
|
||||||
e.preventDefault();
|
|
||||||
this.props.downloadDirent(this.props.dirent);
|
this.props.downloadDirent(this.props.dirent);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { isOpIconShown } = this.state;
|
let { isHighlighted, isOpIconShown } = this.state;
|
||||||
let { dirent } = this.props;
|
let { dirent } = this.props;
|
||||||
let iconUrl = Utils.getDirentIcon(dirent);
|
let iconUrl = Utils.getDirentIcon(dirent);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<tr onMouseEnter={this.handleMouseOver} onMouseLeave={this.handleMouseOut}>
|
<tr
|
||||||
|
className={classnames({ 'tr-highlight': isHighlighted })}
|
||||||
|
onMouseEnter={this.handleMouseOver}
|
||||||
|
onMouseLeave={this.handleMouseOut}
|
||||||
|
>
|
||||||
<td className="text-center"><img src={iconUrl} width="24" alt='' /></td>
|
<td className="text-center"><img src={iconUrl} width="24" alt='' /></td>
|
||||||
<td>
|
<td>
|
||||||
{dirent.is_file ?
|
{dirent.is_file ?
|
||||||
@@ -56,7 +63,7 @@ class DirentItem extends React.Component {
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{isOpIconShown && dirent.is_file &&
|
{isOpIconShown && dirent.is_file &&
|
||||||
<a href="#" className="op-icon sf3-font sf3-font-download1" title={gettext('Download')} onClick={this.downloadDirent}></a>
|
<i role="button" className="op-icon sf3-font sf3-font-download1" title={gettext('Download')} onClick={this.downloadDirent}></i>
|
||||||
}
|
}
|
||||||
</td>
|
</td>
|
||||||
<td>{Utils.bytesToSize(dirent.size)}</td>
|
<td>{Utils.bytesToSize(dirent.size)}</td>
|
||||||
@@ -70,7 +77,6 @@ class DirentItem extends React.Component {
|
|||||||
DirentItem.propTypes = {
|
DirentItem.propTypes = {
|
||||||
dirent: PropTypes.object.isRequired,
|
dirent: PropTypes.object.isRequired,
|
||||||
openFolder: PropTypes.func.isRequired,
|
openFolder: PropTypes.func.isRequired,
|
||||||
deleteDirent: PropTypes.func.isRequired,
|
|
||||||
downloadDirent: PropTypes.func.isRequired,
|
downloadDirent: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -93,7 +99,7 @@ class DirContent extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<table className="table-hover">
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th width="5%">{/* icon*/}</th>
|
<th width="5%">{/* icon*/}</th>
|
||||||
@@ -109,7 +115,6 @@ class DirContent extends React.Component {
|
|||||||
key={index}
|
key={index}
|
||||||
dirent={dirent}
|
dirent={dirent}
|
||||||
openFolder={this.props.openFolder}
|
openFolder={this.props.openFolder}
|
||||||
deleteDirent={this.props.deleteDirent}
|
|
||||||
downloadDirent={this.props.downloadDirent}
|
downloadDirent={this.props.downloadDirent}
|
||||||
/>;
|
/>;
|
||||||
})}
|
})}
|
||||||
@@ -125,7 +130,6 @@ DirContent.propTypes = {
|
|||||||
errorMsg: PropTypes.string.isRequired,
|
errorMsg: PropTypes.string.isRequired,
|
||||||
direntList: PropTypes.array.isRequired,
|
direntList: PropTypes.array.isRequired,
|
||||||
openFolder: PropTypes.func.isRequired,
|
openFolder: PropTypes.func.isRequired,
|
||||||
deleteDirent: PropTypes.func.isRequired,
|
|
||||||
downloadDirent: PropTypes.func.isRequired,
|
downloadDirent: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -3,13 +3,16 @@ import PropTypes from 'prop-types';
|
|||||||
import { Link } from '@gatsbyjs/reach-router';
|
import { Link } from '@gatsbyjs/reach-router';
|
||||||
import { siteRoot, gettext } from '../../utils/constants';
|
import { siteRoot, gettext } from '../../utils/constants';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
|
import LastPathItemWrapper from './last-path-item-wrapper';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
repoID: PropTypes.string.isRequired,
|
||||||
repoName: PropTypes.string.isRequired,
|
repoName: PropTypes.string.isRequired,
|
||||||
currentPath: PropTypes.string.isRequired,
|
currentPath: PropTypes.string.isRequired,
|
||||||
onPathClick: PropTypes.func.isRequired,
|
onPathClick: PropTypes.func.isRequired,
|
||||||
onTabNavClick: PropTypes.func.isRequired,
|
onTabNavClick: PropTypes.func.isRequired,
|
||||||
repoID: PropTypes.string.isRequired,
|
userPerm: PropTypes.string.isRequired,
|
||||||
|
openFileInput: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
class DirPath extends React.Component {
|
class DirPath extends React.Component {
|
||||||
@@ -31,7 +34,12 @@ class DirPath extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
<span className="path-split">/</span>
|
<span className="path-split">/</span>
|
||||||
<span className="path-file-name">{item}</span>
|
<LastPathItemWrapper
|
||||||
|
userPerm={this.props.userPerm}
|
||||||
|
openFileInput={this.props.openFileInput}
|
||||||
|
>
|
||||||
|
<span className="last-path-item" title={item}>{item}</span>
|
||||||
|
</LastPathItemWrapper>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -39,7 +47,7 @@ class DirPath extends React.Component {
|
|||||||
return (
|
return (
|
||||||
<Fragment key={index} >
|
<Fragment key={index} >
|
||||||
<span className="path-split">/</span>
|
<span className="path-split">/</span>
|
||||||
<a className="path-link" data-path={nodePath} onClick={this.onPathClick}>{item}</a>
|
<span className="path-item" role="button" data-path={nodePath} onClick={this.onPathClick} title={item}>{item}</span>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -48,16 +56,23 @@ class DirPath extends React.Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let { currentPath, repoName } = this.props;
|
const { currentPath, repoName } = this.props;
|
||||||
let pathElem = this.turnPathToLink(currentPath);
|
const pathElem = this.turnPathToLink(currentPath);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="path-container">
|
<div className="path-container dir-view-path">
|
||||||
<Link to={siteRoot + 'shared-with-ocm/'} className="normal" onClick={(e) => this.props.onTabNavClick('shared-with-ocm')}>{gettext('All')}</Link>
|
<Link to={siteRoot + 'shared-with-ocm/'} className="path-item normal mw-100" onClick={(e) => this.props.onTabNavClick('shared-with-ocm')} title={gettext('Shared from other servers')}>{gettext('Shared from other servers')}</Link>
|
||||||
<span className="path-split">/</span>
|
<span className="path-split">/</span>
|
||||||
{(currentPath === '/' || currentPath === '') ?
|
{(currentPath === '/' || currentPath === '')
|
||||||
<span className="path-repo-name">{repoName}</span> :
|
? (
|
||||||
<a className="path-link" data-path="/" onClick={this.onPathClick}>{repoName}</a>
|
<LastPathItemWrapper
|
||||||
|
userPerm={this.props.userPerm}
|
||||||
|
openFileInput={this.props.openFileInput}
|
||||||
|
>
|
||||||
|
<span className="last-path-item" title={repoName}>{repoName}</span>
|
||||||
|
</LastPathItemWrapper>
|
||||||
|
)
|
||||||
|
: <span role="button" className="path-item" data-path="/" onClick={this.onPathClick} title={repoName}>{repoName}</span>
|
||||||
}
|
}
|
||||||
{pathElem}
|
{pathElem}
|
||||||
</div>
|
</div>
|
||||||
|
@@ -3,16 +3,19 @@ import PropTypes from 'prop-types';
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { siteRoot } from '../../utils/constants';
|
import { siteRoot, gettext } from '../../utils/constants';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import DirPathBar from './remote-dir-path';
|
import DirPathBar from './remote-dir-path';
|
||||||
import DirContent from './remote-dir-content';
|
import DirContent from './remote-dir-content';
|
||||||
|
|
||||||
|
import '../../css/lib-content-view.css';
|
||||||
|
|
||||||
class Dirent {
|
class Dirent {
|
||||||
constructor(obj) {
|
constructor(obj) {
|
||||||
this.name = obj.name;
|
this.name = obj.name;
|
||||||
this.mtime = obj.mtime;
|
this.mtime = obj.mtime;
|
||||||
this.size = obj.size;
|
this.size = obj.size;
|
||||||
|
this.type = obj.type;
|
||||||
this.is_file = obj.type === 'file';
|
this.is_file = obj.type === 'file';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,6 +122,9 @@ class DirView extends Component {
|
|||||||
this.setState({
|
this.setState({
|
||||||
direntList: direntList
|
direntList: direntList
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const msg = gettext('Successfully added the file.');
|
||||||
|
toaster.success(msg);
|
||||||
});
|
});
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
let errMessage = Utils.getErrorMsg(err);
|
let errMessage = Utils.getErrorMsg(err);
|
||||||
@@ -127,15 +133,11 @@ class DirView extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, errorMsg, repoName, direntList, path } = this.state;
|
const { loading, errorMsg, repoName, direntList, path, userPerm } = this.state;
|
||||||
const { repoID } = this.props;
|
const { repoID } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<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>}
|
|
||||||
*/}
|
|
||||||
<div className="main-panel-center flex-row">
|
<div className="main-panel-center flex-row">
|
||||||
<div className="cur-view-container">
|
<div className="cur-view-container">
|
||||||
<div className="cur-view-path align-items-center">
|
<div className="cur-view-path align-items-center">
|
||||||
@@ -145,7 +147,10 @@ class DirView extends Component {
|
|||||||
currentPath={path}
|
currentPath={path}
|
||||||
onPathClick={this.onPathClick}
|
onPathClick={this.onPathClick}
|
||||||
onTabNavClick={this.props.onTabNavClick}
|
onTabNavClick={this.props.onTabNavClick}
|
||||||
|
userPerm={userPerm}
|
||||||
|
openFileInput={this.openFileInput}
|
||||||
/>
|
/>
|
||||||
|
<input className="d-none" type="file" onChange={this.onFileInputChange} ref={this.fileInput} />
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
<div className="cur-view-content">
|
||||||
<DirContent
|
<DirContent
|
||||||
@@ -153,7 +158,6 @@ class DirView extends Component {
|
|||||||
errorMsg={errorMsg}
|
errorMsg={errorMsg}
|
||||||
direntList={direntList}
|
direntList={direntList}
|
||||||
openFolder={this.openFolder}
|
openFolder={this.openFolder}
|
||||||
deleteDirent={this.deleteDirent}
|
|
||||||
downloadDirent={this.downloadDirent}
|
downloadDirent={this.downloadDirent}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -2,59 +2,133 @@ import React, { Component, Fragment } from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime';
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
||||||
import { Link } from '@gatsbyjs/reach-router';
|
import cookie from 'react-cookies';
|
||||||
|
import classnames from 'classnames';
|
||||||
|
import { Link, navigate } from '@gatsbyjs/reach-router';
|
||||||
|
import { Dropdown, DropdownToggle, DropdownItem } from 'reactstrap';
|
||||||
import { gettext, siteRoot } from '../../utils/constants';
|
import { gettext, siteRoot } from '../../utils/constants';
|
||||||
import { seafileAPI } from '../../utils/seafile-api';
|
import { seafileAPI } from '../../utils/seafile-api';
|
||||||
import { Utils } from '../../utils/utils';
|
import { Utils } from '../../utils/utils';
|
||||||
import toaster from '../../components/toast';
|
import toaster from '../../components/toast';
|
||||||
import Loading from '../../components/loading';
|
import Loading from '../../components/loading';
|
||||||
import EmptyTip from '../../components/empty-tip';
|
import EmptyTip from '../../components/empty-tip';
|
||||||
|
import ViewModes from '../../components/view-modes';
|
||||||
|
import ReposSortMenu from '../../components/sort-menu';
|
||||||
|
import SortOptionsDialog from '../../components/dialog/sort-options';
|
||||||
|
import LibsMobileThead from '../../components/libs-mobile-thead';
|
||||||
|
import { LIST_MODE } from '../../components/dir-view-mode/constants';
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
currentViewMode: PropTypes.string,
|
||||||
|
inAllLibs: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
|
||||||
class Content extends Component {
|
class Content extends Component {
|
||||||
|
|
||||||
render() {
|
sortByName = (e) => {
|
||||||
const { loading, errorMsg, items } = this.props;
|
e.preventDefault();
|
||||||
|
const sortBy = 'name';
|
||||||
|
const sortOrder = this.props.sortOrder == 'asc' ? 'desc' : 'asc';
|
||||||
|
this.props.sortItems(sortBy, sortOrder);
|
||||||
|
};
|
||||||
|
|
||||||
const emptyTip = (
|
renderItems = () => {
|
||||||
<EmptyTip
|
const { items, currentViewMode, inAllLibs } = this.props;
|
||||||
title={gettext('No libraries have been shared with you')}
|
const isDesktop = Utils.isDesktop();
|
||||||
text={gettext('No libraries have been shared with you from other servers.')}
|
return (
|
||||||
>
|
<>
|
||||||
</EmptyTip>
|
{items.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Item
|
||||||
|
key={index}
|
||||||
|
item={item}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
|
inAllLibs={inAllLibs}
|
||||||
|
isDesktop={isDesktop}
|
||||||
|
leaveShare={this.props.leaveShare}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loading, errorMsg, items, sortOrder, currentViewMode, inAllLibs } = this.props;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
} else if (errorMsg) {
|
} else if (errorMsg) {
|
||||||
return <p className="error text-center">{errorMsg}</p>;
|
return <p className="error text-center">{errorMsg}</p>;
|
||||||
} else {
|
} else {
|
||||||
const table = (
|
if (items.length == 0) {
|
||||||
<table>
|
const emptyTipTitle = gettext('No libraries have been shared with you');
|
||||||
<thead>
|
const emptyTip = inAllLibs
|
||||||
<tr>
|
? <p className={`libraries-empty-tip-in-${currentViewMode}-mode`}>{emptyTipTitle}</p>
|
||||||
<th width="4%"></th>
|
: (
|
||||||
<th width="20%">{gettext('Name')}</th>
|
<EmptyTip
|
||||||
<th width="20%">{gettext('Shared by')}</th>
|
title={emptyTipTitle}
|
||||||
<th width="26%">{gettext('At server')}</th>
|
text={gettext('No libraries have been shared with you from other servers.')}
|
||||||
<th width="20%">{gettext('Time')}</th>
|
>
|
||||||
<th width="10%">{/* operations */}</th>
|
</EmptyTip>
|
||||||
</tr>
|
);
|
||||||
</thead>
|
return emptyTip;
|
||||||
<tbody>
|
}
|
||||||
{items.map((item, index) => {
|
|
||||||
return <Item
|
const isDesktop = Utils.isDesktop();
|
||||||
key={index}
|
if (isDesktop) {
|
||||||
item={item}
|
const sortIcon = sortOrder === 'asc'
|
||||||
leaveShare={this.props.leaveShare}
|
? <span className="sf3-font sf3-font-down rotate-180 d-inline-block"></span>
|
||||||
/>;
|
: <span className="sf3-font sf3-font-down"></span>;
|
||||||
})}
|
|
||||||
</tbody>
|
return currentViewMode == LIST_MODE
|
||||||
</table>
|
? (
|
||||||
);
|
<table className={classnames({ 'table-thead-hidden': inAllLibs })}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="4%"></th>
|
||||||
|
<th width="3%"><span className="sr-only">{gettext('Library Type')}</span></th>
|
||||||
|
<th width="35%"><a className="d-block table-sort-op" href="#" onClick={this.sortByName}>{gettext('Name')} {this.props.sortBy === 'name' && sortIcon}</a></th>
|
||||||
|
<th width="10%"><span className="sr-only">{gettext('Actions')}</span></th>
|
||||||
|
{inAllLibs
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
<th width="14%">{gettext('Size')}</th>
|
||||||
|
<th width="17%">{gettext('Last Update')}</th>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<th width="31%">{gettext('At server')}</th>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<th width="17%">{gettext('Owner')}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{this.renderItems()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div className="d-flex justify-content-between flex-wrap">
|
||||||
|
{this.renderItems()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
} else { // mobile
|
||||||
|
return (
|
||||||
|
<table className="table-thead-hidden">
|
||||||
|
<LibsMobileThead inAllLibs={inAllLibs} />
|
||||||
|
<tbody>
|
||||||
|
{this.renderItems()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return items.length ? table : emptyTip;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,18 +145,22 @@ class Item extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isOpIconShown: false
|
isHighlighted: false,
|
||||||
|
isOpIconShown: false,
|
||||||
|
isItemMenuShow: false // for mobile
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
handleMouseOver = () => {
|
handleMouseOver = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isHighlighted: true,
|
||||||
isOpIconShown: true
|
isOpIconShown: true
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMouseOut = () => {
|
handleMouseOut = () => {
|
||||||
this.setState({
|
this.setState({
|
||||||
|
isHighlighted: false,
|
||||||
isOpIconShown: false
|
isOpIconShown: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -92,26 +170,113 @@ class Item extends Component {
|
|||||||
this.props.leaveShare(this.props.item);
|
this.props.leaveShare(this.props.item);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleOperationMenu = () => {
|
||||||
|
this.setState({ isItemMenuShow: !this.state.isItemMenuShow });
|
||||||
|
};
|
||||||
|
|
||||||
|
visitRepo = () => {
|
||||||
|
navigate(this.repoURL);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const item = this.props.item;
|
const { item, isDesktop, currentViewMode, inAllLibs } = this.props;
|
||||||
const { isOpIconShown } = this.state;
|
const { isHighlighted, isOpIconShown } = this.state;
|
||||||
|
|
||||||
item.icon_url = Utils.getLibIconUrl(item);
|
item.icon_url = Utils.getLibIconUrl(item);
|
||||||
item.icon_title = Utils.getLibIconTitle(item);
|
item.icon_title = Utils.getLibIconTitle(item);
|
||||||
|
|
||||||
let shareRepoUrl = `${siteRoot}remote-library/${this.props.item.provider_id}/${this.props.item.repo_id}/${Utils.encodePath(this.props.item.repo_name)}/`;
|
let shareRepoUrl = `${siteRoot}remote-library/${this.props.item.provider_id}/${this.props.item.repo_id}/${Utils.encodePath(this.props.item.repo_name)}/`;
|
||||||
return (
|
this.repoURL = shareRepoUrl;
|
||||||
<tr onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut} onFocus={this.handleMouseOver}>
|
|
||||||
<td><img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" /></td>
|
if (isDesktop) {
|
||||||
<td><Link to={shareRepoUrl}>{item.repo_name}</Link></td>
|
return currentViewMode == LIST_MODE
|
||||||
<td>{item.from_user}</td>
|
? (
|
||||||
<td>{item.from_server_url}</td>
|
<tr
|
||||||
<td title={dayjs(item.last_modified).format('dddd, MMMM D, YYYY h:mm:ss A')}>{dayjs(item.ctime).fromNow()}</td>
|
className={isHighlighted ? 'tr-highlight' : ''}
|
||||||
<td>
|
onMouseOver={this.handleMouseOver}
|
||||||
<a href="#" role="button" className={`action-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Leave Share')} aria-label={gettext('Leave Share')} onClick={this.leaveShare}></a>
|
onMouseOut={this.handleMouseOut}
|
||||||
</td>
|
onFocus={this.handleMouseOver}
|
||||||
</tr>
|
>
|
||||||
);
|
<td></td>
|
||||||
|
<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>
|
||||||
|
<i role="button" className={`op-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Leave Share')} aria-label={gettext('Leave Share')} onClick={this.leaveShare}></i>
|
||||||
|
</td>
|
||||||
|
{inAllLibs
|
||||||
|
? (
|
||||||
|
<>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<>
|
||||||
|
<td>{item.from_server_url}</td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<td>{item.from_user}</td>
|
||||||
|
</tr>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div
|
||||||
|
className="library-grid-item px-3 d-flex justify-content-between align-items-center"
|
||||||
|
onMouseOver={this.handleMouseOver}
|
||||||
|
onMouseOut={this.handleMouseOut}
|
||||||
|
onFocus={this.handleMouseOver}
|
||||||
|
>
|
||||||
|
<div className="d-flex align-items-center text-truncate">
|
||||||
|
<img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="36" className="mr-2" />
|
||||||
|
<Link to={shareRepoUrl} className="library-name text-truncate" title={item.repo_name}>{item.repo_name}</Link>
|
||||||
|
</div>
|
||||||
|
<div className="flex-shrink-0">
|
||||||
|
<i role="button" className={`op-icon sf2-icon-x3 ${isOpIconShown ? '' : 'invisible'}`} title={gettext('Leave Share')} aria-label={gettext('Leave Share')} onClick={this.leaveShare}></i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// mobile
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
className={isHighlighted ? 'tr-highlight' : ''}
|
||||||
|
onMouseOver={this.handleMouseOver}
|
||||||
|
onMouseOut={this.handleMouseOut}
|
||||||
|
>
|
||||||
|
<td onClick={this.visitRepo}>
|
||||||
|
<img src={item.icon_url} title={item.icon_title} alt={item.icon_title} width="24" />
|
||||||
|
</td>
|
||||||
|
<td onClick={this.visitRepo}>
|
||||||
|
{item.repo_name && (
|
||||||
|
<div>
|
||||||
|
<Link to={shareRepoUrl}>{item.repo_name}</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<span className="item-meta-info">{item.from_user}</span>
|
||||||
|
<span className="item-meta-info">{item.from_server_url}</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<Dropdown isOpen={this.state.isItemMenuShow} toggle={this.toggleOperationMenu}>
|
||||||
|
<DropdownToggle
|
||||||
|
tag="i"
|
||||||
|
className="sf-dropdown-toggle sf3-font sf3-font-more-vertical ml-0"
|
||||||
|
title={gettext('More operations')}
|
||||||
|
aria-label={gettext('More operations')}
|
||||||
|
data-toggle="dropdown"
|
||||||
|
aria-expanded={this.state.isItemMenuShow}
|
||||||
|
/>
|
||||||
|
<div className={`${this.state.isItemMenuShow ? '' : 'd-none'}`} onClick={this.toggleOperationMenu}>
|
||||||
|
<div className="mobile-operation-menu-bg-layer"></div>
|
||||||
|
<div className="mobile-operation-menu">
|
||||||
|
<DropdownItem className="mobile-menu-item" onClick={this.leaveShare}>{gettext('Leave Share')}</DropdownItem>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,18 +288,30 @@ Item.propTypes = {
|
|||||||
class SharedWithOCM extends Component {
|
class SharedWithOCM extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
this.sortOptions = [
|
||||||
|
{ value: 'name-asc', text: gettext('By name ascending') },
|
||||||
|
{ value: 'name-desc', text: gettext('By name descending') }
|
||||||
|
];
|
||||||
this.state = {
|
this.state = {
|
||||||
loading: true,
|
loading: true,
|
||||||
errorMsg: '',
|
errorMsg: '',
|
||||||
items: []
|
items: [],
|
||||||
|
currentViewMode: localStorage.getItem('sf_repo_list_view_mode') || LIST_MODE,
|
||||||
|
sortBy: 'name',
|
||||||
|
sortOrder: this.props.sortOrder || cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
|
||||||
|
isSortOptionsDialogOpen: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
seafileAPI.listOCMSharesReceived().then((res) => {
|
seafileAPI.listOCMSharesReceived().then((res) => {
|
||||||
|
const { ocm_share_received_list } = res.data;
|
||||||
this.setState({
|
this.setState({
|
||||||
loading: false,
|
loading: false,
|
||||||
items: res.data.ocm_share_received_list
|
items: ocm_share_received_list
|
||||||
|
}, () => {
|
||||||
|
const { sortBy, sortOrder } = this.state;
|
||||||
|
this.sortItems(sortBy, sortOrder);
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
@@ -144,6 +321,18 @@ class SharedWithOCM extends Component {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromProps(props, state) {
|
||||||
|
if (props.sortBy == 'name' && props.sortOrder != state.sortOrder) {
|
||||||
|
cookie.save('seafile-repo-dir-sort-order', props.sortOrder);
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
sortOrder: props.sortOrder,
|
||||||
|
items: Utils.sortRepos(state.items, props.sortBy, props.sortOrder)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
leaveShare = (item) => {
|
leaveShare = (item) => {
|
||||||
const { id, repo_name } = item;
|
const { id, repo_name } = item;
|
||||||
seafileAPI.deleteOCMShareReceived(id).then((res) => {
|
seafileAPI.deleteOCMShareReceived(id).then((res) => {
|
||||||
@@ -158,27 +347,131 @@ class SharedWithOCM extends Component {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderSortIconInMobile = () => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{(!Utils.isDesktop() && this.state.items.length > 0) &&
|
||||||
|
<span
|
||||||
|
className="sf3-font sf3-font-sort action-icon"
|
||||||
|
onClick={this.toggleSortOptionsDialog}
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
switchViewMode = (newMode) => {
|
||||||
|
this.setState({
|
||||||
|
currentViewMode: newMode
|
||||||
|
}, () => {
|
||||||
|
localStorage.setItem('sf_repo_list_view_mode', newMode);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
onSelectSortOption = (sortOption) => {
|
||||||
|
const [sortBy, sortOrder] = sortOption.value.split('-');
|
||||||
|
this.setState({ sortBy, sortOrder }, () => {
|
||||||
|
this.sortItems(sortBy, sortOrder);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
sortItems = (sortBy, sortOrder) => {
|
||||||
|
cookie.save('seafile-repo-dir-sort-by', sortBy);
|
||||||
|
cookie.save('seafile-repo-dir-sort-order', sortOrder);
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
sortBy: sortBy,
|
||||||
|
sortOrder: sortOrder,
|
||||||
|
items: Utils.sortRepos(this.state.items, sortBy, sortOrder)
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
toggleSortOptionsDialog = () => {
|
||||||
|
this.setState({
|
||||||
|
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
renderContent = (currentViewMode) => {
|
||||||
|
return (
|
||||||
|
<Content
|
||||||
|
inAllLibs={this.props.inAllLibs}
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
|
loading={this.state.loading}
|
||||||
|
errorMsg={this.state.errorMsg}
|
||||||
|
items={this.state.items}
|
||||||
|
sortBy={this.state.sortBy}
|
||||||
|
sortOrder={this.state.sortOrder}
|
||||||
|
sortItems={this.sortItems}
|
||||||
|
leaveShare={this.leaveShare}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { inAllLibs = false, currentViewMode: propCurrentViewMode } = this.props; // inAllLibs: in 'All Libs'('Files') page
|
||||||
|
const { sortBy, sortOrder, currentViewMode: stateCurrentViewMode } = this.state;
|
||||||
|
const currentViewMode = inAllLibs ? propCurrentViewMode : stateCurrentViewMode;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="main-panel-center">
|
{inAllLibs
|
||||||
<div className="cur-view-container">
|
? (
|
||||||
<div className="cur-view-path">
|
<>
|
||||||
<h3 className="sf-heading m-0">{gettext('Shared from other servers')}</h3>
|
<div className={`d-flex justify-content-between mt-3 py-1 ${currentViewMode == LIST_MODE ? 'sf-border-bottom' : ''}`}>
|
||||||
|
<h4 className="sf-heading m-0">
|
||||||
|
<span className="sf3-font-share-with-me sf3-font nav-icon" aria-hidden="true"></span>
|
||||||
|
{gettext('Shared from other servers')}
|
||||||
|
</h4>
|
||||||
|
{/* this.renderSortIconInMobile() */}
|
||||||
|
</div>
|
||||||
|
{this.renderContent(currentViewMode)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<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>
|
||||||
|
{Utils.isDesktop() && (
|
||||||
|
<div className="d-flex align-items-center">
|
||||||
|
<div className="mr-2">
|
||||||
|
<ViewModes
|
||||||
|
currentViewMode={currentViewMode}
|
||||||
|
switchViewMode={this.switchViewMode}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ReposSortMenu
|
||||||
|
sortOptions={this.sortOptions}
|
||||||
|
sortBy={sortBy}
|
||||||
|
sortOrder={sortOrder}
|
||||||
|
onSelectSortOption={this.onSelectSortOption}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{this.renderSortIconInMobile()}
|
||||||
|
</div>
|
||||||
|
<div className={classnames('cur-view-content', 'repos-container', { 'pt-3': currentViewMode != LIST_MODE })}>
|
||||||
|
{this.renderContent(currentViewMode)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="cur-view-content">
|
)}
|
||||||
<Content
|
{this.state.isSortOptionsDialogOpen &&
|
||||||
loading={this.state.loading}
|
<SortOptionsDialog
|
||||||
errorMsg={this.state.errorMsg}
|
toggleDialog={this.toggleSortOptionsDialog}
|
||||||
items={this.state.items}
|
sortOptions={this.sortOptions}
|
||||||
leaveShare={this.leaveShare}
|
sortBy={this.state.sortBy}
|
||||||
/>
|
sortOrder={this.state.sortOrder}
|
||||||
</div>
|
sortItems={this.sortItems}
|
||||||
</div>
|
/>
|
||||||
</div>
|
}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SharedWithOCM.propTypes = propTypes;
|
||||||
|
|
||||||
export default SharedWithOCM;
|
export default SharedWithOCM;
|
||||||
|
@@ -93,8 +93,7 @@ class Item extends Component {
|
|||||||
this.setState({ isShowSharedDialog: false });
|
this.setState({ isShowSharedDialog: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
onToggleStarRepo = (e) => {
|
onToggleStarRepo = () => {
|
||||||
e.preventDefault();
|
|
||||||
const repoName = this.props.data.repo_name;
|
const repoName = this.props.data.repo_name;
|
||||||
if (this.state.isStarred) {
|
if (this.state.isStarred) {
|
||||||
seafileAPI.unstarItem(this.props.data.repo_id, '/').then(() => {
|
seafileAPI.unstarItem(this.props.data.repo_id, '/').then(() => {
|
||||||
|
Reference in New Issue
Block a user