Commit 2f44a3a0 authored by Dirk Luijk's avatar Dirk Luijk

Added animations...

parent 3c22a865
......@@ -7,7 +7,7 @@
"start": "ng serve --aot -pc proxy.conf.json --open",
"build": "ng build --prod",
"test": "ng test",
"lint": "ng lint",
"lint": "ng lint --type-check=true",
"e2e": "ng e2e"
},
"private": true,
......
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { FormsModule } from '@angular/forms';
......@@ -34,7 +34,7 @@ const routes: Routes = [
PeersComponent
],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
RouterModule.forRoot(routes, { useHash: true }),
HttpClientModule
......
<h3 class="mb-4">Blockchain</h3>
<p><button (click)="mine()" class="btn btn-primary btn-lg">Mine!</button></p>
<p>
<button (click)="mine()" class="btn btn-primary btn-lg">Mine!</button>
</p>
<div *ngFor="let block of blockchain?.chain; trackBy: trackFn" class="block">
<h4 class="mb-3">
<span class="badge badge-primary">Block {{ block.index }}</span>
<span class="badge badge-secondary">{{ block.timestamp | date: 'mediumTime' }}</span>
<span class="badge badge-success">Proof {{ block.proof }}</span>
</h4>
<div [@slide]="blockchain?.chain.length">
<div *ngFor="let block of blockchain?.chain; trackBy: trackByFn" class="block">
<h4 class="mb-3">
<span class="badge badge-primary">Block {{ block.index }}</span>
<span class="badge badge-secondary">{{ block.timestamp | date: 'mediumTime' }}</span>
<span class="badge badge-success">Proof {{ block.proof }}</span>
</h4>
<ng-container *ngIf="block.transactions.length === 0">No transactions.</ng-container>
<ng-container *ngIf="block.transactions.length > 0">
<div *ngFor="let transaction of block.transactions" class="transaction mt-3">
<p class="lead mb-0">{{ transaction.id }}</p>
<span class="badge badge-primary">from {{ transaction.from }}</span>
<span class="badge badge-secondary">to {{ transaction.to }}</span>
<span class="badge badge-info">{{ transaction.amount | currency: 'EUR'}}</span>
</div>
</ng-container>
<ng-container *ngIf="block.transactions.length === 0">No transactions.</ng-container>
<ng-container *ngIf="block.transactions.length > 0">
<div *ngFor="let transaction of block.transactions" class="transaction mt-3">
<p class="lead mb-0">{{ transaction.id }}</p>
<span class="badge badge-primary">from {{ transaction.from }}</span>
<span class="badge badge-secondary">to {{ transaction.to }}</span>
<span class="badge badge-info">{{ transaction.amount | currency: 'EUR'}}</span>
</div>
</ng-container>
</div>
</div>
......@@ -4,4 +4,5 @@
padding: 0.5rem 1rem;
display: block;
margin: 2rem 0;
overflow: hidden;
}
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { useAnimation, transition, trigger } from '@angular/animations';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/switchMap';
......@@ -8,11 +9,19 @@ import 'rxjs/add/operator/switchMap';
import { Block, Blockchain } from '../api';
import { BlockchainService } from './blockchain.service';
import { slideAnimation } from '../slide.animation';
@Component({
selector: 'app-blockchain',
templateUrl: './blockchain.component.html',
styleUrls: ['./blockchain.component.scss']
styleUrls: ['./blockchain.component.scss'],
animations: [
trigger('slide', [
transition('* => *', [
useAnimation(slideAnimation),
]),
]),
],
})
export class BlockchainComponent implements OnInit, OnDestroy {
blockchain: Blockchain;
......@@ -31,11 +40,11 @@ export class BlockchainComponent implements OnInit, OnDestroy {
this.subscription.unsubscribe();
}
trackFn(block: Block): number {
trackByFn(index: number, block: Block): number {
return block.index;
}
mine(): void {
this.blockchainService.doMine().subscribe(block => this.blockchain.chain.push(block));
this.blockchainService.doMine().subscribe(block => this.blockchain.chain.unshift(block));
}
}
......@@ -2,7 +2,9 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/empty';
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/catch';
import { MessageService } from '../messages/message.service';
import { Block, Blockchain } from '../api';
......@@ -14,12 +16,21 @@ export class BlockchainService {
getBlockchain(): Observable<Blockchain> {
this.messageService.log('Fetching blockchain...');
return this.http.get<Blockchain>('/api/blockchain')
.do(blockchain => {
// todo: move sorting to backend?
blockchain.chain = blockchain.chain.sort((block1: Block, block2: Block) => block2.index - block1.index);
})
.do(blockchain => this.messageService.log(`Got blockchain with ${blockchain.chain.length} block(s)`));
}
doMine(): Observable<Block> {
this.messageService.log('Mining...');
return this.http.post<Block>('/api/mine', {})
.do(block => this.messageService.log(`Successfully mined block ${block.index} with ${block.transactions.length} transaction(s)`));
.do(block => this.messageService.log(`Successfully mined block ${block.index} with ${block.transactions.length} transaction(s)`))
.catch(() => {
this.messageService.log(`ERROR while mining!`);
return Observable.empty();
});
}
}
......@@ -3,7 +3,11 @@
<div *ngIf="messages.length === 0">No messages.</div>
<div *ngIf="messages.length > 0">
<code><p *ngFor="let message of messages">{{ message.time | date: 'mediumTime' }} - {{ message.text }}</p></code>
<code style="overflow: hidden" [@slide]="messages.length">
<p *ngFor="let message of messages; trackBy: trackByFn">
{{ message.time | date: 'mediumTime' }} - {{ message.text }}
</p>
</code>
<button class="btn btn-secondary btn-sm" (click)="clear()">Clear messages</button>
</div>
import { Component, OnInit } from '@angular/core';
import { transition, trigger, useAnimation } from '@angular/animations';
import { slideAnimation } from '../slide.animation';
import { MessageService, Message } from './message.service';
@Component({
selector: 'app-messages',
templateUrl: './messages.component.html'
templateUrl: './messages.component.html',
animations: [
trigger('slide', [
transition('* => *', [
useAnimation(slideAnimation),
]),
]),
],
})
export class MessagesComponent implements OnInit {
messages: Message[] = [];
......@@ -15,6 +25,10 @@ export class MessagesComponent implements OnInit {
this.messageService.messages$.subscribe(messages => this.messages = messages.slice(-20));
}
trackByFn(index: number, message: Message): number {
return message.time.getTime();
}
clear(): void {
this.messageService.clear();
}
......
import { animate, animation, query, style, stagger } from '@angular/animations';
export const slideAnimation = animation([
query(':enter', [
style({height: 0, paddingTop: 0, paddingBottom: 0, opacity: 0}),
stagger(100, [
animate(200),
]),
], {optional: true}),
]);
<h3 class="mb-4">Pending transactions</h3>
<div *ngIf="transactions.length === 0" class="jumbotron">
<p class="lead">No pending transactions. <a [routerLink]="['create']">Let's create one!</a></p>
<p class="lead mb-0">No pending transactions. <a [routerLink]="['create']">Let's create one!</a></p>
</div>
<div *ngIf="transactions.length > 0">
<div *ngIf="transactions.length > 0" >
<p><a [routerLink]="['create']" class="btn btn-primary">New transaction</a></p>
<table class="table">
......@@ -14,7 +14,7 @@
<th>To</th>
<th>Amount</th>
</tr>
<tr *ngFor="let transaction of transactions" [class.table-active]="transaction === selectedTransaction" (click)="onSelect(transaction)">
<tr *ngFor="let transaction of transactions; trackBy: trackByFn">
<td>{{transaction.id}}</td>
<td>{{transaction.from}}</td>
<td>{{transaction.to}}</td>
......
import { Component } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Observable } from 'rxjs/Observable';
......@@ -12,8 +12,7 @@ import { TransactionService } from '../transaction.service';
selector: 'app-pending-transactions',
templateUrl: './pending-transactions.component.html'
})
export class PendingTransactionsComponent {
selectedTransaction: Transaction;
export class PendingTransactionsComponent implements OnInit, OnDestroy {
transactions: Transaction[] = [];
private subscription: Subscription;
......@@ -30,7 +29,7 @@ export class PendingTransactionsComponent {
this.subscription.unsubscribe();
}
onSelect(transaction: Transaction): void {
this.selectedTransaction = transaction;
trackByFn(index: number, transaction: Transaction): string {
return transaction.id;
}
}
......@@ -6,25 +6,27 @@
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"variable-name": [
true,
"check-format",
"allow-leading-underscore"
],
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs",
"rxjs/Rx"
"rxjs"
],
"import-spacing": true,
"indent": [
true,
"spaces"
"spaces",
2
],
"interface-over-type-literal": true,
"label-position": true,
......@@ -32,15 +34,29 @@
true,
140
],
"member-access": false,
"member-access": [
true,
"no-public"
],
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
"public-static-field",
"public-static-method",
"protected-static-field",
"protected-static-method",
"private-static-field",
"private-static-method",
"public-instance-field",
"protected-instance-field",
"private-instance-field",
"public-constructor",
"protected-constructor",
"private-constructor",
"public-instance-method",
"protected-instance-method",
"private-instance-method"
]
}
],
......@@ -56,14 +72,16 @@
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-duplicate-variable": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
"typedef": [
true,
"ignore-params"
"call-signature",
"property-declaration"
],
"no-inferrable-types": true,
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
......@@ -73,17 +91,43 @@
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"only-arrow-functions": [
true,
"allow-declarations",
"allow-named-functions"
],
"trailing-comma": [
true,
{
"multiline": "always",
"singleline": "never"
}
],
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-whitespace",
"check-else",
"check-whitespace"
"check-catch",
"check-finally"
],
"no-consecutive-blank-lines": [
true,
1
],
"ordered-imports": [
true,
{
"import-sources-order": "any",
"named-imports-order": "case-insensitive"
}
],
"prefer-const": true,
"prefer-method-signature": true,
"quotemark": [
true,
"single"
......@@ -97,6 +141,11 @@
true,
"allow-null-check"
],
"align": [
true,
"parameters",
"statements"
],
"typedef-whitespace": [
true,
{
......@@ -105,37 +154,68 @@
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
],
"space-before-function-paren": [
true,
{
"anonymous": "never",
"named": "never",
"asyncArrow": "always",
"method": "never",
"constructor": "never"
}
],
"space-within-parens": 0,
"newline-before-return": true,
"typeof-compare": true,
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
"check-type",
"check-module",
"check-typecast",
"check-preblock"
],
"directive-selector": [
true,
"attribute",
"app",
"p1",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
[
"p1",
"eb"
],
"kebab-case"
],
"no-output-on-prefix": true,
"angular-whitespace": [
true,
"check-interpolation",
"check-pipe"
],
"banana-in-box": true,
"templates-no-negated-async": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"no-duplicate-imports": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment