mirror of
https://github.com/ringtools/ringtools-web-v2.git
synced 2025-02-20 13:34:29 +01:00
Implemented visual page
This commit is contained in:
parent
5392848a57
commit
ab6d8617ac
39 changed files with 2104 additions and 56 deletions
|
@ -20,6 +20,7 @@
|
|||
"@angular/platform-browser": "~13.1.0",
|
||||
"@angular/platform-browser-dynamic": "~13.1.0",
|
||||
"@angular/router": "~13.1.0",
|
||||
"@egjs/hammerjs": "^2.0.17",
|
||||
"@ng-bootstrap/ng-bootstrap": "11.0.0",
|
||||
"@ngrx/effects": "^13.0.2",
|
||||
"@ngrx/entity": "^13.0.2",
|
||||
|
@ -29,10 +30,16 @@
|
|||
"bootstrap": "^4.6.0",
|
||||
"bootstrap-icons": "^1.7.2",
|
||||
"d3": "^7.3.0",
|
||||
"keycharm": "^0.4.0",
|
||||
"ng2-dragula": "^2.1.1",
|
||||
"ngrx-store-localstorage": "^12.0.1",
|
||||
"rxjs": "7.5.2",
|
||||
"socket.io-client": "^4.4.1",
|
||||
"timsort": "^0.3.0",
|
||||
"tslib": "^2.3.0",
|
||||
"vis-data": "^7.1.2",
|
||||
"vis-network": "^9.1.0",
|
||||
"vis-util": "^5.0.2",
|
||||
"zone.js": "~0.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -19,6 +19,8 @@ import { SharedModule } from './shared/shared.module';
|
|||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { ParticipantTableComponent } from './components/participant-table/participant-table.component';
|
||||
import { ParticipantRingComponent } from './components/participant-ring/participant-ring.component';
|
||||
import { PartialsModule } from './partials/partials.module';
|
||||
import { VisModule } from './vis/vis.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -33,6 +35,7 @@ import { ParticipantRingComponent } from './components/participant-ring/particip
|
|||
imports: [
|
||||
SharedModule,
|
||||
LayoutModule,
|
||||
PartialsModule,
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
|
|
|
@ -18,7 +18,7 @@ import * as fromRoot from '../../reducers';
|
|||
templateUrl: './overview.component.html',
|
||||
styleUrls: ['./overview.component.scss'],
|
||||
})
|
||||
export class OverviewComponent implements OnInit, OnDestroy {
|
||||
export class OverviewComponent implements OnDestroy {
|
||||
viewMode!: string;
|
||||
nodeOwners$!: Observable<NodeOwner[]>;
|
||||
settings$!: Observable<SettingState>;
|
||||
|
@ -53,8 +53,6 @@ export class OverviewComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.lnData.channelSocket.emit('unsubscribe_all');
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { loadNodeOwners } from 'src/app/actions/node-owner.actions';
|
||||
import { NodeOwner } from 'src/app/models/node-owner.model';
|
||||
import { RingSetting } from 'src/app/models/ring-setting.model';
|
||||
import { SettingState } from 'src/app/reducers/setting.reducer';
|
||||
import { selectNodeOwners } from 'src/app/selectors/node-owner.selectors';
|
||||
import { selectRingSettings } from 'src/app/selectors/ring-setting.selectors';
|
||||
import { selectSettings } from 'src/app/selectors/setting.selectors';
|
||||
import { FileService } from 'src/app/services/file.service';
|
||||
import { NotificationService } from 'src/app/services/notification.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import * as fromRoot from '../../reducers';
|
||||
|
||||
|
@ -33,7 +36,11 @@ export class SettingsComponent {
|
|||
ringName: any = '';
|
||||
ringSize!: number;
|
||||
|
||||
constructor(private store: Store<fromRoot.State>) {
|
||||
constructor(
|
||||
private file: FileService,
|
||||
private notification: NotificationService,
|
||||
private store: Store<fromRoot.State>
|
||||
) {
|
||||
this.store.select(selectSettings).subscribe((settings: SettingState) => {
|
||||
this.settings = settings;
|
||||
});
|
||||
|
@ -42,7 +49,16 @@ export class SettingsComponent {
|
|||
this.nodeOwners$ = this.store.select(selectNodeOwners);
|
||||
}
|
||||
|
||||
loadSettings(item: any) {}
|
||||
loadSettings(item: RingSetting) {
|
||||
console.log('load', item);
|
||||
|
||||
this.pubkeysText = this.file.convertToCsv(item.ringParticipants);
|
||||
|
||||
this.store.dispatch(loadNodeOwners({ nodeOwners: item.ringParticipants }));
|
||||
|
||||
|
||||
this.notification.showSuccess(`Ring load ${item.cleanRingName} successful`);
|
||||
}
|
||||
|
||||
removeSettings(item: any) {}
|
||||
|
||||
|
|
|
@ -1,42 +1,31 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h3>Ring Designer
|
||||
<div class="btn-group btn-small small btn-group-toggle" ngbRadioGroup name="names" [(ngModel)]="settings.viewMode"
|
||||
(ngModelChange)="viewChange($event)">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" value="node"> Nodename
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" value="tg"> TG username
|
||||
</label>
|
||||
</div>
|
||||
</h3>
|
||||
<div id="graphContainer"></div>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<!-- <app-ring-order></app-ring-order>
|
||||
<app-file-exporter></app-file-exporter> -->
|
||||
<hr />
|
||||
<!-- <form class="form-inline">
|
||||
<label class="my-1 mr-2" for="inlineFormCustomSelectPref">Igniter</label>
|
||||
<select class="form-control" [(ngModel)]="selectedIgniter" name="selectedIgniter">
|
||||
<option [value]="undefined">Select</option>
|
||||
<ng-container *ngFor="let s of nodeOwners">
|
||||
<option [ngValue]="s">
|
||||
<ng-template [ngIf]="viewMode == 'tg'" [ngIfElse]="elseBlock">
|
||||
{{ s.first_name }}
|
||||
</ng-template>
|
||||
<ng-template #elseBlock>
|
||||
{{ s.nodename }}
|
||||
</ng-template>
|
||||
</option>
|
||||
</ng-container>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary my-1" (click)="reorderIgniter()">Reorder</button>
|
||||
|
||||
</form> -->
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<h3>Node connections
|
||||
<div class="btn-group btn-small small btn-group-toggle" ngbRadioGroup name="names"
|
||||
[(ngModel)]="settings.viewMode" (ngModelChange)="viewChange($event)">
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" value="node"> Nodename
|
||||
</label>
|
||||
<label ngbButtonLabel class="btn-primary btn-sm">
|
||||
<input ngbButton type="radio" value="tg"> TG username
|
||||
</label>
|
||||
</div>
|
||||
</h3>
|
||||
<app-node-connections></app-node-connections>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<app-edit-ring-order></app-edit-ring-order>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<app-reorder-participants></app-reorder-participants>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<app-file-exporter></app-file-exporter>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -1,7 +1,10 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { setViewMode } from 'src/app/actions/setting.actions';
|
||||
import { NodeOwner } from 'src/app/models/node-owner.model';
|
||||
import { SettingState } from 'src/app/reducers/setting.reducer';
|
||||
import { selectNodeOwners } from 'src/app/selectors/node-owner.selectors';
|
||||
import { selectSettings } from 'src/app/selectors/setting.selectors';
|
||||
import * as fromRoot from '../../reducers';
|
||||
|
||||
|
@ -12,6 +15,8 @@ import * as fromRoot from '../../reducers';
|
|||
})
|
||||
export class VisualComponent {
|
||||
settings$!: Observable<SettingState>;
|
||||
nodeOwners$!: Observable<NodeOwner[]>;
|
||||
|
||||
settings!: SettingState;
|
||||
|
||||
constructor(
|
||||
|
@ -19,13 +24,14 @@ export class VisualComponent {
|
|||
|
||||
) {
|
||||
this.settings$ = this.store.select(selectSettings);
|
||||
this.nodeOwners$ = this.store.select(selectNodeOwners);
|
||||
|
||||
this.settings$.subscribe((settings: SettingState) => {
|
||||
this.settings = settings;
|
||||
})
|
||||
}
|
||||
|
||||
viewChange($event: any) {
|
||||
|
||||
viewChange($event: string) {
|
||||
this.store.dispatch(setViewMode($event));
|
||||
}
|
||||
}
|
||||
|
|
6
src/app/models/export_file.enum.ts
Normal file
6
src/app/models/export_file.enum.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export enum ExportFile {
|
||||
RingToolsPubKeysTxt = 'pubkeys.txt',
|
||||
RingToolsChannelsTxt = 'channels.txt',
|
||||
IgniterPubkeys = 'igniter_pubkeys.txt',
|
||||
IgniterSh = 'igniter.sh'
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<h3>Ring order</h3>
|
||||
<ul class="list-group" dragula="PARTICIPANTS" id="participants" [(dragulaModel)]="nodeOwners">
|
||||
<li class="list-group-item d-flex justify-content-between" *ngFor="let s of nodeOwners">
|
||||
<ng-template [ngIf]="(settings$ | async)?.viewMode === 'tg'" [ngIfElse]="elseBlock">
|
||||
{{ getCbUsername(s) }}
|
||||
</ng-template>
|
||||
<ng-template #elseBlock>
|
||||
{{ s.nodename }}
|
||||
</ng-template>
|
||||
<small>{{ s.nodename }}</small>
|
||||
</li>
|
||||
</ul>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EditRingOrderComponent } from './edit-ring-order.component';
|
||||
|
||||
describe('EditRingOrderComponent', () => {
|
||||
let component: EditRingOrderComponent;
|
||||
let fixture: ComponentFixture<EditRingOrderComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ EditRingOrderComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EditRingOrderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,48 @@
|
|||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { DragulaService } from 'ng2-dragula';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { NodeOwner } from 'src/app/models/node-owner.model';
|
||||
import { RingSetting } from 'src/app/models/ring-setting.model';
|
||||
import { SettingState } from 'src/app/reducers/setting.reducer';
|
||||
import { selectNodeOwners } from 'src/app/selectors/node-owner.selectors';
|
||||
import { selectSettings } from 'src/app/selectors/setting.selectors';
|
||||
import * as fromRoot from '../../reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-edit-ring-order',
|
||||
templateUrl: './edit-ring-order.component.html',
|
||||
styleUrls: ['./edit-ring-order.component.scss'],
|
||||
})
|
||||
export class EditRingOrderComponent implements OnDestroy {
|
||||
nodeOwners$: Observable<NodeOwner[]>;
|
||||
nodeOwners: NodeOwner[] = [];
|
||||
ringSettings$!: Observable<RingSetting[]>;
|
||||
settings$: Observable<SettingState>;
|
||||
|
||||
subs = new Subscription();
|
||||
|
||||
constructor(
|
||||
private store: Store<fromRoot.State>,
|
||||
private dragulaService: DragulaService
|
||||
) {
|
||||
this.nodeOwners$ = this.store.select(selectNodeOwners);
|
||||
this.settings$ = this.store.select(selectSettings)
|
||||
|
||||
this.nodeOwners$.subscribe((data) => {
|
||||
this.nodeOwners = data;
|
||||
})
|
||||
}
|
||||
|
||||
getCbUsername(nodeOwner: NodeOwner) {
|
||||
if (nodeOwner.username == 'None' || nodeOwner.username == 'undefined') {
|
||||
return nodeOwner.first_name;
|
||||
}
|
||||
return `${nodeOwner.first_name} (@${nodeOwner.username})`;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subs.unsubscribe();
|
||||
this.dragulaService.destroy('PARTICIPANTS');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<button type="button" class="btn btn-warning btn-lg btn-block btn-sm" (click)="persistOrder()">Persist channel
|
||||
order</button>
|
||||
<button type="button" class="btn btn-primary btn-lg btn-block btn-sm" (click)="downloadChannelsTxt()">Download
|
||||
channels.txt</button>
|
||||
<button type="button" class="btn btn-success btn-lg btn-block btn-sm" (click)="downloadPubKeysTxt()">Download
|
||||
pubkeys.txt</button>
|
||||
<button type="button" class="btn btn-secondary btn-lg btn-block btn-sm" (click)="downloadIgniterPubkeys()">Download
|
||||
igniter.sh pubkeys</button>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FileExporterComponent } from './file-exporter.component';
|
||||
|
||||
describe('FileExporterComponent', () => {
|
||||
let component: FileExporterComponent;
|
||||
let fixture: ComponentFixture<FileExporterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ FileExporterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FileExporterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
31
src/app/partials/file-exporter/file-exporter.component.ts
Normal file
31
src/app/partials/file-exporter/file-exporter.component.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ExportFile } from 'src/app/models/export_file.enum';
|
||||
import { FileService } from 'src/app/services/file.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-file-exporter',
|
||||
templateUrl: './file-exporter.component.html',
|
||||
styleUrls: ['./file-exporter.component.scss']
|
||||
})
|
||||
export class FileExporterComponent {
|
||||
|
||||
constructor(
|
||||
private file: FileService
|
||||
) { }
|
||||
|
||||
persistOrder() {
|
||||
console.log('Method not implemented');
|
||||
}
|
||||
|
||||
downloadChannelsTxt() {
|
||||
this.file.generateAndDownload(ExportFile.RingToolsChannelsTxt);
|
||||
}
|
||||
|
||||
downloadPubKeysTxt() {
|
||||
this.file.generateAndDownload(ExportFile.RingToolsPubKeysTxt);
|
||||
}
|
||||
|
||||
downloadIgniterPubkeys() {
|
||||
this.file.generateAndDownload(ExportFile.IgniterPubkeys);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
<div class="network-canvas" [visNetwork]="visNetwork" [visNetworkData]="visNetworkData"
|
||||
[visNetworkOptions]="visNetworkOptions"></div>
|
|
@ -0,0 +1,5 @@
|
|||
.network-canvas {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border: 1px solid lightgray;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NodeConnectionsComponent } from './node-connections.component';
|
||||
|
||||
describe('NodeConnectionsComponent', () => {
|
||||
let component: NodeConnectionsComponent;
|
||||
let fixture: ComponentFixture<NodeConnectionsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ NodeConnectionsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NodeConnectionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
120
src/app/partials/node-connections/node-connections.component.ts
Normal file
120
src/app/partials/node-connections/node-connections.component.ts
Normal file
|
@ -0,0 +1,120 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { LightningNode } from 'src/app/models/lightning_node.model';
|
||||
import { NodeInfo } from 'src/app/models/node-info.model';
|
||||
import { NodeOwner } from 'src/app/models/node-owner.model';
|
||||
import { selectNodeOwners } from 'src/app/selectors/node-owner.selectors';
|
||||
import { LnDataService } from 'src/app/services/ln-data.service';
|
||||
import {
|
||||
Data,
|
||||
DataSet,
|
||||
Edge,
|
||||
Node,
|
||||
Options,
|
||||
VisNetworkService,
|
||||
} from 'src/app/vis/vis.module';
|
||||
import * as fromRoot from '../../reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-node-connections',
|
||||
templateUrl: './node-connections.component.html',
|
||||
styleUrls: ['./node-connections.component.scss'],
|
||||
})
|
||||
export class NodeConnectionsComponent implements OnInit {
|
||||
public visNetwork: string = 'networkId1';
|
||||
public visNetworkData!: Data;
|
||||
public nodes!: DataSet<Node>;
|
||||
public edges!: DataSet<Edge>;
|
||||
public visNetworkOptions!: Options;
|
||||
|
||||
nodeOwners: NodeOwner[] = [];
|
||||
nodeOwners$: Observable<NodeOwner[]>;
|
||||
|
||||
constructor(
|
||||
private store: Store<fromRoot.State>,
|
||||
private lnData: LnDataService,
|
||||
private visNetworkService: VisNetworkService
|
||||
) {
|
||||
this.nodeOwners$ = this.store.select(selectNodeOwners);
|
||||
|
||||
this.nodeOwners$.subscribe((data) => {
|
||||
this.nodeOwners = data;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.nodes = new DataSet<Node>();
|
||||
this.edges = new DataSet<Edge>([]);
|
||||
this.visNetworkData = {
|
||||
nodes: this.nodes,
|
||||
edges: this.edges,
|
||||
};
|
||||
|
||||
this.visNetworkOptions = {
|
||||
interaction: { hover: true },
|
||||
manipulation: {
|
||||
enabled: true,
|
||||
},
|
||||
layout: {
|
||||
randomSeed: 681154853,
|
||||
},
|
||||
edges: {},
|
||||
};
|
||||
|
||||
// if (this.ringData.isLoaded) {
|
||||
this.buildNodes();
|
||||
// } else {
|
||||
// this.ringData.isReady$.subscribe(() => {
|
||||
// this.buildNodes();
|
||||
// });
|
||||
//}
|
||||
}
|
||||
|
||||
public bestFit() {
|
||||
this.visNetworkService.bestFit(this.visNetwork, this.nodes);
|
||||
}
|
||||
|
||||
buildNodes() {
|
||||
for (let node of this.nodeOwners) {
|
||||
let data = this.lnData
|
||||
.getNodeInfo(node.pub_key)
|
||||
.subscribe((data: NodeInfo) => {
|
||||
let label;
|
||||
|
||||
//if (this.viewMode == 'node') {
|
||||
label = node.nodename;
|
||||
//} else {
|
||||
// label = node.username_or_name
|
||||
// }
|
||||
|
||||
let nodeInfo: Node = {
|
||||
id: data.node.pub_key,
|
||||
color: data.node.color,
|
||||
label: data.node.alias,
|
||||
};
|
||||
|
||||
this.nodes.add(nodeInfo);
|
||||
|
||||
channelloop: for (let edge of data.channels) {
|
||||
if (!this.edges.get(edge.channel_id)) {
|
||||
let e: any = {
|
||||
id: edge.channel_id,
|
||||
from: edge.node1_pub,
|
||||
to: edge.node2_pub,
|
||||
dashes: true,
|
||||
};
|
||||
|
||||
if (!edge.node1_policy || !edge.node2_policy) {
|
||||
e.label = 'no info';
|
||||
e.color = '#ffcc00';
|
||||
}
|
||||
|
||||
this.edges.add(e);
|
||||
continue channelloop;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
37
src/app/partials/partials.module.ts
Normal file
37
src/app/partials/partials.module.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FileExporterComponent } from './file-exporter/file-exporter.component';
|
||||
import { EditRingOrderComponent } from './edit-ring-order/edit-ring-order.component';
|
||||
import { DragulaModule } from 'ng2-dragula';
|
||||
import { ReorderParticipantsComponent } from './reorder-participants/reorder-participants.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NodeConnectionsComponent } from './node-connections/node-connections.component';
|
||||
import { VisModule } from '../vis/vis.module';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
FileExporterComponent,
|
||||
EditRingOrderComponent,
|
||||
ReorderParticipantsComponent,
|
||||
NodeConnectionsComponent
|
||||
],
|
||||
imports: [
|
||||
HttpClientModule,
|
||||
VisModule,
|
||||
FormsModule,
|
||||
SharedModule,
|
||||
DragulaModule.forRoot(),
|
||||
CommonModule
|
||||
],
|
||||
exports: [
|
||||
FileExporterComponent,
|
||||
EditRingOrderComponent,
|
||||
ReorderParticipantsComponent,
|
||||
NodeConnectionsComponent
|
||||
]
|
||||
})
|
||||
export class PartialsModule { }
|
|
@ -0,0 +1,18 @@
|
|||
<form class="form-inline">
|
||||
<label class="my-1 mr-2" for="inlineFormCustomSelectPref">Ringleader</label>
|
||||
<select class="form-control" [(ngModel)]="selectedIgniter" name="selectedIgniter">
|
||||
<option [value]="undefined">Select</option>
|
||||
<ng-container *ngFor="let s of nodeOwners">
|
||||
<option [ngValue]="s">
|
||||
<ng-template [ngIf]="(settings$ | async)?.viewMode === 'tg'" [ngIfElse]="elseBlock">
|
||||
{{ s.first_name }}
|
||||
</ng-template>
|
||||
<ng-template #elseBlock>
|
||||
{{ s.nodename }}
|
||||
</ng-template>
|
||||
</option>
|
||||
</ng-container>
|
||||
</select>
|
||||
<button type="submit" class="btn btn-primary my-1" (click)="reorderIgniter()">Reorder</button>
|
||||
|
||||
</form>
|
|
@ -0,0 +1,25 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ReorderParticipantsComponent } from './reorder-participants.component';
|
||||
|
||||
describe('ReorderParticipantsComponent', () => {
|
||||
let component: ReorderParticipantsComponent;
|
||||
let fixture: ComponentFixture<ReorderParticipantsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ReorderParticipantsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ReorderParticipantsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Observable } from 'rxjs';
|
||||
import { loadNodeOwners } from 'src/app/actions/node-owner.actions';
|
||||
import { NodeOwner } from 'src/app/models/node-owner.model';
|
||||
import { RingSetting } from 'src/app/models/ring-setting.model';
|
||||
import { SettingState } from 'src/app/reducers/setting.reducer';
|
||||
import { selectNodeOwners } from 'src/app/selectors/node-owner.selectors';
|
||||
import { selectSettings } from 'src/app/selectors/setting.selectors';
|
||||
import { NotificationService } from 'src/app/services/notification.service';
|
||||
import * as fromRoot from '../../reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reorder-participants',
|
||||
templateUrl: './reorder-participants.component.html',
|
||||
styleUrls: ['./reorder-participants.component.scss']
|
||||
})
|
||||
export class ReorderParticipantsComponent{
|
||||
nodeOwners$: Observable<NodeOwner[]>;
|
||||
nodeOwners: NodeOwner[] = [];
|
||||
ringSettings$!: Observable<RingSetting[]>;
|
||||
settings$: Observable<SettingState>;
|
||||
selectedIgniter: any;
|
||||
|
||||
|
||||
constructor(
|
||||
private store: Store<fromRoot.State>,
|
||||
private notificiation: NotificationService,
|
||||
) {
|
||||
this.nodeOwners$ = this.store.select(selectNodeOwners);
|
||||
this.settings$ = this.store.select(selectSettings)
|
||||
|
||||
this.nodeOwners$.subscribe((data) => {
|
||||
this.nodeOwners = data;
|
||||
})
|
||||
}
|
||||
|
||||
reorderIgniter() {
|
||||
let idx = this.nodeOwners.indexOf(this.selectedIgniter);
|
||||
if (idx == -1) {
|
||||
this.notificiation.show('No igniter selected', { classname: 'bg-danger' });
|
||||
return;
|
||||
}
|
||||
|
||||
let partsUntilIgniter = this.nodeOwners.slice(0, (idx + 1));
|
||||
let partsFromIgniter = this.nodeOwners.slice((idx+1));
|
||||
let newOrder = partsFromIgniter.concat(partsUntilIgniter);
|
||||
try {
|
||||
this.store.dispatch(loadNodeOwners({ nodeOwners: newOrder }))
|
||||
// this.ringData.saveRingSettings(this.nodeOwners);
|
||||
this.notificiation.show('Node reorder persisted', { classname: 'bg-success' });
|
||||
} catch (e) {
|
||||
this.notificiation.show('Error reordering', { classname: 'bg-danger' });
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -7,12 +7,21 @@ export const nodeOwnersFeatureKey = 'nodeOwners';
|
|||
|
||||
export interface NodeOwnersState extends EntityState<NodeOwner> {
|
||||
// additional entities state properties
|
||||
pub_key: string | null
|
||||
|
||||
}
|
||||
|
||||
export const adapter: EntityAdapter<NodeOwner> = createEntityAdapter<NodeOwner>();
|
||||
export function selectNodeOwner(a: NodeOwner): string {
|
||||
//In this case this would be optional since primary key is id
|
||||
return a.pub_key;
|
||||
}
|
||||
|
||||
export const adapter: EntityAdapter<NodeOwner> = createEntityAdapter<NodeOwner>({
|
||||
selectId: selectNodeOwner,
|
||||
});
|
||||
|
||||
export const initialState: NodeOwnersState = adapter.getInitialState({
|
||||
// additional entity state properties
|
||||
pub_key: ''
|
||||
});
|
||||
|
||||
export const nodeOwnersReducer = createReducer(
|
||||
|
|
|
@ -1,9 +1,93 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ExportFile } from '../models/export_file.enum';
|
||||
import { NodeOwner } from '../models/node-owner.model';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FileService {
|
||||
constructor() {}
|
||||
|
||||
constructor() { }
|
||||
convertToCsv(ringParticipants:NodeOwner[], header: boolean = true) {
|
||||
let pkContents = '';
|
||||
|
||||
if (header)
|
||||
pkContents = 'user_name,nodename,pub_key,new,handle,capacity_sat\r\n';
|
||||
|
||||
for (let p of ringParticipants) {
|
||||
pkContents += `${p.first_name},${p.nodename},${p.pub_key},false,${p.username},0\r\n`;
|
||||
}
|
||||
|
||||
return pkContents;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSV
|
||||
* @returns
|
||||
*/
|
||||
parseCsvToType(contents: string) {
|
||||
let segmentLines = contents.split('\n');
|
||||
let segments: NodeOwner[] = [];
|
||||
for (let line of segmentLines.slice(1)) {
|
||||
let parts = line.split(',');
|
||||
if (parts.length > 1) {
|
||||
let nodeOwner: NodeOwner = {
|
||||
first_name: parts[0],
|
||||
nodename: parts[1],
|
||||
pub_key: parts[2],
|
||||
username: parts[4],
|
||||
username_or_name: undefined
|
||||
};
|
||||
segments.push(nodeOwner);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
doDownloadFileWithData(data: string, filename: string) {
|
||||
let element = document.createElement('a');
|
||||
element.setAttribute(
|
||||
'href',
|
||||
'data:text/plain;charset=utf-8,' + encodeURIComponent(data)
|
||||
);
|
||||
element.setAttribute('download', filename);
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
element.click();
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
convertToExportFormat(ringParticipants: NodeOwner[]) {
|
||||
let pkContents = '';
|
||||
let add = '';
|
||||
for (let p of ringParticipants) {
|
||||
pkContents += `${add}${p.first_name},${p.username},${p.pub_key},${p.nodename}`;
|
||||
add = "|";
|
||||
}
|
||||
|
||||
return pkContents;
|
||||
}
|
||||
|
||||
parseNewExportFormat(data: string) {
|
||||
let segmentLines = data.split('|');
|
||||
let segments: NodeOwner[] = [];
|
||||
for (let line of segmentLines) {
|
||||
let parts = line.split(',');
|
||||
if (parts.length > 1) {
|
||||
let nodeOwner:NodeOwner = {
|
||||
first_name: parts[0],
|
||||
username: parts[1],
|
||||
pub_key: parts[2],
|
||||
nodename: parts[3],
|
||||
username_or_name: undefined
|
||||
};
|
||||
segments.push(nodeOwner);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
generateAndDownload(file_template: ExportFile) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
import { NodeInfo } from '../models/node-info.model';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
|
@ -9,10 +11,16 @@ export class LnDataService {
|
|||
nodeSocket: Socket<any, any>;
|
||||
channelSocket: Socket<any, any>;
|
||||
|
||||
constructor() {
|
||||
constructor(private http: HttpClient) {
|
||||
const url = 'http://localhost:7464';
|
||||
this.socket = io(url)
|
||||
this.nodeSocket = io(`${url}/node`)
|
||||
this.channelSocket = io(`${url}/channel`)
|
||||
}
|
||||
|
||||
getNodeInfo(pubKey: string) {
|
||||
return this.http.get<NodeInfo>(
|
||||
`http://localhost:7464/node/${pubKey}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,22 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { ToastService } from '../shared/notification/toast/toast.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
|
||||
constructor() { }
|
||||
constructor(private toast: ToastService) {
|
||||
|
||||
}
|
||||
|
||||
showSuccess(message: string) {
|
||||
this.toast.show(`${message}`, {
|
||||
classname: 'bg-success',
|
||||
});
|
||||
}
|
||||
|
||||
show(message: string, options: any) {
|
||||
this.toast.show(`${message}`, options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ import { NgModule } from '@angular/core';
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { ToastComponent } from './notification/toast/toast.component';
|
||||
import { NgbToastModule } from '@ng-bootstrap/ng-bootstrap';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -8,4 +8,33 @@ const colorScale = d3
|
|||
// @ts-ignore
|
||||
.interpolate(d3.interpolateHcl);
|
||||
|
||||
export { colorScale };
|
||||
const copyToClipboard = (data: string) => {
|
||||
const listener = (e: ClipboardEvent) => {
|
||||
if (!e.clipboardData) return;
|
||||
e.clipboardData.setData('text/plain', data);
|
||||
e.preventDefault();
|
||||
document.removeEventListener('copy', listener);
|
||||
};
|
||||
document.addEventListener('copy', listener);
|
||||
document.execCommand('copy');
|
||||
};
|
||||
|
||||
const stringToBoolean = (string: string) => {
|
||||
switch (string.toLowerCase().trim()) {
|
||||
case 'true':
|
||||
case 'yes':
|
||||
case '1':
|
||||
return true;
|
||||
|
||||
case 'false':
|
||||
case 'no':
|
||||
case '0':
|
||||
case null:
|
||||
return false;
|
||||
|
||||
default:
|
||||
return Boolean(string);
|
||||
}
|
||||
};
|
||||
|
||||
export { colorScale, copyToClipboard, stringToBoolean };
|
||||
|
|
8
src/app/vis/network/vis-network.directive.spec.ts
Normal file
8
src/app/vis/network/vis-network.directive.spec.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { VisNetworkDirective } from './vis-network.directive';
|
||||
|
||||
describe('VisNetworkDirective', () => {
|
||||
it('should create an instance', () => {
|
||||
const directive = new VisNetworkDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
});
|
||||
});
|
135
src/app/vis/network/vis-network.directive.ts
Normal file
135
src/app/vis/network/vis-network.directive.ts
Normal file
|
@ -0,0 +1,135 @@
|
|||
import { Directive, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChange } from '@angular/core';
|
||||
import { Data, Options } from 'vis-network/esnext';
|
||||
|
||||
import { VisNetworkService } from './vis-network.service';
|
||||
|
||||
/**
|
||||
* Use this directive with a div container to show network data.
|
||||
*
|
||||
* @export
|
||||
* @class VisNetworkDirective
|
||||
* @implements {OnInit}
|
||||
* @implements {OnDestroy}
|
||||
* @implements {OnChanges}
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[visNetwork]',
|
||||
})
|
||||
export class VisNetworkDirective implements OnInit, OnDestroy, OnChanges {
|
||||
/**
|
||||
* The name or identifier of the network (must be unique in your application).
|
||||
* This property is used once on init and must not be changed.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
@Input('visNetwork')
|
||||
public visNetwork!: string;
|
||||
|
||||
/**
|
||||
* The data that will be used to create the network.
|
||||
* Changes to the nodes or edges property won't be detected but
|
||||
* changes to the reference of this object.
|
||||
* Changes lead to a call to setData of this network instance.
|
||||
*
|
||||
* @type {Data}
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
@Input()
|
||||
public visNetworkData!: Data;
|
||||
|
||||
/**
|
||||
* The options that will be used with this network instance.
|
||||
* Only reference changes to the whole options object will be detected
|
||||
* but not changes to properties.
|
||||
* Changes lead to a call to setOptions of the network instance.
|
||||
*
|
||||
* @type {VisOptions}
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
@Input()
|
||||
public visNetworkOptions!: Options;
|
||||
|
||||
/**
|
||||
* This event will be raised when the network is initialized.
|
||||
* At this point of time the network is successfully registered
|
||||
* with the VisNetworkService and you can register to events.
|
||||
* The event data is the name of the network as a string.
|
||||
*
|
||||
* @type {EventEmitter<any>}
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
@Output()
|
||||
public initialized: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
private visNetworkContainer: any;
|
||||
private isInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* Creates an instance of VisNetworkDirective.
|
||||
*
|
||||
* @param {ElementRef} elementRef The HTML element reference.
|
||||
* @param {VisNetworkService} visNetworkService The VisNetworkService.
|
||||
*
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
public constructor(private elementRef: ElementRef, private visNetworkService: VisNetworkService) {
|
||||
this.visNetworkContainer = elementRef.nativeElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the network when at least visNetwork and visData
|
||||
* are defined.
|
||||
*
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
if (!this.isInitialized && this.visNetwork && this.visNetworkData) {
|
||||
this.createNetwork();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the network data or options on reference changes to
|
||||
* the visData or visOptions properties.
|
||||
*
|
||||
* @param {{[propName: string]: SimpleChange}} changes
|
||||
*
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
public ngOnChanges(changes: { [propName: string]: SimpleChange }): void {
|
||||
if (!this.isInitialized && this.visNetwork && this.visNetworkData) {
|
||||
this.createNetwork();
|
||||
}
|
||||
|
||||
for (const propertyName in changes) {
|
||||
if (changes.hasOwnProperty(propertyName)) {
|
||||
const change = changes[propertyName];
|
||||
if (!change.isFirstChange()) {
|
||||
if (propertyName === 'visData') {
|
||||
this.visNetworkService.setData(this.visNetwork, changes[propertyName].currentValue);
|
||||
}
|
||||
if (propertyName === 'visOptions') {
|
||||
this.visNetworkService.setOptions(this.visNetwork, changes[propertyName].currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the destroy function for this network instance.
|
||||
*
|
||||
* @memberOf VisNetworkDirective
|
||||
*/
|
||||
public ngOnDestroy(): void {
|
||||
this.isInitialized = false;
|
||||
this.visNetworkService.destroy(this.visNetwork);
|
||||
}
|
||||
|
||||
private createNetwork(): void {
|
||||
this.visNetworkService.create(this.visNetwork, this.visNetworkContainer, this.visNetworkData, this.visNetworkOptions);
|
||||
this.isInitialized = true;
|
||||
this.initialized.emit(this.visNetwork);
|
||||
}
|
||||
}
|
16
src/app/vis/network/vis-network.service.spec.ts
Normal file
16
src/app/vis/network/vis-network.service.spec.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VisNetworkService } from './vis-network.service';
|
||||
|
||||
describe('VisNetworkService', () => {
|
||||
let service: VisNetworkService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(VisNetworkService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
1134
src/app/vis/network/vis-network.service.ts
Normal file
1134
src/app/vis/network/vis-network.service.ts
Normal file
File diff suppressed because it is too large
Load diff
33
src/app/vis/vis.module.ts
Normal file
33
src/app/vis/vis.module.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
|
||||
import { DataSet } from 'vis-data/esnext';
|
||||
|
||||
import { Data, Edge, Node, Options } from 'vis-network/esnext';
|
||||
import { VisNetworkDirective } from './network/vis-network.directive';
|
||||
import { VisNetworkService } from './network/vis-network.service';
|
||||
|
||||
export {
|
||||
VisNetworkDirective,
|
||||
VisNetworkService,
|
||||
Data,
|
||||
DataSet,
|
||||
Edge,
|
||||
Options,
|
||||
Node,
|
||||
};
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
VisNetworkDirective,
|
||||
],
|
||||
imports: [
|
||||
CommonModule
|
||||
],
|
||||
exports: [VisNetworkDirective],
|
||||
providers: [VisNetworkService],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
|
||||
})
|
||||
export class VisModule { }
|
|
@ -49,9 +49,11 @@ import '@angular/localize/init';
|
|||
/***************************************************************************************************
|
||||
* Zone JS is required by default for Angular itself.
|
||||
*/
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
import 'zone.js'; // Included with Angular CLI.
|
||||
|
||||
/***************************************************************************************************
|
||||
* APPLICATION IMPORTS
|
||||
*/
|
||||
|
||||
// Required for Dragula: https://github.com/valor-software/ng2-dragula#1-important-add-the-following-line-to-your-polyfillsts
|
||||
(window as any).global = window;
|
||||
|
|
26
src/style/dragula.scss
Normal file
26
src/style/dragula.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
.gu-mirror {
|
||||
position: fixed !important;
|
||||
margin: 0 !important;
|
||||
z-index: 9999 !important;
|
||||
opacity: 0.8;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=80)";
|
||||
filter: alpha(opacity=80);
|
||||
pointer-events: none;
|
||||
}
|
||||
/* high-performance display:none; helper */
|
||||
.gu-hide {
|
||||
left: -9999px !important;
|
||||
}
|
||||
/* added to mirrorContainer (default = body) while dragging */
|
||||
.gu-unselectable {
|
||||
-webkit-user-select: none !important;
|
||||
-moz-user-select: none !important;
|
||||
-ms-user-select: none !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
/* added to the source element while its mirror is dragged */
|
||||
.gu-transit {
|
||||
opacity: 0.2;
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=20)";
|
||||
filter: alpha(opacity=20);
|
||||
}
|
|
@ -5,3 +5,6 @@
|
|||
@import '~bootstrap/scss/bootstrap';
|
||||
@import 'style/bootswatch.scss';
|
||||
@import '~bootstrap-icons/font/bootstrap-icons.scss';
|
||||
|
||||
/* Dragula Drag & Drop */
|
||||
@import 'style/dragula.scss';
|
||||
|
|
85
yarn.lock
85
yarn.lock
|
@ -1298,6 +1298,13 @@
|
|||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
|
||||
integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==
|
||||
|
||||
"@egjs/hammerjs@^2.0.17":
|
||||
version "2.0.17"
|
||||
resolved "https://registry.yarnpkg.com/@egjs/hammerjs/-/hammerjs-2.0.17.tgz#5dc02af75a6a06e4c2db0202cae38c9263895124"
|
||||
integrity sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==
|
||||
dependencies:
|
||||
"@types/hammerjs" "^2.0.36"
|
||||
|
||||
"@eslint/eslintrc@^1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318"
|
||||
|
@ -1783,6 +1790,11 @@
|
|||
"@types/d3-transition" "*"
|
||||
"@types/d3-zoom" "*"
|
||||
|
||||
"@types/dragula@^2.1.34":
|
||||
version "2.1.36"
|
||||
resolved "https://registry.yarnpkg.com/@types/dragula/-/dragula-2.1.36.tgz#a58236ac095e26cd271b128f3c42df7fa41bd210"
|
||||
integrity sha512-K1GIMqdiviBIvUsLJPO1xkjpDFS308nU2l57zmV7LEO+znF3gtZGnWQ+c/ef78r6Ngb0cniQk8pnNkObeNlXQQ==
|
||||
|
||||
"@types/eslint-scope@^3.7.0":
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.3.tgz#125b88504b61e3c8bc6f870882003253005c3224"
|
||||
|
@ -1809,6 +1821,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca"
|
||||
integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA==
|
||||
|
||||
"@types/hammerjs@^2.0.36":
|
||||
version "2.0.41"
|
||||
resolved "https://registry.yarnpkg.com/@types/hammerjs/-/hammerjs-2.0.41.tgz#f6ecf57d1b12d2befcce00e928a6a097c22980aa"
|
||||
integrity sha512-ewXv/ceBaJprikMcxCmWU1FKyMAQ2X7a9Gtmzw8fcg2kIePI1crERDM818W+XYrxqdBBOdlf2rm137bU+BltCA==
|
||||
|
||||
"@types/http-proxy@^1.17.5":
|
||||
version "1.17.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.8.tgz#968c66903e7e42b483608030ee85800f22d03f55"
|
||||
|
@ -2336,6 +2353,11 @@ at-least-node@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
|
||||
integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==
|
||||
|
||||
atoa@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/atoa/-/atoa-1.0.0.tgz#0cc0e91a480e738f923ebc103676471779b34a49"
|
||||
integrity sha1-DMDpGkgOc4+SPrwQNnZHF3mzSkk=
|
||||
|
||||
atob@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
|
||||
|
@ -2844,6 +2866,14 @@ content-type@~1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
|
||||
contra@1.9.4:
|
||||
version "1.9.4"
|
||||
resolved "https://registry.yarnpkg.com/contra/-/contra-1.9.4.tgz#f53bde42d7e5b5985cae4d99a8d610526de8f28d"
|
||||
integrity sha1-9TveQtfltZhcrk2ZqNYQUm3o8o0=
|
||||
dependencies:
|
||||
atoa "1.0.0"
|
||||
ticky "1.0.1"
|
||||
|
||||
convert-source-map@^1.5.1, convert-source-map@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
|
||||
|
@ -2943,6 +2973,13 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
|
|||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
crossvent@1.5.5:
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/crossvent/-/crossvent-1.5.5.tgz#ad20878e4921e9be73d9d6976f8b2ecd0f71a0b1"
|
||||
integrity sha1-rSCHjkkh6b5z2daXb4suzQ9xoLE=
|
||||
dependencies:
|
||||
custom-event "^1.0.0"
|
||||
|
||||
css-blank-pseudo@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz#dfdefd3254bf8a82027993674ccf35483bfcb3c5"
|
||||
|
@ -3019,7 +3056,7 @@ cssesc@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||
|
||||
custom-event@~1.0.0:
|
||||
custom-event@^1.0.0, custom-event@~1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/custom-event/-/custom-event-1.0.1.tgz#5d02a46850adf1b4a317946a3928fccb5bfd0425"
|
||||
integrity sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=
|
||||
|
@ -3489,6 +3526,14 @@ domutils@^2.8.0:
|
|||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
dragula@^3.7.2:
|
||||
version "3.7.3"
|
||||
resolved "https://registry.yarnpkg.com/dragula/-/dragula-3.7.3.tgz#909460fd0b4acba5409c6dbb1b64d24f5bc9efb6"
|
||||
integrity sha512-/rRg4zRhcpf81TyDhaHLtXt6sEywdfpv1cRUMeFFy7DuypH2U0WUL0GTdyAQvXegviT4PJK4KuMmOaIDpICseQ==
|
||||
dependencies:
|
||||
contra "1.9.4"
|
||||
crossvent "1.5.5"
|
||||
|
||||
ee-first@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
|
||||
|
@ -4995,6 +5040,11 @@ karma@~6.3.0:
|
|||
ua-parser-js "^0.7.30"
|
||||
yargs "^16.1.1"
|
||||
|
||||
keycharm@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/keycharm/-/keycharm-0.4.0.tgz#8d684ea9cc01379a07fbddee33ff32d97f5ae2a7"
|
||||
integrity sha512-TyQTtsabOVv3MeOpR92sIKk/br9wxS+zGj4BG7CR8YbK4jM3tyIBaF0zhzeBUMx36/Q/iQLOKKOT+3jOQtemRQ==
|
||||
|
||||
kind-of@^6.0.2:
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
|
||||
|
@ -5396,6 +5446,14 @@ neo-async@^2.6.2:
|
|||
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
|
||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||
|
||||
ng2-dragula@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/ng2-dragula/-/ng2-dragula-2.1.1.tgz#350e78978b6f7e1ea0b16c61ba78161c84ec6386"
|
||||
integrity sha512-PSo6N2Ja894KDogVLLBI0Hzpylikay7L1hWqp+qQmW+qsNsNT9J/6J2Qim9XwGzK4VQZjAwBJaJjgJ/TijRkLQ==
|
||||
dependencies:
|
||||
"@types/dragula" "^2.1.34"
|
||||
dragula "^3.7.2"
|
||||
|
||||
ngrx-store-localstorage@^12.0.1:
|
||||
version "12.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ngrx-store-localstorage/-/ngrx-store-localstorage-12.0.1.tgz#282683b135011cc643e19ad883f2fb424fc81559"
|
||||
|
@ -7147,6 +7205,16 @@ thunky@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
|
||||
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
|
||||
|
||||
ticky@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ticky/-/ticky-1.0.1.tgz#b7cfa71e768f1c9000c497b9151b30947c50e46d"
|
||||
integrity sha1-t8+nHnaPHJAAxJe5FRswlHxQ5G0=
|
||||
|
||||
timsort@^0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
|
||||
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
|
||||
|
||||
tmp@0.2.1, tmp@^0.2.1, tmp@~0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
|
||||
|
@ -7344,6 +7412,21 @@ vary@^1, vary@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||
|
||||
vis-data@^7.1.2:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/vis-data/-/vis-data-7.1.2.tgz#b7d076ac79cb54f7c5e9c80f5b03b93cc8cc1fda"
|
||||
integrity sha512-RPSegFxEcnp3HUEJSzhS2vBdbJ2PSsrYYuhRlpHp2frO/MfRtTYbIkkLZmPkA/Sg3pPfBlR235gcoKbtdm4mbw==
|
||||
|
||||
vis-network@^9.1.0:
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vis-network/-/vis-network-9.1.0.tgz#511db833b68060f279bedc4a852671261d40204e"
|
||||
integrity sha512-rx96L144RJWcqOa6afjiFyxZKUerRRbT/YaNMpsusHdwzxrVTO2LlduR45PeJDEztrAf3AU5l2zmiG+1ydUZCw==
|
||||
|
||||
vis-util@^5.0.2:
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/vis-util/-/vis-util-5.0.2.tgz#47e8a31580c0805680c43d253ac7da21501990b9"
|
||||
integrity sha512-oPDmPc4o0uQLoKpKai2XD1DjrhYsA7MRz75Wx9KmfX84e9LLgsbno7jVL5tR0K9eNVQkD6jf0Ei8NtbBHDkF1A==
|
||||
|
||||
void-elements@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
|
||||
|
|
Loading…
Add table
Reference in a new issue