Merge pull request #2478 from matrix-org/bwindels/jumptobottommakeover
Redesign: pull jump to bottom button out of room status bar
This commit is contained in:
commit
d803005d1c
7 changed files with 147 additions and 58 deletions
|
@ -98,6 +98,7 @@
|
||||||
@import "./views/rooms/_AuxPanel.scss";
|
@import "./views/rooms/_AuxPanel.scss";
|
||||||
@import "./views/rooms/_EntityTile.scss";
|
@import "./views/rooms/_EntityTile.scss";
|
||||||
@import "./views/rooms/_EventTile.scss";
|
@import "./views/rooms/_EventTile.scss";
|
||||||
|
@import "./views/rooms/_JumpToBottomButton.scss";
|
||||||
@import "./views/rooms/_LinkPreviewWidget.scss";
|
@import "./views/rooms/_LinkPreviewWidget.scss";
|
||||||
@import "./views/rooms/_MemberDeviceInfo.scss";
|
@import "./views/rooms/_MemberDeviceInfo.scss";
|
||||||
@import "./views/rooms/_MemberInfo.scss";
|
@import "./views/rooms/_MemberInfo.scss";
|
||||||
|
|
69
res/css/views/rooms/_JumpToBottomButton.scss
Normal file
69
res/css/views/rooms/_JumpToBottomButton.scss
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@charset "utf-8";
|
||||||
|
|
||||||
|
.mx_JumpToBottomButton {
|
||||||
|
z-index: 1000;
|
||||||
|
position: absolute;
|
||||||
|
// 12 because height is 50 but button is only 38 = 12+(50-38) = 24
|
||||||
|
bottom: 12px;
|
||||||
|
right: 24px;
|
||||||
|
width: 38px;
|
||||||
|
// give it a fixed height so the badge doesn't make
|
||||||
|
// it taller and pop upwards when visible
|
||||||
|
height: 50px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_JumpToBottomButton_badge {
|
||||||
|
position: relative;
|
||||||
|
top: -12px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 14px;
|
||||||
|
text-align: center;
|
||||||
|
// to be able to get it centered
|
||||||
|
// with text-align in parent
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0 4px;
|
||||||
|
color: $secondary-accent-color;
|
||||||
|
background-color: $warning-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_JumpToBottomButton_scrollDown {
|
||||||
|
position: relative;
|
||||||
|
height: 38px;
|
||||||
|
border-radius: 19px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: $primary-bg-color;
|
||||||
|
border: 1.3px solid $roomtile-name-color;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mx_JumpToBottomButton_scrollDown:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
mask: url('$(res)/img/icon-jump-to-bottom.svg');
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
mask-position: 9px 14px;
|
||||||
|
background: $roomtile-name-color;
|
||||||
|
}
|
32
res/img/icon-jump-to-bottom.svg
Normal file
32
res/img/icon-jump-to-bottom.svg
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
id="svg4"
|
||||||
|
width="18.666187"
|
||||||
|
height="8.7375822"
|
||||||
|
viewBox="0 0 18.666187 8.7375818"
|
||||||
|
version="1.1"
|
||||||
|
style="fill:none">
|
||||||
|
<metadata
|
||||||
|
id="metadata8">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
<dc:title></dc:title>
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<path
|
||||||
|
id="path2-8-3"
|
||||||
|
style="fill:none;stroke:#000000;stroke-width:1.29999995;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 17.909095,0.75599092 9.3330939,7.987602 0.75709259,0.75599092" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1,016 B |
|
@ -45,14 +45,6 @@ module.exports = React.createClass({
|
||||||
propTypes: {
|
propTypes: {
|
||||||
// the room this statusbar is representing.
|
// the room this statusbar is representing.
|
||||||
room: PropTypes.object.isRequired,
|
room: PropTypes.object.isRequired,
|
||||||
|
|
||||||
// the number of messages which have arrived since we've been scrolled up
|
|
||||||
numUnreadMessages: PropTypes.number,
|
|
||||||
|
|
||||||
// this is true if we are fully scrolled-down, and are looking at
|
|
||||||
// the end of the live timeline.
|
|
||||||
atEndOfLiveTimeline: PropTypes.bool,
|
|
||||||
|
|
||||||
// This is true when the user is alone in the room, but has also sent a message.
|
// This is true when the user is alone in the room, but has also sent a message.
|
||||||
// Used to suggest to the user to invite someone
|
// Used to suggest to the user to invite someone
|
||||||
sentMessageAndIsAlone: PropTypes.bool,
|
sentMessageAndIsAlone: PropTypes.bool,
|
||||||
|
@ -82,9 +74,6 @@ module.exports = React.createClass({
|
||||||
// 'you are alone' bar
|
// 'you are alone' bar
|
||||||
onStopWarningClick: PropTypes.func,
|
onStopWarningClick: PropTypes.func,
|
||||||
|
|
||||||
// callback for when the user clicks on the 'scroll to bottom' button
|
|
||||||
onScrollToBottomClick: PropTypes.func,
|
|
||||||
|
|
||||||
// callback for when we do something that changes the size of the
|
// callback for when we do something that changes the size of the
|
||||||
// status bar. This is used to trigger a re-layout in the parent
|
// status bar. This is used to trigger a re-layout in the parent
|
||||||
// component.
|
// component.
|
||||||
|
@ -180,8 +169,6 @@ module.exports = React.createClass({
|
||||||
// indicate other sizes.
|
// indicate other sizes.
|
||||||
_getSize: function() {
|
_getSize: function() {
|
||||||
if (this._shouldShowConnectionError() ||
|
if (this._shouldShowConnectionError() ||
|
||||||
this.props.numUnreadMessages ||
|
|
||||||
!this.props.atEndOfLiveTimeline ||
|
|
||||||
this.props.hasActiveCall ||
|
this.props.hasActiveCall ||
|
||||||
this.props.sentMessageAndIsAlone
|
this.props.sentMessageAndIsAlone
|
||||||
) {
|
) {
|
||||||
|
@ -194,28 +181,6 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
// return suitable content for the image on the left of the status bar.
|
// return suitable content for the image on the left of the status bar.
|
||||||
_getIndicator: function() {
|
_getIndicator: function() {
|
||||||
if (this.props.numUnreadMessages) {
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomStatusBar_scrollDownIndicator"
|
|
||||||
onClick={this.props.onScrollToBottomClick}>
|
|
||||||
<img src={require("../../../res/img/newmessages.svg")} width="24" height="24"
|
|
||||||
alt="" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
|
|
||||||
if (!this.props.atEndOfLiveTimeline) {
|
|
||||||
return (
|
|
||||||
<AccessibleButton className="mx_RoomStatusBar_scrollDownIndicator"
|
|
||||||
onClick={this.props.onScrollToBottomClick}>
|
|
||||||
<img src={require("../../../res/img/scrolldown.svg")} width="24" height="24"
|
|
||||||
alt={_t("Scroll to bottom of page")}
|
|
||||||
title={_t("Scroll to bottom of page")} />
|
|
||||||
</AccessibleButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.hasActiveCall) {
|
if (this.props.hasActiveCall) {
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
return (
|
return (
|
||||||
|
@ -231,9 +196,7 @@ module.exports = React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_shouldShowConnectionError: function() {
|
_shouldShowConnectionError: function() {
|
||||||
// no conn bar trumps unread count since you can't get unread messages
|
// no conn bar trumps the "some not sent" msg since you can't resend without
|
||||||
// without a connection! (technically may already have some but meh)
|
|
||||||
// It also trumps the "some not sent" msg since you can't resend without
|
|
||||||
// a connection!
|
// a connection!
|
||||||
// There's one situation in which we don't show this 'no connection' bar, and that's
|
// There's one situation in which we don't show this 'no connection' bar, and that's
|
||||||
// if it's a resource limit exceeded error: those are shown in the top bar.
|
// if it's a resource limit exceeded error: those are shown in the top bar.
|
||||||
|
@ -363,20 +326,6 @@ module.exports = React.createClass({
|
||||||
return this._getUnsentMessageContent();
|
return this._getUnsentMessageContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
// unread count trumps who is typing since the unread count is only
|
|
||||||
// set when you've scrolled up
|
|
||||||
if (this.props.numUnreadMessages) {
|
|
||||||
// MUST use var name "count" for pluralization to kick in
|
|
||||||
const unreadMsgs = _t("%(count)s new messages", {count: this.props.numUnreadMessages});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomStatusBar_unreadMessagesBar"
|
|
||||||
onClick={this.props.onScrollToBottomClick}>
|
|
||||||
{ unreadMsgs }
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.props.hasActiveCall) {
|
if (this.props.hasActiveCall) {
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomStatusBar_callBar">
|
<div className="mx_RoomStatusBar_callBar">
|
||||||
|
|
|
@ -1473,11 +1473,10 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
onStatusBarHidden: function() {
|
onStatusBarHidden: function() {
|
||||||
// This is currently not desired as it is annoying if it keeps expanding and collapsing
|
// This is currently not desired as it is annoying if it keeps expanding and collapsing
|
||||||
// TODO: Find a less annoying way of hiding the status bar
|
if (this.unmounted) return;
|
||||||
/*if (this.unmounted) return;
|
|
||||||
this.setState({
|
this.setState({
|
||||||
statusBarVisible: false,
|
statusBarVisible: false,
|
||||||
});*/
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1651,14 +1650,11 @@ module.exports = React.createClass({
|
||||||
isStatusAreaExpanded = this.state.statusBarVisible;
|
isStatusAreaExpanded = this.state.statusBarVisible;
|
||||||
statusBar = <RoomStatusBar
|
statusBar = <RoomStatusBar
|
||||||
room={this.state.room}
|
room={this.state.room}
|
||||||
numUnreadMessages={this.state.numUnreadMessages}
|
|
||||||
atEndOfLiveTimeline={this.state.atEndOfLiveTimeline}
|
|
||||||
sentMessageAndIsAlone={this.state.isAlone}
|
sentMessageAndIsAlone={this.state.isAlone}
|
||||||
hasActiveCall={inCall}
|
hasActiveCall={inCall}
|
||||||
isPeeking={myMembership !== "join"}
|
isPeeking={myMembership !== "join"}
|
||||||
onInviteClick={this.onInviteButtonClick}
|
onInviteClick={this.onInviteButtonClick}
|
||||||
onStopWarningClick={this.onStopAloneWarningClick}
|
onStopWarningClick={this.onStopAloneWarningClick}
|
||||||
onScrollToBottomClick={this.jumpToLiveTimeline}
|
|
||||||
onResize={this.onChildResize}
|
onResize={this.onChildResize}
|
||||||
onVisible={this.onStatusBarVisible}
|
onVisible={this.onStatusBarVisible}
|
||||||
onHidden={this.onStatusBarHidden}
|
onHidden={this.onStatusBarHidden}
|
||||||
|
@ -1864,6 +1860,14 @@ module.exports = React.createClass({
|
||||||
onCloseClick={this.forgetReadMarker}
|
onCloseClick={this.forgetReadMarker}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
let jumpToBottom;
|
||||||
|
if (!this.state.atEndOfLiveTimeline) {
|
||||||
|
const JumpToBottomButton = sdk.getComponent('rooms.JumpToBottomButton');
|
||||||
|
jumpToBottom = (<JumpToBottomButton
|
||||||
|
numUnreadMessages={this.state.numUnreadMessages}
|
||||||
|
onScrollToBottomClick={this.jumpToLiveTimeline}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
const statusBarAreaClass = classNames(
|
const statusBarAreaClass = classNames(
|
||||||
"mx_RoomView_statusArea",
|
"mx_RoomView_statusArea",
|
||||||
{
|
{
|
||||||
|
@ -1901,6 +1905,7 @@ module.exports = React.createClass({
|
||||||
{ auxPanel }
|
{ auxPanel }
|
||||||
<div className="mx_RoomView_timeline">
|
<div className="mx_RoomView_timeline">
|
||||||
{ topUnreadMessagesBar }
|
{ topUnreadMessagesBar }
|
||||||
|
{ jumpToBottom }
|
||||||
{ messagePanel }
|
{ messagePanel }
|
||||||
{ searchResultsPanel }
|
{ searchResultsPanel }
|
||||||
</div>
|
</div>
|
||||||
|
|
32
src/components/views/rooms/JumpToBottomButton.js
Normal file
32
src/components/views/rooms/JumpToBottomButton.js
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
Copyright 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { _t } from '../../../languageHandler';
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
|
||||||
|
export default (props) => {
|
||||||
|
let badge;
|
||||||
|
if (props.numUnreadMessages) {
|
||||||
|
badge = (<div className="mx_JumpToBottomButton_badge">{props.numUnreadMessages}</div>);
|
||||||
|
}
|
||||||
|
return (<div className="mx_JumpToBottomButton">
|
||||||
|
<AccessibleButton className="mx_JumpToBottomButton_scrollDown"
|
||||||
|
title={_t("Scroll to bottom of page")}
|
||||||
|
onClick={props.onScrollToBottomClick}>
|
||||||
|
</AccessibleButton>
|
||||||
|
{ badge }
|
||||||
|
</div>);
|
||||||
|
};
|
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2016 OpenMarket Ltd
|
Copyright 2016 OpenMarket Ltd
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 Vector Creations Ltd
|
||||||
|
Copyright 2019 New Vector Ltd
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue