diff --git a/tabby-settings/src/components/editProfileModal.component.pug b/tabby-settings/src/components/editProfileModal.component.pug index 7869099b..0619345d 100644 --- a/tabby-settings/src/components/editProfileModal.component.pug +++ b/tabby-settings/src/components/editProfileModal.component.pug @@ -24,8 +24,10 @@ type='text', alwaysVisibleTypeahead, placeholder='Ungrouped', - [(ngModel)]='profile.group', + [(ngModel)]='profileGroup', [ngbTypeahead]='groupTypeahead', + [inputFormatter]="groupFormatter", + [resultFormatter]="groupFormatter", ) .mb-3(*ngIf='!defaultsMode') diff --git a/tabby-settings/src/components/editProfileModal.component.ts b/tabby-settings/src/components/editProfileModal.component.ts index 90636672..a57ee865 100644 --- a/tabby-settings/src/components/editProfileModal.component.ts +++ b/tabby-settings/src/components/editProfileModal.component.ts @@ -2,7 +2,8 @@ import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } from 'rxjs' import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' -import { ConfigProxy, ConfigService, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService, TAB_COLORS } from 'tabby-core' +import { ConfigProxy, ConfigService, PartialProfileGroup, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService, TAB_COLORS, ProfileGroup } from 'tabby-core' +import { v4 as uuidv4 } from 'uuid' const iconsData = require('../../../tabby-core/src/icons.json') const iconsClassList = Object.keys(iconsData).map( @@ -20,7 +21,8 @@ export class EditProfileModalComponent

{ @Input() profileProvider: ProfileProvider

@Input() settingsComponent: new () => ProfileSettingsComponent

@Input() defaultsMode = false - groupNames: string[] + @Input() profileGroup: PartialProfileGroup | string | undefined + groups: PartialProfileGroup[] @ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef private _profile: Profile @@ -33,11 +35,12 @@ export class EditProfileModalComponent

{ config: ConfigService, private modalInstance: NgbActiveModal, ) { - this.groupNames = [...new Set( - (config.store.profiles as Profile[]) - .map(x => x.group) - .filter(x => !!x), - )].sort() as string[] + if (!this.defaultsMode) { + this.profilesService.getProfileGroups().then(groups => { + this.groups = groups + this.profileGroup = groups.find(g => g.id === this.profile.group) + }) + } } colorsAutocomplete = text$ => text$.pipe( @@ -72,13 +75,15 @@ export class EditProfileModalComponent

{ } } - groupTypeahead = (text$: Observable) => + groupTypeahead: OperatorFunction[]> = (text$: Observable) => text$.pipe( debounceTime(200), distinctUntilChanged(), - map(q => this.groupNames.filter(x => !q || x.toLowerCase().includes(q.toLowerCase()))), + map(q => this.groups.filter(g => !q || g.name.toLowerCase().includes(q.toLowerCase()))), ) + groupFormatter = (g: PartialProfileGroup) => g.name + iconSearch: OperatorFunction = (text$: Observable) => text$.pipe( debounceTime(200), @@ -86,7 +91,20 @@ export class EditProfileModalComponent

{ ) save () { - this.profile.group ||= undefined + if (!this.profileGroup) { + this.profile.group = undefined + } else { + if (typeof this.profileGroup === 'string') { + const newGroup: PartialProfileGroup = { + id: uuidv4(), + name: this.profileGroup + } + this.groups.push(newGroup) + this.profileGroup = newGroup + } + this.profile.group = this.profileGroup.id + } + this.settingsComponentInstance?.save?.() this.profile.__cleanup() this.modalInstance.close(this._profile) diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 6508e912..2ede9f2d 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -4,16 +4,9 @@ import slugify from 'slugify' import deepClone from 'clone-deep' import { Component, Inject } from '@angular/core' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider, TranslateService, Platform, AppHotkeyProvider } from 'tabby-core' +import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider, TranslateService, Platform, AppHotkeyProvider, ProfileGroup, PartialProfileGroup } from 'tabby-core' import { EditProfileModalComponent } from './editProfileModal.component' -interface ProfileGroup { - name?: string - profiles: PartialProfile[] - editable: boolean - collapsed: boolean -} - _('Filter') _('Ungrouped') @@ -26,7 +19,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { profiles: PartialProfile[] = [] builtinProfiles: PartialProfile[] = [] templateProfiles: PartialProfile[] = [] - profileGroups: ProfileGroup[] + profileGroups: PartialProfileGroup[] filter = '' Platform = Platform @@ -158,55 +151,27 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } } - refresh (): void { + async refresh (): Promise { this.profiles = this.config.store.profiles - this.profileGroups = [] - const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') - - for (const profile of this.profiles) { - // Group null, undefined and empty together - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - let group = this.profileGroups.find(x => x.name === (profile.group || '')) - if (!group) { - group = { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - name: profile.group || '', - profiles: [], - editable: true, - collapsed: profileGroupCollapsed[profile.group ?? ''] ?? false, - } - this.profileGroups.push(group) - } - group.profiles.push(profile) - } - - this.profileGroups.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? -1) - - const builtIn = { - name: this.translate.instant('Built-in'), - profiles: this.builtinProfiles, - editable: false, - collapsed: false, - } - builtIn.collapsed = profileGroupCollapsed[builtIn.name ?? ''] ?? false - this.profileGroups.push(builtIn) + const groups = await this.profilesService.getProfileGroups(true, true) + groups.sort((a, b) => a.name.localeCompare(b.name)) + groups.sort((a, b) => (a.id === 'built-in' ? 1 : 0) - (b.id === 'built-in' ? 1 : 0)) + groups.sort((a, b) => (a.id === 'ungrouped' ? 0 : 1) - (b.id === 'ungrouped' ? 0 : 1)) + this.profileGroups = groups } - async editGroup (group: ProfileGroup): Promise { + async editGroup (group: PartialProfileGroup): Promise { const modal = this.ngbModal.open(PromptModalComponent) modal.componentInstance.prompt = this.translate.instant('New name') modal.componentInstance.value = group.name const result = await modal.result if (result) { - for (const profile of this.profiles.filter(x => x.group === group.name)) { - profile.group = result.value - } - this.config.store.profiles = this.profiles + group.name = result.value await this.config.save() } } - async deleteGroup (group: ProfileGroup): Promise { + async deleteGroup (group: PartialProfileGroup): Promise { if ((await this.platform.showMessageBox( { type: 'warning', @@ -231,18 +196,18 @@ export class ProfilesSettingsTabComponent extends BaseComponent { cancelId: 0, }, )).response === 0) { - for (const profile of this.profiles.filter(x => x.group === group.name)) { + for (const profile of this.profiles.filter(x => x.group === group.id)) { delete profile.group } } else { - this.config.store.profiles = this.config.store.profiles.filter(x => x.group !== group.name) + this.config.store.profiles = this.config.store.profiles.filter(x => x.group !== group.id) } await this.config.save() } } - isGroupVisible (group: ProfileGroup): boolean { - return !this.filter || group.profiles.some(x => this.isProfileVisible(x)) + isGroupVisible (group: PartialProfileGroup): boolean { + return !this.filter || (group.profiles ?? []).some(x => this.isProfileVisible(x)) } isProfileVisible (profile: PartialProfile): boolean { @@ -270,11 +235,9 @@ export class ProfilesSettingsTabComponent extends BaseComponent { }[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning' } - toggleGroupCollapse (group: ProfileGroup): void { + toggleGroupCollapse (group: PartialProfileGroup): void { group.collapsed = !group.collapsed - const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') - profileGroupCollapsed[group.name ?? ''] = group.collapsed - window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) + this.profilesService.saveProfileGroupCollapse(group) } async editDefaults (provider: ProfileProvider): Promise {