moved profile settings view to settings plugin, web entry cleanup

This commit is contained in:
Eugene Pankov
2021-07-07 01:22:50 +02:00
parent 93a89e3c86
commit 0ad32fa79d
27 changed files with 47 additions and 130 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "tabby-local",
"version": "1.0.144",
"version": "1.0.145-nightly.0",
"description": "Tabby's local shell plugin",
"keywords": [
"tabby-builtin-plugin"

View File

@@ -1,65 +0,0 @@
.modal-header
h3.m-0 {{profile.name}}
.modal-body
.row
.col-12.col-lg-4
.form-group
label Name
input.form-control(
type='text',
autofocus,
[(ngModel)]='profile.name',
)
.form-group
label Group
input.form-control(
type='text',
alwaysVisibleTypeahead,
placeholder='Ungrouped',
[(ngModel)]='profile.group',
[ngbTypeahead]='groupTypeahead',
)
.form-group
label Icon
.input-group
input.form-control(
type='text',
alwaysVisibleTypeahead,
[(ngModel)]='profile.icon',
[ngbTypeahead]='iconSearch',
[resultTemplate]='rt'
)
.input-group-append
.input-group-text
i([class]='"fa-fw " + profile.icon')
ng-template(#rt,let-r='result',let-t='term')
i([class]='"fa-fw " + r')
ngb-highlight.ml-2([result]='r', [term]='t')
.form-line
.header
.title Color
input.form-control.w-50(
type='text',
[(ngModel)]='profile.color',
placeholder='#000000'
)
.form-line
.header
.title Disable dynamic tab title
.description Connection name will be used instead
toggle([(ngModel)]='profile.disableDynamicTitle')
.mb-4
.col-12.col-lg-8
ng-template(#placeholder)
.modal-footer
button.btn.btn-outline-primary((click)='save()') Save
button.btn.btn-outline-danger((click)='cancel()') Cancel

View File

@@ -1,75 +0,0 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Observable, OperatorFunction, debounceTime, map, distinctUntilChanged } from 'rxjs'
import { Component, Input, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { UACService } from '../services/uac.service'
import { LocalProfile } from '../api'
import { ConfigService, Profile, ProfileProvider, ProfileSettingsComponent } from 'tabby-core'
const iconsData = require('../../../tabby-core/src/icons.json')
const iconsClassList = Object.keys(iconsData).map(
icon => iconsData[icon].map(
style => `fa${style[0]} fa-${icon}`
)
).flat()
/** @hidden */
@Component({
template: require('./editProfileModal.component.pug'),
})
export class EditProfileModalComponent {
@Input() profile: LocalProfile
@Input() profileProvider: ProfileProvider
@Input() settingsComponent: new () => ProfileSettingsComponent
groupNames: string[]
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
private settingsComponentInstance: ProfileSettingsComponent
constructor (
public uac: UACService,
private injector: Injector,
private componentFactoryResolver: ComponentFactoryResolver,
config: ConfigService,
private modalInstance: NgbActiveModal,
) {
this.groupNames = [...new Set(
(config.store.profiles as Profile[])
.map(x => x.group)
.filter(x => !!x)
)].sort() as string[]
}
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)
})
}
groupTypeahead = (text$: Observable<string>) =>
text$.pipe(
debounceTime(200),
distinctUntilChanged(),
map(q => this.groupNames.filter(x => !q || x.toLowerCase().includes(q.toLowerCase())))
)
iconSearch: OperatorFunction<string, string[]> = (text$: Observable<string>) =>
text$.pipe(
debounceTime(200),
map(term => iconsClassList.filter(v => v.toLowerCase().includes(term.toLowerCase())).slice(0, 10))
)
save () {
this.profile.group ||= undefined
this.settingsComponentInstance.save?.()
this.modalInstance.close(this.profile)
}
cancel () {
this.modalInstance.dismiss()
}
}

View File

@@ -1,94 +0,0 @@
h3.mb-3 Profiles
.form-line
.header
.title Default profile for new tabs
select.form-control(
[(ngModel)]='config.store.terminal.profile',
(ngModelChange)='config.save()',
)
option(
*ngFor='let profile of profiles',
[ngValue]='profile.id'
) {{profile.name}}
option(
*ngFor='let profile of builtinProfiles',
[ngValue]='profile.id'
) {{profile.name}}
.form-line(*ngIf='config.store.profiles.length > 0')
.header
.title Show built-in profiles in selector
.description If disabled, only custom profiles will show up in the profile selector
toggle(
[(ngModel)]='config.store.terminal.showBuiltinProfiles',
(ngModelChange)='config.save()'
)
.d-flex.mb-3.mt-4
.input-group
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', placeholder='Filter', [(ngModel)]='filter')
button.btn.btn-primary.flex-shrink-0.ml-3((click)='newProfile()')
i.fas.fa-fw.fa-plus
| New profile
.list-group.list-group-light.mt-3.mb-3
ng-container(*ngFor='let group of profileGroups')
ng-container(*ngIf='isGroupVisible(group)')
.list-group-item.list-group-item-action.d-flex.align-items-center(
(click)='group.collapsed = !group.collapsed'
)
.fa.fa-fw.fa-chevron-right(*ngIf='group.collapsed')
.fa.fa-fw.fa-chevron-down(*ngIf='!group.collapsed')
span.ml-3.mr-auto {{group.name || "Ungrouped"}}
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
*ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); editGroup(group)'
)
i.fas.fa-pencil-alt
button.btn.btn-sm.btn-link.hover-reveal.ml-2(
*ngIf='group.editable && group.name',
(click)='$event.stopPropagation(); deleteGroup(group)'
)
i.fas.fa-trash
ng-container(*ngIf='!group.collapsed')
ng-container(*ngFor='let profile of group.profiles')
.list-group-item.pl-5.d-flex.align-items-center(
*ngIf='isProfileVisible(profile)',
[class.list-group-item-action]='!profile.isBuiltin',
(click)='profile.isBuiltin ? null : editProfile(profile)'
)
i.icon(
class='fa-fw {{profile.icon}}',
[style.color]='profile.color',
*ngIf='!iconIsSVG(profile.icon)'
)
.icon(
[fastHtmlBind]='profile.icon',
*ngIf='iconIsSVG(profile.icon)'
)
div {{profile.name}}
.text-muted.ml-2 {{getDescription(profile)}}
.mr-auto
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); launchProfile(profile)')
i.fas.fa-play
button.btn.btn-link.hover-reveal.ml-1((click)='$event.stopPropagation(); newProfile(profile)')
i.fas.fa-copy
button.btn.btn-link.text-danger.hover-reveal.ml-1(
*ngIf='!profile.isBuiltin',
(click)='$event.stopPropagation(); deleteProfile(profile)'
)
i.fas.fa-trash
.ml-1(class='badge badge-{{getTypeColorClass(profile)}}') {{getTypeLabel(profile)}}

View File

@@ -1,8 +0,0 @@
.icon {
width: 1.25rem;
margin-right: 0.25rem;
}
.icon + * {
margin-left: 10px;
}

View File

