make frontend keyboard shortcuts configurable

This commit is contained in:
Johannes Millan 2017-02-02 19:58:54 +01:00
parent f788df99a0
commit c43d33247d
7 changed files with 159 additions and 73 deletions

View file

@ -136,6 +136,7 @@ TODO configure more restrictive Content-Security-Policy
<script src="scripts/hint/hint-d.js"></script>
<script src="scripts/inline-markdown/inline-markdown-d.js"></script>
<script src="scripts/input-duration/input-duration-d.js"></script>
<script src="scripts/keyboard-key-input/keyboard-key-input-d.js"></script>
<script src="scripts/main/global-filters/duration-filter.js"></script>
<script src="scripts/main/global-filters/number-to-month-filter.js"></script>
<script src="scripts/main/global-services/git-log-s.js"></script>

View file

@ -82,7 +82,7 @@
InitGlobalModels();
}
function initGlobalShortcuts($document, Dialogs) {
function initGlobalShortcuts($document, Dialogs, $localStorage) {
// we just use this single one as this usually does mess
// up with the default browser shortcuts
// better to use the global electron shortcuts here
@ -91,7 +91,7 @@
// only trigger if not in typing mode
if (ev.target.tagName !== 'INPUT' && ev.target.tagName !== 'TEXTAREA') {
// on star
if (ev.key === '*') {
if (ev.key === $localStorage.keys.addNewTask) {
Dialogs('ADD_TASK', undefined, true);
}
}

View file

@ -29,12 +29,13 @@
},
keys: {
globalShowHide: 'Ctrl+Shift+X',
taskToggleDone: 'd',
addNewTask: '*',
taskEditTitle: 'Enter',
taskToggleNotes: 'n',
taskOpenEstimationDialog: 't',
taskToggleDone: 'd',
taskAddSubTask: '+',
taskDelete: 'entf',
taskEditTitle: 'enter'
taskDelete: 'Delete'
},
config: {
isTakeABreakEnabled: false,

View file

@ -0,0 +1,62 @@
/**
* @ngdoc directive
* @name superProductivity.directive:keyboardKeyInput
* @description
* # keyboardKeyInput
*/
(function () {
'use strict';
angular
.module('superProductivity')
.directive('keyboardKeyInput', keyboardKeyInput);
/* @ngInject */
function keyboardKeyInput($parse) {
return {
link: linkFn,
restrict: 'A'
};
function linkFn(scope, element, attrs) {
const modelGetter = $parse(attrs['ngModel']);
const modelSetter = modelGetter.assign;
let prevVal = modelGetter(scope);
element.on('keydown', ($event) => {
$event.preventDefault();
$event.stopPropagation();
//console.log($event);
//console.log(ngModelCtrl.$modelValue);
//console.log(attrs);
// focus out on escape
if ($event.keyCode === 27 || $event.key === 'Escape') {
modelSetter(scope, prevVal);
element.blur();
}
else {
if (attrs.keyboardKeyInputControlKeys) {
let val = '';
if ($event.ctrlKey) {
val += 'Ctrl+';
}
if ($event.altKey) {
val += 'Alt+';
}
if ($event.shiftKey) {
val += 'Shift+';
}
modelSetter(scope, val + $event.key);
}
else {
modelSetter(scope, $event.key);
}
}
scope.$apply();
});
}
}
})();

View file

@ -0,0 +1,21 @@
'use strict';
describe('Directive: keyboardKeyInput', function () {
// load the directive's module
beforeEach(module('superProductivity'));
beforeEach(module('templates'));
var element,
scope;
beforeEach(inject(function ($rootScope) {
scope = $rootScope.$new();
}));
it('should do something', inject(function ($compile) {
element = $compile('<keyboard-key-input></keyboard-key-input>')(scope);
scope.$digest();
expect(true).toBe(true);
}));
});

View file

@ -255,43 +255,61 @@
<section>
<h2 class="md-title">Keyboard Shortcuts</h2>
<h3 class="md-caption">Global Shortcuts</h3>
<section ng-if="vm.IS_ELECTRON">
<h3 class="md-caption">Global Shortcuts (system wide)</h3>
<md-input-container class="md-block">
<label>Show/Hide Super Productivity</label>
<input type="text"
keyboard-key-input
keyboard-key-input-control-keys="true"
ng-model="r.keys.globalShowHide">
</md-input-container>
</section>
<h3 class="md-caption">Global Shortcuts (application wide)</h3>
<md-input-container class="md-block">
<label>Show/Hide Super Productivity</label>
<label>Add new task</label>
<input type="text"
ng-model="r.keys.globalShowHide">
keyboard-key-input
ng-model="r.keys.addNewTask">
</md-input-container>
<h3 class="md-caption">Tasks</h3>
<p>The following shortcuts apply for the currently selected task (via tab or mouse).</p>
<p>The following shortcuts apply for the currently selected task (selected via tab or mouse).</p>
<md-input-container class="md-block">
<label>Edit title</label>
<input type="text"
keyboard-key-input
ng-model="r.keys.taskEditTitle">
</md-input-container>
<md-input-container class="md-block">
<label>Show/Hide Notes</label>
<input type="text"
keyboard-key-input
ng-model="r.keys.taskToggleNotes">
</md-input-container>
<md-input-container class="md-block">
<label>Edit estimation / time spent</label>
<input type="text"
keyboard-key-input
ng-model="r.keys.taskOpenEstimationDialog">
</md-input-container>
<md-input-container class="md-block">
<label>Toggle Done</label>
<input type="text"
keyboard-key-input
ng-model="r.keys.taskToggleDone">
</md-input-container>
<md-input-container class="md-block">
<label>Add sub task</label>
<input type="text"
keyboard-key-input
ng-model="r.keys.taskAddSubTask">
</md-input-container>
<md-input-container class="md-block">
<label>Delete Task</label>
<input type="text"
keyboard-key-input
ng-model="r.keys.taskDelete">
</md-input-container>
@ -303,28 +321,29 @@
<div flex-gt-sm="50">
<md-switch ng-model="r.config.isShortSyntaxEnabled"
aria-label="Use short syntax (e.g. 'TaskTitleBla e:30m')">
Use short syntax (e.g. 'TaskTitleBla e:30m') for task creation
Use short syntax (e.g. 'TaskTitleBla t:30m') for task creation
</md-switch>
</div>
</md-input-container>
<md-input-container class="md-block">
<div flex-gt-sm="50">
<md-switch ng-model="r.config.isTakeABreakEnabled"
aria-label="Enable take a break reminder">
Enable take a break reminder
</md-switch>
</div>
</md-input-container>
<md-input-container class="md-block md-icon-float"
ng-hide="!r.config.isTakeABreakEnabled">
<label>Remind when I worked longer than X without a break</label>
<ng-md-icon icon="timer"
aria-label="timer"></ng-md-icon>
<input type="text"
input-duration
ng-model="r.config.takeABreakMinWorkingTime">
</md-input-container>
<section ng-if="vm.IS_ELECTRON">
<md-input-container class="md-block">
<div flex-gt-sm="50">
<md-switch ng-model="r.config.isTakeABreakEnabled"
aria-label="Enable take a break reminder">
Enable take a break reminder
</md-switch>
</div>
</md-input-container>
<md-input-container class="md-block md-icon-float"
ng-hide="!r.config.isTakeABreakEnabled">
<label>Remind when I worked longer than X without a break</label>
<ng-md-icon icon="timer"
aria-label="timer"></ng-md-icon>
<input type="text"
input-duration
ng-model="r.config.takeABreakMinWorkingTime">
</md-input-container>
</section>
</section>
</div>

View file

@ -12,7 +12,7 @@
class TaskListCtrl {
/* @ngInject */
constructor(Dialogs, $mdToast, $timeout, Tasks, EDIT_ON_CLICK_TOGGLE_EV, $scope, ShortSyntax, $element, Jira) {
constructor(Dialogs, $localStorage, $mdToast, $timeout, Tasks, EDIT_ON_CLICK_TOGGLE_EV, $scope, ShortSyntax, $element, Jira) {
this.Dialogs = Dialogs;
this.$mdToast = $mdToast;
this.$timeout = $timeout;
@ -22,9 +22,11 @@
this.ShortSyntax = ShortSyntax;
this.$element = $element;
this.Jira = Jira;
this.$localStorage = $localStorage;
this.lastFocusedTaskEl = undefined;
this.boundHandleKeyPress = this.handleKeyPress.bind(this);
this.boundFocusLastTaskEl = this.focusLastFocusedTaskEl.bind(this);
}
$onDestroy() {
@ -113,7 +115,7 @@
estimateTime(task) {
this.Dialogs('TIME_ESTIMATE', { task })
.then(this.focusLastFocusedTaskEl, this.focusLastFocusedTaskEl);
.then(this.boundFocusLastTaskEl, this.boundFocusLastTaskEl);
}
deleteTask(task, $index) {
@ -188,48 +190,27 @@
taskEl = angular.element(taskEl);
const task = this.lastFocusedTaskEl.scope().modelValue;
// only trigger if target is li
const USED_KEYS = [
'+',
'a',
't',
'n',
'd'
];
const USED_KEY_CODES = [
46,
13
];
if ($event.key === this.$localStorage.keys.taskEditTitle) {
this.$scope.$broadcast(this.EDIT_ON_CLICK_TOGGLE_EV, task.id);
}
if ($event.key === this.$localStorage.keys.taskToggleNotes) {
task.showNotes = !task.showNotes;
}
if ($event.key === this.$localStorage.keys.taskOpenEstimationDialog) {
this.estimateTime(task);
}
if ($event.key === this.$localStorage.keys.taskToggleDone) {
task.isDone = !task.isDone;
}
if ($event.key === this.$localStorage.keys.taskAddSubTask) {
this.addSubTask(task);
}
if (USED_KEY_CODES.indexOf($event.keyCode) > -1 || USED_KEYS.indexOf($event.key) > -1) {
// + or a
if ($event.key === '+' || $event.key === 'a') {
this.addSubTask(task);
}
// t
if ($event.key === 't') {
this.estimateTime(task);
}
// n
if ($event.key === 'n') {
task.showNotes = !task.showNotes;
}
// d
if ($event.key === 'd') {
task.isDone = !task.isDone;
}
// entf
if ($event.keyCode === 46) {
this.deleteTask(task);
// don't propagate to next focused element
$event.preventDefault();
$event.stopPropagation();
}
// enter
if ($event.keyCode === 13) {
this.$scope.$broadcast(this.EDIT_ON_CLICK_TOGGLE_EV, task.id);
}
this.$scope.$apply();
if ($event.key === this.$localStorage.keys.taskDelete) {
this.deleteTask(task);
// don't propagate to next focused element
$event.preventDefault();
$event.stopPropagation();
}
// moving items via shift+ctrl+keyUp/keyDown
@ -242,7 +223,6 @@
if ($event.keyCode === 38) {
if (taskIndex > 0) {
TaskListCtrl.moveItem(this.tasks, taskIndex, taskIndex - 1);
this.$scope.$apply();
// we need to manually re-add focus after timeout
this.$timeout(() => {
@ -254,10 +234,12 @@
if ($event.keyCode === 40) {
if (taskIndex < this.tasks.length - 1) {
TaskListCtrl.moveItem(this.tasks, taskIndex, taskIndex + 1);
this.$scope.$apply();
}
}
}
// finally apply
this.$scope.$apply();
}
addSubTask(task) {