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 ( + + {iconTitle} + {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 ( + + {iconTitle}/ + + {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 ( + + + + + + + + + + + + + {this.props.repoList.map(repo => { + return ( + + ); + })} + +
{gettext("Library Type")}{gettext("Name")} + {/*TODO: sort*/} + {gettext("Actions")}{gettext("Size")}{gettext("Last Update")} + {/*TODO: sort*/} + {gettext("Owner")}
+ ); + } + + renderMobileUI = () => { + return ( + + + + + + + + + + {this.props.repoList.map(repo => { + return ( + + ); + })} + +
{gettext("Library Type")} + {gettext("Sort:")} {/* TODO: sort */} + {gettext("name")} + {gettext("last update")} + {gettext("Actions")}
+ ); + } + + 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;