This commit is contained in:
Eugene Pankov
2017-03-05 17:20:47 +01:00
parent b7745bdd5b
commit 8beda026c1
27 changed files with 587 additions and 245 deletions

View File

@@ -1,141 +0,0 @@
@brand-primary: #f7e61d;
@brand-success: #5cb85c;
@brand-info: #5bc0de;
@brand-warning: #f0ad4e;
@brand-danger: #FF1C01;
// New
@brand-primary: #f7e61d;
@brand-success: #42B500;
@brand-info: #01BAEF;
@brand-warning: #DB8A00;
@brand-danger: #EF2F00;
@control-shadow: 0 1px 1px rgba(0,0,0,.25);
@control-shadow-active: 0 1px 1px rgba(0,0,0,.25) inset, @control-shadow;
@control-dropdown-shadow: 0 0 50px rgba(0,0,0,.5), @control-shadow;
@form-accent: #DBCA00;
@form-accent-bright: @brand-primary;
@body-bg: #1D272D;
@text-color: #aaa;
@font-family-sans-serif: "Source Sans Pro", "PT Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
@icon-font-path: "../fonts/";
@component-active-color: rgba(0,0,0,.15);
@component-active-color: darken(@component-active-bg, 30%);
@table-bg: #444;
@table-bg-accent: rgba(255,255,255,.15);
@table-bg-hover: #666;
@table-border-color: #2e2e2e;
@table-line-border-color: #4f4f4f;
@btn-default-color: @text-color;
@btn-default-bg: #243D49;
@btn-default-border: transparent;
@btn-primary-color: @component-active-color;
@btn-primary-border: #584E00;
@btn-danger-border: rgba(0,0,0,.5);
@btn-danger-color: white;
@btn-danger-bg: #FF4630;
@btn-danger-border: transparent;//@brand-danger;
@btn-link-disabled-color: darken(@text-color, 20%);
@input-bg: #11181C;
@input-bg-disabled: #2a2f31;
@input-color: #bbb;
@input-border: #3a3a3a;
@input-group-addon-border-color: @input-bg;
@input-color-placeholder: #777;
@input-group-addon-bg: @input-bg;
@dropdown-bg: rgba(64,64,64,.95); //@body-bg;
@dropdown-link-color: @text-color;
@dropdown-link-hover-color: #ddd;
@dropdown-link-hover-bg: #444;
@dropdown-link-disabled-color: darken(@text-color, 5%);
@navbar-default-bg: #23272A;
@navbar-default-border: #111;
@nav-tabs-border-color: #666;
@nav-tabs-link-hover-border-color: transparent;
@nav-tabs-active-link-hover-bg: rgba(255,255,255,.1);
@nav-tabs-active-link-hover-color: @brand-primary;
@nav-tabs-active-link-border-color: @brand-primary;
@nav-tabs-active-link-hover-border-color: @brand-primary;
@pagination-color: @btn-default-color;
@pagination-bg: @btn-default-bg;
@pagination-border: @btn-default-border;
@pagination-hover-color: @btn-default-color;
@pagination-hover-bg: lighten(@btn-default-bg, 5%);
@pagination-hover-border: @btn-default-border;
@pagination-active-color: @brand-primary;
@pagination-active-bg: darken(@btn-default-bg, 5%);
@pagination-active-border: @btn-default-border;
@pagination-disabled-color: @btn-link-disabled-color;
@pagination-disabled-bg: darken(@btn-default-bg, 5%);
@pagination-disabled-border: @btn-default-border;
//@state-success-bg: #234116; //#dff0d8;
//@state-info-bg: #0C3A50; //#d9edf7;
//@state-danger-bg: #9E3B3B;///#f2dede;
@popover-bg: rgba(64,64,64,.95);
@popover-arrow-color: #444;
@progress-bg: #555;
@list-group-bg: rgba(255,255,255,.1);
@list-group-disabled-bg: #333;
@list-group-border: transparent;
@list-group-line-border: rgba(255,255,255,.1);
@list-group-hover-bg: rgba(255,255,255,.2);
@list-group-link-color: @text-color;
@list-group-link-heading-color: @text-color;
@list-group-active-bg: rgba(255,255,255,.3);
@list-group-active-border: transparent;
@panel-bg: @table-bg;
@panel-inner-border: @table-border-color;
@panel-footer-bg: #666;
@panel-default-text: #ddd;
@panel-default-border: @table-border-color;
@panel-default-heading-bg: #666;
@well-bg: #222;
@badge-bg: #333;
@badge-active-bg: #333;
@code-bg: #222;
@pre-bg: #222;
@pre-color: #bbb;
@blockquote-border-color: #444;
@page-header-border-color: #444;
@alert-bg: #2A2A2A;
@state-success-text: @brand-success;
@state-success-bg: @alert-bg;
@state-success-border: @state-success-text;
@state-info-text: @brand-info;
@state-info-bg: @alert-bg;
@state-info-border: @state-info-text;
@state-warning-text: #F27208;
@state-warning-bg: @alert-bg;
@state-warning-border: @state-warning-text;
@state-danger-text: @brand-danger;
@state-danger-bg: @alert-bg;
@state-danger-border: @state-danger-text;
@border-radius-base: 1px;
@border-radius-large: 3px;
@border-radius-small: 1px;

