Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Redesign: typing notifications in timeline #2276

Merged
merged 11 commits into from
Nov 14, 2018
1 change: 1 addition & 0 deletions res/css/_components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
@import "./views/rooms/_SearchableEntityList.scss";
@import "./views/rooms/_Stickers.scss";
@import "./views/rooms/_TopUnreadMessagesBar.scss";
@import "./views/rooms/_WhoIsTypingTile.scss";
@import "./views/settings/_DevicesPanel.scss";
@import "./views/settings/_IntegrationsManager.scss";
@import "./views/settings/_Notifications.scss";
Expand Down
77 changes: 77 additions & 0 deletions res/css/views/rooms/_WhoIsTypingTile.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright 2018 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.
*/

.mx_WhoIsTypingTile {
margin-left: -18px; //offset padding from mx_RoomView_MessageList to center avatars
padding-top: 18px;
display: flex;
align-items: center;
}

/* position the indicator in the same place horizontally as .mx_EventTile_avatar. */
.mx_WhoIsTypingTile_avatars {
flex: 0 0 83px; // 18 + 65
text-align: center;
}

.mx_WhoIsTypingTile_avatars > :not(:first-child) {
margin-left: -12px;
}

.mx_WhoIsTypingTile_avatars .mx_BaseAvatar_image {
border: 1px solid $primary-bg-color;
}

.mx_WhoIsTypingTile_avatars .mx_BaseAvatar_initial {
padding-top: 1px;
}

.mx_WhoIsTypingTile_remainingAvatarPlaceholder {
display: inline-block;
color: #acacac;
background-color: #ddd;
border: 1px solid $primary-bg-color;
border-radius: 40px;
width: 24px;
height: 24px;
line-height: 24px;
font-size: 0.8em;
vertical-align: top;
text-align: center;
}

.mx_WhoIsTypingTile_label {
flex: 1;
font-size: 14px;
font-weight: 600;
color: $eventtile-meta-color;
}

.mx_WhoIsTypingTile_label > span {
background-image: url('../../img/typing-indicator-2x.gif');
background-size: 25px;
background-position: left bottom;
background-repeat: no-repeat;
padding-bottom: 15px;
display: block;
}

.mx_MatrixChat_useCompactLayout {

.mx_WhoIsTypingTile {
padding-top: 4px;
}
}
Binary file added res/img/typing-indicator-2x.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions res/themes/dharma/css/_dharma.scss
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ $topleftmenu-color: #212121;
$roomheader-color: #45474a;
$roomheader-addroom-color: #929eb4;
$roomtopic-color: #9fa9ba;
$eventtile-meta-color: $roomtopic-color;

// ********************

Expand Down
2 changes: 1 addition & 1 deletion res/themes/light/css/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ $topleftmenu-color: $primary-fg-color;
$roomheader-color: $primary-fg-color;
$roomheader-addroom-color: $primary-bg-color;
$roomtopic-color: $settings-grey-fg-color;

$eventtile-meta-color: $roomtopic-color;
// ********************

$roomtile-name-color: rgba(69, 69, 69, 0.8);
Expand Down
6 changes: 3 additions & 3 deletions src/WhoIsTyping.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,16 @@ module.exports = {
if (whoIsTyping.length == 0) {
return '';
} else if (whoIsTyping.length == 1) {
return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name});
return _t('%(displayName)s is typing', {displayName: whoIsTyping[0].name});
}
const names = whoIsTyping.map(function(m) {
return m.name;
});
if (othersCount>=1) {
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
return _t('%(names)s and %(count)s others are typing', {names: names.slice(0, limit - 1).join(', '), count: othersCount});
} else {
const lastPerson = names.pop();
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
return _t('%(names)s and %(lastPerson)s are typing', {names: names.join(', '), lastPerson: lastPerson});
}
},
};
9 changes: 9 additions & 0 deletions src/components/structures/MessagePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,12 +631,20 @@ module.exports = React.createClass({
}
},

_scrollDownIfAtBottom: function() {
const scrollPanel = this.refs.scrollPanel;
if (scrollPanel) {
scrollPanel.checkScroll();
}
},

onResize: function() {
dis.dispatch({ action: 'timeline_resize' }, true);
},

