Add lightning channel link previews
This commit is contained in:
		
							parent
							
								
									18d18fa234
								
							
						
					
					
						commit
						9216936a71
					
				@ -0,0 +1,72 @@
 | 
			
		||||
<div class="box preview-box" *ngIf="(channel$ | async) as channel">
 | 
			
		||||
  <div class="row d-flex justify-content-between full-width-row">
 | 
			
		||||
    <h1 class="title">
 | 
			
		||||
      <span i18n="lightning.channel">Channel</span>
 | 
			
		||||
      <a [routerLink]="['/lightning/channel' | relativeUrl, channel.id]"> {{ channel.short_id }}</a>
 | 
			
		||||
    </h1>
 | 
			
		||||
    <div class="badges mb-2">
 | 
			
		||||
      <span class="badge rounded-pill badge-secondary" *ngIf="channel.status === 0">Inactive</span>
 | 
			
		||||
      <span class="badge rounded-pill badge-success" *ngIf="channel.status === 1">Active</span>
 | 
			
		||||
      <span class="badge rounded-pill badge-danger" *ngIf="channel.status === 2">Closed</span>
 | 
			
		||||
 | 
			
		||||
      <app-closing-type [type]="channel.closing_reason" *ngIf="channel.status === 2"></app-closing-type>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row d-flex justify-content-between full-width-row nodes">
 | 
			
		||||
    <span class="node left">
 | 
			
		||||
      {{ channel.node_left.alias || '?' }}
 | 
			
		||||
    </span>
 | 
			
		||||
    <fa-icon class="between-arrow" [icon]="['fas', 'arrow-right-arrow-left']" [fixedWidth]="true" title="channel between"></fa-icon>
 | 
			
		||||
    <span class="node right">
 | 
			
		||||
      {{ channel.node_right.alias || '?' }}
 | 
			
		||||
    </span>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col-md">
 | 
			
		||||
      <table class="table table-borderless table-striped">
 | 
			
		||||
        <tbody>
 | 
			
		||||
          <tr></tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="channel.created">Created</td>
 | 
			
		||||
            <td>{{ channel.created | date:'yyyy-MM-dd HH:mm' }}</td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="channel.capacity">Capacity</td>
 | 
			
		||||
            <td><app-amount [satoshis]="channel.capacity" [noFiat]="true"></app-amount></td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="channel.fee-rate">Fee rate</td>
 | 
			
		||||
            <td>
 | 
			
		||||
              <div class="dual-cell">
 | 
			
		||||
                <span>{{ channel.node_left.fee_rate }} <span class="symbol">ppm</span></span>
 | 
			
		||||
                <fa-icon class="between-arrow" [icon]="['fas', 'arrow-right-arrow-left']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
                <span>{{ channel.node_right.fee_rate }} <span class="symbol">ppm</span></span>
 | 
			
		||||
              </div>
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td i18n="channel.base-fee">Base fee</td>
 | 
			
		||||
            <td>
 | 
			
		||||
              <div class="dual-cell">
 | 
			
		||||
                <app-sats [satoshis]="channel.node_left.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
 | 
			
		||||
                <fa-icon class="between-arrow" [icon]="['fas', 'arrow-right-arrow-left']" [fixedWidth]="true"></fa-icon>
 | 
			
		||||
                <app-sats [satoshis]="channel.node_right.base_fee_mtokens / 1000" digitsInfo="1.0-2"></app-sats>
 | 
			
		||||
              </div>
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div class="col-md map-col">
 | 
			
		||||
      <app-nodes-channels-map *ngIf="!error" [style]="'channelpage'" [channel]="channelGeo" [fitContainer]="true" (readyEvent)="onMapReady()"></app-nodes-channels-map>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template [ngIf]="error">
 | 
			
		||||
  <div class="text-center">
 | 
			
		||||
    <span i18n="error.general-loading-data">Error loading data.</span>
 | 
			
		||||
    <br><br>
 | 
			
		||||
    <i>{{ error.status }}: {{ error.error }}</i>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -0,0 +1,76 @@
 | 
			
		||||
