From f99c540b3daeadfc3a38a964d518b28ac81f1097 Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 29 Jun 2017 17:03:05 +0100 Subject: [PATCH] Groups page / Create Group dialog --- src/components/structures/MyGroups.js | 112 +++++++++++ .../views/dialogs/CreateGroupDialog.js | 190 ++++++++++++++++++ 2 files changed, 302 insertions(+) create mode 100644 src/components/structures/MyGroups.js create mode 100644 src/components/views/dialogs/CreateGroupDialog.js diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js new file mode 100644 index 0000000000..a3459a16c7 --- /dev/null +++ b/src/components/structures/MyGroups.js @@ -0,0 +1,112 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import sdk from '../../index'; +import { _t } from '../../languageHandler'; +import WithMatrixClient from '../../wrappers/WithMatrixClient'; +import AccessibleButton from '../views/elements/AccessibleButton'; +import dis from '../../dispatcher'; +import PropTypes from 'prop-types'; +import Modal from '../../Modal'; + +const GroupTile = React.createClass({ + displayName: 'GroupTile', + + propTypes: { + groupId: PropTypes.string.isRequired, + }, + + onClick: function(e) { + e.preventDefault(); + dis.dispatch({ + action: 'view_group', + group_id: this.props.groupId, + }); + }, + + render: function() { + return {this.props.groupId}; + } +}); + +module.exports = WithMatrixClient(React.createClass({ + displayName: 'GroupList', + + propTypes: { + matrixClient: React.PropTypes.object.isRequired, + }, + + getInitialState: function() { + return { + groups: null, + error: null, + }; + }, + + componentWillMount: function() { + this._fetch(); + }, + + componentWillUnmount: function() { + }, + + _onCreateGroupClick: function() { + const CreateGroupDialog = sdk.getComponent("dialogs.CreateGroupDialog"); + Modal.createDialog(CreateGroupDialog); + }, + + _fetch: function() { + this.props.matrixClient.getJoinedGroups().done((result) => { + this.setState({groups: result.groups, error: null}); + }, (err) => { + this.setState({result: null, error: err}); + }); + }, + + render: function() { + const Loader = sdk.getComponent("elements.Spinner"); + const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader'); + + let content; + if (this.state.groups) { + let groupNodes = []; + this.state.groups.forEach((g) => { + groupNodes.push( +
+ +
+ ); + }); + content =
{groupNodes}
; + } else if (this.state.error) { + content =
+ Error whilst fetching joined groups +
; + } + + return
+ +
+ + {_t('Create a new group')} + +
+ You are a member of these groups: + {content} +
; + }, +})); diff --git a/src/components/views/dialogs/CreateGroupDialog.js b/src/components/views/dialogs/CreateGroupDialog.js new file mode 100644 index 0000000000..64984ebf5c --- /dev/null +++ b/src/components/views/dialogs/CreateGroupDialog.js @@ -0,0 +1,190 @@ +/* +Copyright 2017 Vector Creations Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import sdk from '../../../index'; +import dis from '../../../dispatcher'; +import { _t } from '../../../languageHandler'; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import AccessibleButton from '../elements/AccessibleButton'; + +// We match fairly liberally and leave it up to the server to reject if +// there are invalid characters etc. +const GROUP_REGEX = /^\+(.*?):(.*)$/; + +export default React.createClass({ + displayName: 'CreateGroupDialog', + propTypes: { + onFinished: React.PropTypes.func.isRequired, + }, + + getInitialState: function() { + return { + groupName: '', + groupId: '', + groupError: null, + creating: false, + createError: null, + }; + }, + + _onGroupNameChange: function(e) { + this.setState({ + groupName: e.target.value, + }); + }, + + _onGroupIdChange: function(e) { + this.setState({ + groupId: e.target.value, + }); + }, + + _onGroupIdBlur: function(e) { + this._checkGroupId(); + }, + + _checkGroupId: function(e) { + const parsedGroupId = this._parseGroupId(this.state.groupId); + let error = null; + if (parsedGroupId === null) { + error = _t("Group IDs must be of the form +localpart:%(domain)s", {domain: MatrixClientPeg.get().getDomain()}); + } else { + const localpart = parsedGroupId[0]; + const domain = parsedGroupId[1]; + if (domain !== MatrixClientPeg.get().getDomain()) { + error = _t( + "It is currently only possible to create groups on your own home server: use a group ID ending with %(domain)s", + {domain: MatrixClientPeg.get().getDomain()} + ); + } + } + this.setState({ + groupIdError: error, + }); + return error; + }, + + _onFormSubmit: function(e) { + e.preventDefault(); + + if (this._checkGroupId()) return; + + const parsedGroupId = this._parseGroupId(this.state.groupId); + const profile = {}; + if (this.state.groupName !== '') { + profile.name = this.state.groupName; + } + this.setState({creating: true}); + MatrixClientPeg.get().createGroup({ + localpart: parsedGroupId[0], + profile: profile, + }).then((result) => { + dis.dispatch({ + action: 'view_group', + group_id: result.group_id, + }); + this.props.onFinished(true); + }).catch((e) => { + this.setState({createError: e}); + }).finally(() => { + this.setState({creating: false}); + }).done(); + }, + + _onCancel: function() { + this.props.onFinished(false); + }, + + /** + * Parse a string that may be a group ID + * If the string is a valid group ID, return a list of [localpart, domain], + * otherwise return null. + */ + _parseGroupId: function(groupId) { + const matches = GROUP_REGEX.exec(this.state.groupId); + if (!matches || matches.length < 3) { + return null; + } + return [matches[1], matches[2]]; + }, + + render: function() { + const BaseDialog = sdk.getComponent('views.dialogs.BaseDialog'); + const Loader = sdk.getComponent("elements.Spinner"); + + if (this.state.creating) { + return ; + } + + let createErrorNode; + if (this.state.createError) { + // XXX: We should catch errcodes and give sensible i18ned messages for them, + // rather than displaying what the server gives us, but synapse doesn't give + // any yet. + createErrorNode =
+
{_t('Room creation failed')}
+
{this.state.createError.message}
+
; + } + + return ( + +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+ {this.state.groupIdError} +
+ {createErrorNode} +
+
+ + +
+
+
+ ); + }, +});