support local echo for adding a reaction

This commit is contained in:
Bruno Windels 2021-06-04 15:34:44 +02:00
parent 8bf160dfc0
commit b7402ce43c
6 changed files with 176 additions and 26 deletions

View file

@ -23,23 +23,47 @@ export class ReactionsViewModel {
this._reactions = this._map.sortValues((a, b) => a._compare(b));
}
update(annotations) {
for (const key in annotations) {
if (annotations.hasOwnProperty(key)) {
const annotation = annotations[key];
update(annotations, pendingAnnotations) {
if (annotations) {
for (const key in annotations) {
if (annotations.hasOwnProperty(key)) {
const annotation = annotations[key];
const reaction = this._map.get(key);
if (reaction) {
if (reaction._tryUpdate(annotation)) {
this._map.update(key);
}
} else {
this._map.add(key, new ReactionViewModel(key, annotation, 0, this._parentEntry));
}
}
}
}
if (pendingAnnotations) {
for (const [key, count] of pendingAnnotations.entries()) {
const reaction = this._map.get(key);
if (reaction) {
if (reaction._tryUpdate(annotation)) {
if (reaction._tryUpdatePending(count)) {
this._map.update(key);
}
} else {
this._map.add(key, new ReactionViewModel(key, annotation, this._parentEntry));
this._map.add(key, new ReactionViewModel(key, null, count, this._parentEntry));
}
}
}
for (const existingKey of this._map.keys()) {
if (!annotations.hasOwnProperty(existingKey)) {
const hasPending = pendingAnnotations?.has(existingKey);
const hasRemote = annotations?.hasOwnProperty(existingKey);
if (!hasRemote && !hasPending) {
this._map.remove(existingKey);
} else if (!hasRemote) {
if (this._map.get(existingKey)._tryUpdate(null)) {
this._map.update(existingKey);
}
} else if (!hasPending) {
if (this._map.get(existingKey)._tryUpdatePending(0)) {
this._map.update(existingKey);
}
}
}
}
@ -50,43 +74,65 @@ export class ReactionsViewModel {
}
class ReactionViewModel {
constructor(key, annotation, parentEntry) {
constructor(key, annotation, pendingCount, parentEntry) {
this._key = key;
this._annotation = annotation;
this._pendingCount = pendingCount;
this._parentEntry = parentEntry;
}
_tryUpdate(annotation) {
if (
const oneSetAndOtherNot = !!this._annotation !== !!annotation;
const bothSet = this._annotation && annotation;
const areDifferent = bothSet && (
annotation.me !== this._annotation.me ||
annotation.count !== this._annotation.count ||
annotation.firstTimestamp !== this._annotation.firstTimestamp
) {
);
if (oneSetAndOtherNot || areDifferent) {
this._annotation = annotation;
return true;
}
return false;
}
_tryUpdatePending(pendingCount) {
if (pendingCount !== this._pendingCount) {
this._pendingCount = pendingCount;
return true;
}
return false;
}
get key() {
return this._key;
}
get count() {
return this._annotation.count;
return (this._annotation?.count || 0) + this._pendingCount;
}
get isPending() {
return this._pendingCount !== 0;
}
get haveReacted() {
return this._annotation.me;
return this._annotation?.me || this.isPending;
}
_compare(other) {
const a = this._annotation;
const b = other._annotation;
if (a.count !== b.count) {
return b.count - a.count;
if (this.count !== other.count) {
return other.count - this.count;
} else {
return a.firstTimestamp - b.firstTimestamp;
const a = this._annotation;
const b = other._annotation;
if (a && b) {
return a.firstTimestamp - b.firstTimestamp;
} else if (a) {
return -1;
} else {
return 1;
}
}
}
@ -97,4 +143,5 @@ class ReactionViewModel {
return this._parentEntry.react(this.key);
}
}
}
}

View file

@ -24,7 +24,7 @@ export class BaseMessageTile extends SimpleTile {
this._date = this._entry.timestamp ? new Date(this._entry.timestamp) : null;
this._isContinuation = false;
this._reactions = null;
if (this._entry.annotations) {
if (this._entry.annotations || this._entry.pendingAnnotations) {
this._updateReactions();
}
}
@ -135,8 +135,8 @@ export class BaseMessageTile extends SimpleTile {
}
_updateReactions() {
const {annotations} = this._entry;
if (!annotations) {
const {annotations, pendingAnnotations} = this._entry;
if (!annotations && !pendingAnnotations) {
if (this._reactions) {
this._reactions = null;
}
@ -144,7 +144,7 @@ export class BaseMessageTile extends SimpleTile {
if (!this._reactions) {
this._reactions = new ReactionsViewModel(this);
}
this._reactions.update(annotations);
this._reactions.update(annotations, pendingAnnotations);
}
}
}

View file

@ -0,0 +1,69 @@
/*
Copyright 2021 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import {getRelationFromContent} from "./relations.js";
class PendingAnnotations {
constructor() {
this.aggregatedAnnotations = new Map();
this._entries = [];
}
add(pendingEventEntry) {
const relation = getRelationFromContent(pendingEventEntry.content);
const key = relation.key;
if (!key) {
return;
}
const count = this.aggregatedAnnotations.get(key) || 0;
//const addend = pendingEventEntry.isRedacted ? -1 : 1;
//this.aggregatedAnnotations.set(key, count + addend);
this.aggregatedAnnotations.set(key, count + 1);
this._entries.push(pendingEventEntry);
}
remove(pendingEventEntry) {
const idx = this._entries.indexOf(pendingEventEntry);
if (idx === -1) {
return;
}
this._entries.splice(idx, 1);
const relation = getRelationFromContent(pendingEventEntry.content);
const key = relation.key;
let count = this.aggregatedAnnotations.get(key);
if (count !== undefined) {
count -= 1;
if (count <= 0) {
this.aggregatedAnnotations.delete(key);
} else {
this.aggregatedAnnotations.set(key, count);
}
}
}
findForKey(key) {
return this._entries.find(e => {
const relation = getRelationFromContent(e.content);
if (relation.key === key) {
return e;
}
});
}
get isEmpty() {
return this._entries.length;
}
}

View file

@ -16,12 +16,14 @@ limitations under the License.
import {BaseEntry} from "./BaseEntry.js";
import {REDACTION_TYPE} from "../../common.js";
import {createAnnotation} from "../relations.js";
import {createAnnotation, getRelationFromContent, ANNOTATION_RELATION_TYPE} from "../relations.js";
import {PendingAnnotations} from "../PendingAnnotations.js";
export class BaseEventEntry extends BaseEntry {
constructor(fragmentIdComparer) {
super(fragmentIdComparer);
this._pendingRedactions = null;
this._pendingAnnotations = null;
}
get isRedacting() {
@ -52,6 +54,15 @@ export class BaseEventEntry extends BaseEntry {
if (this._pendingRedactions.length === 1) {
return "isRedacted";
}
} else {
const relation = getRelationFromContent(entry.content);
if (relation && relation.rel_type === ANNOTATION_RELATION_TYPE) {
if (!this._pendingAnnotations) {
this._pendingAnnotations = new PendingAnnotations();
}
this._pendingAnnotations.add(entry);
return "pendingAnnotations";
}
}
}
@ -69,6 +80,15 @@ export class BaseEventEntry extends BaseEntry {
return "isRedacted";
}
}
} else {
const relation = getRelationFromContent(entry.content);
if (relation && relation.rel_type === ANNOTATION_RELATION_TYPE && this._pendingAnnotations) {
this._pendingAnnotations.remove(entry);
if (this._pendingAnnotations.isEmpty) {
this._pendingAnnotations = null;
}
return "pendingAnnotations";
}
}
}
@ -85,4 +105,13 @@ export class BaseEventEntry extends BaseEntry {
annotate(key) {
return createAnnotation(this.id, key);
}
}
get pendingAnnotations() {
return this._pendingAnnotations?.aggregatedAnnotations;
}
async getOwnAnnotationId(room, key) {
const pendingEvent = this._pendingAnnotations?.findForKey(key);
return pendingEvent?.id;
}
}

View file

@ -131,7 +131,12 @@ export class EventEntry extends BaseEventEntry {
return this._eventEntry.annotations;
}
getOwnAnnotationId(room, key) {
return room.getOwnAnnotationEventId(this.id, key);
async getOwnAnnotationId(room, key) {
const localId = await super.getOwnAnnotationId(room, key);
if (localId) {
return localId;
} else {
return room.getOwnAnnotationEventId(this.id, key);
}
}
}

View file

@ -33,7 +33,7 @@ class ReactionView extends TemplateView {
render(t, vm) {
const haveReacted = vm => vm.haveReacted;
return t.button({
className: {haveReacted},
className: {haveReacted, isPending: vm => vm.isPending},
}, [vm.key, " ", vm => `${vm.count}`]);
}