render: function() {
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
const Spinner = sdk.getComponent("elements.Spinner");
let topSpinner;
let bottomSpinner;
Expand Down Expand Up @@ -666,6 +674,7 @@ module.exports = React.createClass({
stickyBottom={this.props.stickyBottom}>
{ topSpinner }
{ this._getEventTiles() }
<WhoIsTypingTile room={this.props.room} onVisible={this._scrollDownIfAtBottom} />
{ bottomSpinner }
</ScrollPanel>
);
Expand Down
79 changes: 2 additions & 77 deletions src/components/structures/RoomStatusBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ module.exports = React.createClass({
// more interesting)
hasActiveCall: PropTypes.bool,

// Number of names to display in typing indication. E.g. set to 3, will
// result in "X, Y, Z and 100 others are typing."
whoIsTypingLimit: PropTypes.number,

// true if the room is being peeked at. This affects components that shouldn't
// logically be shown when peeking, such as a prompt to invite people to a room.
isPeeking: PropTypes.bool,
Expand Down Expand Up @@ -103,24 +99,16 @@ module.exports = React.createClass({
onVisible: PropTypes.func,
},

getDefaultProps: function() {
return {
whoIsTypingLimit: 3,
};
},

getInitialState: function() {
return {
syncState: MatrixClientPeg.get().getSyncState(),
syncStateData: MatrixClientPeg.get().getSyncStateData(),
usersTyping: WhoIsTyping.usersTypingApartFromMe(this.props.room),
unsentMessages: getUnsentMessages(this.props.room),
};
},

componentWillMount: function() {
MatrixClientPeg.get().on("sync", this.onSyncStateChange);
MatrixClientPeg.get().on("RoomMember.typing", this.onRoomMemberTyping);
MatrixClientPeg.get().on("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);

this._checkSize();
Expand All @@ -135,7 +123,6 @@ module.exports = React.createClass({
const client = MatrixClientPeg.get();
if (client) {
client.removeListener("sync", this.onSyncStateChange);
client.removeListener("RoomMember.typing", this.onRoomMemberTyping);
client.removeListener("Room.localEchoUpdated", this._onRoomLocalEchoUpdated);
}
},
Expand All @@ -150,12 +137,6 @@ module.exports = React.createClass({
});
},

onRoomMemberTyping: function(ev, member) {
this.setState({
usersTyping: WhoIsTyping.usersTypingApartFromMeAndIgnored(this.props.room),
});
},

_onSendWithoutVerifyingClick: function() {
cryptodevices.getUnknownDevicesForRoom(MatrixClientPeg.get(), this.props.room).then((devices) => {
cryptodevices.markAllDevicesKnown(MatrixClientPeg.get(), devices);
Expand Down Expand Up @@ -199,7 +180,6 @@ module.exports = React.createClass({
// indicate other sizes.
_getSize: function() {
if (this._shouldShowConnectionError() ||
(this.state.usersTyping.length > 0) ||
this.props.numUnreadMessages ||
!this.props.atEndOfLiveTimeline ||
this.props.hasActiveCall ||
Expand All @@ -213,10 +193,7 @@ module.exports = React.createClass({
},

// return suitable content for the image on the left of the status bar.
//
// if wantPlaceholder is true, we include a "..." placeholder if
// there is nothing better to put in.
_getIndicator: function(wantPlaceholder) {
_getIndicator: function() {
if (this.props.numUnreadMessages) {
return (
<div className="mx_RoomStatusBar_scrollDownIndicator"
Expand Down Expand Up @@ -250,49 +227,9 @@ module.exports = React.createClass({
return null;
}

if (wantPlaceholder) {
return (
<div className="mx_RoomStatusBar_typingIndicatorAvatars">
{ this._renderTypingIndicatorAvatars(this.props.whoIsTypingLimit) }
</div>
);
}

return null;
},

_renderTypingIndicatorAvatars: function(limit) {
let users = this.state.usersTyping;

let othersCount = 0;
if (users.length > limit) {
othersCount = users.length - limit + 1;
users = users.slice(0, limit - 1);
}

const avatars = users.map((u) => {
return (
<MemberAvatar
key={u.userId}
member={u}
width={24}
height={24}
resizeMethod="crop"
/>
);
});

if (othersCount > 0) {
avatars.push(
<span className="mx_RoomStatusBar_typingIndicatorRemaining" key="others">
+{ othersCount }
</span>,
);
}

return avatars;
},

_shouldShowConnectionError: function() {
// no conn bar trumps unread count since you can't get unread messages
// without a connection! (technically may already have some but meh)
Expand Down Expand Up @@ -440,18 +377,6 @@ module.exports = React.createClass({
);
}

const typingString = WhoIsTyping.whoIsTypingString(
this.state.usersTyping,
this.props.whoIsTypingLimit,
);
if (typingString) {
return (
<div className="mx_RoomStatusBar_typingBar">
<EmojiText>{ typingString }</EmojiText>
</div>
);
}

if (this.props.hasActiveCall) {
return (
<div className="mx_RoomStatusBar_callBar">
Expand Down Expand Up @@ -483,7 +408,7 @@ module.exports = React.createClass({

render: function() {
const content = this._getContent();
const indicator = this._getIndicator(this.state.usersTyping.length > 0);
const indicator = this._getIndicator();

return (
<div className="mx_RoomStatusBar">
Expand Down
1 change: 0 additions & 1 deletion src/components/structures/RoomView.js
Original file line number Diff line number Diff line change
Expand Up @@ -1613,7 +1613,6 @@ module.exports = React.createClass({
onResize={this.onChildResize}
onVisible={this.onStatusBarVisible}
onHidden={this.onStatusBarHidden}
whoIsTypingLimit={3}
/>;
}

Expand Down
1 change: 1 addition & 0 deletions src/components/structures/TimelinePanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,7 @@ var TimelinePanel = React.createClass({
);
return (
<MessagePanel ref="messagePanel"
room={this.props.timelineSet.room}
hidden={this.props.hidden}
backPaginating={this.state.backPaginating}
forwardPaginating={forwardPaginating}
Expand Down
Loading