move chat data do chat component, notifications

This commit is contained in:
BENEDEK László 2025-06-07 15:11:06 +02:00
parent ebbaf6716d
commit 6d690b5043
7 changed files with 90 additions and 75 deletions

View File

@ -3,7 +3,7 @@
mat-stroked-button
[matTooltip]="channel.description"
matTooltipPosition="right"
[matBadge]="this.hasAlert ? '1' : ''"
[matBadge]="this.notifications != 0 ? this.notifications : ''"
matBadgeSize="medium"
[disabled]="this.selected">
{{channel.name}}

View File

@ -11,14 +11,15 @@ export class ChannelEntryComponent implements OnChanges {
@Input("channel") public channel!: Channel;
@Input("selected") public selected!: boolean;
public hasAlert: boolean = false;
public notifications: number = 0;
ngOnChanges(changes: SimpleChanges): void {
ngOnChanges(_: SimpleChanges): void {
if (this.selected) {
this.hasAlert = false;
this.notifications = 0;
}
}
// TODO: subsribe to message alerts and display them
// unsubscirbe when leaving
public Notify() {
if (!this.selected) this.notifications++;
}
}

View File

@ -1,6 +1,4 @@
<app-channel-entry
*ngFor="let channel of this.channels; let i = index"
[channel]="channels[i]"
(click)="Select(i)"
[selected]="channels[i].id == selectedChannel?.id"
/>
<ng-container *ngIf="this.channels && this.channels.length != 0">
<app-channel-entry #entry *ngFor="let channel of this.channels; let i = index" [channel]="channel" (click)="Select(i)"
[selected]="i == SelectedChannel" />
</ng-container>

View File

@ -1,7 +1,6 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, Output, QueryList, ViewChildren } from '@angular/core';
import { Channel } from '../../../models/channel';
import { ChatService } from '../../../services/chat.service';
import { Subject, takeUntil } from 'rxjs';
import { ChannelEntryComponent } from './channel-entry/channel-entry.component';
@Component({
selector: 'app-channel-list',
@ -9,31 +8,24 @@ import { Subject, takeUntil } from 'rxjs';
templateUrl: './channel-list.component.html',
styleUrl: './channel-list.component.scss'
})
export class ChannelListComponent implements OnInit, OnDestroy {
@Output("select") selectEmitter: EventEmitter<Channel> = new EventEmitter<Channel>();
export class ChannelListComponent {
@Input('channels') public channels!: Channel[];
@Output("select") selectEmitter: EventEmitter<number> = new EventEmitter<number>();
public channels!: Channel[];
public selectedChannel?: Channel;
@ViewChildren("entry") entries!: QueryList<ChannelEntryComponent>;
private destroy = new Subject<void>();
constructor(private chatService: ChatService) { }
ngOnInit() {
this.chatService.ListAvailableChannels()
.pipe(takeUntil(this.destroy))
.subscribe(channels => {
this.channels = channels;
this.selectedChannel = this.channels[0];
});
private selectedChannel?: number;
public get SelectedChannel() : number {
return this.selectedChannel ?? 0;
}
ngOnDestroy(): void {
this.destroy.next();
this.destroy.complete();
}
constructor() { }
public Select(index: number): void {
this.selectEmitter.emit(this.selectedChannel = this.channels[index]);
this.selectEmitter.emit(this.selectedChannel = index);
}
public Notify(channel: number): void {
this.entries.find(entry=>entry.channel.id == channel)?.Notify()
}
}

View File

@ -1,9 +1,11 @@
<div id="container">
<app-toolbar />
<app-channel-list (select)="Select($event)"></app-channel-list>
<app-channel-list #channelList [channels]="this.GetChannelList()" (select)="Select($event)"></app-channel-list>
<app-feed [channel]="this.selectedChannel"></app-feed>
<app-feed #feed
[messages]="(this.channels && this.channels.length) != 0 ? this.channels[this.selectedChannel].messages : []"></app-feed>
<app-message-bar [channel]="this.selectedChannel?.id ?? 1"></app-message-bar>
<app-message-bar
[channel]="(this.channels && this.channels.length) != 0 ? this.channels[this.selectedChannel].channel.id : 1"></app-message-bar>
</div>

View File

@ -1,5 +1,10 @@
import { Component } from '@angular/core';
import { Component, OnInit, ViewChild } from '@angular/core';
import { Channel } from '../../models/channel';
import { Message } from '../../models/message';
import { ChatService } from '../../services/chat.service';
import { ToastrService } from 'ngx-toastr';
import { FeedComponent } from './feed/feed.component';
import { ChannelListComponent } from './channel-list/channel-list.component';
@Component({
selector: 'app-chat',
@ -7,10 +12,52 @@ import { Channel } from '../../models/channel';
templateUrl: './chat.component.html',
styleUrl: './chat.component.scss'
})
export class ChatComponent {
public selectedChannel?: Channel;
export class ChatComponent implements OnInit {
public selectedChannel: number = 0;
public channels: { channel: Channel, messages: Message[] }[] = [];
public Select(channel: Channel): void {
@ViewChild("feed") feed!: FeedComponent;
@ViewChild("channelList") channelList!: ChannelListComponent;
constructor(private chatService: ChatService, private toastrService: ToastrService) { }
ngOnInit(): void {
this.getChannels();
}
private getChannels(): void {
this.chatService.ListAvailableChannels().subscribe({
next: channels => {
this.channels = channels.map(channel => { return { channel: channel, messages: [] as Message[] } });
this.getMessages();
},
error: _ => {
this.toastrService.error("Failed to fetch channels.", "Error");
}
})
}
private getMessages(): void {
this.channels.forEach((channelObj, i) => {
this.chatService.GetMessages(channelObj.channel.id).subscribe({
next: messages => {
channelObj.messages.push(messages);
this.feed.ScrollEventHandler();
this.channelList.Notify(channelObj.channel.id);
},
error: _ => {
this.toastrService.error(`Failed to fetch messages for channel ${channelObj.channel.name}.`, "Error");
}
});
});
}
public GetChannelList(): Channel[] {
return this.channels.map(c => c.channel);
}
public Select(channel: number): void {
this.selectedChannel = channel;
this.feed.ScrollEventHandler();
}
}

View File

@ -1,8 +1,5 @@
import { AfterContentInit, AfterViewChecked, AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, ViewChild } from '@angular/core';
import { AfterViewInit, Component, DoCheck, ElementRef, EventEmitter, Input, IterableDiffer, IterableDiffers, Output } from '@angular/core';
import { Message } from '../../../models/message';
import { ChatService } from '../../../services/chat.service';
import { Channel } from '../../../models/channel';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-feed',
@ -10,41 +7,15 @@ import { Subscription } from 'rxjs';
templateUrl: './feed.component.html',
styleUrl: './feed.component.scss'
})
export class FeedComponent implements OnChanges, OnDestroy, AfterViewInit {
private readonly DEFAULT_CHANNEL_ID: number = 1;
export class FeedComponent implements AfterViewInit {
@Input('messages') public messages?: Message[];
@Input('channel') public channel?: Channel;
public messages: Message[] = [];
public subscription?: Subscription;
constructor(private chatService: ChatService, private element: ElementRef<HTMLElement>) { }
constructor(private element: ElementRef<HTMLElement>) { }
ngAfterViewInit(): void {
this.scrollToBottom();
}
ngOnChanges(): void {
if (this.subscription) {
this.subscription.unsubscribe();
this.messages = [];
}
this.subscription =
this.chatService.GetMessages(this.channel?.id ?? this.DEFAULT_CHANNEL_ID)
.subscribe(message => {
this.messages.push(message);
if (this.isAtBottom()) this.scrollToBottom();
});
}
ngOnDestroy(): void {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
private isAtBottom(): boolean {
let element = this.element.nativeElement;
return Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop) <= 100;
@ -53,4 +24,8 @@ export class FeedComponent implements OnChanges, OnDestroy, AfterViewInit {
private scrollToBottom(): void {
setTimeout(() => this.element.nativeElement.scrollTop = this.element.nativeElement.scrollHeight, 100);
}
public ScrollEventHandler(): void {
if (this.isAtBottom()) this.scrollToBottom();
}
}