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.

This commit is contained in:
akastijn 2026-08-09 02:04:11 +02:00
parent 8c02543bca
commit 1a0bc902ac
4 changed files with 45 additions and 19 deletions

View File

@ -37,6 +37,7 @@ public class ChatService {
private final EventPublisher eventPublisher;
private final NavigableMap<Instant, ChatMessage> chatMessages = new TreeMap<>();
private final Map<String, EventUser> eventUserMap = new HashMap<>();
private final Map<String, ServerDto> 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<ServerDto> 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<ServerDto> 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<ChatMessage> chatMessageList) {

View File

@ -5,9 +5,7 @@
@for (player of server.players; track player.uuid) {
<div class="user indented">
<span class="user-name">
@for (segment of getDisplayName(player); track $index) {
<span [style.color]="segment.color">{{ segment.text }}</span>
}
<mini-message [node]="getDisplayName(player)"></mini-message>
</span>
</div>
}

View File

@ -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);
}
}

View File

@ -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<string, string> = {
'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;