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