diff --git a/tabby-core/src/services/config.service.ts b/tabby-core/src/services/config.service.ts index 8b5f7629..5d199f90 100644 --- a/tabby-core/src/services/config.service.ts +++ b/tabby-core/src/services/config.service.ts @@ -213,7 +213,9 @@ export class ConfigService { * Reads config YAML as string */ readRaw (): string { - return yaml.dump(this._store) + // Scrub undefined values + const cleanStore = JSON.parse(JSON.stringify(this._store)) + return yaml.dump(cleanStore) } /** @@ -351,6 +353,14 @@ export class ConfigService { delete window.localStorage.lastSerialConnection config.version = 3 } + if (config.version < 4) { + for (const p of config.profiles ?? []) { + if (!p.id) { + p.id = `${p.type}:custom:${uuidv4()}` + } + } + config.version = 4 + } } private async maybeDecryptConfig (store) { diff --git a/tabby-local/src/index.ts b/tabby-local/src/index.ts index d7633930..3730c93d 100644 --- a/tabby-local/src/index.ts +++ b/tabby-local/src/index.ts @@ -21,7 +21,7 @@ import { RecoveryProvider } from './recoveryProvider' import { ShellSettingsTabProvider } from './settings' import { TerminalConfigProvider } from './config' import { LocalTerminalHotkeyProvider } from './hotkeys' -import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu' +import { NewTabContextMenu } from './tabContextMenu' import { AutoOpenTabCLIHandler, OpenPathCLIHandler, TerminalCLIHandler } from './cli' import { LocalProfilesService } from './profiles' @@ -47,7 +47,6 @@ import { LocalProfilesService } from './profiles' { provide: ProfileProvider, useClass: LocalProfilesService, multi: true }, { provide: TabContextMenuItemProvider, useClass: NewTabContextMenu, multi: true }, - { provide: TabContextMenuItemProvider, useClass: SaveAsProfileContextMenu, multi: true }, { provide: CLIHandler, useClass: TerminalCLIHandler, multi: true }, { provide: CLIHandler, useClass: OpenPathCLIHandler, multi: true }, diff --git a/tabby-local/src/tabContextMenu.ts b/tabby-local/src/tabContextMenu.ts index 1671e652..108ad7e5 100644 --- a/tabby-local/src/tabContextMenu.ts +++ b/tabby-local/src/tabContextMenu.ts @@ -1,59 +1,9 @@ import { Inject, Injectable, Optional } from '@angular/core' -import { NgbModal } from '@ng-bootstrap/ng-bootstrap' -import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, NotificationsService, MenuItemOptions, ProfilesService, PromptModalComponent, TranslateService } from 'tabby-core' +import { ConfigService, BaseTabComponent, TabContextMenuItemProvider, MenuItemOptions, ProfilesService, TranslateService } from 'tabby-core' import { TerminalTabComponent } from './components/terminalTab.component' import { TerminalService } from './services/terminal.service' import { LocalProfile, UACService } from './api' -/** @hidden */ -@Injectable() -export class SaveAsProfileContextMenu extends TabContextMenuItemProvider { - constructor ( - private config: ConfigService, - private ngbModal: NgbModal, - private notifications: NotificationsService, - private translate: TranslateService, - ) { - super() - } - - async getItems (tab: BaseTabComponent): Promise { - if (!(tab instanceof TerminalTabComponent)) { - return [] - } - const terminalTab = tab - const items: MenuItemOptions[] = [ - { - label: this.translate.instant('Save as profile'), - click: async () => { - const modal = this.ngbModal.open(PromptModalComponent) - modal.componentInstance.prompt = this.translate.instant('New profile name') - const name = (await modal.result)?.value - if (!name) { - return - } - const profile = { - options: { - ...terminalTab.profile.options, - cwd: await terminalTab.session?.getWorkingDirectory() ?? terminalTab.profile.options.cwd, - }, - name, - type: 'local', - } - this.config.store.profiles = [ - ...this.config.store.profiles, - profile, - ] - this.config.save() - this.notifications.info(this.translate.instant('Saved')) - }, - }, - ] - - return items - } -} - /** @hidden */ @Injectable() export class NewTabContextMenu extends TabContextMenuItemProvider { diff --git a/tabby-terminal/src/index.ts b/tabby-terminal/src/index.ts index 35b0ac70..77cd82ed 100644 --- a/tabby-terminal/src/index.ts +++ b/tabby-terminal/src/index.ts @@ -28,7 +28,7 @@ import { PathDropDecorator } from './features/pathDrop' import { ZModemDecorator } from './features/zmodem' import { TerminalConfigProvider } from './config' import { TerminalHotkeyProvider } from './hotkeys' -import { CopyPasteContextMenu, MiscContextMenu, LegacyContextMenu, ReconnectContextMenu } from './tabContextMenu' +import { CopyPasteContextMenu, MiscContextMenu, LegacyContextMenu, ReconnectContextMenu, SaveAsProfileContextMenu } from './tabContextMenu' import { Frontend } from './frontends/frontend' import { XTermFrontend, XTermWebGLFrontend } from './frontends/xtermFrontend' @@ -60,6 +60,7 @@ import { DefaultColorSchemes } from './colorSchemes' { provide: TabContextMenuItemProvider, useClass: MiscContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: LegacyContextMenu, multi: true }, { provide: TabContextMenuItemProvider, useClass: ReconnectContextMenu, multi: true }, + { provide: TabContextMenuItemProvider, useClass: SaveAsProfileContextMenu, multi: true }, { provide: CLIHandler, useClass: TerminalCLIHandler, multi: true }, { provide: TerminalColorSchemeProvider, useClass: DefaultColorSchemes, multi: true }, diff --git a/tabby-terminal/src/tabContextMenu.ts b/tabby-terminal/src/tabContextMenu.ts index 9be79476..bef7cbba 100644 --- a/tabby-terminal/src/tabContextMenu.ts +++ b/tabby-terminal/src/tabContextMenu.ts @@ -1,9 +1,12 @@ import { Injectable, Optional, Inject } from '@angular/core' -import { BaseTabComponent, TabContextMenuItemProvider, NotificationsService, MenuItemOptions, TranslateService, SplitTabComponent } from 'tabby-core' +import { NgbModal } from '@ng-bootstrap/ng-bootstrap' +import { BaseTabComponent, TabContextMenuItemProvider, NotificationsService, MenuItemOptions, TranslateService, SplitTabComponent, PromptModalComponent, ConfigService, PartialProfile, Profile } from 'tabby-core' import { BaseTerminalTabComponent } from './api/baseTerminalTab.component' import { TerminalContextMenuItemProvider } from './api/contextMenuProvider' import { MultifocusService } from './services/multifocus.service' import { ConnectableTerminalTabComponent } from './api/connectableTerminalTab.component' +import { v4 as uuidv4 } from 'uuid' +import slugify from 'slugify' /** @hidden */ @Injectable() @@ -150,3 +153,66 @@ export class LegacyContextMenu extends TabContextMenuItemProvider { } } + +/** @hidden */ +@Injectable() +export class SaveAsProfileContextMenu extends TabContextMenuItemProvider { + constructor ( + private config: ConfigService, + private ngbModal: NgbModal, + private notifications: NotificationsService, + private translate: TranslateService, + ) { + super() + } + + async getItems (tab: BaseTabComponent): Promise { + if (tab instanceof BaseTerminalTabComponent) { + return [ + { + label: this.translate.instant('Save as profile'), + click: async () => { + const modal = this.ngbModal.open(PromptModalComponent) + modal.componentInstance.prompt = this.translate.instant('New profile name') + modal.componentInstance.value = tab.profile.name + const name = (await modal.result)?.value + if (!name) { + return + } + + const options = { + ...tab.profile.options, + } + + const cwd = await tab.session?.getWorkingDirectory() ?? tab.profile.options.cwd + if (cwd) { + options.cwd = cwd + } + + const profile: PartialProfile = { + type: tab.profile.type, + name, + options, + } + + profile.id = `${profile.type}:custom:${slugify(name)}:${uuidv4()}` + profile.group = tab.profile.group + profile.icon = tab.profile.icon + profile.color = tab.profile.color + profile.disableDynamicTitle = tab.profile.disableDynamicTitle + profile.behaviorOnSessionEnd = tab.profile.behaviorOnSessionEnd + + this.config.store.profiles = [ + ...this.config.store.profiles, + profile, + ] + this.config.save() + this.notifications.info(this.translate.instant('Saved')) + }, + }, + ] + } + + return [] + } +}