strongly typed partial profiles wip

This commit is contained in:
Eugene Pankov
2021-07-13 23:44:23 +02:00
parent 5ddf36d4c1
commit 2f13f3a401
16 changed files with 118 additions and 107 deletions

View File

@@ -16,7 +16,7 @@ export { BootstrapData, PluginInfo, BOOTSTRAP_DATA } from './mainProcess'
export { HostWindowService } from './hostWindow' export { HostWindowService } from './hostWindow'
export { HostAppService, Platform } from './hostApp' export { HostAppService, Platform } from './hostApp'
export { FileProvider } from './fileProvider' export { FileProvider } from './fileProvider'
export { ProfileProvider, Profile, ProfileSettingsComponent } from './profileProvider' export { ProfileProvider, Profile, PartialProfile, ProfileSettingsComponent } from './profileProvider'
export { PromptModalComponent } from '../components/promptModal.component' export { PromptModalComponent } from '../components/promptModal.component'
export { AppService } from '../services/app.service' export { AppService } from '../services/app.service'

View File

@@ -1,45 +1,56 @@
/* eslint-disable @typescript-eslint/no-type-alias */
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-empty-function */
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { NewTabParameters } from '../services/tabs.service' import { NewTabParameters } from '../services/tabs.service'
export interface Profile { export interface Profile {
id?: string id: string
type: string type: string
name: string name: string
group?: string group?: string
options: Record<string, any> options: any
icon?: string icon?: string
color?: string color?: string
disableDynamicTitle?: boolean disableDynamicTitle: boolean
weight?: number weight: number
isBuiltin?: boolean isBuiltin: boolean
isTemplate?: boolean isTemplate: boolean
} }
export interface ProfileSettingsComponent { export type PartialProfile<T extends Profile> = Omit<Omit<Omit<{
profile: Profile [K in keyof T]?: T[K]
}, 'options'>, 'type'>, 'name'> & {
type: string
name: string
options?: {
[K in keyof T['options']]?: T['options'][K]
}
}
export interface ProfileSettingsComponent<P extends Profile> {
profile: P
save?: () => void save?: () => void
} }
export abstract class ProfileProvider { export abstract class ProfileProvider<P extends Profile> {
id: string id: string
name: string name: string
supportsQuickConnect = false supportsQuickConnect = false
settingsComponent?: new (...args: any[]) => ProfileSettingsComponent settingsComponent?: new (...args: any[]) => ProfileSettingsComponent<P>
configDefaults = {} configDefaults = {}
abstract getBuiltinProfiles (): Promise<Profile[]> abstract getBuiltinProfiles (): Promise<PartialProfile<P>[]>
abstract getNewTabParameters (profile: Profile): Promise<NewTabParameters<BaseTabComponent>> abstract getNewTabParameters (profile: PartialProfile<P>): Promise<NewTabParameters<BaseTabComponent>>
abstract getDescription (profile: Profile): string abstract getDescription (profile: PartialProfile<P>): string
quickConnect (query: string): Profile|null { quickConnect (query: string): PartialProfile<P>|null {
return null return null
} }
deleteProfile (profile: Profile): void { } deleteProfile (profile: P): void { }
} }

View File

