From 9ed9422af84dc3095c3064820bc01b2b29422778 Mon Sep 17 00:00:00 2001
From: lukebarnard <luke.barnard99@gmail.com>
Date: Tue, 21 Nov 2017 11:50:41 +0000
Subject: [PATCH] Move group publication toggles to UserSettings

---
 src/components/structures/GroupView.js        | 32 -------
 src/components/structures/MyGroups.js         | 58 +-----------
 src/components/structures/UserSettings.js     |  7 ++
 .../views/groups/GroupPublicityToggle.js      | 91 +++++++++++++++++++
 src/components/views/groups/GroupTile.js      | 91 +++++++++++++++++++
 .../views/groups/GroupUserSettings.js         | 66 ++++++++++++++
 src/i18n/strings/en_EN.json                   | 10 +-
 7 files changed, 262 insertions(+), 93 deletions(-)
 create mode 100644 src/components/views/groups/GroupPublicityToggle.js
 create mode 100644 src/components/views/groups/GroupTile.js
 create mode 100644 src/components/views/groups/GroupUserSettings.js

diff --git a/src/components/structures/GroupView.js b/src/components/structures/GroupView.js
index b137893bde..45befdd60f 100644
--- a/src/components/structures/GroupView.js
+++ b/src/components/structures/GroupView.js
@@ -672,18 +672,6 @@ export default React.createClass({
         showGroupAddRoomDialog(this.props.groupId);
     },
 
-    _onPublicityToggle: function() {
-        this.setState({
-            publicityBusy: true,
-        });
-        const publicity = !this.state.isGroupPublicised;
-        this._groupStore.setGroupPublicity(publicity).then(() => {
-            this.setState({
-                publicityBusy: false,
-            });
-        });
-    },
-
     _getGroupSection: function() {
         const groupSettingsSectionClasses = classnames({
             "mx_GroupView_group": this.state.editing,
@@ -903,25 +891,6 @@ export default React.createClass({
         return null;
     },
 
-    _getMemberSettingsSection: function() {
-        return <div className="mx_GroupView_memberSettings">
-            <h2> { _t("Community Member Settings") } </h2>
-            <div className="mx_GroupView_memberSettings_toggle">
-                <input type="checkbox"
-                    onClick={this._onPublicityToggle}
-                    checked={this.state.isGroupPublicised}
-                    tabIndex="3"
-                    id="isGroupPublicised"
-                />
-                <label htmlFor="isGroupPublicised"
-                    onClick={this._onPublicityToggle}
-                >
-                    { _t("Publish this community on your profile") }
-                </label>
-            </div>
-        </div>;
-    },
-
     _getLongDescriptionNode: function() {
         const summary = this.state.summary;
         let description = null;
@@ -976,7 +945,6 @@ export default React.createClass({
             let shortDescNode;
             const bodyNodes = [
                 this._getMembershipSection(),
-                this.state.editing ? this._getMemberSettingsSection() : null,
                 this._getGroupSection(),
             ];
             const rightButtons = [];
diff --git a/src/components/structures/MyGroups.js b/src/components/structures/MyGroups.js
index c669d7dd73..2e8bae52c3 100644
--- a/src/components/structures/MyGroups.js
+++ b/src/components/structures/MyGroups.js
@@ -15,70 +15,13 @@ limitations under the License.
 */
 
 import React from 'react';
-import PropTypes from 'prop-types';
 import GeminiScrollbar from 'react-gemini-scrollbar';
-import {MatrixClient} from 'matrix-js-sdk';
 import sdk from '../../index';
 import { _t } from '../../languageHandler';
 import withMatrixClient from '../../wrappers/withMatrixClient';
 import AccessibleButton from '../views/elements/AccessibleButton';
-import dis from '../../dispatcher';
 import Modal from '../../Modal';
 
-import FlairStore from '../../stores/FlairStore';
-
-const GroupTile = React.createClass({
-    displayName: 'GroupTile',
-
-    propTypes: {
-        groupId: PropTypes.string.isRequired,
-    },
-
-    contextTypes: {
-        matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
-    },
-
-    getInitialState() {
-        return {
-            profile: null,
-        };
-    },
-
-    componentWillMount: function() {
-        FlairStore.getGroupProfileCached(this.context.matrixClient, this.props.groupId).then((profile) => {
-            this.setState({profile});
-        });
-    },
-
-    onClick: function(e) {
-        e.preventDefault();
-        dis.dispatch({
-            action: 'view_group',
-            group_id: this.props.groupId,
-        });
-    },
-
-    render: function() {
-        const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
-        const profile = this.state.profile || {};
-        const name = profile.name || this.props.groupId;
-        const desc = profile.shortDescription;
-        const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
-            profile.avatarUrl, 50, 50, "crop",
-        ) : null;
-        return <AccessibleButton className="mx_GroupTile" onClick={this.onClick}>
-            <div className="mx_GroupTile_avatar">
-                <BaseAvatar name={name} url={httpUrl} width={50} height={50} />
-            </div>
-            <div className="mx_GroupTile_profile">
-                <h3 className="mx_GroupTile_name">{ name }</h3>
-                <div className="mx_GroupTile_desc">{ desc }</div>
-                <div className="mx_GroupTile_groupId">{ this.props.groupId }</div>
-            </div>
-        </AccessibleButton>;
-    },
-});
-
 export default withMatrixClient(React.createClass({
     displayName: 'MyGroups',
 
@@ -114,6 +57,7 @@ export default withMatrixClient(React.createClass({
         const Loader = sdk.getComponent("elements.Spinner");
         const SimpleRoomHeader = sdk.getComponent('rooms.SimpleRoomHeader');
         const TintableSvg = sdk.getComponent("elements.TintableSvg");
+        const GroupTile = sdk.getComponent("groups.GroupTile");
 
         let content;
         let contentHeader;
diff --git a/src/components/structures/UserSettings.js b/src/components/structures/UserSettings.js
index 88619266ce..09844c3d63 100644
--- a/src/components/structures/UserSettings.js
+++ b/src/components/structures/UserSettings.js
@@ -595,6 +595,11 @@ module.exports = React.createClass({
         });
     },
 
+    _renderGroupSettings: function() {
+        const GroupUserSettings = sdk.getComponent('groups.GroupUserSettings');
+        return <GroupUserSettings />;
+    },
+
     _renderReferral: function() {
         const teamToken = this.props.teamToken;
         if (!teamToken) {
@@ -1249,6 +1254,8 @@ module.exports = React.createClass({
                     { accountJsx }
                 </div>
 
+                { this._renderGroupSettings() }
+
                 { this._renderReferral() }
 
                 { notificationArea }
diff --git a/src/components/views/groups/GroupPublicityToggle.js b/src/components/views/groups/GroupPublicityToggle.js
new file mode 100644
index 0000000000..ec910adcf5
--- /dev/null
+++ b/src/components/views/groups/GroupPublicityToggle.js
@@ -0,0 +1,91 @@
+/*
+Copyright 2017 New Vector 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 PropTypes from 'prop-types';
+import { MatrixClient } from 'matrix-js-sdk';
+import sdk from '../../../index';
+import GroupStoreCache from '../../../stores/GroupStoreCache';
+import GroupStore from '../../../stores/GroupStore';
+import { _t } from '../../../languageHandler.js';
+
+export default React.createClass({
+    displayName: 'GroupPublicityToggle',
+
+    propTypes: {
+        groupId: PropTypes.string.isRequired,
+    },
+
+    contextTypes: {
+        matrixClient: PropTypes.instanceOf(MatrixClient),
+    },
+
+    getInitialState() {
+        return {
+            busy: false,
+            ready: false,
+            isGroupPublicised: null,
+        };
+    },
+
+    componentWillMount: function() {
+        this._initGroupStore(this.props.groupId);
+    },
+
+    _initGroupStore: function(groupId) {
+        this._groupStore = GroupStoreCache.getGroupStore(this.context.matrixClient, groupId);
+        this._groupStore.registerListener(() => {
+            this.setState({
+                isGroupPublicised: this._groupStore.getGroupPublicity(),
+                ready: this._groupStore.isStateReady(GroupStore.STATE_KEY.Summary),
+            });
+        });
+    },
+
+    _onPublicityToggle: function(e) {
+        e.stopPropagation();
+        this.setState({
+            busy: true,
+            // Optimistic early update
+            isGroupPublicised: !this.state.isGroupPublicised,
+        });
+        this._groupStore.setGroupPublicity(!this.state.isGroupPublicised).then(() => {
+            this.setState({
+                busy: false,
+            });
+        });
+    },
+
+    render() {
+        const GroupTile = sdk.getComponent('groups.GroupTile');
+        const input = <input type="checkbox"
+            onClick={this._onPublicityToggle}
+            checked={this.state.isGroupPublicised}
+        />;
+        const labelText = !this.state.ready ? _t("Loading...") :
+            (this.state.isGroupPublicised ?
+             _t("Flair will appear if enabled in room settings") :
+             _t("Flair will not appear")
+            );
+        return <div className="mx_GroupPublicity_toggle">
+            <GroupTile groupId={this.props.groupId} showDescription={false} avatarHeight={40} />
+            <label onClick={this._onPublicityToggle}>
+                { input }
+                { labelText }
+            </label>
+        </div>;
+    },
+});
diff --git a/src/components/views/groups/GroupTile.js b/src/components/views/groups/GroupTile.js
new file mode 100644
index 0000000000..35c8dde2ab
--- /dev/null
+++ b/src/components/views/groups/GroupTile.js
@@ -0,0 +1,91 @@
+/*
+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 PropTypes from 'prop-types';
+import {MatrixClient} from 'matrix-js-sdk';
+import sdk from '../../../index';
+import dis from '../../../dispatcher';
+import FlairStore from '../../../stores/FlairStore';
+
+const GroupTile = React.createClass({
+    displayName: 'GroupTile',
+
+    propTypes: {
+        groupId: PropTypes.string.isRequired,
+        // Whether to show the short description of the group on the tile
+        showDescription: PropTypes.bool,
+        // Height of the group avatar in pixels
+        avatarHeight: PropTypes.number,
+    },
+
+    contextTypes: {
+        matrixClient: React.PropTypes.instanceOf(MatrixClient).isRequired,
+    },
+
+    getInitialState() {
+        return {
+            profile: null,
+        };
+    },
+
+    getDefaultProps() {
+        return {
+            showDescription: true,
+            avatarHeight: 50,
+        };
+    },
+
+    componentWillMount: function() {
+        FlairStore.getGroupProfileCached(this.context.matrixClient, this.props.groupId).then((profile) => {
+            this.setState({profile});
+        });
+    },
+
+    onClick: function(e) {
+        e.preventDefault();
+        dis.dispatch({
+            action: 'view_group',
+            group_id: this.props.groupId,
+        });
+    },
+
+    render: function() {
+        const BaseAvatar = sdk.getComponent('avatars.BaseAvatar');
+        const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
+        const profile = this.state.profile || {};
+        const name = profile.name || this.props.groupId;
+        const avatarHeight = this.props.avatarHeight;
+        const descElement = this.props.showDescription ?
+            <div className="mx_GroupTile_desc">{ profile.shortDescription }</div> :
+            <div />;
+        const httpUrl = profile.avatarUrl ? this.context.matrixClient.mxcUrlToHttp(
+            profile.avatarUrl, avatarHeight, avatarHeight, "crop",
+        ) : null;
+        return <AccessibleButton className="mx_GroupTile" onClick={this.onClick}>
+            <div className="mx_GroupTile_avatar">
+                <BaseAvatar name={name} url={httpUrl} width={avatarHeight} height={avatarHeight} />
+            </div>
+            <div className="mx_GroupTile_profile">
+                <div className="mx_GroupTile_name">{ name }</div>
+                { descElement }
+                <div className="mx_GroupTile_groupId">{ this.props.groupId }</div>
+            </div>
+        </AccessibleButton>;
+    },
+});
+
+export default GroupTile;
diff --git a/src/components/views/groups/GroupUserSettings.js b/src/components/views/groups/GroupUserSettings.js
new file mode 100644
index 0000000000..fd6fb3d85f
--- /dev/null
+++ b/src/components/views/groups/GroupUserSettings.js
@@ -0,0 +1,66 @@
+/*
+Copyright 2017 New Vector 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 PropTypes from 'prop-types';
+import GeminiScrollbar from 'react-gemini-scrollbar';
+import sdk from '../../../index';
+import { MatrixClient } from 'matrix-js-sdk';
+import { _t } from '../../../languageHandler';
+
+export default React.createClass({
+    displayName: 'GroupUserSettings',
+
+    contextTypes: {
+        matrixClient: PropTypes.instanceOf(MatrixClient),
+    },
+
+    getInitialState() {
+        return {
+            err: null,
+            groups: [],
+        };
+    },
+
+    componentWillMount: function() {
+        this.context.matrixClient.getJoinedGroups().done((result) => {
+            this.setState({groups: result.groups, error: null});
+        }, (err) => {
+            console.error(err);
+            this.setState({groups: null, error: err});
+        });
+    },
+
+    render() {
+        const GroupPublicityToggle = sdk.getComponent('groups.GroupPublicityToggle');
+        const groupPublicityToggles = this.state.groups.map((groupId, index) => {
+            return <GroupPublicityToggle key={index} groupId={groupId} />;
+        });
+        return <div>
+            <h3>{ _t('Flair') }</h3>
+            <div className="mx_UserSettings_section">
+                <p>
+                    { _t('Display your community flair in rooms configured to show it.') }
+                </p>
+                <div className="mx_GroupUserSettings_groupPublicity_scrollbox">
+                    <GeminiScrollbar>
+                        { groupPublicityToggles }
+                    </GeminiScrollbar>
+                </div>
+            </div>
+        </div>;
+    },
+});
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 128a07bc15..cf108b3a6e 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -227,8 +227,6 @@
     "Delete": "Delete",
     "Disable Notifications": "Disable Notifications",
     "Enable Notifications": "Enable Notifications",
-    "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
-    "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
     "Cannot add any more widgets": "Cannot add any more widgets",
     "The maximum permitted number of widgets have already been added to this room.": "The maximum permitted number of widgets have already been added to this room.",
     "Add a widget": "Add a widget",
@@ -456,6 +454,8 @@
     "New community ID (e.g. +foo:%(localDomain)s)": "New community ID (e.g. +foo:%(localDomain)s)",
     "You have <a>enabled</a> URL previews by default.": "You have <a>enabled</a> URL previews by default.",
     "You have <a>disabled</a> URL previews by default.": "You have <a>disabled</a> URL previews by default.",
+    "URL previews are enabled by default for participants in this room.": "URL previews are enabled by default for participants in this room.",
+    "URL previews are disabled by default for participants in this room.": "URL previews are disabled by default for participants in this room.",
     "URL Previews": "URL Previews",
     "Error decrypting audio": "Error decrypting audio",
     "Error decrypting attachment": "Error decrypting attachment",
@@ -517,6 +517,8 @@
     "Failed to withdraw invitation": "Failed to withdraw invitation",
     "Failed to remove user from community": "Failed to remove user from community",
     "Filter community members": "Filter community members",
+    "Flair will appear if enabled in room settings": "Flair will appear if enabled in room settings",
+    "Flair will not appear": "Flair will not appear",
     "Are you sure you want to remove '%(roomName)s' from %(groupId)s?": "Are you sure you want to remove '%(roomName)s' from %(groupId)s?",
     "Removing a room from the community will also remove it from the community page.": "Removing a room from the community will also remove it from the community page.",
     "Remove": "Remove",
@@ -528,6 +530,8 @@
     "Visible to everyone": "Visible to everyone",
     "Only visible to community members": "Only visible to community members",
     "Filter community rooms": "Filter community rooms",
+    "Flair": "Flair",
+    "Display your community flair in rooms configured to show it.": "Display your community flair in rooms configured to show it.",
     "Unknown Address": "Unknown Address",
     "NOTE: Apps are not end-to-end encrypted": "NOTE: Apps are not end-to-end encrypted",
     "Do you want to load widget from URL:": "Do you want to load widget from URL:",
@@ -723,8 +727,6 @@
     "%(inviter)s has invited you to join this community": "%(inviter)s has invited you to join this community",
     "You are an administrator of this community": "You are an administrator of this community",
     "You are a member of this community": "You are a member of this community",
-    "Community Member Settings": "Community Member Settings",
-    "Publish this community on your profile": "Publish this community on your profile",
     "Your community hasn't got a Long Description, a HTML page to show to community members.<br />Click here to open settings and give it one!": "Your community hasn't got a Long Description, a HTML page to show to community members.<br />Click here to open settings and give it one!",
     "Long Description (HTML)": "Long Description (HTML)",
     "Description": "Description",