47
app/defaultConfig.yaml Normal file
View File

@@ -0,0 +1,47 @@
hotkeys:
new-tab:
- ['Ctrl-A', 'C']
- ['Ctrl-A', 'Ctrl-C']
- 'Ctrl-Shift-T'
close-tab:
- 'Ctrl-Shift-W'
- ['Ctrl-A', 'K']
toggle-last-tab:
- ['Ctrl-A', 'A']
- ['Ctrl-A', 'Ctrl-A']
next-tab:
- 'Ctrl-Shift-ArrowRight'
- ['Ctrl-A', 'N']
previous-tab:
- 'Ctrl-Shift-ArrowLeft'
- ['Ctrl-A', 'P']
tab-1:
- 'Alt-1'
- ['Ctrl-A', '1']
tab-2:
- 'Alt-2'
- ['Ctrl-A', '2']
tab-3:
- 'Alt-3'
- ['Ctrl-A', '3']
tab-4:
- 'Alt-4'
- ['Ctrl-A', '4']
tab-5:
- 'Alt-5'
- ['Ctrl-A', '5']
tab-6:
- 'Alt-6'
- ['Ctrl-A', '6']
tab-7:
- 'Alt-7'
- ['Ctrl-A', '7']
tab-8:
- 'Alt-8'
- ['Ctrl-A', '8']
tab-9:
- 'Alt-9'
- ['Ctrl-A', '9']
tab-10:
- 'Alt-0'
- ['Ctrl-A', '0']

View File

@@ -10,5 +10,8 @@
"electron-is-dev": "^0.1.2",
"path": "^0.12.7",
"pty.js": "https://github.com/Tyriar/pty.js/tarball/c75c2dcb6dcad83b0cb3ef2ae42d0448fb912642"
},
"devDependencies": {
"js-yaml": "^3.8.2"
}
}

View File

@@ -12,6 +12,7 @@ import { LogService } from 'services/log'
import { HotkeysService } from 'services/hotkeys'
import { ModalService } from 'services/modal'
import { NotifyService } from 'services/notify'
import { PluginDispatcherService } from 'services/pluginDispatcher'
import { QuitterService } from 'services/quitter'
import { SessionsService } from 'services/sessions'
import { LocalStorageService } from 'angular2-localstorage/LocalStorageEmitter'
@@ -42,6 +43,7 @@ import { TerminalComponent } from 'components/terminal'
LogService,
ModalService,
NotifyService,
PluginDispatcherService,
QuitterService,
SessionsService,
LocalStorageService,
@@ -63,4 +65,8 @@ import { TerminalComponent } from 'components/terminal'
AppComponent
]
})
export class AppModule {}
export class AppModule {
constructor (pluginDispatcher: PluginDispatcherService) {
pluginDispatcher.register(require('./plugin.hyperlinks').default)
}
}

View File

