add avatar support to creating room
This commit is contained in:
parent
afe8e17a6f
commit
83d2b58bad
5 changed files with 111 additions and 41 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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[]> {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Reference in a new issue