Eugene Pankov 8 lat temu
rodzic
commit
2c2da1d697

+ 1 - 0
app/defaultConfigStructure.yaml

@@ -1,2 +1,3 @@
 appearance: { }
 appearance: { }
 hotkeys: { }
 hotkeys: { }
+terminal: { }

+ 5 - 0
app/defaultConfigValues.yaml

@@ -1,6 +1,9 @@
 appearance:
 appearance:
   font: monospace
   font: monospace
   fontSize: 14
   fontSize: 14
+  dock: 'off'
+  dockScreen: 'current'
+  dockFill: 50
 hotkeys:
 hotkeys:
   new-tab:
   new-tab:
     - ['Ctrl-A', 'C']
     - ['Ctrl-A', 'C']
@@ -48,3 +51,5 @@ hotkeys:
   tab-10:
   tab-10:
     - 'Alt-0'
     - 'Alt-0'
     - ['Ctrl-A', '0']
     - ['Ctrl-A', '0']
+terminal:
+  bell: off

+ 0 - 1
app/index.pug

@@ -2,7 +2,6 @@ doctype html
 html
 html
     head
     head
         meta(charset='UTF-8')
         meta(charset='UTF-8')
-        title ELEMENTS Benchmark
         base(href='index.html')
         base(href='index.html')
         script.
         script.
             console.timeStamp('index')
             console.timeStamp('index')

+ 28 - 17
app/main.js

