From 8d0bcb94b1649a6a4febd81923cf6112e1c743cb Mon Sep 17 00:00:00 2001 From: Eugene Pankov Date: Thu, 2 Sep 2021 22:30:21 +0200 Subject: [PATCH] better separate profiles by group - fixes #4517 --- tabby-core/src/api/selector.ts | 1 + .../components/selectorModal.component.pug | 37 ++++++++++--------- .../components/selectorModal.component.scss | 5 +++ .../src/components/selectorModal.component.ts | 13 ++++++- tabby-core/src/services/profiles.service.ts | 6 +-- 5 files changed, 40 insertions(+), 22 deletions(-) diff --git a/tabby-core/src/api/selector.ts b/tabby-core/src/api/selector.ts index c8437721..12c69020 100644 --- a/tabby-core/src/api/selector.ts +++ b/tabby-core/src/api/selector.ts @@ -1,6 +1,7 @@ export interface SelectorOption { name: string description?: string + group?: string result?: T icon?: string freeInputPattern?: string diff --git a/tabby-core/src/components/selectorModal.component.pug b/tabby-core/src/components/selectorModal.component.pug index cdb076d6..a45ce96a 100644 --- a/tabby-core/src/components/selectorModal.component.pug +++ b/tabby-core/src/components/selectorModal.component.pug @@ -8,21 +8,24 @@ ) .list-group.list-group-light(*ngIf='filteredOptions.length') - a.list-group-item.list-group-item-action.d-flex.align-items-center( - #item, - (click)='selectOption(option)', - [class.active]='selectedIndex == i', - *ngFor='let option of filteredOptions; let i = index' - ) - i.icon( - class='fa-fw {{option.icon}}', - style='color: {{option.color}}', - *ngIf='!iconIsSVG(option.icon)' + ng-container(*ngFor='let option of filteredOptions; let i = index') + label.group-header( + *ngIf='hasGroups && option.group !== filteredOptions[i - 1]?.group' + ) {{option.group}} + a.list-group-item.list-group-item-action.d-flex.align-items-center( + #item, + (click)='selectOption(option)', + [class.active]='selectedIndex == i' ) - .icon( - [fastHtmlBind]='option.icon', - style='color: {{option.color}}', - *ngIf='iconIsSVG(option.icon)' - ) - .title.mr-2 {{getOptionText(option)}} - .description.no-wrap.text-muted {{option.description}} + i.icon( + class='fa-fw {{option.icon}}', + style='color: {{option.color}}', + *ngIf='!iconIsSVG(option.icon)' + ) + .icon( + [fastHtmlBind]='option.icon', + style='color: {{option.color}}', + *ngIf='iconIsSVG(option.icon)' + ) + .title.mr-2 {{getOptionText(option)}} + .description.no-wrap.text-muted {{option.description}} diff --git a/tabby-core/src/components/selectorModal.component.scss b/tabby-core/src/components/selectorModal.component.scss index e4edc45b..f63a475e 100644 --- a/tabby-core/src/components/selectorModal.component.scss +++ b/tabby-core/src/components/selectorModal.component.scss @@ -9,6 +9,11 @@ border-top-right-radius: 0; } +.group-header { + padding: 0 1rem; + margin: 20px 0 10px; +} + .icon { width: 1.25rem; margin-right: 0.25rem; diff --git a/tabby-core/src/components/selectorModal.component.ts b/tabby-core/src/components/selectorModal.component.ts index 8dd256ed..4012e6b1 100644 --- a/tabby-core/src/components/selectorModal.component.ts +++ b/tabby-core/src/components/selectorModal.component.ts @@ -14,6 +14,7 @@ export class SelectorModalComponent { @Input() filter = '' @Input() name: string @Input() selectedIndex = 0 + hasGroups = false @ViewChildren('item') itemChildren: QueryList constructor ( @@ -22,6 +23,7 @@ export class SelectorModalComponent { ngOnInit (): void { this.onFilterChange() + this.hasGroups = this.options.some(x => x.group) } @HostListener('keyup', ['$event']) onKeyUp (event: KeyboardEvent): void { @@ -48,16 +50,23 @@ export class SelectorModalComponent { onFilterChange (): void { const f = this.filter.trim().toLowerCase() if (!f) { - this.filteredOptions = this.options.filter(x => !x.freeInputPattern) + this.filteredOptions = this.options.slice() + .sort((a, b) => a.group?.localeCompare(b.group ?? '') ?? 0) + .filter(x => !x.freeInputPattern) } else { const terms = f.split(' ') // eslint-disable-next-line @typescript-eslint/restrict-plus-operands - this.filteredOptions = this.options.filter(x => x.freeInputPattern ?? terms.every(term => (x.name + (x.description ?? '')).toLowerCase().includes(term))) + this.filteredOptions = this.options.filter(x => x.freeInputPattern ?? this.filterMatches(x, terms)) } this.selectedIndex = Math.max(0, this.selectedIndex) this.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex) } + filterMatches (option: SelectorOption, terms: string[]): boolean { + const content = (option.group ?? '') + option.name + (option.description ?? '') + return terms.every(term => content.toLowerCase().includes(term)) + } + getOptionText (option: SelectorOption): string { if (option.freeInputPattern) { return option.freeInputPattern.replace('%s', this.filter) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 4d719e8f..1626679a 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -84,9 +84,7 @@ export class ProfilesService { selectorOptionForProfile

(profile: PartialProfile

): SelectorOption { const fullProfile = this.getConfigProxyForProfile(profile) return { - name: profile.group ? `${fullProfile.group} / ${fullProfile.name}` : fullProfile.name, - icon: profile.icon, - color: profile.color, + ...profile, description: this.providerForProfile(fullProfile)?.getDescription(fullProfile), } } @@ -99,6 +97,7 @@ export class ProfilesService { let options: SelectorOption[] = recentProfiles.map(p => ({ ...this.selectorOptionForProfile(p), + group: 'Recent', icon: 'fas fa-history', color: p.color, callback: async () => { @@ -111,6 +110,7 @@ export class ProfilesService { if (recentProfiles.length) { options.push({ name: 'Clear recent connections', + group: 'Recent', icon: 'fas fa-eraser', callback: async () => { window.localStorage.removeItem('recentProfiles')