From ed98bd184e05a9344abbdea16193c558b9327cfa Mon Sep 17 00:00:00 2001 From: DmitryAstafyev Date: Wed, 12 Feb 2025 12:05:50 +0100 Subject: [PATCH] Basic view for PluginManager --- application/client/src/app/service/bridge.ts | 10 +- application/client/src/app/service/plugins.ts | 2 +- .../src/app/ui/tabs/plugins/component.ts | 63 ++++------- .../client/src/app/ui/tabs/plugins/desc.ts | 61 ++++++++++ .../app/ui/tabs/plugins/details/component.ts | 25 ++++ .../app/ui/tabs/plugins/details/styles.less | 12 ++ .../app/ui/tabs/plugins/details/template.html | 4 + .../src/app/ui/tabs/plugins/list/component.ts | 64 +++++++++++ .../src/app/ui/tabs/plugins/list/styles.less | 8 ++ .../app/ui/tabs/plugins/list/template.html | 18 +++ .../client/src/app/ui/tabs/plugins/module.ts | 10 +- .../app/ui/tabs/plugins/plugin/component.ts | 28 +++++ .../app/ui/tabs/plugins/plugin/styles.less | 26 +++++ .../app/ui/tabs/plugins/plugin/template.html | 10 ++ .../src/app/ui/tabs/plugins/provider.ts | 107 ++++++++++++++++++ .../src/app/ui/tabs/plugins/styles.less | 12 ++ .../src/app/ui/tabs/plugins/template.html | 34 ++++-- application/platform/types/files.ts | 7 ++ 18 files changed, 440 insertions(+), 61 deletions(-) create mode 100644 application/client/src/app/ui/tabs/plugins/desc.ts create mode 100644 application/client/src/app/ui/tabs/plugins/details/component.ts create mode 100644 application/client/src/app/ui/tabs/plugins/details/styles.less create mode 100644 application/client/src/app/ui/tabs/plugins/details/template.html create mode 100644 application/client/src/app/ui/tabs/plugins/list/component.ts create mode 100644 application/client/src/app/ui/tabs/plugins/list/styles.less create mode 100644 application/client/src/app/ui/tabs/plugins/list/template.html create mode 100644 application/client/src/app/ui/tabs/plugins/plugin/component.ts create mode 100644 application/client/src/app/ui/tabs/plugins/plugin/styles.less create mode 100644 application/client/src/app/ui/tabs/plugins/plugin/template.html create mode 100644 application/client/src/app/ui/tabs/plugins/provider.ts diff --git a/application/client/src/app/service/bridge.ts b/application/client/src/app/service/bridge.ts index ce5cd248a..027f55af7 100644 --- a/application/client/src/app/service/bridge.ts +++ b/application/client/src/app/service/bridge.ts @@ -1,6 +1,6 @@ import { SetupService, Interface, Implementation, register } from '@platform/entity/service'; import { services } from '@register/services'; -import { File, Entity } from '@platform/types/files'; +import { File, Entity, ParsedPath } from '@platform/types/files'; import { FolderEntity } from '@platform/types/bindings'; import { FileType } from '@platform/types/observe/types/file'; import { DltStatisticInfo, Profile } from '@platform/types/bindings'; @@ -55,9 +55,7 @@ export class Service extends Implementation { isBinary(file: string): Promise; checksumWithCache(filename: string): Promise; exists(path: string): Promise; - name( - path: string, - ): Promise<{ name: string; filename: string; parent: string; ext: string }>; + name(path: string): Promise; cp(src: string, dest: string): Promise; copy(files: string[], dest: string): Promise; read(filename: string): Promise; @@ -185,9 +183,7 @@ export class Service extends Implementation { return response.exists; }); }, - name: ( - path: string, - ): Promise<{ name: string; filename: string; parent: string; ext: string }> => { + name: (path: string): Promise => { return Requests.IpcRequest.send( Requests.File.Name.Response, new Requests.File.Name.Request({ diff --git a/application/client/src/app/service/plugins.ts b/application/client/src/app/service/plugins.ts index 733c91113..a5d8eaf6e 100644 --- a/application/client/src/app/service/plugins.ts +++ b/application/client/src/app/service/plugins.ts @@ -1,8 +1,8 @@ import { SetupService, Interface, Implementation, register } from '@platform/entity/service'; import { services } from '@register/services'; +import { PluginEntity } from '@platform/types/bindings/plugins'; import * as Requests from '@platform/ipc/request/index'; -import { PluginEntity } from '@platform/types/bindings/plugins'; @SetupService(services['plugins']) export class Service extends Implementation { diff --git a/application/client/src/app/ui/tabs/plugins/component.ts b/application/client/src/app/ui/tabs/plugins/component.ts index 12af63eed..167165a7d 100644 --- a/application/client/src/app/ui/tabs/plugins/component.ts +++ b/application/client/src/app/ui/tabs/plugins/component.ts @@ -1,9 +1,9 @@ -import { Component, ChangeDetectorRef, AfterContentInit, Input } from '@angular/core'; +import { Component, ChangeDetectorRef, AfterContentInit, OnDestroy } from '@angular/core'; import { Ilc, IlcInterface } from '@env/decorators/component'; import { Initial } from '@env/decorators/initial'; import { ChangesDetector } from '@ui/env/extentions/changes'; -//TODO: -// import { State } from './state'; +import { Provider } from './provider'; +import { Target } from './list/component'; @Component({ selector: 'app-tabs-plugins-manager', @@ -13,57 +13,34 @@ import { ChangesDetector } from '@ui/env/extentions/changes'; }) @Initial() @Ilc() -export class PluginsManager extends ChangesDetector implements AfterContentInit { - @Input() public allPlugins!: string; - @Input() public activePlugins!: string; +export class PluginsManager extends ChangesDetector implements AfterContentInit, OnDestroy { + public provider: Provider = new Provider(); + public get Target(): typeof Target { + return Target; + } constructor(cdRef: ChangeDetectorRef) { super(cdRef); } - public onReloadClick(): void { - this.allPlugins = 'Loading ...'; - this.activePlugins = 'Loading ...'; - this.detectChanges(); - - this.ilc() - .services.system.plugins.reloadPlugins() - - .then(() => { - this.loadPlugins(); - }) - .catch((err: Error) => { - this.log().error(`Error while reloading: ${err}`); - }); + public ngOnDestroy(): void { + this.provider.destroy(); } - loadPlugins(): void { - this.ilc() - .services.system.plugins.allPlugins() - .then((plugins) => { - const plugins_pretty = JSON.stringify(plugins, null, 2); - - this.allPlugins = plugins_pretty; + public ngAfterContentInit(): void { + this.env().subscriber.register( + this.provider.subjects.get().state.subscribe(() => { this.detectChanges(); - }) - .catch((err: Error) => { - this.log().error(`Fail to get all plugins: ${err}`); - }); - - this.ilc() - .services.system.plugins.activePlugins() - .then((activePlugins) => { - const plugins_pretty = JSON.stringify(activePlugins, null, 2); - this.activePlugins = plugins_pretty; + }), + this.provider.subjects.get().selected.subscribe(() => { this.detectChanges(); - }) - .catch((err: Error) => { - this.log().error(`Fail to get active plugins: ${err}`); - }); + }), + ); + this.provider.load(); } - public ngAfterContentInit(): void { - this.loadPlugins(); + public reload() { + this.provider.load(); } } diff --git a/application/client/src/app/ui/tabs/plugins/desc.ts b/application/client/src/app/ui/tabs/plugins/desc.ts new file mode 100644 index 000000000..469a234d4 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/desc.ts @@ -0,0 +1,61 @@ +import { PluginEntity } from '@platform/types/bindings/plugins'; +import { bridge } from '@service/bridge'; +import { ParsedPath } from '@platform/types/files'; + +export class PluginDesc { + public name: string = ''; + public desc: string = ''; + public icon: string = ''; + public path: ParsedPath | undefined; + + constructor(public readonly entity: PluginEntity) {} + + public load(): Promise { + return bridge + .files() + .name(this.entity.dir_path) + .then((path: ParsedPath) => { + this.path = path; + this.update(); + }); + } + + protected update() { + this.icon = this.getIcon(); + this.name = this.getName(); + this.desc = this.getDesc(); + } + + protected getIcon(): string { + switch (this.entity.plugin_type) { + case 'Parser': + return 'swap_vert'; + case 'ByteSource': + return 'input'; + } + } + + protected getName(): string { + if (!this.entity.metadata && !this.path) { + return this.entity.dir_path; + } else if (!this.entity.metadata && this.path) { + return this.path.name; + } else if (this.entity.metadata) { + return this.entity.metadata.name; + } else { + return this.entity.dir_path; + } + } + + protected getDesc(): string { + if (!this.entity.metadata && !this.path) { + return this.entity.dir_path; + } else if (!this.entity.metadata && this.path) { + return this.path.name; + } else if (this.entity.metadata && this.entity.metadata.description) { + return this.entity.metadata.description; + } else { + return this.entity.dir_path; + } + } +} diff --git a/application/client/src/app/ui/tabs/plugins/details/component.ts b/application/client/src/app/ui/tabs/plugins/details/component.ts new file mode 100644 index 000000000..52b0f85a0 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/details/component.ts @@ -0,0 +1,25 @@ +import { Component, ChangeDetectorRef, Input } from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { PluginDesc } from '../desc'; +import { Provider } from '../provider'; + +@Component({ + selector: 'app-plugins-manager-details', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + standalone: false, +}) +@Initial() +@Ilc() +export class Details extends ChangesDetector { + @Input() public provider!: Provider; + @Input() public plugin!: PluginDesc; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } +} + +export interface Details extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/plugins/details/styles.less b/application/client/src/app/ui/tabs/plugins/details/styles.less new file mode 100644 index 000000000..60919c817 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/details/styles.less @@ -0,0 +1,12 @@ +@import '../../../styles/variables.less'; + +:host { + position: relative; + & div.info { + margin: 0 6px; + overflow: hidden; + & p { + text-align: left; + } + } +} diff --git a/application/client/src/app/ui/tabs/plugins/details/template.html b/application/client/src/app/ui/tabs/plugins/details/template.html new file mode 100644 index 000000000..39598102b --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/details/template.html @@ -0,0 +1,4 @@ +
+

{{plugin.name}}

+

{{plugin.desc}}

+
diff --git a/application/client/src/app/ui/tabs/plugins/list/component.ts b/application/client/src/app/ui/tabs/plugins/list/component.ts new file mode 100644 index 000000000..130033b80 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/list/component.ts @@ -0,0 +1,64 @@ +import { Component, ChangeDetectorRef, AfterContentInit, Input } from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { Provider } from '../provider'; +import { PluginDesc } from '../desc'; + +export enum Target { + Active, + Available, +} + +@Component({ + selector: 'app-plugins-manager-list', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + standalone: false, +}) +@Initial() +@Ilc() +export class List extends ChangesDetector implements AfterContentInit { + @Input() public provider!: Provider; + @Input() public target!: Target; + + public plugins: PluginDesc[] = []; + + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } + + public ngAfterContentInit(): void { + this.env().subscriber.register( + this.provider.subjects.get().load.subscribe(() => { + this.update(); + }), + this.provider.subjects.get().state.subscribe(() => { + this.update(); + }), + ); + } + + public getTitle(): string { + switch (this.target) { + case Target.Active: + return 'Active Plugins'; + case Target.Available: + return 'Available Plugins'; + } + } + + protected update() { + switch (this.target) { + case Target.Active: + this.plugins = this.provider.get().active(); + break; + case Target.Available: + this.plugins = this.provider.get().available(); + break; + } + this.detectChanges(); + } +} + +export interface List extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/plugins/list/styles.less b/application/client/src/app/ui/tabs/plugins/list/styles.less new file mode 100644 index 000000000..4a9052cb3 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/list/styles.less @@ -0,0 +1,8 @@ +@import '../../../styles/variables.less'; + +:host { + position: relative; + display: block; + width: 100%; + overflow: hidden; +} diff --git a/application/client/src/app/ui/tabs/plugins/list/template.html b/application/client/src/app/ui/tabs/plugins/list/template.html new file mode 100644 index 000000000..c65716879 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/list/template.html @@ -0,0 +1,18 @@ + + {{getTitle()}} + + + + + + + + +

No plugins

+
+
+
diff --git a/application/client/src/app/ui/tabs/plugins/module.ts b/application/client/src/app/ui/tabs/plugins/module.ts index f0d08aa9a..77635c464 100644 --- a/application/client/src/app/ui/tabs/plugins/module.ts +++ b/application/client/src/app/ui/tabs/plugins/module.ts @@ -1,14 +1,18 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { FormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { MatButtonModule } from '@angular/material/button'; +import { MatProgressBarModule } from '@angular/material/progress-bar'; +import { MatIconModule } from '@angular/material/icon'; import { PluginsManager } from './component'; +import { List } from './list/component'; +import { Plugin } from './plugin/component'; +import { Details } from './details/component'; @NgModule({ - imports: [CommonModule, FormsModule, MatCardModule, MatButtonModule], - declarations: [PluginsManager], + imports: [CommonModule, MatCardModule, MatButtonModule, MatProgressBarModule, MatIconModule], + declarations: [PluginsManager, Plugin, List, Details], exports: [PluginsManager], bootstrap: [PluginsManager], }) diff --git a/application/client/src/app/ui/tabs/plugins/plugin/component.ts b/application/client/src/app/ui/tabs/plugins/plugin/component.ts new file mode 100644 index 000000000..e90a84156 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/plugin/component.ts @@ -0,0 +1,28 @@ +import { Component, ChangeDetectorRef, Input, HostListener } from '@angular/core'; +import { Ilc, IlcInterface } from '@env/decorators/component'; +import { Initial } from '@env/decorators/initial'; +import { ChangesDetector } from '@ui/env/extentions/changes'; +import { PluginDesc } from '../desc'; +import { Provider } from '../provider'; + +@Component({ + selector: 'app-plugins-manager-plugin', + templateUrl: './template.html', + styleUrls: ['./styles.less'], + standalone: false, +}) +@Initial() +@Ilc() +export class Plugin extends ChangesDetector { + @Input() public provider!: Provider; + @Input() public plugin!: PluginDesc; + + @HostListener('click', ['$event']) onClick(_event: MouseEvent) { + this.provider.select(this.plugin.entity.dir_path); + } + constructor(cdRef: ChangeDetectorRef) { + super(cdRef); + } +} + +export interface Plugin extends IlcInterface {} diff --git a/application/client/src/app/ui/tabs/plugins/plugin/styles.less b/application/client/src/app/ui/tabs/plugins/plugin/styles.less new file mode 100644 index 000000000..75640804c --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/plugin/styles.less @@ -0,0 +1,26 @@ +@import '../../../styles/variables.less'; + +:host { + position: relative; + display: flex; + align-items: center; + flex-direction: row; + cursor: default; + & div.icon { + & mat-icon { + font-size: 18px; + height: 16px; + } + } + & div.info { + flex: 1; + margin: 0 6px; + overflow: hidden; + & p { + text-align: left; + } + } + &:hover { + background: @scheme-color-3-75; + } +} diff --git a/application/client/src/app/ui/tabs/plugins/plugin/template.html b/application/client/src/app/ui/tabs/plugins/plugin/template.html new file mode 100644 index 000000000..f6193c6de --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/plugin/template.html @@ -0,0 +1,10 @@ +
+ {{plugin.icon}} +
+
+

{{plugin.name}}

+

{{plugin.desc}}

+
+
+ menu +
diff --git a/application/client/src/app/ui/tabs/plugins/provider.ts b/application/client/src/app/ui/tabs/plugins/provider.ts new file mode 100644 index 000000000..a7f6c6f44 --- /dev/null +++ b/application/client/src/app/ui/tabs/plugins/provider.ts @@ -0,0 +1,107 @@ +import { plugins } from '@service/plugins'; +import { PluginEntity } from '@platform/types/bindings/plugins'; +import { Subjects, Subject } from '@platform/env/subscription'; +import { scope } from '@platform/env/scope'; +import { Logger } from '@platform/log'; +import { PluginDesc } from './desc'; + +export class Provider { + protected log: Logger; + protected readonly plugins: { + all: PluginDesc[]; + active: PluginDesc[]; + } = { + all: [], + active: [], + }; + + public subjects: Subjects<{ + load: Subject; + state: Subject; + selected: Subject; + }> = new Subjects({ + load: new Subject(), + state: new Subject(), + selected: new Subject(), + }); + public selected: PluginDesc | undefined; + public state: { + loading: boolean; + error: string | undefined; + } = { + loading: false, + error: undefined, + }; + + constructor() { + this.log = scope.getLogger(`Plugins Provider`); + } + + public load(): Promise { + if (this.state.loading) { + return Promise.resolve(); + } + this.state.loading = true; + this.subjects.get().state.emit(); + return Promise.all([plugins.allPlugins(), plugins.activePlugins()]) + .then((loaded: [PluginEntity[], PluginEntity[]]) => { + this.plugins.all = loaded[0].map((en) => new PluginDesc(en)); + this.plugins.active = loaded[1].map((en) => new PluginDesc(en)); + this.state.error = undefined; + }) + .catch((err: Error) => { + this.log.error(`Fail to load plugins, due error: ${err.message}`); + this.state.error = err.message; + }) + .finally(() => { + Promise.all([ + ...this.plugins.active.map((pl) => pl.load()), + ...this.plugins.all.map((pl) => pl.load()), + ]) + .catch((err: Error) => { + this.log.error(`Fail load some plugins data: ${err.message}`); + }) + .then(() => { + this.state.loading = false; + this.subjects.get().state.emit(); + this.subjects.get().load.emit(); + }); + }); + } + + public destroy() { + this.subjects.destroy(); + } + + public get(): { + /// List of active plugins + active(): PluginDesc[]; + /// List of not active plugins + available(): PluginDesc[]; + /// List of all plugins (active + available) + all(): PluginDesc[]; + } { + return { + active: (): PluginDesc[] => { + return this.plugins.active; + }, + available: (): PluginDesc[] => { + const all = this.plugins.all; + return this.plugins.all.filter((plugin) => { + return ( + all.find((pl) => pl.entity.dir_path == plugin.entity.dir_path) == undefined + ); + }); + }, + all: (): PluginDesc[] => { + return this.plugins.all; + }, + }; + } + public select(path: string) { + this.selected = [...this.plugins.active, ...this.plugins.all].find( + (pl) => pl.entity.dir_path === path, + ); + this.subjects.get().selected.emit(path); + } +} diff --git a/application/client/src/app/ui/tabs/plugins/styles.less b/application/client/src/app/ui/tabs/plugins/styles.less index 61df92ebb..3cd913975 100644 --- a/application/client/src/app/ui/tabs/plugins/styles.less +++ b/application/client/src/app/ui/tabs/plugins/styles.less @@ -73,4 +73,16 @@ & button { min-width: 150px; } + & div.controlls{ + position: relative; + width: 100%; + overflow: hidden; + text-align: right; + white-space: nowrap; + height: auto; + & button { + margin-left: 8px; + min-width:auto; + } + } } diff --git a/application/client/src/app/ui/tabs/plugins/template.html b/application/client/src/app/ui/tabs/plugins/template.html index ca8772e99..5d6cfe5e5 100644 --- a/application/client/src/app/ui/tabs/plugins/template.html +++ b/application/client/src/app/ui/tabs/plugins/template.html @@ -2,17 +2,37 @@
-

All Plugins:

-
{{allPlugins}}
-
-

Active Plugins:

-
{{activePlugins}}
+ +

Select plugin to see details

+
+
+ + - - + +
+ + +
diff --git a/application/platform/types/files.ts b/application/platform/types/files.ts index 24c7c171c..db5cae051 100644 --- a/application/platform/types/files.ts +++ b/application/platform/types/files.ts @@ -87,6 +87,13 @@ export interface Stat { birthtimeMs: number; } +export interface ParsedPath { + name: string; + filename: string; + parent: string; + ext: string; +} + const FILE_NAME_REG = /[^/\\]*$/gi; const FILE_EXT_REG = /[^/\\.]*$/gi; export function getFileName(filename: string): string {