189 lines
4.7 KiB
JavaScript
189 lines
4.7 KiB
JavaScript
import { ElemJS, ejs } from "./basic.js";
|
|
import { Subscribable } from "./store/Subscribable.js";
|
|
import { Anchor } from "./Anchor.js";
|
|
|
|
function eventSearch(list, event, min = 0, max = -1) {
|
|
if (list.length === 0) return { success: false, i: 0 };
|
|
|
|
if (max === -1) max = list.length - 1;
|
|
let mid = Math.floor((max + min) / 2);
|
|
// success condition
|
|
if (list[mid] && list[mid].data.event_id === event.data.event_id)
|
|
return { success: true, i: mid };
|
|
// failed condition
|
|
if (min >= max) {
|
|
while (
|
|
mid !== -1 &&
|
|
(!list[mid] ||
|
|
list[mid].data.origin_server_ts > event.data.origin_server_ts)
|
|
)
|
|
mid--;
|
|
return {
|
|
success: false,
|
|
i: mid + 1,
|
|
};
|
|
}
|
|
// recurse (below)
|
|
if (list[mid].data.origin_server_ts > event.data.origin_server_ts)
|
|
return eventSearch(list, event, min, mid - 1);
|
|
// recurse (above)
|
|
else return eventSearch(list, event, mid + 1, max);
|
|
}
|
|
|
|
class Event extends ElemJS {
|
|
constructor(data) {
|
|
super("div");
|
|
this.class("c-message");
|
|
this.data = null;
|
|
this.update(data);
|
|
}
|
|
|
|
update(data) {
|
|
this.data = data;
|
|
this.render();
|
|
}
|
|
|
|
render() {
|
|
this.child(this.data.content.body);
|
|
}
|
|
}
|
|
|
|
class EventGroup extends ElemJS {
|
|
constructor(list) {
|
|
super("div");
|
|
this.class("c-message-group");
|
|
this.list = list;
|
|
this.data = {
|
|
sender: list[0].data.sender,
|
|
origin_server_ts: list[0].data.origin_server_ts,
|
|
};
|
|
this.child(
|
|
ejs("div")
|
|
.class("c-message-group__avatar")
|
|
.child(ejs("div").class("c-message-group__icon")),
|
|
(this.messages = ejs("div")
|
|
.class("c-message-group__messages")
|
|
.child(
|
|
ejs("div")
|
|
.class("c-message-group__intro")
|
|
.child(
|
|
ejs("div").class("c-message-group__name").text(this.data.sender),
|
|
ejs("div")
|
|
.class("c-message-group__date")
|
|
.text(this.data.origin_server_ts)
|
|
),
|
|
...this.list
|
|
))
|
|
);
|
|
}
|
|
|
|
addEvent(event) {
|
|
const index = eventSearch(this.list, event).i;
|
|
this.list.splice(index, 0, event);
|
|
this.messages.childAt(index + 1, event);
|
|
}
|
|
}
|
|
|
|
class ReactiveTimeline extends ElemJS {
|
|
constructor(list) {
|
|
super("div");
|
|
this.class("c-event-groups");
|
|
this.list = list;
|
|
this.render();
|
|
}
|
|
|
|
addEvent(event) {
|
|
const search = eventSearch(this.list, event);
|
|
// console.log(search, this.list.map(l => l.data.sender), event.data)
|
|
if (!search.success && search.i >= 1)
|
|
this.tryAddGroups(event, [search.i - 1, search.i]);
|
|
else this.tryAddGroups(event, [search.i]);
|
|
}
|
|
|
|
tryAddGroups(event, indices) {
|
|
const success = indices.some((i) => {
|
|
if (!this.list[i]) {
|
|
// if (printed++ < 100) console.log("tryadd success, created group")
|
|
const group = new EventGroup([event]);
|
|
this.list.splice(i, 0, group);
|
|
this.childAt(i, group);
|
|
return true;
|
|
} else if (
|
|
this.list[i] &&
|
|
this.list[i].data.sender === event.data.sender
|
|
) {
|
|
// if (printed++ < 100) console.log("tryadd success, using existing group")
|
|
this.list[i].addEvent(event);
|
|
return true;
|
|
}
|
|
});
|
|
if (!success)
|
|
console.log(
|
|
"tryadd failure",
|
|
indices,
|
|
this.list.map((l) => l.data.sender),
|
|
event.data
|
|
);
|
|
}
|
|
|
|
render() {
|
|
this.clearChildren();
|
|
this.list.forEach((group) => this.child(group));
|
|
this.anchor = new Anchor();
|
|
this.child(this.anchor);
|
|
}
|
|
}
|
|
|
|
class Timeline extends Subscribable {
|
|
constructor() {
|
|
super();
|
|
Object.assign(this.events, {
|
|
beforeChange: [],
|
|
});
|
|
Object.assign(this.eventDeps, {
|
|
beforeChange: [],
|
|
});
|
|
this.list = [];
|
|
this.map = new Map();
|
|
this.reactiveTimeline = new ReactiveTimeline([]);
|
|
this.latest = 0;
|
|
}
|
|
|
|
updateEvents(events) {
|
|
this.broadcast("beforeChange");
|
|
for (const eventData of events) {
|
|
this.latest = Math.max(this.latest, eventData.origin_server_ts);
|
|
if (this.map.has(eventData.event_id)) {
|
|
this.map.get(eventData.event_id).update(eventData);
|
|
} else {
|
|
const event = new Event(eventData);
|
|
this.reactiveTimeline.addEvent(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
getTimeline() {
|
|
return this.reactiveTimeline;
|
|
}
|
|
/*
|
|
getGroupedEvents() {
|
|
let currentSender = Symbol("N/A")
|
|
let groups = []
|
|
let currentGroup = []
|
|
for (const event of this.list) {
|
|
if (event.sender === currentSender) {
|
|
currentGroup.push(event)
|
|
} else {
|
|
if (currentGroup.length) groups.push(currentGroup)
|
|
currentGroup = [event]
|
|
currentSender = event.sender
|
|
}
|
|
}
|
|
if (currentGroup.length) groups.push(currentGroup)
|
|
return groups
|
|
}
|
|
*/
|
|
}
|
|
|
|
export { Timeline };
|