mirror of
https://github.com/Eugeny/tabby.git
synced 2025-07-20 02:18:01 +00:00
project rename
This commit is contained in:
12
tabby-settings/src/api.ts
Normal file
12
tabby-settings/src/api.ts
Normal 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
|
||||
}
|
||||
}
|
42
tabby-settings/src/buttonProvider.ts
Normal file
42
tabby-settings/src/buttonProvider.ts
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
12
tabby-settings/src/components/hotkeyInputModal.component.pug
Normal file
12
tabby-settings/src/components/hotkeyInputModal.component.pug
Normal 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
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
88
tabby-settings/src/components/hotkeyInputModal.component.ts
Normal file
88
tabby-settings/src/components/hotkeyInputModal.component.ts
Normal 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()
|
||||
}
|
||||
}
|
@@ -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)'
|
||||
)
|
@@ -0,0 +1,7 @@
|
||||
.hotkeys-table {
|
||||
margin-top: 30px;
|
||||
|
||||
td, th {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
}
|
57
tabby-settings/src/components/hotkeySettingsTab.component.ts
Normal file
57
tabby-settings/src/components/hotkeySettingsTab.component.ts
Normal 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())
|
||||
}
|
||||
}
|
@@ -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...
|
@@ -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;
|
||||
}
|
||||
}
|
46
tabby-settings/src/components/multiHotkeyInput.component.ts
Normal file
46
tabby-settings/src/components/multiHotkeyInput.component.ts
Normal 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)
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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)
|
||||
}
|
||||
}
|
114
tabby-settings/src/components/settingsTab.component.pug
Normal file
114
tabby-settings/src/components/settingsTab.component.pug
Normal 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')
|
45
tabby-settings/src/components/settingsTab.component.scss
Normal file
45
tabby-settings/src/components/settingsTab.component.scss
Normal 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;
|
||||
}
|
117
tabby-settings/src/components/settingsTab.component.ts
Normal file
117
tabby-settings/src/components/settingsTab.component.ts
Normal 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
|
||||
}
|
||||
}
|
26
tabby-settings/src/components/settingsTabBody.component.ts
Normal file
26
tabby-settings/src/components/settingsTabBody.component.ts
Normal 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()
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
46
tabby-settings/src/components/vaultSettingsTab.component.pug
Normal file
46
tabby-settings/src/components/vaultSettingsTab.component.pug
Normal 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
|
94
tabby-settings/src/components/vaultSettingsTab.component.ts
Normal file
94
tabby-settings/src/components/vaultSettingsTab.component.ts
Normal 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)
|
||||
}
|
||||
}
|
279
tabby-settings/src/components/windowSettingsTab.component.pug
Normal file
279
tabby-settings/src/components/windowSettingsTab.component.pug
Normal 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();',
|
||||
)
|
58
tabby-settings/src/components/windowSettingsTab.component.ts
Normal file
58
tabby-settings/src/components/windowSettingsTab.component.ts
Normal 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()
|
||||
}
|
||||
}
|
||||
}
|
23
tabby-settings/src/config.ts
Normal file
23
tabby-settings/src/config.ts
Normal 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-,'],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
17
tabby-settings/src/hotkeys.ts
Normal file
17
tabby-settings/src/hotkeys.ts
Normal 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
|
||||
}
|
||||
}
|
1
tabby-settings/src/icons/cog.svg
Normal file
1
tabby-settings/src/icons/cog.svg
Normal 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 |
61
tabby-settings/src/index.ts
Normal file
61
tabby-settings/src/index.ts
Normal 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 }
|
43
tabby-settings/src/settings.ts
Normal file
43
tabby-settings/src/settings.ts
Normal 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
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user