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() {
|
get _avatarSource() {
|
||||||
return this._roomBeingCreated;
|
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;
|
return this._roomsBeingCreated;
|
||||||
}
|
}
|
||||||
|
|
||||||
createRoom({type, isEncrypted, explicitName, topic, invites, loadProfiles = true}, log = undefined) {
|
createRoom(options, log = undefined) {
|
||||||
let roomBeingCreated;
|
let roomBeingCreated;
|
||||||
this._platform.logger.runDetached("create room", async log => {
|
this._platform.logger.runDetached("create room", async log => {
|
||||||
const localId = `local-${Math.floor(this._platform.random() * Number.MAX_SAFE_INTEGER)}`;
|
const localId = `local-${Math.floor(this._platform.random() * Number.MAX_SAFE_INTEGER)}`;
|
||||||
roomBeingCreated = new RoomBeingCreated(localId, type, isEncrypted,
|
roomBeingCreated = new RoomBeingCreated(
|
||||||
explicitName, topic, invites, this._roomsBeingCreatedUpdateCallback,
|
localId, options, this._roomsBeingCreatedUpdateCallback,
|
||||||
this._mediaRepository, log);
|
this._mediaRepository, this._platform, log);
|
||||||
this._roomsBeingCreated.set(localId, roomBeingCreated);
|
this._roomsBeingCreated.set(localId, roomBeingCreated);
|
||||||
const promises = [roomBeingCreated.create(this._hsApi, log)];
|
const promises = [roomBeingCreated.create(this._hsApi, log)];
|
||||||
|
const loadProfiles = !(options.loadProfiles === false); // default to true
|
||||||
if (loadProfiles) {
|
if (loadProfiles) {
|
||||||
promises.push(roomBeingCreated.loadProfiles(this._hsApi, log));
|
promises.push(roomBeingCreated.loadProfiles(this._hsApi, log));
|
||||||
}
|
}
|
||||||
|
@ -715,6 +716,7 @@ export class Session {
|
||||||
.set("roomId", roomBeingCreated.roomId);
|
.set("roomId", roomBeingCreated.roomId);
|
||||||
observableStatus.set(observableStatus.get() | RoomStatus.Replaced);
|
observableStatus.set(observableStatus.get() | RoomStatus.Replaced);
|
||||||
}
|
}
|
||||||
|
roomBeingCreated.dispose();
|
||||||
this._roomsBeingCreated.remove(roomBeingCreated.localId);
|
this._roomsBeingCreated.remove(roomBeingCreated.localId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,17 +40,15 @@ export class AttachmentUpload {
|
||||||
return this._sentBytes;
|
return this._sentBytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
|
||||||
abort() {
|
abort() {
|
||||||
this._uploadRequest?.abort();
|
this._uploadRequest?.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @public */
|
|
||||||
get localPreview() {
|
get localPreview() {
|
||||||
return this._unencryptedBlob;
|
return this._unencryptedBlob;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @package */
|
/** @internal */
|
||||||
async encrypt() {
|
async encrypt() {
|
||||||
if (this._encryptionInfo) {
|
if (this._encryptionInfo) {
|
||||||
throw new Error("already encrypted");
|
throw new Error("already encrypted");
|
||||||
|
@ -60,7 +58,7 @@ export class AttachmentUpload {
|
||||||
this._encryptionInfo = info;
|
this._encryptionInfo = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @package */
|
/** @internal */
|
||||||
async upload(hsApi, progressCallback, log) {
|
async upload(hsApi, progressCallback, log) {
|
||||||
this._uploadRequest = hsApi.uploadAttachment(this._transferredBlob, this._filename, {
|
this._uploadRequest = hsApi.uploadAttachment(this._transferredBlob, this._filename, {
|
||||||
uploadProgress: sentBytes => {
|
uploadProgress: sentBytes => {
|
||||||
|
@ -73,7 +71,7 @@ export class AttachmentUpload {
|
||||||
this._mxcUrl = content_uri;
|
this._mxcUrl = content_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @package */
|
/** @internal */
|
||||||
applyToContent(urlPath, content) {
|
applyToContent(urlPath, content) {
|
||||||
if (!this._mxcUrl) {
|
if (!this._mxcUrl) {
|
||||||
throw new Error("upload has not finished");
|
throw new Error("upload has not finished");
|
||||||
|
|
|
@ -18,10 +18,12 @@ import {calculateRoomName} from "./members/Heroes";
|
||||||
import {createRoomEncryptionEvent} from "../e2ee/common";
|
import {createRoomEncryptionEvent} from "../e2ee/common";
|
||||||
import {MediaRepository} from "../net/MediaRepository";
|
import {MediaRepository} from "../net/MediaRepository";
|
||||||
import {EventEmitter} from "../../utils/EventEmitter";
|
import {EventEmitter} from "../../utils/EventEmitter";
|
||||||
|
import {AttachmentUpload} from "./AttachmentUpload";
|
||||||
|
|
||||||
import type {StateEvent} from "../storage/types";
|
|
||||||
import type {HomeServerApi} from "../net/HomeServerApi";
|
import type {HomeServerApi} from "../net/HomeServerApi";
|
||||||
import type {ILogItem} from "../../logging/types";
|
import type {ILogItem} from "../../logging/types";
|
||||||
|
import type {Platform} from "../../platform/web/Platform";
|
||||||
|
import type {IBlobHandle} from "../../platform/types/types";
|
||||||
|
|
||||||
type CreateRoomPayload = {
|
type CreateRoomPayload = {
|
||||||
is_direct?: boolean;
|
is_direct?: boolean;
|
||||||
|
@ -29,7 +31,31 @@ type CreateRoomPayload = {
|
||||||
name?: string;
|
name?: string;
|
||||||
topic?: string;
|
topic?: string;
|
||||||
invite?: 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 {
|
export enum RoomType {
|
||||||
|
@ -64,54 +90,72 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> {
|
||||||
private profiles: Profile[] = [];
|
private profiles: Profile[] = [];
|
||||||
|
|
||||||
public readonly isEncrypted: boolean;
|
public readonly isEncrypted: boolean;
|
||||||
private _name: string;
|
private _calculatedName: string;
|
||||||
private _error?: Error;
|
private _error?: Error;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly localId: string,
|
public readonly localId: string,
|
||||||
private readonly type: RoomType,
|
private readonly options: Options,
|
||||||
isEncrypted: boolean | undefined,
|
|
||||||
private readonly explicitName: string | undefined,
|
|
||||||
private readonly topic: string | undefined,
|
|
||||||
private readonly inviteUserIds: string[] | undefined,
|
|
||||||
private readonly updateCallback: (self: RoomBeingCreated, params: string | undefined) => void,
|
private readonly updateCallback: (self: RoomBeingCreated, params: string | undefined) => void,
|
||||||
public readonly mediaRepository: MediaRepository,
|
public readonly mediaRepository: MediaRepository,
|
||||||
|
public readonly platform: Platform,
|
||||||
log: ILogItem
|
log: ILogItem
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this.isEncrypted = isEncrypted === undefined ? defaultE2EEStatusForType(this.type) : isEncrypted;
|
this.isEncrypted = options.isEncrypted === undefined ? defaultE2EEStatusForType(options.type) : options.isEncrypted;
|
||||||
if (explicitName) {
|
if (options.name) {
|
||||||
this._name = explicitName;
|
this._calculatedName = options.name;
|
||||||
} else {
|
} else {
|
||||||
const summaryData = {
|
const summaryData = {
|
||||||
joinCount: 1, // ourselves
|
joinCount: 1, // ourselves
|
||||||
inviteCount: (this.inviteUserIds?.length || 0)
|
inviteCount: (options.invites?.length || 0)
|
||||||
};
|
};
|
||||||
const userIdProfiles = (inviteUserIds || []).map(userId => new UserIdProfile(userId));
|
const userIdProfiles = (options.invites || []).map(userId => new UserIdProfile(userId));
|
||||||
this._name = calculateRoomName(userIdProfiles, summaryData, log);
|
this._calculatedName = calculateRoomName(userIdProfiles, summaryData, log);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async create(hsApi: HomeServerApi, log: ILogItem): Promise<void> {
|
async create(hsApi: HomeServerApi, log: ILogItem): Promise<void> {
|
||||||
const options: CreateRoomPayload = {
|
let avatarEventContent;
|
||||||
is_direct: this.type === RoomType.DirectMessage,
|
if (this.options.avatar) {
|
||||||
preset: presetForType(this.type)
|
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) {
|
attachment.applyToContent("url", avatarEventContent);
|
||||||
options.name = this.explicitName;
|
|
||||||
}
|
}
|
||||||
if (this.topic) {
|
const createOptions: CreateRoomPayload = {
|
||||||
options.topic = this.topic;
|
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) {
|
if (this.options.topic) {
|
||||||
options.invite = this.inviteUserIds;
|
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) {
|
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 {
|
try {
|
||||||
const response = await hsApi.createRoom(options, {log}).response();
|
const response = await hsApi.createRoom(createOptions, {log}).response();
|
||||||
this._roomId = response["room_id"];
|
this._roomId = response["room_id"];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._error = err;
|
this._error = err;
|
||||||
|
@ -127,13 +171,13 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> {
|
||||||
/** @internal */
|
/** @internal */
|
||||||
async loadProfiles(hsApi: HomeServerApi, log: ILogItem): Promise<void> {
|
async loadProfiles(hsApi: HomeServerApi, log: ILogItem): Promise<void> {
|
||||||
// only load profiles if we need it for the room name and avatar
|
// only load profiles if we need it for the room name and avatar
|
||||||
if (!this.explicitName && this.inviteUserIds) {
|
if (!this.options.name && this.options.invites) {
|
||||||
this.profiles = await loadProfiles(this.inviteUserIds, hsApi, log);
|
this.profiles = await loadProfiles(this.options.invites, hsApi, log);
|
||||||
const summaryData = {
|
const summaryData = {
|
||||||
joinCount: 1, // ourselves
|
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();
|
this.emitChange();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,16 +187,23 @@ export class RoomBeingCreated extends EventEmitter<{change: never}> {
|
||||||
this.emit("change");
|
this.emit("change");
|
||||||
}
|
}
|
||||||
|
|
||||||
get avatarColorId(): string { return this.inviteUserIds?.[0] ?? this._roomId ?? this.localId; }
|
get avatarColorId(): string { return this.options.invites?.[0] ?? this._roomId ?? this.localId; }
|
||||||
get avatarUrl(): string | undefined { return this.profiles[0]?.avatarUrl; }
|
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 roomId(): string | undefined { return this._roomId; }
|
||||||
get name() { return this._name; }
|
get name() { return this._calculatedName; }
|
||||||
get isBeingCreated(): boolean { return true; }
|
get isBeingCreated(): boolean { return true; }
|
||||||
get error(): Error | undefined { return this._error; }
|
get error(): Error | undefined { return this._error; }
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
// TODO: remove from collection somehow
|
// 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[]> {
|
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 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