Telegram Web K with changes to work inside I2P
https://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
7.2 KiB
230 lines
7.2 KiB
import rootScope from "../../lib/rootScope"; |
|
import { generatePathData } from "../../helpers/dom"; |
|
import { MyMessage } from "../../lib/appManagers/appMessagesManager"; |
|
import Chat from "./chat"; |
|
|
|
type Group = {bubble: HTMLDivElement, mid: number, timestamp: number}[]; |
|
type BubbleGroup = {timestamp: number, fromId: number, mid: number, group: Group}; |
|
export default class BubbleGroups { |
|
private bubbles: Array<BubbleGroup> = []; // map to group |
|
private groups: Array<Group> = []; |
|
//updateRAFs: Map<HTMLDivElement[], number> = new Map(); |
|
private newGroupDiff = 121; // * 121 in scheduled messages |
|
|
|
constructor(private chat: Chat) { |
|
|
|
} |
|
|
|
removeBubble(bubble: HTMLDivElement, mid: number) { |
|
const details = this.bubbles.findAndSplice(g => g.mid === mid); |
|
if(details && details.group.length) { |
|
details.group.findAndSplice(d => d.bubble === bubble); |
|
if(!details.group.length) { |
|
this.groups.findAndSplice(g => g === details.group); |
|
} else { |
|
this.updateGroup(details.group); |
|
} |
|
} |
|
} |
|
|
|
addBubble(bubble: HTMLDivElement, message: MyMessage, reverse: boolean) { |
|
const timestamp = message.date; |
|
const mid = message.mid; |
|
let fromId = message.fromId; |
|
let group: Group; |
|
|
|
// fix for saved messages forward to self |
|
if(fromId === rootScope.myId && message.peerId === rootScope.myId && (message as any).fwdFromId === fromId) { |
|
fromId = -fromId; |
|
} |
|
|
|
// try to find added |
|
//this.removeBubble(message.mid); |
|
|
|
const insertObject = {bubble, mid, timestamp}; |
|
if(this.bubbles.length) { |
|
let foundBubble: BubbleGroup; |
|
let foundAtIndex = -1; |
|
for(let i = 0; i < this.bubbles.length; ++i) { |
|
const bubble = this.bubbles[i]; |
|
const diff = Math.abs(bubble.timestamp - timestamp); |
|
const good = bubble.fromId === fromId && diff <= this.newGroupDiff; |
|
|
|
if(good) { |
|
foundAtIndex = i; |
|
|
|
if(this.chat.type === 'scheduled') { |
|
break; |
|
} |
|
} else { |
|
foundAtIndex = -1; |
|
} |
|
|
|
if(this.chat.type !== 'scheduled') { |
|
if(mid > bubble.mid) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if(foundAtIndex !== -1) { |
|
foundBubble = this.bubbles[foundAtIndex]; |
|
} |
|
/* const foundBubble = this.bubbles.find(bubble => { |
|
const diff = Math.abs(bubble.timestamp - timestamp); |
|
return bubble.fromId === fromId && diff <= this.newGroupDiff; |
|
}); */ |
|
|
|
if(!foundBubble) this.groups.push(group = [insertObject]); |
|
else { |
|
group = foundBubble.group; |
|
|
|
let i = 0, foundMidOnSameTimestamp = 0; |
|
for(; i < group.length; ++i) { |
|
const _timestamp = group[i].timestamp; |
|
const _mid = group[i].mid; |
|
|
|
if(timestamp < _timestamp) { |
|
break; |
|
} else if(timestamp === _timestamp) { |
|
foundMidOnSameTimestamp = _mid; |
|
} |
|
|
|
if(foundMidOnSameTimestamp && mid < foundMidOnSameTimestamp) { |
|
break; |
|
} |
|
} |
|
|
|
group.splice(i, 0, insertObject); |
|
} |
|
} else { |
|
this.groups.push(group = [insertObject]); |
|
} |
|
|
|
//console.log('[BUBBLE]: addBubble', bubble, message.mid, fromId, reverse, group); |
|
|
|
if(mid > 0) { |
|
let insertIndex = 0; |
|
for(; insertIndex < this.bubbles.length; ++insertIndex) { |
|
if(this.bubbles[insertIndex].mid < mid) { |
|
break; |
|
} |
|
} |
|
|
|
this.bubbles.splice(insertIndex, 0, {timestamp, fromId, mid: message.mid, group}); |
|
} else { |
|
this.bubbles.unshift({timestamp, fromId, mid: message.mid, group}); |
|
} |
|
|
|
//this.bubbles.push({timestamp, fromId, mid: message.mid, group}); |
|
this.updateGroup(group); |
|
} |
|
|
|
setClipIfNeeded(bubble: HTMLDivElement, remove = false) { |
|
//console.log('setClipIfNeeded', bubble, remove); |
|
const className = bubble.className; |
|
if(className.includes('is-message-empty')/* && !className.includes('is-reply') */ |
|
&& (className.includes('photo') || className.includes('video'))) { |
|
let container = bubble.querySelector('.bubble__media-container') as SVGSVGElement; |
|
//console.log('setClipIfNeeded', bubble, remove, container); |
|
if(!container) return; |
|
|
|
try { |
|
Array.from(container.children).forEach(object => { |
|
if(object instanceof SVGDefsElement) return; |
|
|
|
if(remove) { |
|
object.removeAttributeNS(null, 'clip-path'); |
|
} else { |
|
let clipId = container.dataset.clipId; |
|
let path = container.firstElementChild.firstElementChild.lastElementChild as SVGPathElement; |
|
let width = +object.getAttributeNS(null, 'width'); |
|
let height = +object.getAttributeNS(null, 'height'); |
|
let isOut = className.includes('is-out'); |
|
let isReply = className.includes('is-reply'); |
|
let d = ''; |
|
|
|
//console.log('setClipIfNeeded', object, width, height, isOut); |
|
|
|
let tr: number, tl: number; |
|
if(className.includes('forwarded') || isReply) { |
|
tr = tl = 0; |
|
} else if(isOut) { |
|
tr = className.includes('is-group-first') ? 12 : 6; |
|
tl = 12; |
|
} else { |
|
tr = 12; |
|
tl = className.includes('is-group-first') ? 12 : 6; |
|
} |
|
|
|
if(isOut) { |
|
d = generatePathData(0, 0, width - 9, height, tl, tr, 0, 12); |
|
} else { |
|
d = generatePathData(9, 0, width - 9, height, tl, tr, 12, 0); |
|
} |
|
|
|
path.setAttributeNS(null, 'd', d); |
|
object.setAttributeNS(null, 'clip-path', 'url(#' + clipId + ')'); |
|
} |
|
}); |
|
} catch(err) {} |
|
} |
|
} |
|
|
|
updateGroup(group: Group) { |
|
/* if(this.updateRAFs.has(group)) { |
|
window.cancelAnimationFrame(this.updateRAFs.get(group)); |
|
this.updateRAFs.delete(group); |
|
} */ |
|
|
|
//this.updateRAFs.set(group, window.requestAnimationFrame(() => { |
|
//this.updateRAFs.delete(group); |
|
|
|
if(!group.length) { |
|
return; |
|
} |
|
|
|
const first = group[0].bubble; |
|
|
|
//console.log('[BUBBLE]: updateGroup', group, first); |
|
|
|
if(group.length == 1) { |
|
first.classList.add('is-group-first', 'is-group-last'); |
|
this.setClipIfNeeded(first); |
|
return; |
|
} else { |
|
first.classList.remove('is-group-last'); |
|
first.classList.add('is-group-first'); |
|
this.setClipIfNeeded(first, true); |
|
} |
|
|
|
const length = group.length - 1; |
|
for(let i = 1; i < length; ++i) { |
|
const bubble = group[i].bubble; |
|
bubble.classList.remove('is-group-last', 'is-group-first'); |
|
this.setClipIfNeeded(bubble, true); |
|
} |
|
|
|
const last = group[group.length - 1].bubble; |
|
last.classList.remove('is-group-first'); |
|
last.classList.add('is-group-last'); |
|
this.setClipIfNeeded(last); |
|
//})); |
|
} |
|
|
|
updateGroupByMessageId(mid: number) { |
|
const details = this.bubbles.find(g => g.mid == mid); |
|
if(details) { |
|
this.updateGroup(details.group); |
|
} |
|
} |
|
|
|
cleanup() { |
|
this.bubbles = []; |
|
this.groups = []; |
|
/* for(let value of this.updateRAFs.values()) { |
|
window.cancelAnimationFrame(value); |
|
} |
|
this.updateRAFs.clear(); */ |
|
} |
|
} |