This commit is contained in:
Eugene Pankov
2017-06-01 22:23:36 +02:00
parent c0f7cd9a7a
commit 73722c0b2f
31 changed files with 2946 additions and 589 deletions

1
terminus-plugin-manager/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

2649
terminus-plugin-manager/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,46 @@
{
"name": "terminus-plugin-manager",
"version": "0.0.1",
"description": "Terminus' plugin manager",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"scripts": {
"build": "webpack --progress --color --display-modules",
"watch": "webpack --progress --color --watch"
},
"files": [
"dist"
],
"author": "Eugene Pankov",
"license": "MIT",
"devDependencies": {
"@types/mz": "0.0.31",
"@types/node": "7.0.12",
"@types/npm": "^2.0.28",
"@types/semver": "^5.3.31",
"@types/webpack-env": "1.13.0",
"awesome-typescript-loader": "3.1.2",
"css-loader": "^0.28.0",
"pug": "^2.0.0-beta11",
"pug-loader": "^2.3.0",
"raw-loader": "^0.5.1",
"sass-loader": "^6.0.3",
"semver": "^5.3.0",
"to-string-loader": "^1.1.5",
"typescript": "^2.2.2",
"webpack": "2.3.3"
},
"peerDependencies": {
"@angular/common": "4.0.1",
"@angular/core": "4.0.1",
"@angular/forms": "4.0.1",
"@angular/platform-browser": "4.0.1",
"@ng-bootstrap/ng-bootstrap": "1.0.0-alpha.22",
"terminus-core": "*",
"terminus-settings": "*",
"rxjs": "5.3.0"
},
"dependencies": {
"axios": "^0.16.1"
}
}

View File

@@ -0,0 +1,46 @@
h3 Installed
.list-group
ng-container(*ngFor='let plugin of pluginManager.installedPlugins')
.list-group-item.flex-column.align-items-start(*ngIf='knownUpgrades[plugin.name]')
.d-flex.w-100
h6.mr-auto.mb-0 {{plugin.name}}
p.mb-0.mr-3 {{plugin.version}}
button.btn.btn-outline-primary((click)='upgradePlugin(plugin)')
i.fa.fa-arrow-up
span Upgrade ({{knownUpgrades[plugin.name].version}})
small.mb-0 {{plugin.description}}
ng-container(*ngFor='let plugin of pluginManager.installedPlugins')
.list-group-item.flex-column.align-items-start(*ngIf='!knownUpgrades[plugin.name]')
.d-flex.w-100
h6.mr-auto.mb-0 {{plugin.name}}
p.mb-0.mr-3 {{plugin.version}}
button.btn.btn-outline-danger((click)='uninstallPlugin(plugin)', *ngIf='!plugin.isBuiltin')
i.fa.fa-trash-o
small.mb-0 {{plugin.description}}
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)='availablePluginsQuery$.next(_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
h6.mr-auto.mb-0 {{plugin.name}}
p.mb-0.mr-3 {{plugin.version}}
button.btn.btn-outline-primary((click)='installPlugin(plugin)')
i.fa.fa-download
span Install
small.mb-0 {{plugin.description}}

View File

@@ -0,0 +1,8 @@
.appearance-preview {
padding: 10px 20px;
margin: 0 0 10px;
overflow: hidden;
span {
white-space: pre;
}
}

View File

