From bdc86eb26c49c44e62241ced905b59feaa3c6d62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?BENEDEK=20L=C3=A1szl=C3=B3?= Date: Thu, 5 Jun 2025 03:18:56 +0200 Subject: [PATCH] material theming --- angular.json | 8 +- package.json | 2 +- src/app/app.component.html | 1 - src/app/app.component.scss | 0 src/app/app.component.ts | 7 +- src/app/chat/chat.module.ts | 6 +- .../channel-entry.component.html | 16 +- .../channel-entry.component.scss | 13 +- .../channel-list/channel-list.component.scss | 3 +- src/app/chat/chat/chat.component.scss | 2 +- src/app/chat/chat/feed/feed.component.scss | 9 +- src/app/chat/chat/feed/feed.component.ts | 17 +- .../profile-picture.component.scss | 2 +- .../profile-picture.component.ts | 10 +- src/app/common/toolbar/toolbar.component.scss | 4 + src/app/services/auth.service.ts | 15 +- src/app/services/chat.service.ts | 161 +++++++++++++++--- src/app/services/responses/user.ts | 6 + src/app/services/user.service.ts | 51 ++++-- src/environment/environment.prod.ts | 4 + src/environment/environment.ts | 4 + src/index.html | 2 +- src/styles.scss | 26 ++- 23 files changed, 283 insertions(+), 86 deletions(-) delete mode 100644 src/app/app.component.html delete mode 100644 src/app/app.component.scss create mode 100644 src/app/services/responses/user.ts create mode 100644 src/environment/environment.prod.ts create mode 100644 src/environment/environment.ts diff --git a/angular.json b/angular.json index 4220595..c32e89a 100644 --- a/angular.json +++ b/angular.json @@ -32,7 +32,6 @@ } ], "styles": [ - "@angular/material/prebuilt-themes/azure-blue.css", "node_modules/ngx-toastr/toastr.css", "src/styles.scss" ], @@ -40,6 +39,12 @@ }, "configurations": { "production": { + "fileReplacements": [ + { + "replace": "src/environment/environment.ts", + "with": "src/environment/environment.prod.ts" + } + ], "budgets": [ { "type": "initial", @@ -93,7 +98,6 @@ } ], "styles": [ - "@angular/material/prebuilt-themes/cyan-orange.css", "src/styles.scss" ], "scripts": [] diff --git a/package.json b/package.json index d259baf..5e882e2 100644 --- a/package.json +++ b/package.json @@ -38,4 +38,4 @@ "karma-jasmine-html-reporter": "~2.1.0", "typescript": "~5.7.2" } -} +} \ No newline at end of file diff --git a/src/app/app.component.html b/src/app/app.component.html deleted file mode 100644 index 67e7bd4..0000000 --- a/src/app/app.component.html +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/app/app.component.scss b/src/app/app.component.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a9c69e6..0a5429b 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -4,9 +4,6 @@ import { RouterOutlet } from '@angular/router'; @Component({ selector: 'app-root', imports: [RouterOutlet], - templateUrl: './app.component.html', - styleUrl: './app.component.scss' + template: "", }) -export class AppComponent { - -} +export class AppComponent { } diff --git a/src/app/chat/chat.module.ts b/src/app/chat/chat.module.ts index 4cc1552..c4d22db 100644 --- a/src/app/chat/chat.module.ts +++ b/src/app/chat/chat.module.ts @@ -10,6 +10,8 @@ import { MessageComponent } from './chat/feed/message/message.component'; import { ProfilePictureComponent } from './chat/feed/message/profile-picture/profile-picture.component'; 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'; @NgModule({ declarations: [ @@ -24,7 +26,9 @@ import { MatBadgeModule } from '@angular/material/badge'; CommonModule, ChatRoutingModule, ToolbarComponent, - MatBadgeModule + MatBadgeModule, + MatButtonModule, + MatTooltipModule ] }) export class ChatModule { } diff --git a/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.html b/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.html index 209483a..3580943 100644 --- a/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.html +++ b/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.html @@ -1,7 +1,11 @@ -
- - - {{channel.name}} - - +
+
diff --git a/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.scss b/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.scss index 3bb7cd2..addffd9 100644 --- a/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.scss +++ b/src/app/chat/chat/channel-list/channel-entry/channel-entry.component.scss @@ -1,10 +1,5 @@ -div { - padding: 5px 10px; - cursor: pointer; -} - -.selected { - color: var(--mat-sys-on-secondary); - background: linear-gradient(to right, var(--mat-sys-secondary), var(--mat-sys-secondary-container) 90%, rgba(0,0,0,0)); - transition: all .3s ease-in-out; +button { + width: 70%; + display: block; + margin: 10px auto; } \ No newline at end of file diff --git a/src/app/chat/chat/channel-list/channel-list.component.scss b/src/app/chat/chat/channel-list/channel-list.component.scss index e599875..e7a6dca 100644 --- a/src/app/chat/chat/channel-list/channel-list.component.scss +++ b/src/app/chat/chat/channel-list/channel-list.component.scss @@ -1,4 +1,3 @@ :host { - color: var(--mat-sys-on-surface); - background-color: var(--mat-sys-surface); + background-color: var(--mat-sys-surface-container); } \ 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 650d6ca..d9c4120 100644 --- a/src/app/chat/chat/chat.component.scss +++ b/src/app/chat/chat/chat.component.scss @@ -1,5 +1,5 @@ #container { display: grid; - grid-template-columns: minmax(auto, 200px) 1fr; + grid-template-columns: minmax(auto, 150px) 1fr; height: 100%; } \ 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 75740b9..1ce52f5 100644 --- a/src/app/chat/chat/feed/feed.component.scss +++ b/src/app/chat/chat/feed/feed.component.scss @@ -1,8 +1,7 @@ :host { - color: var(--mat-sys-on-background); - background-color: var(--mat-sys-background); - - border-left: 2px solid var(--mat-sys-secondary); - border-top: 2px solid var(--mat-sys-secondary); + 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); } \ 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 fcfcef8..d86becc 100644 --- a/src/app/chat/chat/feed/feed.component.ts +++ b/src/app/chat/chat/feed/feed.component.ts @@ -3,6 +3,7 @@ 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'; @Component({ selector: 'app-feed', @@ -14,14 +15,20 @@ export class FeedComponent implements OnChanges { private readonly DEFAULT_CHANNEL_ID: number = 1; @Input('channel') public channel?: Channel; - public messages!: Message[]; + public messages: Message[] = []; + + public subscription?: Subscription; constructor(private chatService: ChatService, private userService: UserService) { } ngOnChanges() { - this.chatService.GetMessages(this.channel?.id ?? this.DEFAULT_CHANNEL_ID) - .subscribe(messages => { - this.messages = messages; - }); + 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)); } } diff --git a/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.scss b/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.scss index 2dda265..f743977 100644 --- a/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.scss +++ b/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.scss @@ -3,5 +3,5 @@ img { width: 100%; border-radius: 50%; - box-shadow: 0px 0px 5px rgba(0, 0, 0, 1); + box-shadow: 0px 0px 5px var(--mat-sys-on-background); } \ No newline at end of file diff --git a/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts b/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts index bc35fd2..8b9752c 100644 --- a/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts +++ b/src/app/chat/chat/feed/message/profile-picture/profile-picture.component.ts @@ -1,5 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { UserService } from '../../../../../services/user.service'; +import { ToastrService } from 'ngx-toastr'; @Component({ selector: 'app-profile-picture', @@ -11,10 +12,15 @@ export class ProfilePictureComponent implements OnInit { @Input("username") public username!: string; public url?: string; - constructor(private userService: UserService) { } + constructor(private userService: UserService, private toastrService: ToastrService) { } ngOnInit(): void { this.userService.GetProfilePictureURL(this.username) - .subscribe(url => this.url = url); + .subscribe({ + next: url => this.url = url, + error: _ => { + this.toastrService.error("failed to fetch user info", "Error") + } + }); } } diff --git a/src/app/common/toolbar/toolbar.component.scss b/src/app/common/toolbar/toolbar.component.scss index b4e2bcb..9d8ba15 100644 --- a/src/app/common/toolbar/toolbar.component.scss +++ b/src/app/common/toolbar/toolbar.component.scss @@ -1,3 +1,7 @@ +mat-toolbar { + background-color: var(--mat-sys-surface-container); +} + .toolbar-right-side { width: 100%; diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts index efc42aa..f5bc6ce 100644 --- a/src/app/services/auth.service.ts +++ b/src/app/services/auth.service.ts @@ -3,13 +3,12 @@ import { inject, Injectable } from '@angular/core'; import { catchError, map, Observable, of } from 'rxjs'; import { LoginResponse, RegisterResponse } from './responses/auth'; import { ActivatedRouteSnapshot, CanActivateFn, Router, RouterStateSnapshot } from '@angular/router'; +import { environment } from '../../environment/environment'; @Injectable({ providedIn: 'root' }) export class AuthService { - private readonly API_BASE: string = "http://localhost:5000" - private readonly SESSION_COOKIE: string = "session"; private readonly USERNAME_FIELD: string = "username"; private readonly PASSWORD_FIELD: string = "password"; @@ -18,7 +17,7 @@ export class AuthService { constructor(private http: HttpClient) { } public Login(username: string, password: string): Observable { - let url = `${this.API_BASE}/auth/login`; + let url = `${environment.apiBase}/auth/login`; let formData = new FormData(); formData.append(this.USERNAME_FIELD, username); @@ -28,7 +27,7 @@ export class AuthService { } public Register(username: string, password: string, repeatPassword: string): Observable { - let url = `${this.API_BASE}/auth/register`; + let url = `${environment.apiBase}/auth/register`; let formData = new FormData(); formData.append(this.USERNAME_FIELD, username); @@ -39,7 +38,7 @@ export class AuthService { } public Logout(): Observable { - let url = `${this.API_BASE}/auth/logout`; + let url = `${environment.apiBase}/auth/logout`; return this.http.get(url, { withCredentials: true }).pipe( map(() => true), @@ -48,7 +47,7 @@ export class AuthService { } public Bump(): Observable { - let url = `${this.API_BASE}/auth/bump` + let url = `${environment.apiBase}/auth/bump` return this.http.get(url, { withCredentials: true }).pipe( map(() => true), @@ -68,8 +67,8 @@ export class AuthService { } export const IsLoggedInCanActivate: CanActivateFn = ( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot + _: ActivatedRouteSnapshot, + __: RouterStateSnapshot ) => { if (inject(AuthService).IsLoggedIn()) { return true; diff --git a/src/app/services/chat.service.ts b/src/app/services/chat.service.ts index e7ded5c..8e82b80 100644 --- a/src/app/services/chat.service.ts +++ b/src/app/services/chat.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; +import { from, Observable } from 'rxjs'; import { Channel } from '../models/channel'; import { Message } from '../models/message'; @@ -21,7 +21,7 @@ export class ChatService { }, { id: 1, - name: 'gaming', + name: 'XIV. Leo', description: 'this is another channel' }, ]); @@ -32,25 +32,142 @@ export class ChatService { // 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 { - return new Observable(subscriber => { - subscriber.next([ - { - id: 1, - sender: 'Test User 1', - channel: 1, - time: new Date(), - content: 'this is my first message' - }, - { - id: 2, - sender: 'Test User 2', - channel: 2, - time: new Date(), - content: 'this is my second message' - } - ]); - subscriber.complete(); - }); + public GetMessages(channelID: number): Observable { + + 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' + } + ]); } } diff --git a/src/app/services/responses/user.ts b/src/app/services/responses/user.ts new file mode 100644 index 0000000..0c5fccb --- /dev/null +++ b/src/app/services/responses/user.ts @@ -0,0 +1,6 @@ +import { User } from "../../models/user"; +import { APIResponse } from "./basic"; + +export class UserInfoResponse extends APIResponse { + public user?: User; +} \ No newline at end of file diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index 112fe1c..a887b87 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -1,23 +1,52 @@ import { Injectable } from '@angular/core'; -import { Observable } from 'rxjs'; +import { catchError, map, Observable, throwError } from 'rxjs'; import { User } from '../models/user'; +import { HttpClient } from '@angular/common/http'; +import { UserInfoResponse } from './responses/user'; +import { environment } from '../../environment/environment'; @Injectable({ providedIn: 'root' }) export class UserService { - constructor() { } + private users: Map = new Map(); - // TODO: implement - public GetProfilePictureURL(username: string): Observable { - return new Observable(subscriber => { - subscriber.next("https://i.pinimg.com/736x/00/70/16/00701602b0eac0390b3107b9e2a665e0.jpg"); - subscriber.complete(); - }); + constructor(private http: HttpClient) { } + + public GetUser(username: string): Observable { + if (this.users.has(username)) { + return new Observable(subscriber => { + subscriber.next(this.users.get(username)!); + subscriber.complete(); + }); + } else { + let url = `${environment.apiBase}/user/info/${username}` + return this.http.get(url, { withCredentials: true }).pipe( + map(response => { + if (response.error) { + throw new Error(response.error); + } + + if (response.user) { + this.users.set(username, response.user); + return response.user; + } + + throw new Error("bad API response, missing user with no error"); + }), + catchError(error => throwError(() => new Error(error.error.message))) + ); + } } - // TODO: implement - public GetUser(username: string): Observable { - throw new Error('Not implemented'); + public GetProfilePictureURL(username: string): Observable { + if (this.users.has(username)) { + return new Observable(subscriber => { + subscriber.next(this.users.get(username)!.picture); + subscriber.complete(); + }); + } else { + return this.GetUser(username).pipe(map(user => user.picture)); + } } } diff --git a/src/environment/environment.prod.ts b/src/environment/environment.prod.ts new file mode 100644 index 0000000..d59ecc4 --- /dev/null +++ b/src/environment/environment.prod.ts @@ -0,0 +1,4 @@ +export const environment = { + production: true, + apiBase: "https://chat.tek.govt.hu/api" +} \ No newline at end of file diff --git a/src/environment/environment.ts b/src/environment/environment.ts new file mode 100644 index 0000000..a367b01 --- /dev/null +++ b/src/environment/environment.ts @@ -0,0 +1,4 @@ +export const environment = { + production: false, + apiBase: "http://localhost:5000" +} \ No newline at end of file diff --git a/src/index.html b/src/index.html index 4fb474e..47e6b64 100644 --- a/src/index.html +++ b/src/index.html @@ -2,7 +2,7 @@ - Ui + Chat diff --git a/src/styles.scss b/src/styles.scss index 9862660..ffe6595 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -1,4 +1,24 @@ -/* You can add global styles to this file, and also import other style files */ +@use '@angular/material' as mat; -html, body { height: 100%; } -body { margin: 0; font-family: Roboto, "Helvetica Neue", sans-serif; } \ No newline at end of file +html, +body { + height: 100%; + margin: 0; + + font-family: var(--mat-sys-body-medium-font); + background-color: var(--mat-sys-surface-container); + + color-scheme: light dark; + @include mat.theme(( + color: mat.$azure-palette, + typography: Roboto, + density: 0)); +} + +.force-light-mode { + color-scheme: light !important; +} + +.force-dark-mode { + color-scheme: dark !important; +} \ No newline at end of file