Rework context menu behavior
This commit is contained in:
parent
f3c4af20a6
commit
8ff331c683
7929
package-lock.json
generated
7929
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
34
package.json
34
package.json
@ -27,24 +27,24 @@
|
||||
},
|
||||
"homepage": "https://github.com/kiswa/TaskBoard#readme",
|
||||
"devDependencies": {
|
||||
"@angular/common": "4.3.2",
|
||||
"@angular/compiler": "4.3.2",
|
||||
"@angular/core": "4.3.2",
|
||||
"@angular/forms": "4.3.2",
|
||||
"@angular/http": "4.3.2",
|
||||
"@angular/platform-browser": "4.3.2",
|
||||
"@angular/platform-browser-dynamic": "4.3.2",
|
||||
"@angular/router": "4.3.2",
|
||||
"@angular/common": "4.3.5",
|
||||
"@angular/compiler": "4.3.5",
|
||||
"@angular/core": "4.3.5",
|
||||
"@angular/forms": "4.3.5",
|
||||
"@angular/http": "4.3.5",
|
||||
"@angular/platform-browser": "4.3.5",
|
||||
"@angular/platform-browser-dynamic": "4.3.5",
|
||||
"@angular/router": "4.3.5",
|
||||
"@types/chartist": "^0.9.35",
|
||||
"@types/core-js": "^0.9.42",
|
||||
"@types/highlight.js": "^9.1.9",
|
||||
"@types/marked": "0.0.28",
|
||||
"@types/marked": "0.3.0",
|
||||
"bourbon": "^4.3.4",
|
||||
"bourbon-neat": "1.9.0",
|
||||
"chai": "^4.1.0",
|
||||
"chai": "^4.1.1",
|
||||
"chartist": "^0.11.0",
|
||||
"chartist-plugin-tooltip": "git+https://github.com/Globegitter/chartist-plugin-tooltip.git",
|
||||
"core-js": "^2.4.1",
|
||||
"chartist-plugin-tooltips": "^0.0.17",
|
||||
"core-js": "^2.5.0",
|
||||
"del": "^3.0.0",
|
||||
"dragula": "^3.7.2",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
@ -53,7 +53,7 @@
|
||||
"gulp-chmod": "^2.0.0",
|
||||
"gulp-composer": "^0.4.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-cssimport": "^5.0.0",
|
||||
"gulp-cssimport": "^5.1.1",
|
||||
"gulp-cssnano": "^2.1.2",
|
||||
"gulp-imagemin": "^3.3.0",
|
||||
"gulp-istanbul": "^1.1.2",
|
||||
@ -61,7 +61,7 @@
|
||||
"gulp-phpunit": "^0.23.0",
|
||||
"gulp-sass": "^3.1.0",
|
||||
"gulp-scss-lint": "^0.5.0",
|
||||
"gulp-tslint": "^8.1.1",
|
||||
"gulp-tslint": "^8.1.2",
|
||||
"gulp-typescript": "^3.2.1",
|
||||
"gulp-uglify": "^3.0.0",
|
||||
"gulp-util": "^3.0.8",
|
||||
@ -72,12 +72,12 @@
|
||||
"ng2-dragula": "^1.5.0",
|
||||
"node-normalize-scss": "^3.0.0",
|
||||
"reflect-metadata": "^0.1.10",
|
||||
"rxjs": "5.4.2",
|
||||
"rxjs": "5.4.3",
|
||||
"scss-base": "^1.3.4",
|
||||
"systemjs": "0.20.17",
|
||||
"systemjs": "0.20.18",
|
||||
"systemjs-builder": "0.16.9",
|
||||
"touch": "^3.1.0",
|
||||
"tslint": "^5.5.0",
|
||||
"tslint": "^5.6.0",
|
||||
"typescript": "2.4.2",
|
||||
"zone.js": "^0.8.16"
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ class Tasks extends BaseController {
|
||||
R::store($task);
|
||||
|
||||
$actor = R::load('user', Auth::GetUserId($request));
|
||||
$this->updateTaskOrder($task, $actor);
|
||||
$this->updateTaskOrder($task, $actor, true);
|
||||
|
||||
$this->dbLogger->logChange($actor->id,
|
||||
$actor->username . ' added task ' . $task->title . '.',
|
||||
@ -106,6 +106,8 @@ class Tasks extends BaseController {
|
||||
R::store($update);
|
||||
|
||||
$actor = R::load('user', Auth::GetUserId($request));
|
||||
$this->updateTaskOrder($task, $actor, false);
|
||||
|
||||
$this->dbLogger->logChange($actor->id,
|
||||
$actor->username . ' updated task ' . $task->title,
|
||||
json_encode($task), json_encode($update),
|
||||
@ -151,6 +153,8 @@ class Tasks extends BaseController {
|
||||
R::trash($task);
|
||||
|
||||
$actor = R::load('user', Auth::GetUserId($request));
|
||||
$this->updateTaskOrder($task, $actor, false);
|
||||
|
||||
$this->dbLogger->logChange($actor->id,
|
||||
$actor->username . ' removed task ' . $before->title,
|
||||
json_encode($before), '', 'task', $id);
|
||||
@ -171,7 +175,7 @@ class Tasks extends BaseController {
|
||||
return $column->board_id;
|
||||
}
|
||||
|
||||
private function updateTaskOrder($task, $user) {
|
||||
private function updateTaskOrder($task, $user, $isNew) {
|
||||
$column = R::load('column', $task->column_id);
|
||||
$user_opts = R::load('useroption', $user->user_option_id);
|
||||
|
||||
@ -181,8 +185,9 @@ class Tasks extends BaseController {
|
||||
$counter++;
|
||||
}
|
||||
|
||||
if ($user_opts->new_tasks_at_bottom) {
|
||||
R::store($column);
|
||||
R::store($column);
|
||||
|
||||
if (!$isNew || $user_opts->new_tasks_at_bottom) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -12,6 +12,7 @@ import { API_HTTP_PROVIDERS } from './app.api-http';
|
||||
import { Constants } from './shared/constants';
|
||||
import {
|
||||
AuthGuard,
|
||||
CompileDirective,
|
||||
ContextMenu,
|
||||
InlineEdit,
|
||||
Modal,
|
||||
@ -47,8 +48,9 @@ import { BoardService } from './board/board.service';
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
InlineEdit,
|
||||
CompileDirective,
|
||||
ContextMenu,
|
||||
InlineEdit,
|
||||
Modal,
|
||||
Notifications,
|
||||
TopNav,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<h3 [class.near-limit]="columnData.hasTaskLimit() &&
|
||||
columnData.task_limit - columnData.tasks.length === 1"
|
||||
columnData.task_limit - columnData.tasks.length === 0"
|
||||
[class.limit-reached]="columnData.hasTaskLimit() &&
|
||||
columnData.task_limit - columnData.tasks.length < 1">
|
||||
columnData.task_limit - columnData.tasks.length < 0">
|
||||
<span class="icon icon-minus-squared-alt"
|
||||
[title]="strings['boards_collapseAllTasks']"
|
||||
*ngIf="!collapseTasks"
|
||||
@ -44,6 +44,7 @@
|
||||
(click)="saveLimitChanges()"></i>
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<span class="sort-by">
|
||||
{{ strings['sortBy'] }}:
|
||||
<select [(ngModel)]="sortOption"
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
<div class="description" *ngIf="!isCollapsed"
|
||||
[innerHTML]="getTaskDescription()"></div>
|
||||
<pre>{{ taskData.position | json }}</pre>
|
||||
|
||||
<div class="stats">
|
||||
<span *ngIf="userOptions.show_assignee">
|
||||
@ -33,7 +34,9 @@
|
||||
</span>
|
||||
|
||||
<span class="right">
|
||||
<span *ngIf="taskData.due_date">
|
||||
<span *ngIf="taskData.due_date"
|
||||
[class.overdue]="isOverdue"
|
||||
[class.near-due]="isNearlyDue">
|
||||
{{ strings['boards_taskDue'] }}: {{ taskData.due_date }}
|
||||
</span>
|
||||
<span *ngIf="taskData.comments.length" class="icon icon-chat-empty"
|
||||
|
@ -34,7 +34,6 @@ export class TaskDisplay implements OnInit {
|
||||
private strings: any;
|
||||
private userOptions: UserOptions;
|
||||
private contextMenuItems: Array<ContextMenuItem>;
|
||||
private selectMenuItem: ContextMenuItem;
|
||||
|
||||
private activeBoard: Board;
|
||||
private boardsList: Array<Board>;
|
||||
@ -43,6 +42,9 @@ export class TaskDisplay implements OnInit {
|
||||
private completeTasks: number;
|
||||
private percentComplete: number;
|
||||
|
||||
private isOverdue: boolean;
|
||||
private isNearlyDue: boolean;
|
||||
|
||||
@Input('task') taskData: Task;
|
||||
@Input('add-task') addTask: Function;
|
||||
@Input('edit-task') editTask: Function;
|
||||
@ -98,6 +100,7 @@ export class TaskDisplay implements OnInit {
|
||||
this.generateContextMenuItems();
|
||||
this.initMarked();
|
||||
this.calcPercentComplete();
|
||||
this.checkDueDate();
|
||||
}
|
||||
|
||||
getTaskDescription(): SafeHtml {
|
||||
@ -129,6 +132,32 @@ export class TaskDisplay implements OnInit {
|
||||
return yiq >= 140 ? '#333333' : '#efefef';
|
||||
}
|
||||
|
||||
private checkDueDate() {
|
||||
if (this.taskData.due_date === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
let dueDate = new Date(this.taskData.due_date);
|
||||
|
||||
if (isNaN(dueDate.valueOf())) {
|
||||
return;
|
||||
}
|
||||
|
||||
let millisecondsPerDay = (1000 * 3600 * 24),
|
||||
today = new Date(),
|
||||
timeDiff = today.getTime() - dueDate.getTime(),
|
||||
daysDiff = Math.ceil(timeDiff / millisecondsPerDay);
|
||||
|
||||
if (daysDiff > 0) {
|
||||
// past due date
|
||||
this.isOverdue = true;
|
||||
}
|
||||
|
||||
if (daysDiff <= 0 && daysDiff > -3) {
|
||||
this.isNearlyDue = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Needs anonymous function for proper `this` context.
|
||||
private markedCallback = (error: any, text: string) => {
|
||||
this.activeBoard.issue_trackers.forEach(tracker => {
|
||||
@ -161,7 +190,9 @@ export class TaskDisplay implements OnInit {
|
||||
|
||||
private getMoveMenuItem() {
|
||||
let menuText = this.strings.boards_moveTask +
|
||||
': <select id="columnsList' + this.taskData.id + '">';
|
||||
': <select id="columnsList' + this.taskData.id + '" ' +
|
||||
'(click)="action($event)">' +
|
||||
'<option value="0">' + this.strings.boards_selectColumn + '</option>';
|
||||
|
||||
this.activeBoard.columns.forEach((column: Column) => {
|
||||
menuText += '<option value="' + column.id + '">' + column.name + '</option>';
|
||||
@ -177,9 +208,7 @@ export class TaskDisplay implements OnInit {
|
||||
this.changeTaskColumn();
|
||||
};
|
||||
|
||||
return new ContextMenuItem(menuText,
|
||||
action,
|
||||
false, false);
|
||||
return new ContextMenuItem(menuText, action, false, false, true);
|
||||
}
|
||||
|
||||
private changeTaskColumn() {
|
||||
@ -237,8 +266,10 @@ export class TaskDisplay implements OnInit {
|
||||
private getMenuItem(text: string): ContextMenuItem {
|
||||
let menuText = text + ': ' +
|
||||
'<i class="icon icon-help-circled" ' +
|
||||
'data-help="' + this.strings.boards_copyMoveHelp + '"></i> ' +
|
||||
'<select id="boardsList' + text.split(' ')[0] + '">';
|
||||
'data-help="' + this.strings.boards_copyMoveHelp + '"></i> ' +
|
||||
'<select id="boardsList' + this.taskData.id + text.split(' ')[0] + '" ' +
|
||||
'(click)="action($event)">' +
|
||||
'<option value="0">' + this.strings.boards_selectBoard + '</option>';
|
||||
|
||||
this.boardsList.forEach((board: Board) => {
|
||||
if (board.name !== this.activeBoard.name) {
|
||||
@ -261,11 +292,11 @@ export class TaskDisplay implements OnInit {
|
||||
this.moveTaskToBoard();
|
||||
};
|
||||
|
||||
return new ContextMenuItem(menuText, action, false, false);
|
||||
return new ContextMenuItem(menuText, action, false, false, true);
|
||||
}
|
||||
|
||||
private copyTaskToBoard() {
|
||||
let select = document.getElementById('boardsList' +
|
||||
let select = document.getElementById('boardsList' + this.taskData.id +
|
||||
this.strings.boards_copyTaskTo.split(' ')[0]) as HTMLSelectElement;
|
||||
|
||||
let newBoardId = +select[select.selectedIndex].value;
|
||||
@ -298,7 +329,7 @@ export class TaskDisplay implements OnInit {
|
||||
}
|
||||
|
||||
private moveTaskToBoard() {
|
||||
let select = document.getElementById('boardsList' +
|
||||
let select = document.getElementById('boardsList' + this.taskData.id +
|
||||
this.strings.boards_moveTaskTo.split(' ')[0]) as HTMLSelectElement;
|
||||
|
||||
let newBoardId = +select[select.selectedIndex].value;
|
||||
|
84
src/app/shared/compile/compile.directive.ts
Normal file
84
src/app/shared/compile/compile.directive.ts
Normal file
@ -0,0 +1,84 @@
|
||||
import {
|
||||
Compiler,
|
||||
Component,
|
||||
ComponentRef,
|
||||
Directive,
|
||||
Input,
|
||||
ModuleWithComponentFactories,
|
||||
NgModule,
|
||||
OnChanges,
|
||||
Type,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
@Directive({
|
||||
selector: '[compile]'
|
||||
})
|
||||
export class CompileDirective implements OnChanges {
|
||||
@Input() compile: string;
|
||||
@Input() compileContext: any;
|
||||
|
||||
compRef: ComponentRef<any>;
|
||||
|
||||
constructor(private vcRef: ViewContainerRef,
|
||||
private compiler: Compiler) { }
|
||||
|
||||
ngOnChanges() {
|
||||
if (!this.compile) {
|
||||
if (this.compRef) {
|
||||
this.updateProperties();
|
||||
return;
|
||||
}
|
||||
|
||||
throw Error('You forgot to provide template');
|
||||
}
|
||||
|
||||
this.vcRef.clear();
|
||||
this.compRef = null;
|
||||
|
||||
const component = this.createDynamicComponent(this.compile);
|
||||
const module = this.createDynamicModule(component);
|
||||
|
||||
this.compiler.compileModuleAndAllComponentsAsync(module)
|
||||
.then((moduleWithFactories: ModuleWithComponentFactories<any>) => {
|
||||
let compFactory = moduleWithFactories.componentFactories
|
||||
.find(x => x.componentType === component);
|
||||
|
||||
this.compRef = this.vcRef.createComponent(compFactory);
|
||||
this.updateProperties();
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error); // tslint:disable-line
|
||||
});
|
||||
}
|
||||
|
||||
updateProperties() {
|
||||
for (let prop in this.compileContext) {
|
||||
if (this.compileContext.hasOwnProperty(prop)) {
|
||||
this.compRef.instance[prop] = this.compileContext[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private createDynamicComponent (template: string) {
|
||||
@Component({
|
||||
selector: 'custom-dynamic-component',
|
||||
template
|
||||
})
|
||||
class CustomDynamicComponent {}
|
||||
|
||||
return CustomDynamicComponent;
|
||||
}
|
||||
|
||||
private createDynamicModule (component: Type<any>) {
|
||||
@NgModule({
|
||||
imports: [CommonModule],
|
||||
declarations: [component]
|
||||
})
|
||||
class DynamicModule {}
|
||||
|
||||
return DynamicModule;
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,16 @@ export class ContextMenuItem {
|
||||
constructor(public text: string = '',
|
||||
public action: Function = null,
|
||||
public isSeparator: boolean = false,
|
||||
public canHighlight: boolean = true) {
|
||||
public canHighlight: boolean = true,
|
||||
public isCustom: boolean = false) {
|
||||
if (isSeparator) {
|
||||
text = '<hr>';
|
||||
canHighlight = false;
|
||||
this.text = '<hr>';
|
||||
this.canHighlight = false;
|
||||
}
|
||||
|
||||
if (!isCustom) {
|
||||
this.text = '<span (click)="action($event)">' + this.text +
|
||||
'</span>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
<div class="context-menu-container"
|
||||
[ngClass]="{ 'menu-open': isOpen }">
|
||||
[ngClass]="{ 'menu-open': isOpen }"
|
||||
(click)="$event.stopPropagation()">
|
||||
|
||||
<div class="menu-item" *ngFor="let item of menuItems"
|
||||
[ngClass]="{ 'no-highlight': item.isSeparator || !item.canHighlight }"
|
||||
(click)="callAction($event, item.action)"
|
||||
[innerHTML]="getText(item)">
|
||||
(click)="menuService.closeAllMenus()"
|
||||
[ngClass]="{ 'no-highlight': item.isSeparator || !item.canHighlight }">
|
||||
<ng-template
|
||||
*compile="item.text; context: { action: item.action }"></ng-template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -33,15 +33,6 @@ export class ContextMenu {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(item.text);
|
||||
}
|
||||
|
||||
callAction(event: MouseEvent, action: Function) {
|
||||
console.log(event); // tslint:disable-line
|
||||
if (action) {
|
||||
action(event);
|
||||
}
|
||||
|
||||
this.menuService.closeAllMenus();
|
||||
}
|
||||
|
||||
private captureChildEvents(event: MouseEvent) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
@ -1,5 +1,6 @@
|
||||
export * from './constants';
|
||||
export * from './auth/index';
|
||||
export * from './compile/compile.directive';
|
||||
export * from './constants';
|
||||
export * from './context-menu/index';
|
||||
export * from './inline-edit/inline-edit.component';
|
||||
export * from './modal/index';
|
||||
|
@ -131,6 +131,7 @@
|
||||
"settings_noActionsAdmin": "There are no automatic actions. Use the <strong>Add Action</strong> form below to add one.",
|
||||
|
||||
"boards_selectBoard": "Select Board",
|
||||
"boards_selectColumn": "Select Column",
|
||||
"boards_hideFiltered": "Hide filtered items",
|
||||
"boards_userFilter": "User Filter",
|
||||
"boards_categoryFilter": "Category Filter",
|
||||
|
@ -131,6 +131,7 @@
|
||||
"settings_noActionsAdmin": "No hay acciones automáticas. Utilice el botón <strong>Agregar Acción</strong> de abajo para agregar uno.",
|
||||
|
||||
"boards_selectBoard": "Seleccionar el Tablero",
|
||||
"boards_selectColumn": "Seleccionar la Columna",
|
||||
"boards_hideFiltered": "Ocultar los elementos filtrados",
|
||||
"boards_userFilter": "Filtro de usuario",
|
||||
"boards_categoryFilter": "Filtro de categoría",
|
||||
|
@ -280,6 +280,14 @@
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.overdue {
|
||||
text-shadow: 0 0 2px rgba(255, 0, 0, 1);
|
||||
}
|
||||
|
||||
.near-due {
|
||||
text-shadow: 0 0 2px rgba(255, 180, 0, 1);
|
||||
}
|
||||
|
||||
&.compact {
|
||||
.description {
|
||||
display: none;
|
||||
|
Reference in New Issue
Block a user