From 4be82cd4722db5ee2e5bfd5b675732adc5df288b Mon Sep 17 00:00:00 2001 From: Bruno Windels <274386+bwindels@users.noreply.github.com> Date: Thu, 17 Mar 2022 13:07:55 +0100 Subject: [PATCH] WIP on UI --- src/domain/session/room/CallViewModel.ts | 37 +++++++++++++---- src/domain/session/room/RoomViewModel.js | 9 +++- src/platform/web/ui/session/room/CallView.ts | 43 ++++++++++++++++++++ src/platform/web/ui/session/room/RoomView.js | 5 ++- 4 files changed, 81 insertions(+), 13 deletions(-) create mode 100644 src/platform/web/ui/session/room/CallView.ts diff --git a/src/domain/session/room/CallViewModel.ts b/src/domain/session/room/CallViewModel.ts index 52f04d32..0c5f28d2 100644 --- a/src/domain/session/room/CallViewModel.ts +++ b/src/domain/session/room/CallViewModel.ts @@ -1,6 +1,5 @@ /* -Copyright 2020 Bruno Windels -Copyright 2020 The Matrix.org Foundation C.I.C. +Copyright 2022 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. @@ -15,20 +14,40 @@ See the License for the specific language governing permissions and limitations under the License. */ -import {ViewModel} from "../../ViewModel"; +import {ViewModel, Options as BaseOptions} from "../../ViewModel"; import type {GroupCall} from "../../../matrix/calls/group/GroupCall"; +import type {Member} from "../../../matrix/calls/group/Member"; +import type {BaseObservableList} from "../../../observable/list/BaseObservableList"; +import type {Track} from "../../../platform/types/MediaDevices"; -export class CallViewModel extends ViewModel { +type Options = BaseOptions & {call: GroupCall}; - private call: GroupCall; +export class CallViewModel extends ViewModel { + + public readonly memberViewModels: BaseObservableList; - constructor(options) { + constructor(options: Options) { super(options); - const {call} = options; - this.call = call; + this.memberViewModels = this.getOption("call").members + .mapValues(member => new CallMemberViewModel(this.childOptions({member}))) + .sortValues((a, b) => { + + }); } get name(): string { - return this.call.name; + return this.getOption("call").name; + } + + get localTracks(): Track[] { + return this.getOption("call").localMedia?.tracks ?? []; + } +} + +type MemberOptions = BaseOptions & {member: Member}; + +class CallMemberViewModel extends ViewModel { + get tracks(): Track[] { + return this.getOption("member").remoteTracks; } } diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 4540fdb1..f79be3c0 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -43,6 +43,10 @@ export class RoomViewModel extends ViewModel { } this._clearUnreadTimout = null; this._closeUrl = this.urlCreator.urlUntilSegment("session"); + this._setupCallViewModel(); + } + + _setupCallViewModel() { // pick call for this room with lowest key this._callObservable = new PickMapObservableValue(session.callHandler.calls.filterValues(c => c.roomId === this.roomId)); this._callViewModel = undefined; @@ -53,8 +57,9 @@ export class RoomViewModel extends ViewModel { } this.emitChange("callViewModel"); })); - if (this._callObservable.get()) { - this._callViewModel = new CallViewModel(this.childOptions({call: this._callObservable.get()})); + const call = this._callObservable.get(); + if (call) { + this._callViewModel = new CallViewModel(this.childOptions({call})); } } diff --git a/src/platform/web/ui/session/room/CallView.ts b/src/platform/web/ui/session/room/CallView.ts new file mode 100644 index 00000000..2c10f82f --- /dev/null +++ b/src/platform/web/ui/session/room/CallView.ts @@ -0,0 +1,43 @@ +/* +Copyright 2022 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, TemplateBuilder} from "../../general/TemplateView"; +import {Track, TrackType} from "../../../../types/MediaDevices"; +import type {TrackWrapper} from "../../../dom/MediaDevices"; +import type {CallViewModel} from "../../../../../domain/session/room/CallViewModel"; + +function bindVideoTracks(t: TemplateBuilder, video: HTMLVideoElement, propSelector: (vm: T) => Track[]) { + t.mapSideEffect(propSelector, tracks => { + if (tracks.length) { + video.srcObject = (tracks[0] as TrackWrapper).stream; + } + }); +} + +export class CallView extends TemplateView { + render(t: TemplateBuilder, vm: CallViewModel): HTMLElement { + return t.div({class: "CallView"}, [ + t.div({class: "CallView_me"}, bindVideoTracks(t, t.video(), vm => vm.localTracks)), + t.view(new ListView(vm.memberViewModels, vm => new MemberView(vm))) + ]); + } +} + +class MemberView extends TemplateView { + render(t, vm) { + return bindVideoTracks(t, t.video(), vm => vm.tracks); + } +} diff --git a/src/platform/web/ui/session/room/RoomView.js b/src/platform/web/ui/session/room/RoomView.js index 541cc4d8..4d010a7c 100644 --- a/src/platform/web/ui/session/room/RoomView.js +++ b/src/platform/web/ui/session/room/RoomView.js @@ -23,6 +23,7 @@ import {TimelineLoadingView} from "./TimelineLoadingView.js"; import {MessageComposer} from "./MessageComposer.js"; import {RoomArchivedView} from "./RoomArchivedView.js"; import {AvatarView} from "../../AvatarView.js"; +import {CallView} from "./CallView"; export class RoomView extends TemplateView { constructor(options) { @@ -52,8 +53,8 @@ export class RoomView extends TemplateView { ]), t.div({className: "RoomView_body"}, [ t.div({className: "RoomView_error"}, vm => vm.error), - t.map(vm => vm.callViewModel, (callViewModel, t) => { - return t.p(["A call is in progress", callViewModel => callViewModel.name]) + t.mapView(vm => vm.callViewModel, callViewModel => { + return new CallView(callViewModel); }), t.mapView(vm => vm.timelineViewModel, timelineViewModel => { return timelineViewModel ?