switch timeline messages to css grid, and add menu button

This commit is contained in:
Bruno Windels 2021-05-28 12:02:55 +02:00
parent 13ac41b264
commit bbf9832d6a
9 changed files with 233 additions and 185 deletions

View file

@ -16,6 +16,7 @@ limitations under the License.
*/ */
@import url('inter.css'); @import url('inter.css');
@import url('timeline.css');
:root { :root {
font-size: 10px; font-size: 10px;
@ -508,168 +509,6 @@ a {
background-color: #E3E8F0; background-color: #E3E8F0;
} }
ul.Timeline > li:not(.continuation) {
margin-top: 7px;
}
ul.Timeline > li.continuation .profile {
display: none;
}
ul.Timeline > li.continuation time {
display: none;
}
ul.Timeline > li.messageStatus .message-container > p {
font-style: italic;
color: #777;
}
.message-container {
padding: 1px 10px 0px 10px;
margin: 5px 10px 0 10px;
/* so the .media can grow horizontally and its spacer can grow vertically */
width: 100%;
}
.message-container .profile {
display: flex;
align-items: center;
}
.TextMessageView {
width: 100%;
}
.TextMessageView.continuation .message-container {
margin-top: 0;
margin-bottom: 0;
}
.message-container .sender {
margin: 6px 0;
margin-left: 6px;
font-weight: bold;
line-height: 1.7rem;
}
.hydrogen .sender.usercolor1 { color: var(--usercolor1); }
.hydrogen .sender.usercolor2 { color: var(--usercolor2); }
.hydrogen .sender.usercolor3 { color: var(--usercolor3); }
.hydrogen .sender.usercolor4 { color: var(--usercolor4); }
.hydrogen .sender.usercolor5 { color: var(--usercolor5); }
.hydrogen .sender.usercolor6 { color: var(--usercolor6); }
.hydrogen .sender.usercolor7 { color: var(--usercolor7); }
.hydrogen .sender.usercolor8 { color: var(--usercolor8); }
.message-container time {
padding: 2px 0 0px 10px;
font-size: 0.8em;
line-height: normal;
color: #aaa;
}
.message-container .media {
display: grid;
margin-top: 4px;
width: 100%;
}
.message-container .media > a {
text-decoration: none;
width: 100%;
display: block;
}
/* .spacer grows with an inline padding-top to the size of the image,
so the timeline doesn't jump when the image loads */
.message-container .media > * {
grid-row: 1;
grid-column: 1;
}
.message-container .media img, .message-container .media video {
width: 100%;
height: auto;
/* for IE11 to still scale even though the spacer is too tall */
align-self: start;
border-radius: 4px;
display: block;
}
/* stretch the image (to the spacer) on platforms
where we can trust the spacer to always have the correct height,
otherwise the image starts with height 0 and with loading=lazy
only loads when the top comes into view*/
.hydrogen:not(.legacy) .message-container .media img,
.hydrogen:not(.legacy) .message-container .media video {
align-self: stretch;
}
.message-container .media > .sendStatus {
align-self: end;
justify-self: start;
font-size: 0.8em;
}
.message-container .media > progress {
align-self: center;
justify-self: center;
width: 75%;
}
.message-container .media > time {
align-self: end;
justify-self: end;
}
.message-container .media > time,
.message-container .media > .sendStatus {
color: #2e2f32;
display: block;
padding: 2px;
margin: 4px;
background-color: rgba(255, 255, 255, 0.75);
border-radius: 4px;
}
.message-container .media > .spacer {
/* TODO: can we implement this with a pseudo element? or perhaps they are not grid items? */
width: 100%;
/* don't stretch height as it is a spacer, just in case it doesn't match with image height */
align-self: start;
}
.TextMessageView.unsent .message-container {
color: #ccc;
}
.TextMessageView.unverified .message-container {
color: #ff4b55;
}
.message-container p {
margin: 3px 0;
line-height: 2.2rem;
}
.AnnouncementView {
margin: 5px 0;
padding: 5px 10%;
}
.AnnouncementView > div {
margin: 0 auto;
padding: 10px 20px;
background-color: rgba(245, 245, 245, 0.90);
text-align: center;
border-radius: 10px;
}
.GapView > :not(:first-child) {
margin-left: 12px;
}
.SettingsBody { .SettingsBody {
padding: 0px 16px; padding: 0px 16px;
} }

View file

@ -0,0 +1,218 @@
/*
Copyright 2020 Bruno Windels <bruno@windels.cloud>
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.
*/
ul.Timeline > li.messageStatus .Timeline_messageBody > p {
font-style: italic;
color: #777;
}
.Timeline_message {
display: grid;
grid-template:
"avatar sender" auto
"avatar body" auto
"time body" 1fr /
30px 1fr;
column-gap: 8px;
padding: 4px;
margin: 0 12px;
/* TODO: check whether this is needed for .media to maintain aspect ratio (on IE11) like the 100% above */
/* width: 100%; */
box-sizing: border-box;
}
.Timeline_message:not(.continuation) {
margin-top: 4px;
}
@media screen and (max-width: 800px) {
.Timeline_message {
grid-template:
"avatar sender" auto
"body body" 1fr
"time time" auto /
30px 1fr;
}
.Timeline_messageSender {
margin-top: 0 !important;
align-self: center;
}
}
.Timeline_message:hover, .Timeline_message.selected {
background-color: rgba(141, 151, 165, 0.1);
border-radius: 4px;
}
.Timeline_message:hover > .Timeline_messageOptions {
display: block;
}
.Timeline_messageAvatar {
grid-area: avatar;
}
.Timeline_messageSender {
grid-area: sender;
font-weight: bold;
line-height: 1.7rem;
}
.Timeline_messageSender, .Timeline_messageBody {
/* reset body margin */
margin: 0;
}
.Timeline_message:not(.continuation) .Timeline_messageSender,
.Timeline_message:not(.continuation) .Timeline_messageBody {
margin-top: 4px;
}
.Timeline_messageOptions {
display: none;
grid-area: body;
align-self: start;
justify-self: right;
margin-top: -12px;
margin-right: 4px;
}
.Timeline_messageTime {
grid-area: time;
}
.Timeline_messageBody time {
padding: 2px 0 0px 10px;
}
.Timeline_messageBody time, .Timeline_messageTime {
font-size: 0.8em;
line-height: normal;
color: #aaa;
}
.Timeline_messageBody {
grid-area: body;
line-height: 2.2rem;
/* so the .media can grow horizontally and its spacer can grow vertically */
width: 100%;
}
.hydrogen .Timeline_messageSender.usercolor1 { color: var(--usercolor1); }
.hydrogen .Timeline_messageSender.usercolor2 { color: var(--usercolor2); }
.hydrogen .Timeline_messageSender.usercolor3 { color: var(--usercolor3); }
.hydrogen .Timeline_messageSender.usercolor4 { color: var(--usercolor4); }
.hydrogen .Timeline_messageSender.usercolor5 { color: var(--usercolor5); }
.hydrogen .Timeline_messageSender.usercolor6 { color: var(--usercolor6); }
.hydrogen .Timeline_messageSender.usercolor7 { color: var(--usercolor7); }
.hydrogen .Timeline_messageSender.usercolor8 { color: var(--usercolor8); }
.Timeline_messageBody .media {
display: grid;
margin-top: 4px;
width: 100%;
}
.Timeline_messageBody .media > a {
text-decoration: none;
width: 100%;
display: block;
}
/* .spacer grows with an inline padding-top to the size of the image,
so the timeline doesn't jump when the image loads */
.Timeline_messageBody .media > * {
grid-row: 1;
grid-column: 1;
}
.Timeline_messageBody .media img, .Timeline_messageBody .media video {
width: 100%;
height: auto;
/* for IE11 to still scale even though the spacer is too tall */
align-self: start;
border-radius: 4px;
display: block;
}
/* stretch the image (to the spacer) on platforms
where we can trust the spacer to always have the correct height,
otherwise the image starts with height 0 and with loading=lazy
only loads when the top comes into view*/
.hydrogen:not(.legacy) .Timeline_messageBody .media img,
.hydrogen:not(.legacy) .Timeline_messageBody .media video {
align-self: stretch;
}
.Timeline_messageBody .media > .sendStatus {
align-self: end;
justify-self: start;
font-size: 0.8em;
}
.Timeline_messageBody .media > progress {
align-self: center;
justify-self: center;
width: 75%;
}
.Timeline_messageBody .media > time {
align-self: end;
justify-self: end;
}
.Timeline_messageBody .media > time,
.Timeline_messageBody .media > .sendStatus {
color: #2e2f32;
display: block;
padding: 2px;
margin: 4px;
background-color: rgba(255, 255, 255, 0.75);
border-radius: 4px;
}
.Timeline_messageBody .media > .spacer {
/* TODO: can we implement this with a pseudo element? or perhaps they are not grid items? */
width: 100%;
/* don't stretch height as it is a spacer, just in case it doesn't match with image height */
align-self: start;
}
.Timeline_messageBody.unsent .Timeline_messageBody {
color: #ccc;
}
.Timeline_messageBody.unverified .Timeline_messageBody {
color: #ff4b55;
}
.AnnouncementView {
margin: 5px 0;
padding: 5px 10%;
}
.AnnouncementView > div {
margin: 0 auto;
padding: 10px 20px;
background-color: rgba(245, 245, 245, 0.90);
text-align: center;
border-radius: 10px;
}
.GapView > :not(:first-child) {
margin-left: 12px;
}

View file

@ -43,11 +43,6 @@ limitations under the License.
display: block; display: block;
} }
.TextMessageView {
display: flex;
min-width: 0;
}
.AnnouncementView { .AnnouncementView {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -52,9 +52,9 @@ export class BaseMediaView extends TemplateView {
}); });
children.push(sendStatus, progress); children.push(sendStatus, progress);
} }
return renderMessage(t, vm, [ return renderMessage(t, vm, t.div({className: "Timeline_messageBody"}, [
t.div({className: "media", style: `max-width: ${vm.width}px`}, children), t.div({className: "media", style: `max-width: ${vm.width}px`}, children),
t.if(vm => vm.error, t => t.p({className: "error"}, vm.error)) t.if(vm => vm.error, t => t.p({className: "error"}, vm.error))
]); ]));
} }
} }

