Attachments fully working

This commit is contained in:
Matthew Ross 2020-05-11 19:10:36 -04:00
parent 5748f762e9
commit a109c9d0a5
8 changed files with 203 additions and 69 deletions

View File

@ -35,6 +35,7 @@ export const ROUTES: Routes = [
{
path: 'files/:hash',
component: FileViewerComponent,
canActivate: [ AuthGuard ]
}
];

View File

@ -123,6 +123,14 @@ export class BoardService {
);
}
addComment(comment: Comment): Observable<ApiResponse> {
return this.http.post('api/comments', comment)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
);
}
updateComment(comment: Comment): Observable<ApiResponse> {
return this.http.post('api/comments/' + comment.id, comment)
.pipe(
@ -139,8 +147,7 @@ export class BoardService {
);
}
/* istanbul ignore next */
uploadAttachment(attachment: Attachment): Observable<ApiResponse> {
addAttachment(attachment: Attachment): Observable<ApiResponse> {
return this.http.post('api/attachments', attachment)
.pipe(
map((response: ApiResponse) => response),
@ -148,6 +155,14 @@ export class BoardService {
);
}
uploadAttachment(data: FormData, hash: string): Observable<ApiResponse> {
return this.http.post('api/upload/' + hash, data)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
);
}
removeAttachment(id: number): Observable<ApiResponse> {
return this.http.delete('api/attachments/' + id)
.pipe(

View File

@ -178,8 +178,11 @@
<i class="icon icon-eye" (click)="viewFile(item.diskfilename)"
[title]="strings['boards_taskView'] + ' ' + item.filename"></i>
<i class="icon icon-download" [title]="strings['boards_taskDownload']
+ ' ' + item.filename"></i>
<a [href]="getUrl(item.diskfilename)" download="{{ item.filename }}">
<i class="icon icon-download"
[title]="strings['boards_taskDownload'] + ' ' +
item.filename"></i>
</a>
<i class="icon icon-trash-empty"
[title]="strings['settings_remove'] + ' ' + item.filename"
@ -191,12 +194,14 @@
</div>
</div>
<div>
<div class="file-upload">
<h3>{{ strings['boards_taskAddAttachment'] }}</h3>
<input type="file" #fileupload (change)="fileChange(fileupload.files[0])">
<input type="file" class="fileuploadinput" #fileupload
(change)="fileChange(fileupload.files[0])">
<button (click)="uploadFile()" [disabled]="!fileupload.files[0]">
<button (click)="addFile()"
[disabled]="!fileupload.files[0] || fileUploading">
<i class="icon icon-upload"></i>
{{ strings['boards_taskUpload'] }}

View File

@ -7,7 +7,7 @@ import {
OnDestroy,
Output
} from '@angular/core';
import { DomSanitizer, } from '@angular/platform-browser';
import { DomSanitizer } from '@angular/platform-browser';
import {
CdkDragDrop,
moveItemInArray,
@ -40,12 +40,15 @@ import { BoardService } from '../board.service';
templateUrl: './column.component.html'
})
export class ColumnDisplayComponent implements OnInit, OnDestroy {
public moveItemInArray: any;
public transferArrayItem: any;
public fileUpload: any;
private fileUpload: any;
private subs = [];
public viewTaskActivities: ActivitySimple[];
public fileUploading: boolean;
public showActivity: boolean;
public collapseActivity: boolean;
public isOverdue: boolean;
@ -88,7 +91,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
constructor(public elRef: ElementRef,
private auth: AuthService,
private notes: NotificationsService,
public notes: NotificationsService,
public modal: ModalService,
public stringsService: StringsService,
public boardService: BoardService,
@ -107,6 +110,9 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.modalProps = new Task();
this.viewModalProps = new Task();
this.moveItemInArray = moveItemInArray;
this.transferArrayItem = transferArrayItem;
let sub = stringsService.stringsChanged.subscribe(newStrings => {
this.strings = newStrings;
});
@ -231,9 +237,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
return;
}
this.modal.close(this.MODAL_ID + (this.columnData
? this.columnData.id + ''
: ''));
this.modal.close(this.MODAL_ID + this.columnData.id + '');
const boardData = response.data[2][0];
boardData.ownColumn.forEach((column: any) => {
@ -252,10 +256,10 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
drop(event: CdkDragDrop<string[]>, colIndex: number) {
if (event.previousContainer === event.container) {
moveItemInArray(event.container.data,
this.moveItemInArray(event.container.data,
event.previousIndex, event.currentIndex);
} else {
transferArrayItem(event.previousContainer.data,
this.transferArrayItem(event.previousContainer.data,
event.container.data, event.previousIndex, event.currentIndex);
}
@ -286,9 +290,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
}
this.boardService.updateActiveBoard(response.data[2][0]);
this.modal.close(this.MODAL_ID + (this.columnData
? this.columnData.id + ''
: ''));
this.modal.close(this.MODAL_ID + this.columnData?.id + '');
this.boardService.refreshToken();
this.saving = false;
@ -309,60 +311,63 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
});
}
/* istanbul ignore next */
fileChange(file: File) {
this.fileUpload = file;
}
/* istanbul ignore next */
uploadFile() {
addFile() {
if (!this.fileUpload) {
this.notes.add({ type: 'error', text: this.strings.boards_taskNoFileError });
this.notes
.add({ type: 'error', text: this.strings.boards_taskNoFileError });
return;
}
const fileReader = new FileReader();
fileReader.onload = () => {
const attachment = new Attachment();
this.fileUploading = true;
const attachment = new Attachment();
attachment.filename = this.fileUpload.name;
attachment.name = attachment.filename.split('.').slice(0, -1).join('.');
attachment.type = this.fileUpload.type;
attachment.user_id = this.activeUser.id;
attachment.task_id = this.viewModalProps.id;
attachment.data = fileReader.result;
attachment.filename = this.fileUpload.name;
attachment.name = attachment.filename.split('.').slice(0, -1).join('.');
attachment.type = this.fileUpload.type;
attachment.user_id = this.activeUser.id;
attachment.task_id = this.viewModalProps.id;
this.boardService.uploadAttachment(attachment)
.subscribe(response => {
response.alerts.forEach(note => this.notes.add(note));
this.boardService.addAttachment(attachment).subscribe(response => {
if (response.status !== 'success') {
this.fileUploading = false;
this.resetFileInput();
if (response.status === 'success') {
attachment.id = response.data[1].id;
attachment.diskfilename = response.data[1].diskfilename;
return;
}
this.viewModalProps.attachments.push(attachment);
}
});
}
attachment.id = response.data[1].id;
attachment.diskfilename = response.data[1].diskfilename;
fileReader.readAsBinaryString(this.fileUpload);
this.uploadFile(attachment, response);
});
}
viewFile(hash: string) {
window.open(`./files/${hash}`, 'tb-file-view');
}
getUrl(hash: string) {
const url = `./api/uploads/${hash}`;
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
removeAttachment() {
this.boardService.removeAttachment(this.attachmentToRemove.id).subscribe(res => {
res.alerts.forEach(note => this.notes.add(note));
this.boardService.removeAttachment(this.attachmentToRemove.id)
.subscribe(res => {
res.alerts.forEach(note => this.notes.add(note));
if (res.status === 'success') {
const index = this.viewModalProps.attachments
.findIndex(x => x.id === this.attachmentToRemove.id);
if (res.status === 'success') {
const index = this.viewModalProps.attachments
.findIndex(x => x.id === this.attachmentToRemove.id);
this.viewModalProps.attachments.splice(index, 1);
}
});
this.viewModalProps.attachments.splice(index, 1);
this.updateTaskActivity(this.viewModalProps.id);
}
});
}
addComment() {
@ -370,14 +375,15 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
return;
}
this.viewModalProps.comments.push(
new Comment(0, this.newComment, this.activeUser.id,
this.viewModalProps.id));
const comment = new Comment(0, this.newComment, this.activeUser.id,
this.viewModalProps.id);
this.newComment = '';
this.boardService.updateTask(this.viewModalProps)
this.boardService.addComment(comment)
.subscribe((response: ApiResponse) => {
response.alerts.forEach(note => this.notes.add(note));
if (response.status !== 'success') {
return;
}
@ -582,7 +588,8 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
}
getUserName(userId: number) {
const user = this.activeBoard.users.find((test: User) => test.id === +userId);
const user = this.activeBoard.users
.find((test: User) => test.id === +userId);
return user.username;
}
@ -614,6 +621,34 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
}
}
private uploadFile(attachment: Attachment, response: ApiResponse) {
const data = new FormData();
data.append('file', this.fileUpload);
this.boardService.uploadAttachment(data, attachment.diskfilename)
.subscribe(res => {
res.alerts.forEach(note => this.notes.add(note));
this.fileUploading = false;
this.resetFileInput();
if (res.status === 'success') {
response.alerts.forEach(note => this.notes.add(note));
this.viewModalProps.attachments.push(attachment);
this.updateTaskActivity(this.viewModalProps.id);
}
});
}
private resetFileInput() {
const upload = document.getElementsByClassName('fileuploadinput');
Array.from(upload).forEach((input: any) => {
input.value = '';
})
}
private updateTaskActivity(id: number) {
this.viewTaskActivities = [];

View File

@ -1 +1,17 @@
<tb-top-nav page-name="{{ pageName }}"></tb-top-nav>
<tb-top-nav page-name="{{ pageName }}" [show-buttons]="false"></tb-top-nav>
<div class="file-viewer">
<div class="header">
{{ strings['attachment'] }}: {{ attachment?.filename }}
<span class="right">
<a [href]="fileUrl" download="{{ attachment?.filename }}">
{{strings['boards_taskDownload']}}
</a>
</span>
</div>
<div class="content" *ngIf="isLoaded">
<iframe seamless [src]="fileUrl"></iframe>
</div>
</div>

View File

@ -1,42 +1,78 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Title } from '@angular/platform-browser';
import { ActivatedRoute } from '@angular/router';
import {
Title,
DomSanitizer,
SafeResourceUrl
} from '@angular/platform-browser';
import { StringsService, AuthService } from '../shared/services';
import { User } from '../shared/models';
import { FileViewerService } from './file-viewer.service';
import { User, Attachment } from '../shared/models';
import {
StringsService,
AuthService,
NotificationsService
} from '../shared/services';
@Component({
selector: 'tb-file-viewer',
templateUrl: './file-viewer.component.html'
})
export class FileViewerComponent implements OnInit, OnDestroy {
export class FileViewerComponent implements OnInit, OnDestroy {
private subs: any[];
private fileHash: string;
public activeUser: User;
public pageName: string;
public strings: any;
public pageName: string;
public isLoaded: boolean;
public fileUrl: SafeResourceUrl;
constructor(public title: Title,
public auth: AuthService,
public stringsService: StringsService) {
public attachment: Attachment;
public activeUser: User;
constructor(private title: Title,
private active: ActivatedRoute,
private sanitizer: DomSanitizer,
public service: FileViewerService,
private notes: NotificationsService,
private auth: AuthService,
private stringsService: StringsService) {
title.setTitle('TaskBoard - File Viewer');
this.isLoaded = false;
this.subs = [];
let sub = stringsService.stringsChanged.subscribe(newStrings => {
let sub = this.stringsService.stringsChanged.subscribe(newStrings => {
this.strings = newStrings;
this.pageName = this.strings.files;
this.title.setTitle(`TaskBoard - ${this.pageName}`);
});
this.subs.push(sub);
sub = auth.userChanged.subscribe((user: User) => {
sub = this.auth.userChanged.subscribe((user: User) => {
this.activeUser = user;
});
this.subs.push(sub);
sub = this.active.params.subscribe(params => {
this.fileHash = params.hash;
});
this.subs.push(sub);
}
ngOnInit() {
this.pageName = this.strings.files;
this.service.getAttachmentInfo(this.fileHash).subscribe(res => {
res.alerts.forEach(note => this.notes.add(note));
console.log(this.stringsService, this.activeUser, this.pageName);
if (res.status === 'success') {
this.attachment = res.data[1];
const url = `./api/uploads/${this.attachment.diskfilename}`;
this.fileUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
this.isLoaded = true;
}
});
}
ngOnDestroy() {

View File

@ -5,6 +5,7 @@ import { RouterModule } from '@angular/router';
import { SharedModule } from '../shared/shared.module';
import { FileViewerComponent } from './file-viewer.component';
import { FileViewerService } from './file-viewer.service';
@NgModule({
imports: [
@ -15,6 +16,9 @@ import { FileViewerComponent } from './file-viewer.component';
declarations: [
FileViewerComponent
],
providers: [
FileViewerService
],
exports: [
FileViewerComponent
]

View File

@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import { ApiResponse } from '../shared/models';
@Injectable()
export class FileViewerService {
constructor(private http: HttpClient) {}
getAttachmentInfo(hash: string): Observable<ApiResponse> {
return this.http.get('api/attachments/hash/' + hash)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
);
}
}