better separate profiles by group - fixes #4517

This commit is contained in:
Eugene Pankov
2021-09-02 22:30:21 +02:00
parent f681f0e50a
commit 8d0bcb94b1
5 changed files with 40 additions and 22 deletions

View File

@@ -1,6 +1,7 @@
export interface SelectorOption<T> { export interface SelectorOption<T> {
name: string name: string
description?: string description?: string
group?: string
result?: T result?: T
icon?: string icon?: string
freeInputPattern?: string freeInputPattern?: string

View File

@@ -8,21 +8,24 @@
) )
.list-group.list-group-light(*ngIf='filteredOptions.length') .list-group.list-group-light(*ngIf='filteredOptions.length')
a.list-group-item.list-group-item-action.d-flex.align-items-center( ng-container(*ngFor='let option of filteredOptions; let i = index')
#item, label.group-header(
(click)='selectOption(option)', *ngIf='hasGroups && option.group !== filteredOptions[i - 1]?.group'
[class.active]='selectedIndex == i', ) {{option.group}}
*ngFor='let option of filteredOptions; let i = index' a.list-group-item.list-group-item-action.d-flex.align-items-center(
) #item,
i.icon( (click)='selectOption(option)',
class='fa-fw {{option.icon}}', [class.active]='selectedIndex == i'
style='color: {{option.color}}',
*ngIf='!iconIsSVG(option.icon)'
) )
.icon( i.icon(
[fastHtmlBind]='option.icon', class='fa-fw {{option.icon}}',
style='color: {{option.color}}', style='color: {{option.color}}',
*ngIf='iconIsSVG(option.icon)' *ngIf='!iconIsSVG(option.icon)'
) )
.title.mr-2 {{getOptionText(option)}} .icon(
.description.no-wrap.text-muted {{option.description}} [fastHtmlBind]='option.icon',
style='color: {{option.color}}',
*ngIf='iconIsSVG(option.icon)'
)
.title.mr-2 {{getOptionText(option)}}
.description.no-wrap.text-muted {{option.description}}

View File

@@ -9,6 +9,11 @@
border-top-right-radius: 0; border-top-right-radius: 0;
} }
.group-header {
padding: 0 1rem;
margin: 20px 0 10px;
}
.icon { .icon {
width: 1.25rem; width: 1.25rem;
margin-right: 0.25rem; margin-right: 0.25rem;

View File

@@ -14,6 +14,7 @@ export class SelectorModalComponent<T> {
@Input() filter = '' @Input() filter = ''
@Input() name: string @Input() name: string
@Input() selectedIndex = 0 @Input() selectedIndex = 0
hasGroups = false
@ViewChildren('item') itemChildren: QueryList<ElementRef> @ViewChildren('item') itemChildren: QueryList<ElementRef>
constructor ( constructor (
@@ -22,6 +23,7 @@ export class SelectorModalComponent<T> {
ngOnInit (): void { ngOnInit (): void {
this.onFilterChange() this.onFilterChange()
this.hasGroups = this.options.some(x => x.group)
} }
@HostListener('keyup', ['$event']) onKeyUp (event: KeyboardEvent): void { @HostListener('keyup', ['$event']) onKeyUp (event: KeyboardEvent): void {
@@ -48,16 +50,23 @@ export class SelectorModalComponent<T> {
onFilterChange (): void { onFilterChange (): void {
const f = this.filter.trim().toLowerCase() const f = this.filter.trim().toLowerCase()
if (!f) { 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 { } else {
const terms = f.split(' ') const terms = f.split(' ')
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands // 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.max(0, this.selectedIndex)
this.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex) this.selectedIndex = Math.min(this.filteredOptions.length - 1, this.selectedIndex)
} }
filterMatches (option: SelectorOption<T>, terms: string[]): boolean {
const content = (option.group ?? '') + option.name + (option.description ?? '')
return terms.every(term => content.toLowerCase().includes(term))
}
getOptionText (option: SelectorOption<T>): string { getOptionText (option: SelectorOption<T>): string {
if (option.freeInputPattern) { if (option.freeInputPattern) {
return option.freeInputPattern.replace('%s', this.filter) return option.freeInputPattern.replace('%s', this.filter)

View File

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