web: performance!
Instead of actively updating the timeline we render all elements into a map. When there are new events to render a button shows up at the top allowing the user to manually view them. This needs a fix to view more down the page (I set it to 50000 for view switchign) and I need to fix the initial page view.
This commit is contained in:
parent
5bcb63973c
commit
ca7abdd0b6
6 changed files with 124 additions and 73 deletions
|
@ -138,6 +138,7 @@ button.nav > img.icon {
|
||||||
top: 0;
|
top: 0;
|
||||||
z-index: var(--zHeader);
|
z-index: var(--zHeader);
|
||||||
backdrop-filter: blur(20px);
|
backdrop-filter: blur(20px);
|
||||||
|
-webkit-backdrop-filter: blur(20px);
|
||||||
}
|
}
|
||||||
#view header > label {
|
#view header > label {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
@ -185,6 +186,18 @@ button.nav > img.icon {
|
||||||
transform: rotate(360deg);
|
transform: rotate(360deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.show-new {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.show-new > button {
|
||||||
|
color: var(--clrText);
|
||||||
|
border: none;
|
||||||
|
padding: 15px;
|
||||||
|
background: transparent;
|
||||||
|
width: 100%;
|
||||||
|
font-size: var(--fsNormal);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
.userpic { /* TODO remove .userpic and use helper class */
|
.userpic { /* TODO remove .userpic and use helper class */
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,6 +132,10 @@
|
||||||
<img class="dark-invert" src="icon/loader-fragment.svg"/>
|
<img class="dark-invert" src="icon/loader-fragment.svg"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="show-new" class="show-new bottom-border hide">
|
||||||
|
<button onclick="show_new()">
|
||||||
|
Show New (<span role="count">0</span>)</button>
|
||||||
|
</div>
|
||||||
<div id="timeline" class="events"></div>
|
<div id="timeline" class="events"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -81,7 +81,7 @@ function event_is_spam(ev, contacts, pow) {
|
||||||
return ev.pow >= pow
|
return ev.pow >= pow
|
||||||
}
|
}
|
||||||
|
|
||||||
function event_cmp_created(a, b) {
|
function event_cmp_created(a={}, b={}) {
|
||||||
if (a.created_at > b.created_at)
|
if (a.created_at > b.created_at)
|
||||||
return 1;
|
return 1;
|
||||||
if (a.created_at < b.created_at)
|
if (a.created_at < b.created_at)
|
||||||
|
|
|
@ -41,7 +41,7 @@ function model_process_event(model, ev) {
|
||||||
if (model_event_has_unknown_ids(model, ev))
|
if (model_event_has_unknown_ids(model, ev))
|
||||||
schedule_unknown_refetch(model);
|
schedule_unknown_refetch(model);
|
||||||
|
|
||||||
// Refresh timeline
|
// Queue event for rendering
|
||||||
model.invalidated.push(ev.id);
|
model.invalidated.push(ev.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,5 +250,6 @@ function new_model() {
|
||||||
friend_of_friends: new Set(),
|
friend_of_friends: new Set(),
|
||||||
},
|
},
|
||||||
invalidated: [], // event ids which should be added/removed
|
invalidated: [], // event ids which should be added/removed
|
||||||
|
elements: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,13 +42,28 @@ function view_timeline_apply_mode(model, mode, opts={}) {
|
||||||
find_node("#newpost").classList.toggle("hide", mode != VM_FRIENDS);
|
find_node("#newpost").classList.toggle("hide", mode != VM_FRIENDS);
|
||||||
find_node("#timeline").classList.toggle("reverse", mode == VM_THREAD);
|
find_node("#timeline").classList.toggle("reverse", mode == VM_THREAD);
|
||||||
|
|
||||||
// Show or hide all applicable events related to the mode.
|
// Remove all
|
||||||
xs = el.querySelectorAll(".event");
|
// This is faster than removing one by one
|
||||||
for (const x of xs) {
|
el.innerHTML = "";
|
||||||
let evid = x.id.substr(2);
|
// Build DOM fragment and render it
|
||||||
let ev = model.all_events[evid];
|
let count = 0;
|
||||||
x.classList.toggle("hide",
|
const evs = model_events_arr(model)
|
||||||
!view_mode_contains_event(model, ev, mode, opts));
|
const fragment = new DocumentFragment();
|
||||||
|
for (let i = evs.length - 1; i >= 0 && count < 50000; i--) {
|
||||||
|
const ev = evs[i];
|
||||||
|
if (!view_mode_contains_event(model, ev, mode, opts))
|
||||||
|
continue;
|
||||||
|
let ev_el = model.elements[ev.id];
|
||||||
|
if (!ev_el)
|
||||||
|
continue;
|
||||||
|
fragment.appendChild(ev_el);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
|
find_node("#view .loading-events").classList.add("hide");
|
||||||
|
el.append(fragment);
|
||||||
|
view_set_show_count(0);
|
||||||
|
view_timeline_update_timestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
return mode;
|
return mode;
|
||||||
|
@ -65,19 +80,15 @@ function view_timeline_update(model) {
|
||||||
pubkey: el.dataset.pubkey,
|
pubkey: el.dataset.pubkey,
|
||||||
};
|
};
|
||||||
|
|
||||||
// for each event not rendered, go through the list and render it marking
|
let count = 0;
|
||||||
// it as rendered and adding it to the appropriate fragment. fragments are
|
const latest_ev = el.firstChild ?
|
||||||
// created based on slices in the existing timeline. fragments are started
|
model.all_events[el.firstChild.id.slice(2)] : undefined;
|
||||||
// at the previous event
|
|
||||||
// const fragments = {};
|
|
||||||
// const cache = {};
|
|
||||||
|
|
||||||
// Dumb function to insert needed events
|
|
||||||
let visible_count = 0;
|
|
||||||
const all = model_events_arr(model);
|
const all = model_events_arr(model);
|
||||||
const left_overs = [];
|
const left_overs = [];
|
||||||
while (model.invalidated.length > 0) {
|
while (model.invalidated.length > 0) {
|
||||||
var evid = model.invalidated.pop();
|
var evid = model.invalidated.pop();
|
||||||
|
if (model.elements[evid])
|
||||||
|
continue;
|
||||||
var ev = model.all_events[evid];
|
var ev = model.all_events[evid];
|
||||||
if (!event_is_renderable(ev) || model_is_event_deleted(model, evid)) {
|
if (!event_is_renderable(ev) || model_is_event_deleted(model, evid)) {
|
||||||
let x = find_node("#ev"+evid, el);
|
let x = find_node("#ev"+evid, el);
|
||||||
|
@ -85,46 +96,71 @@ function view_timeline_update(model) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If event is in el already, do nothing or update?
|
const html = render_event(model, ev, {});
|
||||||
let ev_el = find_node("#ev"+evid, el);
|
// Put it back on the stack to re-render if it's not ready.
|
||||||
if (ev_el) {
|
if (html == "") {
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
const html = render_event(model, ev, {});
|
|
||||||
// Put it back on the stack to re-render if it's not ready.
|
|
||||||
if (html == "") {
|
|
||||||
left_overs.push(evid);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const div = document.createElement("div");
|
|
||||||
div.innerHTML = html;
|
|
||||||
ev_el = div.firstChild;
|
|
||||||
if (!view_mode_contains_event(model, ev, mode, opts)) {
|
|
||||||
ev_el.classList.add("hide");
|
|
||||||
} else {
|
|
||||||
visible_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// find prior event element and insert it before that
|
|
||||||
let prior_el;
|
|
||||||
let prior_idx = arr_bsearch_insert(all, ev, event_cmp_created);
|
|
||||||
while (prior_idx >= 0 && !prior_el) {
|
|
||||||
prior_el = find_node("#ev"+all[prior_idx].id, el);
|
|
||||||
prior_idx--;
|
|
||||||
}
|
|
||||||
if (prior_el) {
|
|
||||||
el.insertBefore(ev_el, prior_el);
|
|
||||||
} else if (el.childElementCount == 0) {
|
|
||||||
el.appendChild(ev_el);
|
|
||||||
} else {
|
|
||||||
left_overs.push(evid);
|
left_overs.push(evid);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.innerHTML = html;
|
||||||
|
ev_el = div.firstChild;
|
||||||
|
model.elements[evid] = ev_el;
|
||||||
|
|
||||||
|
// If the new element is newer than the latest & is viewable then
|
||||||
|
// we want to increase the count of how many to add to view
|
||||||
|
if (event_cmp_created(ev, latest_ev) >= 0 && view_mode_contains_event(model, ev, mode, opts)) {
|
||||||
|
count++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.invalidated = model.invalidated.concat(left_overs);
|
model.invalidated = model.invalidated.concat(left_overs);
|
||||||
|
|
||||||
if (visible_count > 0)
|
if (count > 0) {
|
||||||
|
view_set_show_count(count, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function view_set_show_count(count, add) {
|
||||||
|
const show_el = find_node("#show-new")
|
||||||
|
const num_el = find_node("#show-new span", show_el);
|
||||||
|
if (add) {
|
||||||
|
count += parseInt(num_el.innerText || 0)
|
||||||
|
}
|
||||||
|
num_el.innerText = count;
|
||||||
|
show_el.classList.toggle("hide", count <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function view_timeline_show_new(model) {
|
||||||
|
const el = view_get_timeline_el();
|
||||||
|
const mode = el.dataset.mode;
|
||||||
|
const opts = {
|
||||||
|
thread_id: el.dataset.threadId,
|
||||||
|
pubkey: el.dataset.pubkey,
|
||||||
|
};
|
||||||
|
const latest_evid = el.firstChild ? el.firstChild.id.slice(2) : undefined;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
const evs = model_events_arr(model)
|
||||||
|
const fragment = new DocumentFragment();
|
||||||
|
for (let i = evs.length - 1; i >= 0 && count < 500; i--) {
|
||||||
|
const ev = evs[i];
|
||||||
|
if (latest_evid && ev.id == latest_evid) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!view_mode_contains_event(model, ev, mode, opts))
|
||||||
|
continue;
|
||||||
|
let ev_el = model.elements[ev.id];
|
||||||
|
if (!ev_el)
|
||||||
|
continue;
|
||||||
|
fragment.appendChild(ev_el);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (count > 0) {
|
||||||
find_node("#view .loading-events").classList.add("hide");
|
find_node("#view .loading-events").classList.add("hide");
|
||||||
|
el.prepend(fragment);
|
||||||
|
}
|
||||||
|
view_set_show_count(-count, true);
|
||||||
|
view_timeline_update_timestamps();
|
||||||
}
|
}
|
||||||
|
|
||||||
function view_timeline_update_profiles(model, ev) {
|
function view_timeline_update_profiles(model, ev) {
|
||||||
|
@ -138,22 +174,19 @@ function view_timeline_update_profiles(model, ev) {
|
||||||
redraw_my_pfp(model);
|
redraw_my_pfp(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update displayed names
|
const name = fmt_profile_name(p, fmt_pubkey(pk));
|
||||||
xs = el.querySelectorAll(`.username[data-pubkey='${pk}']`)
|
const pic = get_picture(pk, p)
|
||||||
html = fmt_profile_name(p, fmt_pubkey(pk));
|
for (const evid in model.elements) {
|
||||||
for (const x of xs) {
|
if (model.all_events[evid].pubkey != pk)
|
||||||
x.innerText = html;
|
continue;
|
||||||
}
|
const el = model.elements[evid];
|
||||||
|
find_node(`.username[data-pubkey='${pk}']`, el).innerText = name;
|
||||||
// Update profile pictures
|
find_node(`img.pfp[data-pubkey='${pk}']`, el).src = pic;
|
||||||
xs = el.querySelectorAll(`img.pfp[data-pubkey='${pk}']`);
|
|
||||||
html = get_picture(pk, p)
|
|
||||||
for (const x of xs) {
|
|
||||||
x.src = html;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function view_timeline_update_timestamps(model) {
|
function view_timeline_update_timestamps() {
|
||||||
|
// TODO only update elements that are fresh and are in DOM
|
||||||
const el = view_get_timeline_el();
|
const el = view_get_timeline_el();
|
||||||
let xs = el.querySelectorAll(".timestamp");
|
let xs = el.querySelectorAll(".timestamp");
|
||||||
let now = new Date().getTime();
|
let now = new Date().getTime();
|
||||||
|
@ -166,16 +199,12 @@ function view_timeline_update_timestamps(model) {
|
||||||
function view_timeline_update_reaction(model, ev) {
|
function view_timeline_update_reaction(model, ev) {
|
||||||
let el;
|
let el;
|
||||||
const o = event_parse_reaction(ev);
|
const o = event_parse_reaction(ev);
|
||||||
if (!o) {
|
if (!o)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
const ev_id = o.e;
|
const ev_id = o.e;
|
||||||
const root = find_node(`#ev${ev_id}`);
|
const root = model.elements[ev_id];
|
||||||
if (!root) {
|
if (!root)
|
||||||
// It's possible the event didn't get rendered yet from the
|
|
||||||
// invalidation stack. In which case emojis will get rendered then.
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
// Update reaction groups
|
// Update reaction groups
|
||||||
el = find_node(`.reactions`, root);
|
el = find_node(`.reactions`, root);
|
||||||
|
|
|
@ -115,6 +115,10 @@ function click_toggle_follow_user(el) {
|
||||||
contacts_save(contacts);
|
contacts_save(contacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function show_new() {
|
||||||
|
view_timeline_show_new(DAMUS);
|
||||||
|
}
|
||||||
|
|
||||||
/* click_event opens the thread view from the element's specified element id
|
/* click_event opens the thread view from the element's specified element id
|
||||||
* "dataset.eid".
|
* "dataset.eid".
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue