Group channels by type with toggleable collapsed view in ChannelListComponent: added grouping logic, collapsed state management, updated UI with group titles, and adjusted styles for indentation.

This commit is contained in:
akastijn 2026-08-01 21:46:02 +02:00
parent d1acf39a22
commit a9d788b319
3 changed files with 62 additions and 14 deletions

View File

@ -1,19 +1,26 @@
<div class="channel-list"> <div class="channel-list">
<p>Channels</p> <p>Channels</p>
@for (channel of channels(); track channel.type + channel.name) { @for (group of groupedChannels(); track group.type) {
<div class="channel" <p class="channel-group-title" (click)="toggleGroup(group.type)">
[class.selected]="selectedChannel()?.name === channel.name && selectedChannel()?.type === channel.type" {{ group.type }} {{ collapsedGroups().has(group.type) ? '▼' : '▲' }}
(click)="selectedChannelChange.emit(channel)"> </p>
<span class="channel-name"> @if (!collapsedGroups().has(group.type)) {
@for (segment of parseColoredName(getDisplayName(channel)); track $index) { @for (channel of group.channels; track channel.name) {
<span [style.color]="segment.color">{{ segment.text }}</span> <div class="channel indented"
} [class.selected]="selectedChannel()?.name === channel.name && selectedChannel()?.type === channel.type"
</span> (click)="selectedChannelChange.emit(channel)">
@if (channel.unreadMessages > 0) { <span class="channel-name">
<div class="notification"> @for (segment of parseColoredName(getDisplayName(channel)); track $index) {
<svg-circle></svg-circle> <span [style.color]="segment.color">{{ segment.text }}</span>
}
</span>
@if (channel.unreadMessages > 0) {
<div class="notification">
<svg-circle></svg-circle>
</div>
}
</div> </div>
} }
</div> }
} }
</div> </div>

View File

@ -30,4 +30,18 @@
color: #3b82f6; color: #3b82f6;
} }
} }
.channel.indented {
padding-left: 40px;
}
.channel-group-title {
padding: 10px 20px;
color: #888;
font-size: 0.8rem;
font-weight: bold;
text-transform: uppercase;
cursor: pointer;
user-select: none;
}
} }

View File

@ -1,4 +1,4 @@
import {Component, effect, inject, input, output} from '@angular/core'; import {Component, computed, effect, inject, input, output, signal} from '@angular/core';
import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object'; import {ChatChannel} from '@pages/altitude/chat/objects/chat-channel.object';
import {CircleSvgComponent} from '@pages/altitude/chat/svg/circle.svg'; import {CircleSvgComponent} from '@pages/altitude/chat/svg/circle.svg';
import {ChatService} from '@pages/altitude/chat/service/chat.service'; import {ChatService} from '@pages/altitude/chat/service/chat.service';
@ -22,6 +22,33 @@ export class ChannelListComponent {
public readonly selectedChannel = input.required<ChatChannel | null>(); public readonly selectedChannel = input.required<ChatChannel | null>();
public readonly selectedChannelChange = output<ChatChannel>(); public readonly selectedChannelChange = output<ChatChannel>();
protected readonly collapsedGroups = signal<Set<string>>(new Set());
protected toggleGroup(type: string) {
const next = new Set(this.collapsedGroups());
if (next.has(type)) {
next.delete(type);
} else {
next.add(type);
}
this.collapsedGroups.set(next);
}
protected readonly groupedChannels = computed(() => {
const channels = this.channels();
const groups: { type: ChatChannel['type'], channels: ChatChannel[] }[] = [];
const types: ChatChannel['type'][] = ['SERVER', 'DM', 'PARTY', 'GAC'];
for (const type of types) {
const typeChannels = channels.filter(c => c.type === type);
if (typeChannels.length > 0) {
groups.push({type, channels: typeChannels});
}
}
return groups;
});
constructor() { constructor() {
effect(() => { effect(() => {
const chatChannels = this.channels(); const chatChannels = this.channels();