mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-04 18:39:54 +00:00
tab context menu option to save split layouts as profiles - fixes #3468
This commit is contained in:
parent
908f90cd52
commit
2706045cc2
@ -28,7 +28,7 @@ export abstract class ProfileProvider {
|
||||
id: string
|
||||
name: string
|
||||
supportsQuickConnect = false
|
||||
settingsComponent: new (...args: any[]) => ProfileSettingsComponent
|
||||
settingsComponent?: new (...args: any[]) => ProfileSettingsComponent
|
||||
configDefaults = {}
|
||||
|
||||
abstract getBuiltinProfiles (): Promise<Profile[]>
|
||||
|
@ -1,19 +1,19 @@
|
||||
.modal-body
|
||||
input.form-control(
|
||||
[type]='password ? "password" : "text"',
|
||||
[type]='password ? "password" : "text"',
|
||||
autofocus,
|
||||
[(ngModel)]='value',
|
||||
#input,
|
||||
[placeholder]='prompt',
|
||||
[(ngModel)]='value',
|
||||
#input,
|
||||
[placeholder]='prompt',
|
||||
(keyup.enter)='ok()',
|
||||
(keyup.esc)='cancel()',
|
||||
)
|
||||
.d-flex.align-items-start.mt-2
|
||||
checkbox(
|
||||
*ngIf='showRememberCheckbox',
|
||||
[(ngModel)]='remember',
|
||||
[(ngModel)]='remember',
|
||||
text='Remember'
|
||||
)
|
||||
button.btn.btn-primary.ml-auto(
|
||||
(click)='ok()',
|
||||
) Enter
|
||||
) OK
|
||||
|
@ -636,7 +636,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
||||
}
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> {
|
||||
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
||||
return recoveryToken.type === 'app:split-tab'
|
||||
|
@ -30,7 +30,7 @@ import { AlwaysVisibleTypeaheadDirective } from './directives/alwaysVisibleTypea
|
||||
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
|
||||
import { DropZoneDirective } from './directives/dropZone.directive'
|
||||
|
||||
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ToolbarButtonProvider, ProfilesService } from './api'
|
||||
import { Theme, CLIHandler, TabContextMenuItemProvider, TabRecoveryProvider, HotkeyProvider, ConfigProvider, PlatformService, FileProvider, ToolbarButtonProvider, ProfilesService, ProfileProvider } from './api'
|
||||
|
||||
import { AppService } from './services/app.service'
|
||||
import { ConfigService } from './services/config.service'
|
||||
@ -43,6 +43,7 @@ import { AppHotkeyProvider } from './hotkeys'
|
||||
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu, ProfilesContextMenu } from './tabContextMenu'
|
||||
import { LastCLIHandler, ProfileCLIHandler } from './cli'
|
||||
import { ButtonProvider } from './buttonProvider'
|
||||
import { SplitLayoutProfilesService } from './profiles'
|
||||
|
||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||
import 'ng2-dnd/bundles/style.css'
|
||||
@ -57,12 +58,13 @@ const PROVIDERS = [
|
||||
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||
{ provide: TabContextMenuItemProvider, useClass: ProfilesContextMenu, multi: true },
|
||||
{ provide: TabRecoveryProvider, useClass: SplitTabRecoveryProvider, multi: true },
|
||||
{ provide: TabRecoveryProvider, useExisting: SplitTabRecoveryProvider, multi: true },
|
||||
{ provide: CLIHandler, useClass: ProfileCLIHandler, multi: true },
|
||||
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true },
|
||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
||||
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
|
||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||
{ provide: ProfileProvider, useExisting: SplitLayoutProfilesService, multi: true },
|
||||
]
|
||||
|
||||
/** @hidden */
|
||||
|
57
tabby-core/src/profiles.ts
Normal file
57
tabby-core/src/profiles.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import slugify from 'slugify'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { ConfigService, NewTabParameters, Profile, ProfileProvider } from './api'
|
||||
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
|
||||
|
||||
export interface SplitLayoutProfileOptions {
|
||||
recoveryToken: any
|
||||
}
|
||||
|
||||
export interface SplitLayoutProfile extends Profile {
|
||||
options: SplitLayoutProfileOptions
|
||||
}
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class SplitLayoutProfilesService extends ProfileProvider {
|
||||
id = 'split-layout'
|
||||
name = 'Saved layout'
|
||||
configDefaults = {
|
||||
options: {
|
||||
recoveryToken: null,
|
||||
},
|
||||
}
|
||||
|
||||
constructor (
|
||||
private splitTabRecoveryProvider: SplitTabRecoveryProvider,
|
||||
private config: ConfigService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
||||
async getBuiltinProfiles (): Promise<Profile[]> {
|
||||
return []
|
||||
}
|
||||
|
||||
async getNewTabParameters (profile: SplitLayoutProfile): Promise<NewTabParameters<SplitTabComponent>> {
|
||||
return this.splitTabRecoveryProvider.recover(profile.options.recoveryToken)
|
||||
}
|
||||
|
||||
getDescription (_: SplitLayoutProfile): string {
|
||||
return ''
|
||||
}
|
||||
|
||||
async createProfile (tab: SplitTabComponent, name: string): Promise<void> {
|
||||
const token = await tab.getRecoveryToken()
|
||||
const profile: SplitLayoutProfile = {
|
||||
id: `${this.id}:custom:${slugify(name)}:${uuidv4()}`,
|
||||
type: this.id,
|
||||
name,
|
||||
options: {
|
||||
recoveryToken: token,
|
||||
},
|
||||
}
|
||||
this.config.store.profiles.push(profile)
|
||||
await this.config.save()
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||
import { Injectable } from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { AppService } from './services/app.service'
|
||||
import { BaseTabComponent } from './components/baseTab.component'
|
||||
@ -10,6 +11,8 @@ import { MenuItemOptions } from './api/menu'
|
||||
import { ProfilesService } from './services/profiles.service'
|
||||
import { TabsService } from './services/tabs.service'
|
||||
import { HotkeysService } from './services/hotkeys.service'
|
||||
import { PromptModalComponent } from './components/promptModal.component'
|
||||
import { SplitLayoutProfilesService } from './profiles'
|
||||
|
||||
/** @hidden */
|
||||
@Injectable()
|
||||
@ -103,6 +106,8 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
|
||||
constructor (
|
||||
private app: AppService,
|
||||
private ngbModal: NgbModal,
|
||||
private splitLayoutProfilesService: SplitLayoutProfilesService,
|
||||
) {
|
||||
super()
|
||||
}
|
||||
@ -133,6 +138,21 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
||||
})) as MenuItemOptions[],
|
||||
},
|
||||
]
|
||||
|
||||
if (tab instanceof SplitTabComponent && tab.getAllTabs().length > 1) {
|
||||
items.push({
|
||||
label: 'Save layout as profile',
|
||||
click: async () => {
|
||||
const modal = this.ngbModal.open(PromptModalComponent)
|
||||
modal.componentInstance.prompt = 'Profile name'
|
||||
const name = (await modal.result)?.value
|
||||
if (!name) {
|
||||
return
|
||||
}
|
||||
this.splitLayoutProfilesService.createProfile(tab, name)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
@ -57,7 +57,7 @@
|
||||
|
||||
.mb-4
|
||||
|
||||
.col-12.col-lg-8
|
||||
.col-12.col-lg-8(*ngIf='this.profileProvider.settingsComponent')
|
||||
ng-template(#placeholder)
|
||||
|
||||
.modal-footer
|
||||
|
@ -16,7 +16,7 @@ const iconsClassList = Object.keys(iconsData).map(
|
||||
template: require('./editProfileModal.component.pug'),
|
||||
})
|
||||
export class EditProfileModalComponent {
|
||||
@Input() profile: Profile|ConfigProxy
|
||||
@Input() profile: Profile & ConfigProxy
|
||||
@Input() profileProvider: ProfileProvider
|
||||
@Input() settingsComponent: new () => ProfileSettingsComponent
|
||||
groupNames: string[]
|
||||
@ -41,17 +41,20 @@ export class EditProfileModalComponent {
|
||||
|
||||
ngOnInit () {
|
||||
this._profile = this.profile
|
||||
this.profile = this.profilesService.getConfigProxyForProfile(this.profile)
|
||||
this.profile = this.profilesService.getConfigProxyForProfile(this.profile) as any
|
||||
}
|
||||
|
||||
ngAfterViewInit () {
|
||||
setTimeout(() => {
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.profileProvider.settingsComponent)
|
||||
const componentRef = componentFactory.create(this.injector)
|
||||
this.settingsComponentInstance = componentRef.instance
|
||||
this.settingsComponentInstance.profile = this.profile
|
||||
this.placeholder.insert(componentRef.hostView)
|
||||
})
|
||||
const componentType = this.profileProvider.settingsComponent
|
||||
if (componentType) {
|
||||
setTimeout(() => {
|
||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType)
|
||||
const componentRef = componentFactory.create(this.injector)
|
||||
this.settingsComponentInstance = componentRef.instance
|
||||
this.settingsComponentInstance.profile = this.profile
|
||||
this.placeholder.insert(componentRef.hostView)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
groupTypeahead = (text$: Observable<string>) =>
|
||||
|
@ -206,6 +206,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
||||
ssh: 'secondary',
|
||||
serial: 'success',
|
||||
telnet: 'info',
|
||||
'split-layout': 'primary',
|
||||
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user