Carbon/src/js/Timeline.js

166 lines
4.1 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}