diff --git a/app/src/app.module.ts b/app/src/app.module.ts index 35f691fe..23def87a 100644 --- a/app/src/app.module.ts +++ b/app/src/app.module.ts @@ -5,7 +5,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap' export function getRootModule (plugins: any[]) { let imports = [ BrowserModule, - ...(plugins.map(x => x.default.forRoot ? x.default.forRoot() : x.default)), + ...plugins, NgbModule.forRoot(), ] let bootstrap = [ diff --git a/app/src/entry.ts b/app/src/entry.ts index 5f7e6c40..fb804cbc 100644 --- a/app/src/entry.ts +++ b/app/src/entry.ts @@ -30,6 +30,7 @@ async function bootstrap (plugins: IPluginInfo[], safeMode = false): Promise { let paths = nodeModule.globalPaths let foundPlugins: IPluginInfo[] = [] - let candidateLocations: { pluginDir: string, pluginName: string }[] = [] + let candidateLocations: { pluginDir: string, packageName: string }[] = [] for (let pluginDir of paths) { pluginDir = normalizePath(pluginDir) @@ -93,24 +93,26 @@ export async function findPlugins (): Promise { if (await fs.exists(path.join(pluginDir, 'package.json'))) { candidateLocations.push({ pluginDir: path.dirname(pluginDir), - pluginName: path.basename(pluginDir) + packageName: path.basename(pluginDir) }) } - for (let pluginName of pluginNames) { - candidateLocations.push({ pluginDir, pluginName }) + for (let packageName of pluginNames) { + candidateLocations.push({ pluginDir, packageName }) } } - for (let { pluginDir, pluginName } of candidateLocations) { - let pluginPath = path.join(pluginDir, pluginName) + for (let { pluginDir, packageName } of candidateLocations) { + let pluginPath = path.join(pluginDir, packageName) let infoPath = path.join(pluginPath, 'package.json') if (!await fs.exists(infoPath)) { continue } - if (foundPlugins.some(x => x.name === pluginName.substring('terminus-'.length))) { - console.info(`Plugin ${pluginName} already exists, overriding`) - foundPlugins = foundPlugins.filter(x => x.name !== pluginName.substring('terminus-'.length)) + let name = packageName.substring('terminus-'.length) + + if (foundPlugins.some(x => x.name === name)) { + console.info(`Plugin ${packageName} already exists, overriding`) + foundPlugins = foundPlugins.filter(x => x.name !== name) } try { @@ -121,8 +123,8 @@ export async function findPlugins (): Promise { let author = info.author author = author.name || author foundPlugins.push({ - name: pluginName.substring('terminus-'.length), - packageName: pluginName, + name: name, + packageName: packageName, isBuiltin: pluginDir === builtinPluginsPath, version: info.version, description: info.description, @@ -131,7 +133,7 @@ export async function findPlugins (): Promise { info, }) } catch (error) { - console.error('Cannot load package info for', pluginName) + console.error('Cannot load package info for', packageName) } } @@ -147,7 +149,10 @@ export async function loadPlugins (foundPlugins: IPluginInfo[], progress: Progre console.info(`Loading ${foundPlugin.name}: ${nodeRequire.resolve(foundPlugin.path)}`) progress(index, foundPlugins.length) try { - let pluginModule = nodeRequire(foundPlugin.path) + let packageModule = nodeRequire(foundPlugin.path) + let pluginModule = packageModule.default.forRoot ? packageModule.default.forRoot() : packageModule.default + pluginModule['pluginName'] = foundPlugin.name + pluginModule['bootstrap'] = packageModule.bootstrap plugins.push(pluginModule) } catch (error) { console.error(`Could not load ${foundPlugin.name}:`, error) diff --git a/terminus-core/src/components/appRoot.component.ts b/terminus-core/src/components/appRoot.component.ts index cc9a1d6d..3df7403e 100644 --- a/terminus-core/src/components/appRoot.component.ts +++ b/terminus-core/src/components/appRoot.component.ts @@ -170,7 +170,7 @@ export class AppRootComponent { private getToolbarButtons (aboveZero: boolean): IToolbarButton[] { let buttons: IToolbarButton[] = [] - this.toolbarButtonProviders.forEach((provider) => { + this.config.enabledServices(this.toolbarButtonProviders).forEach(provider => { buttons = buttons.concat(provider.provide()) }) return buttons diff --git a/terminus-core/src/components/startPage.component.ts b/terminus-core/src/components/startPage.component.ts index 9696b58e..9359e77a 100644 --- a/terminus-core/src/components/startPage.component.ts +++ b/terminus-core/src/components/startPage.component.ts @@ -1,6 +1,7 @@ import * as os from 'os' import { Component, Inject } from '@angular/core' import { ElectronService } from '../services/electron.service' +import { ConfigService } from '../services/config.service' import { IToolbarButton, ToolbarButtonProvider } from '../api' @Component({ @@ -13,13 +14,14 @@ export class StartPageComponent { constructor ( private electron: ElectronService, + private config: ConfigService, @Inject(ToolbarButtonProvider) private toolbarButtonProviders: ToolbarButtonProvider[], ) { this.version = electron.app.getVersion() } getButtons (): IToolbarButton[] { - return this.toolbarButtonProviders + return this.config.enabledServices(this.toolbarButtonProviders) .map(provider => provider.provide()) .reduce((a, b) => a.concat(b)) .sort((a: IToolbarButton, b: IToolbarButton) => (a.weight || 0) - (b.weight || 0)) diff --git a/terminus-core/src/configDefaults.yaml b/terminus-core/src/configDefaults.yaml index 93b593ce..44f6fee1 100644 --- a/terminus-core/src/configDefaults.yaml +++ b/terminus-core/src/configDefaults.yaml @@ -6,3 +6,4 @@ appearance: theme: Standard frame: thin css: '/* * { color: blue !important; } */' +pluginBlacklist: [] diff --git a/terminus-core/src/services/config.service.ts b/terminus-core/src/services/config.service.ts index c261f1ee..7c7da494 100644 --- a/terminus-core/src/services/config.service.ts +++ b/terminus-core/src/services/config.service.ts @@ -6,6 +6,7 @@ import { Injectable, Inject } from '@angular/core' import { ConfigProvider } from '../api/configProvider' import { ElectronService } from './electron.service' import { HostAppService } from './hostApp.service' +import * as Reflect from 'core-js/es7/reflect' const configMerge = (a, b) => require('deepmerge')(a, b, { arrayMerge: (_d, s) => s }) @@ -57,6 +58,7 @@ export class ConfigService { private _store: any private path: string private defaults: any + private servicesCache: { [id: string]: Function[] } = null constructor ( electron: ElectronService, @@ -98,4 +100,28 @@ export class ConfigService { requestRestart (): void { this.restartRequested = true } + + enabledServices (services: T[]): T[] { + if (!this.servicesCache) { + this.servicesCache = {} + let ngModule = Reflect.getMetadata('annotations', window['rootModule'])[0] + for (let imp of ngModule.imports) { + let module = imp['module'] || imp + let annotations = Reflect.getMetadata('annotations', module) + if (annotations) { + this.servicesCache[module['pluginName']] = annotations[0].providers.map(provider => { + return provider['useClass'] || provider + }) + } + } + } + return services.filter(service => { + for (let pluginName in this.servicesCache) { + if (this.servicesCache[pluginName].includes(service.constructor)) { + return !this.store.pluginBlacklist.includes(pluginName) + } + } + return true + }) + } } diff --git a/terminus-core/src/services/hotkeys.service.ts b/terminus-core/src/services/hotkeys.service.ts index cfd0ccc0..0a7a655e 100644 --- a/terminus-core/src/services/hotkeys.service.ts +++ b/terminus-core/src/services/hotkeys.service.ts @@ -42,7 +42,7 @@ export class HotkeysService { } }) }) - this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b)) + this.hotkeyDescriptions = this.config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b)) this.config.changed$.subscribe(() => { this.registerGlobalHotkey() }) diff --git a/terminus-core/src/services/tabRecovery.service.ts b/terminus-core/src/services/tabRecovery.service.ts index 4ce8e37f..0bc3f0fe 100644 --- a/terminus-core/src/services/tabRecovery.service.ts +++ b/terminus-core/src/services/tabRecovery.service.ts @@ -3,6 +3,7 @@ import { TabRecoveryProvider, RecoveredTab } from '../api/tabRecovery' import { BaseTabComponent } from '../components/baseTab.component' import { Logger, LogService } from '../services/log.service' import { AppService } from '../services/app.service' +import { ConfigService } from '../services/config.service' @Injectable() export class TabRecoveryService { @@ -11,6 +12,7 @@ export class TabRecoveryService { constructor ( @Inject(TabRecoveryProvider) private tabRecoveryProviders: TabRecoveryProvider[], private app: AppService, + private config: ConfigService, log: LogService ) { this.logger = log.create('tabRecovery') @@ -31,7 +33,7 @@ export class TabRecoveryService { if (window.localStorage.tabsRecovery) { let tabs: RecoveredTab[] = [] for (let token of JSON.parse(window.localStorage.tabsRecovery)) { - for (let provider of this.tabRecoveryProviders) { + for (let provider of this.config.enabledServices(this.tabRecoveryProviders)) { try { let tab = await provider.recover(token) if (tab) { diff --git a/terminus-core/src/services/themes.service.ts b/terminus-core/src/services/themes.service.ts index 5b199150..ce5c65db 100644 --- a/terminus-core/src/services/themes.service.ts +++ b/terminus-core/src/services/themes.service.ts @@ -17,7 +17,7 @@ export class ThemesService { } findTheme (name: string): Theme { - return this.themes.find(x => x.name === name) + return this.config.enabledServices(this.themes).find(x => x.name === name) } findCurrentTheme (): Theme { diff --git a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug index bca9f2bd..8e7548c6 100644 --- a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug +++ b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.pug @@ -10,34 +10,23 @@ h3 Installed .list-group ng-container(*ngFor='let plugin of pluginManager.installedPlugins|orderBy:"name"') - .list-group-item.flex-column.align-items-start(*ngIf='knownUpgrades[plugin.name]') + .list-group-item.flex-column.align-items-start .d-flex.w-100 .mr-auto.d-flex.flex-column strong {{plugin.name}} - small.text-muted.mb-0((click)='showPluginInfo(plugin)') {{plugin.description}} + a.text-muted.mb-0((click)='showPluginInfo(plugin)') + small {{plugin.description}} .d-flex.flex-column.align-items-end.mr-3 div {{plugin.version}} small.text-muted {{plugin.author}} button.btn.btn-outline-primary( - *ngIf='npmInstalled', + *ngIf='npmInstalled && knownUpgrades[plugin.name]', (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}}) - - 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 - .mr-auto.d-flex.flex-column - strong {{plugin.name}} - a.text-muted.mb-0((click)='showPluginInfo(plugin)') - small {{plugin.description}} - .d-flex.flex-column.align-items-end.mr-3 - div {{plugin.version}} - small.text-muted {{plugin.author}} - i.fa.fa-check.text-success.ml-1(*ngIf='plugin.isOfficial', title='Official') button.btn.btn-outline-danger( (click)='uninstallPlugin(plugin)', *ngIf='!plugin.isBuiltin && npmInstalled', @@ -45,6 +34,16 @@ h3 Installed ) 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') + button.btn.btn-outline-danger( + *ngIf='config.store.pluginBlacklist.includes(plugin.name)', + (click)='enablePlugin(plugin)' + ) + i.fa.fa-fw.fa-play + button.btn.btn-outline-primary( + *ngIf='!config.store.pluginBlacklist.includes(plugin.name)', + (click)='disablePlugin(plugin)' + ) + i.fa.fa-fw.fa-pause .text-center.mt-5(*ngIf='npmMissing') h4 npm not installed diff --git a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts index 2b6004ea..b70e2494 100644 --- a/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts +++ b/terminus-plugin-manager/src/components/pluginsSettingsTab.component.ts @@ -105,4 +105,16 @@ export class PluginsSettingsTabComponent { showPluginInfo (plugin: IPluginInfo) { this.electron.shell.openExternal('https://www.npmjs.com/package/' + plugin.packageName) } + + enablePlugin (plugin: IPluginInfo) { + this.config.store.pluginBlacklist = this.config.store.pluginBlacklist.filter(x => x !== plugin.name) + this.config.save() + this.config.requestRestart() + } + + disablePlugin (plugin: IPluginInfo) { + this.config.store.pluginBlacklist.push(plugin.name) + this.config.save() + this.config.requestRestart() + } } diff --git a/terminus-settings/src/components/settingsTab.component.ts b/terminus-settings/src/components/settingsTab.component.ts index cf9ef08b..b62671d1 100644 --- a/terminus-settings/src/components/settingsTab.component.ts +++ b/terminus-settings/src/components/settingsTab.component.ts @@ -27,10 +27,12 @@ export class SettingsTabComponent extends BaseTabComponent { @Inject(Theme) public themes: Theme[], ) { super() - this.hotkeyDescriptions = hotkeyProviders.map(x => x.hotkeys).reduce((a, b) => a.concat(b)) + this.hotkeyDescriptions = config.enabledServices(hotkeyProviders).map(x => x.hotkeys).reduce((a, b) => a.concat(b)) this.title = 'Settings' this.scrollable = true this.screens = this.docking.getScreens() + this.settingsProviders = config.enabledServices(this.settingsProviders) + this.themes = config.enabledServices(this.themes) } getRecoveryToken (): any { diff --git a/terminus-terminal/src/components/terminalSettingsTab.component.ts b/terminus-terminal/src/components/terminalSettingsTab.component.ts index 3e91edcd..a2f816e4 100644 --- a/terminus-terminal/src/components/terminalSettingsTab.component.ts +++ b/terminus-terminal/src/components/terminalSettingsTab.component.ts @@ -27,7 +27,7 @@ export class TerminalSettingsTabComponent { @Inject(TerminalColorSchemeProvider) private colorSchemeProviders: TerminalColorSchemeProvider[], @Inject(SessionPersistenceProvider) persistenceProviders: SessionPersistenceProvider[], ) { - this.persistenceProviders = persistenceProviders.filter(x => x.isAvailable()) + this.persistenceProviders = this.config.enabledServices(persistenceProviders).filter(x => x.isAvailable()) } async ngOnInit () { @@ -46,8 +46,8 @@ export class TerminalSettingsTabComponent { this.fonts.sort() }) } - this.colorSchemes = (await Promise.all(this.colorSchemeProviders.map(x => x.getSchemes()))).reduce((a, b) => a.concat(b)) - this.shells = (await Promise.all(this.shellProviders.map(x => x.provide()))).reduce((a, b) => a.concat(b)) + this.colorSchemes = (await Promise.all(this.config.enabledServices(this.colorSchemeProviders).map(x => x.getSchemes()))).reduce((a, b) => a.concat(b)) + this.shells = (await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide()))).reduce((a, b) => a.concat(b)) } fontAutocomplete = (text$: Observable) => { diff --git a/terminus-terminal/src/components/terminalTab.component.ts b/terminus-terminal/src/components/terminalTab.component.ts index e00075a1..426556cb 100644 --- a/terminus-terminal/src/components/terminalTab.component.ts +++ b/terminus-terminal/src/components/terminalTab.component.ts @@ -125,7 +125,7 @@ export class TerminalTabComponent extends BaseTabComponent { }) this.hterm = new hterm.hterm.Terminal() - this.decorators.forEach((decorator) => { + this.config.enabledServices(this.decorators).forEach((decorator) => { decorator.attach(this) }) @@ -406,7 +406,7 @@ export class TerminalTabComponent extends BaseTabComponent { } ngOnDestroy () { - this.decorators.forEach(decorator => { + this.config.enabledServices(this.decorators).forEach(decorator => { decorator.detach(this) }) this.hotkeysSubscription.unsubscribe() diff --git a/terminus-terminal/src/services/sessions.service.ts b/terminus-terminal/src/services/sessions.service.ts index a78e0b74..fed4d1b2 100644 --- a/terminus-terminal/src/services/sessions.service.ts +++ b/terminus-terminal/src/services/sessions.service.ts @@ -202,7 +202,7 @@ export class SessionsService { ) { nodePTY = electron.remoteRequirePluginModule('terminus-terminal', 'node-pty-tmp', global as any) this.logger = log.create('sessions') - this.persistenceProviders = this.persistenceProviders.filter(x => x.isAvailable()) + this.persistenceProviders = this.config.enabledServices(this.persistenceProviders).filter(x => x.isAvailable()) } async prepareNewSession (options: SessionOptions): Promise { diff --git a/terminus-terminal/src/services/terminal.service.ts b/terminus-terminal/src/services/terminal.service.ts index 8cd09773..add65b79 100644 --- a/terminus-terminal/src/services/terminal.service.ts +++ b/terminus-terminal/src/services/terminal.service.ts @@ -27,7 +27,7 @@ export class TerminalService { async reloadShells () { this.shells$ = new AsyncSubject() - let shellLists = await Promise.all(this.shellProviders.map(x => x.provide())) + let shellLists = await Promise.all(this.config.enabledServices(this.shellProviders).map(x => x.provide())) this.shells$.next(shellLists.reduce((a, b) => a.concat(b))) this.shells$.complete() }