This commit is contained in:
Bruno Windels 2021-06-08 13:20:55 +02:00
parent 280de98858
commit 2ebadb36c3
8 changed files with 126 additions and 37 deletions

View file

@ -34,7 +34,7 @@ export class ReactionsViewModel {
this._map.update(key); this._map.update(key);
} }
} else { } else {
this._map.add(key, new ReactionViewModel(key, annotation, 0, this._parentEntry)); this._map.add(key, new ReactionViewModel(key, annotation, null, this._parentEntry));
} }
} }
} }
@ -61,7 +61,7 @@ export class ReactionsViewModel {
this._map.update(existingKey); this._map.update(existingKey);
} }
} else if (!hasPending) { } else if (!hasPending) {
if (this._map.get(existingKey)._tryUpdatePending(0)) { if (this._map.get(existingKey)._tryUpdatePending(null)) {
this._map.update(existingKey); this._map.update(existingKey);
} }
} }
@ -109,11 +109,18 @@ class ReactionViewModel {
} }
get count() { get count() {
return (this._annotation?.count || 0) + this._pendingCount; let count = 0;
if (this._annotation) {
count += this._annotation.count;
}
if (this._pendingCount !== null) {
count += this._pendingCount;
}
return count;
} }
get isPending() { get isPending() {
return this._pendingCount !== 0; return this._pendingCount !== null;
} }
get haveReacted() { get haveReacted() {
@ -137,7 +144,10 @@ class ReactionViewModel {
} }
toggleReaction() { toggleReaction() {
if (this.haveReacted) { const havePendingReaction = this._pendingCount > 0;
const haveRemoteReaction = this._annotation?.me;
const haveReaction = havePendingReaction || haveRemoteReaction;
if (haveReaction) {
return this._parentEntry.redactReaction(this.key); return this._parentEntry.redactReaction(this.key);
} else { } else {
return this._parentEntry.react(this.key); return this._parentEntry.react(this.key);

View file

@ -123,15 +123,30 @@ export class BaseMessageTile extends SimpleTile {
return true; return true;
} }
react(key) { react(key, log = null) {
return this._room.sendEvent("m.reaction", this._entry.annotate(key)); return this.logger.wrapOrRun(log, "react", log => {
// this assumes the existing reaction is not a remote one
// we would need to do getOwnAnnotation(Id) and see if there are any pending redactions for it
const pee = this._entry.getPendingAnnotationEntry(key);
const redaction = pee?.pendingRedaction;
log.set("has_redaction", !!redaction);
log.set("has_redaction", !!redaction);
if (redaction && !redaction.hasStartedSending) {
log.set("abort_redaction", true);
return redaction.pendingEvent.abort();
} else {
return this._room.sendEvent("m.reaction", this._entry.annotate(key), null, log);
}
});
} }
async redactReaction(key) { async redactReaction(key, log = null) {
return this.logger.wrapOrRun(log, "redactReaction", log => {
const id = await this._entry.getOwnAnnotationId(this._room, key); const id = await this._entry.getOwnAnnotationId(this._room, key);
if (id) { if (id) {
this._room.sendRedaction(id); this._room.sendRedaction(id, null, log);
} }
});
} }
_updateReactions() { _updateReactions() {

View file

@ -54,8 +54,7 @@ export class SimpleTile extends ViewModel {
get canAbortSending() { get canAbortSending() {
return this._entry.isPending && return this._entry.isPending &&
this._entry.pendingEvent.status !== SendStatus.Sending && !this._entry.pendingEvent.hasStartedSending;
this._entry.pendingEvent.status !== SendStatus.Sent;
} }
abortSending() { abortSending() {

View file

@ -116,6 +116,10 @@ export class PendingEvent {
get status() { return this._status; } get status() { return this._status; }
get error() { return this._error; } get error() { return this._error; }
get hasStartedSending() {
return this._status !== SendStatus.Sending && this._status !== SendStatus.Sent;
}
get attachmentsTotalBytes() { get attachmentsTotalBytes() {
return this._attachmentsTotalBytes; return this._attachmentsTotalBytes;
} }

View file

@ -157,8 +157,8 @@ export class SendQueue {
} }
async _removeEvent(pendingEvent) { async _removeEvent(pendingEvent) {
const idx = this._pendingEvents.array.indexOf(pendingEvent); let hasEvent = this._pendingEvents.array.indexOf(pendingEvent) !== -1;
if (idx !== -1) { if (hasEvent) {
const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]); const txn = await this._storage.readWriteTxn([this._storage.storeNames.pendingEvents]);
try { try {
txn.pendingEvents.remove(pendingEvent.roomId, pendingEvent.queueIndex); txn.pendingEvents.remove(pendingEvent.roomId, pendingEvent.queueIndex);
@ -166,8 +166,13 @@ export class SendQueue {
txn.abort(); txn.abort();
} }
await txn.complete(); await txn.complete();
// lookup index after async txn is complete,
// to make sure we're not racing with anything
const idx = this._pendingEvents.array.indexOf(pendingEvent);
if (idx !== -1) {
this._pendingEvents.remove(idx); this._pendingEvents.remove(idx);
} }
}
pendingEvent.dispose(); pendingEvent.dispose();
} }

View file

@ -22,30 +22,33 @@ export class PendingAnnotations {
this._entries = []; this._entries = [];
} }
add(pendingEventEntry) { /** adds either a pending annotation entry, or a remote annotation entry with a pending redaction */
const relation = getRelationFromContent(pendingEventEntry.content); add(annotationEntry) {
const relation = getRelationFromContent(annotationEntry.content);
const key = relation.key; const key = relation.key;
if (!key) { if (!key) {
return; return;
} }
const count = this.aggregatedAnnotations.get(key) || 0; const count = this.aggregatedAnnotations.get(key) || 0;
//const addend = pendingEventEntry.isRedacted ? -1 : 1; const addend = annotationEntry.isRedacted ? -1 : 1;
//this.aggregatedAnnotations.set(key, count + addend); console.log("add", count, addend);
this.aggregatedAnnotations.set(key, count + 1); this.aggregatedAnnotations.set(key, count + addend);
this._entries.push(pendingEventEntry); this._entries.push(annotationEntry);
} }
remove(pendingEventEntry) { /** removes either a pending annotation entry, or a remote annotation entry with a pending redaction */
const idx = this._entries.indexOf(pendingEventEntry); remove(annotationEntry) {
const idx = this._entries.indexOf(annotationEntry);
if (idx === -1) { if (idx === -1) {
return; return;
} }
this._entries.splice(idx, 1); this._entries.splice(idx, 1);
const relation = getRelationFromContent(pendingEventEntry.content); const relation = getRelationFromContent(annotationEntry.content);
const key = relation.key; const key = relation.key;
let count = this.aggregatedAnnotations.get(key); let count = this.aggregatedAnnotations.get(key);
if (count !== undefined) { if (count !== undefined) {
count -= 1; const addend = annotationEntry.isRedacted ? 1 : -1;
count += addend;
if (count <= 0) { if (count <= 0) {
this.aggregatedAnnotations.delete(key); this.aggregatedAnnotations.delete(key);
} else { } else {

View file

@ -22,6 +22,7 @@ import {TimelineReader} from "./persistence/TimelineReader.js";
import {PendingEventEntry} from "./entries/PendingEventEntry.js"; import {PendingEventEntry} from "./entries/PendingEventEntry.js";
import {RoomMember} from "../members/RoomMember.js"; import {RoomMember} from "../members/RoomMember.js";
import {PowerLevels} from "./PowerLevels.js"; import {PowerLevels} from "./PowerLevels.js";
import {getRelationFromContent} from "./relations.js";
export class Timeline { export class Timeline {
constructor({roomId, storage, closeCallback, fragmentIdComparer, pendingEvents, clock}) { constructor({roomId, storage, closeCallback, fragmentIdComparer, pendingEvents, clock}) {
@ -101,20 +102,62 @@ export class Timeline {
if (this._pendingEvents) { if (this._pendingEvents) {
this._localEntries = new MappedList(this._pendingEvents, pe => { this._localEntries = new MappedList(this._pendingEvents, pe => {
const pee = new PendingEventEntry({pendingEvent: pe, member: this._ownMember, clock: this._clock}); const pee = new PendingEventEntry({pendingEvent: pe, member: this._ownMember, clock: this._clock});
this._applyAndEmitLocalRelationChange(pee.pendingEvent, target => target.addLocalRelation(pee)); this._onAddPendingEvent(pee);
return pee; return pee;
}, (pee, params) => { }, (pee, params) => {
// is sending but redacted, who do we detect that here to remove the relation? // is sending but redacted, who do we detect that here to remove the relation?
pee.notifyUpdate(params); pee.notifyUpdate(params);
}, pee => { }, pee => this._onRemovePendingEvent(pee));
this._applyAndEmitLocalRelationChange(pee.pendingEvent, target => target.removeLocalRelation(pee));
});
} else { } else {
this._localEntries = new ObservableArray(); this._localEntries = new ObservableArray();
} }
this._allEntries = new ConcatList(this._remoteEntries, this._localEntries); this._allEntries = new ConcatList(this._remoteEntries, this._localEntries);
} }
_onAddPendingEvent(pee) {
let redactedEntry;
this._applyAndEmitLocalRelationChange(pee.pendingEvent, target => {
const wasRedacted = target.isRedacted;
const params = target.addLocalRelation(pee);
if (!wasRedacted && target.isRedacted) {
redactedEntry = target;
}
return params;
});
console.log("redactedEntry", redactedEntry);
if (redactedEntry) {
const redactedRelation = getRelationFromContent(redactedEntry.content);
if (redactedRelation?.event_id) {
const found = this._remoteEntries.findAndUpdate(
e => e.id === redactedRelation.event_id,
relationTarget => relationTarget.addLocalRelation(redactedEntry) || false
);
console.log("found", found);
}
}
}
_onRemovePendingEvent(pee) {
let unredactedEntry;
this._applyAndEmitLocalRelationChange(pee.pendingEvent, target => {
const wasRedacted = target.isRedacted;
const params = target.removeLocalRelation(pee);
if (wasRedacted && !target.isRedacted) {
unredactedEntry = target;
}
return params;
});
if (unredactedEntry) {
const redactedRelation = getRelationFromContent(unredactedEntry.content);
if (redactedRelation?.event_id) {
this._remoteEntries.findAndUpdate(
e => e.id === redactedRelation.event_id,
relationTarget => relationTarget.removeLocalRelation(unredactedEntry) || false
);
}
}
}
_applyAndEmitLocalRelationChange(pe, updater) { _applyAndEmitLocalRelationChange(pe, updater) {
const updateOrFalse = e => { const updateOrFalse = e => {
const params = updater(e); const params = updater(e);

View file

@ -42,7 +42,7 @@ export class BaseEventEntry extends BaseEntry {
} }
/** /**
aggregates local relation. aggregates local relation or local redaction of remote relation.
@return [string] returns the name of the field that has changed, if any @return [string] returns the name of the field that has changed, if any
*/ */
addLocalRelation(entry) { addLocalRelation(entry) {
@ -102,6 +102,13 @@ export class BaseEventEntry extends BaseEntry {
} }
} }
get pendingRedaction() {
if (this._pendingRedactions) {
return this._pendingRedactions[0];
}
return null;
}
annotate(key) { annotate(key) {
return createAnnotation(this.id, key); return createAnnotation(this.id, key);
} }
@ -111,7 +118,10 @@ export class BaseEventEntry extends BaseEntry {
} }
async getOwnAnnotationId(room, key) { async getOwnAnnotationId(room, key) {
const pendingEvent = this._pendingAnnotations?.findForKey(key); return this.getPendingAnnotationEntry(key)?.id;
return pendingEvent?.id; }
getPendingAnnotationEntry(key) {
return this._pendingAnnotations?.findForKey(key);
} }
} }