Skip to content
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

Merged
merged 4 commits into from Feb 8, 2023

Conversation

amanagr
Copy link
Member

@amanagr amanagr commented Dec 19, 2022

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:
image

@amanagr
Copy link
Member Author

amanagr commented Dec 19, 2022

In message-basics.ts, this part of the test is failing for some reason, instead of clicking on stream header, it clicks on the search box.

    console.log("Narrowing by clicking stream");
    await page.click(`#zhome [title='Narrow to stream "Verona"']`);
    await expect_verona_stream(page);

failure-0

Comment on lines 1481 to 1512
/* 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;
}
}
Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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!

Copy link
Sponsor Member

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?

Copy link
Member Author

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;
            }
        }

Copy link
Member Author

@amanagr amanagr Jan 13, 2023

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.

/* 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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$headers.first().next() then?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok!

Comment on lines 1528 to 1529
const rendered_date = timerender.render_date(time, undefined, today)[0].outerHTML;
$(sticky_header).find(".recipient_row_date").html(rendered_date);
Copy link
Member

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.

Copy link
Member Author

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().

// The message_header is NOT considered to be part of the visible
// Sticky message_header is considered to be part of the visible
Copy link
Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, thanks for noticing.

Comment on lines 1461 to 1464
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%);
Copy link
Member

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.

Copy link
Member

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.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, updated!

Copy link
Collaborator

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 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

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!

@amanagr
Copy link
Member Author

amanagr commented Jan 5, 2023

@andersk thanks for the review. Updated the PR based on your comments.

@alya
Copy link
Contributor

alya commented Jan 5, 2023

@andersk Any more feedback from your side, or does it look good to you now?

@amanagr
Copy link
Member Author

amanagr commented Jan 6, 2023

@andersk updated based on your last review!

static/styles/zulip.css Outdated Show resolved Hide resolved
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%);
Copy link
Sponsor Member

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?

Copy link
Member Author

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.

* 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. */
Copy link
Sponsor Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer relevant.

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;
Copy link
Sponsor Member

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()?

Copy link
Sponsor Member

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.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No longer relevant.

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();
Copy link
Sponsor Member

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.

Copy link
Member Author

@amanagr amanagr Jan 13, 2023

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.

window.innerWidth / 2,
visible_top + header_height - 1,
);
let $sticky_header = $(
Copy link
Sponsor Member

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?

Copy link
Member Author

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;
}
Copy link
Sponsor Member

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)

Copy link
Member Author

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();
}
Copy link
Sponsor Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes.

@timabbott
Copy link
Sponsor Member

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.

image

I also at one point got a JS exception pop up while scrolling around "All messages",

@amanagr
Copy link
Member Author

amanagr commented Jan 13, 2023

@timabbott updated! The bugs you faced should no longer be reproduced too!

@amanagr
Copy link
Member Author

amanagr commented Jan 16, 2023

Can you explain what changes resolved the comments that are no longer relevant. (I've not tried to read the updated PR in full yet)

I changed most of the code in update_sticky_recipient_headers and since the code on which you reviewed are no longer present in the PR, they became irrelevant.

@timabbott timabbott added the product review Added by maintainers when a PR needs product review. label Jan 17, 2023
@timabbott
Copy link
Sponsor Member

@alya @andersk can you do another review on this? The algorithm in message_list_view.js is rewritten since the previous version.

@alya
Copy link
Contributor

alya commented Jan 19, 2023

I didn't see any glitches when playing around with this for a bit.

@alya alya removed the product review Added by maintainers when a PR needs product review. label Jan 19, 2023
@timabbott timabbott added the chat.zulip.org review Added by maintainers when a PR requires chat.zulip.org testing to proceed. label Jan 20, 2023
@timabbott
Copy link
Sponsor Member

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.
@amanagr
Copy link
Member Author

amanagr commented Feb 6, 2023

Pushed to resolve conflicts.

@timabbott timabbott merged commit 4dfe3d3 into zulip:main Feb 8, 2023
@timabbott
Copy link
Sponsor Member

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 :).

@amanagr amanagr deleted the floating_recipient_bar branch February 8, 2023 10:40
laurynmm added a commit to laurynmm/zulip that referenced this pull request Oct 2, 2023
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.
laurynmm added a commit to laurynmm/zulip that referenced this pull request Oct 3, 2023
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.
laurynmm added a commit to laurynmm/zulip that referenced this pull request Oct 5, 2023
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.
timabbott pushed a commit that referenced this pull request Oct 6, 2023
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.
ToBadForYou pushed a commit to ToBadForYou/zulip that referenced this pull request Oct 7, 2023
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.
macieksarnowicz pushed a commit to macieksarnowicz/zulip that referenced this pull request Oct 16, 2023
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.
emmawitt pushed a commit to emmawitt/zulip that referenced this pull request Nov 7, 2023
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
chat.zulip.org review Added by maintainers when a PR requires chat.zulip.org testing to proceed. size: XL
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants