frontend: enhance hidden participants indicator with improved layout and description text

This commit is contained in:
Carlos Santos 2025-12-22 14:36:18 +01:00
parent 3a83efa668
commit b4f482f9d7
6 changed files with 73 additions and 42 deletions

View File

@ -4,7 +4,11 @@
<!-- Horizontal/Bar Layout -->
<div class="horizontal-content">
<div class="count-badge-horizontal">
{{ displayText() }}
@if (count() === 1) {
<mat-icon>person</mat-icon>
} @else {
<mat-icon>people</mat-icon>
}
</div>
<div class="text-horizontal">
<span class="count-number">{{ count() }}</span>
@ -14,27 +18,11 @@
} @else {
<!-- Vertical/Standard Layout -->
<div class="vertical-content">
<div class="header-section">
<div class="icon-group">
<svg class="participants-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/>
</svg>
</div>
<div class="title">Hidden Participants</div>
</div>
<div class="count-section">
<div class="count-badge-vertical">
{{ displayText() }}
</div>
<div class="count-label">
{{ count() }} {{ count() === 1 ? 'participant' : 'participants' }}
</div>
</div>
<div class="info-section">
<svg class="info-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/>
</svg>
<div class="info-text">Not currently visible in the layout</div>
<div class="count-label"> {{ count() }} {{ descriptionText() }}</div>
</div>
</div>
}

View File

@ -11,6 +11,9 @@
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
transition: all 0.2s ease-in-out;
container-type: size;
container-name: hidden-indicator;
&:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
@ -71,11 +74,11 @@
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
justify-content: center;
width: 100%;
height: 100%;
padding: 20px;
gap: 16px;
padding: clamp(4px, 3cqh, 20px) clamp(4px, 2cqw, 16px);
gap: clamp(4px, 2cqh, 16px);
}
.header-section {
@ -111,7 +114,7 @@
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
gap: clamp(4px, 1.5cqh, 12px);
flex: 1;
justify-content: center;
@ -119,23 +122,28 @@
display: flex;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
// width: clamp(32px, 40cqw, 80px);
height: clamp(44px, 40cqh, 80px);
aspect-ratio: 1 / 1;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
font-size: 36px;
font-size: clamp(22px, 6cqh, 36px);
font-weight: 700;
color: #ffffff;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
animation: pulse 2s ease-in-out infinite;
}
.count-label {
font-size: 15px;
font-size: clamp(14px, 2.5cqh, 15px);
font-weight: 500;
color: #e0e0e0;
text-align: center;
letter-spacing: 0.3px;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding: 0 4px;
}
}
@ -164,18 +172,38 @@
}
}
@keyframes pulse {
0%, 100% {
transform: scale(1);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
@container hidden-indicator (max-height: 100px) {
.count-label {
display: none;
}
50% {
transform: scale(1.05);
box-shadow: 0 6px 16px rgba(102, 126, 234, 0.7);
.count-section {
gap: 0;
}
}
@container hidden-indicator (max-width: 120px) {
.count-label {
display: none;
}
.count-section {
gap: 0;
}
}
// Para espacios extra pequeños, optimizar padding y sombras
@container hidden-indicator (max-width: 100px) {
.vertical-content {
padding: 4px;
}
.count-badge-vertical {
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4);
}
}
// Responsive adjustments
@media (max-width: 768px), (max-height: 500px) {
.count-badge-horizontal {
min-width: 40px;

View File

@ -1,5 +1,6 @@
import { CommonModule } from '@angular/common';
import { Component, computed, input } from '@angular/core';
import { MatIconModule } from '@angular/material/icon';
/**
* Component that displays an indicator for participants not visible in the current layout.
@ -10,7 +11,7 @@ import { Component, computed, input } from '@angular/core';
*/
@Component({
selector: 'ov-hidden-participants-indicator',
imports: [CommonModule],
imports: [CommonModule, MatIconModule],
templateUrl: './hidden-participants-indicator.component.html',
styleUrl: './hidden-participants-indicator.component.scss'
})
@ -30,11 +31,10 @@ export class HiddenParticipantsIndicatorComponent {
* Get the display text for the hidden participants count
*/
protected displayText = computed(() => {
if (this.count() === 0) return '';
return `+${this.count()}`;
});
protected descriptionText = computed(() => {
return this.count() === 1 ? 'participant not visible' : 'participants not visible';
return this.count() === 1 ? 'participant not currently visible' : 'participants not currently visible';
});
}

View File

@ -15,8 +15,16 @@
</div>
</ng-container>
} @else if (isSmartMosaicActive() && hiddenParticipantsCount() > 0) {
<ng-container *ovLayoutAdditionalElements>
<div [ngClass]="{ 'OV_top-bar': showTopBarHiddenParticipantsIndicator() }">
<!-- Use bottom slot to position indicator at the end -->
<ng-container *ovLayoutAdditionalElements="'bottom'">
<div
[ngClass]="{
'OV_top-bar': showTopBarHiddenParticipantsIndicator(),
OV_last: !showTopBarHiddenParticipantsIndicator(),
'participant-indicator-container': true
}"
(click)="toggleParticipantsPanel()"
>
<ov-hidden-participants-indicator
[count]="hiddenParticipantsCount()"
[mode]="showTopBarHiddenParticipantsIndicator() ? 'topbar' : 'standard'"

View File

@ -1,6 +1,5 @@
@use '../../../../../../../src/assets/styles/design-tokens';
.remote-participant {
height: -webkit-fill-available;
height: -moz-available;
@ -80,6 +79,9 @@
cursor: move;
}
.participant-indicator-container {
cursor: pointer;
}
.OV_top-bar {
box-sizing: border-box;
padding: 4px 4px 2px 4px !important;

View File

@ -1,6 +1,6 @@
import { CommonModule } from '@angular/common';
import { Component, computed, effect, inject, signal, untracked } from '@angular/core';
import { ILogger, LoggerService, OpenViduComponentsUiModule, ParticipantModel } from 'openvidu-components-angular';
import { ILogger, LoggerService, OpenViduComponentsUiModule, PanelService, PanelType, ParticipantModel } from 'openvidu-components-angular';
import { HiddenParticipantsIndicatorComponent, ShareMeetingLinkComponent } from '../../../components';
import { CustomParticipantModel } from '../../../models';
import { MeetingContextService, MeetingService, MeetLayoutService } from '../../../services';
@ -21,6 +21,7 @@ export class MeetingCustomLayoutComponent {
protected readonly layoutService = inject(MeetLayoutService);
protected readonly meetingContextService = inject(MeetingContextService);
protected readonly meetingService = inject(MeetingService);
protected readonly panelService = inject(PanelService);
protected readonly linkOverlayConfig = {
title: 'Start collaborating',
subtitle: 'Share this link to bring others into the meeting',
@ -84,6 +85,10 @@ export class MeetingCustomLayoutComponent {
return this.isLayoutSwitchingAllowed() && this.layoutService.isSmartMosaicEnabled();
}
protected toggleParticipantsPanel(): void {
this.panelService.togglePanel(PanelType.PARTICIPANTS);
}
private setupVisibleParticipantsUpdate(): void {
effect(
() => {