From 2e5c8bdfd33baaaaa75810f1054b561e448ae25a Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 6 Feb 2022 01:20:26 +0400 Subject: [PATCH 01/13] Featured assets and asset groups --- backend/src/index.ts | 2 + backend/src/routes.ts | 20 ++++++ frontend/src/app/app-routing.module.ts | 35 +++++++-- frontend/src/app/app.module.ts | 8 ++- frontend/src/app/assets/assets.component.html | 71 ------------------- .../src/app/assets/assets.component.spec.ts | 25 ------- .../asset-group/asset-group.component.html | 26 +++++++ .../asset-group/asset-group.component.scss | 57 +++++++++++++++ .../asset-group/asset-group.component.ts | 45 ++++++++++++ .../assets-featured.component.html | 27 +++++++ .../assets-featured.component.scss | 46 ++++++++++++ .../assets-featured.component.ts | 34 +++++++++ .../assets-nav/assets-nav.component.html | 31 ++++++++ .../assets-nav/assets-nav.component.scss | 9 +++ .../assets/assets-nav/assets-nav.component.ts | 23 ++++++ .../components/assets/assets.component.html | 52 ++++++++++++++ .../assets/assets.component.scss | 0 .../assets/assets.component.ts | 12 ++-- frontend/src/app/services/api.service.ts | 8 +++ 19 files changed, 421 insertions(+), 110 deletions(-) delete mode 100644 frontend/src/app/assets/assets.component.html delete mode 100644 frontend/src/app/assets/assets.component.spec.ts create mode 100644 frontend/src/app/components/assets/asset-group/asset-group.component.html create mode 100644 frontend/src/app/components/assets/asset-group/asset-group.component.scss create mode 100644 frontend/src/app/components/assets/asset-group/asset-group.component.ts create mode 100644 frontend/src/app/components/assets/assets-featured/assets-featured.component.html create mode 100644 frontend/src/app/components/assets/assets-featured/assets-featured.component.scss create mode 100644 frontend/src/app/components/assets/assets-featured/assets-featured.component.ts create mode 100644 frontend/src/app/components/assets/assets-nav/assets-nav.component.html create mode 100644 frontend/src/app/components/assets/assets-nav/assets-nav.component.scss create mode 100644 frontend/src/app/components/assets/assets-nav/assets-nav.component.ts create mode 100644 frontend/src/app/components/assets/assets.component.html rename frontend/src/app/{ => components}/assets/assets.component.scss (100%) rename frontend/src/app/{ => components}/assets/assets.component.ts (94%) diff --git a/backend/src/index.ts b/backend/src/index.ts index f78c5922b..5dea199fc 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -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) ; } diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 044f9a3ac..e937678cc 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -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(); diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 36a53781f..243691661 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -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: 'asset/:id', - component: AssetComponent - }, { path: 'assets', - component: AssetsComponent, + component: AssetsNavComponent, + children: [ + { + path: 'featured', + component: AssetsFeaturedComponent, + }, + { + path: 'all', + component: AssetsComponent, + }, + { + path: 'asset/:id', + component: AssetComponent + }, + { + 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', diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index f9eae0666..97fc16204 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -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' }), diff --git a/frontend/src/app/assets/assets.component.html b/frontend/src/app/assets/assets.component.html deleted file mode 100644 index c8962cd15..000000000 --- a/frontend/src/app/assets/assets.component.html +++ /dev/null @@ -1,71 +0,0 @@ -
-
-

Registered assets

-
-
- -
-
- -
- -
-
-
- - - - - - - - - - - - - - - - - -
NameTickerIssuer domainAsset ID
{{ asset.name }}{{ asset.ticker }}{{ asset.entity && asset.entity.domain }}{{ asset.asset_id | shortenString : 13 }}
- -
- - - -
- - - - - - - - - - - - - - - - - - -
NameTickerIssuer domainAsset ID
- -
- - -
- Error loading assets data. -
- {{ error.error }} -
-
- -
- -
diff --git a/frontend/src/app/assets/assets.component.spec.ts b/frontend/src/app/assets/assets.component.spec.ts deleted file mode 100644 index ed39b7122..000000000 --- a/frontend/src/app/assets/assets.component.spec.ts +++ /dev/null @@ -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; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [ AssetsComponent ] - }) - .compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(AssetsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.html b/frontend/src/app/components/assets/asset-group/asset-group.component.html new file mode 100644 index 000000000..a6858b76f --- /dev/null +++ b/frontend/src/app/components/assets/asset-group/asset-group.component.html @@ -0,0 +1,26 @@ +
+ +
+

{{ group.group.name }}

+ +
Group of {{ group.group.assets.length | number }} assets
+
+ + +
+ +
+ +
+
+
+ + +
{{ asset.ticker }}
+
+
+
+ +
diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.scss b/frontend/src/app/components/assets/asset-group/asset-group.component.scss new file mode 100644 index 000000000..7a0735a24 --- /dev/null +++ b/frontend/src/app/components/assets/asset-group/asset-group.component.scss @@ -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; +} diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.ts b/frontend/src/app/components/assets/asset-group/asset-group.component.ts new file mode 100644 index 000000000..0143121b5 --- /dev/null +++ b/frontend/src/app/components/assets/asset-group/asset-group.component.ts @@ -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; + + 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 + }; + }) + ); + } +} diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.html b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html new file mode 100644 index 000000000..bb5ab7f3c --- /dev/null +++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html @@ -0,0 +1,27 @@ +
+ +
+
+ + + +
Group of {{ group.assets.length | number }} assets
+
+ + + +
{{ group.ticker }}
+
+
+
+ +
+ + +
+
+
+
+
\ No newline at end of file diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.scss b/frontend/src/app/components/assets/assets-featured/assets-featured.component.scss new file mode 100644 index 000000000..41be2b748 --- /dev/null +++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.scss @@ -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; +} diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts b/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts new file mode 100644 index 000000000..23c84679b --- /dev/null +++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts @@ -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; + + 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, + }; + }) + ); + } + +} diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.html b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html new file mode 100644 index 000000000..9ee7ac72a --- /dev/null +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html @@ -0,0 +1,31 @@ +
+
+

