diff --git a/res/css/_components.scss b/res/css/_components.scss index 29c4d2c84c..5d26185393 100644 --- a/res/css/_components.scss +++ b/res/css/_components.scss @@ -118,6 +118,7 @@ @import "./views/messages/_MEmoteBody.scss"; @import "./views/messages/_MFileBody.scss"; @import "./views/messages/_MImageBody.scss"; +@import "./views/messages/_MKeyVerificationRequest.scss"; @import "./views/messages/_MNoticeBody.scss"; @import "./views/messages/_MStickerBody.scss"; @import "./views/messages/_MTextBody.scss"; diff --git a/res/css/views/messages/_MKeyVerificationRequest.scss b/res/css/views/messages/_MKeyVerificationRequest.scss new file mode 100644 index 0000000000..aff44e4109 --- /dev/null +++ b/res/css/views/messages/_MKeyVerificationRequest.scss @@ -0,0 +1,93 @@ +/* +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_KeyVerification { + + display: grid; + grid-template-columns: 24px minmax(0, 1fr) min-content; + + &.mx_KeyVerification_icon::after { + grid-column: 1; + grid-row: 1 / 3; + width: 12px; + height: 16px; + content: ""; + mask: url("$(res)/img/e2e/verified.svg"); + mask-repeat: no-repeat; + mask-size: 100%; + margin-top: 4px; + background-color: $primary-fg-color; + } + + &.mx_KeyVerification_icon_verified::after { + background-color: $accent-color; + } + + .mx_KeyVerification_title, .mx_KeyVerification_subtitle, .mx_KeyVerification_state { + overflow-wrap: break-word; + } + + .mx_KeyVerification_title { + font-weight: 600; + font-size: 15px; + grid-column: 2; + grid-row: 1; + } + + .mx_KeyVerification_subtitle { + grid-column: 2; + grid-row: 2; + } + + .mx_KeyVerification_state, .mx_KeyVerification_subtitle { + font-size: 12px; + } + + .mx_KeyVerification_state, .mx_KeyVerification_buttons { + grid-column: 3; + grid-row: 1 / 3; + } + + .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 { + width: 130px; + padding: 10px 20px; + margin: auto 0; + text-align: center; + color: $notice-secondary-color; + } +} diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index db34200b16..04c1065092 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -22,6 +22,15 @@ limitations under the License. position: relative; } +.mx_EventTile_bubble { + background-color: $dark-panel-bg-color; + padding: 10px; + border-radius: 5px; + margin: 10px auto; + max-width: 75%; + box-sizing: border-box; +} + .mx_EventTile.mx_EventTile_info { padding-top: 0px; } @@ -112,6 +121,21 @@ limitations under the License. line-height: 22px; } +.mx_EventTile_bubbleContainer.mx_EventTile_bubbleContainer { + display: grid; + grid-template-columns: 1fr 100px; + + .mx_EventTile_line { + margin-right: 0px; + grid-column: 1 / 3; + padding: 0; + } + + .mx_EventTile_msgOption { + grid-column: 2; + } +} + .mx_EventTile_reply { margin-right: 10px; } @@ -617,4 +641,5 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } } } + /* stylelint-enable no-descending-specificity */ diff --git a/res/themes/light/css/_light.scss b/res/themes/light/css/_light.scss index b412261d10..dcd7ce166e 100644 --- a/res/themes/light/css/_light.scss +++ b/res/themes/light/css/_light.scss @@ -12,7 +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); $notice-primary-color: #ff4b55; +$notice-primary-bg-color: rgba(255, 75, 85, 0.08); $notice-secondary-color: #61708b; $header-panel-bg-color: #f3f8fd; diff --git a/src/components/views/messages/MKeyVerificationRequest.js b/src/components/views/messages/MKeyVerificationRequest.js new file mode 100644 index 0000000000..21d82309ed --- /dev/null +++ b/src/components/views/messages/MKeyVerificationRequest.js @@ -0,0 +1,141 @@ +/* +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 PropTypes from 'prop-types'; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import {verificationMethods} from 'matrix-js-sdk/lib/crypto'; +import sdk from '../../../index'; +import Modal from "../../../Modal"; +import { _t } from '../../../languageHandler'; +import KeyVerificationStateObserver, {getNameForEventRoom, userLabelForEventRoom} + from '../../../utils/KeyVerificationStateObserver'; + +export default class MKeyVerificationRequest extends React.Component { + constructor(props) { + super(props); + this.keyVerificationState = new KeyVerificationStateObserver(this.props.mxEvent, MatrixClientPeg.get(), () => { + this.setState(this._copyState()); + }); + this.state = this._copyState(); + } + + _copyState() { + const {accepted, done, cancelled, cancelPartyUserId, otherPartyUserId} = this.keyVerificationState; + return {accepted, done, cancelled, cancelPartyUserId, otherPartyUserId}; + } + + componentDidMount() { + this.keyVerificationState.attach(); + } + + componentWillUnmount() { + this.keyVerificationState.detach(); + } + + _onAcceptClicked = () => { + const IncomingSasDialog = sdk.getComponent('views.dialogs.IncomingSasDialog'); + // todo: validate event, for example if it has sas in the methods. + const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS); + Modal.createTrackedDialog('Incoming Verification', '', IncomingSasDialog, { + verifier, + }); + }; + + _onRejectClicked = () => { + // todo: validate event, for example if it has sas in the methods. + const verifier = MatrixClientPeg.get().acceptVerificationDM(this.props.mxEvent, verificationMethods.SAS); + verifier.cancel("User declined"); + }; + + _acceptedLabel(userId) { + const client = MatrixClientPeg.get(); + const myUserId = client.getUserId(); + if (userId === myUserId) { + return _t("You accepted"); + } else { + return _t("%(name)s accepted", {name: getNameForEventRoom(userId, this.props.mxEvent)}); + } + } + + _cancelledLabel(userId) { + const client = MatrixClientPeg.get(); + const myUserId = client.getUserId(); + if (userId === myUserId) { + return _t("You cancelled"); + } else { + return _t("%(name)s cancelled", {name: getNameForEventRoom(userId, this.props.mxEvent)}); + } + } + + render() { + const {mxEvent} = this.props; + const fromUserId = mxEvent.getSender(); + const content = mxEvent.getContent(); + const toUserId = content.to; + const client = MatrixClientPeg.get(); + const myUserId = client.getUserId(); + const isOwn = fromUserId === myUserId; + + let title; + let subtitle; + let stateNode; + + if (this.state.accepted || this.state.cancelled) { + let stateLabel; + if (this.state.accepted) { + stateLabel = this._acceptedLabel(toUserId); + } else if (this.state.cancelled) { + stateLabel = this._cancelledLabel(this.state.cancelPartyUserId); + } + stateNode = (