global profile settings - fixes #4098

This commit is contained in:
Eugene Pankov 2021-08-22 00:10:56 +02:00
parent 0a3debb691
commit 20116d7af6
No known key found for this signature in database
GPG key ID: 5896FCBBDD1CF4F4
6 changed files with 162 additions and 108 deletions

View file

@ -21,6 +21,8 @@ hotkeys:
profile: profile:
__nonStructural: true __nonStructural: true
profiles: [] profiles: []
profileDefaults:
__nonStructural: true
recentProfiles: [] recentProfiles: []
recoverTabs: true recoverTabs: true
enableAnalytics: true enableAnalytics: true

View file

@ -179,9 +179,13 @@ export class ProfilesService {
return null return null
} }
getConfigProxyForProfile <T extends Profile> (profile: PartialProfile<T>): T { getConfigProxyForProfile <T extends Profile> (profile: PartialProfile<T>, skipUserDefaults = false): T {
const provider = this.providerForProfile(profile) const provider = this.providerForProfile(profile)
const defaults = configMerge(this.profileDefaults, provider?.configDefaults ?? {}) 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 return new ConfigProxy(profile, defaults) as unknown as T
} }
} }

View file

@ -1,10 +1,13 @@
.modal-header .modal-header(*ngIf='!defaultsMode')
h3.m-0 {{profile.name}} h3.m-0 {{profile.name}}
.modal-header(*ngIf='defaultsMode')
h3.m-0 Defaults for {{profileProvider.name}}
.modal-body .modal-body
.row .row
.col-12.col-lg-4 .col-12.col-lg-4
.form-group .form-group(*ngIf='!defaultsMode')
label Name label Name
input.form-control( input.form-control(
type='text', type='text',
@ -12,7 +15,7 @@
[(ngModel)]='profile.name', [(ngModel)]='profile.name',
) )
.form-group .form-group(*ngIf='!defaultsMode')
label Group label Group
input.form-control( input.form-control(
type='text', type='text',
@ -22,7 +25,7 @@
[ngbTypeahead]='groupTypeahead', [ngbTypeahead]='groupTypeahead',
) )
.form-group .form-group(*ngIf='!defaultsMode')
label Icon label Icon
.input-group .input-group
input.form-control( input.form-control(
@ -47,7 +50,6 @@
type='text', type='text',
[(ngModel)]='profile.color', [(ngModel)]='profile.color',
placeholder='#000000', placeholder='#000000',
alwaysVisibleTypeahead,
[ngbTypeahead]='colorsAutocomplete', [ngbTypeahead]='colorsAutocomplete',
[resultFormatter]='colorsFormatter' [resultFormatter]='colorsFormatter'
) )

View file

