From 4dedbbc25a8f09d69630bbe9de81be5824fa0fbf Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Thu, 20 Jul 2023 20:07:55 +0200 Subject: [PATCH 01/38] feat[core): Eugeny/tabby#7057 order recent profile --- tabby-core/src/services/profiles.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 943d11c2..cc0fc1f9 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -118,12 +118,12 @@ export class ProfilesService { try { const recentProfiles = this.getRecentProfiles() - let options: SelectorOption[] = recentProfiles.map(p => ({ + let options: SelectorOption[] = recentProfiles.map((p, i) => ({ ...this.selectorOptionForProfile(p), group: this.translate.instant('Recent'), icon: 'fas fa-history', color: p.color, - weight: -2, + weight: i - (recentProfiles.length + 1), callback: async () => { if (p.id) { p = (await this.getProfiles()).find(x => x.id === p.id) ?? p From d57757c66cad3427eb8e11c88f1731fe5326e8a7 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Thu, 20 Jul 2023 20:15:36 +0200 Subject: [PATCH 02/38] fix(core): Eugeny/tabby#8709 sort freeInputPattern by weight --- tabby-core/src/components/selectorModal.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabby-core/src/components/selectorModal.component.ts b/tabby-core/src/components/selectorModal.component.ts index 76092314..448a6a7b 100644 --- a/tabby-core/src/components/selectorModal.component.ts +++ b/tabby-core/src/components/selectorModal.component.ts @@ -76,7 +76,7 @@ export class SelectorModalComponent { { sort: true }, ).search(f) - this.options.filter(x => x.freeInputPattern).forEach(freeOption => { + this.options.filter(x => x.freeInputPattern).sort(firstBy, number>(x => x.weight ?? 0)).forEach(freeOption => { if (!this.filteredOptions.includes(freeOption)) { this.filteredOptions.push(freeOption) } From 272b9ee5dcbdd9c94f51b06509667b44af074e50 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 16:12:34 +0200 Subject: [PATCH 03/38] wip ref(core): update profileDefaults in view of Eugeny/tabby#3999 --- tabby-core/src/api/profileProvider.ts | 5 ++ tabby-core/src/services/config.service.ts | 11 +++ tabby-core/src/services/profiles.service.ts | 77 +++++++++++++++++-- .../profilesSettingsTab.component.pug | 2 +- .../profilesSettingsTab.component.ts | 17 +++- 5 files changed, 101 insertions(+), 11 deletions(-) diff --git a/tabby-core/src/api/profileProvider.ts b/tabby-core/src/api/profileProvider.ts index a6e6bdd6..7010d25a 100644 --- a/tabby-core/src/api/profileProvider.ts +++ b/tabby-core/src/api/profileProvider.ts @@ -21,6 +21,11 @@ export interface Profile { isTemplate: boolean } +export interface ProfileDefaults { + id: string + //[provider]: +} + export type PartialProfile = Omit, 'type'>, 'name'> & { diff --git a/tabby-core/src/services/config.service.ts b/tabby-core/src/services/config.service.ts index daa0d746..2b9cf0db 100644 --- a/tabby-core/src/services/config.service.ts +++ b/tabby-core/src/services/config.service.ts @@ -10,6 +10,7 @@ import { PlatformService } from '../api/platform' import { HostAppService } from '../api/hostApp' import { Vault, VaultService } from './vault.service' import { serializeFunction } from '../utils' +import { ProfileDefaults } from '../api/profileProvider' const deepmerge = require('deepmerge') // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -364,6 +365,16 @@ export class ConfigService { } config.version = 4 } + if (config.version < 5) { + const oldDefaults = config.profileDefaults + const globalDefaults: ProfileDefaults = { + id: 'global', + ...oldDefaults + } + config.profileDefaults = [] + config.profileDefaults.push(globalDefaults) + config.version = 5 + } } private async maybeDecryptConfig (store) { diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index cc0fc1f9..1afd50c7 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@angular/core' import { TranslateService } from '@ngx-translate/core' import { NewTabParameters } from './tabs.service' import { BaseTabComponent } from '../components/baseTab.component' -import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider' +import { PartialProfile, Profile, ProfileDefaults, ProfileProvider } from '../api/profileProvider' import { SelectorOption } from '../api/selector' import { AppService } from './app.service' import { configMerge, ConfigProxy, ConfigService } from './config.service' @@ -212,12 +212,7 @@ export class ProfilesService { } getConfigProxyForProfile (profile: PartialProfile, skipUserDefaults = false): T { - const provider = this.providerForProfile(profile) - const defaults = [ - this.profileDefaults, - provider?.configDefaults ?? {}, - !provider || skipUserDefaults ? {} : this.config.store.profileDefaults[provider.id] ?? {}, - ].reduce(configMerge, {}) + const defaults = this.getProfileDefaults(profile).reduce(configMerge, {}) return new ConfigProxy(profile, defaults) as unknown as T } @@ -234,4 +229,72 @@ export class ProfilesService { } window.localStorage['recentProfiles'] = JSON.stringify(recentProfiles) } + + /* + * Methods used to interract with Profile/ProfileGroup/Global defaults + */ + + /** + * Return ProfileDefaults Array from config + */ + private getAllDefaults (): ProfileDefaults[] { + return this.config.store.profileDefaults + } + + /** + * Return ProfileDefaults for a given defaultsId (ex. 'global', 'profileId' or 'groupId') + */ + private getDefaults (defaultsId: string): ProfileDefaults { + return this.getAllDefaults().find(x => x.id == defaultsId) ?? { id: defaultsId } + } + + /** + * Replace or insert ProfileDefaults in config + */ + private setDefaults (defaults: ProfileDefaults) { + let allDefaults = this.getAllDefaults().filter(x => x.id !== defaults.id) + allDefaults.push(defaults) + + this.config.store.profileDefaults = allDefaults + } + + /** + * Replace or insert ProfileDefaults in config + */ + /*private deleteDefaults (defaults: ProfileDefaults) { + if (defaults.id === 'global') { + throw new Error('Unable to delete \'global\' profile Defaults') + } + this.config.store.profileDefaults = this.getAllDefaults().filter(x => x.id !== defaults.id) + }*/ + + /** + * Return global defaults for a given profile provider + */ + getProviderDefaults (defaultsId: string, provider: ProfileProvider): any { + const defaults = this.getDefaults(defaultsId) + return defaults[provider.id] ?? {} + } + + /** + * Set global defaults for a given profile provider + */ + setProviderDefaults (defaultsId: string, provider: ProfileProvider, pdefaults: any) { + const defaults = this.getDefaults(defaultsId) + defaults[provider.id] = pdefaults + this.setDefaults(defaults) + } + + /** + * Return defaults for a given profile + */ + getProfileDefaults (profile: PartialProfile, skipUserDefaults = false): any { + const provider = this.providerForProfile(profile) + return [ + this.profileDefaults, + provider?.configDefaults ?? {}, + !provider || skipUserDefaults ? {} : this.getProviderDefaults('global', provider), + ] + } + } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.pug b/tabby-settings/src/components/profilesSettingsTab.component.pug index 9ce3f981..df8f902d 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.pug +++ b/tabby-settings/src/components/profilesSettingsTab.component.pug @@ -170,7 +170,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') .list-group.mt-3.mb-3.content-box a.list-group-item.list-group-item-action( - (click)='editDefaults(provider)', + (click)='editGlobalDefaults(provider)', *ngFor='let provider of profileProviders' ) {{provider.name|translate}} diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 38898ae6..be7cd4b4 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -277,12 +277,16 @@ export class ProfilesSettingsTabComponent extends BaseComponent { window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) } - async editDefaults (provider: ProfileProvider): Promise { + /** + * Edit Defaults for a given profile provider + * defaultsId: used to identify defaults stored in config (ex. value: group ID) + */ + async editDefaults (defaultsId: string, provider: ProfileProvider): Promise { const modal = this.ngbModal.open( EditProfileModalComponent, { size: 'lg' }, ) - const model = this.config.store.profileDefaults[provider.id] ?? {} + const model = this.profilesService.getProviderDefaults(defaultsId, provider) model.type = provider.id modal.componentInstance.profile = Object.assign({}, model) modal.componentInstance.profileProvider = provider @@ -295,10 +299,17 @@ export class ProfilesSettingsTabComponent extends BaseComponent { delete model[k] } Object.assign(model, result) - this.config.store.profileDefaults[provider.id] = model + this.profilesService.setProviderDefaults(defaultsId, provider, model) await this.config.save() } + /** + * Edit global Defaults for a given profile provider + */ + async editGlobalDefaults (provider: ProfileProvider): Promise { + return this.editDefaults('global', provider) + } + blacklistProfile (profile: PartialProfile): void { this.config.store.profileBlacklist = [...this.config.store.profileBlacklist, profile.id] this.config.save() From ee4487a51763ad8ed61a90e5cfd1b8a65cb34a4d Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 19:43:52 +0200 Subject: [PATCH 04/38] Revert "wip ref(core): update profileDefaults in view of Eugeny/tabby#3999" This reverts commit 272b9ee5dcbdd9c94f51b06509667b44af074e50. --- tabby-core/src/api/profileProvider.ts | 5 -- tabby-core/src/services/config.service.ts | 11 --- tabby-core/src/services/profiles.service.ts | 77 ++----------------- .../profilesSettingsTab.component.pug | 2 +- .../profilesSettingsTab.component.ts | 17 +--- 5 files changed, 11 insertions(+), 101 deletions(-) diff --git a/tabby-core/src/api/profileProvider.ts b/tabby-core/src/api/profileProvider.ts index 7010d25a..a6e6bdd6 100644 --- a/tabby-core/src/api/profileProvider.ts +++ b/tabby-core/src/api/profileProvider.ts @@ -21,11 +21,6 @@ export interface Profile { isTemplate: boolean } -export interface ProfileDefaults { - id: string - //[provider]: -} - export type PartialProfile = Omit, 'type'>, 'name'> & { diff --git a/tabby-core/src/services/config.service.ts b/tabby-core/src/services/config.service.ts index 2b9cf0db..daa0d746 100644 --- a/tabby-core/src/services/config.service.ts +++ b/tabby-core/src/services/config.service.ts @@ -10,7 +10,6 @@ import { PlatformService } from '../api/platform' import { HostAppService } from '../api/hostApp' import { Vault, VaultService } from './vault.service' import { serializeFunction } from '../utils' -import { ProfileDefaults } from '../api/profileProvider' const deepmerge = require('deepmerge') // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -365,16 +364,6 @@ export class ConfigService { } config.version = 4 } - if (config.version < 5) { - const oldDefaults = config.profileDefaults - const globalDefaults: ProfileDefaults = { - id: 'global', - ...oldDefaults - } - config.profileDefaults = [] - config.profileDefaults.push(globalDefaults) - config.version = 5 - } } private async maybeDecryptConfig (store) { diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 1afd50c7..cc0fc1f9 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@angular/core' import { TranslateService } from '@ngx-translate/core' import { NewTabParameters } from './tabs.service' import { BaseTabComponent } from '../components/baseTab.component' -import { PartialProfile, Profile, ProfileDefaults, ProfileProvider } from '../api/profileProvider' +import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider' import { SelectorOption } from '../api/selector' import { AppService } from './app.service' import { configMerge, ConfigProxy, ConfigService } from './config.service' @@ -212,7 +212,12 @@ export class ProfilesService { } getConfigProxyForProfile (profile: PartialProfile, skipUserDefaults = false): T { - const defaults = this.getProfileDefaults(profile).reduce(configMerge, {}) + const provider = this.providerForProfile(profile) + const defaults = [ + this.profileDefaults, + provider?.configDefaults ?? {}, + !provider || skipUserDefaults ? {} : this.config.store.profileDefaults[provider.id] ?? {}, + ].reduce(configMerge, {}) return new ConfigProxy(profile, defaults) as unknown as T } @@ -229,72 +234,4 @@ export class ProfilesService { } window.localStorage['recentProfiles'] = JSON.stringify(recentProfiles) } - - /* - * Methods used to interract with Profile/ProfileGroup/Global defaults - */ - - /** - * Return ProfileDefaults Array from config - */ - private getAllDefaults (): ProfileDefaults[] { - return this.config.store.profileDefaults - } - - /** - * Return ProfileDefaults for a given defaultsId (ex. 'global', 'profileId' or 'groupId') - */ - private getDefaults (defaultsId: string): ProfileDefaults { - return this.getAllDefaults().find(x => x.id == defaultsId) ?? { id: defaultsId } - } - - /** - * Replace or insert ProfileDefaults in config - */ - private setDefaults (defaults: ProfileDefaults) { - let allDefaults = this.getAllDefaults().filter(x => x.id !== defaults.id) - allDefaults.push(defaults) - - this.config.store.profileDefaults = allDefaults - } - - /** - * Replace or insert ProfileDefaults in config - */ - /*private deleteDefaults (defaults: ProfileDefaults) { - if (defaults.id === 'global') { - throw new Error('Unable to delete \'global\' profile Defaults') - } - this.config.store.profileDefaults = this.getAllDefaults().filter(x => x.id !== defaults.id) - }*/ - - /** - * Return global defaults for a given profile provider - */ - getProviderDefaults (defaultsId: string, provider: ProfileProvider): any { - const defaults = this.getDefaults(defaultsId) - return defaults[provider.id] ?? {} - } - - /** - * Set global defaults for a given profile provider - */ - setProviderDefaults (defaultsId: string, provider: ProfileProvider, pdefaults: any) { - const defaults = this.getDefaults(defaultsId) - defaults[provider.id] = pdefaults - this.setDefaults(defaults) - } - - /** - * Return defaults for a given profile - */ - getProfileDefaults (profile: PartialProfile, skipUserDefaults = false): any { - const provider = this.providerForProfile(profile) - return [ - this.profileDefaults, - provider?.configDefaults ?? {}, - !provider || skipUserDefaults ? {} : this.getProviderDefaults('global', provider), - ] - } - } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.pug b/tabby-settings/src/components/profilesSettingsTab.component.pug index df8f902d..9ce3f981 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.pug +++ b/tabby-settings/src/components/profilesSettingsTab.component.pug @@ -170,7 +170,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') .list-group.mt-3.mb-3.content-box a.list-group-item.list-group-item-action( - (click)='editGlobalDefaults(provider)', + (click)='editDefaults(provider)', *ngFor='let provider of profileProviders' ) {{provider.name|translate}} diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index be7cd4b4..38898ae6 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -277,16 +277,12 @@ export class ProfilesSettingsTabComponent extends BaseComponent { window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) } - /** - * Edit Defaults for a given profile provider - * defaultsId: used to identify defaults stored in config (ex. value: group ID) - */ - async editDefaults (defaultsId: string, provider: ProfileProvider): Promise { + async editDefaults (provider: ProfileProvider): Promise { const modal = this.ngbModal.open( EditProfileModalComponent, { size: 'lg' }, ) - const model = this.profilesService.getProviderDefaults(defaultsId, provider) + const model = this.config.store.profileDefaults[provider.id] ?? {} model.type = provider.id modal.componentInstance.profile = Object.assign({}, model) modal.componentInstance.profileProvider = provider @@ -299,17 +295,10 @@ export class ProfilesSettingsTabComponent extends BaseComponent { delete model[k] } Object.assign(model, result) - this.profilesService.setProviderDefaults(defaultsId, provider, model) + this.config.store.profileDefaults[provider.id] = model await this.config.save() } - /** - * Edit global Defaults for a given profile provider - */ - async editGlobalDefaults (provider: ProfileProvider): Promise { - return this.editDefaults('global', provider) - } - blacklistProfile (profile: PartialProfile): void { this.config.store.profileBlacklist = [...this.config.store.profileBlacklist, profile.id] this.config.save() From 8a85fcac21f5ea3dc2bd4b1ae04f88865f61ca31 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 20:16:27 +0200 Subject: [PATCH 05/38] wip ref(core/profiles.service): add methods to interract with Provider defaults --- tabby-core/src/services/profiles.service.ts | 41 ++++++++++++++++--- .../profilesSettingsTab.component.ts | 4 +- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index cc0fc1f9..c4bafb2e 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -212,12 +212,7 @@ export class ProfilesService { } getConfigProxyForProfile (profile: PartialProfile, skipUserDefaults = false): T { - const provider = this.providerForProfile(profile) - const defaults = [ - this.profileDefaults, - provider?.configDefaults ?? {}, - !provider || skipUserDefaults ? {} : this.config.store.profileDefaults[provider.id] ?? {}, - ].reduce(configMerge, {}) + const defaults = this.getProfileDefaults(profile).reduce(configMerge, {}) return new ConfigProxy(profile, defaults) as unknown as T } @@ -234,4 +229,38 @@ export class ProfilesService { } window.localStorage['recentProfiles'] = JSON.stringify(recentProfiles) } + + /* + * Methods used to interract with Profile/ProfileGroup/Global defaults + */ + + /** + * Return global defaults for a given profile provider + * Always return something, empty object if no defaults found + */ + getProviderDefaults (provider: ProfileProvider): any { + const defaults = this.config.store.profileDefaults + return defaults[provider.id] ?? {} + } + + /** + * Set global defaults for a given profile provider + */ + setProviderDefaults (provider: ProfileProvider, pdefaults: any) { + this.config.store.profileDefaults[provider.id] = pdefaults + } + + /** + * Return defaults for a given profile + * Always return something, empty object if no defaults found + */ + getProfileDefaults (profile: PartialProfile, skipUserDefaults = false): any { + const provider = this.providerForProfile(profile) + return [ + this.profileDefaults, + provider?.configDefaults ?? {}, + !provider || skipUserDefaults ? {} : this.getProviderDefaults(provider), + ] + } + } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 38898ae6..6508e912 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -282,7 +282,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { EditProfileModalComponent, { size: 'lg' }, ) - const model = this.config.store.profileDefaults[provider.id] ?? {} + const model = this.profilesService.getProviderDefaults(provider) model.type = provider.id modal.componentInstance.profile = Object.assign({}, model) modal.componentInstance.profileProvider = provider @@ -295,7 +295,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { delete model[k] } Object.assign(model, result) - this.config.store.profileDefaults[provider.id] = model + this.profilesService.setProviderDefaults(provider, model) await this.config.save() } From c1e03ed5321f2a0add06a7051008e6deffdb038c Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 21:36:40 +0200 Subject: [PATCH 06/38] wip ref(core/profiles.service): move out group managment from settings --- tabby-core/src/api/index.ts | 2 +- tabby-core/src/api/profileProvider.ts | 16 ++++++ tabby-core/src/services/config.service.ts | 32 +++++++++++ tabby-core/src/services/profiles.service.ts | 62 ++++++++++++++++++++- 4 files changed, 110 insertions(+), 2 deletions(-) diff --git a/tabby-core/src/api/index.ts b/tabby-core/src/api/index.ts index 0384396d..00e915d5 100644 --- a/tabby-core/src/api/index.ts +++ b/tabby-core/src/api/index.ts @@ -16,7 +16,7 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess' export { HostWindowService } from './hostWindow' export { HostAppService, Platform } from './hostApp' export { FileProvider } from './fileProvider' -export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent } from './profileProvider' +export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent, ProfileGroup, PartialProfileGroup} from './profileProvider' export { PromptModalComponent } from '../components/promptModal.component' export * from './commands' diff --git a/tabby-core/src/api/profileProvider.ts b/tabby-core/src/api/profileProvider.ts index a6e6bdd6..9b53bbc2 100644 --- a/tabby-core/src/api/profileProvider.ts +++ b/tabby-core/src/api/profileProvider.ts @@ -31,6 +31,22 @@ export type PartialProfile = Omit[] + defaults: any + editable: boolean + collapsed: boolean +} + +export type PartialProfileGroup = Omit, 'name'> & { + id: string + name: string +} + export interface ProfileSettingsComponent

