1
1
mirror of https://github.com/Eugeny/tabby.git synced 2024-12-23 02:22:02 +03:00
This commit is contained in:
Eugene Pankov 2017-03-29 14:04:01 +02:00
parent 482343e383
commit cb7e1cd157
28 changed files with 295 additions and 703 deletions

View File

@ -1,541 +0,0 @@
@import (less, reference) "../../../node_modules/font-awesome/css/font-awesome.css";
.glyphicon:extend(.fa all) {
&.glyphicon-chevron-right:extend(.fa-chevron-right all) {};
&.glyphicon-chevron-left:extend(.fa-chevron-left all) {};
&.glyphicon-chevron-up:extend(.fa-chevron-up all) {};
&.glyphicon-chevron-down:extend(.fa-chevron-down all) {};
}
h4 {
margin-bottom: 5px;
}
a {
color: #EFEAB1;
transition: opacity 0.125s, background 0.125s, color0.125s ;
&:hover {
color: #FFF79A;
}
}
.block-element {
display: block;
}
textarea {
resize: vertical;
}
.btn {
border: none !important;
box-shadow: 0 1px 1px rgba(0,0,0,.125);
&:active {
outline: 5px auto @brand-info;
}
&:active,
&.active {
.box-shadow(@control-shadow-active);
}
-webkit-transition: all 0.125s ease-out;
-moz-transition: all 0.125s ease-out;
-ms-transition: all 0.125s ease-out;
-o-transition: all 0.125s ease-out;
transition: all 0.125s ease-out;
&[disabled] {
cursor: default !important;
}
}
.btn-ink {
background: transparent !important;
box-shadow: none;
&.btn-danger {
color: #FF4832;
}
&.btn-xs {
padding-top: 0;
padding-bottom: 0;
}
}
.btn-toolbar {
margin: 0;
.btn, .btn-group, [uib-dropdown] {
float: none;
}
> .btn-group > * {
float: left;
}
> .btn, > .btn-group, > [uib-dropdown] {
margin: 0 5px 5px 0px;
vertical-align: top;
> .btn {
margin: 0;
}
}
}
@media (max-width: @screen-xs-max) {
.btn-toolbar-collapsible {
.btn {
font-size: 0;
.fa::before, .caret {
font-size: @font-size-base;
}
}
}
}
[uib-dropdown] {
position: relative;
}
[uib-dropdown-menu], .dropdown-menu {
.box-shadow(@control-dropdown-shadow);
top: 25px;
> li > * {
display: block;
padding: 5px 20px;
clear: both;
font-weight: normal;
line-height: @line-height-base;
color: @dropdown-link-color;
white-space: nowrap;
&[disabled] {
color: #888;
cursor: not-allowed;
}
}
> li.disabled a {
color: #666;
&:hover {
color: #666;
}
}
}
[uib-dropdown-menu] > .active > * {
&,
&:hover,
&:focus {
color: @dropdown-link-active-color;
text-decoration: none;
outline: 0;
background-color: @dropdown-link-active-bg;
}
}
.form-control {
border: none;
&[checkbox] {
border: none;
background: transparent;
display: inline-block;
margin: @padding-base-vertical 0 0;
}
border-radius: 0;
.box-shadow(~"0 1px 1px rgba(0, 0, 0, 0.51)");
&:focus {
.box-shadow(~"0 1px 1px rgba(0, 0, 0, 0.51)");
}
.transition(0.25s background);
&::-webkit-input-placeholder {
font-style: italic;
}
}
.input-group {
.box-shadow(~"0 1px 1px rgba(0, 0, 0, 0.51)");
.form-control {
.box-shadow(none);
}
}
.input-group-addon {
border: none;
padding: 6px 8px;
}
@media (min-width: @screen-sm-min) {
.control-label {
font-size: 12px;
padding-top: 10px;
font-weight: normal;
}
}
.form-group .control-label {
font-size: 12px;
padding-top: 10px;
font-weight: bold;
color: #777;
}
label.form-control-static {
font-size: inherit;
}
.input-group {
width: 100%;
*:focus + .input-group-addon,
*:focus + .input-group-btn {
//border-bottom: 1px solid @brand-primary;
}
.input-group-addon, .input-group-btn {
border: none;
&.input-sm {
border-radius: 0;
}
a {
margin: 0 !important;
}
text-decoration: none !important;
}
}
.label {
font-size: 76%;
position: relative;
padding: 5px 4px 4px;
}
.list-group {
.box-shadow(@control-shadow);
}
.list-group-item {
border: none;
border-top: 1px solid @list-group-line-border;
cursor: default;
&:first-child {
border-top: none;
}
&.active {
color: #f7e61d;
border-right: 2px solid #f7e61d;
}
}
.list-group-item.combi {
padding: 0;
clear: both;
tr& {
a.main, .btn {
display: table-cell !important;
}
}
& > .main {
display: block;
h4 {
margin-top: 4px;
}
pre {
background: transparent;
margin: 0;
}
video-thumbnail, asset-thumbnail {
margin-right: 15px;
}
}
& > a.main {
cursor: pointer;
color: inherit;
}
& > a.main, & > a.btn, & > button, & > [uib-dropdown] > button {
&:hover,
&:focus {
text-decoration: none;
color: @list-group-link-hover-color;
background-color: @list-group-hover-bg;
}
}
& > a.btn, & > button, & > [uib-dropdown] > button {
background-color: @list-group-bg;
position: relative; // more z-index
z-index: 2;
display: block;
float: right;
border: none;
margin: 0;
box-shadow: none;
}
& > .drag-handle {
float: left;
cursor: move;
}
&.single > .main {
height: 40px;
line-height: 39px;
padding: 0 15px;
[checkbox] {
display: inline;
}
.label-lg {
top: 7px;
}
}
&.double > .main {
height: 68px;
line-height: 25px;
padding: 10px 15px;
}
&.single {
a.btn, button, .drag-handle {
padding: 10px 12px;
}
}
&.double {
a.btn, button, .drag-handle {
padding: 24px;
}
}
}
.form-control-focus(@color: @input-border-focus) {
@color-rgba: rgba(red(@color), green(@color), blue(@color), .3);
}
.modal {
background-color: rgba(0,0,0,.5);
position: fixed !important;
.modal-dialog {
margin: 0 auto;
top: 50px;
.modal-content {
background-color: @body-bg;
.modal-body {
max-height: 80vh;
overflow-y: auto;
padding: 25px 15px;
-webkit-app-region: no-drag;
}
.modal-footer {
border-top: 1px solid #222;
padding: 0;
-webkit-app-region: no-drag;
.btn.btn-default {
background: @input-bg;
border: none !important;
cursor: pointer;
box-shadow: none !important;
padding: 10px;
&:hover {
background: rgba(0,0,0,.25);
}
&:active {
background: rgba(0,0,0,.5);
}
}
}
}
}
}
.modal-backdrop {
background: rgba(0,0,0,.25);
}
.navbar-fixed-top {
border-width: 0 0 2px;
height: 52px;
}
.navbar-form {
box-shadow: none !important;
border: none !important;
}
.nav-tabs-justified,
.nav-tabs {
border-bottom: 1px solid @nav-tabs-border-color;
> li {
> a {
border: 1px solid transparent;
border-radius: 0;
color: @text-color;
&:hover {
background-color: @nav-tabs-active-link-hover-bg;
border-bottom: 1px solid @nav-tabs-border-color;
}
}
// Active state, and its :hover to override normal :hover
&.active > a {
&,
&:hover,
&:focus {
border-radius: 0;
border: 1px solid transparent;
border-bottom: 1px solid @nav-tabs-active-link-hover-border-color;
}
}
}
}
.nav-justified {
> li {
display: table-cell;
width: 1%;
> a {
margin-bottom: 0;
}
}
}
.popover-content {
//padding: 0;
}
.progress {
margin-bottom: 0;
}
table {
background: transparent;
td {
vertical-align: top;
}
.table-condensed {
display: block;
}
}
.table {
background: @table-bg;
.box-shadow(@control-shadow);
&.no-margin {
margin-bottom: 0;
}
> thead,
> tbody,
> tfoot {
> tr {
background-color: transparent;
> th,
> td {
border-top: 1px solid @table-line-border-color;
}
&:first-child {
th, td {
border-top: none;
}
}
}
}
> thead > tr > th {
border-bottom: 2px solid @table-line-border-color;
}
}
.table-bordered {
> thead,
> tbody,
> tfoot {
> tr {
> th,
> td {
border: 1px solid @table-line-border-color;
}
}
}
}
.help-block {
color: darken(@text-color, 10%);
}
.label {
margin-right: 5px;
}
.datepicker-dropdown {
&.datepicker-orient-top:before {
display: none !important;
}
&.datepicker-orient-top:after {
border-bottom: 7px solid @dropdown-bg !important;
}
&.datepicker-orient-bottom:after {
border-top: 7px solid @dropdown-bg !important;
}
table tr td.day:hover {
background: #555 !important;
}
table tr td.active {
background: @brand-primary !important;
color: #222 !important;
text-shadow: none !important;
}
}

View File

@ -1,15 +1,9 @@
appearance: appearance:
font: monospace
fontSize: 14
dock: 'off' dock: 'off'
dockScreen: 'current' dockScreen: 'current'
dockFill: 50 dockFill: 50
tabsOnTop: true tabsOnTop: true
hotkeys: hotkeys:
new-tab:
- ['Ctrl-A', 'C']
- ['Ctrl-A', 'Ctrl-C']
- 'Ctrl-Shift-T'
close-tab: close-tab:
- 'Ctrl-Shift-W' - 'Ctrl-Shift-W'
- ['Ctrl-A', 'K'] - ['Ctrl-A', 'K']
@ -52,5 +46,3 @@ hotkeys:
tab-10: tab-10:
- 'Alt-0' - 'Alt-0'
- ['Ctrl-A', '0'] - ['Ctrl-A', '0']
terminal:
bell: off

View File

@ -0,0 +1,4 @@
export abstract class ConfigProvider {
configStructure: any = {}
defaultConfigValues: any = {}
}

View File

@ -0,0 +1,8 @@
export interface IHotkeyDescription {
id: string,
name: string,
}
export abstract class HotkeyProvider {
hotkeys: IHotkeyDescription[] = []
}

View File

@ -1,7 +1,10 @@
export { Tab } from './tab' export { Tab } from './tab'
export { TabRecoveryProvider } from './tabRecovery' export { TabRecoveryProvider } from './tabRecovery'
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider' export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
export { ConfigProvider } from './configProvider'
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
export { AppService } from 'services/app' export { AppService } from 'services/app'
export { PluginsService } from 'services/plugins' export { PluginsService } from 'services/plugins'
export { ElectronService } from 'services/electron' export { ElectronService } from 'services/electron'
export { HotkeysService } from 'services/hotkeys'

