@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.
- package/README.md +24 -0
- package/esm2022/arthakosh-chat.mjs +5 -0
- package/esm2022/lib/all-chats/all-chats.component.mjs +121 -0
- package/esm2022/lib/chat-launcher/chat-launcher.component.mjs +96 -0
- package/esm2022/lib/chat-widget-notification/chat-widget-notification.component.mjs +69 -0
- package/esm2022/lib/chat-widget.module.mjs +47 -0
- package/esm2022/lib/chat-widget.service.mjs +12 -0
- package/esm2022/lib/chat-window/chat-window.component.mjs +210 -0
- package/esm2022/lib/core/config/chat-config.mjs +2 -0
- package/esm2022/lib/core/config/chat.tokens.mjs +4 -0
- package/esm2022/lib/core/models/chat.models.mjs +2 -0
- package/esm2022/lib/core/models/notification.model.mjs +2 -0
- package/esm2022/lib/core/services/chat.service.mjs +117 -0
- package/esm2022/lib/core/services/socket.service.mjs +148 -0
- package/esm2022/public-api.mjs +10 -0
- package/fesm2022/arthakosh-chat.mjs +776 -0
- package/fesm2022/arthakosh-chat.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/all-chats/all-chats.component.d.ts +23 -0
- package/lib/chat-launcher/chat-launcher.component.d.ts +34 -0
- package/lib/chat-widget-notification/chat-widget-notification.component.d.ts +22 -0
- package/lib/chat-widget.module.d.ts +14 -0
- package/lib/chat-widget.service.d.ts +6 -0
- package/lib/chat-window/chat-window.component.d.ts +49 -0
- package/lib/core/config/chat-config.d.ts +4 -0
- package/lib/core/config/chat.tokens.d.ts +3 -0
- package/lib/core/models/chat.models.d.ts +22 -0
- package/lib/core/models/notification.model.d.ts +6 -0
- package/lib/core/services/chat.service.d.ts +34 -0
- package/lib/core/services/socket.service.d.ts +38 -0
- package/package.json +42 -0
- 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
|