frontend: add role permissions step in room wizard
This commit is contained in:
parent
177134d2a6
commit
4794b30ff9
@ -71,6 +71,9 @@
|
||||
@case ('config') {
|
||||
<ov-room-config></ov-room-config>
|
||||
}
|
||||
@case ('rolePermissions') {
|
||||
<ov-role-permissions></ov-role-permissions>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import { RoomBasicCreationComponent } from '../room-basic-creation/room-basic-cr
|
||||
import { RecordingConfigComponent } from './steps/recording-config/recording-config.component';
|
||||
import { RecordingLayoutComponent } from './steps/recording-layout/recording-layout.component';
|
||||
import { RecordingTriggerComponent } from './steps/recording-trigger/recording-trigger.component';
|
||||
import { RolePermissionsComponent } from './steps/role-permissions/role-permissions.component';
|
||||
import { RoomConfigComponent } from './steps/room-config/room-config.component';
|
||||
import { RoomWizardRoomDetailsComponent } from './steps/room-details/room-details.component';
|
||||
|
||||
@ -33,7 +34,8 @@ import { RoomWizardRoomDetailsComponent } from './steps/room-details/room-detail
|
||||
RecordingConfigComponent,
|
||||
RecordingTriggerComponent,
|
||||
RecordingLayoutComponent,
|
||||
RoomConfigComponent
|
||||
RoomConfigComponent,
|
||||
RolePermissionsComponent
|
||||
],
|
||||
templateUrl: './room-wizard.component.html',
|
||||
styleUrl: './room-wizard.component.scss'
|
||||
|
||||
@ -0,0 +1,121 @@
|
||||
<div class="role-permissions-step fade-in">
|
||||
<!-- Header Section -->
|
||||
<header class="step-header">
|
||||
<mat-icon class="step-icon">admin_panel_settings</mat-icon>
|
||||
<div class="step-title-group">
|
||||
<h3 class="step-title">Role Permissions</h3>
|
||||
<p class="step-description">Configure what each role is allowed to do within the room.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Tabs Section -->
|
||||
<main class="step-content">
|
||||
<form [formGroup]="rolePermissionsForm">
|
||||
<mat-tab-group class="roles-tab-group" animationDuration="200ms" [disableRipple]="true">
|
||||
<!-- Moderator Tab -->
|
||||
<mat-tab label="Moderator">
|
||||
<div class="tab-content" [formGroup]="moderatorForm">
|
||||
<!-- Anonymous Access -->
|
||||
<div class="anonymous-access-row">
|
||||
<div class="permission-info">
|
||||
<mat-icon class="permission-icon anonymous-icon">no_accounts</mat-icon>
|
||||
<div class="permission-text">
|
||||
<span class="permission-label">Anonymous Access</span>
|
||||
<span class="permission-description"
|
||||
>Allow users to join as Moderator without logging in</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<mat-slide-toggle
|
||||
formControlName="anonymousEnabled"
|
||||
color="primary"
|
||||
class="permission-toggle"
|
||||
></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<!-- Individual Permissions -->
|
||||
<div class="permissions-section">
|
||||
<p class="section-label">INDIVIDUAL PERMISSIONS</p>
|
||||
|
||||
@for (group of permissionGroups; track group.label) {
|
||||
<div class="permission-group">
|
||||
@for (permission of group.permissions; track permission.key) {
|
||||
<div class="permission-row">
|
||||
<div class="permission-info">
|
||||
<mat-icon class="permission-icon material-symbols-outlined">{{
|
||||
permission.icon
|
||||
}}</mat-icon>
|
||||
<div class="permission-text">
|
||||
<span class="permission-label">{{ permission.label }}</span>
|
||||
<span class="permission-description">{{
|
||||
permission.description
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-slide-toggle
|
||||
[formControlName]="permission.key"
|
||||
color="primary"
|
||||
class="permission-toggle"
|
||||
></mat-slide-toggle>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<!-- Speaker Tab -->
|
||||
<mat-tab label="Speaker">
|
||||
<div class="tab-content" [formGroup]="speakerForm">
|
||||
<!-- Anonymous Access -->
|
||||
<div class="anonymous-access-row">
|
||||
<div class="permission-info">
|
||||
<mat-icon class="permission-icon anonymous-icon">no_accounts</mat-icon>
|
||||
<div class="permission-text">
|
||||
<span class="permission-label">Anonymous Access</span>
|
||||
<span class="permission-description"
|
||||
>Allow users to join as Speaker without logging in</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<mat-slide-toggle
|
||||
formControlName="anonymousEnabled"
|
||||
color="primary"
|
||||
class="permission-toggle"
|
||||
></mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<!-- Individual Permissions -->
|
||||
<div class="permissions-section">
|
||||
<p class="section-label">INDIVIDUAL PERMISSIONS</p>
|
||||
|
||||
@for (group of permissionGroups; track group.label) {
|
||||
<div class="permission-group">
|
||||
@for (permission of group.permissions; track permission.key) {
|
||||
<div class="permission-row">
|
||||
<div class="permission-info">
|
||||
<mat-icon class="permission-icon">{{ permission.icon }}</mat-icon>
|
||||
<div class="permission-text">
|
||||
<span class="permission-label">{{ permission.label }}</span>
|
||||
<span class="permission-description">{{
|
||||
permission.description
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-slide-toggle
|
||||
[formControlName]="permission.key"
|
||||
color="primary"
|
||||
class="permission-toggle"
|
||||
></mat-slide-toggle>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</form>
|
||||
</main>
|
||||
</div>
|
||||
@ -0,0 +1,212 @@
|
||||
@use '../../../../../../../../../../src/assets/styles/design-tokens';
|
||||
|
||||
@mixin permission-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
padding: var(--ov-meet-spacing-sm) var(--ov-meet-spacing-md);
|
||||
border-radius: var(--ov-meet-radius-sm);
|
||||
transition: background-color var(--ov-meet-transition-fast);
|
||||
|
||||
&:hover {
|
||||
background: var(--ov-meet-surface-hover);
|
||||
}
|
||||
|
||||
.permission-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.permission-icon {
|
||||
@include design-tokens.ov-icon(md);
|
||||
color: var(--ov-meet-icon-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.permission-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
|
||||
.permission-label {
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
color: var(--ov-meet-text-primary);
|
||||
line-height: var(--ov-meet-line-height-tight);
|
||||
}
|
||||
|
||||
.permission-description {
|
||||
font-size: var(--ov-meet-font-size-sm);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
line-height: var(--ov-meet-line-height-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.permission-toggle {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.role-permissions-step {
|
||||
@include design-tokens.ov-page-content;
|
||||
@include design-tokens.ov-container;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
// ─── Step Header ────────────────────────────────────────────────────────────
|
||||
|
||||
.step-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
margin-bottom: var(--ov-meet-spacing-lg);
|
||||
|
||||
.step-icon {
|
||||
@include design-tokens.ov-icon(xl);
|
||||
color: var(--ov-meet-icon-settings);
|
||||
margin-top: var(--ov-meet-spacing-xs);
|
||||
}
|
||||
|
||||
.step-title-group {
|
||||
flex: 1;
|
||||
|
||||
.step-title {
|
||||
margin: 0 0 var(--ov-meet-spacing-sm) 0;
|
||||
font-size: var(--ov-meet-font-size-xl);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
color: var(--ov-meet-text-primary);
|
||||
line-height: var(--ov-meet-line-height-tight);
|
||||
}
|
||||
|
||||
.step-description {
|
||||
margin: 0;
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
color: var(--ov-meet-text-secondary);
|
||||
line-height: var(--ov-meet-line-height-normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Step Content ────────────────────────────────────────────────────────────
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
|
||||
form {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Tabs ────────────────────────────────────────────────────────────────────
|
||||
|
||||
.roles-tab-group {
|
||||
// @include design-tokens.ov-card;
|
||||
// border: 1px solid var(--ov-meet-border-secondary);
|
||||
overflow: hidden;
|
||||
|
||||
::ng-deep {
|
||||
.mat-mdc-tab-header {
|
||||
border-bottom: 1px solid var(--ov-meet-border-secondary);
|
||||
background: var(--ov-meet-card-background);
|
||||
|
||||
.mat-mdc-tab {
|
||||
min-width: 120px;
|
||||
font-size: var(--ov-meet-font-size-md);
|
||||
font-weight: var(--ov-meet-font-weight-medium);
|
||||
}
|
||||
|
||||
.mat-mdc-tab.mdc-tab--active .mdc-tab__text-label {
|
||||
color: var(--ov-meet-color-primary);
|
||||
}
|
||||
|
||||
.mat-mdc-tab-indicator .mdc-tab-indicator__content--underline {
|
||||
border-color: var(--ov-meet-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-tab-body-wrapper {
|
||||
overflow-y: auto;
|
||||
// max-height: 420px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: var(--ov-meet-spacing-md);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-sm);
|
||||
}
|
||||
|
||||
// ─── Anonymous Access Row ────────────────────────────────────────────────────
|
||||
|
||||
.anonymous-access-row {
|
||||
@include permission-row;
|
||||
margin-bottom: var(--ov-meet-spacing-xs);
|
||||
border: 1px solid var(--ov-meet-border-secondary);
|
||||
background: var(--ov-meet-surface-variant);
|
||||
|
||||
&:hover {
|
||||
background: var(--ov-meet-surface-hover);
|
||||
}
|
||||
|
||||
.anonymous-icon {
|
||||
color: var(--ov-meet-icon-settings) !important;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Individual Permissions Section ─────────────────────────────────────────
|
||||
|
||||
.permissions-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ov-meet-spacing-xs);
|
||||
|
||||
.section-label {
|
||||
margin: var(--ov-meet-spacing-xs) 0 var(--ov-meet-spacing-xs) var(--ov-meet-spacing-xs);
|
||||
font-size: var(--ov-meet-font-size-xs);
|
||||
font-weight: var(--ov-meet-font-weight-semibold);
|
||||
color: var(--ov-meet-text-hint);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.permission-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
margin-bottom: var(--ov-meet-spacing-xs);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.permission-row {
|
||||
@include permission-row;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Responsive ─────────────────────────────────────────────────────────────
|
||||
|
||||
@include design-tokens.ov-mobile-down {
|
||||
.roles-tab-group ::ng-deep .mat-mdc-tab-body-wrapper {
|
||||
max-height: 60dvh;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: var(--ov-meet-spacing-sm);
|
||||
}
|
||||
|
||||
.anonymous-access-row,
|
||||
.permissions-section .permission-row {
|
||||
padding: var(--ov-meet-spacing-xs) var(--ov-meet-spacing-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,118 @@
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { MatTabsModule } from '@angular/material/tabs';
|
||||
import { MeetRoomMemberPermissions } from '@openvidu-meet/typings';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { RoomWizardStateService } from '../../../../services';
|
||||
|
||||
export interface PermissionItem {
|
||||
key: keyof MeetRoomMemberPermissions;
|
||||
label: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface PermissionGroup {
|
||||
label: string;
|
||||
icon: string;
|
||||
permissions: PermissionItem[];
|
||||
}
|
||||
|
||||
export const PERMISSION_GROUPS: PermissionGroup[] = [
|
||||
{
|
||||
label: 'Meeting',
|
||||
icon: 'groups',
|
||||
permissions: [
|
||||
{ key: 'canJoinMeeting', label: 'Can join meeting', description: 'Allow joining the meeting', icon: 'login' },
|
||||
{ key: 'canEndMeeting', label: 'Can end meeting', description: 'Allow ending the meeting for all participants', icon: 'meeting_room' },
|
||||
{ key: 'canMakeModerator', label: 'Can make moderator', description: 'Allow promoting participants to moderator role', icon: 'manage_accounts' },
|
||||
{ key: 'canKickParticipants', label: 'Can kick participants', description: 'Allow removing participants from the meeting', icon: 'person_remove' },
|
||||
{ key: 'canShareAccessLinks', label: 'Can share access links', description: 'Allow sharing invite links with others', icon: 'link' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Media',
|
||||
icon: 'perm_media',
|
||||
permissions: [
|
||||
{ key: 'canPublishVideo', label: 'Can publish video', description: 'Allow sharing camera video', icon: 'videocam' },
|
||||
{ key: 'canPublishAudio', label: 'Can publish audio', description: 'Allow sharing microphone audio', icon: 'mic' },
|
||||
{ key: 'canShareScreen', label: 'Can share screen', description: 'Allow sharing desktop or browser tabs', icon: 'screen_share' },
|
||||
{ key: 'canChangeVirtualBackground', label: 'Can change virtual background', description: 'Allow changing the virtual background', icon: 'background_replace' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Recordings',
|
||||
icon: 'video_library',
|
||||
permissions: [
|
||||
{ key: 'canRecord', label: 'Can record', description: 'Allow starting and stopping recordings', icon: 'fiber_manual_record' },
|
||||
{ key: 'canRetrieveRecordings', label: 'Can retrieve recordings', description: 'Allow listing and playing recordings', icon: 'play_circle' },
|
||||
{ key: 'canDeleteRecordings', label: 'Can delete recordings', description: 'Allow deleting recordings', icon: 'delete' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Chat',
|
||||
icon: 'chat',
|
||||
permissions: [
|
||||
{ key: 'canReadChat', label: 'Can read chat', description: 'Allow reading chat messages', icon: 'visibility' },
|
||||
{ key: 'canWriteChat', label: 'Can write chat', description: 'Allow sending chat messages', icon: 'edit' }
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
@Component({
|
||||
selector: 'ov-role-permissions',
|
||||
imports: [ReactiveFormsModule, MatCardModule, MatIconModule, MatSlideToggleModule, MatTabsModule],
|
||||
templateUrl: './role-permissions.component.html',
|
||||
styleUrl: './role-permissions.component.scss'
|
||||
})
|
||||
export class RolePermissionsComponent implements OnDestroy {
|
||||
rolePermissionsForm: FormGroup;
|
||||
permissionGroups = PERMISSION_GROUPS;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(private wizardService: RoomWizardStateService) {
|
||||
const currentStep = this.wizardService.currentStep();
|
||||
this.rolePermissionsForm = currentStep!.formGroup;
|
||||
|
||||
this.rolePermissionsForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.saveFormData(value);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
get moderatorForm(): FormGroup {
|
||||
return this.rolePermissionsForm.get('moderator') as FormGroup;
|
||||
}
|
||||
|
||||
get speakerForm(): FormGroup {
|
||||
return this.rolePermissionsForm.get('speaker') as FormGroup;
|
||||
}
|
||||
|
||||
private saveFormData(formValue: any): void {
|
||||
const buildPermissions = (roleValue: any): Partial<MeetRoomMemberPermissions> => {
|
||||
const { anonymousEnabled, ...perms } = roleValue;
|
||||
return perms as Partial<MeetRoomMemberPermissions>;
|
||||
};
|
||||
|
||||
const stepData = {
|
||||
roles: {
|
||||
moderator: { permissions: buildPermissions(formValue.moderator) },
|
||||
speaker: { permissions: buildPermissions(formValue.speaker) }
|
||||
},
|
||||
anonymous: {
|
||||
moderator: { enabled: formValue.moderator.anonymousEnabled ?? false },
|
||||
speaker: { enabled: formValue.speaker.anonymousEnabled ?? false }
|
||||
}
|
||||
};
|
||||
|
||||
this.wizardService.updateStepData('rolePermissions', stepData);
|
||||
}
|
||||
}
|
||||
@ -5,10 +5,46 @@ import {
|
||||
MeetRoomConfig,
|
||||
MeetRoomDeletionPolicyWithMeeting,
|
||||
MeetRoomDeletionPolicyWithRecordings,
|
||||
MeetRoomMemberPermissions,
|
||||
MeetRoomOptions
|
||||
} from '@openvidu-meet/typings';
|
||||
import { WizardNavigationConfig, WizardStep } from '../models';
|
||||
|
||||
// Default permissions for each role
|
||||
const DEFAULT_MODERATOR_PERMISSIONS: MeetRoomMemberPermissions = {
|
||||
canRecord: true,
|
||||
canRetrieveRecordings: true,
|
||||
canDeleteRecordings: true,
|
||||
canJoinMeeting: true,
|
||||
canShareAccessLinks: true,
|
||||
canMakeModerator: true,
|
||||
canKickParticipants: true,
|
||||
canEndMeeting: true,
|
||||
canPublishVideo: true,
|
||||
canPublishAudio: true,
|
||||
canShareScreen: true,
|
||||
canReadChat: true,
|
||||
canWriteChat: true,
|
||||
canChangeVirtualBackground: true
|
||||
};
|
||||
|
||||
const DEFAULT_SPEAKER_PERMISSIONS: MeetRoomMemberPermissions = {
|
||||
canRecord: false,
|
||||
canRetrieveRecordings: true,
|
||||
canDeleteRecordings: false,
|
||||
canJoinMeeting: true,
|
||||
canShareAccessLinks: true,
|
||||
canMakeModerator: false,
|
||||
canKickParticipants: false,
|
||||
canEndMeeting: false,
|
||||
canPublishVideo: true,
|
||||
canPublishAudio: true,
|
||||
canShareScreen: true,
|
||||
canReadChat: true,
|
||||
canWriteChat: true,
|
||||
canChangeVirtualBackground: true
|
||||
};
|
||||
|
||||
// Default room config following the app's defaults
|
||||
const DEFAULT_CONFIG: MeetRoomConfig = {
|
||||
recording: {
|
||||
@ -193,6 +229,27 @@ export class RoomWizardStateService {
|
||||
e2eeEnabled: initialRoomOptions.config!.e2ee!.enabled,
|
||||
captionsEnabled: initialRoomOptions.config!.captions!.enabled
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 'rolePermissions',
|
||||
label: 'Role Permissions',
|
||||
isCompleted: editMode,
|
||||
isActive: false,
|
||||
isVisible: true,
|
||||
formGroup: this.formBuilder.group({
|
||||
moderator: this.formBuilder.group({
|
||||
anonymousEnabled: initialRoomOptions.anonymous?.moderator?.enabled ?? false,
|
||||
...this.buildPermissionsFormConfig(
|
||||
initialRoomOptions.roles?.moderator?.permissions ?? DEFAULT_MODERATOR_PERMISSIONS
|
||||
)
|
||||
}),
|
||||
speaker: this.formBuilder.group({
|
||||
anonymousEnabled: initialRoomOptions.anonymous?.speaker?.enabled ?? false,
|
||||
...this.buildPermissionsFormConfig(
|
||||
initialRoomOptions.roles?.speaker?.permissions ?? DEFAULT_SPEAKER_PERMISSIONS
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
@ -212,83 +269,138 @@ export class RoomWizardStateService {
|
||||
*/
|
||||
updateStepData(stepId: string, stepData: Partial<MeetRoomOptions>): void {
|
||||
const currentOptions = this._roomOptions();
|
||||
let updatedOptions: MeetRoomOptions;
|
||||
|
||||
switch (stepId) {
|
||||
case 'roomDetails':
|
||||
updatedOptions = {
|
||||
...currentOptions
|
||||
};
|
||||
|
||||
// Only update fields that are explicitly provided
|
||||
if ('roomName' in stepData) {
|
||||
updatedOptions.roomName = stepData.roomName;
|
||||
}
|
||||
if ('autoDeletionDate' in stepData) {
|
||||
updatedOptions.autoDeletionDate = stepData.autoDeletionDate;
|
||||
}
|
||||
if ('autoDeletionPolicy' in stepData) {
|
||||
updatedOptions.autoDeletionPolicy = stepData.autoDeletionPolicy;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'recording':
|
||||
case 'recordingLayout':
|
||||
updatedOptions = {
|
||||
...currentOptions,
|
||||
config: {
|
||||
...currentOptions.config,
|
||||
recording: {
|
||||
...currentOptions.config?.recording,
|
||||
...stepData.config?.recording
|
||||
}
|
||||
} as MeetRoomConfig
|
||||
};
|
||||
break;
|
||||
case 'recordingTrigger':
|
||||
// These steps don't update room options
|
||||
updatedOptions = { ...currentOptions };
|
||||
break;
|
||||
case 'config':
|
||||
updatedOptions = {
|
||||
...currentOptions,
|
||||
config: {
|
||||
...currentOptions.config,
|
||||
chat: {
|
||||
...currentOptions.config?.chat,
|
||||
...stepData.config?.chat
|
||||
},
|
||||
virtualBackground: {
|
||||
...currentOptions.config?.virtualBackground,
|
||||
...stepData.config?.virtualBackground
|
||||
},
|
||||
e2ee: {
|
||||
...currentOptions.config?.e2ee,
|
||||
...stepData.config?.e2ee
|
||||
},
|
||||
captions: {
|
||||
...currentOptions.config?.captions,
|
||||
...stepData.config?.captions
|
||||
},
|
||||
recording: {
|
||||
...currentOptions.config?.recording,
|
||||
// If recording is explicitly set in stepData, use it
|
||||
...(stepData.config?.recording?.enabled !== undefined && {
|
||||
enabled: stepData.config.recording.enabled
|
||||
})
|
||||
}
|
||||
} as MeetRoomConfig
|
||||
};
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown step ID: ${stepId}`);
|
||||
updatedOptions = currentOptions;
|
||||
}
|
||||
const updatedOptions = this.getUpdatedOptionsForStep(stepId, stepData, currentOptions);
|
||||
|
||||
this._roomOptions.set(updatedOptions);
|
||||
this.updateStepsVisibility();
|
||||
}
|
||||
|
||||
private getUpdatedOptionsForStep(
|
||||
stepId: string,
|
||||
stepData: Partial<MeetRoomOptions>,
|
||||
currentOptions: MeetRoomOptions
|
||||
): MeetRoomOptions {
|
||||
switch (stepId) {
|
||||
case 'roomDetails':
|
||||
return this.mergeRoomDetailsData(currentOptions, stepData);
|
||||
case 'recording':
|
||||
case 'recordingLayout':
|
||||
return this.mergeRecordingData(currentOptions, stepData);
|
||||
case 'recordingTrigger':
|
||||
return currentOptions;
|
||||
case 'config':
|
||||
return this.mergeConfigData(currentOptions, stepData);
|
||||
case 'rolePermissions':
|
||||
return this.mergeRolePermissionsData(currentOptions, stepData);
|
||||
default:
|
||||
console.warn(`Unknown step ID: ${stepId}`);
|
||||
return currentOptions;
|
||||
}
|
||||
}
|
||||
|
||||
private mergeRoomDetailsData(
|
||||
currentOptions: MeetRoomOptions,
|
||||
stepData: Partial<MeetRoomOptions>
|
||||
): MeetRoomOptions {
|
||||
return {
|
||||
...currentOptions,
|
||||
...('roomName' in stepData ? { roomName: stepData.roomName } : {}),
|
||||
...('autoDeletionDate' in stepData ? { autoDeletionDate: stepData.autoDeletionDate } : {}),
|
||||
...('autoDeletionPolicy' in stepData ? { autoDeletionPolicy: stepData.autoDeletionPolicy } : {})
|
||||
};
|
||||
}
|
||||
|
||||
private mergeRecordingData(
|
||||
currentOptions: MeetRoomOptions,
|
||||
stepData: Partial<MeetRoomOptions>
|
||||
): MeetRoomOptions {
|
||||
return {
|
||||
...currentOptions,
|
||||
config: this.buildMergedConfig(currentOptions.config, {
|
||||
recording: stepData.config?.recording
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
private mergeConfigData(currentOptions: MeetRoomOptions, stepData: Partial<MeetRoomOptions>): MeetRoomOptions {
|
||||
return {
|
||||
...currentOptions,
|
||||
config: this.buildMergedConfig(currentOptions.config, stepData.config)
|
||||
};
|
||||
}
|
||||
|
||||
private mergeRolePermissionsData(
|
||||
currentOptions: MeetRoomOptions,
|
||||
stepData: Partial<MeetRoomOptions>
|
||||
): MeetRoomOptions {
|
||||
const currentModeratorPermissions =
|
||||
currentOptions.roles?.moderator?.permissions ?? DEFAULT_MODERATOR_PERMISSIONS;
|
||||
const currentSpeakerPermissions = currentOptions.roles?.speaker?.permissions ?? DEFAULT_SPEAKER_PERMISSIONS;
|
||||
|
||||
return {
|
||||
...currentOptions,
|
||||
roles: {
|
||||
moderator: {
|
||||
permissions: {
|
||||
...currentModeratorPermissions,
|
||||
...stepData.roles?.moderator?.permissions
|
||||
}
|
||||
},
|
||||
speaker: {
|
||||
permissions: {
|
||||
...currentSpeakerPermissions,
|
||||
...stepData.roles?.speaker?.permissions
|
||||
}
|
||||
}
|
||||
},
|
||||
anonymous: {
|
||||
moderator: {
|
||||
enabled:
|
||||
stepData.anonymous?.moderator?.enabled ??
|
||||
currentOptions.anonymous?.moderator?.enabled ??
|
||||
false
|
||||
},
|
||||
speaker: {
|
||||
enabled:
|
||||
stepData.anonymous?.speaker?.enabled ?? currentOptions.anonymous?.speaker?.enabled ?? false
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private buildMergedConfig(
|
||||
currentConfig: Partial<MeetRoomConfig> | undefined,
|
||||
incomingConfig: Partial<MeetRoomConfig> | undefined
|
||||
): MeetRoomConfig {
|
||||
return {
|
||||
recording: {
|
||||
...DEFAULT_CONFIG.recording,
|
||||
...currentConfig?.recording,
|
||||
...incomingConfig?.recording
|
||||
},
|
||||
chat: {
|
||||
...DEFAULT_CONFIG.chat,
|
||||
...currentConfig?.chat,
|
||||
...incomingConfig?.chat
|
||||
},
|
||||
virtualBackground: {
|
||||
...DEFAULT_CONFIG.virtualBackground,
|
||||
...currentConfig?.virtualBackground,
|
||||
...incomingConfig?.virtualBackground
|
||||
},
|
||||
e2ee: {
|
||||
...DEFAULT_CONFIG.e2ee,
|
||||
...currentConfig?.e2ee,
|
||||
...incomingConfig?.e2ee
|
||||
},
|
||||
captions: {
|
||||
...DEFAULT_CONFIG.captions,
|
||||
...currentConfig?.captions,
|
||||
...incomingConfig?.captions
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility of wizard steps based on current room options.
|
||||
* For example, recording-related steps are only visible when recording is enabled.
|
||||
@ -427,6 +539,16 @@ export class RoomWizardStateService {
|
||||
return isEditMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a flat form controls config from a permissions object.
|
||||
*/
|
||||
private buildPermissionsFormConfig(permissions: Partial<MeetRoomMemberPermissions>): Record<string, boolean> {
|
||||
return Object.fromEntries(Object.entries(permissions).map(([key, value]) => [key, value ?? false])) as Record<
|
||||
string,
|
||||
boolean
|
||||
>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the wizard to its initial state with default options.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user