mirror of https://github.com/OpenVidu/openvidu.git
ov-components: enhance toolbar media buttons for responsive design and improved visibility
parent
677a9129a2
commit
2a9f3a62fa
|
@ -1,11 +1,12 @@
|
||||||
<!-- Camera button -->
|
<!-- Camera button -->
|
||||||
@if (showCameraButton) {
|
@if (showCameraButtonDirect()) {
|
||||||
<button
|
<button
|
||||||
id="camera-btn"
|
id="camera-btn"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="onCameraToggle()"
|
(click)="onCameraToggle()"
|
||||||
[disabled]="isConnectionLost || !hasVideoDevices || cameraMuteChanging"
|
[disabled]="isConnectionLost || !hasVideoDevices || cameraMuteChanging"
|
||||||
[class.disabled]="!isCameraEnabled"
|
[class.disabled]="!isCameraEnabled"
|
||||||
|
[class.mobile-btn]="isMobileView()"
|
||||||
[matTooltip]="isCameraEnabled ? ('TOOLBAR.STOP_VIDEO' | translate) : ('TOOLBAR.START_VIDEO' | translate)"
|
[matTooltip]="isCameraEnabled ? ('TOOLBAR.STOP_VIDEO' | translate) : ('TOOLBAR.START_VIDEO' | translate)"
|
||||||
[matTooltipDisabled]="!hasVideoDevices"
|
[matTooltipDisabled]="!hasVideoDevices"
|
||||||
>
|
>
|
||||||
|
@ -16,13 +17,14 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Microphone button -->
|
<!-- Microphone button -->
|
||||||
@if (showMicrophoneButton) {
|
@if (showMicrophoneButtonDirect()) {
|
||||||
<button
|
<button
|
||||||
id="mic-btn"
|
id="mic-btn"
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
(click)="onMicrophoneToggle()"
|
(click)="onMicrophoneToggle()"
|
||||||
[disabled]="isConnectionLost || !hasAudioDevices || microphoneMuteChanging"
|
[disabled]="isConnectionLost || !hasAudioDevices || microphoneMuteChanging"
|
||||||
[class.disabled]="!isMicrophoneEnabled"
|
[class.disabled]="!isMicrophoneEnabled"
|
||||||
|
[class.mobile-btn]="isMobileView()"
|
||||||
[matTooltip]="isMicrophoneEnabled ? ('TOOLBAR.MUTE_AUDIO' | translate) : ('TOOLBAR.UNMUTE_AUDIO' | translate)"
|
[matTooltip]="isMicrophoneEnabled ? ('TOOLBAR.MUTE_AUDIO' | translate) : ('TOOLBAR.UNMUTE_AUDIO' | translate)"
|
||||||
[matTooltipDisabled]="!hasAudioDevices"
|
[matTooltipDisabled]="!hasAudioDevices"
|
||||||
>
|
>
|
||||||
|
@ -32,14 +34,15 @@
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- Enable Screenshare button -->
|
<!-- Screenshare button - Only visible on tablet+ or when active -->
|
||||||
@if (!isMinimal && showScreenshareButton) {
|
@if (showScreenshareButtonDirect()) {
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
id="screenshare-btn"
|
id="screenshare-btn"
|
||||||
[matMenuTriggerFor]="isScreenShareEnabled ? screenshareMenu : null"
|
[matMenuTriggerFor]="isScreenShareEnabled ? screenshareMenu : null"
|
||||||
[disabled]="isConnectionLost"
|
[disabled]="isConnectionLost"
|
||||||
[class.active-btn]="isScreenShareEnabled"
|
[class.active-btn]="isScreenShareEnabled"
|
||||||
|
[class.mobile-btn]="isMobileView()"
|
||||||
matTooltip="{{ isScreenShareEnabled ? ('TOOLBAR.DISABLE_SCREEN' | translate) : ('TOOLBAR.ENABLE_SCREEN' | translate) }}"
|
matTooltip="{{ isScreenShareEnabled ? ('TOOLBAR.DISABLE_SCREEN' | translate) : ('TOOLBAR.ENABLE_SCREEN' | translate) }}"
|
||||||
(click)="!isScreenShareEnabled && onScreenShareToggle()"
|
(click)="!isScreenShareEnabled && onScreenShareToggle()"
|
||||||
>
|
>
|
||||||
|
@ -65,17 +68,42 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- More options button -->
|
<!-- More options button -->
|
||||||
@if (!isMinimal && showMoreOptionsButton) {
|
@if (showMoreOptionsButtonDirect()) {
|
||||||
<button
|
<button
|
||||||
mat-icon-button
|
mat-icon-button
|
||||||
id="more-options-btn"
|
id="more-options-btn"
|
||||||
[matMenuTriggerFor]="settingsMenu"
|
[matMenuTriggerFor]="settingsMenu"
|
||||||
[disabled]="isConnectionLost"
|
[disabled]="isConnectionLost"
|
||||||
|
[class.mobile-btn]="isMobileView()"
|
||||||
|
[matBadge]="hasActiveFeatures() && isMobileView() ? '!' : ''"
|
||||||
|
matBadgeColor="accent"
|
||||||
|
matBadgeSize="small"
|
||||||
|
[matBadgeHidden]="!hasActiveFeatures() || !isMobileView()"
|
||||||
matTooltip="{{ 'TOOLBAR.MORE_OPTIONS' | translate }}"
|
matTooltip="{{ 'TOOLBAR.MORE_OPTIONS' | translate }}"
|
||||||
>
|
>
|
||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<mat-menu #settingsMenu="matMenu" id="more-options-menu">
|
<mat-menu #settingsMenu="matMenu" id="more-options-menu">
|
||||||
|
|
||||||
|
<!-- Dynamic mobile buttons -->
|
||||||
|
@for (button of buttonsInMoreOptions(); track button.key) {
|
||||||
|
@if (button.show) {
|
||||||
|
<button
|
||||||
|
mat-menu-item
|
||||||
|
[id]="button.key + '-btn'"
|
||||||
|
[disabled]="button.disabled"
|
||||||
|
(click)="button.action()"
|
||||||
|
>
|
||||||
|
<mat-icon [color]="button.color || ''">{{ button.icon }}</mat-icon>
|
||||||
|
<span>{{ button.label | translate }}</span>
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
<!-- Divider if there are mobile buttons -->
|
||||||
|
@if (buttonsInMoreOptions().length > 0) {
|
||||||
|
<mat-divider class="divider"></mat-divider>
|
||||||
|
}
|
||||||
<!-- Fullscreen button -->
|
<!-- Fullscreen button -->
|
||||||
@if (showFullscreenButton) {
|
@if (showFullscreenButton) {
|
||||||
<button mat-menu-item id="fullscreen-btn" (click)="onFullscreenToggle()">
|
<button mat-menu-item id="fullscreen-btn" (click)="onFullscreenToggle()">
|
||||||
|
@ -181,8 +209,14 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Leave session button -->
|
<!-- Leave session button -->
|
||||||
@if (showLeaveButton) {
|
@if (showLeaveButtonDirect()) {
|
||||||
<button mat-icon-button (click)="onLeaveClick()" id="leave-btn" matTooltip="{{ 'TOOLBAR.LEAVE' | translate }}">
|
<button
|
||||||
|
mat-icon-button
|
||||||
|
(click)="onLeaveClick()"
|
||||||
|
id="leave-btn"
|
||||||
|
[class.mobile-btn]="isMobileView()"
|
||||||
|
matTooltip="{{ 'TOOLBAR.LEAVE' | translate }}"
|
||||||
|
>
|
||||||
<mat-icon>call_end</mat-icon>
|
<mat-icon>call_end</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,19 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
gap: 2px;
|
||||||
|
|
||||||
.mat-mdc-icon-button {
|
.mat-mdc-icon-button {
|
||||||
--mat-mdc-button-persistent-ripple-color: transparent !important;
|
--mat-mdc-button-persistent-ripple-color: transparent !important;
|
||||||
--mat-mdc-button-ripple-color: transparent !important;
|
--mat-mdc-button-ripple-color: transparent !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Responsive container adjustments
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
gap: 1px;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
mat-icon {
|
mat-icon {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
@ -28,9 +35,19 @@
|
||||||
background-color: var(--ov-primary-action-color);
|
background-color: var(--ov-primary-action-color);
|
||||||
color: var(--ov-secondary-action-color);
|
color: var(--ov-secondary-action-color);
|
||||||
margin: 6px;
|
margin: 6px;
|
||||||
}
|
transition: all 0.2s ease-in-out;
|
||||||
|
|
||||||
#screenshare-menu {
|
// Mobile responsive styles
|
||||||
|
&.mobile-btn {
|
||||||
|
margin: 3px;
|
||||||
|
width: 36px;
|
||||||
|
height: 36px;
|
||||||
|
text-align: justify;
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#disable-screen-button > mat-icon {
|
#disable-screen-button > mat-icon {
|
||||||
|
@ -47,6 +64,13 @@
|
||||||
border-radius: var(--ov-leave-button-radius) !important;
|
border-radius: var(--ov-leave-button-radius) !important;
|
||||||
width: 65px !important;
|
width: 65px !important;
|
||||||
color: #ffffff !important;
|
color: #ffffff !important;
|
||||||
|
|
||||||
|
&.mobile-btn {
|
||||||
|
width: 56px !important;
|
||||||
|
height: 36px !important;
|
||||||
|
margin: 3px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mat-mdc-icon-button[disabled] {
|
.mat-mdc-icon-button[disabled] {
|
||||||
|
@ -94,6 +118,23 @@
|
||||||
border-radius: var(--ov-surface-radius) !important;
|
border-radius: var(--ov-surface-radius) !important;
|
||||||
background-color: var(--ov-surface-color) !important;
|
background-color: var(--ov-surface-color) !important;
|
||||||
box-shadow: var(--ov-border-shadow) !important;
|
box-shadow: var(--ov-border-shadow) !important;
|
||||||
|
|
||||||
|
// Mobile menu improvements
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mobile badge styling
|
||||||
|
::ng-deep .mat-badge-content {
|
||||||
|
background-color: var(--ov-accent-action-color) !important;
|
||||||
|
color: white !important;
|
||||||
|
font-size: 10px !important;
|
||||||
|
font-weight: bold !important;
|
||||||
|
width: 16px !important;
|
||||||
|
height: 16px !important;
|
||||||
|
line-height: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles for XS screens */
|
/* Styles for XS screens */
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
|
import { Component, EventEmitter, Input, Output, TemplateRef, computed, inject } from '@angular/core';
|
||||||
import { RecordingStatus } from '../../../models/recording.model';
|
import { RecordingStatus } from '../../../models/recording.model';
|
||||||
import { BroadcastingStatus } from '../../../models/broadcasting.model';
|
import { BroadcastingStatus } from '../../../models/broadcasting.model';
|
||||||
import { ToolbarAdditionalButtonsPosition } from '../../../models/toolbar.model';
|
import { ToolbarAdditionalButtonsPosition } from '../../../models/toolbar.model';
|
||||||
|
import { ViewportService } from '../../../services/viewport/viewport.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ov-toolbar-media-buttons',
|
selector: 'ov-toolbar-media-buttons',
|
||||||
|
@ -69,6 +70,99 @@ export class ToolbarMediaButtonsComponent {
|
||||||
_recordingStatus = RecordingStatus;
|
_recordingStatus = RecordingStatus;
|
||||||
_broadcastingStatus = BroadcastingStatus;
|
_broadcastingStatus = BroadcastingStatus;
|
||||||
|
|
||||||
|
// Viewport service for responsive behavior
|
||||||
|
private viewportService = inject(ViewportService);
|
||||||
|
|
||||||
|
// Computed properties for responsive button grouping
|
||||||
|
readonly isMobileView = computed(() => this.viewportService.isMobile());
|
||||||
|
readonly isTabletView = computed(() => this.viewportService.isTablet());
|
||||||
|
readonly isDesktopView = computed(() => this.viewportService.isDesktop());
|
||||||
|
|
||||||
|
// Essential buttons that always stay visible
|
||||||
|
readonly showCameraButtonDirect = computed(() =>
|
||||||
|
this.showCameraButton && !this.isMinimal
|
||||||
|
);
|
||||||
|
|
||||||
|
readonly showMicrophoneButtonDirect = computed(() =>
|
||||||
|
this.showMicrophoneButton && !this.isMinimal
|
||||||
|
);
|
||||||
|
|
||||||
|
// Screenshare button - visible on tablet+ or when already active
|
||||||
|
readonly showScreenshareButtonDirect = computed(() =>
|
||||||
|
this.showScreenshareButton &&
|
||||||
|
!this.isMinimal &&
|
||||||
|
(!this.isMobileView() || this.isScreenShareEnabled)
|
||||||
|
);
|
||||||
|
|
||||||
|
// More options button - always visible when not minimal
|
||||||
|
readonly showMoreOptionsButtonDirect = computed(() =>
|
||||||
|
this.showMoreOptionsButton && !this.isMinimal
|
||||||
|
);
|
||||||
|
|
||||||
|
// Leave button - always visible
|
||||||
|
readonly showLeaveButtonDirect = computed(() =>
|
||||||
|
this.showLeaveButton
|
||||||
|
);
|
||||||
|
|
||||||
|
// Buttons that should be moved to "More Options" on mobile
|
||||||
|
readonly buttonsInMoreOptions = computed(() => {
|
||||||
|
const buttons: Array<{
|
||||||
|
key: string;
|
||||||
|
show: boolean;
|
||||||
|
label: string;
|
||||||
|
icon: string;
|
||||||
|
action: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
color?: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
const isMobile = this.isMobileView();
|
||||||
|
|
||||||
|
// On mobile, screenshare goes to more options when not active
|
||||||
|
if (isMobile && this.showScreenshareButton && !this.isScreenShareEnabled) {
|
||||||
|
buttons.push({
|
||||||
|
key: 'screenshare',
|
||||||
|
show: true,
|
||||||
|
label: 'TOOLBAR.ENABLE_SCREEN',
|
||||||
|
icon: 'screen_share',
|
||||||
|
action: () => this.onScreenShareToggle(),
|
||||||
|
disabled: this.isConnectionLost
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace screenshare option when active on mobile
|
||||||
|
if (isMobile && this.showScreenshareButton && this.isScreenShareEnabled) {
|
||||||
|
buttons.push({
|
||||||
|
key: 'screenshare-replace',
|
||||||
|
show: true,
|
||||||
|
label: 'STREAM.REPLACE_SCREEN',
|
||||||
|
icon: 'picture_in_picture',
|
||||||
|
action: () => this.onScreenTrackReplace(),
|
||||||
|
disabled: this.isConnectionLost
|
||||||
|
});
|
||||||
|
|
||||||
|
buttons.push({
|
||||||
|
key: 'screenshare-stop',
|
||||||
|
show: true,
|
||||||
|
label: 'TOOLBAR.DISABLE_SCREEN',
|
||||||
|
icon: 'stop_screen_share',
|
||||||
|
action: () => this.onScreenShareToggle(),
|
||||||
|
disabled: this.isConnectionLost,
|
||||||
|
color: 'warn'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return buttons;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check if there are active features that should show a badge on More Options
|
||||||
|
readonly hasActiveFeatures = computed(() =>
|
||||||
|
this.isScreenShareEnabled ||
|
||||||
|
this.recordingStatus === this._recordingStatus.STARTED ||
|
||||||
|
this.broadcastingStatus === this._broadcastingStatus.STARTED
|
||||||
|
);
|
||||||
|
|
||||||
// Media button outputs
|
// Media button outputs
|
||||||
@Output() cameraToggled = new EventEmitter<void>();
|
@Output() cameraToggled = new EventEmitter<void>();
|
||||||
@Output() microphoneToggled = new EventEmitter<void>();
|
@Output() microphoneToggled = new EventEmitter<void>();
|
||||||
|
|
Loading…
Reference in New Issue