diff --git a/res/css/structures/_TagPanel.scss b/res/css/structures/_TagPanel.scss index b2d05ad7e6..6c85341aaf 100644 --- a/res/css/structures/_TagPanel.scss +++ b/res/css/structures/_TagPanel.scss @@ -51,9 +51,9 @@ limitations under the License. .mx_TagPanel .mx_TagPanel_divider { height: 0px; - width: 34px; - border-bottom: 1px solid $panel-divider-color; - display: none; + width: 90%; + border: none; + border-bottom: 1px solid $tagpanel-divider-color; } .mx_TagPanel .mx_TagPanel_scroller { @@ -116,6 +116,10 @@ limitations under the License. border-radius: 0 3px 3px 0; } +.mx_TagPanel .mx_TagTile.mx_TagTile_large.mx_TagTile_selected::before { + left: -10px; +} + .mx_TagPanel .mx_TagTile.mx_AccessibleButton:focus { filter: none; } diff --git a/res/themes/dark/css/_dark.scss b/res/themes/dark/css/_dark.scss index d48abf6a4c..a3b03c777e 100644 --- a/res/themes/dark/css/_dark.scss +++ b/res/themes/dark/css/_dark.scss @@ -119,6 +119,8 @@ $roomlist-bg-color: rgba(33, 38, 44, 0.90); $roomlist-header-color: $tertiary-fg-color; $roomsublist-divider-color: $primary-fg-color; +$tagpanel-divider-color: $roomlist-header-color; + $roomtile-preview-color: $secondary-fg-color; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: rgba(141, 151, 165, 0.2); diff --git a/res/themes/legacy-dark/css/_legacy-dark.scss b/res/themes/legacy-dark/css/_legacy-dark.scss index 4ab5f99942..2741dcebf8 100644 --- a/res/themes/legacy-dark/css/_legacy-dark.scss +++ b/res/themes/legacy-dark/css/_legacy-dark.scss @@ -116,6 +116,8 @@ $roomlist-bg-color: $header-panel-bg-color; $roomsublist-divider-color: $primary-fg-color; +$tagpanel-divider-color: $roomlist-header-color; + $roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: #1A1D23; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 6e66964fdf..4fd2a3615b 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -183,6 +183,8 @@ $roomlist-bg-color: $header-panel-bg-color; $roomlist-header-color: $primary-fg-color; $roomsublist-divider-color: $primary-fg-color; +$tagpanel-divider-color: $roomlist-header-color; + $roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: #fff; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index ceb8d5677c..05302a2a80 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -177,6 +177,8 @@ $roomlist-bg-color: rgba(245, 245, 245, 0.90); $roomlist-header-color: $tertiary-fg-color; $roomsublist-divider-color: $primary-fg-color; +$tagpanel-divider-color: $roomlist-header-color; + $roomtile-preview-color: $secondary-fg-color; $roomtile-default-badge-bg-color: #61708b; $roomtile-selected-bg-color: #FFF; diff --git a/src/components/structures/RoomDirectory.js b/src/components/structures/RoomDirectory.js index 5b12dae7df..11d3508ee5 100644 --- a/src/components/structures/RoomDirectory.js +++ b/src/components/structures/RoomDirectory.js @@ -30,6 +30,10 @@ import { instanceForInstanceId, protocolNameForInstanceId } from '../../utils/Di import Analytics from '../../Analytics'; import {getHttpUriForMxc} from "matrix-js-sdk/src/content-repo"; import {ALL_ROOMS} from "../views/directory/NetworkDropdown"; +import SettingsStore from "../../settings/SettingsStore"; +import TagOrderStore from "../../stores/TagOrderStore"; +import GroupStore from "../../stores/GroupStore"; +import FlairStore from "../../stores/FlairStore"; const MAX_NAME_LENGTH = 80; const MAX_TOPIC_LENGTH = 160; @@ -46,6 +50,7 @@ export default createReactClass({ }, getInitialState: function() { + const selectedCommunityId = TagOrderStore.getSelectedTags()[0]; return { publicRooms: [], loading: true, @@ -54,6 +59,10 @@ export default createReactClass({ instanceId: undefined, roomServer: MatrixClientPeg.getHomeserverName(), filterString: null, + selectedCommunityId: SettingsStore.getValue("feature_communities_v2_prototypes") + ? selectedCommunityId + : null, + communityName: null, }; }, @@ -71,28 +80,39 @@ export default createReactClass({ this.setState({protocolsLoading: false}); return; } - MatrixClientPeg.get().getThirdpartyProtocols().then((response) => { - this.protocols = response; - this.setState({protocolsLoading: false}); - }, (err) => { - console.warn(`error loading third party protocols: ${err}`); - this.setState({protocolsLoading: false}); - if (MatrixClientPeg.get().isGuest()) { - // Guests currently aren't allowed to use this API, so - // ignore this as otherwise this error is literally the - // thing you see when loading the client! - return; - } - track('Failed to get protocol list from homeserver'); - const brand = SdkConfig.get().brand; - this.setState({ - error: _t( - '%(brand)s failed to get the protocol list from the homeserver. ' + - 'The homeserver may be too old to support third party networks.', - { brand }, - ), + + if (!this.state.selectedCommunityId) { + MatrixClientPeg.get().getThirdpartyProtocols().then((response) => { + this.protocols = response; + this.setState({protocolsLoading: false}); + }, (err) => { + console.warn(`error loading third party protocols: ${err}`); + this.setState({protocolsLoading: false}); + if (MatrixClientPeg.get().isGuest()) { + // Guests currently aren't allowed to use this API, so + // ignore this as otherwise this error is literally the + // thing you see when loading the client! + return; + } + track('Failed to get protocol list from homeserver'); + const brand = SdkConfig.get().brand; + this.setState({ + error: _t( + '%(brand)s failed to get the protocol list from the homeserver. ' + + 'The homeserver may be too old to support third party networks.', + {brand}, + ), + }); }); - }); + } else { + // We don't use the protocols in the communities v2 prototype experience + this.setState({protocolsLoading: false}); + + // Grab the profile info async + FlairStore.getGroupProfileCached(MatrixClientPeg.get(), this.state.selectedCommunityId).then(profile => { + this.setState({communityName: profile.name}); + }); + } this.refreshRoomList(); }, @@ -105,6 +125,33 @@ export default createReactClass({ }, refreshRoomList: function() { + if (this.state.selectedCommunityId) { + this.setState({ + publicRooms: GroupStore.getGroupRooms(this.state.selectedCommunityId).map(r => { + return { + // Translate all the group properties to the directory format + room_id: r.roomId, + name: r.name, + topic: r.topic, + canonical_alias: r.canonicalAlias, + num_joined_members: r.numJoinedMembers, + avatarUrl: r.avatarUrl, + world_readable: r.worldReadable, + guest_can_join: r.guestsCanJoin, + }; + }).filter(r => { + const filterString = this.state.filterString; + if (filterString) { + const containedIn = (s: string) => (s || "").toLowerCase().includes(filterString.toLowerCase()); + return containedIn(r.name) || containedIn(r.topic) || containedIn(r.canonical_alias); + } + return true; + }), + loading: false, + }); + return; + } + this.nextBatch = null; this.setState({ publicRooms: [], @@ -114,6 +161,7 @@ export default createReactClass({ }, getMoreRooms: function() { + if (this.state.selectedCommunityId) return Promise.resolve(); // no more rooms if (!MatrixClientPeg.get()) return Promise.resolve(); this.setState({ @@ -239,7 +287,7 @@ export default createReactClass({ }, onRoomClicked: function(room, ev) { - if (ev.shiftKey) { + if (ev.shiftKey && !this.state.selectedCommunityId) { ev.preventDefault(); this.removeFromDirectory(room); } else { @@ -610,6 +658,18 @@ export default createReactClass({ } } + let dropdown = ( + + ); + if (this.state.selectedCommunityId) { + dropdown = null; + } + listHeader =
- + {dropdown}
; } const explanation = @@ -637,12 +692,16 @@ export default createReactClass({ }}, ); + const title = this.state.selectedCommunityId + ? _t("Explore rooms in %(communityName)s", { + communityName: this.state.communityName || this.state.selectedCommunityId, + }) : _t("Explore rooms"); return (
{explanation} diff --git a/src/components/structures/TagPanel.js b/src/components/structures/TagPanel.js index 4f8a051e62..3acec417f2 100644 --- a/src/components/structures/TagPanel.js +++ b/src/components/structures/TagPanel.js @@ -29,6 +29,8 @@ import { Droppable } from 'react-beautiful-dnd'; import classNames from 'classnames'; import MatrixClientContext from "../../contexts/MatrixClientContext"; import AutoHideScrollbar from "./AutoHideScrollbar"; +import SettingsStore from "../../settings/SettingsStore"; +import UserTagTile from "../views/elements/UserTagTile"; const TagPanel = createReactClass({ displayName: 'TagPanel', @@ -102,6 +104,17 @@ const TagPanel = createReactClass({ dis.dispatch({action: 'deselect_tags'}); }, + renderGlobalIcon() { + if (!SettingsStore.getValue("feature_communities_v2_prototypes")) return null; + + return ( +
+ +
+
+ ); + }, + render() { const DNDTagTile = sdk.getComponent('elements.DNDTagTile'); const AccessibleButton = sdk.getComponent('elements.AccessibleButton'); @@ -137,7 +150,6 @@ const TagPanel = createReactClass({
{ clearButton }
-
+ { this.renderGlobalIcon() } { tags }
{ - dis.dispatch({ - action: 'view_group', - group_id: result.group_id, - group_is_new: true, - }); + if (result.room_id) { + dis.dispatch({ + action: 'view_room', + room_id: result.room_id, + }); + } else { + dis.dispatch({ + action: 'view_group', + group_id: result.group_id, + group_is_new: true, + }); + } this.props.onFinished(true); }).catch((e) => { this.setState({createError: e}); diff --git a/src/components/views/elements/TagTile.js b/src/components/views/elements/TagTile.js index 6a12c87024..49b336a577 100644 --- a/src/components/views/elements/TagTile.js +++ b/src/components/views/elements/TagTile.js @@ -47,6 +47,7 @@ export default createReactClass({ contextMenuButtonRef: PropTypes.object, openMenu: PropTypes.func, menuDisplayed: PropTypes.bool, + selected: PropTypes.bool, }, statics: { diff --git a/src/components/views/elements/UserTagTile.tsx b/src/components/views/elements/UserTagTile.tsx new file mode 100644 index 0000000000..c652423753 --- /dev/null +++ b/src/components/views/elements/UserTagTile.tsx @@ -0,0 +1,99 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +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 defaultDispatcher from "../../../dispatcher/dispatcher"; +import { OwnProfileStore } from "../../../stores/OwnProfileStore"; +import { UPDATE_EVENT } from "../../../stores/AsyncStore"; +import * as fbEmitter from "fbemitter"; +import TagOrderStore from "../../../stores/TagOrderStore"; +import AccessibleTooltipButton from "./AccessibleTooltipButton"; +import BaseAvatar from "../avatars/BaseAvatar"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import classNames from "classnames"; + +interface IProps{} + +interface IState { + selected: boolean; +} + +export default class UserTagTile extends React.PureComponent { + private tagStoreRef: fbEmitter.EventSubscription; + + constructor(props: IProps) { + super(props); + + this.state = { + selected: TagOrderStore.getSelectedTags().length === 0, + }; + } + + public componentDidMount() { + OwnProfileStore.instance.on(UPDATE_EVENT, this.onProfileUpdate); + this.tagStoreRef = TagOrderStore.addListener(this.onTagStoreUpdate); + } + + public componentWillUnmount() { + OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); + } + + private onProfileUpdate = () => { + this.forceUpdate(); + }; + + private onTagStoreUpdate = () => { + const selected = TagOrderStore.getSelectedTags().length === 0; + this.setState({selected}); + }; + + private onTileClick = (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + + // Deselect all tags + defaultDispatcher.dispatch({action: "deselect_tags"}); + }; + + public render() { + // XXX: We reuse TagTile classes for ease of demonstration - we should probably generify + // TagTile instead if we continue to use this component. + const avatarHeight = 36; + const name = OwnProfileStore.instance.displayName || MatrixClientPeg.get().getUserId(); + const className = classNames({ + mx_TagTile: true, + mx_TagTile_selected: this.state.selected, + mx_TagTile_large: true, + }); + return ( + +
+ +
+
+ ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 9bc13df30d..d0627efcc4 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -2064,6 +2064,7 @@ "Find a room…": "Find a room…", "Find a room… (e.g. %(exampleRoom)s)": "Find a room… (e.g. %(exampleRoom)s)", "If you can't find the room you're looking for, ask for an invite or Create a new room.": "If you can't find the room you're looking for, ask for an invite or Create a new room.", + "Explore rooms in %(communityName)s": "Explore rooms in %(communityName)s", "Clear filter": "Clear filter", "Search rooms": "Search rooms", "You can't send any messages until you review and agree to our terms and conditions.": "You can't send any messages until you review and agree to our terms and conditions.",