diff --git a/app/assets/bootstrap/overrides.less b/app/assets/bootstrap/overrides.less deleted file mode 100644 index 2191ab51..00000000 --- a/app/assets/bootstrap/overrides.less +++ /dev/null @@ -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; - } -} diff --git a/app/defaultConfigValues.yaml b/app/defaultConfigValues.yaml index e6c67aa5..5300b213 100644 --- a/app/defaultConfigValues.yaml +++ b/app/defaultConfigValues.yaml @@ -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 diff --git a/app/src/api/configProvider.ts b/app/src/api/configProvider.ts new file mode 100644 index 00000000..34f21f4a --- /dev/null +++ b/app/src/api/configProvider.ts @@ -0,0 +1,4 @@ +export abstract class ConfigProvider { + configStructure: any = {} + defaultConfigValues: any = {} +} diff --git a/app/src/api/hotkeyProvider.ts b/app/src/api/hotkeyProvider.ts new file mode 100644 index 00000000..fa3fe1b0 --- /dev/null +++ b/app/src/api/hotkeyProvider.ts @@ -0,0 +1,8 @@ +export interface IHotkeyDescription { + id: string, + name: string, +} + +export abstract class HotkeyProvider { + hotkeys: IHotkeyDescription[] = [] +} diff --git a/app/src/api/index.ts b/app/src/api/index.ts index 39291908..d5910cd8 100644 --- a/app/src/api/index.ts +++ b/app/src/api/index.ts @@ -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' diff --git a/app/src/app.module.ts b/app/src/app.module.ts index 73555cc9..d21adb19 100644 --- a/app/src/app.module.ts +++ b/app/src/app.module.ts @@ -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) - } } diff --git a/app/src/components/appRoot.ts b/app/src/components/appRoot.ts index f254ff32..5b82437d 100644 --- a/app/src/components/appRoot.ts +++ b/app/src/components/appRoot.ts @@ -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) { diff --git a/app/src/global.less b/app/src/global.less index 17f1f832..9f98b241 100644 --- a/app/src/global.less +++ b/app/src/global.less @@ -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; diff --git a/app/src/overrides.scss b/app/src/overrides.scss new file mode 100644 index 00000000..610ba49c --- /dev/null +++ b/app/src/overrides.scss @@ -0,0 +1,9 @@ +ngb-tabset.vertical { + display: flex; + + > .nav { + display: flex; + flex-direction: column; + flex: none; + } +} diff --git a/app/src/services/config.ts b/app/src/services/config.ts index 4b2561b9..53ddf26a 100644 --- a/app/src/services/config.ts +++ b/app/src/services/config.ts @@ -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 } } diff --git a/app/src/services/hotkeys.ts b/app/src/services/hotkeys.ts index 7617d2a5..a3060678 100644 --- a/app/src/services/hotkeys.ts +++ b/app/src/services/hotkeys.ts @@ -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', + }, + ] +} diff --git a/app/src/settings/api.ts b/app/src/settings/api.ts index 9ed83161..675cd7de 100644 --- a/app/src/settings/api.ts +++ b/app/src/settings/api.ts @@ -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 { diff --git a/app/src/settings/components/multiHotkeyInput.pug b/app/src/settings/components/multiHotkeyInput.pug new file mode 100644 index 00000000..5e848e3a --- /dev/null +++ b/app/src/settings/components/multiHotkeyInput.pug @@ -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 diff --git a/app/src/settings/components/multiHotkeyInput.scss b/app/src/settings/components/multiHotkeyInput.scss new file mode 100644 index 00000000..d38dc84a --- /dev/null +++ b/app/src/settings/components/multiHotkeyInput.scss @@ -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; +} diff --git a/app/src/settings/components/multiHotkeyInput.ts b/app/src/settings/components/multiHotkeyInput.ts new file mode 100644 index 00000000..29a8803c --- /dev/null +++ b/app/src/settings/components/multiHotkeyInput.ts @@ -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() +} diff --git a/app/src/settings/components/settingsPane.deep.css b/app/src/settings/components/settingsPane.deep.css new file mode 100644 index 00000000..ecab3379 --- /dev/null +++ b/app/src/settings/components/settingsPane.deep.css @@ -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; +} diff --git a/app/src/settings/components/settingsPane.pug b/app/src/settings/components/settingsPane.pug index 71cf18c1..90717bef 100644 --- a/app/src/settings/components/settingsPane.pug +++ b/app/src/settings/components/settingsPane.pug @@ -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]') + diff --git a/app/src/settings/components/settingsPane.less b/app/src/settings/components/settingsPane.scss similarity index 88% rename from app/src/settings/components/settingsPane.less rename to app/src/settings/components/settingsPane.scss index 5da83ebe..4e08ab46 100644 --- a/app/src/settings/components/settingsPane.less +++ b/app/src/settings/components/settingsPane.scss @@ -1,6 +1,6 @@ :host { + display: flex; flex: auto; - margin: 15px; >.btn-block { margin-bottom: 20px; diff --git a/app/src/settings/components/settingsPane.ts b/app/src/settings/components/settingsPane.ts index 34fa5882..2426a58f 100644 --- a/app/src/settings/components/settingsPane.ts +++ b/app/src/settings/components/settingsPane.ts @@ -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 { 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 () { diff --git a/app/src/settings/components/settingsTabBody.ts b/app/src/settings/components/settingsTabBody.ts index a3ea6cff..c9e956bd 100644 --- a/app/src/settings/components/settingsTabBody.ts +++ b/app/src/settings/components/settingsTabBody.ts @@ -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: '', }) export class SettingsTabBodyComponent { - @Input() provider: SettingsProvider + @Input() provider: SettingsTabProvider @ViewChild('placeholder', {read: ViewContainerRef}) placeholder: ViewContainerRef private component: ComponentRef diff --git a/app/src/settings/index.ts b/app/src/settings/index.ts index 2fdedd6a..ae490261 100644 --- a/app/src/settings/index.ts +++ b/app/src/settings/index.ts @@ -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, ], diff --git a/app/src/terminal/buttonProvider.ts b/app/src/terminal/buttonProvider.ts index f28dac34..a3c3ed13 100644 --- a/app/src/terminal/buttonProvider.ts +++ b/app/src/terminal/buttonProvider.ts @@ -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 { + 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()) } }] } diff --git a/app/src/terminal/components/settings.pug b/app/src/terminal/components/settings.pug index c56d33b8..3c552c04 100644 --- a/app/src/terminal/components/settings.pug +++ b/app/src/terminal/components/settings.pug @@ -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 diff --git a/app/src/terminal/components/terminalTab.ts b/app/src/terminal/components/terminalTab.ts index 38d6187e..0cff2658 100644 --- a/app/src/terminal/components/terminalTab.ts +++ b/app/src/terminal/components/terminalTab.ts @@ -83,8 +83,8 @@ export class TerminalTabComponent extends BaseTabComponent { 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) diff --git a/app/src/terminal/config.ts b/app/src/terminal/config.ts new file mode 100644 index 00000000..44967160 --- /dev/null +++ b/app/src/terminal/config.ts @@ -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: {}, + } +} diff --git a/app/src/terminal/index.ts b/app/src/terminal/index.ts index 4e6689f8..e5f24772 100644 --- a/app/src/terminal/index.ts +++ b/app/src/terminal/index.ts @@ -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, diff --git a/app/src/terminal/settings.ts b/app/src/terminal/settings.ts index e0c69355..efd2a2ae 100644 --- a/app/src/terminal/settings.ts +++ b/app/src/terminal/settings.ts @@ -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 { diff --git a/webpack.config.js b/webpack.config.js index c6d0e71b..d7de5d4c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -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",