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'];
$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->apiJson->addAlert('error', 'Error removing task. ' .
'No task found for ID ' . $id . '.');
@ -134,8 +134,9 @@ class Tasks extends BaseController {
return $this->jsonResponse($response);
}
if (!$this->checkBoardAccess(
$this->getBoardId($task->column_id), $request)) {
$boardId = $this->getBoardId($task->column_id);
if (!$this->checkBoardAccess($boardId, $request)) {
return $this->jsonResponse($response, 403);
}
@ -151,6 +152,9 @@ class Tasks extends BaseController {
$this->apiJson->addAlert('success',
'Task ' . $before->title . ' removed.');
$board = R::load('board', $boardId);
$this->apiJson->addData(R::exportAll($board));
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
refreshToken(): void {
this.http.post('api/refresh', {}).subscribe();

View File

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

View File

@ -40,7 +40,10 @@ export class ColumnDisplay implements OnInit {
private contextMenuItems: Array<ContextMenuItem>;
private MODAL_ID: string;
private MODAL_CONFIRM_ID: string;
private modalProps: Task;
private taskToRemove: number;
@Input('column') columnData: Column;
@ -55,11 +58,13 @@ export class ColumnDisplay implements OnInit {
this.collapseTasks = false;
this.contextMenuItems = [
new ContextMenuItem('Add New Task',
new ContextMenuItem('Add Task',
this.getShowModalFunction())
];
this.MODAL_ID = 'add-task-form-';
this.MODAL_CONFIRM_ID = 'task-remove-confirm';
this.modalProps = new Task();
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 {
return () => { this.showModal(); };
}

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import {
Input,
ElementRef
} from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { ContextMenuItem } from './context-menu-item.model';
import { ContextMenuService } from './context-menu.service';
@ -18,7 +19,8 @@ export class ContextMenu {
animate = true;
constructor(private el: ElementRef,
private menuService: ContextMenuService) {
private menuService: ContextMenuService,
private sanitizer: DomSanitizer) {
menuService.registerMenu(this);
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) {
if (action) {
action();
@ -49,7 +55,7 @@ export class ContextMenu {
target.style.top = event.pageY + 'px';
// Adjust position if near an edge
setTimeout(() => {
let adjustPosition = () => {
let rect = target.getBoundingClientRect();
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.top = event.pageY - (offsetY ? rect.height : 0) + 'px';
},
0);
};
setTimeout(adjustPosition, 0);
}
}