View file

@ -26,7 +26,7 @@ export class FileView extends TemplateView {
t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Cancel`), t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Cancel`),
])); ]));
} else { } else {
return renderMessage(t, vm, t.p([ return renderMessage(t, vm, t.p({className: "Timeline_messageBody"}, [
t.button({className: "link", onClick: () => vm.download()}, vm => vm.label), t.button({className: "link", onClick: () => vm.download()}, vm => vm.label),
t.time(vm.date + " " + vm.time) t.time(vm.date + " " + vm.time)
])); ]));

View file

@ -20,6 +20,6 @@ import {renderMessage} from "./common.js";
export class MissingAttachmentView extends TemplateView { export class MissingAttachmentView extends TemplateView {
render(t, vm) { render(t, vm) {
const remove = t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Remove`); const remove = t.button({className: "link", onClick: () => vm.abortSending()}, vm.i18n`Remove`);
return renderMessage(t, vm, t.p([vm.label, " ", remove])); return renderMessage(t, vm, t.p({className: "Timeline_messageBody"}, [vm.label, " ", remove]));
} }
} }

View file

@ -21,7 +21,7 @@ export class RedactedView extends TemplateView {
render(t, vm) { render(t, vm) {
const cancelButton = t.if(vm => vm.isRedacting, t => t.button({onClick: () => vm.abortPendingRedaction()}, "Cancel")); const cancelButton = t.if(vm => vm.isRedacting, t => t.button({onClick: () => vm.abortPendingRedaction()}, "Cancel"));
return renderMessage(t, vm, return renderMessage(t, vm,
[t.p([vm => vm.description, " ", cancelButton, t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)])] t.p({className: "Timeline_messageBody"}, [vm => vm.description, " ", cancelButton])
); );
} }
} }

View file

@ -22,9 +22,8 @@ import {renderMessage} from "./common.js";
export class TextMessageView extends TemplateView { export class TextMessageView extends TemplateView {
render(t, vm) { render(t, vm) {
const bodyView = t.mapView(vm => vm.body, body => new BodyView(body)); const bodyView = t.mapView(vm => vm.body, body => new BodyView(body));
const redactButton = t.button({onClick: () => vm.redact()}, "Redact");
return renderMessage(t, vm, return renderMessage(t, vm,
[t.p([bodyView, redactButton, t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)])] t.p({className: "Timeline_messageBody"}, [bodyView, t.time({className: {hidden: !vm.date}}, vm.date + " " + vm.time)])
); );
} }
} }

View file

@ -17,23 +17,20 @@ limitations under the License.
import {renderStaticAvatar} from "../../../avatar.js"; import {renderStaticAvatar} from "../../../avatar.js";
export function renderMessage(t, vm, children) { export function renderMessage(t, vm, body) {
const classes = { const classes = {
"TextMessageView": true, "Timeline_message": true,
own: vm.isOwn, own: vm.isOwn,
unsent: vm.isUnsent, unsent: vm.isUnsent,
unverified: vm.isUnverified, unverified: vm.isUnverified,
continuation: vm => vm.isContinuation, continuation: vm => vm.isContinuation,
messageStatus: vm => vm.shape === "message-status" || vm.shape === "missing-attachment" || vm.shape === "file" || vm.shape === "redacted", messageStatus: vm => vm.shape === "message-status" || vm.shape === "missing-attachment" || vm.shape === "file" || vm.shape === "redacted",
}; };
return t.li({className: classes}, [
const profile = t.div({className: "profile"}, [ t.if(vm => !vm.isContinuation, t => renderStaticAvatar(vm, 30, "Timeline_messageAvatar")),
renderStaticAvatar(vm, 30), t.if(vm => !vm.isContinuation, t => t.div({className: `Timeline_messageSender usercolor${vm.avatarColorNumber}`}, vm.displayName)),
t.div({className: `sender usercolor${vm.avatarColorNumber}`}, vm.displayName) body,
// should be after body as it is overlayed on top
t.button({className: "Timeline_messageOptions"}, "⋮"),
]); ]);
children = [profile].concat(children);
return t.li(
{className: classes},
t.div({className: "message-container"}, children)
);
} }