@@ -0,0 +1,55 @@
import { BehaviorSubject, Observable } from 'rxjs'
import * as fs from 'fs-promise'
import * as path from 'path'
import * as semver from 'semver'
import { exec } from 'mz/child_process'
import { Component, Inject, ChangeDetectionStrategy } from '@angular/core'
import { IPluginInfo, PluginManagerService } from '../services/pluginManager.service'
@Component({
template: require('./pluginsSettingsTab.component.pug'),
styles: [require('./pluginsSettingsTab.component.scss')],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PluginsSettingsTabComponent {
availablePlugins$: Observable<IPluginInfo[]>
availablePluginsQuery$ = new BehaviorSubject<string>('')
availablePluginsReady = false
knownUpgrades: {[id: string]: IPluginInfo} = {}
busy: boolean
constructor (
public pluginManager: PluginManagerService
) {
}
ngOnInit () {
this.availablePlugins$ = this.availablePluginsQuery$
.debounceTime(200)
.distinctUntilChanged()
.flatMap(query => {
this.availablePluginsReady = false
return this.pluginManager.listAvailable(query).do(() => {
this.availablePluginsReady = true
})
})
this.availablePlugins$.first().subscribe(available => {
for (let plugin of this.pluginManager.installedPlugins) {
this.knownUpgrades[plugin.name] = available.find(x => x.name === plugin.name && semver.gt(x.version, plugin.version))
}
})
}
isAlreadyInstalled (plugin: IPluginInfo): boolean {
return this.pluginManager.installedPlugins.some(x => x.name === plugin.name)
}
async installPlugin (plugin: IPluginInfo): Promise<void> {
this.busy = true
}
async upgradePlugin (plugin: IPluginInfo): Promise<void> {
return this.installPlugin(this.knownUpgrades[plugin.name])
}
}

View File

@@ -0,0 +1,31 @@
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'
import { SettingsTabProvider } from 'terminus-settings'
import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.component'
import { PluginManagerService } from './services/pluginManager.service'
import { PluginsSettingsTabProvider } from './settings'
@NgModule({
imports: [
BrowserModule,
FormsModule,
NgbModule,
],
providers: [
{ provide: SettingsTabProvider, useClass: PluginsSettingsTabProvider, multi: true },
PluginManagerService,
],
entryComponents: [
PluginsSettingsTabComponent,
],
declarations: [
PluginsSettingsTabComponent,
],
})
export default class PluginManagerModule { }
export { PluginManagerService }

View File

@@ -0,0 +1,49 @@
import * as fs from 'fs-promise'
import { Observable } from 'rxjs'
import { Injectable } from '@angular/core'
import { Logger, LogService } from 'terminus-core'
import axios from 'axios'
const NAME_PREFIX = 'terminus-'
export interface IPluginInfo {
name: string
description: string
packageName: string
isBuiltin: boolean
version: string
homepage?: string
path?: string
}
@Injectable()
export class PluginManagerService {
logger: Logger
builtinPluginsPath: string = (window as any).builtinPluginsPath
userPluginsPath: string = (window as any).userPluginsPath
installedPlugins: IPluginInfo[] = (window as any).installedPlugins
constructor (
log: LogService,
) {
this.logger = log.create('pluginManager')
}
listAvailable (query?: string): Observable<IPluginInfo[]> {
return Observable
.fromPromise(
axios.get(`https://www.npmjs.com/-/search?text=${NAME_PREFIX}+${encodeURIComponent(query || '')}&from=0&size=1000`)
)
.map(response => response.data.objects.map(item => ({
name: item.package.name.substring(NAME_PREFIX.length),
packageName: item.package.name,
description: item.package.description,
version: item.package.version,
homepage: item.package.links.homepage,
})))
.map(plugins => plugins.filter(x => x.packageName.startsWith(NAME_PREFIX)))
}
async installPlugin (plugin: IPluginInfo) {
}
}

View File

@@ -0,0 +1,13 @@
import { Injectable } from '@angular/core'
import { SettingsTabProvider, ComponentType } from 'terminus-settings'
import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.component'
@Injectable()
export class PluginsSettingsTabProvider extends SettingsTabProvider {
title = 'Plugins'
getComponentType (): ComponentType {
return PluginsSettingsTabComponent
}
}

View File

@@ -0,0 +1,26 @@
{
"compilerOptions": {
"baseUrl": "src",
"module": "commonjs",
"target": "es2016",
"declaration": false,
"noImplicitAny": false,
"removeComments": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"sourceMap": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedParameters": true,
"noUnusedLocals": true,
"declaration": true,
"declarationDir": "dist",
"lib": [
"dom",
"es2015",
"es7"
]
},
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,49 @@
const path = require('path')
module.exports = {
target: 'node',
entry: 'src/index.ts',
devtool: 'source-map',
context: __dirname,
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
pathinfo: true,
libraryTarget: 'umd',
devtoolModuleFilenameTemplate: 'webpack-terminus-plugin-manager:///[resource-path]',
},
resolve: {
modules: ['.', 'src', 'node_modules', '../app/node_modules'].map(x => path.join(__dirname, x)),
extensions: ['.ts', '.js'],
},
module: {
loaders: [
{
test: /\.ts$/,
loader: 'awesome-typescript-loader',
query: {
configFileName: path.resolve(__dirname, 'tsconfig.json'),
paths: {
"terminus-*": [path.resolve(__dirname, '../terminus-*')],
"*": [path.resolve(__dirname, '../app/node_modules/*')],
}
}
},
{ test: /\.pug$/, use: ['apply-loader', 'pug-loader'] },
{ test: /\.scss$/, use: ['to-string-loader', 'css-loader', 'sass-loader'] },
]
},
externals: [
'fs',
'fs-promise',
'font-manager',
'path',
'node-pty',
'mz/child_process',
'winreg',
/^rxjs/,
/^@angular/,
/^@ng-bootstrap/,
/^terminus-/,
]
}