Simple POC for moving download button to action bar

This commit is contained in:
Travis Ralston 2021-07-14 20:51:20 -06:00
parent aaa9040634
commit 0a99f76e7f
6 changed files with 237 additions and 24 deletions

59
src/utils/LazyValue.ts Normal file
View file

@ -0,0 +1,59 @@
/*
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.
*/
/**
* Utility class for lazily getting a variable.
*/
export class LazyValue<T> {
private val: T;
private prom: Promise<T>;
private done = false;
public constructor(private getFn: () => Promise<T>) {
}
/**
* Whether or not a cached value is present.
*/
public get present(): boolean {
// we use a tracking variable just in case the final value is falsey
return this.done;
}
/**
* Gets the value without invoking a get. May be undefined until the
* value is fetched properly.
*/
public get cachedValue(): T {
return this.val;
}
/**
* Gets a promise which resolves to the value, eventually.
*/
public get value(): Promise<T> {
if (this.prom) return this.prom;
this.prom = this.getFn();
// Fork the promise chain to avoid accidentally making it return undefined always.
this.prom.then(v => {
this.val = v;
this.done = true;
});
return this.prom;
}
}

View file

@ -0,0 +1,90 @@
/*
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 { MatrixEvent } from "matrix-js-sdk/src";
import { LazyValue } from "./LazyValue";
import { Media, mediaFromContent } from "../customisations/Media";
import { decryptFile } from "./DecryptFile";
import { IMediaEventContent } from "../customisations/models/IMediaEventContent";
import { IDestroyable } from "./IDestroyable";
// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192
export class MediaEventHelper implements IDestroyable {
public readonly sourceUrl: LazyValue<string>;
public readonly thumbnailUrl: LazyValue<string>;
public readonly sourceBlob: LazyValue<Blob>;
public readonly thumbnailBlob: LazyValue<Blob>;
public readonly media: Media;
public constructor(private event: MatrixEvent) {
this.sourceUrl = new LazyValue(this.prepareSourceUrl);
this.thumbnailUrl = new LazyValue(this.prepareThumbnailUrl);
this.sourceBlob = new LazyValue(this.fetchSource);
this.thumbnailBlob = new LazyValue(this.fetchThumbnail);
this.media = mediaFromContent(this.event.getContent());
}
public destroy() {
if (this.media.isEncrypted) {
if (this.sourceUrl.present) URL.revokeObjectURL(this.sourceUrl.cachedValue);
if (this.thumbnailUrl.present) URL.revokeObjectURL(this.thumbnailUrl.cachedValue);
}
}
private prepareSourceUrl = async () => {
if (this.media.isEncrypted) {
const blob = await this.sourceBlob.value;
return URL.createObjectURL(blob);
} else {
return this.media.srcHttp;
}
};
private prepareThumbnailUrl = async () => {
if (this.media.isEncrypted) {
const blob = await this.thumbnailBlob.value;
return URL.createObjectURL(blob);
} else {
return this.media.thumbnailHttp;
}
};
private fetchSource = () => {
if (this.media.isEncrypted) {
return decryptFile(this.event.getContent<IMediaEventContent>().file);
}
return this.media.downloadSource().then(r => r.blob());
};
private fetchThumbnail = () => {
if (!this.media.hasThumbnail) return Promise.resolve(null);
if (this.media.isEncrypted) {
const content = this.event.getContent<IMediaEventContent>();
if (content.info?.thumbnail_file) {
return decryptFile(content.info.thumbnail_file);
} else {
// "Should never happen"
console.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found");
return Promise.resolve(null);
}
}
return fetch(this.media.thumbnailHttp).then(r => r.blob());
};
}