Migrate media thread views to composition API

This commit is contained in:
N-Pex 2025-06-10 13:35:51 +02:00
parent 77eebafb79
commit 44578048aa
22 changed files with 1144 additions and 598 deletions

View file

@ -1,21 +1,12 @@
import { MatrixClient, MatrixEvent } from "matrix-js-sdk";
import { KeanuEventExtension } from "./eventAttachment";
import { EventAttachment, KeanuEventExtension } from "./eventAttachment";
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
import { Counter, ModeOfOperation } from "aes-js";
import { Attachment } from "./attachment";
import proofmode from "../plugins/proofmode";
import imageSize from "image-size";
import imageResize from "image-resize";
import { reactive } from "vue";
type CacheEntry = {
attachment?: string;
thumbnail?: string;
attachmentPromise?: Promise<string>;
thumbnailPromise?: Promise<string>;
attachmentProgress?: ((progress: number) => void)[];
thumbnailProgress?: ((progress: number) => void)[];
};
import { Reactive, reactive } from "vue";
export class AttachmentManager {
matrixClient: MatrixClient;
@ -23,7 +14,7 @@ export class AttachmentManager {
maxSizeUploads: number;
maxSizeAutoDownloads: number;
cache: Map<string | undefined, CacheEntry>;
cache: Map<string | undefined, Reactive<EventAttachment>>;
constructor(matrixClient: MatrixClient, useAuthedMedia: boolean, maxSizeAutoDownloads: number) {
this.matrixClient = matrixClient;
@ -34,9 +25,12 @@ export class AttachmentManager {
this.cache = new Map();
// Get max upload size
this.matrixClient.getMediaConfig(useAuthedMedia).then((config) => {
this.matrixClient
.getMediaConfig(useAuthedMedia)
.then((config) => {
this.maxSizeUploads = config["m.upload.size"] ?? 0;
}).catch(() => {});
})
.catch(() => {});
}
public createAttachment(file: File): Attachment {
@ -88,9 +82,13 @@ export class AttachmentManager {
width: newWidth,
height: newHeight,
};
// Use scaled version if the image does not contain C2PA
attachment.useScaled = attachment.scaledFile !== undefined && (attachment.proof === undefined || attachment.proof.integrity === undefined || attachment.proof.integrity.c2pa === undefined)
attachment.useScaled =
attachment.scaledFile !== undefined &&
(attachment.proof === undefined ||
attachment.proof.integrity === undefined ||
attachment.proof.integrity.c2pa === undefined);
})
.catch((err) => {
console.error("Resize failed:", err);
@ -110,76 +108,62 @@ export class AttachmentManager {
return attachment;
}
public async loadEventAttachment(
event: MatrixEvent & KeanuEventExtension,
progress?: (percent: number) => void,
outputObject?: { src: string; thumbnailSrc: string }
): Promise<string> {
console.error("GET ATTACHMENT FOR EVENT", event.getId());
const entry = this.cache.get(event.getId()) ?? {};
if (entry.attachment) {
if (outputObject) {
outputObject.src = entry.attachment;
}
return entry.attachment;
public getEventAttachment(event: MatrixEvent & KeanuEventExtension): Reactive<EventAttachment> {
let entry = this.cache.get(event.getId());
if (entry !== undefined) {
return entry;
}
if (!entry.attachmentPromise) {
entry.attachmentPromise = this._loadEventAttachmentOrThumbnail(event, false, progress)
.then((attachment) => {
entry.attachment = attachment;
return attachment;
})
.catch((err) => {
entry.attachmentPromise = undefined;
throw err;
});
this.cache.set(event.getId(), entry);
}
entry.attachmentProgress = (entry.attachmentProgress ?? []).concat();
return entry.attachmentPromise.then((attachment) => {
console.error("GOT ATTACHMENT", attachment);
if (outputObject) {
outputObject.src = attachment;
}
return attachment;
const attachment: Reactive<EventAttachment> = reactive({
event: event,
srcProgress: -1,
thumbnailProgress: -1,
loadSrc: () => Promise.reject("Not implemented"),
loadThumbnail: () => Promise.reject("Not implemented"),
release: () => Promise.reject("Not implemented"),
});
}
public async loadEventThumbnail(
event: MatrixEvent & KeanuEventExtension,
progress?: (percent: number) => void,
outputObject?: { src: string; thumbnailSrc: string }
): Promise<string> {
console.error("GET THUMB FOR EVENT", event.getId());
const entry = this.cache.get(event.getId()) ?? {};
if (entry.thumbnail) {
if (outputObject) {
outputObject.thumbnailSrc = entry.thumbnail;
attachment.loadSrc = () => {
if (attachment.src) {
return Promise.resolve(attachment.src);
} else if (attachment.srcPromise) {
return attachment.srcPromise;
}
return entry.thumbnail;
}
if (!entry.thumbnailPromise) {
entry.thumbnailPromise = this._loadEventAttachmentOrThumbnail(event, true, progress)
.then((thummbnail) => {
entry.thumbnail = thummbnail;
attachment.srcPromise = this._loadEventAttachmentOrThumbnail(event, false, (percent) => {
attachment.srcProgress = percent;
}).then((src) => {
attachment.src = src;
return src;
});
return attachment.srcPromise;
};
attachment.loadThumbnail = () => {
if (attachment.thumbnail) {
return Promise.resolve(attachment.thumbnail);
} else if (attachment.thumbnailPromise) {
return attachment.thumbnailPromise;
}
attachment.thumbnailPromise = this._loadEventAttachmentOrThumbnail(event, true, (percent) => {
attachment.thumbnailProgress = percent;
}).then((thummbnail) => {
attachment.thumbnail = thummbnail;
return thummbnail;
})
.catch((err) => {
entry.thumbnailPromise = undefined;
throw err;
});
this.cache.set(event.getId(), entry);
}
return entry.thumbnailPromise.then((thumbnail) => {
console.error("GOT THUMB", thumbnail);
if (outputObject) {
outputObject.thumbnailSrc = thumbnail;
return attachment.thumbnailPromise;
};
attachment.release = (src: boolean, thumbnail: boolean) => {
// TODO - figure out logic
if (entry) {
// TODO - abortable promises
this.cache.delete(event.getId());
if (attachment.src) {
URL.revokeObjectURL(attachment.src);
}
if (attachment.thumbnail) {
URL.revokeObjectURL(attachment.thumbnail);
}
}
return thumbnail;
});
}
this.cache.set(event.getId(), attachment!);
return attachment;
}
private async _loadEventAttachmentOrThumbnail(
@ -277,21 +261,6 @@ export class AttachmentManager {
return URL.createObjectURL(new Blob([bytes.buffer], { type: mime }));
}
releaseEvent(event: MatrixEvent & KeanuEventExtension): void {
console.error("Release event", event.getId());
const entry = this.cache.get(event.getId());
if (entry) {
// TODO - abortable promises
this.cache.delete(event.getId());
if (entry.attachment) {
URL.revokeObjectURL(entry.attachment);
}
if (entry.thumbnail) {
URL.revokeObjectURL(entry.thumbnail);
}
}
}
private b64toBuffer(val: any) {
const baseValue = val.replaceAll("-", "+").replaceAll("_", "/");
return Buffer.from(baseValue, "base64");

View file

@ -1,9 +1,11 @@
import { MatrixEvent } from "matrix-js-sdk";
import { MatrixEvent, Room } from "matrix-js-sdk";
export type KeanuEventExtension = {
isMxThread?: boolean;
isChannelMessage?: boolean;
isPinned?: boolean;
parentThread?: MatrixEvent & KeanuEventExtension;
replyEvent?: MatrixEvent & KeanuEventExtension;
}
export type EventAttachment = {
@ -12,4 +14,15 @@ export type EventAttachment = {
thumbnail?: string;
srcPromise?: Promise<string>;
thumbnailPromise?: Promise<string>;
srcProgress: number;
thumbnailProgress: number;
loadSrc: () => void;
loadThumbnail: () => Promise<string>;
release: (src: boolean, thumbnail: boolean) => void;
};
export type KeanuEvent = MatrixEvent & KeanuEventExtension;
export type KeanuRoom = Room & {
displayType: "im.keanu.room_type_default" | "im.keanu.room_type_voice" | "im.keanu.room_type_file" | "im.keanu.room_type_channel" | undefined;
}