-
-
-
-
-
-
-
-
-
-
-
- @if (!roomClosed) {
-
- } @else {
-
-
warning
-
- Sorry, this room is closed. You cannot join at this time. Please contact the meeting
- organizer for more information.
-
-
- }
-
-
-
-
- @if (showRecordingCard) {
-
-
-
-
-
-
- Access previously recorded meetings from this room. You can watch, download, or
- manage existing recordings.
-
-
-
-
-
-
- }
-
-
-
- @if (!roomClosed && features().canModerateRoom) {
-
- }
-
-
- @if (showBackButton) {
-
-
-
- }
-
-
}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.scss b/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.scss
index 97ebf04..b868940 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.scss
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.scss
@@ -1,327 +1,9 @@
@use '../../../../../../src/assets/styles/design-tokens';
-// Room Access Container - Main layout using design tokens
-.room-access-container {
- @include design-tokens.ov-container;
- @include design-tokens.ov-page-content;
- padding-top: var(--ov-meet-spacing-xxl);
- background: var(--ov-meet-background-color);
- gap: 0;
-}
-
-// Room Header - Clean title section
-.room-header {
- @include design-tokens.ov-flex-center;
- flex-direction: column;
- gap: var(--ov-meet-spacing-md);
- margin-bottom: var(--ov-meet-spacing-xxl);
- text-align: center;
-
- .room-icon {
- @include design-tokens.ov-icon(xl);
- color: var(--ov-meet-icon-rooms);
- margin-bottom: var(--ov-meet-spacing-sm);
- }
-
- .room-info {
- .room-title {
- margin: 0;
- font-size: var(--ov-meet-font-size-hero);
- font-weight: var(--ov-meet-font-weight-light);
- color: var(--ov-meet-text-primary);
- line-height: var(--ov-meet-line-height-tight);
- }
- }
-}
-
-// Action Cards Grid - Responsive layout
-.action-cards-grid {
- @include design-tokens.ov-grid-responsive(320px);
- gap: var(--ov-meet-spacing-xl);
- margin-bottom: var(--ov-meet-spacing-xxl);
+.prejoin-loading-container,
+.prejoin-error-container {
+ display: flex;
justify-content: center;
-
- // When there's only one card, limit its width to maintain visual consistency
- &:has(.action-card:only-child) {
- display: flex;
- justify-content: center;
-
- .action-card {
- max-width: 400px;
- width: 100%;
- }
- }
-
- @include design-tokens.ov-tablet-down {
- grid-template-columns: 1fr;
- gap: var(--ov-meet-spacing-lg);
-
- // On tablets and mobile, single cards should use full width
- &:has(.action-card:only-child) {
- .action-card {
- max-width: none;
- }
- }
- }
-}
-
-// Action Card Base - Consistent card styling
-.action-card {
- @include design-tokens.ov-card;
- @include design-tokens.ov-hover-lift(-4px);
- @include design-tokens.ov-theme-transition;
- padding: 0;
- overflow: hidden;
- min-height: 300px;
- display: flex;
- flex-direction: column;
-
- // Card Header
- .card-header {
- padding: var(--ov-meet-spacing-lg);
- border-bottom: 1px solid var(--ov-meet-border-color-light);
- display: flex;
- align-items: center;
- gap: var(--ov-meet-spacing-md);
- flex-shrink: 0;
-
- .card-icon {
- @include design-tokens.ov-icon(lg);
- flex-shrink: 0;
- }
-
- .card-title-group {
- flex: 1;
-
- .mat-mdc-card-title {
- margin: 0;
- font-size: var(--ov-meet-font-size-xl);
- font-weight: var(--ov-meet-font-weight-semibold);
- color: var(--ov-meet-text-primary);
- line-height: var(--ov-meet-line-height-tight);
- }
-
- .mat-mdc-card-subtitle {
- margin: var(--ov-meet-spacing-xs) 0 0 0;
- font-size: var(--ov-meet-font-size-sm);
- color: var(--ov-meet-text-secondary);
- line-height: var(--ov-meet-line-height-normal);
- }
- }
- }
-
- // Card Content
- .card-content {
- padding: var(--ov-meet-spacing-lg);
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- }
-}
-
-// Primary Card - Join meeting styling
-.primary-card {
- .card-header {
- background: linear-gradient(135deg, var(--ov-meet-surface-color) 0%, var(--ov-meet-color-primary-light) 180%);
- color: var(--ov-meet-text-on-primary);
- }
-
- &.room-closed-card {
- .card-header {
- background: linear-gradient(135deg, var(--ov-meet-surface-color) 0%, var(--ov-meet-color-warning) 180%);
-
- .mat-icon {
- color: var(--ov-meet-color-warning) !important;
- }
- }
- }
-}
-
-.room-closed-message {
- @include design-tokens.ov-flex-center;
- flex-direction: column;
- gap: var(--ov-meet-spacing-md);
- text-align: center;
-
- .warning-icon {
- @include design-tokens.ov-icon(xl);
- color: var(--ov-meet-color-warning);
- }
-
- p {
- margin: 0;
- font-size: var(--ov-meet-font-size-md);
- color: var(--ov-meet-text-secondary);
- line-height: var(--ov-meet-line-height-relaxed);
- }
-}
-
-// Secondary Card - Recordings styling
-.secondary-card {
- .card-header {
- background: linear-gradient(135deg, var(--ov-meet-surface-color) 0%, var(--ov-meet-color-accent) 180%);
- }
-
- .card-content {
- text-align: center;
- }
-}
-
-// Join Form - Form styling
-.join-form {
- display: flex;
- flex-direction: column;
- gap: var(--ov-meet-spacing-lg);
- flex: 1;
-
- .name-field {
- width: 100%;
-
- .mat-mdc-form-field-icon-suffix {
- color: var(--ov-meet-text-hint);
- }
- }
-
- .join-button {
- @include design-tokens.ov-button-base;
- height: 56px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: var(--ov-meet-spacing-sm);
- margin-top: auto;
- background-color: var(--ov-meet-color-secondary);
- color: var(--ov-meet-text-on-secondary);
- }
-}
-
-// Recordings Info - Content for recordings card
-.recordings-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- gap: var(--ov-meet-spacing-lg);
-
- .recordings-description {
- margin: 0;
- font-size: var(--ov-meet-font-size-md);
- color: var(--ov-meet-text-secondary);
- line-height: var(--ov-meet-line-height-relaxed);
- }
-}
-
-.recordings-button {
- @include design-tokens.ov-button-base;
- height: 56px;
- display: flex;
align-items: center;
- justify-content: center;
- gap: var(--ov-meet-spacing-sm);
- margin-top: auto;
-}
-
-// Quick Actions - Footer actions
-.quick-actions {
- @include design-tokens.ov-flex-center;
- margin-top: var(--ov-meet-spacing-xl);
-
- .quick-action-button {
- display: flex;
- align-items: center;
- gap: var(--ov-meet-spacing-sm);
- color: var(--ov-meet-text-secondary);
- @include design-tokens.ov-theme-transition;
-
- &:hover {
- color: var(--ov-meet-text-primary);
- background-color: var(--ov-meet-surface-hover);
- }
- }
-}
-
-.share-meeting-link-container {
- padding: 10px;
-}
-
-.main-share-meeting-link-container {
- background-color: var(--ov-surface-color); // Use ov-components variable
- display: flex;
- align-items: center;
- justify-content: center;
- border-radius: var(--ov-meet-radius-md);
-
- .main-share-meeting-link {
- max-width: 100%;
- }
-}
-
-// Responsive adjustments
-@include design-tokens.ov-mobile-down {
- .room-access-container {
- padding: 0;
- padding-top: var(--ov-meet-spacing-sm);
- margin-bottom: var(--ov-meet-spacing-xxl);
- }
-
- .room-header {
- margin-bottom: var(--ov-meet-spacing-xl);
-
- .room-info .room-title {
- font-size: var(--ov-meet-font-size-xxl);
- }
- }
-
- .action-card {
- min-height: auto;
-
- .card-header {
- padding: var(--ov-meet-spacing-md);
-
- .card-title-group {
- .mat-mdc-card-title {
- font-size: var(--ov-meet-font-size-lg);
- }
- }
- }
-
- .card-content {
- padding: var(--ov-meet-spacing-md);
- }
- }
-}
-
-// Custom leave button styling (existing functionality)
-::ng-deep {
- #media-buttons-container .custom-leave-btn {
- text-align: center;
- background-color: var(--ov-meet-color-error) !important;
- color: #fff !important;
- border-radius: var(--ov-meet-radius-md) !important;
- width: 65px;
- margin: 6px !important;
- }
-}
-
-.force-disconnect-btn,
-.remove-moderator-btn {
- color: var(--ov-meet-color-error);
-}
-
-.make-moderator-btn {
- color: var(--ov-meet-color-warning);
-}
-
-.participant-item-container {
- align-items: center;
-
- ::ng-deep .participant-container {
- padding: 2px 10px !important;
- }
- .moderator-badge {
- color: var(--ov-meet-color-warning);
- mat-icon {
- vertical-align: bottom;
- }
- }
+ height: 100%;
}
diff --git a/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts b/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts
index 9cb481f..187ee5b 100644
--- a/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts
+++ b/meet-ce/frontend/projects/shared-meet-components/src/lib/pages/meeting/meeting.component.ts
@@ -1,65 +1,36 @@
import { Clipboard } from '@angular/cdk/clipboard';
-import { CommonModule } from '@angular/common';
-import { Component, effect, OnInit, Signal } from '@angular/core';
-import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
-import { MatButtonModule, MatIconButton } from '@angular/material/button';
-import { MatCardModule } from '@angular/material/card';
-import { MatRippleModule } from '@angular/material/core';
-import { MatDividerModule } from '@angular/material/divider';
-import { MatFormFieldModule } from '@angular/material/form-field';
-import { MatIconModule } from '@angular/material/icon';
-import { MatInputModule } from '@angular/material/input';
-import { MatMenuModule } from '@angular/material/menu';
-import { MatTooltipModule } from '@angular/material/tooltip';
-import { ActivatedRoute } from '@angular/router';
-import { ShareMeetingLinkComponent } from '../../components';
-import { CustomParticipantModel, ErrorReason } from '../../models';
+import { CommonModule, NgComponentOutlet } from '@angular/common';
+import { Component, computed, effect, inject, OnInit, Signal, signal } from '@angular/core';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
+import { CustomParticipantModel } from '../../models';
+import { MeetingComponentsPlugins, MEETING_COMPONENTS_TOKEN, MEETING_ACTION_HANDLER_TOKEN } from '../../customization';
import {
- AppDataService,
ApplicationFeatures,
- AuthService,
FeatureConfigurationService,
GlobalConfigService,
MeetingService,
- NavigationService,
NotificationService,
ParticipantService,
- RecordingService,
- RoomService,
- SessionStorageService,
- TokenStorageService,
- WebComponentManagerService
+ WebComponentManagerService,
+ MeetingEventHandlerService
} from '../../services';
-import {
- LeftEventReason,
- MeetRoom,
- MeetRoomStatus,
- ParticipantRole,
- WebComponentEvent,
- WebComponentOutboundEventMessage,
- MeetParticipantRoleUpdatedPayload,
- MeetRoomConfigUpdatedPayload,
- MeetSignalType
-} from '@openvidu-meet/typings';
+import { MeetRoom, ParticipantRole } from '@openvidu-meet/typings';
import {
ParticipantService as ComponentParticipantService,
- DataPacket_Kind,
OpenViduComponentsUiModule,
OpenViduService,
OpenViduThemeMode,
OpenViduThemeService,
- ParticipantLeftEvent,
- ParticipantLeftReason,
- ParticipantModel,
- RecordingStartRequestedEvent,
- RecordingStopRequestedEvent,
- RemoteParticipant,
Room,
- RoomEvent,
Track,
ViewportService
} from 'openvidu-components-angular';
import { combineLatest, Subject, takeUntil } from 'rxjs';
+import { MeetingLobbyService } from '../../services/meeting/meeting-lobby.service';
+import { MeetingPluginManagerService } from '../../services/meeting/meeting-plugin-manager.service';
+import { LobbyState } from '../../models/lobby.model';
+import { MatIconModule } from '@angular/material/icon';
+import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
@Component({
selector: 'ov-meeting',
@@ -67,71 +38,56 @@ import { combineLatest, Subject, takeUntil } from 'rxjs';
styleUrls: ['./meeting.component.scss'],
imports: [
OpenViduComponentsUiModule,
- // ApiDirectiveModule,
CommonModule,
- MatFormFieldModule,
- MatInputModule,
FormsModule,
ReactiveFormsModule,
- MatCardModule,
- MatButtonModule,
+ NgComponentOutlet,
MatIconModule,
- MatIconButton,
- MatMenuModule,
- MatDividerModule,
- MatTooltipModule,
- MatRippleModule,
- ShareMeetingLinkComponent
- ]
+ MatProgressSpinnerModule
+ ],
+ providers: [MeetingLobbyService, MeetingPluginManagerService, MeetingEventHandlerService]
})
export class MeetingComponent implements OnInit {
- participantForm = new FormGroup({
- name: new FormControl('', [Validators.required])
- });
+ lobbyState?: LobbyState;
+ protected localParticipant = signal