diff --git a/scripts/vars.js b/scripts/vars.js index 0ac33025..98b62b85 100755 --- a/scripts/vars.js +++ b/scripts/vars.js @@ -9,6 +9,7 @@ exports.builtinPlugins = [ 'terminus-settings', 'terminus-terminal', 'terminus-community-color-schemes', + 'terminus-plugin-manager', ] exports.version = appInfo.version exports.electronVersion = pkgInfo.devDependencies.electron diff --git a/terminus-core/src/components/appRoot.component.pug b/terminus-core/src/components/appRoot.component.pug index 94159226..c9832dc3 100644 --- a/terminus-core/src/components/appRoot.component.pug +++ b/terminus-core/src/components/appRoot.component.pug @@ -40,21 +40,9 @@ title-bar( ) i.fa([class]='"fa fa-" + button.icon') - .btn-group.window-controls( + window-controls( *ngIf='config.store.appearance.frame == "thin" && (hostApp.platform == Platform.Windows || hostApp.platform == Platform.Linux)', ) - button.btn.btn-secondary.btn-minimize.btn-tab-bar( - (click)='hostApp.minimize()', - ) - i.fa.fa-window-minimize - button.btn.btn-secondary.btn-maximize.btn-tab-bar( - (click)='hostApp.toggleMaximize()', - ) - i.fa.fa-window-maximize - button.btn.btn-secondary.btn-close.btn-tab-bar( - (click)='hostApp.quit()', - ) - i.fa.fa-close start-page(*ngIf='ready && app.tabs.length == 0') diff --git a/terminus-core/src/components/appRoot.component.scss b/terminus-core/src/components/appRoot.component.scss index f9b9464a..218f4120 100644 --- a/terminus-core/src/components/appRoot.component.scss +++ b/terminus-core/src/components/appRoot.component.scss @@ -46,17 +46,6 @@ $tab-border-radius: 4px; border: none; border-radius: 0; - &.btn-minimize { - margin-left: 10px; - } - - &.btn-minimize, &.btn-maximize { - font-size: 8px; - } - - &.btn-close { - font-size: 12px; - } } &>.tabs { @@ -71,13 +60,13 @@ $tab-border-radius: 4px; -webkit-app-region: drag; } - .window-controls { - flex: 0 0 none; - } - &.inset { padding-left: 85px; } + + window-controls { + margin-left: 10px; + } } .tabs-content { diff --git a/terminus-core/src/components/titleBar.component.pug b/terminus-core/src/components/titleBar.component.pug index 8014a22c..55133783 100644 --- a/terminus-core/src/components/titleBar.component.pug +++ b/terminus-core/src/components/titleBar.component.pug @@ -1,7 +1,2 @@ .title((dblclick)='hostApp.toggleMaximize()') Terminus -button.btn.btn-secondary.btn-minimize((click)='hostApp.minimize()') - i.fa.fa-window-minimize -button.btn.btn-secondary.btn-maximize((click)='hostApp.toggleMaximize()') - i.fa.fa-window-maximize -button.btn.btn-secondary.btn-close((click)='hostApp.quit()') - i.fa.fa-close +window-controls diff --git a/terminus-core/src/components/titleBar.component.scss b/terminus-core/src/components/titleBar.component.scss index e9dd1153..e648eecf 100644 --- a/terminus-core/src/components/titleBar.component.scss +++ b/terminus-core/src/components/titleBar.component.scss @@ -11,26 +11,6 @@ $titlebar-height: 30px; -webkit-app-region: drag; } - button { - flex: none; - border: none; - box-shadow: none; - border-radius: 0; - font-size: 8px; - width: 40px; - padding: 0; - line-height: $titlebar-height; - text-align: center; - - &:not(:hover):not(:active) { - background: transparent; - } - } - - .btn-close { - font-size: 12px; - } - &.inset { flex-basis: 36px; @@ -39,7 +19,7 @@ $titlebar-height: 30px; line-height: 36px; } - button { + window-controls { display: none; } } diff --git a/terminus-core/src/components/windowControls.component.pug b/terminus-core/src/components/windowControls.component.pug new file mode 100644 index 00000000..ad649930 --- /dev/null +++ b/terminus-core/src/components/windowControls.component.pug @@ -0,0 +1,15 @@ +button.btn.btn-secondary.btn-minimize( + (click)='hostApp.minimize()', +) + svg(version='1.1', width='10', height='10') + path(d='M 0,5 10,5 10,6 0,6 Z') +button.btn.btn-secondary.btn-maximize( + (click)='hostApp.toggleMaximize()', +) + svg(version='1.1', width='10', height='10') + path(d='M 0,0 0,10 10,10 10,0 Z M 1,1 9,1 9,9 1,9 Z') +button.btn.btn-secondary.btn-close( + (click)='hostApp.quit()', +) + svg(version='1.1', width='10', height='10') + path(d='M 0,0 0,0.7 4.3,5 0,9.3 0,10 0.7,10 5,5.7 9.3,10 10,10 10,9.3 5.7,5 10,0.7 10,0 9.3,0 5,4.3 0.7,0 Z') diff --git a/terminus-core/src/components/windowControls.component.scss b/terminus-core/src/components/windowControls.component.scss new file mode 100644 index 00000000..6262196d --- /dev/null +++ b/terminus-core/src/components/windowControls.component.scss @@ -0,0 +1,23 @@ +:host { + display: flex; +} + +button { + flex: none; + border: none; + box-shadow: none; + border-radius: 0; + font-size: 8px; + width: 40px; + padding: 0; + line-height: 0; + text-align: center; + + &:not(:hover):not(:active) { + background: transparent; + } + + &:focus { + box-shadow: none; + } +} diff --git a/terminus-core/src/components/windowControls.component.ts b/terminus-core/src/components/windowControls.component.ts new file mode 100644 index 00000000..d1e37734 --- /dev/null +++ b/terminus-core/src/components/windowControls.component.ts @@ -0,0 +1,11 @@ +import { Component } from '@angular/core' +import { HostAppService } from '../services/hostApp.service' + +@Component({ + selector: 'window-controls', + template: require('./windowControls.component.pug'), + styles: [require('./windowControls.component.scss')], +}) +export class WindowControlsComponent { + constructor (private hostApp: HostAppService) { } +} diff --git a/terminus-core/src/index.ts b/terminus-core/src/index.ts index d7b0f288..e5a0d9ee 100644 --- a/terminus-core/src/index.ts +++ b/terminus-core/src/index.ts @@ -11,7 +11,6 @@ import { ElectronService } from './services/electron.service' import { HostAppService } from './services/hostApp.service' import { LogService } from './services/log.service' import { HotkeysService, AppHotkeyProvider } from './services/hotkeys.service' -import { NotifyService } from './services/notify.service' import { QuitterService } from './services/quitter.service' import { DockingService } from './services/docking.service' import { TabRecoveryService } from './services/tabRecovery.service' @@ -22,6 +21,7 @@ import { TabBodyComponent } from './components/tabBody.component' import { StartPageComponent } from './components/startPage.component' import { TabHeaderComponent } from './components/tabHeader.component' import { TitleBarComponent } from './components/titleBar.component' +import { WindowControlsComponent } from './components/windowControls.component' import { HotkeyProvider } from './api/hotkeyProvider' import { ConfigProvider } from './api/configProvider' @@ -40,7 +40,6 @@ const PROVIDERS = [ HostAppService, HotkeysService, LogService, - NotifyService, TabRecoveryService, ThemesService, QuitterService, @@ -65,6 +64,7 @@ const PROVIDERS = [ TabBodyComponent, TabHeaderComponent, TitleBarComponent, + WindowControlsComponent, ], }) export default class AppModule { diff --git a/terminus-core/src/services/hotkeys.service.ts b/terminus-core/src/services/hotkeys.service.ts index 2d5eccbc..bb5f95c5 100644 --- a/terminus-core/src/services/hotkeys.service.ts +++ b/terminus-core/src/services/hotkeys.service.ts @@ -91,9 +91,13 @@ export class HotkeysService { value.forEach(item => { item = (typeof item === 'string') ? [item] : item - this.electron.globalShortcut.register(item[0].replace(/-/g, '+'), () => { - this.globalHotkey.emit() - }) + try { + this.electron.globalShortcut.register(item[0].replace(/-/g, '+'), () => { + this.globalHotkey.emit() + }) + } catch (err) { + console.error('Could not register the global hotkey:', err) + } }) } diff --git a/terminus-core/src/services/notify.service.ts b/terminus-core/src/services/notify.service.ts deleted file mode 100644 index 1191d011..00000000 --- a/terminus-core/src/services/notify.service.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Injectable } from '@angular/core' -import { ToasterService } from 'angular2-toaster' - -@Injectable() -export class NotifyService { - constructor ( - private toaster: ToasterService, - ) {} - - pop (options) { - this.toaster.pop(options) - } - - info (title: string, body: string = null) { - return this.pop({ - type: 'info', - title, body, - timeout: 4000, - }) - } - - success (title: string, body: string = null) { - return this.pop({ - type: 'success', - title, body, - timeout: 4000, - }) - } - - warning (title: string, body: string = null) { - return this.pop({ - type: 'warning', - title, body, - timeout: 4000, - }) - } - - error (title: string, body: string = null) { - return this.pop({ - type: 'error', - title, body, - timeout: 4000, - }) - } -} diff --git a/terminus-core/src/theme.scss b/terminus-core/src/theme.scss index 8ba92ed7..2eb09bd7 100644 --- a/terminus-core/src/theme.scss +++ b/terminus-core/src/theme.scss @@ -76,10 +76,20 @@ $list-group-bg: $body-bg2; title-bar { background: $body-bg2; +} - button { - &:hover { background: $button-hover-bg !important; } - &:active { background: $button-active-bg !important; } +window-controls { + svg { + transition: 0.25s fill; + fill: #aaa; + } + + button:hover svg { + fill: white; + } + + .btn-close:hover { + background: #8a2828; } } diff --git a/terminus-plugin-manager/package.json b/terminus-plugin-manager/package.json index 45d57bc0..78a9caf7 100644 --- a/terminus-plugin-manager/package.json +++ b/terminus-plugin-manager/package.json @@ -21,6 +21,7 @@ "@types/webpack-env": "1.13.0", "awesome-typescript-loader": "3.1.2", "css-loader": "^0.28.0", + "ngx-pipes": "^1.6.1", "pug": "^2.0.0-beta11", "pug-loader": "^2.3.0", "raw-loader": "^0.5.1", diff --git a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug index df9d973f..55bb87fb 100644 --- a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug +++ b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug @@ -2,63 +2,81 @@ strong Error in {{erroredPlugin}}: pre {{errorMessage}} +button.btn.btn-outline-info.btn-sm.pull-right((click)='openPluginsFolder()') + i.fa.fa-folder + span Plugins folder + h3 Installed .list-group - ng-container(*ngFor='let plugin of pluginManager.installedPlugins') + ng-container(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"') .list-group-item.flex-column.align-items-start(*ngIf='knownUpgrades[plugin.name]') .d-flex.w-100 - h5.mr-auto.mb-0 {{plugin.name}} + .mr-auto.d-flex.flex-column + strong {{plugin.name}} + small.text-muted.mb-0 {{plugin.description}} p.mb-0.mr-3 {{plugin.version}} button.btn.btn-outline-primary( + *ngIf='npmInstalled', (click)='upgradePlugin(plugin)', [disabled]='busy[plugin.name] != undefined' ) i.fa.fa-fw.fa-arrow-up(*ngIf='busy[plugin.name] != BusyState.Installing') i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing') span Upgrade ({{knownUpgrades[plugin.name].version}}) - small.text-muted.mb-0 {{plugin.description}} - ng-container(*ngFor='let plugin of pluginManager.installedPlugins') + ng-container(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"') .list-group-item.flex-column.align-items-start(*ngIf='!knownUpgrades[plugin.name]') .d-flex.w-100 - h5.mr-auto.mb-0 {{plugin.name}} + .mr-auto.d-flex.flex-column + strong {{plugin.name}} + small.text-muted.mb-0 {{plugin.description}} p.mb-0.mr-3 {{plugin.version}} button.btn.btn-outline-danger( (click)='uninstallPlugin(plugin)', - *ngIf='!plugin.isBuiltin', + *ngIf='!plugin.isBuiltin && npmInstalled', [disabled]='busy[plugin.name] != undefined' ) i.fa.fa-fw.fa-trash-o(*ngIf='busy[plugin.name] != BusyState.Uninstalling') i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Uninstalling') - small.text-muted.mb-0 {{plugin.description}} + +.text-center.mt-5(*ngIf='npmMissing') + h4 NPM not installed + p.mb-2 The Node Package Manager is required to install Terminus plugins. + .btn-group + button.btn.btn-outline-primary((click)='downloadNPM()') + i.fa.fa-download + span Download NPM + button.btn.btn-outline-info((click)='checkNPM()') + i.fa.fa-refresh + span Try again + +div(*ngIf='npmInstalled') + h3.mt-4 Available + + .input-group.mb-4 + .input-group-addon + i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady') + i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady') + input.form-control( + type='text', + '[(ngModel)]'='_1', + (ngModelChange)='searchAvailable(_1)', + placeholder='Search plugins' + ) -h3.mt-4 Available - -.input-group.mb-4 - .input-group-addon - i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='!availablePluginsReady') - i.fa.fa-fw.fa-search(*ngIf='availablePluginsReady') - input.form-control( - type='text', - '[(ngModel)]'='_1', - (ngModelChange)='searchAvailable(_1)', - placeholder='Search plugins' - ) - - -.list-group(*ngIf='availablePlugins$') - ng-container(*ngFor='let plugin of (availablePlugins$|async)') - .list-group-item.flex-column.align-items-start(*ngIf='!isAlreadyInstalled(plugin)') - .d-flex.w-100 - h5.mr-auto.mb-0 {{plugin.name}} - p.mb-0.mr-3 {{plugin.version}} - button.btn.btn-outline-primary( - (click)='installPlugin(plugin)', - [disabled]='busy[plugin.name] != undefined' - ) - i.fa.fa-fw.fa-download(*ngIf='busy[plugin.name] != BusyState.Installing') - i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing') - span Install - small.text-muted.mb-0 {{plugin.description}} + .list-group(*ngIf='availablePlugins$') + ng-container(*ngFor='let plugin of (availablePlugins$|async|orderBy:"name")') + .list-group-item.flex-column.align-items-start(*ngIf='!isAlreadyInstalled(plugin)') + .d-flex.w-100 + .mr-auto.d-flex.flex-column + strong {{plugin.name}} + small.text-muted.mb-0 {{plugin.description}} + p.mb-0.mr-3 {{plugin.version}} + button.btn.btn-outline-primary( + (click)='installPlugin(plugin)', + [disabled]='busy[plugin.name] != undefined' + ) + i.fa.fa-fw.fa-download(*ngIf='busy[plugin.name] != BusyState.Installing') + i.fa.fa-fw.fa-circle-o-notch.fa-spin(*ngIf='busy[plugin.name] == BusyState.Installing') diff --git a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts index 97ce1145..82683e71 100644 --- a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts +++ b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts @@ -2,7 +2,7 @@ import { BehaviorSubject, Observable } from 'rxjs' import * as semver from 'semver' import { Component, Input } from '@angular/core' -import { ConfigService } from 'terminus-core' +import { ConfigService, HostAppService } from 'terminus-core' import { IPluginInfo, PluginManagerService } from '../services/pluginManager.service' enum BusyState { Installing, Uninstalling } @@ -20,9 +20,12 @@ export class PluginsSettingsTabComponent { @Input() busy: {[id: string]: BusyState} = {} @Input() erroredPlugin: string @Input() errorMessage: string + @Input() npmInstalled = false + @Input() npmMissing = false constructor ( private config: ConfigService, + private hostApp: HostAppService, public pluginManager: PluginManagerService ) { } @@ -42,6 +45,20 @@ export class PluginsSettingsTabComponent { this.knownUpgrades[plugin.name] = available.find(x => x.name === plugin.name && semver.gt(x.version, plugin.version)) } }) + this.checkNPM() + } + + openPluginsFolder (): void { + this.hostApp.getShell().openItem(this.pluginManager.userPluginsPath) + } + + downloadNPM (): void { + this.hostApp.getShell().openExternal('https://nodejs.org/en/download/current/') + } + + async checkNPM () { + this.npmInstalled = await this.pluginManager.isNPMInstalled() + this.npmMissing = !this.npmInstalled } searchAvailable (query: string) { diff --git a/terminus-plugin-manager/src/index.ts b/terminus-plugin-manager/src/index.ts index 2731f155..e3b7d58a 100644 --- a/terminus-plugin-manager/src/index.ts +++ b/terminus-plugin-manager/src/index.ts @@ -1,6 +1,7 @@ import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' +import { NgPipesModule } from 'ngx-pipes' import { NgbModule } from '@ng-bootstrap/ng-bootstrap' import { SettingsTabProvider } from 'terminus-settings' @@ -14,6 +15,7 @@ import { PluginsSettingsTabProvider } from './settings' BrowserModule, FormsModule, NgbModule, + NgPipesModule, ], providers: [ { provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true }, diff --git a/terminus-plugin-manager/src/services/pluginManager.service.ts b/terminus-plugin-manager/src/services/pluginManager.service.ts index 10e8c650..1e34e556 100644 --- a/terminus-plugin-manager/src/services/pluginManager.service.ts +++ b/terminus-plugin-manager/src/services/pluginManager.service.ts @@ -22,6 +22,7 @@ export class PluginManagerService { builtinPluginsPath: string = (window as any).builtinPluginsPath userPluginsPath: string = (window as any).userPluginsPath installedPlugins: IPluginInfo[] = (window as any).installedPlugins + npmBinary = 'npm' constructor ( log: LogService, @@ -29,6 +30,15 @@ export class PluginManagerService { this.logger = log.create('pluginManager') } + async isNPMInstalled (): Promise { + try { + await exec(`${this.npmBinary} -v`) + return true + } catch (_) { + return false + } + } + listAvailable (query?: string): Observable { return Observable .fromPromise( @@ -45,14 +55,14 @@ export class PluginManagerService { } async installPlugin (plugin: IPluginInfo) { - let result = await exec(`npm --prefix "${this.userPluginsPath}" install ${plugin.packageName}@${plugin.version}`) + let result = await exec(`${this.npmBinary} --prefix "${this.userPluginsPath}" install ${plugin.packageName}@${plugin.version}`) console.log(result) this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName) this.installedPlugins.push(plugin) } async uninstallPlugin (plugin: IPluginInfo) { - await exec(`npm --prefix "${this.userPluginsPath}" remove ${plugin.packageName}`) + await exec(`${this.npmBinary} --prefix "${this.userPluginsPath}" remove ${plugin.packageName}`) this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName) } } diff --git a/terminus-settings/package.json b/terminus-settings/package.json index 87702340..bca35aa7 100644 --- a/terminus-settings/package.json +++ b/terminus-settings/package.json @@ -8,7 +8,9 @@ "build": "webpack --progress --color --display-modules", "watch": "webpack --progress --color --watch" }, - "files": ["dist"], + "files": [ + "dist" + ], "author": "Eugene Pankov", "license": "MIT", "devDependencies": { @@ -17,7 +19,7 @@ "@types/webpack-env": "1.13.0", "awesome-typescript-loader": "3.1.2", "css-loader": "^0.28.0", - "ng2-filter-pipe": "^0.1.7", + "ngx-pipes": "^1.6.1", "node-sass": "^4.5.2", "pug": "^2.0.0-beta3", "pug-loader": "^2.3.0", diff --git a/terminus-settings/src/buttonProvider.ts b/terminus-settings/src/buttonProvider.ts index 1f8e68cd..534dd2a3 100644 --- a/terminus-settings/src/buttonProvider.ts +++ b/terminus-settings/src/buttonProvider.ts @@ -13,7 +13,7 @@ export class ButtonProvider extends ToolbarButtonProvider { provide (): IToolbarButton[] { return [{ - icon: 'cog', + icon: 'sliders', title: 'Settings', weight: 10, click: () => { diff --git a/terminus-settings/src/components/settingsTab.component.pug b/terminus-settings/src/components/settingsTab.component.pug index 32c3fe3f..95f55bcb 100644 --- a/terminus-settings/src/components/settingsTab.component.pug +++ b/terminus-settings/src/components/settingsTab.component.pug @@ -149,14 +149,14 @@ ngb-tabset.vertical(type='tabs') template(ngbTabTitle) | Hotkeys template(ngbTabContent) - input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter.name') + input.form-control(type='search', placeholder='Search hotkeys', [(ngModel)]='hotkeyFilter') .form-group table.hotkeys-table tr th Name th ID th Hotkey - tr(*ngFor='let hotkey of hotkeyDescriptions|filterBy:hotkeyFilter') + tr(*ngFor='let hotkey of hotkeyDescriptions|filterBy:["name"]:hotkeyFilter') td {{hotkey.name}} td {{hotkey.id}} td diff --git a/terminus-settings/src/components/settingsTab.component.ts b/terminus-settings/src/components/settingsTab.component.ts index 49ee90a9..2ffb8d09 100644 --- a/terminus-settings/src/components/settingsTab.component.ts +++ b/terminus-settings/src/components/settingsTab.component.ts @@ -12,7 +12,7 @@ import { SettingsTabProvider } from '../api' ], }) export class SettingsTabComponent extends BaseTabComponent { - hotkeyFilter = { name: null } + hotkeyFilter = '' private hotkeyDescriptions: IHotkeyDescription[] constructor ( diff --git a/terminus-settings/src/index.ts b/terminus-settings/src/index.ts index cb12af06..8b9dd63e 100644 --- a/terminus-settings/src/index.ts +++ b/terminus-settings/src/index.ts @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core' import { BrowserModule } from '@angular/platform-browser' import { FormsModule } from '@angular/forms' import { NgbModule } from '@ng-bootstrap/ng-bootstrap' -import { Ng2FilterPipeModule } from 'ng2-filter-pipe' +import { NgPipesModule } from 'ngx-pipes' import { ToolbarButtonProvider, TabRecoveryProvider } from 'terminus-core' @@ -19,7 +19,7 @@ import { RecoveryProvider } from './recoveryProvider' BrowserModule, FormsModule, NgbModule, - Ng2FilterPipeModule, + NgPipesModule, ], providers: [ { provide: ToolbarButtonProvider, useClass: ButtonProvider, multi: true }, diff --git a/terminus-settings/webpack.config.js b/terminus-settings/webpack.config.js index f5685e68..a2174e6d 100644 --- a/terminus-settings/webpack.config.js +++ b/terminus-settings/webpack.config.js @@ -29,7 +29,6 @@ module.exports = { } } }, - { test: /schemes\/.*$/, use: "raw-loader" }, { test: /\.pug$/, use: ['apply-loader', 'pug-loader'] }, { test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] }, { test: /\.css$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },