frontend: Controls captions button based on admin config
Updates the captions button to respect the global admin configuration. The button now displays a disabled state and tooltip when captions are disabled globally, preventing users from toggling them. The UI is updated to show disabled state and a specific subtitle off icon, reflecting whether captions are enabled at the system level.
This commit is contained in:
parent
4ac182c244
commit
3f91e281b3
@ -2,10 +2,20 @@
|
||||
@if (showCaptionsButton()) {
|
||||
@if (isMobile()) {
|
||||
<!-- On mobile, the captions button will be inside a menu -->
|
||||
<button id="captions-button" mat-menu-item (click)="onCaptionsClick()" [disableRipple]="true">
|
||||
<button
|
||||
id="captions-button"
|
||||
mat-menu-item
|
||||
(click)="onCaptionsClick()"
|
||||
[disabled]="isCaptionsButtonDisabled()"
|
||||
[disableRipple]="true"
|
||||
>
|
||||
<mat-icon class="material-symbols-outlined">subtitles</mat-icon>
|
||||
<span class="button-text">
|
||||
{{ areCaptionsEnabledByUser() ? 'Disable live captions' : 'Enable live captions' }}
|
||||
@if (isCaptionsButtonDisabled()) {
|
||||
Live captions (disabled by admin)
|
||||
} @else {
|
||||
{{ areCaptionsEnabledByUser() ? 'Disable live captions' : 'Enable live captions' }}
|
||||
}
|
||||
</span>
|
||||
</button>
|
||||
} @else {
|
||||
@ -14,10 +24,22 @@
|
||||
[ngClass]="areCaptionsEnabledByUser() ? 'active' : ''"
|
||||
mat-icon-button
|
||||
(click)="onCaptionsClick()"
|
||||
[disabled]="isCaptionsButtonDisabled()"
|
||||
[disabledInteractive]="isCaptionsButtonDisabled()"
|
||||
[disableRipple]="true"
|
||||
[matTooltip]="areCaptionsEnabledByUser() ? 'Disable live captions' : 'Enable live captions'"
|
||||
[matTooltip]="
|
||||
isCaptionsButtonDisabled()
|
||||
? 'Live captions are disabled by admin'
|
||||
: areCaptionsEnabledByUser()
|
||||
? 'Disable live captions'
|
||||
: 'Enable live captions'
|
||||
"
|
||||
>
|
||||
<mat-icon>subtitles</mat-icon>
|
||||
@if (isCaptionsButtonDisabled()) {
|
||||
<mat-icon>subtitles_off</mat-icon>
|
||||
} @else {
|
||||
<mat-icon>subtitles</mat-icon>
|
||||
}
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,3 +3,7 @@
|
||||
#captions-button.active {
|
||||
background-color: var(--ov-accent-action-color);
|
||||
}
|
||||
|
||||
#captions-button.mat-mdc-button-disabled {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@ -34,9 +34,19 @@ export class MeetingToolbarExtraButtonsComponent {
|
||||
protected showCopyLinkButton = computed(() => this.meetingContextService.canModerateRoom());
|
||||
|
||||
/**
|
||||
* Whether to show the captions button
|
||||
* Captions status based on room and global configuration
|
||||
*/
|
||||
protected showCaptionsButton = computed(() => this.meetingContextService.areCaptionsAllowed());
|
||||
protected captionsStatus = computed(() => this.meetingContextService.getCaptionsStatus());
|
||||
|
||||
/**
|
||||
* Whether to show the captions button (visible when not HIDDEN)
|
||||
*/
|
||||
protected showCaptionsButton = computed(() => this.captionsStatus() !== 'HIDDEN');
|
||||
|
||||
/**
|
||||
* Whether captions button is disabled (true when DISABLED_WITH_WARNING)
|
||||
*/
|
||||
protected isCaptionsButtonDisabled = computed(() => this.captionsStatus() === 'DISABLED_WITH_WARNING');
|
||||
|
||||
/**
|
||||
* Whether the device is mobile (affects button style)
|
||||
@ -56,6 +66,11 @@ export class MeetingToolbarExtraButtonsComponent {
|
||||
}
|
||||
|
||||
onCaptionsClick(): void {
|
||||
// Don't allow toggling if captions are disabled at system level
|
||||
if (this.isCaptionsButtonDisabled()) {
|
||||
this.log.w('Captions are disabled at system level (MEET_CAPTIONS_ENABLED=false)');
|
||||
return;
|
||||
}
|
||||
this.captionService.areCaptionsEnabledByUser() ? this.captionService.disable() : this.captionService.enable();
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,7 +120,10 @@ export class MeetingComponent implements OnInit {
|
||||
if (token && this.showLobby) {
|
||||
// The meeting view must be shown before loading the appearance config
|
||||
this.showLobby = false;
|
||||
await this.configService.loadRoomsAppearanceConfig();
|
||||
await Promise.all([
|
||||
this.configService.loadRoomsAppearanceConfig(),
|
||||
this.configService.loadCaptionsConfig()
|
||||
]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -187,11 +187,11 @@ export class MeetingContextService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether captions feature is allowed in the room
|
||||
* @returns true if captions feature is allowed, false otherwise
|
||||
* Returns the captions status based on room and global configuration
|
||||
* @returns CaptionsStatus ('HIDDEN' | 'ENABLED' | 'DISABLED_WITH_WARNING')
|
||||
*/
|
||||
areCaptionsAllowed(): boolean {
|
||||
return this.featureConfigService.features().showCaptions;
|
||||
getCaptionsStatus() {
|
||||
return this.featureConfigService.features().captionsStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -16,6 +16,11 @@ export enum Edition {
|
||||
PRO = 'PRO'
|
||||
}
|
||||
|
||||
/**
|
||||
* Status of captions feature based on room and global configuration
|
||||
*/
|
||||
export type CaptionsStatus = 'HIDDEN' | 'ENABLED' | 'DISABLED_WITH_WARNING';
|
||||
|
||||
/**
|
||||
* Interface that defines all available features in the application
|
||||
*/
|
||||
@ -31,7 +36,7 @@ export interface ApplicationFeatures {
|
||||
showRecordingPanel: boolean;
|
||||
showChat: boolean;
|
||||
showBackgrounds: boolean;
|
||||
showCaptions: boolean;
|
||||
captionsStatus: CaptionsStatus;
|
||||
showParticipantList: boolean;
|
||||
showSettings: boolean;
|
||||
showFullscreen: boolean;
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
import { computed, Injectable, signal } from '@angular/core';
|
||||
import {
|
||||
MeetAppearanceConfig,
|
||||
MeetRoomCaptionsConfig,
|
||||
MeetRoomConfig,
|
||||
MeetRoomMemberPermissions,
|
||||
MeetRoomMemberRole,
|
||||
TrackSource
|
||||
} from '@openvidu-meet/typings';
|
||||
import { LoggerService } from 'openvidu-components-angular';
|
||||
import { ApplicationFeatures } from '../models/app.model';
|
||||
import { ApplicationFeatures, CaptionsStatus } from '../models/app.model';
|
||||
|
||||
/**
|
||||
* Base configuration for default features
|
||||
@ -22,7 +23,7 @@ const DEFAULT_FEATURES: ApplicationFeatures = {
|
||||
showRecordingPanel: true,
|
||||
showChat: true,
|
||||
showBackgrounds: true,
|
||||
showCaptions: false,
|
||||
captionsStatus: 'ENABLED',
|
||||
showParticipantList: true,
|
||||
showSettings: true,
|
||||
showFullscreen: true,
|
||||
@ -52,6 +53,7 @@ export class FeatureConfigurationService {
|
||||
protected roomMemberRole = signal<MeetRoomMemberRole | undefined>(undefined);
|
||||
protected roomMemberPermissions = signal<MeetRoomMemberPermissions | undefined>(undefined);
|
||||
protected appearanceConfig = signal<MeetAppearanceConfig | undefined>(undefined);
|
||||
protected captionsGlobalConfig = signal<boolean>(false);
|
||||
|
||||
// Computed signal to derive features based on current configurations
|
||||
public readonly features = computed<ApplicationFeatures>(() =>
|
||||
@ -59,7 +61,8 @@ export class FeatureConfigurationService {
|
||||
this.roomConfig(),
|
||||
this.roomMemberRole(),
|
||||
this.roomMemberPermissions(),
|
||||
this.appearanceConfig()
|
||||
this.appearanceConfig(),
|
||||
this.captionsGlobalConfig()
|
||||
)
|
||||
);
|
||||
|
||||
@ -99,6 +102,14 @@ export class FeatureConfigurationService {
|
||||
this.appearanceConfig.set(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates captions global config
|
||||
*/
|
||||
setCaptionsGlobalConfig(enabled: boolean): void {
|
||||
this.log.d('Updating captions global config', enabled);
|
||||
this.captionsGlobalConfig.set(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Core logic to calculate features based on all configurations
|
||||
*/
|
||||
@ -106,7 +117,8 @@ export class FeatureConfigurationService {
|
||||
roomConfig?: MeetRoomConfig,
|
||||
role?: MeetRoomMemberRole,
|
||||
permissions?: MeetRoomMemberPermissions,
|
||||
appearanceConfig?: MeetAppearanceConfig
|
||||
appearanceConfig?: MeetAppearanceConfig,
|
||||
captionsGlobalEnabled: boolean = false
|
||||
): ApplicationFeatures {
|
||||
// Start with default configuration
|
||||
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
|
||||
@ -116,7 +128,7 @@ export class FeatureConfigurationService {
|
||||
features.showRecordingPanel = roomConfig.recording.enabled;
|
||||
features.showChat = roomConfig.chat.enabled;
|
||||
features.showBackgrounds = roomConfig.virtualBackground.enabled;
|
||||
features.showCaptions = roomConfig.captions?.enabled ?? false;
|
||||
features.captionsStatus = this.computeCaptionsStatus(roomConfig.captions, captionsGlobalEnabled);
|
||||
}
|
||||
|
||||
// Apply room member permissions (these can restrict enabled features)
|
||||
@ -164,6 +176,22 @@ export class FeatureConfigurationService {
|
||||
return features;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the captions status based on room and global configuration
|
||||
* HIDDEN: room config disabled
|
||||
* ENABLED: room config enabled AND global config enabled
|
||||
* DISABLED_WITH_WARNING: room config enabled BUT global config disabled
|
||||
*/
|
||||
protected computeCaptionsStatus(
|
||||
roomCaptionsConfig: MeetRoomCaptionsConfig | undefined,
|
||||
globalEnabled: boolean
|
||||
): CaptionsStatus {
|
||||
if (!roomCaptionsConfig?.enabled) {
|
||||
return 'HIDDEN';
|
||||
}
|
||||
return globalEnabled ? 'ENABLED' : 'DISABLED_WITH_WARNING';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets all configurations to their initial values
|
||||
*/
|
||||
@ -172,5 +200,6 @@ export class FeatureConfigurationService {
|
||||
this.roomMemberRole.set(undefined);
|
||||
this.roomMemberPermissions.set(undefined);
|
||||
this.appearanceConfig.set(undefined);
|
||||
this.captionsGlobalConfig.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,4 +76,19 @@ export class GlobalConfigService {
|
||||
const path = `${this.GLOBAL_CONFIG_API}/rooms/appearance`;
|
||||
await this.httpService.putRequest(path, { appearance: config });
|
||||
}
|
||||
|
||||
private async getCaptionsConfig(): Promise<{ enabled: boolean }> {
|
||||
const path = `${this.GLOBAL_CONFIG_API}/captions`;
|
||||
return await this.httpService.getRequest<{ enabled: boolean }>(path);
|
||||
}
|
||||
|
||||
async loadCaptionsConfig(): Promise<void> {
|
||||
try {
|
||||
const config = await this.getCaptionsConfig();
|
||||
this.featureConfService.setCaptionsGlobalConfig(config.enabled);
|
||||
} catch (error) {
|
||||
this.log.e('Error loading captions config:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user