frontend: enhance hidden participants indicator to display names of hidden participants in both horizontal and vertical layouts

This commit is contained in:
Carlos Santos 2025-12-23 12:49:13 +01:00
parent 6a9bd2be95
commit 8fbe8fb716
5 changed files with 198 additions and 117 deletions

View File

@ -3,26 +3,55 @@
@if (isTopBarMode()) {
<!-- Horizontal/Bar Layout -->
<div class="horizontal-content">
<div class="count-badge-horizontal">
@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>
<span class="description">{{ descriptionText() }}</span>
<div class="left-section">
<div class="count-badge">
<mat-icon class="badge-icon">
@if (count() === 1) {
person
} @else {
people
}
</mat-icon>
<span class="badge-number">+{{ count() }}</span>
</div>
<div class="count-info">
<span class="count-number">+{{ count() }}</span>
<span class="description">{{ descriptionText() }}</span>
</div>
</div>
@if (formattedParticipantNames()) {
<div class="right-section">
<div class="participant-names">
<mat-icon class="names-icon">visibility_off</mat-icon>
<span class="names-text">{{ formattedParticipantNames() }}</span>
</div>
</div>
}
</div>
} @else {
<!-- Vertical/Standard Layout -->
<div class="vertical-content">
<div class="count-section">
<div class="count-badge-vertical">
{{ displayText() }}
<div class="count-badge">
<mat-icon class="badge-icon">
@if (count() === 1) {
person
} @else {
people
}
</mat-icon>
<span class="badge-number">+{{ count() }}</span>
</div>
<div class="count-label"> {{ count() }} {{ descriptionText() }}</div>
<div class="count-info">
<span class="count-number">+{{ count() }}</span>
<span class="description">{{ descriptionText() }}</span>
</div>
@if (formattedParticipantNames()) {
<div class="participant-names-vertical">
<mat-icon class="names-icon">visibility_off</mat-icon>
<span class="names-text">{{ formattedParticipantNames() }}</span>
</div>
}
</div>
</div>
}

View File