@ -19,6 +19,7 @@ export class EditProfileModalComponent<P extends Profile> {
@Input() profile: P & ConfigProxy @Input() profile: P & ConfigProxy
@Input() profileProvider: ProfileProvider<P> @Input() profileProvider: ProfileProvider<P>
@Input() settingsComponent: new () => ProfileSettingsComponent<P> @Input() settingsComponent: new () => ProfileSettingsComponent<P>
@Input() defaultsMode = false
groupNames: string[] groupNames: string[]
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef @ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
@ -55,7 +56,7 @@ export class EditProfileModalComponent<P extends Profile> {
ngOnInit () { ngOnInit () {
this._profile = this.profile this._profile = this.profile
this.profile = this.profilesService.getConfigProxyForProfile(this.profile) this.profile = this.profilesService.getConfigProxyForProfile(this.profile, this.defaultsMode)
} }
ngAfterViewInit () { ngAfterViewInit () {

View file

@ -1,108 +1,127 @@
h3.mb-3 Profiles h3.mb-3 Profiles
.form-line ul.nav-tabs(ngbNav, #nav='ngbNav')
.header li(ngbNavItem)
.title Default profile for new tabs a(ngbNavLink) Profiles
ng-template(ngbNavContent)
.form-line
.header
.title Default profile for new tabs
select.form-control( select.form-control(
[(ngModel)]='config.store.terminal.profile', [(ngModel)]='config.store.terminal.profile',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
)
option(
*ngFor='let profile of profiles',
[ngValue]='profile.id'
) {{profile.name}}
option(
*ngFor='let profile of builtinProfiles',
[ngValue]='profile.id'
) {{profile.name}}
.form-line(*ngIf='config.store.profiles.length > 0')
.header
.title Show recent profiles in selector
.description Set to 0 to disable recent profiles
input.form-control(
type='number',
min='0',
step='1',
[(ngModel)]='config.store.terminal.showRecentProfiles',
(ngModelChange)='config.save()'
)
.form-line(*ngIf='config.store.profiles.length > 0')
.header
.title Show built-in profiles in selector
.description If disabled, only custom profiles will show up in the profile selector
toggle(
[(ngModel)]='config.store.terminal.showBuiltinProfiles',
(ngModelChange)='config.save()'
)
.d-flex.mb-3.mt-4
.input-group
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', placeholder='Filter', [(ngModel)]='filter')
button.btn.btn-primary.flex-shrink-0.ml-3((click)='newProfile()')
i.fas.fa-fw.fa-plus
| New profile
.list-group.list-group-light.mt-3.mb-3
ng-container(*ngFor='let group of profileGroups')
ng-container(*ngIf='isGroupVisible(group)')
.list-group-item.list-group-item-action.d-flex.align-items-center(
(click)='group.collapsed = !group.collapsed'
)
.fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed')
.fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed')
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
*ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); editGroup(group)'
) )
i.fas.fa-pencil-alt option(
button.btn.btn-sm.btn-link.hover-reveal.ml-2( *ngFor='let profile of profiles',
*ngIf='group.editable && group.name', [ngValue]='profile.id'
(click)='$event.stopPropagation(); deleteGroup(group)' ) {{profile.name}}
option(
*ngFor='let profile of builtinProfiles',
[ngValue]='profile.id'
) {{profile.name}}
.d-flex.mb-3.mt-4
.input-group
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', placeholder='Filter', [(ngModel)]='filter')
button.btn.btn-primary.flex-shrink-0.ml-3((click)='newProfile()')
i.fas.fa-fw.fa-plus
| New profile
.list-group.list-group-light.mt-3.mb-3
ng-container(*ngFor='let group of profileGroups')
ng-container(*ngIf='isGroupVisible(group)')
.list-group-item.list-group-item-action.d-flex.align-items-center(
(click)='group.collapsed = !group.collapsed'
)
.fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed')
.fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed')
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
*ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); editGroup(group)'
)
i.fas.fa-pencil-alt
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
*ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); deleteGroup(group)'
)
i.fas.fa-trash-alt
ng-container(*ngIf='!group.collapsed')
ng-container(*ngFor='let profile of group.profiles')
.list-group-item.pl-5.d-flex.align-items-center(
*ngIf='isProfileVisible(profile)',
[class.list-group-item-action]='!profile.isBuiltin',
(click)='profile.isBuiltin ? null : editProfile(profile)'
)
i.icon(
class='fa-fw {{profile.icon}}',
[style.color]='profile.color',
*ngIf='!iconIsSVG(profile.icon)'
)
.icon(
[fastHtmlBind]='profile.icon',
*ngIf='iconIsSVG(profile.icon)'
)
div {{profile.name}}
.text-muted.ml-2 {{getDescription(profile)}}
.mr-auto
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); launchProfile(profile)')
i.fas.fa-play
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)')
i.fas.fa-copy
button.btn.btn-link.hover-reveal.ml-1(
*ngIf='!profile.isBuiltin',
(click)='$event.stopPropagation(); deleteProfile(profile)'
)
i.fas.fa-trash-alt
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
li(ngbNavItem)
a(ngbNavLink) Advanced
ng-template(ngbNavContent)
.form-line(*ngIf='config.store.profiles.length > 0')
.header
.title Show recent profiles in selector
.description Set to 0 to disable recent profiles
input.form-control(
type='number',
min='0',
step='1',
[(ngModel)]='config.store.terminal.showRecentProfiles',
(ngModelChange)='config.save()'
) )
i.fas.fa-trash-alt
ng-container(*ngIf='!group.collapsed')
ng-container(*ngFor='let profile of group.profiles')
.list-group-item.pl-5.d-flex.align-items-center(
*ngIf='isProfileVisible(profile)',
[class.list-group-item-action]='!profile.isBuiltin',
(click)='profile.isBuiltin ? null : editProfile(profile)'
)
i.icon(
class='fa-fw {{profile.icon}}',
[style.color]='profile.color',
*ngIf='!iconIsSVG(profile.icon)'
)
.icon(
[fastHtmlBind]='profile.icon',
*ngIf='iconIsSVG(profile.icon)'
)
div {{profile.name}} .form-line(*ngIf='config.store.profiles.length > 0')
.text-muted.ml-2 {{getDescription(profile)}} .header
.title Show built-in profiles in selector
.description If disabled, only custom profiles will show up in the profile selector
.mr-auto toggle(
[(ngModel)]='config.store.terminal.showBuiltinProfiles',
(ngModelChange)='config.save()'
)
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); launchProfile(profile)') .form-line
i.fas.fa-play .header
.title Default profile settings
.description These apply to all profiles of a given type
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)') .list-group.list-group-light.mt-3.mb-3
i.fas.fa-copy a.list-group-item.list-group-item-action(
(click)='editDefaults(provider)',
*ngFor='let provider of profileProviders'
) {{provider.name}}
button.btn.btn-link.hover-reveal.ml-1( div([ngbNavOutlet]='nav')
*ngIf='!profile.isBuiltin',
(click)='$event.stopPropagation(); deleteProfile(profile)'
)
i.fas.fa-trash-alt
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}

View file

@ -1,9 +1,9 @@
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import slugify from 'slugify' import slugify from 'slugify'
import deepClone from 'clone-deep' import deepClone from 'clone-deep'
import { Component } from '@angular/core' import { Component, Inject } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap' import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile } from 'tabby-core' import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile, ProfileProvider } from 'tabby-core'
import { EditProfileModalComponent } from './editProfileModal.component' import { EditProfileModalComponent } from './editProfileModal.component'
interface ProfileGroup { interface ProfileGroup {
@ -28,12 +28,14 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
constructor ( constructor (
public config: ConfigService, public config: ConfigService,
public hostApp: HostAppService, public hostApp: HostAppService,
@Inject(ProfileProvider) public profileProviders: ProfileProvider<Profile>[],
private profilesService: ProfilesService, private profilesService: ProfilesService,
private selector: SelectorService, private selector: SelectorService,
private ngbModal: NgbModal, private ngbModal: NgbModal,
private platform: PlatformService, private platform: PlatformService,
) { ) {
super() super()
this.profileProviders.sort((a, b) => a.name.localeCompare(b.name))
} }
async ngOnInit (): Promise<void> { async ngOnInit (): Promise<void> {
@ -102,6 +104,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
delete profile[k] delete profile[k]
} }
Object.assign(profile, result) Object.assign(profile, result)
profile.type = modal.componentInstance.profileProvider.id
} }
async deleteProfile (profile: PartialProfile<Profile>): Promise<void> { async deleteProfile (profile: PartialProfile<Profile>): Promise<void> {
@ -224,4 +228,26 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
'split-layout': 'primary', 'split-layout': 'primary',
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning' }[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
} }
async editDefaults (provider: ProfileProvider<Profile>): Promise<void> {
const modal = this.ngbModal.open(
EditProfileModalComponent,
{ size: 'lg' },
)
const model = this.config.store.profileDefaults[provider.id] ?? {}
model.type = provider.id
modal.componentInstance.profile = Object.assign({}, model)
modal.componentInstance.profileProvider = provider
modal.componentInstance.defaultsMode = true
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]
}
Object.assign(model, result)
this.config.store.profileDefaults[provider.id] = model
await this.config.save()
}
} }