project rename

This commit is contained in:
Eugene Pankov
2021-06-29 23:57:04 +02:00
parent c61be3d52b
commit 43cd3318da
609 changed files with 510 additions and 530 deletions

12
tabby-settings/src/api.ts Normal file
View File

@@ -0,0 +1,12 @@
/**
* Extend to add your own settings tabs
*/
export abstract class SettingsTabProvider {
id: string
icon: string
title: string
getComponentType (): any {
return null
}
}

View File

@@ -0,0 +1,42 @@
import { Injectable } from '@angular/core'
import { ToolbarButtonProvider, ToolbarButton, AppService, HostAppService, HotkeysService } from 'tabby-core'
import { SettingsTabComponent } from './components/settingsTab.component'
/** @hidden */
@Injectable()
export class ButtonProvider extends ToolbarButtonProvider {
constructor (
hostApp: HostAppService,
hotkeys: HotkeysService,
private app: AppService,
) {
super()
hostApp.settingsUIRequest$.subscribe(() => this.open())
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
if (hotkey === 'settings') {
this.open()
}
})
}
provide (): ToolbarButton[] {
return [{
icon: require('./icons/cog.svg'),
title: 'Settings',
touchBarNSImage: 'NSTouchBarComposeTemplate',
weight: 10,
click: (): void => this.open(),
}]
}
open (): void {
const settingsTab = this.app.tabs.find(tab => tab instanceof SettingsTabComponent)
if (settingsTab) {
this.app.selectTab(settingsTab)
} else {
this.app.openNewTabRaw(SettingsTabComponent)
}
}
}

View File

@@ -0,0 +1,12 @@
.modal-header
h5 Press the key now
.modal-body
.input
.stroke(*ngFor='let stroke of value', [@animateKey]='true') {{stroke}}
.timeout
div([style.width]='timeoutProgress + "%"')
.modal-footer
button.btn.btn-outline-primary((click)='close()') Cancel

View File

@@ -0,0 +1,22 @@
:host {
>.modal-body {
padding: 0 !important;
}
.input {
display: flex;
.stroke {
flex: none;
}
}
.timeout {
height: 5px;
margin: 0 0 15px;
div {
height: 5px;
}
}
}

View File

@@ -0,0 +1,88 @@
import { Component, Input } from '@angular/core'
import { trigger, transition, style, animate } from '@angular/animations'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { HotkeysService, BaseComponent } from 'tabby-core'
const INPUT_TIMEOUT = 1000
/** @hidden */
@Component({
selector: 'hotkey-input-modal',
template: require('./hotkeyInputModal.component.pug'),
styles: [require('./hotkeyInputModal.component.scss')],
animations: [
trigger('animateKey', [
transition(':enter', [
style({
transform: 'translateX(25px)',
opacity: '0',
}),
animate('250ms ease-out', style({
transform: 'translateX(0)',
opacity: '1',
})),
]),
transition(':leave', [
style({
transform: 'translateX(0)',
opacity: '1',
}),
animate('250ms ease-in', style({
transform: 'translateX(25px)',
opacity: '0',
})),
]),
]),
],
})
export class HotkeyInputModalComponent extends BaseComponent {
@Input() value: string[] = []
@Input() timeoutProgress = 0
private lastKeyEvent: number|null = null
private keyTimeoutInterval: number|null = null
constructor (
private modalInstance: NgbActiveModal,
public hotkeys: HotkeysService,
) {
super()
this.hotkeys.clearCurrentKeystrokes()
this.subscribeUntilDestroyed(hotkeys.key, (event) => {
this.lastKeyEvent = performance.now()
this.value = this.hotkeys.getCurrentKeystrokes()
event.preventDefault()
event.stopPropagation()
})
}
splitKeys (keys: string): string[] {
return keys.split('+').map((x) => x.trim())
}
ngOnInit (): void {
this.keyTimeoutInterval = window.setInterval(() => {
if (!this.lastKeyEvent) {
return
}
this.timeoutProgress = Math.min(100, (performance.now() - this.lastKeyEvent) * 100 / INPUT_TIMEOUT)
if (this.timeoutProgress === 100) {
clearInterval(this.keyTimeoutInterval!)
this.modalInstance.close(this.value)
}
}, 25)
this.hotkeys.disable()
}
ngOnDestroy (): void {
clearInterval(this.keyTimeoutInterval!)
this.hotkeys.clearCurrentKeystrokes()
this.hotkeys.enable()
super.ngOnDestroy()
}
close (): void {
clearInterval(this.keyTimeoutInterval!)
this.modalInstance.dismiss()
}
}

