From 4eab55fdf93e4375c6a5fa3ebb4dbb8333f2610c Mon Sep 17 00:00:00 2001 From: llj Date: Fri, 19 Apr 2024 14:51:41 +0800 Subject: [PATCH] Files (#5881) * [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') --- frontend/src/app.js | 10 +- .../components/dialog/create-group-dialog.js | 14 +- frontend/src/components/main-side-nav.js | 106 +++--- .../shared-repo-list-item.js | 13 +- .../shared-repo-list-view.js | 7 +- .../src/components/toolbar/groups-toolbar.js | 57 ++-- ...repo-view-toobar.js => my-libs-toolbar.js} | 45 ++- frontend/src/pages/groups/group-item.js | 100 ++++++ frontend/src/pages/groups/group-view.js | 19 -- frontend/src/pages/groups/groups-view.js | 169 ++-------- frontend/src/pages/libraries/index.js | 305 ++++++++++++++++++ frontend/src/pages/my-libs/my-libs.js | 56 +--- .../src/pages/my-libs/mylib-repo-list-item.js | 9 +- .../src/pages/my-libs/mylib-repo-list-view.js | 6 +- frontend/src/pages/shared-libs/shared-libs.js | 68 ++-- frontend/src/pages/shared-with-all/index.js | 198 ++++++++++++ .../shared-with-all/public-shared-view.js | 259 --------------- .../src/pages/shared-with-all/top-toolbar.js | 125 +++++++ media/css/seahub_react.css | 6 + seahub/urls.py | 1 + 20 files changed, 947 insertions(+), 626 deletions(-) rename frontend/src/components/toolbar/{repo-view-toobar.js => my-libs-toolbar.js} (63%) create mode 100644 frontend/src/pages/groups/group-item.js create mode 100644 frontend/src/pages/libraries/index.js create mode 100644 frontend/src/pages/shared-with-all/index.js delete mode 100644 frontend/src/pages/shared-with-all/public-shared-view.js create mode 100644 frontend/src/pages/shared-with-all/top-toolbar.js diff --git a/frontend/src/app.js b/frontend/src/app.js index bf80f0aebf..fe763f66ff 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -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 ? - : - ; - return ( @@ -212,7 +209,8 @@ class App extends Component { - {home} + + diff --git a/frontend/src/components/dialog/create-group-dialog.js b/frontend/src/components/dialog/create-group-dialog.js index 3d98626937..7577fd42fe 100644 --- a/frontend/src/components/dialog/create-group-dialog.js +++ b/frontend/src/components/dialog/create-group-dialog.js @@ -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( - - {gettext('New Group')} + + {gettext('New Group')} {this.state.errorMsg} - + @@ -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; diff --git a/frontend/src/components/main-side-nav.js b/frontend/src/components/main-side-nav.js index 5faf778594..236fb1b15c 100644 --- a/frontend/src/components/main-side-nav.js +++ b/frontend/src/components/main-side-nav.js @@ -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 (
-

{gettext('Files')}

    - {canAddRepo && ( -
  • - this.tabItemClick(e, 'my-libs')}> - - {gettext('My Libraries')} - -
  • - )} -
  • - this.tabItemClick(e, 'shared-libs')}> - - {gettext('Shared with me')} +
  • + this.tabItemClick(e, 'libraries')}> + + {gettext('Files')} + +
      + {canAddRepo && ( +
    • + this.tabItemClick(e, 'my-libs')}> + {gettext('My Libraries')} + +
    • + )} +
    • + this.tabItemClick(e, 'shared-libs')}> + {gettext('Shared with me')} + +
    • + {canViewOrg && +
    • this.tabItemClick(e, 'org')}> + + {gettext('Shared with all')} + +
    • + } +
    • + + {gettext('Shared with groups')} + + + {this.renderSharedGroups()} +
    • + {enableOCM && +
    • + this.tabItemClick(e, 'shared-with-ocm')}> + {gettext('Shared from other servers')} + +
    • + } + {enableOCMViaWebdav && +
    • + this.tabItemClick(e, 'ocm-via-webdav')}> + {gettext('Shared from other servers')} + +
    • + } +
  • - { canViewOrg && -
  • this.tabItemClick(e, 'org')}> - - - {gettext('Shared with all')} - -
  • - } -
  • - - - {gettext('Shared with groups')} - - - {this.renderSharedGroups()} -
  • - {enableOCM && -
  • - this.tabItemClick(e, 'shared-with-ocm')}> - - {gettext('Shared from other servers')} - -
  • - } - {enableOCMViaWebdav && -
  • - this.tabItemClick(e, 'ocm-via-webdav')}> - - {gettext('Shared from other servers')} - -
  • - } -
