tabbed pages support

This commit is contained in:
Maria Teresa Paratore 2023-09-01 22:27:00 +02:00
parent 66985c8551
commit 5d5d33e354
22 changed files with 447 additions and 60 deletions

59
package-lock.json generated
View File

@ -23,12 +23,16 @@
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@material/theme": "^15.0.0-canary.b994146f6.0",
"@ng-bootstrap/ng-bootstrap": "15.1.0",
"@ng-bootstrap/ng-bootstrap": "^15.1.0",
"@popperjs/core": "2.11.8",
"@types/jquery": "^3.5.17",
"axios": "^1.4.0",
"bootstrap": "5.3.0",
"bootstrap": "^5.3.1",
"dayjs": "1.11.9",
"jquery": "^3.7.1",
"ngx-bootstrap": "^11.0.2",
"ngx-infinite-scroll": "16.0.0",
"popper.js": "^1.16.1",
"rxjs": "7.8.1",
"tslib": "2.6.0",
"zone.js": "0.13.1"
@ -6230,6 +6234,14 @@
"pretty-format": "^29.0.0"
}
},
"node_modules/@types/jquery": {
"version": "3.5.17",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.17.tgz",
"integrity": "sha512-U40tNEAGSTZ7R1OC6kGkD7f4TKW5DoVx6jd9kTB9mo5truFMi1m9Yohnw9kl1WpTPvDdj7zAw38LfCHSqnk5kA==",
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/jsdom": {
"version": "20.0.1",
"resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz",
@ -6340,6 +6352,11 @@
"@types/node": "*"
}
},
"node_modules/@types/sizzle": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz",
"integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ=="
},
"node_modules/@types/sockjs": {
"version": "0.3.33",
"resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.33.tgz",
@ -8538,9 +8555,9 @@
"dev": true
},
"node_modules/bootstrap": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.0.tgz",
"integrity": "sha512-UnBV3E3v4STVNQdms6jSGO2CvOkjUMdDAVR2V5N4uCMdaIkaQjbcEAMqRimDHIs4uqBYzDAKCQwCB+97tJgHQw==",
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.1.tgz",
"integrity": "sha512-jzwza3Yagduci2x0rr9MeFSORjcHpt0lRZukZPZQJT1Dth5qzV7XcgGqYzi39KGAVYR8QEDVoO0ubFKOxzMG+g==",
"funding": [
{
"type": "github",
@ -8552,7 +8569,7 @@
}
],
"peerDependencies": {
"@popperjs/core": "^2.11.7"
"@popperjs/core": "^2.11.8"
}
},
"node_modules/bplist-parser": {
@ -16576,6 +16593,11 @@
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/jquery": {
"version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg=="
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@ -18210,6 +18232,21 @@
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true
},
"node_modules/ngx-bootstrap": {
"version": "11.0.2",
"resolved": "https://registry.npmjs.org/ngx-bootstrap/-/ngx-bootstrap-11.0.2.tgz",
"integrity": "sha512-McvQ72XB6692Jus47jahWWwjpSCa6EtHMIqoyMewKCEHMv0ybDgVnOAdEsWKvwfulowHn7Y/jDjeiURwYJG9cQ==",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": "^16.0.0",
"@angular/common": "^16.0.0",
"@angular/core": "^16.0.0",
"@angular/forms": "^16.0.0",
"rxjs": "^6.5.3 || ^7.6.0"
}
},
"node_modules/ngx-infinite-scroll": {
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/ngx-infinite-scroll/-/ngx-infinite-scroll-16.0.0.tgz",
@ -19512,6 +19549,16 @@
"node": ">=4"
}
},
"node_modules/popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==",
"deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/popperjs"
}
},
"node_modules/portscanner": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz",

View File

@ -80,12 +80,16 @@
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@material/theme": "^15.0.0-canary.b994146f6.0",
"@ng-bootstrap/ng-bootstrap": "15.1.0",
"@ng-bootstrap/ng-bootstrap": "^15.1.0",
"@popperjs/core": "2.11.8",
"@types/jquery": "^3.5.17",
"axios": "^1.4.0",
"bootstrap": "5.3.0",
"bootstrap": "^5.3.1",
"dayjs": "1.11.9",
"jquery": "^3.7.1",
"ngx-bootstrap": "^11.0.2",
"ngx-infinite-scroll": "16.0.0",
"popper.js": "^1.16.1",
"rxjs": "7.8.1",
"tslib": "2.6.0",
"zone.js": "0.13.1"

View File

@ -0,0 +1,6 @@
<!-- VEDI QUA: https://angular.io/guide/class-binding -->
<div [hidden]="!active" [ngClass]="{ 'nav-link': true, active: active }">
<ng-content></ng-content>
<ng-container *ngIf="template" [ngTemplateOutlet]="template" [ngTemplateOutletContext]="{ IHostingnode: dataContext }"> </ng-container>
</div>

View File

@ -0,0 +1,18 @@
@import url('https://fonts.googleapis.com/css?family=Roboto:400,700|Material+Icons');
#hn-table-title {
text-align: center;
color: teal;
padding-bottom: 3%;
}
#detail-title {
text-align: left;
color: teal;
padding-top: 3%;
padding-bottom: 2%;
}
.pane {
padding: 1em;
}

View File

@ -0,0 +1,17 @@
import { Component, Input } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
standalone: true,
imports: [CommonModule],
selector: 'jhi-dyn-tab',
templateUrl: './dyn-tab.component.html',
styleUrls: ['./dyn-tab.component.scss'],
})
export class DynTabComponent {
@Input() tabTitle: string | undefined;
@Input() active = false;
@Input() isCloseable = false;
@Input() template: any;
@Input() dataContext: any;
}

View File

@ -0,0 +1,9 @@
import { Directive, ViewContainerRef } from '@angular/core';
@Directive({
standalone: true,
selector: '[jhiDynTabs]',
})
export class DynTabsDirective {
constructor(public viewContainer: ViewContainerRef) {}
}

View File

@ -0,0 +1,16 @@
<div class="container">
<h2 id="hn-table-title">Hosting Nodes</h2>
<ul class="nav nav-tabs">
<li [ngClass]="{ 'nav-link': true, active: tab.active }" *ngFor="let tab of tabs" (click)="selectTab(tab)">
<a routerLink="/dyn-tabs" class="nav-link">{{ tab.tabTitle }}</a>
</li>
<!-- dynamic tabs (chiudibili) -->
<li [ngClass]="{ 'nav-link': true, active: tab.active }" *ngFor="let tab of dynamicTabs" (click)="selectTab(tab)">
<a routerLink="/dyn-tabs" class="nav-link"
>{{ tab.tabTitle }} <span class="tab-close" *ngIf="tab.isCloseable" (click)="closeTab(tab)">x</span></a
>
</li>
</ul>
</div>
<ng-content></ng-content>
<ng-template jhiDynTabs #tabscontainer></ng-template>

View File

@ -0,0 +1,31 @@
@import url('https://fonts.googleapis.com/css?family=Roboto:400,700|Material+Icons');
.nav-tabs .nav-link,
.nav-tabs .nav-link:hover {
border: 0;
font-size: 115%;
border-bottom: 0;
color: slategray;
}
.nav-tabs .nav-link:hover {
color: darkslategrey;
}
.nav-tabs .nav-link.active {
color: teal;
border-radius: 0;
border-bottom: 3px solid teal;
}
#hn-table-title {
text-align: center;
color: teal;
padding-bottom: 3%;
}
.tab-close {
color: gray;
text-align: right;
cursor: pointer;
}

View File

@ -0,0 +1,110 @@
import { CommonModule } from '@angular/common';
import {
Component,
ContentChildren,
QueryList,
AfterContentInit,
ViewChild,
ViewContainerRef,
ComponentFactoryResolver,
ComponentRef,
ComponentFactory,
} from '@angular/core';
import { DynTabsDirective } from 'app/dyn-tabs.directive';
import { DynTabComponent } from 'app/dyn-tab/dyn-tab.component';
import { MockCtxloaderService } from 'app/services/mock-ctxloader.service';
import { ResourcesLoaderService } from 'app/services/resources-loader.service';
@Component({
standalone: true,
imports: [DynTabsDirective, CommonModule],
selector: 'jhi-dyn-tabs',
templateUrl: './dyn-tabs.component.html',
styleUrls: ['./dyn-tabs.component.scss'],
providers: [MockCtxloaderService, ResourcesLoaderService],
})
export class DynTabsComponent implements AfterContentInit {
//inizializzazioni
dynamicTabs: DynTabComponent[] = [];
@ContentChildren(DynTabComponent) tabs: QueryList<DynTabComponent>;
//@ViewChild(DynTabsDirective, {read: ViewContainerRef}) tabscontainer:ViewContainerRef;
@ViewChild(DynTabsDirective) tabscontainer: DynTabsDirective;
constructor(private _componentFactoryResolver: ComponentFactoryResolver) {
this.tabs = {} as QueryList<DynTabComponent>;
this.tabscontainer = {} as DynTabsDirective;
}
ngAfterContentInit(): void {
// get all active tabs
const activeTabs = this.tabs.filter(tab => tab.active);
// if there is no active tab set, activate the first
if (activeTabs.length === 0) {
this.selectTab(this.tabs.first);
}
}
//OPEN TAB
openTab(title: string, template: any, data: any, isCloseable = false): void {
// get a component factory for our TabComponent
const componentFactory = this._componentFactoryResolver.resolveComponentFactory(DynTabComponent);
// fetch the view container reference from our anchor directive
const viewContainerRef = this.tabscontainer.viewContainer;
//istanza del component
const componentRef = viewContainerRef.createComponent(componentFactory);
// set the according properties on our component instance
const instance: DynTabComponent = componentRef.instance;
instance.tabTitle = title;
instance.template = template;
instance.dataContext = data;
instance.isCloseable = isCloseable;
// remember the dynamic component for rendering the
// tab navigation headers
this.dynamicTabs.push(componentRef.instance);
// set it active
this.selectTab(this.dynamicTabs[this.dynamicTabs.length - 1]);
}
//TODO: ATTENZIONE-->nella direttiva Angular JHipster vuole questo sotto
//selector: '[jhiDynTabs]'
//SELECT TAB
selectTab(tab: DynTabComponent): void {
// deactivate all tabs
this.tabs.toArray().forEach(tb => (tb.active = false));
this.dynamicTabs.forEach(tb => (tb.active = false));
// activate the tab the user has clicked on.
tab.active = true;
}
// CLOSE TAB
closeTab(tab: DynTabComponent): void {
for (let i = 0; i < this.dynamicTabs.length; i++) {
if (this.dynamicTabs[i] === tab) {
// remove the tab from our array
this.dynamicTabs.splice(i, 1);
// destroy our dynamically created component again
const viewContainerRef = this.tabscontainer.viewContainer;
viewContainerRef.remove(i);
// set tab index to 1st one
this.selectTab(this.tabs.first);
break;
}
}
}
//CLOSE ACTIVE TAB
closeActiveTab(): void {
const activeTabs = this.dynamicTabs.filter(tab => tab.active);
if (activeTabs.length > 0) {
// close the 1st active tab (should only be one at a time)
this.closeTab(activeTabs[0]);
}
}
}

View File

@ -1 +0,0 @@
<p>dynamic-tabs works!</p>

View File

@ -1,8 +0,0 @@
import { Component } from '@angular/core';
@Component({
selector: 'jhi-dynamic-tabs',
templateUrl: './dynamic-tabs.component.html',
styleUrls: ['./dynamic-tabs.component.scss'],
})
export class DynamicTabsComponent {}

View File

