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 ee2ae29..68f84cd 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 @@ -11,7 +11,7 @@ [class.selected]="selectedChannel()?.name === channel.name && selectedChannel()?.type === channel.type" (click)="selectedChannelChange.emit(channel)"> - @for (segment of parseColoredName(getDisplayName(channel)); track $index) { + @for (segment of getDisplayName(channel); track $index) { {{ segment.text }} } diff --git a/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.scss b/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.scss index 6e1620a..26ca85f 100644 --- a/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.scss +++ b/frontend/src/app/pages/altitude/chat/components/channel-list/channel-list.component.scss @@ -1,6 +1,9 @@ .channel-list { height: 100%; background-color: #172133; + display: flex; + flex-direction: column; + min-height: 0; .channel { position: relative; @@ -44,8 +47,13 @@ cursor: pointer; user-select: none; } -} -.scroll { - overflow: scroll; + .scroll { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + box-sizing: border-box; + scrollbar-width: thin; + scrollbar-color: rgba(255, 255, 255, 0.35) transparent; + } } 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 a02dd35..26180d7 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 @@ -1,12 +1,8 @@ import {Component, computed, effect, inject, input, output, signal} from '@angular/core'; 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; -} +import {NameFormatService} from '@pages/altitude/chat/service/name-format.service'; +import {ColoredNameSegment} from '@pages/altitude/chat/objects/colored-name-segment.object'; @Component({ selector: 'app-channel-list', @@ -17,7 +13,7 @@ interface ColoredNameSegment { styleUrl: './channel-list.component.scss' }) export class ChannelListComponent { - protected readonly chatService = inject(ChatService); + protected readonly nameFormatService: NameFormatService = inject(NameFormatService); public readonly channels = input.required(); public readonly selectedChannel = input.required(); public readonly selectedChannelChange = output(); @@ -58,121 +54,7 @@ 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' || channel.type === 'SPY') { - return this.chatService.userNameMap().get(channel.name) ?? channel.name; - } - return channel.name; + protected getDisplayName(chatChannel: ChatChannel): ColoredNameSegment[] { + return this.nameFormatService.getDisplayName(chatChannel); } - - 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 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('')}`; - } - } diff --git a/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.html b/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.html index f2fce40..2636837 100644 --- a/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.html +++ b/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.html @@ -1,13 +1,17 @@ -
- - {{ selectedChannel()?.name }} - -
- @if (notificationEnabled()) { - - } @else { - +@if (selectedChannel(); as chatChannel) { +
+ + @for (segment of getDisplayName(chatChannel); track $index) { + {{ segment.text }} } + +
+ @if (notificationEnabled()) { + + } @else { + + } +
-
+} diff --git a/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.ts b/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.ts index 58fe6d8..8a69c21 100644 --- a/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.ts +++ b/frontend/src/app/pages/altitude/chat/components/chat-info/chat-info.component.ts @@ -4,6 +4,8 @@ import {NotificationService} from '@pages/altitude/chat/service/chat-notificatio import {NotificationEnabledSvgComponent} from '@pages/altitude/chat/svg/notification-enabled.svg'; import {NotificationDisabledSvgComponent} from '@pages/altitude/chat/svg/notification-disabled.svg'; import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object'; +import {ColoredNameSegment} from '@pages/altitude/chat/objects/colored-name-segment.object'; +import {NameFormatService} from '@pages/altitude/chat/service/name-format.service'; @Component({ selector: 'app-chat-info', @@ -16,6 +18,7 @@ import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object'; }) export class ChatInfoComponent { private readonly notificationService: NotificationService = inject(NotificationService); + private readonly nameFormatService: NameFormatService = inject(NameFormatService); public readonly selectedChannel = input.required(); protected readonly channelKey = computed(() => { @@ -34,4 +37,8 @@ export class ChatInfoComponent { this.notificationService.disableNotifications(this.channelKey()); } + protected getDisplayName(chatChannel: ChatChannel): ColoredNameSegment[] { + return this.nameFormatService.getDisplayName(chatChannel); + } + } diff --git a/frontend/src/app/pages/altitude/chat/objects/colored-name-segment.object.ts b/frontend/src/app/pages/altitude/chat/objects/colored-name-segment.object.ts new file mode 100644 index 0000000..9313980 --- /dev/null +++ b/frontend/src/app/pages/altitude/chat/objects/colored-name-segment.object.ts @@ -0,0 +1,4 @@ +export interface ColoredNameSegment { + text: string; + color?: string; +} 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 new file mode 100644 index 0000000..cd6d966 --- /dev/null +++ b/frontend/src/app/pages/altitude/chat/service/name-format.service.ts @@ -0,0 +1,131 @@ +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 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})(<>|[<>]?)}/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 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('')}`; + } +} diff --git a/frontend/src/app/pages/altitude/chat/util/colored-name-segment.ts b/frontend/src/app/pages/altitude/chat/util/colored-name-segment.ts new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/pages/altitude/chat/util/server-color.ts b/frontend/src/app/pages/altitude/chat/util/server-color.ts index 1a36376..035e9c3 100644 --- a/frontend/src/app/pages/altitude/chat/util/server-color.ts +++ b/frontend/src/app/pages/altitude/chat/util/server-color.ts @@ -6,7 +6,7 @@ export function getServerColor(server: string) { } else if (server === 'creative') { return "#FFA500"; } - return "white"; + return "#0290e8"; } export function getServerTextColor(server: string) {