Assets

+
+ + + +
+
+ +
+ +
+
+
+ +
+ + + +
+ +
diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.scss b/frontend/src/app/components/assets/assets-nav/assets-nav.component.scss new file mode 100644 index 000000000..530ed7bdf --- /dev/null +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.scss @@ -0,0 +1,9 @@ +ul { + margin-bottom: 20px; + float: left; + +} + +form { + float: right; +} diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts new file mode 100644 index 000000000..e1b31372b --- /dev/null +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts @@ -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] + }); + } + +} diff --git a/frontend/src/app/components/assets/assets.component.html b/frontend/src/app/components/assets/assets.component.html new file mode 100644 index 000000000..ec9ac079f --- /dev/null +++ b/frontend/src/app/components/assets/assets.component.html @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + +
NameTickerIssuer domainAsset ID
{{ asset.name }}{{ asset.ticker }}{{ asset.entity && asset.entity.domain }}{{ asset.asset_id | shortenString : 13 }}
+ +
+ + + +
+ + + + + + + + + + + + + + + + + + +
NameTickerIssuer domainAsset ID
+ +
+ + +
+ Error loading assets data. +
+ {{ error.error }} +
+
diff --git a/frontend/src/app/assets/assets.component.scss b/frontend/src/app/components/assets/assets.component.scss similarity index 100% rename from frontend/src/app/assets/assets.component.scss rename to frontend/src/app/components/assets/assets.component.scss diff --git a/frontend/src/app/assets/assets.component.ts b/frontend/src/app/components/assets/assets.component.ts similarity index 94% rename from frontend/src/app/assets/assets.component.ts rename to frontend/src/app/components/assets/assets.component.ts index 49c42d76e..d5cbd10ab 100644 --- a/frontend/src/app/assets/assets.component.ts +++ b/frontend/src/app/components/assets/assets.component.ts @@ -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; + 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), diff --git a/frontend/src/app/services/api.service.ts b/frontend/src/app/services/api.service.ts index 03200c64c..3d9125c25 100644 --- a/frontend/src/app/services/api.service.ts +++ b/frontend/src/app/services/api.service.ts @@ -117,6 +117,14 @@ export class ApiService { return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/liquid/pegs/month'); } + listFeaturedAssets$(): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/assets/featured'); + } + + getAssetGroup$(id: string): Observable { + return this.httpClient.get(this.apiBaseUrl + this.apiBasePath + '/api/v1/asset-group/' + id); + } + postTransaction$(hexPayload: string): Observable { return this.httpClient.post(this.apiBaseUrl + this.apiBasePath + '/api/tx', hexPayload, { responseType: 'text' as 'json'}); } From d33c12cdee3bd2ae0708a1dde3c7e3767360063c Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 6 Feb 2022 03:31:50 +0400 Subject: [PATCH 02/13] Asset search --- .../assets-nav/assets-nav.component.html | 2 +- .../assets/assets-nav/assets-nav.component.ts | 78 ++++++++++++- .../app/components/assets/assets.component.ts | 103 +++--------------- frontend/src/app/services/assets.service.ts | 27 ++++- 4 files changed, 118 insertions(+), 92 deletions(-) diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.html b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html index 9ee7ac72a..73d391254 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.html +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html @@ -15,7 +15,7 @@
- +
diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts index e1b31372b..d7280d50b 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts @@ -1,5 +1,15 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; +import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap'; +import { merge, Observable, of, Subject } from 'rxjs'; +import { distinctUntilChanged, filter, map, switchMap } from 'rxjs/operators'; +import { AssetExtended } from 'src/app/interfaces/electrs.interface'; +import { AssetsService } from 'src/app/services/assets.service'; +import { SeoService } from 'src/app/services/seo.service'; +import { StateService } from 'src/app/services/state.service'; +import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; +import { environment } from 'src/environments/environment'; @Component({ selector: 'app-assets-nav', @@ -7,16 +17,78 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; styleUrls: ['./assets-nav.component.scss'] }) export class AssetsNavComponent implements OnInit { - activeTab = 0; + @ViewChild('instance', {static: true}) instance: NgbTypeahead; + nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; searchForm: FormGroup; + assetsCache: AssetExtended[]; + + typeaheadSearchFn: ((text: Observable) => Observable); + formatterFn = (asset: AssetExtended) => asset.name + ' (' + asset.ticker + ')'; + focus$ = new Subject(); + click$ = new Subject(); + + itemsPerPage = 15; constructor( private formBuilder: FormBuilder, + private seoService: SeoService, + private router: Router, + private assetsService: AssetsService, + private stateService: StateService, + private relativeUrlPipe: RelativeUrlPipe, ) { } ngOnInit(): void { + this.typeaheadSearchFn = this.typeaheadSearch; + this.searchForm = this.formBuilder.group({ - searchText: [{ value: '', disabled: true }, Validators.required] + searchText: [{ value: '', disabled: false }, Validators.required] + }); + } + + typeaheadSearch = (text$: Observable) => { + const debouncedText$ = text$.pipe( + distinctUntilChanged() + ); + const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen())); + const inputFocus$ = this.focus$; + + return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$) + .pipe( + switchMap((searchText) => { + if (!searchText.length) { + return of([]); + } + return this.assetsService.getAssetsJson$.pipe( + map((assets) => { + if (searchText.length ) { + const filteredAssets = assets.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 + || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1 + || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1); + assets = filteredAssets; + return filteredAssets.slice(0, this.itemsPerPage); + } else { + return assets.slice(0, this.itemsPerPage); + } + }) + ) + }), + ); + } + + itemSelected() { + setTimeout(() => this.search()); + } + + search() { + const searchText = this.searchForm.value.searchText; + this.navigate('/assets/asset/', searchText.asset_id); + } + + navigate(url: string, searchText: string, extras?: any) { + this.router.navigate([this.relativeUrlPipe.transform(url), searchText], extras); + this.searchForm.setValue({ + searchText: '', }); } diff --git a/frontend/src/app/components/assets/assets.component.ts b/frontend/src/app/components/assets/assets.component.ts index d5cbd10ab..542765033 100644 --- a/frontend/src/app/components/assets/assets.component.ts +++ b/frontend/src/app/components/assets/assets.component.ts @@ -1,10 +1,10 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; 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 { FormGroup } from '@angular/forms'; +import { filter, map, switchMap, take } from 'rxjs/operators'; import { ActivatedRoute, Router } from '@angular/router'; -import { merge, combineLatest, Observable } from 'rxjs'; +import { combineLatest, Observable } from 'rxjs'; import { AssetExtended } from 'src/app/interfaces/electrs.interface'; import { SeoService } from 'src/app/services/seo.service'; import { StateService } from 'src/app/services/state.service'; @@ -32,7 +32,6 @@ export class AssetsComponent implements OnInit { constructor( private assetsService: AssetsService, - private formBuilder: FormBuilder, private route: ActivatedRoute, private router: Router, private seoService: SeoService, @@ -43,56 +42,20 @@ export class AssetsComponent implements OnInit { this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`); this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); - this.searchForm = this.formBuilder.group({ - searchText: [{ value: '', disabled: true }, Validators.required] - }); - this.assets$ = combineLatest([ this.assetsService.getAssetsJson$, this.route.queryParams, ]) - .pipe( - take(1), - mergeMap(([assets, qp]) => { - this.assets = Object.values(assets); - if (this.stateService.network === 'liquid') { - // @ts-ignore - this.assets.push({ - name: 'Liquid Bitcoin', - ticker: 'L-BTC', - asset_id: this.nativeAssetId, - }); - } else if (this.stateService.network === 'liquidtestnet') { - // @ts-ignore - this.assets.push({ - name: 'Test Liquid Bitcoin', - ticker: 'tL-BTC', - asset_id: this.nativeAssetId, - }); - } + .pipe( + take(1), + switchMap(([assets, qp]) => { + this.assets = assets; - this.assets = this.assets.sort((a: any, b: any) => a.name.localeCompare(b.name)); - this.assetsCache = this.assets; - this.searchForm.get('searchText').enable(); - - if (qp.search) { - this.searchForm.get('searchText').setValue(qp.search, { emitEvent: false }); - } - - return merge( - this.searchForm.get('searchText').valueChanges - .pipe( - distinctUntilChanged(), - tap((text) => { - this.page = 1; - this.searchTextChanged(text); - }) - ), - this.route.queryParams + return this.route.queryParams .pipe( filter((queryParams) => { const newPage = parseInt(queryParams.page, 10); - if (newPage !== this.page || queryParams.search !== this.searchForm.get('searchText').value) { + if (newPage !== this.page) { return true; } return false; @@ -104,38 +67,19 @@ export class AssetsComponent implements OnInit { } else { this.page = 1; } - if (this.searchForm.get('searchText').value !== (queryParams.search || '')) { - this.searchTextChanged(queryParams.search); - } - if (queryParams.search) { - this.searchForm.get('searchText').setValue(queryParams.search, { emitEvent: false }); - return queryParams.search; - } return ''; }) - ), - ); - }), - map((searchText) => { - const start = (this.page - 1) * this.itemsPerPage; - if (searchText.length ) { - const filteredAssets = this.assetsCache.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 - || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1); - this.assets = filteredAssets; - return filteredAssets.slice(start, this.itemsPerPage + start); - } else { - this.assets = this.assetsCache; + ); + }), + map(() => { + const start = (this.page - 1) * this.itemsPerPage; return this.assets.slice(start, this.itemsPerPage + start); - } - }) - ); + }) + ); } pageChange(page: number) { - const queryParams = { page: page, search: this.searchForm.get('searchText').value }; - if (queryParams.search === '') { - queryParams.search = null; - } + const queryParams = { page: page }; if (queryParams.page === 1) { queryParams.page = null; } @@ -147,21 +91,6 @@ export class AssetsComponent implements OnInit { }); } - searchTextChanged(text: string) { - const queryParams = { search: text, page: 1 }; - if (queryParams.search === '') { - queryParams.search = null; - } - if (queryParams.page === 1) { - queryParams.page = null; - } - this.router.navigate([], { - relativeTo: this.route, - queryParams: queryParams, - queryParamsHandling: 'merge', - }); - } - trackByAsset(index: number, asset: any) { return asset.asset_id; } diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index 260a48b7b..d5693abf8 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -3,12 +3,16 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { map, shareReplay, switchMap } from 'rxjs/operators'; import { StateService } from './state.service'; +import { environment } from 'src/environments/environment'; +import { AssetExtended } from '../interfaces/electrs.interface'; @Injectable({ providedIn: 'root' }) export class AssetsService { - getAssetsJson$: Observable; + nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; + + getAssetsJson$: Observable; getAssetsMinimalJson$: Observable; getMiningPools$: Observable; @@ -24,6 +28,27 @@ export class AssetsService { this.getAssetsJson$ = this.stateService.networkChanged$ .pipe( switchMap(() => this.httpClient.get(`${apiBaseUrl}/resources/assets${this.stateService.network === 'liquidtestnet' ? '-testnet' : ''}.json`)), + map((rawAssets) => { + const assets: AssetExtended[] = Object.values(rawAssets); + + if (this.stateService.network === 'liquid') { + // @ts-ignore + assets.push({ + name: 'Liquid Bitcoin', + ticker: 'L-BTC', + asset_id: this.nativeAssetId, + }); + } else if (this.stateService.network === 'liquidtestnet') { + // @ts-ignore + assets.push({ + name: 'Test Liquid Bitcoin', + ticker: 'tL-BTC', + asset_id: this.nativeAssetId, + }); + } + + return assets.sort((a: any, b: any) => a.name.localeCompare(b.name)); + }), shareReplay(1), ); this.getAssetsMinimalJson$ = this.stateService.networkChanged$ From 91082f27e7186d37afc945350e9fe5ab52157c56 Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 6 Feb 2022 04:18:56 +0400 Subject: [PATCH 03/13] SEO and various render fixes. --- .../src/app/components/asset/asset.component.ts | 1 + .../assets/asset-group/asset-group.component.ts | 3 +-- .../assets-featured.component.html | 2 +- .../assets-featured.component.ts | 17 ++--------------- .../assets/assets-nav/assets-nav.component.ts | 6 +++--- .../app/components/assets/assets.component.ts | 2 +- frontend/src/app/services/assets.service.ts | 7 +++++-- 7 files changed, 14 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/components/asset/asset.component.ts b/frontend/src/app/components/asset/asset.component.ts index ecb216052..e57bbee7a 100644 --- a/frontend/src/app/components/asset/asset.component.ts +++ b/frontend/src/app/components/asset/asset.component.ts @@ -63,6 +63,7 @@ export class AssetComponent implements OnInit, OnDestroy { .pipe( switchMap((params: ParamMap) => { this.error = undefined; + this.imageError = false; this.isLoadingAsset = true; this.loadedConfirmedTxCount = 0; this.asset = null; diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.ts b/frontend/src/app/components/assets/asset-group/asset-group.component.ts index 0143121b5..29cb10dc7 100644 --- a/frontend/src/app/components/assets/asset-group/asset-group.component.ts +++ b/frontend/src/app/components/assets/asset-group/asset-group.component.ts @@ -32,9 +32,8 @@ export class AssetGroupComponent implements OnInit { const items = []; // @ts-ignore for (const item of group.assets) { - items.push(assets[item]); + items.push(assets.objects[item]); } - console.log(group); return { group: group, assets: items diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.html b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html index bb5ab7f3c..937ca8113 100644 --- a/frontend/src/app/components/assets/assets-featured/assets-featured.component.html +++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html @@ -1,6 +1,6 @@
-
+
diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts b/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts index 23c84679b..db16a8f2b 100644 --- a/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts +++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.ts @@ -1,8 +1,6 @@ import { Component, OnInit } from '@angular/core'; -import { combineLatest, Observable } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { Observable } from 'rxjs'; import { ApiService } from 'src/app/services/api.service'; -import { AssetsService } from 'src/app/services/assets.service'; @Component({ selector: 'app-assets-featured', @@ -14,21 +12,10 @@ export class AssetsFeaturedComponent implements OnInit { 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, - }; - }) - ); + this.featuredAssets$ = this.apiService.listFeaturedAssets$(); } } diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts index d7280d50b..ac8dded67 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.ts @@ -39,6 +39,7 @@ export class AssetsNavComponent implements OnInit { ) { } ngOnInit(): void { + this.seoService.setTitle($localize`:@@ee8f8008bae6ce3a49840c4e1d39b4af23d4c263:Assets`); this.typeaheadSearchFn = this.typeaheadSearch; this.searchForm = this.formBuilder.group({ @@ -62,13 +63,12 @@ export class AssetsNavComponent implements OnInit { return this.assetsService.getAssetsJson$.pipe( map((assets) => { if (searchText.length ) { - const filteredAssets = assets.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 + const filteredAssets = assets.array.filter((asset) => asset.name.toLowerCase().indexOf(searchText.toLowerCase()) > -1 || (asset.ticker || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1 || (asset.entity && asset.entity.domain || '').toLowerCase().indexOf(searchText.toLowerCase()) > -1); - assets = filteredAssets; return filteredAssets.slice(0, this.itemsPerPage); } else { - return assets.slice(0, this.itemsPerPage); + return assets.array.slice(0, this.itemsPerPage); } }) ) diff --git a/frontend/src/app/components/assets/assets.component.ts b/frontend/src/app/components/assets/assets.component.ts index 542765033..b6a8a8f1b 100644 --- a/frontend/src/app/components/assets/assets.component.ts +++ b/frontend/src/app/components/assets/assets.component.ts @@ -49,7 +49,7 @@ export class AssetsComponent implements OnInit { .pipe( take(1), switchMap(([assets, qp]) => { - this.assets = assets; + this.assets = assets.array; return this.route.queryParams .pipe( diff --git a/frontend/src/app/services/assets.service.ts b/frontend/src/app/services/assets.service.ts index d5693abf8..9454ef7e2 100644 --- a/frontend/src/app/services/assets.service.ts +++ b/frontend/src/app/services/assets.service.ts @@ -12,7 +12,7 @@ import { AssetExtended } from '../interfaces/electrs.interface'; export class AssetsService { nativeAssetId = this.stateService.network === 'liquidtestnet' ? environment.nativeTestAssetId : environment.nativeAssetId; - getAssetsJson$: Observable; + getAssetsJson$: Observable<{ array: AssetExtended[]; objects: any}>; getAssetsMinimalJson$: Observable; getMiningPools$: Observable; @@ -47,7 +47,10 @@ export class AssetsService { }); } - return assets.sort((a: any, b: any) => a.name.localeCompare(b.name)); + return { + objects: rawAssets, + array: assets.sort((a: any, b: any) => a.name.localeCompare(b.name)), + }; }), shareReplay(1), ); From ff4c097c4812f5afeda5c5faef35303a6ddf75ec Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 6 Feb 2022 04:44:40 +0400 Subject: [PATCH 04/13] Mobile layout fixes. --- .../asset-group/asset-group.component.html | 2 +- .../assets-featured.component.html | 8 +++-- .../assets-nav/assets-nav.component.html | 34 ++++++++++--------- .../assets-nav/assets-nav.component.scss | 15 ++++++++ .../components/assets/assets.component.html | 2 +- .../app/components/assets/assets.component.ts | 2 ++ 6 files changed, 43 insertions(+), 20 deletions(-) diff --git a/frontend/src/app/components/assets/asset-group/asset-group.component.html b/frontend/src/app/components/assets/asset-group/asset-group.component.html index a6858b76f..64fe182e9 100644 --- a/frontend/src/app/components/assets/asset-group/asset-group.component.html +++ b/frontend/src/app/components/assets/asset-group/asset-group.component.html @@ -16,7 +16,7 @@
{{ asset.ticker }}
diff --git a/frontend/src/app/components/assets/assets-featured/assets-featured.component.html b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html index 937ca8113..f94157766 100644 --- a/frontend/src/app/components/assets/assets-featured/assets-featured.component.html +++ b/frontend/src/app/components/assets/assets-featured/assets-featured.component.html @@ -3,12 +3,16 @@
- + + +
Group of {{ group.assets.length | number }} assets
- + + + diff --git a/frontend/src/app/components/assets/assets-nav/assets-nav.component.html b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html index 73d391254..c543e1893 100644 --- a/frontend/src/app/components/assets/assets-nav/assets-nav.component.html +++ b/frontend/src/app/components/assets/assets-nav/assets-nav.component.html @@ -3,24 +3,26 @@

Assets

- +