mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-05 02:50:00 +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
|
id: string
|
||||||
name: string
|
name: string
|
||||||
supportsQuickConnect = false
|
supportsQuickConnect = false
|
||||||
settingsComponent: new (...args: any[]) => ProfileSettingsComponent
|
settingsComponent?: new (...args: any[]) => ProfileSettingsComponent
|
||||||
configDefaults = {}
|
configDefaults = {}
|
||||||
|
|
||||||
abstract getBuiltinProfiles (): Promise<Profile[]>
|
abstract getBuiltinProfiles (): Promise<Profile[]>
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
.modal-body
|
.modal-body
|
||||||
input.form-control(
|
input.form-control(
|
||||||
[type]='password ? "password" : "text"',
|
[type]='password ? "password" : "text"',
|
||||||
autofocus,
|
autofocus,
|
||||||
[(ngModel)]='value',
|
[(ngModel)]='value',
|
||||||
#input,
|
#input,
|
||||||
[placeholder]='prompt',
|
[placeholder]='prompt',
|
||||||
(keyup.enter)='ok()',
|
(keyup.enter)='ok()',
|
||||||
(keyup.esc)='cancel()',
|
(keyup.esc)='cancel()',
|
||||||
)
|
)
|
||||||
.d-flex.align-items-start.mt-2
|
.d-flex.align-items-start.mt-2
|
||||||
checkbox(
|
checkbox(
|
||||||
*ngIf='showRememberCheckbox',
|
*ngIf='showRememberCheckbox',
|
||||||
[(ngModel)]='remember',
|
[(ngModel)]='remember',
|
||||||
text='Remember'
|
text='Remember'
|
||||||
)
|
)
|
||||||
button.btn.btn-primary.ml-auto(
|
button.btn.btn-primary.ml-auto(
|
||||||
(click)='ok()',
|
(click)='ok()',
|
||||||
) Enter
|
) OK
|
||||||
|
@ -636,7 +636,7 @@ export class SplitTabComponent extends BaseTabComponent implements AfterViewInit
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable({ providedIn: 'root' })
|
||||||
export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> {
|
export class SplitTabRecoveryProvider extends TabRecoveryProvider<SplitTabComponent> {
|
||||||
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
async applicableTo (recoveryToken: RecoveryToken): Promise<boolean> {
|
||||||
return recoveryToken.type === 'app:split-tab'
|
return recoveryToken.type === 'app:split-tab'
|
||||||
|
@ -30,7 +30,7 @@ import { AlwaysVisibleTypeaheadDirective } from './directives/alwaysVisibleTypea
|
|||||||
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
|
import { FastHtmlBindDirective } from './directives/fastHtmlBind.directive'
|
||||||
import { DropZoneDirective } from './directives/dropZone.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 { AppService } from './services/app.service'
|
||||||
import { ConfigService } from './services/config.service'
|
import { ConfigService } from './services/config.service'
|
||||||
@ -43,6 +43,7 @@ import { AppHotkeyProvider } from './hotkeys'
|
|||||||
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu, ProfilesContextMenu } from './tabContextMenu'
|
import { TaskCompletionContextMenu, CommonOptionsContextMenu, TabManagementContextMenu, ProfilesContextMenu } from './tabContextMenu'
|
||||||
import { LastCLIHandler, ProfileCLIHandler } from './cli'
|
import { LastCLIHandler, ProfileCLIHandler } from './cli'
|
||||||
import { ButtonProvider } from './buttonProvider'
|
import { ButtonProvider } from './buttonProvider'
|
||||||
|
import { SplitLayoutProfilesService } from './profiles'
|
||||||
|
|
||||||
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
import 'perfect-scrollbar/css/perfect-scrollbar.css'
|
||||||
import 'ng2-dnd/bundles/style.css'
|
import 'ng2-dnd/bundles/style.css'
|
||||||
@ -57,12 +58,13 @@ const PROVIDERS = [
|
|||||||
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TabManagementContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
{ provide: TabContextMenuItemProvider, useClass: TaskCompletionContextMenu, multi: true },
|
||||||
{ provide: TabContextMenuItemProvider, useClass: ProfilesContextMenu, 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: ProfileCLIHandler, multi: true },
|
||||||
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true },
|
{ provide: CLIHandler, useClass: LastCLIHandler, multi: true },
|
||||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: { suppressScrollX: true } },
|
||||||
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
|
{ provide: FileProvider, useClass: VaultFileProvider, multi: true },
|
||||||
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
|
||||||
|
{ provide: ProfileProvider, useExisting: SplitLayoutProfilesService, multi: true },
|
||||||
]
|
]
|
||||||
|
|
||||||
/** @hidden */
|
/** @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 */
|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
|
||||||
import { Injectable } from '@angular/core'
|
import { Injectable } from '@angular/core'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
import { Subscription } from 'rxjs'
|
import { Subscription } from 'rxjs'
|
||||||
import { AppService } from './services/app.service'
|
import { AppService } from './services/app.service'
|
||||||
import { BaseTabComponent } from './components/baseTab.component'
|
import { BaseTabComponent } from './components/baseTab.component'
|
||||||
@ -10,6 +11,8 @@ import { MenuItemOptions } from './api/menu'
|
|||||||
import { ProfilesService } from './services/profiles.service'
|
import { ProfilesService } from './services/profiles.service'
|
||||||
import { TabsService } from './services/tabs.service'
|
import { TabsService } from './services/tabs.service'
|
||||||
import { HotkeysService } from './services/hotkeys.service'
|
import { HotkeysService } from './services/hotkeys.service'
|
||||||
|
import { PromptModalComponent } from './components/promptModal.component'
|
||||||
|
import { SplitLayoutProfilesService } from './profiles'
|
||||||
|
|
||||||
/** @hidden */
|
/** @hidden */
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -103,6 +106,8 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
|||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private app: AppService,
|
private app: AppService,
|
||||||
|
private ngbModal: NgbModal,
|
||||||
|
private splitLayoutProfilesService: SplitLayoutProfilesService,
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
@ -133,6 +138,21 @@ export class CommonOptionsContextMenu extends TabContextMenuItemProvider {
|
|||||||
})) as MenuItemOptions[],
|
})) 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
|
return items
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
.mb-4
|
.mb-4
|
||||||
|
|
||||||
.col-12.col-lg-8
|
.col-12.col-lg-8(*ngIf='this.profileProvider.settingsComponent')
|
||||||
ng-template(#placeholder)
|
ng-template(#placeholder)
|
||||||
|
|
||||||
.modal-footer
|
.modal-footer
|
||||||
|
@ -16,7 +16,7 @@ const iconsClassList = Object.keys(iconsData).map(
|
|||||||
template: require('./editProfileModal.component.pug'),
|
template: require('./editProfileModal.component.pug'),
|
||||||
})
|
})
|
||||||
export class EditProfileModalComponent {
|
export class EditProfileModalComponent {
|
||||||
@Input() profile: Profile|ConfigProxy
|
@Input() profile: Profile & ConfigProxy
|
||||||
@Input() profileProvider: ProfileProvider
|
@Input() profileProvider: ProfileProvider
|
||||||
@Input() settingsComponent: new () => ProfileSettingsComponent
|
@Input() settingsComponent: new () => ProfileSettingsComponent
|
||||||
groupNames: string[]
|
groupNames: string[]
|
||||||
@ -41,17 +41,20 @@ export class EditProfileModalComponent {
|
|||||||
|
|
||||||
ngOnInit () {
|
ngOnInit () {
|
||||||
this._profile = this.profile
|
this._profile = this.profile
|
||||||
this.profile = this.profilesService.getConfigProxyForProfile(this.profile)
|
this.profile = this.profilesService.getConfigProxyForProfile(this.profile) as any
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterViewInit () {
|
ngAfterViewInit () {
|
||||||
setTimeout(() => {
|
const componentType = this.profileProvider.settingsComponent
|
||||||
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(this.profileProvider.settingsComponent)
|
if (componentType) {
|
||||||
const componentRef = componentFactory.create(this.injector)
|
setTimeout(() => {
|
||||||
this.settingsComponentInstance = componentRef.instance
|
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentType)
|
||||||
this.settingsComponentInstance.profile = this.profile
|
const componentRef = componentFactory.create(this.injector)
|
||||||
this.placeholder.insert(componentRef.hostView)
|
this.settingsComponentInstance = componentRef.instance
|
||||||
})
|
this.settingsComponentInstance.profile = this.profile
|
||||||
|
this.placeholder.insert(componentRef.hostView)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
groupTypeahead = (text$: Observable<string>) =>
|
groupTypeahead = (text$: Observable<string>) =>
|
||||||
|
@ -206,6 +206,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
|
|||||||
ssh: 'secondary',
|
ssh: 'secondary',
|
||||||
serial: 'success',
|
serial: 'success',
|
||||||
telnet: 'info',
|
telnet: 'info',
|
||||||
|
'split-layout': 'primary',
|
||||||
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
|
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user