View File

@ -10,7 +10,7 @@ import { ConfigService } from 'services/config'
import { ElectronService } from 'services/electron' import { ElectronService } from 'services/electron'
import { HostAppService } from 'services/hostApp' import { HostAppService } from 'services/hostApp'
import { LogService } from 'services/log' import { LogService } from 'services/log'
import { HotkeysService } from 'services/hotkeys' import { HotkeysService, AppHotkeyProvider } from 'services/hotkeys'
import { ModalService } from 'services/modal' import { ModalService } from 'services/modal'
import { NotifyService } from 'services/notify' import { NotifyService } from 'services/notify'
import { PluginsService } from 'services/plugins' import { PluginsService } from 'services/plugins'
@ -23,6 +23,8 @@ import { TabBodyComponent } from 'components/tabBody'
import { TabHeaderComponent } from 'components/tabHeader' import { TabHeaderComponent } from 'components/tabHeader'
import { TitleBarComponent } from 'components/titleBar' import { TitleBarComponent } from 'components/titleBar'
import { HotkeyProvider } from 'api/hotkeyProvider'
let plugins = [ let plugins = [
require('./settings').default, require('./settings').default,
@ -50,6 +52,7 @@ let plugins = [
NotifyService, NotifyService,
PluginsService, PluginsService,
QuitterService, QuitterService,
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
], ],
entryComponents: [ entryComponents: [
], ],
@ -65,7 +68,4 @@ let plugins = [
] ]
}) })
export class AppModule { export class AppModule {
constructor () {
//pluginDispatcher.register(require('./plugin.hyperlinks').default)
}
} }

View File

