tas-uell-sdk 0.0.5 → 0.1.0
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 +161 -51
- package/esm2020/lib/components/tas-avatar/tas-avatar.component.mjs +75 -0
- package/esm2020/lib/components/tas-btn/tas-btn.component.mjs +146 -61
- package/esm2020/lib/components/tas-floating-call/tas-floating-call.component.mjs +48 -23
- package/esm2020/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.mjs +109 -0
- package/esm2020/lib/components/tas-videocall/tas-videocall.component.mjs +217 -20
- package/esm2020/lib/components/tas-waiting-room/tas-waiting-room.component.mjs +226 -160
- package/esm2020/lib/config/tas.config.mjs +1 -1
- package/esm2020/lib/interfaces/tas.interfaces.mjs +45 -2
- package/esm2020/lib/services/geolocation.service.mjs +56 -0
- package/esm2020/lib/services/tas.service.mjs +400 -34
- package/esm2020/lib/tas-uell-sdk.module.mjs +25 -21
- package/esm2020/public-api.mjs +4 -1
- package/fesm2015/tas-uell-sdk.mjs +1323 -302
- package/fesm2015/tas-uell-sdk.mjs.map +1 -1
- package/fesm2020/tas-uell-sdk.mjs +1307 -300
- 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 +35 -15
- package/lib/components/tas-floating-call/tas-floating-call.component.d.ts +5 -1
- package/lib/components/tas-incoming-appointment/tas-incoming-appointment.component.d.ts +33 -0
- package/lib/components/tas-videocall/tas-videocall.component.d.ts +49 -3
- package/lib/components/tas-waiting-room/tas-waiting-room.component.d.ts +50 -35
- package/lib/config/tas.config.d.ts +7 -0
- package/lib/interfaces/tas.interfaces.d.ts +127 -35
- package/lib/services/geolocation.service.d.ts +24 -0
- package/lib/services/tas.service.d.ts +98 -9
- package/lib/tas-uell-sdk.module.d.ts +6 -3
- package/package.json +1 -1
- package/public-api.d.ts +3 -0
- package/src/lib/styles/tas-global.scss +27 -28
- package/INSTALL_AND_TEST.md +0 -427
|
@@ -1,13 +1,14 @@
|
|
|
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, EventEmitter, Output, 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
|
+
import { __awaiter } from 'tslib';
|
|
6
7
|
import interact from 'interactjs';
|
|
7
8
|
import * as i1 from '@ng-bootstrap/ng-bootstrap';
|
|
8
|
-
import
|
|
9
|
+
import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap';
|
|
10
|
+
import * as i4 from '@angular/common';
|
|
9
11
|
import { CommonModule } from '@angular/common';
|
|
10
|
-
import * as i4 from '@angular/forms';
|
|
11
12
|
import { FormsModule } from '@angular/forms';
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -19,11 +20,13 @@ const TAS_CONFIG = new InjectionToken('TAS_CONFIG');
|
|
|
19
20
|
*/
|
|
20
21
|
const TAS_HTTP_CLIENT = new InjectionToken('TAS_HTTP_CLIENT');
|
|
21
22
|
|
|
23
|
+
// Enums
|
|
22
24
|
var TasRoomType;
|
|
23
25
|
(function (TasRoomType) {
|
|
24
26
|
TasRoomType["TAS"] = "TAS";
|
|
25
27
|
TasRoomType["JM"] = "JM";
|
|
26
28
|
TasRoomType["WEBINAR"] = "WEBINAR";
|
|
29
|
+
TasRoomType["WELLNESS_MANAGER"] = "WELLNESS_MANAGER";
|
|
27
30
|
})(TasRoomType || (TasRoomType = {}));
|
|
28
31
|
var TasSessionType;
|
|
29
32
|
(function (TasSessionType) {
|
|
@@ -36,7 +39,42 @@ var TasUserRole;
|
|
|
36
39
|
TasUserRole["USER"] = "USER";
|
|
37
40
|
TasUserRole["MODERATOR"] = "MODERATOR";
|
|
38
41
|
})(TasUserRole || (TasUserRole = {}));
|
|
39
|
-
|
|
42
|
+
var TasBusinessRole;
|
|
43
|
+
(function (TasBusinessRole) {
|
|
44
|
+
TasBusinessRole["ADMIN_MANAGER"] = "ADMIN_MANAGER";
|
|
45
|
+
TasBusinessRole["MANAGER"] = "MANAGER";
|
|
46
|
+
TasBusinessRole["BACKOFFICE"] = "BACKOFFICE";
|
|
47
|
+
TasBusinessRole["USER"] = "USER";
|
|
48
|
+
})(TasBusinessRole || (TasBusinessRole = {}));
|
|
49
|
+
var VideoSessionStatus;
|
|
50
|
+
(function (VideoSessionStatus) {
|
|
51
|
+
VideoSessionStatus["ACTIVE"] = "ACTIVE";
|
|
52
|
+
VideoSessionStatus["INACTIVE"] = "INACTIVE";
|
|
53
|
+
VideoSessionStatus["PENDING"] = "PENDING";
|
|
54
|
+
VideoSessionStatus["SCHEDULED"] = "SCHEDULED";
|
|
55
|
+
})(VideoSessionStatus || (VideoSessionStatus = {}));
|
|
56
|
+
var UserStatus;
|
|
57
|
+
(function (UserStatus) {
|
|
58
|
+
UserStatus["ACTIVE"] = "ACTIVE";
|
|
59
|
+
UserStatus["INACTIVE"] = "INACTIVE";
|
|
60
|
+
UserStatus["ASSIGNED"] = "ASSIGNED";
|
|
61
|
+
})(UserStatus || (UserStatus = {}));
|
|
62
|
+
var UserCallAction;
|
|
63
|
+
(function (UserCallAction) {
|
|
64
|
+
UserCallAction["WAITING_ROOM_ENTER"] = "WAITING_ROOM_ENTER";
|
|
65
|
+
UserCallAction["WAITING_ROOM_LEAVE"] = "WAITING_ROOM_LEAVE";
|
|
66
|
+
UserCallAction["BAN"] = "BAN";
|
|
67
|
+
UserCallAction["CHANGE_STATUS"] = "CHANGE_STATUS";
|
|
68
|
+
UserCallAction["REQUEST_GEOLOCALIZATION"] = "REQUEST_GEOLOCALIZATION";
|
|
69
|
+
UserCallAction["ACTIVATE_GEOLOCATION"] = "ACTIVATE_GEOLOCATION";
|
|
70
|
+
})(UserCallAction || (UserCallAction = {}));
|
|
71
|
+
var RoomUserStatus;
|
|
72
|
+
(function (RoomUserStatus) {
|
|
73
|
+
RoomUserStatus["ASSIGNED"] = "ASSIGNED";
|
|
74
|
+
RoomUserStatus["WAITING"] = "WAITING";
|
|
75
|
+
RoomUserStatus["JOINED"] = "JOINED";
|
|
76
|
+
RoomUserStatus["FINISHED"] = "FINISHED";
|
|
77
|
+
})(RoomUserStatus || (RoomUserStatus = {}));
|
|
40
78
|
var CallState;
|
|
41
79
|
(function (CallState) {
|
|
42
80
|
CallState["IDLE"] = "IDLE";
|
|
@@ -50,6 +88,12 @@ var ViewMode;
|
|
|
50
88
|
ViewMode["FULLSCREEN"] = "FULLSCREEN";
|
|
51
89
|
ViewMode["PIP"] = "PIP";
|
|
52
90
|
})(ViewMode || (ViewMode = {}));
|
|
91
|
+
// Appointment types
|
|
92
|
+
var AppointmentStatus;
|
|
93
|
+
(function (AppointmentStatus) {
|
|
94
|
+
AppointmentStatus["CONFIRMED"] = "CONFIRMED";
|
|
95
|
+
AppointmentStatus["CANCELLED"] = "CANCELLED";
|
|
96
|
+
})(AppointmentStatus || (AppointmentStatus = {}));
|
|
53
97
|
|
|
54
98
|
class TasService {
|
|
55
99
|
constructor(httpClient, config) {
|
|
@@ -67,6 +111,86 @@ class TasService {
|
|
|
67
111
|
// Session info for PiP mode restoration
|
|
68
112
|
this.currentSessionId = null;
|
|
69
113
|
this.currentToken = null;
|
|
114
|
+
this.STORAGE_KEY = 'tas_session_state';
|
|
115
|
+
this.DISCONNECTED_FLAG_KEY = 'tas_session_disconnected';
|
|
116
|
+
this.isFinishingSession = false;
|
|
117
|
+
// Proxy-video circuit state
|
|
118
|
+
this.proxyVideoSessionId = null;
|
|
119
|
+
this.proxyVideoToken = null;
|
|
120
|
+
this.currentBusinessRole = TasBusinessRole.USER;
|
|
121
|
+
// Waiting room and status polling
|
|
122
|
+
this.waitingRoomUsersSubject = new BehaviorSubject([]);
|
|
123
|
+
this.waitingRoomUsers$ = this.waitingRoomUsersSubject.asObservable();
|
|
124
|
+
this.ownerHasJoinedSubject = new BehaviorSubject(false);
|
|
125
|
+
this.ownerHasJoined$ = this.ownerHasJoinedSubject.asObservable();
|
|
126
|
+
// Observable that emits true when owner was present but then left
|
|
127
|
+
this.ownerHasLeftSubject = new BehaviorSubject(false);
|
|
128
|
+
this.ownerHasLeft$ = this.ownerHasLeftSubject.asObservable();
|
|
129
|
+
this.joinableSubject = new BehaviorSubject(false);
|
|
130
|
+
this.joinable$ = this.joinableSubject.asObservable();
|
|
131
|
+
// Observable for when backend requests geolocation activation
|
|
132
|
+
this.activateGeoSubject = new BehaviorSubject(false);
|
|
133
|
+
this.activateGeo$ = this.activateGeoSubject.asObservable();
|
|
134
|
+
// Observable for when geo request is active (owner waiting for user response)
|
|
135
|
+
this.geoRequestActiveSubject = new BehaviorSubject(false);
|
|
136
|
+
this.geoRequestActive$ = this.geoRequestActiveSubject.asObservable();
|
|
137
|
+
// Observable for when all geo has been granted
|
|
138
|
+
this.allGeoGrantedSubject = new BehaviorSubject(false);
|
|
139
|
+
this.allGeoGranted$ = this.allGeoGrantedSubject.asObservable();
|
|
140
|
+
this.statusPollingInterval = null;
|
|
141
|
+
this.DEFAULT_POLL_INTERVAL_MS = 30000; // Default 30s
|
|
142
|
+
this.wasOwnerPresent = false;
|
|
143
|
+
// Current call context
|
|
144
|
+
this.currentAppointmentId = null;
|
|
145
|
+
this.currentVideoCallId = null;
|
|
146
|
+
this.currentTenant = null;
|
|
147
|
+
}
|
|
148
|
+
// ... (Getters and other methods remain unchanged)
|
|
149
|
+
/**
|
|
150
|
+
* Start automatic status polling for the current session.
|
|
151
|
+
* Status is polled every intervalMs (default 30s).
|
|
152
|
+
*/
|
|
153
|
+
startStatusPolling(params, intervalMs = this.DEFAULT_POLL_INTERVAL_MS) {
|
|
154
|
+
this.stopStatusPolling(); // Clear any existing polling
|
|
155
|
+
// Store context for polling
|
|
156
|
+
if (params.sessionId) {
|
|
157
|
+
this.currentSessionId = params.sessionId;
|
|
158
|
+
}
|
|
159
|
+
if (params.appointmentId) {
|
|
160
|
+
this.currentAppointmentId = params.appointmentId;
|
|
161
|
+
}
|
|
162
|
+
if (params.tenant) {
|
|
163
|
+
this.currentTenant = params.tenant;
|
|
164
|
+
}
|
|
165
|
+
console.log(`[TAS DEBUG] Starting status polling with interval ${intervalMs}ms`);
|
|
166
|
+
// Initial status fetch
|
|
167
|
+
this.fetchAndProcessStatus(params);
|
|
168
|
+
// Set up periodic polling
|
|
169
|
+
this.statusPollingInterval = setInterval(() => {
|
|
170
|
+
this.fetchAndProcessStatus(params);
|
|
171
|
+
}, intervalMs);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Stop automatic status polling.
|
|
175
|
+
*/
|
|
176
|
+
stopStatusPolling() {
|
|
177
|
+
if (this.statusPollingInterval) {
|
|
178
|
+
clearInterval(this.statusPollingInterval);
|
|
179
|
+
this.statusPollingInterval = null;
|
|
180
|
+
}
|
|
181
|
+
// Do NOT reset waiting room state here, as other components might observe it
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Reset polling state (waiting room, owner status, etc)
|
|
185
|
+
* Call this when completely exiting the flow
|
|
186
|
+
*/
|
|
187
|
+
resetPollingState() {
|
|
188
|
+
this.stopStatusPolling();
|
|
189
|
+
this.waitingRoomUsersSubject.next([]);
|
|
190
|
+
this.ownerHasJoinedSubject.next(false);
|
|
191
|
+
this.ownerHasLeftSubject.next(false);
|
|
192
|
+
this.joinableSubject.next(false);
|
|
193
|
+
this.wasOwnerPresent = false;
|
|
70
194
|
}
|
|
71
195
|
// Getters
|
|
72
196
|
get currentSession() {
|
|
@@ -84,18 +208,36 @@ class TasService {
|
|
|
84
208
|
get token() {
|
|
85
209
|
return this.currentToken;
|
|
86
210
|
}
|
|
211
|
+
get proxySessionId() {
|
|
212
|
+
return this.proxyVideoSessionId;
|
|
213
|
+
}
|
|
214
|
+
get proxyToken() {
|
|
215
|
+
return this.proxyVideoToken;
|
|
216
|
+
}
|
|
217
|
+
get businessRole() {
|
|
218
|
+
return this.currentBusinessRole;
|
|
219
|
+
}
|
|
87
220
|
get isMuted() {
|
|
88
221
|
return this.isMutedSubject.getValue();
|
|
89
222
|
}
|
|
90
223
|
// View Mode Methods
|
|
91
224
|
setViewMode(mode) {
|
|
92
225
|
this.viewModeSubject.next(mode);
|
|
226
|
+
if (this.currentSessionId && this.currentToken) {
|
|
227
|
+
this.saveSessionState(this.currentSessionId, this.currentToken, mode, this.currentBusinessRole);
|
|
228
|
+
}
|
|
93
229
|
}
|
|
94
230
|
enterPipMode() {
|
|
95
231
|
this.viewModeSubject.next(ViewMode.PIP);
|
|
232
|
+
if (this.currentSessionId && this.currentToken) {
|
|
233
|
+
this.saveSessionState(this.currentSessionId, this.currentToken, ViewMode.PIP, this.currentBusinessRole);
|
|
234
|
+
}
|
|
96
235
|
}
|
|
97
236
|
exitPipMode() {
|
|
98
237
|
this.viewModeSubject.next(ViewMode.FULLSCREEN);
|
|
238
|
+
if (this.currentSessionId && this.currentToken) {
|
|
239
|
+
this.saveSessionState(this.currentSessionId, this.currentToken, ViewMode.FULLSCREEN, this.currentBusinessRole);
|
|
240
|
+
}
|
|
99
241
|
}
|
|
100
242
|
isPipMode() {
|
|
101
243
|
return this.viewModeSubject.getValue() === ViewMode.PIP;
|
|
@@ -123,7 +265,17 @@ class TasService {
|
|
|
123
265
|
}
|
|
124
266
|
}
|
|
125
267
|
// Session Management
|
|
126
|
-
disconnectSession() {
|
|
268
|
+
disconnectSession(clearStorage = true) {
|
|
269
|
+
console.log('[TAS DEBUG] TasService.disconnectSession called. clearStorage:', clearStorage);
|
|
270
|
+
// Call finishSession before disconnecting if we have a sessionId
|
|
271
|
+
const sessionIdToFinish = this.currentSessionId;
|
|
272
|
+
// Clear storage FIRST to prevent any race conditions where state might be saved after disconnect
|
|
273
|
+
if (clearStorage) {
|
|
274
|
+
this.clearSessionState();
|
|
275
|
+
// Set a flag to indicate this session was intentionally disconnected
|
|
276
|
+
// This prevents reconnection on page reload
|
|
277
|
+
sessionStorage.setItem(this.DISCONNECTED_FLAG_KEY, 'true');
|
|
278
|
+
}
|
|
127
279
|
if (this.session) {
|
|
128
280
|
this.session.disconnect();
|
|
129
281
|
this.session = null;
|
|
@@ -132,50 +284,289 @@ class TasService {
|
|
|
132
284
|
this.subscribers = [];
|
|
133
285
|
this.currentSessionId = null;
|
|
134
286
|
this.currentToken = null;
|
|
287
|
+
this.proxyVideoSessionId = null;
|
|
288
|
+
this.proxyVideoToken = null;
|
|
135
289
|
this.isMutedSubject.next(false); // Reset mute state
|
|
136
290
|
this.viewModeSubject.next(ViewMode.FULLSCREEN);
|
|
137
291
|
this.callStateSubject.next(CallState.DISCONNECTED);
|
|
292
|
+
// Call proxy finish after disconnecting (only if not already finishing)
|
|
293
|
+
if (sessionIdToFinish && !this.isFinishingSession) {
|
|
294
|
+
this.isFinishingSession = true;
|
|
295
|
+
this.finishProxyVideoSession({
|
|
296
|
+
sessionId: sessionIdToFinish,
|
|
297
|
+
businessRole: this.currentBusinessRole,
|
|
298
|
+
}).subscribe({
|
|
299
|
+
next: (response) => {
|
|
300
|
+
console.log('[TAS DEBUG] Session finished successfully:', response);
|
|
301
|
+
this.isFinishingSession = false;
|
|
302
|
+
},
|
|
303
|
+
error: (error) => {
|
|
304
|
+
console.error('[TAS DEBUG] Error finishing session:', error);
|
|
305
|
+
this.isFinishingSession = false;
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
}
|
|
138
309
|
}
|
|
139
310
|
isCallActive() {
|
|
140
311
|
return this.callStateSubject.getValue() === CallState.CONNECTED;
|
|
141
312
|
}
|
|
313
|
+
// State Persistence
|
|
314
|
+
saveSessionState(sessionId, token, viewMode, businessRole = TasBusinessRole.USER) {
|
|
315
|
+
const state = {
|
|
316
|
+
sessionId,
|
|
317
|
+
token,
|
|
318
|
+
viewMode,
|
|
319
|
+
businessRole,
|
|
320
|
+
};
|
|
321
|
+
sessionStorage.setItem(this.STORAGE_KEY, JSON.stringify(state));
|
|
322
|
+
}
|
|
323
|
+
clearSessionState() {
|
|
324
|
+
sessionStorage.removeItem(this.STORAGE_KEY);
|
|
325
|
+
// Also clear the disconnected flag when clearing state
|
|
326
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
327
|
+
}
|
|
328
|
+
canRestoreSession() {
|
|
329
|
+
return !!sessionStorage.getItem(this.STORAGE_KEY);
|
|
330
|
+
}
|
|
331
|
+
restoreSession(containerId) {
|
|
332
|
+
const savedState = sessionStorage.getItem(this.STORAGE_KEY);
|
|
333
|
+
if (!savedState)
|
|
334
|
+
return;
|
|
335
|
+
// Check if this session was intentionally disconnected
|
|
336
|
+
// If so, don't restore it on page reload
|
|
337
|
+
const wasDisconnected = sessionStorage.getItem(this.DISCONNECTED_FLAG_KEY);
|
|
338
|
+
if (wasDisconnected === 'true') {
|
|
339
|
+
console.log('[TAS DEBUG] Session was intentionally disconnected, skipping restore');
|
|
340
|
+
this.clearSessionState();
|
|
341
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
// Don't restore if we're already disconnected or if there's no active session
|
|
345
|
+
// This prevents restoring sessions that were properly disconnected
|
|
346
|
+
if (this.callStateSubject.getValue() === CallState.DISCONNECTED) {
|
|
347
|
+
console.log('[TAS DEBUG] Call state is DISCONNECTED, skipping restore');
|
|
348
|
+
this.clearSessionState();
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
try {
|
|
352
|
+
const state = JSON.parse(savedState);
|
|
353
|
+
if (state.sessionId && state.token) {
|
|
354
|
+
console.log('[TAS DEBUG] Restoring session from storage');
|
|
355
|
+
// Force PiP mode for restoration to ensure UI consistency
|
|
356
|
+
this.viewModeSubject.next(ViewMode.PIP);
|
|
357
|
+
if (state.businessRole) {
|
|
358
|
+
this.currentBusinessRole = state.businessRole;
|
|
359
|
+
}
|
|
360
|
+
this.connectSession(state.sessionId, state.token, containerId, // Use the same container for both since we are in PiP
|
|
361
|
+
containerId, this.currentBusinessRole)
|
|
362
|
+
.then(() => {
|
|
363
|
+
console.log('[TAS DEBUG] Session restored successfully');
|
|
364
|
+
// Clear the disconnected flag if restoration succeeds
|
|
365
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
366
|
+
})
|
|
367
|
+
.catch((err) => {
|
|
368
|
+
console.error('[TAS DEBUG] Failed to restore session:', err);
|
|
369
|
+
this.clearSessionState(); // Clear bad state
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
console.error('[TAS DEBUG] Error parsing saved session state', e);
|
|
375
|
+
this.clearSessionState();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
142
378
|
// API Methods
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
379
|
+
/**
|
|
380
|
+
* PROXY circuit token: /v2/proxy/video/start
|
|
381
|
+
*/
|
|
382
|
+
startProxyVideoSession(payload) {
|
|
383
|
+
return this.httpClient
|
|
384
|
+
.post('v2/proxy/video/start', {
|
|
385
|
+
body: payload,
|
|
386
|
+
headers: {},
|
|
387
|
+
})
|
|
388
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
389
|
+
console.error('TAS Service: startProxyVideoSession failed', error);
|
|
390
|
+
throw error;
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* PROXY circuit status: /v2/proxy/video/status
|
|
395
|
+
*/
|
|
396
|
+
getProxyVideoStatus(payload) {
|
|
397
|
+
return this.httpClient
|
|
398
|
+
.post('v2/proxy/video/status', {
|
|
399
|
+
body: payload,
|
|
400
|
+
headers: {},
|
|
401
|
+
})
|
|
402
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
403
|
+
console.error('TAS Service: getProxyVideoStatus failed', error);
|
|
146
404
|
throw error;
|
|
147
405
|
}));
|
|
148
406
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
407
|
+
/**
|
|
408
|
+
* PROXY circuit user modification: /v2/proxy/video/user/modify
|
|
409
|
+
*/
|
|
410
|
+
modifyProxyVideoUser(payload) {
|
|
411
|
+
return this.httpClient
|
|
412
|
+
.patch('v2/proxy/video/user/modify', {
|
|
413
|
+
body: payload,
|
|
414
|
+
headers: {},
|
|
415
|
+
})
|
|
416
|
+
.pipe(catchError((error) => {
|
|
417
|
+
console.error('TAS Service: modifyProxyVideoUser failed', error);
|
|
418
|
+
throw error;
|
|
419
|
+
}));
|
|
420
|
+
}
|
|
421
|
+
finishProxyVideoSession(payload) {
|
|
422
|
+
return this.httpClient
|
|
423
|
+
.post('v2/proxy/video/finish', {
|
|
424
|
+
body: payload,
|
|
425
|
+
headers: {},
|
|
426
|
+
})
|
|
427
|
+
.pipe(map((response) => response), catchError((error) => {
|
|
428
|
+
console.error('TAS Service: finishProxyVideoSession failed', error);
|
|
429
|
+
throw error;
|
|
430
|
+
}));
|
|
431
|
+
}
|
|
432
|
+
/**
|
|
433
|
+
* Get user appointments within a date range.
|
|
434
|
+
* @param params Date range for querying appointments
|
|
435
|
+
* @returns Observable of appointment array
|
|
436
|
+
*/
|
|
437
|
+
getAppointments(params) {
|
|
438
|
+
return this.httpClient
|
|
439
|
+
.get(`v2/proxy/appointment/agendas/user/appointments?fromDate=${params.fromDate}&toDate=${params.toDate}`, { headers: {} })
|
|
440
|
+
.pipe(catchError((error) => {
|
|
441
|
+
console.error('TAS Service: getAppointments failed', error);
|
|
152
442
|
throw error;
|
|
153
443
|
}));
|
|
154
444
|
}
|
|
445
|
+
/**
|
|
446
|
+
* Start automatic status polling for the current session.
|
|
447
|
+
* Status is polled every STATUS_POLL_INTERVAL_MS milliseconds.
|
|
448
|
+
*/
|
|
449
|
+
/**
|
|
450
|
+
* Stop automatic status polling.
|
|
451
|
+
*/
|
|
452
|
+
/**
|
|
453
|
+
* Fetch status and process the response.
|
|
454
|
+
*/
|
|
455
|
+
fetchAndProcessStatus(params) {
|
|
456
|
+
this.getProxyVideoStatus(params).subscribe({
|
|
457
|
+
next: (response) => {
|
|
458
|
+
this.processStatusResponse(response);
|
|
459
|
+
},
|
|
460
|
+
error: (err) => {
|
|
461
|
+
console.error('[TAS DEBUG] Status polling error:', err);
|
|
462
|
+
},
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Process status response to update waiting room users and owner join status.
|
|
467
|
+
*/
|
|
468
|
+
processStatusResponse(response) {
|
|
469
|
+
const content = response.content;
|
|
470
|
+
// Update videoCallId if available
|
|
471
|
+
if (content.videoCallId) {
|
|
472
|
+
this.currentVideoCallId = content.videoCallId;
|
|
473
|
+
}
|
|
474
|
+
// Update sessionId if available
|
|
475
|
+
if (content.sessionId) {
|
|
476
|
+
this.currentSessionId = content.sessionId;
|
|
477
|
+
this.proxyVideoSessionId = content.sessionId;
|
|
478
|
+
}
|
|
479
|
+
// Update joinable status
|
|
480
|
+
this.joinableSubject.next(content.joinable);
|
|
481
|
+
// Update activateGeo status
|
|
482
|
+
if (content.activateGeo !== undefined) {
|
|
483
|
+
console.log('[TAS DEBUG] activateGeo received:', content.activateGeo);
|
|
484
|
+
this.activateGeoSubject.next(content.activateGeo);
|
|
485
|
+
}
|
|
486
|
+
// Update geoRequestActive status (owner waiting for user geo response)
|
|
487
|
+
if (content.geoRequestActive !== undefined) {
|
|
488
|
+
console.log('[TAS DEBUG] geoRequestActive received:', content.geoRequestActive);
|
|
489
|
+
this.geoRequestActiveSubject.next(content.geoRequestActive);
|
|
490
|
+
}
|
|
491
|
+
// Update allGeoGranted status (all users responded with geo)
|
|
492
|
+
if (content.allGeoGranted !== undefined) {
|
|
493
|
+
console.log('[TAS DEBUG] allGeoGranted received:', content.allGeoGranted);
|
|
494
|
+
this.allGeoGrantedSubject.next(content.allGeoGranted);
|
|
495
|
+
}
|
|
496
|
+
// Check if owner has joined
|
|
497
|
+
const ownerJoined = this.checkIfOwnerJoined(content.users);
|
|
498
|
+
this.ownerHasJoinedSubject.next(ownerJoined);
|
|
499
|
+
// Detect if owner left: was present, now not present
|
|
500
|
+
if (this.wasOwnerPresent && !ownerJoined) {
|
|
501
|
+
console.log('[TAS DEBUG] Owner has left the session');
|
|
502
|
+
this.ownerHasLeftSubject.next(true);
|
|
503
|
+
}
|
|
504
|
+
if (ownerJoined) {
|
|
505
|
+
this.wasOwnerPresent = true;
|
|
506
|
+
}
|
|
507
|
+
// Extract waiting room users (status === WAITING)
|
|
508
|
+
const waitingUsers = content.users
|
|
509
|
+
.filter((u) => u.status === 'WAITING')
|
|
510
|
+
.map((u) => ({
|
|
511
|
+
userId: u.userId,
|
|
512
|
+
name: `User ${u.userId}`,
|
|
513
|
+
status: RoomUserStatus.WAITING,
|
|
514
|
+
}));
|
|
515
|
+
this.waitingRoomUsersSubject.next(waitingUsers);
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* Check if at least one OWNER has joined the call.
|
|
519
|
+
*/
|
|
520
|
+
checkIfOwnerJoined(users) {
|
|
521
|
+
return users.some((u) => u.rol === TasUserRole.OWNER && u.status === 'JOINED');
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Admit a user from the waiting room by changing their status.
|
|
525
|
+
*/
|
|
526
|
+
admitUserFromWaitingRoom(userId, videoCallId) {
|
|
527
|
+
return this.modifyProxyVideoUser({
|
|
528
|
+
userId,
|
|
529
|
+
videoCallId,
|
|
530
|
+
action: UserCallAction.WAITING_ROOM_LEAVE,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
// Getters for current call context
|
|
534
|
+
get appointmentId() {
|
|
535
|
+
return this.currentAppointmentId;
|
|
536
|
+
}
|
|
537
|
+
get videoCallId() {
|
|
538
|
+
return this.currentVideoCallId;
|
|
539
|
+
}
|
|
155
540
|
/**
|
|
156
541
|
* Connects to a TokBox video session
|
|
157
542
|
*/
|
|
158
|
-
connectSession(sessionId, token, publisherElement, subscriberElement) {
|
|
543
|
+
connectSession(sessionId, token, publisherElement, subscriberElement, businessRole = TasBusinessRole.USER) {
|
|
159
544
|
this.callStateSubject.next(CallState.CONNECTING);
|
|
160
545
|
this.currentSessionId = sessionId;
|
|
161
546
|
this.currentToken = token;
|
|
547
|
+
this.currentBusinessRole = businessRole;
|
|
548
|
+
this.isFinishingSession = false; // Reset flag for new session
|
|
549
|
+
// Save initial state (defaulting to current view mode)
|
|
550
|
+
this.saveSessionState(sessionId, token, this.viewModeSubject.getValue(), businessRole);
|
|
551
|
+
// Clear the disconnected flag when starting a new connection
|
|
552
|
+
sessionStorage.removeItem(this.DISCONNECTED_FLAG_KEY);
|
|
162
553
|
return new Promise((resolve, reject) => {
|
|
163
554
|
if (!OT.checkSystemRequirements()) {
|
|
164
555
|
this.callStateSubject.next(CallState.ERROR);
|
|
165
|
-
reject(new Error(
|
|
556
|
+
reject(new Error('Browser not compatible with TokBox'));
|
|
166
557
|
return;
|
|
167
558
|
}
|
|
168
559
|
this.session = OT.initSession(this.config.tokBoxApiKey, sessionId);
|
|
169
560
|
// Handle new streams (remote participants joining)
|
|
170
|
-
this.session.on(
|
|
561
|
+
this.session.on('streamCreated', (event) => {
|
|
171
562
|
var _a;
|
|
172
563
|
const subscriber = (_a = this.session) === null || _a === void 0 ? void 0 : _a.subscribe(event.stream, subscriberElement, {
|
|
173
|
-
insertMode:
|
|
174
|
-
width:
|
|
175
|
-
height:
|
|
564
|
+
insertMode: 'append',
|
|
565
|
+
width: '100%',
|
|
566
|
+
height: '100%',
|
|
176
567
|
}, (error) => {
|
|
177
568
|
if (error) {
|
|
178
|
-
console.error(
|
|
569
|
+
console.error('Error subscribing to stream:', error);
|
|
179
570
|
}
|
|
180
571
|
});
|
|
181
572
|
if (subscriber) {
|
|
@@ -183,30 +574,49 @@ class TasService {
|
|
|
183
574
|
}
|
|
184
575
|
});
|
|
185
576
|
// Handle streams ending (remote participants leaving)
|
|
186
|
-
this.session.on(
|
|
577
|
+
this.session.on('streamDestroyed', (event) => {
|
|
187
578
|
this.subscribers = this.subscribers.filter((sub) => { var _a; return ((_a = sub.stream) === null || _a === void 0 ? void 0 : _a.streamId) !== event.stream.streamId; });
|
|
188
579
|
});
|
|
189
580
|
// Handle session disconnection
|
|
190
|
-
this.session.on(
|
|
581
|
+
this.session.on('sessionDisconnected', () => {
|
|
191
582
|
this.callStateSubject.next(CallState.DISCONNECTED);
|
|
583
|
+
// Call finishSession when session is disconnected (e.g., by server)
|
|
584
|
+
// Only call if not already finishing (prevents duplicate calls)
|
|
585
|
+
const sessionIdToFinish = this.currentSessionId;
|
|
586
|
+
if (sessionIdToFinish && !this.isFinishingSession) {
|
|
587
|
+
this.isFinishingSession = true;
|
|
588
|
+
this.finishProxyVideoSession({
|
|
589
|
+
sessionId: sessionIdToFinish,
|
|
590
|
+
businessRole: this.currentBusinessRole,
|
|
591
|
+
}).subscribe({
|
|
592
|
+
next: (response) => {
|
|
593
|
+
console.log('[TAS DEBUG] Session finished on disconnect event:', response);
|
|
594
|
+
this.isFinishingSession = false;
|
|
595
|
+
},
|
|
596
|
+
error: (error) => {
|
|
597
|
+
console.error('[TAS DEBUG] Error finishing session on disconnect:', error);
|
|
598
|
+
this.isFinishingSession = false;
|
|
599
|
+
},
|
|
600
|
+
});
|
|
601
|
+
}
|
|
192
602
|
});
|
|
193
603
|
// Connect to session
|
|
194
604
|
this.session.connect(token, (error) => {
|
|
195
605
|
if (error) {
|
|
196
|
-
console.error(
|
|
606
|
+
console.error('Error connecting to session:', error);
|
|
197
607
|
this.callStateSubject.next(CallState.ERROR);
|
|
198
608
|
reject(error);
|
|
199
609
|
return;
|
|
200
610
|
}
|
|
201
611
|
// Initialize publisher (local video)
|
|
202
612
|
this.publisher = OT.initPublisher(publisherElement, {
|
|
203
|
-
insertMode:
|
|
204
|
-
width:
|
|
205
|
-
height:
|
|
613
|
+
insertMode: 'append',
|
|
614
|
+
width: '100%',
|
|
615
|
+
height: '100%',
|
|
206
616
|
}, (err) => {
|
|
207
617
|
var _a;
|
|
208
618
|
if (err) {
|
|
209
|
-
console.error(
|
|
619
|
+
console.error('Error initializing publisher:', err);
|
|
210
620
|
this.callStateSubject.next(CallState.ERROR);
|
|
211
621
|
reject(err);
|
|
212
622
|
return;
|
|
@@ -214,7 +624,7 @@ class TasService {
|
|
|
214
624
|
// Publish to session
|
|
215
625
|
(_a = this.session) === null || _a === void 0 ? void 0 : _a.publish(this.publisher, (pubErr) => {
|
|
216
626
|
if (pubErr) {
|
|
217
|
-
console.error(
|
|
627
|
+
console.error('Error publishing stream:', pubErr);
|
|
218
628
|
this.callStateSubject.next(CallState.ERROR);
|
|
219
629
|
reject(pubErr);
|
|
220
630
|
}
|
|
@@ -275,12 +685,12 @@ class TasService {
|
|
|
275
685
|
this.movePublisherTo('publisher-container');
|
|
276
686
|
}
|
|
277
687
|
}
|
|
278
|
-
TasService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
279
|
-
TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.
|
|
280
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
688
|
+
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 });
|
|
689
|
+
TasService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, providedIn: 'root' });
|
|
690
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasService, decorators: [{
|
|
281
691
|
type: Injectable,
|
|
282
692
|
args: [{
|
|
283
|
-
providedIn:
|
|
693
|
+
providedIn: 'root',
|
|
284
694
|
}]
|
|
285
695
|
}], ctorParameters: function () {
|
|
286
696
|
return [{ type: undefined, decorators: [{
|
|
@@ -292,14 +702,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
292
702
|
}] }];
|
|
293
703
|
} });
|
|
294
704
|
|
|
705
|
+
class GeolocationService {
|
|
706
|
+
constructor() {
|
|
707
|
+
this.cachedPosition = null;
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Request current geolocation position using Web Geolocation API.
|
|
711
|
+
* Works in both browser and Capacitor environments.
|
|
712
|
+
* @returns Promise with {latitude, longitude} or null if denied/unavailable
|
|
713
|
+
*/
|
|
714
|
+
getCurrentPosition() {
|
|
715
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
716
|
+
if (!navigator.geolocation) {
|
|
717
|
+
console.warn('[GeolocationService] Geolocation not supported');
|
|
718
|
+
return null;
|
|
719
|
+
}
|
|
720
|
+
return new Promise((resolve) => {
|
|
721
|
+
navigator.geolocation.getCurrentPosition((position) => {
|
|
722
|
+
const geoPosition = {
|
|
723
|
+
latitude: position.coords.latitude,
|
|
724
|
+
longitude: position.coords.longitude,
|
|
725
|
+
};
|
|
726
|
+
this.cachedPosition = geoPosition;
|
|
727
|
+
resolve(geoPosition);
|
|
728
|
+
}, (error) => {
|
|
729
|
+
console.warn('[GeolocationService] Geolocation error:', error.message);
|
|
730
|
+
resolve(null);
|
|
731
|
+
}, {
|
|
732
|
+
enableHighAccuracy: false,
|
|
733
|
+
timeout: 30000,
|
|
734
|
+
maximumAge: 60000,
|
|
735
|
+
});
|
|
736
|
+
});
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Get the cached position from the last successful request.
|
|
741
|
+
*/
|
|
742
|
+
getCachedPosition() {
|
|
743
|
+
return this.cachedPosition;
|
|
744
|
+
}
|
|
745
|
+
/**
|
|
746
|
+
* Clear the cached position.
|
|
747
|
+
*/
|
|
748
|
+
clearCache() {
|
|
749
|
+
this.cachedPosition = null;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
GeolocationService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
753
|
+
GeolocationService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, providedIn: 'root' });
|
|
754
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: GeolocationService, decorators: [{
|
|
755
|
+
type: Injectable,
|
|
756
|
+
args: [{
|
|
757
|
+
providedIn: 'root',
|
|
758
|
+
}]
|
|
759
|
+
}] });
|
|
760
|
+
|
|
761
|
+
class TasAvatarComponent {
|
|
762
|
+
constructor() {
|
|
763
|
+
this.name = '';
|
|
764
|
+
this.size = 80;
|
|
765
|
+
}
|
|
766
|
+
get initials() {
|
|
767
|
+
if (!this.name)
|
|
768
|
+
return '';
|
|
769
|
+
return this.name
|
|
770
|
+
.split(' ')
|
|
771
|
+
.filter((n) => n.length > 0)
|
|
772
|
+
.map((n) => n[0])
|
|
773
|
+
.join('')
|
|
774
|
+
.toUpperCase()
|
|
775
|
+
.substring(0, 2);
|
|
776
|
+
}
|
|
777
|
+
get fontSize() {
|
|
778
|
+
return Math.round(this.size * 0.4);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
TasAvatarComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasAvatarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
782
|
+
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: `
|
|
783
|
+
<div
|
|
784
|
+
class="avatar"
|
|
785
|
+
[style.width.px]="size"
|
|
786
|
+
[style.height.px]="size"
|
|
787
|
+
[style.fontSize.px]="fontSize"
|
|
788
|
+
>
|
|
789
|
+
<span class="initials">{{ initials }}</span>
|
|
790
|
+
</div>
|
|
791
|
+
`, 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 });
|
|
792
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasAvatarComponent, decorators: [{
|
|
793
|
+
type: Component,
|
|
794
|
+
args: [{
|
|
795
|
+
selector: 'tas-avatar',
|
|
796
|
+
template: `
|
|
797
|
+
<div
|
|
798
|
+
class="avatar"
|
|
799
|
+
[style.width.px]="size"
|
|
800
|
+
[style.height.px]="size"
|
|
801
|
+
[style.fontSize.px]="fontSize"
|
|
802
|
+
>
|
|
803
|
+
<span class="initials">{{ initials }}</span>
|
|
804
|
+
</div>
|
|
805
|
+
`,
|
|
806
|
+
styles: [
|
|
807
|
+
`
|
|
808
|
+
.avatar {
|
|
809
|
+
display: flex;
|
|
810
|
+
align-items: center;
|
|
811
|
+
justify-content: center;
|
|
812
|
+
border-radius: 50%;
|
|
813
|
+
background-color: #ffffff;
|
|
814
|
+
color: #0072ac;
|
|
815
|
+
font-weight: 600;
|
|
816
|
+
font-family: inherit;
|
|
817
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
.initials {
|
|
821
|
+
text-transform: uppercase;
|
|
822
|
+
user-select: none;
|
|
823
|
+
}
|
|
824
|
+
`,
|
|
825
|
+
],
|
|
826
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
827
|
+
}]
|
|
828
|
+
}], propDecorators: { name: [{
|
|
829
|
+
type: Input
|
|
830
|
+
}], size: [{
|
|
831
|
+
type: Input
|
|
832
|
+
}] } });
|
|
833
|
+
|
|
295
834
|
class TasVideocallComponent {
|
|
296
|
-
constructor(activeModal, tasService) {
|
|
835
|
+
constructor(activeModal, tasService, geolocationService) {
|
|
297
836
|
this.activeModal = activeModal;
|
|
298
837
|
this.tasService = tasService;
|
|
838
|
+
this.geolocationService = geolocationService;
|
|
839
|
+
this.participantName = '';
|
|
840
|
+
this.tenant = '';
|
|
841
|
+
this.businessRole = TasBusinessRole.USER;
|
|
299
842
|
this.isReturningFromPip = false;
|
|
300
843
|
this.isPublisherSmall = true;
|
|
301
844
|
this.callState = CallState.IDLE;
|
|
302
845
|
this.isMuted = false;
|
|
846
|
+
this.waitingRoomUsers = [];
|
|
847
|
+
this.ownerHasJoined = false;
|
|
848
|
+
this.hasVideoStream = false;
|
|
849
|
+
this.dismissedUsers = [];
|
|
850
|
+
this.showLocationPanel = true; // Show by default for owners, will be updated based on user's location status
|
|
851
|
+
this.userHasLocation = false; // Tracks if the user has shared their location
|
|
852
|
+
this.geoLocationStatus = 'unknown';
|
|
853
|
+
// Geo panel states for owner
|
|
854
|
+
this.geoRequestActive = false; // Owner sent request, waiting for user
|
|
855
|
+
this.allGeoGranted = false; // All users responded with geo
|
|
303
856
|
this.subscriptions = new Subscription();
|
|
304
857
|
}
|
|
305
858
|
ngOnInit() {
|
|
@@ -311,6 +864,7 @@ class TasVideocallComponent {
|
|
|
311
864
|
}
|
|
312
865
|
ngOnDestroy() {
|
|
313
866
|
this.subscriptions.unsubscribe();
|
|
867
|
+
this.tasService.resetPollingState();
|
|
314
868
|
// Only disconnect if not in PiP mode (keep session alive for floating window)
|
|
315
869
|
if (!this.tasService.isPipMode()) {
|
|
316
870
|
this.tasService.disconnectSession();
|
|
@@ -337,25 +891,181 @@ class TasVideocallComponent {
|
|
|
337
891
|
onDoubleClick() {
|
|
338
892
|
this.toggleSwap();
|
|
339
893
|
}
|
|
894
|
+
/**
|
|
895
|
+
* Check if current user can admit others (OWNER, BACKOFFICE, or MODERATOR)
|
|
896
|
+
*/
|
|
897
|
+
get canAdmitUsers() {
|
|
898
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
899
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
900
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Admit a user from the waiting room
|
|
904
|
+
*/
|
|
905
|
+
admitUser(userId) {
|
|
906
|
+
if (!this.videoCallId) {
|
|
907
|
+
console.error('Cannot admit user: videoCallId not set');
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
this.tasService
|
|
911
|
+
.modifyProxyVideoUser({
|
|
912
|
+
userId,
|
|
913
|
+
videoCallId: this.videoCallId,
|
|
914
|
+
action: UserCallAction.WAITING_ROOM_LEAVE,
|
|
915
|
+
})
|
|
916
|
+
.subscribe({
|
|
917
|
+
next: () => {
|
|
918
|
+
// Remove user from waiting list after successful admit
|
|
919
|
+
this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
|
|
920
|
+
},
|
|
921
|
+
error: (err) => {
|
|
922
|
+
console.error('Error admitting user:', err);
|
|
923
|
+
},
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
/**
|
|
927
|
+
* Dismiss the waiting room notification for a user
|
|
928
|
+
*/
|
|
929
|
+
dismissWaitingNotification(userId) {
|
|
930
|
+
this.dismissedUsers.push(userId);
|
|
931
|
+
this.waitingRoomUsers = this.waitingRoomUsers.filter((u) => u.userId !== userId);
|
|
932
|
+
}
|
|
933
|
+
/**
|
|
934
|
+
* Close the location panel
|
|
935
|
+
*/
|
|
936
|
+
closeLocationPanel() {
|
|
937
|
+
this.showLocationPanel = false;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Request the user to share their location
|
|
941
|
+
*/
|
|
942
|
+
requestUserLocation() {
|
|
943
|
+
if (!this.videoCallId) {
|
|
944
|
+
console.error('Cannot request location: videoCallId not set');
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
const body = {
|
|
948
|
+
videoCallId: this.videoCallId,
|
|
949
|
+
action: UserCallAction.REQUEST_GEOLOCALIZATION,
|
|
950
|
+
};
|
|
951
|
+
// TODO: Send location request action to backend when endpoint is ready
|
|
952
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
953
|
+
next: () => console.log('Location request sent'),
|
|
954
|
+
error: (err) => console.error('Error requesting location:', err),
|
|
955
|
+
});
|
|
956
|
+
}
|
|
340
957
|
// Private Methods
|
|
341
958
|
setupSubscriptions() {
|
|
342
959
|
// Call state subscription
|
|
343
|
-
this.subscriptions.add(this.tasService.callState$.subscribe(state => {
|
|
960
|
+
this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
|
|
344
961
|
this.callState = state;
|
|
345
962
|
if (state === CallState.DISCONNECTED) {
|
|
346
963
|
this.activeModal.close('hangup');
|
|
347
964
|
}
|
|
965
|
+
// Track if we have an active video stream
|
|
966
|
+
this.hasVideoStream = state === CallState.CONNECTED;
|
|
348
967
|
}));
|
|
349
968
|
// View mode subscription
|
|
350
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
969
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
351
970
|
if (mode === ViewMode.PIP) {
|
|
352
971
|
this.activeModal.close('pip');
|
|
353
972
|
}
|
|
354
973
|
}));
|
|
355
974
|
// Mute state subscription
|
|
356
|
-
this.subscriptions.add(this.tasService.isMuted$.subscribe(muted => {
|
|
975
|
+
this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
|
|
357
976
|
this.isMuted = muted;
|
|
358
977
|
}));
|
|
978
|
+
// Waiting room users subscription
|
|
979
|
+
this.subscriptions.add(this.tasService.waitingRoomUsers$.subscribe((users) => {
|
|
980
|
+
// Filter out dismissed users
|
|
981
|
+
this.waitingRoomUsers = users.filter((u) => !this.dismissedUsers.includes(u.userId));
|
|
982
|
+
}));
|
|
983
|
+
// Owner join status subscription
|
|
984
|
+
this.subscriptions.add(this.tasService.ownerHasJoined$.subscribe((joined) => {
|
|
985
|
+
this.ownerHasJoined = joined;
|
|
986
|
+
}));
|
|
987
|
+
// Owner left subscription - disconnect non-owners
|
|
988
|
+
this.subscriptions.add(this.tasService.ownerHasLeft$.subscribe((hasLeft) => {
|
|
989
|
+
if (hasLeft && !this.canAdmitUsers) { // Non-owner user
|
|
990
|
+
console.log('[TAS DEBUG] Owner left, disconnecting user');
|
|
991
|
+
this.hangUp();
|
|
992
|
+
this.activeModal.close('owner_left');
|
|
993
|
+
}
|
|
994
|
+
}));
|
|
995
|
+
// ActivateGeo subscription - only for non-owners (users)
|
|
996
|
+
this.subscriptions.add(this.tasService.activateGeo$.subscribe((activateGeo) => {
|
|
997
|
+
if (activateGeo && !this.canAdmitUsers) {
|
|
998
|
+
console.log('[TAS DEBUG] activateGeo=true, checking geo status for user...');
|
|
999
|
+
this.handleActivateGeo();
|
|
1000
|
+
}
|
|
1001
|
+
}));
|
|
1002
|
+
// GeoRequestActive subscription - for owners
|
|
1003
|
+
this.subscriptions.add(this.tasService.geoRequestActive$.subscribe((active) => {
|
|
1004
|
+
console.log('[TAS DEBUG] geoRequestActive changed:', active);
|
|
1005
|
+
this.geoRequestActive = active;
|
|
1006
|
+
}));
|
|
1007
|
+
// AllGeoGranted subscription - for owners
|
|
1008
|
+
this.subscriptions.add(this.tasService.allGeoGranted$.subscribe((granted) => {
|
|
1009
|
+
console.log('[TAS DEBUG] allGeoGranted changed:', granted);
|
|
1010
|
+
this.allGeoGranted = granted;
|
|
1011
|
+
}));
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Handle activateGeo request from backend (for non-owner users).
|
|
1015
|
+
* If geo is already active, report it. If not, prompt user.
|
|
1016
|
+
*/
|
|
1017
|
+
handleActivateGeo() {
|
|
1018
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1019
|
+
console.log('[TAS DEBUG] handleActivateGeo - current status:', this.geoLocationStatus);
|
|
1020
|
+
// Check if we already have a cached position
|
|
1021
|
+
const cachedPosition = this.geolocationService.getCachedPosition();
|
|
1022
|
+
if (cachedPosition) {
|
|
1023
|
+
console.log('[TAS DEBUG] Geolocation already active, reporting to backend:', cachedPosition);
|
|
1024
|
+
this.geoLocationStatus = 'active';
|
|
1025
|
+
this.reportGeoStatus(cachedPosition.latitude, cachedPosition.longitude);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
// Try to get position
|
|
1029
|
+
console.log('[TAS DEBUG] Requesting geolocation from user...');
|
|
1030
|
+
const position = yield this.geolocationService.getCurrentPosition();
|
|
1031
|
+
if (position) {
|
|
1032
|
+
console.log('[TAS DEBUG] Geolocation obtained:', position);
|
|
1033
|
+
this.geoLocationStatus = 'active';
|
|
1034
|
+
this.reportGeoStatus(position.latitude, position.longitude);
|
|
1035
|
+
}
|
|
1036
|
+
else {
|
|
1037
|
+
console.log('[TAS DEBUG] Geolocation denied or unavailable');
|
|
1038
|
+
this.geoLocationStatus = 'denied';
|
|
1039
|
+
// Report that geo is not available (with no coordinates)
|
|
1040
|
+
this.reportGeoStatus();
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Report geolocation status to backend.
|
|
1046
|
+
*/
|
|
1047
|
+
reportGeoStatus(latitude, longitude) {
|
|
1048
|
+
if (!this.videoCallId) {
|
|
1049
|
+
console.error('[TAS DEBUG] Cannot report geo status: videoCallId not set');
|
|
1050
|
+
return;
|
|
1051
|
+
}
|
|
1052
|
+
// TODO: If the permission for geo location is not granted, we should not report it to the backend
|
|
1053
|
+
if (!this.geoLocationStatus) {
|
|
1054
|
+
console.error('[TAS DEBUG] Cannot report geo status: geoLocationStatus not set');
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
const body = {
|
|
1058
|
+
userId: this.userId,
|
|
1059
|
+
videoCallId: this.videoCallId,
|
|
1060
|
+
action: UserCallAction.ACTIVATE_GEOLOCATION,
|
|
1061
|
+
latitude,
|
|
1062
|
+
longitude,
|
|
1063
|
+
};
|
|
1064
|
+
console.log('[TAS DEBUG] Reporting geo status to backend:', body);
|
|
1065
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
1066
|
+
next: () => console.log('[TAS DEBUG] Geo status reported successfully'),
|
|
1067
|
+
error: (err) => console.error('[TAS DEBUG] Error reporting geo status:', err),
|
|
1068
|
+
});
|
|
359
1069
|
}
|
|
360
1070
|
initializeCall() {
|
|
361
1071
|
if (this.isReturningFromPip) {
|
|
@@ -364,9 +1074,22 @@ class TasVideocallComponent {
|
|
|
364
1074
|
}
|
|
365
1075
|
else if (this.sessionId && this.token) {
|
|
366
1076
|
// New call - connect to session
|
|
367
|
-
this.tasService
|
|
1077
|
+
this.tasService
|
|
1078
|
+
.connectSession(this.sessionId, this.token, 'publisher-container', 'subscriber-container', this.businessRole)
|
|
1079
|
+
.catch((err) => {
|
|
368
1080
|
console.error('Error connecting to video call:', err);
|
|
369
1081
|
});
|
|
1082
|
+
// Start status polling with shorter interval (5s) to detect owner leaving/location requests
|
|
1083
|
+
if (this.appointmentId) {
|
|
1084
|
+
this.tasService.startStatusPolling({
|
|
1085
|
+
appointmentId: this.appointmentId,
|
|
1086
|
+
tenant: this.tenant,
|
|
1087
|
+
businessRole: this.businessRole,
|
|
1088
|
+
roomType: undefined,
|
|
1089
|
+
entityId: undefined,
|
|
1090
|
+
sessionId: this.sessionId
|
|
1091
|
+
}, 5000);
|
|
1092
|
+
}
|
|
370
1093
|
}
|
|
371
1094
|
}
|
|
372
1095
|
resetVideoPositions() {
|
|
@@ -392,8 +1115,8 @@ class TasVideocallComponent {
|
|
|
392
1115
|
modifiers: [
|
|
393
1116
|
interact.modifiers.restrictRect({
|
|
394
1117
|
restriction: 'parent',
|
|
395
|
-
endOnly: true
|
|
396
|
-
})
|
|
1118
|
+
endOnly: true,
|
|
1119
|
+
}),
|
|
397
1120
|
],
|
|
398
1121
|
autoScroll: true,
|
|
399
1122
|
listeners: {
|
|
@@ -404,8 +1127,8 @@ class TasVideocallComponent {
|
|
|
404
1127
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
405
1128
|
target.setAttribute('data-x', String(x));
|
|
406
1129
|
target.setAttribute('data-y', String(y));
|
|
407
|
-
}
|
|
408
|
-
}
|
|
1130
|
+
},
|
|
1131
|
+
},
|
|
409
1132
|
})
|
|
410
1133
|
.resizable({
|
|
411
1134
|
edges: { left: false, right: true, bottom: true, top: false },
|
|
@@ -421,26 +1144,38 @@ class TasVideocallComponent {
|
|
|
421
1144
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
422
1145
|
target.setAttribute('data-x', String(x));
|
|
423
1146
|
target.setAttribute('data-y', String(y));
|
|
424
|
-
}
|
|
1147
|
+
},
|
|
425
1148
|
},
|
|
426
1149
|
modifiers: [
|
|
427
1150
|
interact.modifiers.restrictEdges({ outer: 'parent' }),
|
|
428
1151
|
interact.modifiers.restrictSize({ min: { width: 150, height: 100 } }),
|
|
429
|
-
interact.modifiers.aspectRatio({ ratio: 'preserve' })
|
|
1152
|
+
interact.modifiers.aspectRatio({ ratio: 'preserve' }),
|
|
430
1153
|
],
|
|
431
|
-
inertia: true
|
|
1154
|
+
inertia: true,
|
|
432
1155
|
});
|
|
433
1156
|
}
|
|
434
1157
|
}
|
|
435
|
-
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
436
|
-
TasVideocallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: TasVideocallComponent, selector: "tas-videocall", inputs: { sessionId: "sessionId", token: "token", 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\t<div id=\"subscriber-container\" \n\t\t[class.subscriber-view]=\"isPublisherSmall\" \n\t\t[class.publisher-view]=\"!isPublisherSmall\"\n\t\t#subscriberContainer\n\t\t(dblclick)=\"onDoubleClick()\">\n\t</div>\n\n\t<div id=\"publisher-container\" \n\t\t[class.publisher-view]=\"isPublisherSmall\" \n\t\t[class.subscriber-view]=\"!isPublisherSmall\"\n\t\t#publisherContainer \n\t\t(dblclick)=\"onDoubleClick()\">\n\t</div>\n\t\n\t<div class=\"controls-container\">\n\t\t<button class=\"btn swap-btn\" (click)=\"toggleSwap()\" title=\"Swap view\">\n\t\t\t<i class=\"fa fa-refresh\"></i>\n\t\t</button>\n\t\t<button class=\"btn pip-btn\" (click)=\"minimize()\" title=\"Minimize (Picture in Picture)\">\n\t\t\t<i class=\"fa fa-compress\"></i>\n\t\t</button>\n\t\t<button class=\"btn mute-btn\" [class.muted]=\"isMuted\" (click)=\"toggleMute()\" [title]=\"isMuted ? 'Unmute microphone' : 'Mute microphone'\">\n\t\t\t<i class=\"fa\" [class.fa-microphone]=\"!isMuted\" [class.fa-microphone-slash]=\"isMuted\"></i>\n\t\t</button>\n\t\t<button class=\"btn hangup-btn\" (click)=\"hangUp()\" title=\"Hang up\">\n\t\t\t<i class=\"fa fa-phone\" style=\"transform: rotate(135deg);\"></i>\n\t\t</button>\n\t</div>\n</div>\n\n", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;background-color:#000;overflow:hidden}.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:#333}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:20px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#00000080;padding:15px 25px;border-radius:50px;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.tas-videocall-container .controls-container .hangup-btn,.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{width:60px;height:60px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;border:none;box-shadow:0 4px 6px #0000004d;transition:all .2s ease}.tas-videocall-container .controls-container .hangup-btn i,.tas-videocall-container .controls-container .swap-btn i,.tas-videocall-container .controls-container .pip-btn i,.tas-videocall-container .controls-container .mute-btn i{color:#fff}.tas-videocall-container .controls-container .hangup-btn{background:#dc3545}.tas-videocall-container .controls-container .hangup-btn:hover{background:#c82333;transform:scale(1.05)}.tas-videocall-container .controls-container .swap-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .swap-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .pip-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .pip-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn.muted{background:#f39c12}.tas-videocall-container .controls-container .mute-btn.muted:hover{background:#e67e22}\n"] });
|
|
437
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1158
|
+
TasVideocallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1159
|
+
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", userId: "userId", 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-wrapper\">\n <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\n <!-- Location panel (shown for owners when user hasn't allowed location) -->\n <div class=\"location-panel\" *ngIf=\"showLocationPanel && canAdmitUsers\">\n <div class=\"location-header\">\n <i class=\"fa fa-map-marker header-icon\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n <button class=\"close-btn\" (click)=\"closeLocationPanel()\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n <div class=\"location-description\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n <div class=\"location-content\">\n <!-- Initial state: Show verify button -->\n <ng-container *ngIf=\"!geoRequestActive && !allGeoGranted\">\n <button class=\"verify-location-btn\" (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Loading state: Spinner while waiting for user response -->\n <ng-container *ngIf=\"geoRequestActive && !allGeoGranted\">\n <div class=\"geo-loading-container\">\n <div class=\"geo-spinner\"></div>\n <p class=\"loading-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"loading-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n <button class=\"verify-location-btn disabled\" disabled>\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Success state: Location verified -->\n <ng-container *ngIf=\"allGeoGranted\">\n <div class=\"geo-success-container\">\n <div class=\"success-icon\">\n <i class=\"fa fa-check\"></i>\n </div>\n <p class=\"success-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n </div>\n <div class=\"location-footer\">\n <span class=\"footer-icon\"><i class=\"fa fa-clock-o\"></i></span>\n <span class=\"footer-icon location-icon\"><i class=\"fa fa-map-marker\"></i></span>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;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}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1160
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasVideocallComponent, decorators: [{
|
|
438
1161
|
type: Component,
|
|
439
|
-
args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-container\">\n\t<div id=\"subscriber-container\" \n\t\t[class.subscriber-view]=\"isPublisherSmall\" \n\t\t[class.publisher-view]=\"!isPublisherSmall\"\n\t\t#subscriberContainer\n\t\t(dblclick)=\"onDoubleClick()\">\n\t</div>\n\n\t<div id=\"publisher-container\" \n\t\t[class.publisher-view]=\"isPublisherSmall\" \n\t\t[class.subscriber-view]=\"!isPublisherSmall\"\n\t\t#publisherContainer \n\t\t(dblclick)=\"onDoubleClick()\">\n\t</div>\n\t\n\t<div class=\"controls-container\">\n\t\t<button class=\"btn swap-btn\" (click)=\"toggleSwap()\" title=\"Swap view\">\n\t\t\t<i class=\"fa fa-refresh\"></i>\n\t\t</button>\n\t\t<button class=\"btn pip-btn\" (click)=\"minimize()\" title=\"Minimize (Picture in Picture)\">\n\t\t\t<i class=\"fa fa-compress\"></i>\n\t\t</button>\n\t\t<button class=\"btn mute-btn\" [class.muted]=\"isMuted\" (click)=\"toggleMute()\" [title]=\"isMuted ? 'Unmute microphone' : 'Mute microphone'\">\n\t\t\t<i class=\"fa\" [class.fa-microphone]=\"!isMuted\" [class.fa-microphone-slash]=\"isMuted\"></i>\n\t\t</button>\n\t\t<button class=\"btn hangup-btn\" (click)=\"hangUp()\" title=\"Hang up\">\n\t\t\t<i class=\"fa fa-phone\" style=\"transform: rotate(135deg);\"></i>\n\t\t</button>\n\t</div>\n</div>\n\n", styles: [".tas-videocall-container{position:relative;width:100vw;height:100vh;background-color:#000;overflow:hidden}.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:#333}.tas-videocall-container .controls-container{display:flex;flex-direction:row;gap:20px;position:absolute;bottom:30px;left:50%;transform:translate(-50%);z-index:3;background-color:#00000080;padding:15px 25px;border-radius:50px;-webkit-backdrop-filter:blur(5px);backdrop-filter:blur(5px)}.tas-videocall-container .controls-container .hangup-btn,.tas-videocall-container .controls-container .swap-btn,.tas-videocall-container .controls-container .pip-btn,.tas-videocall-container .controls-container .mute-btn{width:60px;height:60px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;border:none;box-shadow:0 4px 6px #0000004d;transition:all .2s ease}.tas-videocall-container .controls-container .hangup-btn i,.tas-videocall-container .controls-container .swap-btn i,.tas-videocall-container .controls-container .pip-btn i,.tas-videocall-container .controls-container .mute-btn i{color:#fff}.tas-videocall-container .controls-container .hangup-btn{background:#dc3545}.tas-videocall-container .controls-container .hangup-btn:hover{background:#c82333;transform:scale(1.05)}.tas-videocall-container .controls-container .swap-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .swap-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .pip-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .pip-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn{background:rgba(255,255,255,.2)}.tas-videocall-container .controls-container .mute-btn:hover{background:rgba(255,255,255,.35);transform:scale(1.05)}.tas-videocall-container .controls-container .mute-btn.muted{background:#f39c12}.tas-videocall-container .controls-container .mute-btn.muted:hover{background:#e67e22}\n"] }]
|
|
440
|
-
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }]; }, propDecorators: { sessionId: [{
|
|
1162
|
+
args: [{ selector: 'tas-videocall', template: "<div class=\"tas-videocall-wrapper\">\n <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\n <!-- Location panel (shown for owners when user hasn't allowed location) -->\n <div class=\"location-panel\" *ngIf=\"showLocationPanel && canAdmitUsers\">\n <div class=\"location-header\">\n <i class=\"fa fa-map-marker header-icon\"></i>\n <h3>Ubicaci\u00F3n del colaborador</h3>\n <button class=\"close-btn\" (click)=\"closeLocationPanel()\" aria-label=\"Cerrar\">\u00D7</button>\n </div>\n <div class=\"location-description\">\n <p>El colaborador tiene la ubicaci\u00F3n desactivada, solicita que la active.</p>\n <p>Esta acci\u00F3n nos permitir\u00E1 disponibilizar algunas alertas.</p>\n </div>\n <div class=\"location-content\">\n <!-- Initial state: Show verify button -->\n <ng-container *ngIf=\"!geoRequestActive && !allGeoGranted\">\n <button class=\"verify-location-btn\" (click)=\"requestUserLocation()\">\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Loading state: Spinner while waiting for user response -->\n <ng-container *ngIf=\"geoRequestActive && !allGeoGranted\">\n <div class=\"geo-loading-container\">\n <div class=\"geo-spinner\"></div>\n <p class=\"loading-title\">Verificando ubicaci\u00F3n...</p>\n <p class=\"loading-subtitle\">Esto puede tardar unos segundos.</p>\n </div>\n <button class=\"verify-location-btn disabled\" disabled>\n Verificar ubicaci\u00F3n\n </button>\n </ng-container>\n\n <!-- Success state: Location verified -->\n <ng-container *ngIf=\"allGeoGranted\">\n <div class=\"geo-success-container\">\n <div class=\"success-icon\">\n <i class=\"fa fa-check\"></i>\n </div>\n <p class=\"success-title\">La ubicaci\u00F3n fue verificada</p>\n </div>\n </ng-container>\n </div>\n <div class=\"location-footer\">\n <span class=\"footer-icon\"><i class=\"fa fa-clock-o\"></i></span>\n <span class=\"footer-icon location-icon\"><i class=\"fa fa-map-marker\"></i></span>\n </div>\n </div>\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:flex;width:100vw;height:100vh;box-sizing:border-box;padding:2rem;background:linear-gradient(281deg,rgba(29,164,177,.2) 6.96%,rgba(0,0,0,0) 70.44%),#212532}.tas-videocall-wrapper{display:flex;flex:1;gap:1rem;height:100%}.tas-videocall-container{position:relative;flex:1;height:100%;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}.location-panel{width:280px;height:100%;background:#212532;border-radius:8px;padding:1.5rem;display:flex;flex-direction:column;color:#fff}.location-panel .location-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:1rem}.location-panel .location-header h3{font-size:16px;font-weight:600;margin:0;color:#fff}.location-panel .location-header .close-btn{background:transparent;border:none;color:#fff;font-size:20px;cursor:pointer;padding:0;line-height:1;opacity:.7}.location-panel .location-header .close-btn:hover{opacity:1}.location-panel .location-description{font-size:14px;color:#fffc;line-height:1.5;margin-bottom:.5rem}.location-panel .location-description p{margin:0 0 .5rem}.location-panel .location-content{flex:1;display:flex;flex-direction:column;justify-content:flex-end}.location-panel .verify-location-btn{width:100%;padding:12px 24px;background:var(--Primary-Uell, #1da4b1);color:#fff;border:none;border-radius:8px;font-size:14px;font-weight:500;cursor:pointer;transition:background .2s ease;margin-bottom:1rem}.location-panel .verify-location-btn:hover{background:#178e99}.location-panel .verify-location-btn:disabled{background:rgba(29,164,177,.5);cursor:not-allowed}.location-panel .location-footer{display:flex;justify-content:flex-end;gap:.5rem;padding-top:.5rem;border-top:1px solid rgba(255,255,255,.1)}.location-panel .location-footer .footer-icon{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:rgba(255,255,255,.1);color:#fff;font-size:14px}.location-panel .location-footer .footer-icon.location-icon{background:var(--Primary-Uell, #1da4b1)}.location-panel .header-icon{color:#fff;font-size:16px}.location-panel .geo-loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-loading-container .geo-spinner{width:64px;height:64px;border:4px solid rgba(29,164,177,.2);border-top-color:var(--Primary-Uell, #1da4b1);border-radius:50%;animation:geo-spin 1s linear infinite}.location-panel .geo-loading-container .loading-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 .25rem}.location-panel .geo-loading-container .loading-subtitle{color:#ffffffb3;font-size:14px;margin:0}.location-panel .geo-success-container{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:2rem 0}.location-panel .geo-success-container .success-icon{width:80px;height:80px;border-radius:50%;background:var(--Primary-Uell, #1da4b1);display:flex;align-items:center;justify-content:center;position:relative}.location-panel .geo-success-container .success-icon i{color:#fff;font-size:32px}.location-panel .geo-success-container .success-icon:before,.location-panel .geo-success-container .success-icon:after{content:\"\\2726\";position:absolute;color:#fff;font-size:10px}.location-panel .geo-success-container .success-icon:before{top:-8px;right:-4px}.location-panel .geo-success-container .success-icon:after{bottom:-4px;left:-8px}.location-panel .geo-success-container .success-title{color:#fff;font-size:16px;font-weight:600;margin:1.5rem 0 0}.location-panel .verify-location-btn.disabled{background:rgba(29,164,177,.5);cursor:not-allowed}@keyframes geo-spin{to{transform:rotate(360deg)}}\n"] }]
|
|
1163
|
+
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }]; }, propDecorators: { sessionId: [{
|
|
441
1164
|
type: Input
|
|
442
1165
|
}], token: [{
|
|
443
1166
|
type: Input
|
|
1167
|
+
}], appointmentId: [{
|
|
1168
|
+
type: Input
|
|
1169
|
+
}], videoCallId: [{
|
|
1170
|
+
type: Input
|
|
1171
|
+
}], userId: [{
|
|
1172
|
+
type: Input
|
|
1173
|
+
}], participantName: [{
|
|
1174
|
+
type: Input
|
|
1175
|
+
}], tenant: [{
|
|
1176
|
+
type: Input
|
|
1177
|
+
}], businessRole: [{
|
|
1178
|
+
type: Input
|
|
444
1179
|
}], isReturningFromPip: [{
|
|
445
1180
|
type: Input
|
|
446
1181
|
}], publisherContainer: [{
|
|
@@ -453,238 +1188,312 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
453
1188
|
|
|
454
1189
|
var WaitingRoomState;
|
|
455
1190
|
(function (WaitingRoomState) {
|
|
456
|
-
WaitingRoomState["
|
|
457
|
-
WaitingRoomState["
|
|
458
|
-
WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
|
|
1191
|
+
WaitingRoomState["CHECKING_STATUS"] = "CHECKING_STATUS";
|
|
1192
|
+
WaitingRoomState["WAITING_FOR_JOINABLE"] = "WAITING_FOR_JOINABLE";
|
|
459
1193
|
WaitingRoomState["READY"] = "READY";
|
|
1194
|
+
WaitingRoomState["GETTING_TOKEN"] = "GETTING_TOKEN";
|
|
1195
|
+
WaitingRoomState["JOINING"] = "JOINING";
|
|
460
1196
|
WaitingRoomState["ERROR"] = "ERROR";
|
|
461
1197
|
})(WaitingRoomState || (WaitingRoomState = {}));
|
|
462
1198
|
class TasWaitingRoomComponent {
|
|
463
|
-
constructor(activeModal, tasService, modalService) {
|
|
1199
|
+
constructor(activeModal, tasService, geolocationService, modalService, cdr) {
|
|
464
1200
|
this.activeModal = activeModal;
|
|
465
1201
|
this.tasService = tasService;
|
|
1202
|
+
this.geolocationService = geolocationService;
|
|
466
1203
|
this.modalService = modalService;
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
this.
|
|
470
|
-
this.
|
|
471
|
-
this.regularUserIds = [];
|
|
472
|
-
this.moderatorUserIds = [];
|
|
473
|
-
/** Optional: If provided, skips room creation and goes directly to getting a token */
|
|
474
|
-
this.existingSessionId = '';
|
|
1204
|
+
this.cdr = cdr;
|
|
1205
|
+
// Status endpoint params
|
|
1206
|
+
this.roomType = TasRoomType.TAS;
|
|
1207
|
+
this.businessRole = TasBusinessRole.USER;
|
|
475
1208
|
// Component state
|
|
476
|
-
this.state = WaitingRoomState.
|
|
1209
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
477
1210
|
this.WaitingRoomState = WaitingRoomState; // Expose enum to template
|
|
478
1211
|
this.errorMessage = '';
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
this.
|
|
483
|
-
/** Manual session ID input by user */
|
|
484
|
-
this.manualSessionId = '';
|
|
485
|
-
/** Track if we're joining an existing session (for UI display) */
|
|
486
|
-
this.isJoiningExisting = false;
|
|
487
|
-
// Session data
|
|
488
|
-
this.sessionId = '';
|
|
1212
|
+
this.isJoinable = false;
|
|
1213
|
+
// Session data from status response
|
|
1214
|
+
this.resolvedSessionId = '';
|
|
1215
|
+
this.resolvedAppointmentId = null;
|
|
489
1216
|
this.token = '';
|
|
490
|
-
this.
|
|
1217
|
+
this.videoCallId = null;
|
|
1218
|
+
// Subscriptions
|
|
491
1219
|
this.subscriptions = new Subscription();
|
|
492
1220
|
this.videoCallModalRef = null;
|
|
1221
|
+
// Geolocation
|
|
1222
|
+
this.geoPosition = null;
|
|
1223
|
+
}
|
|
1224
|
+
/** Whether current user is an owner */
|
|
1225
|
+
get isOwner() {
|
|
1226
|
+
var _a;
|
|
1227
|
+
return ((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.role) === TasUserRole.OWNER;
|
|
493
1228
|
}
|
|
494
1229
|
ngOnInit() {
|
|
495
|
-
|
|
496
|
-
this.
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
this.hasExistingSession = true;
|
|
500
|
-
this.sessionId = this.existingSessionId;
|
|
501
|
-
this.isJoiningExisting = true;
|
|
502
|
-
}
|
|
1230
|
+
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnInit');
|
|
1231
|
+
this.requestMediaPermissions();
|
|
1232
|
+
this.requestGeolocation();
|
|
1233
|
+
this.checkStatus();
|
|
503
1234
|
}
|
|
504
|
-
|
|
505
|
-
|
|
1235
|
+
/**
|
|
1236
|
+
* Request camera and microphone permissions.
|
|
1237
|
+
*/
|
|
1238
|
+
requestMediaPermissions() {
|
|
1239
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1240
|
+
console.log('[TAS DEBUG] Requesting media permissions...');
|
|
1241
|
+
try {
|
|
1242
|
+
const stream = yield navigator.mediaDevices.getUserMedia({ audio: true, video: true });
|
|
1243
|
+
// Stop tracks immediately - we just needed the permission
|
|
1244
|
+
stream.getTracks().forEach(track => track.stop());
|
|
1245
|
+
console.log('[TAS DEBUG] Media permissions granted');
|
|
1246
|
+
}
|
|
1247
|
+
catch (error) {
|
|
1248
|
+
console.warn('[TAS DEBUG] Media permissions denied or unavailable:', error);
|
|
1249
|
+
}
|
|
1250
|
+
});
|
|
506
1251
|
}
|
|
507
1252
|
/**
|
|
508
|
-
*
|
|
1253
|
+
* Request geolocation immediately on init.
|
|
1254
|
+
* Only for regular users (not owners/backoffice).
|
|
1255
|
+
* If user allows, store position and send to backend.
|
|
509
1256
|
*/
|
|
510
|
-
|
|
1257
|
+
requestGeolocation() {
|
|
1258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
1259
|
+
// Only request geolocation for regular users, not owners/backoffice
|
|
1260
|
+
if (this.isOwner || this.isBackoffice) {
|
|
1261
|
+
console.log('[TAS DEBUG] Skipping geolocation for owner/backoffice');
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
console.log('[TAS DEBUG] Requesting geolocation...');
|
|
1265
|
+
const position = yield this.geolocationService.getCurrentPosition();
|
|
1266
|
+
if (position) {
|
|
1267
|
+
console.log('[TAS DEBUG] Geolocation obtained:', position);
|
|
1268
|
+
this.geoPosition = position;
|
|
1269
|
+
// Send to backend when videoCallId is available
|
|
1270
|
+
this.sendGeolocationToBackend();
|
|
1271
|
+
}
|
|
1272
|
+
else {
|
|
1273
|
+
console.log('[TAS DEBUG] Geolocation denied or unavailable');
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Send geolocation to backend via modify user endpoint.
|
|
1279
|
+
* NOTE: Endpoint call is prepared but may not be active yet.
|
|
1280
|
+
*/
|
|
1281
|
+
sendGeolocationToBackend() {
|
|
511
1282
|
var _a;
|
|
512
|
-
if (!this.
|
|
513
|
-
this.state = WaitingRoomState.ERROR;
|
|
514
|
-
this.errorMessage = 'Missing configuration data (tenant or user)';
|
|
1283
|
+
if (!this.geoPosition || !this.videoCallId) {
|
|
515
1284
|
return;
|
|
516
1285
|
}
|
|
517
|
-
|
|
518
|
-
this.state = WaitingRoomState.CREATING_ROOM;
|
|
519
|
-
this.errorMessage = '';
|
|
1286
|
+
console.log('[TAS DEBUG] Sending geolocation to backend...');
|
|
520
1287
|
const body = {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
product: this.product
|
|
1288
|
+
userId: (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id,
|
|
1289
|
+
videoCallId: this.videoCallId,
|
|
1290
|
+
action: UserCallAction.ACTIVATE_GEOLOCATION,
|
|
1291
|
+
latitude: this.geoPosition.latitude,
|
|
1292
|
+
longitude: this.geoPosition.longitude,
|
|
527
1293
|
};
|
|
528
|
-
this.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
1294
|
+
this.tasService.modifyProxyVideoUser(body).subscribe({
|
|
1295
|
+
next: () => console.log('[TAS DEBUG] Geolocation sent successfully'),
|
|
1296
|
+
error: (err) => console.error('[TAS DEBUG] Failed to send geolocation:', err),
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
ngOnDestroy() {
|
|
1300
|
+
console.log('[TAS DEBUG] TasWaitingRoomComponent.ngOnDestroy');
|
|
1301
|
+
this.subscriptions.unsubscribe();
|
|
1302
|
+
this.tasService.stopStatusPolling();
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1305
|
+
* Check status to get session info
|
|
1306
|
+
*/
|
|
1307
|
+
checkStatus() {
|
|
1308
|
+
console.log('[TAS DEBUG] checkStatus called with:', {
|
|
1309
|
+
roomType: this.roomType,
|
|
1310
|
+
entityId: this.entityId,
|
|
1311
|
+
tenant: this.tenant,
|
|
1312
|
+
businessRole: this.businessRole,
|
|
1313
|
+
currentUser: this.currentUser,
|
|
1314
|
+
});
|
|
1315
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
1316
|
+
this.errorMessage = '';
|
|
1317
|
+
const statusParams = {
|
|
1318
|
+
roomType: this.roomType,
|
|
1319
|
+
entityId: this.entityId,
|
|
1320
|
+
tenant: this.tenant,
|
|
1321
|
+
businessRole: this.businessRole,
|
|
1322
|
+
};
|
|
1323
|
+
console.log('[TAS DEBUG] Calling getProxyVideoStatus...');
|
|
1324
|
+
this.subscriptions.add(this.tasService.getProxyVideoStatus(statusParams).subscribe({
|
|
1325
|
+
next: (response) => {
|
|
1326
|
+
const content = response.content;
|
|
1327
|
+
// Store session info from response
|
|
1328
|
+
this.resolvedSessionId = content.sessionId;
|
|
1329
|
+
this.resolvedAppointmentId = content.appointmentId;
|
|
1330
|
+
this.videoCallId = content.videoCallId;
|
|
1331
|
+
console.log('[TAS DEBUG] Status response:', content);
|
|
1332
|
+
// Try to send geolocation now that we have videoCallId
|
|
1333
|
+
this.sendGeolocationToBackend();
|
|
1334
|
+
// Start polling for status updates
|
|
1335
|
+
this.tasService.startStatusPolling(statusParams);
|
|
1336
|
+
// Subscribe to joinable status
|
|
1337
|
+
this.subscriptions.add(this.tasService.joinable$.subscribe((joinable) => {
|
|
1338
|
+
this.handleJoinableChange(joinable);
|
|
1339
|
+
}));
|
|
541
1340
|
},
|
|
542
1341
|
error: (err) => {
|
|
543
|
-
console.error('
|
|
1342
|
+
console.error('[TAS DEBUG] Status check failed:', err);
|
|
544
1343
|
this.state = WaitingRoomState.ERROR;
|
|
545
|
-
this.errorMessage = 'Error
|
|
546
|
-
}
|
|
1344
|
+
this.errorMessage = 'Error checking session status. Please try again.';
|
|
1345
|
+
},
|
|
547
1346
|
}));
|
|
548
1347
|
}
|
|
549
1348
|
/**
|
|
550
|
-
*
|
|
1349
|
+
* Handle changes to joinable status
|
|
551
1350
|
*/
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
this.
|
|
1351
|
+
handleJoinableChange(joinable) {
|
|
1352
|
+
console.log('[TAS DEBUG] handleJoinableChange called', {
|
|
1353
|
+
joinable,
|
|
1354
|
+
currentState: this.state,
|
|
1355
|
+
resolvedSessionId: this.resolvedSessionId,
|
|
1356
|
+
});
|
|
1357
|
+
this.isJoinable = joinable;
|
|
1358
|
+
// Don't update state if already getting token, joining, or in error
|
|
1359
|
+
if (this.state === WaitingRoomState.GETTING_TOKEN ||
|
|
1360
|
+
this.state === WaitingRoomState.JOINING ||
|
|
1361
|
+
this.state === WaitingRoomState.ERROR) {
|
|
1362
|
+
console.log('[TAS DEBUG] Skipping state update - already in:', this.state);
|
|
556
1363
|
return;
|
|
557
1364
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
1365
|
+
// Both users and owners: show join button based on joinable
|
|
1366
|
+
if (joinable) {
|
|
1367
|
+
console.log('[TAS DEBUG] Joinable is true, showing join button');
|
|
1368
|
+
this.state = WaitingRoomState.READY;
|
|
1369
|
+
}
|
|
1370
|
+
else {
|
|
1371
|
+
console.log('[TAS DEBUG] Waiting for joinable...');
|
|
1372
|
+
this.state = WaitingRoomState.WAITING_FOR_JOINABLE;
|
|
1373
|
+
}
|
|
1374
|
+
this.cdr.detectChanges();
|
|
561
1375
|
}
|
|
562
1376
|
/**
|
|
563
|
-
*
|
|
1377
|
+
* Called when user clicks the join button.
|
|
1378
|
+
* Calls /start to get token then auto-joins.
|
|
564
1379
|
*/
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
1380
|
+
joinSession() {
|
|
1381
|
+
this.startSessionAndJoin();
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Check if user has owner/backoffice role
|
|
1385
|
+
*/
|
|
1386
|
+
get isBackoffice() {
|
|
1387
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
1388
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
1389
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Start session and join - called when user clicks join button
|
|
1393
|
+
*/
|
|
1394
|
+
startSessionAndJoin() {
|
|
1395
|
+
if (!this.resolvedSessionId) {
|
|
568
1396
|
this.state = WaitingRoomState.ERROR;
|
|
569
|
-
this.errorMessage = '
|
|
1397
|
+
this.errorMessage = 'Session ID not available';
|
|
1398
|
+
this.tasService.stopStatusPolling();
|
|
1399
|
+
this.cdr.detectChanges();
|
|
570
1400
|
return;
|
|
571
1401
|
}
|
|
572
|
-
this.isJoiningExisting = true;
|
|
573
1402
|
this.state = WaitingRoomState.GETTING_TOKEN;
|
|
574
1403
|
this.errorMessage = '';
|
|
575
|
-
|
|
576
|
-
|
|
1404
|
+
console.log('[TAS DEBUG] Calling /start for session:', this.resolvedSessionId);
|
|
1405
|
+
this.subscriptions.add(this.tasService
|
|
1406
|
+
.startProxyVideoSession({
|
|
1407
|
+
sessionId: this.resolvedSessionId,
|
|
577
1408
|
name: this.currentUser.name,
|
|
578
1409
|
lastname: this.currentUser.lastname,
|
|
579
|
-
|
|
580
|
-
|
|
1410
|
+
})
|
|
1411
|
+
.subscribe({
|
|
581
1412
|
next: (tokenResponse) => {
|
|
1413
|
+
var _a;
|
|
1414
|
+
console.log('[TAS DEBUG] Token response:', tokenResponse);
|
|
1415
|
+
// Handle case where HTTP adapter returns error in next instead of error handler
|
|
1416
|
+
if (!((_a = tokenResponse === null || tokenResponse === void 0 ? void 0 : tokenResponse.content) === null || _a === void 0 ? void 0 : _a.token)) {
|
|
1417
|
+
console.error('[TAS DEBUG] Invalid token response:', tokenResponse);
|
|
1418
|
+
this.state = WaitingRoomState.ERROR;
|
|
1419
|
+
this.errorMessage = (tokenResponse === null || tokenResponse === void 0 ? void 0 : tokenResponse.message) || 'Error al iniciar la sesión. Respuesta inválida.';
|
|
1420
|
+
this.tasService.stopStatusPolling();
|
|
1421
|
+
this.cdr.detectChanges();
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
console.log('[TAS DEBUG] Token obtained successfully, joining session...');
|
|
582
1425
|
this.token = tokenResponse.content.token;
|
|
583
|
-
this.state = WaitingRoomState.
|
|
1426
|
+
this.state = WaitingRoomState.JOINING;
|
|
1427
|
+
this.cdr.detectChanges();
|
|
1428
|
+
// Auto-join immediately
|
|
1429
|
+
this.tasService.stopStatusPolling();
|
|
1430
|
+
this.activeModal.close('joining');
|
|
1431
|
+
this.openVideoCallModal();
|
|
584
1432
|
},
|
|
585
1433
|
error: (err) => {
|
|
586
|
-
|
|
1434
|
+
var _a;
|
|
1435
|
+
console.error('[TAS DEBUG] /start request failed:', err);
|
|
587
1436
|
this.state = WaitingRoomState.ERROR;
|
|
588
|
-
this.errorMessage =
|
|
589
|
-
|
|
1437
|
+
this.errorMessage = ((_a = err === null || err === void 0 ? void 0 : err.error) === null || _a === void 0 ? void 0 : _a.message) || (err === null || err === void 0 ? void 0 : err.message) || 'Error al iniciar la sesión. Por favor, intente nuevamente.';
|
|
1438
|
+
this.tasService.stopStatusPolling();
|
|
1439
|
+
console.log('[TAS DEBUG] State set to ERROR, errorMessage:', this.errorMessage);
|
|
1440
|
+
this.cdr.detectChanges();
|
|
1441
|
+
},
|
|
590
1442
|
}));
|
|
591
1443
|
}
|
|
592
|
-
/**
|
|
593
|
-
* Joins the video call session
|
|
594
|
-
*/
|
|
595
|
-
joinSession() {
|
|
596
|
-
if (!this.sessionId || !this.token) {
|
|
597
|
-
this.errorMessage = 'Cannot join session. Incomplete data.';
|
|
598
|
-
return;
|
|
599
|
-
}
|
|
600
|
-
// Close waiting room and open video call
|
|
601
|
-
this.activeModal.close('joining');
|
|
602
|
-
this.openVideoCallModal();
|
|
603
|
-
}
|
|
604
1444
|
/**
|
|
605
1445
|
* Closes the waiting room
|
|
606
1446
|
*/
|
|
607
1447
|
cancel() {
|
|
1448
|
+
this.tasService.stopStatusPolling();
|
|
608
1449
|
this.activeModal.dismiss('cancel');
|
|
609
1450
|
}
|
|
610
1451
|
/**
|
|
611
1452
|
* Retry after an error
|
|
612
1453
|
*/
|
|
613
1454
|
retry() {
|
|
614
|
-
this.state = WaitingRoomState.
|
|
1455
|
+
this.state = WaitingRoomState.CHECKING_STATUS;
|
|
615
1456
|
this.errorMessage = '';
|
|
616
1457
|
this.token = '';
|
|
617
|
-
this.
|
|
618
|
-
// Only reset sessionId if we don't have an existing one from input
|
|
619
|
-
if (!this.hasExistingSession) {
|
|
620
|
-
this.sessionId = '';
|
|
621
|
-
}
|
|
1458
|
+
this.checkStatus();
|
|
622
1459
|
}
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
this.users = [];
|
|
626
|
-
// Add owners from input
|
|
627
|
-
this.ownerUserIds.forEach(id => {
|
|
628
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.OWNER });
|
|
629
|
-
});
|
|
630
|
-
// Add regular users from input
|
|
631
|
-
this.regularUserIds.forEach(id => {
|
|
632
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.USER });
|
|
633
|
-
});
|
|
634
|
-
// Add moderators from input
|
|
635
|
-
this.moderatorUserIds.forEach(id => {
|
|
636
|
-
this.users.push({ userExternalId: id, rol: TasUserRole.MODERATOR });
|
|
637
|
-
});
|
|
638
|
-
}
|
|
639
|
-
setupViewModeSubscription() {
|
|
640
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
641
|
-
// Re-open video call modal when returning from PiP mode
|
|
642
|
-
if (mode === ViewMode.FULLSCREEN &&
|
|
643
|
-
this.tasService.isCallActive() &&
|
|
644
|
-
!this.videoCallModalRef) {
|
|
645
|
-
const sessionId = this.tasService.sessionId;
|
|
646
|
-
const token = this.tasService.token;
|
|
647
|
-
if (sessionId && token) {
|
|
648
|
-
this.openVideoCallModal(true);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}));
|
|
652
|
-
}
|
|
653
|
-
openVideoCallModal(isReturningFromPip = false) {
|
|
1460
|
+
openVideoCallModal() {
|
|
1461
|
+
var _a;
|
|
654
1462
|
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
655
1463
|
size: 'xl',
|
|
656
1464
|
windowClass: 'tas-video-modal',
|
|
657
1465
|
backdrop: 'static',
|
|
658
|
-
keyboard: false
|
|
1466
|
+
keyboard: false,
|
|
1467
|
+
});
|
|
1468
|
+
this.videoCallModalRef.componentInstance.sessionId = this.resolvedSessionId;
|
|
1469
|
+
this.videoCallModalRef.componentInstance.token = this.token;
|
|
1470
|
+
this.videoCallModalRef.componentInstance.appointmentId = this.resolvedAppointmentId;
|
|
1471
|
+
this.videoCallModalRef.componentInstance.videoCallId = this.videoCallId;
|
|
1472
|
+
this.videoCallModalRef.componentInstance.userId = (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id;
|
|
1473
|
+
this.videoCallModalRef.componentInstance.tenant = this.tenant;
|
|
1474
|
+
this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
|
|
1475
|
+
this.videoCallModalRef.componentInstance.isReturningFromPip = false;
|
|
1476
|
+
this.videoCallModalRef.result.then(() => {
|
|
1477
|
+
this.videoCallModalRef = null;
|
|
1478
|
+
}, () => {
|
|
1479
|
+
this.videoCallModalRef = null;
|
|
659
1480
|
});
|
|
660
|
-
const sessionIdToUse = this.sessionId || this.tasService.sessionId;
|
|
661
|
-
const tokenToUse = this.token || this.tasService.token;
|
|
662
|
-
this.videoCallModalRef.componentInstance.sessionId = sessionIdToUse;
|
|
663
|
-
this.videoCallModalRef.componentInstance.token = tokenToUse;
|
|
664
|
-
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
665
|
-
this.videoCallModalRef.result.then(() => { this.videoCallModalRef = null; }, () => { this.videoCallModalRef = null; });
|
|
666
1481
|
}
|
|
667
1482
|
}
|
|
668
|
-
TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
669
|
-
TasWaitingRoomComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.4.0", type: TasWaitingRoomComponent, selector: "tas-waiting-room", inputs: { appointmentId: "appointmentId", product: "product", tenantId: "tenantId", currentUser: "currentUser", ownerUserIds: "ownerUserIds", regularUserIds: "regularUserIds", moderatorUserIds: "moderatorUserIds", existingSessionId: "existingSessionId" }, ngImport: i0, template: "<div class=\"tas-waiting-room\">\n\t<!-- Header -->\n\t<div class=\"waiting-room-header\">\n\t\t<div class=\"header-icon\">\n\t\t\t<i class=\"fa fa-video-camera\"></i>\n\t\t</div>\n\t\t<h2 class=\"header-title\">Waiting Room</h2>\n\t\t<p class=\"header-subtitle\">Prepare for your video call</p>\n\t\t<button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n\t\t\t<span aria-hidden=\"true\">×</span>\n\t\t</button>\n\t</div>\n\n\t<!-- Content -->\n\t<div class=\"waiting-room-content\">\n\t\t<!-- IDLE State - Show options -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.IDLE\">\n\t\t\t\n\t\t\t<!-- Tab switcher (only if no existingSessionId was passed) -->\n\t\t\t<div class=\"mode-tabs\" *ngIf=\"!hasExistingSession\">\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"mode-tab\" \n\t\t\t\t\t[class.active]=\"!showJoinExistingUI\"\n\t\t\t\t\t(click)=\"showJoinExistingUI = false\">\n\t\t\t\t\t<i class=\"fa fa-plus-circle\"></i>\n\t\t\t\t\tCreate New\n\t\t\t\t</button>\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"mode-tab\" \n\t\t\t\t\t[class.active]=\"showJoinExistingUI\"\n\t\t\t\t\t(click)=\"showJoinExistingUI = true\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t\tJoin Existing\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Create New Room UI -->\n\t\t\t<div class=\"mode-content\" *ngIf=\"!showJoinExistingUI && !hasExistingSession\">\n\t\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t\t<i class=\"fa fa-plus-circle\"></i>\n\t\t\t\t</div>\n\t\t\t\t<p class=\"state-message\">\n\t\t\t\t\tCreate a new video call room\n\t\t\t\t</p>\n\t\t\t\t<p class=\"state-submessage\">\n\t\t\t\t\tStart a new session for this appointment\n\t\t\t\t</p>\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t\t(click)=\"createRoom()\">\n\t\t\t\t\t<i class=\"fa fa-plus\"></i>\n\t\t\t\t\tCreate Room\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Join Existing Room UI (manual input) -->\n\t\t\t<div class=\"mode-content\" *ngIf=\"showJoinExistingUI && !hasExistingSession\">\n\t\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t</div>\n\t\t\t\t<p class=\"state-message\">\n\t\t\t\t\tJoin an existing room\n\t\t\t\t</p>\n\t\t\t\t<p class=\"state-submessage\">\n\t\t\t\t\tEnter the session ID to join\n\t\t\t\t</p>\n\t\t\t\t<div class=\"session-input-container\">\n\t\t\t\t\t<input \n\t\t\t\t\t\ttype=\"text\" \n\t\t\t\t\t\tclass=\"session-input\"\n\t\t\t\t\t\t[(ngModel)]=\"manualSessionId\"\n\t\t\t\t\t\tplaceholder=\"Enter Session ID\"\n\t\t\t\t\t\t(keyup.enter)=\"joinExistingWithManualId()\">\n\t\t\t\t\t<button \n\t\t\t\t\t\ttype=\"button\" \n\t\t\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t\t\t[disabled]=\"!manualSessionId || manualSessionId.trim() === ''\"\n\t\t\t\t\t\t(click)=\"joinExistingWithManualId()\">\n\t\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t\t\tJoin Room\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Join Existing Room UI (pre-filled session ID) -->\n\t\t\t<div class=\"mode-content\" *ngIf=\"hasExistingSession\">\n\t\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t</div>\n\t\t\t\t<p class=\"state-message\">\n\t\t\t\t\tJoin existing video call room\n\t\t\t\t</p>\n\t\t\t\t<p class=\"state-submessage\">\n\t\t\t\t\tPress the button to get access\n\t\t\t\t</p>\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t\t(click)=\"getTokenForExistingSession()\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t\tJoin Room\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- CREATING_ROOM State (only for new sessions) -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CREATING_ROOM\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tCreating video call room...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Creating</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && !isJoiningExisting\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tPreparing room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - Joining Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && isJoiningExisting\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tGetting room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Connecting</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- READY State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && !isJoiningExisting\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tRoom is ready!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- READY State - Joining Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && isJoiningExisting\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tReady to join!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Connected</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- ERROR State -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.ERROR\">\n\t\t\t<div class=\"state-icon error\">\n\t\t\t\t<i class=\"fa fa-exclamation-triangle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message error\">\n\t\t\t\tAn error occurred\n\t\t\t</p>\n\t\t\t<p class=\"error-details\" *ngIf=\"errorMessage\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn retry-btn\"\n\t\t\t\t(click)=\"retry()\">\n\t\t\t\t<i class=\"fa fa-refresh\"></i>\n\t\t\t\tRetry\n\t\t\t</button>\n\t\t</div>\n\t</div>\n\n\t<!-- Footer -->\n\t<div class=\"waiting-room-footer\">\n\t\t<button \n\t\t\ttype=\"button\" \n\t\t\tclass=\"btn cancel-btn\"\n\t\t\t(click)=\"cancel()\">\n\t\t\tCancel\n\t\t</button>\n\t</div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:32px 40px 24px;text-align:center;border-bottom:1px solid #e9ecef}.waiting-room-header .header-icon{width:72px;height:72px;margin:0 auto 16px;background:linear-gradient(135deg,#1da4b1 0%,#0077b3 100%);border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #1da4b140}.waiting-room-header .header-icon i{font-size:28px;color:#fff}.waiting-room-header .header-title{margin:0 0 8px;font-size:20px;font-weight:700;line-height:28px;color:#212529}.waiting-room-header .header-subtitle{margin:0;font-size:14px;color:#6c757d;font-weight:400}.waiting-room-header .close-btn{position:absolute;top:16px;right:16px;width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.success{color:#1da4b1}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"], directives: [{ type: i3.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { type: i4.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { type: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
670
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1483
|
+
TasWaitingRoomComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, deps: [{ token: i1.NgbActiveModal }, { token: TasService }, { token: GeolocationService }, { token: i1.NgbModal }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
1484
|
+
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_JOINABLE State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_JOINABLE\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Esperando que la sesi\u00F3n est\u00E9 disponible...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </div>\n\n <!-- READY State (Join button enabled) -->\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 <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 <!-- JOINING State (Auto-joining) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.JOINING\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Ingresando a la llamada...</p>\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: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1485
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasWaitingRoomComponent, decorators: [{
|
|
671
1486
|
type: Component,
|
|
672
|
-
args: [{ selector: 'tas-waiting-room', template: "<div class=\"tas-waiting-room\">\n\t<!-- Header -->\n\t<div class=\"waiting-room-header\">\n\t\t<div class=\"header-icon\">\n\t\t\t<i class=\"fa fa-video-camera\"></i>\n\t\t</div>\n\t\t<h2 class=\"header-title\">Waiting Room</h2>\n\t\t<p class=\"header-subtitle\">Prepare for your video call</p>\n\t\t<button type=\"button\" class=\"close-btn\" (click)=\"cancel()\" aria-label=\"Close\">\n\t\t\t<span aria-hidden=\"true\">×</span>\n\t\t</button>\n\t</div>\n\n\t<!-- Content -->\n\t<div class=\"waiting-room-content\">\n\t\t<!-- IDLE State - Show options -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.IDLE\">\n\t\t\t\n\t\t\t<!-- Tab switcher (only if no existingSessionId was passed) -->\n\t\t\t<div class=\"mode-tabs\" *ngIf=\"!hasExistingSession\">\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"mode-tab\" \n\t\t\t\t\t[class.active]=\"!showJoinExistingUI\"\n\t\t\t\t\t(click)=\"showJoinExistingUI = false\">\n\t\t\t\t\t<i class=\"fa fa-plus-circle\"></i>\n\t\t\t\t\tCreate New\n\t\t\t\t</button>\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"mode-tab\" \n\t\t\t\t\t[class.active]=\"showJoinExistingUI\"\n\t\t\t\t\t(click)=\"showJoinExistingUI = true\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t\tJoin Existing\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Create New Room UI -->\n\t\t\t<div class=\"mode-content\" *ngIf=\"!showJoinExistingUI && !hasExistingSession\">\n\t\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t\t<i class=\"fa fa-plus-circle\"></i>\n\t\t\t\t</div>\n\t\t\t\t<p class=\"state-message\">\n\t\t\t\t\tCreate a new video call room\n\t\t\t\t</p>\n\t\t\t\t<p class=\"state-submessage\">\n\t\t\t\t\tStart a new session for this appointment\n\t\t\t\t</p>\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t\t(click)=\"createRoom()\">\n\t\t\t\t\t<i class=\"fa fa-plus\"></i>\n\t\t\t\t\tCreate Room\n\t\t\t\t</button>\n\t\t\t</div>\n\n\t\t\t<!-- Join Existing Room UI (manual input) -->\n\t\t\t<div class=\"mode-content\" *ngIf=\"showJoinExistingUI && !hasExistingSession\">\n\t\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t</div>\n\t\t\t\t<p class=\"state-message\">\n\t\t\t\t\tJoin an existing room\n\t\t\t\t</p>\n\t\t\t\t<p class=\"state-submessage\">\n\t\t\t\t\tEnter the session ID to join\n\t\t\t\t</p>\n\t\t\t\t<div class=\"session-input-container\">\n\t\t\t\t\t<input \n\t\t\t\t\t\ttype=\"text\" \n\t\t\t\t\t\tclass=\"session-input\"\n\t\t\t\t\t\t[(ngModel)]=\"manualSessionId\"\n\t\t\t\t\t\tplaceholder=\"Enter Session ID\"\n\t\t\t\t\t\t(keyup.enter)=\"joinExistingWithManualId()\">\n\t\t\t\t\t<button \n\t\t\t\t\t\ttype=\"button\" \n\t\t\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t\t\t[disabled]=\"!manualSessionId || manualSessionId.trim() === ''\"\n\t\t\t\t\t\t(click)=\"joinExistingWithManualId()\">\n\t\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t\t\tJoin Room\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t</div>\n\n\t\t\t<!-- Join Existing Room UI (pre-filled session ID) -->\n\t\t\t<div class=\"mode-content\" *ngIf=\"hasExistingSession\">\n\t\t\t\t<div class=\"state-icon idle\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t</div>\n\t\t\t\t<p class=\"state-message\">\n\t\t\t\t\tJoin existing video call room\n\t\t\t\t</p>\n\t\t\t\t<p class=\"state-submessage\">\n\t\t\t\t\tPress the button to get access\n\t\t\t\t</p>\n\t\t\t\t<button \n\t\t\t\t\ttype=\"button\" \n\t\t\t\t\tclass=\"btn action-btn create-btn\"\n\t\t\t\t\t(click)=\"getTokenForExistingSession()\">\n\t\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\t\tJoin Room\n\t\t\t\t</button>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- CREATING_ROOM State (only for new sessions) -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.CREATING_ROOM\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tCreating video call room...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Creating</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && !isJoiningExisting\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tPreparing room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- GETTING_TOKEN State - Joining Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.GETTING_TOKEN && isJoiningExisting\">\n\t\t\t<div class=\"state-icon loading\">\n\t\t\t\t<div class=\"spinner\"></div>\n\t\t\t</div>\n\t\t\t<p class=\"state-message\">\n\t\t\t\tGetting room access...\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step active\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Connecting</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step\">\n\t\t\t\t\t<span class=\"step-indicator\"></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n\t\t<!-- READY State - New Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && !isJoiningExisting\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tRoom is ready!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Created</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Access</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- READY State - Joining Existing Session -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.READY && isJoiningExisting\">\n\t\t\t<div class=\"state-icon ready\">\n\t\t\t\t<i class=\"fa fa-check-circle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message success\">\n\t\t\t\tReady to join!\n\t\t\t</p>\n\t\t\t<p class=\"state-submessage\">\n\t\t\t\tYou can join the video call when ready\n\t\t\t</p>\n\t\t\t<div class=\"progress-steps\">\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Connected</span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"step completed\">\n\t\t\t\t\t<span class=\"step-indicator\"><i class=\"fa fa-check\"></i></span>\n\t\t\t\t\t<span class=\"step-label\">Ready</span>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn join-btn\"\n\t\t\t\t(click)=\"joinSession()\">\n\t\t\t\t<i class=\"fa fa-sign-in\"></i>\n\t\t\t\tJoin Session\n\t\t\t</button>\n\t\t</div>\n\n\t\t<!-- ERROR State -->\n\t\t<div class=\"state-container\" *ngIf=\"state === WaitingRoomState.ERROR\">\n\t\t\t<div class=\"state-icon error\">\n\t\t\t\t<i class=\"fa fa-exclamation-triangle\"></i>\n\t\t\t</div>\n\t\t\t<p class=\"state-message error\">\n\t\t\t\tAn error occurred\n\t\t\t</p>\n\t\t\t<p class=\"error-details\" *ngIf=\"errorMessage\">\n\t\t\t\t{{ errorMessage }}\n\t\t\t</p>\n\t\t\t<button \n\t\t\t\ttype=\"button\" \n\t\t\t\tclass=\"btn action-btn retry-btn\"\n\t\t\t\t(click)=\"retry()\">\n\t\t\t\t<i class=\"fa fa-refresh\"></i>\n\t\t\t\tRetry\n\t\t\t</button>\n\t\t</div>\n\t</div>\n\n\t<!-- Footer -->\n\t<div class=\"waiting-room-footer\">\n\t\t<button \n\t\t\ttype=\"button\" \n\t\t\tclass=\"btn cancel-btn\"\n\t\t\t(click)=\"cancel()\">\n\t\t\tCancel\n\t\t</button>\n\t</div>\n</div>\n", styles: [".tas-waiting-room{display:flex;flex-direction:column;min-height:420px;background:#ffffff;border-radius:5px;overflow:hidden}.waiting-room-header{position:relative;padding:32px 40px 24px;text-align:center;border-bottom:1px solid #e9ecef}.waiting-room-header .header-icon{width:72px;height:72px;margin:0 auto 16px;background:linear-gradient(135deg,#1da4b1 0%,#0077b3 100%);border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px #1da4b140}.waiting-room-header .header-icon i{font-size:28px;color:#fff}.waiting-room-header .header-title{margin:0 0 8px;font-size:20px;font-weight:700;line-height:28px;color:#212529}.waiting-room-header .header-subtitle{margin:0;font-size:14px;color:#6c757d;font-weight:400}.waiting-room-header .close-btn{position:absolute;top:16px;right:16px;width:32px;height:32px;border:none;background:transparent;border-radius:4px;color:#6c757d;cursor:pointer;transition:all .2s ease;font-size:20px}.waiting-room-header .close-btn:hover{background:#f8f9fa;color:#212529}.waiting-room-content{flex:1;display:flex;align-items:center;justify-content:center;padding:32px 40px;background:#ffffff}.state-container{text-align:center;max-width:400px;width:100%}.mode-tabs{display:flex;justify-content:center;gap:8px;margin-bottom:24px;padding:4px;background:#f8f9fa;border-radius:8px}.mode-tab{flex:1;padding:10px 16px;font-size:13px;font-weight:600;border:none;border-radius:6px;background:transparent;color:#6c757d;cursor:pointer;transition:all .2s ease;display:flex;align-items:center;justify-content:center;gap:8px}.mode-tab i{font-size:14px}.mode-tab:hover{color:#212529}.mode-tab.active{background:#ffffff;color:#1da4b1;box-shadow:0 2px 8px #00000014}.mode-content{animation:fadeIn .3s ease}.session-input-container{display:flex;flex-direction:column;gap:16px;margin-top:8px}.session-input{width:100%;padding:14px 16px;font-size:14px;border:2px solid #e9ecef;border-radius:8px;background:#ffffff;color:#212529;transition:all .2s ease;text-align:center;font-family:Monaco,Consolas,monospace;letter-spacing:.5px}.session-input::placeholder{color:#6c757d;font-family:inherit;letter-spacing:normal}.session-input:focus{outline:none;border-color:#1da4b1;box-shadow:0 0 0 3px #1da4b126}.session-input:hover:not(:focus){border-color:#6c757d}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.state-icon{width:80px;height:80px;margin:0 auto 24px;border-radius:50%;display:flex;align-items:center;justify-content:center}.state-icon i{font-size:36px}.state-icon.idle{background:rgba(29,164,177,.1);border:2px dashed #1da4b1}.state-icon.idle i{color:#1da4b1}.state-icon.loading{background:rgba(29,164,177,.1);border:2px solid #1da4b1}.state-icon.ready{background:linear-gradient(135deg,#1da4b1 0%,#38b89a 100%);box-shadow:0 4px 16px #1da4b14d}.state-icon.ready i{color:#fff}.state-icon.error{background:rgba(238,49,107,.1);border:2px solid #ee316b}.state-icon.error i{color:#ee316b}.spinner{width:40px;height:40px;border:3px solid #e9ecef;border-top-color:#1da4b1;border-radius:50%;animation:spin 1s linear infinite}.state-message{font-size:16px;font-weight:600;margin:0 0 8px;color:#212529;line-height:24px}.state-message.success{color:#1da4b1}.state-message.error{color:#ee316b}.state-submessage{font-size:14px;color:#6c757d;margin:0 0 24px;font-weight:400}.error-details{font-size:13px;color:#ee316b;margin:0 0 24px;padding:12px 16px;background:rgba(238,49,107,.08);border-radius:8px;border:1px solid rgba(238,49,107,.2)}.progress-steps{display:flex;justify-content:center;gap:24px;margin:24px 0 32px}.step{display:flex;flex-direction:column;align-items:center;gap:8px}.step .step-indicator{width:32px;height:32px;border-radius:50%;background:#f8f9fa;border:2px solid #e9ecef;display:flex;align-items:center;justify-content:center;transition:all .3s ease}.step .step-indicator i{font-size:12px;color:#fff}.step .step-label{font-size:11px;color:#6c757d;text-transform:uppercase;letter-spacing:.5px;font-weight:500}.step.active .step-indicator{background:rgba(29,164,177,.1);border-color:#1da4b1;animation:pulse-active 1.5s infinite}.step.active .step-label{color:#1da4b1;font-weight:600}.step.completed .step-indicator{background:#1da4b1;border-color:#1da4b1}.step.completed .step-label{color:#1da4b1;font-weight:600}.action-btn{padding:12px 32px;font-size:16px;font-weight:600;border-radius:4px;border:none;cursor:pointer;transition:all .2s ease;display:inline-flex;align-items:center;gap:10px}.action-btn i{font-size:16px}.action-btn.create-btn{background:#0077b3;color:#fff;box-shadow:0 2px 8px #0077b340}.action-btn.create-btn:hover{background:#005c8a;box-shadow:0 4px 12px #0077b359}.action-btn.create-btn:active{transform:translateY(1px)}.action-btn.join-btn{background:#1da4b1;color:#fff;box-shadow:0 2px 8px #1da4b140;animation:pulse-ready 2s infinite}.action-btn.join-btn:hover{background:#17848e;box-shadow:0 4px 12px #1da4b159}.action-btn.join-btn:active{transform:translateY(1px)}.action-btn.retry-btn{background:transparent;color:#6c757d;border:1px solid #e9ecef}.action-btn.retry-btn:hover{background:#f8f9fa;border-color:#6c757d;color:#212529}.waiting-room-footer{padding:16px 40px 24px;background:#ffffff;border-top:1px solid #e9ecef;display:flex;justify-content:center}.waiting-room-footer .cancel-btn{padding:10px 24px;font-size:14px;font-weight:600;border-radius:4px;background:transparent;color:#6c757d;border:none;cursor:pointer;transition:all .2s ease}.waiting-room-footer .cancel-btn:hover{background:#f8f9fa;color:#212529}@keyframes spin{to{transform:rotate(360deg)}}@keyframes pulse-active{0%,to{box-shadow:0 0 #1da4b166}50%{box-shadow:0 0 0 8px #1da4b100}}@keyframes pulse-ready{0%,to{box-shadow:0 2px 8px #1da4b140}50%{box-shadow:0 4px 16px #1da4b166}}@media (max-width: 576px){.tas-waiting-room{min-height:380px}.waiting-room-header{padding:24px 24px 20px}.waiting-room-header .header-icon{width:56px;height:56px}.waiting-room-header .header-icon i{font-size:22px}.waiting-room-header .header-title{font-size:18px}.waiting-room-content{padding:24px}.state-icon{width:64px;height:64px}.state-icon i{font-size:28px}.spinner{width:32px;height:32px}.progress-steps{gap:12px}.step .step-indicator{width:28px;height:28px}.step .step-label{font-size:9px}.action-btn{padding:10px 24px;font-size:14px}.waiting-room-footer{padding:16px 24px 20px}}\n"] }]
|
|
673
|
-
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: i1.NgbModal }]; }, propDecorators: {
|
|
1487
|
+
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_JOINABLE State -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.WAITING_FOR_JOINABLE\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Esperando que la sesi\u00F3n est\u00E9 disponible...</p>\n <p class=\"state-submessage\">Por favor, permanec\u00E9 en esta pantalla.</p>\n </div>\n\n <!-- READY State (Join button enabled) -->\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 <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 <!-- JOINING State (Auto-joining) -->\n <div class=\"state-container\" *ngIf=\"state === WaitingRoomState.JOINING\">\n <div class=\"state-icon loading\">\n <div class=\"spinner\"></div>\n </div>\n <p class=\"state-message\">Ingresando a la llamada...</p>\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"] }]
|
|
1488
|
+
}], ctorParameters: function () { return [{ type: i1.NgbActiveModal }, { type: TasService }, { type: GeolocationService }, { type: i1.NgbModal }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { roomType: [{
|
|
674
1489
|
type: Input
|
|
675
|
-
}],
|
|
1490
|
+
}], entityId: [{
|
|
676
1491
|
type: Input
|
|
677
|
-
}],
|
|
1492
|
+
}], tenant: [{
|
|
678
1493
|
type: Input
|
|
679
|
-
}],
|
|
680
|
-
type: Input
|
|
681
|
-
}], ownerUserIds: [{
|
|
682
|
-
type: Input
|
|
683
|
-
}], regularUserIds: [{
|
|
1494
|
+
}], businessRole: [{
|
|
684
1495
|
type: Input
|
|
685
|
-
}],
|
|
686
|
-
type: Input
|
|
687
|
-
}], existingSessionId: [{
|
|
1496
|
+
}], currentUser: [{
|
|
688
1497
|
type: Input
|
|
689
1498
|
}] } });
|
|
690
1499
|
|
|
@@ -692,117 +1501,206 @@ class TasButtonComponent {
|
|
|
692
1501
|
constructor(modalService, tasService) {
|
|
693
1502
|
this.modalService = modalService;
|
|
694
1503
|
this.tasService = tasService;
|
|
695
|
-
|
|
696
|
-
this.
|
|
697
|
-
this.
|
|
698
|
-
|
|
699
|
-
this.
|
|
700
|
-
|
|
701
|
-
this.existingSessionId = "";
|
|
702
|
-
/** Optional: Custom button text */
|
|
703
|
-
this.buttonText = "Iniciar TAS";
|
|
1504
|
+
// Status endpoint params
|
|
1505
|
+
this.roomType = TasRoomType.TAS;
|
|
1506
|
+
this.businessRole = TasBusinessRole.USER;
|
|
1507
|
+
// Style customization
|
|
1508
|
+
this.variant = 'default';
|
|
1509
|
+
this.buttonLabel = 'Iniciar TAS';
|
|
704
1510
|
this.isLoading = false;
|
|
1511
|
+
// Status check state
|
|
1512
|
+
this.isCheckingStatus = false;
|
|
1513
|
+
this.isStatusError = false;
|
|
1514
|
+
this.statusErrorMessage = '';
|
|
1515
|
+
this.isJoinable = false; // Tracks joinable field from status response
|
|
705
1516
|
this.subscriptions = new Subscription();
|
|
706
1517
|
this.currentModalRef = null;
|
|
707
1518
|
this.videoCallModalRef = null;
|
|
1519
|
+
this.statusPollingInterval = null;
|
|
1520
|
+
this.STATUS_POLL_INTERVAL_MS = 30000; // 30 seconds
|
|
708
1521
|
}
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1522
|
+
/** Whether user is backoffice (or admin/manager) */
|
|
1523
|
+
get isBackoffice() {
|
|
1524
|
+
return (this.businessRole === TasBusinessRole.BACKOFFICE ||
|
|
1525
|
+
this.businessRole === TasBusinessRole.ADMIN_MANAGER ||
|
|
1526
|
+
this.businessRole === TasBusinessRole.MANAGER);
|
|
1527
|
+
}
|
|
1528
|
+
/** Whether the button should be disabled */
|
|
1529
|
+
get isDisabled() {
|
|
1530
|
+
return this.isLoading || this.isStatusError || !this.isJoinable;
|
|
1531
|
+
}
|
|
1532
|
+
/** Reason why the button is disabled (for tooltip) */
|
|
1533
|
+
get disabledReason() {
|
|
1534
|
+
if (this.isLoading) {
|
|
1535
|
+
return '';
|
|
1536
|
+
}
|
|
1537
|
+
if (this.isStatusError) {
|
|
1538
|
+
return this.statusErrorMessage || 'Error al verificar el estado';
|
|
712
1539
|
}
|
|
1540
|
+
if (!this.isJoinable) {
|
|
1541
|
+
return 'Todavía no es el horario de la llamada';
|
|
1542
|
+
}
|
|
1543
|
+
return '';
|
|
1544
|
+
}
|
|
1545
|
+
ngOnInit() {
|
|
713
1546
|
// Subscribe to viewMode to handle PiP return
|
|
714
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
715
|
-
// Reopen video call modal when returning from PiP
|
|
716
|
-
if (mode === ViewMode.FULLSCREEN &&
|
|
717
|
-
this.tasService.isCallActive() &&
|
|
718
|
-
!this.videoCallModalRef) {
|
|
719
|
-
const sessionId = this.tasService.sessionId;
|
|
720
|
-
const token = this.tasService.token;
|
|
721
|
-
if (sessionId && token) {
|
|
722
|
-
this.openVideoCallModal(true);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
1547
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
725
1548
|
// When entering PiP, clear the videoCallModalRef since modal will close
|
|
726
1549
|
if (mode === ViewMode.PIP) {
|
|
727
1550
|
this.videoCallModalRef = null;
|
|
728
1551
|
}
|
|
729
1552
|
}));
|
|
1553
|
+
// Start status checking
|
|
1554
|
+
this.startStatusPolling();
|
|
730
1555
|
}
|
|
731
1556
|
ngOnDestroy() {
|
|
732
1557
|
this.subscriptions.unsubscribe();
|
|
1558
|
+
this.stopStatusPolling();
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Start polling status every 30 seconds
|
|
1562
|
+
*/
|
|
1563
|
+
startStatusPolling() {
|
|
1564
|
+
// Initial status check
|
|
1565
|
+
this.checkStatus();
|
|
1566
|
+
// Set up periodic polling
|
|
1567
|
+
this.statusPollingInterval = setInterval(() => {
|
|
1568
|
+
this.checkStatus();
|
|
1569
|
+
}, this.STATUS_POLL_INTERVAL_MS);
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* Stop status polling
|
|
1573
|
+
*/
|
|
1574
|
+
stopStatusPolling() {
|
|
1575
|
+
if (this.statusPollingInterval) {
|
|
1576
|
+
clearInterval(this.statusPollingInterval);
|
|
1577
|
+
this.statusPollingInterval = null;
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
/**
|
|
1581
|
+
* Check status endpoint to determine if button should be enabled
|
|
1582
|
+
*/
|
|
1583
|
+
checkStatus() {
|
|
1584
|
+
// Skip if required inputs are not available
|
|
1585
|
+
if (!this.tenant || !this.entityId) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
this.isCheckingStatus = true;
|
|
1589
|
+
this.statusErrorMessage = '';
|
|
1590
|
+
this.subscriptions.add(this.tasService.getProxyVideoStatus({
|
|
1591
|
+
roomType: this.roomType,
|
|
1592
|
+
entityId: this.entityId,
|
|
1593
|
+
tenant: this.tenant,
|
|
1594
|
+
businessRole: this.businessRole,
|
|
1595
|
+
}).subscribe({
|
|
1596
|
+
next: (response) => {
|
|
1597
|
+
var _a, _b, _c;
|
|
1598
|
+
// Check if response is actually an error (some HTTP adapters return errors in next)
|
|
1599
|
+
// Also check for undefined/null or missing content
|
|
1600
|
+
const isErrorResponse = !response ||
|
|
1601
|
+
!response.content ||
|
|
1602
|
+
(response === null || response === void 0 ? void 0 : response.ok) === false ||
|
|
1603
|
+
(response === null || response === void 0 ? void 0 : response.status) >= 400 ||
|
|
1604
|
+
(response === null || response === void 0 ? void 0 : response.error) ||
|
|
1605
|
+
(response === null || response === void 0 ? void 0 : response.name) === 'HttpErrorResponse';
|
|
1606
|
+
if (isErrorResponse) {
|
|
1607
|
+
this.isCheckingStatus = false;
|
|
1608
|
+
this.isStatusError = true;
|
|
1609
|
+
this.statusErrorMessage = ((_a = response === null || response === void 0 ? void 0 : response.error) === null || _a === void 0 ? void 0 : _a.message) || (response === null || response === void 0 ? void 0 : response.message) || 'Error checking status';
|
|
1610
|
+
}
|
|
1611
|
+
else {
|
|
1612
|
+
this.isCheckingStatus = false;
|
|
1613
|
+
this.isStatusError = false;
|
|
1614
|
+
this.statusErrorMessage = '';
|
|
1615
|
+
// Update joinable state from response
|
|
1616
|
+
this.isJoinable = (_c = (_b = response.content) === null || _b === void 0 ? void 0 : _b.joinable) !== null && _c !== void 0 ? _c : false;
|
|
1617
|
+
}
|
|
1618
|
+
},
|
|
1619
|
+
error: (err) => {
|
|
1620
|
+
var _a;
|
|
1621
|
+
this.isCheckingStatus = false;
|
|
1622
|
+
this.isStatusError = true;
|
|
1623
|
+
this.statusErrorMessage = ((_a = err === null || err === void 0 ? void 0 : err.error) === null || _a === void 0 ? void 0 : _a.message) || (err === null || err === void 0 ? void 0 : err.message) || 'Error checking status';
|
|
1624
|
+
},
|
|
1625
|
+
}));
|
|
733
1626
|
}
|
|
734
1627
|
onClick() {
|
|
735
1628
|
var _a;
|
|
736
|
-
if (!this.
|
|
737
|
-
|
|
1629
|
+
if (!this.tenant || !((_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
1630
|
+
return;
|
|
1631
|
+
}
|
|
1632
|
+
if (!this.entityId) {
|
|
738
1633
|
return;
|
|
739
1634
|
}
|
|
740
1635
|
this.openWaitingRoomModal();
|
|
741
1636
|
}
|
|
742
1637
|
openWaitingRoomModal() {
|
|
743
1638
|
this.currentModalRef = this.modalService.open(TasWaitingRoomComponent, {
|
|
744
|
-
size:
|
|
745
|
-
windowClass:
|
|
746
|
-
backdrop:
|
|
1639
|
+
size: 'lg',
|
|
1640
|
+
windowClass: 'tas-waiting-room-modal',
|
|
1641
|
+
backdrop: 'static',
|
|
747
1642
|
keyboard: false,
|
|
748
|
-
centered: true
|
|
1643
|
+
centered: true,
|
|
749
1644
|
});
|
|
750
1645
|
// Pass all necessary inputs to the waiting room component
|
|
751
|
-
this.currentModalRef.componentInstance.
|
|
752
|
-
this.currentModalRef.componentInstance.
|
|
753
|
-
this.currentModalRef.componentInstance.
|
|
1646
|
+
this.currentModalRef.componentInstance.roomType = this.roomType;
|
|
1647
|
+
this.currentModalRef.componentInstance.entityId = this.entityId;
|
|
1648
|
+
this.currentModalRef.componentInstance.tenant = this.tenant;
|
|
1649
|
+
this.currentModalRef.componentInstance.businessRole = this.businessRole;
|
|
754
1650
|
this.currentModalRef.componentInstance.currentUser = this.currentUser;
|
|
755
|
-
this.currentModalRef.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
this.currentModalRef.result.then(() => { this.currentModalRef = null; }, () => { this.currentModalRef = null; });
|
|
1651
|
+
this.currentModalRef.result.then(() => {
|
|
1652
|
+
this.currentModalRef = null;
|
|
1653
|
+
}, () => {
|
|
1654
|
+
this.currentModalRef = null;
|
|
1655
|
+
});
|
|
761
1656
|
}
|
|
762
1657
|
openVideoCallModal(isReturningFromPip = false) {
|
|
763
1658
|
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
764
1659
|
size: 'xl',
|
|
765
1660
|
windowClass: 'tas-video-modal',
|
|
766
1661
|
backdrop: 'static',
|
|
767
|
-
keyboard: false
|
|
1662
|
+
keyboard: false,
|
|
768
1663
|
});
|
|
769
1664
|
this.videoCallModalRef.componentInstance.sessionId = this.tasService.sessionId;
|
|
770
1665
|
this.videoCallModalRef.componentInstance.token = this.tasService.token;
|
|
1666
|
+
this.videoCallModalRef.componentInstance.businessRole = this.businessRole;
|
|
771
1667
|
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
772
|
-
this.videoCallModalRef.result.then(() => {
|
|
1668
|
+
this.videoCallModalRef.result.then(() => {
|
|
1669
|
+
this.videoCallModalRef = null;
|
|
1670
|
+
}, () => {
|
|
1671
|
+
this.videoCallModalRef = null;
|
|
1672
|
+
});
|
|
773
1673
|
}
|
|
774
1674
|
}
|
|
775
|
-
TasButtonComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
776
|
-
TasButtonComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
777
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1675
|
+
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 });
|
|
1676
|
+
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", variant: "variant", buttonLabel: "buttonLabel" }, ngImport: i0, template: "<span\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.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}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;padding:10px 24px;font-weight:500;margin-right:0}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"], directives: [{ type: i1.NgbTooltip, selector: "[ngbTooltip]", inputs: ["animation", "autoClose", "placement", "triggers", "container", "disableTooltip", "tooltipClass", "openDelay", "closeDelay", "ngbTooltip"], outputs: ["shown", "hidden"], exportAs: ["ngbTooltip"] }, { type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1677
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasButtonComponent, decorators: [{
|
|
778
1678
|
type: Component,
|
|
779
|
-
args: [{ selector:
|
|
780
|
-
}], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: {
|
|
781
|
-
type: Input
|
|
782
|
-
}], product: [{
|
|
1679
|
+
args: [{ selector: 'tas-btn', template: "<span\n [ngbTooltip]=\"isDisabled && disabledReason ? disabledReason : null\"\n container=\"body\"\n placement=\"top\"\n tooltipClass=\"tas-btn-tooltip\"\n>\n <button\n type=\"button\"\n class=\"btn btn-primary tas-btn\"\n [class.tas-btn--teal]=\"variant === 'teal'\"\n (click)=\"onClick()\"\n [disabled]=\"isDisabled\"\n >\n <i class=\"fa fa-video-camera\" aria-hidden=\"true\" *ngIf=\"!isLoading\"></i>\n <span *ngIf=\"!isLoading\">{{ buttonLabel }}</span>\n <span *ngIf=\"isLoading\">Processing...</span>\n </button>\n</span>\n", styles: [":host{display:inline-block}.tas-btn{background-color:#ee316b!important;color:#fff!important;border-color:#ee316b!important;margin-right:24px;display:flex;padding:6px 14px;justify-content:center;align-items:center;gap:7px;flex-shrink:0;flex-grow:0}.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}.tas-btn--teal{background-color:#0097a7!important;border-color:#0097a7!important;border-radius:24px;padding:10px 24px;font-weight:500;margin-right:0}.tas-btn--teal:hover:not(:disabled){background-color:#00838f!important;border-color:#00838f!important}\n"] }]
|
|
1680
|
+
}], ctorParameters: function () { return [{ type: i1.NgbModal }, { type: TasService }]; }, propDecorators: { roomType: [{
|
|
783
1681
|
type: Input
|
|
784
|
-
}],
|
|
1682
|
+
}], entityId: [{
|
|
785
1683
|
type: Input
|
|
786
|
-
}],
|
|
787
|
-
type: Input
|
|
788
|
-
}], ownerUserIds: [{
|
|
1684
|
+
}], tenant: [{
|
|
789
1685
|
type: Input
|
|
790
|
-
}],
|
|
1686
|
+
}], businessRole: [{
|
|
791
1687
|
type: Input
|
|
792
|
-
}],
|
|
1688
|
+
}], currentUser: [{
|
|
793
1689
|
type: Input
|
|
794
|
-
}],
|
|
1690
|
+
}], variant: [{
|
|
795
1691
|
type: Input
|
|
796
|
-
}],
|
|
1692
|
+
}], buttonLabel: [{
|
|
797
1693
|
type: Input
|
|
798
1694
|
}] } });
|
|
799
1695
|
|
|
800
1696
|
class TasFloatingCallComponent {
|
|
801
|
-
constructor(tasService) {
|
|
1697
|
+
constructor(tasService, modalService) {
|
|
802
1698
|
this.tasService = tasService;
|
|
1699
|
+
this.modalService = modalService;
|
|
803
1700
|
this.isVisible = false;
|
|
804
1701
|
this.isMuted = false;
|
|
805
1702
|
this.subscriptions = new Subscription();
|
|
1703
|
+
this.videoCallModalRef = null;
|
|
806
1704
|
// Margin from screen edges (in pixels)
|
|
807
1705
|
this.PIP_MARGIN = 20;
|
|
808
1706
|
}
|
|
@@ -815,6 +1713,7 @@ class TasFloatingCallComponent {
|
|
|
815
1713
|
}
|
|
816
1714
|
// Public Methods
|
|
817
1715
|
onExpand() {
|
|
1716
|
+
this.openVideoCallModal(true);
|
|
818
1717
|
this.tasService.exitPipMode();
|
|
819
1718
|
}
|
|
820
1719
|
onHangUp() {
|
|
@@ -826,23 +1725,45 @@ class TasFloatingCallComponent {
|
|
|
826
1725
|
// Private Methods
|
|
827
1726
|
setupSubscriptions() {
|
|
828
1727
|
// Call state subscription
|
|
829
|
-
this.subscriptions.add(this.tasService.callState$.subscribe(state => {
|
|
1728
|
+
this.subscriptions.add(this.tasService.callState$.subscribe((state) => {
|
|
830
1729
|
if (state === CallState.DISCONNECTED) {
|
|
831
1730
|
this.isVisible = false;
|
|
832
1731
|
}
|
|
833
1732
|
}));
|
|
834
1733
|
// View mode subscription
|
|
835
|
-
this.subscriptions.add(this.tasService.viewMode$.subscribe(mode => {
|
|
1734
|
+
this.subscriptions.add(this.tasService.viewMode$.subscribe((mode) => {
|
|
836
1735
|
this.isVisible = mode === ViewMode.PIP && this.tasService.isCallActive();
|
|
837
1736
|
if (this.isVisible) {
|
|
838
1737
|
setTimeout(() => this.initInteract(), 100);
|
|
1738
|
+
// Clear modal ref if we enter PiP mode (modal closes itself)
|
|
1739
|
+
this.videoCallModalRef = null;
|
|
839
1740
|
}
|
|
840
1741
|
}));
|
|
841
1742
|
// Mute state subscription
|
|
842
|
-
this.subscriptions.add(this.tasService.isMuted$.subscribe(muted => {
|
|
1743
|
+
this.subscriptions.add(this.tasService.isMuted$.subscribe((muted) => {
|
|
843
1744
|
this.isMuted = muted;
|
|
844
1745
|
}));
|
|
845
1746
|
}
|
|
1747
|
+
openVideoCallModal(isReturningFromPip = false) {
|
|
1748
|
+
if (this.videoCallModalRef) {
|
|
1749
|
+
return;
|
|
1750
|
+
}
|
|
1751
|
+
this.videoCallModalRef = this.modalService.open(TasVideocallComponent, {
|
|
1752
|
+
size: 'xl',
|
|
1753
|
+
windowClass: 'tas-video-modal',
|
|
1754
|
+
backdrop: 'static',
|
|
1755
|
+
keyboard: false,
|
|
1756
|
+
});
|
|
1757
|
+
this.videoCallModalRef.componentInstance.sessionId = this.tasService.sessionId;
|
|
1758
|
+
this.videoCallModalRef.componentInstance.token = this.tasService.token;
|
|
1759
|
+
this.videoCallModalRef.componentInstance.businessRole = this.tasService.businessRole;
|
|
1760
|
+
this.videoCallModalRef.componentInstance.isReturningFromPip = isReturningFromPip;
|
|
1761
|
+
this.videoCallModalRef.result.then(() => {
|
|
1762
|
+
this.videoCallModalRef = null;
|
|
1763
|
+
}, () => {
|
|
1764
|
+
this.videoCallModalRef = null;
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
846
1767
|
initInteract() {
|
|
847
1768
|
interact('.tas-floating-container').unset();
|
|
848
1769
|
// Create restriction area with margin
|
|
@@ -853,17 +1774,15 @@ class TasFloatingCallComponent {
|
|
|
853
1774
|
left: margin,
|
|
854
1775
|
top: margin,
|
|
855
1776
|
right: window.innerWidth - margin,
|
|
856
|
-
bottom: window.innerHeight - margin
|
|
1777
|
+
bottom: window.innerHeight - margin,
|
|
857
1778
|
};
|
|
858
1779
|
},
|
|
859
|
-
elementRect: { left: 0, right: 1, top: 0, bottom: 1 }
|
|
1780
|
+
elementRect: { left: 0, right: 1, top: 0, bottom: 1 },
|
|
860
1781
|
};
|
|
861
1782
|
interact('.tas-floating-container')
|
|
862
1783
|
.draggable({
|
|
863
1784
|
inertia: true,
|
|
864
|
-
modifiers: [
|
|
865
|
-
interact.modifiers.restrict(restrictToBodyWithMargin)
|
|
866
|
-
],
|
|
1785
|
+
modifiers: [interact.modifiers.restrict(restrictToBodyWithMargin)],
|
|
867
1786
|
autoScroll: false,
|
|
868
1787
|
listeners: {
|
|
869
1788
|
move: (event) => {
|
|
@@ -873,8 +1792,8 @@ class TasFloatingCallComponent {
|
|
|
873
1792
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
874
1793
|
target.setAttribute('data-x', String(x));
|
|
875
1794
|
target.setAttribute('data-y', String(y));
|
|
876
|
-
}
|
|
877
|
-
}
|
|
1795
|
+
},
|
|
1796
|
+
},
|
|
878
1797
|
})
|
|
879
1798
|
.resizable({
|
|
880
1799
|
edges: { left: false, right: true, bottom: true, top: false },
|
|
@@ -892,7 +1811,7 @@ class TasFloatingCallComponent {
|
|
|
892
1811
|
target.style.transform = `translate(${x}px, ${y}px)`;
|
|
893
1812
|
target.setAttribute('data-x', String(x));
|
|
894
1813
|
target.setAttribute('data-y', String(y));
|
|
895
|
-
}
|
|
1814
|
+
},
|
|
896
1815
|
},
|
|
897
1816
|
modifiers: [
|
|
898
1817
|
interact.modifiers.restrictEdges({
|
|
@@ -900,25 +1819,126 @@ class TasFloatingCallComponent {
|
|
|
900
1819
|
left: margin,
|
|
901
1820
|
top: margin,
|
|
902
1821
|
right: window.innerWidth - margin,
|
|
903
|
-
bottom: window.innerHeight - margin
|
|
904
|
-
}
|
|
1822
|
+
bottom: window.innerHeight - margin,
|
|
1823
|
+
},
|
|
905
1824
|
}),
|
|
906
1825
|
interact.modifiers.restrictSize({
|
|
907
1826
|
min: { width: 200, height: 130 },
|
|
908
|
-
max: { width: 500, height: 350 }
|
|
1827
|
+
max: { width: 500, height: 350 },
|
|
909
1828
|
}),
|
|
910
|
-
interact.modifiers.aspectRatio({ ratio: 'preserve' })
|
|
1829
|
+
interact.modifiers.aspectRatio({ ratio: 'preserve' }),
|
|
911
1830
|
],
|
|
912
|
-
inertia: true
|
|
1831
|
+
inertia: true,
|
|
913
1832
|
});
|
|
914
1833
|
}
|
|
915
1834
|
}
|
|
916
|
-
TasFloatingCallComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
917
|
-
TasFloatingCallComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.
|
|
918
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.
|
|
1835
|
+
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 });
|
|
1836
|
+
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"] });
|
|
1837
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasFloatingCallComponent, decorators: [{
|
|
919
1838
|
type: Component,
|
|
920
|
-
args: [{ selector: 'tas-floating-call', template: "<div class=\"tas-floating-container\" [class.visible]=\"isVisible\">\n
|
|
921
|
-
}], ctorParameters: function () { return [{ type: TasService }]; } });
|
|
1839
|
+
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"] }]
|
|
1840
|
+
}], ctorParameters: function () { return [{ type: TasService }, { type: i1.NgbModal }]; } });
|
|
1841
|
+
|
|
1842
|
+
class TasIncomingAppointmentComponent {
|
|
1843
|
+
constructor(tasService) {
|
|
1844
|
+
this.tasService = tasService;
|
|
1845
|
+
// Passthrough inputs for tas-btn
|
|
1846
|
+
this.roomType = TasRoomType.TAS;
|
|
1847
|
+
this.businessRole = TasBusinessRole.USER;
|
|
1848
|
+
this.enterCall = new EventEmitter();
|
|
1849
|
+
this.appointment = null;
|
|
1850
|
+
this.isLoading = true;
|
|
1851
|
+
this.hasError = false;
|
|
1852
|
+
this.subscriptions = new Subscription();
|
|
1853
|
+
}
|
|
1854
|
+
ngOnInit() {
|
|
1855
|
+
this.loadAppointments();
|
|
1856
|
+
}
|
|
1857
|
+
ngOnDestroy() {
|
|
1858
|
+
this.subscriptions.unsubscribe();
|
|
1859
|
+
}
|
|
1860
|
+
loadAppointments() {
|
|
1861
|
+
const today = new Date();
|
|
1862
|
+
const in7Days = new Date(today);
|
|
1863
|
+
in7Days.setDate(today.getDate() + 7);
|
|
1864
|
+
this.subscriptions.add(this.tasService
|
|
1865
|
+
.getAppointments({ fromDate: this.formatDate(today), toDate: this.formatDate(in7Days) })
|
|
1866
|
+
.subscribe({
|
|
1867
|
+
next: (response) => {
|
|
1868
|
+
// Handle both array response and wrapped response (e.g., { content: [...] })
|
|
1869
|
+
const appointments = Array.isArray(response)
|
|
1870
|
+
? response
|
|
1871
|
+
: (response === null || response === void 0 ? void 0 : response.content) || [];
|
|
1872
|
+
// Filter for confirmed appointments and get the first one
|
|
1873
|
+
const confirmed = appointments.filter((a) => a.status === AppointmentStatus.CONFIRMED);
|
|
1874
|
+
this.appointment = confirmed.length > 0 ? confirmed[0] : null;
|
|
1875
|
+
this.isLoading = false;
|
|
1876
|
+
},
|
|
1877
|
+
error: () => {
|
|
1878
|
+
this.hasError = true;
|
|
1879
|
+
this.isLoading = false;
|
|
1880
|
+
},
|
|
1881
|
+
}));
|
|
1882
|
+
}
|
|
1883
|
+
onEnterCall() {
|
|
1884
|
+
if (this.appointment) {
|
|
1885
|
+
this.enterCall.emit(this.appointment);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Format date to Spanish format: "Lunes 8 de diciembre"
|
|
1890
|
+
*/
|
|
1891
|
+
get formattedDate() {
|
|
1892
|
+
if (!this.appointment)
|
|
1893
|
+
return '';
|
|
1894
|
+
const [year, month, day] = this.appointment.date.split('-').map(Number);
|
|
1895
|
+
const date = new Date(year, month - 1, day);
|
|
1896
|
+
const dayNames = [
|
|
1897
|
+
'Domingo', 'Lunes', 'Martes', 'Miércoles',
|
|
1898
|
+
'Jueves', 'Viernes', 'Sábado'
|
|
1899
|
+
];
|
|
1900
|
+
const monthNames = [
|
|
1901
|
+
'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio',
|
|
1902
|
+
'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'
|
|
1903
|
+
];
|
|
1904
|
+
const dayName = dayNames[date.getDay()];
|
|
1905
|
+
const dayNum = date.getDate();
|
|
1906
|
+
const monthName = monthNames[date.getMonth()];
|
|
1907
|
+
return `${dayName} ${dayNum} de ${monthName}`;
|
|
1908
|
+
}
|
|
1909
|
+
/**
|
|
1910
|
+
* Format time range: "9:00 - 9:30"
|
|
1911
|
+
*/
|
|
1912
|
+
get formattedTimeRange() {
|
|
1913
|
+
if (!this.appointment)
|
|
1914
|
+
return '';
|
|
1915
|
+
return `${this.appointment.startTime} - ${this.appointment.endTime}`;
|
|
1916
|
+
}
|
|
1917
|
+
formatDate(date) {
|
|
1918
|
+
const year = date.getFullYear();
|
|
1919
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
1920
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
1921
|
+
return `${year}-${month}-${day}`;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
TasIncomingAppointmentComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, deps: [{ token: TasService }], target: i0.ɵɵFactoryTarget.Component });
|
|
1925
|
+
TasIncomingAppointmentComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "12.0.0", version: "13.3.12", type: TasIncomingAppointmentComponent, selector: "tas-incoming-appointment", inputs: { roomType: "roomType", entityId: "entityId", tenant: "tenant", businessRole: "businessRole", currentUser: "currentUser" }, outputs: { enterCall: "enterCall" }, ngImport: i0, template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && !appointment\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointment state -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointment\">\n \n <div class=\"appointment-card\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"appointment.title\" [size]=\"48\"></tas-avatar>\n \n <span class=\"doctor-name\">{{ appointment.title }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formattedDate }}</span>\n <span class=\"time\">{{ formattedTimeRange }}</span>\n </div>\n <tas-btn\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"roomType\"\n [entityId]=\"entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"], components: [{ type: TasAvatarComponent, selector: "tas-avatar", inputs: ["name", "size"] }, { type: TasButtonComponent, selector: "tas-btn", inputs: ["roomType", "entityId", "tenant", "businessRole", "currentUser", "variant", "buttonLabel"] }], directives: [{ type: i4.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] });
|
|
1926
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasIncomingAppointmentComponent, decorators: [{
|
|
1927
|
+
type: Component,
|
|
1928
|
+
args: [{ selector: 'tas-incoming-appointment', template: "<div class=\"incoming-appointment-card\">\n <h3 class=\"card-title\">Pr\u00F3ximo turno</h3>\n\n <!-- Loading state -->\n <div class=\"card-content\" *ngIf=\"isLoading\">\n <div class=\"loading-spinner\"></div>\n </div>\n\n <!-- Empty state -->\n <div class=\"card-content empty-state\" *ngIf=\"!isLoading && !appointment\">\n <div class=\"icon-container\">\n <div class=\"icon-circle\">\n <i class=\"fa fa-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"sparkle sparkle-1\">\u2726</span>\n <span class=\"sparkle sparkle-2\">\u2726</span>\n <span class=\"sparkle sparkle-3\">\u2726</span>\n <span class=\"sparkle sparkle-4\">\u2726</span>\n </div>\n <h4 class=\"empty-title\">Todav\u00EDa no ten\u00E9s turnos agendados</h4>\n <p class=\"empty-subtitle\">\n En caso de que Medicina Laboral requiera una consulta, lo ver\u00E1s en esta secci\u00F3n.\n </p>\n </div>\n\n <!-- Appointment state -->\n <div class=\"card-content appointment-state\" *ngIf=\"!isLoading && appointment\">\n \n <div class=\"appointment-card\">\n <div class=\"appointment-header\">\n <tas-avatar [name]=\"appointment.title\" [size]=\"48\"></tas-avatar>\n \n <span class=\"doctor-name\">{{ appointment.title }}</span>\n </div>\n <div class=\"appointment-details\">\n <div class=\"date-time\">\n <span class=\"date\">{{ formattedDate }}</span>\n <span class=\"time\">{{ formattedTimeRange }}</span>\n </div>\n <tas-btn\n variant=\"teal\"\n buttonLabel=\"Ingresar\"\n [roomType]=\"roomType\"\n [entityId]=\"entityId\"\n [tenant]=\"tenant\"\n [businessRole]=\"businessRole\"\n [currentUser]=\"currentUser\"\n ></tas-btn>\n </div>\n </div>\n </div>\n</div>\n", styles: [":host{display:block}.incoming-appointment-card{background:#ffffff;border-radius:12px;box-shadow:0 2px 8px #00000014;padding:24px;min-width:360px}.card-title{font-size:16px;font-weight:400;color:#6b7280;margin:0 0 24px}.card-content{display:flex;flex-direction:column;align-items:center}.loading-spinner{width:40px;height:40px;border:3px solid #e0f7fa;border-top-color:#0097a7;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}.empty-state{text-align:center;padding:20px 0}.icon-container{position:relative;width:120px;height:120px;margin-bottom:24px}.icon-circle{width:100%;height:100%;background:#e0f7fa;border-radius:50%;display:flex;align-items:center;justify-content:center}.icon-circle i{font-size:40px;color:#0097a7}.sparkle{position:absolute;color:#0097a7;font-size:12px}.sparkle-1{top:10px;right:5px}.sparkle-2{top:0;right:30px}.sparkle-3{top:25px;left:0}.sparkle-4{bottom:20px;right:0}.empty-title{font-size:18px;font-weight:600;color:#1f2937;margin:0 0 12px}.empty-subtitle{font-size:14px;color:#6b7280;margin:0;max-width:320px;line-height:1.5}.appointment-state{align-items:stretch}.appointment-card{border-radius:12px;border:1px solid var(--Primary-White-Uell50, #8ED1D8);background:#F1FAFA;padding:1.5rem}.appointment-header{display:flex;align-items:center;gap:12px;margin-bottom:16px}.doctor-name{overflow:hidden;color:var(--Neutral-GreyDark, #383E52);text-overflow:ellipsis;font-size:16px;font-style:normal;font-weight:600;line-height:24px;letter-spacing:.016px}.appointment-details{display:flex;justify-content:space-between;align-items:flex-end}.date-time{display:flex;flex-direction:column;gap:4px}.date{color:var(--Neutral-GreyDark, #383E52);font-size:12px;font-style:normal;font-weight:600;line-height:16px;letter-spacing:.06px}.time{font-size:14px;color:var(--Neutral-GreyDark, #383E52);font-family:Inter;font-size:10px;font-style:normal;font-weight:400;line-height:14px;letter-spacing:.04px}\n"] }]
|
|
1929
|
+
}], ctorParameters: function () { return [{ type: TasService }]; }, propDecorators: { roomType: [{
|
|
1930
|
+
type: Input
|
|
1931
|
+
}], entityId: [{
|
|
1932
|
+
type: Input
|
|
1933
|
+
}], tenant: [{
|
|
1934
|
+
type: Input
|
|
1935
|
+
}], businessRole: [{
|
|
1936
|
+
type: Input
|
|
1937
|
+
}], currentUser: [{
|
|
1938
|
+
type: Input
|
|
1939
|
+
}], enterCall: [{
|
|
1940
|
+
type: Output
|
|
1941
|
+
}] } });
|
|
922
1942
|
|
|
923
1943
|
class TasUellSdkModule {
|
|
924
1944
|
/**
|
|
@@ -956,43 +1976,44 @@ class TasUellSdkModule {
|
|
|
956
1976
|
providers: [
|
|
957
1977
|
{ provide: TAS_CONFIG, useValue: options.config },
|
|
958
1978
|
{ provide: TAS_HTTP_CLIENT, useClass: options.httpClient },
|
|
959
|
-
TasService
|
|
960
|
-
]
|
|
1979
|
+
TasService,
|
|
1980
|
+
],
|
|
961
1981
|
};
|
|
962
1982
|
}
|
|
963
1983
|
}
|
|
964
|
-
TasUellSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.
|
|
965
|
-
TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.
|
|
1984
|
+
TasUellSdkModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
|
1985
|
+
TasUellSdkModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, declarations: [TasButtonComponent,
|
|
966
1986
|
TasVideocallComponent,
|
|
967
1987
|
TasFloatingCallComponent,
|
|
968
|
-
TasWaitingRoomComponent
|
|
969
|
-
|
|
1988
|
+
TasWaitingRoomComponent,
|
|
1989
|
+
TasAvatarComponent,
|
|
1990
|
+
TasIncomingAppointmentComponent], imports: [CommonModule, FormsModule, NgbTooltipModule], exports: [TasButtonComponent,
|
|
970
1991
|
TasVideocallComponent,
|
|
971
1992
|
TasFloatingCallComponent,
|
|
972
|
-
TasWaitingRoomComponent
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImport: i0, type: TasUellSdkModule, decorators: [{
|
|
1993
|
+
TasWaitingRoomComponent,
|
|
1994
|
+
TasAvatarComponent,
|
|
1995
|
+
TasIncomingAppointmentComponent] });
|
|
1996
|
+
TasUellSdkModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, imports: [[CommonModule, FormsModule, NgbTooltipModule]] });
|
|
1997
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.12", ngImport: i0, type: TasUellSdkModule, decorators: [{
|
|
978
1998
|
type: NgModule,
|
|
979
1999
|
args: [{
|
|
980
2000
|
declarations: [
|
|
981
2001
|
TasButtonComponent,
|
|
982
2002
|
TasVideocallComponent,
|
|
983
2003
|
TasFloatingCallComponent,
|
|
984
|
-
TasWaitingRoomComponent
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
CommonModule,
|
|
988
|
-
FormsModule,
|
|
2004
|
+
TasWaitingRoomComponent,
|
|
2005
|
+
TasAvatarComponent,
|
|
2006
|
+
TasIncomingAppointmentComponent,
|
|
989
2007
|
],
|
|
2008
|
+
imports: [CommonModule, FormsModule, NgbTooltipModule],
|
|
990
2009
|
exports: [
|
|
991
2010
|
TasButtonComponent,
|
|
992
2011
|
TasVideocallComponent,
|
|
993
2012
|
TasFloatingCallComponent,
|
|
994
|
-
TasWaitingRoomComponent
|
|
995
|
-
|
|
2013
|
+
TasWaitingRoomComponent,
|
|
2014
|
+
TasAvatarComponent,
|
|
2015
|
+
TasIncomingAppointmentComponent,
|
|
2016
|
+
],
|
|
996
2017
|
}]
|
|
997
2018
|
}] });
|
|
998
2019
|
|
|
@@ -1004,5 +2025,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.4.0", ngImpor
|
|
|
1004
2025
|
* Generated bundle index. Do not edit.
|
|
1005
2026
|
*/
|
|
1006
2027
|
|
|
1007
|
-
export { CallState, TAS_CONFIG, TAS_HTTP_CLIENT, TasButtonComponent, TasFloatingCallComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, ViewMode, WaitingRoomState };
|
|
2028
|
+
export { AppointmentStatus, CallState, GeolocationService, RoomUserStatus, TAS_CONFIG, TAS_HTTP_CLIENT, TasAvatarComponent, TasBusinessRole, TasButtonComponent, TasFloatingCallComponent, TasIncomingAppointmentComponent, TasRoomType, TasService, TasSessionType, TasUellSdkModule, TasUserRole, TasVideocallComponent, TasWaitingRoomComponent, UserCallAction, UserStatus, VideoSessionStatus, ViewMode, WaitingRoomState };
|
|
1008
2029
|
//# sourceMappingURL=tas-uell-sdk.mjs.map
|