add avatar support to creating room

This commit is contained in:
Bruno Windels 2022-02-09 18:58:30 +01:00
parent afe8e17a6f
commit 83d2b58bad
5 changed files with 111 additions and 41 deletions

View file

@ -50,4 +50,9 @@ export class RoomBeingCreatedTileViewModel extends BaseTileViewModel {
get _avatarSource() {
return this._roomBeingCreated;
}
avatarUrl(size) {
// allow blob url which doesn't need mxc => http resolution
return this._roomBeingCreated.avatarBlobUrl ?? super.avatarUrl(size);
}
}

View file

@ -600,15 +600,16 @@ export class Session {
return this._roomsBeingCreated;
}
createRoom({type, isEncrypted, explicitName, topic, invites, loadProfiles = true}, log = undefined) {
createRoom(options, log = undefined) {
let roomBeingCreated;
this._platform.logger.runDetached("create room", async log => {
const localId = `local-${Math.floor(this._platform.random() * Number.MAX_SAFE_INTEGER)}`;
roomBeingCreated = new RoomBeingCreated(localId, type, isEncrypted,
explicitName, topic, invites, this._roomsBeingCreatedUpdateCallback,
this._mediaRepository, log);
roomBeingCreated = new RoomBeingCreated(
localId, options, this._roomsBeingCreatedUpdateCallback,
this._mediaRepository, this._platform, log);
this._roomsBeingCreated.set(localId, roomBeingCreated);
const promises = [roomBeingCreated.create(this._hsApi, log)];
const loadProfiles = !(options.loadProfiles === false); // default to true
if (loadProfiles) {
promises.push(roomBeingCreated.loadProfiles(this._hsApi, log));
}
@ -715,6 +716,7 @@ export class Session {
.set("roomId", roomBeingCreated.roomId);
observableStatus.set(observableStatus.get() | RoomStatus.Replaced);
}
roomBeingCreated.dispose();
this._roomsBeingCreated.remove(roomBeingCreated.localId);
return;
}

View file

@ -40,17 +40,15 @@ export class AttachmentUpload {
return this._sentBytes;
}
/** @public */
abort() {
this._uploadRequest?.abort();
}
/** @public */
get localPreview() {
return this._unencryptedBlob;
}
/** @package */
/** @internal */
async encrypt() {
if (this._encryptionInfo) {
throw new Error("already encrypted");
@ -60,7 +58,7 @@ export class AttachmentUpload {
this._encryptionInfo = info;
}
/** @package */
/** @internal */
async upload(hsApi, progressCallback, log) {
this._uploadRequest = hsApi.uploadAttachment(this._transferredBlob, this._filename, {
uploadProgress: sentBytes => {
@ -73,7 +71,7 @@ export class AttachmentUpload {
this._mxcUrl = content_uri;
}
/** @package */
/** @internal */
applyToContent(urlPath, content) {
if (!this._mxcUrl) {
throw new Error("upload has not finished");

View file

@ -18,10 +18,12 @@ import {calculateRoomName} from "./members/Heroes";
import {createRoomEncryptionEvent} from "../e2ee/common";
import {MediaRepository} from "../net/MediaRepository";
import {EventEmitter} from "../../utils/EventEmitter";
import {AttachmentUpload} from "./AttachmentUpload";
import type {StateEvent} from "../storage/types";
import type {HomeServerApi} from "../net/HomeServerApi";
import type {ILogItem} from "../../logging/types";
import type {Platform} from "../../platform/web/Platform";
import type {IBlobHandle} from "../../platform/types/types";
type CreateRoomPayload = {
is_direct?: boolean;
@ -29,7 +31,31 @@ type CreateRoomPayload = {
name?: string;
topic?: string;
invite?: string[];
initial_state?: StateEvent[]
room_alias_name?: string;
initial_state: {type: string; state_key: string; content: Record<string, any>}[]
}
type ImageInfo = {
w: number;
h: number;
mimetype: string;
size: number;
}
type Avatar = {
info: ImageInfo;
blob: IBlobHandle;
name: string;
}
type Options = {
type: RoomType;
isEncrypted?: boolean;
name?: string;
topic?: string;
invites?: string[];
avatar?: Avatar;
alias?: string;
}
export enum RoomType {
@ -64,54 +90,72 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> {
private profiles: Profile[] = [];
public readonly isEncrypted: boolean;
private _name: string;
private _calculatedName: string;
private _error?: Error;
constructor(
public readonly localId: string,
private readonly type: RoomType,
isEncrypted: boolean | undefined,
private readonly explicitName: string | undefined,
private readonly topic: string | undefined,
private readonly inviteUserIds: string[] | undefined,
private readonly options: Options,
private readonly updateCallback: (self: RoomBeingCreated, params: string | undefined) => void,
public readonly mediaRepository: MediaRepository,
public readonly platform: Platform,
log: ILogItem
) {
super();
this.isEncrypted = isEncrypted === undefined ? defaultE2EEStatusForType(this.type) : isEncrypted;
if (explicitName) {
this._name = explicitName;
this.isEncrypted = options.isEncrypted === undefined ? defaultE2EEStatusForType(options.type) : options.isEncrypted;
if (options.name) {
this._calculatedName = options.name;
} else {
const summaryData = {
joinCount: 1, // ourselves
inviteCount: (this.inviteUserIds?.length || 0)
inviteCount: (options.invites?.length || 0)
};
const userIdProfiles = (inviteUserIds || []).map(userId => new UserIdProfile(userId));
this._name = calculateRoomName(userIdProfiles, summaryData, log);
const userIdProfiles = (options.invites || []).map(userId => new UserIdProfile(userId));
this._calculatedName = calculateRoomName(userIdProfiles, summaryData, log);
}
}
/** @internal */
async create(hsApi: HomeServerApi, log: ILogItem): Promise<void> {
const options: CreateRoomPayload = {
is_direct: this.type === RoomType.DirectMessage,
preset: presetForType(this.type)
let avatarEventContent;
if (this.options.avatar) {
const {avatar} = this.options;
const attachment = new AttachmentUpload({filename: avatar.name, blob: avatar.blob, platform: this.platform});
await attachment.upload(hsApi, () => {}, log);
avatarEventContent = {
info: avatar.info
};
if (this.explicitName) {
options.name = this.explicitName;
attachment.applyToContent("url", avatarEventContent);
}
if (this.topic) {
options.topic = this.topic;
const createOptions: CreateRoomPayload = {
is_direct: this.options.type === RoomType.DirectMessage,
preset: presetForType(this.options.type),
initial_state: []
};
if (this.options.name) {
createOptions.name = this.options.name;
}
if (this.inviteUserIds) {
options.invite = this.inviteUserIds;
if (this.options.topic) {
createOptions.topic = this.options.topic;
}
if (this.options.invites) {
createOptions.invite = this.options.invites;
}
if (this.options.alias) {
createOptions.room_alias_name = this.options.alias;
}
if (this.isEncrypted) {
options.initial_state = [createRoomEncryptionEvent()];
createOptions.initial_state.push(createRoomEncryptionEvent());
}
if (avatarEventContent) {
createOptions.initial_state.push({
type: "m.room.avatar",
state_key: "",
content: avatarEventContent
});
}
try {
const response = await hsApi.createRoom(options, {log}).response();
const response = await hsApi.createRoom(createOptions, {log}).response();
this._roomId = response["room_id"];
} catch (err) {
this._error = err;
@ -127,13 +171,13 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> {
/** @internal */
async loadProfiles(hsApi: HomeServerApi, log: ILogItem): Promise<void> {
// only load profiles if we need it for the room name and avatar
if (!this.explicitName && this.inviteUserIds) {
this.profiles = await loadProfiles(this.inviteUserIds, hsApi, log);
if (!this.options.name && this.options.invites) {
this.profiles = await loadProfiles(this.options.invites, hsApi, log);
const summaryData = {
joinCount: 1, // ourselves
inviteCount: this.inviteUserIds.length
inviteCount: this.options.invites.length
};
this._name = calculateRoomName(this.profiles, summaryData, log);
this._calculatedName = calculateRoomName(this.profiles, summaryData, log);
this.emitChange();
}
}
@ -143,16 +187,23 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> {
this.emit("change");
}
get avatarColorId(): string { return this.inviteUserIds?.[0] ?? this._roomId ?? this.localId; }
get avatarUrl(): string | undefined { return this.profiles[0]?.avatarUrl; }
get avatarColorId(): string { return this.options.invites?.[0] ?? this._roomId ?? this.localId; }
get avatarUrl(): string | undefined { return this.profiles?.[0].avatarUrl; }
get avatarBlobUrl(): string | undefined { return this.options.avatar?.blob?.url; }
get roomId(): string | undefined { return this._roomId; }
get name() { return this._name; }
get name() { return this._calculatedName; }
get isBeingCreated(): boolean { return true; }
get error(): Error | undefined { return this._error; }
cancel() {
// TODO: remove from collection somehow
}
dispose() {
if (this.options.avatar) {
this.options.avatar.blob.dispose();
}
}
}
export async function loadProfiles(userIds: string[], hsApi: HomeServerApi, log: ILogItem): Promise<Profile[]> {

View file

@ -31,3 +31,17 @@ export interface IRequestOptions {
}
export type RequestFunction = (url: string, options: IRequestOptions) => RequestResult;
export interface IBlobHandle {
nativeBlob: any;
url: string;
size: number;
mimeType: string;
readAsBuffer(): BufferSource;
dispose()
}
export type File = {
readonly name: string;
readonly blob: IBlobHandle;
}