tas-uell-sdk 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -53
- package/esm2020/lib/components/tas-avatar/tas-avatar.component.mjs +75 -0
- package/esm2020/lib/components/tas-btn/tas-btn.component.mjs +156 -63
- package/esm2020/lib/components/tas-floating-call/tas-floating-call.component.mjs +48 -23
- package/esm2020/lib/components/tas-videocall/tas-videocall.component.mjs +109 -18
- package/esm2020/lib/components/tas-waiting-room/tas-waiting-room.component.mjs +163 -131
- package/esm2020/lib/config/tas.config.mjs +1 -1
- package/esm2020/lib/interfaces/tas.interfaces.mjs +39 -2
- package/esm2020/lib/services/tas.service.mjs +363 -34
- package/esm2020/lib/tas-uell-sdk.module.mjs +20 -18
- package/esm2020/public-api.mjs +2 -1
- package/fesm2015/tas-uell-sdk.mjs +951 -270
- package/fesm2015/tas-uell-sdk.mjs.map +1 -1
- package/fesm2020/tas-uell-sdk.mjs +946 -268
- package/fesm2020/tas-uell-sdk.mjs.map +1 -1
- package/lib/components/tas-avatar/tas-avatar.component.d.ts +9 -0
- package/lib/components/tas-btn/tas-btn.component.d.ts +33 -15
- package/lib/components/tas-floating-call/tas-floating-call.component.d.ts +5 -1
- package/lib/components/tas-videocall/tas-videocall.component.d.ts +23 -2
- package/lib/components/tas-waiting-room/tas-waiting-room.component.d.ts +30 -26
- package/lib/config/tas.config.d.ts +4 -0
- package/lib/interfaces/tas.interfaces.d.ts +103 -35
- package/lib/services/tas.service.d.ts +86 -9
- package/lib/tas-uell-sdk.module.d.ts +4 -2
- package/package.json +1 -1
- package/public-api.d.ts +1 -0
- package/src/lib/styles/tas-global.scss +27 -28
- package/INSTALL_AND_TEST.md +0 -427
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, Injectable, Inject, Component, Input, ViewChild, NgModule } from '@angular/core';
|
|
2
|
+
import { InjectionToken, Injectable, Inject, Component, ChangeDetectionStrategy, Input, ViewChild, NgModule } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject, Subscription } from 'rxjs';
|
|
4
|
-
import { map, catchError
|
|
4
|
+
import { map, catchError } from 'rxjs/operators';
|
|
5
5
|
import * as OT from '@opentok/client';
|
|
6
6
|
import interact from 'interactjs';
|
|
7
7
|
import * as i1 from '@ng-bootstrap/ng-bootstrap';
|
|
8
8
|
import * as i3 from '@angular/common';
|
|
9
9
|
import { CommonModule } from '@angular/common';
|
|
10
|
+
import { FormsModule } from '@angular/forms';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Injection token for TAS configuration
|
|
@@ -17,11 +18,13 @@ const TAS_CONFIG = new InjectionToken('TAS_CONFIG');
|
|
|
17
18
|
*/
|
|
18
19
|
const TAS_HTTP_CLIENT = new InjectionToken('TAS_HTTP_CLIENT');
|
|
19
20
|
|
|
21
|
+
// Enums
|
|
20
22
|
var TasRoomType;
|
|
21
23
|
(function (TasRoomType) {
|
|
22
24
|
TasRoomType["TAS"] = "TAS";
|
|
23
25
|
TasRoomType["JM"] = "JM";
|
|
24
26
|
TasRoomType["WEBINAR"] = "WEBINAR";
|
|
27
|
+
TasRoomType["WELLNESS_MANAGER"] = "WELLNESS_MANAGER";
|
|
25
28
|
})(TasRoomType || (TasRoomType = {}));
|
|
26
29
|
var TasSessionType;
|
|
27
30
|
(function (TasSessionType) {
|
|
@@ -34,7 +37,42 @@ var TasUserRole;
|
|
|
34
37
|
TasUserRole["USER"] = "USER";
|
|
35
38
|
TasUserRole["MODERATOR"] = "MODERATOR";
|
|
36
39
|
})(TasUserRole || (TasUserRole = {}));
|
|
37
|
-
|
|
40
|
+
var TasBusinessRole;
|
|
41
|
+
(function (TasBusinessRole) {
|
|
42
|
+
TasBusinessRole["ADMIN_MANAGER"] = "ADMIN_MANAGER";
|
|
43
|
+
TasBusinessRole["MANAGER"] = "MANAGER";
|
|
44
|
+
TasBusinessRole["BACKOFFICE"] = "BACKOFFICE";
|
|
45
|
+
TasBusinessRole["USER"] = "USER";
|
|
46
|
+
})(TasBusinessRole || (TasBusinessRole = {}));
|
|
47
|
+
var VideoSessionStatus;
|
|
48
|
+
(function (VideoSessionStatus) {
|
|
49
|
+
VideoSessionStatus["ACTIVE"] = "ACTIVE";
|
|
50
|
+
VideoSessionStatus["INACTIVE"] = "INACTIVE";
|
|
51
|
+
VideoSessionStatus["PENDING"] = "PENDING";
|
|
52
|
+
VideoSessionStatus["SCHEDULED"] = "SCHEDULED";
|
|
53
|
+
})(VideoSessionStatus || (VideoSessionStatus = {}));
|
|
54
|
+
var UserStatus;
|
|
55
|
+
(function (UserStatus) {
|
|
56
|
+
UserStatus["ACTIVE"] = "ACTIVE";
|
|
57
|
+
UserStatus["INACTIVE"] = "INACTIVE";
|
|
58
|
+
UserStatus["ASSIGNED"] = "ASSIGNED";
|
|
59
|
+
})(UserStatus || (UserStatus = {}));
|
|
60
|
+
var UserCallAction;
|
|
61
|
+
(function (UserCallAction) {
|
|
62
|
+
UserCallAction["WAITING_ROOM_ENTER"] = "WAITING_ROOM_ENTER";
|
|
63
|
+
UserCallAction["WAITING_ROOM_LEAVE"] = "WAITING_ROOM_LEAVE";
|
|
64
|
+
UserCallAction["BAN"] = "BAN";
|
|
65
|
+
UserCallAction["CHANGE_STATUS"] = "CHANGE_STATUS";
|
|
66
|
+
UserCallAction["REQUEST_GEOLOCALIZATION"] = "REQUEST_GEOLOCALIZATION";
|
|
67
|
+
UserCallAction["ACTIVATE_GEOLOCATION"] = "ACTIVATE_GEOLOCATION";
|
|
68
|
+
})(UserCallAction || (UserCallAction = {}));
|
|
69
|
+
var RoomUserStatus;
|
|
70
|
+
(function (RoomUserStatus) {
|
|
71
|
+
RoomUserStatus["ASSIGNED"] = "ASSIGNED";
|
|
72
|
+
RoomUserStatus["WAITING"] = "WAITING";
|
|
73
|
+
RoomUserStatus["JOINED"] = "JOINED";
|
|
74
|
+
RoomUserStatus["FINISHED"] = "FINISHED";
|
|
75
|
+
})(RoomUserStatus || (RoomUserStatus = {}));
|
|
38
76
|
var CallState;
|
|
39
77
|
(function (CallState) {
|
|
40
78
|
CallState["IDLE"] = "IDLE";
|
|
@@ -65,6 +103,77 @@ class TasService {
|
|
|
65
103
|
// Session info for PiP mode restoration
|
|
66
104
|
this.currentSessionId = null;
|
|
67
105
|
this.currentToken = null;
|
|
106
|
+
this.STORAGE_KEY = 'tas_session_state';
|
|
107
|
+
this.DISCONNECTED_FLAG_KEY = 'tas_session_disconnected';
|
|
108
|
+
this.isFinishingSession = false;
|
|
109
|
+
// Proxy-video circuit state
|
|
110
|
+
this.proxyVideoSessionId = null;
|
|
111
|
+
this.proxyVideoToken = null;
|
|
112
|
+
this.currentBusinessRole = TasBusinessRole.USER;
|
|
113
|
+
// Waiting room and status polling
|
|
114
|
+
this.waitingRoomUsersSubject = new BehaviorSubject([]);
|
|
115
|
+
this.waitingRoomUsers$ = this.waitingRoomUsersSubject.asObservable();
|
|
116
|
+
this.ownerHasJoinedSubject = new BehaviorSubject(false);
|
|
117
|
+
this.ownerHasJoined$ = this.ownerHasJoinedSubject.asObservable();
|
|
118
|
+
// Observable that emits true when owner was present but then left
|
|
119
|
+
this.ownerHasLeftSubject = new BehaviorSubject(false);
|
|
120
|
+
this.ownerHasLeft$ = this.ownerHasLeftSubject.asObservable();
|
|
121
|
+
this.joinableSubject = new BehaviorSubject(false);
|
|
122
|
+
this.joinable$ = this.joinableSubject.asObservable();
|
|
123
|
+
this.statusPollingInterval = null;
|
|
124
|
+
this.DEFAULT_POLL_INTERVAL_MS = 30000; // Default 30s
|
|
125
|
+
this.wasOwnerPresent = false;
|
|
126
|
+
// Current call context
|
|
127
|
+
this.currentAppointmentId = null;
|
|
128
|
+
this.currentVideoCallId = null;
|
|
129
|
+
this.currentTenant = null;
|
|
130
|
+
}
|
|
131
|
+
// ... (Getters and other methods remain unchanged)
|
|
132
|
+
/**
|
|
133
|
+
* Start automatic status polling for the current session.
|
|
134
|
+
* Status is polled every intervalMs (default 30s).
|
|
135
|
+
*/
|
|
136
|
+
startStatusPolling(params, intervalMs = this.DEFAULT_POLL_INTERVAL_MS) {
|
|
137
|
+
this.stopStatusPolling(); // Clear any existing polling
|
|
138
|
+
// Store context for polling
|
|
139
|
+
if (params.sessionId) {
|
|
140
|
+
this.currentSessionId = params.sessionId;
|
|
141
|
+
}
|
|
142
|
+
if (params.appointmentId) {
|
|
143
|
+
this.currentAppointmentId = params.appointmentId;
|
|
144
|
+
}
|
|
145
|
+
if (params.tenant) {
|
|
146
|
+
this.currentTenant = params.tenant;
|
|
147
|
+
}
|
|
148
|
+
console.log(`[TAS DEBUG] Starting status polling with interval ${intervalMs}ms`);
|
|
149
|
+
// Initial status fetch
|
|
150
|
+
this.fetchAndProcessStatus(params);
|
|
151
|
+
// Set up periodic polling
|
|
152
|
+
this.statusPollingInterval = setInterval(() => {
|
|
153
|
+
this.fetchAndProcessStatus(params);
|
|
154
|
+
}, intervalMs);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Stop automatic status polling.
|
|
158
|
+
*/
|
|
159
|
+
stopStatusPolling() {
|
|
160
|
+
if (this.statusPollingInterval) {
|
|
161
|
+
clearInterval(this.statusPollingInterval);
|
|
162
|
+
this.statusPollingInterval = null;
|
|
163
|
+
}
|
|
164
|
+
// Do NOT reset waiting room state here, as other components might observe it
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Reset polling state (waiting room, owner status, etc)
|
|
168
|
+
* Call this when completely exiting the flow
|
|
169
|
+
*/
|
|
170
|
+
resetPollingState() {
|
|
171
|
+
this.stopStatusPolling();
|
|
172
|
+
this.waitingRoomUsersSubject.next([]);
|
|
173
|
+
this.ownerHasJoinedSubject.next(false);
|
|
174
|
+
this.ownerHasLeftSubject.next(false);
|
|
175
|
+
this.joinableSubject.next(false);
|
|
176
|
+
this.wasOwnerPresent = false;
|
|
68
177
|
}
|
|
69
178
|
// Getters
|
|
70
179
|
get currentSession() {
|
|
@@ -82,18 +191,36 @@ class TasService {
|
|
|
82
191
|
get token() {
|
|
83
192
|
return this.currentToken;
|
|
84
193
|
}
|
|
194
|
+
get proxySessionId() {
|
|
195
|
+
return this.proxyVideoSessionId;
|
|
196
|
+
}
|
|
197
|
+
get proxyToken() {
|
|
198
|
+
return this.proxyVideoToken;
|
|
199
|
+
}
|
|
200
|
+
get businessRole() {
|
|
201
|
+
return this.currentBusinessRole;
|
|
202
|
+
}
|
|
85
203
|
get isMuted() {
|
|
86
204
|
return this.isMutedSubject.getValue();
|
|
87
205
|
}
|
|
88
206
|
// View Mode Methods
|
|
89
207
|
setViewMode(mode) {
|
|
90
208
|
this.viewModeSubject.next(mode);
|
|
209
|
+
if (this.currentSessionId && this.currentToken) {
|
|
210
|
+
this.saveSessionState(this.currentSessionId, this.currentToken, mode, this.currentBusinessRole);
|
|
211
|
+
}
|
|
91
212
|
}
|
|
92
213
|
enterPipMode() {
|
|
93
214
|
this.viewModeSubject.next(ViewMode.PIP);
|
|
215
|
+
if (this.currentSessionId && this.currentToken) {
|
|
216
|
+
this.saveSessionState(this.currentSessionId, this.currentToken, ViewMode.PIP, this.currentBusinessRole);
|
|
217
|
+
}
|
|
94
218
|
}
|
|
95
219
|
exitPipMode() {
|
|
96
220
|
this.viewModeSubject.next(ViewMode.FULLSCREEN);
|
|
221
|
+
if (this.currentSessionId && this.currentToken) {
|
|
222
|
+
this.saveSessionState(this.currentSessionId, this.currentToken, ViewMode.FULLSCREEN, this.currentBusinessRole);
|
|
223
|
+
}
|
|
97
224
|
}
|
|
98
225
|
isPipMode() {
|
|
99
226
|
return this.viewModeSubject.getValue() === ViewMode.PIP;
|
|
@@ -121,7 +248,17 @@ class TasService {
|
|
|
121
248
|
}
|
|
122
249
|
}
|
|
123
250
|
// Session Management
|
|
124
|
-
disconnectSession() {
|
|
251
|
+
disconnectSession(clearStorage = true) {
|
|
252
|
+
console.log('[TAS DEBUG] TasService.disconnectSession called. clearStorage:', clearStorage);
|
|
253
|
+
// Call finishSession before disconnecting if we have a sessionId
|
|
254
|
+
const sessionIdToFinish = this.currentSessionId;
|
|
255
|
+
// Clear storage FIRST to prevent any race conditions where state might be saved after disconnect
|
|
256
|
+
if (clearStorage) {
|
|
257
|
+
this.clearSessionState();
|
|
258
|
+
// Set a flag to indicate this session was intentionally disconnected
|
|
259
|
+
// This prevents reconnection on page reload
|
|
260
|
+
sessionStorage.setItem(this.DISCONNECTED_FLAG_KEY, 'true');
|
|
261
|
+
}
|
|
125
262
|
if (this.session) {
|
|
126
263
|
this.session.disconnect();
|
|
127
264
|
this.session = null;
|
|
@@ -130,50 +267,261 @@ class TasService {
|
|
|
130
267
|
this.subscribers = [];
|
|
131
268
|
this.currentSessionId = null;
|
|
132
269
|
this.currentToken = null;
|
|
270
|
+
this.proxyVideoSessionId = null;
|
|
271
|
+
this.proxyVideoToken = null;
|
|
133
272
|
this.isMutedSubject.next(false); // Reset mute state
|
|
134
273
|
this.viewModeSubject.next(ViewMode.FULLSCREEN);
|
|
135
274
|
this.callStateSubject.next(CallState.DISCONNECTED);
|
|
275
|
+
// Call proxy finish after disconnecting (only if not already finishing)
|
|
276
|
+
if (sessionIdToFinish && !this.isFinishingSession) {
|
|
277
|
+
this.isFinishingSession = true;
|
|
278
|
+
this.finishProxyVideoSession({
|
|
279
|
+
sessionId: sessionIdToFinish,
|
|
280
|
+
businessRole: this.currentBusinessRole,
|
|
281
|
+
}).subscribe({
|
|
282
|
+
next: (response) => {
|
|
283
|
+
console.log('[TAS DEBUG] Session finished successfully:', response);
|
|
284
|
+
this.isFinishingSession = false;
|
|
285
|
+
},
|
|
286
|
+
error: (error) => {
|
|
287
|
+
console.error('[TAS DEBUG] Error finishing session:', error);
|
|
288
|
+
this.isFinishingSession = false;
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
}
|
|
136
292
|
}
|
|
137
293
|
isCallActive() {
|
|
138
294
|
return this.callStateSubject.getValue() === CallState.CONNECTED;
|
|
139
295
|
}
|
|
296
|
+
// State Persistence
|
|
297
|
+
saveSessionState(sessionId, token, viewMode, businessRole = TasBusinessRole.USER) {
|
|
298
|
+
const state = {
|
|
299
|
+
sessionId,
|
|
300
|
+
token,
|
|
301
|
+
viewMode,
|
|
302
|
+
businessRole,
|
|
303
|
+
};
|
|
304
|
+
sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(state));
|
|
305
|
+
}
|
|
306
|
+
clearSessionState() {
|
|
307
|
+
sessionStorage.removeItem(this.STORAGE_KEY);
|
|
308
|
+
// Also clear the disconnected flag when clearing state
|
|
309
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
310
|
+
}
|
|
311
|
+
canRestoreSession() {
|
|
312
|
+
return !!sessionStorage.getItem(this.STORAGE_KEY);
|
|
313
|
+
}
|
|
314
|
+
restoreSession(containerId) {
|
|
315
|
+
const savedState = sessionStorage.getItem(this.STORAGE_KEY);
|
|
316
|
+
if (!savedState)
|
|
317
|
+
return;
|
|
318
|
+
// Check if this session was intentionally disconnected
|
|
319
|
+
// If so, don't restore it on page reload
|
|
320
|
+
const wasDisconnected = sessionStorage.getItem(this.DISCONNECTED_FLAG_KEY);
|
|
321
|
+
if (wasDisconnected === 'true') {
|
|
322
|
+
console.log('[TAS DEBUG] Session was intentionally disconnected, skipping restore');
|
|
323
|
+
this.clearSessionState();
|
|
324
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
// Don't restore if we're already disconnected or if there's no active session
|
|
328
|
+
// This prevents restoring sessions that were properly disconnected
|
|
329
|
+
if (this.callStateSubject.getValue() === CallState.DISCONNECTED) {
|
|
330
|
+
console.log('[TAS DEBUG] Call state is DISCONNECTED, skipping restore');
|
|
331
|
+
this.clearSessionState();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const state = JSON.parse(savedState);
|
|
336
|
+
if (state.sessionId && state.token) {
|
|
337
|
+
console.log('[TAS DEBUG] Restoring session from storage');
|
|
338
|
+
// Force PiP mode for restoration to ensure UI consistency
|
|
339
|
+
this.viewModeSubject.next(ViewMode.PIP);
|
|
340
|
+
if (state.businessRole) {
|
|
341
|
+
this.currentBusinessRole = state.businessRole;
|
|
342
|
+
}
|
|
343
|
+
this.connectSession(state.sessionId, state.token, containerId, // Use the same container for both since we are in PiP
|
|
344
|
+
containerId, this.currentBusinessRole)
|
|
345
|
+
.then(() => {
|
|
346
|
+
console.log('[TAS DEBUG] Session restored successfully');
|
|
347
|
+
// Clear the disconnected flag if restoration succeeds
|
|
348
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
349
|
+
})
|
|
350
|
+
.catch((err) => {
|
|
351
|
+
console.error('[TAS DEBUG] Failed to restore session:', err);
|
|
352
|
+
this.clearSessionState(); // Clear bad state
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
catch (e) {
|
|
357
|
+
console.error('[TAS DEBUG] Error parsing saved session state', e);
|
|
358
|
+
this.clearSessionState();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
140
361
|
// API Methods
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
362
|
+
/**
|
|
363
|
+
* PROXY circuit token: /v2/proxy/video/start
|
|
364
|
+
*/
|
|
365
|
+
startProxyVideoSession(payload) {
|
|
366
|
+
return this.httpClient
|
|
367
|
+
.post('v2/proxy/video/start', {
|
|
368
|
+
body: payload,
|
|
369
|
+
headers: {},
|
|
370
|
+
})
|
|
371
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
372
|
+
console.error('TAS Service: startProxyVideoSession failed', error);
|
|
373
|
+
throw error;
|
|
374
|
+
}));
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* PROXY circuit status: /v2/proxy/video/status
|
|
378
|
+
*/
|
|
379
|
+
getProxyVideoStatus(payload) {
|
|
380
|
+
return this.httpClient
|
|
381
|
+
.post('v2/proxy/video/status', {
|
|
382
|
+
body: payload,
|
|
383
|
+
headers: {},
|
|
384
|
+
})
|
|
385
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
386
|
+
console.error('TAS Service: getProxyVideoStatus failed', error);
|
|
387
|
+
throw error;
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* PROXY circuit user modification: /v2/proxy/video/user/modify
|
|
392
|
+
*/
|
|
393
|
+
modifyProxyVideoUser(payload) {
|
|
394
|
+
return this.httpClient
|
|
395
|
+
.patch('v2/proxy/video/user/modify', {
|
|
396
|
+
body: payload,
|
|
397
|
+
headers: {},
|
|
398
|
+
})
|
|
399
|
+
.pipe(catchError((error) => {
|
|
400
|
+
console.error('TAS Service: modifyProxyVideoUser failed', error);
|
|
144
401
|
throw error;
|
|
145
402
|
}));
|
|
146
403
|
}
|
|
147
|
-
|
|
148
|
-
return this.httpClient
|
|
149
|
-
|
|
404
|
+
finishProxyVideoSession(payload) {
|
|
405
|
+
return this.httpClient
|
|
406
|
+
.post('v2/proxy/video/finish', {
|
|
407
|
+
body: payload,
|
|
408
|
+
headers: {},
|
|
409
|
+
})
|
|
410
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
411
|
+
console.error('TAS Service: finishProxyVideoSession failed', error);
|
|
150
412
|
throw error;
|
|
151
413
|
}));
|
|
152
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Start automatic status polling for the current session.
|
|
417
|
+
* Status is polled every STATUS_POLL_INTERVAL_MS milliseconds.
|
|
418
|
+
*/
|
|
419
|
+
/**
|
|
420
|
+
* Stop automatic status polling.
|
|
421
|
+
*/
|
|
422
|
+
/**
|
|
423
|
+
* Fetch status and process the response.
|
|
424
|
+
*/
|
|
425
|
+
fetchAndProcessStatus(params) {
|
|
426
|
+
this.getProxyVideoStatus(params).subscribe({
|
|
427
|
+
next: (response) => {
|
|
428
|
+
this.processStatusResponse(response);
|
|
429
|
+
},
|
|
430
|
+
error: (err) => {
|
|
431
|
+
console.error('[TAS DEBUG] Status polling error:', err);
|
|
432
|
+
},
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Process status response to update waiting room users and owner join status.
|
|
437
|
+
*/
|
|
438
|
+
processStatusResponse(response) {
|
|
439
|
+
const content = response.content;
|
|
440
|
+
// Update videoCallId if available
|
|
441
|
+
if (content.videoCallId) {
|
|
442
|
+
this.currentVideoCallId = content.videoCallId;
|
|
443
|
+
}
|
|
444
|
+
// Update sessionId if available
|
|
445
|
+
if (content.sessionId) {
|
|
446
|
+
this.currentSessionId = content.sessionId;
|
|
447
|
+
this.proxyVideoSessionId = content.sessionId;
|
|
448
|
+
}
|
|
449
|
+
// Update joinable status
|
|
450
|
+
this.joinableSubject.next(content.joinable);
|
|
451
|
+
// Check if owner has joined
|
|
452
|
+
const ownerJoined = this.checkIfOwnerJoined(content.users);
|
|
453
|
+
this.ownerHasJoinedSubject.next(ownerJoined);
|
|
454
|
+
// Detect if owner left: was present, now not present
|
|
455
|
+
if (this.wasOwnerPresent && !ownerJoined) {
|
|
456
|
+
console.log('[TAS DEBUG] Owner has left the session');
|
|
457
|
+
this.ownerHasLeftSubject.next(true);
|
|
458
|
+
}
|
|
459
|
+
if (ownerJoined) {
|
|
460
|
+
this.wasOwnerPresent = true;
|
|
461
|
+
}
|
|
462
|
+
// Extract waiting room users (status === WAITING)
|
|
463
|
+
const waitingUsers = content.users
|
|
464
|
+
.filter((u) => u.status === 'WAITING')
|
|
465
|
+
.map((u) => ({
|
|
466
|
+
userId: u.userId,
|
|
467
|
+
name: `User ${u.userId}`,
|
|
468
|
+
status: RoomUserStatus.WAITING,
|
|
469
|
+
}));
|
|
470
|
+
this.waitingRoomUsersSubject.next(waitingUsers);
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Check if at least one OWNER has joined the call.
|
|
474
|
+
*/
|
|
475
|
+
checkIfOwnerJoined(users) {
|
|
476
|
+
return users.some((u) => u.rol === TasUserRole.OWNER && u.status === 'JOINED');
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Admit a user from the waiting room by changing their status.
|
|
480
|
+
*/
|
|
481
|
+
admitUserFromWaitingRoom(userId, videoCallId) {
|
|
482
|
+
return this.modifyProxyVideoUser({
|
|
483
|
+
userId,
|
|
484
|
+
videoCallId,
|
|
485
|
+
action: UserCallAction.WAITING_ROOM_LEAVE,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
// Getters for current call context
|
|
489
|
+
get appointmentId() {
|
|
490
|
+
return this.currentAppointmentId;
|
|
491
|
+
}
|
|
492
|
+
get videoCallId() {
|
|
493
|
+
return this.currentVideoCallId;
|
|
494
|
+
}
|
|
153
495
|
/**
|
|
154
496
|
* Connects to a TokBox video session
|
|
155
497
|
*/
|
|
156
|
-
connectSession(sessionId, token, publisherElement, subscriberElement) {
|
|
498
|
+
connectSession(sessionId, token, publisherElement, subscriberElement, businessRole = TasBusinessRole.USER) {
|
|
157
499
|
this.callStateSubject.next(CallState.CONNECTING);
|
|
158
500
|
this.currentSessionId = sessionId;
|
|
159
501
|
this.currentToken = token;
|
|
502
|
+
this.currentBusinessRole = businessRole;
|
|
503
|
+
this.isFinishingSession = false; // Reset flag for new session
|
|
504
|
+
// Save initial state (defaulting to current view mode)
|
|
505
|
+
this.saveSessionState(sessionId, token, this.viewModeSubject.getValue(), businessRole);
|
|
506
|
+
// Clear the disconnected flag when starting a new connection
|
|
507
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
160
508
|
return new Promise((resolve, reject) => {
|
|
161
509
|
if (!OT.checkSystemRequirements()) {
|
|
162
510
|
this.callStateSubject.next(CallState.ERROR);
|
|
163
|
-
reject(new Error(
|
|
511
|
+
reject(new Error('Browser not compatible with TokBox'));
|
|
164
512
|
return;
|
|
165
513
|
}
|
|
166
514
|
this.session = OT.initSession(this.config.tokBoxApiKey, sessionId);
|
|
167
515
|
// Handle new streams (remote participants joining)
|
|
168
|
-
this.session.on(
|
|
516
|
+
this.session.on('streamCreated', (event) => {
|
|
169
517
|
var _a;
|
|
170
518
|
const subscriber = (_a = this.session) === null || _a === void 0 ? void 0 : _a.subscribe(event.stream, subscriberElement, {
|
|
171
|
-
insertMode:
|
|
172
|
-
width:
|
|
173
|
-
height:
|
|
519
|
+
insertMode: 'append',
|
|
520
|
+
width: '100%',
|
|
521
|
+
height: '100%',
|
|
174
522
|
}, (error) => {
|
|
175
523
|
if (error) {
|
|
176
|
-
console.error(
|
|
524
|
+
console.error('Error subscribing to stream:', error);
|
|
177
525
|
}
|
|
178
526
|
});
|
|
179
527
|
if (subscriber) {
|
|
@@ -181,30 +529,49 @@ class TasService {
|
|
|
181
529
|
}
|
|
182
530
|
});
|
|
183
531
|
// Handle streams ending (remote participants leaving)
|
|
184
|
-
this.session.on(
|
|
532
|
+
this.session.on('streamDestroyed', (event) => {
|
|
185
533
|
this.subscribers = this.subscribers.filter((sub) => { var _a; return ((_a = sub.stream) === null || _a === void 0 ? void 0 : _a.streamId) !== event.stream.streamId; });
|
|
186
534
|
});
|
|
187
535
|
// Handle session disconnection
|
|
188
|
-
this.session.on(
|
|
536
|
+
this.session.on('sessionDisconnected', () => {
|
|
189
537
|
this.callStateSubject.next(CallState.DISCONNECTED);
|
|
538
|
+
// Call finishSession when session is disconnected (e.g., by server)
|
|
539
|
+
// Only call if not already finishing (prevents duplicate calls)
|
|
540
|
+
const sessionIdToFinish = this.currentSessionId;
|
|
541
|
+
if (sessionIdToFinish && !this.isFinishingSession) {
|
|
542
|
+
this.isFinishingSession = true;
|
|
543
|
+
this.finishProxyVideoSession({
|
|
544
|
+
sessionId: sessionIdToFinish,
|
|
545
|
+
businessRole: this.currentBusinessRole,
|
|
546
|
+
}).subscribe({
|
|
547
|
+
next: (response) => {
|
|
548
|
+
console.log('[TAS DEBUG] Session finished on disconnect event:', response);
|
|
549
|
+
this.isFinishingSession = false;
|
|
550
|
+
},
|
|
551
|
+
error: (error) => {
|
|
552
|
+
console.error('[TAS DEBUG] Error finishing session on disconnect:', error);
|
|
553
|
+
this.isFinishingSession = false;
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
}
|
|
190
557
|
});
|
|
191
558
|
// Connect to session
|
|
192
559
|
this.session.connect(token, (error) => {
|
|
193
560
|
if (error) {
|
|
194
|
-
console.error(
|
|
561
|
+
console.error('Error connecting to session:', error);
|
|
195
562
|
this.callStateSubject.next(CallState.ERROR);
|
|
196
563
|
reject(error);
|
|
197
564
|
return;
|
|
198
565
|
}
|
|
199
566
|
// Initialize publisher (local video)
|
|
200
567
|
this.publisher = OT.initPublisher(publisherElement, {
|
|
201
|
-
insertMode:
|
|
202
|
-
width:
|
|
203
|
-
height:
|
|
568
|
+
insertMode: 'append',
|
|
569
|
+
width: '100%',
|
|
570
|
+
height: '100%',
|
|
204
571
|
}, (err) => {
|
|
205
572
|
var _a;
|
|
206
573
|
if (err) {
|
|
207
|
-
console.error(
|
|
574
|
+
console.error('Error initializing publisher:', err);
|
|
208
575
|
this.callStateSubject.next(CallState.ERROR);
|
|
209
576
|
reject(err);
|
|
210
577
|
return;
|
|
@@ -212,7 +579,7 @@ class TasService {
|
|
|
212
579
|
// Publish to session
|
|
213
580
|
(_a = this.session) === null || _a === void 0 ? void 0 : _a.publish(this.publisher, (pubErr) => {
|
|
214
581
|
if (pubErr) {
|
|
215
|
-
console.error(
|
|
582
|
+
console.error('Error publishing stream:', pubErr);
|
|
216
583
|
this.callStateSubject.next(CallState.ERROR);
|
|
217
584
|
reject(pubErr);
|
|
218
585
|
}
|
|
@@ -273,12 +640,12 @@ class TasService {
|
|
|
273
640
|
this.movePublisherTo('publisher-container');
|
|
274
641
|
}
|
|
275
642
|
}
|
|
276
|
-
TasService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
277
|
-
TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.
|
|
278
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
643
|
+
TasService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, deps: [{ token: TAS_HTTP_CLIENT }, { token: TAS_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
644
|
+
TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, providedIn: 'root' });
|
|
645
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, decorators: [{
|
|
279
646
|
type: Injectable,
|
|
280
647
|
args: [{
|
|
281
|
-
providedIn:
|
|
648
|
+
providedIn: 'root',
|
|
282
649
|
}]
|
|
283
650
|
}], ctorParameters: function () {
|
|
284
651
|
return [{ type: undefined, decorators: [{
|
|
@@ -290,14 +657,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
290
657
|
}] }];
|
|
291
658
|
} });
|
|
292
659
|
|
|
660
|
+
class TasAvatarComponent {
|
|
661
|
+
constructor() {
|
|
662
|
+
this.name = '';
|
|
663
|
+
this.size = 80;
|
|
664
|
+
}
|
|
665
|
+
get initials() {
|
|
666
|
+
if (!this.name)
|
|
667
|
+
return '';
|
|
668
|
+
return this.name
|
|
669
|
+
.split(' ')
|
|
670
|
+
.filter((n) => n.length > 0)
|
|
671
|
+
.map((n) => n[0])
|
|
672
|
+
.join('')
|
|
673
|
+
.toUpperCase()
|
|
674
|
+
.substring(0, 2);
|
|
675
|
+
}
|
|
676
|
+
get fontSize() {
|
|
677
|
+
return Math.round(this.size * 0.4);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
TasAvatarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
681
|
+
TasAvatarComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasAvatarComponent, selector: "tas-avatar", inputs: { name: "name", size: "size" }, ngImport: i0, template: `
|
|
682
|
+
<div
|
|
683
|
+
class="avatar"
|
|
684
|
+
[style.width.px]="size"
|
|
685
|
+
[style.height.px]="size"
|
|
686
|
+
[style.fontSize.px]="fontSize"
|
|
687
|
+
>
|
|
688
|
+
<span class="initials">{{ initials }}</span>
|
|
689
|
+
</div>
|
|
690
|
+
`, isInline: true, styles: [".avatar{display:flex;align-items:center;justify-content:center;border-radius:50%;background-color:#fff;color:#0072ac;font-weight:600;font-family:inherit;box-shadow:0 4px 12px #00000026}.initials{text-transform:uppercase;-webkit-user-select:none;user-select:none}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
691
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasAvatarComponent, decorators: [{
|
|
692
|
+
type: Component,
|
|
693
|
+
args: [{
|
|
694
|
+
selector: 'tas-avatar',
|
|
695
|
+
template: `
|
|
696
|
+
<div
|
|
697
|
+
class="avatar"
|
|
698
|
+
[style.width.px]="size"
|
|
699
|
+
[style.height.px]="size"
|
|
700
|
+
[style.fontSize.px]="fontSize"
|
|
701
|
+
>
|
|
702
|
+
<span class="initials">{{ initials }}</span>
|
|
703
|
+
</div>
|
|
704
|
+
`,
|
|
705
|
+
styles: [
|
|
706
|
+
`
|
|
707
|
+
.avatar {
|
|
708
|
+
display: flex;
|
|
709
|
+
align-items: center;
|
|
710
|
+
justify-content: center;
|
|
711
|
+
border-radius: 50%;
|
|
712
|
+
background-color: #ffffff;
|
|
713
|
+
color: #0072ac;
|
|
714
|
+
font-weight: 600;
|
|
715
|
+
font-family: inherit;
|
|
716
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
.initials {
|
|
720
|
+
text-transform: uppercase;
|
|
721
|
+
user-select: none;
|
|
722
|
+
}
|
|
723
|
+
`,
|
|
724
|
+
],
|
|
725
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
726
|
+
}]
|
|
727
|
+
}], propDecorators: { name: [{
|
|
728
|
+
type: Input
|
|
729
|
+
}], size: [{
|
|
730
|
+
type: Input
|
|
731
|
+
}] } });
|
|
732
|
+
|
|
293
733
|
class TasVideocallComponent {
|
|
294
734
|
constructor(activeModal, tasService) {
|
|
295
735
|
this.activeModal = activeModal;
|
|
296
736
|
this.tasService = tasService;
|
|
737
|
+
this.participantName = '';
|
|
738
|
+
this.tenant = '';
|
|
739
|
+
this.businessRole = TasBusinessRole.USER;
|
|
297
740
|
this.isReturningFromPip = false;
|
|
298
741
|
this.isPublisherSmall = true;
|
|
299
742
|
this.callState = CallState.IDLE;
|
|
300
743
|
this.isMuted = false;
|
|
744
|
+
this.waitingRoomUsers = [];
|
|
745
|
+
this.ownerHasJoined = false;
|
|
746
|
+
this.hasVideoStream = false;
|
|
747
|
+
this.dismissedUsers = [];
|
|
301
748
|
this.subscriptions = new Subscription();
|
|
302
749
|
}
|
|
303
750
|
ngOnInit() {
|
|
@@ -309,6 +756,7 @@ class TasVideocallComponent {
|
|
|
309
756
|
}
|
|
310
757
|
ngOnDestroy() {
|
|
311
758
|
this.subscriptions.unsubscribe();
|
|
759
|
+
this.tasService.resetPollingState();
|
|
312
760
|
// Only disconnect if not in PiP mode (keep session alive for floating window)
|
|
313
761
|
if (!this.tasService.isPipMode()) {
|
|
314
762
|
this.tasService.disconnectSession();
|
|
@@ -335,25 +783,83 @@ class TasVideocallComponent {
|
|
|
335
783
|
onDoubleClick() {
|
|
336
784
|
this.toggleSwap();
|
|
337
785
|
}
|
|
786
|
+
/**
|
|
787
|
+
* Check if current user can admit others (OWNER, BACKOFFICE, or MODERATOR)
|
|
788
|
+
*/
|
|
789
|
+
get canAdmitUsers() {
|
|
790
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
791
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
792
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Admit a user from the waiting room
|
|
796
|
+
*/
|
|
797
|
+
admitUser(userId) {
|
|
798
|
+
if (!this.videoCallId) {
|
|
799
|
+
console.error('Cannot admit user: videoCallId not set');
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
this.tasService
|
|
803
|
+
.modifyProxyVideoUser({
|
|
804
|
+
userId,
|
|
805
|
+
videoCallId: this.videoCallId,
|
|
806
|
+
action: UserCallAction.WAITING_ROOM_LEAVE,
|
|
807
|
+
})
|
|
808
|
+
.subscribe({
|
|
809
|
+
next: () => {
|
|
810
|
+
// Remove user from waiting list after successful admit
|
|
811
|
+
this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
|
|
812
|
+
},
|
|
813
|
+
error: (err) => {
|
|
814
|
+
console.error('Error admitting user:', err);
|
|
815
|
+
},
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
/**
|
|
819
|
+
* Dismiss the waiting room notification for a user
|
|
820
|
+
*/
|
|
821
|
+
dismissWaitingNotification(userId) {
|
|
822
|
+
this.dismissedUsers.push(userId);
|
|
823
|
+
this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
|
|
824
|
+
}
|
|
338
825
|
// Private Methods
|
|
339
826
|
setupSubscriptions() {
|
|
340
827
|
// Call state subscription
|
|
341
|
-
this.subscriptions.add(this.tasService.callState$.subscribe(state => {
|
|
828
|
+
this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
|
|
342
829
|
this.callState = state;
|
|
343
830
|
if (state === CallState.DISCONNECTED) {
|
|
344
831
|
this.activeModal.close('hangup');
|
|
345
832
|
}
|
|
833
|
+
// Track if we have an active video stream
|
|
834
|
+
this.hasVideoStream = state === CallState.CONNECTED;
|
|
346
835
|
}));
|
|
347
836
|
// View mode subscription
|
|
348
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
837
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
349
838
|
if (mode === ViewMode.PIP) {
|
|
350
839
|
this.activeModal.close('pip');
|
|
351
840
|
}
|
|
352
841
|
}));
|
|
353
842
|
// Mute state subscription
|
|
354
|
-
this.subscriptions.add(this.tasService.isMuted$.subscribe(muted => {
|
|
843
|
+
this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
|
|
355
844
|
this.isMuted = muted;
|
|
356
845
|
}));
|
|
846
|
+
// Waiting room users subscription
|
|
847
|
+
this.subscriptions.add(this.tasService.waitingRoomUsers$.subscribe((users) => {
|
|
848
|
+
// Filter out dismissed users
|
|
849
|
+
this.waitingRoomUsers = users.filter((u) => !this.dismissedUsers.includes(u.userId));
|
|
850
|
+
}));
|
|
851
|
+
// Owner join status subscription
|
|
852
|
+
this.subscriptions.add(this.tasService.ownerHasJoined$.subscribe((joined) => {
|
|
853
|
+
this.ownerHasJoined = joined;
|
|
854
|
+
}));
|
|
855
|
+
// Owner left subscription - disconnect non-owners
|
|
856
|
+
this.subscriptions.add(this.tasService.ownerHasLeft$.subscribe((hasLeft) => {
|
|
857
|
+
if (hasLeft && !this.canAdmitUsers) { // Non-owner user
|
|
858
|
+
console.log('[TAS DEBUG] Owner left, disconnecting user');
|
|
859
|
+
this.hangUp();
|
|
860
|
+
this.activeModal.close('owner_left');
|
|
861
|
+
}
|
|
862
|
+
}));
|
|
357
863
|
}
|
|
358
864
|
initializeCall() {
|
|
359
865
|
if (this.isReturningFromPip) {
|
|
@@ -362,9 +868,22 @@ class TasVideocallComponent {
|
|
|
362
868
|
}
|
|
363
869
|
else if (this.sessionId && this.token) {
|
|
364
870
|
// New call - connect to session
|
|
365
|
-
this.tasService
|
|
871
|
+
this.tasService
|
|
872
|
+
.connectSession(this.sessionId, this.token, 'publisher-container', 'subscriber-container', this.businessRole)
|
|
873
|
+
.catch((err) => {
|
|
366
874
|
console.error('Error connecting to video call:', err);
|
|
367
875
|
});
|
|
876
|
+
// Start status polling with shorter interval (5s) to detect owner leaving/location requests
|
|
877
|
+
if (this.appointmentId) {
|
|
878
|
+
this.tasService.startStatusPolling({
|
|
879
|
+
appointmentId: this.appointmentId,
|
|
880
|
+
tenant: this.tenant,
|
|
881
|
+
businessRole: this.businessRole,
|
|
882
|
+
roomType: undefined,
|
|
883
|
+
entityId: undefined,
|
|
884
|
+
sessionId: this.sessionId
|
|
885
|
+
}, 5000);
|
|
886
|
+
}
|
|
368
887
|
}
|
|
369
888
|
}
|
|
370
889
|
resetVideoPositions() {
|
|
@@ -390,8 +909,8 @@ class TasVideocallComponent {
|
|
|
390
909
|
modifiers: [
|
|
391
910
|
interact.modifiers.restrictRect({
|
|
392
911
|
restriction: 'parent',
|
|
393
|
-
endOnly: true
|
|
394
|
-
})
|
|
912
|
+
endOnly: true,
|
|
913
|
+
}),
|
|
395
914
|
],
|
|
396
915
|
autoScroll: true,
|
|
397
916
|
listeners: {
|
|
@@ -402,8 +921,8 @@ class TasVideocallComponent {
|
|
|
402
921
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
403
922
|
target.setAttribute('data-x', String(x));
|
|
404
923
|
target.setAttribute('data-y', String(y));
|
|
405
|
-
}
|
|
406
|
-
}
|
|
924
|
+
},
|
|
925
|
+
},
|
|
407
926
|
})
|
|
408
927
|
.resizable({
|
|
409
928
|
edges: { left: false, right: true, bottom: true, top: false },
|
|
@@ -419,26 +938,36 @@ class TasVideocallComponent {
|
|
|
419
938
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
420
939
|
target.setAttribute('data-x', String(x));
|
|
421
940
|
target.setAttribute('data-y', String(y));
|
|
422
|
-
}
|
|
941
|
+
},
|
|
423
942
|
},
|
|
424
943
|
modifiers: [
|
|
425
944
|
interact.modifiers.restrictEdges({ outer: 'parent' }),
|
|
426
945
|
interact.modifiers.restrictSize({ min: { width: 150, height: 100 } }),
|
|
427
|
-
interact.modifiers.aspectRatio({ ratio: 'preserve' })
|
|
946
|
+
interact.modifiers.aspectRatio({ ratio: 'preserve' }),
|
|
428
947
|
],
|
|
429
|
-
inertia: true
|
|
948
|
+
inertia: true,
|
|
430
949
|
});
|
|
431
950
|
}
|
|
432
951
|
}
|
|
433
|
-
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
434
|
-
TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
435
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
952
|
+
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
|
|
953
|
+
TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasVideocallComponent, selector: "tas-videocall", inputs: { sessionId: "sessionId", token: "token", appointmentId: "appointmentId", videoCallId: "videoCallId", participantName: "participantName", tenant: "tenant", businessRole: "businessRole", isReturningFromPip: "isReturningFromPip" }, viewQueries: [{ propertyName: "publisherContainer", first: true, predicate: ["publisherContainer"], descendants: true }, { propertyName: "subscriberContainer", first: true, predicate: ["subscriberContainer"], descendants: true }], ngImport: i0, template: "<div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-video-camera\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n</div>\n", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:var(--Primary-Uell, #1da4b1);cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .control-btn.muted{background:#f39c12}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px);box-shadow:0 4px 12px #0003}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
954
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
|
|
436
955
|
type: Component,
|
|
437
|
-
args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-container\">\n
|
|
956
|
+
args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-container\">\n <!-- Subscriber video (large, background) -->\n <div\n id=\"subscriber-container\"\n [class.subscriber-view]=\"isPublisherSmall\"\n [class.publisher-view]=\"!isPublisherSmall\"\n #subscriberContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Publisher video (small, overlay) -->\n <div\n id=\"publisher-container\"\n [class.publisher-view]=\"isPublisherSmall\"\n [class.subscriber-view]=\"!isPublisherSmall\"\n #publisherContainer\n (dblclick)=\"onDoubleClick()\"\n ></div>\n\n <!-- Centered avatar (shown when no video stream) -->\n <div class=\"avatar-container\" *ngIf=\"!hasVideoStream\">\n <tas-avatar [name]=\"participantName\" [size]=\"80\"></tas-avatar>\n </div>\n\n <!-- Controls -->\n <div class=\"controls-container\">\n <button\n class=\"btn control-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n [attr.aria-label]=\"isMuted ? 'Activar micr\u00F3fono' : 'Silenciar micr\u00F3fono'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button\n class=\"btn control-btn swap-btn\"\n (click)=\"toggleSwap()\"\n title=\"Intercambiar vista\"\n aria-label=\"Intercambiar vista\"\n >\n <i class=\"fa fa-video-camera\"></i>\n </button>\n <button\n class=\"btn control-btn hangup-btn\"\n (click)=\"hangUp()\"\n title=\"Finalizar llamada\"\n aria-label=\"Finalizar llamada\"\n >\n <i class=\"fa fa-phone\"></i>\n </button>\n <button\n class=\"btn control-btn more-btn\"\n title=\"M\u00E1s opciones\"\n aria-label=\"M\u00E1s opciones\"\n >\n <i class=\"fa fa-ellipsis-v\"></i>\n </button>\n </div>\n\n <!-- Waiting room notification (shown for OWNER/BACKOFFICE only) -->\n <div\n class=\"waiting-notification\"\n *ngIf=\"waitingRoomUsers.length > 0 && canAdmitUsers\"\n role=\"alert\"\n aria-live=\"polite\"\n >\n <span class=\"waiting-text\">\n {{ waitingRoomUsers[0].name }} est\u00E1 en la sala de espera.\n </span>\n <button\n class=\"admit-btn\"\n (click)=\"admitUser(waitingRoomUsers[0].userId)\"\n aria-label=\"Admitir usuario\"\n >\n Admitir\n </button>\n <button\n class=\"dismiss-btn\"\n (click)=\"dismissWaitingNotification(waitingRoomUsers[0].userId)\"\n aria-label=\"Cerrar notificaci\u00F3n\"\n >\n \u00D7\n </button>\n </div>\n</div>\n", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;overflow:hidden;border-radius:8px;border:1px solid var(--Neutral-GreyLight, #dadfe9);background:linear-gradient(180deg,#e5f1f7 0%,#0072ac 100%)}.tas-videocall-container ::ng-deep .OT_edge-bar-item,.tas-videocall-container ::ng-deep .OT_mute,.tas-videocall-container ::ng-deep .OT_audio-level-meter,.tas-videocall-container ::ng-deep .OT_bar,.tas-videocall-container ::ng-deep .OT_name{display:none!important}.tas-videocall-container .subscriber-view{width:100%;height:100%;z-index:1}.tas-videocall-container .publisher-view{position:absolute;top:20px;right:20px;width:200px;height:150px;z-index:2;border:2px solid #fff;border-radius:8px;background-color:#0000004d;overflow:hidden}.tas-videocall-container .avatar-container{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);z-index:1;display:flex;align-items:center;justify-content:center}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:12px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#33475bb3;padding:12px 20px;border-radius:30px;backdrop-filter:blur(8px)}.tas-videocall-container .controls-container .control-btn{width:44px;height:44px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:18px;border:none;background:var(--Primary-Uell, #1da4b1);cursor:pointer;transition:all .2s ease}.tas-videocall-container .controls-container .control-btn i{color:#fff}.tas-videocall-container .controls-container .control-btn:hover{transform:scale(1.05);filter:brightness(1.1)}.tas-videocall-container .controls-container .control-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .controls-container .control-btn.muted{background:#f39c12}.tas-videocall-container .controls-container .hangup-btn{background:#f44336}.tas-videocall-container .controls-container .hangup-btn i{transform:rotate(135deg)}.tas-videocall-container .controls-container .hangup-btn:hover{background:#d32f2f}.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn{background:var(--Primary-Uell, #1da4b1)}.tas-videocall-container .controls-container .swap-btn:hover,.tas-videocall-container .controls-container .pip-btn:hover{background:#178e99}.tas-videocall-container .controls-container .more-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .more-btn:hover{background:rgba(255,255,255,.35)}.tas-videocall-container .waiting-notification{position:absolute;bottom:100px;left:16px;display:flex;align-items:center;gap:12px;background-color:#33475be6;padding:10px 16px;border-radius:8px;z-index:4;backdrop-filter:blur(4px);max-width:calc(100% - 32px);box-shadow:0 4px 12px #0003}.tas-videocall-container .waiting-notification .waiting-text{color:#fff;font-size:14px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tas-videocall-container .waiting-notification .admit-btn{background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:4px;padding:6px 16px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;white-space:nowrap}.tas-videocall-container .waiting-notification .admit-btn:hover{background:#178e99}.tas-videocall-container .waiting-notification .admit-btn:focus{outline:2px solid #fff;outline-offset:2px}.tas-videocall-container .waiting-notification .dismiss-btn{background:transparent;color:#fff;border:none;font-size:20px;line-height:1;cursor:pointer;padding:0 4px;opacity:.7;transition:opacity .2s ease}.tas-videocall-container .waiting-notification .dismiss-btn:hover{opacity:1}.tas-videocall-container .waiting-notification .dismiss-btn:focus{outline:2px solid #fff;outline-offset:2px}\n"] }]
|
|
438
957
|
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { sessionId: [{
|
|
439
958
|
type: Input
|
|
440
959
|
}], token: [{
|
|
441
960
|
type: Input
|
|
961
|
+
}], appointmentId: [{
|
|
962
|
+
type: Input
|
|
963
|
+
}], videoCallId: [{
|
|
964
|
+
type: Input
|
|
965
|
+
}], participantName: [{
|
|
966
|
+
type: Input
|
|
967
|
+
}], tenant: [{
|
|
968
|
+
type: Input
|
|
969
|
+
}], businessRole: [{
|
|
970
|
+
type: Input
|
|
442
971
|
}], isReturningFromPip: [{
|
|
443
972
|
type: Input
|
|
444
973
|
}], publisherContainer: [{
|
|
@@ -451,129 +980,195 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
451
980
|
|
|
452
981
|
var WaitingRoomState;
|
|
453
982
|
(function (WaitingRoomState) {
|
|
454
|
-
WaitingRoomState["
|
|
455
|
-
WaitingRoomState["
|
|
456
|
-
WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
|
|
983
|
+
WaitingRoomState["CHECKING_STATUS"] = "CHECKING_STATUS";
|
|
984
|
+
WaitingRoomState["WAITING_FOR_OWNER"] = "WAITING_FOR_OWNER";
|
|
457
985
|
WaitingRoomState["READY"] = "READY";
|
|
986
|
+
WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
|
|
458
987
|
WaitingRoomState["ERROR"] = "ERROR";
|
|
459
988
|
})(WaitingRoomState || (WaitingRoomState = {}));
|
|
460
989
|
class TasWaitingRoomComponent {
|
|
461
|
-
constructor(activeModal, tasService, modalService) {
|
|
990
|
+
constructor(activeModal, tasService, modalService, cdr) {
|
|
462
991
|
this.activeModal = activeModal;
|
|
463
992
|
this.tasService = tasService;
|
|
464
993
|
this.modalService = modalService;
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
this.
|
|
468
|
-
this.
|
|
469
|
-
this.regularUserIds = [];
|
|
470
|
-
this.moderatorUserIds = [];
|
|
471
|
-
/** Optional: If provided, skips room creation and goes directly to getting a token */
|
|
472
|
-
this.existingSessionId = '';
|
|
994
|
+
this.cdr = cdr;
|
|
995
|
+
// Status endpoint params
|
|
996
|
+
this.roomType = TasRoomType.TAS;
|
|
997
|
+
this.businessRole = TasBusinessRole.USER;
|
|
473
998
|
// Component state
|
|
474
|
-
this.state = WaitingRoomState.
|
|
999
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
475
1000
|
this.WaitingRoomState = WaitingRoomState; // Expose enum to template
|
|
476
1001
|
this.errorMessage = '';
|
|
477
|
-
|
|
478
|
-
this.
|
|
479
|
-
|
|
480
|
-
this.sessionId = '';
|
|
1002
|
+
// Session data from status response
|
|
1003
|
+
this.resolvedSessionId = '';
|
|
1004
|
+
this.resolvedAppointmentId = null;
|
|
481
1005
|
this.token = '';
|
|
482
|
-
this.
|
|
1006
|
+
this.videoCallId = null;
|
|
1007
|
+
// Subscriptions
|
|
483
1008
|
this.subscriptions = new Subscription();
|
|
484
1009
|
this.videoCallModalRef = null;
|
|
485
1010
|
}
|
|
1011
|
+
/** Whether current user is an owner */
|
|
1012
|
+
get isOwner() {
|
|
1013
|
+
var _a;
|
|
1014
|
+
return ((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.role) === TasUserRole.OWNER;
|
|
1015
|
+
}
|
|
486
1016
|
ngOnInit() {
|
|
487
|
-
|
|
488
|
-
this.
|
|
489
|
-
// Check if we have an existing session
|
|
490
|
-
if (this.existingSessionId && this.existingSessionId.trim() !== '') {
|
|
491
|
-
this.hasExistingSession = true;
|
|
492
|
-
this.sessionId = this.existingSessionId;
|
|
493
|
-
}
|
|
1017
|
+
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnInit');
|
|
1018
|
+
this.checkStatus();
|
|
494
1019
|
}
|
|
495
1020
|
ngOnDestroy() {
|
|
1021
|
+
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnDestroy');
|
|
496
1022
|
this.subscriptions.unsubscribe();
|
|
1023
|
+
this.tasService.stopStatusPolling();
|
|
497
1024
|
}
|
|
498
1025
|
/**
|
|
499
|
-
*
|
|
1026
|
+
* Check status to get session info
|
|
500
1027
|
*/
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
this.
|
|
505
|
-
this.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
1028
|
+
checkStatus() {
|
|
1029
|
+
console.log('[TAS DEBUG] checkStatus called with:', {
|
|
1030
|
+
roomType: this.roomType,
|
|
1031
|
+
entityId: this.entityId,
|
|
1032
|
+
tenant: this.tenant,
|
|
1033
|
+
businessRole: this.businessRole,
|
|
1034
|
+
currentUser: this.currentUser,
|
|
1035
|
+
});
|
|
1036
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
509
1037
|
this.errorMessage = '';
|
|
510
|
-
const
|
|
511
|
-
roomType:
|
|
512
|
-
|
|
513
|
-
tenant: this.
|
|
514
|
-
|
|
515
|
-
users: this.users,
|
|
516
|
-
product: this.product
|
|
1038
|
+
const statusParams = {
|
|
1039
|
+
roomType: this.roomType,
|
|
1040
|
+
entityId: this.entityId,
|
|
1041
|
+
tenant: this.tenant,
|
|
1042
|
+
businessRole: this.businessRole,
|
|
517
1043
|
};
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
this.
|
|
1044
|
+
console.log('[TAS DEBUG] Calling getProxyVideoStatus...');
|
|
1045
|
+
this.subscriptions.add(this.tasService.getProxyVideoStatus(statusParams).subscribe({
|
|
1046
|
+
next: (response) => {
|
|
1047
|
+
const content = response.content;
|
|
1048
|
+
// Store session info from response
|
|
1049
|
+
this.resolvedSessionId = content.sessionId;
|
|
1050
|
+
this.resolvedAppointmentId = content.appointmentId;
|
|
1051
|
+
this.videoCallId = content.videoCallId;
|
|
1052
|
+
console.log('[TAS DEBUG] Status response:', content);
|
|
1053
|
+
// Start polling for status updates
|
|
1054
|
+
this.tasService.startStatusPolling(statusParams);
|
|
1055
|
+
// Subscribe to joinable status
|
|
1056
|
+
this.subscriptions.add(this.tasService.joinable$.subscribe((joinable) => {
|
|
1057
|
+
this.handleJoinableChange(joinable);
|
|
1058
|
+
}));
|
|
531
1059
|
},
|
|
532
1060
|
error: (err) => {
|
|
533
|
-
console.error('
|
|
1061
|
+
console.error('[TAS DEBUG] Status check failed:', err);
|
|
534
1062
|
this.state = WaitingRoomState.ERROR;
|
|
535
|
-
this.errorMessage = 'Error
|
|
536
|
-
}
|
|
1063
|
+
this.errorMessage = 'Error checking session status. Please try again.';
|
|
1064
|
+
},
|
|
537
1065
|
}));
|
|
538
1066
|
}
|
|
539
1067
|
/**
|
|
540
|
-
*
|
|
1068
|
+
* Handle changes to joinable status
|
|
541
1069
|
*/
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1070
|
+
handleJoinableChange(joinable) {
|
|
1071
|
+
console.log('[TAS DEBUG] handleJoinableChange called', {
|
|
1072
|
+
joinable,
|
|
1073
|
+
currentState: this.state,
|
|
1074
|
+
isOwner: this.isOwner,
|
|
1075
|
+
isBackoffice: this.isBackoffice,
|
|
1076
|
+
resolvedSessionId: this.resolvedSessionId,
|
|
1077
|
+
});
|
|
1078
|
+
// Don't update state if already getting token, ready, or in error
|
|
1079
|
+
if (this.state === WaitingRoomState.GETTING_TOKEN ||
|
|
1080
|
+
this.state === WaitingRoomState.READY ||
|
|
1081
|
+
this.state === WaitingRoomState.ERROR) {
|
|
1082
|
+
console.log('[TAS DEBUG] Skipping state update - already in:', this.state);
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
if (this.isOwner || this.isBackoffice) {
|
|
1086
|
+
console.log('[TAS DEBUG] User is owner/backoffice, calling getTokenForOwner');
|
|
1087
|
+
// Owner/Backoffice: call /start to get token first, then show join button
|
|
1088
|
+
if (this.state === WaitingRoomState.CHECKING_STATUS) {
|
|
1089
|
+
this.getTokenForOwner();
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
else {
|
|
1093
|
+
// Non-owner: wait until joinable is true
|
|
1094
|
+
if (joinable) {
|
|
1095
|
+
console.log('[TAS DEBUG] Non-owner: joinable is true, getting token');
|
|
1096
|
+
this.getTokenForOwner(); // Also get token when joinable
|
|
1097
|
+
}
|
|
1098
|
+
else {
|
|
1099
|
+
console.log('[TAS DEBUG] Non-owner: waiting for owner');
|
|
1100
|
+
this.state = WaitingRoomState.WAITING_FOR_OWNER;
|
|
1101
|
+
this.cdr.detectChanges();
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Check if user has owner/backoffice role
|
|
1107
|
+
*/
|
|
1108
|
+
get isBackoffice() {
|
|
1109
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
1110
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
1111
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
1112
|
+
}
|
|
1113
|
+
/**
|
|
1114
|
+
* Get token for owner/backoffice - call /start endpoint
|
|
1115
|
+
*/
|
|
1116
|
+
getTokenForOwner() {
|
|
1117
|
+
if (!this.resolvedSessionId) {
|
|
545
1118
|
this.state = WaitingRoomState.ERROR;
|
|
546
|
-
this.errorMessage = '
|
|
1119
|
+
this.errorMessage = 'Session ID not available';
|
|
1120
|
+
this.tasService.stopStatusPolling();
|
|
1121
|
+
this.cdr.detectChanges();
|
|
547
1122
|
return;
|
|
548
1123
|
}
|
|
549
1124
|
this.state = WaitingRoomState.GETTING_TOKEN;
|
|
550
1125
|
this.errorMessage = '';
|
|
551
|
-
|
|
552
|
-
|
|
1126
|
+
console.log('[TAS DEBUG] Calling /start for session:', this.resolvedSessionId);
|
|
1127
|
+
this.subscriptions.add(this.tasService
|
|
1128
|
+
.startProxyVideoSession({
|
|
1129
|
+
sessionId: this.resolvedSessionId,
|
|
553
1130
|
name: this.currentUser.name,
|
|
554
1131
|
lastname: this.currentUser.lastname,
|
|
555
|
-
|
|
556
|
-
|
|
1132
|
+
})
|
|
1133
|
+
.subscribe({
|
|
557
1134
|
next: (tokenResponse) => {
|
|
1135
|
+
var _a;
|
|
1136
|
+
console.log('[TAS DEBUG] Token response:', tokenResponse);
|
|
1137
|
+
// Handle case where HTTP adapter returns error in next instead of error handler
|
|
1138
|
+
if (!((_a = tokenResponse === null || tokenResponse === void 0 ? void 0 : tokenResponse.content) === null || _a === void 0 ? void 0 : _a.token)) {
|
|
1139
|
+
console.error('[TAS DEBUG] Invalid token response:', tokenResponse);
|
|
1140
|
+
this.state = WaitingRoomState.ERROR;
|
|
1141
|
+
this.errorMessage = (tokenResponse === null || tokenResponse === void 0 ? void 0 : tokenResponse.message) || 'Error al iniciar la sesión. Respuesta inválida.';
|
|
1142
|
+
this.tasService.stopStatusPolling();
|
|
1143
|
+
this.cdr.detectChanges();
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
console.log('[TAS DEBUG] Token obtained successfully');
|
|
558
1147
|
this.token = tokenResponse.content.token;
|
|
559
1148
|
this.state = WaitingRoomState.READY;
|
|
1149
|
+
this.cdr.detectChanges();
|
|
560
1150
|
},
|
|
561
1151
|
error: (err) => {
|
|
562
|
-
|
|
1152
|
+
var _a;
|
|
1153
|
+
console.error('[TAS DEBUG] /start request failed:', err);
|
|
563
1154
|
this.state = WaitingRoomState.ERROR;
|
|
564
|
-
this.errorMessage = 'Error
|
|
565
|
-
|
|
1155
|
+
this.errorMessage = ((_a = err === null || err === void 0 ? void 0 : err.error) === null || _a === void 0 ? void 0 : _a.message) || (err === null || err === void 0 ? void 0 : err.message) || 'Error al iniciar la sesión. Por favor, intente nuevamente.';
|
|
1156
|
+
this.tasService.stopStatusPolling();
|
|
1157
|
+
console.log('[TAS DEBUG] State set to ERROR, errorMessage:', this.errorMessage);
|
|
1158
|
+
this.cdr.detectChanges();
|
|
1159
|
+
},
|
|
566
1160
|
}));
|
|
567
1161
|
}
|
|
568
1162
|
/**
|
|
569
|
-
*
|
|
1163
|
+
* Join the session - token already obtained
|
|
570
1164
|
*/
|
|
571
1165
|
joinSession() {
|
|
572
|
-
if (!this.
|
|
573
|
-
this.errorMessage = '
|
|
1166
|
+
if (!this.resolvedSessionId || !this.token) {
|
|
1167
|
+
this.errorMessage = 'Session not ready';
|
|
574
1168
|
return;
|
|
575
1169
|
}
|
|
576
1170
|
// Close waiting room and open video call
|
|
1171
|
+
this.tasService.stopStatusPolling();
|
|
577
1172
|
this.activeModal.close('joining');
|
|
578
1173
|
this.openVideoCallModal();
|
|
579
1174
|
}
|
|
@@ -581,85 +1176,53 @@ class TasWaitingRoomComponent {
|
|
|
581
1176
|
* Closes the waiting room
|
|
582
1177
|
*/
|
|
583
1178
|
cancel() {
|
|
1179
|
+
this.tasService.stopStatusPolling();
|
|
584
1180
|
this.activeModal.dismiss('cancel');
|
|
585
1181
|
}
|
|
586
1182
|
/**
|
|
587
1183
|
* Retry after an error
|
|
588
1184
|
*/
|
|
589
1185
|
retry() {
|
|
590
|
-
this.state = WaitingRoomState.
|
|
1186
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
591
1187
|
this.errorMessage = '';
|
|
592
1188
|
this.token = '';
|
|
593
|
-
|
|
594
|
-
if (!this.hasExistingSession) {
|
|
595
|
-
this.sessionId = '';
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
// Private Methods
|
|
599
|
-
buildUsersArray() {
|
|
600
|
-
this.users = [];
|
|
601
|
-
// Add owners from input
|
|
602
|
-
this.ownerUserIds.forEach(id => {
|
|
603
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.OWNER });
|
|
604
|
-
});
|
|
605
|
-
// Add regular users from input
|
|
606
|
-
this.regularUserIds.forEach(id => {
|
|
607
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.USER });
|
|
608
|
-
});
|
|
609
|
-
// Add moderators from input
|
|
610
|
-
this.moderatorUserIds.forEach(id => {
|
|
611
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.MODERATOR });
|
|
612
|
-
});
|
|
613
|
-
}
|
|
614
|
-
setupViewModeSubscription() {
|
|
615
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
616
|
-
// Re-open video call modal when returning from PiP mode
|
|
617
|
-
if (mode === ViewMode.FULLSCREEN &&
|
|
618
|
-
this.tasService.isCallActive() &&
|
|
619
|
-
!this.videoCallModalRef) {
|
|
620
|
-
const sessionId = this.tasService.sessionId;
|
|
621
|
-
const token = this.tasService.token;
|
|
622
|
-
if (sessionId && token) {
|
|
623
|
-
this.openVideoCallModal(true);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}));
|
|
1189
|
+
this.checkStatus();
|
|
627
1190
|
}
|
|
628
|
-
openVideoCallModal(
|
|
1191
|
+
openVideoCallModal() {
|
|
629
1192
|
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
630
1193
|
size: 'xl',
|
|
631
1194
|
windowClass: 'tas-video-modal',
|
|
632
1195
|
backdrop: 'static',
|
|
633
|
-
keyboard: false
|
|
1196
|
+
keyboard: false,
|
|
1197
|
+
});
|
|
1198
|
+
this.videoCallModalRef.componentInstance.sessionId = this.resolvedSessionId;
|
|
1199
|
+
this.videoCallModalRef.componentInstance.token = this.token;
|
|
1200
|
+
this.videoCallModalRef.componentInstance.appointmentId = this.resolvedAppointmentId;
|
|
1201
|
+
this.videoCallModalRef.componentInstance.videoCallId = this.videoCallId;
|
|
1202
|
+
this.videoCallModalRef.componentInstance.tenant = this.tenant;
|
|
1203
|
+
this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
|
|
1204
|
+
this.videoCallModalRef.componentInstance.isReturningFromPip = false;
|
|
1205
|
+
this.videoCallModalRef.result.then(() => {
|
|
1206
|
+
this.videoCallModalRef = null;
|
|
1207
|
+
}, () => {
|
|
1208
|
+
this.videoCallModalRef = null;
|
|
634
1209
|
});
|
|
635
|
-
const sessionIdToUse = this.sessionId || this.tasService.sessionId;
|
|
636
|
-
const tokenToUse = this.token || this.tasService.token;
|
|
637
|
-
this.videoCallModalRef.componentInstance.sessionId = sessionIdToUse;
|
|
638
|
-
this.videoCallModalRef.componentInstance.token = tokenToUse;
|
|
639
|
-
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
640
|
-
this.videoCallModalRef.result.then(() => { this.videoCallModalRef = null; }, () => { this.videoCallModalRef = null; });
|
|
641
1210
|
}
|
|
642
1211
|
}
|
|
643
|
-
TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
644
|
-
TasWaitingRoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: TasWaitingRoomComponent, selector: "tas-waiting-room", inputs: { appointmentId: "appointmentId", product: "product", tenantId: "tenantId", currentUser: "currentUser", ownerUserIds: "ownerUserIds", regularUserIds: "regularUserIds", moderatorUserIds: "moderatorUserIds", existingSessionId: "existingSessionId" }, ngImport: i0, template: "<div class=\"tas-waiting-room\">\n\t<!-- Header -->\n\t<div class=\"waiting-room-header\">\n\t\t<div class=\"header-icon\">\n\t\t\t<i class=\"fa fa-video-camera\"></i>\n\t\t</div>\n\t\t<h2 class=\"header-title\">Waiting Room</h2>\n\t\t<p class=\"header-subtitle\">Prepare for your video call</p>\n\t\t<button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n\t\t\t<span aria-hidden=\"true\">×</span>\n\t\t</button>\n\t</div>\n\n\t<!-- Content -->\n\t<div class=\"waiting-room-content\">\n\t\t<!-- IDLE State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.IDLE && !hasExistingSession\">\n\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t<i class=\"fa fa-plus-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tCreate a new video call room\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tPress the button to begin\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t(click)=\"createRoom()\">\n\t\t\t\t<i class=\"fa fa-plus\"></i>\n\t\t\t\tCreate Room\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- IDLE State - Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.IDLE && hasExistingSession\">\n\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tJoin existing video call room\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tPress the button to get access\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t(click)=\"getTokenForExistingSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Room\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- CREATING_ROOM State (only for new sessions) -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CREATING_ROOM\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tCreating video call room...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Creating</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && !hasExistingSession\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tPreparing room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && hasExistingSession\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tGetting room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Connecting</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- READY State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && !hasExistingSession\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tRoom is ready!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- READY State - Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && hasExistingSession\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tReady to join!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Connected</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- ERROR State -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.ERROR\">\n\t\t\t<div class=\"state-icon error\">\n\t\t\t\t<i class=\"fa fa-exclamation-triangle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message error\">\n\t\t\t\tAn error occurred\n\t\t\t</p>\n\t\t\t<p class=\"error-details\" *ngIf=\"errorMessage\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn retry-btn\"\n\t\t\t\t(click)=\"retry()\">\n\t\t\t\t<i class=\"fa fa-refresh\"></i>\n\t\t\t\tRetry\n\t\t\t</button>\n\t\t</div>\n\t</div>\n\n\t<!-- Footer -->\n\t<div class=\"waiting-room-footer\">\n\t\t<button \n\t\t\ttype=\"button\" \n\t\t\tclass=\"btn cancel-btn\"\n\t\t\t(click)=\"cancel()\">\n\t\t\tCancel\n\t\t</button>\n\t</div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:32px 40px 24px;text-align:center;border-bottom:1px solid #e9ecef}.waiting-room-header .header-icon{width:72px;height:72px;margin:0 auto 16px;background:linear-gradient(135deg,#1da4b1 0%,#0077b3 100%);border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #1da4b140}.waiting-room-header .header-icon i{font-size:28px;color:#fff}.waiting-room-header .header-title{margin:0 0 8px;font-size:20px;font-weight:700;line-height:28px;color:#212529}.waiting-room-header .header-subtitle{margin:0;font-size:14px;color:#6c757d;font-weight:400}.waiting-room-header .close-btn{position:absolute;top:16px;right:16px;width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:360px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.success{color:#1da4b1}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
645
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1212
|
+
TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: i1.NgbModal }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1213
|
+
TasWaitingRoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasWaitingRoomComponent, selector: "tas-waiting-room", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser" }, ngImport: i0, template: "<div class=\"tas-waiting-room\">\n <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_OWNER State (Non-owner waiting) -->\n <div class=\"state-container waiting-for-owner\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_OWNER\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message waiting-title\">Medicina laboral te va a admitir en unos instantes...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla hasta que inicie la consulta.</p>\n </div>\n\n <!-- READY State (Owner can join) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <p class=\"state-submessage\">Pod\u00E9s unirte a la videollamada cuando quieras</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n <p class=\"error-details\" *ngIf=\"errorMessage\">\n {{ errorMessage }}\n </p>\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.success{color:#1da4b1}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1214
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
|
|
646
1215
|
type: Component,
|
|
647
|
-
args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n\t<!-- Header -->\n\t<div class=\"waiting-room-header\">\n\t\t<div class=\"header-icon\">\n\t\t\t<i class=\"fa fa-video-camera\"></i>\n\t\t</div>\n\t\t<h2 class=\"header-title\">Waiting Room</h2>\n\t\t<p class=\"header-subtitle\">Prepare for your video call</p>\n\t\t<button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n\t\t\t<span aria-hidden=\"true\">×</span>\n\t\t</button>\n\t</div>\n\n\t<!-- Content -->\n\t<div class=\"waiting-room-content\">\n\t\t<!-- IDLE State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.IDLE && !hasExistingSession\">\n\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t<i class=\"fa fa-plus-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tCreate a new video call room\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tPress the button to begin\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t(click)=\"createRoom()\">\n\t\t\t\t<i class=\"fa fa-plus\"></i>\n\t\t\t\tCreate Room\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- IDLE State - Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.IDLE && hasExistingSession\">\n\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tJoin existing video call room\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tPress the button to get access\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t(click)=\"getTokenForExistingSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Room\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- CREATING_ROOM State (only for new sessions) -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CREATING_ROOM\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tCreating video call room...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Creating</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && !hasExistingSession\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tPreparing room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && hasExistingSession\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tGetting room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Connecting</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- READY State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && !hasExistingSession\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tRoom is ready!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- READY State - Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && hasExistingSession\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tReady to join!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Connected</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- ERROR State -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.ERROR\">\n\t\t\t<div class=\"state-icon error\">\n\t\t\t\t<i class=\"fa fa-exclamation-triangle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message error\">\n\t\t\t\tAn error occurred\n\t\t\t</p>\n\t\t\t<p class=\"error-details\" *ngIf=\"errorMessage\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn retry-btn\"\n\t\t\t\t(click)=\"retry()\">\n\t\t\t\t<i class=\"fa fa-refresh\"></i>\n\t\t\t\tRetry\n\t\t\t</button>\n\t\t</div>\n\t</div>\n\n\t<!-- Footer -->\n\t<div class=\"waiting-room-footer\">\n\t\t<button \n\t\t\ttype=\"button\" \n\t\t\tclass=\"btn cancel-btn\"\n\t\t\t(click)=\"cancel()\">\n\t\t\tCancel\n\t\t</button>\n\t</div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:32px 40px 24px;text-align:center;border-bottom:1px solid #e9ecef}.waiting-room-header .header-icon{width:72px;height:72px;margin:0 auto 16px;background:linear-gradient(135deg,#1da4b1 0%,#0077b3 100%);border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #1da4b140}.waiting-room-header .header-icon i{font-size:28px;color:#fff}.waiting-room-header .header-title{margin:0 0 8px;font-size:20px;font-weight:700;line-height:28px;color:#212529}.waiting-room-header .header-subtitle{margin:0;font-size:14px;color:#6c757d;font-weight:400}.waiting-room-header .close-btn{position:absolute;top:16px;right:16px;width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:360px;width:100%}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.success{color:#1da4b1}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"] }]
|
|
648
|
-
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: i1.NgbModal }]; }, propDecorators: {
|
|
649
|
-
type: Input
|
|
650
|
-
}], product: [{
|
|
651
|
-
type: Input
|
|
652
|
-
}], tenantId: [{
|
|
653
|
-
type: Input
|
|
654
|
-
}], currentUser: [{
|
|
1216
|
+
args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n <!-- Header -->\n <div class=\"waiting-room-header\">\n <h2 class=\"header-title\">Iniciar turno</h2>\n <button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n <span aria-hidden=\"true\">×</span>\n </button>\n </div>\n\n <!-- Content -->\n <div class=\"waiting-room-content\">\n <!-- CHECKING_STATUS State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CHECKING_STATUS\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Verificando estado de la sesi\u00F3n...</p>\n </div>\n\n <!-- WAITING_FOR_OWNER State (Non-owner waiting) -->\n <div class=\"state-container waiting-for-owner\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_OWNER\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message waiting-title\">Medicina laboral te va a admitir en unos instantes...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla hasta que inicie la consulta.</p>\n </div>\n\n <!-- READY State (Owner can join) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY\">\n <div class=\"state-icon ready\">\n <i class=\"fa fa-check-circle\"></i>\n </div>\n <p class=\"state-message success\">\u00A1La sala est\u00E1 lista!</p>\n <p class=\"state-submessage\">Pod\u00E9s unirte a la videollamada cuando quieras</p>\n <button type=\"button\" class=\"btn action-btn join-btn\" (click)=\"joinSession()\">\n <i class=\"fa fa-sign-in\"></i>\n Unirse a la llamada\n </button>\n </div>\n\n <!-- GETTING_TOKEN State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Conectando...</p>\n </div>\n\n <!-- ERROR State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.ERROR\">\n <div class=\"state-icon error\">\n <i class=\"fa fa-exclamation-triangle\"></i>\n </div>\n <p class=\"state-message error\">Ocurri\u00F3 un error</p>\n <p class=\"error-details\" *ngIf=\"errorMessage\">\n {{ errorMessage }}\n </p>\n <button type=\"button\" class=\"btn action-btn retry-btn\" (click)=\"retry()\">\n <i class=\"fa fa-refresh\"></i>\n Reintentar\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:20px 40px;border-bottom:1px solid #e9ecef;display:flex;align-items:center;justify-content:space-between}.waiting-room-header .header-title{margin:0;font-size:18px;font-weight:600;line-height:24px;color:#212529}.waiting-room-header .close-btn{width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px;display:flex;align-items:center;justify-content:center}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-title{font-size:16px;font-weight:600;color:#212529;margin-bottom:8px}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.success{color:#1da4b1}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"] }]
|
|
1217
|
+
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
|
|
655
1218
|
type: Input
|
|
656
|
-
}],
|
|
1219
|
+
}], entityId: [{
|
|
657
1220
|
type: Input
|
|
658
|
-
}],
|
|
1221
|
+
}], tenant: [{
|
|
659
1222
|
type: Input
|
|
660
|
-
}],
|
|
1223
|
+
}], businessRole: [{
|
|
661
1224
|
type: Input
|
|
662
|
-
}],
|
|
1225
|
+
}], currentUser: [{
|
|
663
1226
|
type: Input
|
|
664
1227
|
}] } });
|
|
665
1228
|
|
|
@@ -667,117 +1230,214 @@ class TasButtonComponent {
|
|
|
667
1230
|
constructor(modalService, tasService) {
|
|
668
1231
|
this.modalService = modalService;
|
|
669
1232
|
this.tasService = tasService;
|
|
670
|
-
|
|
671
|
-
this.
|
|
672
|
-
this.
|
|
673
|
-
this.regularUserIds = [];
|
|
674
|
-
this.moderatorUserIds = [];
|
|
675
|
-
/** Optional: If provided, skips room creation and goes directly to getting a token */
|
|
676
|
-
this.existingSessionId = "";
|
|
677
|
-
/** Optional: Custom button text */
|
|
678
|
-
this.buttonText = "Iniciar TAS";
|
|
1233
|
+
// Status endpoint params
|
|
1234
|
+
this.roomType = TasRoomType.TAS;
|
|
1235
|
+
this.businessRole = TasBusinessRole.USER;
|
|
679
1236
|
this.isLoading = false;
|
|
1237
|
+
this.buttonText = 'Iniciar TAS';
|
|
1238
|
+
// Status check state
|
|
1239
|
+
this.isCheckingStatus = false;
|
|
1240
|
+
this.isStatusError = false;
|
|
1241
|
+
this.statusErrorMessage = '';
|
|
680
1242
|
this.subscriptions = new Subscription();
|
|
681
1243
|
this.currentModalRef = null;
|
|
682
1244
|
this.videoCallModalRef = null;
|
|
1245
|
+
this.statusPollingInterval = null;
|
|
1246
|
+
this.STATUS_POLL_INTERVAL_MS = 30000; // 30 seconds
|
|
683
1247
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
1248
|
+
/** Whether user is backoffice (or admin/manager) */
|
|
1249
|
+
get isBackoffice() {
|
|
1250
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
1251
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
1252
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
1253
|
+
}
|
|
1254
|
+
/** Whether the button should be visible */
|
|
1255
|
+
get isVisible() {
|
|
1256
|
+
// Backoffice: always show (disabled if error)
|
|
1257
|
+
// Other roles: hide if status error
|
|
1258
|
+
if (this.isBackoffice) {
|
|
1259
|
+
return true;
|
|
687
1260
|
}
|
|
1261
|
+
return !this.isStatusError;
|
|
1262
|
+
}
|
|
1263
|
+
/** Whether the button should be disabled */
|
|
1264
|
+
get isDisabled() {
|
|
1265
|
+
return this.isLoading || this.isCheckingStatus || this.isStatusError;
|
|
1266
|
+
}
|
|
1267
|
+
ngOnInit() {
|
|
688
1268
|
// Subscribe to viewMode to handle PiP return
|
|
689
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
690
|
-
// Reopen video call modal when returning from PiP
|
|
691
|
-
if (mode === ViewMode.FULLSCREEN &&
|
|
692
|
-
this.tasService.isCallActive() &&
|
|
693
|
-
!this.videoCallModalRef) {
|
|
694
|
-
const sessionId = this.tasService.sessionId;
|
|
695
|
-
const token = this.tasService.token;
|
|
696
|
-
if (sessionId && token) {
|
|
697
|
-
this.openVideoCallModal(true);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
1269
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
700
1270
|
// When entering PiP, clear the videoCallModalRef since modal will close
|
|
701
1271
|
if (mode === ViewMode.PIP) {
|
|
702
1272
|
this.videoCallModalRef = null;
|
|
703
1273
|
}
|
|
704
1274
|
}));
|
|
1275
|
+
// Start status checking
|
|
1276
|
+
this.startStatusPolling();
|
|
705
1277
|
}
|
|
706
1278
|
ngOnDestroy() {
|
|
707
1279
|
this.subscriptions.unsubscribe();
|
|
1280
|
+
this.stopStatusPolling();
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Start polling status every 30 seconds
|
|
1284
|
+
*/
|
|
1285
|
+
startStatusPolling() {
|
|
1286
|
+
// Initial status check
|
|
1287
|
+
this.checkStatus();
|
|
1288
|
+
// Set up periodic polling
|
|
1289
|
+
this.statusPollingInterval = setInterval(() => {
|
|
1290
|
+
this.checkStatus();
|
|
1291
|
+
}, this.STATUS_POLL_INTERVAL_MS);
|
|
1292
|
+
}
|
|
1293
|
+
/**
|
|
1294
|
+
* Stop status polling
|
|
1295
|
+
*/
|
|
1296
|
+
stopStatusPolling() {
|
|
1297
|
+
if (this.statusPollingInterval) {
|
|
1298
|
+
clearInterval(this.statusPollingInterval);
|
|
1299
|
+
this.statusPollingInterval = null;
|
|
1300
|
+
}
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Check status endpoint to determine if button should be enabled
|
|
1304
|
+
*/
|
|
1305
|
+
checkStatus() {
|
|
1306
|
+
// Skip if required inputs are not available
|
|
1307
|
+
if (!this.tenant || !this.entityId) {
|
|
1308
|
+
console.log('[TAS DEBUG] checkStatus skipped - missing required inputs');
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
this.isCheckingStatus = true;
|
|
1312
|
+
this.statusErrorMessage = '';
|
|
1313
|
+
console.log('[TAS DEBUG] checkStatus called with:', {
|
|
1314
|
+
roomType: this.roomType,
|
|
1315
|
+
entityId: this.entityId,
|
|
1316
|
+
tenant: this.tenant,
|
|
1317
|
+
businessRole: this.businessRole,
|
|
1318
|
+
});
|
|
1319
|
+
this.subscriptions.add(this.tasService.getProxyVideoStatus({
|
|
1320
|
+
roomType: this.roomType,
|
|
1321
|
+
entityId: this.entityId,
|
|
1322
|
+
tenant: this.tenant,
|
|
1323
|
+
businessRole: this.businessRole,
|
|
1324
|
+
}).subscribe({
|
|
1325
|
+
next: (response) => {
|
|
1326
|
+
var _a;
|
|
1327
|
+
// Check if response is actually an error (some HTTP adapters return errors in next)
|
|
1328
|
+
// Also check for undefined/null or missing content
|
|
1329
|
+
const isErrorResponse = !response ||
|
|
1330
|
+
!response.content ||
|
|
1331
|
+
(response === null || response === void 0 ? void 0 : response.ok) === false ||
|
|
1332
|
+
(response === null || response === void 0 ? void 0 : response.status) >= 400 ||
|
|
1333
|
+
(response === null || response === void 0 ? void 0 : response.error) ||
|
|
1334
|
+
(response === null || response === void 0 ? void 0 : response.name) === 'HttpErrorResponse';
|
|
1335
|
+
if (isErrorResponse) {
|
|
1336
|
+
console.error('[TAS DEBUG] Status check returned error in response:', response);
|
|
1337
|
+
this.isCheckingStatus = false;
|
|
1338
|
+
this.isStatusError = true;
|
|
1339
|
+
this.statusErrorMessage = ((_a = response === null || response === void 0 ? void 0 : response.error) === null || _a === void 0 ? void 0 : _a.message) || (response === null || response === void 0 ? void 0 : response.message) || 'Error checking status';
|
|
1340
|
+
}
|
|
1341
|
+
else {
|
|
1342
|
+
console.log('[TAS DEBUG] Status check successful:', response);
|
|
1343
|
+
this.isCheckingStatus = false;
|
|
1344
|
+
this.isStatusError = false;
|
|
1345
|
+
this.statusErrorMessage = '';
|
|
1346
|
+
}
|
|
1347
|
+
},
|
|
1348
|
+
error: (err) => {
|
|
1349
|
+
var _a;
|
|
1350
|
+
console.error('[TAS DEBUG] Status check failed:', err);
|
|
1351
|
+
this.isCheckingStatus = false;
|
|
1352
|
+
this.isStatusError = true;
|
|
1353
|
+
this.statusErrorMessage = ((_a = err === null || err === void 0 ? void 0 : err.error) === null || _a === void 0 ? void 0 : _a.message) || (err === null || err === void 0 ? void 0 : err.message) || 'Error checking status';
|
|
1354
|
+
},
|
|
1355
|
+
}));
|
|
708
1356
|
}
|
|
709
1357
|
onClick() {
|
|
710
1358
|
var _a;
|
|
711
|
-
|
|
712
|
-
|
|
1359
|
+
console.log('[TAS DEBUG] onClick called');
|
|
1360
|
+
console.log('[TAS DEBUG] Inputs:', {
|
|
1361
|
+
tenant: this.tenant,
|
|
1362
|
+
entityId: this.entityId,
|
|
1363
|
+
roomType: this.roomType,
|
|
1364
|
+
businessRole: this.businessRole,
|
|
1365
|
+
currentUser: this.currentUser,
|
|
1366
|
+
});
|
|
1367
|
+
if (!this.tenant || !((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
1368
|
+
console.error('[TAS DEBUG] Tenant or current user not available');
|
|
713
1369
|
return;
|
|
714
1370
|
}
|
|
1371
|
+
if (!this.entityId) {
|
|
1372
|
+
console.error('[TAS DEBUG] entityId is required');
|
|
1373
|
+
return;
|
|
1374
|
+
}
|
|
1375
|
+
console.log('[TAS DEBUG] Validation passed, opening waiting room modal');
|
|
715
1376
|
this.openWaitingRoomModal();
|
|
716
1377
|
}
|
|
717
1378
|
openWaitingRoomModal() {
|
|
718
1379
|
this.currentModalRef = this.modalService.open(TasWaitingRoomComponent, {
|
|
719
|
-
size:
|
|
720
|
-
windowClass:
|
|
721
|
-
backdrop:
|
|
1380
|
+
size: 'lg',
|
|
1381
|
+
windowClass: 'tas-waiting-room-modal',
|
|
1382
|
+
backdrop: 'static',
|
|
722
1383
|
keyboard: false,
|
|
723
|
-
centered: true
|
|
1384
|
+
centered: true,
|
|
724
1385
|
});
|
|
725
1386
|
// Pass all necessary inputs to the waiting room component
|
|
726
|
-
this.currentModalRef.componentInstance.
|
|
727
|
-
this.currentModalRef.componentInstance.
|
|
728
|
-
this.currentModalRef.componentInstance.
|
|
1387
|
+
this.currentModalRef.componentInstance.roomType = this.roomType;
|
|
1388
|
+
this.currentModalRef.componentInstance.entityId = this.entityId;
|
|
1389
|
+
this.currentModalRef.componentInstance.tenant = this.tenant;
|
|
1390
|
+
this.currentModalRef.componentInstance.businessRole = this.businessRole;
|
|
729
1391
|
this.currentModalRef.componentInstance.currentUser = this.currentUser;
|
|
730
|
-
this.currentModalRef.
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
this.currentModalRef.result.then(() => { this.currentModalRef = null; }, () => { this.currentModalRef = null; });
|
|
1392
|
+
this.currentModalRef.result.then(() => {
|
|
1393
|
+
this.currentModalRef = null;
|
|
1394
|
+
}, () => {
|
|
1395
|
+
this.currentModalRef = null;
|
|
1396
|
+
});
|
|
736
1397
|
}
|
|
737
1398
|
openVideoCallModal(isReturningFromPip = false) {
|
|
738
1399
|
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
739
1400
|
size: 'xl',
|
|
740
1401
|
windowClass: 'tas-video-modal',
|
|
741
1402
|
backdrop: 'static',
|
|
742
|
-
keyboard: false
|
|
1403
|
+
keyboard: false,
|
|
743
1404
|
});
|
|
744
1405
|
this.videoCallModalRef.componentInstance.sessionId = this.tasService.sessionId;
|
|
745
1406
|
this.videoCallModalRef.componentInstance.token = this.tasService.token;
|
|
1407
|
+
this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
|
|
746
1408
|
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
747
|
-
this.videoCallModalRef.result.then(() => {
|
|
1409
|
+
this.videoCallModalRef.result.then(() => {
|
|
1410
|
+
this.videoCallModalRef = null;
|
|
1411
|
+
}, () => {
|
|
1412
|
+
this.videoCallModalRef = null;
|
|
1413
|
+
});
|
|
748
1414
|
}
|
|
749
1415
|
}
|
|
750
|
-
TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
751
|
-
TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
752
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1416
|
+
TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, deps: [{ token: i1.NgbModal }, { token: TasService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1417
|
+
TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasButtonComponent, selector: "tas-btn", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser" }, ngImport: i0, template: "<button\n *ngIf=\"isVisible\"\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n>\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">Iniciar TAS</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n</button>\n\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}\n"], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1418
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
|
|
753
1419
|
type: Component,
|
|
754
|
-
args: [{ selector:
|
|
755
|
-
}], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: {
|
|
756
|
-
type: Input
|
|
757
|
-
}], product: [{
|
|
758
|
-
type: Input
|
|
759
|
-
}], tenantId: [{
|
|
1420
|
+
args: [{ selector: 'tas-btn', template: "<button\n *ngIf=\"isVisible\"\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n>\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">Iniciar TAS</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n</button>\n\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px}.tas-btn:disabled{background-color:#ccc!important;border-color:#ccc!important;cursor:not-allowed}.tas-btn:hover:not(:disabled){background-color:#d62a5f!important;border-color:#d62a5f!important}.tas-btn i{margin-right:5px}\n"] }]
|
|
1421
|
+
}], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: { roomType: [{
|
|
760
1422
|
type: Input
|
|
761
|
-
}],
|
|
762
|
-
type: Input
|
|
763
|
-
}], ownerUserIds: [{
|
|
1423
|
+
}], entityId: [{
|
|
764
1424
|
type: Input
|
|
765
|
-
}],
|
|
1425
|
+
}], tenant: [{
|
|
766
1426
|
type: Input
|
|
767
|
-
}],
|
|
1427
|
+
}], businessRole: [{
|
|
768
1428
|
type: Input
|
|
769
|
-
}],
|
|
770
|
-
type: Input
|
|
771
|
-
}], buttonText: [{
|
|
1429
|
+
}], currentUser: [{
|
|
772
1430
|
type: Input
|
|
773
1431
|
}] } });
|
|
774
1432
|
|
|
775
1433
|
class TasFloatingCallComponent {
|
|
776
|
-
constructor(tasService) {
|
|
1434
|
+
constructor(tasService, modalService) {
|
|
777
1435
|
this.tasService = tasService;
|
|
1436
|
+
this.modalService = modalService;
|
|
778
1437
|
this.isVisible = false;
|
|
779
1438
|
this.isMuted = false;
|
|
780
1439
|
this.subscriptions = new Subscription();
|
|
1440
|
+
this.videoCallModalRef = null;
|
|
781
1441
|
// Margin from screen edges (in pixels)
|
|
782
1442
|
this.PIP_MARGIN = 20;
|
|
783
1443
|
}
|
|
@@ -790,6 +1450,7 @@ class TasFloatingCallComponent {
|
|
|
790
1450
|
}
|
|
791
1451
|
// Public Methods
|
|
792
1452
|
onExpand() {
|
|
1453
|
+
this.openVideoCallModal(true);
|
|
793
1454
|
this.tasService.exitPipMode();
|
|
794
1455
|
}
|
|
795
1456
|
onHangUp() {
|
|
@@ -801,23 +1462,45 @@ class TasFloatingCallComponent {
|
|
|
801
1462
|
// Private Methods
|
|
802
1463
|
setupSubscriptions() {
|
|
803
1464
|
// Call state subscription
|
|
804
|
-
this.subscriptions.add(this.tasService.callState$.subscribe(state => {
|
|
1465
|
+
this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
|
|
805
1466
|
if (state === CallState.DISCONNECTED) {
|
|
806
1467
|
this.isVisible = false;
|
|
807
1468
|
}
|
|
808
1469
|
}));
|
|
809
1470
|
// View mode subscription
|
|
810
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
1471
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
811
1472
|
this.isVisible = mode === ViewMode.PIP && this.tasService.isCallActive();
|
|
812
1473
|
if (this.isVisible) {
|
|
813
1474
|
setTimeout(() => this.initInteract(), 100);
|
|
1475
|
+
// Clear modal ref if we enter PiP mode (modal closes itself)
|
|
1476
|
+
this.videoCallModalRef = null;
|
|
814
1477
|
}
|
|
815
1478
|
}));
|
|
816
1479
|
// Mute state subscription
|
|
817
|
-
this.subscriptions.add(this.tasService.isMuted$.subscribe(muted => {
|
|
1480
|
+
this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
|
|
818
1481
|
this.isMuted = muted;
|
|
819
1482
|
}));
|
|
820
1483
|
}
|
|
1484
|
+
openVideoCallModal(isReturningFromPip = false) {
|
|
1485
|
+
if (this.videoCallModalRef) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
1489
|
+
size: 'xl',
|
|
1490
|
+
windowClass: 'tas-video-modal',
|
|
1491
|
+
backdrop: 'static',
|
|
1492
|
+
keyboard: false,
|
|
1493
|
+
});
|
|
1494
|
+
this.videoCallModalRef.componentInstance.sessionId = this.tasService.sessionId;
|
|
1495
|
+
this.videoCallModalRef.componentInstance.token = this.tasService.token;
|
|
1496
|
+
this.videoCallModalRef.componentInstance.businessRole = this.tasService.businessRole;
|
|
1497
|
+
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
1498
|
+
this.videoCallModalRef.result.then(() => {
|
|
1499
|
+
this.videoCallModalRef = null;
|
|
1500
|
+
}, () => {
|
|
1501
|
+
this.videoCallModalRef = null;
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
821
1504
|
initInteract() {
|
|
822
1505
|
interact('.tas-floating-container').unset();
|
|
823
1506
|
// Create restriction area with margin
|
|
@@ -828,17 +1511,15 @@ class TasFloatingCallComponent {
|
|
|
828
1511
|
left: margin,
|
|
829
1512
|
top: margin,
|
|
830
1513
|
right: window.innerWidth - margin,
|
|
831
|
-
bottom: window.innerHeight - margin
|
|
1514
|
+
bottom: window.innerHeight - margin,
|
|
832
1515
|
};
|
|
833
1516
|
},
|
|
834
|
-
elementRect: { left: 0, right: 1, top: 0, bottom: 1 }
|
|
1517
|
+
elementRect: { left: 0, right: 1, top: 0, bottom: 1 },
|
|
835
1518
|
};
|
|
836
1519
|
interact('.tas-floating-container')
|
|
837
1520
|
.draggable({
|
|
838
1521
|
inertia: true,
|
|
839
|
-
modifiers: [
|
|
840
|
-
interact.modifiers.restrict(restrictToBodyWithMargin)
|
|
841
|
-
],
|
|
1522
|
+
modifiers: [interact.modifiers.restrict(restrictToBodyWithMargin)],
|
|
842
1523
|
autoScroll: false,
|
|
843
1524
|
listeners: {
|
|
844
1525
|
move: (event) => {
|
|
@@ -848,8 +1529,8 @@ class TasFloatingCallComponent {
|
|
|
848
1529
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
849
1530
|
target.setAttribute('data-x', String(x));
|
|
850
1531
|
target.setAttribute('data-y', String(y));
|
|
851
|
-
}
|
|
852
|
-
}
|
|
1532
|
+
},
|
|
1533
|
+
},
|
|
853
1534
|
})
|
|
854
1535
|
.resizable({
|
|
855
1536
|
edges: { left: false, right: true, bottom: true, top: false },
|
|
@@ -867,7 +1548,7 @@ class TasFloatingCallComponent {
|
|
|
867
1548
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
868
1549
|
target.setAttribute('data-x', String(x));
|
|
869
1550
|
target.setAttribute('data-y', String(y));
|
|
870
|
-
}
|
|
1551
|
+
},
|
|
871
1552
|
},
|
|
872
1553
|
modifiers: [
|
|
873
1554
|
interact.modifiers.restrictEdges({
|
|
@@ -875,25 +1556,25 @@ class TasFloatingCallComponent {
|
|
|
875
1556
|
left: margin,
|
|
876
1557
|
top: margin,
|
|
877
1558
|
right: window.innerWidth - margin,
|
|
878
|
-
bottom: window.innerHeight - margin
|
|
879
|
-
}
|
|
1559
|
+
bottom: window.innerHeight - margin,
|
|
1560
|
+
},
|
|
880
1561
|
}),
|
|
881
1562
|
interact.modifiers.restrictSize({
|
|
882
1563
|
min: { width: 200, height: 130 },
|
|
883
|
-
max: { width: 500, height: 350 }
|
|
1564
|
+
max: { width: 500, height: 350 },
|
|
884
1565
|
}),
|
|
885
|
-
interact.modifiers.aspectRatio({ ratio: 'preserve' })
|
|
1566
|
+
interact.modifiers.aspectRatio({ ratio: 'preserve' }),
|
|
886
1567
|
],
|
|
887
|
-
inertia: true
|
|
1568
|
+
inertia: true,
|
|
888
1569
|
});
|
|
889
1570
|
}
|
|
890
1571
|
}
|
|
891
|
-
TasFloatingCallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
892
|
-
TasFloatingCallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
893
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1572
|
+
TasFloatingCallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasFloatingCallComponent, deps: [{ token: TasService }, { token: i1.NgbModal }], target: i0.ɵɵFactoryTarget.Component });
|
|
1573
|
+
TasFloatingCallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasFloatingCallComponent, selector: "tas-floating-call", ngImport: i0, template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n <!-- Video content area - shows main video only -->\n <div class=\"floating-content\">\n <!-- Main video container (subscriber if available, otherwise publisher) -->\n <div id=\"pip-main-video\" class=\"pip-main-video\"></div>\n\n <!-- Bottom controls -->\n <div class=\"floating-controls\">\n <button\n class=\"action-btn expand-btn\"\n (click)=\"onExpand()\"\n title=\"Expand to fullscreen\"\n >\n <i class=\"fa fa-expand\"></i>\n </button>\n <button\n class=\"action-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Unmute microphone' : 'Mute microphone'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button class=\"action-btn hangup-btn\" (click)=\"onHangUp()\" title=\"Hang up call\">\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-floating-container{position:fixed;bottom:20px;right:20px;width:280px;height:180px;background:#000;border-radius:12px;box-shadow:0 8px 32px #00000080;z-index:9999;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none;transition:opacity .3s ease,visibility .3s ease,box-shadow .2s ease;opacity:0;visibility:hidden;pointer-events:none}.tas-floating-container.visible{opacity:1;visibility:visible;pointer-events:auto}.tas-floating-container:hover{box-shadow:0 8px 32px #00000080,0 0 0 2px #ffffff4d}.floating-content{position:relative;width:100%;height:100%;overflow:hidden}.pip-main-video{position:absolute;top:0;left:0;width:100%;height:100%;background:#000}.pip-main-video ::ng-deep video{width:100%;height:100%;object-fit:cover}.pip-main-video ::ng-deep .OT_subscriber,.pip-main-video ::ng-deep .OT_publisher{width:100%!important;height:100%!important}.pip-main-video ::ng-deep .OT_edge-bar-item,.pip-main-video ::ng-deep .OT_mute,.pip-main-video ::ng-deep .OT_audio-level-meter,.pip-main-video ::ng-deep .OT_bar,.pip-main-video ::ng-deep .OT_name{display:none!important}.floating-controls{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:12px;padding:6px 14px;background:rgba(0,0,0,.7);border-radius:24px;backdrop-filter:blur(8px);opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease}.tas-floating-container:hover .floating-controls{opacity:1;visibility:visible}.action-btn{width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s ease}.action-btn.expand-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.expand-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn.muted{background:#f39c12;color:#fff}.action-btn.mute-btn.muted:hover{background:#e67e22}.action-btn.hangup-btn{background:#dc3545;color:#fff}.action-btn.hangup-btn:hover{background:#c82333;transform:scale(1.1)}\n"] });
|
|
1574
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasFloatingCallComponent, decorators: [{
|
|
894
1575
|
type: Component,
|
|
895
|
-
args: [{ selector: 'tas-floating-call', template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n
|
|
896
|
-
}], ctorParameters: function () { return [{ type: TasService }]; } });
|
|
1576
|
+
args: [{ selector: 'tas-floating-call', template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n <!-- Video content area - shows main video only -->\n <div class=\"floating-content\">\n <!-- Main video container (subscriber if available, otherwise publisher) -->\n <div id=\"pip-main-video\" class=\"pip-main-video\"></div>\n\n <!-- Bottom controls -->\n <div class=\"floating-controls\">\n <button\n class=\"action-btn expand-btn\"\n (click)=\"onExpand()\"\n title=\"Expand to fullscreen\"\n >\n <i class=\"fa fa-expand\"></i>\n </button>\n <button\n class=\"action-btn mute-btn\"\n [class.muted]=\"isMuted\"\n (click)=\"toggleMute()\"\n [title]=\"isMuted ? 'Unmute microphone' : 'Mute microphone'\"\n >\n <i\n class=\"fa\"\n [class.fa-microphone]=\"!isMuted\"\n [class.fa-microphone-slash]=\"isMuted\"\n ></i>\n </button>\n <button class=\"action-btn hangup-btn\" (click)=\"onHangUp()\" title=\"Hang up call\">\n <i class=\"fa fa-phone\" style=\"transform: rotate(135deg)\"></i>\n </button>\n </div>\n </div>\n</div>\n", styles: [".tas-floating-container{position:fixed;bottom:20px;right:20px;width:280px;height:180px;background:#000;border-radius:12px;box-shadow:0 8px 32px #00000080;z-index:9999;overflow:hidden;touch-action:none;-webkit-user-select:none;user-select:none;transition:opacity .3s ease,visibility .3s ease,box-shadow .2s ease;opacity:0;visibility:hidden;pointer-events:none}.tas-floating-container.visible{opacity:1;visibility:visible;pointer-events:auto}.tas-floating-container:hover{box-shadow:0 8px 32px #00000080,0 0 0 2px #ffffff4d}.floating-content{position:relative;width:100%;height:100%;overflow:hidden}.pip-main-video{position:absolute;top:0;left:0;width:100%;height:100%;background:#000}.pip-main-video ::ng-deep video{width:100%;height:100%;object-fit:cover}.pip-main-video ::ng-deep .OT_subscriber,.pip-main-video ::ng-deep .OT_publisher{width:100%!important;height:100%!important}.pip-main-video ::ng-deep .OT_edge-bar-item,.pip-main-video ::ng-deep .OT_mute,.pip-main-video ::ng-deep .OT_audio-level-meter,.pip-main-video ::ng-deep .OT_bar,.pip-main-video ::ng-deep .OT_name{display:none!important}.floating-controls{position:absolute;bottom:10px;left:50%;transform:translate(-50%);display:flex;gap:12px;padding:6px 14px;background:rgba(0,0,0,.7);border-radius:24px;backdrop-filter:blur(8px);opacity:0;visibility:hidden;transition:opacity .3s ease,visibility .3s ease}.tas-floating-container:hover .floating-controls{opacity:1;visibility:visible}.action-btn{width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:13px;transition:all .2s ease}.action-btn.expand-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.expand-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn{background:rgba(255,255,255,.2);color:#fff}.action-btn.mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.1)}.action-btn.mute-btn.muted{background:#f39c12;color:#fff}.action-btn.mute-btn.muted:hover{background:#e67e22}.action-btn.hangup-btn{background:#dc3545;color:#fff}.action-btn.hangup-btn:hover{background:#c82333;transform:scale(1.1)}\n"] }]
|
|
1577
|
+
}], ctorParameters: function () { return [{ type: TasService }, { type: i1.NgbModal }]; } });
|
|
897
1578
|
|
|
898
1579
|
class TasUellSdkModule {
|
|
899
1580
|
/**
|
|
@@ -931,40 +1612,40 @@ class TasUellSdkModule {
|
|
|
931
1612
|
providers: [
|
|
932
1613
|
{ provide: TAS_CONFIG, useValue: options.config },
|
|
933
1614
|
{ provide: TAS_HTTP_CLIENT, useClass: options.httpClient },
|
|
934
|
-
TasService
|
|
935
|
-
]
|
|
1615
|
+
TasService,
|
|
1616
|
+
],
|
|
936
1617
|
};
|
|
937
1618
|
}
|
|
938
1619
|
}
|
|
939
|
-
TasUellSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
940
|
-
TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.
|
|
1620
|
+
TasUellSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1621
|
+
TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, declarations: [TasButtonComponent,
|
|
941
1622
|
TasVideocallComponent,
|
|
942
1623
|
TasFloatingCallComponent,
|
|
943
|
-
TasWaitingRoomComponent
|
|
1624
|
+
TasWaitingRoomComponent,
|
|
1625
|
+
TasAvatarComponent], imports: [CommonModule, FormsModule], exports: [TasButtonComponent,
|
|
944
1626
|
TasVideocallComponent,
|
|
945
1627
|
TasFloatingCallComponent,
|
|
946
|
-
TasWaitingRoomComponent
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasUellSdkModule, decorators: [{
|
|
1628
|
+
TasWaitingRoomComponent,
|
|
1629
|
+
TasAvatarComponent] });
|
|
1630
|
+
TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule]] });
|
|
1631
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, decorators: [{
|
|
951
1632
|
type: NgModule,
|
|
952
1633
|
args: [{
|
|
953
1634
|
declarations: [
|
|
954
1635
|
TasButtonComponent,
|
|
955
1636
|
TasVideocallComponent,
|
|
956
1637
|
TasFloatingCallComponent,
|
|
957
|
-
TasWaitingRoomComponent
|
|
958
|
-
|
|
959
|
-
imports: [
|
|
960
|
-
CommonModule,
|
|
1638
|
+
TasWaitingRoomComponent,
|
|
1639
|
+
TasAvatarComponent,
|
|
961
1640
|
],
|
|
1641
|
+
imports: [CommonModule, FormsModule],
|
|
962
1642
|
exports: [
|
|
963
1643
|
TasButtonComponent,
|
|
964
1644
|
TasVideocallComponent,
|
|
965
1645
|
TasFloatingCallComponent,
|
|
966
|
-
TasWaitingRoomComponent
|
|
967
|
-
|
|
1646
|
+
TasWaitingRoomComponent,
|
|
1647
|
+
TasAvatarComponent,
|
|
1648
|
+
],
|
|
968
1649
|
}]
|
|
969
1650
|
}] });
|
|
970
1651
|
|
|
@@ -976,5 +1657,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
976
1657
|
* Generated bundle index. Do not edit.
|
|
977
1658
|
*/
|
|
978
1659
|
|
|
979
|
-
export { CallState, TAS_CONFIG, TAS_HTTP_CLIENT, TasButtonComponent, TasFloatingCallComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, ViewMode, WaitingRoomState };
|
|
1660
|
+
export { CallState, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
|
|
980
1661
|
//# sourceMappingURL=tas-uell-sdk.mjs.map
|