Mark duplicate hotkeys

This commit is contained in:
Benjamin Brandmeier 2022-12-18 13:34:06 +01:00 committed by Eugene
parent bc243a2741
commit 72bc58332d
7 changed files with 77 additions and 28 deletions

View file

@ -3,6 +3,11 @@ export interface HotkeyDescription {
name: string name: string
} }
export interface Hotkey {
strokes: string[] | string; // may be a sequence of strokes
isDuplicate: boolean;
}
/** /**
* Extend to provide your own hotkeys. A corresponding [[ConfigProvider]] * Extend to provide your own hotkeys. A corresponding [[ConfigProvider]]
* must also provide the `hotkeys.foo` config options with the default values * must also provide the `hotkeys.foo` config options with the default values

View file

@ -256,6 +256,11 @@ multi-hotkey-input {
} }
} }
.item:has(.duplicate) {
background-color: theme-color('danger');
border: 1px solid theme-color('danger');
}
.add { .add {
color: #777; color: #777;
padding: 4px 10px 0; padding: 4px 10px 0;
@ -265,6 +270,11 @@ multi-hotkey-input {
&:hover { background: darken($body-bg2, 5%); } &:hover { background: darken($body-bg2, 5%); }
&:active { background: darken($body-bg2, 15%); } &:active { background: darken($body-bg2, 15%); }
} }
.add:has(.duplicate), .item:has(.duplicate) .body, .item:has(.duplicate) .remove {
&:hover { background: darken(theme-color('danger'), 5%); }
&:active { background: darken(theme-color('danger'), 15%); }
}
} }
hotkey-input-modal { hotkey-input-modal {

View file

@ -162,6 +162,11 @@ multi-hotkey-input {
} }
} }
.item:has(.duplicate) {
background-color: theme-color('danger');
border: 1px solid theme-color('danger');
}
.add { .add {
color: #777; color: #777;
padding: 4px 10px 0; padding: 4px 10px 0;
@ -171,6 +176,11 @@ multi-hotkey-input {
&:hover { background: darken($body-bg2, 5%); } &:hover { background: darken($body-bg2, 5%); }
&:active { background: darken($body-bg2, 15%); } &:active { background: darken($body-bg2, 15%); }
} }
.add:has(.duplicate), .item:has(.duplicate) .body, .item:has(.duplicate) .remove {
&:hover { background: darken(theme-color('danger'), 5%); }
&:active { background: darken(theme-color('danger'), 15%); }
}
} }
hotkey-input-modal { hotkey-input-modal {

View file

@ -14,6 +14,6 @@ h3.mb-3(translate) Hotkeys
span.ml-2.text-muted ({{hotkey.id}}) span.ml-2.text-muted ({{hotkey.id}})
.col-4.pr-5 .col-4.pr-5
multi-hotkey-input( multi-hotkey-input(
[model]='getHotkey(hotkey.id) || []', [hotkeys]='getHotkeys(hotkey.id) || []',
(modelChange)='setHotkey(hotkey.id, $event)' (hotkeysChange)='setHotkeys(hotkey.id, $event)'
) )

View file

@ -7,6 +7,7 @@ import {
HotkeysService, HotkeysService,
HostAppService, HostAppService,
} from 'tabby-core' } from 'tabby-core'
import { Hotkey } from 'tabby-core/src/api/hotkeyProvider'
_('Search hotkeys') _('Search hotkeys')
@ -30,28 +31,44 @@ export class HotkeySettingsTabComponent {
}) })
} }
getHotkey (id: string) { getHotkeys (id: string): Hotkey[] {
let ptr = this.config.store.hotkeys let ptr = this.config.store.hotkeys
for (const token of id.split(/\./g)) { for (const token of id.split(/\./g)) {
ptr = ptr[token] ptr = ptr[token]
} }
return ptr return (ptr || []).map(hotkey => this.detectDuplicates(hotkey))
} }
setHotkey (id: string, value) { setHotkeys (id: string, hotkeys: Hotkey[]) {
let ptr = this.config.store let ptr = this.config.store
let prop = 'hotkeys' let prop = 'hotkeys'
for (const token of id.split(/\./g)) { for (const token of id.split(/\./g)) {
ptr = ptr[prop] ptr = ptr[prop]
prop = token prop = token
} }
ptr[prop] = value ptr[prop] = hotkeys.map(hotkey =>
hotkey.strokes.length === 1 && Array.isArray(hotkey.strokes)
? hotkey.strokes[0]
: hotkey.strokes,
)
this.config.save() this.config.save()
} }
hotkeyFilterFn (hotkey: HotkeyDescription, query: string): boolean { hotkeyFilterFn (hotkey: HotkeyDescription, query: string): boolean {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands const s = hotkey.name + hotkey.id + this.getHotkeys(hotkey.id).map(h => h.strokes).toString()
const s = hotkey.name + hotkey.id + (this.getHotkey(hotkey.id) || []).toString()
return s.toLowerCase().includes(query.toLowerCase()) return s.toLowerCase().includes(query.toLowerCase())
} }
private detectDuplicates (strokes: string[] | string): Hotkey {
const allHotkeys = Object
.values(this.config.store.hotkeys)
.filter((value: unknown) => Array.isArray(value))
.flat()
const isDuplicate = allHotkeys
.filter(hotkey => JSON.stringify(hotkey) === JSON.stringify(strokes))
.length > 1
return { strokes, isDuplicate }
}
} }

View file

@ -1,6 +1,8 @@
.item(*ngFor='let item of model') .item(*ngFor='let hotkey of hotkeys')
.body((click)='editItem(item)') .body((click)='editItem(hotkey)')
.stroke(*ngFor='let stroke of item') {{stroke}} .stroke(*ngFor='let stroke of hotkey.strokes')
.remove((click)='removeItem(item)') × span(*ngIf='!hotkey.isDuplicate', translate) {{stroke}}
span.duplicate(*ngIf='hotkey.isDuplicate', translate) {{stroke}}
.remove((click)='removeItem(hotkey)') ×
.add((click)='addItem()', translate) Add... .add((click)='addItem()', translate) Add...

View file

@ -1,6 +1,7 @@
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core' import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { HotkeyInputModalComponent } from './hotkeyInputModal.component' import { HotkeyInputModalComponent } from './hotkeyInputModal.component'
import { Hotkey } from 'tabby-core/src/api/hotkeyProvider'
/** @hidden */ /** @hidden */
@Component({ @Component({
@ -10,37 +11,41 @@ import { HotkeyInputModalComponent } from './hotkeyInputModal.component'
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
}) })
export class MultiHotkeyInputComponent { export class MultiHotkeyInputComponent {
@Input() model: string[][] = [] @Input() hotkeys: Hotkey[] = []
@Output() modelChange = new EventEmitter() @Output() hotkeysChange = new EventEmitter()
constructor ( constructor (
private ngbModal: NgbModal, private ngbModal: NgbModal,
) { } ) { }
ngOnChanges (): void { ngOnChanges (): void {
if (typeof this.model === 'string') { this.hotkeys = this.hotkeys.map(hotkey => typeof hotkey.strokes === 'string' ? { ...hotkey, strokes: [hotkey.strokes] } : hotkey)
this.model = [this.model]
}
this.model = this.model.map(item => typeof item === 'string' ? [item] : item)
} }
editItem (item: string[]): void { editItem (item: Hotkey): void {
this.ngbModal.open(HotkeyInputModalComponent).result.then((value: string[]) => { this.ngbModal.open(HotkeyInputModalComponent).result.then((newStrokes: string[]) => {
this.model[this.model.findIndex(x => x === item)] = value this.hotkeys.find(hotkey => this.isEqual(hotkey, item))!.strokes = newStrokes
this.model = this.model.slice() this.storeUpdatedHotkeys()
this.modelChange.emit(this.model)
}) })
} }
addItem (): void { addItem (): void {
this.ngbModal.open(HotkeyInputModalComponent).result.then((value: string[]) => { this.ngbModal.open(HotkeyInputModalComponent).result.then((value: string[]) => {
this.model = this.model.concat([value]) this.hotkeys.push({ strokes: value, isDuplicate: false })
this.modelChange.emit(this.model) this.storeUpdatedHotkeys()
}) })
} }
removeItem (item: string[]): void { removeItem (item: Hotkey): void {
this.model = this.model.filter(x => x !== item) this.hotkeys = this.hotkeys.filter(x => x !== item)
this.modelChange.emit(this.model) this.storeUpdatedHotkeys()
}
private storeUpdatedHotkeys () {
this.hotkeysChange.emit(this.hotkeys)
}
private isEqual (h: Hotkey, item: Hotkey) {
return JSON.stringify(h.strokes) === JSON.stringify(item.strokes)
} }
} }