From 283fd1c58e342e6b30e900cfc03f9458b40ffa10 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 30 Dec 2024 17:21:16 +0000 Subject: [PATCH] add sp widget to custom dashboard --- frontend/custom-sv-config.json | 7 +- .../custom-dashboard.component.html | 14 +++ .../simpleproof-widget.component.html | 75 ++++++++++++ .../simpleproof-widget.component.scss | 114 ++++++++++++++++++ .../simpleproof-widget.component.ts | 86 +++++++++++++ frontend/src/app/master-page.module.ts | 8 ++ .../src/app/services/services-api.service.ts | 5 + frontend/src/app/shared/shared.module.ts | 3 + frontend/src/resources/sp.svg | 36 ++++++ 9 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.html create mode 100644 frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.scss create mode 100644 frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.ts create mode 100644 frontend/src/resources/sp.svg diff --git a/frontend/custom-sv-config.json b/frontend/custom-sv-config.json index dee3dab18..245b47229 100644 --- a/frontend/custom-sv-config.json +++ b/frontend/custom-sv-config.json @@ -38,7 +38,12 @@ } }, { - "component": "blocks" + "component": "simpleproof", + "mobileOrder": 6, + "props": { + "label": "Executive Decrees", + "key": "el_salvador_decretos" + } }, { "component": "addressTransactions", diff --git a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html index 13cdd97ce..285b897bf 100644 --- a/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html +++ b/frontend/src/app/components/custom-dashboard/custom-dashboard.component.html @@ -305,6 +305,20 @@ } + @case ('simpleproof') { +
+ +
+ } } } diff --git a/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.html b/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.html new file mode 100644 index 000000000..dc8f24ea1 --- /dev/null +++ b/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.html @@ -0,0 +1,75 @@ +
+
+

{{ label }}