View File

@@ -0,0 +1,23 @@
h3.mb-3 Hotkeys
.input-group.mb-4
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-search
input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter')
.form-group
table.hotkeys-table
tr
th Name
th ID
th Hotkey
ng-container(*ngFor='let hotkey of hotkeyDescriptions')
tr(*ngIf='!hotkeyFilter || hotkeyFilterFn(hotkey, hotkeyFilter)')
td {{hotkey.name}}
td {{hotkey.id}}
td.pr-5
multi-hotkey-input(
[model]='getHotkey(hotkey.id) || []',
(modelChange)='setHotkey(hotkey.id, $event)'
)

View File

@@ -0,0 +1,7 @@
.hotkeys-table {
margin-top: 30px;
td, th {
padding: 5px 10px;
}
}

View File

@@ -0,0 +1,57 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component, NgZone } from '@angular/core'
import {
ConfigService,
HotkeyDescription,
HotkeysService,
HostAppService,
} from 'tabby-core'
/** @hidden */
@Component({
selector: 'hotkey-settings-tab',
template: require('./hotkeySettingsTab.component.pug'),
styles: [
require('./hotkeySettingsTab.component.scss'),
],
})
export class HotkeySettingsTabComponent {
hotkeyFilter = ''
hotkeyDescriptions: HotkeyDescription[]
constructor (
public config: ConfigService,
public hostApp: HostAppService,
public zone: NgZone,
hotkeys: HotkeysService,
) {
hotkeys.getHotkeyDescriptions().then(descriptions => {
this.hotkeyDescriptions = descriptions
})
}
getHotkey (id: string) {
let ptr = this.config.store.hotkeys
for (const token of id.split(/\./g)) {
ptr = ptr[token]
}
return ptr
}
setHotkey (id: string, value) {
let ptr = this.config.store
let prop = 'hotkeys'
for (const token of id.split(/\./g)) {
ptr = ptr[prop]
prop = token
}
ptr[prop] = value
this.config.save()
}
hotkeyFilterFn (hotkey: HotkeyDescription, query: string): boolean {
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
const s = hotkey.name + (this.getHotkey(hotkey.id) || []).toString()
return s.toLowerCase().includes(query.toLowerCase())
}
}

View File

@@ -0,0 +1,6 @@
.item(*ngFor='let item of model')
.body((click)='editItem(item)')
.stroke(*ngFor='let stroke of item') {{stroke}}
.remove((click)='removeItem(item)') ×
.add((click)='addItem()') Add...

View File

@@ -0,0 +1,35 @@
:host {
display: flex;
flex-wrap: nowrap;
&:hover .add {
display: initial;
}
}
.item {
display: flex;
flex: none;
.body {
flex: none;
display: flex;
.stroke {
flex: none;
}
}
.remove {
flex: none;
}
}
.add {
flex: auto;
display: none;
&:first-child {
display: block;
}
}

View File

@@ -0,0 +1,46 @@
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { HotkeyInputModalComponent } from './hotkeyInputModal.component'
/** @hidden */
@Component({
selector: 'multi-hotkey-input',
template: require('./multiHotkeyInput.component.pug'),
styles: [require('./multiHotkeyInput.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MultiHotkeyInputComponent {
@Input() model: string[][] = []
@Output() modelChange = new EventEmitter()
constructor (
private ngbModal: NgbModal,
) { }
ngOnInit (): void {
if (typeof this.model === 'string') {
this.model = [this.model]
}
this.model = this.model.map(item => typeof item === 'string' ? [item] : item)
}
editItem (item: string[]): void {
this.ngbModal.open(HotkeyInputModalComponent).result.then((value: string[]) => {
this.model[this.model.findIndex(x => x === item)] = value
this.model = this.model.slice()
this.modelChange.emit(this.model)
})
}
addItem (): void {
this.ngbModal.open(HotkeyInputModalComponent).result.then((value: string[]) => {
this.model = this.model.concat([value])
this.modelChange.emit(this.model)
})
}
removeItem (item: string[]): void {
this.model = this.model.filter(x => x !== item)
this.modelChange.emit(this.model)
}
}

View File

@@ -0,0 +1,20 @@
h3.modal-header.m-0.pb-0 Set master passphrase
.modal-body
.mb-2 You can change it later, but it's unrecoverable if forgotten.
.input-group
input.form-control.form-control-lg(
[type]='showPassphrase ? "text" : "password"',
autofocus,
[(ngModel)]='passphrase',
#input,
placeholder='Master passphrase',
(keyup.enter)='ok()',
(keyup.esc)='cancel()',
)
.input-group-append
button.btn.btn-secondary((click)='showPassphrase = !showPassphrase')
i.fas.fa-eye
.modal-footer
button.btn.btn-outline-primary((click)='ok()') Set passphrase
button.btn.btn-outline-danger((click)='cancel()') Cancel

View File

@@ -0,0 +1,29 @@
import { Component, ViewChild, ElementRef } from '@angular/core'
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
/** @hidden */
@Component({
template: require('./setVaultPassphraseModal.component.pug'),
})
export class SetVaultPassphraseModalComponent {
passphrase: string
@ViewChild('input') input: ElementRef
constructor (
private modalInstance: NgbActiveModal,
) { }
ngOnInit (): void {
setTimeout(() => {
this.input.nativeElement.focus()
})
}
ok (): void {
this.modalInstance.close(this.passphrase)
}
cancel (): void {
this.modalInstance.close(null)
}
}

View File

@@ -0,0 +1,114 @@
button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
.content
ul.nav-pills(ngbNav, #nav='ngbNav', [activeId]='activeTab', orientation='vertical')
li(ngbNavItem='application')
a(ngbNavLink)
i.fas.fa-fw.fa-window-maximize.mr-2
| Application
ng-template(ngbNavContent)
.tabby-logo.mt-3
h1.tabby-title Tabby
sup α
.text-center
.text-muted {{homeBase.appVersion}}
.mb-5.mt-3
button.btn.btn-secondary.mr-3((click)='homeBase.openGitHub()')
i.fab.fa-github
span GitHub
button.btn.btn-secondary.mr-3((click)='homeBase.reportBug()')
i.fas.fa-bug
span Report a problem
button.btn.btn-secondary(
*ngIf='!updateAvailable && hostApp.platform !== Platform.Web',
(click)='checkForUpdates()',
[disabled]='checkingForUpdate'
)
i.fas.fa-sync(
[class.fa-spin]='checkingForUpdate'
)
span Check for updates
button.btn.btn-info(
*ngIf='updateAvailable',
(click)='updater.update()',
)
i.fas.fa-sync
span Update
.form-line(*ngIf='platform.isShellIntegrationSupported()')
.header
.title Shell integration
.description Allows quickly opening a terminal in the selected folder
toggle([ngModel]='isShellIntegrationInstalled', (ngModelChange)='toggleShellIntegration()')
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.header
.title Enable analytics
.description We're only tracking your Tabby and OS versions.
toggle(
[(ngModel)]='config.store.enableAnalytics',
(ngModelChange)='saveConfiguration(true)',
)
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.header
.title Automatic Updates
.description Enable automatic installation of updates when they become available.
toggle([(ngModel)]='config.store.enableAutomaticUpdates', (ngModelChange)='saveConfiguration()')
.form-line(*ngIf='hostApp.platform !== Platform.Web')
.header
.title Debugging
button.btn.btn-secondary((click)='hostWindow.openDevTools()')
i.fas.fa-bug
span Open DevTools
li(*ngFor='let provider of settingsProviders', [ngbNavItem]='provider.id')
a(ngbNavLink)
i(class='fas fa-fw mr-2 fa-{{provider.icon || "puzzle-piece"}}')
| {{provider.title}}
ng-template(ngbNavContent)
settings-tab-body([provider]='provider')
li(ngbNavItem='config-file')
a(ngbNavLink)
i.fas.fa-fw.fa-code.mr-2
| Config file
ng-template.test(ngbNavContent)
.d-flex.flex-column.w-100.h-100
.h-100.d-flex
.w-100.d-flex.flex-column
h3 Config File
textarea.form-control.h-100(
[(ngModel)]='configFile'
)
.w-100.d-flex.flex-column(*ngIf='showConfigDefaults')
h3 Defaults
textarea.form-control.h-100(
[(ngModel)]='configDefaults',
readonly
)
.mt-3.d-flex
button.btn.btn-primary((click)='saveConfigFile()', *ngIf='isConfigFileValid()')
i.fas.fa-check.mr-2
| Save and apply
button.btn.btn-primary(disabled, *ngIf='!isConfigFileValid()')
i.fas.fa-exclamation-triangle.mr-2
| Invalid syntax
button.btn.btn-secondary.ml-auto(
(click)='showConfigDefaults = !showConfigDefaults'
) Show defaults
button.btn.btn-secondary.ml-3(
*ngIf='platform.getConfigPath()',
(click)='showConfigFile()'
)
i.fas.fa-external-link-square-alt.mr-2
| Show config file
div([ngbNavOutlet]='nav')

View File

@@ -0,0 +1,45 @@
:host {
display: flex;
flex: auto;
flex-direction: column;
>.btn-block {
margin: 20px;
width: auto;
flex: none;
}
> .content {
display: flex;
min-height: 0;
flex: 1 0 0;
> .nav {
padding: 20px 10px;
width: 190px;
flex: none;
overflow-y: auto;
flex-wrap: nowrap;
}
> .tab-content {
flex: auto;
padding: 20px 30px;
overflow-y: auto;
> ::ng-deep .tab-pane {
height: 100%;
}
}
}
&.pad-window-controls > .content > .nav {
padding-top: 40px;
}
}
textarea {
font-family: 'Source Code Pro', monospace;
font-size: 12px;
min-height: 120px;
}

View File

@@ -0,0 +1,117 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import * as yaml from 'js-yaml'
import { debounce } from 'utils-decorators/dist/cjs'
import { Component, Inject, Input, HostBinding, NgZone } from '@angular/core'
import {
ConfigService,
BaseTabComponent,
HostAppService,
Platform,
HomeBaseService,
UpdaterService,
PlatformService,
HostWindowService,
} from 'tabby-core'
import { SettingsTabProvider } from '../api'
/** @hidden */
@Component({
selector: 'settings-tab',
template: require('./settingsTab.component.pug'),
styles: [
require('./settingsTab.component.scss'),
],
})
export class SettingsTabComponent extends BaseTabComponent {
@Input() activeTab: string
Platform = Platform
configDefaults: any
configFile: string
isShellIntegrationInstalled = false
checkingForUpdate = false
updateAvailable = false
showConfigDefaults = false
@HostBinding('class.pad-window-controls') padWindowControls = false
constructor (
public config: ConfigService,
public hostApp: HostAppService,
public hostWindow: HostWindowService,
public homeBase: HomeBaseService,
public platform: PlatformService,
public zone: NgZone,
private updater: UpdaterService,
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[],
) {
super()
this.setTitle('Settings')
this.settingsProviders = config.enabledServices(this.settingsProviders)
this.settingsProviders.sort((a, b) => a.title.localeCompare(b.title))
this.configDefaults = yaml.dump(config.getDefaults())
const onConfigChange = () => {
this.configFile = config.readRaw()
this.padWindowControls = hostApp.platform === Platform.macOS
&& config.store.appearance.tabsLocation !== 'top'
}
this.subscribeUntilDestroyed(config.changed$, onConfigChange)
onConfigChange()
}
async ngOnInit () {
this.isShellIntegrationInstalled = await this.platform.isShellIntegrationInstalled()
}
async toggleShellIntegration () {
if (!this.isShellIntegrationInstalled) {
await this.platform.installShellIntegration()
} else {
await this.platform.uninstallShellIntegration()
}
this.isShellIntegrationInstalled = await this.platform.isShellIntegrationInstalled()
}
ngOnDestroy () {
this.config.save()
}
restartApp () {
this.hostApp.relaunch()
}
@debounce(500)
saveConfiguration (requireRestart?: boolean) {
this.config.save()
if (requireRestart) {
this.config.requestRestart()
}
}
saveConfigFile () {
if (this.isConfigFileValid()) {
this.config.writeRaw(this.configFile)
}
}
showConfigFile () {
this.platform.showItemInFolder(this.platform.getConfigPath()!)
}
isConfigFileValid () {
try {
yaml.load(this.configFile)
return true
} catch (_) {
return false
}
}
async checkForUpdates () {
this.checkingForUpdate = true
this.updateAvailable = await this.updater.check()
this.checkingForUpdate = false
}
}

View File

@@ -0,0 +1,26 @@
import { Component, Input, ViewContainerRef, ViewChild, ComponentFactoryResolver, ComponentRef } from '@angular/core'
import { SettingsTabProvider } from '../api'
/** @hidden */
@Component({
selector: 'settings-tab-body',
template: '<ng-template #placeholder></ng-template>',
})
export class SettingsTabBodyComponent {
@Input() provider: SettingsTabProvider
@ViewChild('placeholder', { read: ViewContainerRef }) placeholder: ViewContainerRef
component: ComponentRef<Component>
constructor (private componentFactoryResolver: ComponentFactoryResolver) { }
ngAfterViewInit (): void {
// run after the change detection finishes
setImmediate(() => {
this.component = this.placeholder.createComponent(
this.componentFactoryResolver.resolveComponentFactory(
this.provider.getComponentType()
)
)
})
}
}

View File

@@ -0,0 +1,46 @@
.text-center(*ngIf='!vault.isEnabled()')
i.fas.fa-key.fa-3x.m-3
h3.m-3 Vault is not configured
.m-3 Vault is an always-encrypted container for secrets such as SSH passwords and private key passphrases.
button.btn.btn-primary.m-2((click)='enableVault()') Set master passphrase
div(*ngIf='vault.isEnabled()')
.d-flex.align-items-center.mb-3
h3.m-0 Vault
.d-flex.ml-auto(ngbDropdown, *ngIf='vault.isEnabled()')
button.btn.btn-secondary(ngbDropdownToggle) Options
div(ngbDropdownMenu)
a(ngbDropdownItem, (click)='changePassphrase()')
i.fas.fa-fw.fa-key
span Change the master passphrase
a(ngbDropdownItem, (click)='disableVault()')
i.fas.fa-fw.fa-radiation-alt
span Erase the vault
div(*ngIf='vaultContents')
.text-center(*ngIf='!vaultContents.secrets.length')
i.fas.fa-empty-set.fa-3x
h3.m-3 Vault is empty
.list-group
.list-group-item.d-flex.align-items-center.p-1.pl-3(*ngFor='let secret of vaultContents.secrets')
i.fas.fa-key
.mr-auto {{getSecretLabel(secret)}}
button.btn.btn-link((click)='removeSecret(secret)')
i.fas.fa-trash
h3.mt-5 Options
.form-line
.header
.title Encrypt config file
.description Puts all of Tabby's configuration into the vault
toggle(
[ngModel]='config.store.encrypted',
(click)='toggleConfigEncrypted()',
)
.text-center(*ngIf='!vaultContents')
i.fas.fa-key.fa-3x
h3.m-3 Vault is locked
button.btn.btn-primary.m-2((click)='loadVault()') Show vault contents

View File

@@ -0,0 +1,94 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { Component } from '@angular/core'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
import { BaseComponent, VaultService, VaultSecret, Vault, PlatformService, ConfigService, VAULT_SECRET_TYPE_FILE } from 'tabby-core'
import { SetVaultPassphraseModalComponent } from './setVaultPassphraseModal.component'
/** @hidden */
@Component({
selector: 'vault-settings-tab',
template: require('./vaultSettingsTab.component.pug'),
})
export class VaultSettingsTabComponent extends BaseComponent {
vaultContents: Vault|null = null
constructor (
public vault: VaultService,
public config: ConfigService,
private platform: PlatformService,
private ngbModal: NgbModal,
) {
super()
if (vault.isOpen()) {
this.loadVault()
}
}
async loadVault (): Promise<void> {
this.vaultContents = await this.vault.load()
}
async enableVault () {
const modal = this.ngbModal.open(SetVaultPassphraseModalComponent)
const newPassphrase = await modal.result
await this.vault.setEnabled(true, newPassphrase)
this.vaultContents = await this.vault.load(newPassphrase)
}
async disableVault () {
if ((await this.platform.showMessageBox(
{
type: 'warning',
message: 'Delete vault contents?',
buttons: ['Keep', 'Delete'],
defaultId: 1,
}
)).response === 1) {
await this.vault.setEnabled(false)
}
}
async changePassphrase () {
if (!this.vaultContents) {
this.vaultContents = await this.vault.load()
}
if (!this.vaultContents) {
return
}
const modal = this.ngbModal.open(SetVaultPassphraseModalComponent)
const newPassphrase = await modal.result
this.vault.save(this.vaultContents, newPassphrase)
}
async toggleConfigEncrypted () {
this.config.store.encrypted = !this.config.store.encrypted
try {
await this.config.save()
} catch (e) {
this.config.store.encrypted = !this.config.store.encrypted
throw e
}
}
getSecretLabel (secret: VaultSecret) {
if (secret.type === 'ssh:password') {
return `SSH password for ${secret.key.user}@${secret.key.host}:${secret.key.port}`
}
if (secret.type === 'ssh:key-passphrase') {
return `Passphrase for a private key with hash ${secret.key.hash.substring(0, 8)}...`
}
if (secret.type === VAULT_SECRET_TYPE_FILE) {
return `File: ${secret.key.description}`
}
return `Unknown secret of type ${secret.type} for ${JSON.stringify(secret.key)}`
}
removeSecret (secret: VaultSecret) {
if (!this.vaultContents) {
return
}
this.vaultContents.secrets = this.vaultContents.secrets.filter(x => x !== secret)
this.vault.removeSecret(secret.type, secret.key)
}
}

View File

@@ -0,0 +1,279 @@
h3.mb-3 Window
.form-line
.header
.title Theme
select.form-control(
[(ngModel)]='config.store.appearance.theme',
(ngModelChange)='saveConfiguration()',
)
option(*ngFor='let theme of themes', [ngValue]='theme.name') {{theme.name}}
.form-line(*ngIf='platform.supportsWindowControls')
.header
.title(*ngIf='hostApp.platform !== Platform.macOS') Acrylic background
.title(*ngIf='hostApp.platform === Platform.macOS') Vibrancy
.description Gives the window a blurred transparent background
toggle(
[(ngModel)]='config.store.appearance.vibrancy',
(ngModelChange)='saveConfiguration()'
)
.form-line(*ngIf='config.store.appearance.vibrancy && isFluentVibrancySupported')
.header
.title Background type
.btn-group(
[(ngModel)]='config.store.appearance.vibrancyType',
(ngModelChange)='saveConfiguration()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"blur"'
)
| Blur
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"fluent"'
)
| Fluent
.form-line(*ngIf='platform.supportsWindowControls')
.header
.title Opacity
input(
type='range',
[(ngModel)]='config.store.appearance.opacity',
(ngModelChange)='saveConfiguration(); (hostApp.platform === Platform.Linux && config.requestRestart())',
min='0.4',
max='1',
step='0.01'
)
.form-line(*ngIf='platform.supportsWindowControls')
.header
.title Window frame
.description Whether a custom window or an OS native window should be used
.btn-group(
[(ngModel)]='config.store.appearance.frame',
(ngModelChange)='saveConfiguration(true)',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"native"'
)
| Native
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"thin"'
)
| Thin
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"full"'
)
| Full
.form-line(*ngIf='docking')
.header
.title Dock the terminal
.description Snaps the window to a side of the screen
.btn-group(
[(ngModel)]='config.store.appearance.dock',
(ngModelChange)='saveConfiguration(); docking.dock()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"off"'
)
| Off
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"top"'
)
| Top
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"left"'
)
| Left
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"right"'
)
| Right
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"bottom"'
)
| Bottom
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.header
.title Display on
.description Snaps the window to a side of the screen
div(
[(ngModel)]='config.store.appearance.dockScreen',
(ngModelChange)='saveConfiguration(); docking.dock()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
value='current'
)
| Current
label.btn.btn-secondary(*ngFor='let screen of screens', ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='screen.id'
)
| {{screen.name}}
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.header
.title Dock always on top
.description Keep docked terminal always on top
toggle(
[(ngModel)]='config.store.appearance.dockAlwaysOnTop',
(ngModelChange)='saveConfiguration(); docking.dock()',
)
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.header
.title Docked terminal size
input(
type='range',
[(ngModel)]='config.store.appearance.dockFill',
(mouseup)='saveConfiguration(); docking.dock()',
min='0.05',
max='1',
step='0.01'
)
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.header
.title Docked terminal space
input(
type='range',
[(ngModel)]='config.store.appearance.dockSpace',
(mouseup)='saveConfiguration(); docking.dock()',
min='0.2',
max='1',
step='0.01'
)
.ml-5.form-line(*ngIf='docking && config.store.appearance.dock != "off"')
.header
.title Hide dock on blur
.description Hides the docked terminal when you click away.
toggle(
[(ngModel)]='config.store.appearance.dockHideOnBlur',
(ngModelChange)='saveConfiguration(); ',
)
.form-line
.header
.title Tabs location
.btn-group(
[(ngModel)]='config.store.appearance.tabsLocation',
(ngModelChange)='saveConfiguration()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"top"'
)
| Top
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"bottom"'
)
| Bottom
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"left"'
)
| Left
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='"right"'
)
| Right
.form-line
.header
.title Tabs width
.btn-group(
[(ngModel)]='config.store.appearance.flexTabs',
(ngModelChange)='saveConfiguration()',
ngbRadioGroup
)
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='true'
)
| Dynamic
label.btn.btn-secondary(ngbButtonLabel)
input(
type='radio',
ngbButton,
[value]='false'
)
| Fixed
.form-line
.header
.title Hide tab index
toggle(
[(ngModel)]='config.store.terminal.hideTabIndex',
(ngModelChange)='config.save();',
)
.form-line
.header
.title Hide tab close button
toggle(
[(ngModel)]='config.store.terminal.hideCloseButton',
(ngModelChange)='config.save();',
)

