@arthakosh/chat 0.0.1

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.
Files changed (32) hide show
  1. package/README.md +24 -0
  2. package/esm2022/arthakosh-chat.mjs +5 -0
  3. package/esm2022/lib/all-chats/all-chats.component.mjs +121 -0
  4. package/esm2022/lib/chat-launcher/chat-launcher.component.mjs +96 -0
  5. package/esm2022/lib/chat-widget-notification/chat-widget-notification.component.mjs +69 -0
  6. package/esm2022/lib/chat-widget.module.mjs +47 -0
  7. package/esm2022/lib/chat-widget.service.mjs +12 -0
  8. package/esm2022/lib/chat-window/chat-window.component.mjs +210 -0
  9. package/esm2022/lib/core/config/chat-config.mjs +2 -0
  10. package/esm2022/lib/core/config/chat.tokens.mjs +4 -0
  11. package/esm2022/lib/core/models/chat.models.mjs +2 -0
  12. package/esm2022/lib/core/models/notification.model.mjs +2 -0
  13. package/esm2022/lib/core/services/chat.service.mjs +117 -0
  14. package/esm2022/lib/core/services/socket.service.mjs +148 -0
  15. package/esm2022/public-api.mjs +10 -0
  16. package/fesm2022/arthakosh-chat.mjs +776 -0
  17. package/fesm2022/arthakosh-chat.mjs.map +1 -0
  18. package/index.d.ts +5 -0
  19. package/lib/all-chats/all-chats.component.d.ts +23 -0
  20. package/lib/chat-launcher/chat-launcher.component.d.ts +34 -0
  21. package/lib/chat-widget-notification/chat-widget-notification.component.d.ts +22 -0
  22. package/lib/chat-widget.module.d.ts +14 -0
  23. package/lib/chat-widget.service.d.ts +6 -0
  24. package/lib/chat-window/chat-window.component.d.ts +49 -0
  25. package/lib/core/config/chat-config.d.ts +4 -0
  26. package/lib/core/config/chat.tokens.d.ts +3 -0
  27. package/lib/core/models/chat.models.d.ts +22 -0
  28. package/lib/core/models/notification.model.d.ts +6 -0
  29. package/lib/core/services/chat.service.d.ts +34 -0
  30. package/lib/core/services/socket.service.d.ts +38 -0
  31. package/package.json +42 -0
  32. package/public-api.d.ts +6 -0
