Merge pull request #2925 from matrix-org/bwindels/stylepreviewbar

Redesigned room preview bar
This commit is contained in:
Bruno Windels 2019-04-18 08:50:56 +00:00 committed by GitHub
commit 0fbe10a816
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 573 additions and 356 deletions

View file

@ -24,6 +24,7 @@ steps:
plugins: plugins:
- docker#v3.0.1: - docker#v3.0.1:
image: "matrixdotorg/riotweb-ci-e2etests-env:latest" image: "matrixdotorg/riotweb-ci-e2etests-env:latest"
propagate-environment: true
- label: ":karma: Tests" - label: ":karma: Tests"
agents: agents:

View file

@ -523,3 +523,38 @@ textarea {
opacity: 0; opacity: 0;
cursor: pointer; cursor: pointer;
} }
// username colors
// used by SenderProfile & RoomPreviewBar
.mx_Username_color1 {
color: $username-variant1-color;
}
.mx_Username_color2 {
color: $username-variant2-color;
}
.mx_Username_color3 {
color: $username-variant3-color;
}
.mx_Username_color4 {
color: $username-variant4-color;
}
.mx_Username_color5 {
color: $username-variant5-color;
}
.mx_Username_color6 {
color: $username-variant6-color;
}
.mx_Username_color7 {
color: $username-variant7-color;
}
.mx_Username_color8 {
color: $username-variant8-color;
}

View file