@ -1,21 +0,0 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DynamicTabsComponent } from './dynamic-tabs.component';
describe('DynamicTabsComponent', () => {
let component: DynamicTabsComponent;
let fixture: ComponentFixture<DynamicTabsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [DynamicTabsComponent],
});
fixture = TestBed.createComponent(DynamicTabsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -1,22 +0,0 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DynamicTabsComponent } from './dynamic-tabs.component';
import { BrowserModule } from '@angular/platform-browser';
//TODO: VEDI QUA: https://stackblitz.com/edit/angular-dynamic-tabs?file=app%2Fapp.module.ts
@NgModule({
declarations: [DynamicTabsComponent, TabsComponent, TabComponent, DynamicTabsDirective, PersonEditComponent, PeopleListComponent],
imports: [BrowserModule, CommonModule],
})
export class DynamicTabsModule {}
@NgModule({
declarations: [AppComponent, TabsComponent, TabComponent, DynamicTabsDirective, PersonEditComponent, PeopleListComponent],
imports: [BrowserModule, ReactiveFormsModule],
providers: [],
bootstrap: [AppComponent],
// register the dynamic components here
entryComponents: [TabComponent],
})
export class AppModule {}

View File

@ -0,0 +1,24 @@
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Status</th>
<th>Last Modified</th>
<th>Available Memory</th>
<th>HD Space</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of dataSource">
<td>{{ data.name }}</td>
<td>{{ data.status }}</td>
<td>{{ data.lastmod }}</td>
<td>{{ data.memavailable }}</td>
<td>{{ data.hdspace }}</td>
<td>
<!--TODO: qui sotto passare data.id -->
<button mat-icon-button color="primary" (click)="openJsonViewer(data)"><mat-icon>insert_drive_file</mat-icon></button>
</td>
</tr>
</tbody>
</table>

View File

@ -0,0 +1 @@
@import url('https://fonts.googleapis.com/css?family=Roboto:400,700|Material+Icons');

View File

@ -0,0 +1,52 @@
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { IContextNode } from 'app/services/i-context-node';
import { MockCtxloaderService } from 'app/services/mock-ctxloader.service';
import { IHostingnode } from 'app/services/i-hostinngnode';
import { ResourcesLoaderService } from 'app/services/resources-loader.service';
import { SharedModule } from 'app/shared/shared.module';
@Component({
standalone: true,
imports: [SharedModule],
selector: 'jhi-list-screen',
templateUrl: './list-screen.component.html',
styleUrls: ['./list-screen.component.scss'],
providers: [MockCtxloaderService, ResourcesLoaderService],
})
export class ListScreenComponent implements OnChanges {
dataSource: IHostingnode[];
//fetchedRawData?: string;
tableDetail: IHostingnode;
@Input() myCtx: IContextNode; //fetching event from parent
@Output() jsonEmitter = new EventEmitter<IHostingnode>();
constructor(private myServiceTable: ResourcesLoaderService) {
this.myCtx = {} as IContextNode;
this.tableDetail = {} as IHostingnode;
this.dataSource = [];
//this.currentCtxId = "";
}
openJsonViewer(node: IHostingnode): void {
this.jsonEmitter.emit(node);
}
ngOnChanges(changes: SimpleChanges): void {
for (const propName in changes) {
if (propName === 'myCtx') {
const param = changes[propName];
this.myCtx = <IContextNode>param.currentValue;
//controllo che l'oggetto non sia vuoto
if (Object.keys(this.myCtx).length !== 0) {
// eslint-disable-next-line no-console
//console.log('+++++++CONTESTO PRESO DAL PARENT...' + this.myCtx.name);
//mt qui va passato il parametro myCtx.id al rest
this.myServiceTable.getHostingNodes().subscribe(res => {
this.dataSource = res;
});
}
}
}
}
}

View File

@ -0,0 +1,4 @@
<h2 *ngIf="chosenItem" id="detail-title">Raw JSON for item: {{ chosenItem.name }}</h2>
<div class="bg-light">
<pre>{{ fetchedRawData | json }}</pre>
</div>

