WIP
This commit is contained in:
parent
348de312f9
commit
bc09ede09f
20 changed files with 360 additions and 128 deletions
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {ObservableValue} from "../../observable/ObservableValue";
|
||||
import {RoomStatus} from "../../matrix/room/RoomStatus";
|
||||
|
||||
/**
|
||||
Depending on the status of a room (invited, joined, archived, or none),
|
||||
|
@ -34,11 +35,11 @@ the now transferred child view model.
|
|||
This is also why there is an explicit initialize method, see comment there.
|
||||
*/
|
||||
export class RoomViewModelObservable extends ObservableValue {
|
||||
constructor(sessionViewModel, roomId) {
|
||||
constructor(sessionViewModel, roomIdOrLocalId) {
|
||||
super(null);
|
||||
this._statusSubscription = null;
|
||||
this._sessionViewModel = sessionViewModel;
|
||||
this.id = roomId;
|
||||
this.id = roomIdOrLocalId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,11 +60,24 @@ export class RoomViewModelObservable extends ObservableValue {
|
|||
}
|
||||
|
||||
async _statusToViewModel(status) {
|
||||
if (status.invited) {
|
||||
console.log("RoomViewModelObservable received status", status);
|
||||
if (status & RoomStatus.Replaced) {
|
||||
console.log("replaced!");
|
||||
if (status & RoomStatus.BeingCreated) {
|
||||
const {session} = this._sessionViewModel._client;
|
||||
const roomBeingCreated = session.roomsBeingCreated.get(this.id);
|
||||
console.log("new id is", roomBeingCreated.roomId);
|
||||
this._sessionViewModel.navigation.push("room", roomBeingCreated.roomId);
|
||||
} else {
|
||||
throw new Error("Don't know how to replace a room with this status: " + (status ^ RoomStatus.Replaced));
|
||||
}
|
||||
} else if (status & RoomStatus.BeingCreated) {
|
||||
return this._sessionViewModel._createRoomBeingCreatedViewModel(this.id);
|
||||
} else if (status & RoomStatus.Invited) {
|
||||
return this._sessionViewModel._createInviteViewModel(this.id);
|
||||
} else if (status.joined) {
|
||||
} else if (status & RoomStatus.Joined) {
|
||||
return this._sessionViewModel._createRoomViewModel(this.id);
|
||||
} else if (status.archived) {
|
||||
} else if (status & RoomStatus.Archived) {
|
||||
return await this._sessionViewModel._createArchivedRoomViewModel(this.id);
|
||||
} else {
|
||||
return this._sessionViewModel._createUnknownRoomViewModel(this.id);
|
||||
|
|
|
@ -19,6 +19,7 @@ import {LeftPanelViewModel} from "./leftpanel/LeftPanelViewModel.js";
|
|||
import {RoomViewModel} from "./room/RoomViewModel.js";
|
||||
import {UnknownRoomViewModel} from "./room/UnknownRoomViewModel.js";
|
||||
import {InviteViewModel} from "./room/InviteViewModel.js";
|
||||
import {RoomBeingCreatedViewModel} from "./room/RoomBeingCreatedViewModel.js";
|
||||
import {LightboxViewModel} from "./room/LightboxViewModel.js";
|
||||
import {SessionStatusViewModel} from "./SessionStatusViewModel.js";
|
||||
import {RoomGridViewModel} from "./RoomGridViewModel.js";
|
||||
|
@ -37,10 +38,7 @@ export class SessionViewModel extends ViewModel {
|
|||
reconnector: client.reconnector,
|
||||
session: client.session,
|
||||
})));
|
||||
this._leftPanelViewModel = this.track(new LeftPanelViewModel(this.childOptions({
|
||||
invites: this._client.session.invites,
|
||||
rooms: this._client.session.rooms
|
||||
})));
|
||||
this._leftPanelViewModel = this.track(new LeftPanelViewModel(this.childOptions({session: this._client.session})));
|
||||
this._settingsViewModel = null;
|
||||
this._roomViewModelObservable = null;
|
||||
this._gridViewModel = null;
|
||||
|
@ -200,6 +198,17 @@ export class SessionViewModel extends ViewModel {
|
|||
return null;
|
||||
}
|
||||
|
||||
_createRoomBeingCreatedViewModel(localId) {
|
||||
const roomBeingCreated = this._client.session.roomsBeingCreated.get(localId);
|
||||
if (roomBeingCreated) {
|
||||
return new RoomBeingCreatedViewModel(this.childOptions({
|
||||
roomBeingCreated,
|
||||
mediaRepository: this._client.session.mediaRepository,
|
||||
}));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
_updateRoom(roomId) {
|
||||
// opening a room and already open?
|
||||
if (this._roomViewModelObservable?.id === roomId) {
|
||||
|
@ -263,7 +272,7 @@ export class SessionViewModel extends ViewModel {
|
|||
const enable = !!this.navigation.path.get("right-panel")?.value;
|
||||
if (enable) {
|
||||
const room = this._roomFromNavigation();
|
||||
this._rightPanelViewModel = this.track(new RightPanelViewModel(this.childOptions({room})));
|
||||
this._rightPanelViewModel = this.track(new RightPanelViewModel(this.childOptions({room, session: this._client.session})));
|
||||
}
|
||||
this.emitChange("rightPanelViewModel");
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
import {ViewModel} from "../../ViewModel.js";
|
||||
import {RoomTileViewModel} from "./RoomTileViewModel.js";
|
||||
import {InviteTileViewModel} from "./InviteTileViewModel.js";
|
||||
import {RoomBeingCreatedTileViewModel} from "./RoomBeingCreatedTileViewModel.js";
|
||||
import {RoomFilter} from "./RoomFilter.js";
|
||||
import {ApplyMap} from "../../../observable/map/ApplyMap.js";
|
||||
import {addPanelIfNeeded} from "../../navigation/index.js";
|
||||
|
@ -25,8 +26,8 @@ import {addPanelIfNeeded} from "../../navigation/index.js";
|
|||
export class LeftPanelViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {rooms, invites} = options;
|
||||
this._tileViewModelsMap = this._mapTileViewModels(rooms, invites);
|
||||
const {session} = options;
|
||||
this._tileViewModelsMap = this._mapTileViewModels(session.roomsBeingCreated, session.invites, session.rooms);
|
||||
this._tileViewModelsFilterMap = new ApplyMap(this._tileViewModelsMap);
|
||||
this._tileViewModels = this._tileViewModelsFilterMap.sortValues((a, b) => a.compare(b));
|
||||
this._currentTileVM = null;
|
||||
|
@ -35,16 +36,18 @@ export class LeftPanelViewModel extends ViewModel {
|
|||
this._settingsUrl = this.urlCreator.urlForSegment("settings");
|
||||
}
|
||||
|
||||
_mapTileViewModels(rooms, invites) {
|
||||
_mapTileViewModels(roomsBeingCreated, invites, rooms) {
|
||||
// join is not commutative, invites will take precedence over rooms
|
||||
return invites.join(rooms).mapValues((roomOrInvite, emitChange) => {
|
||||
return roomsBeingCreated.join(invites).join(rooms).mapValues((item, emitChange) => {
|
||||
let vm;
|
||||
if (roomOrInvite.isInvite) {
|
||||
vm = new InviteTileViewModel(this.childOptions({invite: roomOrInvite, emitChange}));
|
||||
if (item.isBeingCreated) {
|
||||
vm = new RoomBeingCreatedTileViewModel(this.childOptions({roomBeingCreated: item, emitChange}));
|
||||
} else if (item.isInvite) {
|
||||
vm = new InviteTileViewModel(this.childOptions({invite: item, emitChange}));
|
||||
} else {
|
||||
vm = new RoomTileViewModel(this.childOptions({room: roomOrInvite, emitChange}));
|
||||
vm = new RoomTileViewModel(this.childOptions({room: item, emitChange}));
|
||||
}
|
||||
const isOpen = this.navigation.path.get("room")?.value === roomOrInvite.id;
|
||||
const isOpen = this.navigation.path.get("room")?.value === item.id;
|
||||
if (isOpen) {
|
||||
vm.open();
|
||||
this._updateCurrentVM(vm);
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
Copyright 2020, 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 {BaseTileViewModel} from "./BaseTileViewModel.js";
|
||||
|
||||
export class RoomBeingCreatedTileViewModel extends BaseTileViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {roomBeingCreated} = options;
|
||||
this._roomBeingCreated = roomBeingCreated;
|
||||
this._url = this.urlCreator.openRoomActionUrl(this._roomBeingCreated.localId);
|
||||
}
|
||||
|
||||
get busy() { return true; }
|
||||
|
||||
get kind() {
|
||||
return "roomBeingCreated";
|
||||
}
|
||||
|
||||
get url() {
|
||||
return this._url;
|
||||
}
|
||||
|
||||
compare(other) {
|
||||
const parentComparison = super.compare(other);
|
||||
if (parentComparison !== 0) {
|
||||
return parentComparison;
|
||||
}
|
||||
return other._roomBeingCreated.name.localeCompare(this._roomBeingCreated.name);
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this._roomBeingCreated.name;
|
||||
}
|
||||
|
||||
get _avatarSource() {
|
||||
return this._roomBeingCreated;
|
||||
}
|
||||
}
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {ViewModel} from "../../ViewModel.js";
|
||||
import {RoomType} from "../../../matrix/room/create";
|
||||
import {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
|
||||
|
||||
export class MemberDetailsViewModel extends ViewModel {
|
||||
|
@ -25,6 +26,7 @@ export class MemberDetailsViewModel extends ViewModel {
|
|||
this._member = this._observableMember.get();
|
||||
this._isEncrypted = options.isEncrypted;
|
||||
this._powerLevelsObservable = options.powerLevelsObservable;
|
||||
this._session = options.session;
|
||||
this.track(this._powerLevelsObservable.subscribe(() => this._onPowerLevelsChange()));
|
||||
this.track(this._observableMember.subscribe( () => this._onMemberChange()));
|
||||
}
|
||||
|
@ -79,4 +81,9 @@ export class MemberDetailsViewModel extends ViewModel {
|
|||
get linkToUser() {
|
||||
return `https://matrix.to/#/${this._member.userId}`;
|
||||
}
|
||||
|
||||
async openDirectMessage() {
|
||||
const localId = await this._session.createRoom(RoomType.DirectMessage, undefined, undefined, undefined, [this.userId]);
|
||||
this.navigation.push("room", localId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export class RightPanelViewModel extends ViewModel {
|
|||
constructor(options) {
|
||||
super(options);
|
||||
this._room = options.room;
|
||||
this._session = options.session;
|
||||
this._members = null;
|
||||
this._setupNavigation();
|
||||
}
|
||||
|
@ -48,7 +49,13 @@ export class RightPanelViewModel extends ViewModel {
|
|||
}
|
||||
const isEncrypted = this._room.isEncrypted;
|
||||
const powerLevelsObservable = await this._room.observePowerLevels();
|
||||
return {observableMember, isEncrypted, powerLevelsObservable, mediaRepository: this._room.mediaRepository};
|
||||
return {
|
||||
observableMember,
|
||||
isEncrypted,
|
||||
powerLevelsObservable,
|
||||
mediaRepository: this._room.mediaRepository,
|
||||
session: this._session
|
||||
};
|
||||
}
|
||||
|
||||
_setupNavigation() {
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
# "Room" view models
|
||||
|
||||
InviteViewModel and RoomViewModel are interchangebly used as "room view model":
|
||||
- SessionViewModel.roomViewModel can be an instance of either
|
||||
- RoomGridViewModel.roomViewModelAt(i) can return an instance of either
|
||||
InviteViewModel, RoomViewModel and RoomBeingCreatedViewModel are interchangebly used as "room view model":
|
||||
- SessionViewModel.roomViewModel can be an instance of any
|
||||
- RoomGridViewModel.roomViewModelAt(i) can return an instance of any
|
||||
|
||||
This is because they are accessed by the same url and need to transition into each other, in these two locations. Having two methods, especially in RoomGridViewModel would have been more cumbersome, even though this is not in line with how different view models are exposed in SessionViewModel.
|
||||
|
||||
They share an `id` and `kind` property, the latter can be used to differentiate them from the view, and a `focus` method.
|
||||
Once we convert this folder to typescript, we should use this interface for all the view models:
|
||||
```ts
|
||||
interface IGridItemViewModel {
|
||||
id: string;
|
||||
kind: string;
|
||||
focus();
|
||||
}
|
||||
```
|
||||
|
|
65
src/domain/session/room/RoomBeingCreatedViewModel.js
Normal file
65
src/domain/session/room/RoomBeingCreatedViewModel.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
Copyright 2020, 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 {avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar.js";
|
||||
import {ViewModel} from "../../ViewModel.js";
|
||||
|
||||
export class RoomBeingCreatedViewModel extends ViewModel {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
const {roomBeingCreated, mediaRepository} = options;
|
||||
this._roomBeingCreated = roomBeingCreated;
|
||||
this._mediaRepository = mediaRepository;
|
||||
this._onRoomChange = this._onRoomChange.bind(this);
|
||||
this._closeUrl = this.urlCreator.urlUntilSegment("session");
|
||||
this._roomBeingCreated.on("change", this._onRoomChange);
|
||||
}
|
||||
|
||||
get kind() { return "roomBeingCreated"; }
|
||||
get closeUrl() { return this._closeUrl; }
|
||||
get name() { return this._roomBeingCreated.name; }
|
||||
get id() { return this._roomBeingCreated.localId; }
|
||||
get isEncrypted() { return this._roomBeingCreated.isEncrypted; }
|
||||
|
||||
get avatarLetter() {
|
||||
return avatarInitials(this.name);
|
||||
}
|
||||
|
||||
get avatarColorNumber() {
|
||||
return getIdentifierColorNumber(this._roomBeingCreated.avatarColorId);
|
||||
}
|
||||
|
||||
avatarUrl(size) {
|
||||
return getAvatarHttpUrl(this._roomBeingCreated.avatarUrl, size, this.platform, this._mediaRepository);
|
||||
}
|
||||
|
||||
get avatarTitle() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
focus() {}
|
||||
|
||||
_onRoomChange() {
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
super.dispose();
|
||||
this._roomBeingCreated.off("change", this._onRoomChange);
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,7 @@ limitations under the License.
|
|||
|
||||
import {Room} from "./room/Room.js";
|
||||
import {ArchivedRoom} from "./room/ArchivedRoom.js";
|
||||
import {RoomStatus} from "./room/RoomStatus.js";
|
||||
import {RoomStatus} from "./room/RoomStatus";
|
||||
import {RoomBeingCreated} from "./room/create";
|
||||
import {Invite} from "./room/Invite.js";
|
||||
import {Pusher} from "./push/Pusher";
|
||||
|
@ -64,6 +64,12 @@ export class Session {
|
|||
this._activeArchivedRooms = new Map();
|
||||
this._invites = new ObservableMap();
|
||||
this._inviteUpdateCallback = (invite, params) => this._invites.update(invite.id, params);
|
||||
this._roomsBeingCreatedUpdateCallback = (rbc, params) => {
|
||||
this._roomsBeingCreated.update(rbc.localId, params);
|
||||
if (rbc.roomId && !!this.rooms.get(rbc.roomId)) {
|
||||
this._tryReplaceRoomBeingCreated(rbc.roomId);
|
||||
}
|
||||
};
|
||||
this._roomsBeingCreated = new ObservableMap();
|
||||
this._user = new User(sessionInfo.userId);
|
||||
this._deviceMessageHandler = new DeviceMessageHandler({storage});
|
||||
|
@ -586,14 +592,16 @@ export class Session {
|
|||
return this._roomsBeingCreated;
|
||||
}
|
||||
|
||||
createRoom(type, isEncrypted, explicitName, topic, invites) {
|
||||
const localId = `room-being-created-${this.platform.random()}`;
|
||||
const roomBeingCreated = new RoomBeingCreated(localId, type, isEncrypted, explicitName, topic, invites);
|
||||
this._roomsBeingCreated.set(localId, roomBeingCreated);
|
||||
this._platform.logger.runDetached("create room", async log => {
|
||||
roomBeingCreated.start(this._hsApi, log);
|
||||
createRoom(type, isEncrypted, explicitName, topic, invites, log = undefined) {
|
||||
return this._platform.logger.wrapOrRun(log, "create room", log => {
|
||||
const localId = `room-being-created-${this._platform.random()}`;
|
||||
const roomBeingCreated = new RoomBeingCreated(localId, type, isEncrypted, explicitName, topic, invites, this._roomsBeingCreatedUpdateCallback, this._mediaRepository, log);
|
||||
this._roomsBeingCreated.set(localId, roomBeingCreated);
|
||||
log.wrapDetached("create room network", async log => {
|
||||
roomBeingCreated.start(this._hsApi, log);
|
||||
});
|
||||
return localId;
|
||||
});
|
||||
return localId;
|
||||
}
|
||||
|
||||
async obtainSyncLock(syncResponse) {
|
||||
|
@ -678,18 +686,29 @@ export class Session {
|
|||
}
|
||||
}
|
||||
|
||||
_tryReplaceRoomBeingCreated(roomId) {
|
||||
console.trace("_tryReplaceRoomBeingCreated " + roomId);
|
||||
for (const [,roomBeingCreated] of this._roomsBeingCreated) {
|
||||
if (roomBeingCreated.roomId === roomId) {
|
||||
const observableStatus = this._observedRoomStatus.get(roomBeingCreated.localId);
|
||||
if (observableStatus) {
|
||||
console.log("marking room as replaced", observableStatus.get());
|
||||
observableStatus.set(observableStatus.get() | RoomStatus.Replaced);
|
||||
} else {
|
||||
console.log("no observableStatus");
|
||||
}
|
||||
this._roomsBeingCreated.remove(roomBeingCreated.localId);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
applyRoomCollectionChangesAfterSync(inviteStates, roomStates, archivedRoomStates) {
|
||||
// update the collections after sync
|
||||
for (const rs of roomStates) {
|
||||
if (rs.shouldAdd) {
|
||||
this._rooms.add(rs.id, rs.room);
|
||||
for (const roomBeingCreated of this._roomsBeingCreated) {
|
||||
if (roomBeingCreated.roomId === rs.id) {
|
||||
roomBeingCreated.notifyJoinedRoom();
|
||||
this._roomsBeingCreated.delete(roomBeingCreated.localId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._tryReplaceRoomBeingCreated(rs.id);
|
||||
} else if (rs.shouldRemove) {
|
||||
this._rooms.remove(rs.id);
|
||||
}
|
||||
|
@ -707,12 +726,12 @@ export class Session {
|
|||
if (this._observedRoomStatus.size !== 0) {
|
||||
for (const ars of archivedRoomStates) {
|
||||
if (ars.shouldAdd) {
|
||||
this._observedRoomStatus.get(ars.id)?.set(RoomStatus.archived);
|
||||
this._observedRoomStatus.get(ars.id)?.set(RoomStatus.Archived);
|
||||
}
|
||||
}
|
||||
for (const rs of roomStates) {
|
||||
if (rs.shouldAdd) {
|
||||
this._observedRoomStatus.get(rs.id)?.set(RoomStatus.joined);
|
||||
this._observedRoomStatus.get(rs.id)?.set(RoomStatus.Joined);
|
||||
}
|
||||
}
|
||||
for (const is of inviteStates) {
|
||||
|
@ -731,7 +750,7 @@ export class Session {
|
|||
_forgetArchivedRoom(roomId) {
|
||||
const statusObservable = this._observedRoomStatus.get(roomId);
|
||||
if (statusObservable) {
|
||||
statusObservable.set(statusObservable.get().withoutArchived());
|
||||
statusObservable.set(statusObservable.get() ^ RoomStatus.Archived);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -820,21 +839,25 @@ export class Session {
|
|||
}
|
||||
|
||||
async getRoomStatus(roomId) {
|
||||
const isBeingCreated = !!this._roomsBeingCreated.get(roomId);
|
||||
if (isBeingCreated) {
|
||||
return RoomStatus.BeingCreated;
|
||||
}
|
||||
const isJoined = !!this._rooms.get(roomId);
|
||||
if (isJoined) {
|
||||
return RoomStatus.joined;
|
||||
return RoomStatus.Joined;
|
||||
} else {
|
||||
const isInvited = !!this._invites.get(roomId);
|
||||
const txn = await this._storage.readTxn([this._storage.storeNames.archivedRoomSummary]);
|
||||
const isArchived = await txn.archivedRoomSummary.has(roomId);
|
||||
if (isInvited && isArchived) {
|
||||
return RoomStatus.invitedAndArchived;
|
||||
return RoomStatus.Invited | RoomStatus.Archived;
|
||||
} else if (isInvited) {
|
||||
return RoomStatus.invited;
|
||||
return RoomStatus.Invited;
|
||||
} else if (isArchived) {
|
||||
return RoomStatus.archived;
|
||||
return RoomStatus.Archived;
|
||||
} else {
|
||||
return RoomStatus.none;
|
||||
return RoomStatus.None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -843,9 +866,10 @@ export class Session {
|
|||
let observable = this._observedRoomStatus.get(roomId);
|
||||
if (!observable) {
|
||||
const status = await this.getRoomStatus(roomId);
|
||||
observable = new RetainedObservableValue(status, () => {
|
||||
observable = new FooRetainedObservableValue(status, () => {
|
||||
this._observedRoomStatus.delete(roomId);
|
||||
});
|
||||
|
||||
this._observedRoomStatus.set(roomId, observable);
|
||||
}
|
||||
return observable;
|
||||
|
@ -897,6 +921,13 @@ export class Session {
|
|||
}
|
||||
}
|
||||
|
||||
class FooRetainedObservableValue extends RetainedObservableValue {
|
||||
set(value) {
|
||||
console.log("setting room status to", value);
|
||||
super.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function tests() {
|
||||
function createStorageMock(session, pendingEvents = []) {
|
||||
return {
|
||||
|
|
|
@ -231,6 +231,7 @@ export class Room extends BaseRoom {
|
|||
}
|
||||
}
|
||||
let emitChange = false;
|
||||
console.log("Room summaryChanges", this.id, summaryChanges);
|
||||
if (summaryChanges) {
|
||||
this._summary.applyChanges(summaryChanges);
|
||||
if (!this._summary.data.needsHeroes) {
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
export class RoomStatus {
|
||||
constructor(joined, invited, archived) {
|
||||
this.joined = joined;
|
||||
this.invited = invited;
|
||||
this.archived = archived;
|
||||
}
|
||||
|
||||
withInvited() {
|
||||
if (this.invited) {
|
||||
return this;
|
||||
} else if (this.archived) {
|
||||
return RoomStatus.invitedAndArchived;
|
||||
} else {
|
||||
return RoomStatus.invited;
|
||||
}
|
||||
}
|
||||
|
||||
withoutInvited() {
|
||||
if (!this.invited) {
|
||||
return this;
|
||||
} else if (this.joined) {
|
||||
return RoomStatus.joined;
|
||||
} else if (this.archived) {
|
||||
return RoomStatus.archived;
|
||||
} else {
|
||||
return RoomStatus.none;
|
||||
}
|
||||
}
|
||||
|
||||
withoutArchived() {
|
||||
if (!this.archived) {
|
||||
return this;
|
||||
} else if (this.invited) {
|
||||
return RoomStatus.invited;
|
||||
} else {
|
||||
return RoomStatus.none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RoomStatus.joined = new RoomStatus(true, false, false);
|
||||
RoomStatus.archived = new RoomStatus(false, false, true);
|
||||
RoomStatus.invited = new RoomStatus(false, true, false);
|
||||
RoomStatus.invitedAndArchived = new RoomStatus(false, true, true);
|
||||
RoomStatus.none = new RoomStatus(false, false, false);
|
24
src/matrix/room/RoomStatus.ts
Normal file
24
src/matrix/room/RoomStatus.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
export enum RoomStatus {
|
||||
None = 1 << 0,
|
||||
BeingCreated = 1 << 1,
|
||||
Invited = 1 << 2,
|
||||
Joined = 1 << 3,
|
||||
Replaced = 1 << 4,
|
||||
Archived = 1 << 5,
|
||||
}
|
|
@ -16,6 +16,7 @@ limitations under the License.
|
|||
|
||||
import {calculateRoomName} from "./members/Heroes";
|
||||
import {createRoomEncryptionEvent} from "../e2ee/common";
|
||||
import {MediaRepository} from "../net/MediaRepository";
|
||||
import {EventEmitter} from "../../utils/EventEmitter";
|
||||
|
||||
import type {StateEvent} from "../storage/types";
|
||||
|
@ -58,32 +59,35 @@ function presetForType(type: RoomType): string {
|
|||
}
|
||||
}
|
||||
|
||||
export class RoomBeingCreated extends EventEmitter<{change: never, joined: string}> {
|
||||
export class RoomBeingCreated extends EventEmitter<{change: never}> {
|
||||
private _roomId?: string;
|
||||
private profiles: Profile[] = [];
|
||||
|
||||
public readonly isEncrypted: boolean;
|
||||
public readonly name: string;
|
||||
private _name: string;
|
||||
|
||||
constructor(
|
||||
private readonly localId: string,
|
||||
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 updateCallback,
|
||||
public readonly mediaRepository: MediaRepository,
|
||||
log: ILogItem
|
||||
) {
|
||||
super();
|
||||
this.isEncrypted = isEncrypted === undefined ? defaultE2EEStatusForType(this.type) : isEncrypted;
|
||||
if (explicitName) {
|
||||
this.name = explicitName;
|
||||
this._name = explicitName;
|
||||
} else {
|
||||
const summaryData = {
|
||||
joinCount: 1, // ourselves
|
||||
inviteCount: (this.inviteUserIds?.length || 0)
|
||||
};
|
||||
this.name = calculateRoomName(this.profiles, summaryData, log);
|
||||
const userIdProfiles = (inviteUserIds || []).map(userId => new UserIdProfile(userId));
|
||||
this._name = calculateRoomName(userIdProfiles, summaryData, log);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,31 +115,52 @@ export class RoomBeingCreated extends EventEmitter<{change: never, joined: strin
|
|||
if (this.isEncrypted) {
|
||||
options.initial_state = [createRoomEncryptionEvent()];
|
||||
}
|
||||
|
||||
console.log("going to create the room now");
|
||||
const response = await hsApi.createRoom(options, {log}).response();
|
||||
this._roomId = response["room_id"];
|
||||
this.emit("change");
|
||||
console.log("done creating the room now", this._roomId);
|
||||
// TODO: somehow check in Session if we need to replace this with a joined room
|
||||
// in case the room appears first in sync, and this request returns later
|
||||
this.emitChange();
|
||||
}
|
||||
|
||||
private 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);
|
||||
this.emit("change");
|
||||
console.log("loaded the profiles", this.profiles);
|
||||
const summaryData = {
|
||||
joinCount: 1, // ourselves
|
||||
inviteCount: this.inviteUserIds.length
|
||||
};
|
||||
this._name = calculateRoomName(this.profiles, summaryData, log);
|
||||
console.log("loaded the profiles and the new name", this.name);
|
||||
this.emitChange();
|
||||
}
|
||||
}
|
||||
|
||||
notifyJoinedRoom() {
|
||||
this.emit("joined", this._roomId);
|
||||
private emitChange() {
|
||||
this.updateCallback(this);
|
||||
this.emit("change");
|
||||
}
|
||||
|
||||
get avatarColorId(): string {
|
||||
return this.inviteUserIds?.[0] ?? this._roomId ?? this.localId;
|
||||
}
|
||||
|
||||
get avatarUrl(): string | undefined {
|
||||
return this.profiles[0]?.avatarUrl;
|
||||
const result = this.profiles[0]?.avatarUrl;
|
||||
console.log("RoomBeingCreated.avatarUrl", this.profiles, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
get roomId(): string | undefined {
|
||||
return this._roomId;
|
||||
}
|
||||
|
||||
get name() { return this._name; }
|
||||
|
||||
get isBeingCreated(): boolean { return true; }
|
||||
}
|
||||
|
||||
export async function loadProfiles(userIds: string[], hsApi: HomeServerApi, log: ILogItem): Promise<Profile[]> {
|
||||
|
@ -149,8 +174,8 @@ export async function loadProfiles(userIds: string[], hsApi: HomeServerApi, log:
|
|||
|
||||
interface IProfile {
|
||||
get userId(): string;
|
||||
get displayName(): string;
|
||||
get avatarUrl(): string;
|
||||
get displayName(): string | undefined;
|
||||
get avatarUrl(): string | undefined;
|
||||
get name(): string;
|
||||
}
|
||||
|
||||
|
@ -158,8 +183,16 @@ export class Profile implements IProfile {
|
|||
constructor(
|
||||
public readonly userId: string,
|
||||
public readonly displayName: string,
|
||||
public readonly avatarUrl: string
|
||||
public readonly avatarUrl: string | undefined
|
||||
) {}
|
||||
|
||||
get name() { return this.displayName || this.userId; }
|
||||
}
|
||||
|
||||
class UserIdProfile implements IProfile {
|
||||
constructor(public readonly userId: string) {}
|
||||
get displayName() { return undefined; }
|
||||
get name() { return this.userId; }
|
||||
get avatarUrl() { return undefined; }
|
||||
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {RoomView} from "./room/RoomView.js";
|
||||
import {RoomBeingCreatedView} from "./room/RoomBeingCreatedView.js";
|
||||
import {InviteView} from "./room/InviteView.js";
|
||||
import {TemplateView} from "../general/TemplateView";
|
||||
import {StaticView} from "../general/StaticView.js";
|
||||
|
@ -33,7 +34,9 @@ export class RoomGridView extends TemplateView {
|
|||
},
|
||||
}, t.mapView(vm => vm.roomViewModelAt(i), roomVM => {
|
||||
if (roomVM) {
|
||||
if (roomVM.kind === "invite") {
|
||||
if (roomVM.kind === "roomBeingCreated") {
|
||||
return new RoomBeingCreatedView(roomVM);
|
||||
} else if (roomVM.kind === "invite") {
|
||||
return new InviteView(roomVM);
|
||||
} else {
|
||||
return new RoomView(roomVM);
|
||||
|
|
|
@ -18,6 +18,7 @@ limitations under the License.
|
|||
import {LeftPanelView} from "./leftpanel/LeftPanelView.js";
|
||||
import {RoomView} from "./room/RoomView.js";
|
||||
import {UnknownRoomView} from "./room/UnknownRoomView.js";
|
||||
import {RoomBeingCreatedView} from "./room/RoomBeingCreatedView.js";
|
||||
import {InviteView} from "./room/InviteView.js";
|
||||
import {LightboxView} from "./room/LightboxView.js";
|
||||
import {TemplateView} from "../general/TemplateView";
|
||||
|
@ -48,6 +49,8 @@ export class SessionView extends TemplateView {
|
|||
return new InviteView(vm.currentRoomViewModel);
|
||||
} else if (vm.currentRoomViewModel.kind === "room") {
|
||||
return new RoomView(vm.currentRoomViewModel);
|
||||
} else if (vm.currentRoomViewModel.kind === "roomBeingCreated") {
|
||||
return new RoomBeingCreatedView(vm.currentRoomViewModel);
|
||||
} else {
|
||||
return new UnknownRoomView(vm.currentRoomViewModel);
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
import {TemplateView} from "../../general/TemplateView";
|
||||
import {renderStaticAvatar} from "../../avatar.js";
|
||||
import {AvatarView} from "../../AvatarView";
|
||||
import {spinner} from "../../common.js";
|
||||
|
||||
export class InviteTileView extends TemplateView {
|
||||
|
@ -27,9 +27,9 @@ export class InviteTileView extends TemplateView {
|
|||
};
|
||||
return t.li({"className": classes}, [
|
||||
t.a({href: vm.url}, [
|
||||
renderStaticAvatar(vm, 32),
|
||||
t.view(new AvatarView(vm, 32), {parentProvidesUpdates: true}),
|
||||
t.div({className: "description"}, [
|
||||
t.div({className: "name"}, vm.name),
|
||||
t.div({className: "name"}, vm => vm.name),
|
||||
t.map(vm => vm.busy, busy => {
|
||||
if (busy) {
|
||||
return spinner(t);
|
||||
|
@ -41,4 +41,10 @@ export class InviteTileView extends TemplateView {
|
|||
])
|
||||
]);
|
||||
}
|
||||
|
||||
update(value, props) {
|
||||
super.update(value);
|
||||
// update the AvatarView as we told it to not subscribe itself with parentProvidesUpdates
|
||||
this.updateSubViews(value, props);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ export class LeftPanelView extends TemplateView {
|
|||
list: vm.tileViewModels,
|
||||
},
|
||||
tileVM => {
|
||||
if (tileVM.kind === "invite") {
|
||||
if (tileVM.kind === "invite" || tileVM.kind === "roomBeingCreated") {
|
||||
return new InviteTileView(tileVM);
|
||||
} else {
|
||||
return new RoomTileView(tileVM);
|
||||
|
|
|
@ -46,7 +46,8 @@ export class MemberDetailsView extends TemplateView {
|
|||
t.div({className: "MemberDetailsView_label"}, vm.i18n`Options`),
|
||||
t.div({className: "MemberDetailsView_options"},
|
||||
[
|
||||
t.a({href: vm.linkToUser, target: "_blank", rel: "noopener"}, vm.i18n`Open Link to User`)
|
||||
t.a({href: vm.linkToUser, target: "_blank", rel: "noopener"}, vm.i18n`Open Link to User`),
|
||||
t.button({className: "text", onClick: () => vm.openDirectMessage()}, vm.i18n`Open direct message`)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ export class MessageComposer extends TemplateView {
|
|||
this._clearHeight();
|
||||
}
|
||||
},
|
||||
placeholder: vm.isEncrypted ? "Send an encrypted message…" : "Send a message…",
|
||||
placeholder: vm => vm.isEncrypted ? "Send an encrypted message…" : "Send a message…",
|
||||
rows: "1"
|
||||
});
|
||||
this._focusInput = () => this._input.focus();
|
||||
|
|
25
src/platform/web/ui/session/room/RoomBeingCreatedView.js
Normal file
25
src/platform/web/ui/session/room/RoomBeingCreatedView.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
||||
Copyright 2020 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 {TemplateView} from "../../general/TemplateView";
|
||||
import {renderStaticAvatar} from "../../avatar.js";
|
||||
|
||||
export class RoomBeingCreatedView extends TemplateView {
|
||||
render(t, vm) {
|
||||
return t.h1({className: "middle"}, ["creating room", vm => vm.name]);
|
||||
}
|
||||
}
|
Reference in a new issue