@@ -13,6 +13,10 @@ let windowConfig = new Config({name: 'window'})
 setupWindowManagement = () => {
 setupWindowManagement = () => {
     let windowCloseable
     let windowCloseable
 
 
+    app.window.on('show', () => {
+      electron.ipcMain.send('window-shown')
+    })
+
     app.window.on('close', (e) => {
     app.window.on('close', (e) => {
         windowConfig.set('windowBoundaries', app.window.getBounds())
         windowConfig.set('windowBoundaries', app.window.getBounds())
         if (!windowCloseable) {
         if (!windowCloseable) {
@@ -33,10 +37,6 @@ setupWindowManagement = () => {
         app.window.focus()
         app.window.focus()
     })
     })
 
 
-    electron.ipcMain.on('window-focus', () => {
-        app.window.focus()
-    })
-
     electron.ipcMain.on('window-toggle-focus', () => {
     electron.ipcMain.on('window-toggle-focus', () => {
         if (app.window.isFocused()) {
         if (app.window.isFocused()) {
             app.window.minimize()
             app.window.minimize()
@@ -57,6 +57,10 @@ setupWindowManagement = () => {
         app.window.minimize()
         app.window.minimize()
     })
     })
 
 
+    electron.ipcMain.on('window-set-bounds', (event, bounds) => {
+        app.window.setBounds(bounds, true)
+    })
+
     app.on('before-quit', () => windowCloseable = true)
     app.on('before-quit', () => windowCloseable = true)
 }
 }
 
 
@@ -69,18 +73,20 @@ setupMenu = () => {
             { label: "Quit", accelerator: "CmdOrCtrl+Q", click: () => {
             { label: "Quit", accelerator: "CmdOrCtrl+Q", click: () => {
                 app.window.webContents.send('host:quit-request')
                 app.window.webContents.send('host:quit-request')
             }}
             }}
-        ]}, {
-            label: "Edit",
-            submenu: [
-                { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
-                { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
-                { type: "separator" },
-                { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
-                { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
-                { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
-                { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
-            ]
-        }]
+        ]
+      },
+      {
+        label: "Edit",
+        submenu: [
+            { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" },
+            { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" },
+            { type: "separator" },
+            { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" },
+            { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" },
+            { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" },
+            { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" }
+        ]
+    }]
 
 
     electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate(template))
     electron.Menu.setApplicationMenu(electron.Menu.buildFromTemplate(template))
 }
 }
@@ -141,7 +147,12 @@ start = () => {
     app.window.focus()
     app.window.focus()
 
 
     setupWindowManagement()
     setupWindowManagement()
-    setupMenu()
+
+    if (platform == 'darwin') {
+      setupMenu()
+    } else {
+      app.window.setMenu(null)
+    }
 
 
     console.info(`Host startup: ${Date.now() - t0}ms`)
     console.info(`Host startup: ${Date.now() - t0}ms`)
     t0 = Date.now()
     t0 = Date.now()

+ 2 - 0
app/src/app.module.ts

@@ -15,6 +15,7 @@ import { NotifyService } from 'services/notify'
 import { PluginDispatcherService } from 'services/pluginDispatcher'
 import { PluginDispatcherService } from 'services/pluginDispatcher'
 import { QuitterService } from 'services/quitter'
 import { QuitterService } from 'services/quitter'
 import { SessionsService } from 'services/sessions'
 import { SessionsService } from 'services/sessions'
+import { DockingService } from 'services/docking'
 import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
 import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
 
 
 import { AppComponent } from 'components/app'
 import { AppComponent } from 'components/app'
@@ -37,6 +38,7 @@ import { TerminalComponent } from 'components/terminal'
     ],
     ],
     providers: [
     providers: [
         ConfigService,
         ConfigService,
+        DockingService,
         ElectronService,
         ElectronService,
         HostAppService,
         HostAppService,
         HotkeysService,
         HotkeysService,

+ 37 - 30
app/src/components/app.less

@@ -1,7 +1,21 @@
 @import "~variables.less";
 @import "~variables.less";
-@import "~mixins.less";
 
 
-@title-bg: #0f151b;
+.button-states() {
+    transition: 0.125s all;
+    border: none;
+
+    &:hover:not(.active) {
+        background: rgba(0, 0, 0, .15);
+    }
+
+    &:active:not(.active),
+    &.active {
+        background: rgba(0, 0, 0, .3);
+    }
+}
+
+
+@title-bg: #131d27;
 
 
 :host {
 :host {
     display: flex;
     display: flex;
@@ -20,11 +34,10 @@
 @tab-border-radius: 4px;
 @tab-border-radius: 4px;
 
 
 .titlebar {
 .titlebar {
+    flex: 0 0 @titlebar-height;
+    display: flex;
     height: @titlebar-height;
     height: @titlebar-height;
     background: @title-bg;
     background: @title-bg;
-    flex: none;
-    display: flex;
-    flex-direction: row;
 
 
     .title {
     .title {
         flex: auto;
         flex: auto;
@@ -34,20 +47,14 @@
     }
     }
 
 
     button {
     button {
-        flex: none;
-        line-height: @titlebar-height - 2px;
-        padding: 0 15px;
+        border: none;
+        box-shadow: none;
+        border-radius: 0;
         font-size: 8px;
         font-size: 8px;
-        color: #444;
-        background: transparent;
-        transition: 0.25s all;
 
 
-        .button-states();
-
-        &:hover {
-            color: white;
+        &:not(:hover):not(:active) {
+            background: transparent;
         }
         }
-        cursor: pointer;
     }
     }
 
 
     .btn-close {
     .btn-close {
@@ -69,18 +76,20 @@
 
 
     &>button {
     &>button {
         padding: 0 15px;
         padding: 0 15px;
-        flex: none;
-        flex-grow: 0;
+        flex: 0 0 auto;
         border-bottom: 2px solid transparent;
         border-bottom: 2px solid transparent;
         transition: 0.25s all;
         transition: 0.25s all;
         font-size: 12px;
         font-size: 12px;
 
 
-        .button-states();
-
         text-transform: uppercase;
         text-transform: uppercase;
         font-weight: bold;
         font-weight: bold;
         color: #888;
         color: #888;
-        background: @title-bg;
+        border: none;
+        border-radius: 0;
+
+        &:not(:hover):not(:active) {
+            background: @title-bg;
+        }
     }
     }
 
 
     &.active-tab-0 .btn-new-tab {
     &.active-tab-0 .btn-new-tab {
@@ -98,6 +107,7 @@
 
 
         display: flex;
         display: flex;
         overflow: hidden;
         overflow: hidden;
+
         min-width: 0;
         min-width: 0;
         background: @body-bg;
         background: @body-bg;
         transition: 0.25s all;
         transition: 0.25s all;
@@ -131,11 +141,9 @@
             button {
             button {
                 flex: none;
                 flex: none;
 
 
-                border: none;
                 background: transparent;
                 background: transparent;
                 color: @text-color;
                 color: @text-color;
 
 
-                transition: 0.25s all;
                 display: block;
                 display: block;
                 opacity: 0;
                 opacity: 0;
 
 
@@ -150,13 +158,7 @@
                 text-align: center;
                 text-align: center;
                 font-size: 20px;
                 font-size: 20px;
 
 
-                &:hover {
-                    background: rgba(255, 255, 255, .05);
-                }
-
-                &:active {
-                    background: rgba(0, 0, 0, .1);
-                }
+                .button-states();
             }
             }
 
 
             &:hover button {
             &:hover button {
@@ -204,6 +206,11 @@
         position: relative;
         position: relative;
         padding: 15px;
         padding: 15px;
 
 
+        overflow: hidden;
+        &.scrollable {
+            overflow-y: auto;
+        }
+
         &.active {
         &.active {
             display: flex;
             display: flex;
 
 

+ 11 - 7
app/src/components/app.pug

@@ -1,14 +1,14 @@
-.titlebar(*ngIf='!config.store.appearance.useNativeFrame')
+.titlebar(*ngIf='!config.store.appearance.useNativeFrame && config.store.appearance.dock == "off"')
     .title((dblclick)='hostApp.maximizeWindow()') Term
     .title((dblclick)='hostApp.maximizeWindow()') Term
-    button.btn-minimize((click)='hostApp.minimizeWindow()')
+    button.btn.btn-secondary.btn-minimize((click)='hostApp.minimizeWindow()')
         i.fa.fa-window-minimize
         i.fa.fa-window-minimize
-    button.btn-maximize((click)='hostApp.maximizeWindow()')
+    button.btn.btn-secondary.btn-maximize((click)='hostApp.maximizeWindow()')
         i.fa.fa-window-maximize
         i.fa.fa-window-maximize
-    button.btn-close((click)='hostApp.quit()')
+    button.btn.btn-secondary.btn-close((click)='hostApp.quit()')
         i.fa.fa-close
         i.fa.fa-close
 
 
 .tabs(class='active-tab-{{tabs.indexOf(activeTab)}}')
 .tabs(class='active-tab-{{tabs.indexOf(activeTab)}}')
-    button.btn-new-tab((click)='newTab()')
+    button.btn.btn-secondary.btn-new-tab((click)='newTab()')
         i.fa.fa-plus
         i.fa.fa-plus
     .tab(
     .tab(
         *ngFor='let tab of tabs; let idx = index; trackBy: tab?.id',
         *ngFor='let tab of tabs; let idx = index; trackBy: tab?.id',
@@ -22,11 +22,15 @@
             div.index {{idx + 1}}
             div.index {{idx + 1}}
             div.name {{tab.name || 'Terminal'}}
             div.name {{tab.name || 'Terminal'}}
             button((click)='closeTab(tab)') ×
             button((click)='closeTab(tab)') ×
-    button.btn-settings((click)='showSettings()')
+    button.btn.btn-secondary.btn-settings((click)='showSettings()')
         i.fa.fa-cog
         i.fa.fa-cog
 
 
 .tabs-content
 .tabs-content
-    .tab(*ngFor='let tab of tabs; trackBy: tab?.id', [class.active]='tab == activeTab')
+    .tab(
+        *ngFor='let tab of tabs; trackBy: tab?.id', 
+        [class.active]='tab == activeTab',
+        [class.scrollable]='tab.scrollable',
+    )
         terminal(*ngIf='tab.type == "terminal"', [session]='tab.session', '[(title)]'='tab.name')
         terminal(*ngIf='tab.type == "terminal"', [session]='tab.session', '[(title)]'='tab.name')
         settings-pane(*ngIf='tab.type == "settings"')
         settings-pane(*ngIf='tab.type == "settings"')
 
 

+ 12 - 4
app/src/components/app.ts

@@ -7,10 +7,12 @@ import { HotkeysService } from 'services/hotkeys'
 import { LogService } from 'services/log'
 import { LogService } from 'services/log'
 import { QuitterService } from 'services/quitter'
 import { QuitterService } from 'services/quitter'
 import { ConfigService } from 'services/config'
 import { ConfigService } from 'services/config'
+import { DockingService } from 'services/docking'
 import { Session, SessionsService } from 'services/sessions'
 import { Session, SessionsService } from 'services/sessions'
 
 
 import 'angular2-toaster/lib/toaster.css'
 import 'angular2-toaster/lib/toaster.css'
 import 'global.less'
 import 'global.less'
+import 'theme.scss'
 
 
 
 
 const TYPE_TERMINAL = 'terminal'
 const TYPE_TERMINAL = 'terminal'
@@ -19,6 +21,7 @@ const TYPE_SETTINGS = 'settings'
 class Tab {
 class Tab {
     id: number
     id: number
     name: string
     name: string
+    scrollable: boolean
     static lastTabID = 0
     static lastTabID = 0
 
 
     constructor (public type: string, public session: Session) {
     constructor (public type: string, public session: Session) {
@@ -62,6 +65,7 @@ export class AppComponent {
     constructor(
     constructor(
         private elementRef: ElementRef,
         private elementRef: ElementRef,
         private sessions: SessionsService,
         private sessions: SessionsService,
+        private docking: DockingService,
         public hostApp: HostAppService,
         public hostApp: HostAppService,
         public hotkeys: HotkeysService,
         public hotkeys: HotkeysService,
         public config: ConfigService,
         public config: ConfigService,
@@ -126,6 +130,11 @@ export class AppComponent {
         this.hotkeys.globalHotkey.subscribe(() => {
         this.hotkeys.globalHotkey.subscribe(() => {
             this.hostApp.toggleWindow()
             this.hostApp.toggleWindow()
         })
         })
+
+        this.docking.dock()
+        this.hostApp.shown.subscribe(() => {
+            this.docking.dock()
+        })
     }
     }
 
 
     newTab () {
     newTab () {
@@ -192,18 +201,17 @@ export class AppComponent {
                     this.addTerminalTab(session)
                     this.addTerminalTab(session)
                 })
                 })
             } else {
             } else {
-                this.newTab()
+                // this.newTab()
+                this.showSettings();
             }
             }
         })
         })
     }
     }
 
 
-    ngOnDestroy () {
-    }
-
     showSettings() {
     showSettings() {
         let settingsTab = this.tabs.find((x) => x.type == TYPE_SETTINGS)
         let settingsTab = this.tabs.find((x) => x.type == TYPE_SETTINGS)
         if (!settingsTab) {
         if (!settingsTab) {
             settingsTab = new Tab(TYPE_SETTINGS, null)
             settingsTab = new Tab(TYPE_SETTINGS, null)
+            settingsTab.scrollable = true
             this.tabs.push(settingsTab)
             this.tabs.push(settingsTab)
         }
         }
         this.selectTab(settingsTab)
         this.selectTab(settingsTab)

+ 1 - 1
app/src/components/hotkeyHint.ts

@@ -46,7 +46,7 @@ export class HotkeyHintComponent {
                 this.setMatches(partialMatches)
                 this.setMatches(partialMatches)
 
 
                 if (this.keyTimeoutInterval == null) {
                 if (this.keyTimeoutInterval == null) {
-                    this.keyTimeoutInterval = setInterval(() => {
+                    this.keyTimeoutInterval = window.setInterval(() => {
                         if (this.hotkeys.getCurrentPartiallyMatchedHotkeys().length == 0) {
                         if (this.hotkeys.getCurrentPartiallyMatchedHotkeys().length == 0) {
                             clearInterval(this.keyTimeoutInterval)
                             clearInterval(this.keyTimeoutInterval)
                             this.keyTimeoutInterval = null
                             this.keyTimeoutInterval = null

+ 1 - 1
app/src/components/hotkeyInputModal.ts

@@ -35,7 +35,7 @@ export class HotkeyInputModalComponent {
     }
     }
 
 
     ngOnInit () {
     ngOnInit () {
-        this.keyTimeoutInterval = setInterval(() => {
+        this.keyTimeoutInterval = window.setInterval(() => {
             if (!this.lastKeyEvent) {
             if (!this.lastKeyEvent) {
                 return
                 return
             }
             }

+ 14 - 2
app/src/components/settingsPane.less

@@ -1,7 +1,19 @@
 :host {
 :host {
-    overflow-y: auto;
-    
+    >.btn-block {
+        margin-bottom: 20px;
+    }
+
     >.modal-body {
     >.modal-body {
         padding: 0 0 20px !important;
         padding: 0 0 20px !important;
     }
     }
+
+    .appearance-preview {
+        background: black;
+        padding: 10px 20px;
+        margin: 0 0 10px;
+
+        .text {
+            color: white;
+        }
+    }
 }
 }

+ 152 - 23
app/src/components/settingsPane.pug

@@ -1,36 +1,165 @@
-.restart-bar(*ngIf='restartRequested')
-    button.btn.btn-default.pull-right('(click)'='restartApp()') Restart
-    | Restart the app to apply changes
+button.btn.btn-outline-warning.btn-block(*ngIf='restartRequested', '(click)'='restartApp()') Restart the app to apply changes
 
 
 ngb-tabset(type='tabs')
 ngb-tabset(type='tabs')
     ngb-tab
     ngb-tab
         template(ngbTabTitle)
         template(ngbTabTitle)
-            | General
+            | Application
         template(ngbTabContent)
         template(ngbTabContent)
             .form-group
             .form-group
-                label.form-control
-                    input(
-                        type='checkbox',
-                        '[(ngModel)]'='config.store.appearance.useNativeFrame',
-                        '(ngModelChange)'='config.save(); requestRestart()',
-                    )
-                    | Use native window frame
-                    
+                label Window frame
+                br
+                div(
+                    '[(ngModel)]'='config.store.appearance.useNativeFrame'
+                    '(ngModelChange)'='config.save(); requestRestart()'
+                    ngbRadioGroup
+                )
+                    label.btn.btn-secondary
+                        input(
+                            type='radio',
+                            [value]='true'
+                        )
+                        | Native
+                    label.btn.btn-secondary
+                        input(
+                            type='radio',
+                            [value]='false'
+                        )
+                        | Custom
+                small.form-text.text-muted Whether a custom window or an OS native window should be used
+
             .form-group
             .form-group
-                label.control-label Font
-                input.form-control(
-                    type='text',
-                    [ngbTypeahead]='fontAutocomplete',
-                    '[(ngModel)]'='config.store.appearance.font',
-                    '(ngModelChange)'='config.save()',
+                label Dock the terminal
+                br
+                .row
+                    .col-auto
+                        div(
+                            '[(ngModel)]'='config.store.appearance.dock'
+                            '(ngModelChange)'='config.save(); docking.dock()'
+                            ngbRadioGroup
+                        )
+                            label.btn.btn-secondary
+                                input(
+                                    type='radio',
+                                    [value]='"off"'
+                                )
+                                | Off
+                            label.btn.btn-secondary
+                                input(
+                                    type='radio',
+                                    [value]='"top"'
+                                )
+                                | Top
+                            label.btn.btn-secondary
+                                input(
+                                    type='radio',
+                                    [value]='"left"'
+                                )
+                                | Left
+                            label.btn.btn-secondary
+                                input(
+                                    type='radio',
+                                    [value]='"right"'
+                                )
+                                | Right
+                            label.btn.btn-secondary
+                                input(
+                                    type='radio',
+                                    [value]='"bottom"'
+                                )
+                                | Bottom
+                    .col
+                        input(
+                            type='range',
+                            '[(ngModel)]'='config.store.appearance.dockFill',
+                            '(ngModelChange)'='config.save(); docking.dock()',
+                            min='1',
+                            max='100',
+                            step='1'
+                        )
+                br
+                div(
+                    *ngIf='config.store.appearance.dock != "off"',
+                    '[(ngModel)]'='config.store.appearance.dockScreen'
+                    '(ngModelChange)'='config.save(); docking.dock()'
+                    ngbRadioGroup
                 )
                 )
+                    label.btn.btn-secondary
+                        input(
+                            type='radio',
+                            [value]='"current"'
+                        )
+                        | Current
+                    label.btn.btn-secondary(*ngFor='let screen of docking.getScreens()')
+                        input(
+                            type='radio',
+                            [value]='screen.id'
+                        )
+                        | {{screen.name}}
+
+    ngb-tab
+        template(ngbTabTitle)
+            | Appearance
+        template(ngbTabContent)
+            .row
+                .col-sm-6
+                    .form-group
+                        label Preview
+                        .appearance-preview(
+                            [style.font-family]='config.store.appearance.font',
+                            [style.font-size]='config.store.appearance.fontSize + "px"',
+                        )
+                            .text john@doe-pc$ ls
+                            .text foo bar
+                .col-sm-6
+                    .form-group
+                        label Font
+                        input.form-control(
+                            type='text',
+                            [ngbTypeahead]='fontAutocomplete',
+                            '[(ngModel)]'='config.store.appearance.font',
+                            '(ngModelChange)'='config.save()',
+                        )
+                        small.form-text.text-muted Font to be used in the terminal
+
+                    .form-group
+                        label Font size
+                        input.form-control(
+                            type='number',
+                            '[(ngModel)]'='config.store.appearance.fontSize',
+                            '(ngModelChange)'='config.save()',
+                        )
+                        small.form-text.text-muted Text size to be used in the terminal
+
+    ngb-tab
+        template(ngbTabTitle)
+            | Terminal
+        template(ngbTabContent)
             .form-group
             .form-group
-                label.control-label Font size
-                input.form-control(
-                    type='number',
-                    '[(ngModel)]'='config.store.appearance.fontSize',
-                    '(ngModelChange)'='config.save()',
+                label Terminal bell
+                br
+                div(
+                    '[(ngModel)]'='config.store.terminal.bell'
+                    '(ngModelChange)'='config.save()'
+                    ngbRadioGroup
                 )
                 )
+                    label.btn.btn-secondary
+                        input(
+                            type='radio',
+                            [value]='"off"'
+                        )
+                        | Off
+                    label.btn.btn-secondary
+                        input(
+                            type='radio',
+                            [value]='"sound"'
+                        )
+                        | Sound
+                    label.btn.btn-secondary
+                        input(
+                            type='radio',
+                            [value]='"notification"'
+                        )
+                        | Notification
 
 
     ngb-tab
     ngb-tab
         template(ngbTabTitle)
         template(ngbTabTitle)

+ 2 - 0
app/src/components/settingsPane.ts

@@ -2,6 +2,7 @@ import { Component } from '@angular/core'
 import { ElectronService } from 'services/electron'
 import { ElectronService } from 'services/electron'
 import { HostAppService, PLATFORM_WINDOWS, PLATFORM_LINUX, PLATFORM_MAC } from 'services/hostApp'
 import { HostAppService, PLATFORM_WINDOWS, PLATFORM_LINUX, PLATFORM_MAC } from 'services/hostApp'
 import { ConfigService } from 'services/config'
 import { ConfigService } from 'services/config'
+import { DockingService } from 'services/docking'
 import { Observable } from 'rxjs/Observable'
 import { Observable } from 'rxjs/Observable'
 import 'rxjs/add/operator/map'
 import 'rxjs/add/operator/map'
 import 'rxjs/add/operator/debounceTime'
 import 'rxjs/add/operator/debounceTime'
@@ -27,6 +28,7 @@ export class SettingsPaneComponent {
     constructor(
     constructor(
         public config: ConfigService,
         public config: ConfigService,
         private electron: ElectronService,
         private electron: ElectronService,
+        public docking: DockingService,
         hostApp: HostAppService,
         hostApp: HostAppService,
     ) {
     ) {
         this.isWindows = hostApp.platform == PLATFORM_WINDOWS
         this.isWindows = hostApp.platform == PLATFORM_WINDOWS

+ 5 - 2
app/src/components/terminal.ts

@@ -110,8 +110,11 @@ export class TerminalComponent {
     }
     }
 
 
     configure () {
     configure () {
-        preferenceManager.set('font-family', this.config.full().appearance.font)
-        preferenceManager.set('font-size', this.config.full().appearance.fontSize)
+        let config = this.config.full()
+        preferenceManager.set('font-family', config.appearance.font)
+        preferenceManager.set('font-size', config.appearance.fontSize)
+        preferenceManager.set('audible-bell-sound', '')
+        preferenceManager.set('desktop-notification-bell', config.terminal.bell == 'notification')
     }
     }
 
 
     ngOnDestroy () {
     ngOnDestroy () {

+ 2 - 78
app/src/global.less

@@ -1,16 +1,6 @@
 @import "~variables.less";
 @import "~variables.less";
 @import "~mixins.less";
 @import "~mixins.less";
 
 
-* {
-    box-sizing: border-box;
-}
-
-body {
-    margin: 0;
-    font-family: @font-family;
-    font-size: @font-size;
-    color: @text-color;
-}
 
 
 html.platform-win32 {
 html.platform-win32 {
     body.focused {
     body.focused {
@@ -23,6 +13,7 @@ body {
     transition: 0.5s border;
     transition: 0.5s border;
     overflow: hidden;
     overflow: hidden;
     min-height: 100vh;
     min-height: 100vh;
+    cursor: default;
 }
 }
 
 
 .no-drag, a, button, checkbox, .form-control, #toast-container {
 .no-drag, a, button, checkbox, .form-control, #toast-container {
@@ -91,46 +82,6 @@ ngb-modal-window.fade.in {
     }
     }
 }
 }
 
 
-ngb-tabset {
-    >ul.nav-tabs {
-        border-bottom: none;
-        margin-bottom: 10px;
-        background: rgba(0,0,0,.25);
-        display: flex;
-        align-items: start;
-        padding: 0;
-        margin: 0;
-
-        .nav-item {
-            flex: none;
-            display: flex;
-
-            .nav-link {
-                background: transparent;
-                border: none;
-                padding: 10px 15px;
-                color: @text-color;
-                text-decoration: none;
-
-                &.active {
-                    background: rgba(0,0,0,.5);
-                    border-bottom: 1px solid #777;
-                }
-
-                i {
-                    display: block;
-                    text-align: center;
-                    font-size: 18px;
-                    margin: 0 0 5px;
-                }
-            }
-        }
-    }
-
-    >.tab-content {
-        padding: 10px 0;
-    }
-}
 
 
 
 
 .btn {
 .btn {
@@ -163,33 +114,6 @@ ngb-typeahead-window {
         display: block;
         display: block;
         width: 100%;
         width: 100%;
         -webkit-appearance: none;
         -webkit-appearance: none;
-        border-bottom: 1px solid @dark-border;
-        .list-group-item-style();
+        //border-bottom: 1px solid @dark-border;
     }
     }
 }
 }
-
-.list-group-item {
-    .list-group-item-style();
-}
-
-label.control-label {
-    background: rgba(0, 0, 0, .4);
-    color: #aaa;
-    font-size: 10px;
-    display: block;
-    margin: 0;
-    padding: 5px 10px 0;
-}
-
-.form-control {
-    -webkit-user-select: initial;
-    background: rgba(0, 0, 0, .4);
-    display: block;
-    margin: 0 0 5px;
-    width: 100%;
-    border: none;
-    height: 30px;
-    line-height: 30px;
-    color: #ccc;
-    padding: 0 10px;
-}

+ 18 - 2
app/src/plugin.hyperlinks.ts

@@ -1,6 +1,7 @@
 import * as fs from 'fs'
 import * as fs from 'fs'
 import { ElectronService } from 'services/electron'
 import { ElectronService } from 'services/electron'
 
 
+const debounceDelay = 500
 
 
 abstract class Handler {
 abstract class Handler {
     constructor (protected plugin) { }
     constructor (protected plugin) { }
@@ -48,16 +49,31 @@ export default class HyperlinksPlugin {
         const oldDeleteChars = terminal.screen_.constructor.prototype.deleteChars
         const oldDeleteChars = terminal.screen_.constructor.prototype.deleteChars
         terminal.screen_.insertString = (...args) => {
         terminal.screen_.insertString = (...args) => {
             let ret = oldInsertString.bind(terminal.screen_)(...args)
             let ret = oldInsertString.bind(terminal.screen_)(...args)
-            this.insertLinks(terminal.screen_)
+            this.debounceInsertLinks(terminal.screen_)
             return ret
             return ret
         }
         }
         terminal.screen_.deleteChars = (...args) => {
         terminal.screen_.deleteChars = (...args) => {
             let ret = oldDeleteChars.bind(terminal.screen_)(...args)
             let ret = oldDeleteChars.bind(terminal.screen_)(...args)
-            this.insertLinks(terminal.screen_)
+            this.debounceInsertLinks(terminal.screen_)
             return ret
             return ret
         }
         }
     }
     }
 
 
+    debounceInsertLinks (screen) {
+        if (screen.__insertLinksTimeout) {
+            screen.__insertLinksRebounce = true
+        } else {
+            screen.__insertLinksTimeout = window.setTimeout(() => {
+                this.insertLinks(screen)
+                screen.__insertLinksTimeout = null
+                if (screen.__insertLinksRebounce) {
+                    screen.__insertLinksRebounce = false
+                    this.debounceInsertLinks(screen)
+                }
+            }, debounceDelay)
+        }
+    }
+
     insertLinks (screen) {
     insertLinks (screen) {
         const traverse = (parentNode: Node) => {
         const traverse = (parentNode: Node) => {
             Array.from(parentNode.childNodes).forEach((node) => {
             Array.from(parentNode.childNodes).forEach((node) => {

+ 8 - 0
app/src/services/config.ts

@@ -9,13 +9,21 @@ const defaultConfigValues : IConfigData = require('../../defaultConfigValues.yam
 const defaultConfigStructure : IConfigData = require('../../defaultConfigStructure.yaml')
 const defaultConfigStructure : IConfigData = require('../../defaultConfigStructure.yaml')
 
 
 export interface IAppearanceData {
 export interface IAppearanceData {
+    useNativeFrame: boolean
     font: string
     font: string
     fontSize: number
     fontSize: number
+    dock: string
+    dockScreen: string
+}
+
+export interface ITerminalData {
+    bell: string|boolean
 }
 }
 
 
 export interface IConfigData {
 export interface IConfigData {
     appearance?: IAppearanceData
     appearance?: IAppearanceData
     hotkeys?: any
     hotkeys?: any
+    terminal?: ITerminalData
 }
 }
 
 
 @Injectable()
 @Injectable()

+ 71 - 0
app/src/services/docking.ts

@@ -0,0 +1,71 @@
+import { Injectable } from '@angular/core'
+import { HostAppService } from 'services/hostApp'
+import { ConfigService } from 'services/config'
+import { ElectronService } from 'services/electron'
+
+
+export interface IScreen {
+    id: string
+    name: string
+}
+
+@Injectable()
+export class DockingService {
+    constructor(
+        private electron: ElectronService,
+        private config: ConfigService,
+        private hostApp: HostAppService,
+    ) {}
+
+    dock () {
+        let display = this.electron.screen.getAllDisplays()
+            .filter((x) => x.id == this.config.full().appearance.dockScreen)[0]
+        if (!display) {
+            display = this.getCurrentScreen()
+        }
+
+        let dockSide = this.config.full().appearance.dock
+        let newBounds: Electron.Rectangle = { x: 0, y: 0, width: 0, height: 0 }
+        let fill = 0.5
+
+        if (dockSide == 'off') {
+            return
+        }
+        if (dockSide == 'left' || dockSide == 'right') {
+            newBounds.width = fill * display.bounds.width
+            newBounds.height = display.bounds.height
+        }
+        if (dockSide == 'top' || dockSide == 'bottom') {
+            newBounds.width = display.bounds.width
+            newBounds.height = fill * display.bounds.height
+        }
+        if (dockSide == 'right') {
+            newBounds.x = display.bounds.x + display.bounds.width * (1.0 - fill)
+        } else {
+            newBounds.x = display.bounds.x
+        }
+        if (dockSide == 'bottom') {
+            newBounds.y = display.bounds.y + display.bounds.height * (1.0 - fill)
+        } else {
+            newBounds.y = display.bounds.y
+        }
+
+        this.hostApp.setBounds(newBounds)
+    }
+
+    getCurrentScreen () {
+        return this.electron.screen.getDisplayNearestPoint(this.electron.screen.getCursorScreenPoint())
+    }
+
+    getScreens () {
+        return this.electron.screen.getAllDisplays().map((display, index) => {
+            return {
+                id: display.id,
+                name: {
+                    0: 'Primary display',
+                    1: 'Secondary display',
+                }[index] || `Display ${index + 1}`
+            }
+        })
+    }
+}

+ 2 - 0
app/src/services/electron.ts

@@ -15,6 +15,7 @@ export class ElectronService {
         this.electron = require('electron')
         this.electron = require('electron')
         this.remoteElectron = this.remoteRequire('electron')
         this.remoteElectron = this.remoteRequire('electron')
         this.app = this.remoteElectron.app
         this.app = this.remoteElectron.app
+        this.screen = this.remoteElectron.screen
         this.dialog = this.remoteElectron.dialog
         this.dialog = this.remoteElectron.dialog
         this.shell = this.electron.shell
         this.shell = this.electron.shell
         this.clipboard = this.electron.clipboard
         this.clipboard = this.electron.clipboard
@@ -36,6 +37,7 @@ export class ElectronService {
     dialog: any
     dialog: any
     clipboard: any
     clipboard: any
     globalShortcut: any
     globalShortcut: any
+    screen: any
     private electron: any
     private electron: any
     private remoteElectron: any
     private remoteElectron: any
 }
 }

+ 9 - 0
app/src/services/hostApp.ts

@@ -23,6 +23,10 @@ export class HostAppService {
             console.error('Unhandled exception:', err)
             console.error('Unhandled exception:', err)
         })
         })
 
 
+        electron.ipcRenderer.on('window-shown', () => {
+            this.shown.emit()
+        })
+
         this.ready.subscribe(() => {
         this.ready.subscribe(() => {
             electron.ipcRenderer.send('app:ready')
             electron.ipcRenderer.send('app:ready')
         })
         })
@@ -31,6 +35,7 @@ export class HostAppService {
     platform: string;
     platform: string;
     quitRequested = new EventEmitter<any>()
     quitRequested = new EventEmitter<any>()
     ready = new EventEmitter<any>()
     ready = new EventEmitter<any>()
+    shown = new EventEmitter<any>()
 
 
     private logger: Logger;
     private logger: Logger;
 
 
@@ -74,6 +79,10 @@ export class HostAppService {
         this.electron.ipcRenderer.send('window-maximize')
         this.electron.ipcRenderer.send('window-maximize')
     }
     }
 
 
+    setBounds (bounds: Electron.Rectangle) {
+        this.electron.ipcRenderer.send('window-set-bounds', bounds)
+    }
+
     quit () {
     quit () {
         this.logger.info('Quitting')
         this.logger.info('Quitting')
         this.electron.app.quit()
         this.electron.app.quit()

+ 65 - 0
app/src/theme.scss

@@ -0,0 +1,65 @@
+$white:  #fff !default;
+$black:  #000 !default;
+$red:    #d9534f !default;
+$orange: #f0ad4e !default;
+$yellow: #ffd500 !default;
+$green:  #5cb85c !default;
+$blue:   #0275d8 !default;
+$teal:   #5bc0de !default;
+$pink:   #ff5b77 !default;
+$purple: #613d7c !default;
+
+
+$body-bg: #1D272D;
+$body-bg2: #131d27;
+$body-color: #aaa;
+$font-family-sans-serif: "Source Sans Pro";
+$font-size-base: 14rem / 16;
+
+$btn-secondary-color:            #ccc;
+$btn-secondary-bg:               #222;
+$btn-secondary-border:           #444;
+
+//$btn-warning-bg:                 rgba($orange, .5);
+
+
+$nav-tabs-border-color:                       $body-bg2;
+$nav-tabs-border-width:                       1px;
+$nav-tabs-border-radius:                      0;
+$nav-tabs-link-hover-border-color:            $body-bg2;
+$nav-tabs-active-link-hover-color:            $body-color;
+$nav-tabs-active-link-hover-bg:               #424f56;
+$nav-tabs-active-link-hover-border-color:     $body-bg2;
+
+$input-bg:                       #111;
+$input-bg-disabled:              #333;
+
+$input-color:                    $body-color;
+//$input-border-color:             rgba($black,.15);
+//$input-box-shadow:               inset 0 1px 1px rgba($black,.075);
+
+$input-border-radius:            0;
+
+$input-bg-focus:                 $input-bg;
+//$input-border-focus:             lighten($brand-primary, 25%);
+//$input-box-shadow-focus:         $input-box-shadow, rgba($input-border-focus, .6);
+$input-color-focus:              $input-color;
+
+
+@import '~bootstrap/scss/bootstrap.scss';
+
+.nav-tabs {
+    background: $btn-secondary-bg;
+    .nav-link {
+        transition: 0.25s all;
+        border-bottom-color: $nav-tabs-border-color;
+    }
+}
+
+ngb-tabset .tab-content {
+    padding-top: 20px;
+}
+
+[ngbradiogroup] > label.active {
+    background: $blue;
+}

+ 4 - 0
package.json

@@ -2,7 +2,9 @@
   "name": "term",
   "name": "term",
   "devDependencies": {
   "devDependencies": {
     "apply-loader": "^0.1.0",
     "apply-loader": "^0.1.0",
+    "autoprefixer": "^6.7.7",
     "awesome-typescript-loader": "3.0.8",
     "awesome-typescript-loader": "3.0.8",
+    "bootstrap": "^4.0.0-alpha.6",
     "css-loader": "0.26.1",
     "css-loader": "0.26.1",
     "dataurl": "^0.1.0",
     "dataurl": "^0.1.0",
     "electron": "^1.4.13",
     "electron": "^1.4.13",
@@ -16,10 +18,12 @@
     "less": "^2.7.1",
     "less": "^2.7.1",
     "less-loader": "^2.2.3",
     "less-loader": "^2.2.3",
     "node-gyp": "^3.4.0",
     "node-gyp": "^3.4.0",
+    "node-sass": "^4.5.0",
     "pug-html-loader": "^1.0.9",
     "pug-html-loader": "^1.0.9",
     "pug-loader": "^2.3.0",
     "pug-loader": "^2.3.0",
     "pug-static-loader": "0.0.1",
     "pug-static-loader": "0.0.1",
     "raw-loader": "^0.5.1",
     "raw-loader": "^0.5.1",
+    "sass-loader": "^6.0.3",
     "style-loader": "^0.13.1",
     "style-loader": "^0.13.1",
     "to-string-loader": "^1.1.5",
     "to-string-loader": "^1.1.5",
     "tslint": "4.2.0",
     "tslint": "4.2.0",

+ 4 - 0
webpack.config.js

@@ -50,6 +50,10 @@ module.exports = {
               loader: "to-string-loader!css-loader!less-loader",
               loader: "to-string-loader!css-loader!less-loader",
               include: [/app\/src\/components\//],
               include: [/app\/src\/components\//],
             },
             },
+            {
+              test: /\.scss$/,
+              use: ['style-loader', 'css-loader', 'sass-loader']
+            },
             {
             {
               test: /\.(png|svg)$/,
               test: /\.(png|svg)$/,
               loader: "file-loader",
               loader: "file-loader",