forked from mystiq/hydrogen-web
WIP on UI
This commit is contained in:
parent
e760b8e556
commit
4be82cd472
4 changed files with 81 additions and 13 deletions
|
@ -1,6 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2020 Bruno Windels <bruno@windels.cloud>
|
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with 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.
|
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 {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<Options> {
|
||||||
|
|
||||||
|
public readonly memberViewModels: BaseObservableList<CallMemberViewModel>;
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options: Options) {
|
||||||
super(options);
|
super(options);
|
||||||
const {call} = options;
|
this.memberViewModels = this.getOption("call").members
|
||||||
this.call = call;
|
.mapValues(member => new CallMemberViewModel(this.childOptions({member})))
|
||||||
|
.sortValues((a, b) => {
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
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<MemberOptions> {
|
||||||
|
get tracks(): Track[] {
|
||||||
|
return this.getOption("member").remoteTracks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,10 @@ export class RoomViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
this._clearUnreadTimout = null;
|
this._clearUnreadTimout = null;
|
||||||
this._closeUrl = this.urlCreator.urlUntilSegment("session");
|
this._closeUrl = this.urlCreator.urlUntilSegment("session");
|
||||||
|
this._setupCallViewModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupCallViewModel() {
|
||||||
// pick call for this room with lowest key
|
// pick call for this room with lowest key
|
||||||
this._callObservable = new PickMapObservableValue(session.callHandler.calls.filterValues(c => c.roomId === this.roomId));
|
this._callObservable = new PickMapObservableValue(session.callHandler.calls.filterValues(c => c.roomId === this.roomId));
|
||||||
this._callViewModel = undefined;
|
this._callViewModel = undefined;
|
||||||
|
@ -53,8 +57,9 @@ export class RoomViewModel extends ViewModel {
|
||||||
}
|
}
|
||||||
this.emitChange("callViewModel");
|
this.emitChange("callViewModel");
|
||||||
}));
|
}));
|
||||||
if (this._callObservable.get()) {
|
const call = this._callObservable.get();
|
||||||
this._callViewModel = new CallViewModel(this.childOptions({call: this._callObservable.get()}));
|
if (call) {
|
||||||
|
this._callViewModel = new CallViewModel(this.childOptions({call}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
43
src/platform/web/ui/session/room/CallView.ts
Normal file
43
src/platform/web/ui/session/room/CallView.ts
Normal file
|
@ -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>(t: TemplateBuilder<T>, 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<CallViewModel> {
|
||||||
|
render(t: TemplateBuilder<CallViewModel>, 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<CallMemberViewModel> {
|
||||||
|
render(t, vm) {
|
||||||
|
return bindVideoTracks(t, t.video(), vm => vm.tracks);
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import {TimelineLoadingView} from "./TimelineLoadingView.js";
|
||||||
import {MessageComposer} from "./MessageComposer.js";
|
import {MessageComposer} from "./MessageComposer.js";
|
||||||
import {RoomArchivedView} from "./RoomArchivedView.js";
|
import {RoomArchivedView} from "./RoomArchivedView.js";
|
||||||
import {AvatarView} from "../../AvatarView.js";
|
import {AvatarView} from "../../AvatarView.js";
|
||||||
|
import {CallView} from "./CallView";
|
||||||
|
|
||||||
export class RoomView extends TemplateView {
|
export class RoomView extends TemplateView {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
|
@ -52,8 +53,8 @@ export class RoomView extends TemplateView {
|
||||||
]),
|
]),
|
||||||
t.div({className: "RoomView_body"}, [
|
t.div({className: "RoomView_body"}, [
|
||||||
t.div({className: "RoomView_error"}, vm => vm.error),
|
t.div({className: "RoomView_error"}, vm => vm.error),
|
||||||
t.map(vm => vm.callViewModel, (callViewModel, t) => {
|
t.mapView(vm => vm.callViewModel, callViewModel => {
|
||||||
return t.p(["A call is in progress", callViewModel => callViewModel.name])
|
return new CallView(callViewModel);
|
||||||
}),
|
}),
|
||||||
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
|
t.mapView(vm => vm.timelineViewModel, timelineViewModel => {
|
||||||
return timelineViewModel ?
|
return timelineViewModel ?
|
||||||
|
|
Loading…
Reference in a new issue