From d1acf39a22b0083be51f558fa22ad5ce252aa551 Mon Sep 17 00:00:00 2001 From: akastijn Date: Sat, 1 Aug 2026 21:16:37 +0200 Subject: [PATCH] Add gradient-colored display names in ChannelListComponent: implemented name parsing with gradient support, refactored UI logic to dynamically render colored name segments, and improved message handling in ChatService with null channel filtering. --- .../channel-list/channel-list.component.html | 12 +- .../channel-list/channel-list.component.ts | 115 ++++++++++++++++++ .../altitude/chat/service/chat.service.ts | 14 ++- 3 files changed, 128 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.html b/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.html index 793ef70..12eaddb 100644 --- a/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.html +++ b/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.html @@ -4,13 +4,11 @@
- @if (channel.type === 'PARTY') { - {{ chatService.partieMap().get(channel.name) ?? channel.name }} - } @else if (channel.type === 'DM') { - {{ chatService.userNameMap().get(channel.name) ?? channel.name }} - } @else { - {{ channel.name }} - } + + @for (segment of parseColoredName(getDisplayName(channel)); track $index) { + {{ segment.text }} + } + @if (channel.unreadMessages > 0) {
diff --git a/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.ts b/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.ts index fb484bb..326eb1a 100644 --- a/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.ts +++ b/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.ts @@ -3,6 +3,11 @@ import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object'; import {CircleSvgComponent} from '@pages/altitude/chat/svg/circle.svg'; import {ChatService} from '@pages/altitude/chat/service/chat.service'; +interface ColoredNameSegment { + text: string; + color?: string; +} + @Component({ selector: 'app-channel-list', imports: [ @@ -26,4 +31,114 @@ export class ChannelListComponent { }); } + protected getDisplayName(channel: ChatChannel): string { + if (channel.type === 'PARTY') { + return this.chatService.partieMap().get(channel.name) ?? channel.name; + } + if (channel.type === 'DM') { + return this.chatService.userNameMap().get(channel.name) ?? channel.name; + } + return channel.name; + } + + protected parseColoredName(name: string): ColoredNameSegment[] { + const markerRegex = /\{#([0-9a-fA-F]{6})([<>]?)}/g; + const segments: ColoredNameSegment[] = []; + let currentColor: string | undefined; + let gradientStartColor: string | undefined; + let gradientStartSegmentIndex: number | undefined; + let lastIndex = 0; + let match: RegExpExecArray | null; + + while ((match = markerRegex.exec(name)) !== null) { + this.addColoredTextSegment(segments, name.slice(lastIndex, match.index), currentColor); + + const markerColor = `#${match[1]}`; + const markerType = match[2]; + + if (markerType === '>') { + currentColor = markerColor; + gradientStartColor = markerColor; + gradientStartSegmentIndex = segments.length; + } else if (markerType === '<') { + if (gradientStartColor !== undefined && gradientStartSegmentIndex !== undefined) { + this.applyGradient(segments, gradientStartSegmentIndex, gradientStartColor, markerColor); + } + currentColor = markerColor; + gradientStartColor = undefined; + gradientStartSegmentIndex = undefined; + } else { + currentColor = markerColor; + gradientStartColor = undefined; + gradientStartSegmentIndex = undefined; + } + + lastIndex = markerRegex.lastIndex; + } + + this.addColoredTextSegment(segments, name.slice(lastIndex), currentColor); + + return segments.length > 0 ? segments : [{text: name}]; + } + + private addColoredTextSegment(segments: ColoredNameSegment[], text: string, color?: string): void { + if (text.length === 0) { + return; + } + + segments.push({text, color}); + } + + private applyGradient( + segments: ColoredNameSegment[], + startSegmentIndex: number, + startColor: string, + endColor: string + ): void { + const text = segments.slice(startSegmentIndex).map((segment) => segment.text).join(''); + const length = text.length; + const start = this.hexToRgb(startColor); + const end = this.hexToRgb(endColor); + + if (length === 0 || start === null || end === null) { + return; + } + + const gradientSegments: ColoredNameSegment[] = []; + + for (let i = 0; i < length; i++) { + const ratio = length === 1 ? 0 : i / (length - 1); + gradientSegments.push({ + text: text[i], + color: this.rgbToHex( + start.r + (end.r - start.r) * ratio, + start.g + (end.g - start.g) * ratio, + start.b + (end.b - start.b) * ratio + ) + }); + } + + segments.splice(startSegmentIndex, segments.length - startSegmentIndex, ...gradientSegments); + } + + private hexToRgb(hex: string): { r: number; g: number; b: number } | null { + const match = /^#?([0-9a-fA-F]{6})$/.exec(hex); + if (!match) { + return null; + } + + const value = Number.parseInt(match[1], 16); + return { + r: (value >> 16) & 255, + g: (value >> 8) & 255, + b: value & 255 + }; + } + + private rgbToHex(r: number, g: number, b: number): string { + return `#${[r, g, b] + .map((value) => Math.round(value).toString(16).padStart(2, '0')) + .join('')}`; + } + } diff --git a/frontend/src/app/pages/altitude/chat/service/chat.service.ts b/frontend/src/app/pages/altitude/chat/service/chat.service.ts index 097ef9b..41d5e41 100644 --- a/frontend/src/app/pages/altitude/chat/service/chat.service.ts +++ b/frontend/src/app/pages/altitude/chat/service/chat.service.ts @@ -90,10 +90,13 @@ export class ChatService implements OnDestroy { messageJson: normalizeComponent(JSON.parse(m.messageJson)), }; const channelKey = this.getChannelKey(message); + if (channelKey === null) { + return null; + } message.channelName = channelKey.name; message.channelType = channelKey.type; return message; - }); + }).filter((message) => message !== null) as ChatMessage[]; this._messages.update((old) => [...old, ...messages]); messages.forEach((message) => { @@ -110,7 +113,7 @@ export class ChatService implements OnDestroy { return messages; } - private getChannelKey(message: ChatMessage): { name: string, type: ChatChannel['type'] } { + private getChannelKey(message: ChatMessage): { name: string, type: ChatChannel['type'] } | null { if (message.type === 'GAC') { return {name: 'Global Admin Chat', type: 'GAC'}; } @@ -124,11 +127,10 @@ export class ChatService implements OnDestroy { if (message.type === 'MSG') { const uuid = this.authService.getUuid(); if (message.uuid === uuid) { - this.updateUserNameMap(message.receiver) - return {name: message.receiver, type: 'DM'}; + return null; } - this.updateUserNameMap(message.uuid) - return {name: message.uuid, type: 'DM'}; + this.updateUserNameMap(message.receiver) + return {name: message.receiver, type: 'DM'}; } return {name: message.server, type: 'SERVER'}; }