AltitudeWeb/frontend/src/app/pages/altitude/chat/service/name-format.service.ts

152 lines
4.8 KiB
TypeScript

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";
@Injectable({providedIn: 'root'})
export class NameFormatService {
private readonly chatService: ChatService = inject(ChatService);
public getDisplayName(channel: ChatChannel): ColoredNameSegment[] {
return this.parseColoredName(this.getNameFromType(channel));
}
private readonly LEGACY_COLOR_MAP: Record<string, string> = {
'4': '#AA0000',
'c': '#FF5555',
'6': '#FFAA00',
'e': '#FFFF55',
'2': '#00AA00',
'a': '#55FF55',
'b': '#55FFFF',
'3': '#00AAAA',
'1': '#0000AA',
'9': '#5555FF',
'd': '#FF55FF',
'5': '#AA00AA',
'f': '#FFFFFF',
'7': '#AAAAAA',
'8': '#555555',
'0': '#000000'
};
private getNameFromType(channel: ChatChannel): string {
if (channel.type === 'PARTY') {
return this.chatService.partieMap().get(channel.name) ?? channel.name;
}
if (channel.type === 'DM' || channel.type === 'SPY') {
return this.chatService.userNameMap().get(channel.name) ?? channel.name;
}
return channel.name;
}
private parseColoredName(name: string): ColoredNameSegment[] {
const markerRegex = /\{#([0-9a-fA-F]{6})(<>|[<>]?)\}|&([0-9a-fA-F])/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 isLegacy = !!match[3];
const markerColor = isLegacy ? this.LEGACY_COLOR_MAP[match[3]] : `#${match[1]}`;
const markerType = isLegacy ? undefined : 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 if (markerType === '<>') {
if (gradientStartColor !== undefined && gradientStartSegmentIndex !== undefined) {
this.applyGradient(segments, gradientStartSegmentIndex, gradientStartColor, markerColor);
}
currentColor = markerColor;
gradientStartColor = markerColor;
gradientStartSegmentIndex = segments.length;
} 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('')}`;
}
}