@@ -1,5 +1,6 @@
@import "~bootstrap/less/variables.less";
@import "~bootstrap/variables.less";
@import "~variables.less";
@import "~mixins.less";
:host {
display: flex;
@@ -17,20 +18,6 @@
@tabs-height: 40px;
@tab-border-radius: 4px;
.button-states() {
transition: 0.125s all;
border: none;
background: transparent;
&:hover:not(.active) {
background: rgba(255, 255, 255, .033);
}
&:active:not(.active) {
background: rgba(0, 0, 0, .1);
}
}
.titlebar {
height: @titlebar-height;
background: #141c23;
@@ -50,8 +37,15 @@
line-height: @titlebar-height - 2px;
padding: 0 15px;
font-size: 8px;
color: #444;
background: transparent;
transition: 0.25s all;
.button-states();
&:hover {
color: white;
}
cursor: pointer;
}
@@ -138,8 +132,11 @@
border: none;
background: transparent;
opacity: 0;
color: @text-color;
transition: 0.25s all;
display: block;
opacity: 0;
@button-size: @tabs-height * 0.6;
width: @button-size;
@@ -149,7 +146,6 @@
margin-top: (@tabs-height - @button-size) * 0.4;
margin-right: 10px;
display: block;
text-align: center;
font-size: 20px;
@@ -163,6 +159,8 @@
}
&:hover button {
transition: 0.25s opacity;
display: block;
opacity: 1;
}
}

View File