View File

@ -0,0 +1,6 @@
#detail-title {
text-align: left;
color: teal;
padding-top: 3%;
padding-bottom: 2%;
}

View File

@ -0,0 +1,27 @@
import { CommonModule } from '@angular/common';
import { Component, Input, OnInit } from '@angular/core';
import { IHostingnode } from 'app/services/i-hostinngnode';
import { ResourcesLoaderService } from 'app/services/resources-loader.service';
@Component({
standalone: true,
imports: [CommonModule],
selector: 'jhi-rawjson-view',
templateUrl: './rawjson-view.component.html',
styleUrls: ['./rawjson-view.component.scss'],
providers: [ResourcesLoaderService],
})
export class RawjsonViewComponent implements OnInit {
@Input() chosenItem: IHostingnode | undefined;
fetchedRawData: string | undefined;
constructor(private myService: ResourcesLoaderService) {}
ngOnInit(): void {
//TODO: passare al servizio qui sotto: chosenItem.id come parametro rest
this.myService.getHostingNodeDetail().subscribe(res => {
this.fetchedRawData = res;
});
}
}

View File

@ -0,0 +1,28 @@
<div class="row">
<div class="col-md-3">
<jhi-ctx-tree (treeNode)="loadTreeNode($event)"></jhi-ctx-tree>
</div>
<div class="col-md-9">
<div class="row">
<jhi-dyn-tabs>
<jhi-dyn-tab [tabTitle]="'Lista'">
<jhi-list-screen [myCtx]="currentNodeCtx" (jsonEmitter)="onViewRawJson($event)"></jhi-list-screen>
</jhi-dyn-tab>
</jhi-dyn-tabs>
</div>
</div>
<!--<ng-template #rawjsonView>-->
<ng-template #rawjsonView>
<jhi-rawjson-view [chosenItem]="chosenItem"></jhi-rawjson-view>
</ng-template>
<!--
<ng-template #about>
<p>
Hi, I hope this demo was useful to learn more about dynamic components
in Angular, in specific about <code>ViewContainerRef</code>,
<code>ComponentResolverFactory</code> etc.
</p>
</ng-template>
-->
</div>

View File

@ -0,0 +1,39 @@
import { Component, ViewChild } from '@angular/core';
import { SharedModule } from 'app/shared/shared.module';
import { CtxTreeModule } from 'app/ctx-tree/ctx-tree.module';
import { IContextNode } from 'app/services/i-context-node';
import { DynTabComponent } from 'app/dyn-tab/dyn-tab.component';
import { ListScreenComponent } from 'app/tab-items/list-screen.component';
import { RawjsonViewComponent } from 'app/tab-items/rawjson-view.component';
import { IHostingnode } from 'app/services/i-hostinngnode';
import { DynTabsComponent } from 'app/dyn-tabs/dyn-tabs.component';
@Component({
standalone: true,
imports: [CtxTreeModule, SharedModule, DynTabComponent, DynTabsComponent, ListScreenComponent, RawjsonViewComponent],
selector: 'jhi-tabbed-page',
templateUrl: './tabbed-page.component.html',
styleUrls: ['./tabbed-page.component.scss'],
})
export class TabbedPageComponent {
currentNodeCtx: IContextNode;
chosenItem: IHostingnode;
//attenzione a usare la conversione giusta per il nome del file del template (rawjson-view.component)
@ViewChild('rawjsonView') rawjsonTemplate: any;
@ViewChild(DynTabsComponent) dynTabsComponent: any;
constructor() {
this.currentNodeCtx = {} as IContextNode;
this.chosenItem = {} as IHostingnode;
}
loadTreeNode(node: IContextNode): void {
this.currentNodeCtx = node;
}
onViewRawJson(item: IHostingnode): void {
this.chosenItem = item;
this.dynTabsComponent.openTab(`${this.chosenItem.name}`, this.rawjsonTemplate, this.chosenItem, true);
}
}