@ -163,15 +163,6 @@ limitations under the License.
height: 1px; height: 1px;
} }
.mx_RoomView_body .mx_RoomView_statusArea {
order: 3;
}
.mx_RoomView_body .mx_MessageComposer {
order: 4;
}
.mx_RoomView_messageListWrapper { .mx_RoomView_messageListWrapper {
min-height: 100%; min-height: 100%;

View file

@ -37,6 +37,12 @@ limitations under the License.
.mx_AccessibleButton_kind_primary { .mx_AccessibleButton_kind_primary {
color: $button-primary-fg-color; color: $button-primary-fg-color;
background-color: $button-primary-bg-color; background-color: $button-primary-bg-color;
font-weight: 600;
}
.mx_AccessibleButton_kind_secondary {
color: $accent-color;
font-weight: 600;
} }
.mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled { .mx_AccessibleButton_kind_primary.mx_AccessibleButton_disabled {

View file

@ -18,36 +18,3 @@ limitations under the License.
font-weight: 600; font-weight: 600;
} }
.mx_SenderProfile_color1 {
color: $username-variant1-color;
}
.mx_SenderProfile_color2 {
color: $username-variant2-color;
}
.mx_SenderProfile_color3 {
color: $username-variant3-color;
}
.mx_SenderProfile_color4 {
color: $username-variant4-color;
}
.mx_SenderProfile_color5 {
color: $username-variant5-color;
}
.mx_SenderProfile_color6 {
color: $username-variant6-color;
}
.mx_SenderProfile_color7 {
color: $username-variant7-color;
}
.mx_SenderProfile_color8 {
color: $username-variant8-color;
}

View file

@ -15,48 +15,112 @@ limitations under the License.
*/ */
.mx_RoomPreviewBar { .mx_RoomPreviewBar {
text-align: center; flex: 0 0 auto;
height: 176px;
background-color: $event-selected-color;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
display: flex; display: flex;
background-color: $preview-bar-bg-color;
-webkit-align-items: center; -webkit-align-items: center;
h3 {
font-size: 18px;
font-weight: 600;
&.mx_RoomPreviewBar_spinnerTitle {
display: flex;
flex-direction: row;
align-items: center;
}
}
.mx_Spinner {
width: auto;
height: auto;
margin: 10px 10px 10px 0;
flex: 0 0 auto;
}
} }
.mx_RoomPreviewBar_wrapper { .mx_RoomPreviewBar_dark {
background-color: $tagpanel-bg-color;
color: $accent-fg-color;
} }
.mx_RoomPreviewBar_invite_text { .mx_RoomPreviewBar_actions {
color: $primary-fg-color; display: flex;
} }
.mx_RoomPreviewBar_join_text { .mx_RoomPreviewBar_message {
color: $warning-color; display: flex;
align-items: stretch;
p {
overflow-wrap: break-word;
}
} }
.mx_RoomPreviewBar_preview_text { .mx_RoomPreviewBar_panel {
margin-top: 25px; padding: 8px 8px 8px 20px;
color: $settings-grey-fg-color; border-top: 1px solid $panel-divider-color;
flex-direction: row;
.mx_RoomPreviewBar_actions {
flex: 0 0 auto;
flex-direction: row;
padding: 3px 8px;
&>* {
margin-left: 12px;
}
}
.mx_RoomPreviewBar_message {
flex: 1 0 0;
min-width: 0;
display: flex;
flex-direction: column;
&>* {
margin: 4px;
}
}
} }
.mx_RoomPreviewBar_join_text a { .mx_RoomPreviewBar_dialog {
margin: auto;
box-sizing: content;
width: 400px;
border-radius: 4px;
flex-direction: column;
padding: 20px;
text-align: center;
.mx_RoomPreviewBar_message {
flex-direction: column;
&>* {
margin: 5px 0 20px 0;
}
}
.mx_RoomPreviewBar_actions {
flex-direction: column-reverse;
.mx_AccessibleButton {
padding: 7px 50px;//extra wide
}
&>* {
margin-top: 12px;
}
}
}
.mx_RoomPreviewBar_inviter {
font-weight: 600;
}
a.mx_RoomPreviewBar_inviter {
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
} }
.mx_RoomPreviewBar_warning {
display: flex;
align-items: center;
padding: 8px;
}
.mx_RoomPreviewBar_warningIcon {
padding: 12px;
}
.mx_RoomPreviewBar_spinnerIntro {
margin-top: 50px;
}

View file

@ -565,6 +565,9 @@ export default React.createClass({
}, },
}); });
break; break;
case 'view_user_info':
this._viewUser(payload.userId, payload.subAction);
break;
case 'view_room': case 'view_room':
// Takes either a room ID or room alias: if switching to a room the client is already // Takes either a room ID or room alias: if switching to a room the client is already
// known to be in (eg. user clicks on a room in the recents panel), supply the ID // known to be in (eg. user clicks on a room in the recents panel), supply the ID
@ -919,6 +922,22 @@ export default React.createClass({
this.notifyNewScreen('home'); this.notifyNewScreen('home');
}, },
_viewUser: function(userId, subAction) {
// Wait for the first sync so that `getRoom` gives us a room object if it's
// in the sync response
const waitForSync = this.firstSyncPromise ?
this.firstSyncPromise.promise : Promise.resolve();
waitForSync.then(() => {
if (subAction === 'chat') {
this._chatCreateOrReuse(userId);
return;
}
this.notifyNewScreen('user/' + userId);
this.setState({currentUserId: userId});
this._setPage(PageTypes.UserView);
});
},
_setMxId: function(payload) { _setMxId: function(payload) {
const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog'); const SetMxIdDialog = sdk.getComponent('views.dialogs.SetMxIdDialog');
const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, { const close = Modal.createTrackedDialog('Set MXID', '', SetMxIdDialog, {
@ -1612,19 +1631,10 @@ export default React.createClass({
dis.dispatch(payload); dis.dispatch(payload);
} else if (screen.indexOf('user/') == 0) { } else if (screen.indexOf('user/') == 0) {
const userId = screen.substring(5); const userId = screen.substring(5);
dis.dispatch({
// Wait for the first sync so that `getRoom` gives us a room object if it's action: 'view_user_info',
// in the sync response userId: userId,
const waitFor = this.firstSyncPromise ? subAction: params.action,
this.firstSyncPromise.promise : Promise.resolve();
waitFor.then(() => {
if (params.action === 'chat') {
this._chatCreateOrReuse(userId);
return;
}
this.notifyNewScreen('user/' + userId);
this.setState({currentUserId: userId});
this._setPage(PageTypes.UserView);
}); });
} else if (screen.indexOf('group/') == 0) { } else if (screen.indexOf('group/') == 0) {
const groupId = screen.substring(6); const groupId = screen.substring(6);

View file

@ -1511,16 +1511,21 @@ module.exports = React.createClass({
const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const TintableSvg = sdk.getComponent("elements.TintableSvg"); const TintableSvg = sdk.getComponent("elements.TintableSvg");
const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar"); const RoomPreviewBar = sdk.getComponent("rooms.RoomPreviewBar");
const Loader = sdk.getComponent("elements.Spinner");
const TimelinePanel = sdk.getComponent("structures.TimelinePanel"); const TimelinePanel = sdk.getComponent("structures.TimelinePanel");
const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar"); const RoomUpgradeWarningBar = sdk.getComponent("rooms.RoomUpgradeWarningBar");
const RoomRecoveryReminder = sdk.getComponent("rooms.RoomRecoveryReminder"); const RoomRecoveryReminder = sdk.getComponent("rooms.RoomRecoveryReminder");
if (!this.state.room) { if (!this.state.room) {
if (this.state.roomLoading || this.state.peekLoading) { const loading = this.state.roomLoading || this.state.peekLoading;
if (loading) {
return ( return (
<div className="mx_RoomView"> <div className="mx_RoomView">
<Loader /> <RoomPreviewBar
canPreview={false}
error={this.state.roomLoadError}
loading={loading}
joining={this.state.joining}
/>
</div> </div>
); );
} else { } else {
@ -1538,28 +1543,16 @@ module.exports = React.createClass({
const roomAlias = this.state.roomAlias; const roomAlias = this.state.roomAlias;
return ( return (
<div className="mx_RoomView"> <div className="mx_RoomView">
<RoomHeader ref="header" <RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
canPreview={false} error={this.state.roomLoadError}
roomAlias={roomAlias}
joining={this.state.joining}
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room} room={this.state.room}
oobData={this.props.oobData}
collapsedRhs={this.props.collapsedRhs}
e2eStatus={this.state.e2eStatus}
/> />
<div className="mx_RoomView_body">
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked}
canPreview={false} error={this.state.roomLoadError}
roomAlias={roomAlias}
spinner={this.state.joining}
spinnerState="joining"
inviterName={inviterName}
invitedEmail={invitedEmail}
room={this.state.room}
/>
</div>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div> </div>
); );
} }
@ -1569,9 +1562,12 @@ module.exports = React.createClass({
if (myMembership == 'invite') { if (myMembership == 'invite') {
if (this.state.joining || this.state.rejecting) { if (this.state.joining || this.state.rejecting) {
return ( return (
<div className="mx_RoomView"> <RoomPreviewBar
<Loader /> canPreview={false}
</div> error={this.state.roomLoadError}
joining={this.state.joining}
rejecting={this.state.rejecting}
/>
); );
} else { } else {
const myUserId = MatrixClientPeg.get().credentials.userId; const myUserId = MatrixClientPeg.get().credentials.userId;
@ -1586,26 +1582,14 @@ module.exports = React.createClass({
// We have a regular invite for this room. // We have a regular invite for this room.
return ( return (
<div className="mx_RoomView"> <div className="mx_RoomView">
<RoomHeader <RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
ref="header" onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectButtonClicked}
inviterName={inviterName}
canPreview={false}
joining={this.state.joining}
room={this.state.room} room={this.state.room}
collapsedRhs={this.props.collapsedRhs}
e2eStatus={this.state.e2eStatus}
/> />
<div className="mx_RoomView_body">
<div className="mx_RoomView_auxPanel">
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectButtonClicked}
inviterName={inviterName}
canPreview={false}
spinner={this.state.joining}
spinnerState="joining"
room={this.state.room}
/>
</div>
</div>
<div className="mx_RoomView_messagePanel"></div>
</div> </div>
); );
} }
@ -1661,7 +1645,9 @@ module.exports = React.createClass({
const hiddenHighlightCount = this._getHiddenHighlightCount(); const hiddenHighlightCount = this._getHiddenHighlightCount();
let aux = null; let aux = null;
let previewBar;
let hideCancel = false; let hideCancel = false;
let hideRightPanel = false;
if (this.state.forwardingEvent !== null) { if (this.state.forwardingEvent !== null) {
aux = <ForwardMessage onCancelClick={this.onCancelClick} />; aux = <ForwardMessage onCancelClick={this.onCancelClick} />;
} else if (this.state.searching) { } else if (this.state.searching) {
@ -1688,18 +1674,26 @@ module.exports = React.createClass({
invitedEmail = this.props.thirdPartyInvite.invitedEmail; invitedEmail = this.props.thirdPartyInvite.invitedEmail;
} }
hideCancel = true; hideCancel = true;
aux = ( previewBar = (
<RoomPreviewBar onJoinClick={this.onJoinButtonClicked} <RoomPreviewBar onJoinClick={this.onJoinButtonClicked}
onForgetClick={this.onForgetClick} onForgetClick={this.onForgetClick}
onRejectClick={this.onRejectThreepidInviteButtonClicked} onRejectClick={this.onRejectThreepidInviteButtonClicked}
spinner={this.state.joining} joining={this.state.joining}
spinnerState="joining"
inviterName={inviterName} inviterName={inviterName}
invitedEmail={invitedEmail} invitedEmail={invitedEmail}
canPreview={this.state.canPeek} canPreview={this.state.canPeek}
room={this.state.room} room={this.state.room}
/> />
); );
if (!this.state.canPeek) {
return (
<div className="mx_RoomView">
{ previewBar }
</div>
);
} else {
hideRightPanel = true;
}
} else if (hiddenHighlightCount > 0) { } else if (hiddenHighlightCount > 0) {
aux = ( aux = (
<AccessibleButton element="div" className="mx_RoomView_auxPanel_hiddenHighlights" <AccessibleButton element="div" className="mx_RoomView_auxPanel_hiddenHighlights"
@ -1743,11 +1737,6 @@ module.exports = React.createClass({
/>; />;
} }
if (MatrixClientPeg.get().isGuest()) {
const AuthButtons = sdk.getComponent('views.auth.AuthButtons');
messageComposer = <AuthButtons />;
}
// TODO: Why aren't we storing the term/scope/count in this format // TODO: Why aren't we storing the term/scope/count in this format
// in this.state if this is what RoomHeader desires? // in this.state if this is what RoomHeader desires?
if (this.state.searchResults) { if (this.state.searchResults) {
@ -1875,14 +1864,16 @@ module.exports = React.createClass({
}, },
); );
const rightPanel = this.state.room ? <RightPanel roomId={this.state.room.roomId} resizeNotifier={this.props.resizeNotifier} /> : undefined; const rightPanel = !hideRightPanel && this.state.room &&
<RightPanel roomId={this.state.room.roomId} resizeNotifier={this.props.resizeNotifier} />;
const collapsedRhs = hideRightPanel || this.props.collapsedRhs;
return ( return (
<main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView"> <main className={"mx_RoomView" + (inCall ? " mx_RoomView_inCall" : "")} ref="roomView">
<RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo} <RoomHeader ref="header" room={this.state.room} searchInfo={searchInfo}
oobData={this.props.oobData} oobData={this.props.oobData}
inRoom={myMembership === 'join'} inRoom={myMembership === 'join'}
collapsedRhs={this.props.collapsedRhs} collapsedRhs={collapsedRhs}
onSearchClick={this.onSearchClick} onSearchClick={this.onSearchClick}
onSettingsClick={this.onSettingsClick} onSettingsClick={this.onSettingsClick}
onPinnedClick={this.onPinnedClick} onPinnedClick={this.onPinnedClick}
@ -1893,7 +1884,7 @@ module.exports = React.createClass({
/> />
<MainSplit <MainSplit
panel={rightPanel} panel={rightPanel}
collapsedRhs={this.props.collapsedRhs} collapsedRhs={collapsedRhs}
resizeNotifier={this.props.resizeNotifier} resizeNotifier={this.props.resizeNotifier}
> >
<div className={fadableSectionClasses}> <div className={fadableSectionClasses}>
@ -1910,6 +1901,7 @@ module.exports = React.createClass({
{ statusBar } { statusBar }
</div> </div>
</div> </div>
{ previewBar }
{ messageComposer } { messageComposer }
</div> </div>
</MainSplit> </MainSplit>

View file

@ -1,57 +0,0 @@
/*
Copyright 2017 Vector Creations Ltd
Copyright 2018, 2019 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.
*/
'use strict';
const React = require('react');
import { _t } from '../../../languageHandler';
const dis = require('../../../dispatcher');
const AccessibleButton = require('../elements/AccessibleButton');
module.exports = React.createClass({
displayName: 'AuthButtons',
propTypes: {
},
onLoginClick: function() {
dis.dispatch({ action: 'start_login' });
},
onRegisterClick: function() {
dis.dispatch({ action: 'start_registration' });
},
render: function() {
const loginButton = (
<div className="mx_AuthButtons_loginButton_wrapper">
<AccessibleButton className="mx_AuthButtons_loginButton" element="button" onClick={this.onLoginClick}>
{ _t("Login") }
</AccessibleButton>
<AccessibleButton className="mx_AuthButtons_registerButton" element="button" onClick={this.onRegisterClick}>
{ _t("Register") }
</AccessibleButton>
</div>
);
return (
<div className="mx_AuthButtons">
{ loginButton }
</div>
);
},
});

View file

@ -23,7 +23,7 @@ import sdk from '../../../index';
import Flair from '../elements/Flair.js'; import Flair from '../elements/Flair.js';
import FlairStore from '../../../stores/FlairStore'; import FlairStore from '../../../stores/FlairStore';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {hashCode} from '../../../utils/FormattingUtils'; import {getUserNameColorClass} from '../../../utils/FormattingUtils';
export default React.createClass({ export default React.createClass({
displayName: 'SenderProfile', displayName: 'SenderProfile',
@ -97,7 +97,7 @@ export default React.createClass({
render() { render() {
const EmojiText = sdk.getComponent('elements.EmojiText'); const EmojiText = sdk.getComponent('elements.EmojiText');
const {mxEvent} = this.props; const {mxEvent} = this.props;
const colorNumber = (hashCode(mxEvent.getSender()) % 8) + 1; const colorClass = getUserNameColorClass(mxEvent.getSender());
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender(); const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
const {msgtype} = mxEvent.getContent(); const {msgtype} = mxEvent.getContent();
@ -121,7 +121,7 @@ export default React.createClass({
// Name + flair // Name + flair
const nameFlair = <span> const nameFlair = <span>
<span className={`mx_SenderProfile_name mx_SenderProfile_color${colorNumber}`}> <span className={`mx_SenderProfile_name ${colorClass}`}>
{ nameElem } { nameElem }
</span> </span>
{ flair } { flair }

View file

@ -17,12 +17,29 @@ limitations under the License.
'use strict'; 'use strict';
const React = require('react'); import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
const sdk = require('../../../index'); import sdk from '../../../index';
const MatrixClientPeg = require('../../../MatrixClientPeg'); import MatrixClientPeg from '../../../MatrixClientPeg';
import dis from '../../../dispatcher';
import classNames from 'classnames';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {getUserNameColorClass} from '../../../utils/FormattingUtils';
const MessageCase = Object.freeze({
NotLoggedIn: "NotLoggedIn",
Joining: "Joining",
Loading: "Loading",
Rejecting: "Rejecting",
Kicked: "Kicked",
Banned: "Banned",
OtherThreePIDError: "OtherThreePIDError",
InvitedEmailMismatch: "InvitedEmailMismatch",
Invite: "Invite",
ViewingRoom: "ViewingRoom",
RoomNotFound: "RoomNotFound",
OtherError: "OtherError",
});
module.exports = React.createClass({ module.exports = React.createClass({
displayName: 'RoomPreviewBar', displayName: 'RoomPreviewBar',
@ -31,7 +48,6 @@ module.exports = React.createClass({
onJoinClick: PropTypes.func, onJoinClick: PropTypes.func,
onRejectClick: PropTypes.func, onRejectClick: PropTypes.func,
onForgetClick: PropTypes.func, onForgetClick: PropTypes.func,
// if inviterName is specified, the preview bar will shown an invite to the room. // if inviterName is specified, the preview bar will shown an invite to the room.
// You should also specify onRejectClick if specifiying inviterName // You should also specify onRejectClick if specifiying inviterName
inviterName: PropTypes.string, inviterName: PropTypes.string,
@ -50,7 +66,9 @@ module.exports = React.createClass({
// purpose of the spinner. // purpose of the spinner.
spinner: PropTypes.bool, spinner: PropTypes.bool,
spinnerState: PropTypes.oneOf(["joining"]), spinnerState: PropTypes.oneOf(["joining"]),
loading: PropTypes.bool,
joining: PropTypes.bool,
rejecting: PropTypes.bool,
// The alias that was used to access this room, if appropriate // The alias that was used to access this room, if appropriate
// If given, this will be how the room is referred to (eg. // If given, this will be how the room is referred to (eg.
// in error messages). // in error messages).
@ -60,7 +78,6 @@ module.exports = React.createClass({
getDefaultProps: function() { getDefaultProps: function() {
return { return {
onJoinClick: function() {}, onJoinClick: function() {},
canPreview: true,
}; };
}, },
@ -72,7 +89,7 @@ module.exports = React.createClass({
componentWillMount: function() { componentWillMount: function() {
// If this is an invite and we've been told what email // If this is an invite and we've been told what email
// address was invited, fetch the user's list of 3pids // address was invited, fetch the user's list of Threepids
// so we can check them against the one that was invited // so we can check them against the one that was invited
if (this.props.inviterName && this.props.invitedEmail) { if (this.props.inviterName && this.props.invitedEmail) {
this.setState({busy: true}); this.setState({busy: true});
@ -88,157 +105,334 @@ module.exports = React.createClass({
} }
}, },
_roomNameElement: function() { _onInviterClick(evt) {
return this.props.room ? this.props.room.name : (this.props.room_alias || ""); evt.preventDefault();
const member = this._getInviteMember();
dis.dispatch({action: 'view_user_info', userId: member.userId});
}, },
render: function() { _getMessageCase() {
let joinBlock; let previewBlock; const isGuest = MatrixClientPeg.get().isGuest();
if (this.props.spinner || this.state.busy) { if (isGuest) {
const Spinner = sdk.getComponent("elements.Spinner"); return MessageCase.NotLoggedIn;
let spinnerIntro = "";
if (this.props.spinnerState === "joining") {
spinnerIntro = _t("Joining room...");
}
return (<div className="mx_RoomPreviewBar">
<p className="mx_RoomPreviewBar_spinnerIntro">{ spinnerIntro }</p>
<Spinner />
</div>);
} }
const myMember = this.props.room &&
this.props.room.getMember(MatrixClientPeg.get().getUserId());
if (myMember) {
if (myMember.isKicked()) {
return MessageCase.Kicked;
} else if (myMember.membership === "ban") {
return MessageCase.Banned;
}
}
if (this.props.joining) {
return MessageCase.Joining;
} else if (this.props.rejecting) {
return MessageCase.Rejecting;
} else if (this.props.loading || this.state.busy) {
return MessageCase.Loading;
}
if (this.props.inviterName) {
if (this.props.invitedEmail) {
if (this.state.threePidFetchError) {
return MessageCase.OtherThreePIDError;
} else if (this.state.invitedEmailMxid != MatrixClientPeg.get().getUserId()) {
return MessageCase.InvitedEmailMismatch;
}
}
return MessageCase.Invite;
} else if (this.props.error) {
if (this.props.error.errcode == 'M_NOT_FOUND') {
return MessageCase.RoomNotFound;
} else {
return MessageCase.OtherError;
}
} else {
return MessageCase.ViewingRoom;
}
},
_getKickOrBanInfo() {
const myMember = this.props.room ? const myMember = this.props.room ?
this.props.room.getMember(MatrixClientPeg.get().getUserId()) : this.props.room.getMember(MatrixClientPeg.get().getUserId()) :
null; null;
const kicked = myMember && myMember.isKicked(); if (!myMember) {
const banned = myMember && myMember && myMember.membership == 'ban'; return {};
}
const kickerMember = this.props.room.currentState.getMember(
myMember.events.member.getSender(),
);
const memberName = kickerMember ?
kickerMember.name : myMember.events.member.getSender();
const reason = myMember.events.member.getContent().reason;
return {memberName, reason};
},
if (this.props.inviterName) { _joinRule: function() {
let emailMatchBlock; const room = this.props.room;
if (this.props.invitedEmail) { if (room) {
if (this.state.threePidFetchError) { const joinRules = room.currentState.getStateEvents('m.room.join_rules', '');
emailMatchBlock = <div className="error"> if (joinRules) {
{ _t("Unable to ascertain that the address this invite was sent to matches one associated with your account.") } return joinRules.getContent().join_rule;
</div>;
} else if (this.state.invitedEmailMxid != MatrixClientPeg.get().credentials.userId) {
emailMatchBlock =
<div className="mx_RoomPreviewBar_warning">
<div className="mx_RoomPreviewBar_warningIcon">
<img src={require("../../../../res/img/warning.svg")} width="24" height="23" title= "/!\\" alt="/!\\" />
</div>
<div className="mx_RoomPreviewBar_warningText">
{ _t("This invitation was sent to an email address which is not associated with this account:") }
<b><span className="email">{ this.props.invitedEmail }</span></b>
<br />
{ _t("You may wish to login with a different account, or add this email to this account.") }
</div>
</div>;
}
}
joinBlock = (
<div>
<div className="mx_RoomPreviewBar_invite_text">
{ _t('You have been invited to join this room by %(inviterName)s', {inviterName: this.props.inviterName}) }
</div>
<div className="mx_RoomPreviewBar_join_text">
{ _t(
'Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?',
{},
{
'acceptText': (sub) => <a onClick={this.props.onJoinClick}>{ sub }</a>,
'declineText': (sub) => <a onClick={this.props.onRejectClick}>{ sub }</a>,
},
) }
</div>
{ emailMatchBlock }
</div>
);
} else if (kicked || banned) {
const roomName = this._roomNameElement();
const kickerMember = this.props.room.currentState.getMember(
myMember.events.member.getSender(),
);
const kickerName = kickerMember ?
kickerMember.name : myMember.events.member.getSender();
let reason;
if (myMember.events.member.getContent().reason) {
reason = <div>{ _t("Reason: %(reasonText)s", {reasonText: myMember.events.member.getContent().reason}) }</div>;
}
let rejoinBlock;
if (!banned) {
rejoinBlock = <div><a onClick={this.props.onJoinClick}><b>{ _t("Rejoin") }</b></a></div>;
} }
}
},
let actionText; _roomName: function(atStart = false) {
if (kicked) { const name = this.props.room ? this.props.room.name : this.props.roomAlias;
if (roomName) { if (name) {
actionText = _t("You have been kicked from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName}); return name;
} else { } else if (atStart) {
actionText = _t("You have been kicked from this room by %(userName)s.", {userName: kickerName}); return _t("This room");
}
} else if (banned) {
if (roomName) {
actionText = _t("You have been banned from %(roomName)s by %(userName)s.", {roomName: roomName, userName: kickerName});
} else {
actionText = _t("You have been banned from this room by %(userName)s.", {userName: kickerName});
}
} // no other options possible due to the kicked || banned check above.
joinBlock = (
<div>
<div className="mx_RoomPreviewBar_join_text">
{ actionText }
<br />
{ reason }
{ rejoinBlock }
<a onClick={this.props.onForgetClick}><b>{ _t("Forget room") }</b></a>
</div>
</div>
);
} else if (this.props.error) {
const name = this.props.roomAlias || _t("This room");
let error;
if (this.props.error.errcode == 'M_NOT_FOUND') {
error = _t("%(roomName)s does not exist.", {roomName: name});
} else {
error = _t("%(roomName)s is not accessible at this time.", {roomName: name});
}
joinBlock = (
<div>
<div className="mx_RoomPreviewBar_join_text">
{ error }
</div>
</div>
);
} else { } else {
const name = this._roomNameElement(); return _t("this room");
joinBlock = ( }
<div> },
<div className="mx_RoomPreviewBar_join_text">
{ name ? _t('You are trying to access %(roomName)s.', {roomName: name}) : _t('You are trying to access a room.') } _getInviteMember: function() {
<br /> const {room} = this.props;
{ _t("<a>Click here</a> to join the discussion!", if (!room) {
{}, return;
{ 'a': (sub) => <a onClick={this.props.onJoinClick}><b>{ sub }</b></a> }, }
) } const myUserId = MatrixClientPeg.get().getUserId();
</div> const inviteEvent = room.currentState.getMember(myUserId);
</div> if (!inviteEvent) {
return;
}
const inviterUserId = inviteEvent.events.member.getSender();
return room.currentState.getMember(inviterUserId);
},
onLoginClick: function() {
dis.dispatch({ action: 'start_login' });
},
onRegisterClick: function() {
dis.dispatch({ action: 'start_registration' });
},
render: function() {
let showSpinner = false;
let darkStyle = false;
let title;
let subTitle;
let primaryActionHandler;
let primaryActionLabel;
let secondaryActionHandler;
let secondaryActionLabel;
const messageCase = this._getMessageCase();
switch (messageCase) {
case MessageCase.Joining: {
title = _t("Joining room …");
showSpinner = true;
break;
}
case MessageCase.Loading: {
title = _t("Loading …");
showSpinner = true;
break;
}
case MessageCase.Rejecting: {
title = _t("Rejecting invite …");
showSpinner = true;
break;
}
case MessageCase.NotLoggedIn: {
darkStyle = true;
title = _t("Join the conversation with an account");
primaryActionLabel = _t("Sign Up");
primaryActionHandler = this.onRegisterClick;
secondaryActionLabel = _t("Sign In");
secondaryActionHandler = this.onLoginClick;
break;
}
case MessageCase.Kicked: {
const {memberName, reason} = this._getKickOrBanInfo();
title = _t("You were kicked from %(roomName)s by %(memberName)s",
{memberName, roomName: this._roomName()});
subTitle = _t("Reason: %(reason)s", {reason});
if (this._joinRule() === "invite") {
primaryActionLabel = _t("Forget this room");
primaryActionHandler = this.props.onForgetClick;
} else {
primaryActionLabel = _t("Re-join");
primaryActionHandler = this.props.onJoinClick;
secondaryActionLabel = _t("Forget this room");
secondaryActionHandler = this.props.onForgetClick;
}
break;
}
case MessageCase.Banned: {
const {memberName, reason} = this._getKickOrBanInfo();
title = _t("You were banned from %(roomName)s by %(memberName)s",
{memberName, roomName: this._roomName()});
subTitle = _t("Reason: %(reason)s", {reason});
primaryActionLabel = _t("Forget this room");
primaryActionHandler = this.props.onForgetClick;
break;
}
case MessageCase.OtherThreePIDError: {
title = _t("Something went wrong with your invite to this room");
const joinRule = this._joinRule();
const errCodeMessage = _t("%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.",
{errcode: this.state.threePidFetchError.errcode},
);
switch (joinRule) {
case "invite":
subTitle = [
_t("You can only join it with a working invite."),
errCodeMessage,
];
break;
case "public":
subTitle = _t("You can still join it because this is a public room.");
primaryActionLabel = _t("Join the discussion");
primaryActionHandler = this.props.onJoinClick;
break;
default:
subTitle = errCodeMessage;
primaryActionLabel = _t("Try to join anyway");
primaryActionHandler = this.props.onJoinClick;
break;
}
break;
}
case MessageCase.InvitedEmailMismatch: {
title = _t("The room invite wasn't sent to your account");
const joinRule = this._joinRule();
if (joinRule === "public") {
subTitle = _t("You can still join it because this is a public room.");
primaryActionLabel = _t("Join the discussion");
primaryActionHandler = this.props.onJoinClick;
} else {
subTitle = _t("Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.", {email: this.props.invitedEmail});
if (joinRule !== "invite") {
primaryActionLabel = _t("Try to join anyway");
primaryActionHandler = this.props.onJoinClick;
}
}
break;
}
case MessageCase.Invite: {
const inviteMember = this._getInviteMember();
let avatar;
let inviterElement;
if (inviteMember) {
const MemberAvatar = sdk.getComponent("views.avatars.MemberAvatar");
avatar = (<MemberAvatar member={inviteMember} onClick={this._onInviterClick} />);
const inviterClasses = [
"mx_RoomPreviewBar_inviter",
getUserNameColorClass(inviteMember.userId),
].join(" ");
inviterElement = (
<a onClick={this._onInviterClick} className={inviterClasses}>
{inviteMember.name}
</a>
);
} else {
inviterElement = (<span className="mx_RoomPreviewBar_inviter">{this.props.inviterName}</span>);
}
title = _t("Do you want to join this room?");
subTitle = [
avatar,
_t("<userName/> invited you", {}, {userName: () => inviterElement}),
];
primaryActionLabel = _t("Accept");
primaryActionHandler = this.props.onJoinClick;
secondaryActionLabel = _t("Reject");
secondaryActionHandler = this.props.onRejectClick;
break;
}
case MessageCase.ViewingRoom: {
if (this.props.canPreview) {
title = _t("You're previewing this room. Want to join it?");
} else {
title = _t("%(roomName)s can't be previewed. Do you want to join it?",
{roomName: this._roomName(true)});
}
primaryActionLabel = _t("Join the discussion");
primaryActionHandler = this.props.onJoinClick;
break;
}
case MessageCase.RoomNotFound: {
title = _t("%(roomName)s does not exist.", {roomName: this._roomName(true)});
subTitle = _t("This room doesn't exist. Are you sure you're at the right place?");
break;
}
case MessageCase.OtherError: {
title = _t("%(roomName)s is not accessible at this time.", {roomName: this._roomName(true)});
subTitle = [
_t("Try again later, or ask a room admin to check if you have access."),
_t("%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.",
{ errcode: this.props.error.errcode },
{ issueLink: label => <a href="https://github.com/vector-im/riot-web/issues/new/choose"
target="_blank" rel="noopener">{ label }</a> },
),
];
break;
}
}
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
const Spinner = sdk.getComponent('elements.Spinner');
let subTitleElements;
if (subTitle) {
if (!Array.isArray(subTitle)) {
subTitle = [subTitle];
}
subTitleElements = subTitle.map((t, i) => <p key={`subTitle${i}`}>{t}</p>);
}
let titleElement;
if (showSpinner) {
titleElement = <h3 className="mx_RoomPreviewBar_spinnerTitle"><Spinner />{ title }</h3>;
} else {
titleElement = <h3>{ title }</h3>;
}
let primaryButton;
if (primaryActionHandler) {
primaryButton = (
<AccessibleButton kind="primary" onClick={primaryActionHandler}>
{ primaryActionLabel }
</AccessibleButton>
); );
} }
if (this.props.canPreview) { let secondaryButton;
previewBlock = ( if (secondaryActionHandler) {
<div className="mx_RoomPreviewBar_preview_text"> secondaryButton = (
{ _t('This is a preview of this room. Room interactions have been disabled') }. <AccessibleButton kind="secondary" onClick={secondaryActionHandler}>
</div> { secondaryActionLabel }
</AccessibleButton>
); );
} }
const classes = classNames("mx_RoomPreviewBar", "dark-panel", `mx_RoomPreviewBar_${messageCase}`, {
"mx_RoomPreviewBar_panel": this.props.canPreview,
"mx_RoomPreviewBar_dialog": !this.props.canPreview,
"mx_RoomPreviewBar_dark": darkStyle,
});
return ( return (
<div className="mx_RoomPreviewBar"> <div className={classes}>
<div className="mx_RoomPreviewBar_wrapper"> <div className="mx_RoomPreviewBar_message">
{ joinBlock } { titleElement }
{ previewBlock } { subTitleElements }
</div>
<div className="mx_RoomPreviewBar_actions">
{ secondaryButton }
{ primaryButton }
</div> </div>
</div> </div>
); );

View file

@ -601,7 +601,7 @@
"This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers", "This room is not accessible by remote Matrix servers": "This room is not accessible by remote Matrix servers",
"Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version", "Upgrade this room to the recommended room version": "Upgrade this room to the recommended room version",
"this room": "this room", "this room": "this room",
"View older messages in %(roomName)s": "View older messages in %(roomName)s", "View older messages in %(roomName)s.": "View older messages in %(roomName)s.",
"Room information": "Room information", "Room information": "Room information",
"Internal room ID:": "Internal room ID:", "Internal room ID:": "Internal room ID:",
"Room version": "Room version", "Room version": "Room version",
@ -792,25 +792,36 @@
"Low priority": "Low priority", "Low priority": "Low priority",
"Historical": "Historical", "Historical": "Historical",
"System Alerts": "System Alerts", "System Alerts": "System Alerts",
"Joining room...": "Joining room...",
"Unable to ascertain that the address this invite was sent to matches one associated with your account.": "Unable to ascertain that the address this invite was sent to matches one associated with your account.",
"This invitation was sent to an email address which is not associated with this account:": "This invitation was sent to an email address which is not associated with this account:",
"You may wish to login with a different account, or add this email to this account.": "You may wish to login with a different account, or add this email to this account.",
"You have been invited to join this room by %(inviterName)s": "You have been invited to join this room by %(inviterName)s",
"Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?": "Would you like to <acceptText>accept</acceptText> or <declineText>decline</declineText> this invitation?",
"Reason: %(reasonText)s": "Reason: %(reasonText)s",
"Rejoin": "Rejoin",
"You have been kicked from %(roomName)s by %(userName)s.": "You have been kicked from %(roomName)s by %(userName)s.",
"You have been kicked from this room by %(userName)s.": "You have been kicked from this room by %(userName)s.",
"You have been banned from %(roomName)s by %(userName)s.": "You have been banned from %(roomName)s by %(userName)s.",
"You have been banned from this room by %(userName)s.": "You have been banned from this room by %(userName)s.",
"This room": "This room", "This room": "This room",
"Joining room …": "Joining room …",
"Loading …": "Loading …",
"Rejecting invite …": "Rejecting invite …",
"Join the conversation with an account": "Join the conversation with an account",
"Sign Up": "Sign Up",
"Sign In": "Sign In",
"You were kicked from %(roomName)s by %(memberName)s": "You were kicked from %(roomName)s by %(memberName)s",
"Reason: %(reason)s": "Reason: %(reason)s",
"Forget this room": "Forget this room",
"Re-join": "Re-join",
"You were banned from %(roomName)s by %(memberName)s": "You were banned from %(roomName)s by %(memberName)s",
"Something went wrong with your invite to this room": "Something went wrong with your invite to this room",
"%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.": "%(errcode)s was returned while trying to valide your invite. You could try to pass this information on to a room admin.",
"You can only join it with a working invite.": "You can only join it with a working invite.",
"You can still join it because this is a public room.": "You can still join it because this is a public room.",
"Join the discussion": "Join the discussion",
"Try to join anyway": "Try to join anyway",
"The room invite wasn't sent to your account": "The room invite wasn't sent to your account",
"Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.": "Sign in with a different account, ask for another invite, or add the e-mail address %(email)s to this account.",
"Do you want to join this room?": "Do you want to join this room?",
"<userName/> invited you": "<userName/> invited you",
"Reject": "Reject",
"You're previewing this room. Want to join it?": "You're previewing this room. Want to join it?",
"%(roomName)s can't be previewed. Do you want to join it?": "%(roomName)s can't be previewed. Do you want to join it?",
"%(roomName)s does not exist.": "%(roomName)s does not exist.", "%(roomName)s does not exist.": "%(roomName)s does not exist.",
"This room doesn't exist. Are you sure you're at the right place?": "This room doesn't exist. Are you sure you're at the right place?",
"%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.", "%(roomName)s is not accessible at this time.": "%(roomName)s is not accessible at this time.",
"You are trying to access %(roomName)s.": "You are trying to access %(roomName)s.", "Try again later, or ask a room admin to check if you have access.": "Try again later, or ask a room admin to check if you have access.",
"You are trying to access a room.": "You are trying to access a room.", "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.": "%(errcode)s was returned while trying to access the room. If you think you're seeing this message in error, please <issueLink>submit a bug report</issueLink>.",
"<a>Click here</a> to join the discussion!": "<a>Click here</a> to join the discussion!",
"This is a preview of this room. Room interactions have been disabled": "This is a preview of this room. Room interactions have been disabled",
"Use Key Backup": "Use Key Backup", "Use Key Backup": "Use Key Backup",
"Never lose encrypted messages": "Never lose encrypted messages", "Never lose encrypted messages": "Never lose encrypted messages",
"Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.", "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.": "Messages in this room are secured with end-to-end encryption. Only you and the recipient(s) have the keys to read these messages.",
@ -1245,7 +1256,6 @@
"Reject invitation": "Reject invitation", "Reject invitation": "Reject invitation",
"Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?", "Are you sure you want to reject the invitation?": "Are you sure you want to reject the invitation?",
"Unable to reject invite": "Unable to reject invite", "Unable to reject invite": "Unable to reject invite",
"Reject": "Reject",
"You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)", "You cannot delete this message. (%(code)s)": "You cannot delete this message. (%(code)s)",
"Resend": "Resend", "Resend": "Resend",
"Cancel Sending": "Cancel Sending", "Cancel Sending": "Cancel Sending",
@ -1279,7 +1289,6 @@
"Hide": "Hide", "Hide": "Hide",
"Home": "Home", "Home": "Home",
"Sign in": "Sign in", "Sign in": "Sign in",
"Login": "Login",
"powered by Matrix": "powered by Matrix", "powered by Matrix": "powered by Matrix",
"This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.", "This homeserver would like to make sure you are not a robot.": "This homeserver would like to make sure you are not a robot.",
"Custom Server Options": "Custom Server Options", "Custom Server Options": "Custom Server Options",

View file

@ -58,3 +58,8 @@ export function hashCode(str) {
} }
return Math.abs(hash); return Math.abs(hash);
} }
export function getUserNameColorClass(userId) {
const colorNumber = (hashCode(userId) % 8) + 1;
return `mx_Username_color${colorNumber}`;
}