@@ -0,0 +1,776 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, signal, Inject, Injectable, EventEmitter, input, inject, ViewChild, Output, Input, Component, effect, ViewEncapsulation, computed, NgModule } from '@angular/core';
3
+ import * as i1$1 from '@angular/common';
4
+ import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
7
+ import { map } from 'rxjs/operators';
8
+ import * as i1 from '@angular/common/http';
9
+ import { Observable } from 'rxjs';
10
+ import * as i3 from 'primeng/toast';
11
+ import { ToastModule } from 'primeng/toast';
12
+ import * as i2$1 from 'primeng/api';
13
+ import { MessageService } from 'primeng/api';
14
+ import * as i4 from 'primeng/button';
15
+ import { ButtonModule } from 'primeng/button';
16
+ import * as i5 from 'primeng/avatar';
17
+ import { AvatarModule } from 'primeng/avatar';
18
+ import * as i6 from 'primeng/tooltip';
19
+ import { TooltipModule } from 'primeng/tooltip';
20
+ import * as i7 from 'primeng/inputtextarea';
21
+ import { InputTextareaModule } from 'primeng/inputtextarea';
22
+ import * as i8 from 'primeng/dropdown';
23
+ import { DropdownModule } from 'primeng/dropdown';
24
+ import { ChipModule } from 'primeng/chip';
25
+ import { OverlayPanelModule } from 'primeng/overlaypanel';
26
+ import * as i4$1 from 'primeng/tabview';
27
+ import { TabViewModule } from 'primeng/tabview';
28
+ import { trigger, transition, style, animate } from '@angular/animations';
29
+
30
+ // chat.tokens.ts
31
+ const CHAT_CONFIG = new InjectionToken('CHAT_CONFIG');
32
+
33
+ class SocketService {
34
+ constructor(config) {
35
+ this.config = config;
36
+ this.listeners = new Map();
37
+ this.notifications = signal([]);
38
+ this.hasNewNotification = signal(false);
39
+ this.reconnectAttempts = 0;
40
+ this.MAX_RECONNECT_ATTEMPTS = 5;
41
+ // this.connect();
42
+ }
43
+ connect(userId) {
44
+ // this.socket = new WebSocket('wss://coreuatarthkosh.sarjak.com/ws/');
45
+ if (this.reconnectAttempts >= this.MAX_RECONNECT_ATTEMPTS) {
46
+ console.error('[WS] Max reconnect attempts reached. Giving up.');
47
+ return;
48
+ }
49
+ this.socket = new WebSocket(`${this.config.wsBaseUrl}/ws/`);
50
+ this.socket.onopen = () => {
51
+ console.log('[WS] Connected');
52
+ this.reconnectAttempts = 0;
53
+ this.joinUserChannel(userId);
54
+ this.on('notificationCreated').subscribe((notification) => {
55
+ console.log('Listening for notifications', notification);
56
+ this.notifications.update(list => {
57
+ const roomId = notification.entityId;
58
+ const eventType = notification.eventType;
59
+ // 🔑 Dedup key = room + eventType
60
+ const exists = list.some(n => n.entityId === roomId && n.eventType === eventType);
61
+ // ❌ Already exists → do nothing
62
+ if (exists) {
63
+ return list;
64
+ }
65
+ // ✅ New (room + eventType) combination
66
+ return [
67
+ {
68
+ ...notification,
69
+ unreadCount: 1
70
+ },
71
+ ...list
72
+ ];
73
+ });
74
+ this.notifyArrival();
75
+ });
76
+ };
77
+ this.socket.onmessage = (event) => {
78
+ const msg = JSON.parse(event.data);
79
+ const handlers = this.listeners.get(msg.event) || [];
80
+ handlers.forEach(h => h(msg.data));
81
+ };
82
+ this.socket.onerror = (err) => {
83
+ console.error('[WS] Error', err);
84
+ this.socket.close();
85
+ };
86
+ this.socket.onclose = () => {
87
+ this.reconnectAttempts++;
88
+ console.warn('[WS] Disconnected – reconnecting...');
89
+ if (this.reconnectAttempts < this.MAX_RECONNECT_ATTEMPTS) {
90
+ setTimeout(() => this.connect(userId), 2000);
91
+ }
92
+ else {
93
+ console.error('[WS] Stopped reconnecting.');
94
+ }
95
+ };
96
+ }
97
+ emit(event, data) {
98
+ const payload = { event, data };
99
+ this.socket.send(JSON.stringify(payload));
100
+ }
101
+ joinRoom(roomId) {
102
+ this.emit('joinRoom', roomId);
103
+ }
104
+ joinUserChannel(userId) {
105
+ this.emit('joinUserChannel', userId);
106
+ }
107
+ sendMessage(data) {
108
+ this.emit('sendMessage', data);
109
+ }
110
+ messageSeen(data) {
111
+ this.emit('messageSeen', data);
112
+ }
113
+ on(event) {
114
+ return new Observable(sub => {
115
+ const handler = (data) => sub.next(data);
116
+ if (!this.listeners.has(event)) {
117
+ this.listeners.set(event, []);
118
+ }
119
+ this.listeners.get(event).push(handler);
120
+ return () => {
121
+ const arr = this.listeners.get(event) || [];
122
+ this.listeners.set(event, arr.filter(h => h !== handler));
123
+ };
124
+ });
125
+ }
126
+ onNewMessage(cb) {
127
+ this.addListener('newMessage', cb);
128
+ }
129
+ offNewMessage(cb) {
130
+ this.removeListener('newMessage', cb);
131
+ }
132
+ onMessageSeen(cb) {
133
+ this.addListener('messageSeen', cb);
134
+ }
135
+ offMessageSeen(cb) {
136
+ this.removeListener('messageSeen', cb);
137
+ }
138
+ addListener(event, cb) {
139
+ if (!this.listeners.has(event)) {
140
+ this.listeners.set(event, []);
141
+ }
142
+ this.listeners.get(event).push(cb);
143
+ }
144
+ removeListener(event, cb) {
145
+ if (!cb) {
146
+ this.listeners.delete(event);
147
+ return;
148
+ }
149
+ const arr = this.listeners.get(event) || [];
150
+ this.listeners.set(event, arr.filter(h => h !== cb));
151
+ }
152
+ clearAll() {
153
+ this.notifications.set([]);
154
+ }
155
+ notifyArrival() {
156
+ this.hasNewNotification.set(true);
157
+ setTimeout(() => {
158
+ this.hasNewNotification.set(false);
159
+ }, 2000);
160
+ }
161
+ clearRoom(chat) {
162
+ this.notifications.update(list => list.filter(n => !(n.entityId === chat.entityId &&
163
+ n.eventType === chat.eventType)));
164
+ this.hasNewNotification.set(this.notifications().length > 1);
165
+ }
166
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SocketService, deps: [{ token: CHAT_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable }); }
167
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SocketService, providedIn: 'root' }); }
168
+ }
169
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: SocketService, decorators: [{
170
+ type: Injectable,
171
+ args: [{ providedIn: 'root' }]
172
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
173
+ type: Inject,
174
+ args: [CHAT_CONFIG]
175
+ }] }] });
176
+
177
+ class ChatService {
178
+ constructor(http, config, socket) {
179
+ this.http = http;
180
+ this.config = config;
181
+ this.socket = socket;
182
+ // private baseUrl = `https://coreuatarthkosh.sarjak.com`;
183
+ this.currentUser = signal(null);
184
+ this.userList = signal([]);
185
+ this.listenForNotifications();
186
+ }
187
+ get baseUrl() {
188
+ return this.config.apiBaseUrl;
189
+ }
190
+ /* ---------------- CHAT ROOMS ---------------- */
191
+ createRoom(roomId, name, metadata, createdBy, username, chatUsers) {
192
+ return this.http.post(`${this.baseUrl}/api/chat/chatrooms/create/${roomId}/`, {
193
+ name,
194
+ metadata,
195
+ createdBy,
196
+ username,
197
+ chatUsers
198
+ });
199
+ }
200
+ getRoomUsers(roomId) {
201
+ return this.http.get(`${this.baseUrl}/api/chat/chatrooms/get_user/${roomId}/users/`).pipe(map(list => list.map(u => ({
202
+ id: u.UserId,
203
+ username: u.UserName
204
+ }))));
205
+ }
206
+ addUserToRoom(roomId, userId, userName, createdBy) {
207
+ return this.http.post(`${this.baseUrl}/api/chat/chatrooms/add_user/${roomId}/users/add/`, {
208
+ userId,
209
+ userName,
210
+ createdBy
211
+ });
212
+ }
213
+ /* ---------------- MESSAGES ---------------- */
214
+ getMessages(roomId, since) {
215
+ const params = {};
216
+ if (since)
217
+ params.since = since;
218
+ return this.http.get(`${this.baseUrl}/api/chat/messages/rooms/all_messages/${roomId}/`, { params }).pipe(map(list => list.map(m => ({
219
+ id: m.id,
220
+ chatRoomId: m.chatRoomId,
221
+ senderId: m.senderId,
222
+ senderName: m.senderName,
223
+ content: m.content,
224
+ replyToMessageId: m.replyToMessageId,
225
+ createdAt: m.createdAt,
226
+ hasAttachment: m.hasAttachment ?? false,
227
+ }))));
228
+ }
229
+ sendMessage(roomId, payload) {
230
+ return this.http.post(`${this.baseUrl}/api/chat/messages/rooms/send/${roomId}/`, payload).pipe(map(m => ({
231
+ id: m.id,
232
+ chatRoomId: m.chatRoomId,
233
+ senderId: m.senderId,
234
+ senderName: m.senderName,
235
+ content: m.content,
236
+ replyToMessageId: m.replyToMessageId,
237
+ createdAt: m.createdAt,
238
+ hasAttachment: m.hasAttachment ?? false,
239
+ })));
240
+ }
241
+ getRooms(loggedInUserId, chatRoomId) {
242
+ let url = `${this.baseUrl}/api/chat/chatrooms/${loggedInUserId}`;
243
+ if (chatRoomId) {
244
+ url += `/${chatRoomId}`;
245
+ }
246
+ return this.http.get(`${url}/`);
247
+ }
248
+ archiveRoom(roomId) {
249
+ return this.http.delete(`${this.baseUrl}/api/chat/chatrooms/archive/${roomId}/`);
250
+ }
251
+ listenForNotifications() {
252
+ console.log('Listening');
253
+ // 🔔 ADDED TO ROOM — only added user
254
+ this.socket.on('addedToRoom').subscribe((msg) => {
255
+ console.log('addedToRoom', msg);
256
+ if (msg.addedUserId !== this.currentUser().userId)
257
+ return;
258
+ });
259
+ // 🔔 NEW MESSAGE — everyone EXCEPT sender
260
+ this.socket.on('newMessage').subscribe((msg) => {
261
+ console.log('newMessage', msg);
262
+ if (msg.senderId === this.currentUser().userId)
263
+ return;
264
+ });
265
+ }
266
+ markAsRead(entityId, eventType, user_id) {
267
+ return this.http.post(`${this.baseUrl}/api/chat/notifications/mark_read/${entityId}/${eventType}/`, {
268
+ user_id
269
+ });
270
+ }
271
+ getAllUserNotifications(userId) {
272
+ return this.http.get(`${this.baseUrl}/api/chat/notifications/get_all/${userId}/`);
273
+ }
274
+ getRoomDetails(roomId) {
275
+ return this.http.get(`${this.baseUrl}/api/chat/chatrooms/detail/${roomId}/`);
276
+ }
277
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatService, deps: [{ token: i1.HttpClient }, { token: CHAT_CONFIG }, { token: SocketService }], target: i0.ɵɵFactoryTarget.Injectable }); }
278
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatService, providedIn: 'root' }); }
279
+ }
280
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatService, decorators: [{
281
+ type: Injectable,
282
+ args: [{ providedIn: 'root' }]
283
+ }], ctorParameters: () => [{ type: i1.HttpClient }, { type: undefined, decorators: [{
284
+ type: Inject,
285
+ args: [CHAT_CONFIG]
286
+ }] }, { type: SocketService }] });
287
+
288
+ class ChatWindowComponent {
289
+ constructor() {
290
+ this.header = 'Chat';
291
+ this.metadata = '';
292
+ this.chatUsers = [];
293
+ this.showActions = true;
294
+ this.mode = 'floating';
295
+ this.theme = 'light';
296
+ this.onArchive = new EventEmitter();
297
+ this.refreshRoom = input(false);
298
+ // @Input() currentUser: any;
299
+ this.chatService = inject(ChatService);
300
+ this.socket = inject(SocketService);
301
+ this.users = signal([]);
302
+ this.messages = signal([]);
303
+ this.newMessage = '';
304
+ this.minimized = false;
305
+ this.loading = false;
306
+ this.replyTo = null;
307
+ this.showUsers = false;
308
+ this.suggestion = '';
309
+ this.hoveredReactionUsers = [];
310
+ }
311
+ ngOnInit() {
312
+ this.socket.joinRoom(this.chatRoomId);
313
+ this.socket.joinUserChannel(this.chatService.currentUser().userId);
314
+ this.loadUsers();
315
+ this.loadMessages();
316
+ this.listenSocket();
317
+ }
318
+ loadUsers() {
319
+ this.chatService.getRoomUsers(this.chatRoomId).subscribe(users => {
320
+ this.users.set(users);
321
+ });
322
+ }
323
+ loadMessages() {
324
+ this.loading = true;
325
+ this.chatService.getMessages(this.chatRoomId).subscribe({
326
+ next: msgs => {
327
+ this.messages.set(msgs);
328
+ this.loading = false;
329
+ },
330
+ error: () => (this.loading = false)
331
+ });
332
+ }
333
+ listenSocket() {
334
+ this.socket.on('newMessage').subscribe(msg => {
335
+ if (msg.chatRoomId === this.chatRoomId) {
336
+ this.messages.update(list => [...list, msg]);
337
+ }
338
+ // if (msg.senderId !== this.chatService.currentUser().userId) {
339
+ // this.messageService.add({
340
+ // severity: 'info',
341
+ // summary: this.header,
342
+ // detail: msg.content ?? 'New message',
343
+ // life: 2500
344
+ // });
345
+ // }
346
+ });
347
+ this.socket.on('roomUserAdded').subscribe((msg) => {
348
+ if (msg.chatRoomId === this.chatRoomId) {
349
+ this.loadUsers();
350
+ // this.messageService.add({
351
+ // severity: 'info',
352
+ // summary: 'User Added',
353
+ // detail: msg.user?.userName,
354
+ // life: 2500
355
+ // });
356
+ }
357
+ });
358
+ this.socket.on('addedToRoom').subscribe((msg) => {
359
+ if (msg.chatRoomId === this.chatRoomId) {
360
+ this.loadUsers();
361
+ console.log('addedToRoom', msg);
362
+ }
363
+ });
364
+ this.socket.on('userAlreadyInRoom').subscribe((msg) => {
365
+ // if (msg.chatRoomId === this.chatRoomId) {
366
+ // this.messageService.add({
367
+ // severity: 'info',
368
+ // summary: 'User Already Added',
369
+ // detail: msg.user?.userName,
370
+ // life: 2500
371
+ // });
372
+ // }
373
+ });
374
+ }
375
+ send() {
376
+ const content = this.newMessage.trim();
377
+ if (!content)
378
+ return;
379
+ this.chatService.sendMessage(this.chatRoomId, {
380
+ senderId: this.chatService.currentUser().userId,
381
+ senderName: this.chatService.currentUser().name,
382
+ content,
383
+ replyToMessageId: this.replyTo?.replyToMessageId ?? null,
384
+ createdBy: this.chatService.currentUser().userId
385
+ }).subscribe(() => {
386
+ this.newMessage = '';
387
+ this.replyTo = null;
388
+ });
389
+ }
390
+ addUserToRoom() {
391
+ this.chatService.addUserToRoom(this.chatRoomId, this.selectedUser?.user_id, this.selectedUser?.full_name, this.chatService.currentUser().userId).subscribe((res) => {
392
+ });
393
+ }
394
+ toggleMinimize() {
395
+ this.minimized = !this.minimized;
396
+ }
397
+ archiveChat() {
398
+ this.chatService.archiveRoom(this.chatRoomId).subscribe({
399
+ next: () => {
400
+ this.onArchive.emit(this.chatRoomId);
401
+ },
402
+ error: err => console.error(err)
403
+ });
404
+ }
405
+ setReply(msg) {
406
+ this.replyTo = msg;
407
+ }
408
+ clearReply() {
409
+ this.replyTo = null;
410
+ }
411
+ trackById(index, msg) {
412
+ return msg.id;
413
+ }
414
+ ngOnDestroy() {
415
+ this.socket.offNewMessage();
416
+ this.socket.offMessageSeen();
417
+ this.observer?.disconnect();
418
+ }
419
+ ngAfterViewInit() {
420
+ const el = this.messagesContainer.nativeElement;
421
+ this.observer = new MutationObserver(() => {
422
+ // 🔥 Scroll AFTER DOM actually changes
423
+ el.scrollTop = el.scrollHeight;
424
+ });
425
+ this.observer.observe(el, {
426
+ childList: true,
427
+ subtree: true,
428
+ characterData: true
429
+ });
430
+ }
431
+ toggleUsers() {
432
+ this.showUsers = !this.showUsers;
433
+ }
434
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWindowComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
435
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ChatWindowComponent, isStandalone: true, selector: "app-chat-window", inputs: { chatRoomId: { classPropertyName: "chatRoomId", publicName: "chatRoomId", isSignal: false, isRequired: false, transformFunction: null }, header: { classPropertyName: "header", publicName: "header", isSignal: false, isRequired: false, transformFunction: null }, metadata: { classPropertyName: "metadata", publicName: "metadata", isSignal: false, isRequired: false, transformFunction: null }, chatUsers: { classPropertyName: "chatUsers", publicName: "chatUsers", isSignal: false, isRequired: false, transformFunction: null }, showActions: { classPropertyName: "showActions", publicName: "showActions", isSignal: false, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: false, isRequired: false, transformFunction: null }, theme: { classPropertyName: "theme", publicName: "theme", isSignal: false, isRequired: false, transformFunction: null }, refreshRoom: { classPropertyName: "refreshRoom", publicName: "refreshRoom", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { onArchive: "onArchive" }, providers: [MessageService], viewQueries: [{ propertyName: "messagesContainer", first: true, predicate: ["messagesContainer"], descendants: true }, { propertyName: "reactionOverlay", first: true, predicate: ["reactionOverlay"], descendants: true }], ngImport: i0, template: "<p-toast></p-toast>\r\n\r\n<div [class.dark]=\"theme === 'dark'\" [class.floating]=\"mode === 'floating'\" [class.fullscreen]=\"mode === 'fullscreen'\"\r\n [class.minimized]=\"minimized\" class=\"chat-window\">\r\n <div class=\"chat-header\">\r\n <div style=\"display: flex; flex-direction: column;\">\r\n <div class=\"title\">\r\n <span>{{ header }}</span>\r\n <span *ngIf=\"users().length\">({{ users().length }} {{\r\n users().length == 1 ? 'participant' :\r\n 'participants'\r\n }})</span>\r\n </div>\r\n <div>\r\n <span>{{ metadata }}</span>\r\n </div>\r\n </div>\r\n <div class=\"header-actions\">\r\n <button (click)=\"toggleUsers()\" icon=\"pi pi-users\" pButton pTooltip=\"Toogle Users\"\r\n style=\"font-size: 0.5rem\"></button>\r\n @if (showActions) {\r\n <button (click)=\"toggleMinimize()\" [icon]=\"minimized ? 'pi pi-plus' : 'pi pi-minus'\" pButton\r\n pTooltip=\"Hide/Show\" style=\"font-size: 0.5rem\"></button>\r\n <button (click)=\"archiveChat()\" icon=\"pi pi-trash\" pButton pTooltip=\"Archive\"\r\n style=\"font-size: 0.5rem\"></button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"!minimized\" class=\"chat-body\">\r\n <div [class.users-hidden]=\"!showUsers\" class=\"chat-layout\">\r\n <div [class.floating]=\"mode === 'floating'\" [class.fullscreen]=\"mode === 'fullscreen'\" class=\"chat-main\">\r\n <div #messagesContainer [class.loading]=\"loading\" class=\"messages\">\r\n <div *ngFor=\"let msg of messages(); trackBy: trackById\" [ngClass]=\"{ outgoing: msg.senderId === chatService.currentUser().userId, incoming: msg.senderId !== chatService.currentUser().userId }\"\r\n class=\"message-row\">\r\n <div class=\"message-bubble\">\r\n <div class=\"message-meta\">\r\n <span class=\"sender\">\r\n <!-- *ngIf=\"msg.senderId !== chatService.currentUser().userId\" -->\r\n {{ msg.senderName }}\r\n </span>\r\n <!-- <span class=\"time\">\r\n {{ msg.createdAt | date: 'shortTime' }}\r\n </span> -->\r\n </div>\r\n\r\n <!-- MESSAGE TEXT -->\r\n <div class=\"message-content\">\r\n {{ msg.content }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"input-area\">\r\n <div *ngIf=\"replyTo\" class=\"reply-banner\">\r\n <span>Replying to: {{ replyTo.content | slice: 0:50 }}</span>\r\n <button (click)=\"clearReply()\" class=\"p-button-text p-button-sm\" icon=\"pi pi-times\"\r\n pButton></button>\r\n </div>\r\n\r\n <textarea (keyup.enter)=\"send()\" [(ngModel)]=\"newMessage\" autoResize=\"true\" pInputTextarea\r\n placeholder=\"Type a message\"\r\n rows=\"2\">\r\n </textarea>\r\n\r\n <div class=\"input-actions\">\r\n <button (click)=\"send()\" icon=\"pi pi-send\" label=\"Send\" pButton></button>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"chat-sidebar\">\r\n <div class=\"section-title\">Users</div>\r\n <div class=\"user-list\">\r\n <div *ngFor=\"let u of users()\" class=\"user-row\">\r\n <p-avatar [label]=\"u.username[0]\"></p-avatar>\r\n <span>{{ u.username }}</span>\r\n </div>\r\n </div>\r\n @if (showActions) {\r\n <div class=\"user-list-add\">\r\n <p-dropdown [(ngModel)]=\"selectedUser\" [options]=\"chatService.userList()\"\r\n optionLabel=\"full_name\"\r\n placeholder=\"Select User\"/>\r\n <div style=\"margin-top: 2em;\">\r\n <button (click)=\"addUserToRoom()\" icon=\"pi pi-plus\" label=\"Add\" pButton></button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [".chat-window{width:35vw;max-width:95vw;display:flex;flex-direction:column;background:#fff;border-radius:16px;box-shadow:0 8px 30px #0000001f;overflow:visible;font-size:14px;font-family:Inter,system-ui,sans-serif}.chat-window.minimized{height:auto}.chat-header{display:flex;align-items:center;justify-content:space-between;background:#4f6ef7;color:#fff;padding:10px 14px}.chat-header .title{display:flex;flex-direction:row;gap:6px}.chat-header .title span{font-weight:600;font-size:14px}.chat-header .title small{font-size:11px;opacity:.85}.chat-body{display:flex;flex:1;background:#f5f7fb}.chat-layout{display:flex;flex:1;width:100%;overflow:hidden}.chat-layout.users-hidden .chat-sidebar{transform:translate(100%);opacity:0;pointer-events:none;width:0!important}.chat-layout.users-hidden .chat-main{flex:1 1 100%}.chat-main{flex:1;display:flex;flex-direction:column;background:#f5f7fb;height:70vh}.messages{flex:1;padding:16px 14px;overflow-y:auto;scroll-behavior:smooth}.message-row{display:flex;margin-bottom:14px;animation:messageIn .25s ease-out;position:relative;overflow:visible;padding-top:28px}@keyframes messageIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.message-row.incoming{justify-content:flex-start}.message-row.incoming .reaction-bar{left:12px}.message-row.outgoing{justify-content:flex-end}.message-row.outgoing .reaction-bar{right:12px;left:auto}.message-bubble{position:relative;max-width:72%;padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.45;word-break:break-word;overflow:visible}.message-row.incoming .message-bubble{background:#fff;color:#111;border-top-left-radius:6px;box-shadow:0 3px 10px #0000000f;overflow:visible}.message-bubble:after{content:\"\";position:absolute;bottom:-6px;right:8px}.message-row.incoming .message-bubble:after{content:\"\";position:absolute;left:-6px;top:12px;width:0;height:0;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:6px solid #ffffff}.message-row.outgoing .message-bubble{background:#4f6ef7;color:#fff;border-top-right-radius:6px;overflow:visible}.message-row.outgoing .message-bubble:after{content:\"\";position:absolute;right:-6px;top:12px;width:0;height:0;border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #4f6ef7}.message-meta{display:flex;justify-content:flex-end;font-size:10px;opacity:.6;margin-bottom:4px}.typing-indicator{display:inline-flex;align-items:center;gap:4px;padding:8px 12px;background:#fff;border-radius:14px;box-shadow:0 2px 6px #00000014}.typing-indicator span{width:6px;height:6px;background:#999;border-radius:50%;animation:typingDots 1.4s infinite ease-in-out both}.typing-indicator span:nth-child(1){animation-delay:0s}.typing-indicator span:nth-child(2){animation-delay:.2s}.typing-indicator span:nth-child(3){animation-delay:.4s}@keyframes typingDots{0%{transform:translateY(0);opacity:.4}50%{transform:translateY(-4px);opacity:1}to{transform:translateY(0);opacity:.4}}.input-area{background:#fff;padding:10px;border:1px solid #e5e7eb;display:flex;flex-direction:row;justify-content:space-between;gap:5px}.input-area textarea{max-height:60px;overflow:hidden;width:-webkit-fill-available}.reply-banner{background:#eef2ff;border-left:3px solid #4f6ef7;padding:6px 8px;font-size:12px;border-radius:8px;margin-bottom:6px;display:flex;justify-content:space-between}.input-actions{display:flex;justify-content:flex-end;margin-top:6px}.input-actions button{background:#4f6ef7;color:#fff;border-radius:999px;padding:0 16px}.chat-sidebar{background:#fff;border-left:1px solid #e5e7eb;box-shadow:-4px 0 10px #0000000d;display:flex;flex-direction:column}.section-title{font-weight:600;font-size:13px;padding:10px;border-bottom:1px solid #e5e7eb}.user-list{flex:1;overflow-y:auto;padding:8px;max-height:20em}.user-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px}.user-row:hover{background:#f1f5f9}.user-list-add{padding:10px;border-top:1px solid #e5e7eb}.header-actions{display:flex;align-items:center;gap:6px;position:relative;right:20px}.header-actions .p-button{width:25px;height:25px;padding:0;border-radius:50%;border:solid 1px;margin:1px}.header-actions .p-button:hover{background:#ffffff40}.chat-header{min-height:48px}.header-actions{flex-shrink:0}.header-actions .p-button{transition:background .2s ease,transform .1s ease}.header-actions .p-button:active{transform:scale(.92)}.reaction-bar{position:absolute;top:0;left:12px;transform:translateY(-100%);background:#fff;border-radius:20px;padding:4px 8px;display:none;box-shadow:0 2px 8px #0003;z-index:9999}.message-bubble:hover .reaction-bar{display:flex}.reaction-bar span{cursor:pointer;font-size:16px}.reaction-summary{margin-top:4px;display:flex;gap:6px}.reaction-chip{background:#f1f1f1;padding:2px 6px;border-radius:12px;font-size:12px;position:relative}.reaction-users-tooltip{position:absolute;bottom:130%;left:50%;transform:translate(-50%);white-space:nowrap;background:#222;color:#fff;padding:4px 8px;border-radius:6px;z-index:10000;bottom:calc(100% + 6px)}.message-row,.message-bubble,.reaction-summary{overflow:visible!important}.p-tooltip{z-index:9999!important}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i1$1.SlicePipe, name: "slice" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ToastModule }, { kind: "component", type: i3.Toast, selector: "p-toast", inputs: ["key", "autoZIndex", "baseZIndex", "life", "style", "styleClass", "position", "preventOpenDuplicates", "preventDuplicates", "showTransformOptions", "hideTransformOptions", "showTransitionOptions", "hideTransitionOptions", "breakpoints"], outputs: ["onClose"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain"] }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i5.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "style", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "ngmodule", type: InputTextareaModule }, { kind: "directive", type: i7.InputTextarea, selector: "[pInputTextarea]", inputs: ["autoResize", "variant"], outputs: ["onResize"] }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i8.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "autoShowPanelOnPrintableCharacterKeyDown", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: OverlayPanelModule }] }); }
436
+ }
437
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWindowComponent, decorators: [{
438
+ type: Component,
439
+ args: [{ selector: 'app-chat-window', standalone: true, imports: [
440
+ CommonModule,
441
+ FormsModule,
442
+ ToastModule,
443
+ ButtonModule,
444
+ AvatarModule,
445
+ TooltipModule,
446
+ InputTextareaModule,
447
+ ChipModule,
448
+ DropdownModule,
449
+ OverlayPanelModule
450
+ ], providers: [MessageService], template: "<p-toast></p-toast>\r\n\r\n<div [class.dark]=\"theme === 'dark'\" [class.floating]=\"mode === 'floating'\" [class.fullscreen]=\"mode === 'fullscreen'\"\r\n [class.minimized]=\"minimized\" class=\"chat-window\">\r\n <div class=\"chat-header\">\r\n <div style=\"display: flex; flex-direction: column;\">\r\n <div class=\"title\">\r\n <span>{{ header }}</span>\r\n <span *ngIf=\"users().length\">({{ users().length }} {{\r\n users().length == 1 ? 'participant' :\r\n 'participants'\r\n }})</span>\r\n </div>\r\n <div>\r\n <span>{{ metadata }}</span>\r\n </div>\r\n </div>\r\n <div class=\"header-actions\">\r\n <button (click)=\"toggleUsers()\" icon=\"pi pi-users\" pButton pTooltip=\"Toogle Users\"\r\n style=\"font-size: 0.5rem\"></button>\r\n @if (showActions) {\r\n <button (click)=\"toggleMinimize()\" [icon]=\"minimized ? 'pi pi-plus' : 'pi pi-minus'\" pButton\r\n pTooltip=\"Hide/Show\" style=\"font-size: 0.5rem\"></button>\r\n <button (click)=\"archiveChat()\" icon=\"pi pi-trash\" pButton pTooltip=\"Archive\"\r\n style=\"font-size: 0.5rem\"></button>\r\n }\r\n </div>\r\n </div>\r\n\r\n <div *ngIf=\"!minimized\" class=\"chat-body\">\r\n <div [class.users-hidden]=\"!showUsers\" class=\"chat-layout\">\r\n <div [class.floating]=\"mode === 'floating'\" [class.fullscreen]=\"mode === 'fullscreen'\" class=\"chat-main\">\r\n <div #messagesContainer [class.loading]=\"loading\" class=\"messages\">\r\n <div *ngFor=\"let msg of messages(); trackBy: trackById\" [ngClass]=\"{ outgoing: msg.senderId === chatService.currentUser().userId, incoming: msg.senderId !== chatService.currentUser().userId }\"\r\n class=\"message-row\">\r\n <div class=\"message-bubble\">\r\n <div class=\"message-meta\">\r\n <span class=\"sender\">\r\n <!-- *ngIf=\"msg.senderId !== chatService.currentUser().userId\" -->\r\n {{ msg.senderName }}\r\n </span>\r\n <!-- <span class=\"time\">\r\n {{ msg.createdAt | date: 'shortTime' }}\r\n </span> -->\r\n </div>\r\n\r\n <!-- MESSAGE TEXT -->\r\n <div class=\"message-content\">\r\n {{ msg.content }}\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"input-area\">\r\n <div *ngIf=\"replyTo\" class=\"reply-banner\">\r\n <span>Replying to: {{ replyTo.content | slice: 0:50 }}</span>\r\n <button (click)=\"clearReply()\" class=\"p-button-text p-button-sm\" icon=\"pi pi-times\"\r\n pButton></button>\r\n </div>\r\n\r\n <textarea (keyup.enter)=\"send()\" [(ngModel)]=\"newMessage\" autoResize=\"true\" pInputTextarea\r\n placeholder=\"Type a message\"\r\n rows=\"2\">\r\n </textarea>\r\n\r\n <div class=\"input-actions\">\r\n <button (click)=\"send()\" icon=\"pi pi-send\" label=\"Send\" pButton></button>\r\n </div>\r\n </div>\r\n </div>\r\n <div class=\"chat-sidebar\">\r\n <div class=\"section-title\">Users</div>\r\n <div class=\"user-list\">\r\n <div *ngFor=\"let u of users()\" class=\"user-row\">\r\n <p-avatar [label]=\"u.username[0]\"></p-avatar>\r\n <span>{{ u.username }}</span>\r\n </div>\r\n </div>\r\n @if (showActions) {\r\n <div class=\"user-list-add\">\r\n <p-dropdown [(ngModel)]=\"selectedUser\" [options]=\"chatService.userList()\"\r\n optionLabel=\"full_name\"\r\n placeholder=\"Select User\"/>\r\n <div style=\"margin-top: 2em;\">\r\n <button (click)=\"addUserToRoom()\" icon=\"pi pi-plus\" label=\"Add\" pButton></button>\r\n </div>\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n</div>", styles: [".chat-window{width:35vw;max-width:95vw;display:flex;flex-direction:column;background:#fff;border-radius:16px;box-shadow:0 8px 30px #0000001f;overflow:visible;font-size:14px;font-family:Inter,system-ui,sans-serif}.chat-window.minimized{height:auto}.chat-header{display:flex;align-items:center;justify-content:space-between;background:#4f6ef7;color:#fff;padding:10px 14px}.chat-header .title{display:flex;flex-direction:row;gap:6px}.chat-header .title span{font-weight:600;font-size:14px}.chat-header .title small{font-size:11px;opacity:.85}.chat-body{display:flex;flex:1;background:#f5f7fb}.chat-layout{display:flex;flex:1;width:100%;overflow:hidden}.chat-layout.users-hidden .chat-sidebar{transform:translate(100%);opacity:0;pointer-events:none;width:0!important}.chat-layout.users-hidden .chat-main{flex:1 1 100%}.chat-main{flex:1;display:flex;flex-direction:column;background:#f5f7fb;height:70vh}.messages{flex:1;padding:16px 14px;overflow-y:auto;scroll-behavior:smooth}.message-row{display:flex;margin-bottom:14px;animation:messageIn .25s ease-out;position:relative;overflow:visible;padding-top:28px}@keyframes messageIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}.message-row.incoming{justify-content:flex-start}.message-row.incoming .reaction-bar{left:12px}.message-row.outgoing{justify-content:flex-end}.message-row.outgoing .reaction-bar{right:12px;left:auto}.message-bubble{position:relative;max-width:72%;padding:10px 14px;border-radius:16px;font-size:14px;line-height:1.45;word-break:break-word;overflow:visible}.message-row.incoming .message-bubble{background:#fff;color:#111;border-top-left-radius:6px;box-shadow:0 3px 10px #0000000f;overflow:visible}.message-bubble:after{content:\"\";position:absolute;bottom:-6px;right:8px}.message-row.incoming .message-bubble:after{content:\"\";position:absolute;left:-6px;top:12px;width:0;height:0;border-top:6px solid transparent;border-bottom:6px solid transparent;border-right:6px solid #ffffff}.message-row.outgoing .message-bubble{background:#4f6ef7;color:#fff;border-top-right-radius:6px;overflow:visible}.message-row.outgoing .message-bubble:after{content:\"\";position:absolute;right:-6px;top:12px;width:0;height:0;border-top:6px solid transparent;border-bottom:6px solid transparent;border-left:6px solid #4f6ef7}.message-meta{display:flex;justify-content:flex-end;font-size:10px;opacity:.6;margin-bottom:4px}.typing-indicator{display:inline-flex;align-items:center;gap:4px;padding:8px 12px;background:#fff;border-radius:14px;box-shadow:0 2px 6px #00000014}.typing-indicator span{width:6px;height:6px;background:#999;border-radius:50%;animation:typingDots 1.4s infinite ease-in-out both}.typing-indicator span:nth-child(1){animation-delay:0s}.typing-indicator span:nth-child(2){animation-delay:.2s}.typing-indicator span:nth-child(3){animation-delay:.4s}@keyframes typingDots{0%{transform:translateY(0);opacity:.4}50%{transform:translateY(-4px);opacity:1}to{transform:translateY(0);opacity:.4}}.input-area{background:#fff;padding:10px;border:1px solid #e5e7eb;display:flex;flex-direction:row;justify-content:space-between;gap:5px}.input-area textarea{max-height:60px;overflow:hidden;width:-webkit-fill-available}.reply-banner{background:#eef2ff;border-left:3px solid #4f6ef7;padding:6px 8px;font-size:12px;border-radius:8px;margin-bottom:6px;display:flex;justify-content:space-between}.input-actions{display:flex;justify-content:flex-end;margin-top:6px}.input-actions button{background:#4f6ef7;color:#fff;border-radius:999px;padding:0 16px}.chat-sidebar{background:#fff;border-left:1px solid #e5e7eb;box-shadow:-4px 0 10px #0000000d;display:flex;flex-direction:column}.section-title{font-weight:600;font-size:13px;padding:10px;border-bottom:1px solid #e5e7eb}.user-list{flex:1;overflow-y:auto;padding:8px;max-height:20em}.user-row{display:flex;align-items:center;gap:8px;padding:6px 8px;border-radius:8px}.user-row:hover{background:#f1f5f9}.user-list-add{padding:10px;border-top:1px solid #e5e7eb}.header-actions{display:flex;align-items:center;gap:6px;position:relative;right:20px}.header-actions .p-button{width:25px;height:25px;padding:0;border-radius:50%;border:solid 1px;margin:1px}.header-actions .p-button:hover{background:#ffffff40}.chat-header{min-height:48px}.header-actions{flex-shrink:0}.header-actions .p-button{transition:background .2s ease,transform .1s ease}.header-actions .p-button:active{transform:scale(.92)}.reaction-bar{position:absolute;top:0;left:12px;transform:translateY(-100%);background:#fff;border-radius:20px;padding:4px 8px;display:none;box-shadow:0 2px 8px #0003;z-index:9999}.message-bubble:hover .reaction-bar{display:flex}.reaction-bar span{cursor:pointer;font-size:16px}.reaction-summary{margin-top:4px;display:flex;gap:6px}.reaction-chip{background:#f1f1f1;padding:2px 6px;border-radius:12px;font-size:12px;position:relative}.reaction-users-tooltip{position:absolute;bottom:130%;left:50%;transform:translate(-50%);white-space:nowrap;background:#222;color:#fff;padding:4px 8px;border-radius:6px;z-index:10000;bottom:calc(100% + 6px)}.message-row,.message-bubble,.reaction-summary{overflow:visible!important}.p-tooltip{z-index:9999!important}\n"] }]
451
+ }], ctorParameters: () => [], propDecorators: { chatRoomId: [{
452
+ type: Input
453
+ }], header: [{
454
+ type: Input
455
+ }], metadata: [{
456
+ type: Input
457
+ }], chatUsers: [{
458
+ type: Input
459
+ }], showActions: [{
460
+ type: Input
461
+ }], mode: [{
462
+ type: Input
463
+ }], theme: [{
464
+ type: Input
465
+ }], onArchive: [{
466
+ type: Output
467
+ }], messagesContainer: [{
468
+ type: ViewChild,
469
+ args: ['messagesContainer']
470
+ }], reactionOverlay: [{
471
+ type: ViewChild,
472
+ args: ['reactionOverlay']
473
+ }] } });
474
+
475
+ class ChatLauncherComponent {
476
+ constructor() {
477
+ this.roomId = input('');
478
+ this.roomHeader = input('');
479
+ this.metadata = input('');
480
+ this.user = input(null);
481
+ this.userList = input([]);
482
+ this.chatUsers = input([]);
483
+ this.chatService = inject(ChatService);
484
+ this.socketService = inject(SocketService);
485
+ this.messageService = inject(MessageService);
486
+ this.openWindows = signal([]);
487
+ this.lastOpenedRoom = signal(null);
488
+ this.activeIndex = signal(0);
489
+ this.isVisible = signal(true);
490
+ effect(() => {
491
+ const roomId = this.roomId();
492
+ const header = this.roomHeader();
493
+ const metadata = this.metadata();
494
+ const user = this.user();
495
+ this.chatService.currentUser.set({
496
+ roomId: this.roomId(),
497
+ userId: user?.userId,
498
+ name: user?.name
499
+ });
500
+ this.chatService.userList.set(this.userList());
501
+ if (!roomId || !user?.userId)
502
+ return;
503
+ if (this.lastOpenedRoom() === roomId)
504
+ return;
505
+ this.lastOpenedRoom.set(roomId);
506
+ this.openNewRoom(roomId, header, metadata, user.userId, user.name, this.chatUsers());
507
+ }, { allowSignalWrites: true });
508
+ }
509
+ ngOnInit() {
510
+ console.log('ChatLauncher initialized');
511
+ }
512
+ openNewRoom(roomId, roomHeader, metadata, userId, username, chatUsers) {
513
+ this.isVisible.set(true);
514
+ this.chatService.createRoom(roomId, roomHeader, metadata, userId, username, chatUsers).subscribe(room => {
515
+ this.addWindow(room.roomKey, room.name, room.metadata);
516
+ });
517
+ }
518
+ addWindow(roomId, header, metadata) {
519
+ const windows = this.openWindows();
520
+ const existingIndex = windows.findIndex(w => w.roomId === roomId);
521
+ // already open → focus
522
+ if (existingIndex !== -1) {
523
+ this.activeIndex.set(existingIndex);
524
+ return;
525
+ }
526
+ const newIndex = windows.length;
527
+ this.openWindows.update(ws => [...ws, { roomId, header, metadata }]);
528
+ // ✅ force real change: -1 → 0
529
+ setTimeout(() => {
530
+ this.activeIndex.set(newIndex);
531
+ }, 500);
532
+ }
533
+ closeWindow(roomId) {
534
+ this.openWindows.update(ws => ws.filter(w => w.roomId !== roomId));
535
+ }
536
+ setActive(i) {
537
+ this.activeIndex.set(i);
538
+ }
539
+ closeAll() {
540
+ this.openWindows.set([]);
541
+ // this.lastOpenedRoom.set(null);
542
+ // this.activeIndex.set(0);
543
+ this.isVisible.set(false);
544
+ }
545
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatLauncherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
546
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ChatLauncherComponent, isStandalone: true, selector: "app-chat-launcher", inputs: { roomId: { classPropertyName: "roomId", publicName: "roomId", isSignal: true, isRequired: false, transformFunction: null }, roomHeader: { classPropertyName: "roomHeader", publicName: "roomHeader", isSignal: true, isRequired: false, transformFunction: null }, metadata: { classPropertyName: "metadata", publicName: "metadata", isSignal: true, isRequired: false, transformFunction: null }, user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, userList: { classPropertyName: "userList", publicName: "userList", isSignal: true, isRequired: false, transformFunction: null }, chatUsers: { classPropertyName: "chatUsers", publicName: "chatUsers", isSignal: true, isRequired: false, transformFunction: null } }, providers: [MessageService], ngImport: i0, template: "@if (isVisible()) {\r\n <div class=\"chat-float-wrapper\">\r\n <button (click)=\"closeAll()\" icon=\"pi pi-times\" pButton pTooltip=\"Close\"\r\n style=\"font-size: 0.5rem\" class=\"closeButton\"></button>\r\n <p-tabView (activeIndexChange)=\"setActive($event)\" [activeIndex]=\"activeIndex()\" [scrollable]=\"true\"\r\n class=\"chat-tabview\">\r\n <p-tabPanel *ngFor=\"let w of openWindows(); let i = index\" [header]=\"w.header\">\r\n <ng-template pTemplate=\"content\">\r\n <app-chat-window (onArchive)=\"closeWindow($event)\"\r\n [chatRoomId]=\"w.roomId\" [header]=\"w.header\"\r\n [metadata]=\"w.metadata\" mode=\"floating\" theme=\"light\">\r\n </app-chat-window>\r\n </ng-template>\r\n </p-tabPanel>\r\n </p-tabView>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";.chat-float-wrapper{position:fixed!important;bottom:16px!important;right:16px!important;z-index:99999;pointer-events:auto}:host{position:fixed;right:16px;bottom:16px;z-index:2147483647;font-family:Arial,sans-serif}:host,:host *{box-sizing:border-box}:host{isolation:isolate}.chat-tabview{width:100%;height:100%}:host ::ng-deep .p-tabview{height:100%;display:flex;flex-direction:column}:host ::ng-deep .p-tabview-nav{flex-shrink:0}:host ::ng-deep .p-tabview-panels{flex:1;overflow:hidden}:host ::ng-deep .p-tabview-panel{height:100%}:host ::ng-deep app-chat-window{display:block;height:100%}.chat-tabview ::ng-deep .p-tabview-nav{background:transparent;border:none;display:flex;gap:8px;padding:6px 4px}.chat-tabview ::ng-deep .p-tabview-nav li{border:none}.chat-tabview ::ng-deep .p-tabview-nav li .p-tabview-nav-link{border:none;border-radius:18px;padding:6px 14px;font-size:13px;font-weight:500;color:#555;box-shadow:0 1px 4px #00000014;transition:all .2s ease;background:#d3d3d3}.chat-tabview ::ng-deep .p-tabview-nav li .p-tabview-nav-link:hover{color:#1a73e8}.chat-tabview ::ng-deep .p-tabview-nav li.p-highlight .p-tabview-nav-link{background:#1a73e8;color:#fff;box-shadow:0 4px 10px #1a73e859;border-bottom:outset;border-color:#1a73e8}.chat-tabview ::ng-deep .p-tabview-ink-bar{display:none}.chat-tabview ::ng-deep .p-tabview-panels{padding:0;border:none;background:transparent}.closeButton{position:absolute;right:0;top:25px;border-radius:50%;border:solid 2px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ChatWindowComponent, selector: "app-chat-window", inputs: ["chatRoomId", "header", "metadata", "chatUsers", "showActions", "mode", "theme", "refreshRoom"], outputs: ["onArchive"] }, { kind: "ngmodule", type: DropdownModule }, { kind: "directive", type: i2$1.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i4.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "label", "icon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain"] }, { kind: "ngmodule", type: ToastModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: TabViewModule }, { kind: "component", type: i4$1.TabView, selector: "p-tabView", inputs: ["style", "styleClass", "controlClose", "scrollable", "activeIndex", "selectOnFocus", "nextButtonAriaLabel", "prevButtonAriaLabel", "autoHideButtons", "tabindex"], outputs: ["onChange", "onClose", "activeIndexChange"] }, { kind: "component", type: i4$1.TabPanel, selector: "p-tabPanel", inputs: ["closable", "headerStyle", "headerStyleClass", "cache", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "selected", "disabled", "header", "leftIcon", "rightIcon"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i6.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] }); }
547
+ }
548
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatLauncherComponent, decorators: [{
549
+ type: Component,
550
+ args: [{ selector: 'app-chat-launcher', standalone: true, imports: [CommonModule, ChatWindowComponent, DropdownModule, ButtonModule, ToastModule, FormsModule, TabViewModule, TooltipModule], providers: [MessageService], encapsulation: ViewEncapsulation.Emulated, template: "@if (isVisible()) {\r\n <div class=\"chat-float-wrapper\">\r\n <button (click)=\"closeAll()\" icon=\"pi pi-times\" pButton pTooltip=\"Close\"\r\n style=\"font-size: 0.5rem\" class=\"closeButton\"></button>\r\n <p-tabView (activeIndexChange)=\"setActive($event)\" [activeIndex]=\"activeIndex()\" [scrollable]=\"true\"\r\n class=\"chat-tabview\">\r\n <p-tabPanel *ngFor=\"let w of openWindows(); let i = index\" [header]=\"w.header\">\r\n <ng-template pTemplate=\"content\">\r\n <app-chat-window (onArchive)=\"closeWindow($event)\"\r\n [chatRoomId]=\"w.roomId\" [header]=\"w.header\"\r\n [metadata]=\"w.metadata\" mode=\"floating\" theme=\"light\">\r\n </app-chat-window>\r\n </ng-template>\r\n </p-tabPanel>\r\n </p-tabView>\r\n </div>\r\n}\r\n", styles: ["@charset \"UTF-8\";.chat-float-wrapper{position:fixed!important;bottom:16px!important;right:16px!important;z-index:99999;pointer-events:auto}:host{position:fixed;right:16px;bottom:16px;z-index:2147483647;font-family:Arial,sans-serif}:host,:host *{box-sizing:border-box}:host{isolation:isolate}.chat-tabview{width:100%;height:100%}:host ::ng-deep .p-tabview{height:100%;display:flex;flex-direction:column}:host ::ng-deep .p-tabview-nav{flex-shrink:0}:host ::ng-deep .p-tabview-panels{flex:1;overflow:hidden}:host ::ng-deep .p-tabview-panel{height:100%}:host ::ng-deep app-chat-window{display:block;height:100%}.chat-tabview ::ng-deep .p-tabview-nav{background:transparent;border:none;display:flex;gap:8px;padding:6px 4px}.chat-tabview ::ng-deep .p-tabview-nav li{border:none}.chat-tabview ::ng-deep .p-tabview-nav li .p-tabview-nav-link{border:none;border-radius:18px;padding:6px 14px;font-size:13px;font-weight:500;color:#555;box-shadow:0 1px 4px #00000014;transition:all .2s ease;background:#d3d3d3}.chat-tabview ::ng-deep .p-tabview-nav li .p-tabview-nav-link:hover{color:#1a73e8}.chat-tabview ::ng-deep .p-tabview-nav li.p-highlight .p-tabview-nav-link{background:#1a73e8;color:#fff;box-shadow:0 4px 10px #1a73e859;border-bottom:outset;border-color:#1a73e8}.chat-tabview ::ng-deep .p-tabview-ink-bar{display:none}.chat-tabview ::ng-deep .p-tabview-panels{padding:0;border:none;background:transparent}.closeButton{position:absolute;right:0;top:25px;border-radius:50%;border:solid 2px}\n"] }]
551
+ }], ctorParameters: () => [] });
552
+
553
+ class AllChatsComponent {
554
+ constructor() {
555
+ this.user = input(null);
556
+ this.roomId = input(null);
557
+ this.chatUsers = signal([]);
558
+ this.newRoomLoaded = new EventEmitter();
559
+ this.rooms = [];
560
+ this.activeRoomId = signal('');
561
+ this.activeRoomHeader = signal('');
562
+ this.activeRoomMetadata = signal('');
563
+ this.chatService = inject(ChatService);
564
+ this.socketService = inject(SocketService);
565
+ this.showMessages = false;
566
+ }
567
+ ngOnInit() {
568
+ if (!this.user) {
569
+ console.error('user is required');
570
+ return;
571
+ }
572
+ this.chatService.currentUser.set({
573
+ roomId: this.activeRoomId,
574
+ userId: this.user()?.id,
575
+ name: this.user()?.name
576
+ });
577
+ this.socketService.connect(this.user().id);
578
+ this.chatService.getRooms(this.user()?.id, this.roomId()).subscribe(res => {
579
+ this.rooms = res;
580
+ if (this.rooms.length != 0) {
581
+ this.selectRoom(this.rooms[0]);
582
+ this.activeRoomId.set(this.rooms[0].ChatRoomId);
583
+ this.activeRoomHeader.set(this.rooms[0].ChatRoomName);
584
+ this.activeRoomMetadata.set(this.rooms[0].Metadata);
585
+ }
586
+ else {
587
+ this.chatService.addUserToRoom(this.roomId(), this.user()?.id, this.user()?.name, this.chatService.currentUser().userId).subscribe((res) => {
588
+ this.rooms = [];
589
+ this.getRooms();
590
+ });
591
+ }
592
+ });
593
+ }
594
+ getRooms() {
595
+ this.chatService.getRooms(this.user()?.id, this.roomId()).subscribe(res => {
596
+ this.rooms = res;
597
+ if (this.rooms.length != 0) {
598
+ this.selectRoom(this.rooms[0]);
599
+ this.activeRoomId.set(this.rooms[0].ChatRoomId);
600
+ this.activeRoomHeader.set(this.rooms[0].ChatRoomName);
601
+ this.activeRoomMetadata.set(this.rooms[0].Metadata);
602
+ }
603
+ });
604
+ }
605
+ selectRoom(room) {
606
+ this.showMessages = false;
607
+ this.activeRoomId.set(room.ChatRoomId);
608
+ this.activeRoomHeader.set(room.ChatRoomName);
609
+ this.activeRoomMetadata.set(room.Metadata);
610
+ setTimeout(() => {
611
+ this.showMessages = true;
612
+ }, 800);
613
+ }
614
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AllChatsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
615
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: AllChatsComponent, isStandalone: true, selector: "app-all-chats", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, roomId: { classPropertyName: "roomId", publicName: "roomId", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { newRoomLoaded: "newRoomLoaded" }, ngImport: i0, template: "<div class=\"chat-page\">\r\n <!-- LEFT SIDEBAR : CHAT ROOMS -->\r\n <aside class=\"chat-sidebar glass\">\r\n <div class=\"sidebar-header\">\r\n <h3>Chat Rooms</h3>\r\n </div>\r\n <ul class=\"room-list\">\r\n <li (click)=\"selectRoom(room)\" *ngFor=\"let room of rooms;\" [class.active]=\"activeRoomId() === room.ChatRoomId\"\r\n class=\"room-item\">\r\n <div class=\"room-avatar\">\r\n <p-avatar [label]=\"room.ChatRoomName[0]\"></p-avatar>\r\n </div>\r\n <div class=\"room-info\">\r\n <span class=\"room-name\">{{ room.ChatRoomName }}</span>\r\n <span class=\"room-name\">{{ room.Metadata }}</span>\r\n </div>\r\n </li>\r\n </ul>\r\n </aside>\r\n <!-- RIGHT PANEL : CHAT WINDOW -->\r\n @if (showMessages) {\r\n <section *ngIf=\"showMessages\" @roomSwitch class=\"chat-main glass\">\r\n <app-chat-window [chatRoomId]=\"activeRoomId()\" [header]=\"activeRoomHeader()\" [metadata]=\"activeRoomMetadata()\" [showActions]=\"false\"\r\n mode=\"fullscreen\" theme=\"light\">\r\n </app-chat-window>\r\n </section>\r\n } @else {\r\n <section class=\"chat-main glass loading-container\">\r\n <div class=\"spinner\"></div>\r\n <p>Loading messages\u2026</p>\r\n </section>\r\n }\r\n\r\n</div>", styles: [":root{--glass-bg: rgba(255, 255, 255, .75);--glass-border: rgba(255, 255, 255, .25);--primary: #5b6dff;--text-dark: #1c1e21;--text-muted: #65676b}*{box-sizing:border-box;font-family:Segoe UI,system-ui,-apple-system,BlinkMacSystemFont}.chat-page{display:flex;height:100vh;padding:16px;gap:16px;background:linear-gradient(135deg,#e9efff,#f6f7fb)}.glass{background:var(--glass-bg);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-radius:18px;border:1px solid var(--glass-border);box-shadow:0 20px 40px #00000014,inset 0 1px #fff6}.chat-sidebar{width:300px;display:flex;flex-direction:column;overflow:hidden}.sidebar-header{padding:18px 20px;font-size:18px;font-weight:700;color:var(--text-dark);border-bottom:1px solid rgba(0,0,0,.06)}.room-list{list-style:none;overflow-y:auto;padding:6px}.room-item{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:14px;cursor:pointer;transition:all .25s ease;margin-bottom:6px}.room-item:hover{background:#5b6dff14}.room-item.active{background:#5b6dff26;box-shadow:inset 0 0 0 1px #5b6dff40}.room-avatar p-avatar{width:44px;height:44px;border-radius:50%;background:linear-gradient(135deg,#5b6dff,#7a89ff);color:#fff;font-weight:700;display:flex;align-items:center;justify-content:center}.room-info{display:flex;flex-direction:column;overflow:hidden}.room-name{font-size:14px;font-weight:600;color:var(--text-dark);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.room-info span:last-child{font-size:12px;color:var(--text-muted)}.chat-main{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative}.chat-main:after{content:\"\";position:absolute;inset:0;border-radius:18px;pointer-events:none;box-shadow:inset 0 0 0 1px #fff3}.room-item:hover{transform:translateY(-1px)}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}.spinner{width:40px;height:40px;border:4px solid rgba(0,0,0,.1);border-top-color:#4f46e5;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ToastModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "ngmodule", type: AvatarModule }, { kind: "component", type: i5.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "style", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: InputTextareaModule }, { kind: "ngmodule", type: ChipModule }, { kind: "ngmodule", type: DropdownModule }, { kind: "ngmodule", type: OverlayPanelModule }, { kind: "component", type: ChatWindowComponent, selector: "app-chat-window", inputs: ["chatRoomId", "header", "metadata", "chatUsers", "showActions", "mode", "theme", "refreshRoom"], outputs: ["onArchive"] }], animations: [
616
+ trigger('roomSwitch', [
617
+ transition(':enter', [
618
+ style({ opacity: 0, transform: 'translateX(24px)' }),
619
+ animate('300ms cubic-bezier(.4,0,.2,1)', style({ opacity: 1, transform: 'translateX(0)' }))
620
+ ]),
621
+ transition(':leave', [
622
+ animate('200ms ease-in', style({ opacity: 0, transform: 'translateX(-24px)' }))
623
+ ])
624
+ ])
625
+ ] }); }
626
+ }
627
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: AllChatsComponent, decorators: [{
628
+ type: Component,
629
+ args: [{ selector: 'app-all-chats', standalone: true, imports: [
630
+ CommonModule,
631
+ FormsModule,
632
+ ToastModule,
633
+ ButtonModule,
634
+ AvatarModule,
635
+ TooltipModule,
636
+ InputTextareaModule,
637
+ ChipModule,
638
+ DropdownModule,
639
+ OverlayPanelModule,
640
+ ChatWindowComponent
641
+ ], animations: [
642
+ trigger('roomSwitch', [
643
+ transition(':enter', [
644
+ style({ opacity: 0, transform: 'translateX(24px)' }),
645
+ animate('300ms cubic-bezier(.4,0,.2,1)', style({ opacity: 1, transform: 'translateX(0)' }))
646
+ ]),
647
+ transition(':leave', [
648
+ animate('200ms ease-in', style({ opacity: 0, transform: 'translateX(-24px)' }))
649
+ ])
650
+ ])
651
+ ], template: "<div class=\"chat-page\">\r\n <!-- LEFT SIDEBAR : CHAT ROOMS -->\r\n <aside class=\"chat-sidebar glass\">\r\n <div class=\"sidebar-header\">\r\n <h3>Chat Rooms</h3>\r\n </div>\r\n <ul class=\"room-list\">\r\n <li (click)=\"selectRoom(room)\" *ngFor=\"let room of rooms;\" [class.active]=\"activeRoomId() === room.ChatRoomId\"\r\n class=\"room-item\">\r\n <div class=\"room-avatar\">\r\n <p-avatar [label]=\"room.ChatRoomName[0]\"></p-avatar>\r\n </div>\r\n <div class=\"room-info\">\r\n <span class=\"room-name\">{{ room.ChatRoomName }}</span>\r\n <span class=\"room-name\">{{ room.Metadata }}</span>\r\n </div>\r\n </li>\r\n </ul>\r\n </aside>\r\n <!-- RIGHT PANEL : CHAT WINDOW -->\r\n @if (showMessages) {\r\n <section *ngIf=\"showMessages\" @roomSwitch class=\"chat-main glass\">\r\n <app-chat-window [chatRoomId]=\"activeRoomId()\" [header]=\"activeRoomHeader()\" [metadata]=\"activeRoomMetadata()\" [showActions]=\"false\"\r\n mode=\"fullscreen\" theme=\"light\">\r\n </app-chat-window>\r\n </section>\r\n } @else {\r\n <section class=\"chat-main glass loading-container\">\r\n <div class=\"spinner\"></div>\r\n <p>Loading messages\u2026</p>\r\n </section>\r\n }\r\n\r\n</div>", styles: [":root{--glass-bg: rgba(255, 255, 255, .75);--glass-border: rgba(255, 255, 255, .25);--primary: #5b6dff;--text-dark: #1c1e21;--text-muted: #65676b}*{box-sizing:border-box;font-family:Segoe UI,system-ui,-apple-system,BlinkMacSystemFont}.chat-page{display:flex;height:100vh;padding:16px;gap:16px;background:linear-gradient(135deg,#e9efff,#f6f7fb)}.glass{background:var(--glass-bg);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border-radius:18px;border:1px solid var(--glass-border);box-shadow:0 20px 40px #00000014,inset 0 1px #fff6}.chat-sidebar{width:300px;display:flex;flex-direction:column;overflow:hidden}.sidebar-header{padding:18px 20px;font-size:18px;font-weight:700;color:var(--text-dark);border-bottom:1px solid rgba(0,0,0,.06)}.room-list{list-style:none;overflow-y:auto;padding:6px}.room-item{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:14px;cursor:pointer;transition:all .25s ease;margin-bottom:6px}.room-item:hover{background:#5b6dff14}.room-item.active{background:#5b6dff26;box-shadow:inset 0 0 0 1px #5b6dff40}.room-avatar p-avatar{width:44px;height:44px;border-radius:50%;background:linear-gradient(135deg,#5b6dff,#7a89ff);color:#fff;font-weight:700;display:flex;align-items:center;justify-content:center}.room-info{display:flex;flex-direction:column;overflow:hidden}.room-name{font-size:14px;font-weight:600;color:var(--text-dark);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.room-info span:last-child{font-size:12px;color:var(--text-muted)}.chat-main{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative}.chat-main:after{content:\"\";position:absolute;inset:0;border-radius:18px;pointer-events:none;box-shadow:inset 0 0 0 1px #fff3}.room-item:hover{transform:translateY(-1px)}.loading-container{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%}.spinner{width:40px;height:40px;border:4px solid rgba(0,0,0,.1);border-top-color:#4f46e5;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}\n"] }]
652
+ }], ctorParameters: () => [], propDecorators: { newRoomLoaded: [{
653
+ type: Output
654
+ }] } });
655
+
656
+ class ChatWidgetNotificationComponent {
657
+ constructor() {
658
+ this.open = false;
659
+ this.chatService = inject(ChatService);
660
+ this.socketService = inject(SocketService);
661
+ this.userId = input(null);
662
+ this.userName = input(null);
663
+ this.chatLauncher = inject(ChatLauncherComponent);
664
+ this.chatRoomDetails = new EventEmitter();
665
+ this.totalCount = computed(() => this.socketService.notifications().reduce((a, b) => a + b.unreadCount, 0));
666
+ }
667
+ ngOnInit() {
668
+ this.socketService.connect(this.userId());
669
+ this.chatService.getAllUserNotifications(this.userId()).subscribe(notifications => {
670
+ const data = notifications.map(n => {
671
+ return {
672
+ entityId: n.room_id,
673
+ eventType: n.type,
674
+ payload: { chatRoomId: n.room_id, chatRoomName: n.chat_room_name },
675
+ unreadCount: 0,
676
+ title: n.description
677
+ };
678
+ });
679
+ this.socketService.notifications.set(data);
680
+ this.socketService.notifyArrival();
681
+ });
682
+ }
683
+ toggle() {
684
+ this.open = !this.open;
685
+ }
686
+ openRoom(n) {
687
+ this.chatLauncher.isVisible.set(true);
688
+ this.chatService.markAsRead(n.entityId, n.eventType, this.userId()).subscribe(notification => {
689
+ console.log('markAsRead', notification);
690
+ setTimeout(() => {
691
+ this.chatService.getRoomDetails(n.payload.chatRoomId).subscribe(room => {
692
+ const users = room[0].Users
693
+ ? JSON.parse(room[0].Users).map((u) => ({
694
+ user_id: u.user_id,
695
+ full_name: u.full_name
696
+ }))
697
+ : [];
698
+ this.chatRoomDetails.emit({ roomId: room[0].Id, roomName: room[0].Name, metadata: room[0].Metadata, users });
699
+ this.socketService.clearRoom(n);
700
+ });
701
+ }, 800);
702
+ });
703
+ this.open = false;
704
+ }
705
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetNotificationComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
706
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "17.3.12", type: ChatWidgetNotificationComponent, isStandalone: true, selector: "chat-widget-notification", inputs: { userId: { classPropertyName: "userId", publicName: "userId", isSignal: true, isRequired: false, transformFunction: null }, userName: { classPropertyName: "userName", publicName: "userName", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { chatRoomDetails: "chatRoomDetails" }, providers: [ChatLauncherComponent], ngImport: i0, template: "<div (click)=\"toggle()\" class=\"notif-icon\">\r\n <i class=\"pi pi-bell\" [class.pulse]=\"socketService.hasNewNotification()\"></i>\r\n\r\n @if (totalCount() > 0) {\r\n <span class=\"dot\"></span>\r\n <span class=\"badge\">{{ totalCount() }}</span>\r\n }\r\n</div>\r\n\r\n@if (open) {\r\n <div class=\"notif-panel\">\r\n <div class=\"notif-list\">\r\n @for (n of socketService.notifications(); track n.entityId + '_' + n.eventType) {\r\n <div class=\"notif-item\" (click)=\"openRoom(n)\">\r\n <div class=\"notif-left\">\r\n <i class=\"pi pi-comments\"></i>\r\n </div>\r\n\r\n <div class=\"notif-body\">\r\n <div class=\"notif-title\">{{ n?.payload?.chatRoomName }}</div>\r\n <div class=\"notif-text\">\r\n @if (n.eventType === 'NEW_MESSAGE') {\r\n New Message Received\r\n }\r\n\r\n @if (n.eventType === 'USER_ADDED') {\r\n You have been added to the chat\r\n }\r\n </div>\r\n </div>\r\n\r\n<!-- <span class=\"count\">{{ n.count }}</span>-->\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".notif-icon{position:relative;cursor:pointer;display:flex;align-items:center;justify-content:center}.notif-icon i{font-size:1.4rem;color:#444}.pulse{animation:pulse 1.2s ease-out}@keyframes pulse{0%{transform:scale(1)}40%{transform:scale(1.2)}to{transform:scale(1)}}.dot{position:absolute;top:2px;right:4px;width:8px;height:8px;background:#e53935;border-radius:50%}.badge{position:absolute;top:-6px;right:-10px;background:#e53935;color:#fff;font-size:.65rem;min-width:18px;height:18px;padding:0 5px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-weight:600}.notif-panel{position:absolute;top:42px;right:0;width:320px;background:#fff;border-radius:8px;box-shadow:0 12px 28px #00000026;z-index:9999;overflow:hidden;animation:fadeIn .15s ease-out}@keyframes fadeIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.notif-header{padding:12px 16px;font-weight:600;font-size:.9rem;border-bottom:1px solid #eee}.notif-list{max-height:360px;overflow-y:auto}.notif-item{display:flex;align-items:center;padding:12px 14px;cursor:pointer;transition:background .15s ease}.notif-item:hover{background:#f5f7fa}.notif-left{width:36px;height:36px;border-radius:50%;background:#e3f2fd;color:#1976d2;display:flex;align-items:center;justify-content:center;margin-right:12px}.notif-body{flex:1;min-width:0;display:flex;gap:5px;flex-direction:column}.notif-title{font-size:.85rem;font-weight:600;color:#222;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notif-text{font-size:.75rem;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.count{background:#1976d2;color:#fff;font-size:.65rem;min-width:20px;height:20px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-weight:600}.notif-item{background:#f0f6ff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] }); }
707
+ }
708
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetNotificationComponent, decorators: [{
709
+ type: Component,
710
+ args: [{ selector: 'chat-widget-notification', standalone: true, imports: [
711
+ CommonModule,
712
+ FormsModule
713
+ ], providers: [ChatLauncherComponent], template: "<div (click)=\"toggle()\" class=\"notif-icon\">\r\n <i class=\"pi pi-bell\" [class.pulse]=\"socketService.hasNewNotification()\"></i>\r\n\r\n @if (totalCount() > 0) {\r\n <span class=\"dot\"></span>\r\n <span class=\"badge\">{{ totalCount() }}</span>\r\n }\r\n</div>\r\n\r\n@if (open) {\r\n <div class=\"notif-panel\">\r\n <div class=\"notif-list\">\r\n @for (n of socketService.notifications(); track n.entityId + '_' + n.eventType) {\r\n <div class=\"notif-item\" (click)=\"openRoom(n)\">\r\n <div class=\"notif-left\">\r\n <i class=\"pi pi-comments\"></i>\r\n </div>\r\n\r\n <div class=\"notif-body\">\r\n <div class=\"notif-title\">{{ n?.payload?.chatRoomName }}</div>\r\n <div class=\"notif-text\">\r\n @if (n.eventType === 'NEW_MESSAGE') {\r\n New Message Received\r\n }\r\n\r\n @if (n.eventType === 'USER_ADDED') {\r\n You have been added to the chat\r\n }\r\n </div>\r\n </div>\r\n\r\n<!-- <span class=\"count\">{{ n.count }}</span>-->\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n}\r\n", styles: [".notif-icon{position:relative;cursor:pointer;display:flex;align-items:center;justify-content:center}.notif-icon i{font-size:1.4rem;color:#444}.pulse{animation:pulse 1.2s ease-out}@keyframes pulse{0%{transform:scale(1)}40%{transform:scale(1.2)}to{transform:scale(1)}}.dot{position:absolute;top:2px;right:4px;width:8px;height:8px;background:#e53935;border-radius:50%}.badge{position:absolute;top:-6px;right:-10px;background:#e53935;color:#fff;font-size:.65rem;min-width:18px;height:18px;padding:0 5px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-weight:600}.notif-panel{position:absolute;top:42px;right:0;width:320px;background:#fff;border-radius:8px;box-shadow:0 12px 28px #00000026;z-index:9999;overflow:hidden;animation:fadeIn .15s ease-out}@keyframes fadeIn{0%{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:translateY(0)}}.notif-header{padding:12px 16px;font-weight:600;font-size:.9rem;border-bottom:1px solid #eee}.notif-list{max-height:360px;overflow-y:auto}.notif-item{display:flex;align-items:center;padding:12px 14px;cursor:pointer;transition:background .15s ease}.notif-item:hover{background:#f5f7fa}.notif-left{width:36px;height:36px;border-radius:50%;background:#e3f2fd;color:#1976d2;display:flex;align-items:center;justify-content:center;margin-right:12px}.notif-body{flex:1;min-width:0;display:flex;gap:5px;flex-direction:column}.notif-title{font-size:.85rem;font-weight:600;color:#222;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.notif-text{font-size:.75rem;color:#666;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.count{background:#1976d2;color:#fff;font-size:.65rem;min-width:20px;height:20px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-weight:600}.notif-item{background:#f0f6ff}\n"] }]
714
+ }], ctorParameters: () => [], propDecorators: { chatRoomDetails: [{
715
+ type: Output
716
+ }] } });
717
+
718
+ class ChatWidgetModule {
719
+ static forRoot(config) {
720
+ return {
721
+ ngModule: ChatWidgetModule,
722
+ providers: [
723
+ {
724
+ provide: CHAT_CONFIG,
725
+ useValue: config
726
+ }
727
+ ]
728
+ };
729
+ }
730
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
731
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetModule, imports: [CommonModule,
732
+ ChatLauncherComponent,
733
+ ChatWindowComponent,
734
+ AllChatsComponent,
735
+ ChatWidgetNotificationComponent], exports: [ChatLauncherComponent] }); }
736
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetModule, imports: [CommonModule,
737
+ ChatLauncherComponent,
738
+ ChatWindowComponent,
739
+ AllChatsComponent,
740
+ ChatWidgetNotificationComponent] }); }
741
+ }
742
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetModule, decorators: [{
743
+ type: NgModule,
744
+ args: [{
745
+ declarations: [],
746
+ imports: [
747
+ CommonModule,
748
+ ChatLauncherComponent,
749
+ ChatWindowComponent,
750
+ AllChatsComponent,
751
+ ChatWidgetNotificationComponent
752
+ ],
753
+ exports: [ChatLauncherComponent]
754
+ }]
755
+ }] });
756
+
757
+ class ChatWidgetService {
758
+ constructor() { }
759
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
760
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetService, providedIn: 'root' }); }
761
+ }
762
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: ChatWidgetService, decorators: [{
763
+ type: Injectable,
764
+ args: [{ providedIn: 'root' }]
765
+ }], ctorParameters: () => [] });
766
+
767
+ /*
768
+ * Public API Surface of chat-widget
769
+ */
770
+
771
+ /**
772
+ * Generated bundle index. Do not edit.
773
+ */
774
+
775
+ export { AllChatsComponent, ChatLauncherComponent, ChatWidgetModule, ChatWidgetNotificationComponent, ChatWidgetService };
776
+ //# sourceMappingURL=arthakosh-chat.mjs.map