@@ -81,6 +81,12 @@ export class AppComponent {
if (hotkey == 'new-tab') {
this.newTab()
}
if (hotkey.startsWith('tab-')) {
let index = parseInt(hotkey.split('-')[1])
if (index <= this.tabs.length) {
this.selectTab(this.tabs[index - 1])
}
}
if (this.activeTab) {
if (hotkey == 'close-tab') {
this.closeTab(this.activeTab)
@@ -137,7 +143,10 @@ export class AppComponent {
}
this.activeTab = tab
setImmediate(() => {
this.elementRef.nativeElement.querySelector(':scope .tab.active iframe').focus()
let iframe = this.elementRef.nativeElement.querySelector(':scope .tab.active iframe')
if (iframe) {
iframe.focus()
}
})
}

View File

@@ -1,4 +1,4 @@
import { Component, Input, ChangeDetectionStrategy, trigger, style, animate, transition, state } from '@angular/core'
import { Component, Input, trigger, style, animate, transition } from '@angular/core'
@Component({

View File

@@ -1,4 +1,4 @@
import { Component, ChangeDetectionStrategy, Input, trigger, style, animate, transition, state } from '@angular/core'
import { Component, Input, trigger, style, animate, transition, state } from '@angular/core'
import { HotkeysService, PartialHotkeyMatch } from 'services/hotkeys'
@@ -33,7 +33,7 @@ import { HotkeysService, PartialHotkeyMatch } from 'services/hotkeys'
})
export class HotkeyHintComponent {
@Input() partialHotkeyMatches: PartialHotkeyMatch[]
private keyTimeoutInterval: NodeJS.Timer = null
private keyTimeoutInterval: number = null
constructor (
public hotkeys: HotkeysService,

View File

@@ -14,7 +14,7 @@ const INPUT_TIMEOUT = 2000
export class HotkeyInputModalComponent {
private keySubscription: Subscription
private lastKeyEvent: number
private keyTimeoutInterval: NodeJS.Timer
private keyTimeoutInterval: number = null
@Input() value: string[] = []
@Input() timeoutProgress = 0

View File

@@ -1,4 +1,6 @@
:host {
overflow-y: auto;
>.modal-body {
padding: 0 0 20px !important;
}

View File

@@ -1,7 +1,13 @@
ngb-tabset(type='tabs nav-justified')
ngb-tabset(type='tabs')
ngb-tab
template(ngbTabTitle)
| General
template(ngbTabContent)
.form-group
label Font
input.form-control(type='text', [ngbTypeahead]='fontAutocomplete', '[(ngModel)]'='font')
ngb-tab
template(ngbTabTitle)
i.fa.fa-keyboard-o
| Hotkeys
template(ngbTabContent)
.form-group

View File

@@ -2,6 +2,11 @@ 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 { Observable } from 'rxjs/Observable'
import 'rxjs/add/operator/map'
import 'rxjs/add/operator/debounceTime'
import 'rxjs/add/operator/distinctUntilChanged'
const childProcessPromise = nodeRequire('child-process-promise')
@Component({
@@ -27,9 +32,29 @@ export class SettingsPaneComponent {
isLinux: boolean
year: number
version: string
fonts: string[] = []
globalHotkey = ['Ctrl+Shift+G']
ngOnInit () {
childProcessPromise.exec('fc-list :spacing=mono').then((result) => {
this.fonts = result.stdout
.split('\n')
.filter((x) => !!x)
.map((x) => x.split(':')[1].trim())
.map((x) => x.split(',')[0].trim())
this.fonts.sort()
})
}
fontAutocomplete = (text$: Observable<string>) => {
return text$
.debounceTime(200)
.distinctUntilChanged()
.map(query => this.fonts.filter(v => new RegExp(query, 'gi').test(v)))
.map(list => Array.from(new Set(list)))
}
ngOnDestroy() {
this.config.save()
}

View File

@@ -1,9 +1,11 @@
import { Component, NgZone, Input, Output, EventEmitter, ElementRef } from '@angular/core'
import { ConfigService } from 'services/config'
import { PluginDispatcherService } from 'services/pluginDispatcher'
import { Session } from 'services/sessions'
const hterm = require('hterm-commonjs')
const dataurl = require('dataurl')
hterm.hterm.VT.ESC['k'] = function(parseState) {
@@ -21,7 +23,19 @@ hterm.hterm.VT.ESC['k'] = function(parseState) {
hterm.hterm.defaultStorage = new hterm.lib.Storage.Memory()
const pmgr = new hterm.hterm.PreferenceManager('default')
pmgr.set('user-css', ``)
pmgr.set('user-css', dataurl.convert({
data: `
a {
cursor: pointer;
}
a:hover {
text-decoration: underline;
}
`,
mimetype: 'text/css',
charset: 'utf8',
}))
pmgr.set('font-size', 12)
pmgr.set('background-color', '#1D272D')
pmgr.set('color-palette-overrides', {
@@ -43,18 +57,20 @@ export class TerminalComponent {
@Input() session: Session
title: string
@Output() titleChange = new EventEmitter()
private terminal: any
terminal: any
constructor(
private zone: NgZone,
private elementRef: ElementRef,
public config: ConfigService,
private pluginDispatcher: PluginDispatcherService,
) {
}
ngOnInit () {
let io
this.terminal = new hterm.hterm.Terminal()
this.pluginDispatcher.emit('preTerminalInit', { terminal: this.terminal })
this.terminal.setWindowTitle = (title) => {
this.zone.run(() => {
this.title = title
@@ -83,6 +99,7 @@ export class TerminalComponent {
this.session.releaseInitialDataBuffer()
}
this.terminal.decorate(this.elementRef.nativeElement)
this.pluginDispatcher.emit('postTerminalInit', { terminal: this.terminal })
}
ngOnDestroy () {

View File

@@ -1,6 +1,3 @@
import 'source-sans-pro'
import 'font-awesome/css/font-awesome.css'
import '../assets/toaster-custom.less'
import '../assets/bootstrap/bootstrap.less'

View File

@@ -1,5 +1,16 @@
@import "~bootstrap/include.less";
@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 {
@@ -85,25 +96,37 @@ ngb-modal-window.fade.in {
}
ngb-tabset {
>ul.nav-tabs.nav-justified {
>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 .nav-link {
background: transparent;
border: none;
.nav-item {
flex: none;
display: flex;
&.active {
background: rgba(0,0,0,.5);
border-bottom: 1px solid #777;
}
.nav-link {
background: transparent;
border: none;
padding: 10px 15px;
color: @text-color;
text-decoration: none;
i {
display: block;
text-align: center;
font-size: 18px;
margin: 0 0 5px;
&.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;
}
}
}
}
@@ -134,3 +157,21 @@ ngb-tabset {
}
}
}
ngb-typeahead-window {
max-height: 200px;
overflow-y: auto;
>button {
display: block;
width: 100%;
-webkit-appearance: none;
border-bottom: 1px solid @dark-border;
.list-group-item-style();
}
}
.list-group-item {
.list-group-item-style();
}

24
app/src/mixins.less Normal file
View File

@@ -0,0 +1,24 @@
@import "~variables.less";
.button-states() {
transition: 0.125s all;
border: none;
&:hover:not(.active) {
background: rgba(255, 255, 255, .033);
}
&:active:not(.active),
&.active {
background: rgba(0, 0, 0, .1);
}
}
.list-group-item-style() {
display: block;
padding: 10px 15px;
background: @component-bg;
color: @text-color;
text-align: left;
.button-states();
}

View File

@@ -0,0 +1,123 @@
import * as fs from 'fs'
import { ElectronService } from 'services/electron'
abstract class Handler {
constructor (protected plugin) { }
regex: string
convert (uri: string): string { return uri }
verify (_uri: string): boolean { return true }
abstract handle (uri: string): void
}
class URLHandler extends Handler {
regex = 'http(s)?://[^\\s;\'"]+[^.,;\\s]'
handle (uri: string) {
this.plugin.electron.shell.openExternal(uri)
}
}
class FileHandler extends Handler {
regex = '/[^\\s.,;\'"]+'
verify (uri: string) {
return fs.existsSync(uri)
}
handle (uri: string) {
this.plugin.electron.shell.openExternal('file://' + uri)
}
}
export default class HyperlinksPlugin {
handlers = []
handlerClasses = [
URLHandler,
FileHandler,
]
electron: ElectronService
constructor ({ electron }) {
this.electron = electron
this.handlers = this.handlerClasses.map((x) => new x(this))
}
preTerminalInit ({ terminal }) {
const oldInsertString = terminal.screen_.constructor.prototype.insertString
const oldDeleteChars = terminal.screen_.constructor.prototype.deleteChars
terminal.screen_.insertString = (...args) => {
let ret = oldInsertString.bind(terminal.screen_)(...args)
this.insertLinks(terminal.screen_)
return ret
}
terminal.screen_.deleteChars = (...args) => {
let ret = oldDeleteChars.bind(terminal.screen_)(...args)
this.insertLinks(terminal.screen_)
return ret
}
}
insertLinks (screen) {
const traverse = (element) => {
Array.from(element.childNodes).forEach((node) => {
if (node.nodeName == '#text') {
element.replaceChild(this.urlizeNode(node), node)
} else if (node.nodeName != 'A') {
traverse(node)
}
})
}
screen.rowsArray.forEach((x) => traverse(x))
}
urlizeNode (node) {
let matches = []
this.handlers.forEach((handler) => {
let regex = new RegExp(handler.regex, 'gi')
let match
while (match = regex.exec(node.textContent)) {
let uri = handler.convert(match[0])
if (!handler.verify(uri)) {
continue;
}
matches.push({
start: regex.lastIndex - match[0].length,
end: regex.lastIndex,
text: match[0],
uri,
handler
})
}
})
if (matches.length == 0) {
return node
}
matches.sort((a, b) => a.start < b.start ? -1 : 1)
let span = document.createElement('span')
let position = 0
matches.forEach((match) => {
if (match.start < position) {
return
}
if (match.start > position) {
span.appendChild(document.createTextNode(node.textContent.slice(position, match.start)))
}
let a = document.createElement('a')
a.textContent = match.text
a.addEventListener('click', () => {
match.handler.handle(match.uri)
})
span.appendChild(a)
position = match.end
})
span.appendChild(document.createTextNode(node.textContent.slice(position)))
return span
}
}

View File

@@ -1,40 +1,40 @@
import * as yaml from 'js-yaml'
import * as path from 'path'
import * as fs from 'fs'
import { Injectable } from '@angular/core'
const Config = nodeRequire('electron-config')
import { ElectronService } from 'services/electron'
const defaultConfig : IConfigData = require('../../defaultConfig.yaml')
export interface IConfigData {
hotkeys?: any
}
@Injectable()
export class ConfigService {
constructor() {
this.config = new Config({name: 'config'})
constructor (
electron: ElectronService
) {
this.path = path.join(electron.app.getPath('userData'), 'config.yaml')
this.load()
}
private config: any
private store: any
private path: string
private store: IConfigData
set(key: string, value: any) {
this.store.set(key, value)
this.save()
load () {
if (fs.existsSync(this.path)) {
this.store = yaml.safeLoad(fs.readFileSync(this.path, 'utf8'))
} else {
this.store = {}
}
}
get(key: string): any {
return this.store[key]
save () {
fs.writeFileSync(this.path, yaml.safeDump(this.store), 'utf8')
}
has(key: string): boolean {
return this.store[key] != undefined
}
delete(key: string) {
delete this.store[key]
this.save()
}
load() {
this.store = this.config.store
}
save() {
this.config.store = this.store
full () : IConfigData {
return Object.assign({}, defaultConfig, this.store)
}
}

View File

@@ -7,7 +7,6 @@ const hterm = require('hterm-commonjs')
export interface HotkeyDescription {
id: string,
name: string,
defaults: string[][],
}
export interface PartialHotkeyMatch {
@@ -21,27 +20,62 @@ const HOTKEYS: HotkeyDescription[] = [
{
id: 'new-tab',
name: 'New tab',
defaults: [['Ctrl+Shift+T'], ['Ctrl+A', 'C']],
},
{
id: 'close-tab',
name: 'Close tab',
defaults: [['Ctrl+Shift+W'], ['Ctrl+A', 'K']],
},
{
id: 'toggle-last-tab',
name: 'Toggle last tab',
defaults: [['Ctrl+A', 'A'], ['Ctrl+A', 'Ctrl+A']],
},
{
id: 'next-tab',
name: 'Next tab',
defaults: [['Ctrl+Shift-ArrowRight'], ['Ctrl+A', 'N']],
},
{
id: 'previous-tab',
name: 'Previous tab',
defaults: [['Ctrl+Shift-ArrowLeft'], ['Ctrl+A', 'P']],
},
{
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',
},
]
@@ -86,10 +120,6 @@ export class HotkeysService {
oldHandler.bind(this)(nativeEvent)
}
})
if (!config.get('hotkeys')) {
config.set('hotkeys', {})
}
}
emitNativeEvent (name, nativeEvent) {
@@ -122,18 +152,21 @@ export class HotkeysService {
registerHotkeys () {
this.electron.globalShortcut.unregisterAll()
this.electron.globalShortcut.register('`', () => {
// TODO
this.electron.globalShortcut.register('PrintScreen', () => {
this.globalHotkey.emit()
})
}
getHotkeysConfig () {
let keys = {}
for (let key of HOTKEYS) {
keys[key.id] = key.defaults
}
for (let key in this.config.get('hotkeys')) {
keys[key] = this.config.get('hotkeys')[key]
for (let key in this.config.full().hotkeys) {
let value = this.config.full().hotkeys[key]
if (typeof value == 'string') {
value = [value]
}
value = value.map((item) => (typeof item == 'string') ? [item] : item)
keys[key] = value
}
return keys
}

View File

@@ -52,7 +52,7 @@ export function stringifyKeySequence(events: NativeKeyEvent[]): string[] {
continue
}
itemKeys.push(lastEvent.key)
items.push(itemKeys.join('+'))
items.push(itemKeys.join('-'))
}
lastEvent = event
}

View File

@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core'
import { ConfigService } from 'services/config'
import { ElectronService } from 'services/electron'
@Injectable()
export class PluginDispatcherService {
plugins = []
constructor (
private config: ConfigService,
private electron: ElectronService,
) {
}
register (plugin) {
if (!this.plugins.includes(plugin)) {
this.plugins.push(new plugin({
config: this.config,
electron: this.electron,
}))
}
}
emit (event: string, parameters: any) {
this.plugins.forEach((plugin) => {
if (plugin[event]) {
plugin[event].bind(plugin)(parameters)
}
})
}
}

16
app/src/variables.less Normal file
View File

@@ -0,0 +1,16 @@
@brand-primary: #f7e61d;
@brand-success: #42B500;
@brand-info: #01BAEF;
@brand-warning: #DB8A00;
@brand-danger: #EF2F00;
@body-bg: #1D272D;
@text-color: #aaa;
@font-family: "Source Sans Pro";
@font-size: 14px;
@dark-border: rgba(0,0,0,.25);
@light-border: rgba(255,255,255,.25);
@component-bg: #161d21;