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;
|
|
@@ -120,7 +247,17 @@ class TasService {
|
|
|
120
247
|
}
|
|
121
248
|
}
|
|
122
249
|
// Session Management
|
|
123
|
-
disconnectSession() {
|
|
250
|
+
disconnectSession(clearStorage = true) {
|
|
251
|
+
console.log('[TAS DEBUG] TasService.disconnectSession called. clearStorage:', clearStorage);
|
|
252
|
+
// Call finishSession before disconnecting if we have a sessionId
|
|
253
|
+
const sessionIdToFinish = this.currentSessionId;
|
|
254
|
+
// Clear storage FIRST to prevent any race conditions where state might be saved after disconnect
|
|
255
|
+
if (clearStorage) {
|
|
256
|
+
this.clearSessionState();
|
|
257
|
+
// Set a flag to indicate this session was intentionally disconnected
|
|
258
|
+
// This prevents reconnection on page reload
|
|
259
|
+
sessionStorage.setItem(this.DISCONNECTED_FLAG_KEY, 'true');
|
|
260
|
+
}
|
|
124
261
|
if (this.session) {
|
|
125
262
|
this.session.disconnect();
|
|
126
263
|
this.session = null;
|
|
@@ -129,49 +266,260 @@ class TasService {
|
|
|
129
266
|
this.subscribers = [];
|
|
130
267
|
this.currentSessionId = null;
|
|
131
268
|
this.currentToken = null;
|
|
269
|
+
this.proxyVideoSessionId = null;
|
|
270
|
+
this.proxyVideoToken = null;
|
|
132
271
|
this.isMutedSubject.next(false); // Reset mute state
|
|
133
272
|
this.viewModeSubject.next(ViewMode.FULLSCREEN);
|
|
134
273
|
this.callStateSubject.next(CallState.DISCONNECTED);
|
|
274
|
+
// Call proxy finish after disconnecting (only if not already finishing)
|
|
275
|
+
if (sessionIdToFinish && !this.isFinishingSession) {
|
|
276
|
+
this.isFinishingSession = true;
|
|
277
|
+
this.finishProxyVideoSession({
|
|
278
|
+
sessionId: sessionIdToFinish,
|
|
279
|
+
businessRole: this.currentBusinessRole,
|
|
280
|
+
}).subscribe({
|
|
281
|
+
next: (response) => {
|
|
282
|
+
console.log('[TAS DEBUG] Session finished successfully:', response);
|
|
283
|
+
this.isFinishingSession = false;
|
|
284
|
+
},
|
|
285
|
+
error: (error) => {
|
|
286
|
+
console.error('[TAS DEBUG] Error finishing session:', error);
|
|
287
|
+
this.isFinishingSession = false;
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
}
|
|
135
291
|
}
|
|
136
292
|
isCallActive() {
|
|
137
293
|
return this.callStateSubject.getValue() === CallState.CONNECTED;
|
|
138
294
|
}
|
|
295
|
+
// State Persistence
|
|
296
|
+
saveSessionState(sessionId, token, viewMode, businessRole = TasBusinessRole.USER) {
|
|
297
|
+
const state = {
|
|
298
|
+
sessionId,
|
|
299
|
+
token,
|
|
300
|
+
viewMode,
|
|
301
|
+
businessRole,
|
|
302
|
+
};
|
|
303
|
+
sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(state));
|
|
304
|
+
}
|
|
305
|
+
clearSessionState() {
|
|
306
|
+
sessionStorage.removeItem(this.STORAGE_KEY);
|
|
307
|
+
// Also clear the disconnected flag when clearing state
|
|
308
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
309
|
+
}
|
|
310
|
+
canRestoreSession() {
|
|
311
|
+
return !!sessionStorage.getItem(this.STORAGE_KEY);
|
|
312
|
+
}
|
|
313
|
+
restoreSession(containerId) {
|
|
314
|
+
const savedState = sessionStorage.getItem(this.STORAGE_KEY);
|
|
315
|
+
if (!savedState)
|
|
316
|
+
return;
|
|
317
|
+
// Check if this session was intentionally disconnected
|
|
318
|
+
// If so, don't restore it on page reload
|
|
319
|
+
const wasDisconnected = sessionStorage.getItem(this.DISCONNECTED_FLAG_KEY);
|
|
320
|
+
if (wasDisconnected === 'true') {
|
|
321
|
+
console.log('[TAS DEBUG] Session was intentionally disconnected, skipping restore');
|
|
322
|
+
this.clearSessionState();
|
|
323
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
// Don't restore if we're already disconnected or if there's no active session
|
|
327
|
+
// This prevents restoring sessions that were properly disconnected
|
|
328
|
+
if (this.callStateSubject.getValue() === CallState.DISCONNECTED) {
|
|
329
|
+
console.log('[TAS DEBUG] Call state is DISCONNECTED, skipping restore');
|
|
330
|
+
this.clearSessionState();
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
try {
|
|
334
|
+
const state = JSON.parse(savedState);
|
|
335
|
+
if (state.sessionId && state.token) {
|
|
336
|
+
console.log('[TAS DEBUG] Restoring session from storage');
|
|
337
|
+
// Force PiP mode for restoration to ensure UI consistency
|
|
338
|
+
this.viewModeSubject.next(ViewMode.PIP);
|
|
339
|
+
if (state.businessRole) {
|
|
340
|
+
this.currentBusinessRole = state.businessRole;
|
|
341
|
+
}
|
|
342
|
+
this.connectSession(state.sessionId, state.token, containerId, // Use the same container for both since we are in PiP
|
|
343
|
+
containerId, this.currentBusinessRole)
|
|
344
|
+
.then(() => {
|
|
345
|
+
console.log('[TAS DEBUG] Session restored successfully');
|
|
346
|
+
// Clear the disconnected flag if restoration succeeds
|
|
347
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
348
|
+
})
|
|
349
|
+
.catch((err) => {
|
|
350
|
+
console.error('[TAS DEBUG] Failed to restore session:', err);
|
|
351
|
+
this.clearSessionState(); // Clear bad state
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
console.error('[TAS DEBUG] Error parsing saved session state', e);
|
|
357
|
+
this.clearSessionState();
|
|
358
|
+
}
|
|
359
|
+
}
|
|
139
360
|
// API Methods
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
361
|
+
/**
|
|
362
|
+
* PROXY circuit token: /v2/proxy/video/start
|
|
363
|
+
*/
|
|
364
|
+
startProxyVideoSession(payload) {
|
|
365
|
+
return this.httpClient
|
|
366
|
+
.post('v2/proxy/video/start', {
|
|
367
|
+
body: payload,
|
|
368
|
+
headers: {},
|
|
369
|
+
})
|
|
370
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
371
|
+
console.error('TAS Service: startProxyVideoSession failed', error);
|
|
143
372
|
throw error;
|
|
144
373
|
}));
|
|
145
374
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
375
|
+
/**
|
|
376
|
+
* PROXY circuit status: /v2/proxy/video/status
|
|
377
|
+
*/
|
|
378
|
+
getProxyVideoStatus(payload) {
|
|
379
|
+
return this.httpClient
|
|
380
|
+
.post('v2/proxy/video/status', {
|
|
381
|
+
body: payload,
|
|
382
|
+
headers: {},
|
|
383
|
+
})
|
|
384
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
385
|
+
console.error('TAS Service: getProxyVideoStatus failed', error);
|
|
149
386
|
throw error;
|
|
150
387
|
}));
|
|
151
388
|
}
|
|
389
|
+
/**
|
|
390
|
+
* PROXY circuit user modification: /v2/proxy/video/user/modify
|
|
391
|
+
*/
|
|
392
|
+
modifyProxyVideoUser(payload) {
|
|
393
|
+
return this.httpClient
|
|
394
|
+
.patch('v2/proxy/video/user/modify', {
|
|
395
|
+
body: payload,
|
|
396
|
+
headers: {},
|
|
397
|
+
})
|
|
398
|
+
.pipe(catchError((error) => {
|
|
399
|
+
console.error('TAS Service: modifyProxyVideoUser failed', error);
|
|
400
|
+
throw error;
|
|
401
|
+
}));
|
|
402
|
+
}
|
|
403
|
+
finishProxyVideoSession(payload) {
|
|
404
|
+
return this.httpClient
|
|
405
|
+
.post('v2/proxy/video/finish', {
|
|
406
|
+
body: payload,
|
|
407
|
+
headers: {},
|
|
408
|
+
})
|
|
409
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
410
|
+
console.error('TAS Service: finishProxyVideoSession failed', error);
|
|
411
|
+
throw error;
|
|
412
|
+
}));
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Start automatic status polling for the current session.
|
|
416
|
+
* Status is polled every STATUS_POLL_INTERVAL_MS milliseconds.
|
|
417
|
+
*/
|
|
418
|
+
/**
|
|
419
|
+
* Stop automatic status polling.
|
|
420
|
+
*/
|
|
421
|
+
/**
|
|
422
|
+
* Fetch status and process the response.
|
|
423
|
+
*/
|
|
424
|
+
fetchAndProcessStatus(params) {
|
|
425
|
+
this.getProxyVideoStatus(params).subscribe({
|
|
426
|
+
next: (response) => {
|
|
427
|
+
this.processStatusResponse(response);
|
|
428
|
+
},
|
|
429
|
+
error: (err) => {
|
|
430
|
+
console.error('[TAS DEBUG] Status polling error:', err);
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Process status response to update waiting room users and owner join status.
|
|
436
|
+
*/
|
|
437
|
+
processStatusResponse(response) {
|
|
438
|
+
const content = response.content;
|
|
439
|
+
// Update videoCallId if available
|
|
440
|
+
if (content.videoCallId) {
|
|
441
|
+
this.currentVideoCallId = content.videoCallId;
|
|
442
|
+
}
|
|
443
|
+
// Update sessionId if available
|
|
444
|
+
if (content.sessionId) {
|
|
445
|
+
this.currentSessionId = content.sessionId;
|
|
446
|
+
this.proxyVideoSessionId = content.sessionId;
|
|
447
|
+
}
|
|
448
|
+
// Update joinable status
|
|
449
|
+
this.joinableSubject.next(content.joinable);
|
|
450
|
+
// Check if owner has joined
|
|
451
|
+
const ownerJoined = this.checkIfOwnerJoined(content.users);
|
|
452
|
+
this.ownerHasJoinedSubject.next(ownerJoined);
|
|
453
|
+
// Detect if owner left: was present, now not present
|
|
454
|
+
if (this.wasOwnerPresent && !ownerJoined) {
|
|
455
|
+
console.log('[TAS DEBUG] Owner has left the session');
|
|
456
|
+
this.ownerHasLeftSubject.next(true);
|
|
457
|
+
}
|
|
458
|
+
if (ownerJoined) {
|
|
459
|
+
this.wasOwnerPresent = true;
|
|
460
|
+
}
|
|
461
|
+
// Extract waiting room users (status === WAITING)
|
|
462
|
+
const waitingUsers = content.users
|
|
463
|
+
.filter((u) => u.status === 'WAITING')
|
|
464
|
+
.map((u) => ({
|
|
465
|
+
userId: u.userId,
|
|
466
|
+
name: `User ${u.userId}`,
|
|
467
|
+
status: RoomUserStatus.WAITING,
|
|
468
|
+
}));
|
|
469
|
+
this.waitingRoomUsersSubject.next(waitingUsers);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Check if at least one OWNER has joined the call.
|
|
473
|
+
*/
|
|
474
|
+
checkIfOwnerJoined(users) {
|
|
475
|
+
return users.some((u) => u.rol === TasUserRole.OWNER && u.status === 'JOINED');
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Admit a user from the waiting room by changing their status.
|
|
479
|
+
*/
|
|
480
|
+
admitUserFromWaitingRoom(userId, videoCallId) {
|
|
481
|
+
return this.modifyProxyVideoUser({
|
|
482
|
+
userId,
|
|
483
|
+
videoCallId,
|
|
484
|
+
action: UserCallAction.WAITING_ROOM_LEAVE,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
// Getters for current call context
|
|
488
|
+
get appointmentId() {
|
|
489
|
+
return this.currentAppointmentId;
|
|
490
|
+
}
|
|
491
|
+
get videoCallId() {
|
|
492
|
+
return this.currentVideoCallId;
|
|
493
|
+
}
|
|
152
494
|
/**
|
|
153
495
|
* Connects to a TokBox video session
|
|
154
496
|
*/
|
|
155
|
-
connectSession(sessionId, token, publisherElement, subscriberElement) {
|
|
497
|
+
connectSession(sessionId, token, publisherElement, subscriberElement, businessRole = TasBusinessRole.USER) {
|
|
156
498
|
this.callStateSubject.next(CallState.CONNECTING);
|
|
157
499
|
this.currentSessionId = sessionId;
|
|
158
500
|
this.currentToken = token;
|
|
501
|
+
this.currentBusinessRole = businessRole;
|
|
502
|
+
this.isFinishingSession = false; // Reset flag for new session
|
|
503
|
+
// Save initial state (defaulting to current view mode)
|
|
504
|
+
this.saveSessionState(sessionId, token, this.viewModeSubject.getValue(), businessRole);
|
|
505
|
+
// Clear the disconnected flag when starting a new connection
|
|
506
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
159
507
|
return new Promise((resolve, reject) => {
|
|
160
508
|
if (!OT.checkSystemRequirements()) {
|
|
161
509
|
this.callStateSubject.next(CallState.ERROR);
|
|
162
|
-
reject(new Error(
|
|
510
|
+
reject(new Error('Browser not compatible with TokBox'));
|
|
163
511
|
return;
|
|
164
512
|
}
|
|
165
513
|
this.session = OT.initSession(this.config.tokBoxApiKey, sessionId);
|
|
166
514
|
// Handle new streams (remote participants joining)
|
|
167
|
-
this.session.on(
|
|
515
|
+
this.session.on('streamCreated', (event) => {
|
|
168
516
|
const subscriber = this.session?.subscribe(event.stream, subscriberElement, {
|
|
169
|
-
insertMode:
|
|
170
|
-
width:
|
|
171
|
-
height:
|
|
517
|
+
insertMode: 'append',
|
|
518
|
+
width: '100%',
|
|
519
|
+
height: '100%',
|
|
172
520
|
}, (error) => {
|
|
173
521
|
if (error) {
|
|
174
|
-
console.error(
|
|
522
|
+
console.error('Error subscribing to stream:', error);
|
|
175
523
|
}
|
|
176
524
|
});
|
|
177
525
|
if (subscriber) {
|
|
@@ -179,29 +527,48 @@ class TasService {
|
|
|
179
527
|
}
|
|
180
528
|
});
|
|
181
529
|
// Handle streams ending (remote participants leaving)
|
|
182
|
-
this.session.on(
|
|
530
|
+
this.session.on('streamDestroyed', (event) => {
|
|
183
531
|
this.subscribers = this.subscribers.filter((sub) => sub.stream?.streamId !== event.stream.streamId);
|
|
184
532
|
});
|
|
185
533
|
// Handle session disconnection
|
|
186
|
-
this.session.on(
|
|
534
|
+
this.session.on('sessionDisconnected', () => {
|
|
187
535
|
this.callStateSubject.next(CallState.DISCONNECTED);
|
|
536
|
+
// Call finishSession when session is disconnected (e.g., by server)
|
|
537
|
+
// Only call if not already finishing (prevents duplicate calls)
|
|
538
|
+
const sessionIdToFinish = this.currentSessionId;
|
|
539
|
+
if (sessionIdToFinish && !this.isFinishingSession) {
|
|
540
|
+
this.isFinishingSession = true;
|
|
541
|
+
this.finishProxyVideoSession({
|
|
542
|
+
sessionId: sessionIdToFinish,
|
|
543
|
+
businessRole: this.currentBusinessRole,
|
|
544
|
+
}).subscribe({
|
|
545
|
+
next: (response) => {
|
|
546
|
+
console.log('[TAS DEBUG] Session finished on disconnect event:', response);
|
|
547
|
+
this.isFinishingSession = false;
|
|
548
|
+
},
|
|
549
|
+
error: (error) => {
|
|
550
|
+
console.error('[TAS DEBUG] Error finishing session on disconnect:', error);
|
|
551
|
+
this.isFinishingSession = false;
|
|
552
|
+
},
|
|
553
|
+
});
|
|
554
|
+
}
|
|
188
555
|
});
|
|
189
556
|
// Connect to session
|
|
190
557
|
this.session.connect(token, (error) => {
|
|
191
558
|
if (error) {
|
|
192
|
-
console.error(
|
|
559
|
+
console.error('Error connecting to session:', error);
|
|
193
560
|
this.callStateSubject.next(CallState.ERROR);
|
|
194
561
|
reject(error);
|
|
195
562
|
return;
|
|
196
563
|
}
|
|
197
564
|
// Initialize publisher (local video)
|
|
198
565
|
this.publisher = OT.initPublisher(publisherElement, {
|
|
199
|
-
insertMode:
|
|
200
|
-
width:
|
|
201
|
-
height:
|
|
566
|
+
insertMode: 'append',
|
|
567
|
+
width: '100%',
|
|
568
|
+
height: '100%',
|
|
202
569
|
}, (err) => {
|
|
203
570
|
if (err) {
|
|
204
|
-
console.error(
|
|
571
|
+
console.error('Error initializing publisher:', err);
|
|
205
572
|
this.callStateSubject.next(CallState.ERROR);
|
|
206
573
|
reject(err);
|
|
207
574
|
return;
|
|
@@ -209,7 +576,7 @@ class TasService {
|
|
|
209
576
|
// Publish to session
|
|
210
577
|
this.session?.publish(this.publisher, (pubErr) => {
|
|
211
578
|
if (pubErr) {
|
|
212
|
-
console.error(
|
|
579
|
+
console.error('Error publishing stream:', pubErr);
|
|
213
580
|
this.callStateSubject.next(CallState.ERROR);
|
|
214
581
|
reject(pubErr);
|
|
215
582
|
}
|
|
@@ -269,12 +636,12 @@ class TasService {
|
|
|
269
636
|
this.movePublisherTo('publisher-container');
|
|
270
637
|
}
|
|
271
638
|
}
|
|
272
|
-
TasService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
273
|
-
TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.
|
|
274
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
639
|
+
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 });
|
|
640
|
+
TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, providedIn: 'root' });
|
|
641
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, decorators: [{
|
|
275
642
|
type: Injectable,
|
|
276
643
|
args: [{
|
|
277
|
-
providedIn:
|
|
644
|
+
providedIn: 'root',
|
|
278
645
|
}]
|
|
279
646
|
}], ctorParameters: function () { return [{ type: undefined, decorators: [{
|
|
280
647
|
type: Inject,
|
|
@@ -284,14 +651,94 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
284
651
|
args: [TAS_CONFIG]
|
|
285
652
|
}] }]; } });
|
|
286
653
|
|
|
654
|
+
class TasAvatarComponent {
|
|
655
|
+
constructor() {
|
|
656
|
+
this.name = '';
|
|
657
|
+
this.size = 80;
|
|
658
|
+
}
|
|
659
|
+
get initials() {
|
|
660
|
+
if (!this.name)
|
|
661
|
+
return '';
|
|
662
|
+
return this.name
|
|
663
|
+
.split(' ')
|
|
664
|
+
.filter((n) => n.length > 0)
|
|
665
|
+
.map((n) => n[0])
|
|
666
|
+
.join('')
|
|
667
|
+
.toUpperCase()
|
|
668
|
+
.substring(0, 2);
|
|
669
|
+
}
|
|
670
|
+
get fontSize() {
|
|
671
|
+
return Math.round(this.size * 0.4);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
TasAvatarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
675
|
+
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: `
|
|
676
|
+
<div
|
|
677
|
+
class="avatar"
|
|
678
|
+
[style.width.px]="size"
|
|
679
|
+
[style.height.px]="size"
|
|
680
|
+
[style.fontSize.px]="fontSize"
|
|
681
|
+
>
|
|
682
|
+
<span class="initials">{{ initials }}</span>
|
|
683
|
+
</div>
|
|
684
|
+
`, 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 });
|
|
685
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasAvatarComponent, decorators: [{
|
|
686
|
+
type: Component,
|
|
687
|
+
args: [{
|
|
688
|
+
selector: 'tas-avatar',
|
|
689
|
+
template: `
|
|
690
|
+
<div
|
|
691
|
+
class="avatar"
|
|
692
|
+
[style.width.px]="size"
|
|
693
|
+
[style.height.px]="size"
|
|
694
|
+
[style.fontSize.px]="fontSize"
|
|
695
|
+
>
|
|
696
|
+
<span class="initials">{{ initials }}</span>
|
|
697
|
+
</div>
|
|
698
|
+
`,
|
|
699
|
+
styles: [
|
|
700
|
+
`
|
|
701
|
+
.avatar {
|
|
702
|
+
display: flex;
|
|
703
|
+
align-items: center;
|
|
704
|
+
justify-content: center;
|
|
705
|
+
border-radius: 50%;
|
|
706
|
+
background-color: #ffffff;
|
|
707
|
+
color: #0072ac;
|
|
708
|
+
font-weight: 600;
|
|
709
|
+
font-family: inherit;
|
|
710
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
.initials {
|
|
714
|
+
text-transform: uppercase;
|
|
715
|
+
user-select: none;
|
|
716
|
+
}
|
|
717
|
+
`,
|
|
718
|
+
],
|
|
719
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
720
|
+
}]
|
|
721
|
+
}], propDecorators: { name: [{
|
|
722
|
+
type: Input
|
|
723
|
+
}], size: [{
|
|
724
|
+
type: Input
|
|
725
|
+
}] } });
|
|
726
|
+
|
|
287
727
|
class TasVideocallComponent {
|
|
288
728
|
constructor(activeModal, tasService) {
|
|
289
729
|
this.activeModal = activeModal;
|
|
290
730
|
this.tasService = tasService;
|
|
731
|
+
this.participantName = '';
|
|
732
|
+
this.tenant = '';
|
|
733
|
+
this.businessRole = TasBusinessRole.USER;
|
|
291
734
|
this.isReturningFromPip = false;
|
|
292
735
|
this.isPublisherSmall = true;
|
|
293
736
|
this.callState = CallState.IDLE;
|
|
294
737
|
this.isMuted = false;
|
|
738
|
+
this.waitingRoomUsers = [];
|
|
739
|
+
this.ownerHasJoined = false;
|
|
740
|
+
this.hasVideoStream = false;
|
|
741
|
+
this.dismissedUsers = [];
|
|
295
742
|
this.subscriptions = new Subscription();
|
|
296
743
|
}
|
|
297
744
|
ngOnInit() {
|
|
@@ -303,6 +750,7 @@ class TasVideocallComponent {
|
|
|
303
750
|
}
|
|
304
751
|
ngOnDestroy() {
|
|
305
752
|
this.subscriptions.unsubscribe();
|
|
753
|
+
this.tasService.resetPollingState();
|
|
306
754
|
// Only disconnect if not in PiP mode (keep session alive for floating window)
|
|
307
755
|
if (!this.tasService.isPipMode()) {
|
|
308
756
|
this.tasService.disconnectSession();
|
|
@@ -329,25 +777,83 @@ class TasVideocallComponent {
|
|
|
329
777
|
onDoubleClick() {
|
|
330
778
|
this.toggleSwap();
|
|
331
779
|
}
|
|
780
|
+
/**
|
|
781
|
+
* Check if current user can admit others (OWNER, BACKOFFICE, or MODERATOR)
|
|
782
|
+
*/
|
|
783
|
+
get canAdmitUsers() {
|
|
784
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
785
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
786
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Admit a user from the waiting room
|
|
790
|
+
*/
|
|
791
|
+
admitUser(userId) {
|
|
792
|
+
if (!this.videoCallId) {
|
|
793
|
+
console.error('Cannot admit user: videoCallId not set');
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
this.tasService
|
|
797
|
+
.modifyProxyVideoUser({
|
|
798
|
+
userId,
|
|
799
|
+
videoCallId: this.videoCallId,
|
|
800
|
+
action: UserCallAction.WAITING_ROOM_LEAVE,
|
|
801
|
+
})
|
|
802
|
+
.subscribe({
|
|
803
|
+
next: () => {
|
|
804
|
+
// Remove user from waiting list after successful admit
|
|
805
|
+
this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
|
|
806
|
+
},
|
|
807
|
+
error: (err) => {
|
|
808
|
+
console.error('Error admitting user:', err);
|
|
809
|
+
},
|
|
810
|
+
});
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Dismiss the waiting room notification for a user
|
|
814
|
+
*/
|
|
815
|
+
dismissWaitingNotification(userId) {
|
|
816
|
+
this.dismissedUsers.push(userId);
|
|
817
|
+
this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
|
|
818
|
+
}
|
|
332
819
|
// Private Methods
|
|
333
820
|
setupSubscriptions() {
|
|
334
821
|
// Call state subscription
|
|
335
|
-
this.subscriptions.add(this.tasService.callState$.subscribe(state => {
|
|
822
|
+
this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
|
|
336
823
|
this.callState = state;
|
|
337
824
|
if (state === CallState.DISCONNECTED) {
|
|
338
825
|
this.activeModal.close('hangup');
|
|
339
826
|
}
|
|
827
|
+
// Track if we have an active video stream
|
|
828
|
+
this.hasVideoStream = state === CallState.CONNECTED;
|
|
340
829
|
}));
|
|
341
830
|
// View mode subscription
|
|
342
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
831
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
343
832
|
if (mode === ViewMode.PIP) {
|
|
344
833
|
this.activeModal.close('pip');
|
|
345
834
|
}
|
|
346
835
|
}));
|
|
347
836
|
// Mute state subscription
|
|
348
|
-
this.subscriptions.add(this.tasService.isMuted$.subscribe(muted => {
|
|
837
|
+
this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
|
|
349
838
|
this.isMuted = muted;
|
|
350
839
|
}));
|
|
840
|
+
// Waiting room users subscription
|
|
841
|
+
this.subscriptions.add(this.tasService.waitingRoomUsers$.subscribe((users) => {
|
|
842
|
+
// Filter out dismissed users
|
|
843
|
+
this.waitingRoomUsers = users.filter((u) => !this.dismissedUsers.includes(u.userId));
|
|
844
|
+
}));
|
|
845
|
+
// Owner join status subscription
|
|
846
|
+
this.subscriptions.add(this.tasService.ownerHasJoined$.subscribe((joined) => {
|
|
847
|
+
this.ownerHasJoined = joined;
|
|
848
|
+
}));
|
|
849
|
+
// Owner left subscription - disconnect non-owners
|
|
850
|
+
this.subscriptions.add(this.tasService.ownerHasLeft$.subscribe((hasLeft) => {
|
|
851
|
+
if (hasLeft && !this.canAdmitUsers) { // Non-owner user
|
|
852
|
+
console.log('[TAS DEBUG] Owner left, disconnecting user');
|
|
853
|
+
this.hangUp();
|
|
854
|
+
this.activeModal.close('owner_left');
|
|
855
|
+
}
|
|
856
|
+
}));
|
|
351
857
|
}
|
|
352
858
|
initializeCall() {
|
|
353
859
|
if (this.isReturningFromPip) {
|
|
@@ -356,9 +862,22 @@ class TasVideocallComponent {
|
|
|
356
862
|
}
|
|
357
863
|
else if (this.sessionId && this.token) {
|
|
358
864
|
// New call - connect to session
|
|
359
|
-
this.tasService
|
|
865
|
+
this.tasService
|
|
866
|
+
.connectSession(this.sessionId, this.token, 'publisher-container', 'subscriber-container', this.businessRole)
|
|
867
|
+
.catch((err) => {
|
|
360
868
|
console.error('Error connecting to video call:', err);
|
|
361
869
|
});
|
|
870
|
+
// Start status polling with shorter interval (5s) to detect owner leaving/location requests
|
|
871
|
+
if (this.appointmentId) {
|
|
872
|
+
this.tasService.startStatusPolling({
|
|
873
|
+
appointmentId: this.appointmentId,
|
|
874
|
+
tenant: this.tenant,
|
|
875
|
+
businessRole: this.businessRole,
|
|
876
|
+
roomType: undefined,
|
|
877
|
+
entityId: undefined,
|
|
878
|
+
sessionId: this.sessionId
|
|
879
|
+
}, 5000);
|
|
880
|
+
}
|
|
362
881
|
}
|
|
363
882
|
}
|
|
364
883
|
resetVideoPositions() {
|
|
@@ -383,8 +902,8 @@ class TasVideocallComponent {
|
|
|
383
902
|
modifiers: [
|
|
384
903
|
interact.modifiers.restrictRect({
|
|
385
904
|
restriction: 'parent',
|
|
386
|
-
endOnly: true
|
|
387
|
-
})
|
|
905
|
+
endOnly: true,
|
|
906
|
+
}),
|
|
388
907
|
],
|
|
389
908
|
autoScroll: true,
|
|
390
909
|
listeners: {
|
|
@@ -395,8 +914,8 @@ class TasVideocallComponent {
|
|
|
395
914
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
396
915
|
target.setAttribute('data-x', String(x));
|
|
397
916
|
target.setAttribute('data-y', String(y));
|
|
398
|
-
}
|
|
399
|
-
}
|
|
917
|
+
},
|
|
918
|
+
},
|
|
400
919
|
})
|
|
401
920
|
.resizable({
|
|
402
921
|
edges: { left: false, right: true, bottom: true, top: false },
|
|
@@ -412,26 +931,36 @@ class TasVideocallComponent {
|
|
|
412
931
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
413
932
|
target.setAttribute('data-x', String(x));
|
|
414
933
|
target.setAttribute('data-y', String(y));
|
|
415
|
-
}
|
|
934
|
+
},
|
|
416
935
|
},
|
|
417
936
|
modifiers: [
|
|
418
937
|
interact.modifiers.restrictEdges({ outer: 'parent' }),
|
|
419
938
|
interact.modifiers.restrictSize({ min: { width: 150, height: 100 } }),
|
|
420
|
-
interact.modifiers.aspectRatio({ ratio: 'preserve' })
|
|
939
|
+
interact.modifiers.aspectRatio({ ratio: 'preserve' }),
|
|
421
940
|
],
|
|
422
|
-
inertia: true
|
|
941
|
+
inertia: true,
|
|
423
942
|
});
|
|
424
943
|
}
|
|
425
944
|
}
|
|
426
|
-
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
427
|
-
TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
428
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
945
|
+
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 });
|
|
946
|
+
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"] }] });
|
|
947
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
|
|
429
948
|
type: Component,
|
|
430
|
-
args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-container\">\n
|
|
949
|
+
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"] }]
|
|
431
950
|
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { sessionId: [{
|
|
432
951
|
type: Input
|
|
433
952
|
}], token: [{
|
|
434
953
|
type: Input
|
|
954
|
+
}], appointmentId: [{
|
|
955
|
+
type: Input
|
|
956
|
+
}], videoCallId: [{
|
|
957
|
+
type: Input
|
|
958
|
+
}], participantName: [{
|
|
959
|
+
type: Input
|
|
960
|
+
}], tenant: [{
|
|
961
|
+
type: Input
|
|
962
|
+
}], businessRole: [{
|
|
963
|
+
type: Input
|
|
435
964
|
}], isReturningFromPip: [{
|
|
436
965
|
type: Input
|
|
437
966
|
}], publisherContainer: [{
|
|
@@ -444,127 +973,192 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
444
973
|
|
|
445
974
|
var WaitingRoomState;
|
|
446
975
|
(function (WaitingRoomState) {
|
|
447
|
-
WaitingRoomState["
|
|
448
|
-
WaitingRoomState["
|
|
449
|
-
WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
|
|
976
|
+
WaitingRoomState["CHECKING_STATUS"] = "CHECKING_STATUS";
|
|
977
|
+
WaitingRoomState["WAITING_FOR_OWNER"] = "WAITING_FOR_OWNER";
|
|
450
978
|
WaitingRoomState["READY"] = "READY";
|
|
979
|
+
WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
|
|
451
980
|
WaitingRoomState["ERROR"] = "ERROR";
|
|
452
981
|
})(WaitingRoomState || (WaitingRoomState = {}));
|
|
453
982
|
class TasWaitingRoomComponent {
|
|
454
|
-
constructor(activeModal, tasService, modalService) {
|
|
983
|
+
constructor(activeModal, tasService, modalService, cdr) {
|
|
455
984
|
this.activeModal = activeModal;
|
|
456
985
|
this.tasService = tasService;
|
|
457
986
|
this.modalService = modalService;
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
this.
|
|
461
|
-
this.
|
|
462
|
-
this.regularUserIds = [];
|
|
463
|
-
this.moderatorUserIds = [];
|
|
464
|
-
/** Optional: If provided, skips room creation and goes directly to getting a token */
|
|
465
|
-
this.existingSessionId = '';
|
|
987
|
+
this.cdr = cdr;
|
|
988
|
+
// Status endpoint params
|
|
989
|
+
this.roomType = TasRoomType.TAS;
|
|
990
|
+
this.businessRole = TasBusinessRole.USER;
|
|
466
991
|
// Component state
|
|
467
|
-
this.state = WaitingRoomState.
|
|
992
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
468
993
|
this.WaitingRoomState = WaitingRoomState; // Expose enum to template
|
|
469
994
|
this.errorMessage = '';
|
|
470
|
-
|
|
471
|
-
this.
|
|
472
|
-
|
|
473
|
-
this.sessionId = '';
|
|
995
|
+
// Session data from status response
|
|
996
|
+
this.resolvedSessionId = '';
|
|
997
|
+
this.resolvedAppointmentId = null;
|
|
474
998
|
this.token = '';
|
|
475
|
-
this.
|
|
999
|
+
this.videoCallId = null;
|
|
1000
|
+
// Subscriptions
|
|
476
1001
|
this.subscriptions = new Subscription();
|
|
477
1002
|
this.videoCallModalRef = null;
|
|
478
1003
|
}
|
|
1004
|
+
/** Whether current user is an owner */
|
|
1005
|
+
get isOwner() {
|
|
1006
|
+
return this.currentUser?.role === TasUserRole.OWNER;
|
|
1007
|
+
}
|
|
479
1008
|
ngOnInit() {
|
|
480
|
-
|
|
481
|
-
this.
|
|
482
|
-
// Check if we have an existing session
|
|
483
|
-
if (this.existingSessionId && this.existingSessionId.trim() !== '') {
|
|
484
|
-
this.hasExistingSession = true;
|
|
485
|
-
this.sessionId = this.existingSessionId;
|
|
486
|
-
}
|
|
1009
|
+
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnInit');
|
|
1010
|
+
this.checkStatus();
|
|
487
1011
|
}
|
|
488
1012
|
ngOnDestroy() {
|
|
1013
|
+
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnDestroy');
|
|
489
1014
|
this.subscriptions.unsubscribe();
|
|
1015
|
+
this.tasService.stopStatusPolling();
|
|
490
1016
|
}
|
|
491
1017
|
/**
|
|
492
|
-
*
|
|
1018
|
+
* Check status to get session info
|
|
493
1019
|
*/
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
this.
|
|
497
|
-
this.
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
1020
|
+
checkStatus() {
|
|
1021
|
+
console.log('[TAS DEBUG] checkStatus called with:', {
|
|
1022
|
+
roomType: this.roomType,
|
|
1023
|
+
entityId: this.entityId,
|
|
1024
|
+
tenant: this.tenant,
|
|
1025
|
+
businessRole: this.businessRole,
|
|
1026
|
+
currentUser: this.currentUser,
|
|
1027
|
+
});
|
|
1028
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
501
1029
|
this.errorMessage = '';
|
|
502
|
-
const
|
|
503
|
-
roomType:
|
|
504
|
-
|
|
505
|
-
tenant: this.
|
|
506
|
-
|
|
507
|
-
users: this.users,
|
|
508
|
-
product: this.product
|
|
1030
|
+
const statusParams = {
|
|
1031
|
+
roomType: this.roomType,
|
|
1032
|
+
entityId: this.entityId,
|
|
1033
|
+
tenant: this.tenant,
|
|
1034
|
+
businessRole: this.businessRole,
|
|
509
1035
|
};
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
this.
|
|
1036
|
+
console.log('[TAS DEBUG] Calling getProxyVideoStatus...');
|
|
1037
|
+
this.subscriptions.add(this.tasService.getProxyVideoStatus(statusParams).subscribe({
|
|
1038
|
+
next: (response) => {
|
|
1039
|
+
const content = response.content;
|
|
1040
|
+
// Store session info from response
|
|
1041
|
+
this.resolvedSessionId = content.sessionId;
|
|
1042
|
+
this.resolvedAppointmentId = content.appointmentId;
|
|
1043
|
+
this.videoCallId = content.videoCallId;
|
|
1044
|
+
console.log('[TAS DEBUG] Status response:', content);
|
|
1045
|
+
// Start polling for status updates
|
|
1046
|
+
this.tasService.startStatusPolling(statusParams);
|
|
1047
|
+
// Subscribe to joinable status
|
|
1048
|
+
this.subscriptions.add(this.tasService.joinable$.subscribe((joinable) => {
|
|
1049
|
+
this.handleJoinableChange(joinable);
|
|
1050
|
+
}));
|
|
523
1051
|
},
|
|
524
1052
|
error: (err) => {
|
|
525
|
-
console.error('
|
|
1053
|
+
console.error('[TAS DEBUG] Status check failed:', err);
|
|
526
1054
|
this.state = WaitingRoomState.ERROR;
|
|
527
|
-
this.errorMessage = 'Error
|
|
528
|
-
}
|
|
1055
|
+
this.errorMessage = 'Error checking session status. Please try again.';
|
|
1056
|
+
},
|
|
529
1057
|
}));
|
|
530
1058
|
}
|
|
531
1059
|
/**
|
|
532
|
-
*
|
|
1060
|
+
* Handle changes to joinable status
|
|
1061
|
+
*/
|
|
1062
|
+
handleJoinableChange(joinable) {
|
|
1063
|
+
console.log('[TAS DEBUG] handleJoinableChange called', {
|
|
1064
|
+
joinable,
|
|
1065
|
+
currentState: this.state,
|
|
1066
|
+
isOwner: this.isOwner,
|
|
1067
|
+
isBackoffice: this.isBackoffice,
|
|
1068
|
+
resolvedSessionId: this.resolvedSessionId,
|
|
1069
|
+
});
|
|
1070
|
+
// Don't update state if already getting token, ready, or in error
|
|
1071
|
+
if (this.state === WaitingRoomState.GETTING_TOKEN ||
|
|
1072
|
+
this.state === WaitingRoomState.READY ||
|
|
1073
|
+
this.state === WaitingRoomState.ERROR) {
|
|
1074
|
+
console.log('[TAS DEBUG] Skipping state update - already in:', this.state);
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
if (this.isOwner || this.isBackoffice) {
|
|
1078
|
+
console.log('[TAS DEBUG] User is owner/backoffice, calling getTokenForOwner');
|
|
1079
|
+
// Owner/Backoffice: call /start to get token first, then show join button
|
|
1080
|
+
if (this.state === WaitingRoomState.CHECKING_STATUS) {
|
|
1081
|
+
this.getTokenForOwner();
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
else {
|
|
1085
|
+
// Non-owner: wait until joinable is true
|
|
1086
|
+
if (joinable) {
|
|
1087
|
+
console.log('[TAS DEBUG] Non-owner: joinable is true, getting token');
|
|
1088
|
+
this.getTokenForOwner(); // Also get token when joinable
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
console.log('[TAS DEBUG] Non-owner: waiting for owner');
|
|
1092
|
+
this.state = WaitingRoomState.WAITING_FOR_OWNER;
|
|
1093
|
+
this.cdr.detectChanges();
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
/**
|
|
1098
|
+
* Check if user has owner/backoffice role
|
|
1099
|
+
*/
|
|
1100
|
+
get isBackoffice() {
|
|
1101
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
1102
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
1103
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Get token for owner/backoffice - call /start endpoint
|
|
533
1107
|
*/
|
|
534
|
-
|
|
535
|
-
if (!this.
|
|
1108
|
+
getTokenForOwner() {
|
|
1109
|
+
if (!this.resolvedSessionId) {
|
|
536
1110
|
this.state = WaitingRoomState.ERROR;
|
|
537
|
-
this.errorMessage = '
|
|
1111
|
+
this.errorMessage = 'Session ID not available';
|
|
1112
|
+
this.tasService.stopStatusPolling();
|
|
1113
|
+
this.cdr.detectChanges();
|
|
538
1114
|
return;
|
|
539
1115
|
}
|
|
540
1116
|
this.state = WaitingRoomState.GETTING_TOKEN;
|
|
541
1117
|
this.errorMessage = '';
|
|
542
|
-
|
|
543
|
-
|
|
1118
|
+
console.log('[TAS DEBUG] Calling /start for session:', this.resolvedSessionId);
|
|
1119
|
+
this.subscriptions.add(this.tasService
|
|
1120
|
+
.startProxyVideoSession({
|
|
1121
|
+
sessionId: this.resolvedSessionId,
|
|
544
1122
|
name: this.currentUser.name,
|
|
545
1123
|
lastname: this.currentUser.lastname,
|
|
546
|
-
|
|
547
|
-
|
|
1124
|
+
})
|
|
1125
|
+
.subscribe({
|
|
548
1126
|
next: (tokenResponse) => {
|
|
1127
|
+
console.log('[TAS DEBUG] Token response:', tokenResponse);
|
|
1128
|
+
// Handle case where HTTP adapter returns error in next instead of error handler
|
|
1129
|
+
if (!tokenResponse?.content?.token) {
|
|
1130
|
+
console.error('[TAS DEBUG] Invalid token response:', tokenResponse);
|
|
1131
|
+
this.state = WaitingRoomState.ERROR;
|
|
1132
|
+
this.errorMessage = tokenResponse?.message || 'Error al iniciar la sesión. Respuesta inválida.';
|
|
1133
|
+
this.tasService.stopStatusPolling();
|
|
1134
|
+
this.cdr.detectChanges();
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
console.log('[TAS DEBUG] Token obtained successfully');
|
|
549
1138
|
this.token = tokenResponse.content.token;
|
|
550
1139
|
this.state = WaitingRoomState.READY;
|
|
1140
|
+
this.cdr.detectChanges();
|
|
551
1141
|
},
|
|
552
1142
|
error: (err) => {
|
|
553
|
-
console.error('
|
|
1143
|
+
console.error('[TAS DEBUG] /start request failed:', err);
|
|
554
1144
|
this.state = WaitingRoomState.ERROR;
|
|
555
|
-
this.errorMessage = 'Error
|
|
556
|
-
|
|
1145
|
+
this.errorMessage = err?.error?.message || err?.message || 'Error al iniciar la sesión. Por favor, intente nuevamente.';
|
|
1146
|
+
this.tasService.stopStatusPolling();
|
|
1147
|
+
console.log('[TAS DEBUG] State set to ERROR, errorMessage:', this.errorMessage);
|
|
1148
|
+
this.cdr.detectChanges();
|
|
1149
|
+
},
|
|
557
1150
|
}));
|
|
558
1151
|
}
|
|
559
1152
|
/**
|
|
560
|
-
*
|
|
1153
|
+
* Join the session - token already obtained
|
|
561
1154
|
*/
|
|
562
1155
|
joinSession() {
|
|
563
|
-
if (!this.
|
|
564
|
-
this.errorMessage = '
|
|
1156
|
+
if (!this.resolvedSessionId || !this.token) {
|
|
1157
|
+
this.errorMessage = 'Session not ready';
|
|
565
1158
|
return;
|
|
566
1159
|
}
|
|
567
1160
|
// Close waiting room and open video call
|
|
1161
|
+
this.tasService.stopStatusPolling();
|
|
568
1162
|
this.activeModal.close('joining');
|
|
569
1163
|
this.openVideoCallModal();
|
|
570
1164
|
}
|
|
@@ -572,85 +1166,53 @@ class TasWaitingRoomComponent {
|
|
|
572
1166
|
* Closes the waiting room
|
|
573
1167
|
*/
|
|
574
1168
|
cancel() {
|
|
1169
|
+
this.tasService.stopStatusPolling();
|
|
575
1170
|
this.activeModal.dismiss('cancel');
|
|
576
1171
|
}
|
|
577
1172
|
/**
|
|
578
1173
|
* Retry after an error
|
|
579
1174
|
*/
|
|
580
1175
|
retry() {
|
|
581
|
-
this.state = WaitingRoomState.
|
|
1176
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
582
1177
|
this.errorMessage = '';
|
|
583
1178
|
this.token = '';
|
|
584
|
-
|
|
585
|
-
if (!this.hasExistingSession) {
|
|
586
|
-
this.sessionId = '';
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
// Private Methods
|
|
590
|
-
buildUsersArray() {
|
|
591
|
-
this.users = [];
|
|
592
|
-
// Add owners from input
|
|
593
|
-
this.ownerUserIds.forEach(id => {
|
|
594
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.OWNER });
|
|
595
|
-
});
|
|
596
|
-
// Add regular users from input
|
|
597
|
-
this.regularUserIds.forEach(id => {
|
|
598
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.USER });
|
|
599
|
-
});
|
|
600
|
-
// Add moderators from input
|
|
601
|
-
this.moderatorUserIds.forEach(id => {
|
|
602
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.MODERATOR });
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
setupViewModeSubscription() {
|
|
606
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
607
|
-
// Re-open video call modal when returning from PiP mode
|
|
608
|
-
if (mode === ViewMode.FULLSCREEN &&
|
|
609
|
-
this.tasService.isCallActive() &&
|
|
610
|
-
!this.videoCallModalRef) {
|
|
611
|
-
const sessionId = this.tasService.sessionId;
|
|
612
|
-
const token = this.tasService.token;
|
|
613
|
-
if (sessionId && token) {
|
|
614
|
-
this.openVideoCallModal(true);
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
}));
|
|
1179
|
+
this.checkStatus();
|
|
618
1180
|
}
|
|
619
|
-
openVideoCallModal(
|
|
1181
|
+
openVideoCallModal() {
|
|
620
1182
|
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
621
1183
|
size: 'xl',
|
|
622
1184
|
windowClass: 'tas-video-modal',
|
|
623
1185
|
backdrop: 'static',
|
|
624
|
-
keyboard: false
|
|
1186
|
+
keyboard: false,
|
|
1187
|
+
});
|
|
1188
|
+
this.videoCallModalRef.componentInstance.sessionId = this.resolvedSessionId;
|
|
1189
|
+
this.videoCallModalRef.componentInstance.token = this.token;
|
|
1190
|
+
this.videoCallModalRef.componentInstance.appointmentId = this.resolvedAppointmentId;
|
|
1191
|
+
this.videoCallModalRef.componentInstance.videoCallId = this.videoCallId;
|
|
1192
|
+
this.videoCallModalRef.componentInstance.tenant = this.tenant;
|
|
1193
|
+
this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
|
|
1194
|
+
this.videoCallModalRef.componentInstance.isReturningFromPip = false;
|
|
1195
|
+
this.videoCallModalRef.result.then(() => {
|
|
1196
|
+
this.videoCallModalRef = null;
|
|
1197
|
+
}, () => {
|
|
1198
|
+
this.videoCallModalRef = null;
|
|
625
1199
|
});
|
|
626
|
-
const sessionIdToUse = this.sessionId || this.tasService.sessionId;
|
|
627
|
-
const tokenToUse = this.token || this.tasService.token;
|
|
628
|
-
this.videoCallModalRef.componentInstance.sessionId = sessionIdToUse;
|
|
629
|
-
this.videoCallModalRef.componentInstance.token = tokenToUse;
|
|
630
|
-
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
631
|
-
this.videoCallModalRef.result.then(() => { this.videoCallModalRef = null; }, () => { this.videoCallModalRef = null; });
|
|
632
1200
|
}
|
|
633
1201
|
}
|
|
634
|
-
TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
635
|
-
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"] }] });
|
|
636
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1202
|
+
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 });
|
|
1203
|
+
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"] }] });
|
|
1204
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
|
|
637
1205
|
type: Component,
|
|
638
|
-
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"] }]
|
|
639
|
-
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: i1.NgbModal }]; }, propDecorators: {
|
|
640
|
-
type: Input
|
|
641
|
-
}], product: [{
|
|
642
|
-
type: Input
|
|
643
|
-
}], tenantId: [{
|
|
644
|
-
type: Input
|
|
645
|
-
}], currentUser: [{
|
|
1206
|
+
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"] }]
|
|
1207
|
+
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
|
|
646
1208
|
type: Input
|
|
647
|
-
}],
|
|
1209
|
+
}], entityId: [{
|
|
648
1210
|
type: Input
|
|
649
|
-
}],
|
|
1211
|
+
}], tenant: [{
|
|
650
1212
|
type: Input
|
|
651
|
-
}],
|
|
1213
|
+
}], businessRole: [{
|
|
652
1214
|
type: Input
|
|
653
|
-
}],
|
|
1215
|
+
}], currentUser: [{
|
|
654
1216
|
type: Input
|
|
655
1217
|
}] } });
|
|
656
1218
|
|
|
@@ -658,116 +1220,211 @@ class TasButtonComponent {
|
|
|
658
1220
|
constructor(modalService, tasService) {
|
|
659
1221
|
this.modalService = modalService;
|
|
660
1222
|
this.tasService = tasService;
|
|
661
|
-
|
|
662
|
-
this.
|
|
663
|
-
this.
|
|
664
|
-
this.regularUserIds = [];
|
|
665
|
-
this.moderatorUserIds = [];
|
|
666
|
-
/** Optional: If provided, skips room creation and goes directly to getting a token */
|
|
667
|
-
this.existingSessionId = "";
|
|
668
|
-
/** Optional: Custom button text */
|
|
669
|
-
this.buttonText = "Iniciar TAS";
|
|
1223
|
+
// Status endpoint params
|
|
1224
|
+
this.roomType = TasRoomType.TAS;
|
|
1225
|
+
this.businessRole = TasBusinessRole.USER;
|
|
670
1226
|
this.isLoading = false;
|
|
1227
|
+
this.buttonText = 'Iniciar TAS';
|
|
1228
|
+
// Status check state
|
|
1229
|
+
this.isCheckingStatus = false;
|
|
1230
|
+
this.isStatusError = false;
|
|
1231
|
+
this.statusErrorMessage = '';
|
|
671
1232
|
this.subscriptions = new Subscription();
|
|
672
1233
|
this.currentModalRef = null;
|
|
673
1234
|
this.videoCallModalRef = null;
|
|
1235
|
+
this.statusPollingInterval = null;
|
|
1236
|
+
this.STATUS_POLL_INTERVAL_MS = 30000; // 30 seconds
|
|
674
1237
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1238
|
+
/** Whether user is backoffice (or admin/manager) */
|
|
1239
|
+
get isBackoffice() {
|
|
1240
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
1241
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
1242
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
1243
|
+
}
|
|
1244
|
+
/** Whether the button should be visible */
|
|
1245
|
+
get isVisible() {
|
|
1246
|
+
// Backoffice: always show (disabled if error)
|
|
1247
|
+
// Other roles: hide if status error
|
|
1248
|
+
if (this.isBackoffice) {
|
|
1249
|
+
return true;
|
|
678
1250
|
}
|
|
1251
|
+
return !this.isStatusError;
|
|
1252
|
+
}
|
|
1253
|
+
/** Whether the button should be disabled */
|
|
1254
|
+
get isDisabled() {
|
|
1255
|
+
return this.isLoading || this.isCheckingStatus || this.isStatusError;
|
|
1256
|
+
}
|
|
1257
|
+
ngOnInit() {
|
|
679
1258
|
// Subscribe to viewMode to handle PiP return
|
|
680
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
681
|
-
// Reopen video call modal when returning from PiP
|
|
682
|
-
if (mode === ViewMode.FULLSCREEN &&
|
|
683
|
-
this.tasService.isCallActive() &&
|
|
684
|
-
!this.videoCallModalRef) {
|
|
685
|
-
const sessionId = this.tasService.sessionId;
|
|
686
|
-
const token = this.tasService.token;
|
|
687
|
-
if (sessionId && token) {
|
|
688
|
-
this.openVideoCallModal(true);
|
|
689
|
-
}
|
|
690
|
-
}
|
|
1259
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
691
1260
|
// When entering PiP, clear the videoCallModalRef since modal will close
|
|
692
1261
|
if (mode === ViewMode.PIP) {
|
|
693
1262
|
this.videoCallModalRef = null;
|
|
694
1263
|
}
|
|
695
1264
|
}));
|
|
1265
|
+
// Start status checking
|
|
1266
|
+
this.startStatusPolling();
|
|
696
1267
|
}
|
|
697
1268
|
ngOnDestroy() {
|
|
698
1269
|
this.subscriptions.unsubscribe();
|
|
1270
|
+
this.stopStatusPolling();
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Start polling status every 30 seconds
|
|
1274
|
+
*/
|
|
1275
|
+
startStatusPolling() {
|
|
1276
|
+
// Initial status check
|
|
1277
|
+
this.checkStatus();
|
|
1278
|
+
// Set up periodic polling
|
|
1279
|
+
this.statusPollingInterval = setInterval(() => {
|
|
1280
|
+
this.checkStatus();
|
|
1281
|
+
}, this.STATUS_POLL_INTERVAL_MS);
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Stop status polling
|
|
1285
|
+
*/
|
|
1286
|
+
stopStatusPolling() {
|
|
1287
|
+
if (this.statusPollingInterval) {
|
|
1288
|
+
clearInterval(this.statusPollingInterval);
|
|
1289
|
+
this.statusPollingInterval = null;
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Check status endpoint to determine if button should be enabled
|
|
1294
|
+
*/
|
|
1295
|
+
checkStatus() {
|
|
1296
|
+
// Skip if required inputs are not available
|
|
1297
|
+
if (!this.tenant || !this.entityId) {
|
|
1298
|
+
console.log('[TAS DEBUG] checkStatus skipped - missing required inputs');
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
this.isCheckingStatus = true;
|
|
1302
|
+
this.statusErrorMessage = '';
|
|
1303
|
+
console.log('[TAS DEBUG] checkStatus called with:', {
|
|
1304
|
+
roomType: this.roomType,
|
|
1305
|
+
entityId: this.entityId,
|
|
1306
|
+
tenant: this.tenant,
|
|
1307
|
+
businessRole: this.businessRole,
|
|
1308
|
+
});
|
|
1309
|
+
this.subscriptions.add(this.tasService.getProxyVideoStatus({
|
|
1310
|
+
roomType: this.roomType,
|
|
1311
|
+
entityId: this.entityId,
|
|
1312
|
+
tenant: this.tenant,
|
|
1313
|
+
businessRole: this.businessRole,
|
|
1314
|
+
}).subscribe({
|
|
1315
|
+
next: (response) => {
|
|
1316
|
+
// Check if response is actually an error (some HTTP adapters return errors in next)
|
|
1317
|
+
// Also check for undefined/null or missing content
|
|
1318
|
+
const isErrorResponse = !response ||
|
|
1319
|
+
!response.content ||
|
|
1320
|
+
response?.ok === false ||
|
|
1321
|
+
response?.status >= 400 ||
|
|
1322
|
+
response?.error ||
|
|
1323
|
+
response?.name === 'HttpErrorResponse';
|
|
1324
|
+
if (isErrorResponse) {
|
|
1325
|
+
console.error('[TAS DEBUG] Status check returned error in response:', response);
|
|
1326
|
+
this.isCheckingStatus = false;
|
|
1327
|
+
this.isStatusError = true;
|
|
1328
|
+
this.statusErrorMessage = response?.error?.message || response?.message || 'Error checking status';
|
|
1329
|
+
}
|
|
1330
|
+
else {
|
|
1331
|
+
console.log('[TAS DEBUG] Status check successful:', response);
|
|
1332
|
+
this.isCheckingStatus = false;
|
|
1333
|
+
this.isStatusError = false;
|
|
1334
|
+
this.statusErrorMessage = '';
|
|
1335
|
+
}
|
|
1336
|
+
},
|
|
1337
|
+
error: (err) => {
|
|
1338
|
+
console.error('[TAS DEBUG] Status check failed:', err);
|
|
1339
|
+
this.isCheckingStatus = false;
|
|
1340
|
+
this.isStatusError = true;
|
|
1341
|
+
this.statusErrorMessage = err?.error?.message || err?.message || 'Error checking status';
|
|
1342
|
+
},
|
|
1343
|
+
}));
|
|
699
1344
|
}
|
|
700
1345
|
onClick() {
|
|
701
|
-
|
|
702
|
-
|
|
1346
|
+
console.log('[TAS DEBUG] onClick called');
|
|
1347
|
+
console.log('[TAS DEBUG] Inputs:', {
|
|
1348
|
+
tenant: this.tenant,
|
|
1349
|
+
entityId: this.entityId,
|
|
1350
|
+
roomType: this.roomType,
|
|
1351
|
+
businessRole: this.businessRole,
|
|
1352
|
+
currentUser: this.currentUser,
|
|
1353
|
+
});
|
|
1354
|
+
if (!this.tenant || !this.currentUser?.name) {
|
|
1355
|
+
console.error('[TAS DEBUG] Tenant or current user not available');
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
if (!this.entityId) {
|
|
1359
|
+
console.error('[TAS DEBUG] entityId is required');
|
|
703
1360
|
return;
|
|
704
1361
|
}
|
|
1362
|
+
console.log('[TAS DEBUG] Validation passed, opening waiting room modal');
|
|
705
1363
|
this.openWaitingRoomModal();
|
|
706
1364
|
}
|
|
707
1365
|
openWaitingRoomModal() {
|
|
708
1366
|
this.currentModalRef = this.modalService.open(TasWaitingRoomComponent, {
|
|
709
|
-
size:
|
|
710
|
-
windowClass:
|
|
711
|
-
backdrop:
|
|
1367
|
+
size: 'lg',
|
|
1368
|
+
windowClass: 'tas-waiting-room-modal',
|
|
1369
|
+
backdrop: 'static',
|
|
712
1370
|
keyboard: false,
|
|
713
|
-
centered: true
|
|
1371
|
+
centered: true,
|
|
714
1372
|
});
|
|
715
1373
|
// Pass all necessary inputs to the waiting room component
|
|
716
|
-
this.currentModalRef.componentInstance.
|
|
717
|
-
this.currentModalRef.componentInstance.
|
|
718
|
-
this.currentModalRef.componentInstance.
|
|
1374
|
+
this.currentModalRef.componentInstance.roomType = this.roomType;
|
|
1375
|
+
this.currentModalRef.componentInstance.entityId = this.entityId;
|
|
1376
|
+
this.currentModalRef.componentInstance.tenant = this.tenant;
|
|
1377
|
+
this.currentModalRef.componentInstance.businessRole = this.businessRole;
|
|
719
1378
|
this.currentModalRef.componentInstance.currentUser = this.currentUser;
|
|
720
|
-
this.currentModalRef.
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
this.currentModalRef.result.then(() => { this.currentModalRef = null; }, () => { this.currentModalRef = null; });
|
|
1379
|
+
this.currentModalRef.result.then(() => {
|
|
1380
|
+
this.currentModalRef = null;
|
|
1381
|
+
}, () => {
|
|
1382
|
+
this.currentModalRef = null;
|
|
1383
|
+
});
|
|
726
1384
|
}
|
|
727
1385
|
openVideoCallModal(isReturningFromPip = false) {
|
|
728
1386
|
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
729
1387
|
size: 'xl',
|
|
730
1388
|
windowClass: 'tas-video-modal',
|
|
731
1389
|
backdrop: 'static',
|
|
732
|
-
keyboard: false
|
|
1390
|
+
keyboard: false,
|
|
733
1391
|
});
|
|
734
1392
|
this.videoCallModalRef.componentInstance.sessionId = this.tasService.sessionId;
|
|
735
1393
|
this.videoCallModalRef.componentInstance.token = this.tasService.token;
|
|
1394
|
+
this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
|
|
736
1395
|
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
737
|
-
this.videoCallModalRef.result.then(() => {
|
|
1396
|
+
this.videoCallModalRef.result.then(() => {
|
|
1397
|
+
this.videoCallModalRef = null;
|
|
1398
|
+
}, () => {
|
|
1399
|
+
this.videoCallModalRef = null;
|
|
1400
|
+
});
|
|
738
1401
|
}
|
|
739
1402
|
}
|
|
740
|
-
TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
741
|
-
TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
742
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1403
|
+
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 });
|
|
1404
|
+
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"] }] });
|
|
1405
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
|
|
743
1406
|
type: Component,
|
|
744
|
-
args: [{ selector:
|
|
745
|
-
}], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: {
|
|
746
|
-
type: Input
|
|
747
|
-
}], product: [{
|
|
748
|
-
type: Input
|
|
749
|
-
}], tenantId: [{
|
|
750
|
-
type: Input
|
|
751
|
-
}], currentUser: [{
|
|
752
|
-
type: Input
|
|
753
|
-
}], ownerUserIds: [{
|
|
1407
|
+
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"] }]
|
|
1408
|
+
}], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: { roomType: [{
|
|
754
1409
|
type: Input
|
|
755
|
-
}],
|
|
1410
|
+
}], entityId: [{
|
|
756
1411
|
type: Input
|
|
757
|
-
}],
|
|
1412
|
+
}], tenant: [{
|
|
758
1413
|
type: Input
|
|
759
|
-
}],
|
|
1414
|
+
}], businessRole: [{
|
|
760
1415
|
type: Input
|
|
761
|
-
}],
|
|
1416
|
+
}], currentUser: [{
|
|
762
1417
|
type: Input
|
|
763
1418
|
}] } });
|
|
764
1419
|
|
|
765
1420
|
class TasFloatingCallComponent {
|
|
766
|
-
constructor(tasService) {
|
|
1421
|
+
constructor(tasService, modalService) {
|
|
767
1422
|
this.tasService = tasService;
|
|
1423
|
+
this.modalService = modalService;
|
|
768
1424
|
this.isVisible = false;
|
|
769
1425
|
this.isMuted = false;
|
|
770
1426
|
this.subscriptions = new Subscription();
|
|
1427
|
+
this.videoCallModalRef = null;
|
|
771
1428
|
// Margin from screen edges (in pixels)
|
|
772
1429
|
this.PIP_MARGIN = 20;
|
|
773
1430
|
}
|
|
@@ -780,6 +1437,7 @@ class TasFloatingCallComponent {
|
|
|
780
1437
|
}
|
|
781
1438
|
// Public Methods
|
|
782
1439
|
onExpand() {
|
|
1440
|
+
this.openVideoCallModal(true);
|
|
783
1441
|
this.tasService.exitPipMode();
|
|
784
1442
|
}
|
|
785
1443
|
onHangUp() {
|
|
@@ -791,23 +1449,45 @@ class TasFloatingCallComponent {
|
|
|
791
1449
|
// Private Methods
|
|
792
1450
|
setupSubscriptions() {
|
|
793
1451
|
// Call state subscription
|
|
794
|
-
this.subscriptions.add(this.tasService.callState$.subscribe(state => {
|
|
1452
|
+
this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
|
|
795
1453
|
if (state === CallState.DISCONNECTED) {
|
|
796
1454
|
this.isVisible = false;
|
|
797
1455
|
}
|
|
798
1456
|
}));
|
|
799
1457
|
// View mode subscription
|
|
800
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
1458
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
801
1459
|
this.isVisible = mode === ViewMode.PIP && this.tasService.isCallActive();
|
|
802
1460
|
if (this.isVisible) {
|
|
803
1461
|
setTimeout(() => this.initInteract(), 100);
|
|
1462
|
+
// Clear modal ref if we enter PiP mode (modal closes itself)
|
|
1463
|
+
this.videoCallModalRef = null;
|
|
804
1464
|
}
|
|
805
1465
|
}));
|
|
806
1466
|
// Mute state subscription
|
|
807
|
-
this.subscriptions.add(this.tasService.isMuted$.subscribe(muted => {
|
|
1467
|
+
this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
|
|
808
1468
|
this.isMuted = muted;
|
|
809
1469
|
}));
|
|
810
1470
|
}
|
|
1471
|
+
openVideoCallModal(isReturningFromPip = false) {
|
|
1472
|
+
if (this.videoCallModalRef) {
|
|
1473
|
+
return;
|
|
1474
|
+
}
|
|
1475
|
+
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
1476
|
+
size: 'xl',
|
|
1477
|
+
windowClass: 'tas-video-modal',
|
|
1478
|
+
backdrop: 'static',
|
|
1479
|
+
keyboard: false,
|
|
1480
|
+
});
|
|
1481
|
+
this.videoCallModalRef.componentInstance.sessionId = this.tasService.sessionId;
|
|
1482
|
+
this.videoCallModalRef.componentInstance.token = this.tasService.token;
|
|
1483
|
+
this.videoCallModalRef.componentInstance.businessRole = this.tasService.businessRole;
|
|
1484
|
+
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
1485
|
+
this.videoCallModalRef.result.then(() => {
|
|
1486
|
+
this.videoCallModalRef = null;
|
|
1487
|
+
}, () => {
|
|
1488
|
+
this.videoCallModalRef = null;
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
811
1491
|
initInteract() {
|
|
812
1492
|
interact('.tas-floating-container').unset();
|
|
813
1493
|
// Create restriction area with margin
|
|
@@ -818,17 +1498,15 @@ class TasFloatingCallComponent {
|
|
|
818
1498
|
left: margin,
|
|
819
1499
|
top: margin,
|
|
820
1500
|
right: window.innerWidth - margin,
|
|
821
|
-
bottom: window.innerHeight - margin
|
|
1501
|
+
bottom: window.innerHeight - margin,
|
|
822
1502
|
};
|
|
823
1503
|
},
|
|
824
|
-
elementRect: { left: 0, right: 1, top: 0, bottom: 1 }
|
|
1504
|
+
elementRect: { left: 0, right: 1, top: 0, bottom: 1 },
|
|
825
1505
|
};
|
|
826
1506
|
interact('.tas-floating-container')
|
|
827
1507
|
.draggable({
|
|
828
1508
|
inertia: true,
|
|
829
|
-
modifiers: [
|
|
830
|
-
interact.modifiers.restrict(restrictToBodyWithMargin)
|
|
831
|
-
],
|
|
1509
|
+
modifiers: [interact.modifiers.restrict(restrictToBodyWithMargin)],
|
|
832
1510
|
autoScroll: false,
|
|
833
1511
|
listeners: {
|
|
834
1512
|
move: (event) => {
|
|
@@ -838,8 +1516,8 @@ class TasFloatingCallComponent {
|
|
|
838
1516
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
839
1517
|
target.setAttribute('data-x', String(x));
|
|
840
1518
|
target.setAttribute('data-y', String(y));
|
|
841
|
-
}
|
|
842
|
-
}
|
|
1519
|
+
},
|
|
1520
|
+
},
|
|
843
1521
|
})
|
|
844
1522
|
.resizable({
|
|
845
1523
|
edges: { left: false, right: true, bottom: true, top: false },
|
|
@@ -857,7 +1535,7 @@ class TasFloatingCallComponent {
|
|
|
857
1535
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
858
1536
|
target.setAttribute('data-x', String(x));
|
|
859
1537
|
target.setAttribute('data-y', String(y));
|
|
860
|
-
}
|
|
1538
|
+
},
|
|
861
1539
|
},
|
|
862
1540
|
modifiers: [
|
|
863
1541
|
interact.modifiers.restrictEdges({
|
|
@@ -865,25 +1543,25 @@ class TasFloatingCallComponent {
|
|
|
865
1543
|
left: margin,
|
|
866
1544
|
top: margin,
|
|
867
1545
|
right: window.innerWidth - margin,
|
|
868
|
-
bottom: window.innerHeight - margin
|
|
869
|
-
}
|
|
1546
|
+
bottom: window.innerHeight - margin,
|
|
1547
|
+
},
|
|
870
1548
|
}),
|
|
871
1549
|
interact.modifiers.restrictSize({
|
|
872
1550
|
min: { width: 200, height: 130 },
|
|
873
|
-
max: { width: 500, height: 350 }
|
|
1551
|
+
max: { width: 500, height: 350 },
|
|
874
1552
|
}),
|
|
875
|
-
interact.modifiers.aspectRatio({ ratio: 'preserve' })
|
|
1553
|
+
interact.modifiers.aspectRatio({ ratio: 'preserve' }),
|
|
876
1554
|
],
|
|
877
|
-
inertia: true
|
|
1555
|
+
inertia: true,
|
|
878
1556
|
});
|
|
879
1557
|
}
|
|
880
1558
|
}
|
|
881
|
-
TasFloatingCallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
882
|
-
TasFloatingCallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
883
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1559
|
+
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 });
|
|
1560
|
+
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"] });
|
|
1561
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasFloatingCallComponent, decorators: [{
|
|
884
1562
|
type: Component,
|
|
885
|
-
args: [{ selector: 'tas-floating-call', template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n
|
|
886
|
-
}], ctorParameters: function () { return [{ type: TasService }]; } });
|
|
1563
|
+
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"] }]
|
|
1564
|
+
}], ctorParameters: function () { return [{ type: TasService }, { type: i1.NgbModal }]; } });
|
|
887
1565
|
|
|
888
1566
|
class TasUellSdkModule {
|
|
889
1567
|
/**
|
|
@@ -921,40 +1599,40 @@ class TasUellSdkModule {
|
|
|
921
1599
|
providers: [
|
|
922
1600
|
{ provide: TAS_CONFIG, useValue: options.config },
|
|
923
1601
|
{ provide: TAS_HTTP_CLIENT, useClass: options.httpClient },
|
|
924
|
-
TasService
|
|
925
|
-
]
|
|
1602
|
+
TasService,
|
|
1603
|
+
],
|
|
926
1604
|
};
|
|
927
1605
|
}
|
|
928
1606
|
}
|
|
929
|
-
TasUellSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
930
|
-
TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.
|
|
1607
|
+
TasUellSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1608
|
+
TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, declarations: [TasButtonComponent,
|
|
931
1609
|
TasVideocallComponent,
|
|
932
1610
|
TasFloatingCallComponent,
|
|
933
|
-
TasWaitingRoomComponent
|
|
1611
|
+
TasWaitingRoomComponent,
|
|
1612
|
+
TasAvatarComponent], imports: [CommonModule, FormsModule], exports: [TasButtonComponent,
|
|
934
1613
|
TasVideocallComponent,
|
|
935
1614
|
TasFloatingCallComponent,
|
|
936
|
-
TasWaitingRoomComponent
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasUellSdkModule, decorators: [{
|
|
1615
|
+
TasWaitingRoomComponent,
|
|
1616
|
+
TasAvatarComponent] });
|
|
1617
|
+
TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule]] });
|
|
1618
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, decorators: [{
|
|
941
1619
|
type: NgModule,
|
|
942
1620
|
args: [{
|
|
943
1621
|
declarations: [
|
|
944
1622
|
TasButtonComponent,
|
|
945
1623
|
TasVideocallComponent,
|
|
946
1624
|
TasFloatingCallComponent,
|
|
947
|
-
TasWaitingRoomComponent
|
|
948
|
-
|
|
949
|
-
imports: [
|
|
950
|
-
CommonModule,
|
|
1625
|
+
TasWaitingRoomComponent,
|
|
1626
|
+
TasAvatarComponent,
|
|
951
1627
|
],
|
|
1628
|
+
imports: [CommonModule, FormsModule],
|
|
952
1629
|
exports: [
|
|
953
1630
|
TasButtonComponent,
|
|
954
1631
|
TasVideocallComponent,
|
|
955
1632
|
TasFloatingCallComponent,
|
|
956
|
-
TasWaitingRoomComponent
|
|
957
|
-
|
|
1633
|
+
TasWaitingRoomComponent,
|
|
1634
|
+
TasAvatarComponent,
|
|
1635
|
+
],
|
|
958
1636
|
}]
|
|
959
1637
|
}] });
|
|
960
1638
|
|
|
@@ -966,5 +1644,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
966
1644
|
* Generated bundle index. Do not edit.
|
|
967
1645
|
*/
|
|
968
1646
|
|
|
969
|
-
export { CallState, TAS_CONFIG, TAS_HTTP_CLIENT, TasButtonComponent, TasFloatingCallComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, ViewMode, WaitingRoomState };
|
|
1647
|
+
export { CallState, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
|
|
970
1648
|
//# sourceMappingURL=tas-uell-sdk.mjs.map
|