Skip to content

Commit

Permalink
Merge pull request #3661 from matrix-org/bwindels/verif-toasts
Browse files Browse the repository at this point in the history
Show incoming verification requests in in-app notifications
  • Loading branch information
bwindels committed Nov 22, 2019
2 parents 7df8ef2 + 8ce1ed4 commit 4a684d0
Show file tree
Hide file tree
Showing 13 changed files with 424 additions and 29 deletions.
2 changes: 2 additions & 0 deletions res/css/_components.scss
Expand Up @@ -25,6 +25,7 @@
@import "./structures/_TabbedView.scss";
@import "./structures/_TagPanel.scss";
@import "./structures/_TagPanelButtons.scss";
@import "./structures/_ToastContainer.scss";
@import "./structures/_TopLeftMenuButton.scss";
@import "./structures/_UploadBar.scss";
@import "./structures/_ViewSource.scss";
Expand Down Expand Up @@ -91,6 +92,7 @@
@import "./views/elements/_ErrorBoundary.scss";
@import "./views/elements/_EventListSummary.scss";
@import "./views/elements/_Field.scss";
@import "./views/elements/_FormButton.scss";
@import "./views/elements/_IconButton.scss";
@import "./views/elements/_ImageView.scss";
@import "./views/elements/_InlineSpinner.scss";
Expand Down
98 changes: 98 additions & 0 deletions res/css/structures/_ToastContainer.scss
@@ -0,0 +1,98 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_ToastContainer {
position: absolute;
top: 0;
left: 70px;
z-index: 101;
padding: 4px;
display: grid;
grid-template-rows: 1fr 14px 6px;

&.mx_ToastContainer_stacked::before {
content: "";
margin: 0 4px;
grid-row: 2 / 4;
grid-column: 1;
background-color: white;
box-shadow: 0px 4px 12px $menu-box-shadow-color;
border-radius: 8px;
}

.mx_Toast_toast {
grid-row: 1 / 3;
grid-column: 1;
color: $primary-fg-color;
background-color: $primary-bg-color;
box-shadow: 0px 4px 12px $menu-box-shadow-color;
border-radius: 8px;
overflow: hidden;
display: grid;
grid-template-columns: 20px 1fr;
column-gap: 10px;
row-gap: 4px;
padding: 8px;
padding-right: 16px;

&.mx_Toast_hasIcon {
&::after {
content: "";
width: 20px;
height: 20px;
grid-column: 1;
grid-row: 1;
mask-size: 100%;
mask-repeat: no-repeat;
}

&.mx_Toast_icon_verification::after {
mask-image: url("$(res)/img/e2e/normal.svg");
background-color: $primary-fg-color;
}

h2, .mx_Toast_body {
grid-column: 2;
}
}

h2 {
grid-column: 1 / 3;
grid-row: 1;
margin: 0;
font-size: 15px;
font-weight: 600;
}

.mx_Toast_body {
grid-column: 1 / 3;
grid-row: 2;
}

.mx_Toast_buttons {
display: flex;
}

.mx_Toast_description {
max-width: 400px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 4px 0 11px 0;
font-size: 12px;
}
}
}
36 changes: 36 additions & 0 deletions res/css/views/elements/_FormButton.scss
@@ -0,0 +1,36 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

.mx_FormButton {
line-height: 16px;
padding: 5px 15px;
font-size: 12px;
height: min-content;

&:not(:last-child) {
margin-right: 8px;
}

&.mx_AccessibleButton_kind_primary {
color: $accent-color;
background-color: $accent-bg-color;
}

&.mx_AccessibleButton_kind_danger {
color: $notice-primary-color;
background-color: $notice-primary-bg-color;
}
}
17 changes: 0 additions & 17 deletions res/css/views/messages/_MKeyVerificationRequest.scss
Expand Up @@ -65,23 +65,6 @@ limitations under the License.
.mx_KeyVerification_buttons {
align-items: center;
display: flex;

.mx_AccessibleButton_kind_decline {
color: $notice-primary-color;
background-color: $notice-primary-bg-color;
}

.mx_AccessibleButton_kind_accept {
color: $accent-color;
background-color: $accent-bg-color;
}

[role=button] {
margin: 10px;
padding: 7px 15px;
border-radius: 5px;
height: min-content;
}
}

