forked from mystiq/hydrogen-web
WIP
This commit is contained in:
parent
280de98858
commit
2ebadb36c3
8 changed files with 126 additions and 37 deletions
|
@ -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);
|
||||||
|
|
|
@ -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) {
|
||||||
const id = await this._entry.getOwnAnnotationId(this._room, key);
|
return this.logger.wrapOrRun(log, "redactReaction", log => {
|
||||||
if (id) {
|
const id = await this._entry.getOwnAnnotationId(this._room, key);
|
||||||
this._room.sendRedaction(id);
|
if (id) {
|
||||||
}
|
this._room.sendRedaction(id, null, log);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateReactions() {
|
_updateReactions() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,7 +166,12 @@ export class SendQueue {
|
||||||
txn.abort();
|
txn.abort();
|
||||||
}
|
}
|
||||||
await txn.complete();
|
await txn.complete();
|
||||||
this._pendingEvents.remove(idx);
|
// 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
pendingEvent.dispose();
|
pendingEvent.dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue