diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index a14401d6..cb42b743 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -37,6 +37,117 @@ export class ProfilesService { @Inject(ProfileProvider) private profileProviders: ProfileProvider[], ) { } + /* + * Methods used to interract with ProfileProvider + */ + + getProviders (): ProfileProvider[] { + return [...this.profileProviders] + } + + providerForProfile (profile: PartialProfile): ProfileProvider|null { + const provider = this.profileProviders.find(x => x.id === profile.type) ?? null + return provider as unknown as ProfileProvider|null + } + + getDescription

(profile: PartialProfile

): string|null { + profile = this.getConfigProxyForProfile(profile) + return this.providerForProfile(profile)?.getDescription(profile) ?? null + } + + /* + * Methods used to interract with Profile + */ + + /* + * Return ConfigProxy for a given Profile + * arg: skipUserDefaults -> do not merge global provider defaults in ConfigProxy + */ + getConfigProxyForProfile (profile: PartialProfile, skipUserDefaults = false): T { + const defaults = this.getProfileDefaults(profile, skipUserDefaults).reduce(configMerge, {}) + return new ConfigProxy(profile, defaults) as unknown as T + } + + /** + * Return an Array of Profiles + * arg: includeBuiltin (default: true) -> include BuiltinProfiles + * arg: clone (default: false) -> return deepclone Array + */ + async getProfiles (includeBuiltin = true, clone = false): Promise[]> { + let list = this.config.store.profiles ?? [] + if (includeBuiltin) { + const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles())) + list = [ + ...this.config.store.profiles ?? [], + ...lists.reduce((a, b) => a.concat(b), []), + ] + } + + const sortKey = p => `${this.resolveProfileGroupName(p.group ?? '')} / ${p.name}` + list.sort((a, b) => sortKey(a).localeCompare(sortKey(b))) + list.sort((a, b) => (a.isBuiltin ? 1 : 0) - (b.isBuiltin ? 1 : 0)) + return clone ? deepClone(list) : list + } + + /** + * Write a Profile in config + * arg: saveConfig (default: true) -> invoke after the Profile was updated + */ + async writeProfile (profile: PartialProfile, saveConfig = true): Promise { + this.deleteProfile(profile, false) + + this.config.store.profiles.push(profile) + if (saveConfig) { + return this.config.save() + } + } + + /** + * Delete a Profile from config + * arg: saveConfig (default: true) -> invoke after the Profile was deleted + */ + async deleteProfile (profile: PartialProfile, saveConfig = true): Promise { + this.providerForProfile(profile)?.deleteProfile(this.getConfigProxyForProfile(profile)) + this.config.store.profiles = this.config.store.profiles.filter(p => p.id !== profile.id) + + const profileHotkeyName = ProfilesService.getProfileHotkeyName(profile) + if (this.config.store.hotkeys.profile.hasOwnProperty(profileHotkeyName)) { + const profileHotkeys = deepClone(this.config.store.hotkeys.profile) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete profileHotkeys[profileHotkeyName] + this.config.store.hotkeys.profile = profileHotkeys + } + + if (saveConfig) { + return this.config.save() + } + } + + /** + * Delete all Profiles from config using option filter + * arg: options { group: string } -> options used to filter which profile have to be deleted + * arg: saveConfig (default: true) -> invoke after the Profile was deleted + */ + async deleteBulkProfiles (options: { group: string }, saveConfig = true): Promise { + for (const profile of this.config.store.profiles.filter(p => p.group === options.group)) { + this.providerForProfile(profile)?.deleteProfile(this.getConfigProxyForProfile(profile)) + + const profileHotkeyName = ProfilesService.getProfileHotkeyName(profile) + if (this.config.store.hotkeys.profile.hasOwnProperty(profileHotkeyName)) { + const profileHotkeys = deepClone(this.config.store.hotkeys.profile) + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete profileHotkeys[profileHotkeyName] + this.config.store.hotkeys.profile = profileHotkeys + } + } + + this.config.store.profiles = this.config.store.profiles.filter(p => p.group !== options.group) + + if (saveConfig) { + return this.config.save() + } + } + async openNewTabForProfile

(profile: PartialProfile

): Promise { const params = await this.newTabParametersForProfile(profile) if (params) { @@ -64,32 +175,27 @@ export class ProfilesService { return params } - getProviders (): ProfileProvider[] { - return [...this.profileProviders] + async launchProfile (profile: PartialProfile): Promise { + await this.openNewTabForProfile(profile) + + let recentProfiles: PartialProfile[] = JSON.parse(window.localStorage['recentProfiles'] ?? '[]') + if (this.config.store.terminal.showRecentProfiles > 0) { + recentProfiles = recentProfiles.filter(x => x.group !== profile.group || x.name !== profile.name) + recentProfiles.unshift(profile) + recentProfiles = recentProfiles.slice(0, this.config.store.terminal.showRecentProfiles) + } else { + recentProfiles = [] + } + window.localStorage['recentProfiles'] = JSON.stringify(recentProfiles) } - async getProfiles (): Promise[]> { - const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles())) - let list = lists.reduce((a, b) => a.concat(b), []) - list = [ - ...this.config.store.profiles ?? [], - ...list, - ] - const sortKey = p => `${this.resolveProfileGroupName(p.group ?? '')} / ${p.name}` - list.sort((a, b) => sortKey(a).localeCompare(sortKey(b))) - list.sort((a, b) => (a.isBuiltin ? 1 : 0) - (b.isBuiltin ? 1 : 0)) - return list + static getProfileHotkeyName (profile: PartialProfile): string { + return (profile.id ?? profile.name).replace(/\./g, '-') } - providerForProfile (profile: PartialProfile): ProfileProvider|null { - const provider = this.profileProviders.find(x => x.id === profile.type) ?? null - return provider as unknown as ProfileProvider|null - } - - getDescription

(profile: PartialProfile

): string|null { - profile = this.getConfigProxyForProfile(profile) - return this.providerForProfile(profile)?.getDescription(profile) ?? null - } + /* + * Methods used to interract with Profile Selector + */ selectorOptionForProfile

(profile: PartialProfile

): SelectorOption { const fullProfile = this.getConfigProxyForProfile(profile) @@ -103,12 +209,6 @@ export class ProfilesService { } } - getRecentProfiles (): PartialProfile[] { - let recentProfiles: PartialProfile[] = JSON.parse(window.localStorage['recentProfiles'] ?? '[]') - recentProfiles = recentProfiles.slice(0, this.config.store.terminal.showRecentProfiles) - return recentProfiles - } - showProfileSelector (): Promise|null> { if (this.selector.active) { return Promise.resolve(null) @@ -198,6 +298,12 @@ export class ProfilesService { }) } + getRecentProfiles (): PartialProfile[] { + let recentProfiles: PartialProfile[] = JSON.parse(window.localStorage['recentProfiles'] ?? '[]') + recentProfiles = recentProfiles.slice(0, this.config.store.terminal.showRecentProfiles) + return recentProfiles + } + async quickConnect (query: string): Promise|null> { for (const provider of this.getProviders()) { if (provider.supportsQuickConnect) { @@ -211,25 +317,6 @@ export class ProfilesService { return null } - getConfigProxyForProfile (profile: PartialProfile, skipUserDefaults = false): T { - const defaults = this.getProfileDefaults(profile, skipUserDefaults).reduce(configMerge, {}) - return new ConfigProxy(profile, defaults) as unknown as T - } - - async launchProfile (profile: PartialProfile): Promise { - await this.openNewTabForProfile(profile) - - let recentProfiles: PartialProfile[] = JSON.parse(window.localStorage['recentProfiles'] ?? '[]') - if (this.config.store.terminal.showRecentProfiles > 0) { - recentProfiles = recentProfiles.filter(x => x.group !== profile.group || x.name !== profile.name) - recentProfiles.unshift(profile) - recentProfiles = recentProfiles.slice(0, this.config.store.terminal.showRecentProfiles) - } else { - recentProfiles = [] - } - window.localStorage['recentProfiles'] = JSON.stringify(recentProfiles) - } - /* * Methods used to interract with Profile/ProfileGroup/Global defaults */ @@ -254,6 +341,7 @@ export class ProfilesService { /** * Return defaults for a given profile * Always return something, empty object if no defaults found + * arg: skipUserDefaults -> do not merge global provider defaults in ConfigProxy */ getProfileDefaults (profile: PartialProfile, skipUserDefaults = false): any { const provider = this.providerForProfile(profile) @@ -276,7 +364,7 @@ export class ProfilesService { async getProfileGroups (includeProfiles = false, includeNonUserGroup = false): Promise[]> { let profiles: PartialProfile[] = [] if (includeProfiles) { - profiles = await this.getProfiles() + profiles = await this.getProfiles(includeNonUserGroup, true) } const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') @@ -327,7 +415,7 @@ export class ProfilesService { * arg: saveConfig (default: true) -> invoke after the ProfileGroup was updated */ async writeProfileGroup (group: PartialProfileGroup, saveConfig = true): Promise { - this.deleteProfileGroup(group, false) + this.deleteProfileGroup(group, false, false) delete group.profiles delete group.editable @@ -346,12 +434,13 @@ export class ProfilesService { async deleteProfileGroup (group: PartialProfileGroup, saveConfig = true, deleteProfiles = true): Promise { this.config.store.groups = this.config.store.groups.filter(g => g.id !== group.id) if (deleteProfiles) { - this.config.store.profiles = this.config.store.profiles.filter(x => x.group !== group.id) + await this.deleteBulkProfiles({ group: group.id }, false) } else { for (const profile of this.config.store.profiles.filter(x => x.group === group.id)) { delete profile.group } } + if (saveConfig) { return this.config.save() } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 4826a03d..47c60463 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -4,7 +4,7 @@ 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, ProfileGroup, PartialProfileGroup } from 'tabby-core' +import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider, TranslateService, Platform, ProfileGroup, PartialProfileGroup } from 'tabby-core' import { EditProfileModalComponent } from './editProfileModal.component' _('Filter') @@ -94,7 +94,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { return } Object.assign(profile, result) - await this.config.save() + await this.profilesService.writeProfile(profile) } async showProfileEditModal (profile: PartialProfile): Promise|null> { @@ -137,17 +137,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { cancelId: 1, }, )).response === 0) { - this.profilesService.providerForProfile(profile)?.deleteProfile( - this.profilesService.getConfigProxyForProfile(profile)) - this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile) - const profileHotkeyName = AppHotkeyProvider.getProfileHotkeyName(profile) - if (this.config.store.hotkeys.profile.hasOwnProperty(profileHotkeyName)) { - const profileHotkeys = deepClone(this.config.store.hotkeys.profile) - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete profileHotkeys[profileHotkeyName] - this.config.store.hotkeys.profile = profileHotkeys - } - await this.config.save() + this.profilesService.deleteProfile(profile) } }