diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 470358db0f..20483b73f0 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -10793,9 +10793,9 @@
}
},
"seafile-js": {
- "version": "0.2.41",
- "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.41.tgz",
- "integrity": "sha512-yDNdzALYn5rMt6TeZwWbbZvmFWyS4xhFoEJQIZImGSHXiNHykcEuLkKA2YbUS1z6AwsDPWGJrU0UvzpmQUpX2Q==",
+ "version": "0.2.42",
+ "resolved": "https://registry.npmjs.org/seafile-js/-/seafile-js-0.2.42.tgz",
+ "integrity": "sha512-qbia7tcIRRtI16ks42ocJHd2i1z7QkfsvqGawATKtrnvij2kXNRnTnZqwbHpSjv1H1yctots+NN9F1JRziHDkQ==",
"requires": {
"axios": "^0.18.0",
"form-data": "^2.3.2",
diff --git a/frontend/package.json b/frontend/package.json
index 4f6a4c760a..ddf844003c 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -29,7 +29,7 @@
"react-moment": "^0.7.9",
"react-select": "^2.1.1",
"reactstrap": "^6.4.0",
- "seafile-js": "^0.2.41",
+ "seafile-js": "^0.2.42",
"seafile-ui": "^0.1.10",
"sw-precache-webpack-plugin": "0.11.4",
"unified": "^7.0.0",
diff --git a/frontend/src/app.js b/frontend/src/app.js
index 45d03eae4c..e7c2c410de 100644
--- a/frontend/src/app.js
+++ b/frontend/src/app.js
@@ -18,8 +18,8 @@ import ShareAdminUploadLinks from './pages/share-admin/upload-links';
import SharedLibraries from './pages/shared-libs/shared-libs';
import MyLibraries from './pages/my-libs/my-libs';
import DirView from './components/dir-view/dir-view';
-import Groups from './pages/group/groups';
-import Group from './pages/group/group';
+import Group from './pages/groups/group-view';
+import Groups from './pages/groups/groups-view';
import MainContentWrapper from './components/main-content-wrapper';
import './assets/css/fa-solid.css';
diff --git a/frontend/src/components/dialog/create-department-repo-dialog.js b/frontend/src/components/dialog/create-department-repo-dialog.js
new file mode 100644
index 0000000000..b320cdae46
--- /dev/null
+++ b/frontend/src/components/dialog/create-department-repo-dialog.js
@@ -0,0 +1,106 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Button, Modal, ModalHeader, Input, ModalBody, ModalFooter, Form, FormGroup, Label, Col, FormText } from 'reactstrap';
+import { gettext, maxFileName } from '../../utils/constants';
+
+const propTypes = {
+ onCreateRepo: PropTypes.func.isRequired,
+ onCreateToggle: PropTypes.func.isRequired
+};
+
+class CreateDepartmentRepoDialog extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ repoName: '',
+ errMessage: '',
+ };
+ this.newInput = React.createRef();
+ }
+
+ handleChange = (e) => {
+ this.setState({
+ repoName: e.target.value,
+ });
+ }
+
+ handleSubmit = () => {
+ let isValid = this.validateRepoName();
+ if (isValid) {
+ let repo = this.createRepo(this.state.repoName);
+ this.props.onCreateRepo(repo);
+ }
+
+ }
+
+ handleKeyPress = (e) => {
+ if (e.key === 'Enter') {
+ this.handleSubmit();
+ }
+ }
+
+ toggle = () => {
+ this.props.onCreateToggle();
+ }
+
+ componentDidMount = () => {
+ this.newInput.focus();
+ }
+
+ validateRepoName = () => {
+ let errMessage = '';
+ let repoName = this.state.repoName.trim();
+ if (!repoName.length) {
+ errMessage = 'Name is required';
+ this.setState({errMessage: errMessage});
+ return false;
+ }
+ if (repoName.indexOf('/') > -1) {
+ errMessage = 'Name should not include \'/\'.';
+ this.setState({errMessage: errMessage});
+ return false;
+ }
+ if (repoName.length > maxFileName) {
+ errMessage = 'RepoName\'s length is must little than ' + maxFileName;
+ this.setState({errMessage: errMessage});
+ return false;
+ }
+ return true;
+ }
+
+ createRepo = (repoName) => {
+ let repo = { repo_name: repoName };
+ return repo;
+ }
+
+ render() {
+ return (
+
+ {gettext('New Department Library')}
+
+
+ {gettext(this.state.errMessage)}
+
+
+ {gettext('Submit')}
+
+
+ );
+ }
+}
+
+CreateDepartmentRepoDialog.propTypes = propTypes;
+
+export default CreateDepartmentRepoDialog;
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
new file mode 100644
index 0000000000..3f47415558
--- /dev/null
+++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-item.js
@@ -0,0 +1,309 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
+import { Utils } from '../../utils/utils';
+import { gettext, siteRoot, isPro, username, folderPermEnabled } from '../../utils/constants';
+
+const propTypes = {
+ currentGroup: PropTypes.object,
+ repo: PropTypes.object.isRequired,
+ isItemFreezed: PropTypes.bool.isRequired,
+ onFreezedItem: PropTypes.func.isRequired,
+ onItemUnshare: PropTypes.func.isRequired,
+};
+
+class SharedRepoListItem extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ highlight: false,
+ isOperationShow: false,
+ isItemMenuShow: false,
+ };
+ this.isDeparementOnwerGroupMember = false;
+ }
+
+ onMouseEnter = () => {
+ if (!this.props.isItemFreezed) {
+ this.setState({
+ highlight: true,
+ isOperationShow: true,
+ });
+ }
+ }
+
+ onMouseOver = () => {
+ if (!this.props.isItemFreezed) {
+ this.setState({
+ highlight: true,
+ isOperationShow: true,
+ });
+ }
+ }
+
+ onMouseLeave = () => {
+ if (!this.props.isItemFreezed) {
+ this.setState({
+ highlight: false,
+ isOperationShow: false,
+ });
+ }
+ }
+
+ clickOperationMenuToggle = (e) => {
+ e.preventDefault();
+ this.toggleOperationMenu();
+ }
+
+ toggleOperationMenu = () => {
+ if (this.props.isItemFreezed) {
+ this.setState({
+ highlight: false,
+ isOperationShow: false,
+ isItemMenuShow: !this.state.isItemMenuShow
+ });
+ } else {
+ this.setState({
+ isItemMenuShow: !this.state.isItemMenuShow
+ });
+ }
+ this.props.onFreezedItem();
+ }
+
+ getRepoComputeParams = () => {
+ let repo = this.props.repo;
+ let currentGroup = this.props.currentGroup; //todo--change to libray
+ let isReadyOnly = false;
+ if ( repo.permission === 'r' || repo.permission === 'preview') {
+ isReadyOnly = true;
+ }
+ let iconUrl = Utils.getLibIconUrl({
+ is_encryted: repo.encrypted,
+ is_readyonly: isReadyOnly,
+ size: Utils.isHiDPI() ? 48 : 24
+ });
+ let iconTitle = Utils.getLibIconTitle({
+ 'encrypted': repo.encrypted,
+ 'is_admin': repo.is_admin,
+ 'permission': repo.permission
+ });
+
+ //todo change to library; div-view is not compatibility
+ let libPath = `${siteRoot}#group/${currentGroup.id}/lib/${this.props.repo.repo_id}/`;
+
+ return { iconUrl, iconTitle, libPath };
+ }
+
+ onMenuItemClick = (e) => {
+ let operation = e.target.dataset.toggle;
+ switch(operation) {
+ case 'Rename':
+ this.onItemRename();
+ break;
+ case 'Folder Permission':
+ this.onItemPermisionChanged();
+ break;
+ case 'Details':
+ this.onItemDetails();
+ break;
+ case 'Share':
+ this.onItemShared();
+ break;
+ case 'Unshare':
+ this.onItemUnshare();
+ break;
+ default:
+ break;
+ }
+ }
+
+ onItemRename = () => {
+ // todo
+ }
+
+ onItemPermisionChanged = () => {
+ // todo
+ }
+
+ onItemDetails = () => {
+ // todo
+ }
+
+ onItemShare = () => {
+ // todo
+ }
+
+ onItemUnshare = () => {
+ // todo
+ this.props.onItemUnshare(this.props.repo);
+ }
+
+ onItemDelete = () => {
+ // todo
+ }
+
+ generatorOperations = () => {
+ let { repo, currentGroup } = this.props;
+ //todo this have a bug; use current api is not return admins param;
+ let isStaff = currentGroup.admins && currentGroup.admins.indexOf(username) > -1; //for group repolist;
+ let isRepoOwner = repo.owner_email === username;
+ let isAdmin = repo.is_admin;
+ let operations = [];
+ // todo ,shared width me shared width all;
+ if (isPro) {
+ if (repo.owner_email.indexOf('@seafile_group') != -1) { //current repo is belong to a group;
+ if (isStaff && repo.owner_email == currentGroup.id + '@seafile_group') { //is a member of this current group,
+ this.isDeparementOnwerGroupMember = true;
+ if (folderPermEnabled) {
+ operations = ['Rename', 'Folder Permission', 'deatils'];
+ } else {
+ operations = ['Rename', 'Details']
+ }
+ } else {
+ operations.push('Unshare');
+ }
+ } else {
+ if (isRepoOwner || isAdmin) {
+ operations.push('Share');
+ }
+ if (isStaff || isRepoOwner || isAdmin) {
+ operations.push('Unshare');
+ }
+ }
+ } else {
+ if (isRepoOwner) {
+ operations.push('share');
+ }
+ if (isStaff || isRepoOwner) {
+ operations.push('Unshare');
+ }
+ }
+ return operations;
+ }
+
+ generatorMobileMenu = () => {
+ let operations = this.generatorOperations();
+ if (this.isDeparementOnwerGroupMember) {
+ operations.unshift('unshare');
+ operations.unshift('share');
+ }
+ return (
+
+
+
+
+
+ {operations.map((item, index) => {
+ return (
+ {gettext(item)}
+ );
+ })}
+
+
+
+ );
+ }
+
+ generatorPCMenu = () => {
+ // scene one: (share, delete, itemToggle and other operations);
+ // scene two: (share, unshare), (share), (unshare)
+ let operations = this.generatorOperations();
+ const shareOperation = ;
+ const unshareOperation =
+ const deleteOperation = ;
+
+ if (this.isDeparementOnwerGroupMember) {
+ return (
+
+ {shareOperation}
+ {deleteOperation}
+
+
+
+ {operations.map((item, index) => {
+ return {gettext(item)}
+ })}
+
+
+
+ );
+ } else {
+ if (operations.length == 2) {
+ return (
+
+ {shareOperation}
+ {unshareOperation}
+
+ );
+ }
+ if (operations.length == 1 && operations[0] === 'share') {
+ return shareOperation;
+ }
+
+ if (operations.length == 1 && operations[0] === 'unshare') {
+ return unshareOperation;
+ }
+ }
+ return null;
+ }
+
+ renderPCUI = () => {
+ let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
+ let { repo } = this.props;
+ return (
+
+
+ {repo.repo_name}
+ {this.state.isOperationShow && this.generatorPCMenu()}
+ {repo.size}
+ {moment(repo.last_modified).fromNow()}
+ {repo.owner_name}
+
+ );
+ }
+
+ renderMobileUI = () => {
+ let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
+ let { repo } = this.props;
+ return (
+
+
+
+ {repo.repo_name}
+ {repo.owner_name}
+ {repo.size}
+ {moment(repo.last_modified).fromNow()}
+
+ {this.generatorMobileMenu()}
+
+ );
+ }
+
+ render() {
+ if (window.innerWidth >= 768) {
+ return this.renderPCUI();
+ } else {
+ return this.renderMobileUI();
+ }
+ }
+}
+
+SharedRepoListItem.propTypes = propTypes;
+
+export default SharedRepoListItem;
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
new file mode 100644
index 0000000000..70f9faca26
--- /dev/null
+++ b/frontend/src/components/shared-repo-list-view/shared-repo-list-view.js
@@ -0,0 +1,105 @@
+import React, {Fragment} from 'react';
+import PropTypes from 'prop-types';
+import { gettext } from '../../utils/constants';
+import SharedRepoListItem from './shared-repo-list-item';
+
+const propTypes = {
+ currentGroup: PropTypes.object,
+ repoList: PropTypes.array.isRequired,
+ isShowTableThread: PropTypes.bool,
+ onItemUnshare: PropTypes.func.isRequired,
+};
+
+class SharedRepoListView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isItemFreezed: false,
+ };
+ }
+
+ onFreezedItem = () => {
+ this.setState({
+ isItemFreezed: !this.state.isItemFreezed,
+ });
+ }
+
+ renderRepoListView = () => {
+ return (
+
+ {this.props.repoList.map(repo => {
+ return (
+
+ );
+ })}
+
+ );
+ }
+
+ renderPCUI = () => {
+ let isShowTableThread = this.props.isShowTableThread !== undefined ? this.props.isShowTableThread : true;
+ return (
+
+
+
+ {gettext("Library Type")}
+ {gettext("Name")}
+ {/*TODO: sort*/}
+
+ {gettext("Actions")}
+ {gettext("Size")}
+ {gettext("Last Update")}
+ {/*TODO: sort*/}
+
+ {gettext("Owner")}
+
+
+
+ {this.renderRepoListView()}
+
+
+ );
+ }
+
+ renderMobileUI = () => {
+ let isShowTableThread = this.props.isShowTableThread !== undefined ? this.props.isShowTableThread : true;
+ return (
+
+
+
+ {gettext("Library Type")}
+
+ {gettext("Sort:")} {/* TODO: sort */}
+ {gettext("name")}
+ {gettext("last update")}
+
+ {gettext("Actions")}
+
+
+
+ {this.renderRepoListView()}
+
+
+ );
+ }
+
+ render() {
+ if (window.innerWidth >= 768) {
+ return this.renderPCUI();
+ } else {
+ return this.renderMobileUI();
+ }
+ }
+}
+
+SharedRepoListView.propTypes = propTypes;
+
+export default SharedRepoListView;
diff --git a/frontend/src/models/group.js b/frontend/src/models/group.js
new file mode 100644
index 0000000000..36960ed65d
--- /dev/null
+++ b/frontend/src/models/group.js
@@ -0,0 +1,17 @@
+class Group {
+ constructor(object) {
+ this.id= object.id;
+ this.name = object.name;
+ this.owner = object.owner;
+ this.admins = object.admins || [];
+ this.avatar_url = object.avatar_url;
+ this.created_at = object.created_at;
+ this.parent_group_id = object.parent_group_id;
+ this.wiki_enabled = object.wiki_enabled;
+ if (object.repos) {
+ this.repos = object.repos;
+ }
+ }
+}
+
+export default Group;
diff --git a/frontend/src/models/repoInfo.js b/frontend/src/models/repoInfo.js
new file mode 100644
index 0000000000..ca49afda7e
--- /dev/null
+++ b/frontend/src/models/repoInfo.js
@@ -0,0 +1,24 @@
+import { Utils } from '../utils/utils';
+
+class RepoInfo {
+ constructor(object) {
+ this.repo_id = object.repo_id;
+ this.repo_name = object.repo_name;
+ this.permission = object.permission;
+ this.size = Utils.bytesToSize(object.size);
+ this.owner_name = object.owner_name;
+ this.owner_email = object.owner_email;
+ this.owner_contact_email = object.owner_contact_name;
+ this.is_admin = object.is_admin;
+ this.encrypted = object.encrypted;
+ this.last_modified = object.last_modified;
+ this.modifier_contact_email = object.modifier_contact_email;
+ this.modifier_email = object.modifier_email;
+ this.modifier_name = object.modifier_name;
+ this.mtime = object.mtime;
+ }
+}
+
+export default RepoInfo;
+
+//todo rename to repo, and rename repo to repoInfo
diff --git a/frontend/src/pages/group/group-repo-item.js b/frontend/src/pages/group/group-repo-item.js
deleted file mode 100644
index 8f81b4288f..0000000000
--- a/frontend/src/pages/group/group-repo-item.js
+++ /dev/null
@@ -1,270 +0,0 @@
-import React, { Component } from 'react';
-import { Dropdown, DropdownMenu, DropdownToggle, DropdownItem } from 'reactstrap';
-import moment from 'moment';
-import { seafileAPI } from '../../utils/seafile-api';
-import { Utils } from '../../utils/utils';
-import { gettext, siteRoot, loginUrl, isPro, folderPermEnabled, username } from '../../utils/constants';
-
-class GroupRepoItem extends Component {
-
- constructor(props) {
- super(props);
- this.state = {
- showOpIcon: false,
- operationMenuOpen: false,
- unshared: false
- };
-
- this.handleMouseOver = this.handleMouseOver.bind(this);
- this.handleMouseOut = this.handleMouseOut.bind(this);
- this.toggleOperationMenu = this.toggleOperationMenu.bind(this);
- this.clickOperationMenuToggle = this.clickOperationMenuToggle.bind(this);
-
- this.share = this.share.bind(this);
- this.unshare = this.unshare.bind(this);
-
- this.deleteItem = this.deleteItem.bind(this);
-
- this.rename = this.rename.bind(this);
- this.folderPerm = this.folderPerm.bind(this);
- this.showDetails = this.showDetails.bind(this);
- }
-
- handleMouseOver() {
- this.setState({
- showOpIcon: true
- });
- }
-
- handleMouseOut() {
- this.setState({
- showOpIcon: false
- });
- }
-
- toggleOperationMenu() {
- this.setState({
- operationMenuOpen: !this.state.operationMenuOpen
- });
- }
-
- clickOperationMenuToggle(e) {
- e.preventDefault();
- this.toggleOperationMenu();
- }
-
- share(e) {
- e.preventDefault();
- // TODO
- }
-
- unshare(e) {
- e.preventDefault();
- // TODO
-
- const data = this.props.data;
-
- let request;
- if (data.owner_email.indexOf('@seafile_group') == -1) {
- let options = {
- 'share_type': 'personal',
- 'from': data.owner_email
- };
- request = seafileAPI.leaveShareRepo(data.repo_id, options);
- } else {
- request = seafileAPI.leaveShareGroupOwnedRepo(data.repo_id);
- }
-
- request.then((res) => {
- this.setState({
- unshared: true
- });
- // TODO: show feedback msg
- }).catch((error) => {
- // TODO: show feedback msg
- });
- }
-
- deleteItem() {
- // TODO
- const data = this.props.data;
- seafileAPI.deleteRepo(data.repo_id).then((res) => {
- this.setState({
- deleted: true
- });
- // TODO: show feedback msg
- }).catch((error) => {
- // TODO: show feedback msg
- });
- }
-
- rename() {
- }
-
- folderPerm() {
- }
-
- showDetails() {
- }
-
- render() {
-
- if (this.state.unshared) {
- return null;
- }
-
- const data = this.props.data;
- const permission = data.permission;
-
- const {groupId, isStaff, showRepoOwner} = this.props.extra;
- const isRepoOwner = username == data.owner_email;
- const isAdmin = data.is_admin;
-
- let is_readonly = false;
- if (permission == 'r' || permission == 'preview') {
- is_readonly = true;
- }
- data.icon_url = Utils.getLibIconUrl({
- is_encrypted: data.encrypted,
- is_readonly: is_readonly,
- size: Utils.isHiDPI() ? 48 : 24
- });
- data.icon_title = Utils.getLibIconTitle({
- 'encrypted': data.encrypted,
- 'is_admin': data.is_admin,
- 'permission': permission
- });
- data.url = `${siteRoot}#group/${groupId}/lib/${data.repo_id}/`;
-
- let iconVisibility = this.state.showOpIcon ? '' : ' invisible';
- let shareIconClassName = 'sf2-icon-share sf2-x repo-share-btn op-icon' + iconVisibility;
- let unshareIconClassName = 'sf2-icon-x3 sf2-x op-icon' + iconVisibility;
- let deleteIconClassName = 'sf2-icon-delete sf2-x op-icon' + iconVisibility;
- let operationMenuToggleIconClassName = 'sf2-icon-caret-down item-operation-menu-toggle-icon op-icon';
- if (window.innerWidth >= 768) {
- operationMenuToggleIconClassName += iconVisibility;
- }
-
- const commonToggle = (
-
-
- );
-
- const commonOperationsInMenu = (
-
- {gettext('Rename')}
- {folderPermEnabled ? {gettext('Folder Permission')} : null}
- {gettext('Details')}
-
- );
-
- let desktopOperations;
- let mobileOperationMenu;
-
- const share = ;
- const unshare =
-
- const shareDropdownItem = {gettext('Share')} ;
- const unshareDropdownItem = {gettext('Unshare')} ;
-
- if (isPro) {
- if (data.owner_email.indexOf('@seafile_group') != -1) { // group owned repo
- if (isStaff) {
- if (data.owner_email == groupId + '@seafile_group') { // this repo belongs to the current group
- desktopOperations = (
-
- {share}
-
-
- {commonToggle}
-
- {commonOperationsInMenu}
-
-
-
- );
- mobileOperationMenu = (
-
- {shareDropdownItem}
- {gettext('Delete')}
- {commonOperationsInMenu}
-
- );
- } else {
- desktopOperations = unshare;
- mobileOperationMenu = unshareDropdownItem;
- }
- }
- } else {
- desktopOperations = (
-
- {isRepoOwner || isAdmin ? share : null}
- {isStaff || isRepoOwner || isAdmin ? unshare : null}
-
- );
- mobileOperationMenu = (
-
- {isRepoOwner || isAdmin ? shareDropdownItem : null}
- {isStaff || isRepoOwner || isAdmin ? unshareDropdownItem : null}
-
- );
- }
- } else {
- desktopOperations = (
-
- {isRepoOwner ? share : null}
- {isStaff || isRepoOwner ? unshare : null}
-
- );
- mobileOperationMenu = (
-
- {isRepoOwner ? shareDropdownItem : null}
- {isStaff || isRepoOwner ? unshareDropdownItem : null}
-
- );
- }
-
- const mobileOperations = (
-
- {commonToggle}
-
-
-
- {mobileOperationMenu}
-
-
-
- );
-
- const desktopItem = (
-
-
- {data.repo_name}
- {desktopOperations}
- {Utils.formatSize({bytes: data.size})}
- {moment(data.last_modified).fromNow()}
- {showRepoOwner ? {data.owner_name} : null}
-
- );
-
- const mobileItem = (
-
-
-
- {data.repo_name}
- {showRepoOwner ? {data.owner_name} : null}
- {Utils.formatSize({bytes: data.size})}
- {moment(data.last_modified).fromNow()}
-
- {mobileOperations}
-
- );
-
- return window.innerWidth >= 768 ? desktopItem : mobileItem;
- }
-}
-
-export default GroupRepoItem;
diff --git a/frontend/src/pages/group/group.js b/frontend/src/pages/group/group.js
deleted file mode 100644
index 74a78e15a1..0000000000
--- a/frontend/src/pages/group/group.js
+++ /dev/null
@@ -1,305 +0,0 @@
-import React, { Component, Fragment } from 'react';
-import { seafileAPI } from '../../utils/seafile-api';
-import { Utils } from '../../utils/utils';
-import { gettext, siteRoot, loginUrl, username } from '../../utils/constants';
-import Loading from '../../components/loading';
-import GroupRepoItem from './group-repo-item';
-import CommonToolbar from '../../components/toolbar/common-toolbar';
-import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
-
-import '../../css/groups.css';
-
-class Header extends Component {
-
- render() {
- const {loading, errorMsg, data} = this.props.data;
-
- if (loading) {
- return ;
- } else if (errorMsg) {
- return {errorMsg}
;
- } else {
- /*
- admins: ["lj@1.com"]
- avatar_url: "http://127.0.0.1:8000/media/avatars/groups/default.png"
- created_at: "2018-10-25T08:18:11+00:00"
- id: 2
- name: "g1"
- owner: "lj@1.com"
- parent_group_id: 0
- wiki_enabled: false
- */
- const path = (
-
- );
-
- let showSettingsIcon = true;
- if (data.parent_group_id != 0 && data.admins.indexOf(username) == -1) {
- showSettingsIcon = false;
- }
-
- // TODO: click icon
- const toolbar = (
-
- {showSettingsIcon ?
: null}
-
-
- );
-
- return (
-
- {path}
- {toolbar}
-
- );
- }
- }
-}
-
-class Content extends Component {
-
- render() {
- const {loading, errorMsg, items} = this.props.data.repos;
-
- if (loading) {
- return ;
- } else if (errorMsg) {
- return {errorMsg}
;
- } else {
- if (!items) {
- return null;
- }
-
- const groupInfo = this.props.data.groupMetaInfo.data;
-
- let emptyTip;
- if (groupInfo.parent_group_id == 0) {
- emptyTip = (
-
-
{gettext('No library is shared to this group')}
-
{gettext('You can share libraries by clicking the "New Library" button above or the "Share" icon on your libraries list.')}
-
{gettext('Libraries shared as writable can be downloaded and synced by other group members. Read only libraries can only be downloaded, updates by others will not be uploaded.')}
-
- );
- } else {
- if (groupInfo.admins.indexOf(username) == -1) {
- emptyTip = (
-
-
{gettext('No libraries')}
-
- );
- } else {
- emptyTip = (
-
-
{gettext('No libraries')}
-
{gettext('You can create libraries by clicking the "New Library" button above.')}
-
- );
- }
- }
-
- const desktopThead = (
-
-
- {gettext("Library Type")}
- {gettext("Name")}{/*TODO: sort*/}
- {gettext("Actions")}
- {gettext("Size")}
- {gettext("Last Update")}{/*TODO: sort*/}
- {gettext("Owner")}
-
-
- );
-
- const mobileThead = (
-
-
- {gettext("Library Type")}
-
- {gettext("Sort:")} {/* TODO: sort */}
- {gettext("name")}
- {gettext("last update")}
-
- {gettext("Actions")}
-
-
- );
-
- const extraData = {
- groupId: groupInfo.id,
- isStaff: groupInfo.admins.indexOf(username) != -1,
- showRepoOwner: true
- };
-
- const table = (
-
- {window.innerWidth >= 768 ? desktopThead : mobileThead}
-
-
- );
-
- return items.length ? table : emptyTip;
- }
- }
-}
-
-class TableBody extends Component {
-
- constructor(props) {
- super(props);
- this.state = {
- items: this.props.items
- };
- }
-
- render() {
-
- let listItems = this.state.items.map(function(item, index) {
- return ;
- }, this);
-
- return (
- {listItems}
- );
- }
-}
-
-class Group extends Component {
- constructor(props) {
- super(props);
- this.state = {
- groupMetaInfo: {
- loading: true,
- errorMsg: ''
- },
- repos: {
- loading: false,
- errorMsg: ''
- }
- };
- }
-
- componentDidMount() {
- this.updateGroupRepoList(this.props.groupID);
- }
-
- componentWillReceiveProps(nextProps) {
- if (nextProps.groupID !== this.props.groupID) {
- this.updateGroupRepoList(nextProps.groupID);
- }
- }
-
- updateGroupRepoList = (groupID) => {
- seafileAPI.getGroup(groupID).then((res) => {
- // res: {data: {...}, status: 200, statusText: "OK", headers: {…}, config: {…}, …}
- this.setState({
- groupMetaInfo: {
- loading: false,
- data: res.data
- }
- });
- this.listGroupRepos();
- }).catch((error) => {
- if (error.response) {
- if (error.response.status == 403) {
- this.setState({
- groupMetaInfo: {
- loading: false,
- errorMsg: gettext("Permission denied")
- }
- });
- location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
- } else {
- this.setState({
- groupMetaInfo: {
- loading: false,
- errorMsg: gettext("Error")
- }
- });
- }
- } else {
- this.setState({
- groupMetaInfo: {
- loading: false,
- errorMsg: gettext("Please check the network.")
- }
- });
- }
- });
- }
-
- listGroupRepos() {
- seafileAPI.listGroupRepos(this.props.groupID).then((res) => {
- // res: {data: [...], status: 200, statusText: "OK", headers: {…}, config: {…}, …}
- this.setState({
- repos: {
- loading: false,
- items: res.data
- }
- });
- }).catch((error) => {
- if (error.response) {
- if (error.response.status == 403) {
- this.setState({
- repos: {
- loading: false,
- errorMsg: gettext("Permission denied")
- }
- });
- location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
- } else {
- this.setState({
- repos: {
- loading: false,
- errorMsg: gettext("Error")
- }
- });
- }
- } else {
- this.setState({
- repos: {
- loading: false,
- errorMsg: gettext("Please check the network.")
- }
- });
- }
- });
- }
-
- onCreateRepo = (repo) => {
- let groupId = this.props.groupID;
- seafileAPI.createGroupRepo(groupId, repo).then(() => {
- //todo update group list
- });
- }
-
- render() {
- return (
-
-
-
-
-
-
-
- );
- }
-}
-
-export default Group;
diff --git a/frontend/src/pages/group/groups.js b/frontend/src/pages/group/groups.js
deleted file mode 100644
index e12873c1b2..0000000000
--- a/frontend/src/pages/group/groups.js
+++ /dev/null
@@ -1,178 +0,0 @@
-import React, { Component, Fragment } from 'react';
-import { seafileAPI } from '../../utils/seafile-api';
-import { gettext, siteRoot, loginUrl, username } from '../../utils/constants';
-import Loading from '../../components/loading';
-import GroupRepoItem from './group-repo-item';
-import GeneralToolbar from '../../components/toolbar/general-toolbar';
-
-import '../../css/groups.css';
-
-class Content extends Component {
-
- render() {
- const {loading, errorMsg, items} = this.props.data;
-
- if (loading) {
- return ;
- } else if (errorMsg) {
- return {errorMsg}
;
- } else {
- /* TODO:
- {% if user.permissions.can_add_group %}
- {% blocktrans %}Groups allow multiple people to collaborate on libraries. You can create a group by clicking the "New Group" button.{% endblocktrans %}
- {% else %}
- {% trans "Groups allow multiple people to collaborate on libraries. Groups you join will be listed here." %}
- {% endif %}
- */
- const emptyTip = (
-
-
{gettext('You are not in any groups')}
-
{gettext('Groups allow multiple people to collaborate on libraries.')}
-
- );
-
- let listItems = items.map(function(item, index) {
- return ;
- }, this);
-
- const groupItems = (
-
- {listItems}
-
- );
-
- return items.length ? groupItems : emptyTip;
- }
- }
-}
-
-class GroupItem extends Component {
-
- render() {
-
- const data = this.props.data;
-
- const desktopThead = (
-
-
- {gettext("Library Type")}
- {gettext("Name")}
- {gettext("Actions")}
- {gettext("Size")}
- {gettext("Last Update")}
-
-
- );
-
- const mobileThead = (
-
-
- {gettext("Library Type")}
- {gettext("name")}
- {gettext("Actions")}
-
-
- );
-
- const extraData = {
- groupId: data.id,
- isStaff: data.admins.indexOf(username) != -1,
- showRepoOwner: false
- };
-
- const table = (
-
- {window.innerWidth >= 768 ? desktopThead : mobileThead}
-
-
- );
-
- const emptyTip = {gettext('No libraries')}
;
-
- const item = (
-
-
- {data.repos.length ? table : emptyTip}
-
- );
-
- return item;
- }
-}
-
-class TableBody extends Component {
-
- render() {
-
- let listItems = this.props.items.map(function(item, index) {
- return ;
- }, this);
-
- return (
- {listItems}
- );
- }
-}
-
-class Groups extends Component {
- constructor(props) {
- super(props);
- this.state = {
- loading: true,
- errorMsg: '',
- items: []
- };
- }
-
- componentDidMount() {
- seafileAPI.listGroupsV2({'with_repos': 1}).then((res) => { // TODO: api name
- // `{'with_repos': 1}`: list repos of every group
- // res: {data: [...], status: 200, statusText: "OK", headers: {…}, config: {…}, …}
- this.setState({
- loading: false,
- items: res.data
- });
- }).catch((error) => {
- if (error.response) {
- if (error.response.status == 403) {
- this.setState({
- loading: false,
- errorMsg: gettext("Permission denied")
- });
- location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
- } else {
- this.setState({
- loading: false,
- errorMsg: gettext("Error")
- });
- }
-
- } else {
- this.setState({
- loading: false,
- errorMsg: gettext("Please check the network.")
- });
- }
- });
- }
-
- render() {
- return (
-
-
-
-
-
-
{gettext("My Groups")}
-
-
-
-
-
-
-
- );
- }
-}
-
-export default Groups;
diff --git a/frontend/src/pages/groups/group-view.js b/frontend/src/pages/groups/group-view.js
new file mode 100644
index 0000000000..c35f78b818
--- /dev/null
+++ b/frontend/src/pages/groups/group-view.js
@@ -0,0 +1,259 @@
+import React,{ Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { gettext, siteRoot, username, loginUrl } from '../../utils/constants';
+import { seafileAPI } from '../../utils/seafile-api';
+import Loading from '../../components/loading';
+import ModalPortal from '../../components/modal-portal';
+import Group from '../../models/group';
+import RepoInfo from '../../models/repoInfo';
+import CommonToolbar from '../../components/toolbar/common-toolbar';
+import CreateRepoDialog from '../../components/dialog/create-repo-dialog';
+import CreateDepartmentRepoDialog from '../../components/dialog/create-department-repo-dialog';
+import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
+
+const propTypes = {
+ onShowSidePanel: PropTypes.func.isRequired,
+ onSearchedClick: PropTypes.func.isRequired,
+};
+
+const DEPARETMENT_GROUP_ONWER_NAME = 'system admin';
+
+class GroupView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isLoading: true,
+ errMessage: '',
+ emptyTip: null,
+ currentGroup: null,
+ isStaff: false,
+ repoList: [],
+ libraryType: 'group',
+ isCreateRepoDialogShow: false,
+ isDepartmentGroup: false,
+ }
+ }
+
+ componentDidMount() {
+ let groupID = this.props.groupID;
+ this.loadGroup(groupID);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ if (nextProps.groupID !== this.props.groupID) {
+ this.loadGroup(nextProps.groupID);
+ }
+ }
+
+ loadGroup = (groupID) => {
+ seafileAPI.getGroup(groupID).then((res) => {
+ let currentGroup = new Group(res.data);
+ let emptyTip = this.getEmptyTip(currentGroup);
+ let isStaff = currentGroup.admins.indexOf(username) > -1; //for item operations
+ let isDepartmentGroup = currentGroup.owner === DEPARETMENT_GROUP_ONWER_NAME;
+ this.setState({
+ emptyTip: emptyTip,
+ currentGroup: currentGroup,
+ isStaff: isStaff,
+ isDepartmentGroup: isDepartmentGroup,
+ });
+ this.loadRepos(groupID);
+ }).catch((error) => {
+ if (error.response) {
+ if (error.response.status == 403) {
+ this.setState({
+ isLoading: false,
+ errMessage: gettext("Permission denied")
+ });
+ location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
+ } else {
+ this.setState({
+ isLoading: false,
+ errMessage: gettext("Error")
+ });
+ }
+ } else {
+ this.setState({
+ isLoading: false,
+ errMessage: gettext("Please check the network.")
+ });
+ }
+ });
+ }
+
+ loadRepos = (groupID) => {
+ this.setState({isLoading: true});
+ seafileAPI.listGroupRepos(groupID).then((res) => {
+ let repoList = res.data.map(item => {
+ let repoInfo = new RepoInfo(item);
+ return repoInfo;
+ });
+ this.setState({
+ isLoading: false,
+ repoList: repoList
+ });
+ }).catch((error) => {
+ if (error.response) {
+ if (error.response.status == 403) {
+ this.setState({
+ isLoading: false,
+ errMessage: gettext("Permission denied")
+ });
+ location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
+ } else {
+ this.setState({
+ isLoading: false,
+ errMessage: gettext("Error")
+ });
+ }
+ } else {
+ this.setState({
+ isLoading: false,
+ errMessage: gettext("Please check the network.")
+ });
+ }
+ });
+ }
+
+ getEmptyTip = (currentGroup) => {
+ let emptyTip = null;
+ if (currentGroup) {
+ if (currentGroup.parent_group_id === 0) {
+ emptyTip = (
+
+
{gettext('No library is shared to this group')}
+
{gettext('You can share libraries by clicking the "New Library" button above or the "Share" icon on your libraries list.')}
+
{gettext('Libraries shared as writable can be downloaded and synced by other group members. Read only libraries can only be downloaded, updates by others will not be uploaded.')}
+
+ );
+ } else {
+ if (currentGroup.admins.indexOf(username) == -1) { // is a member of this group
+ emptyTip = (
+
+
{gettext('No libraries')}
+
+ );
+ } else {
+ emptyTip = (
+
+
{gettext('No libraries')}
+
{gettext('You can create libraries by clicking the "New Library" button above.')}
+
+ );
+ }
+ }
+ }
+ return emptyTip;
+ }
+
+ onCreateRepoToggle = () => {
+ this.setState({isCreateRepoDialogShow: !this.state.isCreateRepoDialogShow});
+ }
+
+ onCreateRepo = (repo) => {
+ let groupId = this.props.groupID;
+ seafileAPI.createGroupRepo(groupId, repo).then(res => {
+ let repo = new RepoInfo(res.data);
+ let repoList = this.addRepoItem(repo);
+ this.setState({repoList: repoList});
+ }).catch(() => {
+ //todo
+ });
+ this.onCreateRepoToggle();
+ }
+
+ addRepoItem = (repo) => {
+ let newRepoList = this.state.repoList.map(item => {return item;});
+ newRepoList.push(repo);
+ return newRepoList;
+ }
+
+ onItemUnshare = (repo) => {
+ let group = this.state.currentGroup;
+ seafileAPI.unshareRepo(repo.repo_id, {share_type: 'group', group_id: group.id}).then(() => {
+ let repoList = this.state.repoList.filter(item => {
+ return item.repo_id !== repo.repo_id;
+ });
+ this.setState({repoList: repoList});
+ });
+ }
+
+ render() {
+ let { errMessage, emptyTip, currentGroup } = this.state;
+ let isShowSettingIcon = !(currentGroup && currentGroup.parent_group_id !== 0 && currentGroup.admins.indexOf(username) === -1);
+ return (
+
+
+
+
+
+
+
+ {gettext('New Library')}
+
+
+
+
+
+
+
+
+ {currentGroup && (
+
+
+
{gettext("Groups")}
+
/
+
{currentGroup.name}
+ {currentGroup.parent_group_id !== 0 && (
+
+ )}
+
+
+ {isShowSettingIcon && (
+
+ )}
+
+
+
+ )}
+
+
+ {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.isCreateRepoDialogShow && !this.state.isDepartmentGroup && (
+
+
+
+ )}
+ {this.state.isCreateRepoDialogShow && this.state.isDepartmentGroup &&
+
+ }
+
+ );
+ }
+}
+
+GroupView.propTypes = propTypes;
+
+export default GroupView;
diff --git a/frontend/src/pages/groups/groups-view.js b/frontend/src/pages/groups/groups-view.js
new file mode 100644
index 0000000000..cd15a23cf4
--- /dev/null
+++ b/frontend/src/pages/groups/groups-view.js
@@ -0,0 +1,145 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import { gettext, siteRoot, loginUrl } from '../../utils/constants';
+import { seafileAPI } from '../../utils/seafile-api';
+import Loading from '../../components/loading';
+import Group from '../../models/group';
+import RepoInfo from '../../models/repoInfo';
+import GeneralToolbar from '../../components/toolbar/general-toolbar';
+import SharedRepoListView from '../../components/shared-repo-list-view/shared-repo-list-view';
+
+import '../../css/groups.css';
+
+const propTypes = {
+ group: PropTypes.object.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 RepoInfo(item);
+ return repo;
+ });
+ this.setState({repoList: repoList});
+ }
+
+ onItemUnshare = (repo) => {
+ let group = this.props.group;
+ seafileAPI.unshareRepo(repo.repo_id, {share_type: 'group', group_id: group.id}).then(() => {
+ let repoList = this.state.repoList.filter(item => {
+ return item.repo_id !== repo.repo_id;
+ });
+ this.setState({repoList: repoList});
+ });
+ }
+
+ render() {
+ let group = this.props.group;
+ const emptyTip = {gettext('No libraries')}
;
+ return (
+
+
+ {this.state.repoList.length === 0 ?
+ emptyTip :
+
+ }
+
+ );
+ }
+ }
+
+RepoListViewPanel.propTypes = propTypes;
+
+class GroupsView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isLoading: true,
+ errorMsg: '',
+ groupList: [],
+ }
+ }
+
+ componentDidMount() {
+ seafileAPI.listGroupsV2({'with_repos': 1}).then((res) => { // TODO: api name
+ // `{'with_repos': 1}`: list repos of every group
+ // res: {data: [...], status: 200, statusText: "OK", headers: {…}, config: {…}, …}
+ let groupList = res.data.map(item => {
+ let group = new Group(item);
+ return group;
+ })
+ this.setState({
+ isLoading: false,
+ groupList: groupList,
+ });
+ }).catch((error) => {
+ if (error.response) {
+ if (error.response.status == 403) {
+ this.setState({
+ isLoading: false,
+ errorMsg: gettext("Permission denied")
+ });
+ location.href = `${loginUrl}?next=${encodeURIComponent(location.href)}`;
+ } else {
+ this.setState({
+ isLoading: false,
+ errorMsg: gettext("Error")
+ });
+ }
+ } else {
+ this.setState({
+ isLoading: false,
+ errorMsg: gettext("Please check the network.")
+ });
+ }
+ });
+ }
+
+ render() {
+ return (
+
+
+
+
+
+
{gettext("My Groups")}
+
+
+ {this.state.isLoading && }
+ {(!this.state.isLoading && this.state.errorMsg !== '') && this.state.errorMsg}
+ {/* {(!this.state.isLoading && this.state.groupList.length === 0 ) && emptyTip} //todo */}
+ {!this.state.isLoading && this.state.groupList.map((group, index) => {
+ return (
+
+ );
+ })}
+
+
+
+
+ );
+ }
+}
+
+// Groups.propTypes = propTypes;
+
+export default GroupsView;
diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.js
index e3c808ed31..e7acdb2347 100644
--- a/frontend/src/utils/constants.js
+++ b/frontend/src/utils/constants.js
@@ -27,6 +27,7 @@ export const storages = window.app.pageOptions.storages; // storage backends
export const enableRepoSnapshotLabel = window.app.pageOptions.enableRepoSnapshotLabel;
export const shareLinkExpireDaysMin = window.app.pageOptions.shareLinkExpireDaysMin;
export const shareLinkExpireDaysMax = window.app.pageOptions.shareLinkExpireDaysMax;
+export const maxFileName = window.app.pageOptions.maxFileName;
// wiki
export const slug = window.wiki ? window.wiki.config.slug : '';
diff --git a/media/css/seahub_react.css b/media/css/seahub_react.css
index 576d39cd51..90029ba071 100644
--- a/media/css/seahub_react.css
+++ b/media/css/seahub_react.css
@@ -80,6 +80,7 @@
.sf2-icon-move:before {content:"\e029"}
.sf2-icon-menu:before { content: "\e031"; }
.sf2-icon-more:before { content: "\e032"; }
+.sf2-icon-x3:before {content:"\e035";}
.sf2-icon-close:before { content:"\e035"; }
.sf2-icon-two-columns:before { content:"\e036"; }
.sf2-icon-tag:before {content:"\e037"}
diff --git a/seahub/api2/endpoints/groups.py b/seahub/api2/endpoints/groups.py
index 950031b357..9588835982 100644
--- a/seahub/api2/endpoints/groups.py
+++ b/seahub/api2/endpoints/groups.py
@@ -153,7 +153,7 @@ class Groups(APIView):
contact_email_dict[email] = email2contact_email(email)
for r in group_repos:
- repo_owner = repo_id_owner_dict.get(r.id, r.user),
+ repo_owner = repo_id_owner_dict.get(r.id, r.user)
repo = {
"id": r.id,
"repo_id": r.id,
diff --git a/seahub/templates/base_for_react.html b/seahub/templates/base_for_react.html
index 7c52d81dd6..3aeb0fb049 100644
--- a/seahub/templates/base_for_react.html
+++ b/seahub/templates/base_for_react.html
@@ -61,6 +61,7 @@
enableRepoSnapshotLabel: {% if enable_repo_snapshot_label %} true {% else %} false {% endif %},
shareLinkExpireDaysMin: "{{ share_link_expire_days_min }}",
shareLinkExpireDaysMax: "{{ share_link_expire_days_max }}",
+ maxFileName: "{{ max_file_name }}"
}
};