mirror of https://github.com/Ombi-app/Ombi
Merge pull request #4083 from Ombi-app/feature/issues-rework
Feature/issues reworkpull/4091/head v4.0.1142
commit
5bd64a93e1
@ -0,0 +1,27 @@
|
||||
<mat-card class="issue-card" *ngIf="!deleted">
|
||||
<mat-card-header>
|
||||
<mat-card-title>{{issue.subject}}</mat-card-title>
|
||||
<mat-card-subtitle>{{issue.userReported?.userName}} on {{issue.createdDate | date:short}}</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<p>
|
||||
{{issue.description}}
|
||||
</p>
|
||||
</mat-card-content>
|
||||
<mat-card-actions>
|
||||
<button mat-raised-button (click)="openChat(issue)" color="accent"><i class="far fa-comments"></i> {{'Issues.Chat' | translate }}</button>
|
||||
<div *ngIf="isAdmin && settings;then content else empty">here is ignored</div>
|
||||
<ng-template #content>
|
||||
<button mat-raised-button color="accent"
|
||||
*ngIf="issue.status === IssueStatus.Pending && settings.enableInProgress"
|
||||
(click)="inProgress(issue)">{{'Issues.MarkInProgress' | translate }}</button>
|
||||
|
||||
<button mat-raised-button color="accent"
|
||||
*ngIf="issue.status === IssueStatus.Pending && !settings.enableInProgress || issue.status == IssueStatus.InProgress"
|
||||
(click)="resolve(issue)">{{'Issues.MarkResolved' | translate}}</button>
|
||||
|
||||
<button mat-raised-button color="warn" (click)="delete(issue)"><i class="far fa-times-circle"></i> {{'Issues.Delete' | translate}}</button></ng-template>
|
||||
<ng-template #empty></ng-template>
|
||||
</mat-card-actions>
|
||||
</mat-card>
|
||||
|
@ -0,0 +1,9 @@
|
||||
@import "~styles/variables.scss";
|
||||
|
||||
::ng-deep .issue-card {
|
||||
border: 3px solid $ombi-background-primary-accent;
|
||||
}
|
||||
|
||||
.top-spacing {
|
||||
margin-top:2%;
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { IIssues, IIssueSettings, IssueStatus } from "../../../interfaces";
|
||||
import { IssuesService, NotificationService } from "../../../services";
|
||||
import { IssueChatComponent } from "../issue-chat/issue-chat.component";
|
||||
|
||||
@Component({
|
||||
selector: "issues-details-group",
|
||||
templateUrl: "details-group.component.html",
|
||||
styleUrls: ["details-group.component.scss"],
|
||||
})
|
||||
export class DetailsGroupComponent {
|
||||
|
||||
@Input() public issue: IIssues;
|
||||
@Input() public isAdmin: boolean;
|
||||
@Input() public settings: IIssueSettings;
|
||||
|
||||
public deleted: boolean;
|
||||
public IssueStatus = IssueStatus;
|
||||
public get hasRequest(): boolean {
|
||||
if (this.issue.requestId) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private translateService: TranslateService, private issuesService: IssuesService,
|
||||
private notificationService: NotificationService, private dialog: MatDialog) { }
|
||||
|
||||
public async delete(issue: IIssues) {
|
||||
await this.issuesService.deleteIssue(issue.id);
|
||||
this.notificationService.success(this.translateService.instant("Issues.DeletedIssue"));
|
||||
this.deleted = true;
|
||||
}
|
||||
|
||||
public openChat(issue: IIssues) {
|
||||
this.dialog.open(IssueChatComponent, { width: "100vh", data: { issueId: issue.id }, panelClass: 'modal-panel' })
|
||||
}
|
||||
|
||||
public resolve(issue: IIssues) {
|
||||
this.issuesService.updateStatus({issueId: issue.id, status: IssueStatus.Resolved}).subscribe(() => {
|
||||
this.notificationService.success(this.translateService.instant("Issues.MarkedAsResolved"));
|
||||
issue.status = IssueStatus.Resolved;
|
||||
});
|
||||
}
|
||||
|
||||
public inProgress(issue: IIssues) {
|
||||
this.issuesService.updateStatus({issueId: issue.id, status: IssueStatus.InProgress}).subscribe(() => {
|
||||
this.notificationService.success(this.translateService.instant("Issues.MarkedAsInProgress"));
|
||||
issue.status = IssueStatus.InProgress;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
<div class="container top-spacing" *ngIf="details">
|
||||
|
||||
<h1>Issues for {{details.title}}</h1>
|
||||
<div>
|
||||
<span>{{'Issues.Requested' | translate}}
|
||||
<i *ngIf="!hasRequest" class="far fa-times-circle"></i>
|
||||
<i *ngIf="hasRequest" class="far fa-check-circle"></i>
|
||||
</span>
|
||||
<div>
|
||||
<button mat-raised-button color="accent" (click)="navToMedia()"><i class="far fa-eye"></i> {{'Common.ViewDetails' | translate }}</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6 top-spacing" *ngFor="let issue of details.issues">
|
||||
<issues-details-group [issue]="issue" [settings]="settings" [isAdmin]="isAdmin"></issues-details-group>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,9 @@
|
||||
@import "~styles/variables.scss";
|
||||
|
||||
::ng-deep .mat-card {
|
||||
background: $ombi-background-primary-accent;
|
||||
}
|
||||
|
||||
.top-spacing {
|
||||
margin-top:2%;
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
import { Component, Inject, OnInit, ViewEncapsulation } from "@angular/core";
|
||||
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, ActivatedRouteSnapshot, Router } from "@angular/router";
|
||||
import { TranslateService } from "@ngx-translate/core";
|
||||
import { AuthService } from "../../../auth/auth.service";
|
||||
import { IIssues, IIssueSettings, IIssuesSummary, IssueStatus, RequestType } from "../../../interfaces";
|
||||
import { IssuesService, NotificationService, SettingsService } from "../../../services";
|
||||
import { IssuesV2Service } from "../../../services/issuesv2.service";
|
||||
import { IssueChatComponent } from "../issue-chat/issue-chat.component";
|
||||
|
||||
|
||||
export interface IssuesDetailsGroupData {
|
||||
issues: IIssues[];
|
||||
title: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "issues-details",
|
||||
templateUrl: "details.component.html",
|
||||
styleUrls: ["details.component.scss"],
|
||||
encapsulation: ViewEncapsulation.None
|
||||
})
|
||||
export class IssuesDetailsComponent implements OnInit {
|
||||
|
||||
public details: IIssuesSummary;
|
||||
public isAdmin: boolean;
|
||||
public IssueStatus = IssueStatus;
|
||||
public settings: IIssueSettings;
|
||||
public get hasRequest(): boolean {
|
||||
return this.details.issues.some(x => x.requestId);
|
||||
}
|
||||
|
||||
private providerId: string;
|
||||
|
||||
constructor(private authService: AuthService, private settingsService: SettingsService,
|
||||
private issueServiceV2: IssuesV2Service, private route: ActivatedRoute, private router: Router,
|
||||
private issuesService: IssuesService, private translateService: TranslateService, private notificationService: NotificationService,
|
||||
private dialog: MatDialog) {
|
||||
this.route.params.subscribe(async (params: any) => {
|
||||
if (typeof params.providerId === 'string' || params.providerId instanceof String) {
|
||||
this.providerId = params.providerId;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
this.isAdmin = this.authService.hasRole("Admin") || this.authService.hasRole("PowerUser");
|
||||
this.settingsService.getIssueSettings().subscribe(x => this.settings = x);
|
||||
this.issueServiceV2.getIssuesByProviderId(this.providerId).subscribe(x => this.details = x);
|
||||
}
|
||||
|
||||
public resolve(issue: IIssues) {
|
||||
this.issuesService.updateStatus({issueId: issue.id, status: IssueStatus.Resolved}).subscribe(x => {
|
||||
this.notificationService.success(this.translateService.instant("Issues.MarkedAsResolved"));
|
||||
issue.status = IssueStatus.Resolved;
|
||||
});
|
||||
}
|
||||
|
||||
public inProgress(issue: IIssues) {
|
||||
this.issuesService.updateStatus({issueId: issue.id, status: IssueStatus.InProgress}).subscribe(x => {
|
||||
this.notificationService.success(this.translateService.instant("Issues.MarkedAsInProgress"));
|
||||
issue.status = IssueStatus.InProgress;
|
||||
});
|
||||
}
|
||||
|
||||
public async delete(issue: IIssues) {
|
||||
await this.issuesService.deleteIssue(issue.id);
|
||||
this.notificationService.success(this.translateService.instant("Issues.DeletedIssue"));
|
||||
this.details.issues = this.details.issues.filter((el) => { return el.id !== issue.id; });
|
||||
}
|
||||
|
||||
public openChat(issue: IIssues) {
|
||||
this.dialog.open(IssueChatComponent, { width: "100vh", data: { issueId: issue.id }, panelClass: 'modal-panel' })
|
||||
}
|
||||
|
||||
public navToMedia() {
|
||||
const firstIssue = this.details.issues[0];
|
||||
switch(firstIssue.requestType) {
|
||||
case RequestType.movie:
|
||||
this.router.navigate(['/details/movie/', firstIssue.providerId]);
|
||||
return;
|
||||
|
||||
case RequestType.album:
|
||||
this.router.navigate(['/details/artist/', firstIssue.providerId]);
|
||||
return;
|
||||
|
||||
case RequestType.tvShow:
|
||||
this.router.navigate(['/details/tv/', firstIssue.providerId]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,20 +1,19 @@
|
||||
import { AuthGuard } from "../../auth/auth.guard";
|
||||
import { IssuesListComponent } from "./issues-list/issues-list.component";
|
||||
import { Routes } from "@angular/router";
|
||||
import { IssuesV2Service } from "../../services/issuesv2.service";
|
||||
import { IdentityService, SearchService } from "../../services";
|
||||
import { IssuesDetailsComponent } from "./details/details.component";
|
||||
import { IssueChatComponent } from "./issue-chat/issue-chat.component";
|
||||
import { ChatBoxComponent } from "../../shared/chat-box/chat-box.component";
|
||||
|
||||
|
||||
|
||||
export const components: any[] = [
|
||||
IssuesListComponent,
|
||||
];
|
||||
|
||||
|
||||
export const entryComponents: any[] = [
|
||||
IssuesDetailsComponent,
|
||||
IssueChatComponent,
|
||||
ChatBoxComponent,
|
||||
];
|
||||
|
||||
export const providers: any[] = [
|
||||
];
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: "", component: IssuesListComponent, canActivate: [AuthGuard] },
|
||||
IssuesV2Service,
|
||||
IdentityService,
|
||||
SearchService,
|
||||
];
|
@ -0,0 +1,6 @@
|
||||
<ombi-chat-box *ngIf="loaded"
|
||||
[messages]="messages"
|
||||
(onAddMessage)="addComment($event)"
|
||||
>
|
||||
|
||||
</ombi-chat-box>
|
@ -0,0 +1,9 @@
|
||||
@import "~styles/variables.scss";
|
||||
|
||||
::ng-deep .mat-card {
|
||||
background: $ombi-background-primary-accent;
|
||||
}
|
||||
|
||||
.top-spacing {
|
||||
margin-top:2%;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
import { Component, Inject, OnInit } from "@angular/core";
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { AuthService } from "../../../auth/auth.service";
|
||||
import { ILocalUser } from "../../../auth/IUserLogin";
|
||||
import { IIssuesChat, IIssueSettings, IssueStatus } from "../../../interfaces";
|
||||
import { IssuesService, SettingsService } from "../../../services";
|
||||
import { ChatMessages, ChatType } from "../../../shared/chat-box/chat-box.component";
|
||||
|
||||
|
||||
export interface ChatData {
|
||||
issueId: number;
|
||||
title: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "issue-chat",
|
||||
templateUrl: "issue-chat.component.html",
|
||||
styleUrls: ["issue-chat.component.scss"],
|
||||
})
|
||||
export class IssueChatComponent implements OnInit {
|
||||
|
||||
public isAdmin: boolean;
|
||||
public comments: IIssuesChat[] = [];
|
||||
public IssueStatus = IssueStatus;
|
||||
public settings: IIssueSettings;
|
||||
public messages: ChatMessages[] = [];
|
||||
|
||||
public loaded: boolean;
|
||||
|
||||
private user: ILocalUser;
|
||||
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<IssueChatComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: ChatData,
|
||||
private authService: AuthService, private settingsService: SettingsService,
|
||||
private issueService: IssuesService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.isAdmin = this.authService.isAdmin();
|
||||
this.user = this.authService.claims();
|
||||
this.settingsService.getIssueSettings().subscribe(x => this.settings = x);
|
||||
this.issueService.getComments(this.data.issueId).subscribe(x => {
|
||||
this.comments = x;
|
||||
this.mapMessages();
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public deleteComment(commentId: number) {
|
||||
|
||||
}
|
||||
|
||||
public addComment(comment: string) {
|
||||
this.issueService.addComment({
|
||||
comment: comment,
|
||||
issueId: this.data.issueId
|
||||
}).subscribe(comment => {
|
||||
this.messages.push({
|
||||
chatType: ChatType.Sender,
|
||||
date: comment.date,
|
||||
id: -1,
|
||||
message: comment.comment,
|
||||
username: comment.user.userName
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public close() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
private mapMessages() {
|
||||
this.comments.forEach((m: IIssuesChat) => {
|
||||
this.messages.push({
|
||||
chatType: m.username === this.user.name ? ChatType.Sender : ChatType.Reciever,
|
||||
date: m.date,
|
||||
id: m.id,
|
||||
message: m.comment,
|
||||
username: m.username
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
<!-- <table mat-table [dataSource]="pendingIssues" multiTemplateDataRows class="mat-elevation-z8">
|
||||
<ng-container matColumnDef="{{column}}" *ngFor="let column of columnsToDisplay">
|
||||
<th mat-header-cell *matHeaderCellDef> {{column}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element[column]}} </td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="expandedDetail">
|
||||
<td mat-cell *matCellDef="let element" [attr.colspan]="columnsToDisplay.length">
|
||||
<div class="example-element-detail" [@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
|
||||
<div class="example-element-diagram">
|
||||
<div class="example-element-position"> {{element.requestId}} </div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
|
||||
<tr mat-row *matRowDef="let element; columns: columnsToDisplay;" class="example-element-row" [class.example-expanded-row]="expandedElement === element" (click)="expandedElement = expandedElement === element ? null : element">
|
||||
</tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="example-detail-row"></tr>
|
||||
</table> -->
|
@ -1,68 +0,0 @@
|
||||
import { Component, OnInit } from "@angular/core";
|
||||
|
||||
import { IssuesService } from "../../../services";
|
||||
|
||||
import { IIssueCount, IIssues, IPagenator, IssueStatus } from "../../../interfaces";
|
||||
import { COLUMNS } from "./issues-list.constants";
|
||||
|
||||
@Component({
|
||||
selector: "issues-list",
|
||||
templateUrl: "issues-list.component.html",
|
||||
})
|
||||
export class IssuesListComponent implements OnInit {
|
||||
|
||||
public columnsToDisplay = COLUMNS
|
||||
|
||||
public pendingIssues: IIssues[];
|
||||
public inProgressIssues: IIssues[];
|
||||
public resolvedIssues: IIssues[];
|
||||
|
||||
public count: IIssueCount;
|
||||
|
||||
private takeAmount = 10;
|
||||
private pendingSkip = 0;
|
||||
private inProgressSkip = 0;
|
||||
private resolvedSkip = 0;
|
||||
|
||||
constructor(private issueService: IssuesService) { }
|
||||
|
||||
public ngOnInit() {
|
||||
this.getPending();
|
||||
this.getInProg();
|
||||
this.getResolved();
|
||||
this.issueService.getIssuesCount().subscribe(x => this.count = x);
|
||||
}
|
||||
|
||||
public changePagePending(event: IPagenator) {
|
||||
this.pendingSkip = event.first;
|
||||
this.getPending();
|
||||
}
|
||||
|
||||
public changePageInProg(event: IPagenator) {
|
||||
this.inProgressSkip = event.first;
|
||||
this.getInProg();
|
||||
}
|
||||
|
||||
public changePageResolved(event: IPagenator) {
|
||||
this.resolvedSkip = event.first;
|
||||
this.getResolved();
|
||||
}
|
||||
|
||||
private getPending() {
|
||||
this.issueService.getIssuesPage(this.takeAmount, this.pendingSkip, IssueStatus.Pending).subscribe(x => {
|
||||
this.pendingIssues = x;
|
||||
});
|
||||
}
|
||||
|
||||
private getInProg() {
|
||||
this.issueService.getIssuesPage(this.takeAmount, this.inProgressSkip, IssueStatus.InProgress).subscribe(x => {
|
||||
this.inProgressIssues = x;
|
||||
});
|
||||
}
|
||||
|
||||
private getResolved() {
|
||||
this.issueService.getIssuesPage(this.takeAmount, this.resolvedSkip, IssueStatus.Resolved).subscribe(x => {
|
||||
this.resolvedIssues = x;
|
||||
});
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
export const COLUMNS = [
|
||||
"title"
|
||||
]
|
@ -1,25 +1,28 @@
|
||||
<div class="small-middle-container">
|
||||
<mat-tab-group>
|
||||
<mat-tab label="{{'Issues.PendingTitle' | translate}}">
|
||||
<ng-template matTabContent>
|
||||
<div *ngIf="pendingIssues">
|
||||
<issues-table [issues]="pendingIssues" (changePage)="changePagePending($event)" [totalRecords]="count.pending"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="inProgressIssues.length > 0" label="{{'Issues.InProgressTitle' | translate}}">
|
||||
<ng-template matTabContent>
|
||||
<div *ngIf="inProgressIssues">
|
||||
<issues-table [issues]="inProgressIssues" (changePage)="changePageInProg($event)" [totalRecords]="count.inProgress"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{'Issues.ResolvedTitle' | translate}}">
|
||||
<ng-template matTabContent>
|
||||
<div *ngIf="resolvedIssues">
|
||||
<issues-table [issues]="resolvedIssues" (changePage)="changePageResolved($event)" [totalRecords]="count.resolved"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<div *ngIf="count">
|
||||
|
||||
<mat-tab-group>
|
||||
<mat-tab label="{{'Issues.PendingTitle' | translate}}">
|
||||
<ng-template matTabContent>
|
||||
<div *ngIf="pendingIssues.length > 0">
|
||||
<issues-table [issues]="pendingIssues" (changePage)="changePagePending($event)" [totalRecords]="count.pending"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab *ngIf="inProgressIssues.length > 0" label="{{'Issues.InProgressTitle' | translate}}">
|
||||
<ng-template matTabContent>
|
||||
<div *ngIf="inProgressIssues">
|
||||
<issues-table [issues]="inProgressIssues" (changePage)="changePageInProg($event)" [totalRecords]="count.inProgress"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
<mat-tab label="{{'Issues.ResolvedTitle' | translate}}">
|
||||
<ng-template matTabContent>
|
||||
<div *ngIf="resolvedIssues.length > 0">
|
||||
<issues-table [issues]="resolvedIssues" (changePage)="changePageResolved($event)" [totalRecords]="count.resolved"></issues-table>
|
||||
</div>
|
||||
</ng-template>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { PlatformLocation, APP_BASE_HREF } from "@angular/common";
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { Observable } from "rxjs";
|
||||
|
||||
import { IIssueCategory, IIssueComments, IIssueCount, IIssues, IIssuesChat, IIssuesSummary, INewIssueComments, IssueStatus, IUpdateStatus } from "../interfaces";
|
||||
import { ServiceHelpers } from "./service.helpers";
|
||||
|
||||
@Injectable()
|
||||
export class IssuesV2Service extends ServiceHelpers {
|
||||
constructor(http: HttpClient, @Inject(APP_BASE_HREF) href:string) {
|
||||
super(http, "/api/v2/Issues/", href);
|
||||
}
|
||||
|
||||
public getIssues(position: number, take: number, status: IssueStatus): Observable<IIssuesSummary[]> {
|
||||
return this.http.get<IIssuesSummary[]>(`${this.url}${position}/${take}/${status}`, {headers: this.headers});
|
||||
}
|
||||
|
||||
public getIssuesByProviderId(providerId: string): Observable<IIssuesSummary> {
|
||||
return this.http.get<IIssuesSummary>(`${this.url}details/${providerId}`, {headers: this.headers});
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
<div class='container'>
|
||||
<div class='chatbox'>
|
||||
<div class='chatbox__user-list'>
|
||||
<h1>Users</h1>
|
||||
<div class='chatbox__user--active' *ngFor="let user of userList">
|
||||
<p>{{user}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chatbox__messages" *ngFor="let m of messages">
|
||||
<div class="chatbox__messages__user-message">
|
||||
<div class="chatbox__messages__user-message--ind-message" [ngClass]="{'sender': m.chatType === ChatType.Sender, 'reciever':m.chatType === ChatType.Reciever }">
|
||||
<p class="name" *ngIf="m?.username">{{m.username}}</p>
|
||||
<br/>
|
||||
<p class="message">{{m.message}}</p>
|
||||
<p class="timestamp">{{m.date | amLocal | amDateFormat: 'l LT'}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form">
|
||||
<input type="text" [(ngModel)]="currentMessage" placeholder="Enter your message">
|
||||
<button mat-raised-button class="add-message" (click)="addMessage()">Send</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,330 @@
|
||||
// Variables
|
||||
$primary: rgba(23, 190, 187, 1);
|
||||
$secondary: rgba(240, 166, 202, 1);
|
||||
|
||||
$active: rgba(23, 190, 187, 0.8);
|
||||
$busy: rgba(252, 100, 113, 0.8);
|
||||
$away: rgba(255, 253, 130, 0.8);
|
||||
|
||||
// Triangle Mixin
|
||||
@mixin triangle($color, $size, $direction) {
|
||||
width: 0;
|
||||
height: 0;
|
||||
@if $direction == "up" {
|
||||
border-right: ($size + px) solid transparent;
|
||||
border-left: ($size + px) solid transparent;
|
||||
border-bottom: ($size + px) solid $color;
|
||||
}
|
||||
@if $direction == "down" {
|
||||
border-right: ($size + px) solid transparent;
|
||||
border-left: ($size + px) solid transparent;
|
||||
border-top: ($size + px) solid $color;
|
||||
}
|
||||
@if $direction == "right" {
|
||||
border-top: ($size + px) solid transparent;
|
||||
border-bottom: ($size + px) solid transparent;
|
||||
border-left: ($size + px) solid $color;
|
||||
}
|
||||
@if $direction == "left" {
|
||||
border-top: ($size + px) solid transparent;
|
||||
border-bottom: ($size + px) solid transparent;
|
||||
border-right: ($size + px) solid $color;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0; padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Nunito', sans-serif;
|
||||
}
|
||||
|
||||
html,body {
|
||||
background: linear-gradient(120deg, $primary, $secondary);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
height: 70vh;
|
||||
width: 100%;
|
||||
h1 {
|
||||
margin: 0.5em auto;
|
||||
color: #FFF;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.chatbox {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
width: 85%;
|
||||
height: 100%;
|
||||
border-radius: 0.2em;
|
||||
position: relative;
|
||||
box-shadow: 1px 1px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.sender {
|
||||
float: right;
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
margin: -1.5em -18.98em;
|
||||
@include triangle(rgba(255, 255, 255, 0.2), 10, left);
|
||||
}
|
||||
}
|
||||
.reciever {
|
||||
float: left;
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
margin: -1.5em 2.65em;
|
||||
@include triangle(rgba(255, 255, 255, 0.2), 10, right);
|
||||
}
|
||||
}
|
||||
&__messages__user-message {
|
||||
width: 450px;
|
||||
}
|
||||
&__messages__user-message--ind-message {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
padding: 1em 0;
|
||||
height: auto;
|
||||
width: 65%;
|
||||
border-radius: 5px;
|
||||
margin: 2em 1em;
|
||||
overflow: auto;
|
||||
& > p.name {
|
||||
color: #FFF;
|
||||
font-size: 1em;
|
||||
}
|
||||
& > p.message {
|
||||
color: #FFF;
|
||||
font-size: 0.7em;
|
||||
margin: 0 2.8em;
|
||||
}& > p.timestamp {
|
||||
color: #FFF;
|
||||
font-size: 0.7em;
|
||||
margin: 0 2.8em;
|
||||
}
|
||||
}
|
||||
&__user-list {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
width: 25%;
|
||||
height: 100%;
|
||||
float: right;
|
||||
border-top-right-radius: 0.2em;
|
||||
border-bottom-right-radius: 0.2em;
|
||||
h1 {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-size: 0.9em;
|
||||
padding: 1em;
|
||||
margin: 0;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
&__user {
|
||||
width: 0.5em;
|
||||
height: 0.5em;
|
||||
border-radius: 100%;
|
||||
margin: 1em 0.7em;
|
||||
&--active {
|
||||
@extend .chatbox__user;
|
||||
background: $active;
|
||||
}
|
||||
&--busy {
|
||||
@extend .chatbox__user;
|
||||
background: $busy;
|
||||
}
|
||||
&--away {
|
||||
@extend .chatbox__user;
|
||||
background: $away;
|
||||
}
|
||||
}
|
||||
p {
|
||||
float: left;
|
||||
text-align: left;
|
||||
margin: -0.25em 2em;
|
||||
font-size: 0.7em;
|
||||
font-weight: 300;
|
||||
color: #FFF;
|
||||
width: 200px;
|
||||
}
|
||||
.form {
|
||||
background: #222;
|
||||
input {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border: none;
|
||||
width: 75%;
|
||||
padding: 1.2em;
|
||||
outline: none;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-weight: 300;
|
||||
}
|
||||
.add-message {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
position: absolute;
|
||||
bottom: 1.5%;
|
||||
right: 26%;
|
||||
border: none;
|
||||
outline: none;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Placeholder Styling
|
||||
::-webkit-input-placeholder {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
:-moz-placeholder {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
|
||||
// ::-webkit-scrollbar {
|
||||
// width: 4px;
|
||||
// }
|
||||
// ::-webkit-scrollbar-thumb {
|
||||
// background-color: #4c4c6a;
|
||||
// border-radius: 2px;
|
||||
// }
|
||||
// .chatbox {
|
||||
// width: 300px;
|
||||
// height: 400px;
|
||||
// max-height: 400px;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// overflow: hidden;
|
||||
// box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28);
|
||||
// }
|
||||
// .chat-window {
|
||||
// flex: auto;
|
||||
// max-height: calc(100% - 60px);
|
||||
// background: #2f323b;
|
||||
// overflow: auto;
|
||||
// }
|
||||
// .chat-input {
|
||||
// flex: 0 0 auto;
|
||||
// height: 60px;
|
||||
// background: #40434e;
|
||||
// border-top: 1px solid #2671ff;
|
||||
// box-shadow: 0 0 4px rgba(0,0,0,.14),0 4px 8px rgba(0,0,0,.28);
|
||||
// }
|
||||
// .chat-input input {
|
||||
// height: 59px;
|
||||
// line-height: 60px;
|
||||
// outline: 0 none;
|
||||
// border: none;
|
||||
// width: calc(100% - 60px);
|
||||
// color: white;
|
||||
// text-indent: 10px;
|
||||
// font-size: 12pt;
|
||||
// padding: 0;
|
||||
// background: #40434e;
|
||||
// }
|
||||
// .chat-input button {
|
||||
// float: right;
|
||||
// outline: 0 none;
|
||||
// border: none;
|
||||
// background: rgba(255,255,255,.25);
|
||||
// height: 40px;
|
||||
// width: 40px;
|
||||
// border-radius: 50%;
|
||||
// padding: 2px 0 0 0;
|
||||
// margin: 10px;
|
||||
// transition: all 0.15s ease-in-out;
|
||||
// }
|
||||
// .chat-input input[good] + button {
|
||||
// box-shadow: 0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24);
|
||||
// background: #2671ff;
|
||||
// }
|
||||
// .chat-input input[good] + button:hover {
|
||||
// box-shadow: 0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
|
||||
// }
|
||||
// .chat-input input[good] + button path {
|
||||
// fill: white;
|
||||
// }
|
||||
// .msg-container {
|
||||
// position: relative;
|
||||
// display: inline-block;
|
||||
// width: 100%;
|
||||
// margin: 0 0 10px 0;
|
||||
// padding: 0;
|
||||
// }
|
||||
// .msg-box {
|
||||
// display: flex;
|
||||
// background: #5b5e6c;
|
||||
// padding: 10px 10px 0 10px;
|
||||
// border-radius: 0 6px 6px 0;
|
||||
// max-width: 80%;
|
||||
// width: auto;
|
||||
// float: left;
|
||||
// box-shadow: 0 0 2px rgba(0,0,0,.12),0 2px 4px rgba(0,0,0,.24);
|
||||
// }
|
||||
// .user-img {
|
||||
// display: inline-block;
|
||||
// border-radius: 50%;
|
||||
// height: 40px;
|
||||
// width: 40px;
|
||||
// background: #2671ff;
|
||||
// margin: 0 10px 10px 0;
|
||||
// }
|
||||
// .flr {
|
||||
// flex: 1 0 auto;
|
||||
// display: flex;
|
||||
// flex-direction: column;
|
||||
// width: calc(100% - 50px);
|
||||
// }
|
||||
// .messages {
|
||||
// flex: 1 0 auto;
|
||||
// }
|
||||
// .msg {
|
||||
// display: inline-block;
|
||||
// font-size: 11pt;
|
||||
// line-height: 13pt;
|
||||
// color: rgba(255,255,255,.7);
|
||||
// margin: 0 0 4px 0;
|
||||
// }
|
||||
// .msg:first-of-type {
|
||||
// margin-top: 8px;
|
||||
// }
|
||||
// .timestamp {
|
||||
// color: rgba(0,0,0,.38);
|
||||
// font-size: 8pt;
|
||||
// margin-bottom: 10px;
|
||||
// }
|
||||
// .username {
|
||||
// margin-right: 3px;
|
||||
// }
|
||||
// .posttime {
|
||||
// margin-left: 3px;
|
||||
// }
|
||||
// .msg-self .msg-box {
|
||||
// border-radius: 6px 0 0 6px;
|
||||
// background: #2671ff;
|
||||
// float: right;
|
||||
// }
|
||||
// .msg-self .user-img {
|
||||
// margin: 0 0 10px 10px;
|
||||
// }
|
||||
// .msg-self .msg {
|
||||
// text-align: right;
|
||||
// }
|
||||
// .msg-self .timestamp {
|
||||
// text-align: right;
|
||||
// }
|
@ -0,0 +1,46 @@
|
||||
import { AfterContentChecked, AfterViewInit, Component, EventEmitter, Inject, Input, OnInit, Output } from "@angular/core";
|
||||
|
||||
|
||||
export interface ChatMessages {
|
||||
id: number;
|
||||
message: string;
|
||||
date: Date;
|
||||
username: string;
|
||||
chatType: ChatType;
|
||||
}
|
||||
|
||||
export enum ChatType {
|
||||
Sender,
|
||||
Reciever
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: "ombi-chat-box",
|
||||
templateUrl: "chat-box.component.html",
|
||||
styleUrls: ["chat-box.component.scss"],
|
||||
})
|
||||
export class ChatBoxComponent implements OnInit {
|
||||
@Input() messages: ChatMessages[];
|
||||
@Output() onAddMessage: EventEmitter<string> = new EventEmitter<string>();
|
||||
@Output() onDeleteMessage: EventEmitter<number> = new EventEmitter<number>();
|
||||
|
||||
public currentMessage: string;
|
||||
public userList: string[];
|
||||
public ChatType = ChatType;
|
||||
|
||||
public ngOnInit(): void {
|
||||
const allUsernames = this.messages.map(x => x.username);
|
||||
this.userList = allUsernames.filter((v, i, a) => a.indexOf(v) === i);
|
||||
}
|
||||
|
||||
public deleteMessage(id: number) {
|
||||
this.onDeleteMessage.emit(id);
|
||||
}
|
||||
|
||||
public addMessage() {
|
||||
if (this.currentMessage) {
|
||||
this.onAddMessage.emit(this.currentMessage);
|
||||
this.currentMessage = '';
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue