More for attachments, and cleanup

This commit is contained in:
Matthew Ross 2020-04-27 13:59:39 -04:00
parent fbddac7302
commit babbf81610
6 changed files with 134 additions and 51 deletions

View File

@ -8,18 +8,21 @@
<option [ngValue]="null"> <option [ngValue]="null">
{{ strings['boards_selectBoard'] }}... {{ strings['boards_selectBoard'] }}...
</option> </option>
<option *ngFor="let board of boards" <option *ngFor="let board of boards"
[ngValue]="board.id"> [ngValue]="board.id">
{{ board.name }} {{ board.name }}
</option> </option>
</select> </select>
</label> </label>
<div class="right" *ngIf="activeBoard"> <div class="right" *ngIf="activeBoard">
<label> <label>
{{ strings['boards_hideFiltered'] }}: {{ strings['boards_hideFiltered'] }}:
<input type="checkbox" [(ngModel)]="hideFiltered" <input type="checkbox" [(ngModel)]="hideFiltered"
(change)="toggleFiltered()"> (change)="toggleFiltered()">
</label> </label>
<label> <label>
{{ strings['boards_userFilter'] }}: {{ strings['boards_userFilter'] }}:
<select [(ngModel)]="userFilter" <select [(ngModel)]="userFilter"
@ -27,15 +30,18 @@
<option [ngValue]="null"> <option [ngValue]="null">
{{ strings['boards_filterByAny'] }} {{ strings['boards_filterByAny'] }}
</option> </option>
<option [ngValue]="-1"> <option [ngValue]="-1">
{{ strings['boards_filterByUnassigned'] }} {{ strings['boards_filterByUnassigned'] }}
</option> </option>
<option *ngFor="let user of activeBoard.users" <option *ngFor="let user of activeBoard.users"
[ngValue]="user.id"> [ngValue]="user.id">
{{ user.username }} {{ user.username }}
</option> </option>
</select> </select>
</label> </label>
<label> <label>
{{ strings['boards_categoryFilter'] }}: {{ strings['boards_categoryFilter'] }}:
<select [(ngModel)]="categoryFilter" <select [(ngModel)]="categoryFilter"
@ -43,15 +49,18 @@
<option [ngValue]="null"> <option [ngValue]="null">
{{ strings['boards_filterByAny'] }} {{ strings['boards_filterByAny'] }}
</option> </option>
<option [ngValue]="-1"> <option [ngValue]="-1">
{{ strings['boards_filterByUncategorized'] }} {{ strings['boards_filterByUncategorized'] }}
</option> </option>
<option *ngFor="let category of activeBoard.categories" <option *ngFor="let category of activeBoard.categories"
[ngValue]="category.id"> [ngValue]="category.id">
{{ category.name }} {{ category.name }}
</option> </option>
</select> </select>
</label> </label>
</div> </div>
</div> </div>
@ -62,7 +71,8 @@
</div> </div>
<div class="no-boards center" <div class="no-boards center"
*ngIf="!loading && !activeBoard && this.boards.length > 0 && !activeUser.default_board_id"> *ngIf="!loading && !activeBoard && this.boards.length > 0 &&
!activeUser.default_board_id">
<h1>{{ strings['boards_noDefault'] }}</h1> <h1>{{ strings['boards_noDefault'] }}</h1>
<p>{{ strings['boards_noDefaultMessage'] }} <p>{{ strings['boards_noDefaultMessage'] }}
@ -70,6 +80,7 @@
{{ strings['settings'] }} {{ strings['settings'] }}
</a>. </a>.
</p> </p>
<p></p> <p></p>
</div> </div>

View File

@ -25,7 +25,7 @@ export class BoardDisplayComponent implements OnInit, OnDestroy {
public categoryFilter: number; public categoryFilter: number;
public userFilter: number; public userFilter: number;
public boardNavId: number; public boardNavId: number | null;
public activeUser: User; public activeUser: User;
public activeBoard: Board; public activeBoard: Board;

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import * as marked from 'marked'; import * as marked from 'marked';
import * as hljs from 'highlight.js'; import * as hljs from 'highlight.js';
@ -59,7 +59,7 @@ export class BoardService {
return this.http.get('api/boards') return this.http.get('api/boards')
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -67,7 +67,7 @@ export class BoardService {
return this.http.post('api/users/' + userId + '/cols', { id: columnId }) return this.http.post('api/users/' + userId + '/cols', { id: columnId })
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -75,7 +75,7 @@ export class BoardService {
return this.http.post('api/boards/' + board.id, board) return this.http.post('api/boards/' + board.id, board)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -83,7 +83,7 @@ export class BoardService {
return this.http.post('api/columns/' + column.id, column) return this.http.post('api/columns/' + column.id, column)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -91,7 +91,7 @@ export class BoardService {
return this.http.post('api/tasks', task) return this.http.post('api/tasks', task)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -99,7 +99,7 @@ export class BoardService {
return this.http.post('api/tasks/' + task.id, task) return this.http.post('api/tasks/' + task.id, task)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -107,7 +107,7 @@ export class BoardService {
return this.http.delete('api/tasks/' + taskId) return this.http.delete('api/tasks/' + taskId)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -115,7 +115,7 @@ export class BoardService {
return this.http.get('api/activity/task/' + taskId) return this.http.get('api/activity/task/' + taskId)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -123,7 +123,7 @@ export class BoardService {
return this.http.post('api/comments/' + comment.id, comment) return this.http.post('api/comments/' + comment.id, comment)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
@ -131,35 +131,31 @@ export class BoardService {
return this.http.delete('api/comments/' + commentId) return this.http.delete('api/comments/' + commentId)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
/* istanbul ignore next */ /* istanbul ignore next */
uploadAttachment(attachment: Attachment, data: FormData): Observable<ApiResponse> { uploadAttachment(attachment: Attachment): Observable<ApiResponse> {
const headers = new HttpHeaders(); return this.http.post('api/attachments', attachment)
const options = { headers, params: new HttpParams() };
options.params.set('attachment', JSON.stringify(attachment));
return this.http.post('api/attachments', data, options)
.pipe( .pipe(
map((response: ApiResponse) => response), map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse)) catchError((err) => of(err.error as ApiResponse))
); );
} }
removeAttachment(id: number): Observable<ApiResponse> {
return this.http.delete('api/attachments/' + id)
.pipe(
map((response: ApiResponse) => response),
catchError((err) => of(err.error as ApiResponse))
)
}
refreshToken(): void { refreshToken(): void {
this.http.post('api/refresh', {}).subscribe(); this.http.post('api/refresh', {}).subscribe();
} }
private defaultCallback = (err: any, text: string) => {
if (err) {
return '';
}
return text;
}
private convertBoardData(boardData: any): Board { private convertBoardData(boardData: any): Board {
if (boardData instanceof Board) { if (boardData instanceof Board) {
return boardData; return boardData;
@ -174,6 +170,14 @@ export class BoardService {
boardData.sharedUser); boardData.sharedUser);
} }
private defaultCallback = (err: any, text: string) => {
if (err) {
return '';
}
return text;
}
private initMarked(): void { private initMarked(): void {
const renderer = new marked.Renderer(); const renderer = new marked.Renderer();

View File

@ -155,6 +155,31 @@
<div *ngIf="viewModalProps.attachments.length"> <div *ngIf="viewModalProps.attachments.length">
<h3>{{ strings['boards_taskAttachments'] }}</h3> <h3>{{ strings['boards_taskAttachments'] }}</h3>
<div class="list-group">
<div class="list-group-item" *ngFor="let item of viewModalProps.attachments">
{{ item.filename }}
<div class="detail small">
{{ strings['boards_taskUploadedBy'] }}: {{ userName(item.user_id) }}
{{ strings['boards_taskUploadedOn'] }} {{ item.timestamp * 1000 | date:'full' }}
</div>
<div class="pull-right">
<i class="icon icon-eye"
[title]="strings['boards_taskView'] + ' ' + item.filename"
(click)="viewFile(item.diskfilename)"></i>
<i class="icon icon-download"
[title]="strings['boards_taskDownload'] + ' ' + item.filename"
></i>
<i class="icon icon-trash-empty"
[title]="strings['settings_remove'] + ' ' + item.filename"
(click)="attachmentToRemove = item;
modal.open(MODAL_CONFIRM_ATTACHMENT_ID +
(columnData ? columnData.id : ''))"></i>
</div>
</div>
</div>
</div> </div>
<div> <div>
@ -344,6 +369,25 @@
</div> </div>
</tb-modal> </tb-modal>
<tb-modal modal-title="{{ strings['boards_confirmRemoveAttachment'] }}" blocking="true"
modal-id="{{ MODAL_CONFIRM_ATTACHMENT_ID + (columnData ? columnData.id : '') }}">
<div class="center">
{{ strings['boards_confirmWarningAttachment'] }}<br>
{{ strings['boards_confirmContinue'] }}
</div>
<div class="buttons">
<button class="flat"
(click)="modal.close(MODAL_CONFIRM_ATTACHMENT_ID
+ (columnData ? columnData.id : '')); removeAttachment()">
{{ strings['yes'] }}
</button>
<button #defaultAction #focusMe
(click)="modal.close(MODAL_CONFIRM_ATTACHMENT_ID + (columnData ? columnData.id : ''))">
{{ strings['no'] }}
</button>
</div>
</tb-modal>
<tb-modal modal-title="{{ strings['boards_confirmRemoveComment'] }}" blocking="true" <tb-modal modal-title="{{ strings['boards_confirmRemoveComment'] }}" blocking="true"
modal-id="{{ MODAL_CONFIRM_COMMENT_ID + (columnData ? columnData.id : '') }}"> modal-id="{{ MODAL_CONFIRM_COMMENT_ID + (columnData ? columnData.id : '') }}">
<div class="center"> <div class="center">

View File

@ -65,6 +65,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
public commentEdit: Comment; public commentEdit: Comment;
public commentToRemove: Comment; public commentToRemove: Comment;
public attachmentToRemove: Attachment;
public viewModalProps: Task; public viewModalProps: Task;
public modalProps: Task; public modalProps: Task;
public userOptions: UserOptions; public userOptions: UserOptions;
@ -75,6 +76,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
public MODAL_ID: string; public MODAL_ID: string;
public MODAL_VIEW_ID: string; public MODAL_VIEW_ID: string;
public MODAL_CONFIRM_ID: string; public MODAL_CONFIRM_ID: string;
public MODAL_CONFIRM_ATTACHMENT_ID: string;
public MODAL_CONFIRM_COMMENT_ID: string; public MODAL_CONFIRM_COMMENT_ID: string;
// tslint:disable-next-line // tslint:disable-next-line
@ -100,6 +102,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.MODAL_ID = 'add-task-form-'; this.MODAL_ID = 'add-task-form-';
this.MODAL_VIEW_ID = 'view-task-form-'; this.MODAL_VIEW_ID = 'view-task-form-';
this.MODAL_CONFIRM_ID = 'task-remove-confirm'; this.MODAL_CONFIRM_ID = 'task-remove-confirm';
this.MODAL_CONFIRM_ATTACHMENT_ID = 'attachment-remove-confirm';
this.MODAL_CONFIRM_COMMENT_ID = 'comment-remove-confirm'; this.MODAL_CONFIRM_COMMENT_ID = 'comment-remove-confirm';
this.quickAdd = new Task(); this.quickAdd = new Task();
@ -167,6 +170,10 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.subs.forEach(sub => (sub.unsubscribe())); this.subs.forEach(sub => (sub.unsubscribe()));
} }
userName(id: number) {
return this.activeBoard.users.find(u => u.id === id).username;
}
sortTasks() { sortTasks() {
switch (this.sortOption) { switch (this.sortOption) {
case 'pos': case 'pos':
@ -204,7 +211,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
this.collapseTasks = !this.collapseTasks; this.collapseTasks = !this.collapseTasks;
} }
updateTaskColorByCategory(event: Array<Category>) { updateTaskColorByCategory(event: Category[]) {
this.modalProps.categories = event; this.modalProps.categories = event;
this.modalProps.color = event[event.length - 1].default_task_color; this.modalProps.color = event[event.length - 1].default_task_color;
} }
@ -314,22 +321,45 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
return; return;
} }
const formData = new FormData(); const fileReader = new FileReader();
formData.append('file', this.fileUpload); fileReader.onload = () => {
const attachment = new Attachment();
const attachment = new Attachment(); attachment.filename = this.fileUpload.name;
attachment.filename = this.fileUpload.name; attachment.name = attachment.filename.split('.').slice(0, -1).join('.');
attachment.name = attachment.filename.split('.')[0]; attachment.type = this.fileUpload.type;
attachment.type = this.fileUpload.type; attachment.user_id = this.activeUser.id;
attachment.user_id = this.activeUser.id; attachment.task_id = this.viewModalProps.id;
attachment.task_id = this.viewModalProps.id; attachment.data = fileReader.result;
this.boardService.uploadAttachment(attachment, formData) this.boardService.uploadAttachment(attachment)
.subscribe(response => { .subscribe(response => {
response.alerts.forEach(note => this.notes.add(note)); response.alerts.forEach(note => this.notes.add(note));
console.log(response); if (response.status === 'success') {
}); this.viewModalProps.attachments.push(attachment);
}
});
}
fileReader.readAsBinaryString(this.fileUpload);
}
viewFile(hash: string) {
window.open(`./files/${hash}`, 'tb-file-view');
}
removeAttachment() {
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);
this.viewModalProps.attachments.splice(index, 1);
}
});
} }
addComment() { addComment() {
@ -630,7 +660,7 @@ export class ColumnDisplayComponent implements OnInit, OnDestroy {
}); });
} }
private updateTaskComments(task: Task, newComments: Array<any>) { private updateTaskComments(task: Task, newComments: any[]) {
task.comments = []; task.comments = [];
if (!newComments) { if (!newComments) {

View File

@ -6,7 +6,6 @@ import {
Output Output
} from '@angular/core'; } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { CdkDragDrop, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop';
import { import {
ApiResponse, ApiResponse,
@ -143,14 +142,9 @@ export class TaskDisplayComponent implements OnInit {
const newBoardId = +(select[select.selectedIndex] as HTMLOptionElement).value; const newBoardId = +(select[select.selectedIndex] as HTMLOptionElement).value;
const taskData = { ...this.taskData }; const taskData = { ...this.taskData };
let boardData: Board; const boardData = this.boardsList.find(board => board.id === newBoardId);
this.boardsList.forEach(board => { taskData.column_id = boardData.columns[0].id;
if (board.id === newBoardId) {
taskData.column_id = board.columns[0].id;
boardData = board;
}
});
this.boardService.addTask(taskData) this.boardService.addTask(taskData)
.subscribe((response: ApiResponse) => { .subscribe((response: ApiResponse) => {