1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-04-28 03:10:45 +00:00
* [side nav] redesigned it: added a new item 'Files', and made 'My Libraries' and some other items as its sub nav items

* ['Files'] added new page 'Files'(added 'My Libraries' to it)

* ['Files'] added 'Shared with me' to it

* ['Files'] added 'Shared with all' to it

* ['Files'] added 'Shared with groups' to it (removed 'details' for 'department')
This commit is contained in:
llj 2024-04-19 14:51:41 +08:00 committed by GitHub
parent 0a417c0a23
commit 4eab55fdf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
20 changed files with 947 additions and 626 deletions

View File

@ -22,12 +22,13 @@ import OCMViaWebdav from './pages/ocm-via-webdav/ocm-via-webdav';
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';
import PublicSharedView from './pages/shared-with-all';
import LibContentView from './pages/lib-content-view/lib-content-view';
import Group from './pages/groups/group-view';
import Groups from './pages/groups/groups-view';
import InvitationsView from './pages/invitations/invitations-view';
import Wikis from './pages/wikis/wikis';
import Libraries from './pages/libraries';
import MainContentWrapper from './components/main-content-wrapper';
import './css/layout.css';
@ -201,10 +202,6 @@ class App extends Component {
render() {
let { currentTab, isSidePanelClosed } = this.state;
const home = canAddRepo ?
<MyLibraries path={ siteRoot } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} /> :
<SharedLibrariesWrapper path={ siteRoot } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />;
return (
<React.Fragment>
<SystemNotification />
@ -212,7 +209,8 @@ class App extends Component {
<SidePanel isSidePanelClosed={this.state.isSidePanelClosed} onCloseSidePanel={this.onCloseSidePanel} currentTab={currentTab} tabItemClick={this.tabItemClick} />
<MainPanel>
<Router className="reach-router">
{home}
<Libraries path={ siteRoot } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<Libraries path={ siteRoot + 'libraries' } onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<FilesActivitiesWrapper path={siteRoot + 'dashboard'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<MyFileActivitiesWrapper path={siteRoot + 'my-activities'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />
<StarredWrapper path={siteRoot + 'starred'} onShowSidePanel={this.onShowSidePanel} onSearchedClick={this.onSearchedClick} />

View File

@ -39,7 +39,8 @@ class CreateGroupDialog extends React.Component {
if (name) {
let that = this;
seafileAPI.createGroup(name).then((res)=> {
that.props.onCreateGroup();
that.props.onCreateGroup(res.data);
this.props.toggleDialog();
}).catch((error) => {
let errorMsg = Utils.getErrorMsg(error);
this.setState({errorMsg: errorMsg});
@ -63,8 +64,8 @@ class CreateGroupDialog extends React.Component {
render() {
return(
<Modal isOpen={this.props.showAddGroupModal} toggle={this.props.toggleAddGroupModal} autoFocus={false}>
<ModalHeader toggle={this.props.toggleAddGroupModal}>{gettext('New Group')}</ModalHeader>
<Modal isOpen={true} toggle={this.props.toggleDialog} autoFocus={false}>
<ModalHeader toggle={this.props.toggleDialog}>{gettext('New Group')}</ModalHeader>
<ModalBody>
<label htmlFor="groupName">{gettext('Name')}</label>
<Input
@ -78,7 +79,7 @@ class CreateGroupDialog extends React.Component {
<span className="error">{this.state.errorMsg}</span>
</ModalBody>
<ModalFooter>
<Button color="secondary" onClick={this.props.toggleAddGroupModal}>{gettext('Cancel')}</Button>
<Button color="secondary" onClick={this.props.toggleDialog}>{gettext('Cancel')}</Button>
<Button color="primary" onClick={this.handleSubmitGroup} disabled={!this.state.isSubmitBtnActive}>{gettext('Submit')}</Button>
</ModalFooter>
</Modal>
@ -87,9 +88,8 @@ class CreateGroupDialog extends React.Component {
}
const CreateGroupDialogPropTypes = {
toggleAddGroupModal: PropTypes.func.isRequired,
onCreateGroup: PropTypes.func.isRequired,
showAddGroupModal: PropTypes.bool.isRequired,
toggleDialog: PropTypes.func.isRequired,
onCreateGroup: PropTypes.func.isRequired
};
CreateGroupDialog.propTypes = CreateGroupDialogPropTypes;

View File

@ -18,6 +18,7 @@ class MainSideNav extends React.Component {
constructor(props) {
super(props);
this.state = {
FilesNavUnfolded: false,
groupsExtended: false,
sharedExtended: false,
closeSideBar:false,
@ -172,64 +173,71 @@ class MainSideNav extends React.Component {
);
}
toggleFilesNav = (e) => {
e.preventDefault();
e.stopPropagation();
this.setState({
FilesNavUnfolded: !this.state.FilesNavUnfolded
});
};
render() {
let showActivity = isDocs || isPro || !isDBSqlite3;
const { FilesNavUnfolded } = this.state;
return (
<div className="side-nav">
<div className="side-nav-con">
<h3 className="sf-heading">{gettext('Files')}</h3>
<ul className="nav nav-pills flex-column nav-container">
{canAddRepo && (
<li className="nav-item">
<Link to={ siteRoot + 'my-libs/' } className={`nav-link ellipsis ${this.getActiveClass('my-libs') || this.getActiveClass('deleted') }`} title={gettext('My Libraries')} onClick={(e) => this.tabItemClick(e, 'my-libs')}>
<span className="sf2-icon-user" aria-hidden="true"></span>
<span className="nav-text">{gettext('My Libraries')}</span>
</Link>
</li>
)}
<li className="nav-item">
<Link to={siteRoot + 'shared-libs/'} className={`nav-link ellipsis ${this.getActiveClass('shared-libs')}`} title={gettext('Shared with me')} onClick={(e) => this.tabItemClick(e, 'shared-libs')}>
<span className="sf2-icon-share" aria-hidden="true"></span>
<span className="nav-text">{gettext('Shared with me')}</span>
<li className="nav-item flex-column" id="files">
<Link to={ siteRoot + 'libraries/' } className={`nav-link ellipsis ${this.getActiveClass('libraries')}`} title={gettext('Files')} onClick={(e) => this.tabItemClick(e, 'libraries')}>
<span className="sf3-font-files sf3-font" aria-hidden="true"></span>
<span className="nav-text">{gettext('Files')}</span>
<span className={`toggle-icon fas ${FilesNavUnfolded ? 'fa-caret-down':'fa-caret-left'} ${this.getActiveClass('libraries') ? 'text-white' : ''}`} aria-hidden="true" onClick={this.toggleFilesNav}></span>
</Link>
<ul id="files-sub-nav" className={`nav sub-nav nav-pills flex-column ${FilesNavUnfolded ? 'side-panel-slide' : 'side-panel-slide-up'}`}>
{canAddRepo && (
<li className="nav-item">
<Link to={ siteRoot + 'my-libs/' } className={`nav-link ellipsis ${this.getActiveClass('my-libs') || this.getActiveClass('deleted') }`} title={gettext('My Libraries')} onClick={(e) => this.tabItemClick(e, 'my-libs')}>
<span className="nav-text">{gettext('My Libraries')}</span>
</Link>
</li>
)}
<li className="nav-item">
<Link to={siteRoot + 'shared-libs/'} className={`nav-link ellipsis ${this.getActiveClass('shared-libs')}`} title={gettext('Shared with me')} onClick={(e) => this.tabItemClick(e, 'shared-libs')}>
<span className="nav-text">{gettext('Shared with me')}</span>
</Link>
</li>
{canViewOrg &&
<li className="nav-item" onClick={(e) => this.tabItemClick(e, 'org')}>
<Link to={ siteRoot + 'org/' } className={`nav-link ellipsis ${this.getActiveClass('org')}`} title={gettext('Shared with all')}>
<span className="nav-text">{gettext('Shared with all')}</span>
</Link>
</li>
}
<li className="nav-item flex-column" id="group-nav">
<a className="nav-link ellipsis d-flex pr-1" title={gettext('Shared with groups')} onClick={this.grpsExtend}>
<span className="nav-text">{gettext('Shared with groups')}</span>
<span className={`toggle-icon fas ${this.state.groupsExtended ?'fa-caret-down':'fa-caret-left'}`} aria-hidden="true"></span>
</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="nav-text">{gettext('Shared from other servers')}</span>
</Link>
</li>
}
{enableOCMViaWebdav &&
<li className="nav-item">
<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="nav-text">{gettext('Shared from other servers')}</span>
</Link>
</li>
}
</ul>
</li>
{ canViewOrg &&
<li className="nav-item" onClick={(e) => this.tabItemClick(e, 'org')}>
<Link to={ siteRoot + 'org/' } className={`nav-link ellipsis ${this.getActiveClass('org')}`} title={gettext('Shared with all')}>
<span className="sf2-icon-organization" aria-hidden="true"></span>
<span className="nav-text">{gettext('Shared with all')}</span>
</Link>
</li>
}
<li className="nav-item flex-column" id="group-nav">
<a className="nav-link ellipsis" title={gettext('Shared with groups')} onClick={this.grpsExtend}>
<span className="sf2-icon-group" aria-hidden="true"></span>
<span className="nav-text">{gettext('Shared with groups')}</span>
<span className={`toggle-icon fas ${this.state.groupsExtended ?'fa-caret-down':'fa-caret-left'}`} aria-hidden="true"></span>
</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="sf3-font-share-from-other-servers sf3-font" aria-hidden="true"></span>
<span className="nav-text">{gettext('Shared from other servers')}</span>
</Link>
</li>
}
{enableOCMViaWebdav &&
<li className="nav-item">
<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-from-other-servers sf3-font" aria-hidden="true"></span>
<span className="nav-text">{gettext('Shared from other servers')}</span>
</Link>
</li>
}
</ul>
<h3 className="sf-heading">{gettext('Tools')}</h3>
<ul className="nav nav-pills flex-column nav-container">
<li className="nav-item">
<Link className={`nav-link ellipsis ${this.getActiveClass('starred')}`} to={siteRoot + 'starred/'} title={gettext('Favorites')} onClick={(e) => this.tabItemClick(e, 'starred')}>
<span className="sf2-icon-star" aria-hidden="true"></span>

View File

@ -29,7 +29,6 @@ const propTypes = {
onFreezedItem: PropTypes.func.isRequired,
onUnfreezedItem: PropTypes.func.isRequired,
onItemUnshare: PropTypes.func.isRequired,
onItemDetails: PropTypes.func,
onItemRename: PropTypes.func,
onItemDelete: PropTypes.func,
onMonitorRepo: PropTypes.func
@ -165,9 +164,6 @@ class SharedRepoListItem extends React.Component {
case 'Folder Permission':
this.onItemFolderPermissionToggle();
break;
case 'Details':
this.onItemDetails();
break;
case 'Share':
this.onItemShare();
break;
@ -251,10 +247,6 @@ class SharedRepoListItem extends React.Component {
this.setState({isHistorySettingDialogShow: !this.state.isHistorySettingDialogShow});
};
onItemDetails = () => {
this.props.onItemDetails(this.props.repo);
};
onItemShare = (e) => {
e.preventDefault();
this.setState({isShowSharedDialog: true});
@ -338,9 +330,6 @@ class SharedRepoListItem extends React.Component {
case 'Folder Permission':
translateResult = gettext('Folder Permission');
break;
case 'Details':
translateResult = gettext('Details');
break;
case 'Unshare':
translateResult = gettext('Unshare');
break;
@ -423,7 +412,7 @@ class SharedRepoListItem extends React.Component {
const monitorOp = repo.monitored ? 'Unwatch File Changes' : 'Watch File Changes';
operations.push(monitorOp);
}
operations.push('Divider', 'History Setting', 'Details');
operations.push('Divider', 'History Setting');
if (Utils.isDesktop()) {
operations.push('Advanced');
}

View File

@ -17,7 +17,6 @@ const propTypes = {
repoList: PropTypes.array.isRequired,
onItemUnshare: PropTypes.func.isRequired,
onItemDelete: PropTypes.func,
onItemDetails: PropTypes.func,
onItemRename: PropTypes.func,
hasNextPage: PropTypes.bool,
onMonitorRepo: PropTypes.func,
@ -98,7 +97,6 @@ class SharedRepoListView extends React.Component {
onUnfreezedItem={this.onUnfreezedItem}
onItemUnshare={this.props.onItemUnshare}
onItemDelete={this.props.onItemDelete}
onItemDetails={this.props.onItemDetails}
onItemRename={this.props.onItemRename}
onMonitorRepo={this.props.onMonitorRepo}
/>
@ -109,12 +107,11 @@ class SharedRepoListView extends React.Component {
};
renderPCUI = () => {
let isShowTableThread = this.props.isShowTableThread !== undefined ? this.props.isShowTableThread : true;
const { theadHidden = false } = this.props;
const { sortByName, sortByTime, sortBySize, sortIcon } = this.getSortMetaData();
return (
<table className={isShowTableThread ? '' : 'table-thead-hidden'}>
<table className={theadHidden ? 'table-thead-hidden' : ''}>
<thead>
<tr>
<th width="4%"></th>

View File

@ -1,44 +1,47 @@
import React from 'react';
import PropTypes from 'prop-types';
import MediaQuery from 'react-responsive';
import CommonToolbar from './common-toolbar';
import { Button } from 'reactstrap';
import { gettext, canAddGroup } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { gettext } from '../../utils/constants';
import CreateGroupDialog from '../../components/dialog/create-group-dialog';
const propTypes = {
searchPlaceholder: PropTypes.string,
onShowSidePanel: PropTypes.func.isRequired,
onSearchedClick: PropTypes.func.isRequired,
toggleAddGroupModal: PropTypes.func.isRequired,
onCreateGroup: PropTypes.func.isRequired
};
class GroupsToolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
isDialogOpen: false
};
}
toggleDialog = () => {
this.setState({
isDialogOpen: !this.state.isDialogOpen
});
};
render() {
let { onShowSidePanel, onSearchedClick } = this.props;
return (
<div className="main-panel-north border-left-show">
<div className="cur-view-toolbar">
<span title="Side Nav Menu" onClick={onShowSidePanel} className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none"></span>
{canAddGroup && (
<div className="operation">
<MediaQuery query="(min-width: 768px)">
<Button color="btn btn-secondary operation-item" onClick={this.props.toggleAddGroupModal}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
</Button>
</MediaQuery>
<MediaQuery query="(max-width: 767.8px)">
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Group')} onClick={this.props.toggleAddGroupModal}></span>
</MediaQuery>
</div>
)}
</div>
<CommonToolbar searchPlaceholder={this.props.searchPlaceholder} onSearchedClick={onSearchedClick}/>
</div>
<>
{Utils.isDesktop() ? (
<div className="operation">
<button className="btn btn-secondary operation-item" onClick={this.toggleDialog}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Group')}
</button>
</div>
) : (
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Group')} onClick={this.toggleDialog}></span>
)}
{this.state.isDialogOpen &&
<CreateGroupDialog
toggleDialog={this.toggleDialog}
onCreateGroup={this.props.onCreateGroup}
/>
}
</>
);
}
}

View File

@ -1,24 +1,23 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Utils } from '../../utils/utils';
import { DropdownToggle, Dropdown, DropdownMenu, DropdownItem } from 'reactstrap';
import { Link, navigate } from '@gatsbyjs/reach-router';
import { Utils } from '../../utils/utils';
import { siteRoot, gettext } from '../../utils/constants';
import ModalPortal from '../modal-portal';
import CreateRepoDialog from '../dialog/create-repo-dialog';
import { DropdownToggle, Dropdown, DropdownMenu, DropdownItem } from 'reactstrap';
const propTypes = {
libraryType: PropTypes.string.isRequired,
onCreateRepo: PropTypes.func.isRequired,
onShowSidePanel: PropTypes.func.isRequired,
moreShown: PropTypes.bool
};
class RepoViewToolbar extends React.Component {
class MyLibsToolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
isCreateRepoDialogShow: false,
isCreateRepoDialogOpen: false,
isOpen: false,
};
}
@ -29,7 +28,7 @@ class RepoViewToolbar extends React.Component {
};
onCreateToggle = () => {
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
this.setState({isCreateRepoDialogOpen: !this.state.isCreateRepoDialogOpen});
};
toggleMore = () => {
@ -49,15 +48,15 @@ class RepoViewToolbar extends React.Component {
};
render() {
const { moreShown = false } = this.props;
return (
<Fragment>
<div className="cur-view-toolbar">
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
{Utils.isDesktop() ? (
<div className="operation">
<button className="btn btn-secondary operation-item" title={gettext('New Library')} onClick={this.onCreateToggle}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Library')}
</button>
{Utils.isDesktop() ? (
<div className="operation">
<button className="btn btn-secondary operation-item" title={gettext('New Library')} onClick={this.onCreateToggle}>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('New Library')}
</button>
{moreShown &&
<Dropdown isOpen={this.state.isOpen} toggle={this.toggleMore}>
<DropdownToggle className='btn btn-secondary operation-item' onKeyDown={this.onDropdownToggleKeyDown}>
{gettext('More')}
@ -68,15 +67,15 @@ class RepoViewToolbar extends React.Component {
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
) : (
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Library')} onClick={this.onCreateToggle}></span>
)}
</div>
{this.state.isCreateRepoDialogShow && (
}
</div>
) : (
<span className="sf2-icon-plus mobile-toolbar-icon" title={gettext('New Library')} onClick={this.onCreateToggle}></span>
)}
{this.state.isCreateRepoDialogOpen && (
<ModalPortal>
<CreateRepoDialog
libraryType={this.props.libraryType}
libraryType='mine'
onCreateRepo={this.onCreateRepo}
onCreateToggle={this.onCreateToggle}
/>
@ -87,6 +86,6 @@ class RepoViewToolbar extends React.Component {
}
}
RepoViewToolbar.propTypes = propTypes;
MyLibsToolbar.propTypes = propTypes;
export default RepoViewToolbar;
export default MyLibsToolbar;

View File

@ -0,0 +1,100 @@
import React from 'react';
import PropTypes from 'prop-types';
import { gettext, siteRoot } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
import '../../css/groups.css';
const propTypes = {
group: PropTypes.object.isRequired,
updateGroup: PropTypes.func.isRequired
};
class GroupItem extends React.Component {
constructor(props) {
super(props);
}
onItemUnshare = (repo) => {
const { group } = this.props;
seafileAPI.unshareRepoToGroup(repo.repo_id, group.id).then(() => {
group.repos = group.repos.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.props.updateGroup(group);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onItemDelete = (repo) => {
const { group } = this.props;
group.repos = group.repos.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.props.updateGroup(group);
};
onItemRename = (repo, newName) => {
let group = this.props.group;
seafileAPI.renameGroupOwnedLibrary(group.id, repo.repo_id, newName).then(res => {
const { group } = this.props;
group.repos = group.repos.map(item => {
if (item.repo_id === repo.repo_id) {
item.repo_name = newName;
}
return item;
});
this.props.updateGroup(group);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onMonitorRepo = (repo, monitored) => {
const { group } = this.props;
group.repos = group.repos.map(item => {
if (item.repo_id === repo.repo_id) {
item.monitored = monitored;
}
return item;
});
this.props.updateGroup(group);
};
render() {
const { group } = this.props;
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
return (
<div className="group-list-panel">
<h4 className="group-item-heading ellipsis">
<a href={`${siteRoot}group/${group.id}/`} title={group.name}>{group.name}</a>
</h4>
{group.repos.length === 0 ?
emptyTip :
<SharedRepoListView
theadHidden={true}
isShowRepoOwner={false}
currentGroup={this.props.group}
repoList={group.repos}
onItemUnshare={this.onItemUnshare}
onItemDelete={this.onItemDelete}
onItemRename={this.onItemRename}
onMonitorRepo={this.onMonitorRepo}
/>
}
</div>
);
}
}
GroupItem.propTypes = propTypes;
export default GroupItem;

View File

@ -21,7 +21,6 @@ import ImportMembersDialog from '../../components/dialog/import-members-dialog';
import ManageMembersDialog from '../../components/dialog/manage-members-dialog';
import LeaveGroupDialog from '../../components/dialog/leave-group-dialog';
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
import LibDetail from '../../components/dirent-detail/lib-details';
import SortOptionsDialog from '../../components/dialog/sort-options';
import '../../css/group-view.css';
@ -65,7 +64,6 @@ class GroupView extends React.Component {
showImportMembersDialog: false,
showManageMembersDialog: false,
groupMembers: [],
isShowDetails: false,
isLeaveGroupDialogOpen: false,
};
}
@ -349,17 +347,6 @@ class GroupView extends React.Component {
}
};
onItemDetails = (repo) => {
this.setState({
isShowDetails: true,
currentRepo: repo,
});
};
closeDetails = () => {
this.setState({isShowDetails: false});
};
sortItems = (sortBy, sortOrder) => {
cookie.save('seafile-repo-dir-sort-by', sortBy);
cookie.save('seafile-repo-dir-sort-order', sortOrder);
@ -568,18 +555,12 @@ class GroupView extends React.Component {
sortItems={this.sortItems}
onItemUnshare={this.onItemUnshare}
onItemDelete={this.onItemDelete}
onItemDetails={this.onItemDetails}
onItemRename={this.onItemRename}
onMonitorRepo={this.onMonitorRepo}
/>
}
</div>
</div>
{this.state.isShowDetails && (
<div className="cur-view-detail">
<LibDetail currentRepo={this.state.currentRepo} closeDetails={this.closeDetails}/>
</div>
)}
</div>
{this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && (
<ModalPortal>

View File

@ -1,119 +1,18 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { gettext, siteRoot, canAddGroup } from '../../utils/constants';
import { gettext, canAddGroup } from '../../utils/constants';
import { seafileAPI } from '../../utils/seafile-api';
import { Utils } from '../../utils/utils';
import Loading from '../../components/loading';
import Group from '../../models/group';
import Repo from '../../models/repo';
import toaster from '../../components/toast';
import TopToolbar from '../../components/toolbar/top-toolbar';
import GroupsToolbar from '../../components/toolbar/groups-toolbar';
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
import CreateGroupDialog from '../../components/dialog/create-group-dialog';
import LibDetail from '../../components/dirent-detail/lib-details';
import EmptyTip from '../../components/empty-tip';
import GroupItem from './group-item';
import '../../css/groups.css';
const propTypes = {
group: PropTypes.object.isRequired,
onItemDetails: PropTypes.func.isRequired,
};
class RepoListViewPanel extends React.Component {
constructor(props) {
super(props);
this.state = {
repoList: [],
};
}
componentDidMount() {
let group = this.props.group;
let repoList = group.repos.map(item => {
let repo = new Repo(item);
return repo;
});
this.setState({repoList: repoList});
}
onItemUnshare = (repo) => {
let group = this.props.group;
seafileAPI.unshareRepoToGroup(repo.repo_id, group.id).then(() => {
let repoList = this.state.repoList.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.setState({repoList: repoList});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onItemDelete = (repo) => {
let repoList = this.state.repoList.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.setState({repoList: repoList});
};
onItemRename = (repo, newName) => {
let group = this.props.group;
seafileAPI.renameGroupOwnedLibrary(group.id, repo.repo_id, newName).then(res => {
let repoList = this.state.repoList.map(item => {
if (item.repo_id === repo.repo_id) {
item.repo_name = newName;
}
return item;
});
this.setState({repoList: repoList});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
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() {
let group = this.props.group;
const emptyTip = <p className="group-item-empty-tip">{gettext('No libraries')}</p>;
return (
<div className="group-list-panel">
<h4 className="group-item-heading ellipsis">
<a href={`${siteRoot}group/${group.id}/`} title={group.name}>{group.name}</a>
</h4>
{this.state.repoList.length === 0 ?
emptyTip :
<SharedRepoListView
isShowTableThread={false}
isShowRepoOwner={false}
currentGroup={this.props.group}
repoList={this.state.repoList}
onItemUnshare={this.onItemUnshare}
onItemDelete={this.onItemDelete}
onItemDetails={this.props.onItemDetails}
onItemRename={this.onItemRename}
onMonitorRepo={this.onMonitorRepo}
/>
}
</div>
);
}
}
RepoListViewPanel.propTypes = propTypes;
class GroupsView extends React.Component {
constructor(props) {
@ -121,10 +20,7 @@ class GroupsView extends React.Component {
this.state = {
isLoading: true,
errorMsg: '',
groupList: [],
showAddGroupModal: false,
isShowDetails: false,
currentRepo: null,
groupList: []
};
}
@ -133,6 +29,9 @@ class GroupsView extends React.Component {
// `{'with_repos': 1}`: list repos of every group
let groupList = res.data.map(item => {
let group = new Group(item);
group.repos = item.repos.map(item => {
return new Repo(item);
});
return group;
});
this.setState({
@ -149,36 +48,31 @@ class GroupsView extends React.Component {
});
};
toggleAddGroupModal = () => {
onCreateGroup = (groupData) => {
const newGroup = new Group(groupData);
const { groupList: newList } = this.state;
newList.unshift(newGroup);
this.setState({
showAddGroupModal: !this.state.showAddGroupModal
groupList: newList
});
};
onCreateGroup = () => {
this.setState({
showAddGroupModal: false,
isLoading: true,
groupList: [],
});
this.listGroups();
};
componentDidMount() {
this.listGroups();
}
onItemDetails = (repo) => {
updateGroup = (group) => {
const { groupList } = this.state;
this.setState({
isShowDetails: true,
currentRepo: repo,
groupList: groupList.map((item) => {
if (item.id == group.id) {
item = group;
}
return item;
})
});
};
closeDetails = () => {
this.setState({isShowDetails: false});
};
render() {
const emptyTip = (
<EmptyTip>
@ -192,11 +86,12 @@ class GroupsView extends React.Component {
return (
<Fragment>
<GroupsToolbar
<TopToolbar
onShowSidePanel={this.props.onShowSidePanel}
onSearchedClick={this.props.onSearchedClick}
toggleAddGroupModal={this.toggleAddGroupModal}
/>
>
{canAddGroup && <GroupsToolbar onCreateGroup={this.onCreateGroup} />}
</TopToolbar>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path">
@ -208,28 +103,16 @@ class GroupsView extends React.Component {
{(!this.state.isLoading && !this.state.errorMsg && this.state.groupList.length == 0) && emptyTip}
{!this.state.isLoading && this.state.groupList.map((group, index) => {
return (
<RepoListViewPanel
<GroupItem
key={index}
group={group}
onItemDetails={this.onItemDetails}
updateGroup={this.updateGroup}
/>
);
})}
</div>
</div>
{this.state.isShowDetails && (
<div className="cur-view-detail">
<LibDetail currentRepo={this.state.currentRepo} closeDetails={this.closeDetails}/>
</div>
)}
</div>
{ this.state.showAddGroupModal &&
<CreateGroupDialog
toggleAddGroupModal={this.toggleAddGroupModal}
onCreateGroup={this.onCreateGroup}
showAddGroupModal={this.state.showAddGroupModal}
/>
}
</Fragment>
);
}

View File

@ -0,0 +1,305 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import cookie from 'react-cookies';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, canAddRepo, canViewOrg, canAddGroup } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import toaster from '../../components/toast';
import Repo from '../../models/repo';
import Group from '../../models/group';
import Loading from '../../components/loading';
import EmptyTip from '../../components/empty-tip';
import TopToolbar from '../../components/toolbar/top-toolbar';
import MyLibsToolbar from '../../components/toolbar/my-libs-toolbar';
import GroupsToolbar from '../../components/toolbar/groups-toolbar';
import SortOptionsDialog from '../../components/dialog/sort-options';
import GuideForNewDialog from '../../components/dialog/guide-for-new-dialog';
import MylibRepoListView from '../../pages/my-libs/mylib-repo-list-view';
import SharedLibs from '../../pages/shared-libs/shared-libs.js';
import SharedWithAll from '../../pages/shared-with-all';
import GroupItem from '../../pages/groups/group-item';
const propTypes = {
onShowSidePanel: PropTypes.func.isRequired,
onSearchedClick: PropTypes.func.isRequired
};
class Libraries extends Component {
constructor(props) {
super(props);
this.state = {
// for 'my libs'
errorMsg: '',
isLoading: true,
repoList: [],
isSortOptionsDialogOpen: false,
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
isGuideForNewDialogOpen: window.app.pageOptions.guideEnabled,
// for 'groups'
isGroupsLoading: true,
groupsErrorMsg: '',
groupList: []
};
}
componentDidMount() {
this.listMyLibs();
this.listGroups();
}
listMyLibs = () => {
seafileAPI.listRepos({type: 'mine'}).then((res) => {
let repoList = res.data.repos.map((item) => {
return new Repo(item);
});
this.setState({
isLoading: false,
repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
});
}).catch((error) => {
this.setState({
isLoading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
};
toggleSortOptionsDialog = () => {
this.setState({
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
});
};
onCreateRepo = (repo) => {
seafileAPI.createMineRepo(repo).then((res) => {
const newRepo = new Repo({
repo_id: res.data.repo_id,
repo_name: res.data.repo_name,
size: res.data.repo_size,
mtime: res.data.mtime,
owner_email: res.data.email,
encrypted: res.data.encrypted,
permission: res.data.permission,
storage_name: res.data.storage_name
});
this.state.repoList.unshift(newRepo);
this.setState({repoList: this.state.repoList});
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
sortRepoList = (sortBy, sortOrder) => {
cookie.save('seafile-repo-dir-sort-by', sortBy);
cookie.save('seafile-repo-dir-sort-order', sortOrder);
this.setState({
sortBy: sortBy,
sortOrder: sortOrder,
repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder)
});
};
onTransferRepo = (repoID) => {
let repoList = this.state.repoList.filter(item => {
return item.repo_id !== repoID;
});
this.setState({repoList: repoList});
};
onRenameRepo = (repo, newName) => {
let repoList = this.state.repoList.map(item => {
if (item.repo_id === repo.repo_id) {
item.repo_name = newName;
}
return item;
});
this.setState({repoList: repoList});
};
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});
};
onDeleteRepo = (repo) => {
let repoList = this.state.repoList.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.setState({repoList: repoList});
};
toggleGuideForNewDialog = () => {
window.app.pageOptions.guideEnabled = false;
this.setState({
isGuideForNewDialogOpen: false
});
};
// the following are for 'groups'
listGroups = () => {
seafileAPI.listGroups(true).then((res) => {
// `{'with_repos': 1}`: list repos of every group
let groupList = res.data.map(item => {
let group = new Group(item);
group.repos = item.repos.map(item => {
return new Repo(item);
});
return group;
});
this.setState({
isGroupsLoading: false,
groupList: groupList.sort((a, b) => {
return a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1;
})
});
}).catch((error) => {
this.setState({
isGroupsLoading: false,
groupsErrorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
};
onCreateGroup = (groupData) => {
const newGroup = new Group(groupData);
const { groupList: newList } = this.state;
newList.unshift(newGroup);
this.setState({
groupList: newList
});
};
updateGroup = (group) => {
const { groupList } = this.state;
this.setState({
groupList: groupList.map((item) => {
if (item.id == group.id) {
item = group;
}
return item;
})
});
};
render() {
const emptyTip = (
<EmptyTip>
<h2>{gettext('No libraries')}</h2>
<p>{gettext('You have not created any libraries yet. A library is a container to organize your files and folders. A library can also be shared with others and synced to your connected devices. You can create a library by clicking the "New Library" button in the menu bar.')}</p>
</EmptyTip>
);
const groupsEmptyTip = (
<EmptyTip>
<h2>{gettext('No groups')}</h2>
{canAddGroup ?
<p>{gettext('You are not in any groups. Groups allow multiple people to collaborate on libraries. You can create a group by clicking the "New Group" button in the menu bar.')}</p> :
<p>{gettext('You are not in any groups. Groups allow multiple people to collaborate on libraries. Groups you join will be listed here.')}</p>
}
</EmptyTip>
);
return (
<Fragment>
<TopToolbar
onShowSidePanel={this.props.onShowSidePanel}
onSearchedClick={this.props.onSearchedClick}
>
<>
{canAddRepo && <MyLibsToolbar onCreateRepo={this.onCreateRepo} />}
{canAddGroup && <GroupsToolbar onCreateGroup={this.onCreateGroup} />}
</>
</TopToolbar>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading m-0">{gettext('Files')}</h3>
</div>
<div className="cur-view-content">
{canAddRepo && (
<div className="pb-4">
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
<h4 className="sf-heading m-0">{gettext('My Libraries')}</h4>
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
</div>
{this.state.isLoading ? <Loading /> : (
this.state.errorMsg ? <p className="error text-center mt-8">{this.state.errorMsg}</p> : (
this.state.repoList.length === 0 ? emptyTip : (
<MylibRepoListView
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
repoList={this.state.repoList}
onRenameRepo={this.onRenameRepo}
onDeleteRepo={this.onDeleteRepo}
onTransferRepo={this.onTransferRepo}
onMonitorRepo={this.onMonitorRepo}
onRepoClick={this.onRepoClick}
sortRepoList={this.sortRepoList}
theadHidden={true}
/>
)))}
</div>
)}
<div className="pb-4">
<SharedLibs inAllLibs={true} />
</div>
{canViewOrg && (
<div className="pb-4">
<SharedWithAll inAllLibs={true} />
</div>
)}
<div className="pb-4">
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
<h4 className="sf-heading m-0">{gettext('Shared with groups')}</h4>
</div>
{this.state.isGroupsLoading? <Loading /> : (
this.state.groupsErrorMsg ? <p className="error text-center mt-8">{this.state.groupsErrorMsg}</p> : (
this.state.groupList.length === 0 ? groupsEmptyTip : (
this.state.groupList.map((group, index) => {
return (
<GroupItem
key={index}
group={group}
updateGroup={this.updateGroup}
/>
);
})
)))}
</div>
</div>
</div>
{!this.state.isLoading && !this.state.errorMsg && this.state.isGuideForNewDialogOpen &&
<GuideForNewDialog
toggleDialog={this.toggleGuideForNewDialog}
/>
}
{this.state.isSortOptionsDialogOpen &&
<SortOptionsDialog
toggleDialog={this.toggleSortOptionsDialog}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortRepoList}
/>
}
</div>
</Fragment>
);
}
}
Libraries.propTypes = propTypes;
export default Libraries;

View File

@ -8,12 +8,10 @@ import toaster from '../../components/toast';
import Repo from '../../models/repo';
import Loading from '../../components/loading';
import EmptyTip from '../../components/empty-tip';
import CommonToolbar from '../../components/toolbar/common-toolbar';
import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
import LibDetail from '../../components/dirent-detail/lib-details';
import TopToolbar from '../../components/toolbar/top-toolbar';
import MyLibsToolbar from '../../components/toolbar/my-libs-toolbar';
import MylibRepoListView from './mylib-repo-list-view';
import SortOptionsDialog from '../../components/dialog/sort-options';
import GuideForNewDialog from '../../components/dialog/guide-for-new-dialog';
const propTypes = {
onShowSidePanel: PropTypes.func.isRequired,
@ -27,9 +25,7 @@ class MyLibraries extends Component {
errorMsg: '',
isLoading: true,
repoList: [],
isShowDetails: false,
isSortOptionsDialogOpen: false,
isGuideForNewDialogOpen: window.app.pageOptions.guideEnabled,
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
};
@ -129,37 +125,15 @@ class MyLibraries extends Component {
this.setState({repoList: repoList});
};
onRepoClick = (repo) => {
if (this.state.isShowDetails) {
this.onRepoDetails(repo);
}
};
onRepoDetails = (repo) => {
this.setState({
currentRepo: repo,
isShowDetails: true,
});
};
closeDetails = () => {
this.setState({isShowDetails: !this.state.isShowDetails});
};
toggleGuideForNewDialog = () => {
window.app.pageOptions.guideEnabled = false;
this.setState({
isGuideForNewDialogOpen: false
});
};
render() {
return (
<Fragment>
<div className="main-panel-north border-left-show">
<RepoViewToolbar onShowSidePanel={this.props.onShowSidePanel} onCreateRepo={this.onCreateRepo} libraryType={'mine'}/>
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
</div>
<TopToolbar
onShowSidePanel={this.props.onShowSidePanel}
onSearchedClick={this.props.onSearchedClick}
>
<MyLibsToolbar onCreateRepo={this.onCreateRepo} moreShown={true} />
</TopToolbar>
<div className="main-panel-center flex-row">
<div className="cur-view-container">
<div className="cur-view-path">
@ -179,17 +153,11 @@ class MyLibraries extends Component {
onDeleteRepo={this.onDeleteRepo}
onTransferRepo={this.onTransferRepo}
onMonitorRepo={this.onMonitorRepo}
onRepoClick={this.onRepoClick}
sortRepoList={this.sortRepoList}
/>
}
</div>
</div>
{!this.state.isLoading && !this.state.errorMsg && this.state.isGuideForNewDialogOpen &&
<GuideForNewDialog
toggleDialog={this.toggleGuideForNewDialog}
/>
}
{this.state.isSortOptionsDialogOpen &&
<SortOptionsDialog
toggleDialog={this.toggleSortOptionsDialog}
@ -198,14 +166,6 @@ class MyLibraries extends Component {
sortItems={this.sortRepoList}
/>
}
{this.state.isShowDetails && (
<div className="cur-view-detail">
<LibDetail
currentRepo={this.state.currentRepo}
closeDetails={this.closeDetails}
/>
</div>
)}
</div>
</Fragment>
);

View File

@ -32,7 +32,6 @@ const propTypes = {
onRenameRepo: PropTypes.func.isRequired,
onDeleteRepo: PropTypes.func.isRequired,
onTransferRepo: PropTypes.func.isRequired,
onRepoClick: PropTypes.func.isRequired,
onMonitorRepo: PropTypes.func.isRequired,
};
@ -148,10 +147,6 @@ class MylibRepoListItem extends React.Component {
}
};
onRepoClick = () => {
this.props.onRepoClick(this.props.repo);
};
onToggleStarRepo = (e) => {
e.preventDefault();
const repoName = this.props.repo.repo_name;
@ -328,7 +323,7 @@ class MylibRepoListItem extends React.Component {
let iconTitle = Utils.getLibIconTitle(repo);
let repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick} onFocus={this.onFocus}>
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onFocus={this.onFocus}>
<td className="text-center">
<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>
@ -382,7 +377,7 @@ class MylibRepoListItem extends React.Component {
let repoURL = this.repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`;
return (
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onClick={this.onRepoClick}>
<tr className={this.state.highlight ? 'tr-highlight' : ''} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}>
<td onClick={this.visitRepo}><img src={iconUrl} title={iconTitle} alt={iconTitle} width="24" /></td>
<td onClick={this.visitRepo}>
{this.state.isRenaming && (

View File

@ -13,8 +13,8 @@ const propTypes = {
onRenameRepo: PropTypes.func.isRequired,
onDeleteRepo: PropTypes.func.isRequired,
onTransferRepo: PropTypes.func.isRequired,
onRepoClick: PropTypes.func.isRequired,
onMonitorRepo: PropTypes.func.isRequired,
theadHidden : PropTypes.bool, // for 'My Libraries' in 'Files' page
};
class MylibRepoListView extends React.Component {
@ -70,7 +70,6 @@ class MylibRepoListView extends React.Component {
onDeleteRepo={this.props.onDeleteRepo}
onTransferRepo={this.props.onTransferRepo}
onMonitorRepo={this.props.onMonitorRepo}
onRepoClick={this.props.onRepoClick}
/>
);
})}
@ -79,10 +78,11 @@ class MylibRepoListView extends React.Component {
};
renderPCUI = () => {
const { theadHidden } = this.props;
const showStorageBackend = storages.length > 0;
const sortIcon = this.props.sortOrder === 'asc' ? <span className="fas fa-caret-up"></span> : <span className="fas fa-caret-down"></span>;
return (
<table>
<table className={theadHidden ? 'table-thead-hidden' : ''}>
<thead>
<tr>
<th width="4%"></th>

View File

@ -54,7 +54,7 @@ class Content extends Component {
};
render() {
const { loading, errorMsg, items, sortBy, sortOrder } = this.props;
const { loading, errorMsg, items, sortBy, sortOrder, theadHidden } = this.props;
const emptyTip = (
<EmptyTip>
@ -90,7 +90,7 @@ class Content extends Component {
const isDesktop = Utils.isDesktop();
const table = (
<table className={isDesktop ? '' : 'table-thead-hidden'}>
<table className={(isDesktop && !theadHidden)? '' : 'table-thead-hidden'}>
{isDesktop ? desktopThead : <LibsMobileThead />}
<tbody>
{items.map((item, index) => {
@ -113,6 +113,7 @@ class Content extends Component {
}
Content.propTypes = {
theadHidden: PropTypes.bool.isRequired,
loading: PropTypes.bool.isRequired,
errorMsg: PropTypes.string.isRequired,
items: PropTypes.array.isRequired,
@ -447,28 +448,55 @@ class SharedLibraries extends Component {
this.setState({items: items});
};
renderContent = () => {
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
return (
<Content
loading={this.state.loading}
errorMsg={this.state.errorMsg}
items={this.state.items}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
onMonitorRepo={this.onMonitorRepo}
theadHidden={inAllLibs}
/>
);
};
renderSortIconInMobile = () => {
return (
<>
{(!Utils.isDesktop() && this.state.items.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
</>
);
};
render() {
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
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 with me')}</h3>
{(!Utils.isDesktop() && this.state.items.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
{inAllLibs ? (
<>
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
<h4 className="sf-heading m-0">{gettext('Shared with me')}</h4>
{this.renderSortIconInMobile()}
</div>
<div className="cur-view-content">
<Content
loading={this.state.loading}
errorMsg={this.state.errorMsg}
items={this.state.items}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
onMonitorRepo={this.onMonitorRepo}
/>
{this.renderContent()}
</>
) : (
<div className="main-panel-center">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading m-0">{gettext('Shared with me')}</h3>
{this.renderSortIconInMobile()}
</div>
<div className="cur-view-content">
{this.renderContent()}
</div>
</div>
</div>
</div>
)}
{this.state.isSortOptionsDialogOpen &&
<SortOptionsDialog
toggleDialog={this.toggleSortOptionsDialog}
@ -482,4 +510,8 @@ class SharedLibraries extends Component {
}
}
SharedLibraries.propTypes = {
inAllLibs: PropTypes.bool
};
export default SharedLibraries;

View File

@ -0,0 +1,198 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import cookie from 'react-cookies';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, canAddPublicRepo } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import Repo from '../../models/repo';
import toaster from '../../components/toast';
import Loading from '../../components/loading';
import EmptyTip from '../../components/empty-tip';
import CommonToolbar from '../../components/toolbar/common-toolbar';
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
import SortOptionsDialog from '../../components/dialog/sort-options';
import TopToolbar from './top-toolbar';
const propTypes = {
onShowSidePanel: PropTypes.func,
onSearchedClick: PropTypes.func,
inAllLibs: PropTypes.bool
};
class PublicSharedView extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
errMessage: '',
repoList: [],
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
isSortOptionsDialogOpen: false,
libraryType: 'public',
};
}
componentDidMount() {
seafileAPI.listRepos({type: 'public'}).then((res) => {
let repoList = res.data.repos.map(item => {
let repo = new Repo(item);
return repo;
});
this.setState({
isLoading: false,
repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
});
}).catch((error) => {
this.setState({
isLoading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
onItemUnshare = (repo) => {
seafileAPI.unshareRepo(repo.repo_id, {share_type: 'public'}).then(() => {
let repoList = this.state.repoList.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.setState({repoList: repoList});
let message = gettext('Successfully unshared {name}').replace('{name}', repo.repo_name);
toaster.success(message);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
errMessage = gettext('Failed to unshare {name}').replace('{name}', repo.repo_name);
}
toaster(errMessage);
});
};
onItemDelete = () => {
// todo need to optimized
};
addRepoItem = (repo) => {
let isExist = false;
let repoIndex = 0;
let repoList = this.state.repoList;
for (let i = 0; i < repoList.length; i ++) {
if (repo.repo_id === repoList[i].repo_id) {
isExist = true;
repoIndex = i;
break;
}
}
if (isExist) {
this.state.repoList.splice(repoIndex, 1);
}
let newRepoList = this.state.repoList.map(item => {return item;});
newRepoList.unshift(repo);
this.setState({repoList: newRepoList});
};
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,
repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder)
});
};
toggleSortOptionsDialog = () => {
this.setState({
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
});
};
renderContent = () => {
const { errMessage } = this.state;
const emptyTip = (
<EmptyTip>
<h2>{gettext('No public libraries')}</h2>
<p>{gettext('No public libraries have been created yet. A public library is accessible by all users. You can create a public library by clicking the "Add Library" button in the menu bar.')}</p>
</EmptyTip>
);
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
return (
<>
{this.state.isLoading && <Loading />}
{(!this.state.isLoading && errMessage) && errMessage}
{(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip}
{(!this.state.isLoading && this.state.repoList.length > 0) &&
<SharedRepoListView
libraryType={this.state.libraryType}
repoList={this.state.repoList}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
onItemUnshare={this.onItemUnshare}
onItemDelete={this.onItemDelete}
theadHidden={inAllLibs}
/>
}
</>
);
};
renderSortIconInMobile = () => {
return (
<>
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
</>
);
};
render() {
const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page
if (inAllLibs) {
return (
<>
<div className="d-flex justify-content-between mt-3 p-1 border-bottom">
<h4 className="sf-heading m-0">{gettext('Shared with all')}</h4>
{this.renderSortIconInMobile()}
</div>
{this.renderContent()}
</>
);
}
return (
<Fragment>
<div className="main-panel-north border-left-show">
{canAddPublicRepo && <TopToolbar onShowSidePanel={this.props.onShowSidePanel} addRepoItem={this.addRepoItem} />}
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
</div>
<div className="main-panel-center">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading m-0">{gettext('Shared with all')}</h3>
{this.renderSortIconInMobile()}
</div>
<div className="cur-view-content">
{this.renderContent()}
</div>
</div>
</div>
{this.state.isSortOptionsDialogOpen &&
<SortOptionsDialog
toggleDialog={this.toggleSortOptionsDialog}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
/>
}
</Fragment>
);
}
}
PublicSharedView.propTypes = propTypes;
export default PublicSharedView;

View File

@ -1,259 +0,0 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import cookie from 'react-cookies';
import MediaQuery from 'react-responsive';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext, canAddPublicRepo } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import Repo from '../../models/repo';
import toaster from '../../components/toast';
import Loading from '../../components/loading';
import EmptyTip from '../../components/empty-tip';
import ModalPortal from '../../components/modal-portal';
import CommonToolbar from '../../components/toolbar/common-toolbar';
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
import ShareRepoDialog from '../../components/dialog/share-repo-dialog';
import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
import SortOptionsDialog from '../../components/dialog/sort-options';
const propTypes = {
onShowSidePanel: PropTypes.func.isRequired,
onSearchedClick: PropTypes.func.isRequired,
};
class PublicSharedView extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
errMessage: '',
emptyTip: '',
repoList: [],
sortBy: cookie.load('seafile-repo-dir-sort-by') || 'name', // 'name' or 'time' or 'size'
sortOrder: cookie.load('seafile-repo-dir-sort-order') || 'asc', // 'asc' or 'desc'
isSortOptionsDialogOpen: false,
libraryType: 'public',
isCreateMenuShow: false,
isCreateRepoDialogShow: false,
isSelectRepoDialpgShow: false,
};
}
componentDidMount() {
seafileAPI.listRepos({type: 'public'}).then((res) => {
let repoList = res.data.repos.map(item => {
let repo = new Repo(item);
return repo;
});
this.setState({
isLoading: false,
repoList: Utils.sortRepos(repoList, this.state.sortBy, this.state.sortOrder)
});
}).catch((error) => {
this.setState({
isLoading: false,
errorMsg: Utils.getErrorMsg(error, true) // true: show login tip if 403
});
});
}
onCreateRepo = (repo) => {
seafileAPI.createPublicRepo(repo).then(res => {
let object = { // need modify api return value
repo_id: res.data.id,
repo_name: res.data.name,
permission: res.data.permission,
size: res.data.size,
owner_name: res.data.owner_name,
owner_email: res.data.owner,
mtime: res.data.mtime,
encrypted: res.data.encrypted,
};
let repo = new Repo(object);
let repoList = this.addRepoItem(repo);
this.setState({repoList: repoList});
this.onCreateRepoToggle();
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onRepoSelectedHandler = (selectedRepoList) => {
selectedRepoList.forEach(repo => {
seafileAPI.selectOwnedRepoToPublic(repo.repo_id, {share_type: 'public', permission: repo.sharePermission}).then(() => {
let repoList = this.addRepoItem(repo);
this.setState({repoList: repoList});
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
});
};
onItemUnshare = (repo) => {
seafileAPI.unshareRepo(repo.repo_id, {share_type: 'public'}).then(() => {
let repoList = this.state.repoList.filter(item => {
return item.repo_id !== repo.repo_id;
});
this.setState({repoList: repoList});
let message = gettext('Successfully unshared {name}').replace('{name}', repo.repo_name);
toaster.success(message);
}).catch(error => {
let errMessage = Utils.getErrorMsg(error);
if (errMessage === gettext('Error')) {
errMessage = gettext('Failed to unshare {name}').replace('{name}', repo.repo_name);
}
toaster(errMessage);
});
};
onItemDelete = () => {
// todo need to optimized
};
addRepoItem = (repo) => {
let isExist = false;
let repoIndex = 0;
let repoList = this.state.repoList;
for (let i = 0; i < repoList.length; i ++) {
if (repo.repo_id === repoList[i].repo_id) {
isExist = true;
repoIndex = i;
break;
}
}
if (isExist) {
this.state.repoList.splice(repoIndex, 1);
}
let newRepoList = this.state.repoList.map(item => {return item;});
newRepoList.unshift(repo);
return newRepoList;
};
onAddRepoToggle = () => {
this.setState({isCreateMenuShow: !this.state.isCreateMenuShow});
};
onCreateRepoToggle = () => {
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
};
onSelectRepoToggle = () => {
this.setState({isSelectRepoDialpgShow: !this.state.isSelectRepoDialpgShow});
};
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,
repoList: Utils.sortRepos(this.state.repoList, sortBy, sortOrder)
});
};
toggleSortOptionsDialog = () => {
this.setState({
isSortOptionsDialogOpen: !this.state.isSortOptionsDialogOpen
});
};
render() {
let errMessage = this.state.errMessage;
let emptyTip = (
<EmptyTip>
<h2>{gettext('No public libraries')}</h2>
<p>{gettext('No public libraries have been created yet. A public library is accessible by all users. You can create a public library by clicking the "Add Library" button in the menu bar.')}</p>
</EmptyTip>
);
return (
<Fragment>
<div className="main-panel-north border-left-show">
{canAddPublicRepo &&
<div className="cur-view-toolbar">
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
<div className="operation">
<Dropdown isOpen={this.state.isCreateMenuShow} toggle={this.onAddRepoToggle}>
<MediaQuery query="(min-width: 768px)">
<DropdownToggle className='btn btn-secondary operation-item'>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add Library')}
</DropdownToggle>
</MediaQuery>
<MediaQuery query="(max-width: 767.8px)">
<DropdownToggle
tag="span"
className="sf2-icon-plus mobile-toolbar-icon"
title={gettext('Add Library')}
/>
</MediaQuery>
<DropdownMenu>
<DropdownItem onClick={this.onSelectRepoToggle}>{gettext('Share existing libraries')}</DropdownItem>
<DropdownItem onClick={this.onCreateRepoToggle}>{gettext('New Library')}</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</div>
}
<CommonToolbar onSearchedClick={this.props.onSearchedClick} />
</div>
<div className="main-panel-center">
<div className="cur-view-container">
<div className="cur-view-path">
<h3 className="sf-heading m-0">{gettext('Shared with all')}</h3>
{(!Utils.isDesktop() && this.state.repoList.length > 0) && <span className="sf3-font sf3-font-sort action-icon" onClick={this.toggleSortOptionsDialog}></span>}
</div>
<div className="cur-view-content">
{this.state.isLoading && <Loading />}
{(!this.state.isLoading && errMessage) && errMessage}
{(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip}
{(!this.state.isLoading && this.state.repoList.length > 0) &&
<SharedRepoListView
libraryType={this.state.libraryType}
repoList={this.state.repoList}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
onItemUnshare={this.onItemUnshare}
onItemDelete={this.onItemDelete}
/>
}
</div>
</div>
</div>
{this.state.isSortOptionsDialogOpen &&
<SortOptionsDialog
toggleDialog={this.toggleSortOptionsDialog}
sortBy={this.state.sortBy}
sortOrder={this.state.sortOrder}
sortItems={this.sortItems}
/>
}
{this.state.isCreateRepoDialogShow && (
<ModalPortal>
<CreateRepoDialog
libraryType={this.state.libraryType}
onCreateToggle={this.onCreateRepoToggle}
onCreateRepo={this.onCreateRepo}
/>
</ModalPortal>
)}
{this.state.isSelectRepoDialpgShow && (
<ModalPortal>
<ShareRepoDialog
onRepoSelectedHandler={this.onRepoSelectedHandler}
onShareRepoDialogClose={this.onSelectRepoToggle}
/>
</ModalPortal>
)}
</Fragment>
);
}
}
PublicSharedView.propTypes = propTypes;
export default PublicSharedView;

View File

@ -0,0 +1,125 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import MediaQuery from 'react-responsive';
import { Dropdown, DropdownToggle, DropdownMenu, DropdownItem } from 'reactstrap';
import { seafileAPI } from '../../utils/seafile-api';
import { gettext } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import Repo from '../../models/repo';
import toaster from '../../components/toast';
import ModalPortal from '../../components/modal-portal';
import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
import ShareRepoDialog from '../../components/dialog/share-repo-dialog';
const propTypes = {
onShowSidePanel: PropTypes.func.isRequired,
addRepoItem: PropTypes.func.isRequired
};
class TopToolbar extends React.Component {
constructor(props) {
super(props);
this.state = {
libraryType: 'public',
isCreateMenuShow: false,
isCreateRepoDialogShow: false,
isSelectRepoDialpgShow: false,
};
}
onCreateRepo = (repo) => {
seafileAPI.createPublicRepo(repo).then(res => {
let object = {
repo_id: res.data.id,
repo_name: res.data.name,
permission: res.data.permission,
size: res.data.size,
owner_name: res.data.owner_name,
owner_email: res.data.owner,
mtime: res.data.mtime,
encrypted: res.data.encrypted,
};
let repo = new Repo(object);
this.props.addRepoItem(repo);
this.onCreateRepoToggle();
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
};
onRepoSelectedHandler = (selectedRepoList) => {
selectedRepoList.forEach(repo => {
seafileAPI.selectOwnedRepoToPublic(repo.repo_id, {share_type: 'public', permission: repo.sharePermission}).then(() => {
this.props.addRepoItem(repo);
}).catch((error) => {
let errMessage = Utils.getErrorMsg(error);
toaster.danger(errMessage);
});
});
};
onAddRepoToggle = () => {
this.setState({isCreateMenuShow: !this.state.isCreateMenuShow});
};
onCreateRepoToggle = () => {
this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
};
onSelectRepoToggle = () => {
this.setState({isSelectRepoDialpgShow: !this.state.isSelectRepoDialpgShow});
};
render() {
return (
<Fragment>
<div className="cur-view-toolbar">
<span className="sf2-icon-menu side-nav-toggle hidden-md-up d-md-none" title="Side Nav Menu" onClick={this.props.onShowSidePanel}></span>
<div className="operation">
<Dropdown isOpen={this.state.isCreateMenuShow} toggle={this.onAddRepoToggle}>
<MediaQuery query="(min-width: 768px)">
<DropdownToggle className='btn btn-secondary operation-item'>
<i className="fas fa-plus-square text-secondary mr-1"></i>{gettext('Add Library')}
</DropdownToggle>
</MediaQuery>
<MediaQuery query="(max-width: 767.8px)">
<DropdownToggle
tag="span"
className="sf2-icon-plus mobile-toolbar-icon"
title={gettext('Add Library')}
/>
</MediaQuery>
<DropdownMenu>
<DropdownItem onClick={this.onSelectRepoToggle}>{gettext('Share existing libraries')}</DropdownItem>
<DropdownItem onClick={this.onCreateRepoToggle}>{gettext('New Library')}</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</div>
{this.state.isCreateRepoDialogShow && (
<ModalPortal>
<CreateRepoDialog
libraryType={this.state.libraryType}
onCreateToggle={this.onCreateRepoToggle}
onCreateRepo={this.onCreateRepo}
/>
</ModalPortal>
)}
{this.state.isSelectRepoDialpgShow && (
<ModalPortal>
<ShareRepoDialog
onRepoSelectedHandler={this.onRepoSelectedHandler}
onShareRepoDialogClose={this.onSelectRepoToggle}
/>
</ModalPortal>
)}
</Fragment>
);
}
}
TopToolbar.propTypes = propTypes;
export default TopToolbar;

View File

@ -719,7 +719,13 @@ a, a:hover { color: #ec8000; }
line-height: 1.5rem;
height: auto;
}
.side-nav-con .sub-nav#files-sub-nav .nav-item .nav-link {
padding-left: 3rem;
}
.side-nav-con .sub-nav#files-sub-nav .sub-nav .nav-item .nav-link {
padding-left: 2rem;
}
.side-panel-slide {
transition: all .3s ease-in-out;
}

View File

@ -287,6 +287,7 @@ urlpatterns = [
path('share-admin-upload-links/', react_fake_view, name="share_admin_upload_links"),
path('shared-libs/', react_fake_view, name="shared_libs"),
path('shared-with-ocm/', react_fake_view, name="shared_with_ocm"),
path('libraries/', react_fake_view, name="libs"),
path('my-libs/', react_fake_view, name="my_libs"),
path('groups/', react_fake_view, name="groups"),
path('group/<int:group_id>/', react_fake_view, name="group"),