@@ -1,204 +0,0 @@
import { v4 as uuidv4 } from 'uuid'
import slugify from 'slugify'
import deepClone from 'clone-deep'
import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { ConfigService, HostAppService, Profile, SelectorService, ProfilesService, PromptModalComponent, PlatformService, BaseComponent } from 'tabby-core'
import { EditProfileModalComponent } from './editProfileModal.component'
interface ProfileGroup {
name?: string
profiles: Profile[]
editable: boolean
collapsed: boolean
}
/** @hidden */
@Component({
template: require('./profilesSettingsTab.component.pug'),
styles: [require('./profilesSettingsTab.component.scss')],
})
export class ProfilesSettingsTabComponent extends BaseComponent {
profiles: Profile[] = []
builtinProfiles: Profile[] = []
templateProfiles: Profile[] = []
profileGroups: ProfileGroup[]
filter = ''
constructor (
public config: ConfigService,
public hostApp: HostAppService,
private profilesService: ProfilesService,
private selector: SelectorService,
private ngbModal: NgbModal,
private platform: PlatformService,
) {
super()
}
async ngOnInit (): Promise<void> {
this.refresh()
this.builtinProfiles = (await this.profilesService.getProfiles()).filter(x => x.isBuiltin)
this.templateProfiles = this.builtinProfiles.filter(x => x.isTemplate)
this.builtinProfiles = this.builtinProfiles.filter(x => !x.isTemplate)
this.refresh()
this.subscribeUntilDestroyed(this.config.changed$, () => this.refresh())
}
launchProfile (profile: Profile): void {
this.profilesService.openNewTabForProfile(profile)
}
async newProfile (base?: Profile): Promise<void> {
if (!base) {
const profiles = [...this.templateProfiles, ...this.builtinProfiles, ...this.profiles]
profiles.sort((a, b) => (a.weight ?? 0) - (b.weight ?? 0))
base = await this.selector.show(
'Select a base profile to use as a template',
profiles.map(p => ({
icon: p.icon,
description: this.profilesService.providerForProfile(p)?.getDescription(p),
name: p.group ? `${p.group} / ${p.name}` : p.name,
result: p,
})),
)
}
const profile = deepClone(base)
profile.id = null
profile.name = ''
profile.isBuiltin = false
profile.isTemplate = false
await this.editProfile(profile)
profile.id = `${profile.type}:custom:${slugify(profile.name)}:${uuidv4()}`
this.config.store.profiles = [profile, ...this.config.store.profiles]
await this.config.save()
}
async editProfile (profile: Profile): Promise<void> {
const modal = this.ngbModal.open(
EditProfileModalComponent,
{ size: 'lg' },
)
modal.componentInstance.profile = Object.assign({}, profile)
modal.componentInstance.profileProvider = this.profilesService.providerForProfile(profile)
const result = await modal.result
Object.assign(profile, result)
await this.config.save()
}
async deleteProfile (profile: Profile): Promise<void> {
if ((await this.platform.showMessageBox(
{
type: 'warning',
message: `Delete "${profile.name}"?`,
buttons: ['Keep', 'Delete'],
defaultId: 0,
}
)).response === 1) {
this.profilesService.providerForProfile(profile)?.deleteProfile(profile)
this.config.store.profiles = this.config.store.profiles.filter(x => x !== profile)
await this.config.save()
}
}
refresh (): void {
this.profiles = this.config.store.profiles
this.profileGroups = []
for (const profile of this.profiles) {
let group = this.profileGroups.find(x => x.name === profile.group)
if (!group) {
group = {
name: profile.group,
profiles: [],
editable: true,
collapsed: false,
}
this.profileGroups.push(group)
}
group.profiles.push(profile)
}
this.profileGroups.sort((a, b) => a.name?.localeCompare(b.name ?? '') ?? -1)
this.profileGroups.push({
name: 'Built-in',
profiles: this.builtinProfiles,
editable: false,
collapsed: false,
})
}
async editGroup (group: ProfileGroup): Promise<void> {
const modal = this.ngbModal.open(PromptModalComponent)
modal.componentInstance.prompt = 'New name'
modal.componentInstance.value = group.name
const result = await modal.result
if (result) {
for (const profile of this.profiles.filter(x => x.group === group.name)) {
profile.group = result.value
}
this.config.store.profiles = this.profiles
await this.config.save()
}
}
async deleteGroup (group: ProfileGroup): Promise<void> {
if ((await this.platform.showMessageBox(
{
type: 'warning',
message: `Delete "${group.name}"?`,
buttons: ['Keep', 'Delete'],
defaultId: 0,
}
)).response === 1) {
if ((await this.platform.showMessageBox(
{
type: 'warning',
message: `Delete the group's profiles?`,
buttons: ['Move to "Ungrouped"', 'Delete'],
defaultId: 0,
}
)).response === 0) {
for (const profile of this.profiles.filter(x => x.group === group.name)) {
delete profile.group
}
} else {
this.config.store.profiles = this.config.store.profiles.filter(x => x.group !== group.name)
}
await this.config.save()
}
}
isGroupVisible (group: ProfileGroup): boolean {
return !this.filter || group.profiles.some(x => this.isProfileVisible(x))
}
isProfileVisible (profile: Profile): boolean {
return !this.filter || profile.name.toLowerCase().includes(this.filter.toLowerCase())
}
iconIsSVG (icon?: string): boolean {
return icon?.startsWith('<') ?? false
}
getDescription (profile: Profile): string|null {
return this.profilesService.providerForProfile(profile)?.getDescription(profile) ?? null
}
getTypeLabel (profile: Profile): string {
const name = this.profilesService.providerForProfile(profile)?.name
if (name === 'Local') {
return ''
}
return name ?? 'Unknown'
}
getTypeColorClass (profile: Profile): string {
return {
ssh: 'secondary',
serial: 'success',
telnet: 'info',
}[this.profilesService.providerForProfile(profile)?.id ?? ''] ?? 'warning'
}
}

