Merge branch 'develop' into t3chguy/eslint

This commit is contained in:
Michael Telatynski 2021-07-20 11:08:13 +01:00 committed by GitHub
commit d3934ff7da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 859 additions and 344 deletions

View file

@ -201,6 +201,7 @@
@import "./views/rooms/_EditMessageComposer.scss"; @import "./views/rooms/_EditMessageComposer.scss";
@import "./views/rooms/_EntityTile.scss"; @import "./views/rooms/_EntityTile.scss";
@import "./views/rooms/_EventTile.scss"; @import "./views/rooms/_EventTile.scss";
@import "./views/rooms/_EventBubbleTile.scss";
@import "./views/rooms/_GroupLayout.scss"; @import "./views/rooms/_GroupLayout.scss";
@import "./views/rooms/_IRCLayout.scss"; @import "./views/rooms/_IRCLayout.scss";
@import "./views/rooms/_JumpToBottomButton.scss"; @import "./views/rooms/_JumpToBottomButton.scss";

View file

@ -27,6 +27,7 @@ limitations under the License.
// https://bugzilla.mozilla.org/show_bug.cgi?id=255139 // https://bugzilla.mozilla.org/show_bug.cgi?id=255139
display: inline-block; display: inline-block;
user-select: none; user-select: none;
line-height: 1;
} }
.mx_BaseAvatar_initial { .mx_BaseAvatar_initial {

View file

@ -18,7 +18,6 @@ $timelineImageBorderRadius: 4px;
.mx_MImageBody { .mx_MImageBody {
display: block; display: block;
margin-right: 34px;
} }
.mx_MImageBody_thumbnail { .mx_MImageBody_thumbnail {
@ -29,6 +28,10 @@ $timelineImageBorderRadius: 4px;
top: 0; top: 0;
border-radius: $timelineImageBorderRadius; border-radius: $timelineImageBorderRadius;
display: flex;
justify-content: center;
align-items: center;
> canvas { > canvas {
border-radius: $timelineImageBorderRadius; border-radius: $timelineImageBorderRadius;
} }
@ -43,17 +46,6 @@ $timelineImageBorderRadius: 4px;
position: relative; position: relative;
} }
.mx_MImageBody_thumbnail_spinner {
position: absolute;
left: 50%;
top: 50%;
}
// Inner img should be centered around 0, 0
.mx_MImageBody_thumbnail_spinner > * {
transform: translate(-50%, -50%);
}
.mx_MImageBody_gifLabel { .mx_MImageBody_gifLabel {
position: absolute; position: absolute;
display: block; display: block;

View file

@ -26,6 +26,7 @@ limitations under the License.
height: 24px; height: 24px;
vertical-align: middle; vertical-align: middle;
margin-left: 4px; margin-left: 4px;
margin-right: 4px;
&::before { &::before {
content: ''; content: '';

View file

@ -0,0 +1,323 @@
/*
Copyright 2021 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_EventTile[data-layout=bubble],
.mx_EventTile[data-layout=bubble] ~ .mx_EventListSummary {
--avatarSize: 32px;
--gutterSize: 11px;
--cornerRadius: 12px;
--maxWidth: 70%;
}
.mx_EventTile[data-layout=bubble] {
position: relative;
margin-top: var(--gutterSize);
margin-left: 50px;
margin-right: 100px;
&.mx_EventTile_continuation {
margin-top: 2px;
}
/* For replies */
.mx_EventTile {
padding-top: 0;
}
&:hover {
&::before {
content: '';
position: absolute;
top: -1px;
bottom: -1px;
left: -60px;
right: -60px;
z-index: -1;
background: $eventbubble-bg-hover;
border-radius: 4px;
}
.mx_EventTile_avatar {
img {
box-shadow: 0 0 0 3px $eventbubble-bg-hover;
}
}
}
.mx_SenderProfile,
.mx_EventTile_line {
width: fit-content;
max-width: 70%;
}
.mx_SenderProfile {
position: relative;
top: -2px;
left: 2px;
}
&[data-self=false] {
.mx_EventTile_line {
border-bottom-right-radius: var(--cornerRadius);
}
.mx_EventTile_avatar {
left: -34px;
}
.mx_MessageActionBar {
right: 0;
transform: translate3d(50%, 50%, 0);
}
--backgroundColor: $eventbubble-others-bg;
}
&[data-self=true] {
.mx_EventTile_line {
border-bottom-left-radius: var(--cornerRadius);
float: right;
> a {
left: auto;
right: -48px;
}
}
.mx_SenderProfile {
display: none;
}
.mx_ReactionsRow {
float: right;
clear: right;
display: flex;
/* Moving the "add reaction button" before the reactions */
> :last-child {
order: -1;
}
}
.mx_EventTile_avatar {
top: -19px; // height of the sender block
right: -35px;
}
--backgroundColor: $eventbubble-self-bg;
}
.mx_EventTile_line {
position: relative;
padding: var(--gutterSize);
border-top-left-radius: var(--cornerRadius);
border-top-right-radius: var(--cornerRadius);
background: var(--backgroundColor);
display: flex;
gap: 5px;
margin: 0 -12px 0 -9px;
> a {
position: absolute;
left: -48px;
}
}
&.mx_EventTile_continuation[data-self=false] .mx_EventTile_line {
border-top-left-radius: 0;
}
&.mx_EventTile_lastInSection[data-self=false] .mx_EventTile_line {
border-bottom-left-radius: var(--cornerRadius);
}
&.mx_EventTile_continuation[data-self=true] .mx_EventTile_line {
border-top-right-radius: 0;
}
&.mx_EventTile_lastInSection[data-self=true] .mx_EventTile_line {
border-bottom-right-radius: var(--cornerRadius);
}
.mx_EventTile_avatar {
position: absolute;
top: 0;
line-height: 1;
img {
box-shadow: 0 0 0 3px $eventbubble-avatar-outline;
border-radius: 50%;
}
}
&[data-has-reply=true] {
> .mx_EventTile_line {
flex-direction: column;
}
.mx_ReplyThread_show {
order: 99999;
}
.mx_ReplyThread {
margin: 0 calc(-1 * var(--gutterSize));
.mx_EventTile_reply {
max-width: 90%;
padding: 0;
> a {
display: none !important;
}
}
.mx_EventTile {
display: flex;
gap: var(--gutterSize);
.mx_EventTile_avatar {
position: static;
}
.mx_SenderProfile {
display: none;
}
}
}
}
.mx_EditMessageComposer_buttons {
position: static;
padding: 0;
margin: 0;
background: transparent;
}
.mx_ReactionsRow {
margin-right: -18px;
margin-left: -9px;
}
.mx_ReplyThread {
border-left-width: 2px;
border-left-color: $eventbubble-reply-color;
}
&.mx_EventTile_bubbleContainer,
&.mx_EventTile_info,
& ~ .mx_EventListSummary[data-expanded=false] {
--backgroundColor: transparent;
--gutterSize: 0;
display: flex;
align-items: center;
justify-content: center;
.mx_EventTile_avatar {
position: static;
order: -1;
margin-right: 5px;
}
}
& ~ .mx_EventListSummary {
--maxWidth: 80%;
margin-left: calc(var(--avatarSize) + var(--gutterSize));
margin-right: calc(var(--gutterSize) + var(--avatarSize));
.mx_EventListSummary_toggle {
float: none;
margin: 0;
order: 9;
margin-left: 5px;
}
.mx_EventListSummary_avatars {
padding-top: 0;
}
&::after {
content: "";
clear: both;
}
.mx_EventTile {
margin: 0 6px;
}
.mx_EventTile_line {
margin: 0 5px;
> a {
left: auto;
right: 0;
transform: translateX(calc(100% + 5px));
}
}
.mx_MessageActionBar {
transform: translate3d(50%, 0, 0);
}
}
& ~ .mx_EventListSummary[data-expanded=false] {
padding: 0 34px;
}
/* events that do not require bubble layout */
& ~ .mx_EventListSummary,
&.mx_EventTile_bad {
.mx_EventTile_line {
background: transparent;
}
&:hover {
&::before {
background: transparent;
}
}
}
& + .mx_EventListSummary {
.mx_EventTile {
margin-top: 0;
padding: 0;
}
}
.mx_EventListSummary_toggle {
margin-right: 55px;
}
/* Special layout scenario for "Unable To Decrypt (UTD)" events */
&.mx_EventTile_bad > .mx_EventTile_line {
display: grid;
grid-template:
"reply reply" auto
"shield body" auto
"shield link" auto
/ auto 1fr;
.mx_EventTile_e2eIcon {
grid-area: shield;
}
.mx_UnknownBody {
grid-area: body;
}
.mx_EventTile_keyRequestInfo {
grid-area: link;
}
.mx_ReplyThread_wrapper {
grid-area: reply;
}
}
.mx_EventTile_readAvatars {
position: absolute;
right: -110px;
bottom: 0;
top: auto;
}
.mx_MTextBody {
max-width: 100%;
}
}

View file

@ -1,6 +1,6 @@
/* /*
Copyright 2015, 2016 OpenMarket Ltd Copyright 2015, 2016 OpenMarket Ltd
Copyright 2020 The Matrix.org Foundation C.I.C. Copyright 2020-2021 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.
@ -18,102 +18,305 @@ limitations under the License.
$left-gutter: 64px; $left-gutter: 64px;
$hover-select-border: 4px; $hover-select-border: 4px;
.mx_EventTile { .mx_EventTile:not([data-layout=bubble]) {
max-width: 100%; max-width: 100%;
clear: both; clear: both;
padding-top: 18px; padding-top: 18px;
font-size: $font-14px; font-size: $font-14px;
position: relative; position: relative;
}
.mx_EventTile.mx_EventTile_info { &.mx_EventTile_info {
padding-top: 1px; padding-top: 1px;
} }
.mx_EventTile_avatar { .mx_EventTile_avatar {
top: 14px; top: 14px;
left: 8px; left: 8px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
.mx_EventTile.mx_EventTile_info .mx_EventTile_avatar { &.mx_EventTile_info .mx_EventTile_avatar {
top: $font-6px; top: $font-6px;
left: $left-gutter; left: $left-gutter;
} }
.mx_EventTile_continuation { &.mx_EventTile_continuation {
padding-top: 0px !important; padding-top: 0px !important;
&.mx_EventTile_isEditing {
padding-top: 5px !important;
margin-top: -5px;
}
}
&.mx_EventTile_isEditing { &.mx_EventTile_isEditing {
padding-top: 5px !important; background-color: $header-panel-bg-color;
margin-top: -5px;
} }
}
.mx_EventTile_isEditing { .mx_SenderProfile {
background-color: $header-panel-bg-color; color: $primary-fg-color;
} font-size: $font-14px;
display: inline-block; /* anti-zalgo, with overflow hidden */
overflow: hidden;
cursor: pointer;
padding-bottom: 0px;
padding-top: 0px;
margin: 0px;
/* the next three lines, along with overflow hidden, truncate long display names */
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - $left-gutter);
}
.mx_EventTile .mx_SenderProfile { .mx_SenderProfile .mx_Flair {
color: $primary-fg-color; opacity: 0.7;
font-size: $font-14px; margin-left: 5px;
display: inline-block; /* anti-zalgo, with overflow hidden */ display: inline-block;
overflow: hidden; vertical-align: top;
cursor: pointer; overflow: hidden;
padding-bottom: 0px; user-select: none;
padding-top: 0px;
margin: 0px;
/* the next three lines, along with overflow hidden, truncate long display names */
white-space: nowrap;
text-overflow: ellipsis;
max-width: calc(100% - $left-gutter);
}
.mx_EventTile .mx_SenderProfile .mx_Flair { img {
opacity: 0.7; vertical-align: -2px;
margin-left: 5px; margin-right: 2px;
display: inline-block; border-radius: 8px;
vertical-align: top; }
overflow: hidden; }
user-select: none;
img { &.mx_EventTile_isEditing .mx_MessageTimestamp {
vertical-align: -2px; visibility: hidden;
margin-right: 2px; }
.mx_MessageTimestamp {
display: block;
white-space: nowrap;
left: 0px;
text-align: center;
user-select: none;
}
&.mx_EventTile_continuation .mx_EventTile_line {
clear: both;
}
.mx_EventTile_line, .mx_EventTile_reply {
position: relative;
padding-left: $left-gutter;
border-radius: 8px; border-radius: 8px;
} }
}
.mx_EventTile_isEditing .mx_MessageTimestamp { .mx_EventTile_reply {
visibility: hidden; margin-right: 10px;
}
.mx_EventTile .mx_MessageTimestamp {
display: block;
white-space: nowrap;
left: 0px;
text-align: center;
user-select: none;
}
.mx_EventTile_continuation .mx_EventTile_line {
clear: both;
}
.mx_EventTile_line, .mx_EventTile_reply {
position: relative;
padding-left: $left-gutter;
border-radius: 8px;
}
.mx_RoomView_timeline_rr_enabled,
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
.mx_EventListSummary {
.mx_EventTile_line {
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
margin-right: 110px;
} }
&.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
left: calc(-$hover-select-border);
}
/* this is used for the tile for the event which is selected via the URL.
* TODO: ultimately we probably want some transition on here.
*/
&.mx_EventTile_selected > .mx_EventTile_line {
border-left: $accent-color 4px solid;
padding-left: calc($left-gutter - $hover-select-border);
background-color: $event-selected-color;
}
&.mx_EventTile_highlight,
&.mx_EventTile_highlight .markdown-body {
color: $event-highlight-fg-color;
.mx_EventTile_line {
background-color: $event-highlight-bg-color;
}
}
&.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px);
}
&.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px - $hover-select-border);
}
&.mx_EventTile:hover .mx_EventTile_line,
&.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
&.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
background-color: $event-selected-color;
}
.mx_EventTile_searchHighlight {
background-color: $accent-color;
color: $accent-fg-color;
border-radius: 5px;
padding-left: 2px;
padding-right: 2px;
cursor: pointer;
}
.mx_EventTile_searchHighlight a {
background-color: $accent-color;
color: $accent-fg-color;
}
.mx_EventTile_receiptSent,
.mx_EventTile_receiptSending {
// We don't use `position: relative` on the element because then it won't line
// up with the other read receipts
&::before {
background-color: $tertiary-fg-color;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 14px;
width: 14px;
height: 14px;
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
}
}
.mx_EventTile_receiptSent::before {
mask-image: url('$(res)/img/element-icons/circle-sent.svg');
}
.mx_EventTile_receiptSending::before {
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
}
&.mx_EventTile_contextual {
opacity: 0.4;
}
.mx_EventTile_msgOption {
float: right;
text-align: right;
position: relative;
width: 90px;
/* Hack to stop the height of this pushing the messages apart.
Replaces margin-top: -6px. This interacts better with a read
marker being in between. Content overflows. */
height: 1px;
margin-right: 10px;
}
.mx_EventTile_msgOption a {
text-decoration: none;
}
/* all the overflow-y: hidden; are to trap Zalgos -
but they introduce an implicit overflow-x: auto.
so make that explicitly hidden too to avoid random
horizontal scrollbars occasionally appearing, like in
https://github.com/vector-im/vector-web/issues/1154
*/
.mx_EventTile_content {
display: block;
overflow-y: hidden;
overflow-x: hidden;
margin-right: 34px;
}
/* De-zalgoing */
.mx_EventTile_body {
overflow-y: hidden;
}
/* Spoiler stuff */
.mx_EventTile_spoiler {
cursor: pointer;
}
.mx_EventTile_spoiler_reason {
color: $event-timestamp-color;
font-size: $font-11px;
}
.mx_EventTile_spoiler_content {
filter: blur(5px) saturate(0.1) sepia(1);
transition-duration: 0.5s;
}
.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content {
filter: none;
}
&:hover.mx_EventTile_verified .mx_EventTile_line,
&:hover.mx_EventTile_unverified .mx_EventTile_line,
&:hover.mx_EventTile_unknown .mx_EventTile_line {
padding-left: calc($left-gutter - $hover-select-border);
}
&:hover.mx_EventTile_verified .mx_EventTile_line {
border-left: $e2e-verified-color $EventTile_e2e_state_indicator_width solid;
}
&:hover.mx_EventTile_unverified .mx_EventTile_line {
border-left: $e2e-unverified-color $EventTile_e2e_state_indicator_width solid;
}
&:hover.mx_EventTile_unknown .mx_EventTile_line {
border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
}
&:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
&:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line,
&:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px - $hover-select-border);
}
/* End to end encryption stuff */
&:hover .mx_EventTile_e2eIcon {
opacity: 1;
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
&:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
&:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
&:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
left: calc(-$hover-select-border);
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
&:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon,
&:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon,
&:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon {
display: block;
left: 41px;
}
.mx_MImageBody {
margin-right: 34px;
}
.mx_EventTile_e2eIcon {
position: absolute;
top: 6px;
left: 44px;
bottom: 0;
right: 0;
}
.mx_ReactionsRow {
margin: 0;
padding: 6px 60px;
}
}
.mx_RoomView_timeline_rr_enabled {
.mx_EventTile:not([data-layout=bubble]) {
.mx_EventTile_line {
/* ideally should be 100px, but 95px gives us a max thumbnail size of 800x600, which is nice */
margin-right: 110px;
}
}
// on ELS we need the margin to allow interaction with the expand/collapse button which is normally in the RR gutter
} }
.mx_EventTile_bubbleContainer { .mx_EventTile_bubbleContainer {
@ -132,121 +335,6 @@ $hover-select-border: 4px;
} }
} }
.mx_EventTile_reply {
margin-right: 10px;
}
/* HACK to override line-height which is already marked important elsewhere */
.mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
font-size: 48px !important;
line-height: 57px !important;
}
.mx_EventTile_selected > div > a > .mx_MessageTimestamp {
left: calc(-$hover-select-border);
}
.mx_EventTile:hover .mx_MessageActionBar,
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
[data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar,
.mx_EventTile.focus-visible:focus-within .mx_MessageActionBar {
visibility: visible;
}
/* this is used for the tile for the event which is selected via the URL.
* TODO: ultimately we probably want some transition on here.
*/
.mx_EventTile_selected > .mx_EventTile_line {
border-left: $accent-color 4px solid;
padding-left: calc($left-gutter - $hover-select-border);
background-color: $event-selected-color;
}
.mx_EventTile_highlight,
.mx_EventTile_highlight .markdown-body {
color: $event-highlight-fg-color;
.mx_EventTile_line {
background-color: $event-highlight-bg-color;
}
}
.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px);
}
.mx_EventTile_selected.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px - $hover-select-border);
}
.mx_EventTile:hover .mx_EventTile_line,
.mx_EventTile.mx_EventTile_actionBarFocused .mx_EventTile_line,
.mx_EventTile.focus-visible:focus-within .mx_EventTile_line {
background-color: $event-selected-color;
}
.mx_EventTile_searchHighlight {
background-color: $accent-color;
color: $accent-fg-color;
border-radius: 5px;
padding-left: 2px;
padding-right: 2px;
cursor: pointer;
}
.mx_EventTile_searchHighlight a {
background-color: $accent-color;
color: $accent-fg-color;
}
.mx_EventTile_receiptSent,
.mx_EventTile_receiptSending {
// We don't use `position: relative` on the element because then it won't line
// up with the other read receipts
&::before {
background-color: $tertiary-fg-color;
mask-repeat: no-repeat;
mask-position: center;
mask-size: 14px;
width: 14px;
height: 14px;
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
}
}
.mx_EventTile_receiptSent::before {
mask-image: url('$(res)/img/element-icons/circle-sent.svg');
}
.mx_EventTile_receiptSending::before {
mask-image: url('$(res)/img/element-icons/circle-sending.svg');
}
.mx_EventTile_contextual {
opacity: 0.4;
}
.mx_EventTile_msgOption {
float: right;
text-align: right;
position: relative;
width: 90px;
/* Hack to stop the height of this pushing the messages apart.
Replaces margin-top: -6px. This interacts better with a read
marker being in between. Content overflows. */
height: 1px;
margin-right: 10px;
}
.mx_EventTile_msgOption a {
text-decoration: none;
}
.mx_EventTile_readAvatars { .mx_EventTile_readAvatars {
position: relative; position: relative;
display: inline-block; display: inline-block;
@ -277,52 +365,27 @@ $hover-select-border: 4px;
position: absolute; position: absolute;
} }
/* all the overflow-y: hidden; are to trap Zalgos - /* HACK to override line-height which is already marked important elsewhere */
but they introduce an implicit overflow-x: auto. .mx_EventTile_bigEmoji.mx_EventTile_bigEmoji {
so make that explicitly hidden too to avoid random font-size: 48px !important;
horizontal scrollbars occasionally appearing, like in line-height: 57px !important;
https://github.com/vector-im/vector-web/issues/1154
*/
.mx_EventTile_content {
display: block;
overflow-y: hidden;
overflow-x: hidden;
margin-right: 34px;
} }
/* De-zalgoing */ .mx_EventTile_content .mx_EventTile_edited {
.mx_EventTile_body { user-select: none;
overflow-y: hidden; font-size: $font-12px;
} color: $roomtopic-color;
display: inline-block;
/* Spoiler stuff */ margin-left: 9px;
.mx_EventTile_spoiler {
cursor: pointer; cursor: pointer;
} }
.mx_EventTile_spoiler_reason {
color: $event-timestamp-color;
font-size: $font-11px;
}
.mx_EventTile_spoiler_content {
filter: blur(5px) saturate(0.1) sepia(1);
transition-duration: 0.5s;
}
.mx_EventTile_spoiler.visible > .mx_EventTile_spoiler_content {
filter: none;
}
.mx_EventTile_e2eIcon { .mx_EventTile_e2eIcon {
position: absolute; position: relative;
top: 6px;
left: 44px;
width: 14px; width: 14px;
height: 14px; height: 14px;
display: block; display: block;
bottom: 0;
right: 0;
opacity: 0.2; opacity: 0.2;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: contain; background-size: contain;
@ -381,87 +444,6 @@ $hover-select-border: 4px;
opacity: 1; opacity: 1;
} }
.mx_EventTile_keyRequestInfo {
font-size: $font-12px;
}
.mx_EventTile_keyRequestInfo_text {
opacity: 0.5;
}
.mx_EventTile_keyRequestInfo_text a {
color: $primary-fg-color;
text-decoration: underline;
cursor: pointer;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p {
text-align: auto;
margin-left: 3px;
margin-right: 3px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:first-child {
margin-top: 0px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:last-child {
margin-bottom: 0px;
}
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
padding-left: calc($left-gutter - $hover-select-border);
}
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line {
border-left: $e2e-verified-color $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line {
border-left: $e2e-unverified-color $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line {
border-left: $e2e-unknown-color $EventTile_e2e_state_indicator_width solid;
}
.mx_EventTile:hover.mx_EventTile_verified.mx_EventTile_info .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unverified.mx_EventTile_info .mx_EventTile_line,
.mx_EventTile:hover.mx_EventTile_unknown.mx_EventTile_info .mx_EventTile_line {
padding-left: calc($left-gutter + 18px - $hover-select-border);
}
/* End to end encryption stuff */
.mx_EventTile:hover .mx_EventTile_e2eIcon {
opacity: 1;
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > a > .mx_MessageTimestamp,
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > a > .mx_MessageTimestamp,
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > a > .mx_MessageTimestamp {
left: calc(-$hover-select-border);
}
// Explicit relationships so that it doesn't apply to nested EventTile components (e.g in Replies)
.mx_EventTile:hover.mx_EventTile_verified .mx_EventTile_line > .mx_EventTile_e2eIcon,
.mx_EventTile:hover.mx_EventTile_unverified .mx_EventTile_line > .mx_EventTile_e2eIcon,
.mx_EventTile:hover.mx_EventTile_unknown .mx_EventTile_line > .mx_EventTile_e2eIcon {
display: block;
left: 41px;
}
.mx_EventTile_content .mx_EventTile_edited {
user-select: none;
font-size: $font-12px;
color: $roomtopic-color;
display: inline-block;
margin-left: 9px;
cursor: pointer;
}
/* Various markdown overrides */ /* Various markdown overrides */
.mx_EventTile_body pre { .mx_EventTile_body pre {
@ -595,6 +577,35 @@ $hover-select-border: 4px;
/* end of overrides */ /* end of overrides */
.mx_EventTile_keyRequestInfo {
font-size: $font-12px;
}
.mx_EventTile_keyRequestInfo_text {
opacity: 0.5;
}
.mx_EventTile_keyRequestInfo_text a {
color: $primary-fg-color;
text-decoration: underline;
cursor: pointer;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p {
text-align: auto;
margin-left: 3px;
margin-right: 3px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:first-child {
margin-top: 0px;
}
.mx_EventTile_keyRequestInfo_tooltip_contents p:last-child {
margin-bottom: 0px;
}
.mx_EventTile_tileError { .mx_EventTile_tileError {
color: red; color: red;
text-align: center; text-align: center;
@ -615,6 +626,13 @@ $hover-select-border: 4px;
} }
} }
.mx_EventTile:hover .mx_MessageActionBar,
.mx_EventTile.mx_EventTile_actionBarFocused .mx_MessageActionBar,
[data-whatinput='keyboard'] .mx_EventTile:focus-within .mx_MessageActionBar,
.mx_EventTile.focus-visible:focus-within .mx_MessageActionBar {
visibility: visible;
}
@media only screen and (max-width: 480px) { @media only screen and (max-width: 480px) {
.mx_EventTile_line, .mx_EventTile_reply { .mx_EventTile_line, .mx_EventTile_reply {
padding-left: 0; padding-left: 0;

View file

@ -227,6 +227,13 @@ $groupFilterPanel-background-blur-amount: 30px;
$composer-shadow-color: rgba(0, 0, 0, 0.28); $composer-shadow-color: rgba(0, 0, 0, 0.28);
// Bubble tiles
$eventbubble-self-bg: #143A34;
$eventbubble-others-bg: #394049;
$eventbubble-bg-hover: #433C23;
$eventbubble-avatar-outline: $bg-color;
$eventbubble-reply-color: #C1C6CD;
// ***** Mixins! ***** // ***** Mixins! *****
@define-mixin mx_DialogButton { @define-mixin mx_DialogButton {

View file

@ -347,6 +347,13 @@ $appearance-tab-border-color: $input-darker-bg-color;
$composer-shadow-color: tranparent; $composer-shadow-color: tranparent;
// Bubble tiles
$eventbubble-self-bg: #F8FDFC;
$eventbubble-others-bg: #F7F8F9;
$eventbubble-bg-hover: rgb(242, 242, 242);
$eventbubble-avatar-outline: #fff;
$eventbubble-reply-color: #C1C6CD;
// ***** Mixins! ***** // ***** Mixins! *****
@define-mixin mx_DialogButton { @define-mixin mx_DialogButton {

View file

@ -349,6 +349,13 @@ $groupFilterPanel-background-blur-amount: 20px;
$composer-shadow-color: rgba(0, 0, 0, 0.04); $composer-shadow-color: rgba(0, 0, 0, 0.04);
// Bubble tiles
$eventbubble-self-bg: #F8FDFC;
$eventbubble-others-bg: #F7F8F9;
$eventbubble-bg-hover: #FEFCF5;
$eventbubble-avatar-outline: $primary-bg-color;
$eventbubble-reply-color: #C1C6CD;
// ***** Mixins! ***** // ***** Mixins! *****
@define-mixin mx_DialogButton { @define-mixin mx_DialogButton {

View file

@ -653,8 +653,10 @@ export default class MessagePanel extends React.Component<IProps, IState> {
} }
let willWantDateSeparator = false; let willWantDateSeparator = false;
let lastInSection = true;
if (nextEvent) { if (nextEvent) {
willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date()); willWantDateSeparator = this.wantsDateSeparator(mxEv, nextEvent.getDate() || new Date());
lastInSection = willWantDateSeparator || mxEv.getSender() !== nextEvent.getSender();
} }
// is this a continuation of the previous message? // is this a continuation of the previous message?
@ -712,7 +714,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
isTwelveHour={this.props.isTwelveHour} isTwelveHour={this.props.isTwelveHour}
permalinkCreator={this.props.permalinkCreator} permalinkCreator={this.props.permalinkCreator}
last={last} last={last}
lastInSection={willWantDateSeparator} lastInSection={lastInSection}
lastSuccessful={isLastSuccessful} lastSuccessful={isLastSuccessful}
isSelectedEvent={highlight} isSelectedEvent={highlight}
getRelationsForEvent={this.props.getRelationsForEvent} getRelationsForEvent={this.props.getRelationsForEvent}
@ -720,6 +722,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
layout={this.props.layout} layout={this.props.layout}
enableFlair={this.props.enableFlair} enableFlair={this.props.enableFlair}
showReadReceipts={this.props.showReadReceipts} showReadReceipts={this.props.showReadReceipts}
hideSender={this.props.room.getMembers().length <= 2 && this.props.layout === Layout.Bubble}
/> />
</TileErrorBoundary>, </TileErrorBoundary>,
); );

View file

@ -63,7 +63,7 @@ const EventListSummary: React.FC<IProps> = ({
// If we are only given few events then just pass them through // If we are only given few events then just pass them through
if (events.length < threshold) { if (events.length < threshold) {
return ( return (
<li className="mx_EventListSummary" data-scroll-tokens={eventIds}> <li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={true}>
{ children } { children }
</li> </li>
); );
@ -92,7 +92,7 @@ const EventListSummary: React.FC<IProps> = ({
} }
return ( return (
<li className="mx_EventListSummary" data-scroll-tokens={eventIds}> <li className="mx_EventListSummary" data-scroll-tokens={eventIds} data-expanded={expanded + ""}>
<AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}> <AccessibleButton className="mx_EventListSummary_toggle" onClick={toggleExpanded} aria-expanded={expanded}>
{ expanded ? _t('collapse') : _t('expand') } { expanded ? _t('collapse') : _t('expand') }
</AccessibleButton> </AccessibleButton>
@ -101,4 +101,8 @@ const EventListSummary: React.FC<IProps> = ({
); );
}; };
EventListSummary.defaultProps = {
startExpanded: false,
};
export default EventListSummary; export default EventListSummary;

View file

@ -433,9 +433,9 @@ export default class MImageBody extends React.Component<IProps, IState> {
protected getPlaceholder(width: number, height: number): JSX.Element { protected getPlaceholder(width: number, height: number): JSX.Element {
const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD]; const blurhash = this.props.mxEvent.getContent().info[BLURHASH_FIELD];
if (blurhash) return <Blurhash hash={blurhash} width={width} height={height} />; if (blurhash) return <Blurhash hash={blurhash} width={width} height={height} />;
return <div className="mx_MImageBody_thumbnail_spinner"> return (
<InlineSpinner w={32} h={32} /> <InlineSpinner w={32} h={32} />
</div>; );
} }
// Overidden by MStickerBody // Overidden by MStickerBody

View file

@ -16,12 +16,19 @@ limitations under the License.
*/ */
import React, { forwardRef } from "react"; import React, { forwardRef } from "react";
import { MatrixEvent } from "matrix-js-sdk/src";
export default forwardRef(({ mxEvent }, ref) => { interface IProps {
mxEvent: MatrixEvent;
children?: React.ReactNode;
}
export default forwardRef(({ mxEvent, children }: IProps, ref: React.RefObject<HTMLSpanElement>) => {
const text = mxEvent.getContent().body; const text = mxEvent.getContent().body;
return ( return (
<span className="mx_UnknownBody" ref={ref}> <span className="mx_UnknownBody" ref={ref}>
{ text } { text }
{ children }
</span> </span>
); );
}); });

View file

@ -170,8 +170,6 @@ export function getHandlerTile(ev) {
return eventTileTypes[type]; return eventTileTypes[type];
} }
const MAX_READ_AVATARS = 5;
// Our component structure for EventTiles on the timeline is: // Our component structure for EventTiles on the timeline is:
// //
// .-EventTile------------------------------------------------. // .-EventTile------------------------------------------------.
@ -297,6 +295,9 @@ interface IProps {
// whether or not to always show timestamps // whether or not to always show timestamps
alwaysShowTimestamps?: boolean; alwaysShowTimestamps?: boolean;
// whether or not to display the sender
hideSender?: boolean;
} }
interface IState { interface IState {
@ -656,6 +657,10 @@ export default class EventTile extends React.Component<IProps, IState> {
return <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />; return <SentReceipt messageState={this.props.mxEvent.getAssociatedStatus()} />;
} }
const MAX_READ_AVATARS = this.props.layout == Layout.Bubble
? 2
: 5;
// return early if there are no read receipts // return early if there are no read receipts
if (!this.props.readReceipts || this.props.readReceipts.length === 0) { if (!this.props.readReceipts || this.props.readReceipts.length === 0) {
// We currently must include `mx_EventTile_readAvatars` in the DOM // We currently must include `mx_EventTile_readAvatars` in the DOM
@ -951,7 +956,7 @@ export default class EventTile extends React.Component<IProps, IState> {
); );
} }
if (needsSenderProfile) { if (needsSenderProfile && this.props.hideSender !== true) {
if (!this.props.tileShape) { if (!this.props.tileShape) {
sender = <SenderProfile onClick={this.onSenderProfileClick} sender = <SenderProfile onClick={this.onSenderProfileClick}
mxEvent={this.props.mxEvent} mxEvent={this.props.mxEvent}
@ -971,8 +976,12 @@ export default class EventTile extends React.Component<IProps, IState> {
onFocusChange={this.onActionBarFocusChange} onFocusChange={this.onActionBarFocusChange}
/> : undefined; /> : undefined;
const showTimestamp = this.props.mxEvent.getTs() && const showTimestamp = this.props.mxEvent.getTs()
(this.props.alwaysShowTimestamps || this.props.last || this.state.hover || this.state.actionBarFocused); && (this.props.alwaysShowTimestamps
|| this.props.last
|| this.state.hover
|| this.state.actionBarFocused);
const timestamp = showTimestamp ? const timestamp = showTimestamp ?
<MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null; <MessageTimestamp showTwelveHour={this.props.isTwelveHour} ts={this.props.mxEvent.getTs()} /> : null;
@ -1112,6 +1121,8 @@ export default class EventTile extends React.Component<IProps, IState> {
this.props.alwaysShowTimestamps || this.state.hover, this.props.alwaysShowTimestamps || this.state.hover,
); );
const isOwnEvent = this.props.mxEvent?.sender?.userId === MatrixClientPeg.get().getUserId();
// tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers // tab-index=-1 to allow it to be focusable but do not add tab stop for it, primarily for screen readers
return ( return (
React.createElement(this.props.as || "li", { React.createElement(this.props.as || "li", {
@ -1121,6 +1132,9 @@ export default class EventTile extends React.Component<IProps, IState> {
"aria-live": ariaLive, "aria-live": ariaLive,
"aria-atomic": "true", "aria-atomic": "true",
"data-scroll-tokens": scrollToken, "data-scroll-tokens": scrollToken,
"data-layout": this.props.layout,
"data-self": isOwnEvent,
"data-has-reply": !!thread,
"onMouseEnter": () => this.setState({ hover: true }), "onMouseEnter": () => this.setState({ hover: true }),
"onMouseLeave": () => this.setState({ hover: false }), "onMouseLeave": () => this.setState({ hover: false }),
}, <> }, <>
@ -1142,11 +1156,11 @@ export default class EventTile extends React.Component<IProps, IState> {
onHeightChanged={this.props.onHeightChanged} onHeightChanged={this.props.onHeightChanged}
/> />
{ keyRequestInfo } { keyRequestInfo }
{ reactionsRow }
{ actionBar } { actionBar }
</div> </div>,
{ msgOption } { reactionsRow },
{ avatar } { msgOption },
{ avatar },
</>) </>)
); );
} }

View file

@ -37,6 +37,8 @@ import StyledRadioGroup from "../../../elements/StyledRadioGroup";
import { SettingLevel } from "../../../../../settings/SettingLevel"; import { SettingLevel } from "../../../../../settings/SettingLevel";
import { UIFeature } from "../../../../../settings/UIFeature"; import { UIFeature } from "../../../../../settings/UIFeature";
import { Layout } from "../../../../../settings/Layout"; import { Layout } from "../../../../../settings/Layout";
import classNames from 'classnames';
import StyledRadioButton from '../../../elements/StyledRadioButton';
import { replaceableComponent } from "../../../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../../../utils/replaceableComponent";
import { compare } from "../../../../../utils/strings"; import { compare } from "../../../../../utils/strings";
@ -241,6 +243,19 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
this.setState({ customThemeUrl: e.target.value }); this.setState({ customThemeUrl: e.target.value });
}; };
private onLayoutChange = (e: React.ChangeEvent<HTMLInputElement>): void => {
let layout;
switch (e.target.value) {
case "irc": layout = Layout.IRC; break;
case "group": layout = Layout.Group; break;
case "bubble": layout = Layout.Bubble; break;
}
this.setState({ layout: layout });
SettingsStore.setValue("layout", null, SettingLevel.DEVICE, layout);
};
private onIRCLayoutChange = (enabled: boolean) => { private onIRCLayoutChange = (enabled: boolean) => {
if (enabled) { if (enabled) {
this.setState({ layout: Layout.IRC }); this.setState({ layout: Layout.IRC });
@ -373,6 +388,77 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
</div>; </div>;
} }
private renderLayoutSection = () => {
return <div className="mx_SettingsTab_section mx_AppearanceUserSettingsTab_Layout">
<span className="mx_SettingsTab_subheading">{ _t("Message layout") }</span>
<div className="mx_AppearanceUserSettingsTab_Layout_RadioButtons">
<div className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.IRC,
})}>
<EventTilePreview
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
message={this.MESSAGE_PREVIEW_TEXT}
layout={Layout.IRC}
userId={this.state.userId}
displayName={this.state.displayName}
avatarUrl={this.state.avatarUrl}
/>
<StyledRadioButton
name="layout"
value="irc"
checked={this.state.layout == Layout.IRC}
onChange={this.onLayoutChange}
>
{ "IRC" }
</StyledRadioButton>
</div>
<div className="mx_AppearanceUserSettingsTab_spacer" />
<div className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.Group,
})}>
<EventTilePreview
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
message={this.MESSAGE_PREVIEW_TEXT}
layout={Layout.Group}
userId={this.state.userId}
displayName={this.state.displayName}
avatarUrl={this.state.avatarUrl}
/>
<StyledRadioButton
name="layout"
value="group"
checked={this.state.layout == Layout.Group}
onChange={this.onLayoutChange}
>
{_t("Modern")}
</StyledRadioButton>
</div>
<div className="mx_AppearanceUserSettingsTab_spacer" />
<div className={classNames("mx_AppearanceUserSettingsTab_Layout_RadioButton", {
mx_AppearanceUserSettingsTab_Layout_RadioButton_selected: this.state.layout == Layout.Bubble,
})}>
<EventTilePreview
className="mx_AppearanceUserSettingsTab_Layout_RadioButton_preview"
message={this.MESSAGE_PREVIEW_TEXT}
layout={Layout.Bubble}
userId={this.state.userId}
displayName={this.state.displayName}
avatarUrl={this.state.avatarUrl}
/>
<StyledRadioButton
name="layout"
value="bubble"
checked={this.state.layout == Layout.Bubble}
onChange={this.onLayoutChange}
>
{_t("Message bubbles")}
</StyledRadioButton>
</div>
</div>
</div>;
};
private renderAdvancedSection() { private renderAdvancedSection() {
if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null; if (!SettingsStore.getValue(UIFeature.AdvancedSettings)) return null;
@ -396,14 +482,17 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
name="useCompactLayout" name="useCompactLayout"
level={SettingLevel.DEVICE} level={SettingLevel.DEVICE}
useCheckbox={true} useCheckbox={true}
disabled={this.state.layout == Layout.IRC} disabled={this.state.layout !== Layout.Group}
/> />
<StyledCheckbox
checked={this.state.layout == Layout.IRC} { !SettingsStore.getValue("feature_new_layout_switcher") ?
onChange={(ev) => this.onIRCLayoutChange(ev.target.checked)} <StyledCheckbox
> checked={this.state.layout == Layout.IRC}
{ _t("Enable experimental, compact IRC style layout") } onChange={(ev) => this.onIRCLayoutChange(ev.target.checked)}
</StyledCheckbox> >
{ _t("Enable experimental, compact IRC style layout") }
</StyledCheckbox> : null
}
<SettingsFlag <SettingsFlag
name="useSystemFont" name="useSystemFont"
@ -444,6 +533,7 @@ export default class AppearanceUserSettingsTab extends React.Component<IProps, I
{ _t("Appearance Settings only affect this %(brand)s session.", { brand }) } { _t("Appearance Settings only affect this %(brand)s session.", { brand }) }
</div> </div>
{ this.renderThemeSection() } { this.renderThemeSection() }
{ SettingsStore.getValue("feature_new_layout_switcher") ? this.renderLayoutSection() : null }
{ this.renderFontSection() } { this.renderFontSection() }
{ this.renderAdvancedSection() } { this.renderAdvancedSection() }
</div> </div>

View file

@ -823,6 +823,7 @@
"Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices", "Offline encrypted messaging using dehydrated devices": "Offline encrypted messaging using dehydrated devices",
"Enable advanced debugging for the room list": "Enable advanced debugging for the room list", "Enable advanced debugging for the room list": "Enable advanced debugging for the room list",
"Show info about bridges in room settings": "Show info about bridges in room settings", "Show info about bridges in room settings": "Show info about bridges in room settings",
"New layout switcher (with message bubbles)": "New layout switcher (with message bubbles)",
"Font size": "Font size", "Font size": "Font size",
"Use custom size": "Use custom size", "Use custom size": "Use custom size",
"Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing", "Enable Emoji suggestions while typing": "Enable Emoji suggestions while typing",
@ -1245,6 +1246,9 @@
"Custom theme URL": "Custom theme URL", "Custom theme URL": "Custom theme URL",
"Add theme": "Add theme", "Add theme": "Add theme",
"Theme": "Theme", "Theme": "Theme",
"Message layout": "Message layout",
"Modern": "Modern",
"Message bubbles": "Message bubbles",
"Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.", "Set the name of a font installed on your system & %(brand)s will attempt to use it.": "Set the name of a font installed on your system & %(brand)s will attempt to use it.",
"Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout", "Enable experimental, compact IRC style layout": "Enable experimental, compact IRC style layout",
"Customise your appearance": "Customise your appearance", "Customise your appearance": "Customise your appearance",

View file

@ -1,5 +1,6 @@
/* /*
Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com> Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
Copyright 2021 Quirin Götz <codeworks@supercable.onl>
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.
@ -19,7 +20,8 @@ import PropTypes from 'prop-types';
/* TODO: This should be later reworked into something more generic */ /* TODO: This should be later reworked into something more generic */
export enum Layout { export enum Layout {
IRC = "irc", IRC = "irc",
Group = "group" Group = "group",
Bubble = "bubble",
} }
/* We need this because multiple components are still using JavaScript */ /* We need this because multiple components are still using JavaScript */

View file

@ -41,6 +41,7 @@ import { Layout } from "./Layout";
import ReducedMotionController from './controllers/ReducedMotionController'; import ReducedMotionController from './controllers/ReducedMotionController';
import IncompatibleController from "./controllers/IncompatibleController"; import IncompatibleController from "./controllers/IncompatibleController";
import SdkConfig from "../SdkConfig"; import SdkConfig from "../SdkConfig";
import NewLayoutSwitcherController from './controllers/NewLayoutSwitcherController';
// These are just a bunch of helper arrays to avoid copy/pasting a bunch of times // These are just a bunch of helper arrays to avoid copy/pasting a bunch of times
const LEVELS_ROOM_SETTINGS = [ const LEVELS_ROOM_SETTINGS = [
@ -321,6 +322,13 @@ export const SETTINGS: {[setting: string]: ISetting} = {
displayName: _td("Show info about bridges in room settings"), displayName: _td("Show info about bridges in room settings"),
default: false, default: false,
}, },
"feature_new_layout_switcher": {
isFeature: true,
supportedLevels: LEVELS_FEATURE,
displayName: _td("New layout switcher (with message bubbles)"),
default: false,
controller: new NewLayoutSwitcherController(),
},
"RoomList.backgroundImage": { "RoomList.backgroundImage": {
supportedLevels: LEVELS_ACCOUNT_SETTINGS, supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: null, default: null,

View file

@ -0,0 +1,26 @@
/*
Copyright 2021 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 SettingController from "./SettingController";
import { SettingLevel } from "../SettingLevel";
import SettingsStore from "../SettingsStore";
import { Layout } from "../Layout";
export default class NewLayoutSwitcherController extends SettingController {
public onChange(level: SettingLevel, roomId: string, newValue: any) {
// On disabling switch back to Layout.Group if Layout.Bubble
if (!newValue && SettingsStore.getValue("layout") == Layout.Bubble) {
SettingsStore.setValue("layout", null, SettingLevel.DEVICE, Layout.Group);
}
}
}