+
+
+ +
+ + @if (isLoading) { + loading! +
+
+
+ } @else if (error || !verified.length) { +
+ temporarily unavailable +
+ } @else { +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
FilenameHashVerifiedProof
{{ item.file_name }}{{ item.sha256 }} + + + + + + + Verify + +
+ + + + + + + +
+ + + + + +
+
+
+
+ } +
diff --git a/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.scss b/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.scss new file mode 100644 index 000000000..63e2fe046 --- /dev/null +++ b/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.scss @@ -0,0 +1,114 @@ +.spinner-wrapper, .error-wrapper { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; +} + +.spinner-border { + height: 25px; + width: 25px; + margin-top: -10px; + margin-left: -13px; + flex-shrink: 0; +} + +.container-xl { + max-width: 1400px; +} +.container-xl.widget { + padding-left: 0px; + padding-bottom: 0px; + padding-right: 0px; +} +.container-xl.legacy { + max-width: 1140px; +} + +.container { + max-width: 100%; +} + +tr, td, th { + border: 0px; + padding-top: 0.71rem !important; + padding-bottom: 0.7rem !important; +} + +.clear-link { + color: white; +} + +.disabled { + pointer-events: none; + opacity: 0.5; +} + +.progress { + background-color: var(--secondary); +} + +.filename { + width: 50%; + max-width: 300px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.hash { + width: 25%; + max-width: 700px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +td.hash { + font-family: monospace; +} +.widget .hash { + display: none; +} +@media (max-width: 1200px) { + .hash { + display: none; + } +} + +.verified { + width: 25%; +} +td.verified { + color: var(--tertiary); +} + +.proof { + width: 25%; +} + +.badge-verify { + font-size: 1.05em; + font-weight: normal; + background: var(--nav-bg); + color: white; + + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + width: auto; + + .icon { + margin: -0.25em; + margin-right: 0.5em; + + .icon-img { + width: 16px; + font-size: 16px; + } + } +} diff --git a/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.ts b/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.ts new file mode 100644 index 000000000..6cfa31bdb --- /dev/null +++ b/frontend/src/app/components/simpleproof-widget/simpleproof-widget.component.ts @@ -0,0 +1,86 @@ +import { Component, Input, SecurityContext, SimpleChanges, OnChanges } from '@angular/core'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; +import { ServicesApiServices } from '@app/services/services-api.service'; +import { catchError, of } from 'rxjs'; + +export interface SimpleProof { + file_name: string; + sha256: string; + ots_verification: string; + block_height: number; + block_hash: string; + block_time: number; + simpleproof_url: string; + key?: string; + sanitized_url?: SafeResourceUrl; +} + +@Component({ + selector: 'app-simpleproof-widget', + templateUrl: './simpleproof-widget.component.html', + styleUrls: ['./simpleproof-widget.component.scss'], +}) +export class SimpleProofWidgetComponent implements OnChanges { + @Input() key: string = window['__env']?.customize?.dashboard.widgets?.find(w => w.component ==='simpleproof')?.props?.key ?? ''; + @Input() label: string = window['__env']?.customize?.dashboard.widgets?.find(w => w.component ==='simpleproof')?.props?.label ?? 'Verified Documents'; + @Input() widget: boolean = false; + @Input() width = 300; + @Input() height = 400; + + verified: SimpleProof[] = []; + verifiedPage: SimpleProof[] = []; + isLoading: boolean = true; + error: boolean = false; + page = 1; + lastPage = 1; + itemsPerPage = 15; + paginationMaxSize = window.innerWidth <= 767.98 ? 3 : 5; + + constructor( + private servicesApiService: ServicesApiServices, + public sanitizer: DomSanitizer, + ) {} + + ngOnInit(): void { + this.loadVerifications(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.widget) { + this.itemsPerPage = this.widget ? 6 : 15; + } + if (changes.key) { + this.loadVerifications(); + } + } + + loadVerifications(): void { + if (this.key) { + this.isLoading = true; + this.servicesApiService.getSimpleProofs$(this.key).pipe( + catchError(() => { + this.isLoading = false; + this.error = true; + return of({}); + }), + ).subscribe((data: Record) => { + if (Object.keys(data).length) { + this.verified = Object.keys(data).map(key => ({ + ...data[key], + file_name: data[key].file_name.replace('source-', '').replace('_', ' '), + key, + sanitized_url: this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, data[key]['simpleproof-url']) ?? ''), + })).sort((a, b) => b.key.localeCompare(a.key)); + this.verifiedPage = this.verified.slice((this.page - 1) * this.itemsPerPage, this.page * this.itemsPerPage); + this.isLoading = false; + this.error = false; + } + }); + } + } + + pageChange(page: number): void { + this.page = page; + this.verifiedPage = this.verified.slice((this.page - 1) * this.itemsPerPage, this.page * this.itemsPerPage); + } +} diff --git a/frontend/src/app/master-page.module.ts b/frontend/src/app/master-page.module.ts index 2ee2e0bd8..24c1a91e6 100644 --- a/frontend/src/app/master-page.module.ts +++ b/frontend/src/app/master-page.module.ts @@ -13,6 +13,7 @@ import { RbfList } from '@components/rbf-list/rbf-list.component'; import { ServerHealthComponent } from '@components/server-health/server-health.component'; import { ServerStatusComponent } from '@components/server-health/server-status.component'; import { FaucetComponent } from '@components/faucet/faucet.component' +import { SimpleProofWidgetComponent } from './components/simpleproof-widget/simpleproof-widget.component'; const browserWindow = window || {}; // @ts-ignore @@ -130,6 +131,13 @@ if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) { } } +if (window['__env']?.customize?.dashboard.widgets?.some(w => w.component ==='simpleproof')) { + routes[0].children.push({ + path: 'sp/verified', + component: SimpleProofWidgetComponent, + }); +} + @NgModule({ imports: [ RouterModule.forChild(routes) diff --git a/frontend/src/app/services/services-api.service.ts b/frontend/src/app/services/services-api.service.ts index bec9d88a1..101a38813 100644 --- a/frontend/src/app/services/services-api.service.ts +++ b/frontend/src/app/services/services-api.service.ts @@ -8,6 +8,7 @@ import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMa import { IBackendInfo } from '@interfaces/websocket.interface'; import { Acceleration, AccelerationHistoryParams } from '@interfaces/node-api.interface'; import { AccelerationStats } from '@components/acceleration/acceleration-stats/acceleration-stats.component'; +import { SimpleProof } from '../components/simpleproof-widget/simpleproof-widget.component'; export interface IUser { username: string; @@ -217,4 +218,8 @@ export class ServicesApiServices { getPaymentStatus$(orderId: string): Observable { return this.httpClient.get(`${this.stateService.env.SERVICES_API}/payments/bitcoin/check?order_id=${orderId}`, { observe: 'response' }); } + + getSimpleProofs$(key: string): Observable> { + return this.httpClient.get>(`${this.stateService.env.SERVICES_API}/sp/verified/${key}`); + } } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index ce5ac0f65..214d549cc 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -116,6 +116,7 @@ import { CalculatorComponent } from '@components/calculator/calculator.component import { BitcoinsatoshisPipe } from '@app/shared/pipes/bitcoinsatoshis.pipe'; import { HttpErrorComponent } from '@app/shared/components/http-error/http-error.component'; import { TwitterWidgetComponent } from '@components/twitter-widget/twitter-widget.component'; +import { SimpleProofWidgetComponent } from '@components/simpleproof-widget/simpleproof-widget.component'; import { FaucetComponent } from '@components/faucet/faucet.component'; import { TwitterLogin } from '@components/twitter-login/twitter-login.component'; import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-invoice.component'; @@ -235,6 +236,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/ OrdDataComponent, HttpErrorComponent, TwitterWidgetComponent, + SimpleProofWidgetComponent, FaucetComponent, TwitterLogin, BitcoinInvoiceComponent, @@ -369,6 +371,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/ OrdDataComponent, HttpErrorComponent, TwitterWidgetComponent, + SimpleProofWidgetComponent, TwitterLogin, BitcoinInvoiceComponent, BitcoinsatoshisPipe, diff --git a/frontend/src/resources/sp.svg b/frontend/src/resources/sp.svg new file mode 100644 index 000000000..e8ebb5a3c --- /dev/null +++ b/frontend/src/resources/sp.svg @@ -0,0 +1,36 @@ + + + + + + + + + + +