From 94bf35875a7f59c22cbf4443d01ef9510ead61b6 Mon Sep 17 00:00:00 2001 From: N-Pex Date: Sun, 29 Jun 2025 09:28:56 +0200 Subject: [PATCH] WIP improve export --- src/components/RoomExport.vue | 334 +++++++++--------- .../composition/MessageThreadExport.vue | 6 +- src/models/attachmentManager.ts | 3 +- 3 files changed, 174 insertions(+), 169 deletions(-) diff --git a/src/components/RoomExport.vue b/src/components/RoomExport.vue index a7d60b9..2afe1f6 100644 --- a/src/components/RoomExport.vue +++ b/src/components/RoomExport.vue @@ -27,12 +27,12 @@
@@ -153,6 +153,7 @@ export default { return { timelineSet: null, events: [], + exportComponents: [], fetchedEvents: 0, totalEvents: 0, processedEvents: 0, @@ -179,6 +180,22 @@ export default { }, }, methods: { + componentForEventForExport(event, forExport) { + const comp = this.componentForEvent(event, forExport); + const self = this; + if (comp) { + if (!comp.mixins) { + comp.mixins = []; + } + comp.mixins.push({ + created: function() { + console.error("Created", this.name); + self.exportComponents.push(this); + } + }); + } + return comp; + }, cancelExport() { this.cancelled = true; }, @@ -230,6 +247,8 @@ export default { var currentMediaSize = 0; var maxMediaSize = 1024 * 1024 * 1024; // 1GB + this.exportComponents = []; + this.getEvents() .then((events) => { var decryptionPromises = []; @@ -282,6 +301,8 @@ export default { }); }) .then(() => { + this.totalEvents = this.exportComponents.length; + // UI updated, start processing events zip = new JSZip(); var avatarFolder = zip.folder("avatars"); @@ -291,186 +312,173 @@ export default { var filesFolder = zip.folder("files"); var downloadPromises = []; - let components = this.$refs.exportedEvent; - for (const parentComp of components) { - let childComponents = [parentComp]; - // Some components, i.e. the media threads, have subcomponents - // that we want to export. So pickup subcomponents here as well. - if (parentComp.$refs && parentComp.$refs.exportedEvent) { - if (Array.isArray(parentComp.$refs.exportedEvent)) { - for (const child of parentComp.$refs.exportedEvent) { - childComponents.push(child); - } - } else { - childComponents.push(parentComp.$refs.exportedEvent); - } - } - for (const comp of childComponents.filter((c) => c.event != undefined)) { - // Avatars need downloading? - if (comp.$el && comp.$el.nodeType == 1) { - const avatars = comp.$el.getElementsByClassName("v-avatar"); - if (avatars && avatars.length > 0) { - const member = this.room.getMember(comp.event.getSender()); - if (member) { - const fileName = comp.event.getSender() + ".png"; + for (const comp of this.exportComponents.filter((c) => c.event != undefined)) { + // Avatars need downloading? + if (comp.$el && comp.$el.nodeType == 1) { + const avatars = comp.$el.getElementsByClassName("v-avatar"); + if (avatars && avatars.length > 0) { + const member = this.room.getMember(comp.event.getSender()); + if (member) { + const fileName = comp.event.getSender() + ".png"; - const setSource = (fileName) => { - for (let avatarIndex = 0; avatarIndex < avatars.length; avatarIndex++) { - const avatarElement = avatars[avatarIndex]; - const images = avatarElement.getElementsByTagName("img"); - for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { - const img = images[imageIndex]; - img.onerror = undefined; - img.removeAttribute("src"); - img.setAttribute("data-exported-src", "./avatars/" + fileName); - } + const setSource = (fileName) => { + for (let avatarIndex = 0; avatarIndex < avatars.length; avatarIndex++) { + const avatarElement = avatars[avatarIndex]; + const images = avatarElement.getElementsByTagName("img"); + for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { + const img = images[imageIndex]; + img.onerror = undefined; + img.removeAttribute("src"); + img.setAttribute("data-exported-src", "./avatars/" + fileName); } - }; - - if (!avatarFolder.file(fileName)) { - const url = member.getAvatarUrl( - this.$matrix.matrixClient.getHomeserverUrl(), - 40, - 40, - "scale", - true, - false, - this.$matrix.useAuthedMedia - ); - if (url) { - avatarFolder.file(fileName, "empty"); - downloadPromises.push( - axios - .get(url, { - responseType: "blob", - headers: this.$matrix.useAuthedMedia - ? { - Authorization: `Bearer ${this.$matrix.matrixClient.getAccessToken()}`, - } - : undefined, - }) - .then((result) => { - if (result.data) { - avatarFolder.file(fileName, result.data); - setSource(fileName); - } - }) - .catch((err) => { - console.error("Download error: ", err); - avatarFolder.remove(fileName); - }) - ); - } - } else { - setSource(fileName); } + }; + + if (!avatarFolder.file(fileName)) { + const url = member.getAvatarUrl( + this.$matrix.matrixClient.getHomeserverUrl(), + 40, + 40, + "scale", + true, + false, + this.$matrix.useAuthedMedia + ); + if (url) { + avatarFolder.file(fileName, "empty"); + downloadPromises.push( + axios + .get(url, { + responseType: "blob", + headers: this.$matrix.useAuthedMedia + ? { + Authorization: `Bearer ${this.$matrix.matrixClient.getAccessToken()}`, + } + : undefined, + }) + .then((result) => { + if (result.data) { + avatarFolder.file(fileName, result.data); + setSource(fileName); + } + }) + .catch((err) => { + console.error("Download error: ", err); + avatarFolder.remove(fileName); + }) + ); + } + } else { + setSource(fileName); } } } + } - let componentClass = comp.$options - ? comp.$options.__file.split("/").reverse()[0].split(".")[0] - : "invalid_component"; - let attachment = - comp.event && comp.event.getId - ? this.$matrix.attachmentManager.getEventAttachment(comp.event) - : undefined; + let attachment = + comp.event && comp.event.getId + ? this.$matrix.attachmentManager.getEventAttachment(comp.event) + : undefined; + let componentClass = comp.$options + ? comp.$options.__file.split("/").reverse()[0].split(".")[0] + : "invalid_component"; + console.error("Processi", componentClass, comp.event, comp.originalEvent, attachment); - if (attachment && (attachment.srcSize = 0 || currentMediaSize + attachment.srcSize <= maxMediaSize)) { - downloadPromises.push( - attachment - .loadSrc({ asBlob: true }) - .then((res) => { - const blob = res.data; - if (currentMediaSize + blob.size <= maxMediaSize) { - currentMediaSize += blob.size; - switch (componentClass) { - case "MessageIncomingImageExport": - case "MessageOutgoingImageExport": - { - let mime = blob.type; - var extension = ".png"; - switch (mime) { - case "image/jpeg": - case "image/jpg": - extension = ".jpg"; - break; - case "image/gif": - extension = ".gif"; - } + if (attachment && (attachment.srcSize = 0 || currentMediaSize + attachment.srcSize <= maxMediaSize)) { + downloadPromises.push( + attachment + .loadSrc({ asBlob: true }) + .then((res) => { + const blob = res.data; + if (currentMediaSize + blob.size <= maxMediaSize) { + currentMediaSize += blob.size; - let fileName = comp.event.getId() + extension; - imageFolder.file(fileName, blob); // TODO calc bytes - - // Update source - const images = comp.$el.getElementsByTagName("img"); - for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { - const img = images[imageIndex]; - img.removeAttribute("src"); - img.setAttribute("data-exported-src", "./images/" + fileName); - } - this.processedEvents += 1; + switch (componentClass) { + case "MessageIncomingImageExport": + case "MessageOutgoingImageExport": + { + let mime = blob.type; + var extension = ".png"; + switch (mime) { + case "image/jpeg": + case "image/jpg": + extension = ".jpg"; + break; + case "image/gif": + extension = ".gif"; } - break; - case "MessageIncomingAudioExport": - case "MessageOutgoingAudioExport": - { - var extension = ".webm"; - let fileName = comp.event.getId() + extension; - audioFolder.file(fileName, blob); // TODO calc bytes - let elements = comp.$el.getElementsByTagName("audio"); - let element = elements && elements[0]; - if (element) { - element.setAttribute("data-exported-src", "./audio/" + fileName); - } - this.processedEvents += 1; - } - break; + let fileName = comp.event.getId() + extension; + imageFolder.file(fileName, blob); // TODO calc bytes - case "MessageIncomingVideoExport": - case "MessageOutgoingVideoExport": - { - var extension = ".mp4"; - let fileName = comp.event.getId() + extension; - videoFolder.file(fileName, blob); // TODO calc bytes - // comp.src = "./video/" + fileName; - let elements = comp.$el.getElementsByTagName("video"); - let element = elements && elements[0]; - if (element) { - element.setAttribute("data-exported-src", "./video/" + fileName); - } - this.processedEvents += 1; + // Update source + const images = comp.$el.getElementsByTagName("img"); + for (let imageIndex = 0; imageIndex < images.length; imageIndex++) { + const img = images[imageIndex]; + img.removeAttribute("src"); + img.setAttribute("data-exported-src", "./images/" + fileName); } - break; + this.processedEvents += 1; + } + break; - case "MessageIncomingFileExport": - case "MessageOutgoingFileExport": - { - var extension = util.getFileExtension(comp.event); - let fileName = comp.event.getId() + extension; - filesFolder.file(fileName, blob); - comp.href = "./files/" + fileName; - this.processedEvents += 1; + case "MessageIncomingAudioExport": + case "MessageOutgoingAudioExport": + { + var extension = ".webm"; + let fileName = comp.event.getId() + extension; + audioFolder.file(fileName, blob); // TODO calc bytes + let elements = comp.$el.getElementsByTagName("audio"); + let element = elements && elements[0]; + if (element) { + element.setAttribute("data-exported-src", "./audio/" + fileName); } - break; - } - this.processedEvents += 1; - return true; - } else { - this.processedEvents += 1; - return false; + this.processedEvents += 1; + } + break; + + case "MessageIncomingVideoExport": + case "MessageOutgoingVideoExport": + { + var extension = ".mp4"; + let fileName = comp.event.getId() + extension; + videoFolder.file(fileName, blob); // TODO calc bytes + // comp.src = "./video/" + fileName; + let elements = comp.$el.getElementsByTagName("video"); + let element = elements && elements[0]; + if (element) { + element.setAttribute("data-exported-src", "./video/" + fileName); + } + this.processedEvents += 1; + } + break; + + case "MessageIncomingFileExport": + case "MessageOutgoingFileExport": + { + var extension = util.getFileExtension(comp.event); + let fileName = comp.event.getId() + extension; + filesFolder.file(fileName, blob); + comp.href = "./files/" + fileName; + this.processedEvents += 1; + } + break; } - }) - .catch((ignoredErr) => { this.processedEvents += 1; - }) - ); - } else { - this.processedEvents += 1; - } + return true; + } else { + this.processedEvents += 1; + return false; + } + }) + .catch((ignoredErr) => { + this.processedEvents += 1; + }) + ); + } else { + this.processedEvents += 1; } } return Promise.all(downloadPromises); diff --git a/src/components/messages/composition/MessageThreadExport.vue b/src/components/messages/composition/MessageThreadExport.vue index 8ffdc2d..9dd1add 100644 --- a/src/components/messages/composition/MessageThreadExport.vue +++ b/src/components/messages/composition/MessageThreadExport.vue @@ -32,9 +32,6 @@ import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk"; const { t } = useI18n(); const $matrix: any = inject("globalMatrix"); -type RootType = InstanceType; -const rootRef = useTemplateRef("root"); - const emits = defineEmits< MessageEmits & { (event: "layout-change", value: { element: Element | undefined; action: () => void }): void } >(); @@ -44,8 +41,7 @@ const props = defineProps(); const processThread = () => { if (!event.value?.isRedacted()) { - const el = rootRef.value?.$el; - emits("layout-change", { element: el, action: _processThread }); + _processThread(); } }; diff --git a/src/models/attachmentManager.ts b/src/models/attachmentManager.ts index 9dfafac..1f77564 100644 --- a/src/models/attachmentManager.ts +++ b/src/models/attachmentManager.ts @@ -149,9 +149,10 @@ export class AttachmentManager { attachment.loadSrc = (options?: EventAttachmentLoadSrcOptions) => { if (attachment.src && !options?.asBlob) { return Promise.resolve({data: attachment.src, type: "src"}); - } else if (attachment.srcPromise) { + } else if (attachment.srcPromise && !options?.asBlob) { return attachment.srcPromise; } + Implement loadBlob somewhere here! attachment.srcPromise = this._loadEventAttachmentOrThumbnail(event, false, !!options?.asBlob, (percent) => { attachment.srcProgress = percent; }).then((res) => {