fix(keymap): Change keymap name and abbr algorithm (#365)

* feat(keymap): Change keymap name and abbr algorithm

Close #363

* feat(keymap): When rename a keymap or macro and the new name is exists do nothing
This commit is contained in:
Róbert Kiss
2017-07-23 22:30:19 +02:00
committed by László Monda
parent 42683e32f9
commit aad0c155dd
8 changed files with 165 additions and 116 deletions

View File

@@ -5,14 +5,12 @@
<input #name cancelable <input #name cancelable
class="keymap__name pane-title__name" class="keymap__name pane-title__name"
type="text" type="text"
value="{{ keymap.name }}"
(change)="editKeymapName($event.target.value)" (change)="editKeymapName($event.target.value)"
(keyup.enter)="name.blur()" (keyup.enter)="name.blur()"
/> keymap /> keymap
(<input #abbr cancelable (<input #abbr cancelable
class="keymap__abbrev pane-title__abbrev" class="keymap__abbrev pane-title__abbrev"
type="text" type="text"
value="{{ keymap.abbreviation }}"
(change)="editKeymapAbbr($event.target.value)" (change)="editKeymapAbbr($event.target.value)"
(keyup.enter)="abbr.blur()" (keyup.enter)="abbr.blur()"
[attr.maxLength]="3" [attr.maxLength]="3"

View File

@@ -2,11 +2,11 @@ import {
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
ElementRef, ElementRef,
Input,
Output,
EventEmitter, EventEmitter,
Input,
OnChanges, OnChanges,
Renderer, Output,
Renderer2,
SimpleChanges, SimpleChanges,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
@@ -35,11 +35,13 @@ export class KeymapHeaderComponent implements OnChanges {
starTitle: string; starTitle: string;
trashTitle: string = 'Delete keymap'; trashTitle: string = 'Delete keymap';
constructor(private store: Store<AppState>, private renderer: Renderer) { } constructor(private store: Store<AppState>, private renderer: Renderer2) { }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes['keymap']) { if (changes['keymap']) {
this.setKeymapTitle(); this.setKeymapTitle();
this.setName();
this.setAbbreviation();
} }
if (changes['deletable']) { if (changes['deletable']) {
this.setTrashTitle(); this.setTrashTitle();
@@ -64,7 +66,7 @@ export class KeymapHeaderComponent implements OnChanges {
editKeymapName(name: string) { editKeymapName(name: string) {
if (name.length === 0) { if (name.length === 0) {
this.renderer.setElementProperty(this.keymapName.nativeElement, 'value', this.keymap.name); this.setName();
return; return;
} }
@@ -75,12 +77,12 @@ export class KeymapHeaderComponent implements OnChanges {
const regexp = new RegExp(/^[a-zA-Z\d]+$/g); const regexp = new RegExp(/^[a-zA-Z\d]+$/g);
if (newAbbr.length < 1 || newAbbr.length > 3 || !regexp.test(newAbbr)) { if (newAbbr.length < 1 || newAbbr.length > 3 || !regexp.test(newAbbr)) {
this.renderer.setElementProperty(this.keymapAbbr.nativeElement, 'value', this.keymap.abbreviation); this.setAbbreviation();
return; return;
} }
newAbbr = newAbbr.toUpperCase(); newAbbr = newAbbr.toUpperCase();
this.store.dispatch(KeymapActions.editKeymapAbbr(this.keymap.abbreviation, newAbbr)); this.store.dispatch(KeymapActions.editKeymapAbbr(this.keymap.name, this.keymap.abbreviation, newAbbr));
} }
setKeymapTitle(): void { setKeymapTitle(): void {
@@ -96,4 +98,12 @@ export class KeymapHeaderComponent implements OnChanges {
onDownloadIconClick(): void { onDownloadIconClick(): void {
this.downloadClick.emit(); this.downloadClick.emit();
} }
private setName(): void {
this.renderer.setProperty(this.keymapName.nativeElement, 'value', this.keymap.name);
}
private setAbbreviation() {
this.renderer.setProperty(this.keymapAbbr.nativeElement, 'value', this.keymap.abbreviation);
}
} }

View File

@@ -5,7 +5,6 @@
<input #macroName cancelable <input #macroName cancelable
class="pane-title__name" class="pane-title__name"
type="text" type="text"
value="{{ macro.name }}"
(change)="editMacroName($event.target.value)" (change)="editMacroName($event.target.value)"
(keyup.enter)="macroName.blur()" (keyup.enter)="macroName.blur()"
/> />

View File

@@ -1,5 +1,12 @@
import { import {
AfterViewInit, ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges, Renderer, AfterViewInit,
ChangeDetectionStrategy,
Component,
ElementRef,
Input,
OnChanges,
Renderer2,
SimpleChanges,
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
@@ -21,17 +28,20 @@ export class MacroHeaderComponent implements AfterViewInit, OnChanges {
@Input() isNew: boolean; @Input() isNew: boolean;
@ViewChild('macroName') macroName: ElementRef; @ViewChild('macroName') macroName: ElementRef;
constructor(private store: Store<AppState>, private renderer: Renderer) { } constructor(private store: Store<AppState>, private renderer: Renderer2) { }
ngOnChanges() { ngOnChanges(changes: SimpleChanges) {
if (this.isNew) { if (this.isNew) {
this.renderer.invokeElementMethod(this.macroName.nativeElement, 'select', []); this.setFocusOnName();
}
if (changes['macro']) {
this.setName();
} }
} }
ngAfterViewInit() { ngAfterViewInit() {
if (this.isNew) { if (this.isNew) {
this.renderer.invokeElementMethod(this.macroName.nativeElement, 'select', []); this.setFocusOnName();
} }
} }
@@ -45,11 +55,19 @@ export class MacroHeaderComponent implements AfterViewInit, OnChanges {
editMacroName(name: string) { editMacroName(name: string) {
if (name.length === 0) { if (name.length === 0) {
this.renderer.setElementProperty(this.macroName.nativeElement, 'value', this.macro.name); this.setName();
return; return;
} }
this.store.dispatch(MacroActions.editMacroName(this.macro.id, name)); this.store.dispatch(MacroActions.editMacroName(this.macro.id, name));
} }
private setFocusOnName() {
this.macroName.nativeElement.select();
}
private setName(): void {
this.renderer.setProperty(this.macroName.nativeElement, 'value', this.macro.name);
}
} }

View File

@@ -69,12 +69,13 @@ export namespace KeymapActions {
}; };
} }
export function editKeymapAbbr(abbr: string, newAbbr: string): Action { export function editKeymapAbbr(name: string, abbr: string, newAbbr: string): Action {
return { return {
type: KeymapActions.EDIT_ABBR, type: KeymapActions.EDIT_ABBR,
payload: { payload: {
abbr: abbr, name,
newAbbr: newAbbr abbr,
newAbbr
} }
}; };
} }

View File

@@ -54,9 +54,14 @@ export class KeymapEffects {
@Effect({ dispatch: false }) editAbbr$: any = this.actions$ @Effect({ dispatch: false }) editAbbr$: any = this.actions$
.ofType(KeymapActions.EDIT_ABBR) .ofType(KeymapActions.EDIT_ABBR)
.map(action => action.payload.newAbbr) .withLatestFrom(this.store)
.do(newAbbr => { .do(([action, store]) => {
this.router.navigate(['/keymap', newAbbr]); for (const keymap of store.userConfiguration.keymaps) {
if (keymap.name === action.payload.name && keymap.abbreviation === action.payload.newAbbr) {
this.router.navigate(['/keymap', action.payload.newAbbr]);
return;
}
}
}); });
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { } constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) { }

View File

@@ -14,11 +14,11 @@ import { AppState } from '../index';
@Injectable() @Injectable()
export class MacroEffects { export class MacroEffects {
@Effect({dispatch: false}) remove$: any = this.actions$ @Effect({ dispatch: false }) remove$: any = this.actions$
.ofType(MacroActions.REMOVE) .ofType(MacroActions.REMOVE)
.map(action => this.store.dispatch(KeymapActions.checkMacro(action.payload))) .map(action => this.store.dispatch(KeymapActions.checkMacro(action.payload)))
.withLatestFrom(this.store) .withLatestFrom(this.store)
.map(latest => latest[1].userConfiguration.macros) .map(([action, state]) => state.userConfiguration.macros)
.do(macros => { .do(macros => {
if (macros.length === 0) { if (macros.length === 0) {
this.router.navigate(['/macro']); this.router.navigate(['/macro']);
@@ -27,14 +27,23 @@ export class MacroEffects {
} }
}); });
@Effect({dispatch: false}) add$: any = this.actions$ @Effect({ dispatch: false }) add$: any = this.actions$
.ofType(MacroActions.ADD) .ofType(MacroActions.ADD)
.withLatestFrom(this.store) .withLatestFrom(this.store)
.map(latest => latest[1].userConfiguration.macros) .map(([action, state]) => state.userConfiguration.macros)
.map(macros => macros[macros.length - 1]) .map(macros => macros[macros.length - 1])
.do(lastMacro => { .do(lastMacro => {
this.router.navigate(['/macro', lastMacro.id, 'new']); this.router.navigate(['/macro', lastMacro.id, 'new']);
}); });
@Effect({ dispatch: false }) duplicate: any = this.actions$
.ofType(MacroActions.DUPLICATE)
.withLatestFrom(this.store)
.map(([action, state]) => state.userConfiguration.macros)
.map(macros => macros[macros.length - 1])
.do(lastMacro => {
this.router.navigate(['/macro', lastMacro.id]);
});
constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) {} constructor(private actions$: Actions, private router: Router, private store: Store<AppState>) {}
} }

View File

@@ -5,7 +5,7 @@ import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of'; import 'rxjs/add/observable/of';
import 'rxjs/add/operator/map'; import 'rxjs/add/operator/map';
import { Helper as KeyActionHelper, KeyAction } from '../../config-serializer/config-items/key-action'; import { Helper as KeyActionHelper } from '../../config-serializer/config-items/key-action';
import { Keymap } from '../../config-serializer/config-items/keymap'; import { Keymap } from '../../config-serializer/config-items/keymap';
import { Macro } from '../../config-serializer/config-items/macro'; import { Macro } from '../../config-serializer/config-items/macro';
import { UserConfiguration } from '../../config-serializer/config-items/user-configuration'; import { UserConfiguration } from '../../config-serializer/config-items/user-configuration';
@@ -26,43 +26,51 @@ export default function (state = initialState, action: Action): UserConfiguratio
} }
case KeymapActions.ADD: case KeymapActions.ADD:
case KeymapActions.DUPLICATE: case KeymapActions.DUPLICATE: {
{ const newKeymap: Keymap = new Keymap(action.payload);
const newKeymap: Keymap = new Keymap(action.payload); newKeymap.abbreviation = generateAbbr(state.keymaps, newKeymap.abbreviation);
newKeymap.abbreviation = generateAbbr(state.keymaps, newKeymap.abbreviation); newKeymap.name = generateName(state.keymaps, newKeymap.name);
newKeymap.name = generateName(state.keymaps, newKeymap.name); newKeymap.isDefault = (state.keymaps.length === 0);
newKeymap.isDefault = (state.keymaps.length === 0);
changedUserConfiguration.keymaps = state.keymaps.concat(newKeymap); changedUserConfiguration.keymaps = state.keymaps.concat(newKeymap);
break; break;
} }
case KeymapActions.EDIT_NAME: case KeymapActions.EDIT_NAME: {
{ const name: string = action.payload.name.toUpperCase();
const name: string = generateName(state.keymaps, action.payload.name);
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => { const duplicate = state.keymaps.some((keymap: Keymap) => {
if (keymap.abbreviation === action.payload.abbr) { return keymap.name === name && keymap.abbreviation !== action.payload.abbr;
keymap = Object.assign(new Keymap(), keymap); });
keymap.name = name;
}
return keymap;
});
break;
}
case KeymapActions.EDIT_ABBR:
const abbr: string = generateAbbr(state.keymaps, action.payload.newAbbr);
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => { changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
if (keymap.abbreviation === action.payload.abbr) { keymap = Object.assign(new Keymap(), keymap);
keymap = Object.assign(new Keymap(), keymap);
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
keymap.name = name;
}
return keymap;
});
break;
}
case KeymapActions.EDIT_ABBR: {
const abbr: string = action.payload.newAbbr.toUpperCase();
const duplicate = state.keymaps.some((keymap: Keymap) => {
return keymap.name !== action.payload.name && keymap.abbreviation === abbr;
});
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
keymap = Object.assign(new Keymap(), keymap);
if (!duplicate && keymap.abbreviation === action.payload.abbr) {
keymap.abbreviation = abbr; keymap.abbreviation = abbr;
} else {
keymap = keymap.renameKeymap(action.payload.abbr, action.payload.newAbbr);
} }
return keymap; return keymap;
}); });
break; break;
}
case KeymapActions.SET_DEFAULT: case KeymapActions.SET_DEFAULT:
changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => { changedUserConfiguration.keymaps = state.keymaps.map((keymap: Keymap) => {
if (keymap.abbreviation === action.payload || keymap.isDefault) { if (keymap.abbreviation === action.payload || keymap.isDefault) {
@@ -101,33 +109,32 @@ export default function (state = initialState, action: Action): UserConfiguratio
}); });
break; break;
case KeymapActions.SAVE_KEY: case KeymapActions.SAVE_KEY: {
{ const newKeymap: Keymap = Object.assign(new Keymap(), action.payload.keymap);
const newKeymap: Keymap = Object.assign(new Keymap(), action.payload.keymap); newKeymap.layers = newKeymap.layers.slice();
newKeymap.layers = newKeymap.layers.slice();
const layerIndex: number = action.payload.layer; const layerIndex: number = action.payload.layer;
const newLayer: Layer = Object.assign(new Layer(), newKeymap.layers[layerIndex]); const newLayer: Layer = Object.assign(new Layer(), newKeymap.layers[layerIndex]);
newKeymap.layers[layerIndex] = newLayer; newKeymap.layers[layerIndex] = newLayer;
const moduleIndex: number = action.payload.module; const moduleIndex: number = action.payload.module;
const newModule: Module = Object.assign(new Module(), newLayer.modules[moduleIndex]); const newModule: Module = Object.assign(new Module(), newLayer.modules[moduleIndex]);
newLayer.modules = newLayer.modules.slice(); newLayer.modules = newLayer.modules.slice();
newLayer.modules[moduleIndex] = newModule; newLayer.modules[moduleIndex] = newModule;
const keyIndex: number = action.payload.key; const keyIndex: number = action.payload.key;
newModule.keyActions = newModule.keyActions.slice(); newModule.keyActions = newModule.keyActions.slice();
newModule.keyActions[keyIndex] = KeyActionHelper.createKeyAction(action.payload.keyAction); newModule.keyActions[keyIndex] = KeyActionHelper.createKeyAction(action.payload.keyAction);
changedUserConfiguration.keymaps = state.keymaps.map(keymap => { changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
if (keymap.abbreviation === newKeymap.abbreviation) { if (keymap.abbreviation === newKeymap.abbreviation) {
keymap = newKeymap; keymap = newKeymap;
} }
return keymap; return keymap;
}); });
break; break;
} }
case KeymapActions.CHECK_MACRO: case KeymapActions.CHECK_MACRO:
changedUserConfiguration.keymaps = state.keymaps.map(keymap => { changedUserConfiguration.keymaps = state.keymaps.map(keymap => {
keymap = Object.assign(new Keymap(), keymap); keymap = Object.assign(new Keymap(), keymap);
@@ -135,41 +142,43 @@ export default function (state = initialState, action: Action): UserConfiguratio
return keymap; return keymap;
}); });
break; break;
case MacroActions.ADD: case MacroActions.ADD: {
{ const newMacro = new Macro();
const newMacro = new Macro(); newMacro.id = generateMacroId(state.macros);
newMacro.id = generateMacroId(state.macros); newMacro.name = generateName(state.macros, 'New macro');
newMacro.name = generateName(state.macros, 'New macro'); newMacro.isLooped = false;
newMacro.isLooped = false; newMacro.isPrivate = true;
newMacro.isPrivate = true; newMacro.macroActions = [];
newMacro.macroActions = [];
changedUserConfiguration.macros = state.macros.concat(newMacro); changedUserConfiguration.macros = state.macros.concat(newMacro);
break; break;
} }
case MacroActions.DUPLICATE: case MacroActions.DUPLICATE: {
{ const newMacro = new Macro(action.payload);
const newMacro = new Macro(action.payload); newMacro.name = generateName(state.macros, newMacro.name);
newMacro.name = generateName(state.macros, newMacro.name); newMacro.id = generateMacroId(state.macros);
newMacro.id = generateMacroId(state.macros);
changedUserConfiguration.macros = state.macros.concat(newMacro); changedUserConfiguration.macros = state.macros.concat(newMacro);
break; break;
} }
case MacroActions.EDIT_NAME: case MacroActions.EDIT_NAME: {
{ const name: string = action.payload.name;
const name: string = generateName(state.macros, action.payload.name);
changedUserConfiguration.macros = state.macros.map((macro: Macro) => { const duplicate = state.macros.some((macro: Macro) => {
if (macro.id === action.payload.id) { return macro.id !== action.payload.id && macro.name === name;
macro.name = name; });
}
return macro; changedUserConfiguration.macros = state.macros.map((macro: Macro) => {
}); macro = Object.assign(new Macro(), macro);
if (!duplicate && macro.id === action.payload.id) {
macro.name = name;
}
break; return macro;
} });
break;
}
case MacroActions.REMOVE: case MacroActions.REMOVE:
changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== action.payload); changedUserConfiguration.macros = state.macros.filter((macro: Macro) => macro.id !== action.payload);
break; break;
@@ -274,7 +283,7 @@ export function getMacro(id: number) {
} }
function generateAbbr(keymaps: Keymap[], abbr: string): string { function generateAbbr(keymaps: Keymap[], abbr: string): string {
const chars: string[] = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); const chars: string[] = '23456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('');
let position = 0; let position = 0;
while (keymaps.some((keymap: Keymap) => keymap.abbreviation === abbr)) { while (keymaps.some((keymap: Keymap) => keymap.abbreviation === abbr)) {
@@ -286,15 +295,15 @@ function generateAbbr(keymaps: Keymap[], abbr: string): string {
} }
function generateName(items: { name: string }[], name: string) { function generateName(items: { name: string }[], name: string) {
let suffix = 2; let suffix = 1;
const oldName: string = name; const regexp = / \(\d+\)$/g;
const matchName = name.replace(regexp, '');
while (items.some(item => item.name === name)) { items.forEach(item => {
name = oldName + ` (${suffix})`; if (item.name.replace(regexp, '') === matchName) {
++suffix; suffix++;
} }
});
return name; return `${matchName} (${suffix})`;
} }
function generateMacroId(macros: Macro[]) { function generateMacroId(macros: Macro[]) {