mirror of
https://github.com/Eugeny/tabby.git
synced 2025-09-24 00:56:06 +00:00
better separate profiles by group - fixes #4517
This commit is contained in:
@@ -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
|
||||||
|
@@ -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}}
|
||||||
|
@@ -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;
|
||||||
|
@@ -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)
|
||||||
|
@@ -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')
|
||||||
|
Reference in New Issue
Block a user