10 |
11 |
12 |
17 |
18 |
23 |
24 |
25 |
26 | travel_explore
27 |
28 |
29 |
30 |
31 |
37 | waterfall_chart
38 |
39 |
40 |
41 |
44 |
45 |
46 |
56 | paid
57 |
58 |
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | ## v2.8.0 (December 21, 2024)
4 |
5 | - Added ability to search BNS addresses.
6 |
7 | ## v2.7.0 (April 28, 2024)
8 |
9 | - Added birthday banner which displays every Apirl 1st.
10 |
11 | ## v2.6.1 (July 22, 2023)
12 |
13 | ### Fixed
14 |
15 | - Update account page to ignore anchor tags from URL.
16 |
17 | ## v2.6.0 (June 28, 2023)
18 |
19 | ### Added
20 |
21 | - Added quorum coefficient subtext.
22 |
23 | ## v2.5.0 (June 26, 2023)
24 |
25 | ### Added
26 |
27 | - Added known accounts onto Delegators tab on Account page.
28 | - Added min-weight coefficient data to the Network page.
29 |
30 | ## v2.4.5 (April 9, 2023)
31 |
32 | ### Fixed
33 |
34 | - Fixed issue where "Copy Hash" button on Block page was not displaying correct text.
35 | - Fixed issue where unknown addresses were not being handled correctly.
36 |
37 | ## v2.4.4 (April 4, 2023)
38 |
39 | ### Fixed
40 |
41 | - Trimmed search strings to prevent search errors; fixed in [#45](https://github.com/dev-ptera/yellow-spyglass-client/pull/45).
42 |
43 | ## v2.4.3 (March 10, 2023)
44 |
45 | ### Changed
46 |
47 | - Changed network page "Team funds" to "Distribution funds"
48 |
49 | ## v2.4.2 (March 10, 2023)
50 |
51 | ### Fixed
52 |
53 | - Fixed bug on accounts page where paginator displays incorrect data on tab switch.
54 |
55 | ## v2.4.1 (March 10, 2023)
56 |
57 | ### Fixed
58 |
59 | - Fixed representative pie chart legend not being circles.
60 | - Fixed monitored and large rep table sortation.
61 |
62 | ## v2.4.0 (March 7, 2023)
63 |
64 | ### Added
65 |
66 | - Added link to Known Accounts page from Accounts page.
67 |
68 | ### Changed
69 |
70 | - Changed Account page styles.
71 |
72 | ## v2.3.0 (March 6, 2023)
73 |
74 | ### Added
75 |
76 | - Added type filters on Known Accounts page.
77 | - Added optional `description` field on Known Accounts page.
78 | - Added balance onto Known Accounts page (desktop only).
79 |
80 | ### Changed
81 |
82 | - Changed Known Accounts page mobile view.
83 | - Cleaned up unused dependencies.
84 |
85 | ## v2.2.1 (March 4, 2023)
86 |
87 | ### Changed
88 |
89 | - Performance optimizations.
90 | - Created `AliasComponent` to use throughout Account and Wallets pages.
91 | - Adjusted Wallet and Known Account desktop table row height to 72px.
92 |
93 | ## v2.2.0 (March 3, 2023)
94 |
95 | ### Added
96 |
97 | - Added known account alias onto block page.
98 |
99 | ### Fixed
100 |
101 | - Fixed bug where list of aliases was fetched twice.
102 |
103 | ## v2.1.0 (March 3, 2023)
104 |
105 | ### Added
106 |
107 | - Added auto-tagging & GitHub releases. This marks a stable version of the software.
108 |
--------------------------------------------------------------------------------
/src/app/app.routing.ts:
--------------------------------------------------------------------------------
1 | import { NgModule } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 | import { APP_NAV_ITEMS } from './navigation/nav-items';
4 | import { BookmarksComponent } from '@app/pages/bookmarks/bookmarks.component';
5 | import { RepresentativesComponent } from '@app/pages/representatives/representatives.component';
6 | import { NodeMonitorComponent } from '@app/pages/node-monitor/node-monitor.component';
7 | import { WalletsComponent } from '@app/pages/wallets/wallets.component';
8 | import { KnownAccountsComponent } from '@app/pages/known-accounts/known-accounts.component';
9 | import { NetworkComponent } from '@app/pages/network/network.component';
10 | import { VanityComponent } from '@app/pages/vanity/vanity.component';
11 | import { HomeComponent } from '@app/pages/home/home.component';
12 | import { AccountComponent } from '@app/pages/account/account.component';
13 | import { BlockComponent } from '@app/pages/block/block.component';
14 |
15 | const routes: Routes = [
16 | { path: '', component: HomeComponent },
17 | { path: APP_NAV_ITEMS.representatives.route, component: RepresentativesComponent },
18 | { path: APP_NAV_ITEMS.bookmarks.route, component: BookmarksComponent },
19 | { path: APP_NAV_ITEMS.node.route, component: NodeMonitorComponent },
20 | { path: APP_NAV_ITEMS.wallets.route, component: WalletsComponent },
21 | { path: APP_NAV_ITEMS.knownAccounts.route, component: KnownAccountsComponent },
22 | { path: APP_NAV_ITEMS.network.route, component: NetworkComponent },
23 | { path: APP_NAV_ITEMS.vanity.route, component: VanityComponent },
24 | { path: `${APP_NAV_ITEMS.account.route}/:id`, component: AccountComponent },
25 | { path: `${APP_NAV_ITEMS.hash.route}/:id`, component: BlockComponent },
26 |
27 | // Handle Creeper Legacy Redirects
28 | { path: 'status', redirectTo: APP_NAV_ITEMS.node.route, pathMatch: 'full' },
29 | { path: `explorer/account/:id/history`, redirectTo: `${APP_NAV_ITEMS.account.route}/:id`, pathMatch: 'full' }, // Match Creeper path
30 | { path: `explorer/account/:id`, redirectTo: `${APP_NAV_ITEMS.account.route}/:id`, pathMatch: 'full' }, // Match Creeper path
31 | { path: `explorer/block/:id`, redirectTo: `${APP_NAV_ITEMS.hash.route}/:id`, pathMatch: 'full' }, // Match Creeper path
32 | { path: `explorer/hash/:id`, redirectTo: `${APP_NAV_ITEMS.hash.route}/:id`, pathMatch: 'full' }, // Match Creeper path
33 | { path: `explorer/auto/:id`, redirectTo: `${APP_NAV_ITEMS.hash.route}/:id`, pathMatch: 'full' }, // Match Creeper path
34 |
35 | // Catch all
36 | { path: '**', redirectTo: '', pathMatch: 'full' },
37 | ];
38 | @NgModule({
39 | imports: [RouterModule.forRoot(routes)],
40 | exports: [RouterModule],
41 | })
42 | export class AppRoutingModule {}
43 |
--------------------------------------------------------------------------------
/src/app/pages/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, ViewChild, ViewEncapsulation } from '@angular/core';
2 | import { UtilService } from '@app/services/util/util.service';
3 | import { ViewportService } from '@app/services/viewport/viewport.service';
4 | import { ThemeService } from '@app/services/theme/theme.service';
5 | import { ApiService } from '@app/services/api/api.service';
6 | import { ExplorerSummaryDto } from '@app/types/dto';
7 | import { APP_NAV_ITEMS } from '../../navigation/nav-items';
8 | import { SearchBarComponent } from '../../navigation/search-bar/search-bar.component';
9 |
10 | @Component({
11 | selector: 'app-home',
12 | templateUrl: './home.component.html',
13 | styleUrls: ['./home.component.scss'],
14 | encapsulation: ViewEncapsulation.None,
15 | })
16 | export class HomeComponent {
17 | @ViewChild('searchBar') searchBar: SearchBarComponent;
18 |
19 | showHint: boolean;
20 | routes = APP_NAV_ITEMS;
21 |
22 | marketCap = '$xx,xxx,xx';
23 |
24 | // @ts-ignore
25 | summaryData = {
26 | knownAccountsCount: 'xxx',
27 | circulatingCount: 'x,xxx,xxx,xxx',
28 | devFundCount: 'xxx,xxx,xxx',
29 | totalPrincipalRepsCount: 'xx',
30 | representativesOnlineCount: 'xx',
31 | principalRepsOnlineCount: 'xx',
32 | confirmedTransactionsCount: 'xxx,xxx,xxx',
33 | ledgerSizeMB: 'xx.xx',
34 | ledgerDatabaseType: '',
35 | bananoPriceUsd: 'x.xx',
36 | } as ExplorerSummaryDto;
37 |
38 | constructor(
39 | public vp: ViewportService,
40 | private readonly _themeService: ThemeService,
41 | private readonly _util: UtilService,
42 | private readonly _api: ApiService
43 | ) {}
44 |
45 | ngOnInit(): void {
46 | this._api
47 | .fetchExplorerSummaryData()
48 | .then((data) => {
49 | Object.assign(this.summaryData, data);
50 | if (data.ledgerSizeMB) {
51 | this.summaryData.ledgerSizeMB = Number((data.ledgerSizeMB / 1024).toFixed(1));
52 | }
53 | if (data.bananoPriceUsd) {
54 | const circulatingMarketValue = Number(data.bananoPriceUsd) * Number(data.circulatingCount);
55 | this.marketCap = `$${this._util.numberWithCommas(circulatingMarketValue.toFixed(0))}`;
56 | this.summaryData.bananoPriceUsd = data.bananoPriceUsd.toFixed(4) as any;
57 | }
58 | })
59 | .catch((err) => {
60 | console.error(err);
61 | });
62 | }
63 |
64 | isDarkTheme(): boolean {
65 | return this._themeService.isDarkMode();
66 | }
67 |
68 | search(e: MouseEvent): void {
69 | this.searchBar.searchCurrentValue(e.ctrlKey);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/app/services/account-actions/account-actions.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { MatDialog } from '@angular/material/dialog';
3 | import { MatSnackBar } from '@angular/material/snack-bar';
4 | import { ApiService } from '@app/services/api/api.service';
5 | import { BookmarksService } from '@app/services/bookmarks/bookmarks.service';
6 | import { QrDialogComponent } from '@app/pages/account/qr-dialog/qr-dialog.component';
7 |
8 | @Injectable({
9 | providedIn: 'root',
10 | })
11 | /** This service is dedicated towards handling user interactions with an Account or Block page. */
12 | export class AccountActionsService {
13 | constructor(
14 | public dialog: MatDialog,
15 | private readonly _apiService: ApiService,
16 | private readonly _snackBar: MatSnackBar,
17 | private readonly _bookmarkService: BookmarksService
18 | ) {}
19 |
20 | /** Opens a given address as a QR code in a dialog window. */
21 | openAccountQRCode(address: string): void {
22 | this.dialog.open(QrDialogComponent, {
23 | data: {
24 | address,
25 | },
26 | });
27 | }
28 |
29 | /** Copies a given address or hash to the clipboard. */
30 | copyDataToClipboard(data: string): void {
31 | const el = document.createElement('textarea');
32 | el.value = data;
33 | document.body.appendChild(el);
34 | el.select();
35 | document.execCommand('copy');
36 | document.body.removeChild(el);
37 | this._snackBar.open(el.value.startsWith('ban_') ? 'Copied Address' : 'Copied Hash', undefined, {
38 | duration: 1000,
39 | });
40 | }
41 |
42 | /** Downloads a given account's history to a local CSV file. */
43 | async downloadTxHistory(address: string): Promise