@ -12,6 +12,7 @@ import { DockingService } from 'services/docking'
import { AppService, IToolbarButton, ToolbarButtonProvider } from 'api' import { AppService, IToolbarButton, ToolbarButtonProvider } from 'api'
import 'angular2-toaster/lib/toaster.css' import 'angular2-toaster/lib/toaster.css'
import 'overrides.scss'
import 'global.less' import 'global.less'
import 'theme.scss' import 'theme.scss'
@ -65,9 +66,6 @@ export class AppRootComponent {
}) })
this.hotkeys.matchedHotkey.subscribe((hotkey) => { this.hotkeys.matchedHotkey.subscribe((hotkey) => {
if (hotkey == 'new-tab') {
// TODO this.newTab()
}
if (hotkey.startsWith('tab-')) { if (hotkey.startsWith('tab-')) {
let index = parseInt(hotkey.split('-')[1]) let index = parseInt(hotkey.split('-')[1])
if (index <= this.app.tabs.length) { if (index <= this.app.tabs.length) {

View File

@ -66,24 +66,6 @@ body {
} }
ngb-modal-backdrop {
// ngbmodalwindow has its own, properly animated backdrop
background: transparent !important;
}
ngb-modal-window.fade.in {
&.out {
opacity: 0;
.modal-dialog {
transform: translate(0, -25%);
}
}
}
.btn { .btn {
i + * { i + * {
margin-left: 5px; margin-left: 5px;

9
app/src/overrides.scss Normal file
View File

@ -0,0 +1,9 @@
ngb-tabset.vertical {
display: flex;
> .nav {
display: flex;
flex-direction: column;
flex: none;
}
}

View File

@ -1,69 +1,54 @@
import * as yaml from 'js-yaml' import * as yaml from 'js-yaml'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
import { EventEmitter, Injectable } from '@angular/core' import { EventEmitter, Injectable, Inject } from '@angular/core'
import { ElectronService } from 'services/electron' import { ElectronService } from 'services/electron'
import { ConfigProvider } from 'api/configProvider'
const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s }) const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s })
const defaultConfigValues : IConfigData = require('../../defaultConfigValues.yaml')
const defaultConfigStructure : IConfigData = require('../../defaultConfigStructure.yaml')
export interface IAppearanceData {
useNativeFrame: boolean
font: string
fontSize: number
dock: string
dockScreen: string
dockFill: number
tabsOnTop: boolean
}
export interface ITerminalData {
bell: string|boolean
}
export interface IConfigData {
appearance?: IAppearanceData
hotkeys?: any
terminal?: ITerminalData
}
@Injectable() @Injectable()
export class ConfigService { export class ConfigService {
store: IConfigData store: any
change = new EventEmitter() change = new EventEmitter()
restartRequested: boolean restartRequested: boolean
private path: string private path: string
private configStructure: any = require('../../defaultConfigStructure.yaml')
private defaultConfigValues: any = require('../../defaultConfigValues.yaml')
constructor ( constructor (
electron: ElectronService electron: ElectronService,
@Inject(ConfigProvider) configProviders: ConfigProvider[],
) { ) {
this.path = path.join(electron.app.getPath('userData'), 'config.yaml') this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
this.configStructure = configProviders.map(x => x.configStructure).reduce(configMerge, this.configStructure)
this.defaultConfigValues = configProviders.map(x => x.defaultConfigValues).reduce(configMerge, this.defaultConfigValues)
this.load() this.load()
} }
load () { load (): void {
if (fs.existsSync(this.path)) { if (fs.existsSync(this.path)) {
this.store = configMerge(defaultConfigStructure, yaml.safeLoad(fs.readFileSync(this.path, 'utf8'))) this.store = configMerge(this.configStructure, yaml.safeLoad(fs.readFileSync(this.path, 'utf8')))
} else { } else {
this.store = Object.assign({}, defaultConfigStructure) this.store = Object.assign({}, this.configStructure)
} }
} }
save () { save (): void {
fs.writeFileSync(this.path, yaml.safeDump(this.store), 'utf8') fs.writeFileSync(this.path, yaml.safeDump(this.store), 'utf8')
this.emitChange() this.emitChange()
} }
full () : IConfigData { full (): any {
return configMerge(defaultConfigValues, this.store) return configMerge(this.defaultConfigValues, this.store)
} }
emitChange () { emitChange (): void {
this.change.emit() this.change.emit()
} }
requestRestart () { requestRestart (): void {
this.restartRequested = true this.restartRequested = true
} }
} }

View File

@ -1,13 +1,10 @@
import { Injectable, NgZone, EventEmitter } from '@angular/core' import { Injectable, Inject, NgZone, EventEmitter } from '@angular/core'
import { ElectronService } from 'services/electron' import { ElectronService } from 'services/electron'
import { ConfigService } from 'services/config' import { ConfigService } from 'services/config'
import { NativeKeyEvent, stringifyKeySequence } from './hotkeys.util' import { NativeKeyEvent, stringifyKeySequence } from './hotkeys.util'
import { IHotkeyDescription, HotkeyProvider } from 'api/hotkeyProvider'
const hterm = require('hterm-commonjs') const hterm = require('hterm-commonjs')
export interface HotkeyDescription {
id: string,
name: string,
}
export interface PartialHotkeyMatch { export interface PartialHotkeyMatch {
id: string, id: string,
@ -16,69 +13,6 @@ export interface PartialHotkeyMatch {
} }
const KEY_TIMEOUT = 2000 const KEY_TIMEOUT = 2000
const HOTKEYS: HotkeyDescription[] = [
{
id: 'new-tab',
name: 'New tab',
},
{
id: 'close-tab',
name: 'Close tab',
},
{
id: 'toggle-last-tab',
name: 'Toggle last tab',
},
{
id: 'next-tab',
name: 'Next tab',
},
{
id: 'previous-tab',
name: 'Previous tab',
},
{
id: 'tab-1',
name: 'Tab 1',
},
{
id: 'tab-2',
name: 'Tab 2',
},
{
id: 'tab-3',
name: 'Tab 3',
},
{
id: 'tab-4',
name: 'Tab 4',
},
{
id: 'tab-5',
name: 'Tab 5',
},
{
id: 'tab-6',
name: 'Tab 6',
},
{
id: 'tab-7',
name: 'Tab 7',
},
{
id: 'tab-8',
name: 'Tab 8',
},
{
id: 'tab-9',
name: 'Tab 9',
},
{
id: 'tab-10',
name: 'Tab 10',
},
]
interface EventBufferEntry { interface EventBufferEntry {
event: NativeKeyEvent, event: NativeKeyEvent,
@ -92,11 +26,13 @@ export class HotkeysService {
globalHotkey = new EventEmitter() globalHotkey = new EventEmitter()
private currentKeystrokes: EventBufferEntry[] = [] private currentKeystrokes: EventBufferEntry[] = []
private disabledLevel = 0 private disabledLevel = 0
private hotkeyDescriptions: IHotkeyDescription[]
constructor( constructor(
private zone: NgZone, private zone: NgZone,
private electron: ElectronService, private electron: ElectronService,
private config: ConfigService, private config: ConfigService,
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
) { ) {
let events = [ let events = [
{ {
@ -122,6 +58,7 @@ export class HotkeysService {
oldHandler.bind(this)(nativeEvent) oldHandler.bind(this)(nativeEvent)
} }
}) })
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
} }
emitNativeEvent (name, nativeEvent) { emitNativeEvent (name, nativeEvent) {
@ -214,8 +151,8 @@ export class HotkeysService {
return result return result
} }
getHotkeyDescription (id: string) : HotkeyDescription { getHotkeyDescription (id: string) : IHotkeyDescription {
return HOTKEYS.filter((x) => x.id == id)[0] return this.hotkeyDescriptions.filter((x) => x.id == id)[0]
} }
enable () { enable () {
@ -231,3 +168,70 @@ export class HotkeysService {
} }
} }
@Injectable()
export class AppHotkeyProvider extends HotkeyProvider {
hotkeys: IHotkeyDescription[] = [
{
id: 'new-tab',
name: 'New tab',
},
{
id: 'close-tab',
name: 'Close tab',
},
{
id: 'toggle-last-tab',
name: 'Toggle last tab',
},
{
id: 'next-tab',
name: 'Next tab',
},
{
id: 'previous-tab',
name: 'Previous tab',
},
{
id: 'tab-1',
name: 'Tab 1',
},
{
id: 'tab-2',
name: 'Tab 2',
},
{
id: 'tab-3',
name: 'Tab 3',
},
{
id: 'tab-4',
name: 'Tab 4',
},
{
id: 'tab-5',
name: 'Tab 5',
},
{
id: 'tab-6',
name: 'Tab 6',
},
{
id: 'tab-7',
name: 'Tab 7',
},
{
id: 'tab-8',
name: 'Tab 8',
},
{
id: 'tab-9',
name: 'Tab 9',
},
{
id: 'tab-10',
name: 'Tab 10',
},
]
}

View File

@ -2,7 +2,7 @@ import { Component } from '@angular/core'
export declare type ComponentType = new (...args: any[]) => Component export declare type ComponentType = new (...args: any[]) => Component
export abstract class SettingsProvider { export abstract class SettingsTabProvider {
title: string title: string
getComponentType (): ComponentType { getComponentType (): ComponentType {

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)') &times;
.add((click)='addItem()') Add

View File

@ -0,0 +1,28 @@
:host {
display: flex;
}
.item {
display: flex;
flex: none;
padding: 3px 0;
margin-right: 5px;
.body {
flex: none;
display: flex;
.stroke {
flex: none;
padding: 0 3px;
}
}
.remove {
flex: none;
}
}
.add {
flex: auto;
}

View File

@ -0,0 +1,48 @@
import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'
import { ModalService } from 'services/modal'
import { HotkeyInputModalComponent } from './hotkeyInputModal'
@Component({
selector: 'multi-hotkey-input',
template: require('./multiHotkeyInput.pug'),
styles: [require('./multiHotkeyInput.scss')],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MultiHotkeyInputComponent {
constructor (
private modal: ModalService,
) { }
ngOnInit () {
if (!this.model) {
this.model = []
}
if (typeof this.model == 'string') {
this.model = [this.model]
}
this.model = this.model.map(item => (typeof item == 'string') ? [item] : item)
}
editItem (item) {
this.modal.open(HotkeyInputModalComponent).result.then((value: string[]) => {
Object.assign(item, value)
this.modelChange.emit(this.model)
})
}
addItem () {
this.modal.open(HotkeyInputModalComponent).result.then((value: string[]) => {
this.model.push(value)
this.modelChange.emit(this.model)
})
}
removeItem (item) {
this.model = this.model.filter(x => x !== item)
this.modelChange.emit(this.model)
}
@Input() model: string[][]
@Output() modelChange = new EventEmitter()
}

View File

@ -0,0 +1,10 @@
:host /deep/ ngb-tabset {
flex: auto;
}
:host /deep/ ngb-tabset > .tab-content {
padding: 15px 30px;
margin: 0;
flex: auto;
overflow-y: auto;
}

View File

@ -1,6 +1,6 @@
button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes button.btn.btn-outline-warning.btn-block(*ngIf='config.restartRequested', '(click)'='restartApp()') Restart the app to apply changes
ngb-tabset(type='tabs') ngb-tabset.vertical(type='tabs')
ngb-tab(*ngFor='let provider of settingsProviders') ngb-tab(*ngFor='let provider of settingsProviders')
template(ngbTabTitle) template(ngbTabTitle)
| {{provider.title}} | {{provider.title}}
@ -134,10 +134,13 @@ ngb-tabset(type='tabs')
| Hotkeys | Hotkeys
template(ngbTabContent) template(ngbTabContent)
.form-group .form-group
table.table table
tr tr
th Toggle terminal window th Toggle terminal window
td td
hotkey-input('[(model)]'='globalHotkey') hotkey-input('[(model)]'='globalHotkey')
tr(*ngFor='let hotkey of hotkeyDescriptions')
th {{hotkey.name}}
td
multi-hotkey-input('[(model)]'='config.store.hotkeys[hotkey.id]')

View File

@ -1,6 +1,6 @@
:host { :host {
display: flex;
flex: auto; flex: auto;
margin: 15px;
>.btn-block { >.btn-block {
margin-bottom: 20px; margin-bottom: 20px;

View File

@ -2,27 +2,34 @@ import { Component, Inject } from '@angular/core'
import { ElectronService } from 'services/electron' import { ElectronService } from 'services/electron'
import { ConfigService } from 'services/config' import { ConfigService } from 'services/config'
import { DockingService } from 'services/docking' import { DockingService } from 'services/docking'
import { IHotkeyDescription, HotkeyProvider } from 'api/hotkeyProvider'
import { BaseTabComponent } from 'components/baseTab' import { BaseTabComponent } from 'components/baseTab'
import { SettingsTab } from '../tab' import { SettingsTab } from '../tab'
import { SettingsProvider } from '../api' import { SettingsTabProvider } from '../api'
@Component({ @Component({
selector: 'settings-pane', selector: 'settings-pane',
template: require('./settingsPane.pug'), template: require('./settingsPane.pug'),
styles: [require('./settingsPane.less')], styles: [
require('./settingsPane.scss'),
require('./settingsPane.deep.css'),
],
}) })
export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> { export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> {
globalHotkey = ['Ctrl+Shift+G'] globalHotkey = ['Ctrl+Shift+G']
private hotkeyDescriptions: IHotkeyDescription[]
constructor( constructor(
public config: ConfigService, public config: ConfigService,
private electron: ElectronService, private electron: ElectronService,
public docking: DockingService, public docking: DockingService,
@Inject(SettingsProvider) public settingsProviders: SettingsProvider[] @Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[]
) { ) {
super() super()
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
} }
ngOnDestroy () { ngOnDestroy () {

View File

@ -1,12 +1,12 @@
import { Component, Input, ViewContainerRef, ViewChild, ComponentFactoryResolver, ComponentRef } from '@angular/core' import { Component, Input, ViewContainerRef, ViewChild, ComponentFactoryResolver, ComponentRef } from '@angular/core'
import { SettingsProvider } from '../api' import { SettingsTabProvider } from '../api'
@Component({ @Component({
selector: 'settings-tab-body', selector: 'settings-tab-body',
template: '<template #placeholder></template>', template: '<template #placeholder></template>',
}) })
export class SettingsTabBodyComponent { export class SettingsTabBodyComponent {
@Input() provider: SettingsProvider @Input() provider: SettingsTabProvider
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef @ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
private component: ComponentRef<Component> private component: ComponentRef<Component>

View File

@ -7,6 +7,7 @@ import { HotkeyInputComponent } from './components/hotkeyInput'
import { HotkeyDisplayComponent } from './components/hotkeyDisplay' import { HotkeyDisplayComponent } from './components/hotkeyDisplay'
import { HotkeyHintComponent } from './components/hotkeyHint' import { HotkeyHintComponent } from './components/hotkeyHint'
import { HotkeyInputModalComponent } from './components/hotkeyInputModal' import { HotkeyInputModalComponent } from './components/hotkeyInputModal'
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput'
import { SettingsPaneComponent } from './components/settingsPane' import { SettingsPaneComponent } from './components/settingsPane'
import { SettingsTabBodyComponent } from './components/settingsTabBody' import { SettingsTabBodyComponent } from './components/settingsTabBody'
@ -35,6 +36,7 @@ import { RecoveryProvider } from './recoveryProvider'
HotkeyHintComponent, HotkeyHintComponent,
HotkeyInputComponent, HotkeyInputComponent,
HotkeyInputModalComponent, HotkeyInputModalComponent,
MultiHotkeyInputComponent,
SettingsPaneComponent, SettingsPaneComponent,
SettingsTabBodyComponent, SettingsTabBodyComponent,
], ],

View File

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { ToolbarButtonProvider, IToolbarButton, AppService } from 'api' import { HotkeysService, ToolbarButtonProvider, IToolbarButton, AppService } from 'api'
import { SessionsService } from './services/sessions' import { SessionsService } from './services/sessions'
import { TerminalTab } from './tab' import { TerminalTab } from './tab'
@ -9,8 +9,18 @@ export class ButtonProvider extends ToolbarButtonProvider {
constructor ( constructor (
private app: AppService, private app: AppService,
private sessions: SessionsService, private sessions: SessionsService,
hotkeys: HotkeysService,
) { ) {
super() super()
hotkeys.matchedHotkey.subscribe(async (hotkey) => {
if (hotkey == 'new-tab') {
this.app.openTab(await this.getNewTab())
}
})
}
async getNewTab (): Promise<TerminalTab> {
return new TerminalTab(await this.sessions.createNewSession({ command: 'zsh' }))
} }
provide (): IToolbarButton[] { provide (): IToolbarButton[] {
@ -18,8 +28,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
icon: 'plus', icon: 'plus',
title: 'New terminal', title: 'New terminal',
click: async () => { click: async () => {
let session = await this.sessions.createNewSession({ command: 'zsh' }) this.app.openTab(await this.getNewTab())
this.app.openTab(new TerminalTab(session))
} }
}] }]
} }

View File

@ -3,8 +3,8 @@
.form-group .form-group
label Preview label Preview
.appearance-preview( .appearance-preview(
[style.font-family]='config.full().appearance.font', [style.font-family]='config.full().terminal.font',
[style.font-size]='config.full().appearance.fontSize + "px"', [style.font-size]='config.full().terminal.fontSize + "px"',
) )
.text john@doe-pc$ ls .text john@doe-pc$ ls
.text foo bar .text foo bar
@ -14,7 +14,7 @@
input.form-control( input.form-control(
type='text', type='text',
[ngbTypeahead]='fontAutocomplete', [ngbTypeahead]='fontAutocomplete',
'[(ngModel)]'='config.store.appearance.font', '[(ngModel)]'='config.store.terminal.font',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
) )
small.form-text.text-muted Font to be used in the terminal small.form-text.text-muted Font to be used in the terminal
@ -23,7 +23,7 @@
label Font size label Font size
input.form-control( input.form-control(
type='number', type='number',
'[(ngModel)]'='config.store.appearance.fontSize', '[(ngModel)]'='config.store.terminal.fontSize',
(ngModelChange)='config.save()', (ngModelChange)='config.save()',
) )
small.form-text.text-muted Text size to be used in the terminal small.form-text.text-muted Text size to be used in the terminal

View File

@ -83,8 +83,8 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
configure () { configure () {
let config = this.config.full() let config = this.config.full()
preferenceManager.set('font-family', config.appearance.font) preferenceManager.set('font-family', config.terminal.font)
preferenceManager.set('font-size', config.appearance.fontSize) preferenceManager.set('font-size', config.terminal.fontSize)
preferenceManager.set('audible-bell-sound', '') preferenceManager.set('audible-bell-sound', '')
preferenceManager.set('desktop-notification-bell', config.terminal.bell == 'notification') preferenceManager.set('desktop-notification-bell', config.terminal.bell == 'notification')
preferenceManager.set('enable-clipboard-notice', false) preferenceManager.set('enable-clipboard-notice', false)

View File

@ -0,0 +1,24 @@
import { ConfigProvider } from 'api'
export class TerminalConfigProvider extends ConfigProvider {
defaultConfigValues: any = {
terminal: {
font: 'monospace',
fontSize: 14,
bell: 'off',
},
hotkeys: {
'new-tab': [
['Ctrl-A', 'C'],
['Ctrl-A', 'Ctrl-C'],
'Ctrl-Shift-T',
]
},
}
configStructure: any = {
terminal: {},
hotkeys: {},
}
}

View File

@ -3,8 +3,8 @@ import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms' import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { ToolbarButtonProvider, TabRecoveryProvider } from 'api' import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider } from 'api'
import { SettingsProvider } from '../settings/api' import { SettingsTabProvider } from '../settings/api'
import { TerminalTabComponent } from './components/terminalTab' import { TerminalTabComponent } from './components/terminalTab'
import { SettingsComponent } from './components/settings' import { SettingsComponent } from './components/settings'
@ -14,6 +14,7 @@ import { ButtonProvider } from './buttonProvider'
import { RecoveryProvider } from './recoveryProvider' import { RecoveryProvider } from './recoveryProvider'
import { SessionPersistenceProvider } from './api' import { SessionPersistenceProvider } from './api'
import { TerminalSettingsProvider } from './settings' import { TerminalSettingsProvider } from './settings'
import { TerminalConfigProvider } from './config'
@NgModule({ @NgModule({
imports: [ imports: [
@ -26,7 +27,8 @@ import { TerminalSettingsProvider } from './settings'
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true }, { provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
SessionsService, SessionsService,
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider }, { provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider },
{ provide: SettingsProvider, useClass: TerminalSettingsProvider, multi: true }, { provide: SettingsTabProvider, useClass: TerminalSettingsProvider, multi: true },
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
], ],
entryComponents: [ entryComponents: [
TerminalTabComponent, TerminalTabComponent,

View File

@ -1,10 +1,10 @@
import { Injectable } from '@angular/core' import { Injectable } from '@angular/core'
import { SettingsProvider, ComponentType } from '../settings/api' import { SettingsTabProvider, ComponentType } from '../settings/api'
import { SettingsComponent } from './components/settings' import { SettingsComponent } from './components/settings'
@Injectable() @Injectable()
export class TerminalSettingsProvider extends SettingsProvider { export class TerminalSettingsProvider extends SettingsTabProvider {
title = 'Terminal' title = 'Terminal'
getComponentType (): ComponentType { getComponentType (): ComponentType {

View File

@ -39,7 +39,6 @@ module.exports = {
} }
] ]
}, },
{ test: /\.css$/, loader: "style-loader!css-loader" },
{ {
test: /\.less$/, test: /\.less$/,
loader: "style-loader!css-loader!less-loader", loader: "style-loader!css-loader!less-loader",
@ -60,6 +59,16 @@ module.exports = {
use: ['to-string-loader', 'css-loader', 'sass-loader'], use: ['to-string-loader', 'css-loader', 'sass-loader'],
include: [/app\/.*components\//], include: [/app\/.*components\//],
}, },
{
test: /\.css$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
exclude: [/app\/.*components\//],
},
{
test: /\.css$/,
use: ['to-string-loader', 'css-loader'],
include: [/app\/.*components\//],
},
{ {
test: /\.(png|svg)$/, test: /\.(png|svg)$/,
loader: "file-loader", loader: "file-loader",