@ -25,14 +25,21 @@
.horizontal-content {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
justify-content: space-between;
gap: 24px;
padding: 0 24px;
width: 100%;
height: 100%;
}
.count-badge-horizontal {
.left-section {
display: flex;
align-items: center;
gap: 16px;
flex-shrink: 0;
}
.count-badge {
display: flex;
align-items: center;
justify-content: center;
@ -45,9 +52,20 @@
color: #ffffff;
box-shadow: 0 2px 8px rgba(102, 126, 234, 0.4);
flex-shrink: 0;
position: relative;
.badge-icon {
display: block;
}
.badge-number {
display: none;
position: absolute;
font-size: inherit;
}
}
.text-horizontal {
.count-info {
display: flex;
align-items: baseline;
gap: 8px;
@ -67,6 +85,43 @@
}
}
.right-section {
display: flex;
align-items: center;
justify-content: flex-end;
flex: 1;
min-width: 0; // Allow flex item to shrink below content size
}
.participant-names {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background-color: rgba(102, 126, 234, 0.15);
border-radius: 20px;
border: 1px solid rgba(102, 126, 234, 0.3);
max-width: 100%;
.names-icon {
font-size: 18px;
width: 18px;
height: 18px;
color: #667eea;
flex-shrink: 0;
}
.names-text {
font-size: 14px;
font-weight: 500;
color: #e0e0e0;
letter-spacing: 0.2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
// ============================================
// VERTICAL MODE (Tile estándar 16:9)
// ============================================
@ -81,35 +136,6 @@
gap: clamp(4px, 2cqh, 16px);
}
.header-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
width: 100%;
.icon-group {
display: flex;
align-items: center;
justify-content: center;
}
.participants-icon {
width: 40px;
height: 40px;
color: #667eea;
opacity: 0.9;
}
.title {
font-size: 16px;
font-weight: 600;
color: #ffffff;
text-align: center;
letter-spacing: 0.5px;
}
}
.count-section {
display: flex;
flex-direction: column;
@ -117,80 +143,87 @@
gap: clamp(4px, 1.5cqh, 12px);
flex: 1;
justify-content: center;
width: 100%;
.count-badge-vertical {
.participant-names-vertical {
display: flex;
align-items: center;
justify-content: center;
// 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: clamp(22px, 6cqh, 36px);
font-weight: 700;
color: #ffffff;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.5);
}
gap: 6px;
padding: clamp(4px, 1.5cqh, 8px) clamp(8px, 3cqw, 12px);
background-color: rgba(102, 126, 234, 0.15);
border-radius: 12px;
border: 1px solid rgba(102, 126, 234, 0.3);
max-width: 90%;
.count-label {
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;
}
}
.names-icon {
font-size: clamp(14px, 3cqw, 16px);
width: clamp(14px, 3cqw, 16px);
height: clamp(14px, 3cqw, 16px);
color: #667eea;
flex-shrink: 0;
}
.info-section {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background-color: rgba(255, 255, 255, 0.05);
border-radius: 6px;
width: 100%;
.info-icon {
width: 20px;
height: 20px;
color: #667eea;
flex-shrink: 0;
}
.info-text {
font-size: 12px;
font-weight: 500;
color: #b0b0b0;
line-height: 1.4;
letter-spacing: 0.2px;
.names-text {
font-size: clamp(11px, 2cqh, 13px);
font-weight: 500;
color: #e0e0e0;
letter-spacing: 0.2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
flex: 1;
min-width: 0;
}
}
}
@container hidden-indicator (max-height: 100px) {
.count-label {
.vertical .count-info {
display: none;
}
.count-section {
gap: 0;
}
.participant-names-vertical {
display: none !important;
}
// Show number instead of icon when small
.vertical .count-badge {
.badge-icon {
display: none;
}
.badge-number {
display: block;
}
}
}
@container hidden-indicator (max-width: 120px) {
.count-label {
.vertical .count-info {
display: none;
}
.count-section {
gap: 0;
}
.participant-names-vertical {
display: none !important;
}
// Show number instead of icon when small
.vertical .count-badge {
.badge-icon {
display: none;
}
.badge-number {
display: block;
}
}
}
// Para espacios extra pequeños, optimizar padding y sombras
@ -199,19 +232,19 @@
padding: 4px;
}
.count-badge-vertical {
.count-badge {
box-shadow: 0 2px 6px rgba(102, 126, 234, 0.4);
}
}
@media (max-width: 768px), (max-height: 500px) {
.count-badge-horizontal {
.count-badge {
min-width: 40px;
height: 40px;
font-size: 18px;
}
.text-horizontal {
.count-info {
.count-number {
font-size: 20px;
}
@ -225,31 +258,14 @@
gap: 12px;
}
.header-section {
.participants-icon {
width: 32px;
height: 32px;
}
.title {
font-size: 14px;
}
}
.count-section {
.count-badge-vertical {
.count-badge {
width: 64px;
height: 64px;
font-size: 28px;
}
.count-label {
.count-info {
font-size: 13px;
}
}
.info-section {
padding: 10px 12px;
.info-text {
font-size: 11px;
}
}
}

View File

@ -21,6 +21,11 @@ export class HiddenParticipantsIndicatorComponent {
*/
count = input<number>(0);
/**
* Names of hidden participants (used in topbar mode to show who is hidden)
*/
hiddenParticipantNames = input<string[]>([]);
mode = input<'topbar' | 'standard'>('standard');
protected isTopBarMode = computed(() => this.mode() === 'topbar');
@ -35,6 +40,29 @@ export class HiddenParticipantsIndicatorComponent {
});
protected descriptionText = computed(() => {
return this.count() === 1 ? 'participant not currently visible' : 'participants not currently visible';
return `hidden participant${this.count() === 1 ? '' : 's'}`;
});
/**
* Get formatted participant names for display in topbar mode
* Shows up to 3 names, then "and X more" if there are additional participants
*/
protected formattedParticipantNames = computed(() => {
const names = this.hiddenParticipantNames();
const total = this.count();
if (!names || names.length === 0) {
return '';
}
const maxNamesToShow = 3;
const visibleNames = names.slice(0, maxNamesToShow);
const remaining = total - visibleNames.length;
if (remaining > 0) {
return `${visibleNames.join(', ')} and ${remaining} more`;
}
return visibleNames.join(', ');
});
}

View File

@ -28,6 +28,7 @@
<ov-hidden-participants-indicator
[count]="hiddenParticipantsCount()"
[mode]="showTopBarHiddenParticipantsIndicator() ? 'topbar' : 'standard'"
[hiddenParticipantNames]="hiddenParticipantNames()"
/>
</div>
</ng-container>

View File

@ -51,6 +51,13 @@ export class MeetingCustomLayoutComponent {
return Math.max(0, total - visible);
});
protected readonly hiddenParticipantNames = computed(() => {
const visibleIds = new Set(this.visibleRemoteParticipants().map((p) => p.identity));
return this.remoteParticipants()
.filter((p) => !visibleIds.has(p.identity))
.map((p) => p.name || 'Unknown');
});
/**
* Indicates whether to show the hidden participants indicator in the top bar
* when in smart mosaic mode.