Compare commits
3 Commits
d8fd5028dd
...
ebbaf6716d
Author | SHA1 | Date | |
---|---|---|---|
ebbaf6716d | |||
757e593373 | |||
543dc2b9fb |
@ -74,7 +74,8 @@
|
||||
"buildTarget": "ui:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "ui:build:development"
|
||||
"buildTarget": "ui:build:development",
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
|
8
proxy.conf.json
Normal file
8
proxy.conf.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"/api": {
|
||||
"target": "http://localhost:5000",
|
||||
"secure": false,
|
||||
"ws": true,
|
||||
"changeOrigin": true
|
||||
}
|
||||
}
|
@ -13,8 +13,7 @@
|
||||
|
||||
button {
|
||||
display: block;
|
||||
margin-bottom: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
})
|
||||
|
@ -12,6 +12,11 @@ import { ToolbarComponent } from '../common/toolbar/toolbar.component';
|
||||
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: [
|
||||
@ -20,7 +25,8 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
FeedComponent,
|
||||
ChannelEntryComponent,
|
||||
MessageComponent,
|
||||
ProfilePictureComponent
|
||||
ProfilePictureComponent,
|
||||
MessageBarComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
@ -28,7 +34,11 @@ import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
ToolbarComponent,
|
||||
MatBadgeModule,
|
||||
MatButtonModule,
|
||||
MatTooltipModule
|
||||
MatTooltipModule,
|
||||
MatIconModule,
|
||||
MatFormFieldModule,
|
||||
MatInputModule,
|
||||
ReactiveFormsModule
|
||||
]
|
||||
})
|
||||
export class ChatModule { }
|
||||
|
@ -1,5 +1,5 @@
|
||||
button {
|
||||
width: 70%;
|
||||
width: 95%;
|
||||
display: block;
|
||||
margin: 10px auto;
|
||||
margin: 5px auto;
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Component, EventEmitter, OnChanges, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Channel } from '../../../models/channel';
|
||||
import { ChatService } from '../../../services/chat.service';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
@ -20,7 +20,7 @@ export class ChannelListComponent implements OnInit, OnDestroy {
|
||||
constructor(private chatService: ChatService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.chatService.ListChannels()
|
||||
this.chatService.ListAvailableChannels()
|
||||
.pipe(takeUntil(this.destroy))
|
||||
.subscribe(channels => {
|
||||
this.channels = channels;
|
||||
|
@ -1,6 +1,9 @@
|
||||
<app-toolbar />
|
||||
|
||||
<div id="container">
|
||||
<app-toolbar />
|
||||
|
||||
<app-channel-list (select)="Select($event)"></app-channel-list>
|
||||
|
||||
<app-feed [channel]="this.selectedChannel"></app-feed>
|
||||
|
||||
<app-message-bar [channel]="this.selectedChannel?.id ?? 1"></app-message-bar>
|
||||
</div>
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
<app-message
|
||||
*ngFor="let message of this.messages"
|
||||
[message]="message"
|
||||
/>
|
||||
<div #scrollable id="scrollable">
|
||||
<app-message *ngFor="let message of this.messages" [message]="message" />
|
||||
</div>
|
@ -1,7 +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;
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
import { Component, Input, OnChanges, OnDestroy, OnInit } 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 { UserService } from '../../../services/user.service';
|
||||
import { Channel } from '../../../models/channel';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@ -11,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;
|
||||
@ -19,9 +18,13 @@ export class FeedComponent implements OnChanges, OnDestroy {
|
||||
|
||||
public subscription?: Subscription;
|
||||
|
||||
constructor(private chatService: ChatService) { }
|
||||
constructor(private chatService: ChatService, private element: ElementRef<HTMLElement>) { }
|
||||
|
||||
ngOnChanges() {
|
||||
ngAfterViewInit(): void {
|
||||
this.scrollToBottom();
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.messages = [];
|
||||
@ -29,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 {
|
||||
@ -37,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);
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,8 @@
|
||||
<div class="message-container">
|
||||
<app-profile-picture [username]="this.message.sender" />
|
||||
<app-profile-picture [username]="this.message.sender_name" />
|
||||
|
||||
<div class="message-inner-container">
|
||||
<div class="message-sender">{{this.message.sender}}</div>
|
||||
<div class="message-sender">{{this.message.sender_name}}</div>
|
||||
<div class="message-content">{{this.message.content}}</div>
|
||||
</div>
|
||||
|
||||
|
@ -1 +1,2 @@
|
||||
<img [src]="this.url" [alt]="this.username">
|
||||
<img *ngIf="this.url != ''" [src]="this.url" [alt]="this.username">
|
||||
<mat-icon *ngIf="this.url == ''">face</mat-icon>
|
@ -1,7 +1,23 @@
|
||||
img {
|
||||
:host {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
aspect-ratio: 1;
|
||||
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 0px 5px var(--mat-sys-on-background);
|
||||
box-shadow: 0px 0px 2px var(--mat-sys-on-background);
|
||||
}
|
||||
|
||||
// style alt for missing images
|
||||
img {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
text-align: center;
|
||||
font-size: 50px;
|
||||
}
|
12
src/app/chat/chat/message-bar/message-bar.component.html
Normal file
12
src/app/chat/chat/message-bar/message-bar.component.html
Normal file
@ -0,0 +1,12 @@
|
||||
<form [formGroup]="messageForm" (submit)="Send()">
|
||||
|
||||
<mat-form-field>
|
||||
<mat-label>Message</mat-label>
|
||||
<textarea type="textarea" required matInput formControlName="content"></textarea>
|
||||
</mat-form-field>
|
||||
|
||||
<button mat-button type="button" (click)="Send()">
|
||||
<mat-icon>send</mat-icon>
|
||||
</button>
|
||||
|
||||
</form>
|
23
src/app/chat/chat/message-bar/message-bar.component.scss
Normal file
23
src/app/chat/chat/message-bar/message-bar.component.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
44
src/app/chat/chat/message-bar/message-bar.component.ts
Normal file
44
src/app/chat/chat/message-bar/message-bar.component.ts
Normal file
@ -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")
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,7 @@
|
||||
import { Timestamp } from "rxjs"
|
||||
|
||||
export class Message {
|
||||
public id!: number
|
||||
public sender!: string
|
||||
public channel!: number
|
||||
public sender_name!: string
|
||||
public channel_id!: number
|
||||
public time!: Date
|
||||
public content!: string
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { catchError, map, Observable, of } from 'rxjs';
|
||||
import { catchError, map, Observable, of, tap } from 'rxjs';
|
||||
import { LoginResponse, RegisterResponse } from './responses/auth';
|
||||
import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router';
|
||||
import { environment } from '../../environment/environment';
|
||||
@ -14,6 +14,8 @@ export class AuthService {
|
||||
private readonly PASSWORD_FIELD: string = "password";
|
||||
private readonly REPEAT_PASSWORD_FIELD: string = "repeatPassword";
|
||||
|
||||
private loggedIn: boolean = false;
|
||||
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
public Login(username: string, password: string): Observable<LoginResponse> {
|
||||
@ -51,29 +53,40 @@ export class AuthService {
|
||||
|
||||
return this.http.get(url, { withCredentials: true }).pipe(
|
||||
map(() => true),
|
||||
catchError(() => of(false))
|
||||
catchError(() => of(false)),
|
||||
tap(result => this.loggedIn = result)
|
||||
);
|
||||
}
|
||||
|
||||
public IsLoggedIn(): boolean {
|
||||
public HasToken(): boolean {
|
||||
let cookies = document.cookie.split(';');
|
||||
let found = false;
|
||||
cookies.forEach(cookie => {
|
||||
cookie = cookie.trim();
|
||||
found = found || cookie.startsWith(this.SESSION_COOKIE + "=") && cookie.split('=', 2)[1] != '';
|
||||
});
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
public IsLoggedIn(): boolean { return this.loggedIn; }
|
||||
}
|
||||
|
||||
export const IsLoggedInCanActivate: CanActivateFn = (
|
||||
_: ActivatedRouteSnapshot,
|
||||
__: RouterStateSnapshot
|
||||
) => {
|
||||
if (inject(AuthService).IsLoggedIn()) {
|
||||
return true;
|
||||
} else {
|
||||
inject(Router).navigateByUrl("auth/login");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
): Observable<boolean> => {
|
||||
const authService = inject(AuthService);
|
||||
const router = inject(Router);
|
||||
|
||||
return authService.Bump().pipe(
|
||||
map(isValid => {
|
||||
if (isValid) {
|
||||
return true;
|
||||
} else {
|
||||
router.navigate(['auth/login']);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
);
|
||||
};
|
@ -1,173 +1,85 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { from, Observable } from 'rxjs';
|
||||
import { catchError, from, map, merge, mergeMap, Observable, throwError } from 'rxjs';
|
||||
import { webSocket } from 'rxjs/webSocket';
|
||||
import { Channel } from '../models/channel';
|
||||
import { Message } from '../models/message';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from '../../environment/environment';
|
||||
import { GetMessagesResponse, ListAvailableChannelsResponse, SendMessageResponse } from './responses/chat';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ChatService {
|
||||
|
||||
constructor() { }
|
||||
private readonly CHANNEL: string = "channel_id";
|
||||
private readonly CONTENT: string = "content";
|
||||
|
||||
// TODO: implement
|
||||
public ListChannels(): Observable<Channel[]> {
|
||||
return new Observable<Channel[]>(subscriber => {
|
||||
subscriber.next([
|
||||
{
|
||||
id: 0,
|
||||
name: 'default',
|
||||
description: 'this is the default channel'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'XIV. Leo',
|
||||
description: 'this is another channel'
|
||||
},
|
||||
]);
|
||||
subscriber.complete();
|
||||
});
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
public ListAvailableChannels(): Observable<Channel[]> {
|
||||
let url = `${environment.apiBase}/chat/channels`
|
||||
return this.http.get<ListAvailableChannelsResponse>(url, { withCredentials: true })
|
||||
.pipe(
|
||||
map(response => {
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
if (response.channels) {
|
||||
return response.channels;
|
||||
}
|
||||
|
||||
throw new Error("bad API response, missing channels with no error");
|
||||
}),
|
||||
catchError(error => throwError(() => new Error(error.error.message)))
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
// TODO: refactor this so it first returns the n last messages,
|
||||
// then listens for incoming messages and forwards them as they come
|
||||
public GetMessages(channelID: number): Observable<Message> {
|
||||
let url = `${environment.apiBase}/chat/messages/${channelID}`;
|
||||
let messages: Observable<Message[]> =
|
||||
this.http.get<GetMessagesResponse>(url, { withCredentials: true })
|
||||
.pipe(
|
||||
map(response => {
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
return from([
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
sender: 'admin',
|
||||
channel: 1,
|
||||
time: new Date(),
|
||||
content: 'this is my first message'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
sender: 'admin',
|
||||
channel: 2,
|
||||
time: new Date(),
|
||||
content: 'this is my second message'
|
||||
}
|
||||
]);
|
||||
if (response.messages) {
|
||||
return response.messages;
|
||||
}
|
||||
|
||||
throw new Error("bad API response, missing messages with no error");
|
||||
}),
|
||||
catchError(error => throwError(() => new Error(error.error.message)))
|
||||
);
|
||||
|
||||
url = `${environment.apiBase}/chat/subscribe/${channelID}`;
|
||||
let socket = webSocket<Message>(url);
|
||||
|
||||
return merge(
|
||||
messages.pipe(mergeMap(msgArray => from(msgArray))),
|
||||
socket.asObservable());
|
||||
}
|
||||
|
||||
public SendMessage(channel: number, content: string): Observable<SendMessageResponse> {
|
||||
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<SendMessageResponse>(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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
13
src/app/services/responses/chat.ts
Normal file
13
src/app/services/responses/chat.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Channel } from "../../models/channel";
|
||||
import { Message } from "../../models/message";
|
||||
import { APIResponse } from "./basic";
|
||||
|
||||
export class ListAvailableChannelsResponse extends APIResponse {
|
||||
public channels?: Channel[];
|
||||
}
|
||||
|
||||
export class GetMessagesResponse extends APIResponse {
|
||||
public messages?: Message[];
|
||||
}
|
||||
|
||||
export class SendMessageResponse extends APIResponse { }
|
@ -1,4 +1,4 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiBase: "http://localhost:5000"
|
||||
apiBase: "/api"
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
height: 100dvh;
|
||||
margin: 0;
|
||||
|
||||
font-family: var(--mat-sys-body-medium-font);
|
||||
|
Loading…
Reference in New Issue
Block a user