This commit is contained in:
Eugene Pankov 2017-03-18 21:47:52 +01:00
parent e6221ee482
commit 2c2da1d697
23 changed files with 455 additions and 169 deletions

View file

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

View file

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

View file

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

View file

@ -13,6 +13,10 @@ let windowConfig = new Config({name: 'window'})
setupWindowManagement = () => {
let windowCloseable
app.window.on('show', () => {
electron.ipcMain.send('window-shown')
})
app.window.on('close', (e) => {
windowConfig.set('windowBoundaries', app.window.getBounds())
if (!windowCloseable) {
@ -33,10 +37,6 @@ setupWindowManagement = () => {
app.window.focus()
})
electron.ipcMain.on('window-focus', () => {
app.window.focus()
})
electron.ipcMain.on('window-toggle-focus', () => {
if (app.window.isFocused()) {
app.window.minimize()
@ -57,6 +57,10 @@ setupWindowManagement = () => {
app.window.minimize()
})
electron.ipcMain.on('window-set-bounds', (event, bounds) => {
app.window.setBounds(bounds, true)
})
app.on('before-quit', () => windowCloseable = true)
}
@ -69,18 +73,20 @@ setupMenu = () => {
{ label: "Quit", accelerator: "CmdOrCtrl+Q", click: () => {
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))
}
@ -141,7 +147,12 @@ start = () => {
app.window.focus()
setupWindowManagement()
setupMenu()
if (platform == 'darwin') {
setupMenu()
} else {
app.window.setMenu(null)
}
console.info(`Host startup: ${Date.now() - t0}ms`)
t0 = Date.now()

View file

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

View file

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

View file

@ -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
button.btn-minimize((click)='hostApp.minimizeWindow()')
button.btn.btn-secondary.btn-minimize((click)='hostApp.minimizeWindow()')
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
button.btn-close((click)='hostApp.quit()')
button.btn.btn-secondary.btn-close((click)='hostApp.quit()')
i.fa.fa-close
.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
.tab(
*ngFor='let tab of tabs; let idx = index; trackBy: tab?.id',
@ -22,11 +22,15 @@
div.index {{idx + 1}}
div.name {{tab.name || 'Terminal'}}
button((click)='closeTab(tab)') ×
button.btn-settings((click)='showSettings()')
button.btn.btn-secondary.btn-settings((click)='showSettings()')
i.fa.fa-cog
.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')
settings-pane(*ngIf='tab.type == "settings"')

View file

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

View file

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

View file

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

View file

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

View file

@ -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-tab
template(ngbTabTitle)
| General
| Application
template(ngbTabContent)
.form-group
label.form-control
input(
type='checkbox',
'[(ngModel)]'='config.store.appearance.useNativeFrame',
'(ngModelChange)'='config.save(); requestRestart()',
)
| Use native window frame
.form-group
label.control-label Font
input.form-control(
type='text',
[ngbTypeahead]='fontAutocomplete',
'[(ngModel)]'='config.store.appearance.font',
'(ngModelChange)'='config.save()',
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
label.control-label Font size
input.form-control(
type='number',
'[(ngModel)]'='config.store.appearance.fontSize',
'(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
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
template(ngbTabTitle)

View file

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

View file

@ -110,8 +110,11 @@ export class TerminalComponent {
}
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 () {

View file

@ -1,16 +1,6 @@
@import "~variables.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 {
body.focused {
@ -23,6 +13,7 @@ body {
transition: 0.5s border;
overflow: hidden;
min-height: 100vh;
cursor: default;
}
.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 {
@ -163,33 +114,6 @@ ngb-typeahead-window {
display: block;
width: 100%;
-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;
}

View file

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

View file

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

View file

@ -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}`
}
})
}
}

View file

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

View file

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

65
app/src/theme.scss Normal file
View file

@ -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;
}

View file

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

View file

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