inbox_ui: Use list widget to render topics for channel view.

Rendering all the topics of a channel can be very expensive, so we
use list widget to progressively render the items.
This commit is contained in:
Aman Agrawal
2025-05-26 14:14:01 +05:30
committed by Tim Abbott
parent 9cabf9705a
commit 55d9ea95fb
3 changed files with 95 additions and 47 deletions

View File

@@ -21,20 +21,23 @@ import {$t_html} from "./i18n.ts";
import * as inbox_util from "./inbox_util.ts";
import * as keydown_util from "./keydown_util.ts";
import * as left_sidebar_navigation_area from "./left_sidebar_navigation_area.ts";
import * as list_widget from "./list_widget.ts";
import * as loading from "./loading.ts";
import {localstorage} from "./localstorage.ts";
import * as message_store from "./message_store.ts";
import type {Message} from "./message_store.ts";
import * as message_viewport from "./message_viewport.ts";
import * as onboarding_steps from "./onboarding_steps.ts";
import * as people from "./people.ts";
import * as pm_list from "./pm_list.ts";
import * as stream_color from "./stream_color.ts";
import * as stream_data from "./stream_data.ts";
import * as stream_list from "./stream_list.ts";
import * as stream_topic_history from "./stream_topic_history.ts";
import * as stream_topic_history_util from "./stream_topic_history_util.ts";
import * as sub_store from "./sub_store.ts";
import type {ListInfoNode} from "./topic_list.ts";
import {TopicListWidget} from "./topic_list.ts";
import type {TopicInfo} from "./topic_list_data.ts";
import * as topic_list_data from "./topic_list_data.ts";
import * as unread from "./unread.ts";
import * as unread_ops from "./unread_ops.ts";
import {user_settings} from "./user_settings.ts";
@@ -837,37 +840,6 @@ export function update_channel_view(channel_id: number): void {
}
}
function get_channel_view_formatter(channel_id: number): (conversation: TopicInfo) => ListInfoNode {
return (conversation: TopicInfo): ListInfoNode => {
const render = (): string => {
// Not used when rendering inbox rows.
const latest_msg_id = 0;
const topic_context = format_topic(
channel_id,
false,
conversation.topic_name,
conversation.unread,
latest_msg_id,
true,
);
return render_inbox_row(topic_context);
};
const eq = (other: ListInfoNode): boolean =>
other.type === "topic" && _.isEqual(conversation, other.conversation);
const key = "t:" + conversation.topic_name;
return {
key,
render,
type: "topic",
conversation,
eq,
};
};
}
function show_empty_inbox_channel_view_text(is_empty: boolean): void {
if (is_empty) {
$("#inbox-list").css("border-width", "0");
@@ -885,19 +857,90 @@ function show_empty_inbox_channel_view_text(is_empty: boolean): void {
}
}
function get_min_load_count(already_rendered_count: number, load_count: number): number {
// Height of inbox row is ~28px at 16px = 1.75rem and we want this render to fill the entire view height.
const view_height = message_viewport.height();
const row_height = 1.75 * user_settings.web_font_size_px;
const extra_rows_for_viewing_pleasure = view_height / row_height;
const ideal_rendered_rows_count = row_focus + extra_rows_for_viewing_pleasure;
if (ideal_rendered_rows_count > already_rendered_count + load_count) {
return ideal_rendered_rows_count - already_rendered_count;
}
return load_count;
}
function show_channel_view_loading_indicator(): void {
$("#inbox-loading-indicator .bottom-messages-logo").show();
loading.make_indicator($("#inbox-loading-indicator #loading_more_indicator"), {
abs_positioned: true,
});
}
function hide_channel_view_loading_indicator(): void {
$("#inbox-loading-indicator .bottom-messages-logo").hide();
loading.destroy_indicator($("#inbox-loading-indicator #loading_more_indicator"));
}
class InboxTopicListWidget extends TopicListWidget {
override topic_list_class_name = "inbox-channel-topic-list";
topics_widget?: list_widget.ListWidget<topic_list_data.TopicInfo>;
override build(show_spinner = false): this {
const formatter = get_channel_view_formatter(this.get_stream_id());
override build(): this {
// Hide any existing loading indicators.
hide_channel_view_loading_indicator();
const is_zoomed = true;
super.build(show_spinner, formatter, is_zoomed);
show_empty_inbox_channel_view_text(this.is_empty());
const $container = $("#inbox-list");
const list_info = topic_list_data.get_list_info(
this.my_stream_id,
is_zoomed,
this.filter_topics,
);
const all_topics = list_info.items;
this.topics_widget = list_widget.create($container, all_topics, {
name: "inbox-channel-topics-list",
get_item: list_widget.default_get_item,
$parent_container: $("#inbox-view"),
modifier_html(item) {
const topic_context = format_topic(
item.stream_id,
false,
item.topic_name,
item.unread,
-1,
true,
);
return render_inbox_row(topic_context);
},
$simplebar_container: $("html"),
is_scroll_position_for_render: views_util.is_scroll_position_for_render,
get_min_load_count,
});
if (!stream_topic_history.is_complete_for_stream_id(this.my_stream_id)) {
show_channel_view_loading_indicator();
stream_topic_history_util.get_server_history(this.my_stream_id, () => {
if (channel_view_topic_widget?.get_stream_id() !== this.my_stream_id) {
return;
}
channel_view_topic_widget.build();
});
} else {
show_empty_inbox_channel_view_text(this.is_empty());
}
setTimeout(() => {
revive_current_focus();
}, 0);
return this;
}
override is_empty(): boolean {
if (this.topics_widget === undefined) {
return true;
}
return this.topics_widget.get_current_list().length === 0;
}
}
function filter_topics_in_channel(channel_id: number, topics: string[]): string[] {
@@ -919,15 +962,7 @@ function render_channel_view(channel_id: number): void {
channel_id,
(topic_names: string[]) => filter_topics_in_channel(channel_id, topic_names),
);
const show_spinner = true;
channel_view_topic_widget.build(show_spinner);
stream_topic_history_util.get_server_history(channel_id, () => {
if (channel_view_topic_widget?.get_stream_id() !== channel_id) {
return;
}
channel_view_topic_widget.build();
});
channel_view_topic_widget.build();
}
export function complete_rerender(): void {