Consolidate all EventTile bubble stuff into its own component and use it for the room continuation plinth

This commit is contained in:
Michael Telatynski 2020-11-04 17:00:07 +00:00
parent 971ade57bc
commit ff25a9b45d
13 changed files with 182 additions and 186 deletions

View file

@ -139,6 +139,7 @@
@import "./views/groups/_GroupUserSettings.scss"; @import "./views/groups/_GroupUserSettings.scss";
@import "./views/messages/_CreateEvent.scss"; @import "./views/messages/_CreateEvent.scss";
@import "./views/messages/_DateSeparator.scss"; @import "./views/messages/_DateSeparator.scss";
@import "./views/messages/_EventTileBubble.scss";
@import "./views/messages/_MEmoteBody.scss"; @import "./views/messages/_MEmoteBody.scss";
@import "./views/messages/_MFileBody.scss"; @import "./views/messages/_MFileBody.scss";
@import "./views/messages/_MImageBody.scss"; @import "./views/messages/_MImageBody.scss";

View file

@ -1,5 +1,5 @@
/* /*
Copyright 2018 New Vector Ltd Copyright 2020 The Matrix.org Foundation C.I.C.
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.
@ -15,25 +15,17 @@ limitations under the License.
*/ */
.mx_CreateEvent { .mx_CreateEvent {
background-color: $info-plinth-bg-color; // override default EventTileBubble styling
padding-left: 20px; padding-left: 80px !important;
padding-right: 20px;
padding-top: 10px;
padding-bottom: 10px;
}
.mx_CreateEvent_image { &::before {
float: left; background-color: $primary-fg-color;
margin-right: 20px; mask-image: url('$(res)/img/room-continuation.svg');
width: 72px; mask-repeat: no-repeat;
height: 34px; mask-position: center;
mask-size: 100%;
background-color: $primary-fg-color; width: 72px !important;
mask: url('$(res)/img/room-continuation.svg'); height: 34px !important;
mask-repeat: no-repeat; left: -64px !important;
mask-position: center; }
}
.mx_CreateEvent_header {
font-weight: bold;
} }

View file

@ -0,0 +1,63 @@
/*
Copyright 2019, 2020 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_EventTileBubble {
background-color: $dark-panel-bg-color;
padding: 10px;
border-radius: 8px;
margin: 10px auto;
max-width: 75%;
box-sizing: border-box;
display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content;
&::before, &::after {
position: relative;
grid-column: 1;
grid-row: 1 / 3;
width: 16px;
height: 16px;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
margin-top: 4px;
}
.mx_EventTileBubble_title, .mx_EventTileBubble_subtitle {
overflow-wrap: break-word;
}
.mx_EventTileBubble_title {
font-weight: 600;
font-size: $font-15px;
grid-column: 2;
grid-row: 1;
}
.mx_EventTileBubble_subtitle {
grid-column: 2;
grid-row: 2;
}
.mx_EventTileBubble_subtitle {
font-size: $font-12px;
}
}

View file

@ -15,41 +15,8 @@ limitations under the License.
*/ */
.mx_MJitsiWidgetEvent { .mx_MJitsiWidgetEvent {
display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content;
&::before { &::before {
grid-column: 1;
grid-row: 1 / 3;
width: 16px;
height: 16px;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
background-color: $composer-e2e-icon-color; // XXX: Variable abuse background-color: $composer-e2e-icon-color; // XXX: Variable abuse
margin-top: 4px;
mask-image: url('$(res)/img/element-icons/call/video-call.svg'); mask-image: url('$(res)/img/element-icons/call/video-call.svg');
} }
.mx_MJitsiWidgetEvent_title {
font-weight: 600;
font-size: $font-15px;
grid-column: 2;
grid-row: 1;
}
.mx_MJitsiWidgetEvent_subtitle {
grid-column: 2;
grid-row: 2;
}
.mx_MJitsiWidgetEvent_title,
.mx_MJitsiWidgetEvent_subtitle {
overflow-wrap: break-word;
}
} }

View file

@ -15,28 +15,6 @@ limitations under the License.
*/ */
.mx_cryptoEvent { .mx_cryptoEvent {
display: grid;
grid-template-columns: 24px minmax(0, 1fr) min-content;
&.mx_cryptoEvent_icon::before,
&.mx_cryptoEvent_icon::after {
grid-column: 1;
grid-row: 1 / 3;
width: 16px;
height: 16px;
content: "";
top: 0;
bottom: 0;
left: 0;
right: 0;
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
mask-image: url('$(res)/img/e2e/normal.svg');
background-color: $composer-e2e-icon-color;
margin-top: 4px;
}
// white infill for the transparency // white infill for the transparency
&.mx_cryptoEvent_icon::before { &.mx_cryptoEvent_icon::before {
background-color: #ffffff; background-color: #ffffff;
@ -46,6 +24,11 @@ limitations under the License.
mask-size: 90%; mask-size: 90%;
} }
&.mx_cryptoEvent_icon::after {
mask-image: url('$(res)/img/e2e/normal.svg');
background-color: $composer-e2e-icon-color;
}
&.mx_cryptoEvent_icon_verified::after { &.mx_cryptoEvent_icon_verified::after {
mask-image: url("$(res)/img/e2e/verified.svg"); mask-image: url("$(res)/img/e2e/verified.svg");
background-color: $accent-color; background-color: $accent-color;
@ -56,25 +39,6 @@ limitations under the License.
background-color: $notice-primary-color; background-color: $notice-primary-color;
} }
.mx_cryptoEvent_title, .mx_cryptoEvent_subtitle, .mx_cryptoEvent_state {
overflow-wrap: break-word;
}
.mx_cryptoEvent_title {
font-weight: 600;
font-size: $font-15px;
grid-column: 2;
grid-row: 1;
}
.mx_cryptoEvent_subtitle {
grid-column: 2;
grid-row: 2;
}
.mx_cryptoEvent_state, .mx_cryptoEvent_subtitle {
font-size: $font-12px;
}
.mx_cryptoEvent_state, .mx_cryptoEvent_buttons { .mx_cryptoEvent_state, .mx_cryptoEvent_buttons {
grid-column: 3; grid-column: 3;
@ -92,5 +56,7 @@ limitations under the License.
margin: auto 0; margin: auto 0;
text-align: center; text-align: center;
color: $notice-secondary-color; color: $notice-secondary-color;
overflow-wrap: break-word;
font-size: $font-12px;
} }
} }

View file

@ -25,15 +25,6 @@ $left-gutter: 64px;
position: relative; 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 { .mx_EventTile.mx_EventTile_info {
padding-top: 0px; padding-top: 0px;
} }

View file

@ -18,42 +18,35 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import EventTileBubble from "./EventTileBubble";
export default class EncryptionEvent extends React.Component { export default class EncryptionEvent extends React.Component {
render() { render() {
const {mxEvent} = this.props; const {mxEvent} = this.props;
let body;
let classes = "mx_EventTile_bubble mx_cryptoEvent mx_cryptoEvent_icon";
const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(mxEvent.getRoomId()); const isRoomEncrypted = MatrixClientPeg.get().isRoomEncrypted(mxEvent.getRoomId());
if (mxEvent.getContent().algorithm === 'm.megolm.v1.aes-sha2' && isRoomEncrypted) { if (mxEvent.getContent().algorithm === 'm.megolm.v1.aes-sha2' && isRoomEncrypted) {
body = <div> return <EventTileBubble
<div className="mx_cryptoEvent_title">{_t("Encryption enabled")}</div> className="mx_cryptoEvent mx_cryptoEvent_icon"
<div className="mx_cryptoEvent_subtitle"> title={_t("Encryption enabled")}
{_t( subtitle={_t(
"Messages in this room are end-to-end encrypted. " + "Messages in this room are end-to-end encrypted. " +
"Learn more & verify this user in their user profile.", "Learn more & verify this user in their user profile.",
)} )}
</div> />;
</div>;
} else if (isRoomEncrypted) { } else if (isRoomEncrypted) {
body = <div> return <EventTileBubble
<div className="mx_cryptoEvent_title">{_t("Encryption enabled")}</div> className="mx_cryptoEvent mx_cryptoEvent_icon"
<div className="mx_cryptoEvent_subtitle"> title={_t("Encryption enabled")}
{_t("Ignored attempt to disable encryption")} subtitle={_t("Ignored attempt to disable encryption")}
</div> />;
</div>;
} else {
body = <div>
<div className="mx_cryptoEvent_title">{_t("Encryption not enabled")}</div>
<div className="mx_cryptoEvent_subtitle">{_t("The encryption used by this room isn't supported.")}</div>
</div>;
classes += " mx_cryptoEvent_icon_warning";
} }
return (<div className={classes}> return <EventTileBubble
{body} className="mx_cryptoEvent mx_cryptoEvent_icon mx_cryptoEvent_icon_warning"
</div>); title={_t("Encryption not enabled")}
subtitle={_t("The encryption used by this room isn't supported.")}
/>;
} }
} }

View file

@ -0,0 +1,34 @@
/*
Copyright 2020 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, {ReactNode} from "react";
import classNames from "classnames";
interface IProps {
className: string;
title: string;
subtitle?: ReactNode;
}
const EventTileBubble: React.FC<IProps> = ({ className, title, subtitle, children }) => {
return <div className={classNames("mx_EventTileBubble", className)}>
<div className="mx_EventTileBubble_title">{ title }</div>
{ subtitle && <div className="mx_EventTileBubble_subtitle">{ subtitle }</div> }
{ children }
</div>;
};
export default EventTileBubble;

View file

@ -18,6 +18,7 @@ import React from 'react';
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { _t } from "../../../languageHandler"; import { _t } from "../../../languageHandler";
import WidgetStore from "../../../stores/WidgetStore"; import WidgetStore from "../../../stores/WidgetStore";
import EventTileBubble from "./EventTileBubble";
interface IProps { interface IProps {
mxEvent: MatrixEvent; mxEvent: MatrixEvent;
@ -40,37 +41,24 @@ export default class MJitsiWidgetEvent extends React.PureComponent<IProps> {
if (!url) { if (!url) {
// removed // removed
return ( return <EventTileBubble
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'> className="mx_MJitsiWidgetEvent"
<div className='mx_MJitsiWidgetEvent_title'> title={_t('Video conference ended by %(senderName)s', {senderName})}
{_t('Video conference ended by %(senderName)s', {senderName})} />;
</div>
</div>
);
} else if (prevUrl) { } else if (prevUrl) {
// modified // modified
return ( return <EventTileBubble
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'> className="mx_MJitsiWidgetEvent"
<div className='mx_MJitsiWidgetEvent_title'> title={_t('Video conference updated by %(senderName)s', {senderName})}
{_t('Video conference updated by %(senderName)s', {senderName})} subtitle={joinCopy}
</div> />;
<div className='mx_MJitsiWidgetEvent_subtitle'>
{joinCopy}
</div>
</div>
);
} else { } else {
// assume added // assume added
return ( return <EventTileBubble
<div className='mx_EventTile_bubble mx_MJitsiWidgetEvent'> className="mx_MJitsiWidgetEvent"
<div className='mx_MJitsiWidgetEvent_title'> title={_t("Video conference started by %(senderName)s", {senderName})}
{_t("Video conference started by %(senderName)s", {senderName})} subtitle={joinCopy}
</div> />;
<div className='mx_MJitsiWidgetEvent_subtitle'>
{joinCopy}
</div>
</div>
);
} }
} }
} }

View file

@ -21,6 +21,7 @@ import {MatrixClientPeg} from '../../../MatrixClientPeg';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {getNameForEventRoom, userLabelForEventRoom} import {getNameForEventRoom, userLabelForEventRoom}
from '../../../utils/KeyVerificationStateObserver'; from '../../../utils/KeyVerificationStateObserver';
import EventTileBubble from "./EventTileBubble";
export default class MKeyVerificationConclusion extends React.Component { export default class MKeyVerificationConclusion extends React.Component {
constructor(props) { constructor(props) {
@ -115,14 +116,14 @@ export default class MKeyVerificationConclusion extends React.Component {
} }
if (title) { if (title) {
const subtitle = userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId()); const classes = classNames("mx_cryptoEvent mx_cryptoEvent_icon", {
const classes = classNames("mx_EventTile_bubble", "mx_cryptoEvent", "mx_cryptoEvent_icon", {
mx_cryptoEvent_icon_verified: request.done, mx_cryptoEvent_icon_verified: request.done,
}); });
return (<div className={classes}> return <EventTileBubble
<div className="mx_cryptoEvent_title">{title}</div> className={classes}
<div className="mx_cryptoEvent_subtitle">{subtitle}</div> title={title}
</div>); subtitle={userLabelForEventRoom(request.otherUserId, mxEvent.getRoomId())}
/>;
} }
return null; return null;

View file

@ -24,6 +24,7 @@ import {getNameForEventRoom, userLabelForEventRoom}
import dis from "../../../dispatcher/dispatcher"; import dis from "../../../dispatcher/dispatcher";
import {RightPanelPhases} from "../../../stores/RightPanelStorePhases"; import {RightPanelPhases} from "../../../stores/RightPanelStorePhases";
import {Action} from "../../../dispatcher/actions"; import {Action} from "../../../dispatcher/actions";
import EventTileBubble from "./EventTileBubble";
export default class MKeyVerificationRequest extends React.Component { export default class MKeyVerificationRequest extends React.Component {
constructor(props) { constructor(props) {
@ -146,10 +147,8 @@ export default class MKeyVerificationRequest extends React.Component {
if (!request.initiatedByMe) { if (!request.initiatedByMe) {
const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId()); const name = getNameForEventRoom(request.requestingUserId, mxEvent.getRoomId());
title = (<div className="mx_cryptoEvent_title">{ title = _t("%(name)s wants to verify", {name});
_t("%(name)s wants to verify", {name})}</div>); subtitle = userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId());
subtitle = (<div className="mx_cryptoEvent_subtitle">{
userLabelForEventRoom(request.requestingUserId, mxEvent.getRoomId())}</div>);
if (request.canAccept) { if (request.canAccept) {
stateNode = (<div className="mx_cryptoEvent_buttons"> stateNode = (<div className="mx_cryptoEvent_buttons">
<FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} /> <FormButton kind="danger" onClick={this._onRejectClicked} label={_t("Decline")} />
@ -157,18 +156,18 @@ export default class MKeyVerificationRequest extends React.Component {
</div>); </div>);
} }
} else { // request sent by us } else { // request sent by us
title = (<div className="mx_cryptoEvent_title">{ title = _t("You sent a verification request");
_t("You sent a verification request")}</div>); subtitle = userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId());
subtitle = (<div className="mx_cryptoEvent_subtitle">{
userLabelForEventRoom(request.receivingUserId, mxEvent.getRoomId())}</div>);
} }
if (title) { if (title) {
return (<div className="mx_EventTile_bubble mx_cryptoEvent mx_cryptoEvent_icon"> return <EventTileBubble
{title} className="mx_cryptoEvent mx_cryptoEvent_icon"
{subtitle} title={title}
{stateNode} subtitle={subtitle}
</div>); >
{ stateNode }
</EventTileBubble>;
} }
return null; return null;
} }

View file

@ -22,6 +22,7 @@ import dis from '../../../dispatcher/dispatcher';
import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks'; import { RoomPermalinkCreator } from '../../../utils/permalinks/Permalinks';
import { _t } from '../../../languageHandler'; import { _t } from '../../../languageHandler';
import {MatrixClientPeg} from '../../../MatrixClientPeg'; import {MatrixClientPeg} from '../../../MatrixClientPeg';
import EventTileBubble from "./EventTileBubble";
export default class RoomCreate extends React.Component { export default class RoomCreate extends React.Component {
static propTypes = { static propTypes = {
@ -51,17 +52,16 @@ export default class RoomCreate extends React.Component {
const permalinkCreator = new RoomPermalinkCreator(prevRoom, predecessor['room_id']); const permalinkCreator = new RoomPermalinkCreator(prevRoom, predecessor['room_id']);
permalinkCreator.load(); permalinkCreator.load();
const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']); const predecessorPermalink = permalinkCreator.forEvent(predecessor['event_id']);
return <div className="mx_CreateEvent"> const link = (
<div className="mx_CreateEvent_image" /> <a href={predecessorPermalink} onClick={this._onLinkClicked}>
<div className="mx_CreateEvent_header">
{_t("This room is a continuation of another conversation.")}
</div>
<a className="mx_CreateEvent_link"
href={predecessorPermalink}
onClick={this._onLinkClicked}
>
{_t("Click here to see older messages.")} {_t("Click here to see older messages.")}
</a> </a>
</div>; );
return <EventTileBubble
className="mx_CreateEvent"
title={_t("This room is a continuation of another conversation.")}
subtitle={link}
/>;
} }
} }

View file

@ -647,6 +647,7 @@ export default class EventTile extends React.Component {
// Info messages are basically information about commands processed on a room // Info messages are basically information about commands processed on a room
const isBubbleMessage = eventType.startsWith("m.key.verification") || const isBubbleMessage = eventType.startsWith("m.key.verification") ||
(eventType === "m.room.message" && msgtype && msgtype.startsWith("m.key.verification")) || (eventType === "m.room.message" && msgtype && msgtype.startsWith("m.key.verification")) ||
(eventType === "m.room.create") ||
(eventType === "m.room.encryption") || (eventType === "m.room.encryption") ||
(tileHandler === "messages.MJitsiWidgetEvent"); (tileHandler === "messages.MJitsiWidgetEvent");
let isInfoMessage = ( let isInfoMessage = (