From 8d60d85570e57ad12bc802c4c1f88a0f8a0dc260 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 8 Apr 2021 09:27:41 +0100 Subject: [PATCH 01/22] replace velocity-animate with CSS transitions --- package.json | 1 - res/css/views/rooms/_EventTile.scss | 4 + src/Velociraptor.js | 79 +++++++++++-------- src/VelocityBounce.js | 17 ---- .../views/rooms/ReadReceiptMarker.js | 38 +-------- yarn.lock | 5 -- 6 files changed, 50 insertions(+), 94 deletions(-) delete mode 100644 src/VelocityBounce.js diff --git a/package.json b/package.json index 6a8645adf3..2f1a96eadd 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,6 @@ "tar-js": "^0.3.0", "text-encoding-utf-8": "^1.0.2", "url": "^0.11.0", - "velocity-animate": "^2.0.6", "what-input": "^5.2.10", "zxcvbn": "^4.4.2" }, diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index 028d9a7556..a82c894ac5 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -282,6 +282,10 @@ $left-gutter: 64px; display: inline-block; height: $font-14px; width: $font-14px; + + transition: + left .1s ease-out, + top .3s ease-out; } .mx_EventTile_readAvatarRemainder { diff --git a/src/Velociraptor.js b/src/Velociraptor.js index 2da54babe5..4fd9a23c82 100644 --- a/src/Velociraptor.js +++ b/src/Velociraptor.js @@ -1,6 +1,5 @@ import React from "react"; import ReactDom from "react-dom"; -import Velocity from "velocity-animate"; import PropTypes from 'prop-types'; /** @@ -20,14 +19,10 @@ export default class Velociraptor extends React.Component { // a list of state objects to apply to each child node in turn startStyles: PropTypes.array, - - // a list of transition options from the corresponding startStyle - enterTransitionOpts: PropTypes.array, }; static defaultProps = { startStyles: [], - enterTransitionOpts: [], }; constructor(props) { @@ -41,6 +36,25 @@ export default class Velociraptor extends React.Component { this._updateChildren(this.props.children); } + /** + * + * @param {HTMLElement} node element to apply styles to + * @param {object} styles a key/value pair of CSS properties + * @returns {Promise} promise resolving when the applied styles have finished transitioning + */ + _applyStyles(node, styles) { + Object.entries(styles).forEach(([property, value]) => { + node.style[property] = value; + }); + const transitionEndPromise = new Promise(resolve => { + node.addEventListener("transitionend", () => { + resolve(); + }, { once: true }); + }); + + return Promise.race([timeout(300), transitionEndPromise]); + } + _updateChildren(newChildren) { const oldChildren = this.children || {}; this.children = {}; @@ -50,14 +64,16 @@ export default class Velociraptor extends React.Component { const oldNode = ReactDom.findDOMNode(this.nodes[old.key]); if (oldNode && oldNode.style.left !== c.props.style.left) { - Velocity(oldNode, { left: c.props.style.left }, this.props.transition).then(() => { - // special case visibility because it's nonsensical to animate an invisible element - // so we always hidden->visible pre-transition and visible->hidden after - if (oldNode.style.visibility === 'visible' && c.props.style.visibility === 'hidden') { - oldNode.style.visibility = c.props.style.visibility; - } - }); - //console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); + this._applyStyles(oldNode, { left: c.props.style.left }) + .then(() => { + // special case visibility because it's nonsensical to animate an invisible element + // so we always hidden->visible pre-transition and visible->hidden after + if (oldNode.style.visibility === 'visible' && c.props.style.visibility === 'hidden') { + oldNode.style.visibility = c.props.style.visibility; + } + }); + + console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); } if (oldNode && oldNode.style.visibility === 'hidden' && c.props.style.visibility === 'visible') { oldNode.style.visibility = c.props.style.visibility; @@ -94,33 +110,22 @@ export default class Velociraptor extends React.Component { this.props.startStyles.length > 0 ) { const startStyles = this.props.startStyles; - const transitionOpts = this.props.enterTransitionOpts; const domNode = ReactDom.findDOMNode(node); // start from startStyle 1: 0 is the one we gave it // to start with, so now we animate 1 etc. - for (var i = 1; i < startStyles.length; ++i) { - Velocity(domNode, startStyles[i], transitionOpts[i-1]); - /* - console.log("start:", - JSON.stringify(transitionOpts[i-1]), - "->", - JSON.stringify(startStyles[i]), - ); - */ + for (let i = 1; i < startStyles.length; ++i) { + this._applyStyles(domNode, startStyles[i]); + // console.log("start:" + // JSON.stringify(startStyles[i]), + // ); } // and then we animate to the resting state - Velocity(domNode, restingStyle, - transitionOpts[i-1]) - .then(() => { - // once we've reached the resting state, hide the element if - // appropriate - domNode.style.visibility = restingStyle.visibility; - }); + setTimeout(() => { + this._applyStyles(domNode, restingStyle); + }, 0); // console.log("enter:", - // JSON.stringify(transitionOpts[i-1]), - // "->", // JSON.stringify(restingStyle)); } this.nodes[k] = node; @@ -128,9 +133,13 @@ export default class Velociraptor extends React.Component { render() { return ( - - { Object.values(this.children) } - + <>{ Object.values(this.children) } ); } } + +function timeout(time) { + return new Promise(resolve => + setTimeout(() => resolve(), time), + ); +} diff --git a/src/VelocityBounce.js b/src/VelocityBounce.js deleted file mode 100644 index ffbf7de829..0000000000 --- a/src/VelocityBounce.js +++ /dev/null @@ -1,17 +0,0 @@ -import Velocity from "velocity-animate"; - -// courtesy of https://github.com/julianshapiro/velocity/issues/283 -// We only use easeOutBounce (easeInBounce is just sort of nonsensical) -function bounce( p ) { - let pow2; - let bounce = 4; - - while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) { - // just sets pow2 - } - return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 ); -} - -Velocity.Easings.easeOutBounce = function(p) { - return 1 - bounce(1 - p); -}; diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 7473aac7cd..cf5abeec63 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -17,7 +17,6 @@ limitations under the License. import React, {createRef} from 'react'; import PropTypes from 'prop-types'; -import '../../../VelocityBounce'; import { _t } from '../../../languageHandler'; import {formatDate} from '../../../DateUtils'; import Velociraptor from "../../../Velociraptor"; @@ -25,14 +24,6 @@ import * as sdk from "../../../index"; import {toPx} from "../../../utils/units"; import {replaceableComponent} from "../../../utils/replaceableComponent"; -let bounce = false; -try { - if (global.localStorage) { - bounce = global.localStorage.getItem('avatar_bounce') == 'true'; - } -} catch (e) { -} - @replaceableComponent("views.rooms.ReadReceiptMarker") export default class ReadReceiptMarker extends React.PureComponent { static propTypes = { @@ -139,42 +130,18 @@ export default class ReadReceiptMarker extends React.PureComponent { } const startStyles = []; - const enterTransitionOpts = []; if (oldInfo && oldInfo.left) { // start at the old height and in the old h pos - startStyles.push({ top: startTopOffset+"px", left: toPx(oldInfo.left) }); - - const reorderTransitionOpts = { - duration: 100, - easing: 'easeOut', - }; - - enterTransitionOpts.push(reorderTransitionOpts); } - // then shift to the rightmost column, - // and then it will drop down to its resting position - // - // XXX: We use a small left value to trick velocity-animate into actually animating. - // This is a very annoying bug where if it thinks there's no change to `left` then it'll - // skip applying it, thus making our read receipt at +14px instead of +0px like it - // should be. This does cause a tiny amount of drift for read receipts, however with a - // value so small it's not perceived by a user. - // Note: Any smaller values (or trying to interchange units) might cause read receipts to - // fail to fall down or cause gaps. - startStyles.push({ top: startTopOffset+'px', left: '1px' }); - enterTransitionOpts.push({ - duration: bounce ? Math.min(Math.log(Math.abs(startTopOffset)) * 200, 3000) : 300, - easing: bounce ? 'easeOutBounce' : 'easeOutCubic', - }); + startStyles.push({ top: startTopOffset+'px', left: '0' }); this.setState({ suppressDisplay: false, startStyles: startStyles, - enterTransitionOpts: enterTransitionOpts, }); } @@ -211,8 +178,7 @@ export default class ReadReceiptMarker extends React.PureComponent { return ( + startStyles={this.state.startStyles} > Date: Thu, 8 Apr 2021 10:36:38 +0100 Subject: [PATCH 02/22] Animate read receipts for all component updates --- src/components/views/rooms/ReadReceiptMarker.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index cf5abeec63..2820cea7db 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -106,7 +106,18 @@ export default class ReadReceiptMarker extends React.PureComponent { // we've already done our display - nothing more to do. return; } + this._animateMarker(); + } + componentDidUpdate(prevProps) { + const differentLeftOffset = prevProps.leftOffset !== this.props.leftOffset; + const visibilityChanged = prevProps.hidden !== this.props.hidden; + if (differentLeftOffset || visibilityChanged) { + this._animateMarker(); + } + } + + _animateMarker() { // treat new RRs as though they were off the top of the screen let oldTop = -15; From 1d75726a758a5a43073fdfea8b714051b34c930c Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 8 Apr 2021 11:05:45 +0100 Subject: [PATCH 03/22] Honour prefers reduced motion for read receipts --- res/css/_common.scss | 10 ++++++++++ res/css/views/rooms/_EventTile.scss | 4 ++-- src/Velociraptor.js | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/res/css/_common.scss b/res/css/_common.scss index 0093bde0ab..0b363edaee 100644 --- a/res/css/_common.scss +++ b/res/css/_common.scss @@ -28,6 +28,16 @@ $MessageTimestamp_width_hover: calc($MessageTimestamp_width - 2 * $EventTile_e2e :root { font-size: 10px; + + --transition-short: .1s; + --transition-standard: .3s; +} + +@media (prefers-reduced-motion) { + :root { + --transition-short: 0; + --transition-standard: 0; + } } html { diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index a82c894ac5..f455931f08 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -284,8 +284,8 @@ $left-gutter: 64px; width: $font-14px; transition: - left .1s ease-out, - top .3s ease-out; + left var(--transition-short) ease-out, + top var(--transition-standard) ease-out; } .mx_EventTile_readAvatarRemainder { diff --git a/src/Velociraptor.js b/src/Velociraptor.js index 4fd9a23c82..c453f56fdb 100644 --- a/src/Velociraptor.js +++ b/src/Velociraptor.js @@ -73,7 +73,7 @@ export default class Velociraptor extends React.Component { } }); - console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); + // console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); } if (oldNode && oldNode.style.visibility === 'hidden' && c.props.style.visibility === 'visible') { oldNode.style.visibility = c.props.style.visibility; From bf34e37dcc8ad36c521020e821391520f49176b4 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 8 Apr 2021 11:43:13 +0100 Subject: [PATCH 04/22] fix hiding read receipts animation --- src/Velociraptor.js | 28 ++----------------- .../views/rooms/ReadReceiptMarker.js | 1 - 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/src/Velociraptor.js b/src/Velociraptor.js index c453f56fdb..1592f4be06 100644 --- a/src/Velociraptor.js +++ b/src/Velociraptor.js @@ -40,19 +40,12 @@ export default class Velociraptor extends React.Component { * * @param {HTMLElement} node element to apply styles to * @param {object} styles a key/value pair of CSS properties - * @returns {Promise} promise resolving when the applied styles have finished transitioning + * @returns {void} */ _applyStyles(node, styles) { Object.entries(styles).forEach(([property, value]) => { node.style[property] = value; }); - const transitionEndPromise = new Promise(resolve => { - node.addEventListener("transitionend", () => { - resolve(); - }, { once: true }); - }); - - return Promise.race([timeout(300), transitionEndPromise]); } _updateChildren(newChildren) { @@ -64,20 +57,9 @@ export default class Velociraptor extends React.Component { const oldNode = ReactDom.findDOMNode(this.nodes[old.key]); if (oldNode && oldNode.style.left !== c.props.style.left) { - this._applyStyles(oldNode, { left: c.props.style.left }) - .then(() => { - // special case visibility because it's nonsensical to animate an invisible element - // so we always hidden->visible pre-transition and visible->hidden after - if (oldNode.style.visibility === 'visible' && c.props.style.visibility === 'hidden') { - oldNode.style.visibility = c.props.style.visibility; - } - }); - + this._applyStyles(oldNode, { left: c.props.style.left }); // console.log("translation: "+oldNode.style.left+" -> "+c.props.style.left); } - if (oldNode && oldNode.style.visibility === 'hidden' && c.props.style.visibility === 'visible') { - oldNode.style.visibility = c.props.style.visibility; - } // clone the old element with the props (and children) of the new element // so prop updates are still received by the children. this.children[c.key] = React.cloneElement(old, c.props, c.props.children); @@ -137,9 +119,3 @@ export default class Velociraptor extends React.Component { ); } } - -function timeout(time) { - return new Promise(resolve => - setTimeout(() => resolve(), time), - ); -} diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index 2820cea7db..e2b95a7ada 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -165,7 +165,6 @@ export default class ReadReceiptMarker extends React.PureComponent { const style = { left: toPx(this.props.leftOffset), top: '0px', - visibility: this.props.hidden ? 'hidden' : 'visible', }; let title; From 8d95c012ef7bba98795322742756f80da81bd9bf Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 14 Apr 2021 08:44:33 +0100 Subject: [PATCH 05/22] refactor _startDm invite flow to use async/await --- src/components/views/dialogs/InviteDialog.tsx | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 60f783e889..aabc8b59e3 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -656,35 +656,30 @@ export default class InviteDialog extends React.PureComponent; - const isSelf = targetIds.length === 1 && targetIds[0] === MatrixClientPeg.get().getUserId(); - if (targetIds.length === 1 && !isSelf) { - createRoomOptions.dmUserId = targetIds[0]; - createRoomPromise = createRoom(createRoomOptions); - } else if (isSelf) { - createRoomPromise = createRoom(createRoomOptions); - } else { - // Create a boring room and try to invite the targets manually. - createRoomPromise = createRoom(createRoomOptions).then(roomId => { - return inviteMultipleToRoom(roomId, targetIds); - }).then(result => { - if (this._shouldAbortAfterInviteError(result)) { - return true; // abort - } - }); - } - // the createRoom call will show the room for us, so we don't need to worry about that. - createRoomPromise.then(abort => { - if (abort === true) return; // only abort on true booleans, not roomIds or something - this.props.onFinished(); - }).catch(err => { + try { + const isSelf = targetIds.length === 1 && targetIds[0] === MatrixClientPeg.get().getUserId(); + if (targetIds.length === 1 && !isSelf) { + createRoomOptions.dmUserId = targetIds[0]; + await createRoom(createRoomOptions); + } else if (isSelf) { + await createRoom(createRoomOptions); + } else { + const roomId = await createRoom(createRoomOptions); + const invitesState = await inviteMultipleToRoom(roomId, targetIds); + + const abort = this._shouldAbortAfterInviteError(invitesState); + if (abort === false) { + this.props.onFinished(); + } + } + } catch (err) { console.error(err); this.setState({ busy: false, errorText: _t("We couldn't create your DM."), }); - }); + } }; _inviteUsers = async () => { From f89bbea3f1678754f2ef9b6e1eb2f8f178350f04 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 14 Apr 2021 09:03:47 +0100 Subject: [PATCH 06/22] Ensure room is synced with account before sending invites --- src/components/views/dialogs/InviteDialog.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index aabc8b59e3..cbdfdcbdc8 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -618,13 +618,14 @@ export default class InviteDialog extends React.PureComponent { this.setState({busy: true}); + const client = MatrixClientPeg.get(); const targets = this._convertFilter(); const targetIds = targets.map(t => t.userId); // Check if there is already a DM with these people and reuse it if possible. let existingRoom: Room; if (targetIds.length === 1) { - existingRoom = findDMForUser(MatrixClientPeg.get(), targetIds[0]); + existingRoom = findDMForUser(client, targetIds[0]); } else { existingRoom = DMRoomMap.shared().getDMRoomForIdentifiers(targetIds); } @@ -646,7 +647,6 @@ export default class InviteDialog extends React.PureComponent t instanceof ThreepidMember); if (!has3PidMembers) { - const client = MatrixClientPeg.get(); const allHaveDeviceKeys = await canEncryptToAllUsers(client, targetIds); if (allHaveDeviceKeys) { createRoomOptions.encryption = true; @@ -656,9 +656,8 @@ export default class InviteDialog extends React.PureComponent Date: Wed, 14 Apr 2021 09:37:06 +0100 Subject: [PATCH 07/22] fix closing modal when finished --- src/components/views/dialogs/InviteDialog.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index cbdfdcbdc8..c89e61bd18 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -656,6 +656,7 @@ export default class InviteDialog extends React.PureComponent Date: Wed, 14 Apr 2021 10:18:45 +0100 Subject: [PATCH 08/22] Rename Velociraptor to NodeAnimator after velocity deprecation --- .eslintignore.errorfiles | 2 +- src/{Velociraptor.js => NodeAnimator.js} | 4 ++-- src/components/views/rooms/ReadReceiptMarker.js | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) rename src/{Velociraptor.js => NodeAnimator.js} (96%) diff --git a/.eslintignore.errorfiles b/.eslintignore.errorfiles index 1c0a3d1254..d9177bebb5 100644 --- a/.eslintignore.errorfiles +++ b/.eslintignore.errorfiles @@ -1,7 +1,7 @@ # autogenerated file: run scripts/generate-eslint-error-ignore-file to update. src/Markdown.js -src/Velociraptor.js +src/NodeAnimator.js src/components/structures/RoomDirectory.js src/components/views/rooms/MemberList.js src/ratelimitedfunc.js diff --git a/src/Velociraptor.js b/src/NodeAnimator.js similarity index 96% rename from src/Velociraptor.js rename to src/NodeAnimator.js index 1592f4be06..8456e6e9fd 100644 --- a/src/Velociraptor.js +++ b/src/NodeAnimator.js @@ -3,13 +3,13 @@ import ReactDom from "react-dom"; import PropTypes from 'prop-types'; /** - * The Velociraptor contains components and animates transitions with velocity. + * The NodeAnimator contains components and animates transitions. * It will only pick up direct changes to properties ('left', currently), and so * will not work for animating positional changes where the position is implicit * from DOM order. This makes it a lot simpler and lighter: if you need fully * automatic positional animation, look at react-shuffle or similar libraries. */ -export default class Velociraptor extends React.Component { +export default class NodeAnimator extends React.Component { static propTypes = { // either a list of child nodes, or a single child. children: PropTypes.any, diff --git a/src/components/views/rooms/ReadReceiptMarker.js b/src/components/views/rooms/ReadReceiptMarker.js index e2b95a7ada..709e6a0db0 100644 --- a/src/components/views/rooms/ReadReceiptMarker.js +++ b/src/components/views/rooms/ReadReceiptMarker.js @@ -19,7 +19,7 @@ import React, {createRef} from 'react'; import PropTypes from 'prop-types'; import { _t } from '../../../languageHandler'; import {formatDate} from '../../../DateUtils'; -import Velociraptor from "../../../Velociraptor"; +import NodeAnimator from "../../../NodeAnimator"; import * as sdk from "../../../index"; import {toPx} from "../../../utils/units"; import {replaceableComponent} from "../../../utils/replaceableComponent"; @@ -187,7 +187,7 @@ export default class ReadReceiptMarker extends React.PureComponent { } return ( - - + ); } } From ba0384f381441b8040dedcbe81669b2c8a1a3936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 14 Apr 2021 11:43:42 +0200 Subject: [PATCH 09/22] Render msgOption only if showReadReceipts is enabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- res/css/structures/_ScrollPanel.scss | 2 +- src/components/views/rooms/EventTile.js | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/res/css/structures/_ScrollPanel.scss b/res/css/structures/_ScrollPanel.scss index 699224949b..ebbaff0931 100644 --- a/res/css/structures/_ScrollPanel.scss +++ b/res/css/structures/_ScrollPanel.scss @@ -21,6 +21,6 @@ limitations under the License. display: flex; flex-direction: column; justify-content: flex-end; - overflow-y: hidden; + overflow: hidden; } } diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index a3474161d7..c48e557981 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -988,6 +988,16 @@ export default class EventTile extends React.Component { const groupPadlock = !useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); const ircPadlock = useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); + let msgOption; + if (SettingsStore.getValue("showReadReceipts")) { + msgOption = ( +
+ { readAvatars } +
+ ); + } + + switch (this.props.tileShape) { case 'notif': { const room = this.context.getRoom(this.props.mxEvent.getRoomId()); @@ -1107,9 +1117,7 @@ export default class EventTile extends React.Component { { reactionsRow } { actionBar } -
- { readAvatars } -
+ {msgOption} { // The avatar goes after the event tile as it's absolutely positioned to be over the // event tile line, so needs to be later in the DOM so it appears on top (this avoids From 43c236e8a59e4953f35543d0653200441dbcb95b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 14 Apr 2021 12:24:33 +0200 Subject: [PATCH 10/22] Pass showReadReceipts from MessagePanel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/structures/MessagePanel.js | 1 + src/components/views/rooms/EventTile.js | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index 41a3015721..132d9ab4c3 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -659,6 +659,7 @@ export default class MessagePanel extends React.Component { showReactions={this.props.showReactions} layout={this.props.layout} enableFlair={this.props.enableFlair} + showReadReceipts={this.props.showReadReceipts} /> , diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index c48e557981..f9541ed4f8 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -260,6 +260,9 @@ export default class EventTile extends React.Component { // whether or not to show flair at all enableFlair: PropTypes.bool, + + // whether or not to show read receipts + showReadReceipts: PropTypes.bool, }; static defaultProps = { @@ -989,7 +992,7 @@ export default class EventTile extends React.Component { const ircPadlock = useIRCLayout && !isBubbleMessage && this._renderE2EPadlock(); let msgOption; - if (SettingsStore.getValue("showReadReceipts")) { + if (this.props.showReadReceipts) { msgOption = (
{ readAvatars } From a7b1c5dfe04cbd70b92d670d3de8172064fd055a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Wed, 14 Apr 2021 12:25:48 +0200 Subject: [PATCH 11/22] Run getReadAvatars() only when neccessary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index f9541ed4f8..2a18bfd794 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -861,8 +861,6 @@ export default class EventTile extends React.Component { permalink = this.props.permalinkCreator.forEvent(this.props.mxEvent.getId()); } - const readAvatars = this.getReadAvatars(); - let avatar; let sender; let avatarSize; @@ -993,6 +991,7 @@ export default class EventTile extends React.Component { let msgOption; if (this.props.showReadReceipts) { + const readAvatars = this.getReadAvatars(); msgOption = (
{ readAvatars } From 3b66821258974a82b280e189638926a33d8000f9 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Wed, 14 Apr 2021 19:59:17 +0100 Subject: [PATCH 12/22] move DM invite responsiblity to the server side --- src/components/views/dialogs/InviteDialog.tsx | 39 +++++++++++-------- src/createRoom.ts | 6 +++ 2 files changed, 28 insertions(+), 17 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index c89e61bd18..5ca7e3aec2 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -31,6 +31,7 @@ import Modal from "../../../Modal"; import {humanizeTime} from "../../../utils/humanize"; import createRoom, { canEncryptToAllUsers, ensureDMExists, findDMForUser, privateShouldBeEncrypted, + IInvite3PID, } from "../../../createRoom"; import {inviteMultipleToRoom, showCommunityInviteDialog} from "../../../RoomInvite"; import {Key} from "../../../Keyboard"; @@ -656,29 +657,33 @@ export default class InviteDialog extends React.PureComponent 1) { + createRoomOptions.createOpts = targetIds.reduce( + (roomOptions, address) => { + if (getAddressType(address) === 'email') { + const invite: IInvite3PID = { + id_server: client.getIdentityServerUrl(true), + medium: 'email', + address, + }; + roomOptions.invite_3pid.push(invite); + } else { + roomOptions.invite.push(address); + } + return roomOptions; + }, + { invite: [], invite_3pid: [] }, + ) } + + await createRoom(createRoomOptions); + this.props.onFinished(); } catch (err) { console.error(err); this.setState({ diff --git a/src/createRoom.ts b/src/createRoom.ts index a5343076ac..310d894266 100644 --- a/src/createRoom.ts +++ b/src/createRoom.ts @@ -90,6 +90,12 @@ export interface IOpts { parentSpace?: Room; } +export interface IInvite3PID { + id_server: string, + medium: 'email', + address: string, +} + /** * Create a new room, and switch to it. * From e77d3bea0483b62de7705e26bc5266cc3d58f3ae Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 14 Apr 2021 15:08:11 -0600 Subject: [PATCH 13/22] Pulse animation option for voice record button --- .../views/rooms/_VoiceRecordComposerTile.scss | 22 ++++++++++++++++++- .../legacy-light/css/_legacy-light.scss | 6 +++-- res/themes/light/css/_light.scss | 5 +++-- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 2fb112a38c..4d706c7ed4 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -53,7 +53,9 @@ limitations under the License. font-size: $font-14px; &::before { - // TODO: @@ TravisR: Animate + // Pulsing animation + animation: recording-pulse 1.5s infinite; + content: ''; background-color: $voice-record-live-circle-color; width: 10px; @@ -74,3 +76,21 @@ limitations under the License. width: 42px; // we're not using a monospace font, so fake it } } + +@keyframes recording-pulse { + // Source: https://codepen.io/FlorinPop17/pen/drJJzK + // Same source: https://www.florin-pop.com/blog/2019/03/css-pulse-effect/ + + 0% { + transform: scale(0.95); + box-shadow: 0 0 0 0 $voice-record-live-circle-color; + } + 70% { + transform: scale(1); + box-shadow: 0 0 0 6px transparent; + } + 100% { + transform: scale(0.95); + box-shadow: 0 0 0 0 transparent; + } +} diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 7cb7082c4e..8ce231f47c 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -189,11 +189,13 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%) $groupFilterPanel-divider-color: $roomlist-header-color; +// See non-legacy _light for variable information $voice-record-stop-border-color: #E3E8F0; -$voice-record-stop-symbol-color: $warning-color; +$voice-record-stop-symbol-color: #ff4b55; $voice-record-waveform-bg-color: #E3E8F0; $voice-record-waveform-fg-color: $muted-fg-color; -$voice-record-live-circle-color: $warning-color; +$voice-record-live-circle-color: #ff4b55; +$voice-record-live-halo-color: #ff4b5544; $roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index dc26c4d652..4744c4d5b6 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -181,10 +181,11 @@ $roomsublist-skeleton-ui-bg: linear-gradient(180deg, #ffffff 0%, #ffffff00 100%) $groupFilterPanel-divider-color: $roomlist-header-color; $voice-record-stop-border-color: #E3E8F0; -$voice-record-stop-symbol-color: $warning-color; +$voice-record-stop-symbol-color: #ff4b55; // $warning-color, but without letting people change it in themes $voice-record-waveform-bg-color: #E3E8F0; $voice-record-waveform-fg-color: $muted-fg-color; -$voice-record-live-circle-color: $warning-color; +$voice-record-live-circle-color: #ff4b55; // $warning-color, but without letting people change it in themes +$voice-record-live-halo-color: #ff4b5544; // $warning-color, but with some alpha and without theme support $roomtile-preview-color: $secondary-fg-color; $roomtile-default-badge-bg-color: #61708b; From 595225b98b240df574fc609d7f48618d3c023e08 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Wed, 14 Apr 2021 15:10:57 -0600 Subject: [PATCH 14/22] A different animation option for pulsing record icons --- .../views/rooms/_VoiceRecordComposerTile.scss | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 4d706c7ed4..6eee496e8d 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -54,7 +54,7 @@ limitations under the License. &::before { // Pulsing animation - animation: recording-pulse 1.5s infinite; + animation: recording-pulse 1s infinite; content: ''; background-color: $voice-record-live-circle-color; @@ -78,19 +78,13 @@ limitations under the License. } @keyframes recording-pulse { - // Source: https://codepen.io/FlorinPop17/pen/drJJzK - // Same source: https://www.florin-pop.com/blog/2019/03/css-pulse-effect/ - 0% { - transform: scale(0.95); - box-shadow: 0 0 0 0 $voice-record-live-circle-color; + opacity: 1; } - 70% { - transform: scale(1); - box-shadow: 0 0 0 6px transparent; + 65% { + opacity: 0; } 100% { - transform: scale(0.95); - box-shadow: 0 0 0 0 transparent; + opacity: 1; } } From eed5efdbe1eefe618b3ef2b9aa00f48632753a29 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 15 Apr 2021 00:47:09 -0600 Subject: [PATCH 15/22] Labs: Add quick/cheap "do not disturb" flag This just disables audio notifications and the popup, which is the easiest way to do "do not disturb" for a device. This needs spec changes to be done properly, as it's a shame that mobile devices for the user will still go off. Disabling all of push doesn't sound ideal as it would potentially mean missing highlights for when leaving DND mode. --- res/css/structures/_UserMenu.scss | 26 ++++++++++++++++++++++++++ src/Notifier.ts | 4 ++++ src/components/structures/UserMenu.tsx | 23 +++++++++++++++++++++++ src/i18n/strings/en_EN.json | 1 + src/settings/Settings.ts | 10 ++++++++++ 5 files changed, 64 insertions(+) diff --git a/res/css/structures/_UserMenu.scss b/res/css/structures/_UserMenu.scss index 3badb0850c..17e6ad75df 100644 --- a/res/css/structures/_UserMenu.scss +++ b/res/css/structures/_UserMenu.scss @@ -117,6 +117,32 @@ limitations under the License. .mx_UserMenu_headerButtons { // No special styles: the rest of the layout happens to make it work. } + + .mx_UserMenu_dnd { + width: 24px; + height: 24px; + margin-right: 8px; + position: relative; + + &::before { + content: ''; + position: absolute; + width: 24px; + height: 24px; + mask-position: center; + mask-size: contain; + mask-repeat: no-repeat; + background: $muted-fg-color; + } + + &.mx_UserMenu_dnd_noisy::before { + mask-image: url('$(res)/img/element-icons/notifications.svg'); + } + + &.mx_UserMenu_dnd_muted::before { + mask-image: url('$(res)/img/element-icons/roomlist/notifications-off.svg'); + } + } } &.mx_UserMenu_minimized { diff --git a/src/Notifier.ts b/src/Notifier.ts index f68bfabc18..3e927cea0c 100644 --- a/src/Notifier.ts +++ b/src/Notifier.ts @@ -383,6 +383,10 @@ export const Notifier = { // don't bother notifying as user was recently active in this room return; } + if (SettingsStore.getValue("doNotDisturb")) { + // Don't bother the user if they didn't ask to be bothered + return; + } if (this.isEnabled()) { this._displayPopupNotification(ev, room); diff --git a/src/components/structures/UserMenu.tsx b/src/components/structures/UserMenu.tsx index 0543cc4d07..65861624e6 100644 --- a/src/components/structures/UserMenu.tsx +++ b/src/components/structures/UserMenu.tsx @@ -74,6 +74,7 @@ interface IState { export default class UserMenu extends React.Component { private dispatcherRef: string; private themeWatcherRef: string; + private dndWatcherRef: string; private buttonRef: React.RefObject = createRef(); private tagStoreRef: fbEmitter.EventSubscription; @@ -89,6 +90,9 @@ export default class UserMenu extends React.Component { if (SettingsStore.getValue("feature_spaces")) { SpaceStore.instance.on(UPDATE_SELECTED_SPACE, this.onSelectedSpaceUpdate); } + + // Force update is the easiest way to trigger the UI update (we don't store state for this) + this.dndWatcherRef = SettingsStore.watchSetting("doNotDisturb", null, () => this.forceUpdate()); } private get hasHomePage(): boolean { @@ -103,6 +107,7 @@ export default class UserMenu extends React.Component { public componentWillUnmount() { if (this.themeWatcherRef) SettingsStore.unwatchSetting(this.themeWatcherRef); + if (this.dndWatcherRef) SettingsStore.unwatchSetting(this.dndWatcherRef); if (this.dispatcherRef) defaultDispatcher.unregister(this.dispatcherRef); OwnProfileStore.instance.off(UPDATE_EVENT, this.onProfileUpdate); this.tagStoreRef.remove(); @@ -288,6 +293,12 @@ export default class UserMenu extends React.Component { this.setState({contextMenuPosition: null}); // also close the menu }; + private onDndToggle = (ev) => { + ev.stopPropagation(); + const current = SettingsStore.getValue("doNotDisturb"); + SettingsStore.setValue("doNotDisturb", null, SettingLevel.DEVICE, !current); + }; + private renderContextMenu = (): React.ReactNode => { if (!this.state.contextMenuPosition) return null; @@ -534,6 +545,7 @@ export default class UserMenu extends React.Component { {/* masked image in CSS */} ); + let dnd; if (this.state.selectedSpace) { name = (
@@ -560,6 +572,16 @@ export default class UserMenu extends React.Component {
); isPrototype = true; + } else if (SettingsStore.getValue("feature_dnd")) { + const isDnd = SettingsStore.getValue("doNotDisturb"); + dnd = ; } if (this.props.isMinimized) { name = null; @@ -595,6 +617,7 @@ export default class UserMenu extends React.Component { /> {name} + {dnd} {buttons}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 02236f9997..3bb575415f 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -786,6 +786,7 @@ "%(senderName)s: %(stickerName)s": "%(senderName)s: %(stickerName)s", "Change notification settings": "Change notification settings", "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.": "Spaces prototype. Incompatible with Communities, Communities v2 and Custom Tags. Requires compatible homeserver for some features.", + "Show options to enable 'Do not disturb' mode": "Show options to enable 'Do not disturb' mode", "Send and receive voice messages (in development)": "Send and receive voice messages (in development)", "Render LaTeX maths in messages": "Render LaTeX maths in messages", "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.": "Communities v2 prototypes. Requires compatible homeserver. Highly experimental - use with caution.", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index b38dee6e1a..2a26eeac13 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -128,6 +128,12 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, controller: new ReloadOnChangeController(), }, + "feature_dnd": { + isFeature: true, + displayName: _td("Show options to enable 'Do not disturb' mode"), + supportedLevels: LEVELS_FEATURE, + default: false, + }, "feature_voice_messages": { isFeature: true, displayName: _td("Send and receive voice messages (in development)"), @@ -226,6 +232,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: false, }, + "doNotDisturb": { + supportedLevels: [SettingLevel.DEVICE], + default: false, + }, "mjolnirRooms": { supportedLevels: [SettingLevel.ACCOUNT], default: [], From 3a91dff7112f4b1e653e76ab0df460cd5723c883 Mon Sep 17 00:00:00 2001 From: Germain Souquet Date: Thu, 15 Apr 2021 14:13:37 +0100 Subject: [PATCH 16/22] Check if address type is mx-user-id --- src/components/views/dialogs/InviteDialog.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/views/dialogs/InviteDialog.tsx b/src/components/views/dialogs/InviteDialog.tsx index 5ca7e3aec2..2ebc84ec7c 100644 --- a/src/components/views/dialogs/InviteDialog.tsx +++ b/src/components/views/dialogs/InviteDialog.tsx @@ -666,14 +666,15 @@ export default class InviteDialog extends React.PureComponent 1) { createRoomOptions.createOpts = targetIds.reduce( (roomOptions, address) => { - if (getAddressType(address) === 'email') { + const type = getAddressType(address); + if (type === 'email') { const invite: IInvite3PID = { id_server: client.getIdentityServerUrl(true), medium: 'email', address, }; roomOptions.invite_3pid.push(invite); - } else { + } else if (type === 'mx-user-id') { roomOptions.invite.push(address); } return roomOptions; From 7fe5d2e9e471a7d283a392f0b22b6b2f07981e8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 15 Apr 2021 17:49:35 +0200 Subject: [PATCH 17/22] Remove double blank line MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/rooms/EventTile.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/views/rooms/EventTile.js b/src/components/views/rooms/EventTile.js index 2a18bfd794..f6fb83c064 100644 --- a/src/components/views/rooms/EventTile.js +++ b/src/components/views/rooms/EventTile.js @@ -999,7 +999,6 @@ export default class EventTile extends React.Component { ); } - switch (this.props.tileShape) { case 'notif': { const room = this.context.getRoom(this.props.mxEvent.getRoomId()); From 6b3ac20d76c45edebaba3a246ccf0a716ed6703f Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 15 Apr 2021 09:53:15 -0600 Subject: [PATCH 18/22] Cleanup --- res/css/views/rooms/_VoiceRecordComposerTile.scss | 3 +-- res/themes/legacy-light/css/_legacy-light.scss | 1 - res/themes/light/css/_light.scss | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 6eee496e8d..062f18e196 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -53,8 +53,7 @@ limitations under the License. font-size: $font-14px; &::before { - // Pulsing animation - animation: recording-pulse 1s infinite; + animation: recording-pulse 1.5s infinite; content: ''; background-color: $voice-record-live-circle-color; diff --git a/res/themes/legacy-light/css/_legacy-light.scss b/res/themes/legacy-light/css/_legacy-light.scss index 8ce231f47c..0956f433b2 100644 --- a/res/themes/legacy-light/css/_legacy-light.scss +++ b/res/themes/legacy-light/css/_legacy-light.scss @@ -195,7 +195,6 @@ $voice-record-stop-symbol-color: #ff4b55; $voice-record-waveform-bg-color: #E3E8F0; $voice-record-waveform-fg-color: $muted-fg-color; $voice-record-live-circle-color: #ff4b55; -$voice-record-live-halo-color: #ff4b5544; $roomtile-preview-color: #9e9e9e; $roomtile-default-badge-bg-color: #61708b; diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index 4744c4d5b6..b307dbaba3 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -185,7 +185,6 @@ $voice-record-stop-symbol-color: #ff4b55; // $warning-color, but without letting $voice-record-waveform-bg-color: #E3E8F0; $voice-record-waveform-fg-color: $muted-fg-color; $voice-record-live-circle-color: #ff4b55; // $warning-color, but without letting people change it in themes -$voice-record-live-halo-color: #ff4b5544; // $warning-color, but with some alpha and without theme support $roomtile-preview-color: $secondary-fg-color; $roomtile-default-badge-bg-color: #61708b; From aad22903a7602bba65a9dc6284b01545f296b18e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 15 Apr 2021 18:02:48 +0200 Subject: [PATCH 19/22] Remove overflow: hidden MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This line doesn't seem to do anything * shrug Signed-off-by: Šimon Brandner --- res/css/structures/_ScrollPanel.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/res/css/structures/_ScrollPanel.scss b/res/css/structures/_ScrollPanel.scss index ebbaff0931..a4e501b339 100644 --- a/res/css/structures/_ScrollPanel.scss +++ b/res/css/structures/_ScrollPanel.scss @@ -21,6 +21,5 @@ limitations under the License. display: flex; flex-direction: column; justify-content: flex-end; - overflow: hidden; } } From f0d1e7c5651281a152dcd254a0b0bb13b47eb751 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 15 Apr 2021 10:04:15 -0600 Subject: [PATCH 20/22] Document the lamp effect --- res/css/views/rooms/_VoiceRecordComposerTile.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 062f18e196..6636120117 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -76,6 +76,14 @@ limitations under the License. } } +// The keyframes are slightly weird here to help make a ramping/punch effect +// for the recording dot. We start and end at 100% opacity to help make the +// dot feel a bit like a real lamp that is blinking: the animation ends up +// spending a lot of its time showing a steady state without a fade effect. +// This lamp effect extends into why the 0% opacity keyframe is not in the +// midpoint: lamps take longer to turn off than they do to turn on, and the +// extra frames give it a bit of a realistic punch for when the animation is +// ramping back up to 100% opacity. @keyframes recording-pulse { 0% { opacity: 1; From 3e24d6f8ac403779c1317cc0dc643e1118b3d5d8 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Thu, 15 Apr 2021 10:49:39 -0600 Subject: [PATCH 21/22] Change animation speed --- res/css/views/rooms/_VoiceRecordComposerTile.scss | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/res/css/views/rooms/_VoiceRecordComposerTile.scss b/res/css/views/rooms/_VoiceRecordComposerTile.scss index 6636120117..8100a03890 100644 --- a/res/css/views/rooms/_VoiceRecordComposerTile.scss +++ b/res/css/views/rooms/_VoiceRecordComposerTile.scss @@ -53,7 +53,7 @@ limitations under the License. font-size: $font-14px; &::before { - animation: recording-pulse 1.5s infinite; + animation: recording-pulse 2s infinite; content: ''; background-color: $voice-record-live-circle-color; @@ -84,14 +84,17 @@ limitations under the License. // midpoint: lamps take longer to turn off than they do to turn on, and the // extra frames give it a bit of a realistic punch for when the animation is // ramping back up to 100% opacity. +// +// Target animation timings: steady for 1.5s, fade out for 0.3s, fade in for 0.2s +// (intended to be used in a loop for 2s animation speed) @keyframes recording-pulse { 0% { opacity: 1; } - 65% { + 35% { opacity: 0; } - 100% { + 65% { opacity: 1; } } From d39b7175f7d16947b775e7254565d8efd65f51ff Mon Sep 17 00:00:00 2001 From: David Baker Date: Fri, 16 Apr 2021 15:31:01 +0100 Subject: [PATCH 22/22] Don't include invisible rooms in notify summary This could cause rogue title bar natifications if you had invisible rooms --- src/stores/notifications/RoomNotificationStateStore.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/stores/notifications/RoomNotificationStateStore.ts b/src/stores/notifications/RoomNotificationStateStore.ts index 8b5da674f5..7253b46ddd 100644 --- a/src/stores/notifications/RoomNotificationStateStore.ts +++ b/src/stores/notifications/RoomNotificationStateStore.ts @@ -22,6 +22,7 @@ import { FetchRoomFn, ListNotificationState } from "./ListNotificationState"; import { Room } from "matrix-js-sdk/src/models/room"; import { RoomNotificationState } from "./RoomNotificationState"; import { SummarizedNotificationState } from "./SummarizedNotificationState"; +import { VisibilityProvider } from "../room-list/filters/VisibilityProvider"; interface IState {} @@ -47,7 +48,9 @@ export class RoomNotificationStateStore extends AsyncStoreWithClient { // This will include highlights from the previous version of the room internally const globalState = new SummarizedNotificationState(); for (const room of this.matrixClient.getVisibleRooms()) { - globalState.add(this.getRoomState(room)); + if (VisibilityProvider.instance.isRoomVisible(room)) { + globalState.add(this.getRoomState(room)); + } } return globalState; }