View File

@@ -5,16 +5,11 @@ export class TerminalConfigProvider extends ConfigProvider {
defaults = {
hotkeys: {
'copy-current-path': [],
profile: {
__nonStructural: true,
},
},
terminal: {
autoOpen: false,
useConPTY: true,
showBuiltinProfiles: true,
environment: {},
profiles: [],
setComSpec: false,
},
}

View File

@@ -11,9 +11,7 @@ import { SettingsTabProvider } from 'tabby-settings'
import { TerminalTabComponent } from './components/terminalTab.component'
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
import { EditProfileModalComponent } from './components/editProfileModal.component'
import { EnvironmentEditorComponent } from './components/environmentEditor.component'
import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component'
import { LocalProfileSettingsComponent } from './components/localProfileSettings.component'
import { TerminalService } from './services/terminal.service'
@@ -22,7 +20,7 @@ import { DockMenuService } from './services/dockMenu.service'
import { ButtonProvider } from './buttonProvider'
import { RecoveryProvider } from './recoveryProvider'
import { ShellProvider } from './api'
import { ProfilesSettingsTabProvider, ShellSettingsTabProvider } from './settings'
import { ShellSettingsTabProvider } from './settings'
import { TerminalConfigProvider } from './config'
import { LocalTerminalHotkeyProvider } from './hotkeys'
import { NewTabContextMenu, SaveAsProfileContextMenu } from './tabContextMenu'
@@ -56,7 +54,6 @@ import { LocalProfilesService } from './profiles'
],
providers: [
{ provide: SettingsTabProvider, useClass: ShellSettingsTabProvider, multi: true },
{ provide: SettingsTabProvider, useClass: ProfilesSettingsTabProvider, multi: true },
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
@@ -92,16 +89,12 @@ import { LocalProfilesService } from './profiles'
],
entryComponents: [
TerminalTabComponent,
ProfilesSettingsTabComponent,
ShellSettingsTabComponent,
EditProfileModalComponent,
LocalProfileSettingsComponent,
] as any[],
declarations: [
TerminalTabComponent,
ProfilesSettingsTabComponent,
ShellSettingsTabComponent,
EditProfileModalComponent,
EnvironmentEditorComponent,
LocalProfileSettingsComponent,
] as any[],

View File

@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core'
import { HostAppService, Platform } from 'tabby-core'
import { SettingsTabProvider } from 'tabby-settings'
import { ProfilesSettingsTabComponent } from './components/profilesSettingsTab.component'
import { ShellSettingsTabComponent } from './components/shellSettingsTab.component'
/** @hidden */
@@ -22,15 +21,3 @@ export class ShellSettingsTabProvider extends SettingsTabProvider {
}
}
}
/** @hidden */
@Injectable()
export class ProfilesSettingsTabProvider extends SettingsTabProvider {
id = 'profiles'
icon = 'window-restore'
title = 'Profiles'
getComponentType (): any {
return ProfilesSettingsTabComponent
}
}