diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.html index 22d33091..5edfb8ae 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.html @@ -1,33 +1,76 @@ -
- person -
-

{{ _participant.name }} - ({{ 'PANEL.PARTICIPANTS.YOU' | translate }}) -

-

{{ _participant | tracksPublishedTypes }}

- - -
- + person +
- - - - + +
+
+ {{ participantDisplayName }} + + {{ 'PANEL.PARTICIPANTS.YOU' | translate }} + +
+ +
+ + {{ _participant | tracksPublishedTypes }} + + + + volume_off + {{ 'PANEL.PARTICIPANTS.MUTED' | translate }} + +
+
+ + +
+ + + + +
+ +
+
+ + +
diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss index 6fa1279f..d3317db5 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.scss @@ -1,68 +1,419 @@ :host { + // Container for the participant item + .participant-container { + position: relative; + display: flex; + align-items: center; + padding: 12px 16px; + border-radius: var(--ov-surface-radius, 8px); + background-color: var(--ov-surface-background, #ffffff); + border-bottom: 1px solid var(--ov-surface-border, #e0e0e0); + transition: all 0.2s ease-in-out; + min-height: 64px; + + // &:hover { + // background-color: var(--ov-surface-hover, #f5f5f5); + // transform: translateY(-1px); + // box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + // } + + &:last-child { + border-bottom: none; + } + + // Loading state + &.loading { + opacity: 0.7; + pointer-events: none; + + &::after { + content: ''; + position: absolute; + top: 50%; + right: 16px; + width: 16px; + height: 16px; + border: 2px solid var(--ov-primary-color, #1976d2); + border-radius: 50%; + border-top-color: transparent; + animation: spin 1s linear infinite; + } + } + + // Focus state for keyboard navigation + &:focus-within { + outline: 2px solid var(--ov-primary-color, #1976d2); + outline-offset: 2px; + } + } + + // Avatar styling with improved design .participant-avatar { - display: inherit; + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; border-radius: var(--ov-surface-radius); - margin: auto !important; - padding: 10px; - color: #000000; + margin-right: 12px; + padding: 0; + color: #ffffff; + font-weight: 500; + flex-shrink: 0; + position: relative; + overflow: hidden; + + mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + z-index: 1; + } } - .participant-subtitle { - font-style: italic; - font-size: 11px !important; - margin: 0; - color: var(--ov-text-surface-color); + // Main content area + .participant-content { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; // Allows text truncation + margin-right: 8px; } + + // Participant name styling .participant-name { - font-weight: bold !important; - color: var(--ov-text-surface-color); + font-weight: 600 !important; + font-size: 14px; + line-height: 1.2; + color: var(--ov-text-primary, #212121); + margin: 0 0 4px 0; + display: flex; + align-items: center; + gap: 8px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + // Local participant indicator + .local-indicator { + font-size: 10px; + font-weight: 600; + color: var(--ov-primary-color, #1976d2); + background-color: var(--ov-primary-light, #e3f2fd); + padding: 4px 8px; + border-radius: var(--ov-surface-radius); + text-transform: uppercase; + letter-spacing: 0.5px; + flex-shrink: 0; + border: 1px solid var(--ov-primary-color, #1976d2); + } } + // Subtitle styling + .participant-subtitle { + font-style: normal; + font-size: 12px !important; + font-weight: 400; + margin: 0; + color: var(--ov-text-secondary, #757575); + line-height: 1.3; + display: flex; + align-items: center; + gap: 6px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + // Status indicators + .status-indicator { + display: inline-flex; + align-items: center; + gap: 3px; + + mat-icon { + font-size: 12px; + width: 12px; + height: 12px; + } + + // Different colors for different statuses + &.camera-on { + color: var(--ov-success-color, #4caf50); + } + + &.camera-off { + color: var(--ov-warning-color, #ff9800); + } + + &.microphone-muted { + color: var(--ov-error-color, #d32f2f); + } + } + } + + // Action buttons container .participant-action-buttons { display: flex; + align-items: center; + gap: 4px; + flex-shrink: 0; + margin-left: auto; } - ::ng-deep .participant-action-buttons > *:not(#mute-btn) { - display: contents; + // Mute button styling + #mute-btn { + width: 32px; + height: 32px; + border-radius: 50%; + color: var(--ov-text-secondary, #757575); + background-color: transparent; + transition: all 0.2s ease-in-out; + display: flex; + align-items: center; + justify-content: center; + position: relative; + + &:hover { + background-color: var(--ov-surface-hover, #f5f5f5); + color: var(--ov-text-primary, #212121); + transform: scale(1.1); + } + + &:focus { + outline: 2px solid var(--ov-primary-color, #1976d2); + outline-offset: 2px; + } + + &:disabled { + opacity: 0.5; + pointer-events: none; + } + + &.warn-btn { + color: var(--ov-error-color, #d32f2f); + background-color: var(--ov-error-light, #ffebee); + + &:hover { + background-color: var(--ov-error-color, #d32f2f); + color: #ffffff; + } + + // Pulsing animation for muted state + animation: pulse 2s infinite; + } + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } } - ::ng-deep .participant-action-buttons > *:not(#mute-btn) > * { - margin: auto; + // After local participant content area + .after-local-content { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--ov-surface-border, #e0e0e0); + animation: fadeIn 0.3s ease-in-out; + background-color: var(--ov-surface-alt, #fafafa); + border-radius: var(--ov-surface-radius, 8px); + padding: 12px; + } + + // External item elements styling + .external-elements { + display: flex; + align-items: center; + gap: 4px; + + // Custom styling for external buttons + ::ng-deep button { + transition: all 0.2s ease-in-out; + + &:hover { + transform: scale(1.05); + } + } + } + + // Material Design overrides for better integration + mat-list { + padding: 0; } ::ng-deep .mat-mdc-list-item { - height: max-content !important; - padding-bottom: 10px !important; - } - - ::ng-deep .mat-mdc-list-item:hover { - color: #000000 !important; - } - ::ng-deep .mat-mdc-list-item:hover .mat-mdc-list-item-title { - color: var(--ov-text-surface-color) !important; - } - - mat-list { - padding: 3px; + height: auto !important; + padding: 0 !important; + min-height: auto !important; + border-radius: var(--ov-surface-radius, 8px); } ::ng-deep .mdc-list-item__content { - padding-left: 10px !important; - align-self: center !important; + padding: 0 !important; + align-self: stretch !important; + width: 100%; } ::ng-deep .mat-mdc-list-base { --mdc-list-list-item-hover-label-text-color: unset; --mdc-list-list-item-hover-leading-icon-color: unset; + padding: 0; } - #mute-btn { - border-radius: 50%; - color: var(--ov-text-surface-color); + ::ng-deep .mat-mdc-list-item:hover { + background-color: transparent !important; } - .warn-btn { - /* background-color: var(--ov-error-color) !important; */ - color: var(--ov-error-color); + // Animations + @keyframes fadeIn { + from { + opacity: 0; + transform: translateY(-8px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + @keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } + } + + @keyframes pulse { + 0%, + 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } + } + + // Responsive design + @media (max-width: 768px) { + .participant-container { + padding: 10px 12px; + min-height: 56px; + } + + .participant-avatar { + width: 36px; + height: 36px; + margin-right: 10px; + + mat-icon { + font-size: 18px; + width: 18px; + height: 18px; + } + + &::after { + width: 10px; + height: 10px; + bottom: 1px; + right: 1px; + } + } + + .participant-name { + font-size: 13px; + + .local-indicator { + font-size: 9px; + padding: 2px 6px; + } + } + + .participant-subtitle { + font-size: 11px !important; + } + + #mute-btn { + width: 28px; + height: 28px; + + mat-icon { + font-size: 16px; + width: 16px; + height: 16px; + } + } + + .after-local-content { + margin-top: 10px; + padding-top: 10px; + padding: 10px; + } + } + + // High contrast mode support + @media (prefers-contrast: high) { + .participant-container { + border: 2px solid var(--ov-text-primary, #212121); + } + + .participant-avatar { + border: 2px solid var(--ov-surface-background, #ffffff); + } + + .local-indicator { + border-width: 2px; + } + } + + // Reduced motion support + @media (prefers-reduced-motion: reduce) { + .participant-container, + .participant-avatar, + #mute-btn, + .after-local-content, + .external-elements ::ng-deep button { + transition: none; + animation: none; + } + + .participant-container:hover { + transform: none; + } + + .participant-avatar:hover, + #mute-btn:hover, + .external-elements ::ng-deep button:hover { + transform: none; + } + + #mute-btn.warn-btn { + animation: none; + } + } + + // Dark theme support + @media (prefers-color-scheme: dark) { + .participant-container { + background-color: var(--ov-surface-background, #424242); + border-bottom-color: var(--ov-surface-border, #616161); + + &:hover { + background-color: var(--ov-surface-hover, #484848); + } + } + + .participant-name { + color: var(--ov-text-primary, #ffffff); + } + + .participant-subtitle { + color: var(--ov-text-secondary, #cccccc); + } + + .after-local-content { + background-color: var(--ov-surface-alt, #373737); + } } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.ts index 5d4ebe5e..35fc5026 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/participants-panel/participant-panel-item/participant-panel-item.component.ts @@ -1,17 +1,17 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core'; import { Subscription } from 'rxjs'; import { ParticipantPanelItemElementsDirective } from '../../../../directives/template/openvidu-components-angular.directive'; +// import { ParticipantPanelAfterLocalParticipantDirective } from '../../../../directives/template/internals.directive'; import { ParticipantModel } from '../../../../models/participant.model'; import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service'; import { ParticipantService } from '../../../../services/participant/participant.service'; import { TemplateManagerService, ParticipantPanelItemTemplateConfiguration } from '../../../../services/template/template-manager.service'; /** - * * The **ParticipantPanelItemComponent** is hosted inside of the {@link ParticipantsPanelComponent}. - * It is in charge of displaying the participants information inside of the ParticipansPanelComponent. + * It displays participant information with enhanced UI/UX, including support for custom content + * injection through structural directives. */ - @Component({ selector: 'ov-participant-panel-item', templateUrl: './participant-panel-item.component.html', @@ -42,6 +42,17 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy { } } + // /** + // * @ignore + // */ + // @ContentChild(ParticipantPanelAfterLocalParticipantDirective) + // set externalAfterLocalParticipant(afterLocalParticipant: ParticipantPanelAfterLocalParticipantDirective) { + // this._externalAfterLocalParticipant = afterLocalParticipant; + // if (afterLocalParticipant) { + // this.updateTemplatesAndMarkForCheck(); + // } + // } + /** * @internal * Template configuration managed by the service @@ -50,21 +61,29 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy { // Store directive references for template setup private _externalItemElements?: ParticipantPanelItemElementsDirective; + // private _externalAfterLocalParticipant?: ParticipantPanelAfterLocalParticipantDirective; /** * The participant to be displayed - * @ignore */ @Input() set participant(participant: ParticipantModel) { this._participant = participant; + this.cd.markForCheck(); } /** - * @ignore + * @internal + * Current participant being displayed */ _participant: ParticipantModel; + /** + * Whether to show the mute button for remote participants + */ + @Input() + muteButton: boolean = true; + /** * @ignore */ @@ -91,14 +110,49 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy { } /** - * @ignore + * Toggles the mute state of a remote participant */ toggleMuteForcibly() { - if (this._participant) { + if (this._participant && !this._participant.isLocal) { this.participantService.setRemoteMutedForcibly(this._participant.sid, !this._participant.isMutedForcibly); } } + /** + * Gets the template for content after local participant + */ + // get afterLocalParticipantTemplate(): TemplateRef | undefined { + // return this._externalAfterLocalParticipant?.template; + // } + + /** + * Checks if the current participant is the local participant + */ + get isLocalParticipant(): boolean { + return this._participant?.isLocal || false; + } + + /** + * Gets the participant's display name + */ + get participantDisplayName(): string { + return this._participant?.name || ''; + } + + /** + * Checks if external elements are available + */ + get hasExternalElements(): boolean { + return !!this.participantPanelItemElementsTemplate; + } + + /** + * Checks if after local participant content is available + */ + // get hasAfterLocalContent(): boolean { + // return this.isLocalParticipant && !!this.afterLocalParticipantTemplate; + // } + /** * @internal * Sets up all templates using the template manager service diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json index 90ff3211..47e5c9f8 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json @@ -91,7 +91,9 @@ "MICROPHONE": "麦克风", "SCREEN": "屏幕", "NO_STREAMS": "无", - "YOU": "你" + "YOU": "你", + "MUTE": "静音", + "UNMUTE": "取消静音" }, "SETTINGS": { "TITLE": "设置", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json index c87ce8cc..d79529d7 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json @@ -90,7 +90,9 @@ "MICROPHONE": "MIKROFON", "SCREEN": "BILDSCHIRM", "NO_STREAMS": "KEINE", - "YOU": "Sie" + "YOU": "Sie", + "MUTE": "Stummschalten", + "UNMUTE": "Stummschaltung aufheben" }, "SETTINGS": { "TITLE": "Einstellungen", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json index c8cac44e..8ea2ea0f 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json @@ -91,7 +91,9 @@ "MICROPHONE": "MICROPHONE", "SCREEN": "SCREEN", "NO_STREAMS": "NONE", - "YOU": "You" + "YOU": "You", + "MUTE": "Mute", + "UNMUTE": "Unmute" }, "SETTINGS": { "TITLE": "Settings", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json index 3eec3fdb..70457313 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json @@ -91,7 +91,9 @@ "MICROPHONE": "MICRÓFONO", "SCREEN": "PANTALLA", "NO_STREAMS": "NINGUNO", - "YOU": "Tú" + "YOU": "Tú", + "MUTE": "Silenciar", + "UNMUTE": "Activar audio" }, "SETTINGS": { "TITLE": "Configuración", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json index 664ee167..ecb3fa72 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json @@ -91,7 +91,9 @@ "MICROPHONE": "MICROPHONE", "SCREEN": "ÉCRAN", "NO_STREAMS": "PAS_DE_FLUX", - "YOU": "Vous" + "YOU": "Vous", + "MUTE": "Couper le son", + "UNMUTE": "Désactiver le son" }, "SETTINGS": { "TITLE": "Paramètres", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json index 26ad7ea7..172772eb 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json @@ -91,7 +91,9 @@ "MICROPHONE": "माइक्रोफ़ोन", "SCREEN": "स्क्रीन", "NO_STREAMS": "कोई_स्ट्रीम_नहीं", - "YOU": "आप" + "YOU": "आप", + "MUTE": "मौन", + "UNMUTE": "अनमौन" }, "SETTINGS": { "TITLE": "सेटिंग्स", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json index f377f835..cd5b36fa 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json @@ -91,7 +91,9 @@ "MICROPHONE": "MICROFONO", "SCREEN": "SCREEN", "NO_STREAMS": "NESSUNO", - "YOU": "Tu" + "YOU": "Tu", + "MUTE": "Disattiva l'audio", + "UNMUTE": "Attiva l'audio" }, "SETTINGS": { "TITLE": "Impostazioni", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json index ab8e2ab3..5333a272 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json @@ -91,7 +91,9 @@ "MICROPHONE": "マイクロフォン", "SCREEN": "スクリーン", "NO_STREAMS": "ストリームなし", - "YOU": "あなた" + "YOU": "あなた", + "MUTE": "ミュート", + "UNMUTE": "ミュート解除" }, "SETTINGS": { "TITLE": "設定", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json index b935daec..1eec015a 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json @@ -91,7 +91,9 @@ "MICROPHONE": "MICROFOON", "SCREEN": "SCHERM", "NO_STREAMS": "GEEN", - "YOU": "Jij" + "YOU": "Jij", + "MUTE": "Dempen", + "UNMUTE": "Dempen opheffen" }, "SETTINGS": { "TITLE": "Instellingen", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json index c8c37405..204035d3 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json @@ -91,7 +91,9 @@ "MICROPHONE": "MICROFONE", "SCREEN": "TELA", "NO_STREAMS": "NENHUM", - "YOU": "Você (eu)" + "YOU": "Você (eu)", + "MUTE": "Silenciar", + "UNMUTE": "Ativar som" }, "SETTINGS": { "TITLE": "Configurações",