mirror of
https://github.com/Eugeny/tabby.git
synced 2024-12-09 06:20:22 +08:00
global profile settings - fixes #4098
This commit is contained in:
parent
0a3debb691
commit
20116d7af6
@ -21,6 +21,8 @@ hotkeys:
|
||||
profile:
|
||||
__nonStructural: true
|
||||
profiles: []
|
||||
profileDefaults:
|
||||
__nonStructural: true
|
||||
recentProfiles: []
|
||||
recoverTabs: true
|
||||
enableAnalytics: true
|
||||
|
@ -179,9 +179,13 @@ export class ProfilesService {
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,13 @@
|
||||
.modal-header
|
||||
.modal-header(*ngIf='!defaultsMode')
|
||||
h3.m-0 {{profile.name}}
|
||||
|
||||
.modal-header(*ngIf='defaultsMode')
|
||||
h3.m-0 Defaults for {{profileProvider.name}}
|
||||
|
||||
.modal-body
|
||||
.row
|
||||
.col-12.col-lg-4
|
||||
.form-group
|
||||
.form-group(*ngIf='!defaultsMode')
|
||||
label Name
|
||||
input.form-control(
|
||||
type='text',
|
||||
@ -12,7 +15,7 @@
|
||||
[(ngModel)]='profile.name',
|
||||
)
|
||||
|
||||
.form-group
|
||||
.form-group(*ngIf='!defaultsMode')
|
||||
label Group
|
||||
input.form-control(
|
||||
type='text',
|
||||
@ -22,7 +25,7 @@
|
||||
[ngbTypeahead]='groupTypeahead',
|
||||
)
|
||||
|
||||
.form-group
|
||||
.form-group(*ngIf='!defaultsMode')
|
||||
label Icon
|
||||
.input-group
|
||||
input.form-control(
|
||||
@ -47,7 +50,6 @@
|
||||
type='text',
|
||||
[(ngModel)]='profile.color',
|
||||
placeholder='#000000',
|
||||
alwaysVisibleTypeahead,
|
||||
[ngbTypeahead]='colorsAutocomplete',
|
||||
[resultFormatter]='colorsFormatter'
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ export class EditProfileModalComponent<P extends Profile> {
|
||||
@Input() profile: P & ConfigProxy
|
||||
@Input() profileProvider: ProfileProvider<P>
|
||||
@Input() settingsComponent: new () => ProfileSettingsComponent<P>
|
||||
@Input() defaultsMode = false
|
||||
groupNames: string[]
|
||||
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
|
||||
|
||||
@ -55,7 +56,7 @@ export class EditProfileModalComponent<P extends Profile> {
|
||||
|
||||
ngOnInit () {
|
||||
this._profile = this.profile
|
||||
this.profile = this.profilesService.getConfigProxyForProfile(this.profile)
|
||||
this.profile = this.profilesService.getConfigProxyForProfile(this.profile, this.defaultsMode)
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
|
@ -1,108 +1,127 @@
|
||||
h3.mb-3 Profiles
|
||||
|
||||
.form-line
|
||||
.header
|
||||
.title Default profile for new tabs
|
||||
ul.nav-tabs(ngbNav, #nav='ngbNav')
|
||||
li(ngbNavItem)
|
||||
a(ngbNavLink) Profiles
|
||||
ng-template(ngbNavContent)
|
||||
.form-line
|
||||
.header
|
||||
.title Default profile for new tabs
|
||||
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.profile',
|
||||
(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)'
|
||||
select.form-control(
|
||||
[(ngModel)]='config.store.terminal.profile',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
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)'
|
||||
option(
|
||||
*ngFor='let profile of profiles',
|
||||
[ngValue]='profile.id'
|
||||
) {{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}}
|
||||
.text-muted.ml-2 {{getDescription(profile)}}
|
||||
.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
|
||||
|
||||
.mr-auto
|
||||
toggle(
|
||||
[(ngModel)]='config.store.terminal.showBuiltinProfiles',
|
||||
(ngModelChange)='config.save()'
|
||||
)
|
||||
|
||||
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); launchProfile(profile)')
|
||||
i.fas.fa-play
|
||||
.form-line
|
||||
.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)')
|
||||
i.fas.fa-copy
|
||||
.list-group.list-group-light.mt-3.mb-3
|
||||
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(
|
||||
*ngIf='!profile.isBuiltin',
|
||||
(click)='$event.stopPropagation(); deleteProfile(profile)'
|
||||
)
|
||||
i.fas.fa-trash-alt
|
||||
|
||||
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}
|
||||
div([ngbNavOutlet]='nav')
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import slugify from 'slugify'
|
||||
import deepClone from 'clone-deep'
|
||||
import { Component } from '@angular/core'
|
||||
import { Component, Inject } from '@angular/core'
|
||||
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'
|
||||
|
||||
interface ProfileGroup {
|
||||
@ -28,12 +28,14 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
constructor (
|
||||
public config: ConfigService,
|
||||
public hostApp: HostAppService,
|
||||
@Inject(ProfileProvider) public profileProviders: ProfileProvider<Profile>[],
|
||||
private profilesService: ProfilesService,
|
||||
private selector: SelectorService,
|
||||
private ngbModal: NgbModal,
|
||||
private platform: PlatformService,
|
||||
) {
|
||||
super()
|
||||
this.profileProviders.sort((a, b) => a.name.localeCompare(b.name))
|
||||
}
|
||||
|
||||
async ngOnInit (): Promise<void> {
|
||||
@ -102,6 +104,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
delete profile[k]
|
||||
}
|
||||
Object.assign(profile, result)
|
||||
|
||||
profile.type = modal.componentInstance.profileProvider.id
|
||||
}
|
||||
|
||||
async deleteProfile (profile: PartialProfile<Profile>): Promise<void> {
|
||||
@ -224,4 +228,26 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
'split-layout': 'primary',
|
||||
}[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 New Issue
Block a user