-
- @for (chain of chains; track $index) {
-
-
-
-
-
-
-
-
- {{ chain | json }}
-
+
+
+
+
Color chain
+
+
+ @for (chain of chains; track $index) {
+
+
+
+
+
+
+
+ {{ chain | json }}
+
- }
+ }
+
}
\ No newline at end of file
diff --git a/projects/demo-showcase/src/app/page-structure-viewer/sections/section-chains.component.ts b/projects/demo-showcase/src/app/page-structure-viewer/sections/section-chains.component.ts
index e90555a..9a3de5c 100644
--- a/projects/demo-showcase/src/app/page-structure-viewer/sections/section-chains.component.ts
+++ b/projects/demo-showcase/src/app/page-structure-viewer/sections/section-chains.component.ts
@@ -1,4 +1,4 @@
-import { NgxStructureViewerComponent, Locus, Settings, Source } from '@ngx-structure-viewer';
+import { NgxStructureViewerComponent, Locus, Settings, Source, StructureService, PluginService } from '@ngx-structure-viewer';
import { Observable, interval, map, shareReplay, startWith } from 'rxjs';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -9,6 +9,10 @@ import { CommonModule } from '@angular/common';
NgxStructureViewerComponent,
CommonModule,
],
+ providers: [
+ StructureService,
+ PluginService,
+ ],
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: './section-chains.component.html',
@@ -22,7 +26,10 @@ export class SectionChainsComponent {
readonly chains$: Observable
;
- constructor() {
+ constructor(
+ public structureService: StructureService,
+ public pluginService: PluginService,
+ ) {
// Define settings
this.settings = {
'background-color': '#2b3035ff',
@@ -62,6 +69,11 @@ export class SectionChainsComponent {
// Cache result
shareReplay(1),
);
+
+ // TODO Check that service has been imported
+ structureService.structure$.subscribe(() => {
+ console.log('Hello, world!');
+ });
}
}
diff --git a/projects/demo-showcase/src/app/page-structure-viewer/sections/section-sources.component.ts b/projects/demo-showcase/src/app/page-structure-viewer/sections/section-sources.component.ts
index c296018..9ba75ee 100644
--- a/projects/demo-showcase/src/app/page-structure-viewer/sections/section-sources.component.ts
+++ b/projects/demo-showcase/src/app/page-structure-viewer/sections/section-sources.component.ts
@@ -2,7 +2,7 @@ import { NgxStructureViewerComponent, Settings, Source } from '@ngx-structure-vi
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
-import { Observable, map, of } from 'rxjs';
+import { Observable, map, of, tap } from 'rxjs';
@Component({
selector: 'app-section-sources',
@@ -51,6 +51,8 @@ export class SectionSourcesComponent {
map((data: string) => new Blob([data], { type: 'text/plain' })),
// Provide local source
map((data: Blob) => ({ ...this.local, data })),
+ // TODO Remove this
+ tap((source) => console.log('Local source:', source)),
);
this.remote$ = of({ ...this.remote, link: 'https://files.rcsb.org/view/8VAP.cif' });
diff --git a/projects/demo-showcase/src/styles.scss b/projects/demo-showcase/src/styles.scss
index 1003c29..f237ba2 100644
--- a/projects/demo-showcase/src/styles.scss
+++ b/projects/demo-showcase/src/styles.scss
@@ -35,7 +35,8 @@
@import "../../../node_modules/bootstrap/scss/utilities/api";
// 8. Add additional custom code here
-@import "../../../node_modules/bootstrap-icons/font/bootstrap-icons.scss";
+//@import "../../../node_modules/bootstrap-icons/font/bootstrap-icons.scss";
@import "./styles/background";
@import "./styles/scaffold";
@import "./styles/utils";
+
diff --git a/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.html b/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.html
index 313daea..ef88ef9 100644
--- a/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.html
+++ b/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.html
@@ -1,12 +1,22 @@
-
- @if (label) {
+
+ @if (labelLeft) {
@for(kv of featuresService.traces | keyvalue; track $index) {
-
\ No newline at end of file
+
+ @if(labelRight) {
+
+ @for(kv of featuresService.traces | keyvalue; track $index) {
+
+
+
+
+ }
+ }
+
diff --git a/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.ts b/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.ts
index 86f5dc1..2e58195 100644
--- a/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.ts
+++ b/projects/ngx-features-viewer/src/lib/ngx-features-viewer.component.ts
@@ -1,18 +1,32 @@
-// prettier-ignore
-import { AfterViewInit, ChangeDetectionStrategy, Component, ContentChild, ElementRef, HostListener, Input, OnChanges, OnDestroy, SimpleChanges, ViewChild, ViewEncapsulation } from '@angular/core';
-import { Observable, Subscription, tap, switchMap } from 'rxjs';
-import { CommonModule } from '@angular/common';
+import {
+ AfterContentInit,
+ AfterViewInit,
+ ChangeDetectionStrategy,
+ Component,
+ ContentChildren,
+ ElementRef,
+ HostListener,
+ Input,
+ OnChanges,
+ OnDestroy,
+ QueryList,
+ SimpleChanges,
+ ViewChild,
+ ViewEncapsulation
+} from '@angular/core';
+import {Observable, Subscription, switchMap, tap} from 'rxjs';
+import {CommonModule} from '@angular/common';
// Custom components
-import { NgxFeaturesViewerLabelDirective } from './ngx-features-viewer.directive';
+import {NgxFeaturesViewerLabelDirective} from './ngx-features-viewer.directive';
// Custom providers
-import { InitializeService } from './services/initialize.service';
-import { FeaturesService } from './services/features.service';
-import { ResizeService } from './services/resize.service';
-import { ZoomService } from './services/zoom.service';
-import { DrawService } from './services/draw.service';
+import {InitializeService} from './services/initialize.service';
+import {FeaturesService} from './services/features.service';
+import {ResizeService} from './services/resize.service';
+import {ZoomService} from './services/zoom.service';
+import {DrawService} from './services/draw.service';
// Custom data types
-import { Hierarchy } from './hierarchy';
-import { Settings } from './settings';
+import {Hierarchy} from './hierarchy';
+import {Settings} from './settings';
// TODO Define sequence type
@@ -38,13 +52,17 @@ export type Sequence = Array;
changeDetection: ChangeDetectionStrategy.OnPush,
encapsulation: ViewEncapsulation.None,
})
-export class NgxFeaturesViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
-
+export class NgxFeaturesViewerComponent implements AfterViewInit, AfterContentInit, OnChanges, OnDestroy {
+
@ViewChild('root')
public _root!: ElementRef;
- @ContentChild(NgxFeaturesViewerLabelDirective)
- public label?: NgxFeaturesViewerLabelDirective;
+ @ContentChildren(NgxFeaturesViewerLabelDirective)
+ public labels?: QueryList;
+
+ public labelLeft?: NgxFeaturesViewerLabelDirective;
+
+ public labelRight?: NgxFeaturesViewerLabelDirective;
@Input()
public set settings(settings: Settings) {
@@ -90,7 +108,7 @@ export class NgxFeaturesViewerComponent implements AfterViewInit, OnChanges, OnD
// Initialize zoom scale
tap(() => {
// const { width, height } = this.resizeService;
- const { left: ms, right: me, bottom: mb } = this.resizeService.margin;
+ const {left: ms, right: me, bottom: mb} = this.resizeService.margin;
const h = this.resizeService.height;
const w = this.resizeService.width;
// // Define number of residues in sequnce
@@ -123,7 +141,7 @@ export class NgxFeaturesViewerComponent implements AfterViewInit, OnChanges, OnD
this._update = this.update$.subscribe();
}
- ngOnChanges(changes: SimpleChanges): void {
+ public ngOnChanges(changes: SimpleChanges): void {
// Case input sequence changes
if (changes && changes['sequence']) {
// Emit sequence
@@ -131,12 +149,33 @@ export class NgxFeaturesViewerComponent implements AfterViewInit, OnChanges, OnD
}
}
- ngAfterViewInit(): void {
+ public ngAfterContentInit(): void {
+ // Case label templates are defined
+ if (this.labels) {
+ // Loop thorugh each label template
+ this.labels.forEach((label) => {
+ // Case both labels are defined, then throw error
+ if (this.labelLeft && this.labelRight) {
+ throw new Error('Only one label can be defined');
+ }
+ // Case label is left
+ if (label.where === 'left') {
+ this.labelLeft = label;
+ }
+ // Case label is right
+ if (label.where === 'right') {
+ this.labelRight = label;
+ }
+ });
+ }
+ }
+
+ public ngAfterViewInit(): void {
// Emit root element
this.initService.initialize$.next(this._root);
}
- ngOnDestroy(): void {
+ public ngOnDestroy(): void {
// Unsubscribe from update emission
this._update.unsubscribe();
}
diff --git a/projects/ngx-features-viewer/src/lib/ngx-features-viewer.directive.ts b/projects/ngx-features-viewer/src/lib/ngx-features-viewer.directive.ts
index a1c058d..66e74d2 100644
--- a/projects/ngx-features-viewer/src/lib/ngx-features-viewer.directive.ts
+++ b/projects/ngx-features-viewer/src/lib/ngx-features-viewer.directive.ts
@@ -1,4 +1,4 @@
-import { Directive, TemplateRef } from '@angular/core';
+import { Directive, Input, TemplateRef } from '@angular/core';
@Directive({
// eslint-disable-next-line @angular-eslint/directive-selector
@@ -7,6 +7,16 @@ import { Directive, TemplateRef } from '@angular/core';
})
export class NgxFeaturesViewerLabelDirective {
+ where: 'left' | 'right' = 'left';
+
+ @Input('ngx-features-viewer-label') set _where(value: 'left' | 'right' | '' | undefined) {
+ if (value === 'left' || value === 'right') {
+ this.where = value;
+ } else {
+ this.where = 'left';
+ }
+ }
+
constructor(public templateRef: TemplateRef) { }
}
diff --git a/projects/ngx-structure-viewer/package.json b/projects/ngx-structure-viewer/package.json
index 2b96bd8..ce3032c 100644
--- a/projects/ngx-structure-viewer/package.json
+++ b/projects/ngx-structure-viewer/package.json
@@ -1,6 +1,6 @@
{
"name": "ngx-structure-viewer",
- "version": "0.0.13",
+ "version": "0.0.15",
"license": "MIT",
"author": {
"name": "Damiano Clementel",
@@ -9,7 +9,7 @@
"peerDependencies": {
"@angular/common": "^17.0.0",
"@angular/core": "^17.0.0",
- "molstar": "^3.44.0",
+ "molstar": "^4.0.0",
"rxjs": "~7.8.1"
},
"dependencies": {
diff --git a/projects/ngx-structure-viewer/src/lib/ngx-structure-viewer.component.ts b/projects/ngx-structure-viewer/src/lib/ngx-structure-viewer.component.ts
index 029608b..a2dc5cf 100644
--- a/projects/ngx-structure-viewer/src/lib/ngx-structure-viewer.component.ts
+++ b/projects/ngx-structure-viewer/src/lib/ngx-structure-viewer.component.ts
@@ -1,4 +1,4 @@
-import { Component, ElementRef, Input, Output, ViewChild } from '@angular/core';
+import { Component, ElementRef, Injector, Input, Optional, Output, SkipSelf, ViewChild } from '@angular/core';
import { ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';
// Custom dependencies
@@ -21,12 +21,49 @@ import { Locus } from './interfaces/locus';
CommonModule,
],
providers: [
+ // Mandatory providers
RepresentationService,
HighlightService,
- StructureService,
SettingsService,
- PluginService,
CanvasService,
+ // Optional providers
+ // NOTE Those are created for the instance only if they are not provided by the parent component
+ {
+ provide: StructureService,
+ deps: [Injector, PluginService, [new Optional(), new SkipSelf(), StructureService],],
+ useFactory: (parentInjector: Injector, pluginService: PluginService, structureService?: StructureService) => {
+ // Case structure service is not provided, it must be created
+ if (!structureService) {
+ // Define injector for current component
+ const childInjector = Injector.create({
+ providers: [StructureService, { provide: PluginService, useValue: pluginService }],
+ parent: parentInjector
+ });
+ // get injected service
+ structureService = childInjector.get(StructureService);
+ }
+ // Return structure service
+ return structureService;
+ }
+ },
+ {
+ provide: PluginService,
+ deps: [Injector, [new Optional(), new SkipSelf(), PluginService]],
+ useFactory: (parentInjector: Injector, pluginService?: PluginService) => {
+ // Case plugin service is not provided, it must be created
+ if (!pluginService) {
+ // Define injector for current component
+ const childInjector = Injector.create({
+ providers: [PluginService],
+ parent: parentInjector
+ });
+ // get injected service
+ pluginService = childInjector.get(PluginService);
+ }
+ // Return plugin service
+ return pluginService;
+ }
+ }
],
standalone: true,
// Handle representation
diff --git a/projects/ngx-structure-viewer/src/lib/services/camera.service.ts b/projects/ngx-structure-viewer/src/lib/services/camera.service.ts
index 73fa4b5..5de6924 100644
--- a/projects/ngx-structure-viewer/src/lib/services/camera.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/camera.service.ts
@@ -1,8 +1,6 @@
import { Injectable } from '@angular/core';
-@Injectable({
- providedIn: 'root'
-})
+@Injectable()
export class CameraService {
constructor() { }
diff --git a/projects/ngx-structure-viewer/src/lib/services/canvas.service.ts b/projects/ngx-structure-viewer/src/lib/services/canvas.service.ts
index 8384c06..b9b5056 100644
--- a/projects/ngx-structure-viewer/src/lib/services/canvas.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/canvas.service.ts
@@ -9,10 +9,10 @@ import { PluginService } from './plugin.service';
import { fromHexString } from '../colors';
// React dependencies
-import { createElement } from 'react';
-import { render } from 'react-dom';
+import { createRoot } from 'react-dom/client';
+import { createElement } from "react";
-@Injectable({ providedIn: 'platform' })
+@Injectable()
export class CanvasService {
readonly initialize$ = new ReplaySubject(1);
@@ -38,56 +38,32 @@ export class CanvasService {
// Get HTML div container
const div = container.nativeElement as HTMLDivElement;
// Get first child of div container
- const canvas = div.firstElementChild as HTMLCanvasElement;
+ const canvas = div.firstElementChild as HTMLCanvasElement;
// Return both elements
return { div, canvas };
}),
// Combine with plugin initialization
combineLatestWith(plugin$),
+ // Combine with settings retrieval
+ combineLatestWith(settings$),
// Create plugin's React UI
- map(([{ div: container }, plugin]) => {
- // // const { spec, target, onBeforeUIRender, render } = options;
- // const ctx = new PluginUIContext(spec || DefaultPluginUISpec());
- // await ctx.init();
- // if (onBeforeUIRender) {
- // await onBeforeUIRender(ctx);
- // }
- // Render
- render(createElement(Plugin, { plugin }), container);
- // try {
- // await plugin.canvas3dInitialized;
- // } catch {
- // // Error reported in UI/console elsewhere.
- // }
- // Return initialize plugin
+ map(([[{ div: container }, plugin], settings]) => {
+ // Get background color
+ const [color, alpha] = fromHexString(settings['background-color']);
+ // Update canvas specs before rendering
+ this.pluginService.specs.canvas3d = {
+ renderer: {
+ backgroundColor: color,
+ pickingAlphaThreshold: alpha,
+ }
+ };
+ // Render using React
+ createRoot(container).render(createElement(Plugin, { plugin }));
+ // Return initialized plugin
return plugin;
}),
- // // Bind plugin to HTML elements
- // tap(([{ div: container, canvas }, plugin]) => plugin.initViewer(canvas, container)),
- // // Return initialized plugin
- // map(([, plugin]) => plugin),
// Cache result
shareReplay(1),
- // Combine with settings retrieval
- combineLatestWith(settings$),
- // Update plugin settings
- map(([plugin, settings]) => {
- // Case canvas3d is available
- if (plugin.canvas3d) {
- // Get background color
- const [ color, alpha ] = fromHexString(settings['background-color']);
- // Set background color
- plugin.canvas3d.setProps({
- // Change background color
- renderer: {
- backgroundColor: color,
- pickingAlphaThreshold: alpha,
- }
- });
- }
- // Return updated plugin
- return plugin;
- }),
);
}
}
diff --git a/projects/ngx-structure-viewer/src/lib/services/highlight.service.ts b/projects/ngx-structure-viewer/src/lib/services/highlight.service.ts
index 19d1806..9f34bb3 100644
--- a/projects/ngx-structure-viewer/src/lib/services/highlight.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/highlight.service.ts
@@ -10,7 +10,7 @@ import { Injectable, OnDestroy } from '@angular/core';
export type Highlights = Locus | undefined;
-@Injectable({ providedIn: 'platform' })
+@Injectable()
export class HighlightService implements OnDestroy {
readonly input$ = new ReplaySubject(1);
diff --git a/projects/ngx-structure-viewer/src/lib/services/plugin.service.ts b/projects/ngx-structure-viewer/src/lib/services/plugin.service.ts
index 8a45b0b..005c276 100644
--- a/projects/ngx-structure-viewer/src/lib/services/plugin.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/plugin.service.ts
@@ -1,25 +1,34 @@
-import { DefaultPluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
+import { DefaultPluginUISpec, PluginUISpec } from 'molstar/lib/mol-plugin-ui/spec';
import { PluginUIContext } from 'molstar/lib/mol-plugin-ui/context';
import { PluginConfig } from 'molstar/lib/mol-plugin/config';
// import { PluginContext } from 'molstar/lib/mol-plugin/context';
import { Observable, from, map, shareReplay } from 'rxjs';
import { Injectable } from '@angular/core';
+import { Color } from 'molstar/lib/mol-util/color'; // TODO Remove this
-@Injectable({ providedIn: 'platform' })
+@Injectable()
export class PluginService {
readonly plugin!: PluginUIContext;
+ readonly specs: PluginUISpec = { ...DefaultPluginUISpec(),
+ // Show commands
+ config: [
+ [PluginConfig.VolumeStreaming.Enabled, false]
+ ],
+ // TODO Remove this
+ canvas3d: {
+ renderer: {
+ backgroundColor: Color(0x000000),
+ }
+ }
+ };
+
readonly plugin$: Observable;
constructor() {
// Initialize plugin context
- this.plugin = new PluginUIContext({
- ...DefaultPluginUISpec(),
- config: [
- [PluginConfig.VolumeStreaming.Enabled, false]
- ]
- });
+ this.plugin = new PluginUIContext(this.specs);
// Define plugin initialization pipeline
this.plugin$ = from(this.plugin.init()).pipe(
// Get current plugin instance
@@ -27,43 +36,5 @@ export class PluginService {
// Cache result
shareReplay(1),
);
- // this.container$.pipe(
- // // Get outer HTML element
- // map((container) => {
- // // Get HTML div container
- // const div = container.nativeElement as HTMLDivElement;
- // // Get first child of div container
- // const canvas = div.firstElementChild as HTMLCanvasElement;
- // // Return both elements
- // return { div, canvas };
- // }),
- // // Bind plugin to HTML elements
- // tap(({ div: container, canvas }) => this.plugin.initViewer(canvas, container)),
- // // Initialize plugin
- // switchMap(() => ),
- // // Return initialized plugin
- // map(() => this.plugin),
- // // Cache result
- // shareReplay(1),
- // // Combine with settings retrieval
- // combineLatestWith(settings$),
- // // Update plugin settings
- // map(([plugin, settings]) => {
- // // Case canvas3d is available
- // if (plugin.canvas3d) {
- // // Get background color
- // const [ color, alpha ] = fromHexString(settings['background-color']);
- // // Set background color
- // plugin.canvas3d.setProps({
- // // Change background color
- // renderer: {
- // backgroundColor: color,
- // pickingAlphaThreshold: alpha,
- // }
- // });
- // }
- // // Return updated plugin
- // return plugin;
- // }),
}
}
diff --git a/projects/ngx-structure-viewer/src/lib/services/representation.service.ts b/projects/ngx-structure-viewer/src/lib/services/representation.service.ts
index abf52df..6c8f25e 100644
--- a/projects/ngx-structure-viewer/src/lib/services/representation.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/representation.service.ts
@@ -52,7 +52,7 @@ export function getFilteredBundle(layers: Overpaint.BundleLayer[], structure: St
return Overpaint.filter(merged, structure);
}
-@Injectable({ providedIn: 'platform' })
+@Injectable()
export class RepresentationService implements OnDestroy {
readonly loci$ = new ReplaySubject(1);
diff --git a/projects/ngx-structure-viewer/src/lib/services/select.service.ts b/projects/ngx-structure-viewer/src/lib/services/select.service.ts
index 195fac9..884f418 100644
--- a/projects/ngx-structure-viewer/src/lib/services/select.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/select.service.ts
@@ -1,8 +1,6 @@
import { Injectable } from '@angular/core';
-@Injectable({
- providedIn: 'root'
-})
+@Injectable()
export class SelectService {
constructor() { }
diff --git a/projects/ngx-structure-viewer/src/lib/services/settings.service.ts b/projects/ngx-structure-viewer/src/lib/services/settings.service.ts
index a8d936a..7b5efc7 100644
--- a/projects/ngx-structure-viewer/src/lib/services/settings.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/settings.service.ts
@@ -2,7 +2,7 @@ import { Settings } from '../interfaces/settings';
import { Injectable } from '@angular/core';
import { ReplaySubject } from 'rxjs';
-@Injectable({ providedIn: 'platform' })
+@Injectable()
export class SettingsService {
readonly settings$ = new ReplaySubject();
diff --git a/projects/ngx-structure-viewer/src/lib/services/structure.service.ts b/projects/ngx-structure-viewer/src/lib/services/structure.service.ts
index 49a99c1..e68f820 100644
--- a/projects/ngx-structure-viewer/src/lib/services/structure.service.ts
+++ b/projects/ngx-structure-viewer/src/lib/services/structure.service.ts
@@ -9,7 +9,7 @@ import { PluginService } from './plugin.service';
import { Residue, threeToOne } from '../interfaces/residue';
import { Source } from '../interfaces/source';
-@Injectable({ providedIn: 'platform' })
+@Injectable()
export class StructureService {
// eslint-disable-next-line @typescript-eslint/no-explicit-any