- -

{gettext('Tools')}

-
  • this.tabItemClick(e, 'starred')}> diff --git a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js index 8698913db5..b1ab63694c 100644 --- a/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js +++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js @@ -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'); } diff --git a/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js b/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js index 1fee1ab0cb..4bcd05f5cf 100644 --- a/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js +++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js @@ -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 ( - +
    diff --git a/frontend/src/components/toolbar/groups-toolbar.js b/frontend/src/components/toolbar/groups-toolbar.js index aa5dd83ef1..1628f155da 100644 --- a/frontend/src/components/toolbar/groups-toolbar.js +++ b/frontend/src/components/toolbar/groups-toolbar.js @@ -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 ( -
    -
    - - {canAddGroup && ( -
    - - - - - - -
    - )} -
    - -
    + <> + {Utils.isDesktop() ? ( +
    + +
    + ) : ( + + )} + {this.state.isDialogOpen && + + } + ); } } diff --git a/frontend/src/components/toolbar/repo-view-toobar.js b/frontend/src/components/toolbar/my-libs-toolbar.js similarity index 63% rename from frontend/src/components/toolbar/repo-view-toobar.js rename to frontend/src/components/toolbar/my-libs-toolbar.js index c17a09490e..3bd9e77d76 100644 --- a/frontend/src/components/toolbar/repo-view-toobar.js +++ b/frontend/src/components/toolbar/my-libs-toolbar.js @@ -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 ( -
    - - {Utils.isDesktop() ? ( -
    - + {Utils.isDesktop() ? ( +
    + + {moreShown && {gettext('More')} @@ -68,15 +67,15 @@ class RepoViewToolbar extends React.Component { -
    - ) : ( - - )} -
    - {this.state.isCreateRepoDialogShow && ( + } +
    + ) : ( + + )} + {this.state.isCreateRepoDialogOpen && ( @@ -87,6 +86,6 @@ class RepoViewToolbar extends React.Component { } } -RepoViewToolbar.propTypes = propTypes; +MyLibsToolbar.propTypes = propTypes; -export default RepoViewToolbar; +export default MyLibsToolbar; diff --git a/frontend/src/pages/groups/group-item.js b/frontend/src/pages/groups/group-item.js new file mode 100644 index 0000000000..b8317ce39a --- /dev/null +++ b/frontend/src/pages/groups/group-item.js @@ -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 =

    {gettext('No libraries')}

    ; + return ( +
    +

    + {group.name} +

    + {group.repos.length === 0 ? + emptyTip : + + } +
    + ); + } +} + +GroupItem.propTypes = propTypes; + +export default GroupItem; diff --git a/frontend/src/pages/groups/group-view.js b/frontend/src/pages/groups/group-view.js index a2ffdb1000..87d34d9789 100644 --- a/frontend/src/pages/groups/group-view.js +++ b/frontend/src/pages/groups/group-view.js @@ -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} /> } - {this.state.isShowDetails && ( -
    - -
    - )} {this.state.isCreateRepoDialogShow && !this.state.isDepartmentGroup && ( diff --git a/frontend/src/pages/groups/groups-view.js b/frontend/src/pages/groups/groups-view.js index 3da10d83ae..58b6c476c8 100644 --- a/frontend/src/pages/groups/groups-view.js +++ b/frontend/src/pages/groups/groups-view.js @@ -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 =

    {gettext('No libraries')}

    ; - return ( -
    -

    - {group.name} -

    - {this.state.repoList.length === 0 ? - emptyTip : - - } -
    - ); - } -} - -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 = ( @@ -192,11 +86,12 @@ class GroupsView extends React.Component { return ( - + > + {canAddGroup && } +
    @@ -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 ( - ); })}
    - {this.state.isShowDetails && ( -
    - -
    - )}
    - { this.state.showAddGroupModal && - - }
    ); } diff --git a/frontend/src/pages/libraries/index.js b/frontend/src/pages/libraries/index.js new file mode 100644 index 0000000000..f8c40a5182 --- /dev/null +++ b/frontend/src/pages/libraries/index.js @@ -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 = ( + +

    {gettext('No libraries')}

    +

    {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.')}

    +
    + ); + + const groupsEmptyTip = ( + +

    {gettext('No groups')}

    + {canAddGroup ? +

    {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.')}

    : +

    {gettext('You are not in any groups. Groups allow multiple people to collaborate on libraries. Groups you join will be listed here.')}

    + } +
    + ); + + return ( + + + <> + {canAddRepo && } + {canAddGroup && } + + +
    +
    +
    +

    {gettext('Files')}

    +
    +
    + {canAddRepo && ( +
    +
    +

    {gettext('My Libraries')}

    + {(!Utils.isDesktop() && this.state.repoList.length > 0) && } +
    + {this.state.isLoading ? : ( + this.state.errorMsg ?

    {this.state.errorMsg}

    : ( + this.state.repoList.length === 0 ? emptyTip : ( + + )))} +
    + )} + +
    + +
    + + {canViewOrg && ( +
    + +
    + )} + +
    +
    +

    {gettext('Shared with groups')}

    +
    + {this.state.isGroupsLoading? : ( + this.state.groupsErrorMsg ?

    {this.state.groupsErrorMsg}

    : ( + this.state.groupList.length === 0 ? groupsEmptyTip : ( + this.state.groupList.map((group, index) => { + return ( + + ); + }) + )))} +
    + +
    +
    + {!this.state.isLoading && !this.state.errorMsg && this.state.isGuideForNewDialogOpen && + + } + {this.state.isSortOptionsDialogOpen && + + } +
    +
    + ); + } +} + +Libraries.propTypes = propTypes; + +export default Libraries; diff --git a/frontend/src/pages/my-libs/my-libs.js b/frontend/src/pages/my-libs/my-libs.js index 1dad762ac4..a21ee899b7 100644 --- a/frontend/src/pages/my-libs/my-libs.js +++ b/frontend/src/pages/my-libs/my-libs.js @@ -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 ( -
    - - -
    + + +
    @@ -179,17 +153,11 @@ class MyLibraries extends Component { onDeleteRepo={this.onDeleteRepo} onTransferRepo={this.onTransferRepo} onMonitorRepo={this.onMonitorRepo} - onRepoClick={this.onRepoClick} sortRepoList={this.sortRepoList} /> }
    - {!this.state.isLoading && !this.state.errorMsg && this.state.isGuideForNewDialogOpen && - - } {this.state.isSortOptionsDialogOpen && } - {this.state.isShowDetails && ( -
    - -
    - )}
    ); diff --git a/frontend/src/pages/my-libs/mylib-repo-list-item.js b/frontend/src/pages/my-libs/mylib-repo-list-item.js index 1198be5bc0..828e413267 100644 --- a/frontend/src/pages/my-libs/mylib-repo-list-item.js +++ b/frontend/src/pages/my-libs/mylib-repo-list-item.js @@ -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 ( -
    + +
    @@ -382,7 +377,7 @@ class MylibRepoListItem extends React.Component { let repoURL = this.repoURL = `${siteRoot}library/${repo.repo_id}/${Utils.encodePath(repo.repo_name)}/`; return ( -
    {iconTitle} {this.state.isRenaming && ( diff --git a/frontend/src/pages/my-libs/mylib-repo-list-view.js b/frontend/src/pages/my-libs/mylib-repo-list-view.js index f6cb36c72b..783fce85e5 100644 --- a/frontend/src/pages/my-libs/mylib-repo-list-view.js +++ b/frontend/src/pages/my-libs/mylib-repo-list-view.js @@ -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' ? : ; return ( - +
    diff --git a/frontend/src/pages/shared-libs/shared-libs.js b/frontend/src/pages/shared-libs/shared-libs.js index 15c09fd746..6961d11a20 100644 --- a/frontend/src/pages/shared-libs/shared-libs.js +++ b/frontend/src/pages/shared-libs/shared-libs.js @@ -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 = ( @@ -90,7 +90,7 @@ class Content extends Component { const isDesktop = Utils.isDesktop(); const table = ( -
    +
    {isDesktop ? desktopThead : } {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 ( + + ); + }; + + renderSortIconInMobile = () => { + return ( + <> + {(!Utils.isDesktop() && this.state.items.length > 0) && } + + ); + }; + render() { + const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page return ( -
    -
    -
    -

    {gettext('Shared with me')}

    - {(!Utils.isDesktop() && this.state.items.length > 0) && } + {inAllLibs ? ( + <> +
    +

    {gettext('Shared with me')}

    + {this.renderSortIconInMobile()}
    -
    - + {this.renderContent()} + + ) : ( +
    +
    +
    +

    {gettext('Shared with me')}

    + {this.renderSortIconInMobile()} +
    +
    + {this.renderContent()} +
    -
    + )} {this.state.isSortOptionsDialogOpen && { + 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 = ( + +

    {gettext('No public libraries')}

    +

    {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.')}

    +
    + ); + const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page + return ( + <> + {this.state.isLoading && } + {(!this.state.isLoading && errMessage) && errMessage} + {(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip} + {(!this.state.isLoading && this.state.repoList.length > 0) && + + } + + ); + }; + + renderSortIconInMobile = () => { + return ( + <> + + {(!Utils.isDesktop() && this.state.repoList.length > 0) && } + + ); + }; + + render() { + const { inAllLibs = false } = this.props; // inAllLibs: in 'All Libs'('Files') page + + if (inAllLibs) { + return ( + <> +
    +

    {gettext('Shared with all')}

    + {this.renderSortIconInMobile()} +
    + {this.renderContent()} + + ); + } + + return ( + +
    + {canAddPublicRepo && } + +
    +
    +
    +
    +

    {gettext('Shared with all')}

    + {this.renderSortIconInMobile()} +
    +
    + {this.renderContent()} +
    +
    +
    + {this.state.isSortOptionsDialogOpen && + + } +
    + ); + } +} + +PublicSharedView.propTypes = propTypes; + +export default PublicSharedView; diff --git a/frontend/src/pages/shared-with-all/public-shared-view.js b/frontend/src/pages/shared-with-all/public-shared-view.js deleted file mode 100644 index 7ac4368655..0000000000 --- a/frontend/src/pages/shared-with-all/public-shared-view.js +++ /dev/null @@ -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 = ( - -

    {gettext('No public libraries')}

    -

    {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.')}

    -
    - ); - return ( - -
    - {canAddPublicRepo && -
    - -
    - - - - {gettext('Add Library')} - - - - - - - {gettext('Share existing libraries')} - {gettext('New Library')} - - -
    -
    - } - -
    -
    -
    -
    -

    {gettext('Shared with all')}

    - {(!Utils.isDesktop() && this.state.repoList.length > 0) && } -
    -
    - {this.state.isLoading && } - {(!this.state.isLoading && errMessage) && errMessage} - {(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip} - {(!this.state.isLoading && this.state.repoList.length > 0) && - - } -
    -
    -
    - {this.state.isSortOptionsDialogOpen && - - } - {this.state.isCreateRepoDialogShow && ( - - - - )} - {this.state.isSelectRepoDialpgShow && ( - - - - )} -
    - ); - } -} - -PublicSharedView.propTypes = propTypes; - -export default PublicSharedView; diff --git a/frontend/src/pages/shared-with-all/top-toolbar.js b/frontend/src/pages/shared-with-all/top-toolbar.js new file mode 100644 index 0000000000..f76ba54e69 --- /dev/null +++ b/frontend/src/pages/shared-with-all/top-toolbar.js @@ -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 ( + +
    + +
    + + + + {gettext('Add Library')} + + + + + + + {gettext('Share existing libraries')} + {gettext('New Library')} + + +
    +
    + {this.state.isCreateRepoDialogShow && ( + + + + )} + {this.state.isSelectRepoDialpgShow && ( + + + + )} +
    + ); + } +} + +TopToolbar.propTypes = propTypes; + +export default TopToolbar; diff --git a/media/css/seahub_react.css b/media/css/seahub_react.css index f4e3bbeedc..1fed60f7d6 100644 --- a/media/css/seahub_react.css +++ b/media/css/seahub_react.css @@ -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; } diff --git a/seahub/urls.py b/seahub/urls.py index 169095cfcb..5c4ff5777c 100644 --- a/seahub/urls.py +++ b/seahub/urls.py @@ -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//', react_fake_view, name="group"),