diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 5791439b14..43684f9ae9 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -9964,7 +9964,7 @@
},
"react-popper": {
"version": "0.8.3",
- "resolved": "http://registry.npmjs.org/react-popper/-/react-popper-0.8.3.tgz",
+ "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.8.3.tgz",
"integrity": "sha1-D3MzMTfJ+wr27EB00tBYWgoEYeE=",
"requires": {
"popper.js": "^1.12.9",
@@ -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 a4873d9d51..cab7d02055 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -28,7 +28,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/components/cur-group-path/group-path.js b/frontend/src/components/cur-group-path/group-path.js
new file mode 100644
index 0000000000..2a0ac7f4ad
--- /dev/null
+++ b/frontend/src/components/cur-group-path/group-path.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import {gettext, siteRoot} from '../../utils/constants';
+
+const propTypes = {
+ currentGroup: PropTypes.object.isRequired
+};
+
+class GroupPath extends React.Component {
+
+ render() {
+ let currentGroup = this.props.currentGroup;
+ return (
+
+
{gettext("Groups")}
+
/
+
{currentGroup.name}
+ {currentGroup.parent_group_id !== 0 && (
+
+ )}
+
+ );
+ }
+}
+
+GroupPath.propTypes = propTypes;
+
+export default GroupPath;
diff --git a/frontend/src/components/cur-group-path/group-tool.js b/frontend/src/components/cur-group-path/group-tool.js
new file mode 100644
index 0000000000..edf997083d
--- /dev/null
+++ b/frontend/src/components/cur-group-path/group-tool.js
@@ -0,0 +1,27 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { gettext, username } from '../../utils/constants';
+
+const propTypes = {
+ currentGroup: PropTypes.object.isRequired,
+};
+
+class GroupTool extends React.Component {
+
+ render() {
+ let currentGroup = this.props.currentGroup;
+ let isShowSettingIcon = !(currentGroup.parent_group_id !== 0 && currentGroup.admins.indexOf(username) === -1);
+ return (
+
+ {isShowSettingIcon && (
+
+ )}
+
+
+ );
+ }
+}
+
+GroupTool.propTypes = propTypes;
+
+export default GroupTool;
diff --git a/frontend/src/components/cur-group-path/index.js b/frontend/src/components/cur-group-path/index.js
new file mode 100644
index 0000000000..c56adf8c93
--- /dev/null
+++ b/frontend/src/components/cur-group-path/index.js
@@ -0,0 +1,24 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import GroupPath from './group-path';
+import GroupTool from './group-tool';
+
+const propTypes = {
+ currentGroup: PropTypes.object.isRequired,
+};
+
+class CurGroupPath extends React.Component {
+
+ render() {
+ return (
+
+
+
+
+ );
+ }
+}
+
+CurGroupPath.propTypes = propTypes;
+
+export default CurGroupPath;
diff --git a/frontend/src/components/repo-list-view/repo-list-item.js b/frontend/src/components/repo-list-view/repo-list-item.js
new file mode 100644
index 0000000000..0c7251342f
--- /dev/null
+++ b/frontend/src/components/repo-list-view/repo-list-item.js
@@ -0,0 +1,104 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import moment from 'moment';
+import { Utils } from '../../utils/utils';
+import { siteRoot, } from '../../utils/constants';
+
+const propTypes = {
+ repo: PropTypes.object.isRequired,
+ isItemFreezed: PropTypes.bool.isRequired,
+ isShowRepoOwner: PropTypes.bool.isRequired,
+ isStuff: PropTypes.bool.isRequired,
+};
+
+class RepoListItem extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ highlight: false,
+ isOperationShow: false,
+ isItemMenuShow: false,
+ };
+ }
+
+ onMouseEnter = () => {
+ this.setState({highlight: true});
+ }
+
+ onMouseLeave = () => {
+ this.setState({highlight: false});
+ }
+
+ 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 };
+ }
+
+ generatorMenu = () => {
+ //todo
+ }
+
+ renderPCUI = () => {
+ let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
+ let { repo, isShowRepoOwner } = this.props;
+ return (
+
+  |
+ {repo.repo_name} |
+ {} |
+ {repo.size} |
+ {moment(repo.last_modified).fromNow()} |
+ {isShowRepoOwner && {repo.owner_name} | }
+
+ );
+ }
+
+ renderMobileUI = () => {
+ let { iconUrl, iconTitle, libPath } = this.getRepoComputeParams();
+ let { repo, isShowRepoOwner } = this.props;
+ return (
+
+  |
+
+ {repo.repo_name}
+ {isShowRepoOwner ? {repo.owner_name} : null}
+ {repo.size}
+ {moment(repo.last_modified).fromNow()}
+ |
+ {} |
+
+ );
+ }
+
+ render() {
+ if (window.innerWidth >= 768) {
+ return this.renderPCUI();
+ } else {
+ return this.renderMobileUI();
+ }
+ }
+}
+
+RepoListItem.propTypes = propTypes;
+
+export default RepoListItem;
diff --git a/frontend/src/components/repo-list-view/repo-list-view.js b/frontend/src/components/repo-list-view/repo-list-view.js
new file mode 100644
index 0000000000..344b19a2fd
--- /dev/null
+++ b/frontend/src/components/repo-list-view/repo-list-view.js
@@ -0,0 +1,96 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { gettext } from '../../utils/constants';
+import RepoListItem from './repo-list-item';
+
+const propTypes = {
+ isShowRepoOwner: PropTypes.bool.isRequired,
+ repoList: PropTypes.array.isRequired,
+};
+
+class RepoListView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isItemFreezed: false,
+ };
+ }
+
+ renderPCUI = () => {
+ return (
+
+
+
+ {gettext("Library Type")} |
+ {gettext("Name")}
+ {/*TODO: sort*/}
+ |
+ {gettext("Actions")} |
+ {gettext("Size")} |
+ {gettext("Last Update")}
+ {/*TODO: sort*/}
+ |
+ {gettext("Owner")} |
+
+
+
+ {this.props.repoList.map(repo => {
+ return (
+
+ );
+ })}
+
+
+ );
+ }
+
+ renderMobileUI = () => {
+ return (
+
+
+
+ {gettext("Library Type")} |
+
+ {gettext("Sort:")} {/* TODO: sort */}
+ {gettext("name")}
+ {gettext("last update")}
+ |
+ {gettext("Actions")} |
+
+
+
+ {this.props.repoList.map(repo => {
+ return (
+
+ );
+ })}
+
+
+ );
+ }
+
+ render() {
+ if (window.innerWidth >= 768) {
+ return this.renderPCUI();
+ } else {
+ return this.renderMobileUI();
+ }
+ }
+}
+
+RepoListView.propTypes = propTypes;
+
+export default RepoListView;
diff --git a/frontend/src/models/group.js b/frontend/src/models/group.js
new file mode 100644
index 0000000000..ff897b9ed7
--- /dev/null
+++ b/frontend/src/models/group.js
@@ -0,0 +1,14 @@
+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;
+ }
+}
+
+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/groups/group-view.js b/frontend/src/pages/groups/group-view.js
new file mode 100644
index 0000000000..a7da002cd7
--- /dev/null
+++ b/frontend/src/pages/groups/group-view.js
@@ -0,0 +1,175 @@
+import React, { Fragment } from 'react';
+import PropTypes from 'prop-types';
+import Loading from '../../components/loading';
+import { gettext, username, loginUrl } from '../../utils/constants';
+import { seafileAPI } from '../../utils/seafile-api';
+import Group from '../../models/group';
+import RepoInfo from '../../models/repoInfo';
+import CommonToolbar from '../../components/toolbar/common-toolbar';
+import RepoViewToolbar from '../../components/toolbar/repo-view-toobar';
+import CurGroupPath from '../../components/cur-group-path/';
+import RepoListView from '../../components/repo-list-view/repo-list-view';
+
+const propTypes = {
+
+};
+
+class GroupView extends React.Component {
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ isLoading: true,
+ errMessage: '',
+ currentGroup: null,
+ repoList: [],
+ }
+ }
+
+ 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);
+ this.setState({currentGroup: currentGroup});
+ this.loadRepos(groupID);
+ }).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.")
+ });
+ }
+ });
+ }
+
+ 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,
+ 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.")
+ });
+ }
+ });
+ }
+
+ onCreateRepo = (repo) => {
+ let groupId = this.props.groupID;
+ seafileAPI.createGroupRepo(groupId, repo).then(() => {
+ //todo update group list
+ });
+ }
+
+ getEmptyTip = () => {
+ let currentGroup = this.state.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) {
+ emptyTip = (
+
+
{gettext('No libraries')}
+
+ );
+ } else {
+ emptyTip = (
+
+
{gettext('No libraries')}
+
{gettext('You can create libraries by clicking the "New Library" button above.')}
+
+ );
+ }
+ }
+ }
+ return emptyTip;
+ }
+
+ render() {
+ let emptyTip = this.getEmptyTip();
+ return (
+
+
+
+
+
+
+
+
+ {this.state.currentGroup && }
+
+
+ {this.state.isLoading && }
+ {(!this.state.isLoading && this.state.errMessage) && this.state.errMessage}
+ {(!this.state.isLoading && this.state.repoList.length === 0) && emptyTip}
+ {(!this.state.isLoading && this.state.repoList.length > 0) &&
+
+ }
+
+
+
+
+ );
+ }
+}
+
+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..67927bdaa0
--- /dev/null
+++ b/frontend/src/pages/groups/groups-view.js
@@ -0,0 +1,19 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const propTypes = {
+
+};
+
+class Groups extends React.Component {
+
+ render() {
+ return (
+ {placehodler}
+ );
+ }
+}
+
+Groups.propTypes = propTypes;
+
+export default Groups;