diff --git a/frontend/src/app/components/search-form/search-form.component.scss b/frontend/src/app/components/search-form/search-form.component.scss
index f316c3aa7..448cb28b3 100644
--- a/frontend/src/app/components/search-form/search-form.component.scss
+++ b/frontend/src/app/components/search-form/search-form.component.scss
@@ -32,6 +32,7 @@ form {
}
.search-box-container {
+ position: relative;
width: 100%;
@media (min-width: 768px) {
min-width: 400px;
@@ -48,4 +49,4 @@ form {
.btn {
width: 100px;
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/app/components/search-form/search-form.component.ts b/frontend/src/app/components/search-form/search-form.component.ts
index d83975c50..3914918ad 100644
--- a/frontend/src/app/components/search-form/search-form.component.ts
+++ b/frontend/src/app/components/search-form/search-form.component.ts
@@ -1,41 +1,40 @@
-import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild } from '@angular/core';
+import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, HostListener } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AssetsService } from 'src/app/services/assets.service';
import { StateService } from 'src/app/services/state.service';
-import { Observable, of, Subject, merge } from 'rxjs';
+import { Observable, of, Subject, merge, zip } from 'rxjs';
import { debounceTime, distinctUntilChanged, switchMap, filter, catchError, map } from 'rxjs/operators';
import { ElectrsApiService } from 'src/app/services/electrs-api.service';
-import { NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
-import { ShortenStringPipe } from 'src/app/shared/pipes/shorten-string-pipe/shorten-string.pipe';
+import { ApiService } from 'src/app/services/api.service';
+import { SearchResultsComponent } from './search-results/search-results.component';
@Component({
selector: 'app-search-form',
templateUrl: './search-form.component.html',
styleUrls: ['./search-form.component.scss'],
- changeDetection: ChangeDetectionStrategy.OnPush
+ changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SearchFormComponent implements OnInit {
network = '';
assets: object = {};
isSearching = false;
- typeaheadSearchFn: ((text: Observable
) => Observable);
-
+ typeAhead$: Observable;
searchForm: FormGroup;
- isMobile = (window.innerWidth <= 767.98);
- @Output() searchTriggered = new EventEmitter();
regexAddress = /^([a-km-zA-HJ-NP-Z1-9]{26,35}|[a-km-zA-HJ-NP-Z1-9]{80}|[a-z]{2,5}1[ac-hj-np-z02-9]{8,100}|[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100})$/;
regexBlockhash = /^[0]{8}[a-fA-F0-9]{56}$/;
regexTransaction = /^([a-fA-F0-9]{64}):?(\d+)?$/;
regexBlockheight = /^[0-9]+$/;
-
- @ViewChild('instance', {static: true}) instance: NgbTypeahead;
focus$ = new Subject();
click$ = new Subject();
- formatterFn = (address: string) => this.shortenStringPipe.transform(address, this.isMobile ? 33 : 40);
+ @Output() searchTriggered = new EventEmitter();
+ @ViewChild('searchResults') searchResults: SearchResultsComponent;
+ @HostListener('keydown', ['$event']) keydown($event) {
+ this.handleKeyDown($event);
+ }
constructor(
private formBuilder: FormBuilder,
@@ -43,12 +42,11 @@ export class SearchFormComponent implements OnInit {
private assetsService: AssetsService,
private stateService: StateService,
private electrsApiService: ElectrsApiService,
+ private apiService: ApiService,
private relativeUrlPipe: RelativeUrlPipe,
- private shortenStringPipe: ShortenStringPipe,
) { }
ngOnInit() {
- this.typeaheadSearchFn = this.typeaheadSearch;
this.stateService.networkChanged$.subscribe((network) => this.network = network);
this.searchForm = this.formBuilder.group({
@@ -61,43 +59,63 @@ export class SearchFormComponent implements OnInit {
this.assets = assets;
});
}
- }
- typeaheadSearch = (text$: Observable) => {
- const debouncedText$ = text$.pipe(
- map((text) => {
- if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) {
- return text.substr(1);
- }
- return text;
- }),
- debounceTime(200),
- distinctUntilChanged()
- );
- const clicksWithClosedPopup$ = this.click$.pipe(filter(() => !this.instance.isPopupOpen()));
- const inputFocus$ = this.focus$;
-
- return merge(debouncedText$, inputFocus$, clicksWithClosedPopup$)
+ this.typeAhead$ = this.searchForm.get('searchText').valueChanges
.pipe(
+ map((text) => {
+ if (this.network === 'bisq' && text.match(/^(b)[^c]/i)) {
+ return text.substr(1);
+ }
+ return text;
+ }),
+ debounceTime(300),
+ distinctUntilChanged(),
switchMap((text) => {
if (!text.length) {
- return of([]);
+ return of([
+ [],
+ {
+ nodes: [],
+ channels: [],
+ }
+ ]);
}
- return this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([])));
+ return zip(
+ this.electrsApiService.getAddressesByPrefix$(text).pipe(catchError(() => of([]))),
+ this.apiService.lightningSearch$(text),
+ );
}),
- map((result: string[]) => {
+ map((result: any[]) => {
if (this.network === 'bisq') {
- return result.map((address: string) => 'B' + address);
+ return result[0].map((address: string) => 'B' + address);
}
- return result;
+ return {
+ addresses: result[0],
+ nodes: result[1].nodes,
+ channels: result[1].channels,
+ totalResults: result[0].length + result[1].nodes.length + result[1].channels.length,
+ };
})
);
- }
+ }
+ handleKeyDown($event) {
+ this.searchResults.handleKeyDown($event);
+ }
itemSelected() {
setTimeout(() => this.search());
}
+ selectedResult(result: any) {
+ if (typeof result === 'string') {
+ this.navigate('/address/', result);
+ } else if (result.alias) {
+ this.navigate('/lightning/node/', result.public_key);
+ } else if (result.short_id) {
+ this.navigate('/lightning/channel/', result.id);
+ }
+ }
+
search() {
const searchText = this.searchForm.value.searchText.trim();
if (searchText) {
diff --git a/frontend/src/app/components/search-form/search-results/search-results.component.html b/frontend/src/app/components/search-form/search-results/search-results.component.html
new file mode 100644
index 000000000..e3e3e5212
--- /dev/null
+++ b/frontend/src/app/components/search-form/search-results/search-results.component.html
@@ -0,0 +1,26 @@
+
diff --git a/frontend/src/app/components/search-form/search-results/search-results.component.scss b/frontend/src/app/components/search-form/search-results/search-results.component.scss
new file mode 100644
index 000000000..094865bb6
--- /dev/null
+++ b/frontend/src/app/components/search-form/search-results/search-results.component.scss
@@ -0,0 +1,16 @@
+.card-title {
+ color: #4a68b9;
+ font-size: 10px;
+ margin-bottom: 4px;
+ font-size: 1rem;
+
+ margin-left: 10px;
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 42px;
+ left: 0px;
+ box-shadow: 0.125rem 0.125rem 0.25rem rgba(0,0,0,0.075);
+ width: 100%;
+}
diff --git a/frontend/src/app/components/search-form/search-results/search-results.component.ts b/frontend/src/app/components/search-form/search-results/search-results.component.ts
new file mode 100644
index 000000000..0ce88fe04
--- /dev/null
+++ b/frontend/src/app/components/search-form/search-results/search-results.component.ts
@@ -0,0 +1,68 @@
+import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
+
+@Component({
+ selector: 'app-search-results',
+ templateUrl: './search-results.component.html',
+ styleUrls: ['./search-results.component.scss'],
+})
+export class SearchResultsComponent implements OnChanges {
+ @Input() results: any = {};
+ @Input() searchTerm = '';
+ @Output() selectedResult = new EventEmitter();
+
+ isMobile = (window.innerWidth <= 767.98);
+ resultsFlattened = [];
+ activeIdx = 0;
+ focusFirst = true;
+
+ constructor() { }
+
+ ngOnChanges() {
+ this.activeIdx = 0;
+ if (this.results) {
+ this.resultsFlattened = [...this.results.addresses, ...this.results.nodes, ...this.results.channels];
+ }
+ }
+
+ handleKeyDown(event: KeyboardEvent) {
+ switch (event.key) {
+ case 'ArrowDown':
+ event.preventDefault();
+ this.next();
+ break;
+ case 'ArrowUp':
+ event.preventDefault();
+ this.prev();
+ break;
+ case 'Enter':
+ event.preventDefault();
+ this.selectedResult.emit(this.resultsFlattened[this.activeIdx]);
+ this.results = null;
+ break;
+ }
+ }
+
+ clickItem(id: number) {
+ this.selectedResult.emit(this.resultsFlattened[id]);
+ this.results = null;
+ }
+
+ next() {
+ if (this.activeIdx === this.resultsFlattened.length - 1) {
+ this.activeIdx = this.focusFirst ? (this.activeIdx + 1) % this.resultsFlattened.length : -1;
+ } else {
+ this.activeIdx++;
+ }
+ }
+
+ prev() {
+ if (this.activeIdx < 0) {
+ this.activeIdx = this.resultsFlattened.length - 1;
+ } else if (this.activeIdx === 0) {
+ this.activeIdx = this.focusFirst ? this.resultsFlattened.length - 1 : -1;
+ } else {
+ this.activeIdx--;
+ }
+ }
+
+}
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.ts b/frontend/src/app/components/transactions-list/transactions-list.component.ts
index ab0a742cf..0ea41a2bb 100644
--- a/frontend/src/app/components/transactions-list/transactions-list.component.ts
+++ b/frontend/src/app/components/transactions-list/transactions-list.component.ts
@@ -5,7 +5,7 @@ import { Outspend, Transaction, Vin, Vout } from '../../interfaces/electrs.inter
import { ElectrsApiService } from '../../services/electrs-api.service';
import { environment } from 'src/environments/environment';
import { AssetsService } from 'src/app/services/assets.service';
-import { map, tap, switchMap } from 'rxjs/operators';
+import { filter, map, tap, switchMap } from 'rxjs/operators';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
import { ApiService } from 'src/app/services/api.service';
@@ -78,6 +78,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
),
this.refreshChannels$
.pipe(
+ filter(() => this.stateService.env.LIGHTNING),
switchMap((txIds) => this.apiService.getChannelByTxIds$(txIds)),
map((channels) => {
this.channels = channels;
diff --git a/frontend/src/app/lightning/channels-list/channels-list.component.html b/frontend/src/app/lightning/channels-list/channels-list.component.html
index 066d37a70..fe6d44e42 100644
--- a/frontend/src/app/lightning/channels-list/channels-list.component.html
+++ b/frontend/src/app/lightning/channels-list/channels-list.component.html
@@ -3,10 +3,10 @@
Node Alias |
- Node ID |
- Status |
- Fee Rate |
- Capacity |
+ Node ID |
+ Status |
+ Fee Rate |
+ Capacity |
Channel ID |
@@ -15,18 +15,18 @@
{{ channel.alias_left || '?' }}
|
-
+ |
{{ channel.node1_public_key | shortenString : 10 }}
|
-
+ |
Inactive
Active
Closed
|
-
+ |
{{ channel.node1_fee_rate / 10000 | number }}%
|
@@ -34,22 +34,22 @@
{{ channel.alias_right || '?' }}
|
-
+ |
{{ channel.node2_public_key | shortenString : 10 }}
|
-
+ |
Inactive
Active
Closed
|
-
+ |
{{ channel.node2_fee_rate / 10000 | number }}%
|
-
+ |
|
@@ -66,13 +66,13 @@
|
|
-
+ |
|
-
+ |
|
-
+ |
|
diff --git a/frontend/src/app/lightning/node/node.component.ts b/frontend/src/app/lightning/node/node.component.ts
index f3c0d6153..5842a1b9d 100644
--- a/frontend/src/app/lightning/node/node.component.ts
+++ b/frontend/src/app/lightning/node/node.component.ts
@@ -47,7 +47,6 @@ export class NodeComponent implements OnInit {
socket: node.public_key + '@' + socket,
});
}
- console.log(socketsObject);
node.socketsObject = socketsObject;
return node;
}),
|