View File

@@ -0,0 +1,58 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { debounce } from 'utils-decorators/dist/cjs'
import { Component, Inject, NgZone, Optional } from '@angular/core'
import {
DockingService,
ConfigService,
Theme,
HostAppService,
Platform,
isWindowsBuild,
WIN_BUILD_FLUENT_BG_SUPPORTED,
BaseComponent,
Screen,
PlatformService,
} from 'tabby-core'
/** @hidden */
@Component({
selector: 'window-settings-tab',
template: require('./windowSettingsTab.component.pug'),
})
export class WindowSettingsTabComponent extends BaseComponent {
screens: Screen[]
Platform = Platform
isFluentVibrancySupported = false
constructor (
public config: ConfigService,
public hostApp: HostAppService,
public platform: PlatformService,
public zone: NgZone,
@Inject(Theme) public themes: Theme[],
@Optional() public docking?: DockingService,
) {
super()
this.themes = config.enabledServices(this.themes)
const dockingService = docking
if (dockingService) {
this.subscribeUntilDestroyed(dockingService.screensChanged$, () => {
this.zone.run(() => this.screens = dockingService.getScreens())
})
this.screens = dockingService.getScreens()
}
this.isFluentVibrancySupported = isWindowsBuild(WIN_BUILD_FLUENT_BG_SUPPORTED)
}
@debounce(500)
saveConfiguration (requireRestart?: boolean) {
this.config.save()
if (requireRestart) {
this.config.requestRestart()
}
}
}

