mirror of
https://github.com/vector-im/hydrogen-web.git
synced 2024-12-23 11:35:04 +01:00
Merge pull request #1004 from vector-im/calls-show-toast
Calls: Show a toast notification for call
This commit is contained in:
commit
490c29d2f5
@ -58,3 +58,11 @@ export function getAvatarHttpUrl(avatarUrl: string | undefined, cssSize: number,
|
|||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// move to AvatarView.js when converting to typescript
|
||||||
|
export interface IAvatarContract {
|
||||||
|
avatarLetter: string;
|
||||||
|
avatarColorNumber: number;
|
||||||
|
avatarUrl: (size: number) => string | undefined;
|
||||||
|
avatarTitle: string;
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@ import {ViewModel} from "../ViewModel";
|
|||||||
import {RoomViewModelObservable} from "./RoomViewModelObservable.js";
|
import {RoomViewModelObservable} from "./RoomViewModelObservable.js";
|
||||||
import {RightPanelViewModel} from "./rightpanel/RightPanelViewModel.js";
|
import {RightPanelViewModel} from "./rightpanel/RightPanelViewModel.js";
|
||||||
import {SyncStatus} from "../../matrix/Sync.js";
|
import {SyncStatus} from "../../matrix/Sync.js";
|
||||||
|
import {ToastCollectionViewModel} from "./toast/ToastCollectionViewModel";
|
||||||
|
|
||||||
export class SessionViewModel extends ViewModel {
|
export class SessionViewModel extends ViewModel {
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
@ -47,6 +48,9 @@ export class SessionViewModel extends ViewModel {
|
|||||||
this._gridViewModel = null;
|
this._gridViewModel = null;
|
||||||
this._createRoomViewModel = null;
|
this._createRoomViewModel = null;
|
||||||
this._joinRoomViewModel = null;
|
this._joinRoomViewModel = null;
|
||||||
|
this._toastCollectionViewModel = this.track(new ToastCollectionViewModel(this.childOptions({
|
||||||
|
session: this._client.session,
|
||||||
|
})));
|
||||||
this._setupNavigation();
|
this._setupNavigation();
|
||||||
this._setupForcedLogoutOnAccessTokenInvalidation();
|
this._setupForcedLogoutOnAccessTokenInvalidation();
|
||||||
}
|
}
|
||||||
@ -173,6 +177,10 @@ export class SessionViewModel extends ViewModel {
|
|||||||
return this._joinRoomViewModel;
|
return this._joinRoomViewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get toastCollectionViewModel() {
|
||||||
|
return this._toastCollectionViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
_updateGrid(roomIds) {
|
_updateGrid(roomIds) {
|
||||||
const changed = !(this._gridViewModel && roomIds);
|
const changed = !(this._gridViewModel && roomIds);
|
||||||
const currentRoomId = this.navigation.path.get("room");
|
const currentRoomId = this.navigation.path.get("room");
|
||||||
|
35
src/domain/session/toast/BaseToastNotificationViewModel.ts
Normal file
35
src/domain/session/toast/BaseToastNotificationViewModel.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 {ErrorReportViewModel} from "../../ErrorReportViewModel";
|
||||||
|
import {Options as BaseOptions} from "../../ViewModel";
|
||||||
|
import type {Session} from "../../../matrix/Session.js";
|
||||||
|
import {SegmentType} from "../../navigation";
|
||||||
|
|
||||||
|
export type BaseClassOptions<N extends object = SegmentType> = {
|
||||||
|
dismiss: () => void;
|
||||||
|
session: Session;
|
||||||
|
} & BaseOptions<N>;
|
||||||
|
|
||||||
|
export abstract class BaseToastNotificationViewModel<N extends object = SegmentType, O extends BaseClassOptions<N> = BaseClassOptions<N>> extends ErrorReportViewModel<N, O> {
|
||||||
|
constructor(options: O) {
|
||||||
|
super(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
dismiss(): void {
|
||||||
|
this.getOption("dismiss")();
|
||||||
|
}
|
||||||
|
}
|
92
src/domain/session/toast/CallToastNotificationViewModel.ts
Normal file
92
src/domain/session/toast/CallToastNotificationViewModel.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 type {GroupCall} from "../../../matrix/calls/group/GroupCall";
|
||||||
|
import type {Room} from "../../../matrix/room/Room.js";
|
||||||
|
import {IAvatarContract, avatarInitials, getIdentifierColorNumber, getAvatarHttpUrl} from "../../avatar";
|
||||||
|
import {LocalMedia} from "../../../matrix/calls/LocalMedia";
|
||||||
|
import {BaseClassOptions, BaseToastNotificationViewModel} from "./BaseToastNotificationViewModel";
|
||||||
|
import {SegmentType} from "../../navigation";
|
||||||
|
|
||||||
|
type Options<N extends MinimumNeededSegmentType = SegmentType> = {
|
||||||
|
call: GroupCall;
|
||||||
|
room: Room;
|
||||||
|
} & BaseClassOptions<N>;
|
||||||
|
|
||||||
|
// Since we access the room segment below, the segment type
|
||||||
|
// needs to at least contain the room segment!
|
||||||
|
type MinimumNeededSegmentType = {
|
||||||
|
"room": string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class CallToastNotificationViewModel<N extends MinimumNeededSegmentType = SegmentType, O extends Options<N> = Options<N>> extends BaseToastNotificationViewModel<N, O> implements IAvatarContract {
|
||||||
|
constructor(options: O) {
|
||||||
|
super(options);
|
||||||
|
this.track(this.call.members.observeSize().subscribe(() => {
|
||||||
|
this.emitChange("memberCount");
|
||||||
|
}));
|
||||||
|
// Dismiss the toast if the room is opened manually
|
||||||
|
this.track(
|
||||||
|
this.navigation.observe("room").subscribe((roomId) => {
|
||||||
|
if ((roomId as unknown as string) === this.call.roomId) {
|
||||||
|
this.dismiss();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async join(): Promise<void> {
|
||||||
|
await this.logAndCatch("CallToastNotificationViewModel.join", async (log) => {
|
||||||
|
const stream = await this.platform.mediaDevices.getMediaTracks(false, true);
|
||||||
|
const localMedia = new LocalMedia().withUserMedia(stream);
|
||||||
|
await this.call.join(localMedia, log);
|
||||||
|
const url = this.urlRouter.openRoomActionUrl(this.call.roomId);
|
||||||
|
this.urlRouter.pushUrl(url);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get call(): GroupCall {
|
||||||
|
return this.getOption("call");
|
||||||
|
}
|
||||||
|
|
||||||
|
private get room(): Room {
|
||||||
|
return this.getOption("room");
|
||||||
|
}
|
||||||
|
|
||||||
|
get roomName(): string {
|
||||||
|
return this.room.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
get memberCount(): number {
|
||||||
|
return this.call.members.size;
|
||||||
|
}
|
||||||
|
|
||||||
|
get avatarLetter(): string {
|
||||||
|
return avatarInitials(this.roomName);
|
||||||
|
}
|
||||||
|
|
||||||
|
get avatarColorNumber(): number {
|
||||||
|
return getIdentifierColorNumber(this.room.avatarColorId);
|
||||||
|
}
|
||||||
|
|
||||||
|
avatarUrl(size: number): string | undefined {
|
||||||
|
return getAvatarHttpUrl(this.room.avatarUrl, size, this.platform, this.room.mediaRepository);
|
||||||
|
}
|
||||||
|
|
||||||
|
get avatarTitle(): string {
|
||||||
|
return this.roomName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
87
src/domain/session/toast/ToastCollectionViewModel.ts
Normal file
87
src/domain/session/toast/ToastCollectionViewModel.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 {CallToastNotificationViewModel} from "./CallToastNotificationViewModel";
|
||||||
|
import {ObservableArray} from "../../../observable";
|
||||||
|
import {ViewModel, Options as BaseOptions} from "../../ViewModel";
|
||||||
|
import type {GroupCall} from "../../../matrix/calls/group/GroupCall";
|
||||||
|
import type {Room} from "../../../matrix/room/Room.js";
|
||||||
|
import type {Session} from "../../../matrix/Session.js";
|
||||||
|
import type {SegmentType} from "../../navigation";
|
||||||
|
|
||||||
|
type Options = {
|
||||||
|
session: Session;
|
||||||
|
} & BaseOptions;
|
||||||
|
|
||||||
|
export class ToastCollectionViewModel extends ViewModel<SegmentType, Options> {
|
||||||
|
public readonly toastViewModels: ObservableArray<CallToastNotificationViewModel> = new ObservableArray();
|
||||||
|
|
||||||
|
constructor(options: Options) {
|
||||||
|
super(options);
|
||||||
|
const session = this.getOption("session");
|
||||||
|
const callsObservableMap = session.callHandler.calls;
|
||||||
|
this.track(callsObservableMap.subscribe(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
onAdd(_, call: GroupCall) {
|
||||||
|
if (this._shouldShowNotification(call)) {
|
||||||
|
const room = this._findRoomForCall(call);
|
||||||
|
const dismiss = () => {
|
||||||
|
const idx = this.toastViewModels.array.findIndex(vm => vm.call === call);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.toastViewModels.remove(idx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.toastViewModels.append(
|
||||||
|
new CallToastNotificationViewModel(this.childOptions({ call, room, dismiss }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRemove(_, call: GroupCall) {
|
||||||
|
const idx = this.toastViewModels.array.findIndex(vm => vm.call === call);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.toastViewModels.remove(idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate(_, call: GroupCall) {
|
||||||
|
const idx = this.toastViewModels.array.findIndex(vm => vm.call === call);
|
||||||
|
if (idx !== -1) {
|
||||||
|
this.toastViewModels.update(idx, this.toastViewModels.at(idx)!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onReset() {
|
||||||
|
for (let i = 0; i < this.toastViewModels.length; ++i) {
|
||||||
|
this.toastViewModels.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _findRoomForCall(call: GroupCall): Room {
|
||||||
|
const id = call.roomId;
|
||||||
|
const rooms = this.getOption("session").rooms;
|
||||||
|
return rooms.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _shouldShowNotification(call: GroupCall): boolean {
|
||||||
|
const currentlyOpenedRoomId = this.navigation.path.get("room")?.value;
|
||||||
|
if (!call.isLoadedFromStorage && call.roomId !== currentlyOpenedRoomId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -104,7 +104,7 @@ export class CallHandler implements RoomStateHandler {
|
|||||||
}
|
}
|
||||||
const event = await txn.roomState.get(callEntry.roomId, EventType.GroupCall, callEntry.callId);
|
const event = await txn.roomState.get(callEntry.roomId, EventType.GroupCall, callEntry.callId);
|
||||||
if (event) {
|
if (event) {
|
||||||
const call = new GroupCall(event.event.state_key, false, event.event.content, event.roomId, this.groupCallOptions);
|
const call = new GroupCall(event.event.state_key, true, false, event.event.content, event.roomId, this.groupCallOptions);
|
||||||
this._calls.set(call.id, call);
|
this._calls.set(call.id, call);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -135,7 +135,7 @@ export class CallHandler implements RoomStateHandler {
|
|||||||
if (!intent) {
|
if (!intent) {
|
||||||
intent = CallIntent.Ring;
|
intent = CallIntent.Ring;
|
||||||
}
|
}
|
||||||
const call = new GroupCall(makeId("conf-"), true, {
|
const call = new GroupCall(makeId("conf-"), false, true, {
|
||||||
"m.name": name,
|
"m.name": name,
|
||||||
"m.intent": intent
|
"m.intent": intent
|
||||||
}, roomId, this.groupCallOptions);
|
}, roomId, this.groupCallOptions);
|
||||||
@ -217,7 +217,7 @@ export class CallHandler implements RoomStateHandler {
|
|||||||
txn.calls.remove(call.intent, roomId, call.id);
|
txn.calls.remove(call.intent, roomId, call.id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
call = new GroupCall(event.state_key, false, event.content, roomId, this.groupCallOptions);
|
call = new GroupCall(event.state_key, false, false, event.content, roomId, this.groupCallOptions);
|
||||||
this._calls.set(call.id, call);
|
this._calls.set(call.id, call);
|
||||||
txn.calls.add({
|
txn.calls.add({
|
||||||
intent: call.intent,
|
intent: call.intent,
|
||||||
|
@ -104,6 +104,7 @@ export class GroupCall extends EventEmitter<{change: never}> {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly id: string,
|
public readonly id: string,
|
||||||
|
public readonly isLoadedFromStorage: boolean,
|
||||||
newCall: boolean,
|
newCall: boolean,
|
||||||
private callContent: Record<string, any>,
|
private callContent: Record<string, any>,
|
||||||
public readonly roomId: string,
|
public readonly roomId: string,
|
||||||
|
@ -1224,3 +1224,103 @@ button.RoomDetailsView_row::after {
|
|||||||
.JoinRoomView_status .spinner {
|
.JoinRoomView_status .spinner {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Toast */
|
||||||
|
.ToastCollectionView {
|
||||||
|
display: flex;
|
||||||
|
position: fixed;
|
||||||
|
flex-direction: column;
|
||||||
|
z-index: 1000;
|
||||||
|
left: 44px;
|
||||||
|
top: 52px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ToastCollectionView ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView:not(:first-child) {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: 40px 1fr 1fr 48px;
|
||||||
|
row-gap: 4px;
|
||||||
|
width: 260px;
|
||||||
|
background-color: var(--background-color-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: var(--text-color);
|
||||||
|
box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__top {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto 176px auto;
|
||||||
|
align-items: center;
|
||||||
|
justify-items: center;
|
||||||
|
}
|
||||||
|
.CallToastNotificationView__dismiss-btn {
|
||||||
|
background: center var(--background-color-secondary--darker-5) url("./icons/dismiss.svg?primary=text-color") no-repeat;
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 15px;
|
||||||
|
width: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__name {
|
||||||
|
font-weight: 600;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__description {
|
||||||
|
margin-left: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__call-type::before {
|
||||||
|
content: url("./icons/video-call.svg?primary=light-text-color");
|
||||||
|
display: flex;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__call-type::after {
|
||||||
|
content: "";
|
||||||
|
width: 4px;
|
||||||
|
height: 4px;
|
||||||
|
background-color: var(--text-color);
|
||||||
|
border-radius: 100%;
|
||||||
|
align-self: center;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__member-count::before {
|
||||||
|
content: url("./icons/room-members.svg?primary=light-text-color");
|
||||||
|
display: flex;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__member-count,
|
||||||
|
.CallToastNotificationView__call-type {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__info {
|
||||||
|
display: flex;
|
||||||
|
margin-left: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__action {
|
||||||
|
display: flex;
|
||||||
|
justify-content: end;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.CallToastNotificationView__action .button-action {
|
||||||
|
width: 100px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
@ -30,6 +30,7 @@ import {CreateRoomView} from "./CreateRoomView.js";
|
|||||||
import {RightPanelView} from "./rightpanel/RightPanelView.js";
|
import {RightPanelView} from "./rightpanel/RightPanelView.js";
|
||||||
import {viewClassForTile} from "./room/common";
|
import {viewClassForTile} from "./room/common";
|
||||||
import {JoinRoomView} from "./JoinRoomView";
|
import {JoinRoomView} from "./JoinRoomView";
|
||||||
|
import {ToastCollectionView} from "./toast/ToastCollectionView";
|
||||||
|
|
||||||
export class SessionView extends TemplateView {
|
export class SessionView extends TemplateView {
|
||||||
render(t, vm) {
|
render(t, vm) {
|
||||||
@ -40,6 +41,7 @@ export class SessionView extends TemplateView {
|
|||||||
"right-shown": vm => !!vm.rightPanelViewModel
|
"right-shown": vm => !!vm.rightPanelViewModel
|
||||||
},
|
},
|
||||||
}, [
|
}, [
|
||||||
|
t.view(new ToastCollectionView(vm.toastCollectionViewModel)),
|
||||||
t.view(new SessionStatusView(vm.sessionStatusViewModel)),
|
t.view(new SessionStatusView(vm.sessionStatusViewModel)),
|
||||||
t.view(new LeftPanelView(vm.leftPanelViewModel)),
|
t.view(new LeftPanelView(vm.leftPanelViewModel)),
|
||||||
t.mapView(vm => vm.activeMiddleViewModel, () => {
|
t.mapView(vm => vm.activeMiddleViewModel, () => {
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 {AvatarView} from "../../AvatarView.js";
|
||||||
|
import {ErrorView} from "../../general/ErrorView";
|
||||||
|
import {TemplateView, Builder} from "../../general/TemplateView";
|
||||||
|
import type {CallToastNotificationViewModel} from "../../../../../domain/session/toast/CallToastNotificationViewModel";
|
||||||
|
|
||||||
|
export class CallToastNotificationView extends TemplateView<CallToastNotificationViewModel> {
|
||||||
|
render(t: Builder<CallToastNotificationViewModel>, vm: CallToastNotificationViewModel) {
|
||||||
|
return t.div({ className: "CallToastNotificationView" }, [
|
||||||
|
t.div({ className: "CallToastNotificationView__top" }, [
|
||||||
|
t.view(new AvatarView(vm, 24)),
|
||||||
|
t.span({ className: "CallToastNotificationView__name" }, (vm) => vm.roomName),
|
||||||
|
t.button({
|
||||||
|
className: "button-action CallToastNotificationView__dismiss-btn",
|
||||||
|
onClick: () => vm.dismiss(),
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
t.div({ className: "CallToastNotificationView__description" }, [
|
||||||
|
t.span(vm.i18n`Video call started`)
|
||||||
|
]),
|
||||||
|
t.div({ className: "CallToastNotificationView__info" }, [
|
||||||
|
t.span({className: "CallToastNotificationView__call-type"}, vm.i18n`Video`),
|
||||||
|
t.span({className: "CallToastNotificationView__member-count"}, (vm) => vm.memberCount),
|
||||||
|
]),
|
||||||
|
t.div({ className: "CallToastNotificationView__action" }, [
|
||||||
|
t.button({
|
||||||
|
className: "button-action primary",
|
||||||
|
onClick: () => vm.join(),
|
||||||
|
}, vm.i18n`Join`),
|
||||||
|
]),
|
||||||
|
t.if(vm => !!vm.errorViewModel, t => {
|
||||||
|
return t.div({className: "CallView_error"}, t.view(new ErrorView(vm.errorViewModel!)));
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
33
src/platform/web/ui/session/toast/ToastCollectionView.ts
Normal file
33
src/platform/web/ui/session/toast/ToastCollectionView.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2023 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 {CallToastNotificationView} from "./CallToastNotificationView";
|
||||||
|
import {ListView} from "../../general/ListView";
|
||||||
|
import {TemplateView, Builder} from "../../general/TemplateView";
|
||||||
|
import type {CallToastNotificationViewModel} from "../../../../../domain/session/toast/CallToastNotificationViewModel";
|
||||||
|
import type {ToastCollectionViewModel} from "../../../../../domain/session/toast/ToastCollectionViewModel";
|
||||||
|
|
||||||
|
export class ToastCollectionView extends TemplateView<ToastCollectionViewModel> {
|
||||||
|
render(t: Builder<ToastCollectionViewModel>, vm: ToastCollectionViewModel) {
|
||||||
|
const view = new ListView({
|
||||||
|
list: vm.toastViewModels,
|
||||||
|
parentProvidesUpdates: false,
|
||||||
|
}, (vm: CallToastNotificationViewModel) => new CallToastNotificationView(vm));
|
||||||
|
return t.div({ className: "ToastCollectionView" }, [
|
||||||
|
t.view(view),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user