mirror of
https://github.com/Eugeny/tabby
synced 2024-11-15 01:17:14 +00:00
global profile settings - fixes #4098
This commit is contained in:
parent
0a3debb691
commit
20116d7af6
6 changed files with 162 additions and 108 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 () {
|
||||||
|
|
|
@ -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)}}
|
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue