material theming
This commit is contained in:
parent
cdfa1da90a
commit
bdc86eb26c
@ -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": []
|
||||
|
@ -1 +0,0 @@
|
||||
<router-outlet />
|
@ -4,9 +4,6 @@ import { RouterOutlet } from '@angular/router';
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
template: "<router-outlet />",
|
||||
})
|
||||
export class AppComponent {
|
||||
|
||||
}
|
||||
export class AppComponent { }
|
||||
|
@ -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 { }
|
||||
|
@ -1,7 +1,11 @@
|
||||
<div [title]="channel.description" [class.selected]="selected">
|
||||
|
||||
<span [matBadge]="this.hasAlert ? '1' : ''" matBadgeSize="small">
|
||||
<div>
|
||||
<button
|
||||
mat-stroked-button
|
||||
[matTooltip]="channel.description"
|
||||
matTooltipPosition="right"
|
||||
[matBadge]="this.hasAlert ? '1' : ''"
|
||||
matBadgeSize="medium"
|
||||
[disabled]="this.selected">
|
||||
{{channel.name}}
|
||||
</span>
|
||||
|
||||
</button>
|
||||
</div>
|
||||
|
@ -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;
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
:host {
|
||||
color: var(--mat-sys-on-surface);
|
||||
background-color: var(--mat-sys-surface);
|
||||
background-color: var(--mat-sys-surface-container);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
#container {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(auto, 200px) 1fr;
|
||||
grid-template-columns: minmax(auto, 150px) 1fr;
|
||||
height: 100%;
|
||||
}
|
@ -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);
|
||||
}
|
@ -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() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
this.subscription =
|
||||
this.chatService.GetMessages(this.channel?.id ?? this.DEFAULT_CHANNEL_ID)
|
||||
.subscribe(messages => {
|
||||
this.messages = messages;
|
||||
});
|
||||
.subscribe(message => this.messages.push(message));
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
@ -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")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,7 @@
|
||||
mat-toolbar {
|
||||
background-color: var(--mat-sys-surface-container);
|
||||
}
|
||||
|
||||
.toolbar-right-side {
|
||||
width: 100%;
|
||||
|
||||
|
@ -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<LoginResponse> {
|
||||
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<RegisterResponse> {
|
||||
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<boolean> {
|
||||
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<boolean> {
|
||||
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;
|
||||
|
@ -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<Message[]> {
|
||||
return new Observable<Message[]>(subscriber => {
|
||||
subscriber.next([
|
||||
public GetMessages(channelID: number): Observable<Message> {
|
||||
|
||||
return from([
|
||||
{
|
||||
id: 1,
|
||||
sender: 'Test User 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: 'Test User 2',
|
||||
sender: 'admin',
|
||||
channel: 2,
|
||||
time: new Date(),
|
||||
content: 'this is my second message'
|
||||
}
|
||||
]);
|
||||
subscriber.complete();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
6
src/app/services/responses/user.ts
Normal file
6
src/app/services/responses/user.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { User } from "../../models/user";
|
||||
import { APIResponse } from "./basic";
|
||||
|
||||
export class UserInfoResponse extends APIResponse {
|
||||
public user?: User;
|
||||
}
|
@ -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<string, User> = new Map<string, User>();
|
||||
|
||||
// TODO: implement
|
||||
public GetProfilePictureURL(username: string): Observable<string> {
|
||||
return new Observable<string>(subscriber => {
|
||||
subscriber.next("https://i.pinimg.com/736x/00/70/16/00701602b0eac0390b3107b9e2a665e0.jpg");
|
||||
constructor(private http: HttpClient) { }
|
||||
|
||||
public GetUser(username: string): Observable<User> {
|
||||
if (this.users.has(username)) {
|
||||
return new Observable<User>(subscriber => {
|
||||
subscriber.next(this.users.get(username)!);
|
||||
subscriber.complete();
|
||||
});
|
||||
} else {
|
||||
let url = `${environment.apiBase}/user/info/${username}`
|
||||
return this.http.get<UserInfoResponse>(url, { withCredentials: true }).pipe(
|
||||
map(response => {
|
||||
if (response.error) {
|
||||
throw new Error(response.error);
|
||||
}
|
||||
|
||||
// TODO: implement
|
||||
public GetUser(username: string): Observable<User> {
|
||||
throw new Error('Not implemented');
|
||||
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)))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public GetProfilePictureURL(username: string): Observable<string> {
|
||||
if (this.users.has(username)) {
|
||||
return new Observable<string>(subscriber => {
|
||||
subscriber.next(this.users.get(username)!.picture);
|
||||
subscriber.complete();
|
||||
});
|
||||
} else {
|
||||
return this.GetUser(username).pipe(map(user => user.picture));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
4
src/environment/environment.prod.ts
Normal file
4
src/environment/environment.prod.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiBase: "https://chat.tek.govt.hu/api"
|
||||
}
|
4
src/environment/environment.ts
Normal file
4
src/environment/environment.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiBase: "http://localhost:5000"
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Ui</title>
|
||||
<title>Chat</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
|
@ -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; }
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user