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