project rename

This commit is contained in:
Eugene Pankov
2021-06-29 23:57:04 +02:00
parent c61be3d52b
commit 43cd3318da
609 changed files with 510 additions and 530 deletions

View File

@@ -0,0 +1,78 @@
.alert.alert-danger(*ngIf='errorMessage')
strong Error in {{erroredPlugin}}:
pre {{errorMessage}}
.d-flex
h3.mb-1 Installed
button.btn.btn-outline-secondary.btn-sm.ml-auto((click)='openPluginsFolder()')
i.fas.fa-folder
span Plugins folder
.list-group.list-group-flush.mt-2
.list-group-item.d-flex.align-items-center(*ngFor='let plugin of pluginManager.installedPlugins')
toggle(
[ngModel]='isPluginEnabled(plugin)',
(ngModelChange)='togglePlugin(plugin)',
[disabled]='!canDisablePlugin(plugin)'
)
.mr-auto.d-flex.flex-column
div
strong {{plugin.name}}
small.text-muted.ml-1(*ngIf='!plugin.isBuiltin') {{plugin.version}} / {{plugin.author}}
small.text-muted.ml-1(*ngIf='plugin.isBuiltin') Built-in
small.text-warning.ml-1(*ngIf='!isPluginEnabled(plugin)') Disabled
a.text-muted.mb-0((click)='showPluginInfo(plugin)')
small {{plugin.description}}
button.btn.btn-primary.ml-2(
*ngIf='knownUpgrades[plugin.name]',
(click)='upgradePlugin(plugin)',
[disabled]='busy.has(plugin.name)'
)
i.fas.fa-fw.fa-arrow-up(*ngIf='busy.get(plugin.name) != BusyState.Installing')
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
span Upgrade ({{knownUpgrades[plugin.name].version}})
button.btn.btn-link.text-danger.ml-2(
(click)='uninstallPlugin(plugin)',
*ngIf='!plugin.isBuiltin',
[disabled]='busy.has(plugin.name)'
)
i.fas.fa-fw.fa-trash(*ngIf='busy.get(plugin.name) != BusyState.Uninstalling')
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Uninstalling')
div
h3.mt-4 Available
.input-group.mb-3
.input-group-prepend
.input-group-text
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='!availablePluginsReady')
i.fas.fa-fw.fa-search(*ngIf='availablePluginsReady')
input.form-control(
type='text',
[(ngModel)]='_1',
(ngModelChange)='searchAvailable(_1)',
placeholder='Search plugins'
)
.list-group.list-group-flush.mb-4(*ngIf='availablePlugins$')
ng-container(*ngFor='let plugin of (availablePlugins$|async)')
.list-group-item.d-flex.align-items-center(*ngIf='!isAlreadyInstalled(plugin)')
button.btn.btn-primary.mr-3(
(click)='installPlugin(plugin)',
[disabled]='busy.has(plugin.name)'
)
i.fas.fa-fw.fa-download(*ngIf='busy.get(plugin.name) != BusyState.Installing')
i.fas.fa-fw.fa-circle-notch.fa-spin(*ngIf='busy.get(plugin.name) == BusyState.Installing')
div((click)='showPluginInfo(plugin)')
div
strong {{plugin.name}}
small.text-muted.ml-1 {{plugin.version}}
small.text-muted.ml-1(*ngIf='!plugin.isOfficial') by {{plugin.author}}
small.text-success.ml-1(*ngIf='plugin.isOfficial')
i.fas.fa-check
span.ml-1 Official
small.text-muted {{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,134 @@
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { BehaviorSubject, Observable } from 'rxjs'
import { debounceTime, distinctUntilChanged, first, tap, flatMap, map } from 'rxjs/operators'
import semverGt from 'semver/functions/gt'
import { Component, Input } from '@angular/core'
import { ConfigService, PlatformService, PluginInfo } from 'tabby-core'
import { PluginManagerService } from '../services/pluginManager.service'
enum BusyState { Installing = 'Installing', Uninstalling = 'Uninstalling' }
const FORCE_ENABLE = ['tabby-core', 'tabby-settings']
/** @hidden */
@Component({
template: require('./pluginsSettingsTab.component.pug'),
styles: [require('./pluginsSettingsTab.component.scss')],
})
export class PluginsSettingsTabComponent {
BusyState = BusyState
@Input() availablePlugins$: Observable<PluginInfo[]>
@Input() availablePluginsQuery$ = new BehaviorSubject<string>('')
@Input() availablePluginsReady = false
@Input() knownUpgrades: Record<string, PluginInfo|null> = {}
@Input() busy = new Map<string, BusyState>()
@Input() erroredPlugin: string
@Input() errorMessage: string
constructor (
private config: ConfigService,
private platform: PlatformService,
public pluginManager: PluginManagerService
) {
}
ngOnInit () {
this.availablePlugins$ = this.availablePluginsQuery$
.asObservable()
.pipe(
debounceTime(200),
distinctUntilChanged(),
flatMap(query => {
this.availablePluginsReady = false
return this.pluginManager.listAvailable(query).pipe(tap(() => {
this.availablePluginsReady = true
}))
})
)
this.availablePlugins$.pipe(first(), map((plugins: PluginInfo[]) => {
plugins.sort((a, b) => a.name > b.name ? 1 : -1)
return plugins
})).subscribe(available => {
for (const plugin of this.pluginManager.installedPlugins) {
this.knownUpgrades[plugin.name] = available.find(x => x.name === plugin.name && semverGt(x.version, plugin.version)) || null
}
})
}
openPluginsFolder (): void {
this.platform.openPath(this.pluginManager.userPluginsPath)
}
searchAvailable (query: string) {
this.availablePluginsQuery$.next(query)
}
isAlreadyInstalled (plugin: PluginInfo): boolean {
return this.pluginManager.installedPlugins.some(x => x.name === plugin.name)
}
async installPlugin (plugin: PluginInfo): Promise<void> {
this.busy.set(plugin.name, BusyState.Installing)
try {
await this.pluginManager.installPlugin(plugin)
this.busy.delete(plugin.name)
this.config.requestRestart()
} catch (err) {
this.erroredPlugin = plugin.name
this.errorMessage = err
this.busy.delete(plugin.name)
throw err
}
}
async uninstallPlugin (plugin: PluginInfo): Promise<void> {
this.busy.set(plugin.name, BusyState.Uninstalling)
try {
await this.pluginManager.uninstallPlugin(plugin)
this.busy.delete(plugin.name)
this.config.requestRestart()
} catch (err) {
this.erroredPlugin = plugin.name
this.errorMessage = err
this.busy.delete(plugin.name)
throw err
}
}
async upgradePlugin (plugin: PluginInfo): Promise<void> {
return this.installPlugin(this.knownUpgrades[plugin.name]!)
}
showPluginInfo (plugin: PluginInfo) {
this.platform.openExternal('https://www.npmjs.com/package/' + plugin.packageName)
}
isPluginEnabled (plugin: PluginInfo) {
return !this.config.store.pluginBlacklist.includes(plugin.name)
}
canDisablePlugin (plugin: PluginInfo) {
return !FORCE_ENABLE.includes(plugin.packageName)
}
togglePlugin (plugin: PluginInfo) {
if (this.isPluginEnabled(plugin)) {
this.disablePlugin(plugin)
} else {
this.enablePlugin(plugin)
}
}
enablePlugin (plugin: PluginInfo) {
this.config.store.pluginBlacklist = this.config.store.pluginBlacklist.filter(x => x !== plugin.name)
this.config.save()
this.config.requestRestart()
}
disablePlugin (plugin: PluginInfo) {
this.config.store.pluginBlacklist = [...this.config.store.pluginBlacklist, plugin.name]
this.config.save()
this.config.requestRestart()
}
}

View File

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

View File

@@ -0,0 +1,79 @@
import axios from 'axios'
import { Observable, from, forkJoin } from 'rxjs'
import { map } from 'rxjs/operators'
import { Injectable, Inject } from '@angular/core'
import { Logger, LogService, PlatformService, BOOTSTRAP_DATA, BootstrapData, PluginInfo } from 'tabby-core'
const OFFICIAL_NPM_ACCOUNT = 'eugenepankov'
const BLACKLIST = [
'terminus-shell-selector', // superseded by profiles
]
@Injectable({ providedIn: 'root' })
export class PluginManagerService {
logger: Logger
userPluginsPath: string
installedPlugins: PluginInfo[]
private constructor (
log: LogService,
private platform: PlatformService,
@Inject(BOOTSTRAP_DATA) bootstrapData: BootstrapData,
) {
this.logger = log.create('pluginManager')
this.installedPlugins = bootstrapData.installedPlugins
this.userPluginsPath = bootstrapData.userPluginsPath
}
listAvailable (query?: string): Observable<PluginInfo[]> {
return forkJoin(
this._listAvailableInternal('tabby-', 'tabby-plugin', query),
this._listAvailableInternal('terminus-', 'terminus-plugin', query),
).pipe(map(x => x.reduce((a, b) => a.concat(b), [])))
}
_listAvailableInternal (namePrefix: string, keyword: string, query?: string): Observable<PluginInfo[]> {
return from(
axios.get(`https://www.npmjs.com/search?q=keywords%3A${keyword}+${encodeURIComponent(query ?? '')}&from=0&size=1000`, {
headers: {
'x-spiferack': '1',
},
})
).pipe(
map(response => response.data.objects.map(item => ({
name: item.package.name.substring(namePrefix.length),
packageName: item.package.name,
description: item.package.description,
version: item.package.version,
homepage: item.package.links.homepage,
author: (item.package.author || {}).name,
isOfficial: item.package.publisher.name === OFFICIAL_NPM_ACCOUNT,
}))),
map(plugins => plugins.filter(x => x.packageName.startsWith(namePrefix))),
map(plugins => plugins.filter(x => !BLACKLIST.includes(x.packageName))),
map(plugins => plugins.sort((a, b) => a.name.localeCompare(b.name))),
)
}
async installPlugin (plugin: PluginInfo): Promise<void> {
try {
await this.platform.installPlugin(plugin.packageName, plugin.version)
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
this.installedPlugins.push(plugin)
} catch (err) {
this.logger.error(err)
throw err
}
}
async uninstallPlugin (plugin: PluginInfo): Promise<void> {
try {
await this.platform.uninstallPlugin(plugin.packageName)
this.installedPlugins = this.installedPlugins.filter(x => x.packageName !== plugin.packageName)
} catch (err) {
this.logger.error(err)
throw err
}
}
}

View File

@@ -0,0 +1,15 @@
import { Injectable } from '@angular/core'
import { SettingsTabProvider } from 'tabby-settings'
import { PluginsSettingsTabComponent } from './components/pluginsSettingsTab.component'
/** @hidden */
@Injectable()
export class PluginsSettingsTabProvider extends SettingsTabProvider {
id = 'plugins'
title = 'Plugins'
getComponentType (): any {
return PluginsSettingsTabComponent
}
}