From 6ce3c1d75d314f6fc0634a373bb2db4d97b78fd0 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 24 Jul 2023 10:18:00 +0900 Subject: [PATCH 1/5] [search bar] auto focus only in dashboards --- .../mining-dashboard.component.ts | 10 +++++++-- .../search-form/search-form.component.html | 2 +- .../search-form/search-form.component.ts | 21 ++++++++++++++++--- .../src/app/dashboard/dashboard.component.ts | 10 ++++++--- frontend/src/app/services/state.service.ts | 2 ++ 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts index df4713374..22d0e11fe 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts @@ -1,6 +1,7 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { AfterViewChecked, ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { SeoService } from '../../services/seo.service'; import { WebsocketService } from '../../services/websocket.service'; +import { StateService } from '../../services/state.service'; @Component({ selector: 'app-mining-dashboard', @@ -8,10 +9,11 @@ import { WebsocketService } from '../../services/websocket.service'; styleUrls: ['./mining-dashboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MiningDashboardComponent implements OnInit { +export class MiningDashboardComponent implements OnInit, AfterViewChecked { constructor( private seoService: SeoService, private websocketService: WebsocketService, + private stateService: StateService ) { this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Mining Dashboard`); } @@ -19,4 +21,8 @@ export class MiningDashboardComponent implements OnInit { ngOnInit(): void { this.websocketService.want(['blocks', 'mempool-blocks', 'stats']); } + + ngAfterViewChecked(): void { + this.stateService.searchFocus$.next(true); + } } diff --git a/frontend/src/app/components/search-form/search-form.component.html b/frontend/src/app/components/search-form/search-form.component.html index cdfcfe015..3fc03c83a 100644 --- a/frontend/src/app/components/search-form/search-form.component.html +++ b/frontend/src/app/components/search-form/search-form.component.html @@ -1,7 +1,7 @@
- +
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 2361f8873..2fc25748e 100644 --- a/frontend/src/app/components/search-form/search-form.component.ts +++ b/frontend/src/app/components/search-form/search-form.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, ChangeDetectionStrategy, EventEmitter, Output, ViewChild, HostListener, ElementRef } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; -import { Router } from '@angular/router'; +import { EventType, NavigationStart, Router } from '@angular/router'; import { AssetsService } from '../../services/assets.service'; import { StateService } from '../../services/state.service'; import { Observable, of, Subject, zip, BehaviorSubject, combineLatest } from 'rxjs'; @@ -47,6 +47,8 @@ export class SearchFormComponent implements OnInit { this.handleKeyDown($event); } + @ViewChild('searchInput') searchInput: ElementRef; + constructor( private formBuilder: UntypedFormBuilder, private router: Router, @@ -55,11 +57,24 @@ export class SearchFormComponent implements OnInit { private electrsApiService: ElectrsApiService, private apiService: ApiService, private relativeUrlPipe: RelativeUrlPipe, - private elementRef: ElementRef, - ) { } + private elementRef: ElementRef + ) { + } ngOnInit(): void { this.stateService.networkChanged$.subscribe((network) => this.network = network); + + this.router.events.subscribe((e: NavigationStart) => { // Reset search focus when changing page + if (e.type === EventType.NavigationStart) { + this.searchInput.nativeElement.blur(); + } + }); + + this.stateService.searchFocus$.subscribe(focus => { + if (this.searchInput && focus === true) { + this.searchInput.nativeElement.focus(); + } + }); this.searchForm = this.formBuilder.group({ searchText: ['', Validators.required], diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index aca3593d7..a9c0bb31c 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -1,7 +1,7 @@ -import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewChecked, ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { combineLatest, merge, Observable, of, Subscription } from 'rxjs'; import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators'; -import { BlockExtended, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface'; +import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface'; import { MempoolInfo, TransactionStripped, ReplacementInfo } from '../interfaces/websocket.interface'; import { ApiService } from '../services/api.service'; import { StateService } from '../services/state.service'; @@ -31,7 +31,7 @@ interface MempoolStatsData { styleUrls: ['./dashboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DashboardComponent implements OnInit, OnDestroy { +export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { featuredAssets$: Observable; network$: Observable; mempoolBlocksData$: Observable; @@ -57,6 +57,10 @@ export class DashboardComponent implements OnInit, OnDestroy { private seoService: SeoService ) { } + ngAfterViewChecked(): void { + this.stateService.searchFocus$.next(true); + } + ngOnDestroy(): void { this.currencySubscription.unsubscribe(); this.websocketService.stopTrackRbfSummary(); diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 5ebca9ba1..2c4f06b49 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -138,6 +138,8 @@ export class StateService { fiatCurrency$: BehaviorSubject; rateUnits$: BehaviorSubject; + searchFocus$: Subject = new Subject(); + constructor( @Inject(PLATFORM_ID) private platformId: any, @Inject(LOCALE_ID) private locale: string, From da4a20cb8574916681602b0b995ca93fd2fe4b04 Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 24 Jul 2023 11:35:46 +0900 Subject: [PATCH 2/5] [search bar] dont auto focus if touch screen --- .../mining-dashboard.component.ts | 2 +- .../src/app/dashboard/dashboard.component.ts | 2 +- frontend/src/app/services/state.service.ts | 7 +++++ .../src/app/shared/pipes/bytes-pipe/utils.ts | 26 +++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts index 22d0e11fe..c7670bc1e 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts @@ -23,6 +23,6 @@ export class MiningDashboardComponent implements OnInit, AfterViewChecked { } ngAfterViewChecked(): void { - this.stateService.searchFocus$.next(true); + this.stateService.focusSearchInputDesktop(); } } diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index a9c0bb31c..6d61953cf 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -58,7 +58,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { ) { } ngAfterViewChecked(): void { - this.stateService.searchFocus$.next(true); + this.stateService.focusSearchInputDesktop(); } ngOnDestroy(): void { diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 2c4f06b49..bebe751d6 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -7,6 +7,7 @@ import { Router, NavigationStart } from '@angular/router'; import { isPlatformBrowser } from '@angular/common'; import { filter, map, scan, shareReplay } from 'rxjs/operators'; import { StorageService } from './storage.service'; +import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils'; export interface MarkBlockState { blockHeight?: number; @@ -357,4 +358,10 @@ export class StateService { this.blocks = this.blocks.slice(0, this.env.KEEP_BLOCKS_AMOUNT); this.blocksSubject$.next(this.blocks); } + + focusSearchInputDesktop() { + if (!hasTouchScreen()) { + this.searchFocus$.next(true); + } + } } diff --git a/frontend/src/app/shared/pipes/bytes-pipe/utils.ts b/frontend/src/app/shared/pipes/bytes-pipe/utils.ts index fc8c2b08f..86a1e1a1d 100644 --- a/frontend/src/app/shared/pipes/bytes-pipe/utils.ts +++ b/frontend/src/app/shared/pipes/bytes-pipe/utils.ts @@ -309,3 +309,29 @@ export function takeWhile(input: any[], predicate: CollectionPredicate) { return takeUntil(input, (item: any, index: number | undefined, collection: any[] | undefined) => !predicate(item, index, collection)); } + +// https://developer.mozilla.org/en-US/docs/Web/HTTP/Browser_detection_using_the_user_agent +export function hasTouchScreen(): boolean { + let hasTouchScreen = false; + if ('maxTouchPoints' in navigator) { + hasTouchScreen = navigator.maxTouchPoints > 0; + } else if ('msMaxTouchPoints' in navigator) { + // @ts-ignore + hasTouchScreen = navigator.msMaxTouchPoints > 0; + } else { + const mQ = matchMedia?.('(pointer:coarse)'); + if (mQ?.media === '(pointer:coarse)') { + hasTouchScreen = !!mQ.matches; + } else if ('orientation' in window) { + hasTouchScreen = true; // deprecated, but good fallback + } else { + // @ts-ignore - Only as a last resort, fall back to user agent sniffing + const UA = navigator.userAgent; + hasTouchScreen = + /\b(BlackBerry|webOS|iPhone|IEMobile)\b/i.test(UA) || + /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA); + } + } + console.log(hasTouchScreen); + return hasTouchScreen; +} \ No newline at end of file From 7db391d762514c3ed55fba67ba17f2b82729af7b Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Mon, 24 Jul 2023 11:51:15 +0900 Subject: [PATCH 3/5] [search bar] add missing autofocus on lightning dashboard --- .../lightning-dashboard/lightning-dashboard.component.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts index 6fa4b454c..adaa8d115 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { AfterViewChecked, ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { share } from 'rxjs/operators'; import { INodesRanking } from '../../interfaces/node-api.interface'; @@ -12,7 +12,7 @@ import { LightningApiService } from '../lightning-api.service'; styleUrls: ['./lightning-dashboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class LightningDashboardComponent implements OnInit { +export class LightningDashboardComponent implements OnInit, AfterViewChecked { statistics$: Observable; nodesRanking$: Observable; officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; @@ -30,4 +30,7 @@ export class LightningDashboardComponent implements OnInit { this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share()); } + ngAfterViewChecked(): void { + this.stateService.focusSearchInputDesktop(); + } } From 07b0f24cf15d2e72a3b0458969278733addcb9dc Mon Sep 17 00:00:00 2001 From: softsimon Date: Tue, 25 Jul 2023 14:26:43 +0900 Subject: [PATCH 4/5] Update frontend/src/app/shared/pipes/bytes-pipe/utils.ts --- frontend/src/app/shared/pipes/bytes-pipe/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/app/shared/pipes/bytes-pipe/utils.ts b/frontend/src/app/shared/pipes/bytes-pipe/utils.ts index 86a1e1a1d..2700be45d 100644 --- a/frontend/src/app/shared/pipes/bytes-pipe/utils.ts +++ b/frontend/src/app/shared/pipes/bytes-pipe/utils.ts @@ -332,6 +332,5 @@ export function hasTouchScreen(): boolean { /\b(Android|Windows Phone|iPad|iPod)\b/i.test(UA); } } - console.log(hasTouchScreen); return hasTouchScreen; } \ No newline at end of file From 6d5be78dd06d2b13a6636201eb1d1cb960e50a2f Mon Sep 17 00:00:00 2001 From: nymkappa <1612910616@pm.me> Date: Tue, 25 Jul 2023 15:03:39 +0900 Subject: [PATCH 5/5] [search bar] use afterviewinit instead of afterviewchecked --- .../mining-dashboard.component.ts | 17 +++++++++++++---- .../search-form/search-form.component.ts | 8 +++++--- .../src/app/dashboard/dashboard.component.ts | 6 +++--- .../lightning-dashboard.component.ts | 6 +++--- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts index c7670bc1e..6353ab8b8 100644 --- a/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts +++ b/frontend/src/app/components/mining-dashboard/mining-dashboard.component.ts @@ -1,7 +1,8 @@ -import { AfterViewChecked, ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { SeoService } from '../../services/seo.service'; import { WebsocketService } from '../../services/websocket.service'; import { StateService } from '../../services/state.service'; +import { EventType, NavigationStart, Router } from '@angular/router'; @Component({ selector: 'app-mining-dashboard', @@ -9,11 +10,12 @@ import { StateService } from '../../services/state.service'; styleUrls: ['./mining-dashboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class MiningDashboardComponent implements OnInit, AfterViewChecked { +export class MiningDashboardComponent implements OnInit, AfterViewInit { constructor( private seoService: SeoService, private websocketService: WebsocketService, - private stateService: StateService + private stateService: StateService, + private router: Router ) { this.seoService.setTitle($localize`:@@a681a4e2011bb28157689dbaa387de0dd0aa0c11:Mining Dashboard`); } @@ -22,7 +24,14 @@ export class MiningDashboardComponent implements OnInit, AfterViewChecked { this.websocketService.want(['blocks', 'mempool-blocks', 'stats']); } - ngAfterViewChecked(): void { + ngAfterViewInit(): void { this.stateService.focusSearchInputDesktop(); + this.router.events.subscribe((e: NavigationStart) => { + if (e.type === EventType.NavigationStart) { + if (e.url.indexOf('graphs') === -1) { // The mining dashboard and the graph component are part of the same module so we can't use ngAfterViewInit in graphs.component.ts to blur the input + this.stateService.focusSearchInputDesktop(); + } + } + }); } } 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 2fc25748e..61b3351b7 100644 --- a/frontend/src/app/components/search-form/search-form.component.ts +++ b/frontend/src/app/components/search-form/search-form.component.ts @@ -65,13 +65,15 @@ export class SearchFormComponent implements OnInit { this.stateService.networkChanged$.subscribe((network) => this.network = network); this.router.events.subscribe((e: NavigationStart) => { // Reset search focus when changing page - if (e.type === EventType.NavigationStart) { + if (this.searchInput && e.type === EventType.NavigationStart) { this.searchInput.nativeElement.blur(); } }); - this.stateService.searchFocus$.subscribe(focus => { - if (this.searchInput && focus === true) { + this.stateService.searchFocus$.subscribe(() => { + if (!this.searchInput) { // Try again a bit later once the view is properly initialized + setTimeout(() => this.searchInput.nativeElement.focus(), 100); + } else if (this.searchInput) { this.searchInput.nativeElement.focus(); } }); diff --git a/frontend/src/app/dashboard/dashboard.component.ts b/frontend/src/app/dashboard/dashboard.component.ts index 6d61953cf..05381453d 100644 --- a/frontend/src/app/dashboard/dashboard.component.ts +++ b/frontend/src/app/dashboard/dashboard.component.ts @@ -1,4 +1,4 @@ -import { AfterViewChecked, ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { combineLatest, merge, Observable, of, Subscription } from 'rxjs'; import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators'; import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface'; @@ -31,7 +31,7 @@ interface MempoolStatsData { styleUrls: ['./dashboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { +export class DashboardComponent implements OnInit, OnDestroy, AfterViewInit { featuredAssets$: Observable; network$: Observable; mempoolBlocksData$: Observable; @@ -57,7 +57,7 @@ export class DashboardComponent implements OnInit, OnDestroy, AfterViewChecked { private seoService: SeoService ) { } - ngAfterViewChecked(): void { + ngAfterViewInit(): void { this.stateService.focusSearchInputDesktop(); } diff --git a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts index adaa8d115..e58d5f124 100644 --- a/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts +++ b/frontend/src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts @@ -1,4 +1,4 @@ -import { AfterViewChecked, ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { AfterViewInit, ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { share } from 'rxjs/operators'; import { INodesRanking } from '../../interfaces/node-api.interface'; @@ -12,7 +12,7 @@ import { LightningApiService } from '../lightning-api.service'; styleUrls: ['./lightning-dashboard.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, }) -export class LightningDashboardComponent implements OnInit, AfterViewChecked { +export class LightningDashboardComponent implements OnInit, AfterViewInit { statistics$: Observable; nodesRanking$: Observable; officialMempoolSpace = this.stateService.env.OFFICIAL_MEMPOOL_SPACE; @@ -30,7 +30,7 @@ export class LightningDashboardComponent implements OnInit, AfterViewChecked { this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share()); } - ngAfterViewChecked(): void { + ngAfterViewInit(): void { this.stateService.focusSearchInputDesktop(); } }