WIP improve export

This commit is contained in:
N-Pex 2025-06-29 09:28:56 +02:00
parent 9a124c5ab9
commit 94bf35875a
3 changed files with 174 additions and 169 deletions

View file

@ -27,12 +27,12 @@
<div v-if="!event.isRelation() && !event.isRedacted() && !event.isRedaction()" :ref="event.getId()">
<div class="message-wrapper">
<component
:is="componentForEvent(event, true)"
:is="componentForEventForExport(event, true)"
:room="room"
:originalEvent="event"
:nextEvent="events[index + 1]"
:timelineSet="timelineSet"
:componentFn="componentForEvent"
:componentFn="componentForEventForExport"
ref="exportedEvent"
v-on:layout-change="onLayoutChange"
/>
@ -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);

View file

@ -32,9 +32,6 @@ import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk";
const { t } = useI18n();
const $matrix: any = inject("globalMatrix");
type RootType = InstanceType<typeof MessageOutgoing | typeof MessageIncoming>;
const rootRef = useTemplateRef<RootType>("root");
const emits = defineEmits<
MessageEmits & { (event: "layout-change", value: { element: Element | undefined; action: () => void }): void }
>();
@ -44,8 +41,7 @@ const props = defineProps<MessageProps>();
const processThread = () => {
if (!event.value?.isRedacted()) {
const el = rootRef.value?.$el;
emits("layout-change", { element: el, action: _processThread });
_processThread();
}
};

View file

@ -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) => {