mirror of
https://github.com/Eugeny/tabby
synced 2024-11-14 08:57:21 +00:00
.
This commit is contained in:
parent
482343e383
commit
cb7e1cd157
28 changed files with 295 additions and 703 deletions
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
appearance:
|
||||
font: monospace
|
||||
fontSize: 14
|
||||
dock: 'off'
|
||||
dockScreen: 'current'
|
||||
dockFill: 50
|
||||
tabsOnTop: true
|
||||
hotkeys:
|
||||
new-tab:
|
||||
- ['Ctrl-A', 'C']
|
||||
- ['Ctrl-A', 'Ctrl-C']
|
||||
- 'Ctrl-Shift-T'
|
||||
close-tab:
|
||||
- 'Ctrl-Shift-W'
|
||||
- ['Ctrl-A', 'K']
|
||||
|
@ -52,5 +46,3 @@ hotkeys:
|
|||
tab-10:
|
||||
- 'Alt-0'
|
||||
- ['Ctrl-A', '0']
|
||||
terminal:
|
||||
bell: off
|
||||
|
|
4
app/src/api/configProvider.ts
Normal file
4
app/src/api/configProvider.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export abstract class ConfigProvider {
|
||||
configStructure: any = {}
|
||||
defaultConfigValues: any = {}
|
||||
}
|
8
app/src/api/hotkeyProvider.ts
Normal file
8
app/src/api/hotkeyProvider.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
export interface IHotkeyDescription {
|
||||
id: string,
|
||||
name: string,
|
||||
}
|
||||
|
||||
export abstract class HotkeyProvider {
|
||||
hotkeys: IHotkeyDescription[] = []
|
||||
}
|
|
@ -1,7 +1,10 @@
|
|||
export { Tab } from './tab'
|
||||
export { TabRecoveryProvider } from './tabRecovery'
|
||||
export { ToolbarButtonProvider, IToolbarButton } from './toolbarButtonProvider'
|
||||
export { ConfigProvider } from './configProvider'
|
||||
export { HotkeyProvider, IHotkeyDescription } from './hotkeyProvider'
|
||||
|
||||
export { AppService } from 'services/app'
|
||||
export { PluginsService } from 'services/plugins'
|
||||
export { ElectronService } from 'services/electron'
|
||||
export { HotkeysService } from 'services/hotkeys'
|
||||
|
|
|
@ -10,7 +10,7 @@ import { ConfigService } from 'services/config'
|
|||
import { ElectronService } from 'services/electron'
|
||||
import { HostAppService } from 'services/hostApp'
|
||||
import { LogService } from 'services/log'
|
||||
import { HotkeysService } from 'services/hotkeys'
|
||||
import { HotkeysService, AppHotkeyProvider } from 'services/hotkeys'
|
||||
import { ModalService } from 'services/modal'
|
||||
import { NotifyService } from 'services/notify'
|
||||
import { PluginsService } from 'services/plugins'
|
||||
|
@ -23,6 +23,8 @@ import { TabBodyComponent } from 'components/tabBody'
|
|||
import { TabHeaderComponent } from 'components/tabHeader'
|
||||
import { TitleBarComponent } from 'components/titleBar'
|
||||
|
||||
import { HotkeyProvider } from 'api/hotkeyProvider'
|
||||
|
||||
|
||||
let plugins = [
|
||||
require('./settings').default,
|
||||
|
@ -50,6 +52,7 @@ let plugins = [
|
|||
NotifyService,
|
||||
PluginsService,
|
||||
QuitterService,
|
||||
{ provide: HotkeyProvider, useClass: AppHotkeyProvider, multi: true },
|
||||
],
|
||||
entryComponents: [
|
||||
],
|
||||
|
@ -65,7 +68,4 @@ let plugins = [
|
|||
]
|
||||
})
|
||||
export class AppModule {
|
||||
constructor () {
|
||||
//pluginDispatcher.register(require('./plugin.hyperlinks').default)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { DockingService } from 'services/docking'
|
|||
import { AppService, IToolbarButton, ToolbarButtonProvider } from 'api'
|
||||
|
||||
import 'angular2-toaster/lib/toaster.css'
|
||||
import 'overrides.scss'
|
||||
import 'global.less'
|
||||
import 'theme.scss'
|
||||
|
||||
|
@ -65,9 +66,6 @@ export class AppRootComponent {
|
|||
})
|
||||
|
||||
this.hotkeys.matchedHotkey.subscribe((hotkey) => {
|
||||
if (hotkey == 'new-tab') {
|
||||
// TODO this.newTab()
|
||||
}
|
||||
if (hotkey.startsWith('tab-')) {
|
||||
let index = parseInt(hotkey.split('-')[1])
|
||||
if (index <= this.app.tabs.length) {
|
||||
|
|
|
@ -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 {
|
||||
i + * {
|
||||
margin-left: 5px;
|
||||
|
|
9
app/src/overrides.scss
Normal file
9
app/src/overrides.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
ngb-tabset.vertical {
|
||||
display: flex;
|
||||
|
||||
> .nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: none;
|
||||
}
|
||||
}
|
|
@ -1,69 +1,54 @@
|
|||
import * as yaml from 'js-yaml'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
import { EventEmitter, Injectable } from '@angular/core'
|
||||
import { EventEmitter, Injectable, Inject } from '@angular/core'
|
||||
import { ElectronService } from 'services/electron'
|
||||
import { ConfigProvider } from 'api/configProvider'
|
||||
|
||||
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()
|
||||
export class ConfigService {
|
||||
store: IConfigData
|
||||
store: any
|
||||
change = new EventEmitter()
|
||||
restartRequested: boolean
|
||||
private path: string
|
||||
private configStructure: any = require('../../defaultConfigStructure.yaml')
|
||||
private defaultConfigValues: any = require('../../defaultConfigValues.yaml')
|
||||
|
||||
constructor (
|
||||
electron: ElectronService
|
||||
electron: ElectronService,
|
||||
@Inject(ConfigProvider) configProviders: ConfigProvider[],
|
||||
) {
|
||||
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()
|
||||
}
|
||||
|
||||
load () {
|
||||
load (): void {
|
||||
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 {
|
||||
this.store = Object.assign({}, defaultConfigStructure)
|
||||
this.store = Object.assign({}, this.configStructure)
|
||||
}
|
||||
}
|
||||
|
||||
save () {
|
||||
save (): void {
|
||||
fs.writeFileSync(this.path, yaml.safeDump(this.store), 'utf8')
|
||||
this.emitChange()
|
||||
}
|
||||
|
||||
full () : IConfigData {
|
||||
return configMerge(defaultConfigValues, this.store)
|
||||
full (): any {
|
||||
return configMerge(this.defaultConfigValues, this.store)
|
||||
}
|
||||
|
||||
emitChange () {
|
||||
emitChange (): void {
|
||||
this.change.emit()
|
||||
}
|
||||
|
||||
requestRestart () {
|
||||
requestRestart (): void {
|
||||
this.restartRequested = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 { ConfigService } from 'services/config'
|
||||
import { NativeKeyEvent, stringifyKeySequence } from './hotkeys.util'
|
||||
import { IHotkeyDescription, HotkeyProvider } from 'api/hotkeyProvider'
|
||||
const hterm = require('hterm-commonjs')
|
||||
|
||||
export interface HotkeyDescription {
|
||||
id: string,
|
||||
name: string,
|
||||
}
|
||||
|
||||
export interface PartialHotkeyMatch {
|
||||
id: string,
|
||||
|
@ -16,69 +13,6 @@ export interface PartialHotkeyMatch {
|
|||
}
|
||||
|
||||
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 {
|
||||
event: NativeKeyEvent,
|
||||
|
@ -92,11 +26,13 @@ export class HotkeysService {
|
|||
globalHotkey = new EventEmitter()
|
||||
private currentKeystrokes: EventBufferEntry[] = []
|
||||
private disabledLevel = 0
|
||||
private hotkeyDescriptions: IHotkeyDescription[]
|
||||
|
||||
constructor(
|
||||
private zone: NgZone,
|
||||
private electron: ElectronService,
|
||||
private config: ConfigService,
|
||||
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
||||
) {
|
||||
let events = [
|
||||
{
|
||||
|
@ -122,6 +58,7 @@ export class HotkeysService {
|
|||
oldHandler.bind(this)(nativeEvent)
|
||||
}
|
||||
})
|
||||
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
||||
}
|
||||
|
||||
emitNativeEvent (name, nativeEvent) {
|
||||
|
@ -214,8 +151,8 @@ export class HotkeysService {
|
|||
return result
|
||||
}
|
||||
|
||||
getHotkeyDescription (id: string) : HotkeyDescription {
|
||||
return HOTKEYS.filter((x) => x.id == id)[0]
|
||||
getHotkeyDescription (id: string) : IHotkeyDescription {
|
||||
return this.hotkeyDescriptions.filter((x) => x.id == id)[0]
|
||||
}
|
||||
|
||||
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',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Component } from '@angular/core'
|
|||
|
||||
export declare type ComponentType = new (...args: any[]) => Component
|
||||
|
||||
export abstract class SettingsProvider {
|
||||
export abstract class SettingsTabProvider {
|
||||
title: string
|
||||
|
||||
getComponentType (): ComponentType {
|
||||
|
|
6
app/src/settings/components/multiHotkeyInput.pug
Normal file
6
app/src/settings/components/multiHotkeyInput.pug
Normal 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
|
28
app/src/settings/components/multiHotkeyInput.scss
Normal file
28
app/src/settings/components/multiHotkeyInput.scss
Normal 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;
|
||||
}
|
48
app/src/settings/components/multiHotkeyInput.ts
Normal file
48
app/src/settings/components/multiHotkeyInput.ts
Normal 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()
|
||||
}
|
10
app/src/settings/components/settingsPane.deep.css
Normal file
10
app/src/settings/components/settingsPane.deep.css
Normal 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;
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
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')
|
||||
template(ngbTabTitle)
|
||||
| {{provider.title}}
|
||||
|
@ -134,10 +134,13 @@ ngb-tabset(type='tabs')
|
|||
| Hotkeys
|
||||
template(ngbTabContent)
|
||||
.form-group
|
||||
table.table
|
||||
table
|
||||
tr
|
||||
th Toggle terminal window
|
||||
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]')
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
:host {
|
||||
display: flex;
|
||||
flex: auto;
|
||||
margin: 15px;
|
||||
|
||||
>.btn-block {
|
||||
margin-bottom: 20px;
|
|
@ -2,27 +2,34 @@ import { Component, Inject } from '@angular/core'
|
|||
import { ElectronService } from 'services/electron'
|
||||
import { ConfigService } from 'services/config'
|
||||
import { DockingService } from 'services/docking'
|
||||
import { IHotkeyDescription, HotkeyProvider } from 'api/hotkeyProvider'
|
||||
|
||||
import { BaseTabComponent } from 'components/baseTab'
|
||||
import { SettingsTab } from '../tab'
|
||||
import { SettingsProvider } from '../api'
|
||||
import { SettingsTabProvider } from '../api'
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'settings-pane',
|
||||
template: require('./settingsPane.pug'),
|
||||
styles: [require('./settingsPane.less')],
|
||||
styles: [
|
||||
require('./settingsPane.scss'),
|
||||
require('./settingsPane.deep.css'),
|
||||
],
|
||||
})
|
||||
export class SettingsPaneComponent extends BaseTabComponent<SettingsTab> {
|
||||
globalHotkey = ['Ctrl+Shift+G']
|
||||
private hotkeyDescriptions: IHotkeyDescription[]
|
||||
|
||||
constructor(
|
||||
public config: ConfigService,
|
||||
private electron: ElectronService,
|
||||
public docking: DockingService,
|
||||
@Inject(SettingsProvider) public settingsProviders: SettingsProvider[]
|
||||
@Inject(HotkeyProvider) hotkeyProviders: HotkeyProvider[],
|
||||
@Inject(SettingsTabProvider) public settingsProviders: SettingsTabProvider[]
|
||||
) {
|
||||
super()
|
||||
this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b))
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Component, Input, ViewContainerRef, ViewChild, ComponentFactoryResolver, ComponentRef } from '@angular/core'
|
||||
import { SettingsProvider } from '../api'
|
||||
import { SettingsTabProvider } from '../api'
|
||||
|
||||
@Component({
|
||||
selector: 'settings-tab-body',
|
||||
template: '<template #placeholder></template>',
|
||||
})
|
||||
export class SettingsTabBodyComponent {
|
||||
@Input() provider: SettingsProvider
|
||||
@Input() provider: SettingsTabProvider
|
||||
@ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef
|
||||
private component: ComponentRef<Component>
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import { HotkeyInputComponent } from './components/hotkeyInput'
|
|||
import { HotkeyDisplayComponent } from './components/hotkeyDisplay'
|
||||
import { HotkeyHintComponent } from './components/hotkeyHint'
|
||||
import { HotkeyInputModalComponent } from './components/hotkeyInputModal'
|
||||
import { MultiHotkeyInputComponent } from './components/multiHotkeyInput'
|
||||
import { SettingsPaneComponent } from './components/settingsPane'
|
||||
import { SettingsTabBodyComponent } from './components/settingsTabBody'
|
||||
|
||||
|
@ -35,6 +36,7 @@ import { RecoveryProvider } from './recoveryProvider'
|
|||
HotkeyHintComponent,
|
||||
HotkeyInputComponent,
|
||||
HotkeyInputModalComponent,
|
||||
MultiHotkeyInputComponent,
|
||||
SettingsPaneComponent,
|
||||
SettingsTabBodyComponent,
|
||||
],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { ToolbarButtonProvider, IToolbarButton, AppService } from 'api'
|
||||
import { HotkeysService, ToolbarButtonProvider, IToolbarButton, AppService } from 'api'
|
||||
import { SessionsService } from './services/sessions'
|
||||
import { TerminalTab } from './tab'
|
||||
|
||||
|
@ -9,8 +9,18 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||
constructor (
|
||||
private app: AppService,
|
||||
private sessions: SessionsService,
|
||||
hotkeys: HotkeysService,
|
||||
) {
|
||||
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[] {
|
||||
|
@ -18,8 +28,7 @@ export class ButtonProvider extends ToolbarButtonProvider {
|
|||
icon: 'plus',
|
||||
title: 'New terminal',
|
||||
click: async () => {
|
||||
let session = await this.sessions.createNewSession({ command: 'zsh' })
|
||||
this.app.openTab(new TerminalTab(session))
|
||||
this.app.openTab(await this.getNewTab())
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
.form-group
|
||||
label Preview
|
||||
.appearance-preview(
|
||||
[style.font-family]='config.full().appearance.font',
|
||||
[style.font-size]='config.full().appearance.fontSize + "px"',
|
||||
[style.font-family]='config.full().terminal.font',
|
||||
[style.font-size]='config.full().terminal.fontSize + "px"',
|
||||
)
|
||||
.text john@doe-pc$ ls
|
||||
.text foo bar
|
||||
|
@ -14,7 +14,7 @@
|
|||
input.form-control(
|
||||
type='text',
|
||||
[ngbTypeahead]='fontAutocomplete',
|
||||
'[(ngModel)]'='config.store.appearance.font',
|
||||
'[(ngModel)]'='config.store.terminal.font',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
small.form-text.text-muted Font to be used in the terminal
|
||||
|
@ -23,7 +23,7 @@
|
|||
label Font size
|
||||
input.form-control(
|
||||
type='number',
|
||||
'[(ngModel)]'='config.store.appearance.fontSize',
|
||||
'[(ngModel)]'='config.store.terminal.fontSize',
|
||||
(ngModelChange)='config.save()',
|
||||
)
|
||||
small.form-text.text-muted Text size to be used in the terminal
|
||||
|
|
|
@ -83,8 +83,8 @@ export class TerminalTabComponent extends BaseTabComponent<TerminalTab> {
|
|||
|
||||
configure () {
|
||||
let config = this.config.full()
|
||||
preferenceManager.set('font-family', config.appearance.font)
|
||||
preferenceManager.set('font-size', config.appearance.fontSize)
|
||||
preferenceManager.set('font-family', config.terminal.font)
|
||||
preferenceManager.set('font-size', config.terminal.fontSize)
|
||||
preferenceManager.set('audible-bell-sound', '')
|
||||
preferenceManager.set('desktop-notification-bell', config.terminal.bell == 'notification')
|
||||
preferenceManager.set('enable-clipboard-notice', false)
|
||||
|
|
24
app/src/terminal/config.ts
Normal file
24
app/src/terminal/config.ts
Normal 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: {},
|
||||
}
|
||||
}
|
|
@ -3,8 +3,8 @@ import { BrowserModule } from '@angular/platform-browser'
|
|||
import { FormsModule } from '@angular/forms'
|
||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
import { ToolbarButtonProvider, TabRecoveryProvider } from 'api'
|
||||
import { SettingsProvider } from '../settings/api'
|
||||
import { ToolbarButtonProvider, TabRecoveryProvider, ConfigProvider } from 'api'
|
||||
import { SettingsTabProvider } from '../settings/api'
|
||||
|
||||
import { TerminalTabComponent } from './components/terminalTab'
|
||||
import { SettingsComponent } from './components/settings'
|
||||
|
@ -14,6 +14,7 @@ import { ButtonProvider } from './buttonProvider'
|
|||
import { RecoveryProvider } from './recoveryProvider'
|
||||
import { SessionPersistenceProvider } from './api'
|
||||
import { TerminalSettingsProvider } from './settings'
|
||||
import { TerminalConfigProvider } from './config'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -26,7 +27,8 @@ import { TerminalSettingsProvider } from './settings'
|
|||
{ provide: TabRecoveryProvider, useClass: RecoveryProvider, multi: true },
|
||||
SessionsService,
|
||||
{ provide: SessionPersistenceProvider, useClass: ScreenPersistenceProvider },
|
||||
{ provide: SettingsProvider, useClass: TerminalSettingsProvider, multi: true },
|
||||
{ provide: SettingsTabProvider, useClass: TerminalSettingsProvider, multi: true },
|
||||
{ provide: ConfigProvider, useClass: TerminalConfigProvider, multi: true },
|
||||
],
|
||||
entryComponents: [
|
||||
TerminalTabComponent,
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { Injectable } from '@angular/core'
|
||||
import { SettingsProvider, ComponentType } from '../settings/api'
|
||||
import { SettingsTabProvider, ComponentType } from '../settings/api'
|
||||
import { SettingsComponent } from './components/settings'
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class TerminalSettingsProvider extends SettingsProvider {
|
||||
export class TerminalSettingsProvider extends SettingsTabProvider {
|
||||
title = 'Terminal'
|
||||
|
||||
getComponentType (): ComponentType {
|
||||
|
|
|
@ -39,7 +39,6 @@ module.exports = {
|
|||
}
|
||||
]
|
||||
},
|
||||
{ test: /\.css$/, loader: "style-loader!css-loader" },
|
||||
{
|
||||
test: /\.less$/,
|
||||
loader: "style-loader!css-loader!less-loader",
|
||||
|
@ -60,6 +59,16 @@ module.exports = {
|
|||
use: ['to-string-loader', 'css-loader', 'sass-loader'],
|
||||
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)$/,
|
||||
loader: "file-loader",
|
||||
|
|
Loading…
Reference in a new issue