frontend: Added share link into the layout when only one participant is joined

- enhance share meeting link component with dynamic title, subtitle, and additional info
This commit is contained in:
Carlos Santos 2025-07-31 16:18:42 +02:00
parent f6c5ecf6ef
commit 37375186d4
7 changed files with 98 additions and 10 deletions

View File

@ -1,5 +1,13 @@
<div class="meeting-url-badge fade-in-delayed-more"> <div class="meeting-url-badge">
<div class="meeting-url-badge-title">Invite others with this meeting link</div> @if (title) {
<div class="meeting-url-badge-title" [class]="[titleSize, titleWeight]">{{ title }}</div>
}
@if (subtitle) {
<div class="meeting-url-badge-subtitle">
{{ subtitle }}
</div>
}
<div class="meeting-url-badge-container" (click)="copyClicked.emit()"> <div class="meeting-url-badge-container" (click)="copyClicked.emit()">
<span class="meeting-url-text">{{ meetingUrl }}</span> <span class="meeting-url-text">{{ meetingUrl }}</span>
@ -7,4 +15,8 @@
<mat-icon>content_copy</mat-icon> <mat-icon>content_copy</mat-icon>
</button> </button>
</div> </div>
@if (additionalInfo) {
<div class="meeting-url-badge-additional-info">{{ additionalInfo }}</div>
}
</div> </div>

View File

@ -6,8 +6,41 @@
text-align: center; text-align: center;
font-size: var(--ov-meet-font-size-sm); font-size: var(--ov-meet-font-size-sm);
font-weight: var(--ov-meet-font-weight-light); font-weight: var(--ov-meet-font-weight-light);
color: var(--ov-meet-text-primary); color: var(--ov-meet-text-on-surface);
margin-bottom: var(--ov-meet-spacing-sm); margin-bottom: var(--ov-meet-spacing-sm);
&.sm {
font-size: var(--ov-meet-font-size-sm);
}
&.md {
font-size: var(--ov-meet-font-size-md);
}
&.lg {
font-size: var(--ov-meet-font-size-lg);
}
&.xl {
font-size: var(--ov-meet-font-size-xl);
}
&.light {
font-weight: var(--ov-meet-font-weight-light);
}
&.semibold {
font-weight: var(--ov-meet-font-weight-semibold);
}
&.bold {
font-weight: var(--ov-meet-font-weight-bold);
}
&.normal {
font-weight: var(--ov-meet-font-weight-normal);
}
}
.meeting-url-badge-subtitle{
text-align: center;
font-size: var(--ov-meet-font-size-md);
color: var(--ov-meet-text-on-surface);
margin: var(--ov-meet-spacing-sm);
} }
.meeting-url-badge-container { .meeting-url-badge-container {
@include ov-flex-center; @include ov-flex-center;
@ -29,7 +62,7 @@
.meeting-url-text { .meeting-url-text {
font-family: var(--ov-meet-font-family-mono, 'Roboto Mono', monospace); font-family: var(--ov-meet-font-family-mono, 'Roboto Mono', monospace);
font-size: var(--ov-meet-font-size-sm); font-size: var(--ov-meet-font-size-sm);
color: var(--ov-meet-text-secondary); color: var(--ov-meet-text-on-surface);
font-weight: var(--ov-meet-font-weight-medium); font-weight: var(--ov-meet-font-weight-medium);
letter-spacing: 0.025em; letter-spacing: 0.025em;
user-select: none; user-select: none;

View File

@ -1,6 +1,4 @@
import { Component } from '@angular/core'; import { Component, Input, Output, EventEmitter } from '@angular/core';
import { Input } from '@angular/core';
import { Output, EventEmitter } from '@angular/core';
import { MatButtonModule, MatIconButton } from '@angular/material/button'; import { MatButtonModule, MatIconButton } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@ -12,6 +10,11 @@ import { MatIconModule } from '@angular/material/icon';
styleUrl: './share-meeting-link.component.scss' styleUrl: './share-meeting-link.component.scss'
}) })
export class ShareMeetingLinkComponent { export class ShareMeetingLinkComponent {
@Input() meetingUrl!: string; @Input() meetingUrl!: string;
@Output() copyClicked = new EventEmitter<void>(); @Input() title: string = 'Invite others with this meeting link';
@Input() titleSize: 'sm' | 'md' | 'lg' | 'xl' = 'sm';
@Input() titleWeight: 'light' | 'semibold' | 'bold' | 'normal' = 'normal';
@Input() subtitle?: string;
@Input() additionalInfo?: string;
@Output() copyClicked = new EventEmitter<void>();
} }

View File

