From 1a0bc902ac3755a9e769802f884420685949cbd7 Mon Sep 17 00:00:00 2001 From: akastijn Date: Sun, 9 Aug 2026 02:04:11 +0200 Subject: [PATCH] Refactor user name display logic: integrate `MiniMessageComponent` in `UserListComponent` and transition to `ProcessedMiniMessageNode`. Add server state caching in `ChatService` with eager state dispatch on subscription. --- .../services/chat/ChatService.java | 33 ++++++++++++------- .../user-list/user-list.component.html | 4 +-- .../user-list/user-list.component.ts | 9 ++--- .../chat/service/name-format.service.ts | 18 +++++++++- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java b/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java index 46a0e4a..1f24e11 100644 --- a/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java +++ b/backend/src/main/java/com/alttd/altitudeweb/services/chat/ChatService.java @@ -37,6 +37,7 @@ public class ChatService { private final EventPublisher eventPublisher; private final NavigableMap chatMessages = new TreeMap<>(); private final Map eventUserMap = new HashMap<>(); + private final Map serverStateCache = new HashMap<>(); @Value("${chat.allowed-servers}") private String[] allowedServers; @@ -73,7 +74,9 @@ public class ChatService { public SseEmitter subscribe(EventUser eventUser, String json) { String key = eventUser.uuid().toString(); eventUserMap.put(key, eventUser); - return eventPublisher.subscribe(key, json, this::handleSessionEnd); + SseEmitter emitter = eventPublisher.subscribe(key, json, this::handleSessionEnd); + sendServerStateToUser(key, eventUser); + return emitter; } private void handleSessionEnd(String key, Instant sessionStart, Instant sessionEnd) { @@ -124,18 +127,26 @@ public class ChatService { sendMessagesToListeners(chatMessageList); } - public void updateServerState(ServerStateDto serverStateDto) { - sendToUsers("server-state", (eventUser) -> { - if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) { - return ServerStateMapper.toJson(serverStateDto); - } + public synchronized void updateServerState(ServerStateDto serverStateDto) { + serverStateDto.getServers().forEach(serverDto -> serverStateCache.put(serverDto.getName(), serverDto)); + sendToUsers("server-state", this::getServerStateJson); + } - List filteredServers = serverStateDto.getServers().stream() - .filter(serverDto -> Arrays.asList(allowedServers).contains(serverDto.getName())) - .toList(); + private String getServerStateJson(EventUser eventUser) { + if (eventUser.hasPermission(PermissionClaimDto.HEAD_MOD)) { + return ServerStateMapper.toJson(new ServerStateDto().servers(serverStateCache.values().stream().toList())); + } - return ServerStateMapper.toJson(new ServerStateDto().servers(filteredServers)); - }); + List filteredServers = serverStateCache.values().stream() + .filter(serverDto -> Arrays.asList(allowedServers).contains(serverDto.getName())) + .toList(); + + return ServerStateMapper.toJson(new ServerStateDto().servers(filteredServers)); + } + + private void sendServerStateToUser(String key, EventUser eventUser) { + String json = getServerStateJson(eventUser); + eventPublisher.sendToUser(key, "server-state", json); } private void sendMessagesToListeners(List chatMessageList) { diff --git a/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.html b/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.html index 03a0e6e..a33ff8c 100644 --- a/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.html +++ b/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.html @@ -5,9 +5,7 @@ @for (player of server.players; track player.uuid) {
- @for (segment of getDisplayName(player); track $index) { - {{ segment.text }} - } +
} diff --git a/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.ts b/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.ts index 688df81..74614a2 100644 --- a/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.ts +++ b/frontend/src/app/pages/altitude/chat/components/user-list/user-list.component.ts @@ -1,13 +1,14 @@ import {Component, computed, inject} from '@angular/core'; import {ChatService} from '@pages/altitude/chat/service/chat.service'; import {NameFormatService} from '@pages/altitude/chat/service/name-format.service'; -import {ColoredNameSegment} from '@pages/altitude/chat/objects/colored-name-segment.object'; import {User} from '@pages/altitude/chat/objects/server-state.object'; +import {ProcessedMiniMessageNode} from '@pages/altitude/chat/mini-message/mini-message.types'; +import {MiniMessageComponent} from '@pages/altitude/chat/components/mini-message/mini-message.component'; @Component({ selector: 'app-user-list', standalone: true, - imports: [], + imports: [MiniMessageComponent], templateUrl: './user-list.component.html', styleUrl: './user-list.component.scss' }) @@ -29,7 +30,7 @@ export class UserListComponent { }; }); - protected getDisplayName(user: User): ColoredNameSegment[] { - return this.nameFormatService.parseColoredName(user.styledName || user.name); + protected getDisplayName(user: User): ProcessedMiniMessageNode { + return this.nameFormatService.getUserDisplayName(user); } } diff --git a/frontend/src/app/pages/altitude/chat/service/name-format.service.ts b/frontend/src/app/pages/altitude/chat/service/name-format.service.ts index ab368b1..13fe11d 100644 --- a/frontend/src/app/pages/altitude/chat/service/name-format.service.ts +++ b/frontend/src/app/pages/altitude/chat/service/name-format.service.ts @@ -2,6 +2,10 @@ import {inject, Injectable} from '@angular/core'; import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object'; import {ColoredNameSegment} from '@pages/altitude/chat/objects/colored-name-segment.object'; import {ChatService} from "./chat.service"; +import {User} from '@pages/altitude/chat/objects/server-state.object'; +import {normalizeComponent} from '@pages/altitude/chat/mini-message/normalize.util'; +import {precomputeNode} from '@pages/altitude/chat/mini-message/mini-message.util'; +import {ProcessedMiniMessageNode} from '@pages/altitude/chat/mini-message/mini-message.types'; @Injectable({providedIn: 'root'}) export class NameFormatService { @@ -12,6 +16,18 @@ export class NameFormatService { return this.parseColoredName(this.getNameFromType(channel)); } + public getUserDisplayName(user: User): ProcessedMiniMessageNode { + if (user.styledName) { + try { + return precomputeNode(normalizeComponent(JSON.parse(user.styledName))); + } catch { + return precomputeNode(normalizeComponent({text: user.name})); + } + } + + return precomputeNode(normalizeComponent({text: user.name})); + } + private readonly LEGACY_COLOR_MAP: Record = { '4': '#AA0000', 'c': '#FF5555', @@ -44,7 +60,7 @@ export class NameFormatService { return channel.name; } - public parseColoredName(name: string): ColoredNameSegment[] { + private parseColoredName(name: string): ColoredNameSegment[] { const markerRegex = /\{#([0-9a-fA-F]{6})(<>|[<>]?)\}|&([0-9a-fA-F])/g; const segments: ColoredNameSegment[] = []; let currentColor: string | undefined;