@@ -3,7 +3,7 @@ import { Injectable } from '@angular/core'
import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider' import { ToolbarButton, ToolbarButtonProvider } from './api/toolbarButtonProvider'
import { HostAppService, Platform } from './api/hostApp' import { HostAppService, Platform } from './api/hostApp'
import { Profile } from './api/profileProvider' import { PartialProfile, Profile } from './api/profileProvider'
import { ConfigService } from './services/config.service' import { ConfigService } from './services/config.service'
import { HotkeysService } from './services/hotkeys.service' import { HotkeysService } from './services/hotkeys.service'
import { ProfilesService } from './services/profiles.service' import { ProfilesService } from './services/profiles.service'
@@ -32,7 +32,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
} }
} }
async launchProfile (profile: Profile) { async launchProfile (profile: PartialProfile<Profile>) {
await this.profilesService.openNewTabForProfile(profile) await this.profilesService.openNewTabForProfile(profile)
let recentProfiles = this.config.store.recentProfiles let recentProfiles = this.config.store.recentProfiles

View File

@@ -1,7 +1,7 @@
import slugify from 'slugify' import slugify from 'slugify'
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from 'uuid'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ConfigService, NewTabParameters, Profile, ProfileProvider } from './api' import { ConfigService, NewTabParameters, PartialProfile, Profile, ProfileProvider } from './api'
import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component' import { SplitTabComponent, SplitTabRecoveryProvider } from './components/splitTab.component'
export interface SplitLayoutProfileOptions { export interface SplitLayoutProfileOptions {
@@ -13,7 +13,7 @@ export interface SplitLayoutProfile extends Profile {
} }
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class SplitLayoutProfilesService extends ProfileProvider { export class SplitLayoutProfilesService extends ProfileProvider<SplitLayoutProfile> {
id = 'split-layout' id = 'split-layout'
name = 'Saved layout' name = 'Saved layout'
configDefaults = { configDefaults = {
@@ -29,7 +29,7 @@ export class SplitLayoutProfilesService extends ProfileProvider {
super() super()
} }
async getBuiltinProfiles (): Promise<Profile[]> { async getBuiltinProfiles (): Promise<PartialProfile<SplitLayoutProfile>[]> {
return [] return []
} }
@@ -43,7 +43,7 @@ export class SplitLayoutProfilesService extends ProfileProvider {
async createProfile (tab: SplitTabComponent, name: string): Promise<void> { async createProfile (tab: SplitTabComponent, name: string): Promise<void> {
const token = await tab.getRecoveryToken() const token = await tab.getRecoveryToken()
const profile: SplitLayoutProfile = { const profile: PartialProfile<SplitLayoutProfile> = {
id: `${this.id}:custom:${slugify(name)}:${uuidv4()}`, id: `${this.id}:custom:${slugify(name)}:${uuidv4()}`,
type: this.id, type: this.id,
name, name,

View File

@@ -1,8 +1,8 @@
import { Injectable, Inject } from '@angular/core' import { Injectable, Inject } from '@angular/core'
import { NewTabParameters } from './tabs.service' import { NewTabParameters } from './tabs.service'
import { BaseTabComponent } from '../components/baseTab.component' import { BaseTabComponent } from '../components/baseTab.component'
import { Profile, ProfileProvider } from '../api/profileProvider' import { PartialProfile, Profile, ProfileProvider } from '../api/profileProvider'
import { SelectorOption } from '../api/selector' ;import { SelectorOption } from '../api/selector'
import { AppService } from './app.service' import { AppService } from './app.service'
import { configMerge, ConfigProxy, ConfigService } from './config.service' import { configMerge, ConfigProxy, ConfigService } from './config.service'
import { NotificationsService } from './notifications.service' import { NotificationsService } from './notifications.service'
@@ -29,15 +29,18 @@ export class ProfilesService {
private config: ConfigService, private config: ConfigService,
private notifications: NotificationsService, private notifications: NotificationsService,
private selector: SelectorService, private selector: SelectorService,
@Inject(ProfileProvider) private profileProviders: ProfileProvider[], @Inject(ProfileProvider) private profileProviders: ProfileProvider<Profile>[],
) { } ) { }
async openNewTabForProfile (profile: Profile): Promise<BaseTabComponent|null> { async openNewTabForProfile <P extends Profile> (profile: PartialProfile<P>): Promise<BaseTabComponent|null> {
const params = await this.newTabParametersForProfile(profile) const params = await this.newTabParametersForProfile(profile)
if (params) { if (params) {
const tab = this.app.openNewTab(params) const tab = this.app.openNewTab(params)
;(this.app.getParentTab(tab) ?? tab).color = profile.color ?? null ;(this.app.getParentTab(tab) ?? tab).color = profile.color ?? null
tab.setTitle(profile.name)
if (profile.name) {
tab.setTitle(profile.name)
}
if (profile.disableDynamicTitle) { if (profile.disableDynamicTitle) {
tab['enableDynamicTitle'] = false tab['enableDynamicTitle'] = false
} }
@@ -46,16 +49,16 @@ export class ProfilesService {
return null return null
} }
async newTabParametersForProfile (profile: Profile): Promise<NewTabParameters<BaseTabComponent>|null> { async newTabParametersForProfile <P extends Profile> (profile: PartialProfile<P>): Promise<NewTabParameters<BaseTabComponent>|null> {
profile = this.getConfigProxyForProfile(profile) const fullProfile = this.getConfigProxyForProfile(profile)
return this.providerForProfile(profile)?.getNewTabParameters(profile) ?? null return this.providerForProfile(fullProfile)?.getNewTabParameters(fullProfile) ?? null
} }
getProviders (): ProfileProvider[] { getProviders (): ProfileProvider<Profile>[] {
return [...this.profileProviders] return [...this.profileProviders]
} }
async getProfiles (): Promise<Profile[]> { async getProfiles (): Promise<PartialProfile<Profile>[]> {
const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles())) const lists = await Promise.all(this.config.enabledServices(this.profileProviders).map(x => x.getBuiltinProfiles()))
let list = lists.reduce((a, b) => a.concat(b), []) let list = lists.reduce((a, b) => a.concat(b), [])
list = [ list = [
@@ -68,28 +71,29 @@ export class ProfilesService {
return list return list
} }
providerForProfile (profile: Profile): ProfileProvider|null { providerForProfile <T extends Profile> (profile: PartialProfile<T>): ProfileProvider<T>|null {
return this.profileProviders.find(x => x.id === profile.type) ?? null const provider = this.profileProviders.find(x => x.id === profile.type) ?? null
return provider as unknown as ProfileProvider<T>|null
} }
getDescription (profile: Profile): string|null { getDescription <P extends Profile> (profile: PartialProfile<P>): string|null {
profile = this.getConfigProxyForProfile(profile) profile = this.getConfigProxyForProfile(profile)
return this.providerForProfile(profile)?.getDescription(profile) ?? null return this.providerForProfile(profile)?.getDescription(profile) ?? null
} }
selectorOptionForProfile <T> (profile: Profile): SelectorOption<T> { selectorOptionForProfile <P extends Profile, T> (profile: PartialProfile<P>): SelectorOption<T> {
profile = this.getConfigProxyForProfile(profile) const fullProfile = this.getConfigProxyForProfile(profile)
return { return {
icon: profile.icon, icon: profile.icon,
name: profile.group ? `${profile.group} / ${profile.name}` : profile.name, name: profile.group ? `${fullProfile.group} / ${fullProfile.name}` : fullProfile.name,
description: this.providerForProfile(profile)?.getDescription(profile), description: this.providerForProfile(fullProfile)?.getDescription(fullProfile),
} }
} }
showProfileSelector (): Promise<Profile|null> { showProfileSelector (): Promise<PartialProfile<Profile>|null> {
return new Promise<Profile|null>(async (resolve, reject) => { return new Promise<PartialProfile<Profile>|null>(async (resolve, reject) => {
try { try {
const recentProfiles: Profile[] = this.config.store.recentProfiles const recentProfiles: PartialProfile<Profile>[] = this.config.store.recentProfiles
let options: SelectorOption<void>[] = recentProfiles.map(p => ({ let options: SelectorOption<void>[] = recentProfiles.map(p => ({
...this.selectorOptionForProfile(p), ...this.selectorOptionForProfile(p),
@@ -159,7 +163,7 @@ export class ProfilesService {
}) })
} }
async quickConnect (query: string): Promise<Profile|null> { async quickConnect (query: string): Promise<PartialProfile<Profile>|null> {
for (const provider of this.getProviders()) { for (const provider of this.getProviders()) {
if (provider.supportsQuickConnect) { if (provider.supportsQuickConnect) {
const profile = provider.quickConnect(query) const profile = provider.quickConnect(query)
@@ -172,9 +176,9 @@ export class ProfilesService {
return null return null
} }
getConfigProxyForProfile (profile: Profile): Profile { getConfigProxyForProfile <T extends Profile> (profile: PartialProfile<T>): T {
const provider = this.providerForProfile(profile) const provider = this.providerForProfile(profile)
const defaults = configMerge(this.profileDefaults, provider?.configDefaults ?? {}) const defaults = configMerge(this.profileDefaults, provider?.configDefaults ?? {})
return new ConfigProxy(profile, defaults) as unknown as Profile return new ConfigProxy(profile, defaults) as unknown as T
} }
} }

View File

@@ -10,7 +10,7 @@ import { ProfileSettingsComponent } from 'tabby-core'
@Component({ @Component({
template: require('./localProfileSettings.component.pug'), template: require('./localProfileSettings.component.pug'),
}) })
export class LocalProfileSettingsComponent implements ProfileSettingsComponent { export class LocalProfileSettingsComponent implements ProfileSettingsComponent<LocalProfile> {
profile: LocalProfile profile: LocalProfile
constructor ( constructor (

View File

@@ -1,12 +1,12 @@
import deepClone from 'clone-deep' import deepClone from 'clone-deep'
import { Injectable, Inject } from '@angular/core' import { Injectable, Inject } from '@angular/core'
import { ProfileProvider, Profile, NewTabParameters, ConfigService, SplitTabComponent, AppService } from 'tabby-core' import { ProfileProvider, NewTabParameters, ConfigService, SplitTabComponent, AppService, PartialProfile } from 'tabby-core'
import { TerminalTabComponent } from './components/terminalTab.component' import { TerminalTabComponent } from './components/terminalTab.component'
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component' import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
import { ShellProvider, Shell, SessionOptions } from './api' import { ShellProvider, Shell, SessionOptions, LocalProfile } from './api'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class LocalProfilesService extends ProfileProvider { export class LocalProfilesService extends ProfileProvider<LocalProfile> {
id = 'local' id = 'local'
name = 'Local' name = 'Local'
settingsComponent = LocalProfileSettingsComponent settingsComponent = LocalProfileSettingsComponent
@@ -34,7 +34,7 @@ export class LocalProfilesService extends ProfileProvider {
super() super()
} }
async getBuiltinProfiles (): Promise<Profile[]> { async getBuiltinProfiles (): Promise<PartialProfile<LocalProfile>[]> {
return (await this.getShells()).map(shell => ({ return (await this.getShells()).map(shell => ({
id: `local:${shell.id}`, id: `local:${shell.id}`,
type: 'local', type: 'local',
@@ -45,20 +45,20 @@ export class LocalProfilesService extends ProfileProvider {
})) }))
} }
async getNewTabParameters (profile: Profile): Promise<NewTabParameters<TerminalTabComponent>> { async getNewTabParameters (profile: PartialProfile<LocalProfile>): Promise<NewTabParameters<TerminalTabComponent>> {
profile = deepClone(profile) profile = deepClone(profile)
if (!profile.options?.cwd) { if (!profile.options?.cwd) {
if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) { if (this.app.activeTab instanceof TerminalTabComponent && this.app.activeTab.session) {
profile.options ??= {} profile.options ??= {}
profile.options.cwd = await this.app.activeTab.session.getWorkingDirectory() profile.options.cwd = await this.app.activeTab.session.getWorkingDirectory() ?? undefined
} }
if (this.app.activeTab instanceof SplitTabComponent) { if (this.app.activeTab instanceof SplitTabComponent) {
const focusedTab = this.app.activeTab.getFocusedTab() const focusedTab = this.app.activeTab.getFocusedTab()
if (focusedTab instanceof TerminalTabComponent && focusedTab.session) { if (focusedTab instanceof TerminalTabComponent && focusedTab.session) {
profile.options ??= {} profile.options ??= {}
profile.options.cwd = await focusedTab.session.getWorkingDirectory() profile.options!.cwd = await focusedTab.session.getWorkingDirectory() ?? undefined
} }
} }
} }
@@ -84,7 +84,7 @@ export class LocalProfilesService extends ProfileProvider {
} }
} }
getDescription (profile: Profile): string { getDescription (profile: PartialProfile<LocalProfile>): string {
return profile.options?.command return profile.options?.command ?? ''
} }
} }

View File

@@ -1,6 +1,6 @@
import * as fs from 'mz/fs' import * as fs from 'mz/fs'
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { Logger, LogService, ConfigService, ProfilesService } from 'tabby-core' import { Logger, LogService, ConfigService, ProfilesService, PartialProfile } from 'tabby-core'
import { TerminalTabComponent } from '../components/terminalTab.component' import { TerminalTabComponent } from '../components/terminalTab.component'
import { LocalProfile } from '../api' import { LocalProfile } from '../api'
@@ -17,40 +17,42 @@ export class TerminalService {
this.logger = log.create('terminal') this.logger = log.create('terminal')
} }
async getDefaultProfile (): Promise<LocalProfile> { async getDefaultProfile (): Promise<PartialProfile<LocalProfile>> {
const profiles = await this.profilesService.getProfiles() const profiles = await this.profilesService.getProfiles()
let profile = profiles.find(x => x.id === this.config.store.terminal.profile) let profile = profiles.find(x => x.id === this.config.store.terminal.profile)
if (!profile) { if (!profile) {
profile = profiles.filter(x => x.type === 'local' && x.isBuiltin)[0] profile = profiles.filter(x => x.type === 'local' && x.isBuiltin)[0]
} }
return profile as LocalProfile return profile as PartialProfile<LocalProfile>
} }
/** /**
* Launches a new terminal with a specific shell and CWD * Launches a new terminal with a specific shell and CWD
* @param pause Wait for a keypress when the shell exits * @param pause Wait for a keypress when the shell exits
*/ */
async openTab (profile?: LocalProfile|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> { async openTab (profile?: PartialProfile<LocalProfile>|null, cwd?: string|null, pause?: boolean): Promise<TerminalTabComponent> {
if (!profile) { if (!profile) {
profile = await this.getDefaultProfile() profile = await this.getDefaultProfile()
} }
cwd = cwd ?? profile.options.cwd const fullProfile = this.profilesService.getConfigProxyForProfile(profile)
cwd = cwd ?? fullProfile.options.cwd
if (cwd && !fs.existsSync(cwd)) { if (cwd && !fs.existsSync(cwd)) {
console.warn('Ignoring non-existent CWD:', cwd) console.warn('Ignoring non-existent CWD:', cwd)
cwd = null cwd = null
} }
this.logger.info(`Starting profile ${profile.name}`, profile) this.logger.info(`Starting profile ${fullProfile.name}`, fullProfile)
const options = { const options = {
...profile.options, ...fullProfile.options,
pauseAfterExit: pause, pauseAfterExit: pause,
cwd: cwd ?? undefined, cwd: cwd ?? undefined,
} }
return (await this.profilesService.openNewTabForProfile({ return (await this.profilesService.openNewTabForProfile({
...profile, ...fullProfile,
options, options,
})) as TerminalTabComponent })) as TerminalTabComponent
} }

View File

@@ -9,7 +9,7 @@ import { SerialService } from '../services/serial.service'
@Component({ @Component({
template: require('./serialProfileSettings.component.pug'), template: require('./serialProfileSettings.component.pug'),
}) })
export class SerialProfileSettingsComponent implements ProfileSettingsComponent { export class SerialProfileSettingsComponent implements ProfileSettingsComponent<SerialProfile> {
profile: SerialProfile profile: SerialProfile
foundPorts: SerialPortInfo[] foundPorts: SerialPortInfo[]
Platform = Platform Platform = Platform

View File

@@ -10,7 +10,7 @@ import { SerialService } from './services/serial.service'
import { BAUD_RATES, SerialProfile } from './api' import { BAUD_RATES, SerialProfile } from './api'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class SerialProfilesService extends ProfileProvider { export class SerialProfilesService extends ProfileProvider<SerialProfile> {
id = 'serial' id = 'serial'
name = 'Serial' name = 'Serial'
settingsComponent = SerialProfileSettingsComponent settingsComponent = SerialProfileSettingsComponent

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import SerialPort from 'serialport' import SerialPort from 'serialport'
import { ProfilesService } from 'tabby-core' import { PartialProfile, ProfilesService } from 'tabby-core'
import { SerialPortInfo, SerialProfile } from '../api' import { SerialPortInfo, SerialProfile } from '../api'
import { SerialTabComponent } from '../components/serialTab.component' import { SerialTabComponent } from '../components/serialTab.component'
@@ -24,19 +24,12 @@ export class SerialService {
baudrate = parseInt(path.split('@')[1]) baudrate = parseInt(path.split('@')[1])
path = path.split('@')[0] path = path.split('@')[0]
} }
const profile: SerialProfile = { const profile: PartialProfile<SerialProfile> = {
name: query, name: query,
type: 'serial', type: 'serial',
options: { options: {
port: path, port: path,
baudrate: baudrate, baudrate: baudrate,
databits: 8,
parity: 'none',
rtscts: false,
stopbits: 1,
xany: false,
xoff: false,
xon: false,
}, },
} }
window.localStorage.lastSerialConnection = JSON.stringify(profile) window.localStorage.lastSerialConnection = JSON.stringify(profile)

View File

@@ -15,15 +15,15 @@ const iconsClassList = Object.keys(iconsData).map(
@Component({ @Component({
template: require('./editProfileModal.component.pug'), template: require('./editProfileModal.component.pug'),
}) })
export class EditProfileModalComponent { export class EditProfileModalComponent<P extends Profile> {
@Input() profile: Profile & ConfigProxy @Input() profile: P & ConfigProxy
@Input() profileProvider: ProfileProvider @Input() profileProvider: ProfileProvider<P>
@Input() settingsComponent: new () => ProfileSettingsComponent @Input() settingsComponent: new () => ProfileSettingsComponent<P>
groupNames: string[] groupNames: string[]
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef @ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
private _profile: Profile private _profile: Profile
private settingsComponentInstance: ProfileSettingsComponent private settingsComponentInstance: ProfileSettingsComponent<P>
constructor ( constructor (
private injector: Injector, private injector: Injector,

View File

@@ -3,12 +3,12 @@ import slugify from 'slugify'
import deepClone from 'clone-deep' import deepClone from 'clone-deep'
import { Component } from '@angular/core' import { Component } 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 } from 'tabby-core' import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent, PartialProfile } from 'tabby-core'
import { EditProfileModalComponent } from './editProfileModal.component' import { EditProfileModalComponent } from './editProfileModal.component'
interface ProfileGroup { interface ProfileGroup {
name?: string name?: string
profiles: Profile[] profiles: PartialProfile<Profile>[]
editable: boolean editable: boolean
collapsed: boolean collapsed: boolean
} }
@@ -19,9 +19,9 @@ interface ProfileGroup {
styles: [require('./profilesSettingsTab.component.scss')], styles: [require('./profilesSettingsTab.component.scss')],
}) })
export class ProfilesSettingsTabComponent extends BaseComponent { export class ProfilesSettingsTabComponent extends BaseComponent {
profiles: Profile[] = [] profiles: PartialProfile<Profile>[] = []
builtinProfiles: Profile[] = [] builtinProfiles: PartialProfile<Profile>[] = []
templateProfiles: Profile[] = [] templateProfiles: PartialProfile<Profile>[] = []
profileGroups: ProfileGroup[] profileGroups: ProfileGroup[]
filter = '' filter = ''
@@ -45,11 +45,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
this.subscribeUntilDestroyed(this.config.changed$, () => this.refresh()) this.subscribeUntilDestroyed(this.config.changed$, () => this.refresh())
} }
launchProfile (profile: Profile): void { launchProfile (profile: PartialProfile<Profile>): void {
this.profilesService.openNewTabForProfile(profile) this.profilesService.openNewTabForProfile(profile)
} }
async newProfile (base?: Profile): Promise<void> { async newProfile (base?: PartialProfile<Profile>): Promise<void> {
if (!base) { if (!base) {
const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles] const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0)) profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
@@ -57,7 +57,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
'Select a base profile to use as a template', 'Select a base profile to use as a template',
profiles.map(p => ({ profiles.map(p => ({
icon: p.icon, icon: p.icon,
description: this.profilesService.providerForProfile(p)?.getDescription(p), description: this.profilesService.getDescription(p) ?? undefined,
name: p.group ? `${p.group} / ${p.name}` : p.name, name: p.group ? `${p.group} / ${p.name}` : p.name,
result: p, result: p,
})), })),
@@ -74,7 +74,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
await this.config.save() await this.config.save()
} }
async editProfile (profile: Profile): Promise<void> { async editProfile (profile: PartialProfile<Profile>): Promise<void> {
const modal = this.ngbModal.open( const modal = this.ngbModal.open(
EditProfileModalComponent, EditProfileModalComponent,
{ size: 'lg' }, { size: 'lg' },
@@ -93,7 +93,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
await this.config.save() await this.config.save()
} }
async deleteProfile (profile: Profile): Promise<void> { async deleteProfile (profile: PartialProfile<Profile>): Promise<void> {
if ((await this.platform.showMessageBox( if ((await this.platform.showMessageBox(
{ {
type: 'warning', type: 'warning',
@@ -102,7 +102,8 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
defaultId: 0, defaultId: 0,
} }
)).response === 1) { )).response === 1) {
this.profilesService.providerForProfile(profile)?.deleteProfile(profile) this.profilesService.providerForProfile(profile)?.deleteProfile(
this.profilesService.getConfigProxyForProfile(profile))
this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile) this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile)
await this.config.save() await this.config.save()
} }
@@ -181,7 +182,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
return !this.filter || group.profiles.some(x => this.isProfileVisible(x)) return !this.filter || group.profiles.some(x => this.isProfileVisible(x))
} }
isProfileVisible (profile: Profile): boolean { isProfileVisible (profile: PartialProfile<Profile>): boolean {
return !this.filter || profile.name.toLowerCase().includes(this.filter.toLowerCase()) return !this.filter || profile.name.toLowerCase().includes(this.filter.toLowerCase())
} }
@@ -189,11 +190,11 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
return icon?.startsWith('<') ?? false return icon?.startsWith('<') ?? false
} }
getDescription (profile: Profile): string|null { getDescription (profile: PartialProfile<Profile>): string|null {
return this.profilesService.getDescription(profile) return this.profilesService.getDescription(profile)
} }
getTypeLabel (profile: Profile): string { getTypeLabel (profile: PartialProfile<Profile>): string {
const name = this.profilesService.providerForProfile(profile)?.name const name = this.profilesService.providerForProfile(profile)?.name
if (name === 'Local') { if (name === 'Local') {
return '' return ''
@@ -201,7 +202,7 @@ export class ProfilesSettingsTabComponent extends BaseComponent {
return name ?? 'Unknown' return name ?? 'Unknown'
} }
getTypeColorClass (profile: Profile): string { getTypeColorClass (profile: PartialProfile<Profile>): string {
return { return {
ssh: 'secondary', ssh: 'secondary',
serial: 'success', serial: 'success',

View File

@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ProfileProvider, Profile, NewTabParameters } from 'tabby-core' import { ProfileProvider, NewTabParameters, PartialProfile } from 'tabby-core'
import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component' import { SSHProfileSettingsComponent } from './components/sshProfileSettings.component'
import { SSHTabComponent } from './components/sshTab.component' import { SSHTabComponent } from './components/sshTab.component'
import { PasswordStorageService } from './services/passwordStorage.service' import { PasswordStorageService } from './services/passwordStorage.service'
@@ -8,7 +8,7 @@ import { ALGORITHM_BLACKLIST, SSHAlgorithmType, SSHProfile } from './api'
import * as ALGORITHMS from 'ssh2/lib/protocol/constants' import * as ALGORITHMS from 'ssh2/lib/protocol/constants'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class SSHProfilesService extends ProfileProvider { export class SSHProfilesService extends ProfileProvider<SSHProfile> {
id = 'ssh' id = 'ssh'
name = 'SSH' name = 'SSH'
supportsQuickConnect = true supportsQuickConnect = true
@@ -66,7 +66,7 @@ export class SSHProfilesService extends ProfileProvider {
} }
} }
async getBuiltinProfiles (): Promise<Profile[]> { async getBuiltinProfiles (): Promise<PartialProfile<SSHProfile>[]> {
return [{ return [{
id: `ssh:template`, id: `ssh:template`,
type: 'ssh', type: 'ssh',
@@ -83,22 +83,22 @@ export class SSHProfilesService extends ProfileProvider {
}] }]
} }
async getNewTabParameters (profile: Profile): Promise<NewTabParameters<SSHTabComponent>> { async getNewTabParameters (profile: PartialProfile<SSHProfile>): Promise<NewTabParameters<SSHTabComponent>> {
return { return {
type: SSHTabComponent, type: SSHTabComponent,
inputs: { profile }, inputs: { profile },
} }
} }
getDescription (profile: SSHProfile): string { getDescription (profile: PartialProfile<SSHProfile>): string {
return profile.options.host return profile.options?.host ?? ''
} }
deleteProfile (profile: SSHProfile): void { deleteProfile (profile: SSHProfile): void {
this.passwordStorage.deletePassword(profile) this.passwordStorage.deletePassword(profile)
} }
quickConnect (query: string): SSHProfile { quickConnect (query: string): PartialProfile<SSHProfile> {
let user = 'root' let user = 'root'
let host = query let host = query
let port = 22 let port = 22

View File

@@ -8,6 +8,6 @@ import { TelnetProfile } from '../session'
@Component({ @Component({
template: require('./telnetProfileSettings.component.pug'), template: require('./telnetProfileSettings.component.pug'),
}) })
export class TelnetProfileSettingsComponent implements ProfileSettingsComponent { export class TelnetProfileSettingsComponent implements ProfileSettingsComponent<TelnetProfile> {
profile: TelnetProfile profile: TelnetProfile
} }

View File

@@ -1,11 +1,11 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ProfileProvider, Profile, NewTabParameters } from 'tabby-core' import { ProfileProvider, NewTabParameters, PartialProfile } from 'tabby-core'
import { TelnetProfileSettingsComponent } from './components/telnetProfileSettings.component' import { TelnetProfileSettingsComponent } from './components/telnetProfileSettings.component'
import { TelnetTabComponent } from './components/telnetTab.component' import { TelnetTabComponent } from './components/telnetTab.component'
import { TelnetProfile } from './session' import { TelnetProfile } from './session'
@Injectable({ providedIn: 'root' }) @Injectable({ providedIn: 'root' })
export class TelnetProfilesService extends ProfileProvider { export class TelnetProfilesService extends ProfileProvider<TelnetProfile> {
id = 'telnet' id = 'telnet'
name = 'Telnet' name = 'Telnet'
supportsQuickConnect = false supportsQuickConnect = false
@@ -22,7 +22,7 @@ export class TelnetProfilesService extends ProfileProvider {
}, },
} }
async getBuiltinProfiles (): Promise<TelnetProfile[]> { async getBuiltinProfiles (): Promise<PartialProfile<TelnetProfile>[]> {
return [ return [
{ {
id: `telnet:template`, id: `telnet:template`,
@@ -55,7 +55,7 @@ export class TelnetProfilesService extends ProfileProvider {
] ]
} }
async getNewTabParameters (profile: Profile): Promise<NewTabParameters<TelnetTabComponent>> { async getNewTabParameters (profile: PartialProfile<TelnetProfile>): Promise<NewTabParameters<TelnetTabComponent>> {
return { return {
type: TelnetTabComponent, type: TelnetTabComponent,
inputs: { profile }, inputs: { profile },
@@ -66,7 +66,7 @@ export class TelnetProfilesService extends ProfileProvider {
return profile.options.host ? `${profile.options.host}:${profile.options.port}` : '' return profile.options.host ? `${profile.options.host}:${profile.options.port}` : ''
} }
quickConnect (query: string): TelnetProfile { quickConnect (query: string): PartialProfile<TelnetProfile> {
let host = query let host = query
let port = 23 let port = 23
if (host.includes('[')) { if (host.includes('[')) {