tab context menu option to save split layouts as profiles - fixes #3468

This commit is contained in:
Eugene Pankov 2021-07-11 12:38:35 +02:00
parent 908f90cd52
commit 2706045cc2
No known key found for this signature in database
GPG Key ID: 5896FCBBDD1CF4F4
9 changed files with 103 additions and 20 deletions

View File

@ -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[]>

View File

@ -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

View File

@ -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'

View File

@ -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 */

View 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()
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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>) =>

View File

@ -206,6 +206,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
ssh: 'secondary',
serial: 'success',
telnet: 'info',
'split-layout': 'primary',
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
}
}