@ -100,6 +100,21 @@
></ov-share-meeting-link> ></ov-share-meeting-link>
</div> </div>
</div> </div>
<ng-container *ovLayoutAdditionalElements>
@if (remoteParticipants.length === 0) {
<div class="main-share-meeting-link-container fade-in-delayed-more">
<ov-share-meeting-link
[title]="'Start collaborating'"
[subtitle]="'Share this link to bring others into the meeting'"
[titleSize]="'xl'"
[titleWeight]="'bold'"
[meetingUrl]="hostname + '/' + roomId"
(copyClicked)="copyPublisherLink()"
></ov-share-meeting-link>
</div>
}
</ng-container>
} }
</ov-videoconference> </ov-videoconference>
} @else { } @else {

View File

@ -218,6 +218,14 @@
padding: 10px; padding: 10px;
} }
.main-share-meeting-link-container {
background-color: var(--ov-meet-surface-color);
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--ov-meet-radius-md);
}
// Responsive adjustments // Responsive adjustments
@include ov-mobile-down { @include ov-mobile-down {
.room-access-container { .room-access-container {

View File

@ -44,12 +44,14 @@ import {
ParticipantLeftEvent, ParticipantLeftEvent,
ParticipantLeftReason, ParticipantLeftReason,
ParticipantModel, ParticipantModel,
ParticipantService,
RecordingStartRequestedEvent, RecordingStartRequestedEvent,
RecordingStopRequestedEvent, RecordingStopRequestedEvent,
RemoteParticipant, RemoteParticipant,
Room, Room,
RoomEvent RoomEvent
} from 'openvidu-components-angular'; } from 'openvidu-components-angular';
import { Subject, takeUntil } from 'rxjs';
@Component({ @Component({
selector: 'app-meeting', selector: 'app-meeting',
@ -89,11 +91,14 @@ export class MeetingComponent implements OnInit {
participantName = ''; participantName = '';
participantToken = ''; participantToken = '';
participantRole: ParticipantRole = ParticipantRole.PUBLISHER; participantRole: ParticipantRole = ParticipantRole.PUBLISHER;
remoteParticipants: ParticipantModel[] = [];
showMeeting = false; showMeeting = false;
features: Signal<ApplicationFeatures>; features: Signal<ApplicationFeatures>;
meetingEndedByMe = false; meetingEndedByMe = false;
private destroy$ = new Subject<void>();
constructor( constructor(
protected route: ActivatedRoute, protected route: ActivatedRoute,
protected navigationService: NavigationService, protected navigationService: NavigationService,
@ -104,6 +109,7 @@ export class MeetingComponent implements OnInit {
protected meetingService: MeetingService, protected meetingService: MeetingService,
protected openviduService: OpenViduService, protected openviduService: OpenViduService,
protected participantService: ParticipantTokenService, protected participantService: ParticipantTokenService,
protected componentParticipantService: ParticipantService,
protected appDataService: AppDataService, protected appDataService: AppDataService,
protected wcManagerService: WebComponentManagerService, protected wcManagerService: WebComponentManagerService,
protected sessionStorageService: SessionStorageService, protected sessionStorageService: SessionStorageService,
@ -128,6 +134,11 @@ export class MeetingComponent implements OnInit {
await this.initializeParticipantName(); await this.initializeParticipantName();
} }
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
/** /**
* Sets the back button text based on the application mode and user role * Sets the back button text based on the application mode and user role
*/ */
@ -238,6 +249,12 @@ export class MeetingComponent implements OnInit {
await this.addParticipantNameToUrl(); await this.addParticipantNameToUrl();
await this.roomService.loadPreferences(this.roomId); await this.roomService.loadPreferences(this.roomId);
this.showMeeting = true; this.showMeeting = true;
// Subscribe to remote participants updates for showing/hiding the share meeting link component
this.componentParticipantService.remoteParticipants$
.pipe(takeUntil(this.destroy$))
.subscribe((participants) => {
this.remoteParticipants = participants;
});
} catch (error) { } catch (error) {
console.error('Error accessing meeting:', error); console.error('Error accessing meeting:', error);
} }

View File

@ -19,7 +19,7 @@
--ov-meet-text-on-primary: #ffffff; --ov-meet-text-on-primary: #ffffff;
--ov-meet-text-on-secondary: #ffffff; --ov-meet-text-on-secondary: #ffffff;
--ov-meet-text-on-accent: #ffffff; --ov-meet-text-on-accent: #ffffff;
--ov-meet-text-on-surface: #212121; --ov-meet-text-on-surface: #494949;
--ov-meet-text-on-background: #212121; --ov-meet-text-on-background: #212121;
// === SHADOWS - LIGHT THEME === // === SHADOWS - LIGHT THEME ===