Featured assets and asset groups
This commit is contained in:
		
							parent
							
								
									755c1da8b3
								
							
						
					
					
						commit
						2e5c8bdfd3
					
				@ -319,7 +319,9 @@ class Server {
 | 
			
		||||
    if (Common.isLiquid()) {
 | 
			
		||||
      this.app
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/icons', routes.getAllLiquidIcon)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'assets/featured', routes.$getAllFeaturedLiquidAssets)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'asset/:assetId/icon', routes.getLiquidIcon)
 | 
			
		||||
        .get(config.MEMPOOL.API_URL_PREFIX + 'asset-group/:id', routes.$getAssetGroup)
 | 
			
		||||
      ;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
			
		||||
import elementsParser from './api/liquid/elements-parser';
 | 
			
		||||
import icons from './api/liquid/icons';
 | 
			
		||||
import miningStats from './api/mining';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
 | 
			
		||||
class Routes {
 | 
			
		||||
  constructor() {}
 | 
			
		||||
@ -855,6 +856,25 @@ class Routes {
 | 
			
		||||
      res.status(404).send('Asset icons not found');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getAllFeaturedLiquidAssets(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await axios.get('https://mempool.space/api/v1/assets/featured', { responseType: 'stream', timeout: 10000 });
 | 
			
		||||
      response.data.pipe(res);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).end();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $getAssetGroup(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await axios.get('https://mempool.space/api/v1/asset-group/' + parseInt(req.params.id, 10),
 | 
			
		||||
        { responseType: 'stream', timeout: 10000 });
 | 
			
		||||
      response.data.pipe(res);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).end();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default new Routes();
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ import { TelevisionComponent } from './components/television/television.componen
 | 
			
		||||
import { StatisticsComponent } from './components/statistics/statistics.component';
 | 
			
		||||
import { MempoolBlockComponent } from './components/mempool-block/mempool-block.component';
 | 
			
		||||
import { AssetComponent } from './components/asset/asset.component';
 | 
			
		||||
import { AssetsComponent } from './assets/assets.component';
 | 
			
		||||
import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component';
 | 
			
		||||
import { StatusViewComponent } from './components/status-view/status-view.component';
 | 
			
		||||
import { DashboardComponent } from './dashboard/dashboard.component';
 | 
			
		||||
import { LatestBlocksComponent } from './components/latest-blocks/latest-blocks.component';
 | 
			
		||||
@ -23,6 +23,9 @@ import { SponsorComponent } from './components/sponsor/sponsor.component';
 | 
			
		||||
import { LiquidMasterPageComponent } from './components/liquid-master-page/liquid-master-page.component';
 | 
			
		||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
 | 
			
		||||
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
 | 
			
		||||
import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
 | 
			
		||||
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
 | 
			
		||||
import { AssetsComponent } from './components/assets/assets.component';
 | 
			
		||||
 | 
			
		||||
let routes: Routes = [
 | 
			
		||||
  {
 | 
			
		||||
@ -343,13 +346,31 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
 | 
			
		||||
        path: 'address/:id',
 | 
			
		||||
        component: AddressComponent
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'assets',
 | 
			
		||||
        component: AssetsNavComponent,
 | 
			
		||||
        children: [
 | 
			
		||||
          {
 | 
			
		||||
            path: 'featured',
 | 
			
		||||
            component: AssetsFeaturedComponent,
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'all',
 | 
			
		||||
            component: AssetsComponent,
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'asset/:id',
 | 
			
		||||
            component: AssetComponent
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
        path: 'assets',
 | 
			
		||||
        component: AssetsComponent,
 | 
			
		||||
            path: 'asset-group/:id',
 | 
			
		||||
            component: AssetGroupComponent
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: '**',
 | 
			
		||||
            redirectTo: 'featured'
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        path: 'docs/api/:type',
 | 
			
		||||
@ -440,7 +461,7 @@ if (browserWindowEnv && browserWindowEnv.BASE_MODULE === 'liquid') {
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'assets',
 | 
			
		||||
            component: AssetsComponent,
 | 
			
		||||
            component: AssetsNavComponent,
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            path: 'docs/api/:type',
 | 
			
		||||
 | 
			
		||||
@ -40,7 +40,8 @@ import { MempoolGraphComponent } from './components/mempool-graph/mempool-graph.
 | 
			
		||||
import { PoolRankingComponent } from './components/pool-ranking/pool-ranking.component';
 | 
			
		||||
import { LbtcPegsGraphComponent } from './components/lbtc-pegs-graph/lbtc-pegs-graph.component';
 | 
			
		||||
import { AssetComponent } from './components/asset/asset.component';
 | 
			
		||||
import { AssetsComponent } from './assets/assets.component';
 | 
			
		||||
import { AssetsComponent } from './components/assets/assets.component';
 | 
			
		||||
import { AssetsNavComponent } from './components/assets/assets-nav/assets-nav.component';
 | 
			
		||||
import { StatusViewComponent } from './components/status-view/status-view.component';
 | 
			
		||||
import { MinerComponent } from './components/miner/miner.component';
 | 
			
		||||
import { SharedModule } from './shared/shared.module';
 | 
			
		||||
@ -64,6 +65,8 @@ import { LanguageService } from './services/language.service';
 | 
			
		||||
import { SponsorComponent } from './components/sponsor/sponsor.component';
 | 
			
		||||
import { PushTransactionComponent } from './components/push-transaction/push-transaction.component';
 | 
			
		||||
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
import { AssetsFeaturedComponent } from './components/assets/assets-featured/assets-featured.component';
 | 
			
		||||
import { AssetGroupComponent } from './components/assets/asset-group/asset-group.component';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
  declarations: [
 | 
			
		||||
@ -110,6 +113,9 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
 | 
			
		||||
    PushTransactionComponent,
 | 
			
		||||
    DocsComponent,
 | 
			
		||||
    ApiDocsNavComponent,
 | 
			
		||||
    AssetsNavComponent,
 | 
			
		||||
    AssetsFeaturedComponent,
 | 
			
		||||
    AssetGroupComponent,
 | 
			
		||||
  ],
 | 
			
		||||
  imports: [
 | 
			
		||||
    BrowserModule.withServerTransition({ appId: 'serverApp' }),
 | 
			
		||||
 | 
			
		||||
@ -1,71 +0,0 @@
 | 
			
		||||
<div class="container-xl">
 | 
			
		||||
  <div class="title-asset">
 | 
			
		||||
    <h1 i18n="Registered assets page header">Registered assets</h1>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
  <form [formGroup]="searchForm" class="form-inline">
 | 
			
		||||
    <div class="input-group mb-2">
 | 
			
		||||
      <input style="width: 250px;" formControlName="searchText" type="text" class="form-control" i18n-placeholder="Search Assets Placeholder Text" placeholder="Search asset">
 | 
			
		||||
      <div class="input-group-append">
 | 
			
		||||
        <button [disabled]="!searchForm.get('searchText')?.value.length" class="btn btn-secondary" type="button" (click)="searchForm.get('searchText')?.setValue('');" autocomplete="off" i18n="Search Clear Button">Clear</button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </form>
 | 
			
		||||
 | 
			
		||||
  <ng-container *ngIf="(assets$ | async) as filteredAssets; else isLoading">
 | 
			
		||||
    <table class="table table-borderless table-striped">
 | 
			
		||||
      <thead>
 | 
			
		||||
        <th class="td-name" i18n="Asset name header">Name</th>
 | 
			
		||||
        <th i18n="Asset ticker header">Ticker</th>
 | 
			
		||||
        <th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
			
		||||
        <th i18n="Asset ID header">Asset ID</th>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        <tr *ngFor="let asset of filteredAssets; trackBy: trackByAsset">
 | 
			
		||||
          <td class="td-name"><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a></td>
 | 
			
		||||
          <td>{{ asset.ticker }}</td>
 | 
			
		||||
          <td class="d-none d-md-block">{{ asset.entity && asset.entity.domain }}</td>
 | 
			
		||||
          <td><a [routerLink]="['/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
    <br>
 | 
			
		||||
 | 
			
		||||
    <ngb-pagination [collectionSize]="assets.length" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="5" [boundaryLinks]="true"></ngb-pagination>
 | 
			
		||||
 | 
			
		||||
  </ng-container>
 | 
			
		||||
 | 
			
		||||
  <ng-template #isLoading>
 | 
			
		||||
 | 
			
		||||
    <table class="table table-borderless table-striped">
 | 
			
		||||
      <thead>
 | 
			
		||||
        <th i18n="Asset name header">Name</th>
 | 
			
		||||
        <th i18n="Asset ticker header">Ticker</th>
 | 
			
		||||
        <th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
			
		||||
        <th i18n="Asset ID header">Asset ID</th>
 | 
			
		||||
      </thead>
 | 
			
		||||
      <tbody>
 | 
			
		||||
        <tr *ngFor="let dummy of [0,0,0,0,0,0,0,0,0,0]">
 | 
			
		||||
          <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
          <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
          <td class="d-none d-md-block"><span class="skeleton-loader"></span></td>
 | 
			
		||||
          <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
        </tr>
 | 
			
		||||
      </tbody>
 | 
			
		||||
    </table>
 | 
			
		||||
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
  <ng-template [ngIf]="error">
 | 
			
		||||
    <div class="text-center">
 | 
			
		||||
      <ng-container i18n="Asset data load error">Error loading assets data.</ng-container>
 | 
			
		||||
      <br>
 | 
			
		||||
      <i>{{ error.error }}</i>
 | 
			
		||||
    </div>
 | 
			
		||||
  </ng-template>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<br>
 | 
			
		||||
@ -1,25 +0,0 @@
 | 
			
		||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
 | 
			
		||||
 | 
			
		||||
import { AssetsComponent } from './assets.component';
 | 
			
		||||
 | 
			
		||||
describe('AssetsComponent', () => {
 | 
			
		||||
  let component: AssetsComponent;
 | 
			
		||||
  let fixture: ComponentFixture<AssetsComponent>;
 | 
			
		||||
 | 
			
		||||
  beforeEach(async(() => {
 | 
			
		||||
    TestBed.configureTestingModule({
 | 
			
		||||
      declarations: [ AssetsComponent ]
 | 
			
		||||
    })
 | 
			
		||||
    .compileComponents();
 | 
			
		||||
  }));
 | 
			
		||||
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    fixture = TestBed.createComponent(AssetsComponent);
 | 
			
		||||
    component = fixture.componentInstance;
 | 
			
		||||
    fixture.detectChanges();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should create', () => {
 | 
			
		||||
    expect(component).toBeTruthy();
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@ -0,0 +1,26 @@
 | 
			
		||||
<div *ngIf="group$ | async as group">
 | 
			
		||||
  
 | 
			
		||||
  <div class="main-title">
 | 
			
		||||
    <h2>{{ group.group.name }}</h2>
 | 
			
		||||
 | 
			
		||||
    <div class="sub-title">Group of {{ group.group.assets.length | number }} assets</div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
  <br>
 | 
			
		||||
 | 
			
		||||
  <div class="featuredBox">
 | 
			
		||||
    <div *ngFor="let asset of group.assets">
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + asset.asset_id + '/icon'">
 | 
			
		||||
        <div class="title">
 | 
			
		||||
          <a [routerLink]="['/assets/asset/', group.asset_id]">{{ asset.name }}</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ticker">{{ asset.ticker }}</div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
@ -0,0 +1,57 @@
 | 
			
		||||
.image {
 | 
			
		||||
  width: 150px;
 | 
			
		||||
  float: left;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.main-title {
 | 
			
		||||
  float: left
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sub-title {
 | 
			
		||||
  color: grey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.featuredBox {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: row wrap;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  gap: 27px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card {
 | 
			
		||||
  background-color: #1d1f31;
 | 
			
		||||
  width: 200px;
 | 
			
		||||
  height: 200px;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title {
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sub-title {
 | 
			
		||||
  color: grey;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.assetIcon {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  height: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.image {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  height: 100px;
 | 
			
		||||
  align-self: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.view-link {
 | 
			
		||||
  margin-top: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ticker {
 | 
			
		||||
  color: grey;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,45 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { ActivatedRoute, ParamMap } from '@angular/router';
 | 
			
		||||
import { combineLatest, Observable } from 'rxjs';
 | 
			
		||||
import { map, switchMap } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-asset-group',
 | 
			
		||||
  templateUrl: './asset-group.component.html',
 | 
			
		||||
  styleUrls: ['./asset-group.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class AssetGroupComponent implements OnInit {
 | 
			
		||||
  group$: Observable<any>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private route: ActivatedRoute,
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private assetsService: AssetsService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.group$ = this.route.paramMap
 | 
			
		||||
      .pipe(
 | 
			
		||||
        switchMap((params: ParamMap) => {
 | 
			
		||||
          return combineLatest([
 | 
			
		||||
            this.assetsService.getAssetsJson$,
 | 
			
		||||
            this.apiService.getAssetGroup$(params.get('id')),
 | 
			
		||||
          ]);
 | 
			
		||||
        }),
 | 
			
		||||
        map(([assets, group]) => {
 | 
			
		||||
          const items = [];
 | 
			
		||||
          // @ts-ignore
 | 
			
		||||
          for (const item of group.assets) {
 | 
			
		||||
            items.push(assets[item]);
 | 
			
		||||
          }
 | 
			
		||||
          console.log(group);
 | 
			
		||||
          return {
 | 
			
		||||
            group: group,
 | 
			
		||||
            assets: items
 | 
			
		||||
          };
 | 
			
		||||
        })
 | 
			
		||||
      );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,27 @@
 | 
			
		||||
<div *ngIf="featuredAssets$ | async as featured; else loading" class="featuredBox">
 | 
			
		||||
    
 | 
			
		||||
  <div *ngFor="let group of featured.featured">
 | 
			
		||||
    <div class="card">
 | 
			
		||||
      <ng-template [ngIf]="group.assets" [ngIfElse]="singleAsset">
 | 
			
		||||
        <img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + group.assets[0] + '/icon'">
 | 
			
		||||
        <div class="title"><a [routerLink]="['/assets/asset-group', group.id]">{{ group.name }}</a></div>
 | 
			
		||||
        <div class="sub-title">Group of {{ group.assets.length | number }} assets</div>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
      <ng-template #singleAsset>
 | 
			
		||||
        <img class="assetIcon" [src]="'https://liquid.network/api/v1/asset/' + group.asset + '/icon'">
 | 
			
		||||
        <div class="title">
 | 
			
		||||
          <a [routerLink]="['/assets/asset/', group.asset]">{{ group.name }}</a>
 | 
			
		||||
        </div>
 | 
			
		||||
        <div class="ticker">{{ group.ticker }}</div>
 | 
			
		||||
      </ng-template>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<ng-template #loading>
 | 
			
		||||
  <br>
 | 
			
		||||
  <div class="text-center loadingGraphs">
 | 
			
		||||
    <div class="spinner-border text-light"></div>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -0,0 +1,46 @@
 | 
			
		||||
 | 
			
		||||
.featuredBox {
 | 
			
		||||
  display: flex;
 | 
			
		||||
  flex-flow: row wrap;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  gap: 27px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.card {
 | 
			
		||||
  background-color: #1d1f31;
 | 
			
		||||
  width: 200px;
 | 
			
		||||
  height: 200px;
 | 
			
		||||
  align-items: center;
 | 
			
		||||
  justify-content: center;
 | 
			
		||||
  flex-wrap: wrap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.title {
 | 
			
		||||
  font-size: 14px;
 | 
			
		||||
  font-weight: bold;
 | 
			
		||||
  margin-top: 10px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.sub-title {
 | 
			
		||||
  color: grey;
 | 
			
		||||
  font-size: 12px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.assetIcon {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  height: 100px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.image {
 | 
			
		||||
  width: 100px;
 | 
			
		||||
  height: 100px;
 | 
			
		||||
  align-self: center;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.view-link {
 | 
			
		||||
  margin-top: 30px;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.ticker {
 | 
			
		||||
  color: grey;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,34 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { combineLatest, Observable } from 'rxjs';
 | 
			
		||||
import { map } from 'rxjs/operators';
 | 
			
		||||
import { ApiService } from 'src/app/services/api.service';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-assets-featured',
 | 
			
		||||
  templateUrl: './assets-featured.component.html',
 | 
			
		||||
  styleUrls: ['./assets-featured.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class AssetsFeaturedComponent implements OnInit {
 | 
			
		||||
  featuredAssets$: Observable<any>;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private apiService: ApiService,
 | 
			
		||||
    private assetsService: AssetsService,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.featuredAssets$ = combineLatest([
 | 
			
		||||
      this.assetsService.getAssetsJson$,
 | 
			
		||||
      this.apiService.listFeaturedAssets$(),
 | 
			
		||||
    ]).pipe(
 | 
			
		||||
      map(([assetsJson, featured]) => {
 | 
			
		||||
        return {
 | 
			
		||||
          assetsJson: assetsJson,
 | 
			
		||||
          featured: featured,
 | 
			
		||||
        };
 | 
			
		||||
      })
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,31 @@
 | 
			
		||||
<div class="container-xl">
 | 
			
		||||
  <div class="title-asset">
 | 
			
		||||
    <h1 i18n="Assets page header">Assets</h1>
 | 
			
		||||
  </div>
 | 
			
		||||
 | 
			
		||||
  <ul class="nav nav-pills">
 | 
			
		||||
    <li class="nav-item">
 | 
			
		||||
      <a class="nav-link" [routerLink]="['/assets/featured']" routerLinkActive="active">Featured</a>
 | 
			
		||||
      
 | 
			
		||||
    </li>
 | 
			
		||||
    <li class="nav-item">
 | 
			
		||||
      <a class="nav-link" [routerLink]="['/assets/all']" routerLinkActive="active">All</a>
 | 
			
		||||
    </li>
 | 
			
		||||
  </ul>
 | 
			
		||||
 | 
			
		||||
  <form [formGroup]="searchForm" class="form-inline">
 | 
			
		||||
    <div class="input-group mb-2">
 | 
			
		||||
      <input style="width: 350px;" formControlName="searchText" type="text" class="form-control" i18n-placeholder="Search Assets Placeholder Text" placeholder="Search asset">
 | 
			
		||||
      <div class="input-group-append">
 | 
			
		||||
        <button [disabled]="!searchForm.get('searchText')?.value.length" class="btn btn-secondary" type="button" (click)="searchForm.get('searchText')?.setValue('');" autocomplete="off" i18n="Search Clear Button">Clear</button>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </form>
 | 
			
		||||
 | 
			
		||||
  <div class="clearfix"></div>
 | 
			
		||||
 | 
			
		||||
  <router-outlet></router-outlet>
 | 
			
		||||
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<br>
 | 
			
		||||
@ -0,0 +1,9 @@
 | 
			
		||||
ul {
 | 
			
		||||
  margin-bottom: 20px;
 | 
			
		||||
  float: left;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
form {
 | 
			
		||||
  float: right;
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
import { Component, OnInit } from '@angular/core';
 | 
			
		||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-assets-nav',
 | 
			
		||||
  templateUrl: './assets-nav.component.html',
 | 
			
		||||
  styleUrls: ['./assets-nav.component.scss']
 | 
			
		||||
})
 | 
			
		||||
export class AssetsNavComponent implements OnInit {
 | 
			
		||||
  activeTab = 0;
 | 
			
		||||
  searchForm: FormGroup;
 | 
			
		||||
 | 
			
		||||
  constructor(
 | 
			
		||||
    private formBuilder: FormBuilder,
 | 
			
		||||
  ) { }
 | 
			
		||||
 | 
			
		||||
  ngOnInit(): void {
 | 
			
		||||
    this.searchForm = this.formBuilder.group({
 | 
			
		||||
      searchText: [{ value: '', disabled: true }, Validators.required]
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										52
									
								
								frontend/src/app/components/assets/assets.component.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								frontend/src/app/components/assets/assets.component.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,52 @@
 | 
			
		||||
<ng-container *ngIf="(assets$ | async) as filteredAssets; else isLoading">
 | 
			
		||||
  <table class="table table-borderless table-striped">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <th class="td-name" i18n="Asset name header">Name</th>
 | 
			
		||||
      <th i18n="Asset ticker header">Ticker</th>
 | 
			
		||||
      <th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
			
		||||
      <th i18n="Asset ID header">Asset ID</th>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
      <tr *ngFor="let asset of filteredAssets; trackBy: trackByAsset">
 | 
			
		||||
        <td class="td-name"><a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.name }}</a></td>
 | 
			
		||||
        <td>{{ asset.ticker }}</td>
 | 
			
		||||
        <td class="d-none d-md-block">{{ asset.entity && asset.entity.domain }}</td>
 | 
			
		||||
        <td><a [routerLink]="['/assets/asset/' | relativeUrl, asset.asset_id]">{{ asset.asset_id | shortenString : 13 }}</a> <app-clipboard class="d-none d-sm-inline-block" [text]="asset.asset_id"></app-clipboard></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
  <br>
 | 
			
		||||
 | 
			
		||||
  <ngb-pagination [collectionSize]="assets.length" [rotate]="true" [pageSize]="itemsPerPage" [(page)]="page" (pageChange)="pageChange(page)" [maxSize]="5" [boundaryLinks]="true"></ngb-pagination>
 | 
			
		||||
 | 
			
		||||
</ng-container>
 | 
			
		||||
 | 
			
		||||
<ng-template #isLoading>
 | 
			
		||||
 | 
			
		||||
  <table class="table table-borderless table-striped">
 | 
			
		||||
    <thead>
 | 
			
		||||
      <th i18n="Asset name header">Name</th>
 | 
			
		||||
      <th i18n="Asset ticker header">Ticker</th>
 | 
			
		||||
      <th class="d-none d-md-block" i18n="Asset Issuer Domain header">Issuer domain</th>
 | 
			
		||||
      <th i18n="Asset ID header">Asset ID</th>
 | 
			
		||||
    </thead>
 | 
			
		||||
    <tbody>
 | 
			
		||||
      <tr *ngFor="let dummy of [0,0,0,0,0,0,0,0,0,0]">
 | 
			
		||||
        <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
        <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
        <td class="d-none d-md-block"><span class="skeleton-loader"></span></td>
 | 
			
		||||
        <td><span class="skeleton-loader"></span></td>
 | 
			
		||||
      </tr>
 | 
			
		||||
    </tbody>
 | 
			
		||||
  </table>
 | 
			
		||||
 | 
			
		||||
</ng-template>
 | 
			
		||||
 | 
			
		||||
<ng-template [ngIf]="error">
 | 
			
		||||
  <div class="text-center">
 | 
			
		||||
    <ng-container i18n="Asset data load error">Error loading assets data.</ng-container>
 | 
			
		||||
    <br>
 | 
			
		||||
    <i>{{ error.error }}</i>
 | 
			
		||||
  </div>
 | 
			
		||||
</ng-template>
 | 
			
		||||
@ -1,13 +1,13 @@
 | 
			
		||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
 | 
			
		||||
import { AssetsService } from '../services/assets.service';
 | 
			
		||||
import { AssetsService } from 'src/app/services/assets.service';
 | 
			
		||||
import { environment } from 'src/environments/environment';
 | 
			
		||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
 | 
			
		||||
import { distinctUntilChanged, map, filter, mergeMap, tap, take } from 'rxjs/operators';
 | 
			
		||||
import { ActivatedRoute, Router } from '@angular/router';
 | 
			
		||||
import { merge, combineLatest, Observable } from 'rxjs';
 | 
			
		||||
import { AssetExtended } from '../interfaces/electrs.interface';
 | 
			
		||||
import { SeoService } from '../services/seo.service';
 | 
			
		||||
import { StateService } from '../services/state.service';
 | 
			
		||||
import { AssetExtended } from 'src/app/interfaces/electrs.interface';
 | 
			
		||||
import { SeoService } from 'src/app/services/seo.service';
 | 
			
		||||
import { StateService } from 'src/app/services/state.service';
 | 
			
		||||
 | 
			
		||||
@Component({
 | 
			
		||||
  selector: 'app-assets',
 | 
			
		||||
@ -23,9 +23,9 @@ export class AssetsComponent implements OnInit {
 | 
			
		||||
  searchForm: FormGroup;
 | 
			
		||||
  assets$: Observable<AssetExtended[]>;
 | 
			
		||||
 | 
			
		||||
  page = 1;
 | 
			
		||||
  error: any;
 | 
			
		||||
 | 
			
		||||
  page = 1;
 | 
			
		||||
  itemsPerPage: number;
 | 
			
		||||
  contentSpace = window.innerHeight - (250 + 200);
 | 
			
		||||
  fiveItemsPxSize = 250;
 | 
			
		||||
@ -49,7 +49,7 @@ export class AssetsComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    this.assets$ = combineLatest([
 | 
			
		||||
      this.assetsService.getAssetsJson$,
 | 
			
		||||
      this.route.queryParams
 | 
			
		||||
      this.route.queryParams,
 | 
			
		||||
    ])
 | 
			
		||||
    .pipe(
 | 
			
		||||
      take(1),
 | 
			
		||||
@ -117,6 +117,14 @@ export class ApiService {
 | 
			
		||||
    return this.httpClient.get<LiquidPegs[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  listFeaturedAssets$(): Observable<any[]> {
 | 
			
		||||
    return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/assets/featured');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getAssetGroup$(id: string): Observable<any> {
 | 
			
		||||
    return this.httpClient.get<any[]>(this.apiBaseUrl + this.apiBasePath + '/api/v1/asset-group/' + id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  postTransaction$(hexPayload: string): Observable<any> {
 | 
			
		||||
    return this.httpClient.post<any>(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user