diff --git a/src/app/auth/login/login.component.ts b/src/app/auth/login/login.component.ts
index 631fb46..ac90846 100644
--- a/src/app/auth/login/login.component.ts
+++ b/src/app/auth/login/login.component.ts
@@ -11,7 +11,7 @@ import { Router } from '@angular/router';
styleUrls: ['../auth.module.scss', './login.component.scss']
})
export class LoginComponent implements OnInit {
- loginForm!: FormGroup;
+ public loginForm!: FormGroup;
constructor(
private authService: AuthService,
@@ -41,7 +41,7 @@ export class LoginComponent implements OnInit {
this.router.navigateByUrl('chat');
}
},
- error: _ => {
+ error: () => {
this.toastrService.error("API error", "Error");
}
})
diff --git a/src/app/chat/chat.module.ts b/src/app/chat/chat.module.ts
index d85d4a2..fe4fc1e 100644
--- a/src/app/chat/chat.module.ts
+++ b/src/app/chat/chat.module.ts
@@ -13,6 +13,10 @@ import { MatBadgeModule } from '@angular/material/badge';
import { MatButtonModule } from '@angular/material/button';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatIconModule } from '@angular/material/icon';
+import { MessageBarComponent } from './chat/message-bar/message-bar.component';
+import { MatFormFieldModule } from '@angular/material/form-field';
+import { MatInputModule } from '@angular/material/input';
+import { ReactiveFormsModule } from '@angular/forms';
@NgModule({
declarations: [
@@ -21,7 +25,8 @@ import { MatIconModule } from '@angular/material/icon';
FeedComponent,
ChannelEntryComponent,
MessageComponent,
- ProfilePictureComponent
+ ProfilePictureComponent,
+ MessageBarComponent
],
imports: [
CommonModule,
@@ -30,7 +35,10 @@ import { MatIconModule } from '@angular/material/icon';
MatBadgeModule,
MatButtonModule,
MatTooltipModule,
- MatIconModule
+ MatIconModule,
+ MatFormFieldModule,
+ MatInputModule,
+ ReactiveFormsModule
]
})
export class ChatModule { }
diff --git a/src/app/chat/chat/chat.component.html b/src/app/chat/chat/chat.component.html
index faa7a10..13e9e6f 100644
--- a/src/app/chat/chat/chat.component.html
+++ b/src/app/chat/chat/chat.component.html
@@ -1,6 +1,9 @@
-
-
\ No newline at end of file
diff --git a/src/app/chat/chat/chat.component.scss b/src/app/chat/chat/chat.component.scss
index d9c4120..4ea2f6f 100644
--- a/src/app/chat/chat/chat.component.scss
+++ b/src/app/chat/chat/chat.component.scss
@@ -1,5 +1,35 @@
#container {
- display: grid;
- grid-template-columns: minmax(auto, 150px) 1fr;
height: 100%;
+
+ display: grid;
+ grid-template-columns: 150px 1fr;
+ grid-template-rows: 65px 1fr auto;
+ gap: 0px;
+
+ app-toolbar {
+ grid-column: span 2 / span 2;
+
+ }
+
+ app-channel-list {
+ grid-row: span 2 / span 2;
+ grid-row-start: 2;
+ }
+
+ app-feed {
+ grid-row-start: 2;
+
+ border-left: 2px solid var(--mat-sys-surface-variant);
+ border-top: 2px solid var(--mat-sys-surface-variant);
+ border-radius: 10px 0 0 0;
+
+ background-color: var(--mat-sys-background);
+ }
+
+ app-message-bar {
+ grid-column-start: 2;
+ grid-row-start: 3;
+
+ border-left: 2px solid var(--mat-sys-surface-variant);
+ }
}
\ No newline at end of file
diff --git a/src/app/chat/chat/feed/feed.component.html b/src/app/chat/chat/feed/feed.component.html
index 575a4d5..8ee4812 100644
--- a/src/app/chat/chat/feed/feed.component.html
+++ b/src/app/chat/chat/feed/feed.component.html
@@ -1,4 +1,3 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/src/app/chat/chat/feed/feed.component.scss b/src/app/chat/chat/feed/feed.component.scss
index ef17fef..3c1fe79 100644
--- a/src/app/chat/chat/feed/feed.component.scss
+++ b/src/app/chat/chat/feed/feed.component.scss
@@ -1,9 +1,3 @@
:host {
- border-left: 2px solid var(--mat-sys-surface-variant);
- border-top: 2px solid var(--mat-sys-surface-variant);
- border-radius: 10px 0;
-
- background-color: var(--mat-sys-background);
-
overflow-y: scroll;
}
\ No newline at end of file
diff --git a/src/app/chat/chat/feed/feed.component.ts b/src/app/chat/chat/feed/feed.component.ts
index 8731944..ce468b8 100644
--- a/src/app/chat/chat/feed/feed.component.ts
+++ b/src/app/chat/chat/feed/feed.component.ts
@@ -1,4 +1,4 @@
-import { Component, Input, OnChanges, OnDestroy } from '@angular/core';
+import { AfterContentInit, AfterViewChecked, AfterViewInit, Component, ElementRef, Input, OnChanges, OnDestroy, ViewChild } from '@angular/core';
import { Message } from '../../../models/message';
import { ChatService } from '../../../services/chat.service';
import { Channel } from '../../../models/channel';
@@ -10,7 +10,7 @@ import { Subscription } from 'rxjs';
templateUrl: './feed.component.html',
styleUrl: './feed.component.scss'
})
-export class FeedComponent implements OnChanges, OnDestroy {
+export class FeedComponent implements OnChanges, OnDestroy, AfterViewInit {
private readonly DEFAULT_CHANNEL_ID: number = 1;
@Input('channel') public channel?: Channel;
@@ -18,7 +18,11 @@ export class FeedComponent implements OnChanges, OnDestroy {
public subscription?: Subscription;
- constructor(private chatService: ChatService) { }
+ constructor(private chatService: ChatService, private element: ElementRef) { }
+
+ ngAfterViewInit(): void {
+ this.scrollToBottom();
+ }
ngOnChanges(): void {
if (this.subscription) {
@@ -28,7 +32,11 @@ export class FeedComponent implements OnChanges, OnDestroy {
this.subscription =
this.chatService.GetMessages(this.channel?.id ?? this.DEFAULT_CHANNEL_ID)
- .subscribe(message => this.messages.push(message));
+ .subscribe(message => {
+ this.messages.push(message);
+
+ if (this.isAtBottom()) this.scrollToBottom();
+ });
}
ngOnDestroy(): void {
@@ -36,4 +44,13 @@ export class FeedComponent implements OnChanges, OnDestroy {
this.subscription.unsubscribe();
}
}
+
+ private isAtBottom(): boolean {
+ let element = this.element.nativeElement;
+ return Math.abs(element.scrollHeight - element.clientHeight - element.scrollTop) <= 100;
+ }
+
+ private scrollToBottom(): void {
+ setTimeout(() => this.element.nativeElement.scrollTop = this.element.nativeElement.scrollHeight, 100);
+ }
}
diff --git a/src/app/chat/chat/message-bar/message-bar.component.html b/src/app/chat/chat/message-bar/message-bar.component.html
new file mode 100644
index 0000000..bb89746
--- /dev/null
+++ b/src/app/chat/chat/message-bar/message-bar.component.html
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/app/chat/chat/message-bar/message-bar.component.scss b/src/app/chat/chat/message-bar/message-bar.component.scss
new file mode 100644
index 0000000..282de69
--- /dev/null
+++ b/src/app/chat/chat/message-bar/message-bar.component.scss
@@ -0,0 +1,23 @@
+form {
+ align-items: center;
+
+ padding: 10px;
+
+
+ display: grid;
+ grid-template-columns: 1fr auto;
+
+ mat-form-field {
+ padding-top: 5px;
+ }
+}
+
+button {
+ margin: 0 5px;
+ transform: translateY(-10px);
+
+ mat-icon {
+ margin: auto;
+ vertical-align: middle;
+ }
+}
\ No newline at end of file
diff --git a/src/app/chat/chat/message-bar/message-bar.component.ts b/src/app/chat/chat/message-bar/message-bar.component.ts
new file mode 100644
index 0000000..2a470cc
--- /dev/null
+++ b/src/app/chat/chat/message-bar/message-bar.component.ts
@@ -0,0 +1,44 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { ChatService } from '../../../services/chat.service';
+import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
+import { ToastrService } from 'ngx-toastr';
+
+@Component({
+ selector: 'app-message-bar',
+ standalone: false,
+ templateUrl: './message-bar.component.html',
+ styleUrl: './message-bar.component.scss'
+})
+export class MessageBarComponent implements OnInit {
+ private readonly MAX_MESSAGE_LENGTH: number = 120;
+
+ @Input('channel') channel!: number;
+
+ public messageForm!: FormGroup;
+
+ constructor(private chatService: ChatService,
+ private formBuilder: FormBuilder,
+ private toastrService: ToastrService) { }
+
+ ngOnInit(): void {
+ this.messageForm = this.formBuilder.group({
+ content: new FormControl('', [
+ Validators.required,
+ Validators.minLength(1),
+ Validators.maxLength(this.MAX_MESSAGE_LENGTH)
+ ])
+ });
+ }
+
+ public Send(): void {
+ if (this.messageForm.valid) {
+ this.chatService.SendMessage(
+ this.channel,
+ this.messageForm.get("content")!.value).subscribe(
+ {
+ next: () => this.messageForm.setValue({ "content": "" }),
+ error: () => this.toastrService.error("Failed to send message.", "Error")
+ });
+ }
+ }
+}
diff --git a/src/app/services/chat.service.ts b/src/app/services/chat.service.ts
index de15f61..08a11f4 100644
--- a/src/app/services/chat.service.ts
+++ b/src/app/services/chat.service.ts
@@ -5,13 +5,16 @@ import { Channel } from '../models/channel';
import { Message } from '../models/message';
import { HttpClient } from '@angular/common/http';
import { environment } from '../../environment/environment';
-import { GetMessagesResponse, ListAvailableChannelsResponse } from './responses/chat';
+import { GetMessagesResponse, ListAvailableChannelsResponse, SendMessageResponse } from './responses/chat';
@Injectable({
providedIn: 'root'
})
export class ChatService {
+ private readonly CHANNEL: string = "channel_id";
+ private readonly CONTENT: string = "content";
+
constructor(private http: HttpClient) { }
public ListAvailableChannels(): Observable {
@@ -59,4 +62,24 @@ export class ChatService {
messages.pipe(mergeMap(msgArray => from(msgArray))),
socket.asObservable());
}
+
+ public SendMessage(channel: number, content: string): Observable {
+ let url = `${environment.apiBase}/chat/send`;
+
+ let data = new FormData();
+ data.append(this.CHANNEL, channel.toString());
+ data.append(this.CONTENT, content);
+
+ return this.http.post(url, data, { withCredentials: true })
+ .pipe(
+ map(response => {
+ if (response.error) {
+ throw new Error(response.error);
+ }
+
+ return response;
+ }),
+ catchError(error => throwError(() => new Error(error.error.message)))
+ );
+ }
}
diff --git a/src/app/services/responses/chat.ts b/src/app/services/responses/chat.ts
index 24923f6..2f48cb9 100644
--- a/src/app/services/responses/chat.ts
+++ b/src/app/services/responses/chat.ts
@@ -8,4 +8,6 @@ export class ListAvailableChannelsResponse extends APIResponse {
export class GetMessagesResponse extends APIResponse {
public messages?: Message[];
-}
\ No newline at end of file
+}
+
+export class SendMessageResponse extends APIResponse { }
\ No newline at end of file
diff --git a/src/environment/environment.ts b/src/environment/environment.ts
index 3d9c8e8..0ba156e 100644
--- a/src/environment/environment.ts
+++ b/src/environment/environment.ts
@@ -1,4 +1,4 @@
export const environment = {
production: false,
- apiBase: "http://localhost:5000/api"
+ apiBase: "/api"
}
\ No newline at end of file
diff --git a/src/styles.scss b/src/styles.scss
index ffe6595..2d6b8c0 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -2,7 +2,7 @@
html,
body {
- height: 100%;
+ height: 100dvh;
margin: 0;
font-family: var(--mat-sys-body-medium-font);