frontend: refactor wizard state service to use signals for state management and enhance all related components by removing unnecessary params and methods. Configured app to show error messages in the stepper for better user feedback.
This commit is contained in:
parent
ca22bd8a01
commit
0c7b1ae1c1
@ -13,7 +13,7 @@
|
||||
>
|
||||
<!-- Pro Badge -->
|
||||
@if (option.isPro && showProBadge) {
|
||||
<ov-pro-feature-badge badgeText="PRO"></ov-pro-feature-badge>
|
||||
<ov-pro-feature-badge [badgeText]="proBadgeText" [badgeIcon]="proBadgeIcon"></ov-pro-feature-badge>
|
||||
} @else if (showSelectionIndicator) {
|
||||
<!-- Selection Indicator -->
|
||||
<div class="selection-indicator">
|
||||
@ -27,7 +27,7 @@
|
||||
<div class="card-content">
|
||||
<!-- Image Section -->
|
||||
@if (shouldShowImage()) {
|
||||
<div class="card-image" [style.aspect-ratio]="getImageAspectRatio()">
|
||||
<div class="card-image" [style.aspect-ratio]="imageAspectRatio">
|
||||
<img [src]="option.imageUrl" [alt]="option.title + ' preview'" loading="lazy" />
|
||||
</div>
|
||||
}
|
||||
|
||||
@ -16,7 +16,6 @@ export interface SelectableOption {
|
||||
isPro?: boolean;
|
||||
disabled?: boolean;
|
||||
badge?: string;
|
||||
value?: any; // Additional data associated with the option
|
||||
}
|
||||
|
||||
/**
|
||||
@ -25,7 +24,7 @@ export interface SelectableOption {
|
||||
export interface SelectionEvent {
|
||||
optionId: string;
|
||||
option: SelectableOption;
|
||||
previousSelection?: string;
|
||||
previousSelection?: string | string[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
@ -45,7 +44,7 @@ export class SelectableCardComponent {
|
||||
* Currently selected value(s)
|
||||
* Can be a string (single select) or string[] (multi select)
|
||||
*/
|
||||
@Input() selectedValue: string | string[] | null = null;
|
||||
@Input() selectedValue: string | string[] | undefined;
|
||||
|
||||
/**
|
||||
* Whether multiple options can be selected simultaneously
|
||||
@ -53,23 +52,6 @@ export class SelectableCardComponent {
|
||||
*/
|
||||
@Input() allowMultiSelect: boolean = false;
|
||||
|
||||
/**
|
||||
* Whether the card should show hover effects
|
||||
* @default true
|
||||
*/
|
||||
@Input() enableHover: boolean = true;
|
||||
|
||||
/**
|
||||
* Whether the card should show selection animations
|
||||
* @default true
|
||||
*/
|
||||
@Input() enableAnimations: boolean = true;
|
||||
|
||||
/**
|
||||
* Custom CSS classes to apply to the card
|
||||
*/
|
||||
@Input() customClasses: string = '';
|
||||
|
||||
/**
|
||||
* Whether to show the selection indicator (radio button)
|
||||
* @default true
|
||||
@ -82,6 +64,18 @@ export class SelectableCardComponent {
|
||||
*/
|
||||
@Input() showProBadge: boolean = true;
|
||||
|
||||
/**
|
||||
* Custom icon for the PRO badge
|
||||
* @default 'crown'
|
||||
*/
|
||||
@Input() proBadgeIcon: string = 'crown';
|
||||
|
||||
/**
|
||||
* Custom text for the PRO badge
|
||||
* @default 'PRO'
|
||||
*/
|
||||
@Input() proBadgeText: string = 'PRO';
|
||||
|
||||
/**
|
||||
* Whether to show the recommended badge
|
||||
* @default true
|
||||
@ -107,27 +101,27 @@ export class SelectableCardComponent {
|
||||
@Input() imageAspectRatio: string = '16/9';
|
||||
|
||||
/**
|
||||
* Custom icon for the PRO badge
|
||||
* @default 'star'
|
||||
* Whether the card should show hover effects
|
||||
* @default true
|
||||
*/
|
||||
@Input() proBadgeIcon: string = 'star';
|
||||
@Input() enableHover: boolean = true;
|
||||
|
||||
/**
|
||||
* Custom text for the PRO badge
|
||||
* @default 'PRO'
|
||||
* Whether the card should show selection animations
|
||||
* @default true
|
||||
*/
|
||||
@Input() proBadgeText: string = 'PRO';
|
||||
@Input() enableAnimations: boolean = true;
|
||||
|
||||
/**
|
||||
* Custom CSS classes to apply to the card
|
||||
*/
|
||||
@Input() customClasses: string = '';
|
||||
|
||||
/**
|
||||
* Event emitted when an option is selected
|
||||
*/
|
||||
@Output() optionSelected = new EventEmitter<SelectionEvent>();
|
||||
|
||||
/**
|
||||
* Event emitted when the card is clicked (even if selection doesn't change)
|
||||
*/
|
||||
@Output() cardClicked = new EventEmitter<SelectableOption>();
|
||||
|
||||
/**
|
||||
* Event emitted when the card is hovered
|
||||
*/
|
||||
@ -157,48 +151,17 @@ export class SelectableCardComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit card clicked event
|
||||
this.cardClicked.emit(this.option);
|
||||
|
||||
// Handle selection logic
|
||||
const wasSelected = this.isOptionSelected(optionId);
|
||||
let newSelection: string | string[] | null;
|
||||
let previousSelection: string | undefined;
|
||||
|
||||
if (this.allowMultiSelect) {
|
||||
// Multi-select logic
|
||||
const currentArray = Array.isArray(this.selectedValue) ? [...this.selectedValue] : [];
|
||||
|
||||
if (wasSelected) {
|
||||
// Remove from selection
|
||||
newSelection = currentArray.filter((id) => id !== optionId);
|
||||
if (newSelection.length === 0) {
|
||||
newSelection = null;
|
||||
}
|
||||
} else {
|
||||
// Add to selection
|
||||
newSelection = [...currentArray, optionId];
|
||||
}
|
||||
} else {
|
||||
// Single-select logic
|
||||
if (wasSelected) {
|
||||
// Deselect (optional behavior)
|
||||
newSelection = null;
|
||||
previousSelection = optionId;
|
||||
} else {
|
||||
// Select new option
|
||||
previousSelection = Array.isArray(this.selectedValue) ? undefined : this.selectedValue || undefined;
|
||||
newSelection = optionId;
|
||||
}
|
||||
if (!this.allowMultiSelect && wasSelected) {
|
||||
return; // No change if already selected
|
||||
}
|
||||
|
||||
// Emit selection event
|
||||
const selectionEvent: SelectionEvent = {
|
||||
optionId,
|
||||
option: this.option,
|
||||
previousSelection
|
||||
previousSelection: this.selectedValue
|
||||
};
|
||||
|
||||
this.optionSelected.emit(selectionEvent);
|
||||
}
|
||||
|
||||
@ -229,27 +192,21 @@ export class SelectableCardComponent {
|
||||
if (this.isOptionSelected(this.option.id)) {
|
||||
classes.push('selected');
|
||||
}
|
||||
|
||||
if (this.option.recommended && this.showRecommendedBadge) {
|
||||
classes.push('recommended');
|
||||
}
|
||||
|
||||
if (this.option.isPro && this.showProBadge) {
|
||||
classes.push('pro-feature');
|
||||
}
|
||||
|
||||
if (this.option.disabled) {
|
||||
classes.push('disabled');
|
||||
}
|
||||
|
||||
if (!this.enableHover) {
|
||||
classes.push('no-hover');
|
||||
}
|
||||
|
||||
if (!this.enableAnimations) {
|
||||
classes.push('no-animations');
|
||||
}
|
||||
|
||||
if (this.customClasses) {
|
||||
classes.push(this.customClasses);
|
||||
}
|
||||
@ -278,21 +235,17 @@ export class SelectableCardComponent {
|
||||
if (this.option.recommended) {
|
||||
statusParts.push('Recommended');
|
||||
}
|
||||
|
||||
if (this.option.isPro) {
|
||||
statusParts.push('PRO feature');
|
||||
}
|
||||
|
||||
if (this.option.disabled) {
|
||||
statusParts.push('Disabled');
|
||||
}
|
||||
|
||||
if (this.isOptionSelected(this.option.id)) {
|
||||
statusParts.push('Selected');
|
||||
}
|
||||
|
||||
const statusLabel = statusParts.length > 0 ? `. ${statusParts.join(', ')}` : '';
|
||||
|
||||
return `${baseLabel}${statusLabel}`;
|
||||
}
|
||||
|
||||
@ -312,11 +265,4 @@ export class SelectableCardComponent {
|
||||
}
|
||||
return !this.shouldShowImage() && !!this.option.icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image aspect ratio styles
|
||||
*/
|
||||
getImageAspectRatio(): string {
|
||||
return this.imageAspectRatio;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,29 +1,22 @@
|
||||
<div class="step-indicator-wrapper" [attr.data-layout]="layoutType$ | async">
|
||||
<mat-stepper
|
||||
#stepper
|
||||
[selectedIndex]="safeCurrentStepIndex"
|
||||
[selectedIndex]="currentStepIndex()"
|
||||
[orientation]="(stepperOrientation$ | async)!"
|
||||
[linear]="false"
|
||||
class="wizard-stepper"
|
||||
[attr.data-layout]="layoutType$ | async"
|
||||
(selectionChange)="onStepClick($event)"
|
||||
>
|
||||
@for (step of visibleSteps; track step.id; let i = $index) {
|
||||
@for (step of visibleSteps(); track step.id; let i = $index) {
|
||||
<mat-step
|
||||
[stepControl]="step.validationFormGroup"
|
||||
[stepControl]="step.formGroup"
|
||||
[state]="getStepState(step)"
|
||||
errorMessage="Invalid fields"
|
||||
[editable]="true"
|
||||
[label]="step.label"
|
||||
[completed]="step.isCompleted"
|
||||
>
|
||||
<!-- Custom step icon template -->
|
||||
<ng-template matStepperIcon="edit">
|
||||
<mat-icon>edit</mat-icon>
|
||||
</ng-template>
|
||||
|
||||
<ng-template matStepperIcon="done">
|
||||
<mat-icon>check</mat-icon>
|
||||
</ng-template>
|
||||
</mat-step>
|
||||
}
|
||||
</mat-stepper>
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { BreakpointObserver, Breakpoints } from '@angular/cdk/layout';
|
||||
import { StepperOrientation, StepperSelectionEvent } from '@angular/cdk/stepper';
|
||||
import { StepperOrientation, StepperSelectionEvent, StepState } from '@angular/cdk/stepper';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
|
||||
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import { Component, computed, input, output } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatStepperModule } from '@angular/material/stepper';
|
||||
import { WizardStep } from '@lib/models';
|
||||
import { Observable } from 'rxjs';
|
||||
@ -13,29 +11,28 @@ import { map } from 'rxjs/operators';
|
||||
@Component({
|
||||
selector: 'ov-step-indicator',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatStepperModule, MatIcon, MatButtonModule, ReactiveFormsModule],
|
||||
imports: [CommonModule, MatStepperModule, ReactiveFormsModule],
|
||||
templateUrl: './step-indicator.component.html',
|
||||
styleUrl: './step-indicator.component.scss'
|
||||
})
|
||||
export class StepIndicatorComponent implements OnChanges {
|
||||
@Input() steps: WizardStep[] = [];
|
||||
@Input() allowNavigation: boolean = false;
|
||||
@Input() editMode: boolean = false; // New input for edit mode
|
||||
@Input() currentStepIndex: number = 0;
|
||||
@Output() stepClick = new EventEmitter<{ step: WizardStep; index: number }>();
|
||||
@Output() layoutChange = new EventEmitter<'vertical-sidebar' | 'horizontal-compact' | 'vertical-compact'>();
|
||||
export class StepIndicatorComponent {
|
||||
steps = input.required<WizardStep[]>();
|
||||
currentStepIndex = input.required<number>();
|
||||
allowNavigation = input<boolean>(true);
|
||||
editMode = input<boolean>(false);
|
||||
|
||||
stepClick = output<{ index: number; step: WizardStep }>();
|
||||
|
||||
visibleSteps = computed<WizardStep[]>(() => this.steps().filter((step) => step.isVisible));
|
||||
|
||||
visibleSteps: WizardStep[] = [];
|
||||
stepperOrientation$: Observable<StepperOrientation>;
|
||||
layoutType$: Observable<'vertical-sidebar' | 'horizontal-compact' | 'vertical-compact'>;
|
||||
stepControls: { [key: string]: FormControl } = {};
|
||||
|
||||
constructor(private breakpointObserver: BreakpointObserver) {
|
||||
// Enhanced responsive strategy:
|
||||
// - Large desktop (>1200px): Vertical sidebar for space efficiency
|
||||
// - Medium desktop (768-1200px): Horizontal compact
|
||||
// - Tablet/Mobile (<768px): Vertical compact
|
||||
|
||||
const breakpointState$ = this.breakpointObserver.observe([
|
||||
'(min-width: 1200px)',
|
||||
'(min-width: 768px)',
|
||||
@ -58,128 +55,34 @@ export class StepIndicatorComponent implements OnChanges {
|
||||
return layoutType === 'horizontal-compact' ? 'horizontal' : 'vertical';
|
||||
})
|
||||
);
|
||||
|
||||
// Emit layout changes for parent component
|
||||
this.layoutType$.subscribe((layoutType) => {
|
||||
this.layoutChange.emit(layoutType);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['steps']) {
|
||||
this.updateVisibleSteps();
|
||||
this.createStepControls();
|
||||
}
|
||||
if (changes['currentStepIndex'] || changes['steps']) {
|
||||
this.updateStepControls();
|
||||
}
|
||||
}
|
||||
|
||||
private updateVisibleSteps() {
|
||||
this.visibleSteps = this.steps.filter((step) => step.isVisible);
|
||||
}
|
||||
|
||||
private createStepControls() {
|
||||
this.stepControls = {};
|
||||
this.visibleSteps.forEach((step) => {
|
||||
this.stepControls[step.id] = new FormControl({
|
||||
value: step.isCompleted,
|
||||
disabled: !step.isCompleted && !step.isActive
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private updateStepControls() {
|
||||
this.visibleSteps.forEach((step) => {
|
||||
const control = this.stepControls[step.id];
|
||||
if (control) {
|
||||
control.setValue(step.isCompleted);
|
||||
if (step.isCompleted || step.isActive) {
|
||||
control.enable();
|
||||
} else {
|
||||
control.disable();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getStepControl(step: WizardStep): FormGroup {
|
||||
return step.validationFormGroup;
|
||||
}
|
||||
|
||||
get safeCurrentStepIndex(): number {
|
||||
if (this.visibleSteps.length === 0) {
|
||||
console.warn('No visible steps available. Defaulting to index 0.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
// In edit mode, ensure the index is valid for visible steps
|
||||
let adjustedIndex = this.currentStepIndex;
|
||||
|
||||
// If we are in edit mode and the current index is greater than available visible steps
|
||||
if (this.editMode && this.currentStepIndex >= this.visibleSteps.length) {
|
||||
// Find the first active step in the visible steps
|
||||
const activeStepIndex = this.visibleSteps.findIndex((step) => step.isActive);
|
||||
adjustedIndex = activeStepIndex >= 0 ? activeStepIndex : 0;
|
||||
}
|
||||
|
||||
const safeIndex = Math.min(Math.max(0, adjustedIndex), this.visibleSteps.length - 1);
|
||||
console.log('Safe current step index:', safeIndex, 'for visibleSteps length:', this.visibleSteps.length);
|
||||
return safeIndex;
|
||||
}
|
||||
|
||||
onStepClick(event: StepperSelectionEvent) {
|
||||
if (this.allowNavigation) {
|
||||
const step = this.visibleSteps[event.selectedIndex];
|
||||
this.stepClick.emit({ step, index: event.selectedIndex });
|
||||
if (this.allowNavigation()) {
|
||||
const index = event.selectedIndex;
|
||||
if (index < 0 || index >= this.visibleSteps().length) {
|
||||
console.warn('Invalid step index:', index);
|
||||
return;
|
||||
}
|
||||
|
||||
const step = this.visibleSteps()[index];
|
||||
this.stepClick.emit({ index, step });
|
||||
} else {
|
||||
console.warn('Navigation is not allowed. Step click ignored:', event.selectedIndex);
|
||||
}
|
||||
}
|
||||
|
||||
isStepClickable(step: WizardStep): boolean {
|
||||
if (!this.allowNavigation) {
|
||||
return false;
|
||||
}
|
||||
if (this.editMode) {
|
||||
// In edit mode, allow clicking on any step
|
||||
return true;
|
||||
}
|
||||
|
||||
return step.isActive || step.isCompleted;
|
||||
}
|
||||
|
||||
isStepEditable(step: WizardStep): boolean {
|
||||
return this.isStepClickable(step);
|
||||
}
|
||||
|
||||
getStepState(step: WizardStep): 'done' | 'edit' | 'error' | 'number' {
|
||||
getStepState(step: WizardStep): StepState {
|
||||
if (step.isCompleted && !step.isActive) {
|
||||
return 'done';
|
||||
}
|
||||
|
||||
if (step.isActive && step.validationFormGroup?.invalid) {
|
||||
if (step.isActive && step.formGroup?.invalid) {
|
||||
return 'error';
|
||||
}
|
||||
|
||||
if (step.isActive) {
|
||||
return 'edit';
|
||||
}
|
||||
|
||||
if (step.isCompleted) {
|
||||
return 'done';
|
||||
}
|
||||
|
||||
return 'number';
|
||||
}
|
||||
|
||||
getStepIcon(step: WizardStep): string {
|
||||
if (step.isCompleted) {
|
||||
return 'check';
|
||||
}
|
||||
if (step.isActive) {
|
||||
return 'edit';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
<nav class="wizard-navigation" [class.loading]="config.isLoading" [class.compact]="config.isCompact" id="wizard-navigation">
|
||||
<nav class="wizard-navigation" [class.loading]="false" [class.compact]="false" id="wizard-navigation">
|
||||
<div class="nav-buttons">
|
||||
<!-- Cancel Button -->
|
||||
@if (config.showCancel) {
|
||||
@ -24,7 +24,6 @@
|
||||
mat-stroked-button
|
||||
class="prev-btn"
|
||||
id="wizard-previous-btn"
|
||||
[disabled]="config.isPreviousDisabled"
|
||||
(click)="onPrevious()"
|
||||
[attr.aria-label]="'Go to previous step'"
|
||||
>
|
||||
@ -33,50 +32,46 @@
|
||||
</button>
|
||||
}
|
||||
|
||||
@if (config.showQuickCreate) {
|
||||
@if (config.showSkipAndFinish) {
|
||||
<!-- Skip Wizard Button -->
|
||||
<button
|
||||
mat-raised-button
|
||||
class="skip-btn"
|
||||
class="finish-btn"
|
||||
id="wizard-quick-create-btn"
|
||||
[disabled]="config.disableFinish ?? false"
|
||||
(click)="skipAndFinish()"
|
||||
type="button"
|
||||
[attr.aria-label]="'Skip wizard and finish'"
|
||||
>
|
||||
<mat-icon>bolt</mat-icon>
|
||||
Create with defaults
|
||||
{{ config.skipAndFinishLabel || 'Skip and Finish' }}
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Next/Continue Button -->
|
||||
<!-- Next Button -->
|
||||
@if (config.showNext) {
|
||||
<button
|
||||
mat-raised-button
|
||||
class="next-btn"
|
||||
id="wizard-next-btn"
|
||||
[disabled]="config.isNextDisabled"
|
||||
(click)="onNext()"
|
||||
[attr.aria-label]="'Continue to next step'"
|
||||
>
|
||||
{{ config.nextLabel || 'Next' }}
|
||||
<mat-icon class="trailing-icon">
|
||||
{{ config.isLoading ? 'hourglass_empty' : 'chevron_right' }}
|
||||
</mat-icon>
|
||||
<mat-icon class="trailing-icon">chevron_right</mat-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Finish/Save Button -->
|
||||
<!-- Finish Button -->
|
||||
@if (config.showFinish) {
|
||||
<button
|
||||
mat-raised-button
|
||||
class="finish-btn"
|
||||
id="wizard-finish-btn"
|
||||
[disabled]="config.isFinishDisabled"
|
||||
[disabled]="config.disableFinish ?? false"
|
||||
(click)="onFinish()"
|
||||
[attr.aria-label]="'Complete wizard and save changes'"
|
||||
>
|
||||
<mat-icon class="leading-icon">
|
||||
{{ config.isLoading ? 'hourglass_empty' : 'check' }}
|
||||
</mat-icon>
|
||||
<mat-icon class="leading-icon">check</mat-icon>
|
||||
{{ config.finishLabel || 'Finish' }}
|
||||
</button>
|
||||
}
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { MatButton } from '@angular/material/button';
|
||||
import { MatIcon } from '@angular/material/icon';
|
||||
import type { WizardNavigationConfig, WizardNavigationEvent } from '@lib/models';
|
||||
@ -7,30 +6,25 @@ import type { WizardNavigationConfig, WizardNavigationEvent } from '@lib/models'
|
||||
@Component({
|
||||
selector: 'ov-wizard-nav',
|
||||
standalone: true,
|
||||
imports: [CommonModule, MatButton, MatIcon],
|
||||
imports: [MatButton, MatIcon],
|
||||
templateUrl: './wizard-nav.component.html',
|
||||
styleUrl: './wizard-nav.component.scss'
|
||||
})
|
||||
export class WizardNavComponent implements OnInit, OnChanges {
|
||||
export class WizardNavComponent {
|
||||
/**
|
||||
* Navigation configuration with default values
|
||||
*/
|
||||
@Input() config: WizardNavigationConfig = {
|
||||
showPrevious: true,
|
||||
showPrevious: false,
|
||||
showNext: true,
|
||||
showCancel: true,
|
||||
showFinish: false,
|
||||
showQuickCreate: true,
|
||||
showSkipAndFinish: false,
|
||||
disableFinish: false,
|
||||
nextLabel: 'Next',
|
||||
previousLabel: 'Previous',
|
||||
cancelLabel: 'Cancel',
|
||||
finishLabel: 'Finish',
|
||||
isNextDisabled: false,
|
||||
isPreviousDisabled: false,
|
||||
isFinishDisabled: false,
|
||||
isLoading: false,
|
||||
isCompact: false,
|
||||
ariaLabel: 'Wizard navigation'
|
||||
finishLabel: 'Finish'
|
||||
};
|
||||
|
||||
/**
|
||||
@ -51,90 +45,56 @@ export class WizardNavComponent implements OnInit, OnChanges {
|
||||
*/
|
||||
@Output() navigate = new EventEmitter<WizardNavigationEvent>();
|
||||
|
||||
ngOnInit() {
|
||||
this.validateConfig();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
if (changes['config']) {
|
||||
this.validateConfig();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates navigation configuration
|
||||
*/
|
||||
private validateConfig() {
|
||||
if (!this.config.nextLabel) this.config.nextLabel = 'Next';
|
||||
if (!this.config.previousLabel) this.config.previousLabel = 'Previous';
|
||||
if (!this.config.cancelLabel) this.config.cancelLabel = 'Cancel';
|
||||
if (!this.config.finishLabel) this.config.finishLabel = 'Finish';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle previous step navigation
|
||||
*/
|
||||
onPrevious() {
|
||||
if (!this.config.isPreviousDisabled && !this.config.isLoading) {
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'previous',
|
||||
currentStepId: this.currentStepId
|
||||
};
|
||||
if (!this.config.showPrevious) return;
|
||||
|
||||
this.previous.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'previous',
|
||||
currentStepIndex: this.currentStepId
|
||||
};
|
||||
this.previous.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle next step navigation
|
||||
*/
|
||||
onNext() {
|
||||
if (!this.config.isNextDisabled && !this.config.isLoading) {
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'next',
|
||||
currentStepId: this.currentStepId
|
||||
};
|
||||
if (!this.config.showNext) return;
|
||||
|
||||
this.next.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'next',
|
||||
currentStepIndex: this.currentStepId
|
||||
};
|
||||
this.next.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle wizard cancellation
|
||||
*/
|
||||
onCancel() {
|
||||
if (!this.config.isLoading) {
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'cancel',
|
||||
currentStepId: this.currentStepId
|
||||
};
|
||||
if (!this.config.showCancel) return;
|
||||
|
||||
this.cancel.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'cancel',
|
||||
currentStepIndex: this.currentStepId
|
||||
};
|
||||
this.cancel.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle wizard completion
|
||||
*/
|
||||
onFinish() {
|
||||
if (!this.config.isFinishDisabled && !this.config.isLoading) {
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'finish',
|
||||
currentStepId: this.currentStepId
|
||||
};
|
||||
if (!this.config.showFinish) return;
|
||||
|
||||
this.finish.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'finish',
|
||||
currentStepIndex: this.currentStepId
|
||||
};
|
||||
this.finish.emit(event);
|
||||
this.navigate.emit(event);
|
||||
}
|
||||
|
||||
skipAndFinish() {
|
||||
if (!this.config.showSkipAndFinish) return;
|
||||
|
||||
const event: WizardNavigationEvent = {
|
||||
action: 'finish',
|
||||
currentStepId: this.currentStepId
|
||||
currentStepIndex: this.currentStepId
|
||||
};
|
||||
this.finish.emit(event);
|
||||
this.navigate.emit(event);
|
||||
|
||||
@ -9,42 +9,27 @@ export interface WizardStep {
|
||||
isCompleted: boolean;
|
||||
isActive: boolean;
|
||||
isVisible: boolean;
|
||||
isOptional?: boolean;
|
||||
order: number;
|
||||
validationFormGroup: FormGroup;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
formGroup: FormGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration interface for wizard navigation controls
|
||||
* Supports theming and responsive behavior
|
||||
*/
|
||||
export interface WizardNavigationConfig {
|
||||
// Button visibility
|
||||
// Button visibility flags
|
||||
showPrevious: boolean;
|
||||
showNext: boolean;
|
||||
showCancel: boolean;
|
||||
showFinish: boolean;
|
||||
showQuickCreate: boolean; // Optional for quick create functionality
|
||||
showSkipAndFinish: boolean; // Used for quick create actions
|
||||
disableFinish?: boolean;
|
||||
|
||||
// Button labels (customizable)
|
||||
// Button labels
|
||||
nextLabel?: string;
|
||||
previousLabel?: string;
|
||||
cancelLabel?: string;
|
||||
finishLabel?: string;
|
||||
|
||||
// Button states
|
||||
isNextDisabled: boolean;
|
||||
isPreviousDisabled: boolean;
|
||||
isFinishDisabled?: boolean;
|
||||
|
||||
// UI states
|
||||
isLoading?: boolean;
|
||||
isCompact?: boolean;
|
||||
|
||||
// Accessibility
|
||||
ariaLabel?: string;
|
||||
skipAndFinishLabel?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -52,7 +37,5 @@ export interface WizardNavigationConfig {
|
||||
*/
|
||||
export interface WizardNavigationEvent {
|
||||
action: 'next' | 'previous' | 'cancel' | 'finish';
|
||||
currentStepId?: number;
|
||||
targetStepId?: string;
|
||||
data?: any;
|
||||
currentStepIndex?: number;
|
||||
}
|
||||
|
||||
@ -1,52 +1,53 @@
|
||||
<div class="wizard-container ov-theme-transition">
|
||||
<header class="wizard-header">
|
||||
<!-- <div class="title">
|
||||
<mat-icon class="ov-room-icon">meeting_room</mat-icon>
|
||||
<h2>Room Creation Wizard</h2>
|
||||
</div> -->
|
||||
<p class="subtitle">{{ editMode ? 'Edit your room settings' : 'Create and configure your video room in a few simple steps' }}</p>
|
||||
<ov-step-indicator
|
||||
[steps]="steps"
|
||||
[allowNavigation]="true"
|
||||
[editMode]="editMode"
|
||||
[currentStepIndex]="currentStepIndex"
|
||||
(stepClick)="onStepClick($event)"
|
||||
(layoutChange)="onLayoutChange($event)"
|
||||
>
|
||||
</ov-step-indicator>
|
||||
</header>
|
||||
@if (steps().length !== 0) {
|
||||
<header class="wizard-header">
|
||||
<p class="subtitle">
|
||||
{{
|
||||
editMode ? 'Edit your room settings' : 'Create and configure your video room in a few simple steps'
|
||||
}}
|
||||
</p>
|
||||
<ov-step-indicator
|
||||
[steps]="steps()"
|
||||
[currentStepIndex]="currentStepIndex()"
|
||||
[allowNavigation]="true"
|
||||
[editMode]="editMode"
|
||||
(stepClick)="onStepClick($event)"
|
||||
>
|
||||
</ov-step-indicator>
|
||||
</header>
|
||||
|
||||
<main class="wizard-content">
|
||||
<section class="step-content ov-surface">
|
||||
@switch (currentStep?.id) {
|
||||
@case ('basic') {
|
||||
<ov-room-wizard-basic-info [editMode]="editMode"></ov-room-wizard-basic-info>
|
||||
<main class="wizard-content">
|
||||
<section class="step-content ov-surface">
|
||||
@switch (currentStep()?.id) {
|
||||
@case ('basic') {
|
||||
<ov-room-wizard-basic-info></ov-room-wizard-basic-info>
|
||||
}
|
||||
@case ('recording') {
|
||||
<ov-recording-preferences></ov-recording-preferences>
|
||||
}
|
||||
@case ('recordingTrigger') {
|
||||
<ov-recording-trigger></ov-recording-trigger>
|
||||
}
|
||||
@case ('recordingLayout') {
|
||||
<ov-recording-layout></ov-recording-layout>
|
||||
}
|
||||
@case ('preferences') {
|
||||
<ov-room-preferences></ov-room-preferences>
|
||||
}
|
||||
}
|
||||
@case ('recording') {
|
||||
<ov-recording-preferences></ov-recording-preferences>
|
||||
}
|
||||
@case ('recordingTrigger') {
|
||||
<ov-recording-trigger></ov-recording-trigger>
|
||||
}
|
||||
@case ('recordingLayout') {
|
||||
<ov-recording-layout></ov-recording-layout>
|
||||
}
|
||||
@case ('preferences') {
|
||||
<ov-room-preferences></ov-room-preferences>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
</main>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="wizard-footer">
|
||||
<ov-wizard-nav
|
||||
[config]="navigationConfig"
|
||||
[currentStepId]="currentStepIndex"
|
||||
(previous)="onPrevious()"
|
||||
(next)="onNext()"
|
||||
(cancel)="onCancel()"
|
||||
(finish)="onFinish($event)"
|
||||
>
|
||||
</ov-wizard-nav>
|
||||
</footer>
|
||||
<footer class="wizard-footer">
|
||||
<ov-wizard-nav
|
||||
[config]="navigationConfig()"
|
||||
[currentStepId]="currentStepIndex()"
|
||||
(previous)="onPrevious()"
|
||||
(next)="onNext()"
|
||||
(cancel)="onCancel()"
|
||||
(finish)="onFinish()"
|
||||
>
|
||||
</ov-wizard-nav>
|
||||
</footer>
|
||||
}
|
||||
</div>
|
||||
|
||||
@ -1,14 +1,13 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component, computed, OnInit, Signal } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { StepIndicatorComponent, WizardNavComponent } from '@lib/components';
|
||||
import { WizardNavigationConfig, WizardNavigationEvent, WizardStep } from '@lib/models';
|
||||
import { NavigationService, RoomService, RoomWizardStateService } from '@lib/services';
|
||||
import { MeetRoom, MeetRoomOptions } from '@lib/typings/ce';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { WizardNavigationConfig, WizardStep } from '@lib/models';
|
||||
import { NavigationService, NotificationService, RoomService, RoomWizardStateService } from '@lib/services';
|
||||
import { MeetRoomOptions } from '@lib/typings/ce';
|
||||
import { RoomWizardBasicInfoComponent } from './steps/basic-info/basic-info.component';
|
||||
import { RecordingLayoutComponent } from './steps/recording-layout/recording-layout.component';
|
||||
import { RecordingPreferencesComponent } from './steps/recording-preferences/recording-preferences.component';
|
||||
@ -34,79 +33,40 @@ import { RoomPreferencesComponent } from './steps/room-preferences/room-preferen
|
||||
templateUrl: './room-wizard.component.html',
|
||||
styleUrl: './room-wizard.component.scss'
|
||||
})
|
||||
export class RoomWizardComponent implements OnInit, OnDestroy {
|
||||
export class RoomWizardComponent implements OnInit {
|
||||
editMode: boolean = false;
|
||||
roomId: string | null = null;
|
||||
existingRoomData: MeetRoomOptions | null = null;
|
||||
roomId?: string;
|
||||
existingRoomData?: MeetRoomOptions;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
steps: WizardStep[] = [];
|
||||
currentStep: WizardStep | null = null;
|
||||
currentStepIndex: number = 0;
|
||||
currentLayout: 'vertical-sidebar' | 'horizontal-compact' | 'vertical-compact' = 'horizontal-compact';
|
||||
navigationConfig: WizardNavigationConfig = {
|
||||
showPrevious: false,
|
||||
showNext: true,
|
||||
showCancel: true,
|
||||
showFinish: false,
|
||||
showQuickCreate: true,
|
||||
nextLabel: 'Next',
|
||||
previousLabel: 'Previous',
|
||||
finishLabel: 'Create Room',
|
||||
isNextDisabled: false,
|
||||
isPreviousDisabled: true
|
||||
};
|
||||
wizardData: MeetRoomOptions = {};
|
||||
steps: Signal<WizardStep[]>;
|
||||
currentStep: Signal<WizardStep | undefined>;
|
||||
currentStepIndex: Signal<number>;
|
||||
navigationConfig: Signal<WizardNavigationConfig>;
|
||||
|
||||
constructor(
|
||||
private wizardState: RoomWizardStateService,
|
||||
private wizardService: RoomWizardStateService,
|
||||
protected roomService: RoomService,
|
||||
protected notificationService: NotificationService,
|
||||
private navigationService: NavigationService,
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
) {
|
||||
this.steps = this.wizardService.steps;
|
||||
this.currentStep = this.wizardService.currentStep;
|
||||
this.currentStepIndex = this.wizardService.currentStepIndex;
|
||||
this.navigationConfig = computed(() => this.wizardService.getNavigationConfig());
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
console.log('RoomWizard ngOnInit - starting');
|
||||
|
||||
// Detect edit mode from route
|
||||
this.detectEditMode();
|
||||
|
||||
// If in edit mode, load room data
|
||||
if (this.editMode && this.roomId) {
|
||||
this.navigationConfig.showQuickCreate = false;
|
||||
await this.loadRoomData();
|
||||
}
|
||||
|
||||
// Initialize wizard with edit mode and existing data
|
||||
this.wizardState.initializeWizard(this.editMode, this.existingRoomData || undefined);
|
||||
|
||||
this.wizardState.steps$.pipe(takeUntil(this.destroy$)).subscribe((steps) => {
|
||||
// Only update current step info after steps are available
|
||||
|
||||
if (steps.length > 0) {
|
||||
this.steps = steps;
|
||||
this.currentStep = this.wizardState.getCurrentStep();
|
||||
this.currentStepIndex = this.wizardState.getCurrentStepIndex();
|
||||
this.navigationConfig = this.wizardState.getNavigationConfig();
|
||||
|
||||
// Update navigation config for edit mode
|
||||
if (this.editMode) {
|
||||
this.navigationConfig.finishLabel = 'Update Room';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.wizardState.roomOptions$.pipe(takeUntil(this.destroy$)).subscribe((options) => {
|
||||
this.wizardData = options;
|
||||
});
|
||||
|
||||
this.wizardState.currentStepIndex$.pipe(takeUntil(this.destroy$)).subscribe((index) => {
|
||||
// Only update if we have visible steps
|
||||
if (this.steps.filter((s) => s.isVisible).length > 0) {
|
||||
this.currentStepIndex = index;
|
||||
}
|
||||
});
|
||||
this.wizardService.initializeWizard(this.editMode, this.existingRoomData);
|
||||
}
|
||||
|
||||
private detectEditMode() {
|
||||
@ -116,7 +76,7 @@ export class RoomWizardComponent implements OnInit, OnDestroy {
|
||||
|
||||
// Get roomId from route parameters when in edit mode
|
||||
if (this.editMode) {
|
||||
this.roomId = this.route.snapshot.paramMap.get('roomId');
|
||||
this.roomId = this.route.snapshot.paramMap.get('roomId') || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,15 +84,8 @@ export class RoomWizardComponent implements OnInit, OnDestroy {
|
||||
if (!this.roomId) return;
|
||||
|
||||
try {
|
||||
// Fetch room data from the service
|
||||
const room: MeetRoom = await this.roomService.getRoom(this.roomId);
|
||||
|
||||
// Convert MeetRoom to MeetRoomOptions
|
||||
this.existingRoomData = {
|
||||
roomIdPrefix: room.roomIdPrefix,
|
||||
autoDeletionDate: room.autoDeletionDate,
|
||||
preferences: room.preferences
|
||||
};
|
||||
const { roomIdPrefix, autoDeletionDate, preferences } = await this.roomService.getRoom(this.roomId);
|
||||
this.existingRoomData = { roomIdPrefix, autoDeletionDate, preferences };
|
||||
} catch (error) {
|
||||
console.error('Error loading room data:', error);
|
||||
// Navigate back to rooms list if room not found
|
||||
@ -140,56 +93,42 @@ export class RoomWizardComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
onPrevious() {
|
||||
this.wizardState.goToPreviousStep();
|
||||
this.currentStep = this.wizardState.getCurrentStep();
|
||||
this.navigationConfig = this.wizardState.getNavigationConfig();
|
||||
this.wizardService.goToPreviousStep();
|
||||
}
|
||||
|
||||
onNext() {
|
||||
this.wizardState.goToNextStep();
|
||||
this.currentStep = this.wizardState.getCurrentStep();
|
||||
this.navigationConfig = this.wizardState.getNavigationConfig();
|
||||
this.wizardService.goToNextStep();
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.navigationService.navigateTo('rooms', undefined, true);
|
||||
this.wizardState.resetWizard();
|
||||
onStepClick(event: { index: number; step: WizardStep }) {
|
||||
this.wizardService.goToStep(event.index);
|
||||
}
|
||||
|
||||
onStepClick(event: { step: WizardStep; index: number }) {
|
||||
this.wizardState.goToStep(event.index);
|
||||
this.currentStep = this.wizardState.getCurrentStep();
|
||||
this.navigationConfig = this.wizardState.getNavigationConfig();
|
||||
async onCancel() {
|
||||
this.wizardService.resetWizard();
|
||||
await this.navigationService.navigateTo('rooms', undefined, true);
|
||||
}
|
||||
|
||||
onLayoutChange(layout: 'vertical-sidebar' | 'horizontal-compact' | 'vertical-compact') {
|
||||
this.currentLayout = layout;
|
||||
}
|
||||
|
||||
async onFinish(event: WizardNavigationEvent) {
|
||||
const roomOptions = this.wizardState.getRoomOptions();
|
||||
console.log('Wizard completed with data:', event, roomOptions);
|
||||
async onFinish() {
|
||||
const roomOptions = this.wizardService.roomOptions();
|
||||
console.log('Wizard completed with data:', roomOptions);
|
||||
|
||||
try {
|
||||
if (this.editMode && this.roomId && roomOptions.preferences) {
|
||||
await this.roomService.updateRoom(this.roomId, roomOptions.preferences);
|
||||
//TODO: Show success notification
|
||||
this.notificationService.showSnackbar('Room updated successfully');
|
||||
} else {
|
||||
// Create new room
|
||||
await this.roomService.createRoom(roomOptions);
|
||||
console.log('Room created successfully');
|
||||
// TODO: Show error notification
|
||||
this.notificationService.showSnackbar('Room created successfully');
|
||||
}
|
||||
|
||||
await this.navigationService.navigateTo('rooms', undefined, true);
|
||||
} catch (error) {
|
||||
console.error(`Failed to ${this.editMode ? 'update' : 'create'} room:`, error);
|
||||
const errorMessage = `Failed to ${this.editMode ? 'update' : 'create'} room`;
|
||||
this.notificationService.showSnackbar(errorMessage);
|
||||
console.error(errorMessage, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,9 +16,12 @@
|
||||
<!-- Room Prefix Field -->
|
||||
<mat-form-field appearance="outline" class="form-field">
|
||||
<mat-label>Room Name Prefix</mat-label>
|
||||
<input matInput formControlName="roomIdPrefix" placeholder="e.g. demo-room" maxlength="50" />
|
||||
<input matInput formControlName="roomIdPrefix" placeholder="e.g. demo-room" />
|
||||
<mat-icon matSuffix class="ov-settings-icon">label</mat-icon>
|
||||
<mat-hint>Optional prefix for room names. Leave empty for default naming.</mat-hint>
|
||||
@if (basicInfoForm.get('roomIdPrefix')?.hasError('maxlength')) {
|
||||
<mat-error> Room name prefix cannot exceed 50 characters </mat-error>
|
||||
}
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Deletion Date Field -->
|
||||
@ -39,6 +42,7 @@
|
||||
matSuffix
|
||||
mat-icon-button
|
||||
type="button"
|
||||
[disabled]="basicInfoForm.get('autoDeletionDate')?.disabled"
|
||||
(click)="clearDeletionDate()"
|
||||
matTooltip="Clear date selection"
|
||||
class="clear-date-button"
|
||||
@ -85,10 +89,14 @@
|
||||
<mat-icon matSuffix class="ov-settings-icon">access_time</mat-icon>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="time-hint">
|
||||
<mat-icon class="hint-icon material-symbols-outlined material-icons">auto_delete</mat-icon>
|
||||
<span>Room will be deleted at {{ getFormattedDateTime() }}</span>
|
||||
</div>
|
||||
@if (!basicInfoForm.hasError('minFutureDateTime')) {
|
||||
<div class="time-hint">
|
||||
<mat-icon class="hint-icon material-symbols-outlined material-icons">auto_delete</mat-icon>
|
||||
<span>Room will be deleted at {{ getFormattedDateTime() }}</span>
|
||||
</div>
|
||||
} @else {
|
||||
<mat-error> Deletion date and time must be at least one hour in the future </mat-error>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</form>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, Input, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatNativeDateModule } from '@angular/material/core';
|
||||
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||
@ -17,7 +16,6 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
selector: 'ov-room-wizard-basic-info',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
ReactiveFormsModule,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
@ -31,37 +29,18 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
templateUrl: './basic-info.component.html',
|
||||
styleUrl: './basic-info.component.scss'
|
||||
})
|
||||
export class RoomWizardBasicInfoComponent implements OnInit, OnDestroy {
|
||||
@Input() editMode: boolean = false; // Input to control edit mode from parent component
|
||||
export class RoomWizardBasicInfoComponent implements OnDestroy {
|
||||
basicInfoForm: FormGroup;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
// Arrays for time selection
|
||||
hours = Array.from({ length: 24 }, (_, i) => ({ value: i, display: i.toString().padStart(2, '0') }));
|
||||
minutes = Array.from({ length: 60 }, (_, i) => ({ value: i, display: i.toString().padStart(2, '0') }));
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private wizardState: RoomWizardStateService
|
||||
) {
|
||||
this.basicInfoForm = this.fb.group({
|
||||
roomIdPrefix: ['', [Validators.maxLength(50)]],
|
||||
autoDeletionDate: [null],
|
||||
autoDeletionHour: [23],
|
||||
autoDeletionMinute: [59]
|
||||
});
|
||||
}
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit() {
|
||||
// Disable form controls in edit mode
|
||||
if (this.editMode) {
|
||||
this.basicInfoForm.get('roomIdPrefix')?.disable();
|
||||
this.basicInfoForm.get('autoDeletionDate')?.disable();
|
||||
this.basicInfoForm.get('autoDeletionHour')?.disable();
|
||||
this.basicInfoForm.get('autoDeletionMinute')?.disable();
|
||||
}
|
||||
|
||||
this.loadExistingData();
|
||||
constructor(private wizardService: RoomWizardStateService) {
|
||||
const currentStep = this.wizardService.currentStep();
|
||||
this.basicInfoForm = currentStep!.formGroup;
|
||||
|
||||
this.basicInfoForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.saveFormData(value);
|
||||
@ -73,24 +52,6 @@ export class RoomWizardBasicInfoComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private loadExistingData() {
|
||||
const roomOptions = this.wizardState.getRoomOptions();
|
||||
|
||||
if (roomOptions.autoDeletionDate) {
|
||||
const date = new Date(roomOptions.autoDeletionDate);
|
||||
this.basicInfoForm.patchValue({
|
||||
roomIdPrefix: roomOptions.roomIdPrefix || '',
|
||||
autoDeletionDate: date,
|
||||
autoDeletionHour: date.getHours(),
|
||||
autoDeletionMinute: date.getMinutes()
|
||||
});
|
||||
} else {
|
||||
this.basicInfoForm.patchValue({
|
||||
roomIdPrefix: roomOptions.roomIdPrefix || ''
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private saveFormData(formValue: any) {
|
||||
let autoDeletionDateTime: number | undefined = undefined;
|
||||
|
||||
@ -110,27 +71,13 @@ export class RoomWizardBasicInfoComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
|
||||
// Always save to wizard state (including when values are cleared)
|
||||
this.wizardState.updateStepData('basic', stepData);
|
||||
}
|
||||
|
||||
clearForm() {
|
||||
this.basicInfoForm.reset();
|
||||
this.wizardState.updateStepData('basic', {
|
||||
roomIdPrefix: '',
|
||||
autoDeletionDate: undefined
|
||||
});
|
||||
this.wizardService.updateStepData('basic', stepData);
|
||||
}
|
||||
|
||||
get minDate(): Date {
|
||||
return new Date();
|
||||
}
|
||||
|
||||
clearDeletionDate() {
|
||||
this.basicInfoForm.patchValue({
|
||||
autoDeletionDate: null,
|
||||
autoDeletionHour: 23, // Reset to default values
|
||||
autoDeletionMinute: 59
|
||||
});
|
||||
const now = new Date();
|
||||
now.setHours(now.getHours() + 1, 0, 0, 0); // Set to 1 hour in the future
|
||||
return now;
|
||||
}
|
||||
|
||||
get hasDateSelected(): boolean {
|
||||
@ -160,4 +107,12 @@ export class RoomWizardBasicInfoComponent implements OnInit, OnDestroy {
|
||||
hour12: false
|
||||
});
|
||||
}
|
||||
|
||||
clearDeletionDate() {
|
||||
this.basicInfoForm.patchValue({
|
||||
autoDeletionDate: null,
|
||||
autoDeletionHour: 23,
|
||||
autoDeletionMinute: 59
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,8 +18,8 @@
|
||||
[option]="option"
|
||||
[selectedValue]="selectedOption"
|
||||
[showSelectionIndicator]="true"
|
||||
[showProBadge]="true"
|
||||
[showRecommendedBadge]="false"
|
||||
[showProBadge]="option.isPro ?? false"
|
||||
[showRecommendedBadge]="option.recommended ?? false"
|
||||
[showImage]="true"
|
||||
[imageAspectRatio]="'1/1'"
|
||||
[showImageAndIcon]="false"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@ -24,18 +24,14 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
templateUrl: './recording-layout.component.html',
|
||||
styleUrl: './recording-layout.component.scss'
|
||||
})
|
||||
export class RecordingLayoutComponent implements OnInit, OnDestroy {
|
||||
export class RecordingLayoutComponent implements OnDestroy {
|
||||
layoutForm: FormGroup;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
layoutOptions: SelectableOption[] = [
|
||||
{
|
||||
id: 'grid',
|
||||
title: 'Grid Layout',
|
||||
description: 'Show all participants in a grid view with equal sized tiles',
|
||||
imageUrl: './assets/layouts/grid.png',
|
||||
recommended: false,
|
||||
isPro: false
|
||||
imageUrl: './assets/layouts/grid.png'
|
||||
},
|
||||
{
|
||||
id: 'speaker',
|
||||
@ -43,7 +39,8 @@ export class RecordingLayoutComponent implements OnInit, OnDestroy {
|
||||
description: 'Highlight the active speaker with other participants below',
|
||||
imageUrl: './assets/layouts/speaker.png',
|
||||
isPro: true,
|
||||
disabled: true
|
||||
disabled: true,
|
||||
recommended: true
|
||||
},
|
||||
{
|
||||
id: 'single-speaker',
|
||||
@ -55,26 +52,15 @@ export class RecordingLayoutComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private wizardState: RoomWizardStateService
|
||||
) {
|
||||
this.layoutForm = this.fb.group({
|
||||
layoutType: ['grid'] // default to grid
|
||||
});
|
||||
}
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit() {
|
||||
// Load existing data if available
|
||||
this.loadExistingData();
|
||||
constructor(private wizardService: RoomWizardStateService) {
|
||||
const currentStep = this.wizardService.currentStep();
|
||||
this.layoutForm = currentStep!.formGroup;
|
||||
|
||||
// Subscribe to form changes for auto-save
|
||||
this.layoutForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.saveFormData(value);
|
||||
});
|
||||
|
||||
// Save initial default value if no existing data
|
||||
this.saveInitialDefaultIfNeeded();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -82,30 +68,9 @@ export class RecordingLayoutComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private loadExistingData() {
|
||||
// Note: This component doesn't need to store data in MeetRoomOptions
|
||||
// Recording layout settings are typically stored as metadata or used for UI state only
|
||||
this.layoutForm.patchValue({
|
||||
layoutType: 'grid' // Always default to grid
|
||||
});
|
||||
}
|
||||
|
||||
private saveInitialDefaultIfNeeded() {
|
||||
// Always ensure grid is selected as default
|
||||
if (!this.layoutForm.value.layoutType) {
|
||||
this.layoutForm.patchValue({
|
||||
layoutType: 'grid'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private saveFormData(formValue: any) {
|
||||
// Note: Recording layout type is not part of MeetRoomOptions
|
||||
// This is UI state that affects recording layout but not stored in room options
|
||||
// We could extend this to store in a metadata field if needed in the future
|
||||
|
||||
// For now, just keep the form state - this affects UI behavior but not the final room creation
|
||||
console.log('Recording layout type selected:', formValue.layoutType);
|
||||
// For now, just keep the form state
|
||||
}
|
||||
|
||||
onOptionSelect(event: SelectionEvent): void {
|
||||
@ -115,6 +80,6 @@ export class RecordingLayoutComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get selectedOption(): string {
|
||||
return this.layoutForm.value.layoutType || 'grid'; // Default to grid if not set
|
||||
return this.layoutForm.value.layoutType || 'grid';
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,9 @@
|
||||
<ov-selectable-card
|
||||
[option]="option"
|
||||
[selectedValue]="selectedValue"
|
||||
[allowMultiSelect]="false"
|
||||
[showSelectionIndicator]="true"
|
||||
[showRecommendedBadge]="option.recommended ?? false"
|
||||
(optionSelected)="onOptionSelect($event)"
|
||||
></ov-selectable-card>
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
@ -34,27 +34,26 @@ interface RecordingAccessOption {
|
||||
templateUrl: './recording-preferences.component.html',
|
||||
styleUrl: './recording-preferences.component.scss'
|
||||
})
|
||||
export class RecordingPreferencesComponent implements OnInit, OnDestroy {
|
||||
export class RecordingPreferencesComponent implements OnDestroy {
|
||||
recordingForm: FormGroup;
|
||||
private destroy$ = new Subject<void>();
|
||||
isAnimatingOut = false;
|
||||
|
||||
recordingOptions: SelectableOption[] = [
|
||||
{
|
||||
id: 'disabled',
|
||||
title: 'No Recording',
|
||||
description: 'Room will not be recorded. Participants can join without recording concerns.',
|
||||
icon: 'videocam_off'
|
||||
},
|
||||
{
|
||||
id: 'enabled',
|
||||
title: 'Allow Recording',
|
||||
description:
|
||||
'Enable recording capabilities for this room. Recordings can be started manually or automatically.',
|
||||
icon: 'video_library'
|
||||
icon: 'video_library',
|
||||
recommended: true
|
||||
},
|
||||
{
|
||||
id: 'disabled',
|
||||
title: 'No Recording',
|
||||
description: 'Room will not be recorded. Participants can join without recording concerns.',
|
||||
icon: 'videocam_off'
|
||||
}
|
||||
];
|
||||
|
||||
recordingAccessOptions: RecordingAccessOption[] = [
|
||||
{
|
||||
value: MeetRecordingAccess.ADMIN,
|
||||
@ -70,25 +69,15 @@ export class RecordingPreferencesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private wizardState: RoomWizardStateService
|
||||
) {
|
||||
this.recordingForm = this.fb.group({
|
||||
recordingEnabled: ['disabled'], // default to no recording
|
||||
allowAccessTo: ['admin'] // default access level
|
||||
});
|
||||
}
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit() {
|
||||
this.loadExistingData();
|
||||
constructor(private wizardState: RoomWizardStateService) {
|
||||
const currentStep = this.wizardState.currentStep();
|
||||
this.recordingForm = currentStep!.formGroup;
|
||||
|
||||
this.recordingForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.saveFormData(value);
|
||||
});
|
||||
|
||||
// Save initial default value if no existing data
|
||||
this.saveInitialDefaultIfNeeded();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -96,18 +85,6 @@ export class RecordingPreferencesComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private loadExistingData() {
|
||||
const roomOptions = this.wizardState.getRoomOptions();
|
||||
const recordingPrefs = roomOptions.preferences?.recordingPreferences;
|
||||
|
||||
if (recordingPrefs !== undefined) {
|
||||
this.recordingForm.patchValue({
|
||||
recordingEnabled: recordingPrefs.enabled ? 'enabled' : 'disabled',
|
||||
allowAccessTo: recordingPrefs.allowAccessTo || MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private saveFormData(formValue: any) {
|
||||
const enabled = formValue.recordingEnabled === 'enabled';
|
||||
|
||||
@ -123,16 +100,6 @@ export class RecordingPreferencesComponent implements OnInit, OnDestroy {
|
||||
this.wizardState.updateStepData('recording', stepData);
|
||||
}
|
||||
|
||||
private saveInitialDefaultIfNeeded() {
|
||||
const roomOptions = this.wizardState.getRoomOptions();
|
||||
const recordingPrefs = roomOptions.preferences?.recordingPreferences;
|
||||
|
||||
// If no existing data, save the default value
|
||||
if (recordingPrefs === undefined) {
|
||||
this.saveFormData(this.recordingForm.value);
|
||||
}
|
||||
}
|
||||
|
||||
onOptionSelect(event: SelectionEvent): void {
|
||||
const previouslyEnabled = this.isRecordingEnabled;
|
||||
const willBeEnabled = event.optionId === 'enabled';
|
||||
@ -155,36 +122,15 @@ export class RecordingPreferencesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
isOptionSelected(optionId: 'disabled' | 'enabled'): boolean {
|
||||
return this.recordingForm.value.recordingEnabled === optionId;
|
||||
}
|
||||
|
||||
get selectedValue(): string {
|
||||
return this.recordingForm.value.recordingEnabled;
|
||||
return this.recordingForm.value.recordingEnabled || 'disabled';
|
||||
}
|
||||
|
||||
get isRecordingEnabled(): boolean {
|
||||
return this.recordingForm.value.recordingEnabled === 'enabled';
|
||||
return this.selectedValue === 'enabled';
|
||||
}
|
||||
|
||||
get shouldShowAccessSection(): boolean {
|
||||
return this.isRecordingEnabled || this.isAnimatingOut;
|
||||
}
|
||||
|
||||
setRecommendedOption() {
|
||||
this.recordingForm.patchValue({
|
||||
recordingEnabled: 'enabled'
|
||||
});
|
||||
}
|
||||
|
||||
setDefaultOption() {
|
||||
this.recordingForm.patchValue({
|
||||
recordingEnabled: 'disabled'
|
||||
});
|
||||
}
|
||||
|
||||
get currentSelection(): SelectableOption | undefined {
|
||||
const selectedId = this.recordingForm.value.recordingEnabled;
|
||||
return this.recordingOptions.find((option) => option.id === selectedId);
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,8 +19,8 @@
|
||||
[selectedValue]="selectedOption"
|
||||
[allowMultiSelect]="false"
|
||||
[showSelectionIndicator]="true"
|
||||
[showProBadge]="true"
|
||||
[showRecommendedBadge]="false"
|
||||
[showProBadge]="option.isPro ?? false"
|
||||
[showRecommendedBadge]="option.recommended ?? false"
|
||||
[enableHover]="!option.disabled"
|
||||
(optionSelected)="onOptionChange($event)"
|
||||
></ov-selectable-card>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { Component, OnDestroy } from '@angular/core';
|
||||
import { FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
@ -24,18 +24,15 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
templateUrl: './recording-trigger.component.html',
|
||||
styleUrl: './recording-trigger.component.scss'
|
||||
})
|
||||
export class RecordingTriggerComponent implements OnInit, OnDestroy {
|
||||
export class RecordingTriggerComponent implements OnDestroy {
|
||||
triggerForm: FormGroup;
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
triggerOptions: SelectableOption[] = [
|
||||
{
|
||||
id: 'manual',
|
||||
title: 'Manual Recording',
|
||||
description: 'Start recording manually when needed',
|
||||
icon: 'touch_app',
|
||||
recommended: true,
|
||||
isPro: false
|
||||
recommended: true
|
||||
},
|
||||
{
|
||||
id: 'auto1',
|
||||
@ -55,26 +52,15 @@ export class RecordingTriggerComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
];
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private wizardState: RoomWizardStateService
|
||||
) {
|
||||
this.triggerForm = this.fb.group({
|
||||
triggerType: ['manual'] // default to manual
|
||||
});
|
||||
}
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
ngOnInit() {
|
||||
// Load existing data if available
|
||||
this.loadExistingData();
|
||||
constructor(private wizardService: RoomWizardStateService) {
|
||||
const currentStep = this.wizardService.currentStep();
|
||||
this.triggerForm = currentStep!.formGroup;
|
||||
|
||||
// Subscribe to form changes for auto-save
|
||||
this.triggerForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.saveFormData(value);
|
||||
});
|
||||
|
||||
// Save initial default value if no existing data
|
||||
this.saveInitialDefaultIfNeeded();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
@ -82,31 +68,9 @@ export class RecordingTriggerComponent implements OnInit, OnDestroy {
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
private loadExistingData() {
|
||||
// Note: This component doesn't need to store data in MeetRoomOptions
|
||||
// Recording trigger settings are typically stored as metadata or used for UI state only
|
||||
// For now, we'll use form state only
|
||||
this.triggerForm.patchValue({
|
||||
triggerType: 'manual' // Always default to manual
|
||||
});
|
||||
}
|
||||
|
||||
private saveFormData(formValue: any) {
|
||||
// Note: Recording trigger type is not part of MeetRoomOptions
|
||||
// This is UI state that affects how recording is initiated but not stored in room options
|
||||
// We could extend this to store in a metadata field if needed in the future
|
||||
|
||||
// For now, just keep the form state - this affects UI behavior but not the final room creation
|
||||
console.log('Recording trigger type selected:', formValue.triggerType);
|
||||
}
|
||||
|
||||
private saveInitialDefaultIfNeeded() {
|
||||
// Always ensure manual is selected as default
|
||||
if (!this.triggerForm.value.triggerType) {
|
||||
this.triggerForm.patchValue({
|
||||
triggerType: 'manual'
|
||||
});
|
||||
}
|
||||
// For now, just keep the form state
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
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';
|
||||
@ -11,43 +9,22 @@ import { Subject, takeUntil } from 'rxjs';
|
||||
@Component({
|
||||
selector: 'ov-room-preferences',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, MatCardModule, MatButtonModule, MatIconModule, MatSlideToggleModule],
|
||||
imports: [ReactiveFormsModule, MatCardModule, MatIconModule, MatSlideToggleModule],
|
||||
templateUrl: './room-preferences.component.html',
|
||||
styleUrl: './room-preferences.component.scss'
|
||||
})
|
||||
export class RoomPreferencesComponent implements OnInit, OnDestroy {
|
||||
export class RoomPreferencesComponent implements OnDestroy {
|
||||
preferencesForm: FormGroup;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
private roomWizardStateService: RoomWizardStateService
|
||||
) {
|
||||
this.preferencesForm = this.fb.group({
|
||||
chatEnabled: [true],
|
||||
virtualBackgroundsEnabled: [true]
|
||||
});
|
||||
}
|
||||
constructor(private wizardService: RoomWizardStateService) {
|
||||
const currentStep = this.wizardService.currentStep();
|
||||
this.preferencesForm = currentStep!.formGroup;
|
||||
|
||||
ngOnInit(): void {
|
||||
// Load existing data from wizard state
|
||||
const roomOptions = this.roomWizardStateService.getRoomOptions();
|
||||
const preferences = roomOptions.preferences;
|
||||
|
||||
if (preferences) {
|
||||
this.preferencesForm.patchValue({
|
||||
chatEnabled: preferences.chatPreferences?.enabled ?? true,
|
||||
virtualBackgroundsEnabled: preferences.virtualBackgroundPreferences?.enabled ?? true
|
||||
});
|
||||
}
|
||||
|
||||
// Auto-save form changes
|
||||
this.preferencesForm.valueChanges.pipe(takeUntil(this.destroy$)).subscribe((value) => {
|
||||
this.saveFormData(value);
|
||||
});
|
||||
|
||||
// Save initial default values if no existing data
|
||||
this.saveInitialDefaultIfNeeded();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -67,17 +44,7 @@ export class RoomPreferencesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
};
|
||||
|
||||
this.roomWizardStateService.updateStepData('preferences', stepData);
|
||||
}
|
||||
|
||||
private saveInitialDefaultIfNeeded(): void {
|
||||
const roomOptions = this.roomWizardStateService.getRoomOptions();
|
||||
const preferences = roomOptions.preferences;
|
||||
|
||||
// If no existing preferences data, save the default values
|
||||
if (!preferences?.chatPreferences || !preferences?.virtualBackgroundPreferences) {
|
||||
this.saveFormData(this.preferencesForm.value);
|
||||
}
|
||||
this.wizardService.updateStepData('preferences', stepData);
|
||||
}
|
||||
|
||||
onChatToggleChange(event: any): void {
|
||||
@ -91,10 +58,10 @@ export class RoomPreferencesComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
get chatEnabled(): boolean {
|
||||
return this.preferencesForm.value.chatEnabled;
|
||||
return this.preferencesForm.value.chatEnabled || false;
|
||||
}
|
||||
|
||||
get virtualBackgroundsEnabled(): boolean {
|
||||
return this.preferencesForm.value.virtualBackgroundsEnabled;
|
||||
return this.preferencesForm.value.virtualBackgroundsEnabled || false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,17 @@
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { FormBuilder, Validators } from '@angular/forms';
|
||||
import { computed, Injectable, signal } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, ValidationErrors, Validators } from '@angular/forms';
|
||||
import { WizardNavigationConfig, WizardStep } from '@lib/models';
|
||||
import { MeetRecordingAccess, MeetRoomOptions, MeetRoomPreferences } from '@lib/typings/ce';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
// Default room preferences following the app's defaults
|
||||
const DEFAULT_PREFERENCES: MeetRoomPreferences = {
|
||||
recordingPreferences: {
|
||||
enabled: true,
|
||||
allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||
},
|
||||
chatPreferences: { enabled: true },
|
||||
virtualBackgroundPreferences: { enabled: true }
|
||||
};
|
||||
|
||||
/**
|
||||
* Service to manage the state of the room creation wizard.
|
||||
@ -12,35 +21,29 @@ import { BehaviorSubject } from 'rxjs';
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomWizardStateService {
|
||||
private readonly _formBuilder = inject(FormBuilder);
|
||||
|
||||
// Observables for reactive state management
|
||||
private readonly _currentStepIndex = new BehaviorSubject<number>(0);
|
||||
private readonly _steps = new BehaviorSubject<WizardStep[]>([]);
|
||||
private readonly _roomOptions = new BehaviorSubject<MeetRoomOptions>({
|
||||
preferences: {
|
||||
recordingPreferences: {
|
||||
enabled: false,
|
||||
allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||
},
|
||||
chatPreferences: { enabled: true },
|
||||
virtualBackgroundPreferences: { enabled: true }
|
||||
}
|
||||
// Signals for reactive state management
|
||||
private _steps = signal<WizardStep[]>([]);
|
||||
private _visibleSteps = computed(() => this._steps().filter((step) => step.isVisible));
|
||||
private _currentStepIndex = signal<number>(0);
|
||||
private _roomOptions = signal<MeetRoomOptions>({
|
||||
preferences: DEFAULT_PREFERENCES
|
||||
});
|
||||
|
||||
public readonly currentStepIndex$ = this._currentStepIndex.asObservable();
|
||||
public readonly steps$ = this._steps.asObservable();
|
||||
public readonly roomOptions$ = this._roomOptions.asObservable();
|
||||
public readonly steps = computed(() => this._steps());
|
||||
public readonly currentStepIndex = computed(() => this._currentStepIndex());
|
||||
public readonly currentStep = computed<WizardStep | undefined>(() => {
|
||||
const visibleSteps = this._visibleSteps();
|
||||
const currentIndex = this._currentStepIndex();
|
||||
|
||||
// Default room preferences following the platform's defaults
|
||||
private readonly DEFAULT_PREFERENCES: MeetRoomPreferences = {
|
||||
recordingPreferences: {
|
||||
enabled: false,
|
||||
allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_PUBLISHER
|
||||
},
|
||||
chatPreferences: { enabled: true },
|
||||
virtualBackgroundPreferences: { enabled: true }
|
||||
};
|
||||
if (currentIndex < 0 || currentIndex >= visibleSteps.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return visibleSteps[currentIndex];
|
||||
});
|
||||
public readonly roomOptions = computed(() => this._roomOptions());
|
||||
|
||||
constructor(private formBuilder: FormBuilder) {}
|
||||
|
||||
/**
|
||||
* Initializes the wizard with base steps and default room options.
|
||||
@ -49,22 +52,17 @@ export class RoomWizardStateService {
|
||||
*/
|
||||
initializeWizard(editMode: boolean = false, existingData?: MeetRoomOptions): void {
|
||||
// Initialize room options with defaults merged with existing data
|
||||
console.log('Initializing wizard - editMode:', editMode, 'existingData:', existingData);
|
||||
|
||||
const mergedPreferences: MeetRoomPreferences = {
|
||||
...this.DEFAULT_PREFERENCES,
|
||||
...existingData?.preferences
|
||||
};
|
||||
|
||||
const initialRoomOptions: MeetRoomOptions = {
|
||||
...existingData,
|
||||
preferences: mergedPreferences
|
||||
preferences: {
|
||||
...DEFAULT_PREFERENCES,
|
||||
...(existingData?.preferences || {})
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Initial room options created:', initialRoomOptions);
|
||||
this._roomOptions.next(initialRoomOptions);
|
||||
this._roomOptions.set(initialRoomOptions);
|
||||
|
||||
// Define base wizard steps
|
||||
// Define wizard steps
|
||||
const baseSteps: WizardStep[] = [
|
||||
{
|
||||
id: 'basic',
|
||||
@ -72,14 +70,62 @@ export class RoomWizardStateService {
|
||||
isCompleted: editMode, // In edit mode, mark as completed but not editable
|
||||
isActive: !editMode, // Start with basic step active in create mode
|
||||
isVisible: true,
|
||||
validationFormGroup: this._formBuilder.group({
|
||||
roomPrefix: [
|
||||
{ value: initialRoomOptions.roomIdPrefix || '', disabled: editMode },
|
||||
editMode ? [] : [Validators.minLength(2)]
|
||||
],
|
||||
deletionDate: [{ value: initialRoomOptions.autoDeletionDate || '', disabled: editMode }]
|
||||
}),
|
||||
order: 1
|
||||
formGroup: this.formBuilder.group(
|
||||
{
|
||||
roomIdPrefix: [
|
||||
{ value: initialRoomOptions.roomIdPrefix || '', disabled: editMode },
|
||||
editMode ? [] : [Validators.maxLength(50)]
|
||||
],
|
||||
autoDeletionDate: [
|
||||
{
|
||||
value: initialRoomOptions.autoDeletionDate
|
||||
? new Date(initialRoomOptions.autoDeletionDate)
|
||||
: undefined,
|
||||
disabled: editMode
|
||||
}
|
||||
],
|
||||
autoDeletionHour: [
|
||||
{
|
||||
value: initialRoomOptions.autoDeletionDate
|
||||
? new Date(initialRoomOptions.autoDeletionDate).getHours()
|
||||
: 23,
|
||||
disabled: editMode
|
||||
},
|
||||
editMode ? [] : [Validators.min(0), Validators.max(23)]
|
||||
],
|
||||
autoDeletionMinute: [
|
||||
{
|
||||
value: initialRoomOptions.autoDeletionDate
|
||||
? new Date(initialRoomOptions.autoDeletionDate).getMinutes()
|
||||
: 59,
|
||||
disabled: editMode
|
||||
},
|
||||
editMode ? [] : [Validators.min(0), Validators.max(59)]
|
||||
]
|
||||
},
|
||||
{
|
||||
// Apply future date-time validation only if not in edit mode
|
||||
validators: editMode
|
||||
? []
|
||||
: [
|
||||
(control: AbstractControl): ValidationErrors | null => {
|
||||
const date = control.get('autoDeletionDate')?.value as Date | null;
|
||||
const hour = control.get('autoDeletionHour')?.value as number | null;
|
||||
const minute = control.get('autoDeletionMinute')?.value as number | null;
|
||||
|
||||
if (!date) return null;
|
||||
|
||||
const selected = new Date(date);
|
||||
selected.setHours(hour ?? 0, minute ?? 0, 0, 0);
|
||||
|
||||
const now = new Date();
|
||||
now.setMinutes(now.getMinutes() + 61, 0, 0); // Ensure at least 1 hour in the future
|
||||
|
||||
return selected.getTime() < now.getTime() ? { minFutureDateTime: true } : null;
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
},
|
||||
{
|
||||
id: 'recording',
|
||||
@ -87,13 +133,32 @@ export class RoomWizardStateService {
|
||||
isCompleted: editMode, // In edit mode, all editable steps are completed
|
||||
isActive: editMode, // Only active in edit mode
|
||||
isVisible: true,
|
||||
validationFormGroup: this._formBuilder.group({
|
||||
enabled: [
|
||||
initialRoomOptions.preferences?.recordingPreferences?.enabled ??
|
||||
this.DEFAULT_PREFERENCES.recordingPreferences.enabled
|
||||
]
|
||||
}),
|
||||
order: 2
|
||||
formGroup: this.formBuilder.group({
|
||||
recordingEnabled: initialRoomOptions.preferences!.recordingPreferences.enabled
|
||||
? 'enabled'
|
||||
: 'disabled',
|
||||
allowAccessTo: initialRoomOptions.preferences!.recordingPreferences.allowAccessTo
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 'recordingTrigger',
|
||||
label: 'Recording Trigger',
|
||||
isCompleted: editMode, // In edit mode, all editable steps are completed
|
||||
isActive: false,
|
||||
isVisible: false, // Initially hidden, will be shown based on recording settings
|
||||
formGroup: this.formBuilder.group({
|
||||
triggerType: 'manual'
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 'recordingLayout',
|
||||
label: 'Recording Layout',
|
||||
isCompleted: editMode, // In edit mode, all editable steps are completed
|
||||
isActive: false,
|
||||
isVisible: false, // Initially hidden, will be shown based on recording settings
|
||||
formGroup: this.formBuilder.group({
|
||||
layoutType: 'grid'
|
||||
})
|
||||
},
|
||||
{
|
||||
id: 'preferences',
|
||||
@ -101,35 +166,19 @@ export class RoomWizardStateService {
|
||||
isCompleted: editMode, // In edit mode, all editable steps are completed
|
||||
isActive: false,
|
||||
isVisible: true,
|
||||
validationFormGroup: this._formBuilder.group({
|
||||
chatPreferences: this._formBuilder.group({
|
||||
enabled: [
|
||||
initialRoomOptions.preferences?.chatPreferences?.enabled ??
|
||||
this.DEFAULT_PREFERENCES.chatPreferences.enabled
|
||||
]
|
||||
}),
|
||||
virtualBackgroundPreferences: this._formBuilder.group({
|
||||
enabled: [
|
||||
initialRoomOptions.preferences?.virtualBackgroundPreferences?.enabled ??
|
||||
this.DEFAULT_PREFERENCES.virtualBackgroundPreferences.enabled
|
||||
]
|
||||
})
|
||||
}),
|
||||
order: 5
|
||||
formGroup: this.formBuilder.group({
|
||||
chatEnabled: initialRoomOptions.preferences!.chatPreferences.enabled,
|
||||
virtualBackgroundsEnabled: initialRoomOptions.preferences!.virtualBackgroundPreferences.enabled
|
||||
})
|
||||
}
|
||||
];
|
||||
|
||||
this._steps.next(baseSteps);
|
||||
this._steps.set(baseSteps);
|
||||
const initialStepIndex = editMode ? 1 : 0; // Skip basic step in edit mode
|
||||
this._currentStepIndex.set(initialStepIndex);
|
||||
|
||||
// Set the initial step index after a short delay to ensure steps are processed
|
||||
setTimeout(() => {
|
||||
const initialStepIndex = editMode ? 1 : 0; // Skip basic step in edit mode
|
||||
console.log('Setting initial step index:', initialStepIndex);
|
||||
this._currentStepIndex.next(initialStepIndex);
|
||||
|
||||
// Update step visibility after index is set
|
||||
this.updateStepVisibility();
|
||||
}, 0);
|
||||
// Update step visibility after index is set
|
||||
this.updateStepsVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,8 +188,7 @@ export class RoomWizardStateService {
|
||||
* @param stepData - The data to update in the room options
|
||||
*/
|
||||
updateStepData(stepId: string, stepData: Partial<MeetRoomOptions>): void {
|
||||
console.log(`updateStepData called - stepId: '${stepId}', stepData:`, stepData);
|
||||
const currentOptions = this._roomOptions.value;
|
||||
const currentOptions = this._roomOptions();
|
||||
let updatedOptions: MeetRoomOptions;
|
||||
|
||||
switch (stepId) {
|
||||
@ -148,6 +196,7 @@ export class RoomWizardStateService {
|
||||
updatedOptions = {
|
||||
...currentOptions
|
||||
};
|
||||
|
||||
// Only update fields that are explicitly provided
|
||||
if ('roomIdPrefix' in stepData) {
|
||||
updatedOptions.roomIdPrefix = stepData.roomIdPrefix;
|
||||
@ -155,8 +204,8 @@ export class RoomWizardStateService {
|
||||
if ('autoDeletionDate' in stepData) {
|
||||
updatedOptions.autoDeletionDate = stepData.autoDeletionDate;
|
||||
}
|
||||
break;
|
||||
|
||||
break;
|
||||
case 'recording':
|
||||
updatedOptions = {
|
||||
...currentOptions,
|
||||
@ -169,13 +218,11 @@ export class RoomWizardStateService {
|
||||
} as MeetRoomPreferences
|
||||
};
|
||||
break;
|
||||
|
||||
case 'recordingTrigger':
|
||||
case 'recordingLayout':
|
||||
// These steps don't directly modify room options but could store additional metadata
|
||||
// These steps don't update room options
|
||||
updatedOptions = { ...currentOptions };
|
||||
break;
|
||||
|
||||
case 'preferences':
|
||||
updatedOptions = {
|
||||
...currentOptions,
|
||||
@ -196,77 +243,41 @@ export class RoomWizardStateService {
|
||||
} as MeetRoomPreferences
|
||||
};
|
||||
break;
|
||||
|
||||
default:
|
||||
console.warn(`Unknown step ID: ${stepId}`);
|
||||
updatedOptions = currentOptions;
|
||||
}
|
||||
|
||||
this._roomOptions.next(updatedOptions);
|
||||
console.log(`Updated room options for step '${stepId}':`, updatedOptions);
|
||||
this.updateStepVisibility();
|
||||
this._roomOptions.set(updatedOptions);
|
||||
this.updateStepsVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visibility of wizard steps based on current room options.
|
||||
* For example, recording-related steps are only visible when recording is enabled.
|
||||
*/
|
||||
private updateStepVisibility(): void {
|
||||
const currentSteps = this._steps.value;
|
||||
const currentOptions = this._roomOptions.value;
|
||||
const recordingEnabled = currentOptions.preferences?.recordingPreferences?.enabled ?? false;
|
||||
private updateStepsVisibility(): void {
|
||||
const currentSteps = this._steps();
|
||||
const currentOptions = this._roomOptions();
|
||||
const recordingEnabled = currentOptions.preferences?.recordingPreferences.enabled ?? false;
|
||||
|
||||
// Determine if we're in edit mode by checking if basic step is completed and disabled
|
||||
const basicStep = currentSteps.find((step) => step.id === 'basic');
|
||||
const isEditMode = !!basicStep?.validationFormGroup?.disabled;
|
||||
|
||||
// Remove recording-specific steps if they exist
|
||||
const filteredSteps = currentSteps.filter((step) => !['recordingTrigger', 'recordingLayout'].includes(step.id));
|
||||
|
||||
if (recordingEnabled) {
|
||||
// Add recording-specific steps
|
||||
const recordingSteps: WizardStep[] = [
|
||||
{
|
||||
id: 'recordingTrigger',
|
||||
label: 'Recording Trigger',
|
||||
isCompleted: isEditMode, // In edit mode, mark as completed
|
||||
isActive: false,
|
||||
isVisible: true,
|
||||
validationFormGroup: this._formBuilder.group({
|
||||
type: ['manual']
|
||||
}),
|
||||
order: 3
|
||||
},
|
||||
{
|
||||
id: 'recordingLayout',
|
||||
label: 'Recording Layout',
|
||||
isCompleted: isEditMode, // In edit mode, mark as completed
|
||||
isActive: false,
|
||||
isVisible: true,
|
||||
validationFormGroup: this._formBuilder.group({
|
||||
layout: ['GRID'],
|
||||
access: ['moderator-only']
|
||||
}),
|
||||
order: 4
|
||||
}
|
||||
];
|
||||
|
||||
// Insert recording steps at the correct position (after recording step)
|
||||
filteredSteps.splice(2, 0, ...recordingSteps);
|
||||
}
|
||||
|
||||
// Reorder steps based on visibility
|
||||
filteredSteps.forEach((step, index) => {
|
||||
step.order = index + 1;
|
||||
// Update recording steps visibility based on recordingEnabled
|
||||
const updatedSteps = currentSteps.map((step) => {
|
||||
if (step.id === 'recordingTrigger' || step.id === 'recordingLayout') {
|
||||
return {
|
||||
...step,
|
||||
isVisible: recordingEnabled // Only show if recording is enabled
|
||||
};
|
||||
}
|
||||
return step;
|
||||
});
|
||||
|
||||
this._steps.next(filteredSteps);
|
||||
this._steps.set(updatedSteps);
|
||||
}
|
||||
|
||||
goToNextStep(): boolean {
|
||||
const currentIndex = this._currentStepIndex.value;
|
||||
const steps = this._steps.value;
|
||||
const visibleSteps = steps.filter((step) => step.isVisible);
|
||||
const currentIndex = this._currentStepIndex();
|
||||
const visibleSteps = this._visibleSteps();
|
||||
|
||||
if (currentIndex < visibleSteps.length - 1) {
|
||||
// Mark current step as completed
|
||||
const currentStep = visibleSteps[currentIndex];
|
||||
@ -277,17 +288,18 @@ export class RoomWizardStateService {
|
||||
const nextStep = visibleSteps[currentIndex + 1];
|
||||
nextStep.isActive = true;
|
||||
|
||||
this._currentStepIndex.next(currentIndex + 1);
|
||||
this._steps.next([...steps]);
|
||||
this._currentStepIndex.set(currentIndex + 1);
|
||||
const steps = this._steps();
|
||||
this._steps.set([...steps]); // Trigger reactivity
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
goToPreviousStep(): boolean {
|
||||
const currentIndex = this._currentStepIndex.value;
|
||||
const steps = this._steps.value;
|
||||
const visibleSteps = steps.filter((step) => step.isVisible);
|
||||
const currentIndex = this._currentStepIndex();
|
||||
const visibleSteps = this._visibleSteps();
|
||||
|
||||
if (currentIndex > 0) {
|
||||
// Deactivate current step
|
||||
@ -298,34 +310,26 @@ export class RoomWizardStateService {
|
||||
const previousStep = visibleSteps[currentIndex - 1];
|
||||
previousStep.isActive = true;
|
||||
|
||||
this._currentStepIndex.next(currentIndex - 1);
|
||||
this._steps.next([...steps]);
|
||||
this._currentStepIndex.set(currentIndex - 1);
|
||||
const steps = this._steps();
|
||||
this._steps.set([...steps]); // Trigger reactivity
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getCurrentStep(): WizardStep | null {
|
||||
const steps = this._steps.value;
|
||||
const visibleSteps = steps.filter((step) => step.isVisible);
|
||||
const currentIndex = this._currentStepIndex.value;
|
||||
|
||||
return visibleSteps[currentIndex] || null;
|
||||
}
|
||||
|
||||
getCurrentStepIndex(): number {
|
||||
return this._currentStepIndex.value;
|
||||
}
|
||||
|
||||
getVisibleSteps(): WizardStep[] {
|
||||
return this._steps.value.filter((step) => step.isVisible);
|
||||
}
|
||||
|
||||
goToStep(targetIndex: number): boolean {
|
||||
const visibleSteps = this.getVisibleSteps();
|
||||
const currentIndex = this._currentStepIndex();
|
||||
if (targetIndex === currentIndex) {
|
||||
return false; // No change if the target index is the same as current
|
||||
}
|
||||
|
||||
const visibleSteps = this._visibleSteps();
|
||||
|
||||
if (targetIndex >= 0 && targetIndex < visibleSteps.length) {
|
||||
// Deactivate current step
|
||||
const currentStep = this.getCurrentStep();
|
||||
const currentStep = this.currentStep();
|
||||
if (currentStep) {
|
||||
currentStep.isActive = false;
|
||||
}
|
||||
@ -334,54 +338,48 @@ export class RoomWizardStateService {
|
||||
const targetStep = visibleSteps[targetIndex];
|
||||
targetStep.isActive = true;
|
||||
|
||||
this._currentStepIndex.next(targetIndex);
|
||||
this._steps.next([...this._steps.value]);
|
||||
this._currentStepIndex.set(targetIndex);
|
||||
const steps = this._steps();
|
||||
this._steps.set([...steps]); // Trigger reactivity
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
getNavigationConfig(): WizardNavigationConfig {
|
||||
const currentIndex = this._currentStepIndex.value;
|
||||
const steps = this._steps.value;
|
||||
const visibleSteps = steps.filter((step) => step.isVisible);
|
||||
const isLastStep = currentIndex === visibleSteps.length - 1;
|
||||
const currentIndex = this._currentStepIndex();
|
||||
const visibleSteps = this._visibleSteps();
|
||||
const isFirstStep = currentIndex === 0;
|
||||
const isLastStep = currentIndex === visibleSteps.length - 1;
|
||||
|
||||
// Determine if we're in edit mode
|
||||
const basicStep = steps.find((step) => step.id === 'basic');
|
||||
const isEditMode = !!(basicStep?.isCompleted && basicStep.validationFormGroup?.disabled);
|
||||
const isEditMode = this.isEditMode();
|
||||
const isSomeStepInvalid = visibleSteps.some((step) => step.formGroup.invalid);
|
||||
|
||||
return {
|
||||
showPrevious: !isFirstStep,
|
||||
showNext: !isLastStep,
|
||||
showCancel: true,
|
||||
showFinish: isLastStep,
|
||||
showQuickCreate: !isEditMode && isFirstStep,
|
||||
showSkipAndFinish: !isEditMode && isFirstStep,
|
||||
disableFinish: isSomeStepInvalid,
|
||||
nextLabel: 'Next',
|
||||
previousLabel: 'Previous',
|
||||
finishLabel: isEditMode ? 'Update Room' : 'Create Room',
|
||||
isNextDisabled: false,
|
||||
isPreviousDisabled: isFirstStep
|
||||
skipAndFinishLabel: 'Create with defaults'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current room options configured in the wizard.
|
||||
* @returns The current MeetRoomOptions object
|
||||
* Checks if the wizard is in edit mode.
|
||||
* Edit mode is determined by whether the basic step is completed and its form is disabled.
|
||||
* @returns True if in edit mode, false otherwise
|
||||
*/
|
||||
getRoomOptions(): MeetRoomOptions {
|
||||
const options = this._roomOptions.getValue();
|
||||
console.log('Getting room options:', options);
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the wizard was skipped (user is still on the first step).
|
||||
* @returns True if the wizard was skipped, false otherwise
|
||||
*/
|
||||
isWizardSkipped(): boolean {
|
||||
return this._currentStepIndex.getValue() === 0;
|
||||
private isEditMode(): boolean {
|
||||
const visibleSteps = this._visibleSteps();
|
||||
const basicStep = visibleSteps.find((step) => step.id === 'basic');
|
||||
const isEditMode = !!basicStep && basicStep.isCompleted && basicStep.formGroup.disabled;
|
||||
return isEditMode;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -389,11 +387,10 @@ export class RoomWizardStateService {
|
||||
*/
|
||||
resetWizard(): void {
|
||||
const defaultOptions: MeetRoomOptions = {
|
||||
preferences: this.DEFAULT_PREFERENCES
|
||||
preferences: DEFAULT_PREFERENCES
|
||||
};
|
||||
|
||||
this._roomOptions.next(defaultOptions);
|
||||
this._currentStepIndex.next(0);
|
||||
this.initializeWizard();
|
||||
this._roomOptions.set(defaultOptions);
|
||||
this._steps.set([]);
|
||||
this._currentStepIndex.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
|
||||
import { STEPPER_GLOBAL_OPTIONS } from '@angular/cdk/stepper';
|
||||
import { provideHttpClient, withInterceptors } from '@angular/common/http';
|
||||
import { ApplicationConfig, importProvidersFrom, provideZoneChangeDetection } from '@angular/core';
|
||||
import { provideAnimationsAsync } from '@angular/platform-browser/animations/async';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { OpenViduComponentsModule, OpenViduComponentsConfig } from 'openvidu-components-angular';
|
||||
import { environment } from '@environment/environment';
|
||||
import { routes } from '@app/app.routes';
|
||||
import { environment } from '@environment/environment';
|
||||
import { httpInterceptor } from '@lib/interceptors/index';
|
||||
import { OpenViduComponentsConfig, OpenViduComponentsModule } from 'openvidu-components-angular';
|
||||
|
||||
const ovComponentsconfig: OpenViduComponentsConfig = {
|
||||
production: environment.production
|
||||
@ -17,6 +18,10 @@ export const appConfig: ApplicationConfig = {
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideAnimationsAsync(),
|
||||
provideHttpClient(withInterceptors([httpInterceptor]))
|
||||
provideHttpClient(withInterceptors([httpInterceptor])),
|
||||
{
|
||||
provide: STEPPER_GLOBAL_OPTIONS,
|
||||
useValue: { showError: true } // Show error messages in stepper
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user