.mx_KeyVerification_state {
Expand Down
4 changes: 2 additions & 2 deletions res/themes/light/css/_light.scss
Expand Up @@ -12,9 +12,9 @@ $monospace-font-family: Inconsolata, Twemoji, 'Apple Color Emoji', 'Segoe UI Emo
// unified palette
// try to use these colors when possible
$accent-color: #03b381;
$accent-bg-color: rgba(115, 247, 91, 0.08);
$accent-bg-color: rgba(3, 179, 129, 0.16);
$notice-primary-color: #ff4b55;
$notice-primary-bg-color: rgba(255, 75, 85, 0.08);
$notice-primary-bg-color: rgba(255, 75, 85, 0.16);
$notice-secondary-color: #61708b;
$header-panel-bg-color: #f3f8fd;

Expand Down
2 changes: 2 additions & 0 deletions src/components/structures/LoggedInView.js
Expand Up @@ -525,6 +525,7 @@ const LoggedInView = createReactClass({
const EmbeddedPage = sdk.getComponent('structures.EmbeddedPage');
const GroupView = sdk.getComponent('structures.GroupView');
const MyGroups = sdk.getComponent('structures.MyGroups');
const ToastContainer = sdk.getComponent('structures.ToastContainer');
const MatrixToolbar = sdk.getComponent('globals.MatrixToolbar');
const CookieBar = sdk.getComponent('globals.CookieBar');
const NewVersionBar = sdk.getComponent('globals.NewVersionBar');
Expand Down Expand Up @@ -628,6 +629,7 @@ const LoggedInView = createReactClass({
return (
<div onPaste={this._onPaste} onKeyDown={this._onReactKeyDown} className='mx_MatrixChat_wrapper' aria-hidden={this.props.hideToSRUsers} onMouseDown={this._onMouseDown} onMouseUp={this._onMouseUp}>
{ topBar }
<ToastContainer />
<DragDropContext onDragEnd={this._onDragEnd}>
<div ref={this._setResizeContainerRef} className={bodyClasses}>
<LeftPanel
Expand Down
35 changes: 29 additions & 6 deletions src/components/structures/MatrixChat.js
Expand Up @@ -60,6 +60,7 @@ import { countRoomsWithNotif } from '../../RoomNotifs';
import { ThemeWatcher } from "../../theme";
import { storeRoomAliasInCache } from '../../RoomAliasCache';
import { defer } from "../../utils/promise";
import KeyVerificationStateObserver from '../../utils/KeyVerificationStateObserver';

/** constants for MatrixChat.state.view */
const VIEWS = {
Expand Down Expand Up @@ -1264,7 +1265,6 @@ export default createReactClass({
this.firstSyncComplete = false;
this.firstSyncPromise = defer();
const cli = MatrixClientPeg.get();
const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog');

// Allow the JS SDK to reap timeline events. This reduces the amount of
// memory consumed as the JS SDK stores multiple distinct copies of room
Expand Down Expand Up @@ -1463,12 +1463,35 @@ export default createReactClass({
}
});

cli.on("crypto.verification.start", (verifier) => {
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
verifier,
});
});
if (SettingsStore.isFeatureEnabled("feature_dm_verification")) {
cli.on("crypto.verification.request", request => {
let requestObserver;
if (request.event.getRoomId()) {
requestObserver = new KeyVerificationStateObserver(
request.event, MatrixClientPeg.get());
}

if (!requestObserver || requestObserver.pending) {
dis.dispatch({
action: "show_toast",
toast: {
key: request.event.getId(),
title: _t("Verification Request"),
icon: "verification",
props: {request, requestObserver},
component: sdk.getComponent("toasts.VerificationRequestToast"),
},
});
}
});
} else {
cli.on("crypto.verification.start", (verifier) => {
const IncomingSasDialog = sdk.getComponent("views.dialogs.IncomingSasDialog");
Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, {
verifier,
});
});
}
// Fire the tinter right on startup to ensure the default theme is applied
// A later sync can/will correct the tint to be the right value for the user
const colorScheme = SettingsStore.getValue("roomColor");
Expand Down
85 changes: 85 additions & 0 deletions src/components/structures/ToastContainer.js
@@ -0,0 +1,85 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import * as React from "react";
import dis from "../../dispatcher";
import { _t } from '../../languageHandler';
import classNames from "classnames";

export default class ToastContainer extends React.Component {
constructor() {
super();
this.state = {toasts: []};
}

componentDidMount() {
this._dispatcherRef = dis.register(this.onAction);
}

componentWillUnmount() {
dis.unregister(this._dispatcherRef);
}

onAction = (payload) => {
if (payload.action === "show_toast") {
this._addToast(payload.toast);
}
};

_addToast(toast) {
this.setState({toasts: this.state.toasts.concat(toast)});
}

dismissTopToast = () => {
const [, ...remaining] = this.state.toasts;
this.setState({toasts: remaining});
};

render() {
const totalCount = this.state.toasts.length;
if (totalCount === 0) {
return null;
}
const isStacked = totalCount > 1;
const topToast = this.state.toasts[0];
const {title, icon, key, component, props} = topToast;

const containerClasses = classNames("mx_ToastContainer", {
"mx_ToastContainer_stacked": isStacked,
});

const toastClasses = classNames("mx_Toast_toast", {
"mx_Toast_hasIcon": icon,
[`mx_Toast_icon_${icon}`]: icon,
});

const countIndicator = isStacked ? _t(" (1/%(totalCount)s)", {totalCount}) : null;

const toastProps = Object.assign({}, props, {
dismiss: this.dismissTopToast,
key,
});

return (
<div className={containerClasses}>
<div className={toastClasses}>
<h2>{title}{countIndicator}</h2>
<div className="mx_Toast_body">{React.createElement(component, toastProps)}</div>
</div>
</div>
);
}
}
28 changes: 28 additions & 0 deletions src/components/views/elements/FormButton.js
@@ -0,0 +1,28 @@
/*
Copyright 2019 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import AccessibleButton from "./AccessibleButton";

export default function FormButton(props) {
const {className, label, kind, ...restProps} = props;
const newClassName = (className || "") + " mx_FormButton";
const allProps = Object.assign({}, restProps,
{className: newClassName, kind: kind || "primary", children: [label]});
return React.createElement(AccessibleButton, allProps);
}

FormButton.propTypes = AccessibleButton.propTypes;
6 changes: 3 additions & 3 deletions src/components/views/messages/MKeyVerificationRequest.js
Expand Up @@ -111,10 +111,10 @@ export default class MKeyVerificationRequest extends React.Component {
userLabelForEventRoom(fromUserId, mxEvent)}</div>);
const isResolved = !(this.state.accepted || this.state.cancelled || this.state.done);
if (isResolved) {
const AccessibleButton = sdk.getComponent("elements.AccessibleButton");
const FormButton = sdk.getComponent("elements.FormButton");
stateNode = (<div className="mx_KeyVerification_buttons">
<AccessibleButton kind="decline" onClick={this._onRejectClicked}>{_t("Decline")}</AccessibleButton>
<AccessibleButton kind="accept" onClick={this._onAcceptClicked}>{_t("Accept")}</AccessibleButton>
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
<FormButton onClick={this._onAcceptClicked} label={_t("Accept")} />
</div>);
}
} else if (isOwn) { // request sent by us
Expand Down

0 comments on commit 4a684d0

Please sign in to comment.