.title {
 | 
			
		||||
  font-size: 52px;
 | 
			
		||||
  margin: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.table {
 | 
			
		||||
  font-size: 32px;
 | 
			
		||||
  margin-top: 36px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.badges {
 | 
			
		||||
  font-size: 28px;
 | 
			
		||||
 | 
			
		||||
  ::ng-deep .badge {
 | 
			
		||||
    margin-left: 0.5em;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.row {
 | 
			
		||||
  margin-right: 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.full-width-row {
 | 
			
		||||
  padding-left: 15px;
 | 
			
		||||
  padding-right: 15px;
 | 
			
		||||
 | 
			
		||||
  &:nth-child(even) {
 | 
			
		||||
    background: #181b2d;
 | 
			
		||||
    margin: 15px 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.nodes {
 | 
			
		||||
  font-size: 36px;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.between-arrow {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.map-col {
 | 
			
		||||
  flex-grow: 0;
 | 
			
		||||
  flex-shrink: 0;
 | 
			
		||||
  width: 470px;
 | 
			
		||||
  min-width: 470px;
 | 
			
		||||
  padding: 0;
 | 
			
		||||
  background: #181b2d;
 | 
			
		||||
  max-height: 470px;
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
::ng-deep .symbol {
 | 
			
		||||
  font-size: 24px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.dual-cell {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-direction: row;
 | 
			
		||||
  justify-content: space-between;
 | 
			
		||||
  align-items: baseline;
 | 
			
		||||
 | 
			
		||||
  & > * {
 | 
			
		||||
    width: 0;
 | 
			
		||||
    flex-grow: 1;
 | 
			
		||||
 | 
			
		||||
    &:nth-child(2) {
 | 
			
		||||
      text-align: center;
 | 
			
		||||
      max-width: 1.5em;
 | 
			
		||||
    }
 | 
			
		||||
    &:nth-child(3) {
 | 
			
		||||
      text-align: right;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,67 @@
 | 
			
		||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { Observable, of } from 'rxjs';
 | 
			
		||||
import { catchError, switchMap, tap } from 'rxjs/operators';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { OpenGraphService } from 'src/app/services/opengraph.service';
 | 
			
		||||
import { LightningApiService } from '../lightning-api.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-channel-preview',
 | 
			
		||||
  templateUrl: './channel-preview.component.html',
 | 
			
		||||
  styleUrls: ['./channel-preview.component.scss'],
 | 
			
		||||
  changeDetection: ChangeDetectionStrategy.OnPush,
 | 
			
		||||
})
 | 
			
		||||
export class ChannelPreviewComponent implements OnInit {
 | 
			
		||||
  channel$: Observable<any>;
 | 
			
		||||
  error: any = null;
 | 
			
		||||
  channelGeo: number[] = [];
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private lightningApiService: LightningApiService,
 | 
			
		||||
    private activatedRoute: ActivatedRoute,
 | 
			
		||||
    private seoService: SeoService,
 | 
			
		||||
    private openGraphService: OpenGraphService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.channel$ = this.activatedRoute.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((params: ParamMap) => {
 | 
			
		||||
          this.openGraphService.waitFor('channel-map');
 | 
			
		||||
          this.openGraphService.waitFor('channel-data');
 | 
			
		||||
          this.error = null;
 | 
			
		||||
          this.seoService.setTitle(`Channel: ${params.get('short_id')}`);
 | 
			
		||||
          return this.lightningApiService.getChannel$(params.get('short_id'))
 | 
			
		||||
            .pipe(
 | 
			
		||||
              tap((data) => {
 | 
			
		||||
                if (!data.node_left.longitude || !data.node_left.latitude ||
 | 
			
		||||
                  !data.node_right.longitude || !data.node_right.latitude) {
 | 
			
		||||
                  this.channelGeo = [];
 | 
			
		||||
                } else {
 | 
			
		||||
                  this.channelGeo = [
 | 
			
		||||
                    data.node_left.public_key,
 | 
			
		||||
                    data.node_left.alias,
 | 
			
		||||
                    data.node_left.longitude, data.node_left.latitude,
 | 
			
		||||
                    data.node_right.public_key,
 | 
			
		||||
                    data.node_right.alias,
 | 
			
		||||
                    data.node_right.longitude, data.node_right.latitude,
 | 
			
		||||
                  ];
 | 
			
		||||
                }
 | 
			
		||||
                this.openGraphService.waitOver('channel-data');
 | 
			
		||||
              }),
 | 
			
		||||
              catchError((err) => {
 | 
			
		||||
                this.error = err;
 | 
			
		||||
                this.openGraphService.waitOver('channel-map');
 | 
			
		||||
                this.openGraphService.waitOver('channel-data');
 | 
			
		||||
                return of(null);
 | 
			
		||||
              })
 | 
			
		||||
            );
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onMapReady() {
 | 
			
		||||
    this.openGraphService.waitOver('channel-map');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -7,9 +7,11 @@ import { LightningModule } from './lightning.module';
 | 
			
		||||
import { LightningApiService } from './lightning-api.service';
 | 
			
		||||
import { NodePreviewComponent } from './node/node-preview.component';
 | 
			
		||||
import { LightningPreviewsRoutingModule } from './lightning-previews.routing.module';
 | 
			
		||||
import { ChannelPreviewComponent } from './channel/channel-preview.component';
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
    NodePreviewComponent,
 | 
			
		||||
    ChannelPreviewComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    CommonModule,
 | 
			
		||||
 | 
			
		||||
@ -1,12 +1,17 @@
 | 
			
		||||
import { NgModule } from '@angular/core';
 | 
			
		||||
import { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { NodePreviewComponent } from './node/node-preview.component';
 | 
			
		||||
import { ChannelPreviewComponent } from './channel/channel-preview.component';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
      path: 'node/:public_key',
 | 
			
		||||
      component: NodePreviewComponent,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: 'channel/:short_id',
 | 
			
		||||
      component: ChannelPreviewComponent,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      path: '**',
 | 
			
		||||
      redirectTo: ''
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user