{ profile: P save?: () => void diff --git a/tabby-core/src/services/config.service.ts b/tabby-core/src/services/config.service.ts index daa0d746..206b3b46 100644 --- a/tabby-core/src/services/config.service.ts +++ b/tabby-core/src/services/config.service.ts @@ -10,6 +10,7 @@ import { PlatformService } from '../api/platform' import { HostAppService } from '../api/hostApp' import { Vault, VaultService } from './vault.service' import { serializeFunction } from '../utils' +import { PartialProfileGroup, ProfileGroup } from '../api/profileProvider' const deepmerge = require('deepmerge') // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types @@ -364,6 +365,37 @@ export class ConfigService { } config.version = 4 } + if (config.version < 5) { + const groups: PartialProfileGroup[] = [] + for (const p of config.profiles ?? []) { + if (!`${p.group}`.trim()) { + continue + } + + let group = groups.find(x => x.name === (p.group)) + if (!group) { + group = { + id: `${uuidv4()}`, + name: `${p.group}`, + } + groups.push(group) + } + p.group = group.id + } + + const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') + for (const g of groups) { + if (profileGroupCollapsed[g.name]) { + const collapsed = profileGroupCollapsed[g.name] + delete profileGroupCollapsed[g.name] + profileGroupCollapsed[g.id] = collapsed + } + } + window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) + + config.groups = groups + config.version = 5 + } } private async maybeDecryptConfig (store) { diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index c4bafb2e..a5cff178 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@angular/core' import { TranslateService } from '@ngx-translate/core' import { NewTabParameters } from './tabs.service' import { BaseTabComponent } from '../components/baseTab.component' -import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider' +import { PartialProfile, PartialProfileGroup, Profile, ProfileGroup, ProfileProvider } from '../api/profileProvider' import { SelectorOption } from '../api/selector' import { AppService } from './app.service' import { configMerge, ConfigProxy, ConfigService } from './config.service' @@ -80,6 +80,8 @@ export class ProfilesService { return list } + + providerForProfile (profile: PartialProfile): ProfileProvider|null { const provider = this.profileProviders.find(x => x.id === profile.type) ?? null return provider as unknown as ProfileProvider|null @@ -263,4 +265,62 @@ export class ProfilesService { ] } + /* + * Methods used to interract with ProfileGroup + */ + + /** + * Return an Array of the existing ProfileGroups + * arg: includeProfiles (default: false) -> if false, does not fill up the profiles field of ProfileGroup + * arg: includeNonUserGroup (default: false) -> if false, does not add built-in and ungrouped groups + */ + async getProfileGroups (includeProfiles = false, includeNonUserGroup = false): Promise[]> { + let profiles: PartialProfile[] = [] + if (includeProfiles) { + profiles = await this.getProfiles() + } + + const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') + let groups: PartialProfileGroup[] = this.config.store.groups ?? [] + groups = groups.map(x => { + x.editable = true + x.collapsed = profileGroupCollapsed[x.id ?? ''] ?? false + + if (includeProfiles) { + x.profiles = profiles.filter(p => p.group === x.id) + profiles = profiles.filter(p => p.group !== x.id) + } + + return x + }) + + if (includeNonUserGroup) { + const builtIn: PartialProfileGroup = { + id: 'built-in', + name: this.translate.instant('Built-in'), + editable: false, + } + builtIn.collapsed = profileGroupCollapsed[builtIn.id] ?? false + + const ungrouped: PartialProfileGroup = { + id: 'ungrouped', + name: this.translate.instant('Ungrouped'), + editable: false, + } + ungrouped.collapsed = profileGroupCollapsed[ungrouped.id] ?? false + + if (includeProfiles) { + builtIn.profiles = profiles.filter(p => p.group === builtIn.id) + profiles = profiles.filter(p => p.group !== builtIn.id) + + ungrouped.profiles = profiles + } + + groups.push(builtIn) + groups.push(ungrouped) + } + + return groups + } + } From 5763919d852287ce0b96f51ff111aeaca37efc7c Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 22:13:43 +0200 Subject: [PATCH 07/38] wip ref(core/profiles.service): add methods to manage ProfileGroup collapse state --- tabby-core/src/services/profiles.service.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index a5cff178..0711087a 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -323,4 +323,13 @@ export class ProfilesService { return groups } + /** + * Save ProfileGroup collapse state in localStorage + */ + saveProfileGroupCollapse(group: PartialProfileGroup) { + const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') + profileGroupCollapsed[group.id] = group.collapsed + window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) + } + } From f0e2482dd6caf24f9f0f11eea36467db68c7575a Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 22:20:18 +0200 Subject: [PATCH 08/38] wip fix(core): group migration c1e03ed5321f2a0add06a7051008e6deffdb038c --- tabby-core/src/configDefaults.yaml | 1 + tabby-core/src/services/config.service.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tabby-core/src/configDefaults.yaml b/tabby-core/src/configDefaults.yaml index 693c888b..1ad4f421 100644 --- a/tabby-core/src/configDefaults.yaml +++ b/tabby-core/src/configDefaults.yaml @@ -31,6 +31,7 @@ hotkeys: profile-selectors: __nonStructural: true profiles: [] +groups: [] profileDefaults: __nonStructural: true ssh: diff --git a/tabby-core/src/services/config.service.ts b/tabby-core/src/services/config.service.ts index 206b3b46..50c0f3ab 100644 --- a/tabby-core/src/services/config.service.ts +++ b/tabby-core/src/services/config.service.ts @@ -368,7 +368,7 @@ export class ConfigService { if (config.version < 5) { const groups: PartialProfileGroup[] = [] for (const p of config.profiles ?? []) { - if (!`${p.group}`.trim()) { + if (!(p.group ?? '').trim()) { continue } From 5ba6bfbd7d27ca6ddd4f91a2519398913484aba7 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 22:27:02 +0200 Subject: [PATCH 09/38] wip fix(core): getProfileGroups bad builtin profile filtering c1e03ed5321f2a0add06a7051008e6deffdb038c --- tabby-core/src/services/profiles.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 0711087a..267523fa 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -310,8 +310,8 @@ export class ProfilesService { ungrouped.collapsed = profileGroupCollapsed[ungrouped.id] ?? false if (includeProfiles) { - builtIn.profiles = profiles.filter(p => p.group === builtIn.id) - profiles = profiles.filter(p => p.group !== builtIn.id) + builtIn.profiles = profiles.filter(p => p.isBuiltin) + profiles = profiles.filter(p => !p.isBuiltin) ungrouped.profiles = profiles } From 4d146941f4e7d628e04200d81f0c85f02c6b909c Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 22:31:26 +0200 Subject: [PATCH 10/38] wip fix(core): getConfigProxyForProfile skipUserDefaults param never used c1e03ed5321f2a0add06a7051008e6deffdb038c --- tabby-core/src/services/profiles.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 267523fa..33cf7e07 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -214,7 +214,7 @@ export class ProfilesService { } getConfigProxyForProfile (profile: PartialProfile, skipUserDefaults = false): T { - const defaults = this.getProfileDefaults(profile).reduce(configMerge, {}) + const defaults = this.getProfileDefaults(profile, skipUserDefaults).reduce(configMerge, {}) return new ConfigProxy(profile, defaults) as unknown as T } From 21df0330120514af44c2c9e38cc91f891d71b9a4 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 22 Jul 2023 22:36:05 +0200 Subject: [PATCH 11/38] lint --- tabby-core/src/api/index.ts | 2 +- tabby-core/src/services/config.service.ts | 3 ++- tabby-core/src/services/profiles.service.ts | 9 ++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tabby-core/src/api/index.ts b/tabby-core/src/api/index.ts index 00e915d5..5c9c0ce6 100644 --- a/tabby-core/src/api/index.ts +++ b/tabby-core/src/api/index.ts @@ -16,7 +16,7 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess' export { HostWindowService } from './hostWindow' export { HostAppService, Platform } from './hostApp' export { FileProvider } from './fileProvider' -export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent, ProfileGroup, PartialProfileGroup} from './profileProvider' +export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent, ProfileGroup, PartialProfileGroup } from './profileProvider' export { PromptModalComponent } from '../components/promptModal.component' export * from './commands' diff --git a/tabby-core/src/services/config.service.ts b/tabby-core/src/services/config.service.ts index 50c0f3ab..1bc60a46 100644 --- a/tabby-core/src/services/config.service.ts +++ b/tabby-core/src/services/config.service.ts @@ -372,7 +372,7 @@ export class ConfigService { continue } - let group = groups.find(x => x.name === (p.group)) + let group = groups.find(x => x.name === p.group) if (!group) { group = { id: `${uuidv4()}`, @@ -387,6 +387,7 @@ export class ConfigService { for (const g of groups) { if (profileGroupCollapsed[g.name]) { const collapsed = profileGroupCollapsed[g.name] + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete delete profileGroupCollapsed[g.name] profileGroupCollapsed[g.id] = collapsed } diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 33cf7e07..b198822b 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -80,8 +80,6 @@ export class ProfilesService { return list } - - providerForProfile (profile: PartialProfile): ProfileProvider|null { const provider = this.profileProviders.find(x => x.id === profile.type) ?? null return provider as unknown as ProfileProvider|null @@ -248,7 +246,8 @@ export class ProfilesService { /** * Set global defaults for a given profile provider */ - setProviderDefaults (provider: ProfileProvider, pdefaults: any) { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + setProviderDefaults (provider: ProfileProvider, pdefaults: any): void { this.config.store.profileDefaults[provider.id] = pdefaults } @@ -284,7 +283,7 @@ export class ProfilesService { let groups: PartialProfileGroup[] = this.config.store.groups ?? [] groups = groups.map(x => { x.editable = true - x.collapsed = profileGroupCollapsed[x.id ?? ''] ?? false + x.collapsed = profileGroupCollapsed[x.id] ?? false if (includeProfiles) { x.profiles = profiles.filter(p => p.group === x.id) @@ -326,7 +325,7 @@ export class ProfilesService { /** * Save ProfileGroup collapse state in localStorage */ - saveProfileGroupCollapse(group: PartialProfileGroup) { + saveProfileGroupCollapse (group: PartialProfileGroup): void { const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') profileGroupCollapsed[group.id] = group.collapsed window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) From 1903ec599529f72af3c6274ae6f622727501ba80 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sun, 23 Jul 2023 17:18:26 +0200 Subject: [PATCH 12/38] wip ref(settings): move out group managment from settings --- .../components/editProfileModal.component.pug | 4 +- .../components/editProfileModal.component.ts | 38 +++++++--- .../profilesSettingsTab.component.ts | 71 +++++-------------- 3 files changed, 48 insertions(+), 65 deletions(-) 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 { From 8e9156e2504edb90522a09afec3217f2df7e653b Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sun, 23 Jul 2023 18:14:55 +0200 Subject: [PATCH 13/38] wip ref(core): ProfileGroup avoid direct config interraction outside of profiles.services --- tabby-core/src/services/profiles.service.ts | 50 +++++++++++++++++-- .../components/editProfileModal.component.ts | 6 +-- .../profilesSettingsTab.component.ts | 16 +++--- 3 files changed, 56 insertions(+), 16 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index b198822b..a14401d6 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -8,6 +8,7 @@ import { AppService } from './app.service' import { configMerge, ConfigProxy, ConfigService } from './config.service' import { NotificationsService } from './notifications.service' import { SelectorService } from './selector.service' +import deepClone from 'clone-deep' @Injectable({ providedIn: 'root' }) export class ProfilesService { @@ -74,7 +75,7 @@ export class ProfilesService { ...this.config.store.profiles ?? [], ...list, ] - const sortKey = p => `${p.group ?? ''} / ${p.name}` + 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 @@ -96,8 +97,7 @@ export class ProfilesService { const freeInputEquivalent = provider?.intoQuickConnectString(fullProfile) ?? undefined return { ...profile, - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - group: profile.group || '', + group: this.resolveProfileGroupName(profile.group ?? ''), freeInputEquivalent, description: provider?.getDescription(fullProfile), } @@ -280,7 +280,7 @@ export class ProfilesService { } const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') - let groups: PartialProfileGroup[] = this.config.store.groups ?? [] + let groups: PartialProfileGroup[] = deepClone(this.config.store.groups ?? []) groups = groups.map(x => { x.editable = true x.collapsed = profileGroupCollapsed[x.id] ?? false @@ -322,6 +322,48 @@ export class ProfilesService { return groups } + /** + * Write a ProfileGroup in config + * arg: saveConfig (default: true) -> invoke after the ProfileGroup was updated + */ + async writeProfileGroup (group: PartialProfileGroup, saveConfig = true): Promise { + this.deleteProfileGroup(group, false) + + delete group.profiles + delete group.editable + delete group.collapsed + + this.config.store.groups.push(group) + if (saveConfig) { + return this.config.save() + } + } + + /** + * Delete a ProfileGroup from config + * arg: saveConfig (default: true) -> invoke after the ProfileGroup was deleted + */ + 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) + } else { + for (const profile of this.config.store.profiles.filter(x => x.group === group.id)) { + delete profile.group + } + } + if (saveConfig) { + return this.config.save() + } + } + + /** + * Resolve and return ProfileGroup from ProfileGroup ID + */ + resolveProfileGroupName (groupId: string): string { + return this.config.store.groups.find(g => g.id === groupId)?.name ?? '' + } + /** * Save ProfileGroup collapse state in localStorage */ diff --git a/tabby-settings/src/components/editProfileModal.component.ts b/tabby-settings/src/components/editProfileModal.component.ts index a57ee865..3a17ce0f 100644 --- a/tabby-settings/src/components/editProfileModal.component.ts +++ b/tabby-settings/src/components/editProfileModal.component.ts @@ -36,7 +36,7 @@ export class EditProfileModalComponent

{ private modalInstance: NgbActiveModal, ) { if (!this.defaultsMode) { - this.profilesService.getProfileGroups().then(groups => { + this.profilesService.getProfileGroups().then(groups => { this.groups = groups this.profileGroup = groups.find(g => g.id === this.profile.group) }) @@ -97,9 +97,9 @@ export class EditProfileModalComponent

{ if (typeof this.profileGroup === 'string') { const newGroup: PartialProfileGroup = { id: uuidv4(), - name: this.profileGroup + name: this.profileGroup, } - this.groups.push(newGroup) + this.profilesService.writeProfileGroup(newGroup, false) this.profileGroup = newGroup } this.profile.group = this.profileGroup.id diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 2ede9f2d..4826a03d 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -167,7 +167,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { const result = await modal.result if (result) { group.name = result.value - await this.config.save() + await this.profilesService.writeProfileGroup(group) } } @@ -184,7 +184,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent { cancelId: 1, }, )).response === 0) { - if ((await this.platform.showMessageBox( + let deleteProfiles = false + if ((group.profiles?.length ?? 0) > 0 && (await this.platform.showMessageBox( { type: 'warning', message: this.translate.instant('Delete the group\'s profiles?'), @@ -195,14 +196,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent { defaultId: 0, cancelId: 0, }, - )).response === 0) { - 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.id) + )).response !== 0) { + deleteProfiles = true } - await this.config.save() + + await this.profilesService.deleteProfileGroup(group, true, deleteProfiles) } } From ef040ee342bbcf378799da41cd712bade57ec496 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sun, 23 Jul 2023 19:47:38 +0200 Subject: [PATCH 14/38] wip fix(core): Cannot access 'ProfilesService' before initialization from AppHotkeyProvider --- tabby-core/src/hotkeys.ts | 6 +----- tabby-core/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/tabby-core/src/hotkeys.ts b/tabby-core/src/hotkeys.ts index c14ac666..d4465a36 100644 --- a/tabby-core/src/hotkeys.ts +++ b/tabby-core/src/hotkeys.ts @@ -2,7 +2,6 @@ import { Injectable } from '@angular/core' import { TranslateService } from '@ngx-translate/core' import { ProfilesService } from './services/profiles.service' import { HotkeyDescription, HotkeyProvider } from './api/hotkeyProvider' -import { PartialProfile, Profile } from './api' /** @hidden */ @Injectable() @@ -268,7 +267,7 @@ export class AppHotkeyProvider extends HotkeyProvider { return [ ...this.hotkeys, ...profiles.map(profile => ({ - id: `profile.${AppHotkeyProvider.getProfileHotkeyName(profile)}`, + id: `profile.${ProfilesService.getProfileHotkeyName(profile)}`, name: this.translate.instant('New tab: {profile}', { profile: profile.name }), })), ...this.profilesService.getProviders().map(provider => ({ @@ -278,7 +277,4 @@ export class AppHotkeyProvider extends HotkeyProvider { ] } - static getProfileHotkeyName (profile: PartialProfile): string { - return (profile.id ?? profile.name).replace(/\./g, '-') - } } diff --git a/tabby-core/src/index.ts b/tabby-core/src/index.ts index 6cf14a24..fc08aea4 100644 --- a/tabby-core/src/index.ts +++ b/tabby-core/src/index.ts @@ -177,7 +177,7 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex if (hotkey.startsWith('profile.')) { const id = hotkey.substring(hotkey.indexOf('.') + 1) const profiles = await profilesService.getProfiles() - const profile = profiles.find(x => AppHotkeyProvider.getProfileHotkeyName(x) === id) + const profile = profiles.find(x => ProfilesService.getProfileHotkeyName(x) === id) if (profile) { profilesService.openNewTabForProfile(profile) } From 48d4b8e8f8dfff983330bfb447daf5405412cbc0 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sun, 23 Jul 2023 19:59:45 +0200 Subject: [PATCH 15/38] wip ref(core): Profile avoid direct config interraction outside of profiles.services --- tabby-core/src/services/profiles.service.ts | 189 +++++++++++++----- .../profilesSettingsTab.component.ts | 16 +- 2 files changed, 142 insertions(+), 63 deletions(-) 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) } } From b751e1008260c828cbf5069d9f9105dd80eac38c Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sun, 23 Jul 2023 21:46:36 +0200 Subject: [PATCH 16/38] wip fix(core): deleteProfile & deleteGroupProfile 8e9156e2504edb90522a09afec3217f2df7e653b 48d4b8e8f8dfff983330bfb447daf5405412cbc0 --- tabby-core/src/services/profiles.service.ts | 27 ++++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index cb42b743..7781bdf0 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -94,11 +94,18 @@ export class ProfilesService { * arg: saveConfig (default: true) -> invoke after the Profile was updated */ async writeProfile (profile: PartialProfile, saveConfig = true): Promise { - this.deleteProfile(profile, false) + const cProfile = this.config.store.profiles.find(p => p.id === profile.id) + if (cProfile) { + // Fully replace the config + for (const k in cProfile) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete cProfile[k] + } + Object.assign(cProfile, profile) - this.config.store.profiles.push(profile) - if (saveConfig) { - return this.config.save() + if (saveConfig) { + return this.config.save() + } } } @@ -415,15 +422,17 @@ export class ProfilesService { * arg: saveConfig (default: true) -> invoke after the ProfileGroup was updated */ async writeProfileGroup (group: PartialProfileGroup, saveConfig = true): Promise { - this.deleteProfileGroup(group, false, false) - delete group.profiles delete group.editable delete group.collapsed - this.config.store.groups.push(group) - if (saveConfig) { - return this.config.save() + const cGroup = this.config.store.groups.find(g => g.id === group.id) + if (cGroup) { + Object.assign(cGroup, group) + + if (saveConfig) { + return this.config.save() + } } } From a0804cc5644ccfe207db3fc69add13e0541e8160 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sun, 23 Jul 2023 22:11:01 +0200 Subject: [PATCH 17/38] wip fix(settings): newProfile unresolve group name --- tabby-settings/src/components/profilesSettingsTab.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 47c60463..b8b060cb 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -60,7 +60,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { profiles.map(p => ({ icon: p.icon, description: this.profilesService.getDescription(p) ?? undefined, - name: p.group ? `${p.group} / ${p.name}` : p.name, + name: p.group ? `${this.profilesService.resolveProfileGroupName(p.group)} / ${p.name}` : p.name, result: p, })), ) From 1c06a510bd14f73b0331053f12dcca38b48cdc57 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sun, 23 Jul 2023 22:23:57 +0200 Subject: [PATCH 18/38] wip ref(core): Profile & ProfileGroup add creation methods in ProfileService --- tabby-core/src/services/profiles.service.ts | 46 +++++++++++++++++++ .../components/editProfileModal.component.ts | 2 +- .../profilesSettingsTab.component.ts | 12 ++--- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 7781bdf0..8355e29d 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -9,6 +9,8 @@ import { configMerge, ConfigProxy, ConfigService } from './config.service' import { NotificationsService } from './notifications.service' import { SelectorService } from './selector.service' import deepClone from 'clone-deep' +import { v4 as uuidv4 } from 'uuid' +import slugify from 'slugify' @Injectable({ providedIn: 'root' }) export class ProfilesService { @@ -89,6 +91,28 @@ export class ProfilesService { return clone ? deepClone(list) : list } + /** + * Insert a new Profile in config + * arg: saveConfig (default: true) -> invoke after the Profile was updated + * arg: genId (default: true) -> generate uuid in before pushing Profile into config + */ + async newProfile (profile: PartialProfile, saveConfig = true, genId = true): Promise { + if (genId) { + profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}` + } + + const cProfile = this.config.store.profiles.find(p => p.id === profile.id) + if (cProfile) { + throw new Error(`Cannot insert new Profile, duplicated Id: ${profile.id}`) + } + + this.config.store.profiles.push(profile) + + if (saveConfig) { + return this.config.save() + } + } + /** * Write a Profile in config * arg: saveConfig (default: true) -> invoke after the Profile was updated @@ -417,6 +441,28 @@ export class ProfilesService { return groups } + /** + * Insert a new ProfileGroup in config + * arg: saveConfig (default: true) -> invoke after the Profile was updated + * arg: genId (default: true) -> generate uuid in before pushing Profile into config + */ + async newProfileGroup (group: PartialProfileGroup, saveConfig = true, genId = true): Promise { + if (genId) { + group.id = `${uuidv4()}` + } + + const cProfileGroup = this.config.store.groups.find(p => p.id === group.id) + if (cProfileGroup) { + throw new Error(`Cannot insert new ProfileGroup, duplicated Id: ${group.id}`) + } + + this.config.store.groups.push(group) + + if (saveConfig) { + return this.config.save() + } + } + /** * Write a ProfileGroup in config * arg: saveConfig (default: true) -> invoke after the ProfileGroup was updated diff --git a/tabby-settings/src/components/editProfileModal.component.ts b/tabby-settings/src/components/editProfileModal.component.ts index 3a17ce0f..f1b64f6a 100644 --- a/tabby-settings/src/components/editProfileModal.component.ts +++ b/tabby-settings/src/components/editProfileModal.component.ts @@ -99,7 +99,7 @@ export class EditProfileModalComponent

{ id: uuidv4(), name: this.profileGroup, } - this.profilesService.writeProfileGroup(newGroup, false) + this.profilesService.newProfileGroup(newGroup, false, false) this.profileGroup = newGroup } this.profile.group = this.profileGroup.id diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index b8b060cb..ea71c54f 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -1,6 +1,4 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker' -import { v4 as uuidv4 } from 'uuid' -import slugify from 'slugify' import deepClone from 'clone-deep' import { Component, Inject } from '@angular/core' import { NgbModal } from '@ng-bootstrap/ng-bootstrap' @@ -16,7 +14,6 @@ _('Ungrouped') styleUrls: ['./profilesSettingsTab.component.scss'], }) export class ProfilesSettingsTabComponent extends BaseComponent { - profiles: PartialProfile[] = [] builtinProfiles: PartialProfile[] = [] templateProfiles: PartialProfile[] = [] profileGroups: PartialProfileGroup[] @@ -39,7 +36,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { async ngOnInit (): Promise { this.refresh() - this.builtinProfiles = (await this.profilesService.getProfiles()).filter(x => x.isBuiltin) + this.builtinProfiles = (await this.profilesService.getProfiles(true, false)).filter(x => x.isBuiltin) this.templateProfiles = this.builtinProfiles.filter(x => x.isTemplate) this.builtinProfiles = this.builtinProfiles.filter(x => !x.isTemplate) this.refresh() @@ -52,7 +49,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { async newProfile (base?: PartialProfile): Promise { if (!base) { - let profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles] + let profiles = await this.profilesService.getProfiles() profiles = profiles.filter(x => !this.isProfileBlacklisted(x)) profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0)) base = await this.selector.show( @@ -83,9 +80,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { const cfgProxy = this.profilesService.getConfigProxyForProfile(profile) profile.name = this.profilesService.providerForProfile(profile)?.getSuggestedName(cfgProxy) ?? this.translate.instant('{name} copy', base) } - profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}` - this.config.store.profiles = [profile, ...this.config.store.profiles] - await this.config.save() + this.profilesService.newProfile(profile) } async editProfile (profile: PartialProfile): Promise { @@ -142,7 +137,6 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } async refresh (): Promise { - this.profiles = this.config.store.profiles 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)) From 44c449bd4c85bba01c641ecb81f5fbbc5d9fbacc Mon Sep 17 00:00:00 2001 From: Clem Date: Fri, 4 Aug 2023 14:16:00 +0200 Subject: [PATCH 19/38] wip ref(core): move group collapsed status into profileSettingsTab --- tabby-core/src/api/profileProvider.ts | 1 - tabby-core/src/services/profiles.service.ts | 14 ------- .../profilesSettingsTab.component.ts | 40 ++++++++++++++++--- 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/tabby-core/src/api/profileProvider.ts b/tabby-core/src/api/profileProvider.ts index 9b53bbc2..fbbf1f5d 100644 --- a/tabby-core/src/api/profileProvider.ts +++ b/tabby-core/src/api/profileProvider.ts @@ -37,7 +37,6 @@ export interface ProfileGroup { profiles: PartialProfile[] defaults: any editable: boolean - collapsed: boolean } export type PartialProfileGroup = Omit[] = deepClone(this.config.store.groups ?? []) groups = groups.map(x => { x.editable = true - x.collapsed = profileGroupCollapsed[x.id] ?? false if (includeProfiles) { x.profiles = profiles.filter(p => p.group === x.id) @@ -418,14 +416,12 @@ export class ProfilesService { name: this.translate.instant('Built-in'), editable: false, } - builtIn.collapsed = profileGroupCollapsed[builtIn.id] ?? false const ungrouped: PartialProfileGroup = { id: 'ungrouped', name: this.translate.instant('Ungrouped'), editable: false, } - ungrouped.collapsed = profileGroupCollapsed[ungrouped.id] ?? false if (includeProfiles) { builtIn.profiles = profiles.filter(p => p.isBuiltin) @@ -470,7 +466,6 @@ export class ProfilesService { async writeProfileGroup (group: PartialProfileGroup, saveConfig = true): Promise { delete group.profiles delete group.editable - delete group.collapsed const cGroup = this.config.store.groups.find(g => g.id === group.id) if (cGroup) { @@ -508,13 +503,4 @@ export class ProfilesService { return this.config.store.groups.find(g => g.id === groupId)?.name ?? '' } - /** - * Save ProfileGroup collapse state in localStorage - */ - saveProfileGroupCollapse (group: PartialProfileGroup): void { - const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') - profileGroupCollapsed[group.id] = group.collapsed - window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) - } - } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index ea71c54f..cc6bf7f9 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -8,6 +8,10 @@ import { EditProfileModalComponent } from './editProfileModal.component' _('Filter') _('Ungrouped') +interface CollapsableProfileGroup extends ProfileGroup { + collapsed: boolean +} + /** @hidden */ @Component({ templateUrl: './profilesSettingsTab.component.pug', @@ -16,7 +20,7 @@ _('Ungrouped') export class ProfilesSettingsTabComponent extends BaseComponent { builtinProfiles: PartialProfile[] = [] templateProfiles: PartialProfile[] = [] - profileGroups: PartialProfileGroup[] + profileGroups: PartialProfileGroup[] filter = '' Platform = Platform @@ -137,21 +141,22 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } async refresh (): Promise { + const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') 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 + this.profileGroups = groups.map(g => ProfilesSettingsTabComponent.intoPartialCollapsableProfileGroup(g, profileGroupCollapsed[g.id] ?? false)) } - async editGroup (group: PartialProfileGroup): 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) { group.name = result.value - await this.profilesService.writeProfileGroup(group) + await this.profilesService.writeProfileGroup(ProfilesSettingsTabComponent.collapsableIntoPartialProfileGroup(group)) } } @@ -217,9 +222,9 @@ export class ProfilesSettingsTabComponent extends BaseComponent { }[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning' } - toggleGroupCollapse (group: PartialProfileGroup): void { + toggleGroupCollapse (group: PartialProfileGroup): void { group.collapsed = !group.collapsed - this.profilesService.saveProfileGroupCollapse(group) + this.saveProfileGroupCollapse(group) } async editDefaults (provider: ProfileProvider): Promise { @@ -261,4 +266,27 @@ export class ProfilesSettingsTabComponent extends BaseComponent { getQuickConnectProviders (): ProfileProvider[] { return this.profileProviders.filter(x => x.supportsQuickConnect) } + + /** + * Save ProfileGroup collapse state in localStorage + */ + private saveProfileGroupCollapse (group: PartialProfileGroup): void { + const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') + profileGroupCollapsed[group.id] = group.collapsed + window.localStorage.profileGroupCollapsed = JSON.stringify(profileGroupCollapsed) + } + + private static collapsableIntoPartialProfileGroup (group: PartialProfileGroup): PartialProfileGroup { + const g: any = { ...group } + delete g.collapsed + return g + } + + private static intoPartialCollapsableProfileGroup (group: PartialProfileGroup, collapsed: boolean): PartialProfileGroup { + const collapsableGroup = { + ...group, + collapsed, + } + return collapsableGroup + } } From 30936b739edfced261dc2f255bb3fe0e8928de6f Mon Sep 17 00:00:00 2001 From: Clem Date: Fri, 4 Aug 2023 14:21:46 +0200 Subject: [PATCH 20/38] wip fix(core): writeProfile avoid deleting profile fields one by one --- tabby-core/src/services/profiles.service.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index b206cf79..090dacd0 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -120,11 +120,10 @@ export class ProfilesService { async writeProfile (profile: PartialProfile, saveConfig = true): Promise { const cProfile = this.config.store.profiles.find(p => p.id === profile.id) if (cProfile) { - // Fully replace the config - for (const k in cProfile) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete cProfile[k] + if (!profile.group) { + delete cProfile.group } + Object.assign(cProfile, profile) if (saveConfig) { From 951c69b31a51cc748bfdd4a20fa638f7c255626c Mon Sep 17 00:00:00 2001 From: Clem Date: Fri, 4 Aug 2023 14:39:06 +0200 Subject: [PATCH 21/38] wip ref(core): resolveProfileGroupName return groupId if no name found --- tabby-core/src/services/profiles.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 090dacd0..7427039e 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -496,10 +496,10 @@ export class ProfilesService { } /** - * Resolve and return ProfileGroup from ProfileGroup ID + * Resolve and return ProfileGroup Name from ProfileGroup ID */ resolveProfileGroupName (groupId: string): string { - return this.config.store.groups.find(g => g.id === groupId)?.name ?? '' + return this.config.store.groups.find(g => g.id === groupId)?.name ?? groupId } } From 935c981d2bfddf509145847b12895089ccb9f44b Mon Sep 17 00:00:00 2001 From: Clem Date: Sat, 5 Aug 2023 21:04:10 +0200 Subject: [PATCH 22/38] wip ref(core): getProfileGroups create non editable group for built-in profile --- tabby-core/src/services/profiles.service.ts | 26 ++++++++++++++----- .../profilesSettingsTab.component.ts | 2 +- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 7427039e..229ff941 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -410,11 +410,13 @@ export class ProfilesService { }) if (includeNonUserGroup) { - const builtIn: PartialProfileGroup = { + const builtInGroups: PartialProfileGroup[] = [] + builtInGroups.push({ id: 'built-in', name: this.translate.instant('Built-in'), editable: false, - } + profiles: [], + }) const ungrouped: PartialProfileGroup = { id: 'ungrouped', @@ -423,13 +425,25 @@ export class ProfilesService { } if (includeProfiles) { - builtIn.profiles = profiles.filter(p => p.isBuiltin) - profiles = profiles.filter(p => !p.isBuiltin) + for (const profile of profiles.filter(p => p.isBuiltin)) { + let group: PartialProfileGroup | undefined = builtInGroups.find(g => g.id === slugify(profile.group ?? 'built-in')) + if (!group) { + group = { + id: `${slugify(profile.group!)}`, + name: `${profile.group!}`, + editable: false, + profiles: [], + } + builtInGroups.push(group) + } - ungrouped.profiles = profiles + group.profiles!.push(profile) + } + + ungrouped.profiles = profiles.filter(p => !p.isBuiltin) } - groups.push(builtIn) + groups = groups.concat(builtInGroups) groups.push(ungrouped) } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index cc6bf7f9..08e78561 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -144,7 +144,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') 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 === 'built-in' || !a.editable ? 1 : 0) - (b.id === 'built-in' || !b.editable ? 1 : 0)) groups.sort((a, b) => (a.id === 'ungrouped' ? 0 : 1) - (b.id === 'ungrouped' ? 0 : 1)) this.profileGroups = groups.map(g => ProfilesSettingsTabComponent.intoPartialCollapsableProfileGroup(g, profileGroupCollapsed[g.id] ?? false)) } From 0ef24ddf1d3096f05505e4e37fa57480d68d879d Mon Sep 17 00:00:00 2001 From: Clem Date: Sat, 5 Aug 2023 21:08:09 +0200 Subject: [PATCH 23/38] wip ref(settings): profilesSettingsTab profile template no launchable --- tabby-settings/src/components/profilesSettingsTab.component.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tabby-settings/src/components/profilesSettingsTab.component.pug b/tabby-settings/src/components/profilesSettingsTab.component.pug index 9ce3f981..9c298871 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.pug +++ b/tabby-settings/src/components/profilesSettingsTab.component.pug @@ -67,7 +67,7 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') .me-auto - button.btn.btn-link.hover-reveal.ms-1((click)='$event.stopPropagation(); launchProfile(profile)') + button.btn.btn-link.hover-reveal.ms-1(*ngIf='!profile.isTemplate', (click)='$event.stopPropagation(); launchProfile(profile)') i.fas.fa-play .ms-1.hover-reveal(ngbDropdown, placement='bottom-right top-right auto') From 695c5ba67067fd7f113e3b50af9266964f255040 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Fri, 11 Aug 2023 23:38:16 +0200 Subject: [PATCH 24/38] feat(core/settings): Eugeny/tabby#3999 Allow groups to specify settings that hosts inherit --- tabby-core/src/services/profiles.service.ts | 29 +++++- .../editProfileGroupModal.component.pug | 29 ++++++ .../editProfileGroupModal.component.ts | 34 +++++++ .../components/editProfileModal.component.pug | 11 ++- .../components/editProfileModal.component.ts | 17 +--- .../profilesSettingsTab.component.pug | 22 +++-- .../profilesSettingsTab.component.ts | 93 +++++++++++++++---- tabby-settings/src/index.ts | 2 + 8 files changed, 190 insertions(+), 47 deletions(-) create mode 100644 tabby-settings/src/components/editProfileGroupModal.component.pug create mode 100644 tabby-settings/src/components/editProfileGroupModal.component.ts diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 229ff941..329fc4fe 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -65,8 +65,8 @@ export class ProfilesService { * 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, {}) + getConfigProxyForProfile (profile: PartialProfile, skipGlobalDefaults = false, skipGroupDefaults = false): T { + const defaults = this.getProfileDefaults(profile, skipGlobalDefaults, skipGroupDefaults).reduce(configMerge, {}) return new ConfigProxy(profile, defaults) as unknown as T } @@ -373,12 +373,14 @@ export class ProfilesService { * 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 { + getProfileDefaults (profile: PartialProfile, skipGlobalDefaults = false, skipGroupDefaults = false): any[] { const provider = this.providerForProfile(profile) + return [ this.profileDefaults, provider?.configDefaults ?? {}, - !provider || skipUserDefaults ? {} : this.getProviderDefaults(provider), + provider && !skipGlobalDefaults ? this.getProviderDefaults(provider) : {}, + provider && !skipGlobalDefaults && !skipGroupDefaults ? this.getProviderProfileGroupDefaults(profile.group ?? '', provider) : {}, ] } @@ -386,6 +388,14 @@ export class ProfilesService { * Methods used to interract with ProfileGroup */ + /** + * Synchronously return an Array of the existing ProfileGroups + * Does not return builtin groups + */ + getSyncProfileGroups (): PartialProfileGroup[] { + return deepClone(this.config.store.groups ?? []) + } + /** * Return an Array of the existing ProfileGroups * arg: includeProfiles (default: false) -> if false, does not fill up the profiles field of ProfileGroup @@ -397,7 +407,7 @@ export class ProfilesService { profiles = await this.getProfiles(includeNonUserGroup, true) } - let groups: PartialProfileGroup[] = deepClone(this.config.store.groups ?? []) + let groups: PartialProfileGroup[] = this.getSyncProfileGroups() groups = groups.map(x => { x.editable = true @@ -516,4 +526,13 @@ export class ProfilesService { return this.config.store.groups.find(g => g.id === groupId)?.name ?? groupId } + /** + * Return defaults for a given group ID and provider + * Always return something, empty object if no defaults found + * arg: skipUserDefaults -> do not merge global provider defaults in ConfigProxy + */ + getProviderProfileGroupDefaults (groupId: string, provider: ProfileProvider): any { + return this.getSyncProfileGroups().find(g => g.id === groupId)?.defaults?.[provider.id] ?? {} + } + } diff --git a/tabby-settings/src/components/editProfileGroupModal.component.pug b/tabby-settings/src/components/editProfileGroupModal.component.pug new file mode 100644 index 00000000..54468a3b --- /dev/null +++ b/tabby-settings/src/components/editProfileGroupModal.component.pug @@ -0,0 +1,29 @@ +.modal-header + h3.m-0 {{group.name}} + +.modal-body + .row + .col-12.col-lg-4 + .mb-3 + label(translate) Name + input.form-control( + type='text', + autofocus, + [(ngModel)]='group.name', + ) + + .col-12.col-lg-8 + .form-line.content-box + .header + .title(translate) Default profile group settings + .description(translate) These apply to all profiles of a given type in this group + + .list-group.mt-3.mb-3.content-box + a.list-group-item.list-group-item-action( + (click)='editDefaults(provider)', + *ngFor='let provider of providers' + ) {{provider.name|translate}} + +.modal-footer + button.btn.btn-primary((click)='save()', translate) Save + button.btn.btn-danger((click)='cancel()', translate) Cancel diff --git a/tabby-settings/src/components/editProfileGroupModal.component.ts b/tabby-settings/src/components/editProfileGroupModal.component.ts new file mode 100644 index 00000000..fd00b026 --- /dev/null +++ b/tabby-settings/src/components/editProfileGroupModal.component.ts @@ -0,0 +1,34 @@ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { Component, Input } from '@angular/core' +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' +import { ConfigProxy, ProfileGroup, Profile, ProfileProvider } from 'tabby-core' + +/** @hidden */ +@Component({ + templateUrl: './editProfileGroupModal.component.pug', +}) +export class EditProfileGroupModalComponent { + @Input() group: G & ConfigProxy + @Input() providers: ProfileProvider[] + + constructor ( + private modalInstance: NgbActiveModal, + ) {} + + save () { + this.modalInstance.close({ group: this.group }) + } + + cancel () { + this.modalInstance.dismiss() + } + + editDefaults (provider: ProfileProvider) { + this.modalInstance.close({ group: this.group, provider }) + } +} + +export interface EditProfileGroupModalComponentResult { + group: G + provider?: ProfileProvider +} diff --git a/tabby-settings/src/components/editProfileModal.component.pug b/tabby-settings/src/components/editProfileModal.component.pug index 0619345d..10d7fea2 100644 --- a/tabby-settings/src/components/editProfileModal.component.pug +++ b/tabby-settings/src/components/editProfileModal.component.pug @@ -1,7 +1,7 @@ -.modal-header(*ngIf='!defaultsMode') +.modal-header(*ngIf='defaultsMode === "disabled"') h3.m-0 {{profile.name}} -.modal-header(*ngIf='defaultsMode') +.modal-header(*ngIf='defaultsMode !== "disabled"') h3.m-0( translate='Defaults for {type}', [translateParams]='{type: profileProvider.name}' @@ -10,7 +10,7 @@ .modal-body .row .col-12.col-lg-4 - .mb-3(*ngIf='!defaultsMode') + .mb-3(*ngIf='defaultsMode === "disabled"') label(translate) Name input.form-control( type='text', @@ -18,7 +18,7 @@ [(ngModel)]='profile.name', ) - .mb-3(*ngIf='!defaultsMode') + .mb-3(*ngIf='defaultsMode === "disabled"') label(translate) Group input.form-control( type='text', @@ -28,9 +28,10 @@ [ngbTypeahead]='groupTypeahead', [inputFormatter]="groupFormatter", [resultFormatter]="groupFormatter", + [editable]="false" ) - .mb-3(*ngIf='!defaultsMode') + .mb-3(*ngIf='defaultsMode === "disabled"') label(translate) Icon .input-group input.form-control( diff --git a/tabby-settings/src/components/editProfileModal.component.ts b/tabby-settings/src/components/editProfileModal.component.ts index f1b64f6a..e193aa94 100644 --- a/tabby-settings/src/components/editProfileModal.component.ts +++ b/tabby-settings/src/components/editProfileModal.component.ts @@ -3,7 +3,6 @@ import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' 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,8 +19,8 @@ export class EditProfileModalComponent

{ @Input() profile: P & ConfigProxy @Input() profileProvider: ProfileProvider

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

- @Input() defaultsMode = false - @Input() profileGroup: PartialProfileGroup | string | undefined + @Input() defaultsMode: 'enabled'|'group'|'disabled' = 'disabled' + @Input() profileGroup: PartialProfileGroup | undefined groups: PartialProfileGroup[] @ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef @@ -35,7 +34,7 @@ export class EditProfileModalComponent

{ config: ConfigService, private modalInstance: NgbActiveModal, ) { - if (!this.defaultsMode) { + if (this.defaultsMode === 'disabled') { this.profilesService.getProfileGroups().then(groups => { this.groups = groups this.profileGroup = groups.find(g => g.id === this.profile.group) @@ -59,7 +58,7 @@ export class EditProfileModalComponent

{ ngOnInit () { this._profile = this.profile - this.profile = this.profilesService.getConfigProxyForProfile(this.profile, this.defaultsMode) + this.profile = this.profilesService.getConfigProxyForProfile(this.profile, this.defaultsMode === 'enabled', this.defaultsMode === 'group') } ngAfterViewInit () { @@ -94,14 +93,6 @@ export class EditProfileModalComponent

{ if (!this.profileGroup) { this.profile.group = undefined } else { - if (typeof this.profileGroup === 'string') { - const newGroup: PartialProfileGroup = { - id: uuidv4(), - name: this.profileGroup, - } - this.profilesService.newProfileGroup(newGroup, false, false) - this.profileGroup = newGroup - } this.profile.group = this.profileGroup.id } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.pug b/tabby-settings/src/components/profilesSettingsTab.component.pug index 9c298871..ed61b23d 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.pug +++ b/tabby-settings/src/components/profilesSettingsTab.component.pug @@ -27,9 +27,17 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') i.fas.fa-fw.fa-search input.form-control(type='search', [placeholder]='"Filter"|translate', [(ngModel)]='filter') - button.btn.btn-primary.flex-shrink-0.ms-3((click)='newProfile()') - i.fas.fa-fw.fa-plus - span(translate) New profile + div(ngbDropdown).d-inline-block.flex-shrink-0.ms-3 + button.btn.btn-primary(ngbDropdownToggle) + i.fas.fa-fw.fa-plus + span(translate) New + div(ngbDropdownMenu) + button(ngbDropdownItem, (click)='newProfile()') + i.fas.fa-fw.fa-plus + span(translate) New profile + button(ngbDropdownItem, (click)='newProfileGroup()') + i.fas.fa-fw.fa-plus + span(translate) New profile Group .list-group.mt-3.mb-3 ng-container(*ngFor='let group of profileGroups') @@ -37,17 +45,17 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') .list-group-item.list-group-item-action.d-flex.align-items-center( (click)='toggleGroupCollapse(group)' ) - .fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed') - .fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed') + .fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed && group.profiles?.length > 0') + .fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed && group.profiles?.length > 0') span.ms-3.me-auto {{group.name || ("Ungrouped"|translate)}} button.btn.btn-sm.btn-link.hover-reveal.ms-2( *ngIf='group.editable && group.name', - (click)='$event.stopPropagation(); editGroup(group)' + (click)='$event.stopPropagation(); editProfileGroup(group)' ) i.fas.fa-pencil-alt button.btn.btn-sm.btn-link.hover-reveal.ms-2( *ngIf='group.editable && group.name', - (click)='$event.stopPropagation(); deleteGroup(group)' + (click)='$event.stopPropagation(); deleteProfileGroup(group)' ) i.fas.fa-trash-alt ng-container(*ngIf='!group.collapsed') diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 08e78561..41b3e443 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -4,6 +4,7 @@ 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, ProfileGroup, PartialProfileGroup } from 'tabby-core' import { EditProfileModalComponent } from './editProfileModal.component' +import { EditProfileGroupModalComponent, EditProfileGroupModalComponentResult } from './editProfileGroupModal.component' _('Filter') _('Ungrouped') @@ -140,27 +141,73 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } } - async refresh (): Promise { - const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') - 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' || !a.editable ? 1 : 0) - (b.id === 'built-in' || !b.editable ? 1 : 0)) - groups.sort((a, b) => (a.id === 'ungrouped' ? 0 : 1) - (b.id === 'ungrouped' ? 0 : 1)) - this.profileGroups = groups.map(g => ProfilesSettingsTabComponent.intoPartialCollapsableProfileGroup(g, profileGroupCollapsed[g.id] ?? false)) - } - - async editGroup (group: PartialProfileGroup): Promise { + async newProfileGroup (): Promise { const modal = this.ngbModal.open(PromptModalComponent) - modal.componentInstance.prompt = this.translate.instant('New name') - modal.componentInstance.value = group.name + modal.componentInstance.prompt = this.translate.instant('New group name') const result = await modal.result - if (result) { - group.name = result.value - await this.profilesService.writeProfileGroup(ProfilesSettingsTabComponent.collapsableIntoPartialProfileGroup(group)) + if (result?.value.trim()) { + await this.profilesService.newProfileGroup({ id: '', name: result.value }) } } - async deleteGroup (group: PartialProfileGroup): Promise { + async editProfileGroup (group: PartialProfileGroup): Promise { + const result = await this.showProfileGroupEditModal(group) + if (!result) { + return + } + Object.assign(group, result) + await this.profilesService.writeProfileGroup(ProfilesSettingsTabComponent.collapsableIntoPartialProfileGroup(group)) + } + + async showProfileGroupEditModal (group: PartialProfileGroup): Promise|null> { + const modal = this.ngbModal.open( + EditProfileGroupModalComponent, + { size: 'lg' }, + ) + + modal.componentInstance.group = deepClone(group) + modal.componentInstance.providers = this.profileProviders + + const result: EditProfileGroupModalComponentResult | null = await modal.result.catch(() => null) + if (!result) { + return null + } + + if (result.provider) { + return this.editProfileGroupDefaults(result.group, result.provider) + } + + return result.group + } + + private async editProfileGroupDefaults (group: PartialProfileGroup, provider: ProfileProvider): Promise|null> { + const modal = this.ngbModal.open( + EditProfileModalComponent, + { size: 'lg' }, + ) + const model = group.defaults?.[provider.id] ?? {} + model.type = provider.id + modal.componentInstance.profile = Object.assign({}, model) + modal.componentInstance.profileProvider = provider + modal.componentInstance.defaultsMode = 'group' + + const result = await modal.result.catch(() => null) + if (result) { + // Fully replace the config + for (const k in model) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete model[k] + } + Object.assign(model, result) + if (!group.defaults) { + group.defaults = {} + } + group.defaults[provider.id] = model + } + return this.showProfileGroupEditModal(group) + } + + async deleteProfileGroup (group: PartialProfileGroup): Promise { if ((await this.platform.showMessageBox( { type: 'warning', @@ -193,6 +240,15 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } } + async refresh (): Promise { + const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') + 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' || !a.editable ? 1 : 0) - (b.id === 'built-in' || !b.editable ? 1 : 0)) + groups.sort((a, b) => (a.id === 'ungrouped' ? 0 : 1) - (b.id === 'ungrouped' ? 0 : 1)) + this.profileGroups = groups.map(g => ProfilesSettingsTabComponent.intoPartialCollapsableProfileGroup(g, profileGroupCollapsed[g.id] ?? false)) + } + isGroupVisible (group: PartialProfileGroup): boolean { return !this.filter || (group.profiles ?? []).some(x => this.isProfileVisible(x)) } @@ -223,6 +279,9 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } toggleGroupCollapse (group: PartialProfileGroup): void { + if (group.profiles?.length === 0) { + return + } group.collapsed = !group.collapsed this.saveProfileGroupCollapse(group) } @@ -236,7 +295,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { model.type = provider.id modal.componentInstance.profile = Object.assign({}, model) modal.componentInstance.profileProvider = provider - modal.componentInstance.defaultsMode = true + modal.componentInstance.defaultsMode = 'enabled' const result = await modal.result // Fully replace the config diff --git a/tabby-settings/src/index.ts b/tabby-settings/src/index.ts index 850b8d61..e6ae3a1e 100644 --- a/tabby-settings/src/index.ts +++ b/tabby-settings/src/index.ts @@ -7,6 +7,7 @@ import { InfiniteScrollModule } from 'ngx-infinite-scroll' import TabbyCorePlugin, { ToolbarButtonProvider, HotkeyProvider, ConfigProvider, HotkeysService, AppService } from 'tabby-core' import { EditProfileModalComponent } from './components/editProfileModal.component' +import { EditProfileGroupModalComponent } from './components/editProfileGroupModal.component' import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component' import { HotkeySettingsTabComponent } from './components/hotkeySettingsTab.component' import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component' @@ -48,6 +49,7 @@ import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabP ], declarations: [ EditProfileModalComponent, + EditProfileGroupModalComponent, HotkeyInputModalComponent, HotkeySettingsTabComponent, MultiHotkeyInputComponent, From c108476262770c3661d7ac884cf32902cc5d85ee Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 12 Aug 2023 17:08:37 +0200 Subject: [PATCH 25/38] ref(settings): editProfileModal component remove unused ConfigService --- tabby-settings/src/components/editProfileModal.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tabby-settings/src/components/editProfileModal.component.ts b/tabby-settings/src/components/editProfileModal.component.ts index e193aa94..a3dea3a0 100644 --- a/tabby-settings/src/components/editProfileModal.component.ts +++ b/tabby-settings/src/components/editProfileModal.component.ts @@ -2,7 +2,7 @@ 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, PartialProfileGroup, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService, TAB_COLORS, ProfileGroup } from 'tabby-core' +import { ConfigProxy, PartialProfileGroup, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService, TAB_COLORS, ProfileGroup } from 'tabby-core' const iconsData = require('../../../tabby-core/src/icons.json') const iconsClassList = Object.keys(iconsData).map( @@ -31,7 +31,6 @@ export class EditProfileModalComponent

{ private injector: Injector, private componentFactoryResolver: ComponentFactoryResolver, private profilesService: ProfilesService, - config: ConfigService, private modalInstance: NgbActiveModal, ) { if (this.defaultsMode === 'disabled') { From f369b140c6a0f9cbbf0d2e477f085189654ebd66 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Sat, 12 Aug 2023 17:30:35 +0200 Subject: [PATCH 26/38] feat(settings): Eugeny/tabby#7265 add a button to reset global & groups settings to defaults --- .../editProfileGroupModal.component.pug | 5 ++++- .../editProfileGroupModal.component.ts | 22 ++++++++++++++++++- .../profilesSettingsTab.component.pug | 5 ++++- .../profilesSettingsTab.component.ts | 18 +++++++++++++++ 4 files changed, 47 insertions(+), 3 deletions(-) diff --git a/tabby-settings/src/components/editProfileGroupModal.component.pug b/tabby-settings/src/components/editProfileGroupModal.component.pug index 54468a3b..c5864fdd 100644 --- a/tabby-settings/src/components/editProfileGroupModal.component.pug +++ b/tabby-settings/src/components/editProfileGroupModal.component.pug @@ -19,10 +19,13 @@ .description(translate) These apply to all profiles of a given type in this group .list-group.mt-3.mb-3.content-box - a.list-group-item.list-group-item-action( + a.list-group-item.list-group-item-action.d-flex.align-items-center( (click)='editDefaults(provider)', *ngFor='let provider of providers' ) {{provider.name|translate}} + .me-auto + button.btn.btn-link.hover-reveal.ms-1((click)='$event.stopPropagation(); deleteDefaults(provider)') + i.fas.fa-trash-arrow-up .modal-footer button.btn.btn-primary((click)='save()', translate) Save diff --git a/tabby-settings/src/components/editProfileGroupModal.component.ts b/tabby-settings/src/components/editProfileGroupModal.component.ts index fd00b026..e1cec231 100644 --- a/tabby-settings/src/components/editProfileGroupModal.component.ts +++ b/tabby-settings/src/components/editProfileGroupModal.component.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import { Component, Input } from '@angular/core' import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap' -import { ConfigProxy, ProfileGroup, Profile, ProfileProvider } from 'tabby-core' +import { ConfigProxy, ProfileGroup, Profile, ProfileProvider, PlatformService, TranslateService } from 'tabby-core' /** @hidden */ @Component({ @@ -13,6 +13,8 @@ export class EditProfileGroupModalComponent { constructor ( private modalInstance: NgbActiveModal, + private platform: PlatformService, + private translate: TranslateService, ) {} save () { @@ -26,6 +28,24 @@ export class EditProfileGroupModalComponent { editDefaults (provider: ProfileProvider) { this.modalInstance.close({ group: this.group, provider }) } + + async deleteDefaults (provider: ProfileProvider): Promise { + if ((await this.platform.showMessageBox( + { + type: 'warning', + message: this.translate.instant('Restore settings to inherited defaults ?'), + buttons: [ + this.translate.instant('Delete'), + this.translate.instant('Keep'), + ], + defaultId: 1, + cancelId: 1, + }, + )).response === 0) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete this.group.defaults?.[provider.id] + } + } } export interface EditProfileGroupModalComponentResult { diff --git a/tabby-settings/src/components/profilesSettingsTab.component.pug b/tabby-settings/src/components/profilesSettingsTab.component.pug index ed61b23d..f6d60b1a 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.pug +++ b/tabby-settings/src/components/profilesSettingsTab.component.pug @@ -177,9 +177,12 @@ ul.nav-tabs(ngbNav, #nav='ngbNav') .description(translate) These apply to all profiles of a given type .list-group.mt-3.mb-3.content-box - a.list-group-item.list-group-item-action( + a.list-group-item.list-group-item-action.d-flex.align-items-center( (click)='editDefaults(provider)', *ngFor='let provider of profileProviders' ) {{provider.name|translate}} + .me-auto + button.btn.btn-link.hover-reveal.ms-1((click)='$event.stopPropagation(); deleteDefaults(provider)') + i.fas.fa-trash-arrow-up div([ngbNavOutlet]='nav') diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 41b3e443..7a0b537d 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -308,6 +308,24 @@ export class ProfilesSettingsTabComponent extends BaseComponent { await this.config.save() } + async deleteDefaults (provider: ProfileProvider): Promise { + if ((await this.platform.showMessageBox( + { + type: 'warning', + message: this.translate.instant('Restore settings to defaults ?'), + buttons: [ + this.translate.instant('Delete'), + this.translate.instant('Keep'), + ], + defaultId: 1, + cancelId: 1, + }, + )).response === 0) { + this.profilesService.setProviderDefaults(provider, {}) + await this.config.save() + } + } + blacklistProfile (profile: PartialProfile): void { this.config.store.profileBlacklist = [...this.config.store.profileBlacklist, profile.id] this.config.save() From 21e38c845385ad126fe821a5ff670625e04d9ef1 Mon Sep 17 00:00:00 2001 From: Clem Date: Mon, 14 Aug 2023 14:14:57 +0200 Subject: [PATCH 27/38] ref(core/settings/serial/ssh/telnet): create ConnectableProfile & ConnectableProfileProvider Eugeny/tabby#8416 --- tabby-core/src/api/index.ts | 2 +- tabby-core/src/api/profileProvider.ts | 10 ++++-- tabby-core/src/index.ts | 4 +-- tabby-core/src/services/profiles.service.ts | 32 ++++++++++--------- tabby-serial/src/api.ts | 4 +-- tabby-serial/src/profiles.ts | 4 +-- .../components/editProfileModal.component.pug | 2 +- .../components/editProfileModal.component.ts | 7 +++- .../profilesSettingsTab.component.ts | 4 +-- tabby-ssh/src/api/interfaces.ts | 4 +-- tabby-ssh/src/components/sshTab.component.ts | 2 +- tabby-ssh/src/profiles.ts | 5 ++- .../src/services/sshMultiplexer.service.ts | 2 +- tabby-telnet/src/profiles.ts | 4 +-- tabby-telnet/src/session.ts | 4 +-- .../api/connectableTerminalTab.component.ts | 4 +-- tabby-terminal/src/api/interfaces.ts | 4 ++- 17 files changed, 56 insertions(+), 42 deletions(-) diff --git a/tabby-core/src/api/index.ts b/tabby-core/src/api/index.ts index 0384396d..f74a647b 100644 --- a/tabby-core/src/api/index.ts +++ b/tabby-core/src/api/index.ts @@ -16,7 +16,7 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess' export { HostWindowService } from './hostWindow' export { HostAppService, Platform } from './hostApp' export { FileProvider } from './fileProvider' -export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent } from './profileProvider' +export { ProfileProvider, ConnectableProfileProvider, Profile, ConnectableProfile, PartialProfile, ProfileSettingsComponent } from './profileProvider' export { PromptModalComponent } from '../components/promptModal.component' export * from './commands' diff --git a/tabby-core/src/api/profileProvider.ts b/tabby-core/src/api/profileProvider.ts index a6e6bdd6..31fa1f75 100644 --- a/tabby-core/src/api/profileProvider.ts +++ b/tabby-core/src/api/profileProvider.ts @@ -21,6 +21,9 @@ export interface Profile { isTemplate: boolean } +export interface ConnectableProfile extends Profile { +} + export type PartialProfile = Omit, 'type'>, 'name'> & { @@ -39,7 +42,6 @@ export interface ProfileSettingsComponent

{ export abstract class ProfileProvider

{ id: string name: string - supportsQuickConnect = false settingsComponent?: new (...args: any[]) => ProfileSettingsComponent

configDefaults = {} @@ -53,6 +55,11 @@ export abstract class ProfileProvider

{ abstract getDescription (profile: PartialProfile

): string + deleteProfile (profile: P): void { } +} + +export abstract class ConnectableProfileProvider

extends ProfileProvider

{ + quickConnect (query: string): PartialProfile

|null { return null } @@ -61,5 +68,4 @@ export abstract class ProfileProvider

{ return null } - deleteProfile (profile: P): void { } } diff --git a/tabby-core/src/index.ts b/tabby-core/src/index.ts index 6cf14a24..c6974784 100644 --- a/tabby-core/src/index.ts +++ b/tabby-core/src/index.ts @@ -37,7 +37,7 @@ import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive' import { DropZoneDirective } from './directives/dropZone.directive' import { CdkAutoDropGroup } from './directives/cdkAutoDropGroup.directive' -import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ProfilesService, ProfileProvider, SelectorOption, Profile, SelectorService, CommandProvider } from './api' +import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ProfilesService, ProfileProvider, ConnectableProfileProvider, SelectorOption, Profile, SelectorService, CommandProvider } from './api' import { AppService } from './services/app.service' import { ConfigService } from './services/config.service' @@ -214,7 +214,7 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex callback: () => this.profilesService.openNewTabForProfile(p), })) - if (provider.supportsQuickConnect) { + if (provider instanceof ConnectableProfileProvider) { options.push({ name: this.translate.instant('Quick connect'), freeInputPattern: this.translate.instant('Connect to "%s"...'), diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 943d11c2..931501f3 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@angular/core' import { TranslateService } from '@ngx-translate/core' import { NewTabParameters } from './tabs.service' import { BaseTabComponent } from '../components/baseTab.component' -import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider' +import { ConnectableProfileProvider, PartialProfile, Profile, ProfileProvider } from '../api/profileProvider' import { SelectorOption } from '../api/selector' import { AppService } from './app.service' import { configMerge, ConfigProxy, ConfigService } from './config.service' @@ -93,7 +93,7 @@ export class ProfilesService { selectorOptionForProfile

(profile: PartialProfile

): SelectorOption { const fullProfile = this.getConfigProxyForProfile(profile) const provider = this.providerForProfile(fullProfile) - const freeInputEquivalent = provider?.intoQuickConnectString(fullProfile) ?? undefined + const freeInputEquivalent = provider instanceof ConnectableProfileProvider ? provider.intoQuickConnectString(fullProfile) ?? undefined : undefined return { ...profile, // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing @@ -177,18 +177,20 @@ export class ProfilesService { }) } catch { } - this.getProviders().filter(x => x.supportsQuickConnect).forEach(provider => { - options.push({ - name: this.translate.instant('Quick connect'), - freeInputPattern: this.translate.instant('Connect to "%s"...'), - description: `(${provider.name.toUpperCase()})`, - icon: 'fas fa-arrow-right', - weight: provider.id !== this.config.store.defaultQuickConnectProvider ? 1 : 0, - callback: query => { - const profile = provider.quickConnect(query) - resolve(profile) - }, - }) + this.getProviders().forEach(provider => { + if (provider instanceof ConnectableProfileProvider) { + options.push({ + name: this.translate.instant('Quick connect'), + freeInputPattern: this.translate.instant('Connect to "%s"...'), + description: `(${provider.name.toUpperCase()})`, + icon: 'fas fa-arrow-right', + weight: provider.id !== this.config.store.defaultQuickConnectProvider ? 1 : 0, + callback: query => { + const profile = provider.quickConnect(query) + resolve(profile) + }, + }) + } }) await this.selector.show(this.translate.instant('Select profile or enter an address'), options) @@ -200,7 +202,7 @@ export class ProfilesService { async quickConnect (query: string): Promise|null> { for (const provider of this.getProviders()) { - if (provider.supportsQuickConnect) { + if (provider instanceof ConnectableProfileProvider) { const profile = provider.quickConnect(query) if (profile) { return profile diff --git a/tabby-serial/src/api.ts b/tabby-serial/src/api.ts index 64d46e68..bc9d741c 100644 --- a/tabby-serial/src/api.ts +++ b/tabby-serial/src/api.ts @@ -3,10 +3,10 @@ import { SerialPortStream } from '@serialport/stream' import { LogService, NotificationsService } from 'tabby-core' import { Subject, Observable } from 'rxjs' import { Injector, NgZone } from '@angular/core' -import { BaseSession, BaseTerminalProfile, InputProcessingOptions, InputProcessor, LoginScriptsOptions, SessionMiddleware, StreamProcessingOptions, TerminalStreamProcessor, UTF8SplitterMiddleware } from 'tabby-terminal' +import { BaseSession, ConnectableTerminalProfile, InputProcessingOptions, InputProcessor, LoginScriptsOptions, SessionMiddleware, StreamProcessingOptions, TerminalStreamProcessor, UTF8SplitterMiddleware } from 'tabby-terminal' import { SerialService } from './services/serial.service' -export interface SerialProfile extends BaseTerminalProfile { +export interface SerialProfile extends ConnectableTerminalProfile { options: SerialProfileOptions } diff --git a/tabby-serial/src/profiles.ts b/tabby-serial/src/profiles.ts index c6c5d5bf..cf31c229 100644 --- a/tabby-serial/src/profiles.ts +++ b/tabby-serial/src/profiles.ts @@ -2,14 +2,14 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker' import slugify from 'slugify' import deepClone from 'clone-deep' import { Injectable } from '@angular/core' -import { ProfileProvider, NewTabParameters, SelectorService, HostAppService, Platform, TranslateService } from 'tabby-core' +import { NewTabParameters, SelectorService, HostAppService, Platform, TranslateService, ConnectableProfileProvider } from 'tabby-core' import { SerialProfileSettingsComponent } from './components/serialProfileSettings.component' import { SerialTabComponent } from './components/serialTab.component' import { SerialService } from './services/serial.service' import { BAUD_RATES, SerialProfile } from './api' @Injectable({ providedIn: 'root' }) -export class SerialProfilesService extends ProfileProvider { +export class SerialProfilesService extends ConnectableProfileProvider { id = 'serial' name = _('Serial') settingsComponent = SerialProfileSettingsComponent diff --git a/tabby-settings/src/components/editProfileModal.component.pug b/tabby-settings/src/components/editProfileModal.component.pug index 7869099b..da86efdb 100644 --- a/tabby-settings/src/components/editProfileModal.component.pug +++ b/tabby-settings/src/components/editProfileModal.component.pug @@ -74,7 +74,7 @@ ) option(ngValue='auto', translate) Auto option(ngValue='keep', translate) Keep - option(*ngIf='profile.type == "serial" || profile.type == "telnet" || profile.type == "ssh"', ngValue='reconnect', translate) Reconnect + option(*ngIf='isConnectable()', ngValue='reconnect', translate) Reconnect option(ngValue='close', translate) Close .mb-4 diff --git a/tabby-settings/src/components/editProfileModal.component.ts b/tabby-settings/src/components/editProfileModal.component.ts index 90636672..e259a39a 100644 --- a/tabby-settings/src/components/editProfileModal.component.ts +++ b/tabby-settings/src/components/editProfileModal.component.ts @@ -2,7 +2,7 @@ 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, Profile, ProfileProvider, ProfileSettingsComponent, ProfilesService, TAB_COLORS, ConnectableProfileProvider } from 'tabby-core' const iconsData = require('../../../tabby-core/src/icons.json') const iconsClassList = Object.keys(iconsData).map( @@ -95,4 +95,9 @@ export class EditProfileModalComponent

{ cancel () { this.modalInstance.dismiss() } + + isConnectable (): boolean { + return this.profileProvider instanceof ConnectableProfileProvider + } + } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 38898ae6..1ed3963b 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 } from 'tabby-core' +import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider, TranslateService, Platform, AppHotkeyProvider, ConnectableProfileProvider } from 'tabby-core' import { EditProfileModalComponent } from './editProfileModal.component' interface ProfileGroup { @@ -314,6 +314,6 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } getQuickConnectProviders (): ProfileProvider[] { - return this.profileProviders.filter(x => x.supportsQuickConnect) + return this.profileProviders.filter(x => x instanceof ConnectableProfileProvider) } } diff --git a/tabby-ssh/src/api/interfaces.ts b/tabby-ssh/src/api/interfaces.ts index 901d5dd1..07a77a5c 100644 --- a/tabby-ssh/src/api/interfaces.ts +++ b/tabby-ssh/src/api/interfaces.ts @@ -1,4 +1,4 @@ -import { BaseTerminalProfile, InputProcessingOptions, LoginScriptsOptions } from 'tabby-terminal' +import { ConnectableTerminalProfile, InputProcessingOptions, LoginScriptsOptions } from 'tabby-terminal' export enum SSHAlgorithmType { HMAC = 'hmac', @@ -7,7 +7,7 @@ export enum SSHAlgorithmType { HOSTKEY = 'serverHostKey', } -export interface SSHProfile extends BaseTerminalProfile { +export interface SSHProfile extends ConnectableTerminalProfile { options: SSHProfileOptions } diff --git a/tabby-ssh/src/components/sshTab.component.ts b/tabby-ssh/src/components/sshTab.component.ts index b6c6d3eb..e7fd7815 100644 --- a/tabby-ssh/src/components/sshTab.component.ts +++ b/tabby-ssh/src/components/sshTab.component.ts @@ -83,7 +83,7 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent const jumpSession = await this.setupOneSession( this.injector, - this.profilesService.getConfigProxyForProfile(jumpConnection), + this.profilesService.getConfigProxyForProfile(jumpConnection), ) jumpSession.ref() diff --git a/tabby-ssh/src/profiles.ts b/tabby-ssh/src/profiles.ts index 0952e713..0694de7e 100644 --- a/tabby-ssh/src/profiles.ts +++ b/tabby-ssh/src/profiles.ts @@ -1,5 +1,5 @@ import { Injectable, InjectFlags, Injector } from '@angular/core' -import { ProfileProvider, NewTabParameters, PartialProfile, TranslateService } from 'tabby-core' +import { NewTabParameters, PartialProfile, TranslateService, ConnectableProfileProvider } from 'tabby-core' import * as ALGORITHMS from 'ssh2/lib/protocol/constants' import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component' import { SSHTabComponent } from './components/sshTab.component' @@ -8,10 +8,9 @@ import { ALGORITHM_BLACKLIST, SSHAlgorithmType, SSHProfile } from './api' import { SSHProfileImporter } from './api/importer' @Injectable({ providedIn: 'root' }) -export class SSHProfilesService extends ProfileProvider { +export class SSHProfilesService extends ConnectableProfileProvider { id = 'ssh' name = 'SSH' - supportsQuickConnect = true settingsComponent = SSHProfileSettingsComponent configDefaults = { options: { diff --git a/tabby-ssh/src/services/sshMultiplexer.service.ts b/tabby-ssh/src/services/sshMultiplexer.service.ts index 2665187e..775e20fa 100644 --- a/tabby-ssh/src/services/sshMultiplexer.service.ts +++ b/tabby-ssh/src/services/sshMultiplexer.service.ts @@ -34,7 +34,7 @@ export class SSHMultiplexerService { if (!jumpConnection) { return key } - const jumpProfile = this.profilesService.getConfigProxyForProfile(jumpConnection) + const jumpProfile = this.profilesService.getConfigProxyForProfile(jumpConnection) key += '$' + await this.getMultiplexerKey(jumpProfile) } return key diff --git a/tabby-telnet/src/profiles.ts b/tabby-telnet/src/profiles.ts index fad4f8ce..a88ea0a2 100644 --- a/tabby-telnet/src/profiles.ts +++ b/tabby-telnet/src/profiles.ts @@ -1,11 +1,11 @@ import { Injectable } from '@angular/core' -import { ProfileProvider, NewTabParameters, PartialProfile, TranslateService } from 'tabby-core' +import { NewTabParameters, PartialProfile, TranslateService, ConnectableProfileProvider } from 'tabby-core' import { TelnetProfileSettingsComponent } from './components/telnetProfileSettings.component' import { TelnetTabComponent } from './components/telnetTab.component' import { TelnetProfile } from './session' @Injectable({ providedIn: 'root' }) -export class TelnetProfilesService extends ProfileProvider { +export class TelnetProfilesService extends ConnectableProfileProvider { id = 'telnet' name = 'Telnet' supportsQuickConnect = true diff --git a/tabby-telnet/src/session.ts b/tabby-telnet/src/session.ts index 27c2cc27..1200d1c3 100644 --- a/tabby-telnet/src/session.ts +++ b/tabby-telnet/src/session.ts @@ -3,11 +3,11 @@ import colors from 'ansi-colors' import stripAnsi from 'strip-ansi' import { Injector } from '@angular/core' import { LogService } from 'tabby-core' -import { BaseSession, BaseTerminalProfile, InputProcessingOptions, InputProcessor, LoginScriptsOptions, SessionMiddleware, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal' +import { BaseSession, ConnectableTerminalProfile, InputProcessingOptions, InputProcessor, LoginScriptsOptions, SessionMiddleware, StreamProcessingOptions, TerminalStreamProcessor } from 'tabby-terminal' import { Subject, Observable } from 'rxjs' -export interface TelnetProfile extends BaseTerminalProfile { +export interface TelnetProfile extends ConnectableTerminalProfile { options: TelnetProfileOptions } diff --git a/tabby-terminal/src/api/connectableTerminalTab.component.ts b/tabby-terminal/src/api/connectableTerminalTab.component.ts index 97560ec1..21abaa91 100644 --- a/tabby-terminal/src/api/connectableTerminalTab.component.ts +++ b/tabby-terminal/src/api/connectableTerminalTab.component.ts @@ -4,7 +4,7 @@ import { Injector, Component } from '@angular/core' import { first } from 'rxjs' -import { BaseTerminalProfile } from './interfaces' +import { ConnectableTerminalProfile } from './interfaces' import { BaseTerminalTabComponent } from './baseTerminalTab.component' import { GetRecoveryTokenOptions, RecoveryToken } from 'tabby-core' @@ -13,7 +13,7 @@ import { GetRecoveryTokenOptions, RecoveryToken } from 'tabby-core' * A class to base your custom connectable terminal tabs on */ @Component({ template: '' }) -export abstract class ConnectableTerminalTabComponent

extends BaseTerminalTabComponent

{ +export abstract class ConnectableTerminalTabComponent

extends BaseTerminalTabComponent

{ protected reconnectOffered = false protected isDisconnectedByHand = false diff --git a/tabby-terminal/src/api/interfaces.ts b/tabby-terminal/src/api/interfaces.ts index 74143339..eb61a8f1 100644 --- a/tabby-terminal/src/api/interfaces.ts +++ b/tabby-terminal/src/api/interfaces.ts @@ -1,4 +1,4 @@ -import { Profile } from 'tabby-core' +import { ConnectableProfile, Profile } from 'tabby-core' export interface ResizeEvent { columns: number @@ -19,3 +19,5 @@ export interface TerminalColorScheme { export interface BaseTerminalProfile extends Profile { terminalColorScheme?: TerminalColorScheme } + +export interface ConnectableTerminalProfile extends BaseTerminalProfile, ConnectableProfile {} From ef6b8a4eaa1ce4cf0311649d009d1a8a3e66a71c Mon Sep 17 00:00:00 2001 From: Clem Date: Mon, 14 Aug 2023 15:24:41 +0200 Subject: [PATCH 28/38] feat(core/settings/serial/ssh/telnet): add clearServiceMessagesOnConnect option on connectable profile --- tabby-core/src/api/profileProvider.ts | 1 + tabby-serial/src/profiles.ts | 1 + .../src/components/editProfileModal.component.pug | 8 +++++++- tabby-ssh/src/profiles.ts | 1 + tabby-telnet/src/profiles.ts | 1 + .../src/api/connectableTerminalTab.component.ts | 3 +++ 6 files changed, 14 insertions(+), 1 deletion(-) diff --git a/tabby-core/src/api/profileProvider.ts b/tabby-core/src/api/profileProvider.ts index 31fa1f75..37b11fb1 100644 --- a/tabby-core/src/api/profileProvider.ts +++ b/tabby-core/src/api/profileProvider.ts @@ -22,6 +22,7 @@ export interface Profile { } export interface ConnectableProfile extends Profile { + clearServiceMessagesOnConnect: boolean } export type PartialProfile = Omit { reuseSession: true, input: { backspace: 'backspace' }, }, + clearServiceMessagesOnConnect: true, } constructor ( diff --git a/tabby-telnet/src/profiles.ts b/tabby-telnet/src/profiles.ts index a88ea0a2..0c247e8a 100644 --- a/tabby-telnet/src/profiles.ts +++ b/tabby-telnet/src/profiles.ts @@ -21,6 +21,7 @@ export class TelnetProfilesService extends ConnectableProfileProvider { this.reconnectOffered = false this.isDisconnectedByHand = false + if (this.profile.clearServiceMessagesOnConnect) { + this.frontend?.clear() + } } /** From 5eeaef954c8f5f3e8a60c98a8b055a718d16423a Mon Sep 17 00:00:00 2001 From: Clem Date: Mon, 14 Aug 2023 15:53:58 +0200 Subject: [PATCH 29/38] ref(settings/ssh): migrate and remove deprecated clearServiceMessagesOnConnect ssh option --- tabby-core/src/services/config.service.ts | 7 +++++++ tabby-ssh/src/components/sshSettingsTab.component.pug | 8 -------- tabby-ssh/src/components/sshTab.component.ts | 4 ---- tabby-ssh/src/config.ts | 1 - 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/tabby-core/src/services/config.service.ts b/tabby-core/src/services/config.service.ts index daa0d746..b4c884ed 100644 --- a/tabby-core/src/services/config.service.ts +++ b/tabby-core/src/services/config.service.ts @@ -364,6 +364,13 @@ export class ConfigService { } config.version = 4 } + if (config.version < 6) { + if (config.ssh.clearServiceMessagesOnConnect === false) { + config.profileDefaults.ssh.clearServiceMessagesOnConnect = false + delete config.ssh?.clearServiceMessagesOnConnect + } + config.version = 6 + } } private async maybeDecryptConfig (store) { diff --git a/tabby-ssh/src/components/sshSettingsTab.component.pug b/tabby-ssh/src/components/sshSettingsTab.component.pug index 2ff88f71..fd50f44f 100644 --- a/tabby-ssh/src/components/sshSettingsTab.component.pug +++ b/tabby-ssh/src/components/sshSettingsTab.component.pug @@ -61,12 +61,4 @@ h3 SSH (ngModelChange)='config.save()' ) -.form-line - .header - .title(translate) Clear terminal after connection - toggle( - [(ngModel)]='config.store.ssh.clearServiceMessagesOnConnect', - (ngModelChange)='config.save()', - ) - .alert.alert-info(translate) SSH connection management is now done through the "Profiles & connections" tab diff --git a/tabby-ssh/src/components/sshTab.component.ts b/tabby-ssh/src/components/sshTab.component.ts index e7fd7815..738862dd 100644 --- a/tabby-ssh/src/components/sshTab.component.ts +++ b/tabby-ssh/src/components/sshTab.component.ts @@ -163,10 +163,6 @@ export class SSHTabComponent extends ConnectableTerminalTabComponent await session.start() - if (this.config.store.ssh.clearServiceMessagesOnConnect) { - this.frontend?.clear() - } - this.session?.resize(this.size.columns, this.size.rows) } diff --git a/tabby-ssh/src/config.ts b/tabby-ssh/src/config.ts index 82a35cdc..8c32ba89 100644 --- a/tabby-ssh/src/config.ts +++ b/tabby-ssh/src/config.ts @@ -11,7 +11,6 @@ export class SSHConfigProvider extends ConfigProvider { x11Display: null, knownHosts: [], verifyHostKeys: true, - clearServiceMessagesOnConnect: true, }, hotkeys: { 'restart-ssh-session': [], From d21282501fd8ff6d6cb03b86517dafe1d8c2821a Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Tue, 15 Aug 2023 19:53:00 +0200 Subject: [PATCH 30/38] fix: avoid error Uncaught (in promise) on modal dismiss --- tabby-core/src/services/vault.service.ts | 2 +- tabby-core/src/tabContextMenu.ts | 2 +- .../configSyncSettingsTab.component.ts | 2 +- .../profilesSettingsTab.component.ts | 21 ++++++++++--------- .../components/vaultSettingsTab.component.ts | 16 ++++++++------ .../src/components/sftpPanel.component.ts | 4 ++-- .../sshProfileSettings.component.ts | 14 +++++++------ tabby-ssh/src/session/ssh.ts | 11 +++------- tabby-ssh/src/sftpContextMenu.ts | 2 +- tabby-terminal/src/tabContextMenu.ts | 2 +- 10 files changed, 39 insertions(+), 37 deletions(-) diff --git a/tabby-core/src/services/vault.service.ts b/tabby-core/src/services/vault.service.ts index bcc507b3..8602597e 100644 --- a/tabby-core/src/services/vault.service.ts +++ b/tabby-core/src/services/vault.service.ts @@ -285,7 +285,7 @@ export class VaultFileProvider extends FileProvider { icon: 'fas fa-file', result: f, })), - ]) + ]).catch(() => null) if (result) { return `${this.prefix}${result.key.id}` } diff --git a/tabby-core/src/tabContextMenu.ts b/tabby-core/src/tabContextMenu.ts index 31ee3e1f..56c3c904 100644 --- a/tabby-core/src/tabContextMenu.ts +++ b/tabby-core/src/tabContextMenu.ts @@ -149,7 +149,7 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider { click: async () => { const modal = this.ngbModal.open(PromptModalComponent) modal.componentInstance.prompt = this.translate.instant('Profile name') - const name = (await modal.result)?.value + const name = (await modal.result.catch(() => null))?.value if (!name) { return } diff --git a/tabby-settings/src/components/configSyncSettingsTab.component.ts b/tabby-settings/src/components/configSyncSettingsTab.component.ts index 8e41f624..0cb9c3e3 100644 --- a/tabby-settings/src/components/configSyncSettingsTab.component.ts +++ b/tabby-settings/src/components/configSyncSettingsTab.component.ts @@ -59,7 +59,7 @@ export class ConfigSyncSettingsTabComponent extends BaseComponent { const modal = this.ngbModal.open(PromptModalComponent) modal.componentInstance.prompt = this.translate.instant('Name for the new config') modal.componentInstance.value = name - name = (await modal.result)?.value + name = (await modal.result.catch(() => null))?.value if (!name) { return } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 41fb3e27..1e89b3e9 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -144,7 +144,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { async newProfileGroup (): Promise { const modal = this.ngbModal.open(PromptModalComponent) modal.componentInstance.prompt = this.translate.instant('New group name') - const result = await modal.result + const result = await modal.result.catch(() => null) if (result?.value.trim()) { await this.profilesService.newProfileGroup({ id: '', name: result.value }) } @@ -296,16 +296,17 @@ export class ProfilesSettingsTabComponent extends BaseComponent { modal.componentInstance.profile = Object.assign({}, model) modal.componentInstance.profileProvider = provider modal.componentInstance.defaultsMode = 'enabled' - const result = await modal.result - - // Fully replace the config - for (const k in model) { - // eslint-disable-next-line @typescript-eslint/no-dynamic-delete - delete model[k] + const result = await modal.result.catch(() => null) + if (result) { + // Fully replace the config + for (const k in model) { + // eslint-disable-next-line @typescript-eslint/no-dynamic-delete + delete model[k] + } + Object.assign(model, result) + this.profilesService.setProviderDefaults(provider, model) + await this.config.save() } - Object.assign(model, result) - this.profilesService.setProviderDefaults(provider, model) - await this.config.save() } async deleteDefaults (provider: ProfileProvider): Promise { diff --git a/tabby-settings/src/components/vaultSettingsTab.component.ts b/tabby-settings/src/components/vaultSettingsTab.component.ts index b34fa8d6..8f81b985 100644 --- a/tabby-settings/src/components/vaultSettingsTab.component.ts +++ b/tabby-settings/src/components/vaultSettingsTab.component.ts @@ -35,9 +35,11 @@ export class VaultSettingsTabComponent extends BaseComponent { async enableVault () { const modal = this.ngbModal.open(SetVaultPassphraseModalComponent) - const newPassphrase = await modal.result - await this.vault.setEnabled(true, newPassphrase) - this.vaultContents = await this.vault.load(newPassphrase) + const newPassphrase = await modal.result.catch(() => null) + if (newPassphrase) { + await this.vault.setEnabled(true, newPassphrase) + this.vaultContents = await this.vault.load(newPassphrase) + } } async disableVault () { @@ -65,8 +67,10 @@ export class VaultSettingsTabComponent extends BaseComponent { return } const modal = this.ngbModal.open(SetVaultPassphraseModalComponent) - const newPassphrase = await modal.result - this.vault.save(this.vaultContents, newPassphrase) + const newPassphrase = await modal.result.catch(() => null) + if (newPassphrase) { + this.vault.save(this.vaultContents, newPassphrase) + } } async toggleConfigEncrypted () { @@ -118,7 +122,7 @@ export class VaultSettingsTabComponent extends BaseComponent { modal.componentInstance.prompt = this.translate.instant('New name') modal.componentInstance.value = secret.key.description - const description = (await modal.result)?.value + const description = (await modal.result.catch(() => null))?.value if (!description) { return } diff --git a/tabby-ssh/src/components/sftpPanel.component.ts b/tabby-ssh/src/components/sftpPanel.component.ts index f0bcfbef..b24134d6 100644 --- a/tabby-ssh/src/components/sftpPanel.component.ts +++ b/tabby-ssh/src/components/sftpPanel.component.ts @@ -113,8 +113,8 @@ export class SFTPPanelComponent { async openCreateDirectoryModal (): Promise { const modal = this.ngbModal.open(SFTPCreateDirectoryModalComponent) - const directoryName = await modal.result - if (directoryName !== '') { + const directoryName = await modal.result.catch(() => null) + if (directoryName?.trim()) { this.sftp.mkdir(path.join(this.path, directoryName)).then(() => { this.notifications.notice('The directory was created successfully') this.navigate(path.join(this.path, directoryName)) diff --git a/tabby-ssh/src/components/sshProfileSettings.component.ts b/tabby-ssh/src/components/sshProfileSettings.component.ts index d2891e51..59f148d8 100644 --- a/tabby-ssh/src/components/sshProfileSettings.component.ts +++ b/tabby-ssh/src/components/sshProfileSettings.component.ts @@ -75,7 +75,7 @@ export class SSHProfileSettingsComponent { modal.componentInstance.prompt = `Password for ${this.profile.options.user}@${this.profile.options.host}` modal.componentInstance.password = true try { - const result = await modal.result + const result = await modal.result.catch(() => null) if (result?.value) { this.passwordStorage.savePassword(this.profile, result.value) this.hasSavedPassword = true @@ -89,11 +89,13 @@ export class SSHProfileSettingsComponent { } async addPrivateKey () { - const ref = await this.fileProviders.selectAndStoreFile(`private key for ${this.profile.name}`) - this.profile.options.privateKeys = [ - ...this.profile.options.privateKeys!, - ref, - ] + const ref = await this.fileProviders.selectAndStoreFile(`private key for ${this.profile.name}`).catch(() => null) + if (ref) { + this.profile.options.privateKeys = [ + ...this.profile.options.privateKeys!, + ref, + ] + } } removePrivateKey (path: string) { diff --git a/tabby-ssh/src/session/ssh.ts b/tabby-ssh/src/session/ssh.ts index f2931f57..02e77efe 100644 --- a/tabby-ssh/src/session/ssh.ts +++ b/tabby-ssh/src/session/ssh.ts @@ -210,7 +210,6 @@ export class SSHSession { if (!await this.verifyHostKey(handshake)) { this.ssh.end() reject(new Error('Host key verification failed')) - return } this.logger.info('Handshake complete:', handshake) resolve() @@ -300,7 +299,7 @@ export class SSHSession { const modal = this.ngbModal.open(PromptModalComponent) modal.componentInstance.prompt = `Username for ${this.profile.options.host}` try { - const result = await modal.result + const result = await modal.result.catch(() => null) this.authUsername = result?.value ?? null } catch { this.authUsername = 'root' @@ -428,11 +427,7 @@ export class SSHSession { const modal = this.ngbModal.open(HostKeyPromptModalComponent) modal.componentInstance.selector = selector modal.componentInstance.digest = this.hostKeyDigest - try { - return await modal.result - } catch { - return false - } + return modal.result.catch(() => false) } return true } @@ -495,7 +490,7 @@ export class SSHSession { modal.componentInstance.showRememberCheckbox = true try { - const result = await modal.result + const result = await modal.result.catch(() => null) if (result) { if (result.remember) { this.savedPassword = result.value diff --git a/tabby-ssh/src/sftpContextMenu.ts b/tabby-ssh/src/sftpContextMenu.ts index 5a3a8213..490e6ee8 100644 --- a/tabby-ssh/src/sftpContextMenu.ts +++ b/tabby-ssh/src/sftpContextMenu.ts @@ -53,6 +53,6 @@ export class CommonSFTPContextMenu extends SFTPContextMenuItemProvider { const modal = this.ngbModal.open(SFTPDeleteModalComponent) modal.componentInstance.item = item modal.componentInstance.sftp = session - await modal.result + await modal.result.catch(() => {return}) } } diff --git a/tabby-terminal/src/tabContextMenu.ts b/tabby-terminal/src/tabContextMenu.ts index bef7cbba..817837f0 100644 --- a/tabby-terminal/src/tabContextMenu.ts +++ b/tabby-terminal/src/tabContextMenu.ts @@ -175,7 +175,7 @@ export class SaveAsProfileContextMenu extends TabContextMenuItemProvider { const modal = this.ngbModal.open(PromptModalComponent) modal.componentInstance.prompt = this.translate.instant('New profile name') modal.componentInstance.value = tab.profile.name - const name = (await modal.result)?.value + const name = (await modal.result.catch(() => null))?.value if (!name) { return } From a9c63b53059c842e31b52d1226aec78c6372fcc3 Mon Sep 17 00:00:00 2001 From: Clem Fern Date: Tue, 15 Aug 2023 19:53:52 +0200 Subject: [PATCH 31/38] fix(selector): avoid error Uncaught (in promise) on modal dismiss (fix Eugeny/tabby#8065) --- tabby-core/src/index.ts | 2 +- tabby-core/src/services/commands.service.ts | 2 +- tabby-core/src/services/fileProviders.service.ts | 5 +++-- tabby-core/src/services/profiles.service.ts | 2 +- .../src/components/profilesSettingsTab.component.ts | 5 ++++- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tabby-core/src/index.ts b/tabby-core/src/index.ts index ebe1fc44..d0d06789 100644 --- a/tabby-core/src/index.ts +++ b/tabby-core/src/index.ts @@ -229,7 +229,7 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex }) } - await this.selector.show(this.translate.instant('Select profile'), options) + await this.selector.show(this.translate.instant('Select profile'), options).catch(() => {return}) } static forRoot (): ModuleWithProviders { diff --git a/tabby-core/src/services/commands.service.ts b/tabby-core/src/services/commands.service.ts index f858edcb..dddb2135 100644 --- a/tabby-core/src/services/commands.service.ts +++ b/tabby-core/src/services/commands.service.ts @@ -109,6 +109,6 @@ export class CommandService { description: c.sublabel, icon: c.icon, })), - ) + ).catch(() => {return}) } } diff --git a/tabby-core/src/services/fileProviders.service.ts b/tabby-core/src/services/fileProviders.service.ts index d6983b40..3290e842 100644 --- a/tabby-core/src/services/fileProviders.service.ts +++ b/tabby-core/src/services/fileProviders.service.ts @@ -13,8 +13,9 @@ export class FileProvidersService { ) { } async selectAndStoreFile (description: string): Promise { - const p = await this.selectProvider() - return p.selectAndStoreFile(description) + return this.selectProvider().then(p => { + return p.selectAndStoreFile(description) + }) } async retrieveFile (key: string): Promise { diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index f0616a26..2501bca0 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -323,7 +323,7 @@ export class ProfilesService { } }) - await this.selector.show(this.translate.instant('Select profile or enter an address'), options) + await this.selector.show(this.translate.instant('Select profile or enter an address'), options).catch(() => resolve(null)) } catch (err) { reject(err) } diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 1e89b3e9..9a3589dc 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -65,7 +65,10 @@ export class ProfilesSettingsTabComponent extends BaseComponent { name: p.group ? `${this.profilesService.resolveProfileGroupName(p.group)} / ${p.name}` : p.name, result: p, })), - ) + ).catch(() => undefined) + if (!base) { + return + } } const profile: PartialProfile = deepClone(base) delete profile.id From 7687972e658b98e52777fd89449584a7a2d2df78 Mon Sep 17 00:00:00 2001 From: Clem Date: Fri, 18 Aug 2023 17:18:06 +0200 Subject: [PATCH 32/38] ref ef6b8a4eaa1ce4cf0311649d009d1a8a3e66a71c --- tabby-core/src/api/index.ts | 2 +- tabby-core/src/api/profileProvider.ts | 12 +++++------- tabby-core/src/index.ts | 4 ++-- tabby-core/src/services/profiles.service.ts | 8 ++++---- .../src/components/profilesSettingsTab.component.ts | 4 ++-- tabby-ssh/src/profiles.ts | 4 ++-- tabby-telnet/src/profiles.ts | 12 ++++++++++-- 7 files changed, 26 insertions(+), 20 deletions(-) diff --git a/tabby-core/src/api/index.ts b/tabby-core/src/api/index.ts index 5ca9b636..cc467f96 100644 --- a/tabby-core/src/api/index.ts +++ b/tabby-core/src/api/index.ts @@ -16,7 +16,7 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess' export { HostWindowService } from './hostWindow' export { HostAppService, Platform } from './hostApp' export { FileProvider } from './fileProvider' -export { ProfileProvider, ConnectableProfileProvider, Profile, ConnectableProfile, PartialProfile, ProfileSettingsComponent, ProfileGroup, PartialProfileGroup } from './profileProvider' +export { ProfileProvider, ConnectableProfileProvider, QuickConnectProfileProvider, Profile, ConnectableProfile, PartialProfile, ProfileSettingsComponent, ProfileGroup, PartialProfileGroup } from './profileProvider' export { PromptModalComponent } from '../components/promptModal.component' export * from './commands' diff --git a/tabby-core/src/api/profileProvider.ts b/tabby-core/src/api/profileProvider.ts index 01c7b759..ba13b0ff 100644 --- a/tabby-core/src/api/profileProvider.ts +++ b/tabby-core/src/api/profileProvider.ts @@ -74,14 +74,12 @@ export abstract class ProfileProvider

{ deleteProfile (profile: P): void { } } -export abstract class ConnectableProfileProvider

extends ProfileProvider

{ +export abstract class ConnectableProfileProvider

extends ProfileProvider

{} - quickConnect (query: string): PartialProfile

|null { - return null - } +export abstract class QuickConnectProfileProvider

extends ConnectableProfileProvider

{ - intoQuickConnectString (profile: P): string|null { - return null - } + abstract quickConnect (query: string): PartialProfile

|null + + abstract intoQuickConnectString (profile: P): string|null } diff --git a/tabby-core/src/index.ts b/tabby-core/src/index.ts index d0d06789..e376edec 100644 --- a/tabby-core/src/index.ts +++ b/tabby-core/src/index.ts @@ -37,7 +37,7 @@ import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive' import { DropZoneDirective } from './directives/dropZone.directive' import { CdkAutoDropGroup } from './directives/cdkAutoDropGroup.directive' -import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ProfilesService, ProfileProvider, ConnectableProfileProvider, SelectorOption, Profile, SelectorService, CommandProvider } from './api' +import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ProfilesService, ProfileProvider, QuickConnectProfileProvider, SelectorOption, Profile, SelectorService, CommandProvider } from './api' import { AppService } from './services/app.service' import { ConfigService } from './services/config.service' @@ -214,7 +214,7 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex callback: () => this.profilesService.openNewTabForProfile(p), })) - if (provider instanceof ConnectableProfileProvider) { + if (provider instanceof QuickConnectProfileProvider) { options.push({ name: this.translate.instant('Quick connect'), freeInputPattern: this.translate.instant('Connect to "%s"...'), diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 2501bca0..ad6c991e 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -2,7 +2,7 @@ import { Injectable, Inject } from '@angular/core' import { TranslateService } from '@ngx-translate/core' import { NewTabParameters } from './tabs.service' import { BaseTabComponent } from '../components/baseTab.component' -import { ConnectableProfileProvider, PartialProfile, PartialProfileGroup, Profile, ProfileGroup, ProfileProvider } from '../api/profileProvider' +import { QuickConnectProfileProvider, PartialProfile, PartialProfileGroup, Profile, ProfileGroup, ProfileProvider } from '../api/profileProvider' import { SelectorOption } from '../api/selector' import { AppService } from './app.service' import { configMerge, ConfigProxy, ConfigService } from './config.service' @@ -230,7 +230,7 @@ export class ProfilesService { selectorOptionForProfile

(profile: PartialProfile

): SelectorOption { const fullProfile = this.getConfigProxyForProfile(profile) const provider = this.providerForProfile(fullProfile) - const freeInputEquivalent = provider instanceof ConnectableProfileProvider ? provider.intoQuickConnectString(fullProfile) ?? undefined : undefined + const freeInputEquivalent = provider instanceof QuickConnectProfileProvider ? provider.intoQuickConnectString(fullProfile) ?? undefined : undefined return { ...profile, group: this.resolveProfileGroupName(profile.group ?? ''), @@ -308,7 +308,7 @@ export class ProfilesService { } catch { } this.getProviders().forEach(provider => { - if (provider instanceof ConnectableProfileProvider) { + if (provider instanceof QuickConnectProfileProvider) { options.push({ name: this.translate.instant('Quick connect'), freeInputPattern: this.translate.instant('Connect to "%s"...'), @@ -338,7 +338,7 @@ export class ProfilesService { async quickConnect (query: string): Promise|null> { for (const provider of this.getProviders()) { - if (provider instanceof ConnectableProfileProvider) { + if (provider instanceof QuickConnectProfileProvider) { const profile = provider.quickConnect(query) if (profile) { return profile diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 9a3589dc..d9cb446b 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -2,7 +2,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker' 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, ProfileGroup, PartialProfileGroup, ConnectableProfileProvider } from 'tabby-core' +import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider, TranslateService, Platform, ProfileGroup, PartialProfileGroup, QuickConnectProfileProvider } from 'tabby-core' import { EditProfileModalComponent } from './editProfileModal.component' import { EditProfileGroupModalComponent, EditProfileGroupModalComponentResult } from './editProfileGroupModal.component' @@ -345,7 +345,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { } getQuickConnectProviders (): ProfileProvider[] { - return this.profileProviders.filter(x => x instanceof ConnectableProfileProvider) + return this.profileProviders.filter(x => x instanceof QuickConnectProfileProvider) } /** diff --git a/tabby-ssh/src/profiles.ts b/tabby-ssh/src/profiles.ts index 64610e03..36de9ce2 100644 --- a/tabby-ssh/src/profiles.ts +++ b/tabby-ssh/src/profiles.ts @@ -1,5 +1,5 @@ import { Injectable, InjectFlags, Injector } from '@angular/core' -import { NewTabParameters, PartialProfile, TranslateService, ConnectableProfileProvider } from 'tabby-core' +import { NewTabParameters, PartialProfile, TranslateService, QuickConnectProfileProvider } from 'tabby-core' import * as ALGORITHMS from 'ssh2/lib/protocol/constants' import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component' import { SSHTabComponent } from './components/sshTab.component' @@ -8,7 +8,7 @@ import { ALGORITHM_BLACKLIST, SSHAlgorithmType, SSHProfile } from './api' import { SSHProfileImporter } from './api/importer' @Injectable({ providedIn: 'root' }) -export class SSHProfilesService extends ConnectableProfileProvider { +export class SSHProfilesService extends QuickConnectProfileProvider { id = 'ssh' name = 'SSH' settingsComponent = SSHProfileSettingsComponent diff --git a/tabby-telnet/src/profiles.ts b/tabby-telnet/src/profiles.ts index 0c247e8a..212858e9 100644 --- a/tabby-telnet/src/profiles.ts +++ b/tabby-telnet/src/profiles.ts @@ -1,11 +1,11 @@ import { Injectable } from '@angular/core' -import { NewTabParameters, PartialProfile, TranslateService, ConnectableProfileProvider } from 'tabby-core' +import { NewTabParameters, PartialProfile, TranslateService, QuickConnectProfileProvider } from 'tabby-core' import { TelnetProfileSettingsComponent } from './components/telnetProfileSettings.component' import { TelnetTabComponent } from './components/telnetTab.component' import { TelnetProfile } from './session' @Injectable({ providedIn: 'root' }) -export class TelnetProfilesService extends ConnectableProfileProvider { +export class TelnetProfilesService extends QuickConnectProfileProvider { id = 'telnet' name = 'Telnet' supportsQuickConnect = true @@ -96,4 +96,12 @@ export class TelnetProfilesService extends ConnectableProfileProvider Date: Fri, 25 Aug 2023 13:43:43 +0200 Subject: [PATCH 33/38] ref: a9c63b53059c842e31b52d1226aec78c6372fcc3 bad promise rejection handling --- tabby-core/src/commands.ts | 2 +- tabby-core/src/index.ts | 2 +- tabby-core/src/services/commands.service.ts | 4 ++-- tabby-core/src/services/profiles.service.ts | 2 +- tabby-core/src/tabContextMenu.ts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tabby-core/src/commands.ts b/tabby-core/src/commands.ts index 2af7b6b6..88a81b26 100644 --- a/tabby-core/src/commands.ts +++ b/tabby-core/src/commands.ts @@ -18,7 +18,7 @@ export class CoreCommandProvider extends CommandProvider { } async activate () { - const profile = await this.profilesService.showProfileSelector() + const profile = await this.profilesService.showProfileSelector().catch(() => null) if (profile) { this.profilesService.launchProfile(profile) } diff --git a/tabby-core/src/index.ts b/tabby-core/src/index.ts index e376edec..25b5e1ad 100644 --- a/tabby-core/src/index.ts +++ b/tabby-core/src/index.ts @@ -191,7 +191,7 @@ export default class AppModule { // eslint-disable-line @typescript-eslint/no-ex this.showSelector(provider) } if (hotkey === 'command-selector') { - commands.showSelector() + commands.showSelector().catch(() => {return}) } if (hotkey === 'profile-selector') { diff --git a/tabby-core/src/services/commands.service.ts b/tabby-core/src/services/commands.service.ts index dddb2135..739b0b49 100644 --- a/tabby-core/src/services/commands.service.ts +++ b/tabby-core/src/services/commands.service.ts @@ -101,7 +101,7 @@ export class CommandService { context.tab = tab.getFocusedTab() ?? undefined } const commands = await this.getCommands(context) - await this.selector.show( + return this.selector.show( this.translate.instant('Commands'), commands.map(c => ({ name: c.label, @@ -109,6 +109,6 @@ export class CommandService { description: c.sublabel, icon: c.icon, })), - ).catch(() => {return}) + ).then(() => {return}) } } diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index ad6c991e..51ab8acd 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -323,7 +323,7 @@ export class ProfilesService { } }) - await this.selector.show(this.translate.instant('Select profile or enter an address'), options).catch(() => resolve(null)) + await this.selector.show(this.translate.instant('Select profile or enter an address'), options).catch(() => reject()) } catch (err) { reject(err) } diff --git a/tabby-core/src/tabContextMenu.ts b/tabby-core/src/tabContextMenu.ts index 56c3c904..e0ed6d1f 100644 --- a/tabby-core/src/tabContextMenu.ts +++ b/tabby-core/src/tabContextMenu.ts @@ -262,7 +262,7 @@ export class ProfilesContextMenu extends TabContextMenuItemProvider { } async switchTabProfile (tab: BaseTabComponent) { - const profile = await this.profilesService.showProfileSelector() + const profile = await this.profilesService.showProfileSelector().catch(() => null) if (!profile) { return } From 06859de2de651867be65b0230b679908b58f2fc6 Mon Sep 17 00:00:00 2001 From: Clem Date: Fri, 25 Aug 2023 15:26:44 +0200 Subject: [PATCH 34/38] ref(core): profiles.services config saving should be the caller's responsibility --- tabby-core/src/services/profiles.service.ts | 51 +++---------------- .../profilesSettingsTab.component.ts | 12 ++--- 2 files changed, 14 insertions(+), 49 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 51ab8acd..20be4ea3 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -93,10 +93,9 @@ export class ProfilesService { /** * Insert a new Profile in config - * arg: saveConfig (default: true) -> invoke after the Profile was updated * arg: genId (default: true) -> generate uuid in before pushing Profile into config */ - async newProfile (profile: PartialProfile, saveConfig = true, genId = true): Promise { + async newProfile (profile: PartialProfile, genId = true): Promise { if (genId) { profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}` } @@ -107,17 +106,12 @@ export class ProfilesService { } this.config.store.profiles.push(profile) - - if (saveConfig) { - return this.config.save() - } } /** * Write a Profile in config - * arg: saveConfig (default: true) -> invoke after the Profile was updated */ - async writeProfile (profile: PartialProfile, saveConfig = true): Promise { + async writeProfile (profile: PartialProfile): Promise { const cProfile = this.config.store.profiles.find(p => p.id === profile.id) if (cProfile) { if (!profile.group) { @@ -125,18 +119,13 @@ export class ProfilesService { } Object.assign(cProfile, 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 { + async deleteProfile (profile: PartialProfile): Promise { this.providerForProfile(profile)?.deleteProfile(this.getConfigProxyForProfile(profile)) this.config.store.profiles = this.config.store.profiles.filter(p => p.id !== profile.id) @@ -147,18 +136,13 @@ export class ProfilesService { 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 { + async deleteBulkProfiles (options: { group: string }): Promise { for (const profile of this.config.store.profiles.filter(p => p.group === options.group)) { this.providerForProfile(profile)?.deleteProfile(this.getConfigProxyForProfile(profile)) @@ -172,10 +156,6 @@ export class ProfilesService { } 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 { @@ -464,10 +444,9 @@ export class ProfilesService { /** * Insert a new ProfileGroup in config - * arg: saveConfig (default: true) -> invoke after the Profile was updated * arg: genId (default: true) -> generate uuid in before pushing Profile into config */ - async newProfileGroup (group: PartialProfileGroup, saveConfig = true, genId = true): Promise { + async newProfileGroup (group: PartialProfileGroup, genId = true): Promise { if (genId) { group.id = `${uuidv4()}` } @@ -478,47 +457,33 @@ export class ProfilesService { } this.config.store.groups.push(group) - - if (saveConfig) { - return this.config.save() - } } /** * Write a ProfileGroup in config - * arg: saveConfig (default: true) -> invoke after the ProfileGroup was updated */ - async writeProfileGroup (group: PartialProfileGroup, saveConfig = true): Promise { + async writeProfileGroup (group: PartialProfileGroup): Promise { delete group.profiles delete group.editable const cGroup = this.config.store.groups.find(g => g.id === group.id) if (cGroup) { Object.assign(cGroup, group) - - if (saveConfig) { - return this.config.save() - } } } /** * Delete a ProfileGroup from config - * arg: saveConfig (default: true) -> invoke after the ProfileGroup was deleted */ - async deleteProfileGroup (group: PartialProfileGroup, saveConfig = true, deleteProfiles = true): Promise { + async deleteProfileGroup (group: PartialProfileGroup, deleteProfiles = true): Promise { this.config.store.groups = this.config.store.groups.filter(g => g.id !== group.id) if (deleteProfiles) { - await this.deleteBulkProfiles({ group: group.id }, false) + await this.deleteBulkProfiles({ group: group.id }) } 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 d9cb446b..01063c0d 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -88,7 +88,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { const cfgProxy = this.profilesService.getConfigProxyForProfile(profile) profile.name = this.profilesService.providerForProfile(profile)?.getSuggestedName(cfgProxy) ?? this.translate.instant('{name} copy', base) } - this.profilesService.newProfile(profile) + this.profilesService.newProfile(profile).then(() => this.config.save()) } async editProfile (profile: PartialProfile): Promise { @@ -97,7 +97,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { return } Object.assign(profile, result) - await this.profilesService.writeProfile(profile) + await this.profilesService.writeProfile(profile).then(() => this.config.save()) } async showProfileEditModal (profile: PartialProfile): Promise|null> { @@ -140,7 +140,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { cancelId: 1, }, )).response === 0) { - this.profilesService.deleteProfile(profile) + this.profilesService.deleteProfile(profile).then(() => this.config.save()) } } @@ -149,7 +149,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { modal.componentInstance.prompt = this.translate.instant('New group name') const result = await modal.result.catch(() => null) if (result?.value.trim()) { - await this.profilesService.newProfileGroup({ id: '', name: result.value }) + await this.profilesService.newProfileGroup({ id: '', name: result.value }).then(() => this.config.save()) } } @@ -159,7 +159,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { return } Object.assign(group, result) - await this.profilesService.writeProfileGroup(ProfilesSettingsTabComponent.collapsableIntoPartialProfileGroup(group)) + await this.profilesService.writeProfileGroup(ProfilesSettingsTabComponent.collapsableIntoPartialProfileGroup(group)).then(() => this.config.save()) } async showProfileGroupEditModal (group: PartialProfileGroup): Promise|null> { @@ -239,7 +239,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { deleteProfiles = true } - await this.profilesService.deleteProfileGroup(group, true, deleteProfiles) + await this.profilesService.deleteProfileGroup(group, deleteProfiles).then(() => this.config.save()) } } From 5e5c80832de2d2125a9e55d89321071365c32938 Mon Sep 17 00:00:00 2001 From: Clem Date: Fri, 25 Aug 2023 16:35:53 +0200 Subject: [PATCH 35/38] ref(core): profiles.services optionnal options object argument --- tabby-core/src/services/profiles.service.ts | 42 ++++++++++--------- .../components/editProfileModal.component.ts | 2 +- .../profilesSettingsTab.component.ts | 6 +-- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 20be4ea3..2645ca3d 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -64,9 +64,10 @@ export class ProfilesService { /* * Return ConfigProxy for a given Profile * arg: skipUserDefaults -> do not merge global provider defaults in ConfigProxy + * arg: skipGroupDefaults -> do not merge parent group provider defaults in ConfigProxy */ - getConfigProxyForProfile (profile: PartialProfile, skipGlobalDefaults = false, skipGroupDefaults = false): T { - const defaults = this.getProfileDefaults(profile, skipGlobalDefaults, skipGroupDefaults).reduce(configMerge, {}) + getConfigProxyForProfile (profile: PartialProfile, options?: { skipGlobalDefaults?: boolean, skipGroupDefaults?: boolean }): T { + const defaults = this.getProfileDefaults(profile, options).reduce(configMerge, {}) return new ConfigProxy(profile, defaults) as unknown as T } @@ -75,9 +76,9 @@ export class ProfilesService { * arg: includeBuiltin (default: true) -> include BuiltinProfiles * arg: clone (default: false) -> return deepclone Array */ - async getProfiles (includeBuiltin = true, clone = false): Promise[]> { + async getProfiles (options?: { includeBuiltin?: boolean, clone?: boolean }): Promise[]> { let list = this.config.store.profiles ?? [] - if (includeBuiltin) { + if (options?.includeBuiltin ?? true) { const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles())) list = [ ...this.config.store.profiles ?? [], @@ -88,15 +89,15 @@ export class ProfilesService { 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 + return options?.clone ? deepClone(list) : list } /** * Insert a new Profile in config * arg: genId (default: true) -> generate uuid in before pushing Profile into config */ - async newProfile (profile: PartialProfile, genId = true): Promise { - if (genId) { + async newProfile (profile: PartialProfile, options?: { genId?: boolean }): Promise { + if (options?.genId ?? true) { profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}` } @@ -354,15 +355,16 @@ 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 + * arg: skipGroupDefaults -> do not merge parent group provider defaults in ConfigProxy */ - getProfileDefaults (profile: PartialProfile, skipGlobalDefaults = false, skipGroupDefaults = false): any[] { + getProfileDefaults (profile: PartialProfile, options?: { skipGlobalDefaults?: boolean, skipGroupDefaults?: boolean }): any[] { const provider = this.providerForProfile(profile) return [ this.profileDefaults, provider?.configDefaults ?? {}, - provider && !skipGlobalDefaults ? this.getProviderDefaults(provider) : {}, - provider && !skipGlobalDefaults && !skipGroupDefaults ? this.getProviderProfileGroupDefaults(profile.group ?? '', provider) : {}, + provider && !options?.skipGlobalDefaults ? this.getProviderDefaults(provider) : {}, + provider && !options?.skipGlobalDefaults && !options?.skipGroupDefaults ? this.getProviderProfileGroupDefaults(profile.group ?? '', provider) : {}, ] } @@ -383,17 +385,17 @@ export class ProfilesService { * arg: includeProfiles (default: false) -> if false, does not fill up the profiles field of ProfileGroup * arg: includeNonUserGroup (default: false) -> if false, does not add built-in and ungrouped groups */ - async getProfileGroups (includeProfiles = false, includeNonUserGroup = false): Promise[]> { + async getProfileGroups (options?: { includeProfiles?: boolean, includeNonUserGroup?: boolean }): Promise[]> { let profiles: PartialProfile[] = [] - if (includeProfiles) { - profiles = await this.getProfiles(includeNonUserGroup, true) + if (options?.includeProfiles) { + profiles = await this.getProfiles({ includeBuiltin: options.includeNonUserGroup, clone: true }) } let groups: PartialProfileGroup[] = this.getSyncProfileGroups() groups = groups.map(x => { x.editable = true - if (includeProfiles) { + if (options?.includeProfiles) { x.profiles = profiles.filter(p => p.group === x.id) profiles = profiles.filter(p => p.group !== x.id) } @@ -401,7 +403,7 @@ export class ProfilesService { return x }) - if (includeNonUserGroup) { + if (options?.includeNonUserGroup) { const builtInGroups: PartialProfileGroup[] = [] builtInGroups.push({ id: 'built-in', @@ -416,7 +418,7 @@ export class ProfilesService { editable: false, } - if (includeProfiles) { + if (options.includeProfiles) { for (const profile of profiles.filter(p => p.isBuiltin)) { let group: PartialProfileGroup | undefined = builtInGroups.find(g => g.id === slugify(profile.group ?? 'built-in')) if (!group) { @@ -446,8 +448,8 @@ export class ProfilesService { * Insert a new ProfileGroup in config * arg: genId (default: true) -> generate uuid in before pushing Profile into config */ - async newProfileGroup (group: PartialProfileGroup, genId = true): Promise { - if (genId) { + async newProfileGroup (group: PartialProfileGroup, options?: { genId?: boolean }): Promise { + if (options?.genId ?? true) { group.id = `${uuidv4()}` } @@ -475,9 +477,9 @@ export class ProfilesService { /** * Delete a ProfileGroup from config */ - async deleteProfileGroup (group: PartialProfileGroup, deleteProfiles = true): Promise { + async deleteProfileGroup (group: PartialProfileGroup, options?: { deleteProfiles?: boolean }): Promise { this.config.store.groups = this.config.store.groups.filter(g => g.id !== group.id) - if (deleteProfiles) { + if (options?.deleteProfiles) { await this.deleteBulkProfiles({ group: group.id }) } else { for (const profile of this.config.store.profiles.filter(x => x.group === group.id)) { diff --git a/tabby-settings/src/components/editProfileModal.component.ts b/tabby-settings/src/components/editProfileModal.component.ts index f1ac1c1e..ba037c30 100644 --- a/tabby-settings/src/components/editProfileModal.component.ts +++ b/tabby-settings/src/components/editProfileModal.component.ts @@ -57,7 +57,7 @@ export class EditProfileModalComponent

{ ngOnInit () { this._profile = this.profile - this.profile = this.profilesService.getConfigProxyForProfile(this.profile, this.defaultsMode === 'enabled', this.defaultsMode === 'group') + this.profile = this.profilesService.getConfigProxyForProfile(this.profile, { skipGlobalDefaults: this.defaultsMode === 'enabled', skipGroupDefaults: this.defaultsMode === 'group' }) } ngAfterViewInit () { diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 01063c0d..9100a64c 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -41,7 +41,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent { async ngOnInit (): Promise { this.refresh() - this.builtinProfiles = (await this.profilesService.getProfiles(true, false)).filter(x => x.isBuiltin) + this.builtinProfiles = (await this.profilesService.getProfiles()).filter(x => x.isBuiltin) this.templateProfiles = this.builtinProfiles.filter(x => x.isTemplate) this.builtinProfiles = this.builtinProfiles.filter(x => !x.isTemplate) this.refresh() @@ -239,13 +239,13 @@ export class ProfilesSettingsTabComponent extends BaseComponent { deleteProfiles = true } - await this.profilesService.deleteProfileGroup(group, deleteProfiles).then(() => this.config.save()) + await this.profilesService.deleteProfileGroup(group, { deleteProfiles }).then(() => this.config.save()) } } async refresh (): Promise { const profileGroupCollapsed = JSON.parse(window.localStorage.profileGroupCollapsed ?? '{}') - const groups = await this.profilesService.getProfileGroups(true, true) + const groups = await this.profilesService.getProfileGroups({ includeNonUserGroup: true, includeProfiles: true }) groups.sort((a, b) => a.name.localeCompare(b.name)) groups.sort((a, b) => (a.id === 'built-in' || !a.editable ? 1 : 0) - (b.id === 'built-in' || !b.editable ? 1 : 0)) groups.sort((a, b) => (a.id === 'ungrouped' ? 0 : 1) - (b.id === 'ungrouped' ? 0 : 1)) From 0128013308bbb307275b559b4ce6984581785ebd Mon Sep 17 00:00:00 2001 From: Clem Date: Fri, 25 Aug 2023 16:57:34 +0200 Subject: [PATCH 36/38] ref(core): profiles.services deleteBulkProfiles filter with predicate function --- tabby-core/src/services/profiles.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index 2645ca3d..b7277dee 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -143,8 +143,8 @@ export class ProfilesService { * Delete all Profiles from config using option filter * arg: options { group: string } -> options used to filter which profile have to be deleted */ - async deleteBulkProfiles (options: { group: string }): Promise { - for (const profile of this.config.store.profiles.filter(p => p.group === options.group)) { + async deleteBulkProfiles (filter: (p: PartialProfile) => boolean): Promise { + for (const profile of this.config.store.profiles.filter(filter)) { this.providerForProfile(profile)?.deleteProfile(this.getConfigProxyForProfile(profile)) const profileHotkeyName = ProfilesService.getProfileHotkeyName(profile) @@ -156,7 +156,7 @@ export class ProfilesService { } } - this.config.store.profiles = this.config.store.profiles.filter(p => p.group !== options.group) + this.config.store.profiles = this.config.store.profiles.filter(!filter) } async openNewTabForProfile

(profile: PartialProfile

): Promise { @@ -480,7 +480,7 @@ export class ProfilesService { async deleteProfileGroup (group: PartialProfileGroup, options?: { deleteProfiles?: boolean }): Promise { this.config.store.groups = this.config.store.groups.filter(g => g.id !== group.id) if (options?.deleteProfiles) { - await this.deleteBulkProfiles({ group: group.id }) + await this.deleteBulkProfiles((p) => p.group === group.id) } else { for (const profile of this.config.store.profiles.filter(x => x.group === group.id)) { delete profile.group From 96eee515902146e258e62c8795ac00c52107c08d Mon Sep 17 00:00:00 2001 From: Eugene Date: Fri, 25 Aug 2023 23:03:02 +0200 Subject: [PATCH 37/38] cleanup saving --- .../profilesSettingsTab.component.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tabby-settings/src/components/profilesSettingsTab.component.ts b/tabby-settings/src/components/profilesSettingsTab.component.ts index 9100a64c..58439ca0 100644 --- a/tabby-settings/src/components/profilesSettingsTab.component.ts +++ b/tabby-settings/src/components/profilesSettingsTab.component.ts @@ -88,7 +88,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent { const cfgProxy = this.profilesService.getConfigProxyForProfile(profile) profile.name = this.profilesService.providerForProfile(profile)?.getSuggestedName(cfgProxy) ?? this.translate.instant('{name} copy', base) } - this.profilesService.newProfile(profile).then(() => this.config.save()) + await this.profilesService.newProfile(profile) + await this.config.save() } async editProfile (profile: PartialProfile): Promise { @@ -97,7 +98,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent { return } Object.assign(profile, result) - await this.profilesService.writeProfile(profile).then(() => this.config.save()) + await this.profilesService.writeProfile(profile) + await this.config.save() } async showProfileEditModal (profile: PartialProfile): Promise|null> { @@ -140,7 +142,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent { cancelId: 1, }, )).response === 0) { - this.profilesService.deleteProfile(profile).then(() => this.config.save()) + await this.profilesService.deleteProfile(profile) + await this.config.save() } } @@ -149,7 +152,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent { modal.componentInstance.prompt = this.translate.instant('New group name') const result = await modal.result.catch(() => null) if (result?.value.trim()) { - await this.profilesService.newProfileGroup({ id: '', name: result.value }).then(() => this.config.save()) + await this.profilesService.newProfileGroup({ id: '', name: result.value }) + await this.config.save() } } @@ -159,7 +163,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent { return } Object.assign(group, result) - await this.profilesService.writeProfileGroup(ProfilesSettingsTabComponent.collapsableIntoPartialProfileGroup(group)).then(() => this.config.save()) + await this.profilesService.writeProfileGroup(ProfilesSettingsTabComponent.collapsableIntoPartialProfileGroup(group)) + await this.config.save() } async showProfileGroupEditModal (group: PartialProfileGroup): Promise|null> { @@ -239,7 +244,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent { deleteProfiles = true } - await this.profilesService.deleteProfileGroup(group, { deleteProfiles }).then(() => this.config.save()) + await this.profilesService.deleteProfileGroup(group, { deleteProfiles }) + await this.config.save() } } From eddb50b529badd4309f9b257f682a24742777564 Mon Sep 17 00:00:00 2001 From: Eugene Date: Fri, 25 Aug 2023 23:04:20 +0200 Subject: [PATCH 38/38] fixed delete predicate --- tabby-core/src/services/profiles.service.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tabby-core/src/services/profiles.service.ts b/tabby-core/src/services/profiles.service.ts index b7277dee..fc3b4211 100644 --- a/tabby-core/src/services/profiles.service.ts +++ b/tabby-core/src/services/profiles.service.ts @@ -141,9 +141,9 @@ export class ProfilesService { /** * Delete all Profiles from config using option filter - * arg: options { group: string } -> options used to filter which profile have to be deleted + * arg: filter (p: PartialProfile) => boolean -> predicate used to decide which profiles have to be deleted */ - async deleteBulkProfiles (filter: (p: PartialProfile) => boolean): Promise { + async bulkDeleteProfiles (filter: (p: PartialProfile) => boolean): Promise { for (const profile of this.config.store.profiles.filter(filter)) { this.providerForProfile(profile)?.deleteProfile(this.getConfigProxyForProfile(profile)) @@ -156,7 +156,7 @@ export class ProfilesService { } } - this.config.store.profiles = this.config.store.profiles.filter(!filter) + this.config.store.profiles = this.config.store.profiles.filter(x => !filter(x)) } async openNewTabForProfile

(profile: PartialProfile

): Promise { @@ -480,7 +480,7 @@ export class ProfilesService { async deleteProfileGroup (group: PartialProfileGroup, options?: { deleteProfiles?: boolean }): Promise { this.config.store.groups = this.config.store.groups.filter(g => g.id !== group.id) if (options?.deleteProfiles) { - await this.deleteBulkProfiles((p) => p.group === group.id) + await this.bulkDeleteProfiles((p) => p.group === group.id) } else { for (const profile of this.config.store.profiles.filter(x => x.group === group.id)) { delete profile.group