View File

@@ -0,0 +1,23 @@
import { ConfigProvider, Platform } from 'tabby-core'
/** @hidden */
export class SettingsConfigProvider extends ConfigProvider {
defaults = { }
platformDefaults = {
[Platform.macOS]: {
hotkeys: {
settings: ['⌘-,'],
},
},
[Platform.Windows]: {
hotkeys: {
settings: ['Ctrl-,'],
},
},
[Platform.Linux]: {
hotkeys: {
settings: ['Ctrl-,'],
},
},
}
}

View File

@@ -0,0 +1,17 @@
import { Injectable } from '@angular/core'
import { HotkeyDescription, HotkeyProvider } from 'tabby-core'
/** @hidden */
@Injectable()
export class SettingsHotkeyProvider extends HotkeyProvider {
hotkeys: HotkeyDescription[] = [
{
id: 'settings',
name: 'Open Settings',
},
]
async provide (): Promise<HotkeyDescription[]> {
return this.hotkeys
}
}

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#fff" d="M482.696 299.276l-32.61-18.827a195.168 195.168 0 0 0 0-48.899l32.61-18.827c9.576-5.528 14.195-16.902 11.046-27.501-11.214-37.749-31.175-71.728-57.535-99.595-7.634-8.07-19.817-9.836-29.437-4.282l-32.562 18.798a194.125 194.125 0 0 0-42.339-24.48V38.049c0-11.13-7.652-20.804-18.484-23.367-37.644-8.909-77.118-8.91-114.77 0-10.831 2.563-18.484 12.236-18.484 23.367v37.614a194.101 194.101 0 0 0-42.339 24.48L105.23 81.345c-9.621-5.554-21.804-3.788-29.437 4.282-26.36 27.867-46.321 61.847-57.535 99.595-3.149 10.599 1.47 21.972 11.046 27.501l32.61 18.827a195.168 195.168 0 0 0 0 48.899l-32.61 18.827c-9.576 5.528-14.195 16.902-11.046 27.501 11.214 37.748 31.175 71.728 57.535 99.595 7.634 8.07 19.817 9.836 29.437 4.283l32.562-18.798a194.08 194.08 0 0 0 42.339 24.479v37.614c0 11.13 7.652 20.804 18.484 23.367 37.645 8.909 77.118 8.91 114.77 0 10.831-2.563 18.484-12.236 18.484-23.367v-37.614a194.138 194.138 0 0 0 42.339-24.479l32.562 18.798c9.62 5.554 21.803 3.788 29.437-4.283 26.36-27.867 46.321-61.847 57.535-99.595 3.149-10.599-1.47-21.972-11.046-27.501zm-65.479 100.461l-46.309-26.74c-26.988 23.071-36.559 28.876-71.039 41.059v53.479a217.145 217.145 0 0 1-87.738 0v-53.479c-33.621-11.879-43.355-17.395-71.039-41.059l-46.309 26.74c-19.71-22.09-34.689-47.989-43.929-75.958l46.329-26.74c-6.535-35.417-6.538-46.644 0-82.079l-46.329-26.74c9.24-27.969 24.22-53.869 43.929-75.969l46.309 26.76c27.377-23.434 37.063-29.065 71.039-41.069V44.464a216.79 216.79 0 0 1 87.738 0v53.479c33.978 12.005 43.665 17.637 71.039 41.069l46.309-26.76c19.709 22.099 34.689 47.999 43.929 75.969l-46.329 26.74c6.536 35.426 6.538 46.644 0 82.079l46.329 26.74c-9.24 27.968-24.219 53.868-43.929 75.957zM256 160c-52.935 0-96 43.065-96 96s43.065 96 96 96 96-43.065 96-96-43.065-96-96-96zm0 160c-35.29 0-64-28.71-64-64s28.71-64 64-64 64 28.71 64 64-28.71 64-64 64z"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1,61 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import TabbyCorePlugin, { ToolbarButtonProvider, HotkeyProvider, ConfigProvider } from 'tabby-core'
import { HotkeyInputModalComponent } from './components/hotkeyInputModal.component'
import { HotkeySettingsTabComponent } from './components/hotkeySettingsTab.component'
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput.component'
import { SettingsTabComponent } from './components/settingsTab.component'
import { SettingsTabBodyComponent } from './components/settingsTabBody.component'
import { WindowSettingsTabComponent } from './components/windowSettingsTab.component'
import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
import { SetVaultPassphraseModalComponent } from './components/setVaultPassphraseModal.component'
import { SettingsTabProvider } from './api'
import { ButtonProvider } from './buttonProvider'
import { SettingsHotkeyProvider } from './hotkeys'
import { SettingsConfigProvider } from './config'
import { HotkeySettingsTabProvider, WindowSettingsTabProvider, VaultSettingsTabProvider } from './settings'
/** @hidden */
@NgModule({
imports: [
BrowserModule,
FormsModule,
NgbModule,
TabbyCorePlugin,
],
providers: [
{ provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true },
{ provide: ConfigProvider, useClass: SettingsConfigProvider, multi: true },
{ provide: HotkeyProvider, useClass: SettingsHotkeyProvider, multi: true },
{ provide: SettingsTabProvider, useClass: HotkeySettingsTabProvider, multi: true },
{ provide: SettingsTabProvider, useClass: WindowSettingsTabProvider, multi: true },
{ provide: SettingsTabProvider, useClass: VaultSettingsTabProvider, multi: true },
],
entryComponents: [
HotkeyInputModalComponent,
HotkeySettingsTabComponent,
SettingsTabComponent,
SetVaultPassphraseModalComponent,
VaultSettingsTabComponent,
WindowSettingsTabComponent,
],
declarations: [
HotkeyInputModalComponent,
HotkeySettingsTabComponent,
MultiHotkeyInputComponent,
SettingsTabComponent,
SettingsTabBodyComponent,
SetVaultPassphraseModalComponent,
VaultSettingsTabComponent,
WindowSettingsTabComponent,
],
})
export default class SettingsModule { } // eslint-disable-line @typescript-eslint/no-extraneous-class
export * from './api'
export { SettingsTabComponent }

View File

@@ -0,0 +1,43 @@
import { Injectable } from '@angular/core'
import { SettingsTabProvider } from './api'
import { HotkeySettingsTabComponent } from './components/hotkeySettingsTab.component'
import { WindowSettingsTabComponent } from './components/windowSettingsTab.component'
import { VaultSettingsTabComponent } from './components/vaultSettingsTab.component'
/** @hidden */
@Injectable()
export class HotkeySettingsTabProvider extends SettingsTabProvider {
id = 'hotkeys'
icon = 'keyboard'
title = 'Hotkeys'
getComponentType (): any {
return HotkeySettingsTabComponent
}
}
/** @hidden */
@Injectable()
export class WindowSettingsTabProvider extends SettingsTabProvider {
id = 'window'
icon = 'window-maximize'
title = 'Window'
getComponentType (): any {
return WindowSettingsTabComponent
}
}
/** @hidden */
@Injectable()
export class VaultSettingsTabProvider extends SettingsTabProvider {
id = 'vault'
icon = 'key'
title = 'Vault'
getComponentType (): any {
return VaultSettingsTabComponent
}
}