Replace servers with channels in chat functionality: refactored UI components, service logic, and message model to support channels (SERVER, DM, PARTY, GAC).

This commit is contained in:
2026-08-01 19:10:33 +02:00
parent 0d6a4ee8dd
commit f9f05dcc88
16 changed files with 152 additions and 102 deletions
@@ -9,8 +9,8 @@ export class NotificationService {
this.getStoredNotificationSettings()
);
public notify(server: string) {
if (!this.isNotificationEnabled(server)) {
public notify(channel: string) {
if (!this.isNotificationEnabled(channel)) {
return;
}
this.playSound();
@@ -38,23 +38,23 @@ export class NotificationService {
});
}
public isNotificationEnabled(server: string): boolean {
return this._notificationEnabled()[server];
public isNotificationEnabled(channel: string): boolean {
return this._notificationEnabled()[channel];
}
public enableNotifications(server: string) {
this.setNotificationEnabled(server, true);
public enableNotifications(channel: string) {
this.setNotificationEnabled(channel, true);
this.enableSound()
}
public disableNotifications(server: string) {
this.setNotificationEnabled(server, false);
public disableNotifications(channel: string) {
this.setNotificationEnabled(channel, false);
}
private setNotificationEnabled(server: string, enabled: boolean) {
private setNotificationEnabled(channel: string, enabled: boolean) {
const notificationSettings = {
...this._notificationEnabled(),
[server]: enabled
[channel]: enabled
};
this._notificationEnabled.set(notificationSettings);
@@ -4,7 +4,7 @@ import {EventSourcePolyfill} from 'event-source-polyfill';
import {ChatMessage} from '@pages/altitude/chat/objects/chat-message.object';
import {RawChatMessage} from '@pages/altitude/chat/objects/raw-chat-message.object';
import {normalizeComponent} from '@pages/altitude/chat/mini-message/normalize.util';
import {ChatServer} from '@pages/altitude/chat/objects/chat-server.object';
import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object';
import {NotificationService} from '@pages/altitude/chat/service/chat-notification.service';
interface SsePayloadEvent {
@@ -17,11 +17,18 @@ export class ChatService implements OnDestroy {
private readonly notificationService: NotificationService = inject(NotificationService);
private readonly authService: AuthService = inject(AuthService)
private readonly _messages = signal<ChatMessage[]>([])
public readonly messages = computed(() => this._messages().filter((message) => message.server === this._selectedServer()).sort((a, b) => b.timestamp - a.timestamp));
private readonly _servers = signal<ChatServer[]>([])
public readonly servers = computed(() => this._servers().sort((a, b) => a.server.localeCompare(b.server)));
private readonly _selectedServer = signal<string>('');
public readonly selectedServer = this._selectedServer.asReadonly()
private readonly _channels = signal<ChatChannel[]>([])
public readonly channels = computed(() => this._channels().sort((a, b) => a.name.localeCompare(b.name)));
private readonly _selectedChannel = signal<ChatChannel | null>(null);
public readonly selectedChannel = this._selectedChannel.asReadonly()
public readonly messages = computed(() => {
const selected = this._selectedChannel();
if (!selected) return [];
return this._messages()
.filter((message) => message.channelName === selected.name && message.channelType === selected.type)
.sort((a, b) => b.timestamp - a.timestamp);
});
connect() {
if (this.eventSource) {
@@ -50,14 +57,17 @@ export class ChatService implements OnDestroy {
this.on(source, 'chat', (event) => {
const chatMessages = this.processChatMessages(event);
chatMessages.forEach((message) => {
const find = this.servers().find(server => message.server === server.server && message.server !== this._selectedServer());
const selected = this._selectedChannel();
const isSelected = selected && selected.name === message.channelName && selected.type === message.channelType;
const find = this._channels().find(channel => message.channelName === channel.name && message.channelType === channel.type && !isSelected);
if (find) {
find.unreadMessages++;
}
})
new Set(chatMessages.map(message => message.server))
.forEach((server) => {
this.notificationService.notify(server);
new Set(chatMessages.map(message => `${message.channelType}:${message.channelName}`))
.forEach((channelKeyStr) => {
this.notificationService.notify(channelKeyStr);
});
});
@@ -68,30 +78,56 @@ export class ChatService implements OnDestroy {
private processChatMessages(event: SsePayloadEvent): ChatMessage[] {
const raw = JSON.parse(event.data) as RawChatMessage[];
const messages: ChatMessage[] = raw.map((m) => ({
...m,
messageJson: normalizeComponent(JSON.parse(m.messageJson)),
}));
const messages: ChatMessage[] = raw.map((m) => {
const message: ChatMessage = {
...m,
messageJson: normalizeComponent(JSON.parse(m.messageJson)),
};
const channelKey = this.getChannelKey(message);
message.channelName = channelKey.name;
message.channelType = channelKey.type;
return message;
});
this._messages.update((old) => [...old, ...messages]);
//TODO [Stijn] [2026-07-19]: Handle servers by getting them from backend
messages.map((message) => message.server).forEach((server) => {
const found = this._servers().find(existing => existing.server === server);
messages.forEach((message) => {
const found = this._channels().find(existing => existing.name === message.channelName && existing.type === message.channelType);
if (!found) {
this._servers.update((old) => [...old, {server: server, unreadMessages: 0}]);
this._channels.update((old) => [...old, {
name: message.channelName!,
type: message.channelType!,
unreadMessages: 0
}]);
}
})
return messages;
}
public selectServer(server: string) {
this._selectedServer.set(server);
this._servers.update((old) =>
old.map((chatServer) =>
chatServer.server === server
? {...chatServer, unreadMessages: 0}
: chatServer
private getChannelKey(message: ChatMessage): { name: string, type: ChatChannel['type'] } {
if (message.type === 'GAC') {
return {name: 'Global Admin Chat', type: 'GAC'};
}
if (message.type === 'PARTY') {
return {name: message.channel, type: 'PARTY'};
}
if (message.type === 'MSG') {
const uuid = this.authService.getUuid();
if (message.uuid === uuid) {
return {name: message.receiver, type: 'DM'};
}
return {name: message.uuid, type: 'DM'};
}
return {name: message.server, type: 'SERVER'};
}
public selectChannel(channel: ChatChannel) {
this._selectedChannel.set(channel);
this._channels.update((old) =>
old.map((c) =>
c.name === channel.name && c.type === channel.type
? {...c, unreadMessages: 0}
: c
)
);
}