New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
floating_recipient_bar: Replace with sticky header. #23900
Conversation
4e86329
to
1878d33
Compare
static/js/message_list_view.js
Outdated
/* To get to the sticky header ASAP, we take help from how far has user scrolled. */ | ||
if ($message_viewport.scrollTop() > $message_viewport.height() / 2) { | ||
iterable_headers.reverse(); | ||
} | ||
let sticky_header; | ||
for (const header of iterable_headers) { | ||
if (is_sticky(header)) { | ||
sticky_header = header; | ||
break; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should use a binary search (_.sortedIndexBy
).
Or, better, can we find the message row first with document.elementsFromPoint
, then find the sticky header from the message?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
document.elementsFromPoint
is very risky to use since there are a lot of UI state we would have to take into account.
I don't understand how we can use _.sortedIndexBy
or binary search here. I fear we would end up doing more work in common cases since the user is mostly looking at the latest unread message, in which case, the sticky header is just is 0-5 message headers away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“Risky” in what way? You’re already using document.elementsFromPoint
below, and you’re already doing a complicated computation with UI state in is_sticky
above. If that’s risky, I don’t see this being any worse.
If there are 400 messages visible, binary search does 8 or 9 tests, while your linear search does up to 200. The potential gain if linear search happens to do 0–5 tests is small, while the potential loss is large.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried implementing it using document.elementsFromPoint
, seems to be working fine, it would be great if you can check too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should definitely profile this bit of code while doing scrolling around (ideally including things like using the Mac trackpad acceleration in "all messages"), renarrowing, etc., once we've settled on how we think it's cleanest to do it.
I haven't read this carefully, but one risk with whatever we're doing here is that removing the sticky
class at the start of this function and then trying to read what is in a given location might trigger an intermediate repaint or something that could be slow?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't read this carefully, but one risk with whatever we're doing here is that removing the sticky class at the start of this function and then trying to read what is in a given location might trigger an intermediate repaint or something that could be slow?
Since we are not changing the position of any element by removing the sticky_header
class, it most likely will repaint the date part of the message row if it is still visible in the narrow which is harmless. I will do a profile to confirm though.
&.sticky_header {
.recipient_row_date {
display: block;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I removed the method which used document.elementsFromPoint
and went back to my original method and applied binary search to it as recommended by @andersk. This made some of the comments below not relevant anymore.
static/js/message_list_view.js
Outdated
/* If user at the top of the scroll container, there is no sticky | ||
header, so we just set date of the header to the first visible message. */ | ||
sticky_header = $headers[0]; | ||
$message_row = $($headers[0].nextElementSibling); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is either $($headers[0]).next()
or $($headers[0].nextElementSibling);
of which the second one seems more readable, so I went with it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$headers.first().next()
then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok!
static/js/message_list_view.js
Outdated
const rendered_date = timerender.render_date(time, undefined, today)[0].outerHTML; | ||
$(sticky_header).find(".recipient_row_date").html(rendered_date); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this code was copied, but avoid the .outerHTML
and .html()
round trip—just .append()
the jQuery object directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, directly used html
since we needed to replace the content and I didn't want to use .empty().append()
.
static/js/message_viewport.js
Outdated
// The message_header is NOT considered to be part of the visible | ||
// Sticky message_header is considered to be part of the visible |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The inversion of this comment is wrong. You’re still adding the sticky header height to visible_top
, which is subtracted out of visible_height
, so it’s not considered part of the visible message pane.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, thanks for noticing.
static/styles/zulip.css
Outdated
box-shadow: inset 0 1px 0 2px hsl(215, 47%, 50%), | ||
inset 0 0 0 2px hsl(215, 47%, 50%), -1px -1px 0 0 hsl(215, 47%, 50%), | ||
1px 1px 0 0 hsl(215, 47%, 50%), -1px 1px 0 0 hsl(215, 47%, 50%), | ||
1px -1px 0 0 hsl(215, 47%, 50%); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One would think it shouldn’t take six shadows to draw a rectangle. We can at least replace the last four non-inset
shadows (-1px -1px 0 0
, 1px 1px 0 0
, -1px 1px 0 0
, 1px -1px 0 0
) with one (0 0 0 1px
); this applies to the original rule too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(And, just noting, we’ll need to remember to remove this in #23531.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, updated!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where is this code to be removed (wasn't sure because this comment is on an outdated diff)?
Working on #23531 right now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is in the commit css: Combine box-shadows for selected message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah okay great :) So I've already addressed it. Thanks!
1878d33
to
f76c10b
Compare
@andersk thanks for the review. Updated the PR based on your comments. |
@andersk Any more feedback from your side, or does it look good to you now? |
f76c10b
to
17fea8e
Compare
@andersk updated based on your last review! |
compensate for that. */ | ||
.messagebox-content { | ||
box-shadow: inset 0 1px 0 2px hsl(215, 47%, 50%), | ||
inset 0 0 0 2px hsl(215, 47%, 50%), 0 0 0 1px hsl(215, 47%, 50%); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this HSL color be extracted as a variable?
And does this block need a dark theme analog?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extracting HSL color as variable doesn't make sense here since there is no prior of doing so here.
We don't need to do anything for the dark theme since the same color is used there too.
static/js/message_list_view.js
Outdated
* Date separator | ||
* Z logo at the top of message feed | ||
|
||
If we encounter either loading indicator or date separator, we set sticky header to the next recipient row. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to write this algorithm in a way that doesn't depend on what other elements might be present there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer relevant.
static/js/message_list_view.js
Outdated
let $message_row; | ||
if (date_row.length) { | ||
/* There is always a message header present next to a date_row. */ | ||
const recipient_row = date_row[0].nextElementSibling; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't like hardcoding assumptions about what elements we might have in the DOM -- could we instead do something like .nextAll(".recipient_row").first()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative option, which might be faster, might be to just assert that we got a .recipient_row
in a way that'll throw an exception in development? That will probably mean if we violate this invariant in a future feature, we'll notice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer relevant.
static/js/message_list_view.js
Outdated
if (!message_row.length) { | ||
/* If there is no message row under the header, it means it is not sticky yet, | ||
so we just get the message next to the header. */ | ||
$message_row = $sticky_header.next(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similarly, should this be .nextAll(".message_row").first()
? I think that does the same thing, but is more explicit and robust in the case we add some small/invisible div in between at some point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer relevant / applied the fix elsewhere.
static/js/message_list_view.js
Outdated
window.innerWidth / 2, | ||
visible_top + header_height - 1, | ||
); | ||
let $sticky_header = $( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be called first_fully_visible_message_header
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but that is a long name, so added it as a comment.
if (!rows_length) { | ||
/* No headers are present */ | ||
return; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be more cleanly written as having $headers = $table.find(".message_header");
happen early, doing $headers.first()
as it'll be used, and checking if that exists or not?
(Just thinking about not mixing different data sources in a single calculation, given this doesn't access _rows
anywhere else)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer relevant.
const $sticky_header = $(".sticky_header"); | ||
if ($sticky_header.length) { | ||
res.visible_top += $sticky_header.safeOuterHeight(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to make sure I understand, this calculation and the below one in this file are both the same as before -- the difference is just that previously, there would always be a .floating_recipient
element that was possibly not visible and thus had 0 height, and now there sometimes is no such element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Generally, this looks very promising, and I'll be very glad for us to get to delete the various code this deletes. I posted a bunch of comments above. I was able to find a scroll position where no date is displayed; so that's probably a bug. I also at one point got a JS exception pop up while scrolling around "All messages", |
17fea8e
to
57490c3
Compare
@timabbott updated! The bugs you faced should no longer be reproduced too! |
I changed most of the code in |
23d9236
to
faaf0ca
Compare
I didn't see any glitches when playing around with this for a bit. |
Test-deployed this on chat.zulip.org. I'm really excited about trying to get this integrated. |
Since `message_viewport` library is not jQuery object, it should not have `$` prefix before it.
There are no visual changes since we are replacing 4 single borders with a single box.
In addition to the benefit of using variables, this change makes it more noticeable that the header has a different height on smaller widths.
Pushed to resolve conflicts. |
faaf0ca
to
c1202f5
Compare
Merged, thanks @amanagr! Hopefully we don't have further bugs to track down, but even if we do, I think they're likely to be minor enough that we can fix forward from here. I'm very glad to see that ancient code deleted, even if ideally we'd be able to make something work for the dates that would let it be entirely CSS :). |
In zulip#23900, the floating recipient bar was replaced with a sticky recipient bar. The logic in `id_for_recipient_row` was not updated for that change, so we update that function to just get the first message in the group and return that message ID.
In zulip#23900, the floating recipient bar was replaced with a sticky recipient bar. The logic in `id_for_recipient_row` was not updated for that change, so we update that function to just get the first message in the group and return that message ID.
In zulip#23900, the floating recipient bar was replaced with a sticky recipient bar. The logic in `id_for_recipient_row` was not updated for that change, so we update that function to just get the first message in the group and return that message ID.
In #23900, the floating recipient bar was replaced with a sticky recipient bar. The logic in `id_for_recipient_row` was not updated for that change, so we update that function to just get the first message in the group and return that message ID.
In zulip#23900, the floating recipient bar was replaced with a sticky recipient bar. The logic in `id_for_recipient_row` was not updated for that change, so we update that function to just get the first message in the group and return that message ID.
In zulip#23900, the floating recipient bar was replaced with a sticky recipient bar. The logic in `id_for_recipient_row` was not updated for that change, so we update that function to just get the first message in the group and return that message ID.
In zulip#23900, the floating recipient bar was replaced with a sticky recipient bar. The logic in `id_for_recipient_row` was not updated for that change, so we update that function to just get the first message in the group and return that message ID.
Continuation of #9910
discussion: https://chat.zulip.org/#narrow/stream/101-design/topic/UI.20redesign.3A.20stream.2Ftopic.20header.20bar
blocker for #23782
Please read bullets in #9910 and #16762 by @andersk to check for bugs that needed to be fixed in those PRs.
There should be no visual changes other than a new possible state of the UI in the below screenshot: