Add and remove tasks from context menus

This commit is contained in:
Matthew Ross 2017-05-08 17:18:45 -04:00
parent 783b868220
commit 2243eebda9
8 changed files with 122 additions and 34 deletions

View File

@ -126,7 +126,7 @@ class Tasks extends BaseController {
$id = (int)$args['id']; $id = (int)$args['id'];
$task = R::load('task', $id); $task = R::load('task', $id);
if ((int)$task->id !== $id) { if ((int)$task->id !== $id || (int)$task->id === 0) {
$this->logger->addError('Remove Task: ', [$task]); $this->logger->addError('Remove Task: ', [$task]);
$this->apiJson->addAlert('error', 'Error removing task. ' . $this->apiJson->addAlert('error', 'Error removing task. ' .
'No task found for ID ' . $id . '.'); 'No task found for ID ' . $id . '.');
@ -134,8 +134,9 @@ class Tasks extends BaseController {
return $this->jsonResponse($response); return $this->jsonResponse($response);
} }
if (!$this->checkBoardAccess( $boardId = $this->getBoardId($task->column_id);
$this->getBoardId($task->column_id), $request)) {
if (!$this->checkBoardAccess($boardId, $request)) {
return $this->jsonResponse($response, 403); return $this->jsonResponse($response, 403);
} }
@ -151,6 +152,9 @@ class Tasks extends BaseController {
$this->apiJson->addAlert('success', $this->apiJson->addAlert('success',
'Task ' . $before->title . ' removed.'); 'Task ' . $before->title . ' removed.');
$board = R::load('board', $boardId);
$this->apiJson->addData(R::exportAll($board));
return $this->jsonResponse($response); return $this->jsonResponse($response);
} }

View File

@ -64,6 +64,18 @@ export class BoardService {
}); });
} }
removeTask(taskId: number): Observable<ApiResponse> {
return this.http.delete('api/tasks/' + taskId)
.map(res => {
let response: ApiResponse = res.json();
return response;
})
.catch((res, caught) => {
let response: ApiResponse = res.json();
return Observable.of(response);
});
}
// TODO: Determine when to use this // TODO: Determine when to use this
refreshToken(): void { refreshToken(): void {
this.http.post('api/refresh', {}).subscribe(); this.http.post('api/refresh', {}).subscribe();

View File

@ -29,27 +29,29 @@
<div class="tasks"> <div class="tasks">
<tb-task class="task-container" *ngFor="let task of columnData.ownTask" <tb-task class="task-container" *ngFor="let task of columnData.ownTask"
[task]="task" [add-task]="getShowModalFunction()"></tb-task> [task]="task" [add-task]="getShowModalFunction(task.id)"
[remove-task]="getRemoveTaskFunction(task.id)"></tb-task>
</div> </div>
<tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu> <tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu>
<!--<tb-modal modal-title="Confirm Task Removal" blocking="true">--> <tb-modal modal-title="Confirm Task Removal" blocking="true"
<!-- <div class="center">Removing a task cannot be undone.<br>Continue?</div>--> modal-id="{{ MODAL_CONFIRM_ID + columnData.id }}">
<!-- <div class="buttons">--> <div class="center">Removing a task cannot be undone.<br>Continue?</div>
<!-- <button class="flat"--> <div class="buttons">
<!-- (click)="removeUser()">Yes</button>--> <button class="flat"
<!-- <button #defaultAction--> (click)="removeTask()">Yes</button>
<!-- (click)="modal.close(MODAL_CONFIRM_ID)">No</button>--> <button #defaultAction
<!-- </div>--> (click)="modal.close(MODAL_CONFIRM_ID + columnData.id)">No</button>
<!--</tb-modal>--> </div>
</tb-modal>
<tb-modal *ngIf="activeBoard" modal-title="Add Task" <tb-modal *ngIf="activeBoard" modal-title="Add Task"
modal-id="{{ MODAL_ID + columnData.id }}"> modal-id="{{ MODAL_ID + columnData.id }}">
<label> <label>
Title Title
<input #focusMe type="text" name="title" placeholder="Task Title" <input #focusMe type="text" name="title" placeholder="Task Title"
[(ngModel)]="modalProps.title"> [(ngModel)]="modalProps.title">
</label> </label>
<label> <label>

View File

@ -40,7 +40,10 @@ export class ColumnDisplay implements OnInit {
private contextMenuItems: Array<ContextMenuItem>; private contextMenuItems: Array<ContextMenuItem>;
private MODAL_ID: string; private MODAL_ID: string;
private MODAL_CONFIRM_ID: string;
private modalProps: Task; private modalProps: Task;
private taskToRemove: number;
@Input('column') columnData: Column; @Input('column') columnData: Column;
@ -55,11 +58,13 @@ export class ColumnDisplay implements OnInit {
this.collapseTasks = false; this.collapseTasks = false;
this.contextMenuItems = [ this.contextMenuItems = [
new ContextMenuItem('Add New Task', new ContextMenuItem('Add Task',
this.getShowModalFunction()) this.getShowModalFunction())
]; ];
this.MODAL_ID = 'add-task-form-'; this.MODAL_ID = 'add-task-form-';
this.MODAL_CONFIRM_ID = 'task-remove-confirm';
this.modalProps = new Task(); this.modalProps = new Task();
boardService.activeBoardChanged.subscribe((board: Board) => { boardService.activeBoardChanged.subscribe((board: Board) => {
@ -145,6 +150,36 @@ export class ColumnDisplay implements OnInit {
}); });
} }
removeTask() {
this.boardService.removeTask(this.taskToRemove)
.subscribe((response: ApiResponse) => {
response.alerts.forEach(note => this.notes.add(note));
if (response.status !== 'success') {
return;
}
let boardData = response.data[1][0];
let newBoard = new Board(+boardData.id, boardData.name,
boardData.is_active === '1',
boardData.ownColumn,
boardData.ownCategory,
boardData.ownAutoAction,
boardData.ownIssuetracker,
boardData.sharedUser);
this.boardService.updateActiveBoard(newBoard);
});
}
private getRemoveTaskFunction(taskId: number): Function {
return () => {
this.taskToRemove = taskId;
this.modal.open(this.MODAL_CONFIRM_ID + this.columnData.id);
};
}
private getShowModalFunction(): Function { private getShowModalFunction(): Function {
return () => { this.showModal(); }; return () => { this.showModal(); };
} }

View File

@ -11,10 +11,9 @@
<span *ngIf="taskData.points > 0" class="badge right" title="Points"> <span *ngIf="taskData.points > 0" class="badge right" title="Points">
{{ taskData.points }}</span> {{ taskData.points }}</span>
</h4> </h4>
<div class="description" [innerHTML]="getTaskDescription()"></div> <div class="description" [innerHTML]="getTaskDescription()"></div>
<div class="hidden">
<pre>{{ taskData | json }}</pre>
</div>
<div class="stats"> <div class="stats">
<span *ngIf="userOptions.show_assignee"> <span *ngIf="userOptions.show_assignee">
Assigned To: Assigned To:
@ -25,6 +24,7 @@
Unassigned Unassigned
</span> </span>
</span> </span>
<span class="right"> <span class="right">
<span *ngIf="taskData.due_date"> <span *ngIf="taskData.due_date">
Due: {{ taskData.due_date }} Due: {{ taskData.due_date }}
@ -39,6 +39,7 @@
</span> </span>
</span> </span>
</div> </div>
<tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu> <tb-context-menu [menu-items]="contextMenuItems"></tb-context-menu>
</div> </div>

View File

@ -5,16 +5,23 @@ import {
} from '@angular/core'; } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import * as Marked from 'marked'; import * as marked from 'marked';
import * as hljs from 'highlight.js'; import * as hljs from 'highlight.js';
import { import {
ApiResponse,
Board,
Column,
ContextMenu, ContextMenu,
ContextMenuItem, ContextMenuItem,
Notification,
Task, Task,
UserOptions, UserOptions,
AuthService AuthService,
ModalService,
NotificationsService
} from '../../shared/index'; } from '../../shared/index';
import { BoardService } from '../board.service';
@Component({ @Component({
selector: 'tb-task', selector: 'tb-task',
@ -23,16 +30,35 @@ import {
export class TaskDisplay implements OnInit { export class TaskDisplay implements OnInit {
private userOptions: UserOptions; private userOptions: UserOptions;
private contextMenuItems: Array<ContextMenuItem>; private contextMenuItems: Array<ContextMenuItem>;
private selectMenuItem: ContextMenuItem;
private activeBoard: Board;
@Input('task') taskData: Task; @Input('task') taskData: Task;
@Input('add-task') addTask: Function; @Input('add-task') addTask: Function;
@Input('remove-task') removeTask: Function;
constructor(private auth: AuthService, constructor(private auth: AuthService,
private sanitizer: DomSanitizer) { private sanitizer: DomSanitizer,
private boardService: BoardService,
private modal: ModalService,
private notes: NotificationsService) {
auth.userChanged.subscribe(() => { auth.userChanged.subscribe(() => {
this.userOptions = auth.userOptions; this.userOptions = auth.userOptions;
}); });
boardService.activeBoardChanged.subscribe((board: Board) => {
let menuText = 'Move to Column: <select>';
board.columns.forEach((column: Column) => {
menuText += '<option>' + column.name + '</option>';
});
menuText += '</select>';
this.selectMenuItem = new ContextMenuItem(menuText, null, false, false);
});
this.initMarked(); this.initMarked();
} }
@ -40,17 +66,20 @@ export class TaskDisplay implements OnInit {
this.contextMenuItems = [ this.contextMenuItems = [
new ContextMenuItem('View Task'), new ContextMenuItem('View Task'),
new ContextMenuItem('Edit Task'), new ContextMenuItem('Edit Task'),
new ContextMenuItem('Delete Task'), new ContextMenuItem('Remove Task', this.removeTask),
new ContextMenuItem('', null, true), new ContextMenuItem('', null, true),
new ContextMenuItem('Move to Column:', null, false, false), new ContextMenuItem('Copy To Board'),
new ContextMenuItem('Move To Board'),
new ContextMenuItem('', null, true), new ContextMenuItem('', null, true),
new ContextMenuItem('Add New Task', this.addTask) this.selectMenuItem,
new ContextMenuItem('', null, true),
new ContextMenuItem('Add Task', this.addTask)
]; ];
} }
getTaskDescription(): SafeHtml { getTaskDescription(): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml( return this.sanitizer.bypassSecurityTrustHtml(
Marked(this.taskData.description)); marked(this.taskData.description));
} }
// Expects a color in full HEX with leading #, e.g. #ffffe0 // Expects a color in full HEX with leading #, e.g. #ffffe0
@ -64,7 +93,7 @@ export class TaskDisplay implements OnInit {
} }
private initMarked() { private initMarked() {
let renderer = new Marked.Renderer(); let renderer = new marked.Renderer();
renderer.listitem = text => { renderer.listitem = text => {
if (/^\s*\[[x ]\]\s*/.test(text)) { if (/^\s*\[[x ]\]\s*/.test(text)) {
@ -91,7 +120,7 @@ export class TaskDisplay implements OnInit {
return out; return out;
}; };
Marked.setOptions({ marked.setOptions({
renderer, renderer,
smartypants: true, smartypants: true,
highlight: code => { highlight: code => {

View File

@ -6,9 +6,7 @@
(click)="callAction(item.action)"> (click)="callAction(item.action)">
<hr *ngIf="item.isSeparator"> <hr *ngIf="item.isSeparator">
<div *ngIf="!item.isSeparator"> <div *ngIf="!item.isSeparator"[innerHTML]="getText(item)"></div>
{{ item.text }}
</div>
</div> </div>
</div> </div>

View File

@ -3,6 +3,7 @@ import {
Input, Input,
ElementRef ElementRef
} from '@angular/core'; } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ContextMenuItem } from './context-menu-item.model'; import { ContextMenuItem } from './context-menu-item.model';
import { ContextMenuService } from './context-menu.service'; import { ContextMenuService } from './context-menu.service';
@ -18,7 +19,8 @@ export class ContextMenu {
animate = true; animate = true;
constructor(private el: ElementRef, constructor(private el: ElementRef,
private menuService: ContextMenuService) { private menuService: ContextMenuService,
private sanitizer: DomSanitizer) {
menuService.registerMenu(this); menuService.registerMenu(this);
let parentElement = el.nativeElement.parentElement; let parentElement = el.nativeElement.parentElement;
@ -31,6 +33,10 @@ export class ContextMenu {
}; };
} }
getText(item: ContextMenuItem): SafeHtml {
return this.sanitizer.bypassSecurityTrustHtml(item.text);
}
callAction(action: Function) { callAction(action: Function) {
if (action) { if (action) {
action(); action();
@ -49,7 +55,7 @@ export class ContextMenu {
target.style.top = event.pageY + 'px'; target.style.top = event.pageY + 'px';
// Adjust position if near an edge // Adjust position if near an edge
setTimeout(() => { let adjustPosition = () => {
let rect = target.getBoundingClientRect(); let rect = target.getBoundingClientRect();
let offsetX = (event.pageX + rect.width + edgeBuffer) > window.innerWidth; let offsetX = (event.pageX + rect.width + edgeBuffer) > window.innerWidth;
@ -57,8 +63,9 @@ export class ContextMenu {
target.style.left = event.pageX - (offsetX ? rect.width : 0) + 'px'; target.style.left = event.pageX - (offsetX ? rect.width : 0) + 'px';
target.style.top = event.pageY - (offsetY ? rect.height : 0) + 'px'; target.style.top = event.pageY - (offsetY ? rect.height : 0) + 'px';
}, };
0);
setTimeout(adjustPosition, 0);
} }
} }