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