Compare commits
1 Commits
master
...
mononaut/s
Author | SHA1 | Date | |
---|---|---|---|
|
283fd1c58e |
@ -38,7 +38,12 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"component": "blocks"
|
||||
"component": "simpleproof",
|
||||
"mobileOrder": 6,
|
||||
"props": {
|
||||
"label": "Executive Decrees",
|
||||
"key": "el_salvador_decretos"
|
||||
}
|
||||
},
|
||||
{
|
||||
"component": "addressTransactions",
|
||||
|
@ -305,6 +305,20 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@case ('simpleproof') {
|
||||
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<a class="title-link" href="" [routerLink]="['/sp/verified' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.recent-blocks">{{ widget.props?.label }}</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||
</a>
|
||||
<app-simpleproof-widget [label]="widget.props.label" [key]="widget.props.key" [widget]="true"></app-simpleproof-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
@ -0,0 +1,75 @@
|
||||
<div class="container-xl" style="min-height: 335px" [ngClass]="{'widget': widget, 'full-height': !widget}">
|
||||
<div *ngIf="!widget" class="float-left" style="display: flex; width: 100%; align-items: center;">
|
||||
<h1>{{ label }}</h1>
|
||||
<div *ngIf="!widget && isLoading" class="spinner-border" role="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@if (isLoading) {
|
||||
loading!
|
||||
<div class="spinner-wrapper">
|
||||
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||
</div>
|
||||
} @else if (error || !verified.length) {
|
||||
<div class="error-wrapper">
|
||||
<span>temporarily unavailable</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div style="min-height: 295px">
|
||||
<table class="table table-borderless" [class.table-fixed]="widget">
|
||||
<thead>
|
||||
<th class="filename text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.filename">Filename</th>
|
||||
<th class="hash text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.hash">Hash</th>
|
||||
<th class="verified text-right" [ngClass]="{'widget': widget}" i18n="simpleproof.verified">Verified</th>
|
||||
<th class="proof text-right" [ngClass]="{'widget': widget}" i18n="simpleproof.proof">Proof</th>
|
||||
</thead>
|
||||
<tbody *ngIf="verifiedPage; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<tr *ngFor="let item of verifiedPage">
|
||||
<td class="filename text-left" [class]="widget ? 'widget' : ''">{{ item.file_name }}</td>
|
||||
<td class="hash text-left" [class]="widget ? 'widget' : ''">{{ item.sha256 }}</td>
|
||||
<td class="verified text-right" [class]="widget ? 'widget' : ''">
|
||||
<app-timestamp [unixTime]="item.block_time" [customFormat]="'yyyy-MM-dd'" [hideTimeSince]="true"></app-timestamp>
|
||||
</td>
|
||||
<td class="proof text-right" [class]="widget ? 'widget' : ''">
|
||||
<a [href]="item.sanitized_url" target="_blank" class="badge badge-primary badge-verify">
|
||||
<span class="icon">
|
||||
<img class="icon-img" src="/resources/sp.svg">
|
||||
</span>
|
||||
<span i18n="simpleproof.verify">Verify</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of [].constructor(itemsPerPage)">
|
||||
<td class="filename text-left" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="hash text-left" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="verified text-right" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="proof text-right" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||
[collectionSize]="verified.length" [rotate]="true" [maxSize]="paginationMaxSize" [pageSize]="itemsPerPage" [(page)]="page"
|
||||
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||
</ngb-pagination>
|
||||
|
||||
<ng-template [ngIf]="!widget">
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
</ng-template>
|
||||
</div>
|
||||
}
|
||||
</div>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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<string, SimpleProof>) => {
|
||||
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);
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
@ -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<any> {
|
||||
return this.httpClient.get<any>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/check?order_id=${orderId}`, { observe: 'response' });
|
||||
}
|
||||
|
||||
getSimpleProofs$(key: string): Observable<Record<string, SimpleProof>> {
|
||||
return this.httpClient.get<Record<string, SimpleProof>>(`${this.stateService.env.SERVICES_API}/sp/verified/${key}`);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
36
frontend/src/resources/sp.svg
Normal file
36
frontend/src/resources/sp.svg
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
viewBox="0 0 492.10001 575.79999"
|
||||
width="492.10001"
|
||||
height="575.79999"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<!-- Generator: Adobe Illustrator 29.0.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 186) -->
|
||||
<defs
|
||||
id="defs184">
|
||||
<style
|
||||
id="style182">
|
||||
.st0 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #f88e2b;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g
|
||||
id="g216"
|
||||
transform="translate(-159.5,-152.1)">
|
||||
<polygon
|
||||
class="st0"
|
||||
points="296.6,375.6 296.6,459.1 404.9,524.2 651.6,378.5 651.6,294.6 651.6,294.5 405.3,440.4 "
|
||||
id="polygon212" />
|
||||
<polygon
|
||||
class="st1"
|
||||
points="405.5,644.5 231.3,542 231.3,335.6 405.5,235 520.9,301.7 592.1,259.8 405.5,152.1 159.5,294.1 159.5,583.1 405.5,727.9 651.6,583.1 651.6,447.1 579.7,489.4 579.7,542 "
|
||||
id="polygon214" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 996 B |
Loading…
x
Reference in New Issue
Block a user