diff --git a/.eslintrc.json b/.eslintrc.json index bf3c942c94ccfdeec081ce853e6fb4598812fab1..416d502178940739c2e6b92ba23bb01485a94928 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,7 @@ "/scripts/**/*.*", "/www/**/*.*", "/src/**/*.test*.ts", + "/src/**/*.spec.ts", "/src/interfaces/**/*.*", "index.html" ], @@ -20,10 +21,12 @@ "extends": [ "eslint:recommended", "plugin:prettier/recommended", - "plugin:@rx-angular/recommended", - "plugin:@rx-angular/zoneless", "plugin:@typescript-eslint/recommended", "plugin:@angular-eslint/recommended", + // FIXME enable this + "plugin:@rx-angular/recommended", + "plugin:@rx-angular/zoneless", + // This is required if you use inline templates in Components "plugin:@angular-eslint/template/process-inline-templates" ], diff --git a/angular.json b/angular.json index 655ebd6aee152b372096a695efd3c4a83d97ffec..cb0319489379e92b39acde1884d2d89455a79818 100644 --- a/angular.json +++ b/angular.json @@ -34,7 +34,8 @@ "qrcode", "localforage", "localforage-cordovasqlitedriver", - "moment-timezone" + "moment-timezone", + "tweetnacl" ], "assets": [ { diff --git a/package.json b/package.json index 7a421595efce21a2e7a63b3c52d7e39e1e191967..b197006564dc3c664d77d3dd2337b8e6a4cffd3d 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,13 @@ "scripts": { "ng": "ng", "start": "ng serve", - "start.android": "ionic capacitor run android -l --external", + "start:android": "ionic capacitor run android -l --external", "build": "ng build --configuration production", - "build.android": "ionic capacitor build android --configuration production", - "sync.android": "npx cap sync android", + "build:android": "ionic capacitor build android --configuration production", + "sync:android": "npx cap sync android", "test": "ng test", "lint": "ng lint", + "lint:fix": "ng lint --fix", "typegen": "yarn get:meta && yarn generate:defs && yarn generate:meta", "get:meta": "curl -H \"Content-Type: application/json\" -d '{\"id\":\"1\", \"jsonrpc\":\"2.0\", \"method\": \"state_getMetadata\", \"params\":[]}' http://localhost:9933 > ./src/interfaces/types.json", "generate:defs": "ts-node --skip-project node_modules/.bin/polkadot-types-from-defs --package @duniter/types/interfaces --input ./src/interfaces --endpoint ./src/interfaces/types.json", diff --git a/src/app/account/account.model.ts b/src/app/account/account.model.ts index 52186b7a3ed8a4eba6b2b4df714c7500f2844d64..e899f49d4281d2a543f09434044079bfdc9eb0c3 100644 --- a/src/app/account/account.model.ts +++ b/src/app/account/account.model.ts @@ -1,9 +1,8 @@ -import {KeypairType} from "@polkadot/util-crypto/types"; import {HexString} from "@polkadot/util/types"; import {InjectionToken} from "@angular/core"; import {ListItem} from "@app/shared/popover/list.popover"; -import {Params} from "@polkadot/util-crypto/scrypt/types"; import {ScryptParams} from "@app/account/crypto.utils"; +import {AppEvent} from "@app/shared/types"; export interface Account { address: string; @@ -61,7 +60,6 @@ export interface SelectAccountOptions { showBalance?: boolean; positiveBalanceFirst?: boolean; } -export declare type LoginEvent = MouseEvent | TouchEvent | PointerEvent | CustomEvent; export declare type LoginMethodType = 'v1' | 'v2' | 'keyfile-v1'; export const LoginMethods: ListItem[] = [ @@ -79,7 +77,7 @@ export interface LoginOptions { } export interface IAuthController { - login(event?: LoginEvent, opts?: LoginOptions): Promise<Account>; + login(event?: AppEvent, opts?: LoginOptions): Promise<Account>; createNew(opts?: {redirectToWalletPage?: boolean}): Promise<Account>; unlock(opts?: UnlockOptions): Promise<string>; selectAccount(opts?: SelectAccountOptions): Promise<Account>; diff --git a/src/app/account/accounts.service.ts b/src/app/account/accounts.service.ts index 38776ff98827b91c789b072a16f83b0aa5160d71..89b3f620313e0b7bc7cc90b52ef55f004882d4f9 100644 --- a/src/app/account/accounts.service.ts +++ b/src/app/account/accounts.service.ts @@ -7,7 +7,6 @@ import { APP_AUTH_CONTROLLER, AuthData, IAuthController, - LoginEvent, LoginOptions, SelectAccountOptions } from "./account.model"; @@ -36,6 +35,7 @@ import {RxStartableService} from "@app/shared/services/rx-startable-service.clas import {RxStateProperty, RxStateSelect} from "@app/shared/decorator/state.decorator"; import {ED25519_SEED_LENGTH, SCRYPT_PARAMS} from "@app/account/crypto.utils"; import {KeyringPair} from "@polkadot/keyring/types"; +import {AppEvent} from "@app/shared/types"; export interface LoadAccountDataOptions { reload?: boolean; @@ -75,7 +75,7 @@ export class AccountsService extends RxStartableService<AccountsState> { protected network: NetworkService, protected settings: SettingsService, @Inject(APP_STORAGE) protected storage: IStorage, - @Inject(APP_AUTH_CONTROLLER) protected accountModalController: IAuthController + @Inject(APP_AUTH_CONTROLLER) protected authController: IAuthController ) { super(network, { name: 'account-service' @@ -95,7 +95,7 @@ export class AccountsService extends RxStartableService<AccountsState> { keyring.setSS58Format(currency.prefix || 42 /* = dev format */); // Restoring accounts - let accounts = await this.restoreAccounts(currency); + const accounts = await this.restoreAccounts(currency); return { accounts @@ -108,11 +108,11 @@ export class AccountsService extends RxStartableService<AccountsState> { } selectAccount(opts?: SelectAccountOptions): Promise<Account> { - return this.accountModalController.selectAccount(opts); + return this.authController.selectAccount(opts); } async createNew(opts?: {redirectToWalletPage?: boolean}): Promise<Account> { - const data = await this.accountModalController.createNew(opts); + const data = await this.authController.createNew(opts); if (!data?.address) return null; // User cancelled @@ -124,8 +124,8 @@ export class AccountsService extends RxStartableService<AccountsState> { return data; } - async login(event?: LoginEvent, opts?: LoginOptions): Promise<Account> { - const data = await this.accountModalController.login(event, opts); + async login(event?: AppEvent, opts?: LoginOptions): Promise<Account> { + const data = await this.authController.login(event, opts); if (!data?.address) return null; // User cancelled @@ -162,7 +162,7 @@ export class AccountsService extends RxStartableService<AccountsState> { console.info('[account-service] Loading all accounts [OK] No account found'); } else { - let accounts = keyringAddresses.map(ka => { + const accounts = keyringAddresses.map(ka => { return <Account>{ address: ka.address, publicKey: ka.publicKey, @@ -261,7 +261,7 @@ export class AccountsService extends RxStartableService<AccountsState> { console.debug(`${this._logPrefix}Not auth: opening unlock modal...`); - const data = await this.accountModalController.unlock(); + const data = await this.authController.unlock(); // User cancelled if (isNilOrBlank(data)) { @@ -336,7 +336,7 @@ export class AccountsService extends RxStartableService<AccountsState> { } async saveAccount(account: Account): Promise<Account> { - let accounts = this.accounts || []; + const accounts = this.accounts || []; // Define as default if (account.meta?.default || accounts.length === 1) this.setDefaultAccount(account, accounts); @@ -518,7 +518,7 @@ export class AccountsService extends RxStartableService<AccountsState> { if (this._debug) console.debug(`${this._logPrefix}Block events:`, JSON.stringify(events)); - let outdatedAccounts = [issuerAccount]; + const outdatedAccounts = [issuerAccount]; // Update receiver account if (await this.isAvailable(to.address)) { diff --git a/src/app/account/auth.controller.ts b/src/app/account/auth.controller.ts index 9f4b7fa555f05e82e3bf42204e1381626e9a6c49..f3baca644cbe2864c1f96d23b90d3c7188cfea2c 100644 --- a/src/app/account/auth.controller.ts +++ b/src/app/account/auth.controller.ts @@ -16,7 +16,6 @@ import {RegisterModal, RegisterModalOptions} from "@app/account/register/registe import { Account, IAuthController, - LoginEvent, LoginMethods, LoginOptions, SelectAccountOptions, @@ -25,6 +24,7 @@ import { import {AuthV2Modal} from "@app/account/auth/authv2.modal"; import {UnlockModal} from "@app/account/unlock/unlock.modal"; import {AccountListComponent, AccountListComponentInputs} from "@app/account/list/account-list.component"; +import {AppEvent} from "@app/shared/types"; @Injectable() export class AuthController implements IAuthController { @@ -51,7 +51,7 @@ export class AuthController implements IAuthController { ) { } - async login(event?: LoginEvent, opts?: LoginOptions): Promise<Account> { + async login(event?: AppEvent, opts?: LoginOptions): Promise<Account> { let loginMethod = opts?.loginMethod; diff --git a/src/app/account/auth/auth.form.html b/src/app/account/auth/auth.form.html index 29d6ec8d5097505e87800e11b017e39f8e7df681..bbbf71529f2b263ccf3924afb67fec2adc49292a 100644 --- a/src/app/account/auth/auth.form.html +++ b/src/app/account/auth/auth.form.html @@ -1,89 +1,92 @@ <form [formGroup]="form" novalidate (ngSubmit)="doSubmit($event)" (keyup.enter)="doSubmit($event)"> - <ion-list> <!-- error --> <ion-item lines="none" *ngIf="error && !loading" @slideUpDownAnimation> <ion-icon color="danger" slot="start" name="alert-circle"></ion-icon> - <ion-label color="danger" class="error" [innerHTML]="error|translate"></ion-label> + <ion-label color="danger" class="error" [innerHTML]="error | translate"></ion-label> </ion-item> <!-- Salt --> <ion-item> - <ion-input *ngIf="showSalt; else hideSalt" formControlName="salt" - [label]="'LOGIN.SALT'|translate" - labelPlacement="floating" - autocomplete="off" - required> - </ion-input> + <ion-input + *ngIf="showSalt; else hideSalt" + formControlName="salt" + [label]="'LOGIN.SALT' | translate" + labelPlacement="floating" + autocomplete="off" + required + ></ion-input> <ng-template #hideSalt> - <ion-input formControlName="salt" type="password" - [label]="'LOGIN.SALT'|translate" - labelPlacement="floating" - autocomplete="off" - required> - </ion-input> + <ion-input + formControlName="salt" + type="password" + [label]="'LOGIN.SALT' | translate" + labelPlacement="floating" + autocomplete="off" + required + ></ion-input> </ng-template> <!-- show/hide button --> - <ion-button slot="end" (click)="toggleShowSalt($event)" - fill="clear" color="medium" - [tabindex]="-1"> - <ion-icon slot="icon-only" [name]="showSalt ? 'eye-off': 'eye'" ></ion-icon> + <ion-button slot="end" (click)="toggleShowSalt($event)" fill="clear" color="medium" [tabindex]="-1"> + <ion-icon slot="icon-only" [name]="showSalt ? 'eye-off' : 'eye'"></ion-icon> </ion-button> </ion-item> <!-- Password --> <ion-item> - <ion-input *ngIf="showPwd; else hidePwd" - formControlName="password" - [label]="'LOGIN.PASSWORD'|translate" - labelPlacement="floating" - autocomplete="off" - required> - </ion-input> + <ion-input + *ngIf="showPwd; else hidePwd" + formControlName="password" + [label]="'LOGIN.PASSWORD' | translate" + labelPlacement="floating" + autocomplete="off" + required + ></ion-input> <ng-template #hidePwd> - <ion-input formControlName="password" type="password" - [label]="'LOGIN.PASSWORD'|translate" - labelPlacement="floating" - autocomplete="off" - required> - </ion-input> + <ion-input + formControlName="password" + type="password" + [label]="'LOGIN.PASSWORD' | translate" + labelPlacement="floating" + autocomplete="off" + required + ></ion-input> </ng-template> <!-- show/hide button --> - <ion-button slot="end" (click)="toggleShowPwd($event)" - fill="clear" color="medium" - [tabindex]="-1"> - <ion-icon slot="icon-only" [name]="showPwd ? 'eye-off': 'eye'" ></ion-icon> + <ion-button slot="end" (click)="toggleShowPwd($event)" fill="clear" color="medium" [tabindex]="-1"> + <ion-icon slot="icon-only" [name]="showPwd ? 'eye-off' : 'eye'"></ion-icon> </ion-button> </ion-item> <!-- Pubkey v1 --> <ng-container *rxIf="account$; let account"> <ion-item *ngIf="account.meta?.publicKeyV1" @slideUpDownAnimation> - <ion-textarea [value]="account.meta.publicKeyV1" - [label]="'COMMON.PUBKEY'|translate" - labelPlacement="floating" - [disabled]="true"> - </ion-textarea> + <ion-textarea + [value]="account.meta.publicKeyV1" + [label]="'COMMON.PUBKEY' | translate" + labelPlacement="floating" + [disabled]="true" + ></ion-textarea> </ion-item> <!-- Address --> <ion-item *ngIf="account.address" @slideUpDownAnimation> - <ion-textarea [value]="account.address" - [label]="'COMMON.ADDRESS'|translate" - labelPlacement="floating" - [disabled]="true"> - </ion-textarea> + <ion-textarea + [value]="account.address" + [label]="'COMMON.ADDRESS' | translate" + labelPlacement="floating" + [disabled]="true" + ></ion-textarea> </ion-item> </ng-container> - </ion-list> <!-- Not register yet ? --> <p *ngIf="canRegister" class="ion-padding ion-text-center"> <span translate>LOGIN.NO_ACCOUNT_QUESTION</span> - <br/> + <br /> <a (click)="register()"> <span translate>LOGIN.CREATE_ACCOUNT</span> </a> diff --git a/src/app/account/auth/auth.form.ts b/src/app/account/auth/auth.form.ts index 37dccbcbf75a4650106a0a9e08bb0aeddef86af8..5b935407bf68a4965e5f5eefe4679c9eaabfd601 100644 --- a/src/app/account/auth/auth.form.ts +++ b/src/app/account/auth/auth.form.ts @@ -145,19 +145,19 @@ export class AuthForm extends AppForm<AuthData> implements OnInit { } get pubkey(): string { - let data = this.form.value; + const data = this.form.value; // prevent displaying for empty credentials if(isNil(data.salt) || isNil(data.password)) { return "" } - let pair = getKeyringPairFromV1(data); - let pubkey = pair.publicKey; + const pair = getKeyringPairFromV1(data); + const pubkey = pair.publicKey; return base58Encode(pubkey); } // get address corresponding to form input get address(): string { - let data = this.form.value; + const data = this.form.value; // prevent displaying for empty credentials if(isNil(data.salt) || isNil(data.password)) { return "" diff --git a/src/app/account/auth/auth.modal.html b/src/app/account/auth/auth.modal.html index 6034a728ba66f84d6bd31c00e4b7ba912dd38c61..44741dc8571cf3e72570716e820abc47f37de742 100644 --- a/src/app/account/auth/auth.modal.html +++ b/src/app/account/auth/auth.modal.html @@ -1,19 +1,17 @@ <ion-header> - <ion-toolbar [color]="auth ? 'danger': 'primary'"> + <ion-toolbar [color]="auth ? 'danger' : 'primary'"> <ion-buttons slot="start"> <ion-button (click)="cancel()" *ngIf="mobile"> <ion-icon slot="icon-only" name="arrow-back"></ion-icon> </ion-button> </ion-buttons> - <ion-title [innerHTML]="title|translate"> - </ion-title> + <ion-title [innerHTML]="title | translate"></ion-title> <ion-buttons slot="end"> <ion-spinner *ngIf="loading"></ion-spinner> - <ion-button - (click)="doSubmit()" *ngIf="!loading && mobile"> + <ion-button (click)="doSubmit()" *ngIf="!loading && mobile"> <ion-icon slot="icon-only" name="checkmark"></ion-icon> </ion-button> </ion-buttons> @@ -21,11 +19,7 @@ </ion-header> <ion-content> - <app-auth-form #form - [canRegister]="!auth" - (onSubmit)="doSubmit($event)" - (onCancel)="cancel()"> - </app-auth-form> + <app-auth-form #form [canRegister]="!auth" (onSubmit)="doSubmit($event)" (onCancel)="cancel()"></app-auth-form> </ion-content> <ion-footer *ngIf="!mobile"> @@ -39,12 +33,14 @@ <ion-label translate>COMMON.BTN_CANCEL</ion-label> </ion-button> - <ion-button [fill]="form.invalid ? 'clear' : 'solid'" - [disabled]="loading || form.invalid" - (click)="doSubmit()" - (keyup.enter)="doSubmit()" - [color]="auth ? 'danger' : 'tertiary'"> - <ion-label translate>{{auth?'AUTH.BTN_AUTH':'COMMON.BTN_LOGIN'}}</ion-label> + <ion-button + [fill]="form.invalid ? 'clear' : 'solid'" + [disabled]="loading || form.invalid" + (click)="doSubmit()" + (keyup.enter)="doSubmit()" + [color]="auth ? 'danger' : 'tertiary'" + > + <ion-label translate>{{ auth ? 'AUTH.BTN_AUTH' : 'COMMON.BTN_LOGIN' }}</ion-label> </ion-button> </ion-col> </ion-row> diff --git a/src/app/account/auth/authv2.form.html b/src/app/account/auth/authv2.form.html index fd3e04ecf33939b7341d3eeab9c84d79ed673c0c..f576b273421659393759e4567efd2317f6153053 100644 --- a/src/app/account/auth/authv2.form.html +++ b/src/app/account/auth/authv2.form.html @@ -9,46 +9,44 @@ <!-- error --> <ion-item lines="none" *ngIf="error && !loading" @slideUpDownAnimation> <ion-icon color="danger" slot="start" name="alert-circle"></ion-icon> - <ion-label - color="danger" - class="error" - [innerHTML]="error|translate" - ></ion-label> + <ion-label color="danger" class="error" [innerHTML]="error | translate"></ion-label> </ion-item> <!-- Mnemonic --> <ion-item> - <ion-textarea *ngIf="showMnemonic; else hideMnemonic" - formControlName="mnemonic" - [label]="'LOGIN.MNEMONIC'|translate" - labelPlacement="floating" - autocomplete="off" - required> - </ion-textarea> + <ion-textarea + *ngIf="showMnemonic; else hideMnemonic" + formControlName="mnemonic" + [label]="'LOGIN.MNEMONIC' | translate" + labelPlacement="floating" + autocomplete="off" + required + ></ion-textarea> <ng-template #hideMnemonic> - <ion-textarea formControlName="mnemonic" type="password" - [label]="'LOGIN.MNEMONIC'|translate" - labelPlacement="floating" - autocomplete="off" - required> - </ion-textarea> + <ion-textarea + formControlName="mnemonic" + type="password" + [label]="'LOGIN.MNEMONIC' | translate" + labelPlacement="floating" + autocomplete="off" + required + ></ion-textarea> </ng-template> <!-- show/hide button --> - <ion-button slot="end" (click)="toggleShowMnemonic($event)" - fill="clear" color="medium" - [tabindex]="-1"> - <ion-icon slot="icon-only" [name]="showMnemonic ? 'eye-off': 'eye'" ></ion-icon> + <ion-button slot="end" (click)="toggleShowMnemonic($event)" fill="clear" color="medium" [tabindex]="-1"> + <ion-icon slot="icon-only" [name]="showMnemonic ? 'eye-off' : 'eye'"></ion-icon> </ion-button> </ion-item> <!-- Address --> <ion-item *ngIf="address; let address" @slideUpDownAnimation> - <ion-textarea [value]="address" - [label]="'COMMON.ADDRESS'|translate" - labelPlacement="floating" - [disabled]="true"> - </ion-textarea> + <ion-textarea + [value]="address" + [label]="'COMMON.ADDRESS' | translate" + labelPlacement="floating" + [disabled]="true" + ></ion-textarea> </ion-item> </ion-list> diff --git a/src/app/account/auth/authv2.modal.html b/src/app/account/auth/authv2.modal.html index f044c9b5977533899ba67ad68cbb8ce54021058e..6653dc9f705d79faa247f2004b1bf2fa0a36fbed 100644 --- a/src/app/account/auth/authv2.modal.html +++ b/src/app/account/auth/authv2.modal.html @@ -1,13 +1,12 @@ <ion-header> - <ion-toolbar [color]="auth ? 'danger': 'primary'"> + <ion-toolbar [color]="auth ? 'danger' : 'primary'"> <ion-buttons slot="start"> <ion-button (click)="cancel()" *ngIf="mobile"> <ion-icon slot="icon-only" name="arrow-back"></ion-icon> </ion-button> </ion-buttons> - <ion-title [innerHTML]="(auth ? 'AUTH.TITLE': 'LOGIN.TITLE')|translate"> - </ion-title> + <ion-title [innerHTML]="(auth ? 'AUTH.TITLE' : 'LOGIN.TITLE') | translate"></ion-title> <ion-buttons slot="end"> <ion-spinner *ngIf="loading"></ion-spinner> @@ -19,15 +18,12 @@ </ion-toolbar> </ion-header> -<ion-content > - <app-authv2-form #form - [canRegister]="!auth" - (onSubmit)="doSubmit($event)" (onCancel)="cancel()"> - </app-authv2-form> +<ion-content> + <app-authv2-form #form [canRegister]="!auth" (onSubmit)="doSubmit($event)" (onCancel)="cancel()"></app-authv2-form> </ion-content> <ion-footer *ngIf="!mobile"> - <ion-toolbar > + <ion-toolbar> <ion-row class="ion-no-padding" nowrap> <ion-col></ion-col> @@ -44,9 +40,7 @@ (keyup.enter)="doSubmit()" [color]="auth ? 'danger' : 'tertiary'" > - <ion-label - >{{(auth?'AUTH.BTN_AUTH':'COMMON.BTN_LOGIN')|translate}}</ion-label - > + <ion-label>{{ (auth ? 'AUTH.BTN_AUTH' : 'COMMON.BTN_LOGIN') | translate }}</ion-label> </ion-button> </ion-col> </ion-row> diff --git a/src/app/account/list/account-list.component.html b/src/app/account/list/account-list.component.html index 8c9d0ab619c1e0e2b91c04d8abbc14a35eb44322..d3d1bd08f3b6ac3d1492796e078b31fee18e65c3 100644 --- a/src/app/account/list/account-list.component.html +++ b/src/app/account/list/account-list.component.html @@ -4,34 +4,36 @@ </ion-toolbar> </ion-header> <ion-content> + <ion-list> + <ion-item + *rxFor="let account of accounts$; suspense: loadingSkeleton" + (click)="selectAccount(account)" + @listAnimation + > + <ion-avatar slot="start"> + <svg width="40" width="40" [data-jdenticon-value]="account.data?.randomId || account.address"></svg> + </ion-avatar> -<ion-list> - <ion-item *rxFor="let account of accounts$; suspense: loadingSkeleton" - (click)="selectAccount(account)" - @listAnimation> - <ion-avatar slot="start"> - <svg width="40" width="40" [data-jdenticon-value]="account.data?.randomId||account.address"></svg> - </ion-avatar> + <ion-label>{{ account | accountName }}</ion-label> - <ion-label>{{ account|accountName }}</ion-label> - - <!-- balance --> - <ion-badge *ngIf="showBalance && (account|balance); let balance" slot="end" - [color]="minBalance && balance < minBalance ? 'danger' : 'success'"> - {{balance|amountFormat}} - </ion-badge> - <ion-icon name="chevron-forward-outline" slot="end"></ion-icon> - </ion-item> - - <ng-template #loadingSkeleton> - <ion-item> - <ion-avatar slot="start"></ion-avatar> - <ion-label> - <ion-skeleton-text [animated]="true" style="width: 60%"></ion-skeleton-text> - </ion-label> + <!-- balance --> + <ion-badge + *ngIf="showBalance && (account | balance); let balance" + slot="end" + [color]="minBalance && balance < minBalance ? 'danger' : 'success'" + > + {{ balance | amountFormat }} + </ion-badge> + <ion-icon name="chevron-forward-outline" slot="end"></ion-icon> </ion-item> - </ng-template> - -</ion-list> + <ng-template #loadingSkeleton> + <ion-item> + <ion-avatar slot="start"></ion-avatar> + <ion-label> + <ion-skeleton-text [animated]="true" style="width: 60%"></ion-skeleton-text> + </ion-label> + </ion-item> + </ng-template> + </ion-list> </ion-content> diff --git a/src/app/account/register/register.form.html b/src/app/account/register/register.form.html index 5c244f9fa3ffb112f36262f6704e21ac5aef20e6..e468da8b792a69cd7c39707d3ac3d971fb13c976 100644 --- a/src/app/account/register/register.form.html +++ b/src/app/account/register/register.form.html @@ -1,8 +1,10 @@ -<swiper-container #swiper - [config]="swiperOptions" - [modules]="swiperModules" - [class]="classList" - (slideChangeTransitionStart)="updateState()"> +<swiper-container + #swiper + [config]="swiperOptions" + [modules]="swiperModules" + [class]="classList" + (slideChangeTransitionStart)="updateState()" +> <!-- intro step --> <swiper-slide class="ion-padding"> <ion-grid> @@ -10,7 +12,7 @@ <ion-col size="12"> <ion-text> <p> - {{'ACCOUNT.NEW.INTRO_WARNING_TIME'|translate:{name: currency?.displayName} }} + {{ 'ACCOUNT.NEW.INTRO_WARNING_TIME' | translate : { name: currency?.displayName } }} </p> </ion-text> </ion-col> @@ -19,16 +21,16 @@ <ion-col> <ion-card color="light"> <ion-card-content> - <ion-text [innerHTML]="'ACCOUNT.NEW.INTRO_WARNING_SECURITY'|translate"></ion-text><br/> - <ion-text color="medium" [innerHTML]="'ACCOUNT.NEW.INTRO_WARNING_SECURITY_HELP'|translate"></ion-text> + <ion-text [innerHTML]="'ACCOUNT.NEW.INTRO_WARNING_SECURITY' | translate"></ion-text> + <br /> + <ion-text color="medium" [innerHTML]="'ACCOUNT.NEW.INTRO_WARNING_SECURITY_HELP' | translate"></ion-text> </ion-card-content> </ion-card> </ion-col> </ion-row> <ion-row> <ion-col> - <ion-text [innerHTML]="'ACCOUNT.NEW.INTRO_HELP'|translate"> - </ion-text> + <ion-text [innerHTML]="'ACCOUNT.NEW.INTRO_HELP' | translate"></ion-text> </ion-col> </ion-row> </ion-grid> @@ -37,49 +39,47 @@ <!-- explanation 1 --> <swiper-slide class="ion-padding"> <ion-text> - <p [innerHTML]="'ACCOUNT.NEW.STEP_1_HELP'|translate"></p> - <!-- <img src="assets/account-step-1.png">--> + <p [innerHTML]="'ACCOUNT.NEW.STEP_1_HELP' | translate"></p> + <!-- <img src="assets/account-step-1.png">--> </ion-text> </swiper-slide> <!-- explanation 2 --> <swiper-slide class="ion-padding"> <ion-text> - <p [innerHTML]="'ACCOUNT.NEW.STEP_2_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_2_HELP' | translate"></p> </ion-text> </swiper-slide> <!-- explanation 3 --> <swiper-slide class="ion-padding"> <ion-text> - <p [innerHTML]="'ACCOUNT.NEW.STEP_3_HELP'|translate: {currency: currency?.displayName}"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_3_HELP' | translate : { currency: currency?.displayName }"></p> </ion-text> </swiper-slide> <!-- explanation 4 --> <swiper-slide class="ion-padding"> <ion-text> - <p [innerHTML]="'ACCOUNT.NEW.STEP_4_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_4_HELP' | translate"></p> </ion-text> </swiper-slide> <!-- generate mnemonic --> <swiper-slide> - <ion-grid *ngIf="form|formGetValue:'words'; let words; else wordsSkeleton" class="words"> + <ion-grid *ngIf="form | formGetValue : 'words'; let words; else: wordsSkeleton" class="words"> <ion-row> <ion-col size="12"> - <p [innerHTML]="'ACCOUNT.NEW.STEP_MNEMONIC_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_MNEMONIC_HELP' | translate"></p> </ion-col> </ion-row> <!-- words --> <ion-row class="words"> - <ion-col *ngFor="let word of words; index as index" - size="3" - class="ion-text-center"> + <ion-col *ngFor="let word of words; index as index" size="3" class="ion-text-center"> <ion-text> - <ion-text color="medium">{{index+1}}</ion-text> - <h4>{{word}}</h4> + <ion-text color="medium">{{ index + 1 }}</ion-text> + <h4>{{ word }}</h4> </ion-text> </ion-col> </ion-row> @@ -92,9 +92,7 @@ </ion-button> <!-- ok --> - <ion-button color="primary" (click)="toggleCanNext()" translate> - ACCOUNT.NEW.BTN_WROTE_MY_PHRASE - </ion-button> + <ion-button color="primary" (click)="toggleCanNext()" translate>ACCOUNT.NEW.BTN_WROTE_MY_PHRASE</ion-button> </ion-col> </ion-row> </ion-grid> @@ -102,15 +100,15 @@ <!-- check word --> <swiper-slide> - <ion-list *ngIf="form|formGetValue:'wordNumber' as wordNumber"> + <ion-list *ngIf="form | formGetValue : 'wordNumber' as wordNumber"> <ion-item lines="none"> <ion-text> - <p [innerHTML]="'ACCOUNT.NEW.STEP_CHECK_WORD_HELP'|translate: {number: wordNumber}"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_CHECK_WORD_HELP' | translate : { number: wordNumber }"></p> </ion-text> </ion-item> <ion-item> - <ion-label color="medium">{{'ACCOUNT.NEW.INPUT_WORD'|translate: {number: wordNumber} }}</ion-label> + <ion-label color="medium">{{ 'ACCOUNT.NEW.INPUT_WORD' | translate : { number: wordNumber } }}</ion-label> <ion-input (ionChange)="checkWord($event.detail.value)"></ion-input> <ion-icon slot="end" name="checkmark" *ngIf="slideState.canNext"></ion-icon> </ion-item> @@ -120,24 +118,23 @@ <!-- Explain code #1 --> <swiper-slide class="ion-padding"> <ion-text> - <p [innerHTML]="'ACCOUNT.NEW.STEP_CODE_1_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_CODE_1_HELP' | translate"></p> </ion-text> </swiper-slide> <!-- Explain code #2 --> <swiper-slide class="ion-padding"> <ion-text> - <p [innerHTML]="'ACCOUNT.NEW.STEP_CODE_2_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_CODE_2_HELP' | translate"></p> </ion-text> </swiper-slide> <!-- Secret code --> <swiper-slide> - <ion-grid> <ion-row> <ion-col size="12"> - <p [innerHTML]="'ACCOUNT.NEW.STEP_CODE_3_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_CODE_3_HELP' | translate"></p> </ion-col> <ion-col size="12"> <ion-label color="medium" translate>ACCOUNT.NEW.YOUR_SECRET_CODE</ion-label> @@ -145,7 +142,7 @@ <ion-col size="12" class="ion-padding-top"> <ion-item> <ion-text class="ion-text-center" style="width: 100%"> - <h2>{{form|formGetValue: 'code'}}</h2> + <h2>{{ form | formGetValue : 'code' }}</h2> </ion-text> </ion-item> </ion-col> @@ -160,41 +157,39 @@ </ion-col> </ion-row> </ion-grid> - </swiper-slide> <!-- check code --> <swiper-slide> - <app-unlock-form [control]="form|formGetControl: 'codeConfirmation'" - helpMessage="ACCOUNT.NEW.STEP_CHECK_CODE_HELP" - (change)="checkCodeConfirmation()"> - - </app-unlock-form> + <app-unlock-form + [control]="form | formGetControl : 'codeConfirmation'" + helpMessage="ACCOUNT.NEW.STEP_CHECK_CODE_HELP" + (change)="checkCodeConfirmation()" + ></app-unlock-form> </swiper-slide> <!-- congratulation ! --> <swiper-slide> <ion-row> <ion-col size="12"> - <p [innerHTML]="'ACCOUNT.NEW.STEP_CONGRATULATION_1_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_CONGRATULATION_1_HELP' | translate"></p> </ion-col> <ion-col size="12" class="ion-padding-top"> <ion-item> <ion-text class="ion-text-center" style="width: 100%"> - {{form|formGetValue: 'address'}} + {{ form | formGetValue : 'address' }} </ion-text> </ion-item> </ion-col> <ion-col size="12" class="ion-padding-top"> - <p [innerHTML]="'ACCOUNT.NEW.STEP_CONGRATULATION_2_HELP'|translate"></p> + <p [innerHTML]="'ACCOUNT.NEW.STEP_CONGRATULATION_2_HELP' | translate"></p> </ion-col> <ion-col size="12" class="ion-padding-top"> <ion-item> <ion-text class="ion-text-center" style="width: 100%"> - <h2>{{form|formGetValue: 'address'|addressFormat}}</h2> + <h2>{{ form | formGetValue : 'address' | addressFormat }}</h2> </ion-text> </ion-item> - </ion-col> </ion-row> @@ -202,7 +197,6 @@ </swiper-slide> </swiper-container> - <ng-template #wordsSkeleton> <ion-grid> <ion-row> @@ -223,15 +217,16 @@ <ng-container [ngTemplateOutlet]="wordSkeleton"></ng-container> <ng-container [ngTemplateOutlet]="wordSkeleton"></ng-container> </ion-row> - </ng-template> <ng-template #wordSkeleton> <ion-col size="3"> <ion-text class="ion-text-center"> <span><ion-skeleton-text style="width: 5%"></ion-skeleton-text></span> - <h4> <ion-skeleton-text animated style="width: 40%"></ion-skeleton-text></h4> + <h4> + + <ion-skeleton-text animated style="width: 40%"></ion-skeleton-text> + </h4> </ion-text> </ion-col> - </ng-template> diff --git a/src/app/account/register/register.modal.html b/src/app/account/register/register.modal.html index c4d78d568b5c5cfc0876e059ad0a9e6fc2d848a2..61fed8f108baa4d904bf941d06432a3fff113475 100644 --- a/src/app/account/register/register.modal.html +++ b/src/app/account/register/register.modal.html @@ -14,9 +14,7 @@ <ion-buttons slot="end"> <ion-spinner *ngIf="loading"></ion-spinner> - <ion-button *ngIf="mobile && !form.isEnd()" - [disabled]="loading || !form.canNext()" - (click)="form.slideNext()"> + <ion-button *ngIf="mobile && !form.isEnd()" [disabled]="loading || !form.canNext()" (click)="form.slideNext()"> <ion-label translate>COMMON.BTN_NEXT</ion-label> <ion-icon slot="end" name="arrow-forward"></ion-icon> </ion-button> @@ -29,25 +27,25 @@ </ion-header> <ion-content> - <app-register-form #form (onSubmit)="doSubmit()" (onCancel)="cancel()"> - <ion-row *ngIf="form.debug" codeConfirmation> - <ion-text color="primary" class="ion-padding"> - <small>loading: {{loading}}<br/> - canNext: {{form.canNext()}}<br/> - isEnd: {{form.isEnd()}}<br/> + <ion-text color="primary" class="ion-padding"> + <small> + loading: {{ loading }} + <br /> + canNext: {{ form.canNext() }} + <br /> + isEnd: {{ form.isEnd() }} + <br /> </small> </ion-text> </ion-row> </app-register-form> - </ion-content> <ion-footer *ngIf="!mobile"> <!-- buttons --> <ion-toolbar> - <ion-row> <ion-col></ion-col> <ion-col size="auto"> @@ -55,27 +53,40 @@ <ion-label translate>COMMON.BTN_CANCEL</ion-label> </ion-button> - <ion-button *ngIf="!form.isBeginning()" fill="clear" color="dark" (click)="form.slidePrev()" [disabled]="loading"> + <ion-button + *ngIf="!form.isBeginning()" + fill="clear" + color="dark" + (click)="form.slidePrev()" + [disabled]="loading" + > <ion-icon slot="start" name="arrow-back"></ion-icon> <ion-label translate>COMMON.BTN_BACK</ion-label> </ion-button> - <ion-button *ngIf="form.isBeginning()" (click)="form.slideNext()" fill="solid" color="tertiary"> <ion-label translate>COMMON.BTN_START</ion-label> <ion-icon slot="end" name="arrow-forward"></ion-icon> </ion-button> - <ion-button *ngIf="!form.isBeginning() && !form.isEnd()" - (click)="form.slideNext()" fill="solid" color="tertiary" - [disabled]="loading || !form.canNext()"> + <ion-button + *ngIf="!form.isBeginning() && !form.isEnd()" + (click)="form.slideNext()" + fill="solid" + color="tertiary" + [disabled]="loading || !form.canNext()" + > <ion-label translate>COMMON.BTN_NEXT</ion-label> <ion-icon slot="end" name="arrow-forward"></ion-icon> </ion-button> - <ion-button *ngIf="form.isEnd()" - (click)="doSubmit()" fill="solid" - [disabled]="loading || !form.canNext()" color="tertiary"> + <ion-button + *ngIf="form.isEnd()" + (click)="doSubmit()" + fill="solid" + [disabled]="loading || !form.canNext()" + color="tertiary" + > <span translate>COMMON.BTN_SAVE</span> <ion-icon slot="end" name="save"></ion-icon> </ion-button> diff --git a/src/app/account/unlock/unlock.form.html b/src/app/account/unlock/unlock.form.html index 6812b1e3317f884d314d82cb3ebceb0043c5dbb1..085f3a236e5e71fbca9350b25b44ad38de17b3d7 100644 --- a/src/app/account/unlock/unlock.form.html +++ b/src/app/account/unlock/unlock.form.html @@ -1,25 +1,26 @@ <form [formGroup]="form"> <ion-list> <ion-item lines="none"> - <p [innerHTML]="helpMessage|translate"></p> - + <p [innerHTML]="helpMessage | translate"></p> </ion-item> <ion-item lines="none" class="ion-padding-top"> - <ion-input [formControl]="control" - [label]="'AUTH.PASSPHRASE'|translate" - labelPlacement="floating" - [maxlength]="maxLength" - [minlength]="minLength" - [counter]="maxLength > 0" - (ionInput)="onInput($event)" - required></ion-input> - <ion-icon slot="end" name="checkmark" *ngIf="$valid|async"></ion-icon> + <ion-input + [formControl]="control" + [label]="'AUTH.PASSPHRASE' | translate" + labelPlacement="floating" + [maxlength]="maxLength" + [minlength]="minLength" + [counter]="maxLength > 0" + (ionInput)="onInput($event)" + required + ></ion-input> + <ion-icon slot="end" name="checkmark" *ngIf="$valid | async"></ion-icon> </ion-item> <ion-item *ngIf="debug && expectedCode" lines="none"> <ion-text color="primary"> - <small>expected: {{expectedCode}}</small> + <small>expected: {{ expectedCode }}</small> </ion-text> </ion-item> </ion-list> diff --git a/src/app/account/unlock/unlock.modal.html b/src/app/account/unlock/unlock.modal.html index 4707a801e5bcd0db71e3b516fabcf684c7093e8d..e618893528e045f0bac3ff175e03f90b60b7abc0 100644 --- a/src/app/account/unlock/unlock.modal.html +++ b/src/app/account/unlock/unlock.modal.html @@ -6,14 +6,12 @@ </ion-button> </ion-buttons> - <ion-title [innerHTML]="'AUTH.TITLE'|translate"> - </ion-title> + <ion-title [innerHTML]="'AUTH.TITLE' | translate"></ion-title> <ion-buttons slot="end"> <ion-spinner *ngIf="loading"></ion-spinner> - <ion-button - (click)="doSubmit()" *ngIf="!loading && mobile"> + <ion-button (click)="doSubmit()" *ngIf="!loading && mobile"> <ion-icon slot="icon-only" name="checkmark"></ion-icon> </ion-button> </ion-buttons> @@ -21,13 +19,9 @@ </ion-header> <ion-content style="height: 100%"> - <app-unlock-form #form - (onSubmit)="doSubmit($event)" - (onCancel)="cancel()"> - </app-unlock-form> + <app-unlock-form #form (onSubmit)="doSubmit($event)" (onCancel)="cancel()"></app-unlock-form> <ion-toolbar *ngIf="!mobile"> - <ion-row class="ion-no-padding" nowrap> <ion-col></ion-col> @@ -37,16 +31,16 @@ <ion-label translate>COMMON.BTN_CANCEL</ion-label> </ion-button> - <ion-button [fill]="form.invalid ? 'clear' : 'solid'" - [disabled]="loading || form.invalid" - (click)="doSubmit()" - (keyup.enter)="doSubmit()" - color="danger"> + <ion-button + [fill]="form.invalid ? 'clear' : 'solid'" + [disabled]="loading || form.invalid" + (click)="doSubmit()" + (keyup.enter)="doSubmit()" + color="danger" + > <ion-label translate>AUTH.BTN_AUTH</ion-label> </ion-button> </ion-col> </ion-row> - - </ion-toolbar> </ion-content> diff --git a/src/app/app.component.html b/src/app/app.component.html index 77ef10abdd4b7fd807845be1b247322ad06906c3..0458fff8496b94c514d32a0779e4ea9a79c1dbef 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -3,25 +3,38 @@ <ion-menu contentId="main-content" type="overlay"> <ion-content> <ion-list id="main-menu-list"> - <ion-list-header [innerHTML]="'COMMON.APP_NAME'|translate"></ion-list-header> + <ion-list-header [innerHTML]="'COMMON.APP_NAME' | translate"></ion-list-header> <ion-note></ion-note> <ion-menu-toggle auto-hide="false" *ngFor="let p of appPages; let i = index"> - <ion-item routerDirection="root" *ngIf="p.url && (!p.visible || p.visible())" - [disabled]="p.disabled" - [routerLink]="p.url" lines="none" detail="false" routerLinkActive="selected"> + <ion-item + routerDirection="root" + *ngIf="p.url && (!p.visible || p.visible())" + [disabled]="p.disabled" + [routerLink]="p.url" + lines="none" + detail="false" + routerLinkActive="selected" + > <ion-icon slot="start" [color]="p.color" [ios]="p.icon + '-outline'" [md]="p.icon + '-sharp'"></ion-icon> <ion-label [color]="p.color">{{ p.title | translate }}</ion-label> </ion-item> - <ion-item routerDirection="root" *ngIf="p.handle && (!p.visible || p.visible())" - (click)="p.handle($event)" lines="none" detail="false" tappable class="ion-activatable" - tappable @fadeInAnimation> + <ion-item + routerDirection="root" + *ngIf="p.handle && (!p.visible || p.visible())" + (click)="p.handle($event)" + lines="none" + detail="false" + tappable + class="ion-activatable" + tappable + @fadeInAnimation + > <ion-icon slot="start" [color]="p.color" [ios]="p.icon + '-outline'" [md]="p.icon + '-sharp'"></ion-icon> <ion-label [color]="p.color">{{ p.title | translate }}</ion-label> </ion-item> </ion-menu-toggle> - </ion-list> </ion-content> </ion-menu> diff --git a/src/app/app.component.ts b/src/app/app.component.ts index da2c7060b559bc1904e90151b2742bdc52528867..9a886dc5b72c92b58a1aa78917de5fdc490f2e35 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -37,7 +37,7 @@ export class AppComponent { }, { title: 'COMMON.BTN_SEND_MONEY', icon: 'paper-plane', - handle: (event) => this.transferController.transfer(event), + handle: () => this.transferController.transfer(), visible: () => !this.platform.mobile }, diff --git a/src/app/home/home.page.html b/src/app/home/home.page.html index 103f2c0f132cb0970af69560e9a4d3b83dbd5746..a69980a6953f10c3e2c878435bd2b830a2286e91 100644 --- a/src/app/home/home.page.html +++ b/src/app/home/home.page.html @@ -3,16 +3,13 @@ <ion-buttons slot="start"> <ion-menu-button></ion-menu-button> </ion-buttons> - <ion-buttons slot="end" > + <ion-buttons slot="end"> <ng-container [ngTemplateOutlet]="localeButton"></ng-container> </ion-buttons> </ion-toolbar> - </ion-header> <ion-content [fullscreen]="true" class="circle-bg-dark"> - - <!-- Desktop: translucent top toolbar --> <div *ngIf="!mobile"> <ion-toolbar translucent> @@ -25,27 +22,24 @@ </ion-buttons> <ion-buttons slot="end"> - <ng-container *ngTemplateOutlet="localeButton; context: {$implicit: 'light'}"></ng-container> + <ng-container *ngTemplateOutlet="localeButton; context: { $implicit: 'light' }"></ng-container> </ion-buttons> </ion-toolbar> </div> <div id="container"> - - <ion-card class="main welcome ion-padding ion-text-center ion-align-self-center"> + <ion-card class="main welcome ion-padding ion-text-center ion-align-self-center"> <ion-card-header> <div class="logo"></div> - <ion-card-subtitle [innerHTML]="'HOME.WELCOME'|translate"> - </ion-card-subtitle> + <ion-card-subtitle [innerHTML]="'HOME.WELCOME' | translate"></ion-card-subtitle> <ion-card-title class="ion-text-center"> <ion-spinner *rxIf="loading; else message"></ion-spinner> <ng-template #message> - {{'HOME.MESSAGE'|translate:{currency: currency$|async} }} + {{ 'HOME.MESSAGE' | translate : { currency: currency$ | async } }} </ng-template> </ion-card-title> </ion-card-header> <ion-card-content *ngIf="!loading" @fadeInAnimation> - <ng-container *ngIf="isLogin; else noAccount"> <!-- my account --> <ion-button expand="block" [routerLink]="'/wallet'"> @@ -61,8 +55,10 @@ <!-- disconnect button --> <p [class.cdk-visually-hidden]="mobile" *rxIf="defaultAccount$; let defaultAccount"> - <ion-text [innerHTML]="'HOME.NOT_YOUR_ACCOUNT_QUESTION' | translate: {pubkey: (defaultAccount|accountName) }"></ion-text> - <br/> + <ion-text + [innerHTML]="'HOME.NOT_YOUR_ACCOUNT_QUESTION' | translate : { pubkey: (defaultAccount | accountName) }" + ></ion-text> + <br /> <ion-text> <a href="#" (click)="logout($event)"> <span translate>HOME.BTN_CHANGE_ACCOUNT</span> @@ -73,7 +69,7 @@ <ng-template #noAccount> <!-- register --> - <ion-button expand="block" (click)="register($event)"> + <ion-button expand="block" (click)="register($event)"> <ion-label translate>LOGIN.CREATE_FREE_ACCOUNT</ion-label> </ion-button> @@ -86,16 +82,12 @@ </ng-template> </ion-card-content> </ion-card> - </div> </ion-content> <ng-template #localeButton let-buttonColor> - <!-- locale button --> - <ion-button id="locale-menu-trigger" - [color]="buttonColor" - [title]="'COMMON.LANGUAGE'|translate"> + <ion-button id="locale-menu-trigger" [color]="buttonColor" [title]="'COMMON.LANGUAGE' | translate"> <ion-icon slot="icon-only" name="language"></ion-icon> </ion-button> @@ -104,11 +96,9 @@ <ng-template> <ion-content> <ion-list> - <ion-item *ngFor="let item of locales" - (click)="changeLocale(item.key) && popover.dismiss()" - tappable> - <ion-label>{{item.value}}</ion-label> - <ion-icon slot="end" name="checkmark" *ngIf="data.locale===item.key"></ion-icon> + <ion-item *ngFor="let item of locales" (click)="changeLocale(item.key) && popover.dismiss()" tappable> + <ion-label>{{ item.value }}</ion-label> + <ion-icon slot="end" name="checkmark" *ngIf="data.locale === item.key"></ion-icon> </ion-item> </ion-list> </ion-content> diff --git a/src/app/home/home.page.ts b/src/app/home/home.page.ts index dad90d3378d04acefd7c2b127d9b2d89ba3b6b13..b0d79c650a61d074ea02b16392a36d810e31609f 100644 --- a/src/app/home/home.page.ts +++ b/src/app/home/home.page.ts @@ -95,13 +95,13 @@ export class HomePage extends AppPage<HomePageState> implements OnInit { } } - logout(even?: Event) { + logout(event?: Event) { event?.preventDefault(); this.accountService.forgetAll(); this.defaultAccount = null; } - transfer(event?: UIEvent) { - return this.transferController.transfer(event); + transfer() { + return this.transferController.transfer(); } } diff --git a/src/app/playground/playground.page.html b/src/app/playground/playground.page.html index c8c419165975c1d08886fe1ccf2efe8d5efc520d..2490c5854aa0fb26d2a088bd986b238f489f2aa5 100644 --- a/src/app/playground/playground.page.html +++ b/src/app/playground/playground.page.html @@ -1,11 +1,7 @@ - <ion-content> - <h1>Playground</h1> - <ng-container *rxLet="state$; let state"> <div>{{ state | json }}</div> </ng-container> - </ion-content> diff --git a/src/app/settings/settings.page.html b/src/app/settings/settings.page.html index 7ce703b328c215f8823a1cfcd82025e00f608528..17e913cc346c52f674f93cf2ae475b7739b8ad69 100644 --- a/src/app/settings/settings.page.html +++ b/src/app/settings/settings.page.html @@ -15,17 +15,18 @@ </ion-header> <div id="container"> - <ion-list *ngIf="!loading"> <ion-item> <ion-icon slot="start" name="language"></ion-icon> <ion-label color="dark" translate>COMMON.LANGUAGE</ion-label> - <ion-select [(ngModel)]="data.locale" - [interface]="mobile ? 'action-sheet' : 'popover'" - [okText]="'COMMON.BTN_OK'|translate" - [cancelText]="'COMMON.BTN_CANCEL'|translate"> + <ion-select + [(ngModel)]="data.locale" + [interface]="mobile ? 'action-sheet' : 'popover'" + [okText]="'COMMON.BTN_OK' | translate" + [cancelText]="'COMMON.BTN_CANCEL' | translate" + > <ion-select-option *ngFor="let locale of locales" [value]="locale.key"> - {{locale.value}} + {{ locale.value }} </ion-select-option> </ion-select> </ion-item> @@ -33,13 +34,10 @@ <ion-item-divider translate>SETTINGS.NETWORK_SETTINGS</ion-item-divider> <ion-item> - <ion-icon slot="start" name="cloud-done"></ion-icon> <ion-label color="dark" translate>SETTINGS.PEER</ion-label> - <ion-input [(ngModel)]="data.peer" class="ion-text-end"> - - </ion-input> + <ion-input [(ngModel)]="data.peer" class="ion-text-end"></ion-input> <ion-button slot="end" (click)="selectPeer()"> <ion-label>...</ion-label> </ion-button> @@ -48,23 +46,24 @@ <ion-item-divider translate>SETTINGS.AUTHENTICATION_SETTINGS</ion-item-divider> <ion-item> - <ion-icon slot="start" name="lock-open"></ion-icon> <ion-label color="dark" translate>SETTINGS.KEEP_AUTH</ion-label> - <ion-select [(ngModel)]="data.unAuthDelayMs" - [interface]="mobile ? 'action-sheet' : 'popover'" - [okText]="'COMMON.BTN_OK'|translate" - [cancelText]="'COMMON.BTN_CANCEL'|translate"> + <ion-select + [(ngModel)]="data.unAuthDelayMs" + [interface]="mobile ? 'action-sheet' : 'popover'" + [okText]="'COMMON.BTN_OK' | translate" + [cancelText]="'COMMON.BTN_CANCEL' | translate" + > <ion-select-option *ngFor="let item of unauthOptions" [value]="item.value"> - {{item.label|translate:{value: item.labelParam} }} + {{ item.label | translate : { value: item.labelParam } }} </ion-select-option> </ion-select> </ion-item> </ion-list> <div class="ion-text-center"> - <ion-button (click)="cancel()" color="light" > + <ion-button (click)="cancel()" color="light"> <ion-label translate>COMMON.BTN_CANCEL</ion-label> </ion-button> <ion-button (click)="save()" color="danger"> @@ -73,4 +72,3 @@ </div> </div> </ion-content> - diff --git a/src/app/shared/currencies.ts b/src/app/shared/currencies.ts index 4dc7772dbee8f7f8f4ae607eb1b02134024ff2ac..a1545cf3a259f8d1b11faff0e554c287d05d69a3 100644 --- a/src/app/shared/currencies.ts +++ b/src/app/shared/currencies.ts @@ -7,7 +7,7 @@ export function abbreviate(currency: string): string { const sepChars = ['-', '_', ' ']; let unit = ''; for (let i = 0; i < currency.length; i++) { - var c = currency[i]; + const c = currency[i]; if (i === 0) { unit = (c === 'g' || c === 'G') ? 'Ğ' : c ; } diff --git a/src/app/shared/decorator/state.decorator.ts b/src/app/shared/decorator/state.decorator.ts index 1775e8f65db774c61bf4a51fe507ee73a70073a2..ff20a729d46f4de6a93e73f1f427142654c7fc0d 100644 --- a/src/app/shared/decorator/state.decorator.ts +++ b/src/app/shared/decorator/state.decorator.ts @@ -13,7 +13,7 @@ export function RxStateRegister(): PropertyDecorator { // DEBUG //console.debug(`${target.constructor?.name} @State() ${key}`); - if (!!target[STATE_VAR_NAME_KEY]) throw new Error('Cannot define more than one @State() in class hierarchy'); + if (target[STATE_VAR_NAME_KEY]) throw new Error('Cannot define more than one @State() in class hierarchy'); Object.defineProperty(target, STATE_VAR_NAME_KEY, { value: key, diff --git a/src/app/shared/pipes/account.pipes.ts b/src/app/shared/pipes/account.pipes.ts index 73a40ffd89d5e4027fb85200a719b463692b2d3b..ffe34222c6b8cc4ad219b93e05acd0ffc14e8c17 100644 --- a/src/app/shared/pipes/account.pipes.ts +++ b/src/app/shared/pipes/account.pipes.ts @@ -1,9 +1,6 @@ -import {ChangeDetectorRef, Directive, inject, Pipe, PipeTransform} from '@angular/core'; -import {NumberFormatPipe} from "@app/shared/pipes/number-format.pipe"; -import {NetworkService} from "@app/network/network.service"; -import {Account, AccountData, AccountUtils} from "@app/account/account.model"; -import {equals, getPropertyByPath, isNotNilOrBlank} from "@app/shared/functions"; -import {AddressFormatPipe} from "@app/shared/pipes/address.pipes"; +import {ChangeDetectorRef, inject, Pipe, PipeTransform} from '@angular/core'; +import {Account, AccountUtils} from "@app/account/account.model"; +import {equals, getPropertyByPath} from "@app/shared/functions"; import {Subscription} from "rxjs"; import {formatAddress} from "@app/shared/currencies"; import {AccountsService} from "@app/account/accounts.service"; @@ -86,7 +83,9 @@ export declare type AccountPropertyPipeOptions<T> = string | {key?: string; defa name: 'accountProperty', pure: false }) -export class AccountPropertyPipe<T = any, O extends AccountPropertyPipeOptions<T> = AccountPropertyPipeOptions<T>> extends AccountAbstractPipe<T, O> { +export class AccountPropertyPipe<T = any, O extends AccountPropertyPipeOptions<T> = AccountPropertyPipeOptions<T>> + extends AccountAbstractPipe<T, O> + implements PipeTransform { constructor(_ref: ChangeDetectorRef) { super(_ref); @@ -105,7 +104,7 @@ export class AccountPropertyPipe<T = any, O extends AccountPropertyPipeOptions<T name: 'balance', pure: false }) -export class AccountBalancePipe extends AccountAbstractPipe<number, void> { +export class AccountBalancePipe extends AccountAbstractPipe<number, void> implements PipeTransform { constructor(_ref: ChangeDetectorRef) { super(_ref); @@ -120,7 +119,7 @@ export class AccountBalancePipe extends AccountAbstractPipe<number, void> { name: 'accountName', pure: false }) -export class AccountNamePipe extends AccountAbstractPipe<string, void> { +export class AccountNamePipe extends AccountAbstractPipe<string, void> implements PipeTransform{ constructor(cd: ChangeDetectorRef) { super(cd); diff --git a/src/app/shared/pipes/amount.pipe.ts b/src/app/shared/pipes/amount.pipe.ts index f1ba3d74a98c622db2dc6616d68813d48a744b42..3346a4320c8f075ce84d4192e6ec9c4be73477ff 100644 --- a/src/app/shared/pipes/amount.pipe.ts +++ b/src/app/shared/pipes/amount.pipe.ts @@ -1,4 +1,4 @@ -import {Pipe} from '@angular/core'; +import {Pipe, PipeTransform} from '@angular/core'; import {NumberFormatPipe} from "@app/shared/pipes/number-format.pipe"; import {NetworkService} from "@app/network/network.service"; import {isNil} from "@app/shared/functions"; @@ -6,7 +6,7 @@ import {isNil} from "@app/shared/functions"; @Pipe({ name: 'amountFormat' }) -export class AmountFormatPipe extends NumberFormatPipe { +export class AmountFormatPipe extends NumberFormatPipe implements PipeTransform { constructor(private networkService: NetworkService) { diff --git a/src/app/shared/pipes/arrays.pipe.ts b/src/app/shared/pipes/arrays.pipe.ts index 5856412edf3eff2cb6bc7c3319d2b7633bab177d..6c8246e15ab670fd75e60c6772839a56da01fdcb 100644 --- a/src/app/shared/pipes/arrays.pipe.ts +++ b/src/app/shared/pipes/arrays.pipe.ts @@ -65,6 +65,7 @@ export class ArrayFirstPipe implements PipeTransform { }) export class ArrayPluckPipe implements PipeTransform { + // eslint-disable-next-line @typescript-eslint/no-explicit-any transform<T>(val: T[], opts: string | { property: string; omitNil?: boolean }): any[] { const property = typeof opts === 'string' ? opts : opts?.property; const omitNil = typeof opts === 'string' ? false : opts?.omitNil; @@ -73,6 +74,7 @@ export class ArrayPluckPipe implements PipeTransform { (val || []).map(value => value && value[property]).filter(isNotNil); } } + @Pipe({ name: 'arrayJoin' }) @@ -91,9 +93,8 @@ export class ArrayJoinPipe implements PipeTransform { name: 'arrayIncludes' }) export class ArrayIncludesPipe implements PipeTransform { - - transform<T>(val: T[], args): boolean { - return val && val.includes(args) || false; + transform<T>(val: T[], searchElement: T): boolean { + return val && val.includes(searchElement) || false; } } diff --git a/src/app/shared/pipes/date-diff-duration.pipe.ts b/src/app/shared/pipes/date-diff-duration.pipe.ts index 632e83ef0843f4a8a49b790cbb2744c31f786151..33960fc00622b048a528b04405c9e180339f997c 100644 --- a/src/app/shared/pipes/date-diff-duration.pipe.ts +++ b/src/app/shared/pipes/date-diff-duration.pipe.ts @@ -17,7 +17,7 @@ export class DateDiffDurationPipe implements PipeTransform { private translate: TranslateService) { } - transform(value: { startValue: string | Moment; endValue: string | Moment }, args?: any): string | Promise<string> { + transform(value: { startValue: string | Moment; endValue: string | Moment }): string | Promise<string> { if (!value.startValue || !value.endValue) return ''; const startDate = fromDateISOString(value.startValue); diff --git a/src/app/shared/pipes/date-format.pipe.ts b/src/app/shared/pipes/date-format.pipe.ts index 6657ec568e799fbc8ecf2d929015dd450524ce65..dd0add1d98694073429fbb280f219dd80c587c45 100644 --- a/src/app/shared/pipes/date-format.pipe.ts +++ b/src/app/shared/pipes/date-format.pipe.ts @@ -1,10 +1,7 @@ -import {Injectable, Pipe, PipeTransform} from '@angular/core'; +import {Pipe, PipeTransform} from '@angular/core'; import {isMoment, Moment} from 'moment'; -import {DATE_ISO_PATTERN, fromDateISOString} from '../dates'; +import {fromDateISOString} from '../dates'; import {TranslateService} from '@ngx-translate/core'; -import * as momentImported from 'moment'; - -const moment = momentImported; @Pipe({ name: 'dateFormat' @@ -15,7 +12,7 @@ export class DateFormatPipe implements PipeTransform { private readonly dateTimePattern: string; constructor( - private translate: TranslateService + translate: TranslateService ) { const translations = translate.instant(['COMMON.DATE_PATTERN', 'COMMON.DATE_SHORT_PATTERN']); this.dateTimePattern = translations['COMMON.DATE_PATTERN']; diff --git a/src/app/shared/pipes/form.pipes.ts b/src/app/shared/pipes/form.pipes.ts index d41dc6e7c3a3edc2d58fc49227977c7085f921ec..8f6efb87e4e76f9997feb78e607306da0cf09beb 100644 --- a/src/app/shared/pipes/form.pipes.ts +++ b/src/app/shared/pipes/form.pipes.ts @@ -96,7 +96,7 @@ export class FormGetValuePipe implements PipeTransform, OnDestroy { // subscribe to statusChanges event if (listenStatusChanges) { - this._onControlStatusChanges = control.statusChanges.subscribe((status) => { + this._onControlStatusChanges = control.statusChanges.subscribe(() => { const value = control.value; if (value !== this.value) { this.value = value; diff --git a/src/app/shared/pipes/highlight.pipe.ts b/src/app/shared/pipes/highlight.pipe.ts index d4f16816bb1cfd875e8dda5112cae665738f2a1f..8b39a95141aa72f7e84d5ab07e863554d0aebd20 100644 --- a/src/app/shared/pipes/highlight.pipe.ts +++ b/src/app/shared/pipes/highlight.pipe.ts @@ -7,31 +7,32 @@ import { removeDiacritics, toBoolean } from '../functions'; @Injectable({providedIn: 'root'}) export class HighlightPipe implements PipeTransform { - // Replace search text by bold representation - transform(value: any, args?: string | { search: string; withAccent?: boolean } ): string { - if (typeof value !== 'string' || !args) return value; - const searchText = (typeof args === 'string' ? args : args.search); - const withAccent = (typeof args !== 'string') ? toBoolean(args?.withAccent, false) : false; - if (typeof searchText !== 'string') return value; - const searchRegexp = searchText - .replace(/[.]/g, '[.]') - .replace(/[*]+/g, '.*'); - if (searchRegexp === '.*') return value; // skip if can match everything + // eslint-disable-next-line @typescript-eslint/no-explicit-any + // Replace search text by bold representation + transform(value: any, args?: string | { search: string; withAccent?: boolean } ): string { + if (typeof value !== 'string' || !args) return value; + const searchText = (typeof args === 'string' ? args : args.search); + const withAccent = (typeof args !== 'string') ? toBoolean(args?.withAccent, false) : false; + if (typeof searchText !== 'string') return value; + const searchRegexp = searchText + .replace(/[.]/g, '[.]') + .replace(/[*]+/g, '.*'); + if (searchRegexp === '.*') return value; // skip if can match everything - if (withAccent) { - // Remove all accent characters to allow versatile comparison - const cleanedSearchText = removeDiacritics(searchRegexp).toUpperCase(); - const index = removeDiacritics(value.toUpperCase()).indexOf(cleanedSearchText); - if (index !== -1) { - return value.substring(0, index - 1) + - '<b>' + value.substring(index, cleanedSearchText.length) + '</b>' + - value.substring(index + cleanedSearchText.length); - } - return value; - } else { - // Default behaviour - const regexp = new RegExp('[ ]?' + searchRegexp, 'gi'); - return ('' + value).replace(regexp, '<b>$&</b>'); + if (withAccent) { + // Remove all accent characters to allow versatile comparison + const cleanedSearchText = removeDiacritics(searchRegexp).toUpperCase(); + const index = removeDiacritics(value.toUpperCase()).indexOf(cleanedSearchText); + if (index !== -1) { + return value.substring(0, index - 1) + + '<b>' + value.substring(index, cleanedSearchText.length) + '</b>' + + value.substring(index + cleanedSearchText.length); } + return value; + } else { + // Default behaviour + const regexp = new RegExp('[ ]?' + searchRegexp, 'gi'); + return ('' + value).replace(regexp, '<b>$&</b>'); } + } } diff --git a/src/app/shared/pipes/maps.pipe.ts b/src/app/shared/pipes/maps.pipe.ts index 5e70e05b450167b9df67caeb9be3468dc2d37b13..0f73b9e872f018ee57a5b492959e9954a1973c20 100644 --- a/src/app/shared/pipes/maps.pipe.ts +++ b/src/app/shared/pipes/maps.pipe.ts @@ -20,6 +20,7 @@ export class MapGetPipe implements PipeTransform { @Injectable({providedIn: 'root'}) export class MapKeysPipe implements PipeTransform { + // eslint-disable-next-line @typescript-eslint/no-explicit-any transform(map: any): any[] { if (!map) return null; return Object.keys(map); @@ -32,6 +33,7 @@ export class MapKeysPipe implements PipeTransform { @Injectable({providedIn: 'root'}) export class MapValuesPipe implements PipeTransform { + // eslint-disable-next-line @typescript-eslint/no-explicit-any transform(map: any): any[] { if (!map) return null; return Object.values(map); diff --git a/src/app/shared/pipes/math.pipes.ts b/src/app/shared/pipes/math.pipes.ts index 87e16f41852a872752b0d0c31a40f7035af64cc4..2754adc8707b9ed6df715c1354fcbaa5150e36e3 100644 --- a/src/app/shared/pipes/math.pipes.ts +++ b/src/app/shared/pipes/math.pipes.ts @@ -6,7 +6,7 @@ import {Injectable, Pipe, PipeTransform} from '@angular/core'; @Injectable({providedIn: 'root'}) export class MathAbsPipe implements PipeTransform { - transform(val: number): any { + transform(val: number): number { if (val !== undefined && val !== null) { return Math.abs(val); } else { @@ -21,7 +21,7 @@ export class MathAbsPipe implements PipeTransform { @Injectable({providedIn: 'root'}) export class EvenPipe implements PipeTransform { - transform(val: number): any { + transform(val: number): boolean { return val % 2 === 0; } } @@ -32,7 +32,7 @@ export class EvenPipe implements PipeTransform { @Injectable({providedIn: 'root'}) export class OddPipe implements PipeTransform { - transform(val: number): any { + transform(val: number): boolean { return val % 2 !== 0; } } diff --git a/src/app/shared/popover/list.popover.html b/src/app/shared/popover/list.popover.html index 1cfcef6f7773016639950b19da0f9b9fc4c85a5c..215fe8bda54a6afdb1e4e03514bef0b1da5a0a24 100644 --- a/src/app/shared/popover/list.popover.html +++ b/src/app/shared/popover/list.popover.html @@ -1,11 +1,8 @@ <ion-content> <ion-list> - <ion-list-header *ngIf="title">{{title|translate}}</ion-list-header> - <ion-item *ngFor="let item of items" - [disabled]="item.disabled" - (click)="click(item.value)" - tappable> - {{item.label|translate}} + <ion-list-header *ngIf="title">{{ title | translate }}</ion-list-header> + <ion-item *ngFor="let item of items" [disabled]="item.disabled" (click)="click(item.value)" tappable> + {{ item.label | translate }} </ion-item> </ion-list> </ion-content> diff --git a/src/app/shared/services/platform.service.ts b/src/app/shared/services/platform.service.ts index c6b15c10cef36c2cb316ee02affdab15b13cae28..c1f147ff308a297e17d26665256b35ce9a400974 100644 --- a/src/app/shared/services/platform.service.ts +++ b/src/app/shared/services/platform.service.ts @@ -1,8 +1,7 @@ import {Injectable} from "@angular/core"; import {Platform} from "@ionic/angular"; -import {NetworkService} from "../../network/network.service"; -import {SettingsService} from "../../settings/settings.service"; -import {StartableService} from "@app/shared/services/startable-service.class"; +import {NetworkService} from "@app/network/network.service"; +import {SettingsService} from "@app/settings/settings.service"; import {StorageService} from "@app/shared/services/storage/storage.service"; import {environment} from "@environments/environment.prod"; import {TranslateService} from "@ngx-translate/core"; diff --git a/src/app/shared/services/rx-startable-service.class.ts b/src/app/shared/services/rx-startable-service.class.ts index bc75fb6dd2f58639bb5b6e4f0022d3c4367716b7..02ea26f6417cbde407891a2d7f546aaebcfff44a 100644 --- a/src/app/shared/services/rx-startable-service.class.ts +++ b/src/app/shared/services/rx-startable-service.class.ts @@ -1,15 +1,15 @@ import {Optional} from '@angular/core'; import {firstValueFrom, Subject, takeUntil} from 'rxjs'; import {RxBaseServiceOptions, RxBaseService} from "@app/shared/services/rx-service.class"; -import {IStartableService} from "@app/shared/services/service.model"; +import {ReadyAsyncFunction, IStartableService, IWithReadyService} from "@app/shared/services/service.model"; -export interface RxStartableServiceOptions<T extends object = any> +export interface RxStartableServiceOptions<T extends object = {}> extends RxBaseServiceOptions<T> { } -export abstract class RxStartableService<T extends object = any, +export abstract class RxStartableService<T extends object = {}, O extends RxStartableServiceOptions<T> = RxStartableServiceOptions<T>> extends RxBaseService<T, O> implements IStartableService<T> { @@ -22,7 +22,7 @@ export abstract class RxStartableService<T extends object = any, private _started = false; private _startPromise: Promise<T> = null; - private _startPrerequisite: () => Promise<any> = null; + private _startPrerequisite: ReadyAsyncFunction; get started(): boolean { return this._started; @@ -33,7 +33,7 @@ export abstract class RxStartableService<T extends object = any, } protected constructor( - @Optional() prerequisiteService?: { ready: () => Promise<any> }, + @Optional() prerequisiteService?: IWithReadyService, options?: O ) { super(options); diff --git a/src/app/shared/services/service.class.ts b/src/app/shared/services/service.class.ts index 1f6f31bb2d3e890733a71456c764d310c4a693d8..d3895a98345cae8352ec2b6f1bace08f1545abbf 100644 --- a/src/app/shared/services/service.class.ts +++ b/src/app/shared/services/service.class.ts @@ -1,15 +1,12 @@ import {Subscription} from 'rxjs'; import {environment} from "@environments/environment"; import {OnDestroy} from "@angular/core"; -import {RxState} from "@rx-angular/state"; -export interface BaseServiceOptions<T extends object = any> { +export interface BaseServiceOptions { name?: string; } -export abstract class BaseService< - T extends object = any, - O extends BaseServiceOptions<T> = BaseServiceOptions<T>> +export abstract class BaseService<O extends BaseServiceOptions = BaseServiceOptions> implements OnDestroy { private _subscription: Subscription = null; diff --git a/src/app/shared/services/service.model.ts b/src/app/shared/services/service.model.ts index 61a4a5decb68673be3e52f00d43b9fefe6b2fc20..1e5f6b1b9b205a36be077da0545c6ffcf80c5e08 100644 --- a/src/app/shared/services/service.model.ts +++ b/src/app/shared/services/service.model.ts @@ -1,10 +1,14 @@ import {getPropertyByPathAsString, isNotNil, isNotNilOrBlank, matchUpperCase, startsWithUpperCase} from "../functions"; -export declare type EmptyObject = { - [key: string]: any; -}; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export declare type ReadyAsyncFunction<T = any> = () => Promise<T>; -export declare type FetchMoreFn<R, V = EmptyObject> = (variables?: V) => Promise<R>; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export declare interface IWithReadyService<T = any> { + ready: ReadyAsyncFunction<T>; +} + +export declare type FetchMoreFn<R, V = object> = (variables?: V) => Promise<R>; export declare interface LoadResult<T> { data: T[]; @@ -13,6 +17,7 @@ export declare interface LoadResult<T> { fetchMore?: FetchMoreFn<LoadResult<T>>; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function suggestFromArray<T = any>(values: T[], searchText: any, opts?: { offset?: 0; size?: 0; @@ -28,7 +33,6 @@ export function suggestFromArray<T = any>(values: T[], searchText: any, opts?: { const keys = opts && (opts.searchAttribute && [opts.searchAttribute] || opts.searchAttributes) || ['label']; // If wildcard, search using regexp - let filteredItems: T[]; if ((searchText as string).indexOf('*') !== -1) { searchText = (searchText as string).replace('*', '.*'); values = values.filter(v => keys.findIndex(key => matchUpperCase(getPropertyByPathAsString(v, key), searchText)) !== -1); diff --git a/src/app/shared/services/startable-service.class.ts b/src/app/shared/services/startable-service.class.ts index 9d7213e1d8c121fecdd74e77cc6b8ec9ae12e123..f86059c60dc38ca6cc446e2d800fec4ad97fe172 100644 --- a/src/app/shared/services/startable-service.class.ts +++ b/src/app/shared/services/startable-service.class.ts @@ -1,19 +1,19 @@ import {Optional} from '@angular/core'; import {firstValueFrom, Subject, takeUntil} from 'rxjs'; import {RxBaseServiceOptions} from "@app/shared/services/rx-service.class"; -import {IStartableService} from "@app/shared/services/service.model"; +import {ReadyAsyncFunction, IStartableService, IWithReadyService} from "@app/shared/services/service.model"; import {BaseService} from "@app/shared/services/service.class"; -export interface StartableServiceOptions<T extends object = any> +export interface StartableServiceOptions<T extends object = {}> extends RxBaseServiceOptions<T> { } -export abstract class StartableService<T extends object = any, +export abstract class StartableService<T extends object = {}, O extends StartableServiceOptions<T> = StartableServiceOptions<T>> - extends BaseService<T, O> - implements IStartableService<T> { + extends BaseService<O> + implements IStartableService<T>, IWithReadyService<T> { startSubject = new Subject<T>(); stopSubject = new Subject<void>(); @@ -24,7 +24,7 @@ export abstract class StartableService<T extends object = any, private _started = false; private _startPromise: Promise<T> = null; - private _startPrerequisite: () => Promise<any> = null; + private _startPrerequisite: ReadyAsyncFunction; get started(): boolean { return this._started; @@ -39,7 +39,7 @@ export abstract class StartableService<T extends object = any, } protected constructor( - @Optional() prerequisiteService?: { ready: () => Promise<any> }, + @Optional() prerequisiteService?: IWithReadyService<any>, options?: O ) { super(options); diff --git a/src/app/shared/services/storage/keyring-storage.ts b/src/app/shared/services/storage/keyring-storage.ts index a935fbf9c4251a393b4f64f2b17c5cb240ff7b20..4200dfeb02125814dada7a21910b31f69f76a02c 100644 --- a/src/app/shared/services/storage/keyring-storage.ts +++ b/src/app/shared/services/storage/keyring-storage.ts @@ -28,7 +28,7 @@ export class KeyringStorage implements KeyringStore { } all(cb: (key: string, value: KeyringJson) => void) { - this.storage.forEach((value, key, counter) => { + this.storage.forEach((value, key) => { //console.debug("Reading key=" + key, value); if (key.startsWith(this.storagePrefix)) { const shortKey = key.substring(this.storagePrefix.length); diff --git a/src/app/shared/services/storage/storage.service.ts b/src/app/shared/services/storage/storage.service.ts index 1d2c3dceaf8a0aaf4dbcaed5910e367b4ea81777..702fa6c91427fd2c6a418c5536160c91f93e62af 100644 --- a/src/app/shared/services/storage/storage.service.ts +++ b/src/app/shared/services/storage/storage.service.ts @@ -7,11 +7,11 @@ import {StartableService} from "@app/shared/services/startable-service.class"; @Injectable({ providedIn: 'root' }) -export class StorageService extends StartableService<Storage> implements IStorage { - +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export class StorageService<T = any> extends StartableService<Storage> implements IStorage<T> { get driver(): string | undefined { - return this._data?.driver; + return this.storage?.driver; } constructor(private platform: Platform, @@ -24,44 +24,41 @@ export class StorageService extends StartableService<Storage> implements IStorag await this.platform.ready(); const storage = await this.storage.create(); console.info(`[storage-service] Started using driver=${this.driver}`); - return storage; } - async set(key: string, value: any) { + async set(key: string, value: T) { //if (this._debug) console.debug(`[storage-service] Set ${key} = `, value); if (!this.started) await this.ready(); - return this._data.set(key, value); + return this.storage.set(key, value); } - async get(key: string): Promise<any> { + async get(key: string): Promise<T> { //if (this._debug) console.debug(`[storage-service] Get ${key}`); if (!this.started) await this.ready(); - return this._data.get(key); + return this.storage.get(key); } async remove(key: string) { if (!this.started) await this.ready(); //if (this._debug) console.debug(`[storage-service] Remove key ${key}`); - return this._data.remove(key); + return this.storage.remove(key); } async keys(): Promise<string[]> { //if (this._debug) console.debug(`[storage-service] Get keys`); if (!this.started) await this.ready(); - const keys = await this._data.keys(); - //if (this._debug) console.debug(`[storage-service] ${keys.length} keys found: `, keys); - return keys; + return await this.storage.keys(); } async clear() { if (!this.started) await this.ready(); - await this._data.clear(); + await this.storage.clear(); } - async forEach(iteratorCallback: (value: any, key: string, iterationNumber: Number) => any): Promise<void> { + async forEach(iteratorCallback: (value: T, key: string, iterationNumber: Number) => T): Promise<void> { if (!this.started) await this.ready(); - return this._data.forEach(iteratorCallback); + return this.storage.forEach(iteratorCallback); } } diff --git a/src/app/shared/services/storage/storage.utils.ts b/src/app/shared/services/storage/storage.utils.ts index 136b6f8caeadf59e412a5d050f5006bcd7a4b8cd..96c467ebb0622660b38aae117f411899ef2b751e 100644 --- a/src/app/shared/services/storage/storage.utils.ts +++ b/src/app/shared/services/storage/storage.utils.ts @@ -4,14 +4,15 @@ import * as LocalForage from "localforage"; import * as CordovaSQLiteDriver from 'localforage-cordovasqlitedriver'; -export interface IStorage { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface IStorage<V = any> { readonly driver: string; - set(key: string, value: any): Promise<void>; - get(key: string): Promise<any>; + set(key: string, value: V): Promise<void>; + get(key: string): Promise<V>; remove(key: string): Promise<void> keys(): Promise<string[]>; clear(): Promise<void>; - forEach(iteratorCallback: (value: any, key: string, iterationNumber: Number) => any): Promise<void>; + forEach(iteratorCallback: (value: V, key: string, iterationNumber: Number) => V): Promise<void>; } export const StorageDrivers = { diff --git a/src/app/shared/swiper/app-swiper.directive.ts b/src/app/shared/swiper/app-swiper.directive.ts index bd13c92d1a22f6425b6612ca05930aa26c2eae75..1d8f3f9c8e0e96b957bbb7126b092ea1ad1fecad 100644 --- a/src/app/shared/swiper/app-swiper.directive.ts +++ b/src/app/shared/swiper/app-swiper.directive.ts @@ -9,12 +9,16 @@ import {fromEventPattern, Subscription} from "rxjs"; export class SwiperDirective implements AfterViewInit, OnDestroy { private _subscription = new Subscription(); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any private readonly swiperElement: any; - @Input('config') config?: SwiperOptions; - @Input('modules') modules?: ((opts?: any) => void)[]; + @Input() config?: SwiperOptions; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + @Input() modules?: ((opts?: any) => void)[]; - @Output() slideChangeTransitionStart = new EventEmitter<any>(); + @Output() slideChangeTransitionStart = new EventEmitter<void>(); constructor(private el: ElementRef) { this.swiperElement = el.nativeElement; @@ -32,7 +36,7 @@ export class SwiperDirective implements AfterViewInit, OnDestroy { this.swiperElement.initialize(); this._subscription.add(fromEventPattern((handler) => this.swiper.on('slideChangeTransitionStart', handler)) - .subscribe((value) => this.slideChangeTransitionStart.emit(value)) + .subscribe(() => this.slideChangeTransitionStart.emit()) ); } diff --git a/src/app/shared/types.ts b/src/app/shared/types.ts index 08fe5e7b8f24c75fe4221522ad18c8501f33c903..9be8d9dde59a8bd3dc86191d3a1c54c18cf655cc 100644 --- a/src/app/shared/types.ts +++ b/src/app/shared/types.ts @@ -3,11 +3,11 @@ export declare type KeyValueType<T> = {[key in KeyType]: T}; export declare type KeysEnum<T> = { [P in keyof Required<T>]: boolean }; -export declare interface ObjectMap<O = any> { +export declare interface ObjectMap<O> { [key: string]: O; } -export declare interface ObjectMapEntry<O = any> { +export declare interface ObjectMapEntry<O> { key: string; value?: O; } @@ -23,3 +23,5 @@ export declare interface IconRef { matIcon?: string; // A mat icon matSvgIcon?: string; // A mat SVG icon } + +export declare type AppEvent = MouseEvent | TouchEvent | PointerEvent | CustomEvent; diff --git a/src/app/transfer/transfer.controller.ts b/src/app/transfer/transfer.controller.ts index 16c62aa010f59c15ae7e1832db9fd89b7ca4ea92..a63b307271ca72d13d3ac6bcc3135e66be844b7d 100644 --- a/src/app/transfer/transfer.controller.ts +++ b/src/app/transfer/transfer.controller.ts @@ -1,21 +1,8 @@ -import { - ActionSheetButton, - ActionSheetController, - ActionSheetOptions, - IonModal, - ModalController, - PopoverController -} from "@ionic/angular"; +import {ModalController} from "@ionic/angular"; import {Injectable} from "@angular/core"; import {PlatformService} from "@app/shared/services/platform.service"; -import {PopoverOptions} from "@ionic/core"; -import {ListItem, ListPopover, ListPopoverOptions} from "@app/shared/popover/list.popover"; -import {TranslateService} from "@ngx-translate/core"; -import {AuthModal, AuthModalOptions} from "@app/account/auth/auth.modal"; import {Router} from "@angular/router"; -import {RegisterModal, RegisterModalOptions} from "@app/account/register/register.modal"; import {TransferPage, TransferPageOptions} from "@app/transfer/transfer.page"; -import {Account} from "@app/account/account.model"; @Injectable() export class TransferController { @@ -24,17 +11,17 @@ export class TransferController { constructor( private platform: PlatformService, - private translate: TranslateService, private modalCtrl: ModalController, private router: Router ) { } - async transfer(event: UIEvent, opts?: TransferPageOptions): Promise<string|undefined> { + async transfer(opts?: TransferPageOptions): Promise<string> { if (this._mobile) { console.info('[transfer] Opening transfer page'); - this.router.navigateByUrl('/transfer'); + await this.router.navigateByUrl('/transfer'); + return undefined; } else { console.info('[transfer] Opening transfer modal'); diff --git a/src/app/transfer/transfer.page.html b/src/app/transfer/transfer.page.html index f563f445a0bed8f33549e5a1ca84622c596ee72f..f885199f89af95ee13737c3a62a89bd61ce6ed69 100644 --- a/src/app/transfer/transfer.page.html +++ b/src/app/transfer/transfer.page.html @@ -14,103 +14,89 @@ </ion-toolbar> </ion-header> - <form [formGroup]="form" novalidate (ngSubmit)="doSubmit($event)" (keyup.enter)="doSubmit($event)"> - <ion-list> - <!-- error --> - <ion-item *ngIf="error" lines="none" color="light" class="error"> - <ion-icon slot="start" name="alert-circle" color="danger"></ion-icon> - <ion-label color="danger">{{error|translate}}</ion-label> - </ion-item> - - <!-- TO --> - <ion-item> - <ion-label color="medium" translate>TRANSFER.TO</ion-label> - <ion-textarea *rxIf="recipient$; let recipient; else skeleton60" - [rows]="mobile ? 2 : 1" - class="ion-text-wrap ion-text-end" - [(ngModel)]="recipient.address" (ionFocus)="wotModal.present()"> - </ion-textarea> - <ion-button slot="end" fill="clear" id="open-modal-trigger" - [class.cdk-visually-hidden]="loading || mobile" - [disabled]="loading" - [title]="'COMMON.BTN_SEARCH'|translate"> - <ion-icon slot="icon-only" name="search"></ion-icon> - </ion-button> - - <!-- Scan QR code--> - <ion-button slot="end" fill="clear" color="dark" - *ngIf="_enableScan" - (click)="scanRecipient($event)" - [disabled]="loading"> - <ion-icon slot="icon-only" name="qr-code"></ion-icon> - </ion-button> - </ion-item> - - <!-- FROM --> - <ion-item tappable (click)="selectAccount($event)"> - <ion-label color="medium" translate>TRANSFER.FROM</ion-label> - <ng-container *rxIf="accounts$; let accounts; suspense: skeleton60"> - <ion-input [value]="account|accountName" required></ion-input> - - <ion-badge *ngIf="account|balance; let balance" slot="end" - [color]="amount+fee>balance/100 ? 'danger' : 'success'"> - {{balance|amountFormat}} - </ion-badge> - </ng-container> - - </ion-item> - - <!-- amount --> - <ion-item> - <ion-label color="medium" translate>TRANSFER.AMOUNT</ion-label> - <ion-input type="number" formControlName="amount"> - </ion-input> - <ion-label color="medium" slot="end" *ngIf="currency">{{currency.symbol}}</ion-label> - </ion-item> - - <ion-item lines="none"> -<!-- <ion-toggle [(ngModel)]="showComment">--> -<!-- </ion-toggle>--> -<!-- <ion-label class="ion-text-start" color="medium" translate>TRANSFER.BTN_ADD_COMMENT</ion-label>--> - <ion-badge slot="end" *ngIf="amount && fee">{{'TRANSFER.FEE'|translate:{fee: fee, currency: currency.symbol} }}</ion-badge> - </ion-item> - - <ion-item *ngIf="showComment"> - <ion-textarea [placeholder]="'TRANSFER.COMMENT_HELP'|translate" rows="5"> - - </ion-textarea> - </ion-item> - </ion-list> - -<!-- <div class="ion-text-center" *ngIf="!mobile">--> -<!-- <ng-container *ngTemplateOutlet="buttons"></ng-container>--> -<!-- </div>--> - <ion-toolbar class="ion-text-center" [color]="'transparent'"> - <ng-container *ngTemplateOutlet="buttons"></ng-container> - </ion-toolbar> - </form> - - <ion-modal - #wotModal - [initialBreakpoint]="_initialWotModalBreakpoint" - [breakpoints]="[0.25, 0.5, 0.75]" - [backdropDismiss]="true" - [backdropBreakpoint]="0.5" - > - <ng-template> - <app-wot-lookup - showToolbar="true" - toolbarColor="secondary" - [showSearchBar]="true" - [showItemActions]="false" - (itemClick)="setRecipient($event) && hideWotModal()" - (searchClick)="wotModal.setCurrentBreakpoint(0.75)"> - - <ion-button toolbar-end fill="clear" (click)="hideWotModal($event)" translate>COMMON.BTN_CLOSE</ion-button> - - </app-wot-lookup> - </ng-template> - </ion-modal> + <ion-list> + <!-- error --> + <ion-item *rxIf="error$; let error" lines="none" color="light" class="error"> + <ion-icon slot="start" name="alert-circle" color="danger"></ion-icon> + <ion-label color="danger">{{ error | translate }}</ion-label> + </ion-item> + + <!-- TO --> + <ion-item> + <ion-label color="medium" translate>TRANSFER.TO</ion-label> + <ion-textarea + *rxIf="recipient$; let recipient; else: skeleton60" + [rows]="mobile ? 2 : 1" + [tabIndex]="mobile ? -1 : 1" + class="ion-text-wrap ion-text-end" + [(ngModel)]="recipient.address" + ></ion-textarea> + <ion-button + slot="end" + fill="clear" + [disabled]="loading" + [title]="'COMMON.BTN_SEARCH' | translate" + (click)="openWotModal($event)" + > + <ion-icon slot="icon-only" name="search"></ion-icon> + </ion-button> + + <!-- Scan QR code--> + <ion-button + slot="end" + fill="clear" + color="dark" + *ngIf="_enableScan" + (click)="scanRecipient($event)" + [disabled]="loading" + > + <ion-icon slot="icon-only" name="qr-code"></ion-icon> + </ion-button> + </ion-item> + + <!-- FROM --> + <ion-item tappable (click)="selectAccount()"> + <ion-label color="medium" translate>TRANSFER.FROM</ion-label> + <ng-container *rxIf="accounts$; let accounts; suspense: skeleton60"> + <ion-input [value]="account$ | push | accountName" required></ion-input> + + <ion-badge + *ngIf="account$ | push | balance; let balance" + slot="end" + [color]="amount + fee > balance / 100 ? 'danger' : 'success'" + > + {{ balance | amountFormat }} + </ion-badge> + </ng-container> + </ion-item> + + <!-- amount --> + <ion-item> + <ion-label color="medium" translate>TRANSFER.AMOUNT</ion-label> + <ion-input type="number" [(ngModel)]="amount"></ion-input> + <ion-label color="medium" slot="end" *rxIf="currency$; let currency">{{ currency.symbol }}</ion-label> + </ion-item> + + <ion-item lines="none"> + <!-- <ion-toggle [(ngModel)]="showComment">--> + <!-- </ion-toggle>--> + <!-- <ion-label class="ion-text-start" color="medium" translate>TRANSFER.BTN_ADD_COMMENT</ion-label>--> + <ion-badge slot="end" *ngIf="amount && fee"> + {{ 'TRANSFER.FEE' | translate : { fee: fee, currency: currency.symbol } }} + </ion-badge> + </ion-item> + + <ion-item *ngIf="showComment"> + <ion-textarea [placeholder]="'TRANSFER.COMMENT_HELP' | translate" rows="5"></ion-textarea> + </ion-item> + </ion-list> + + <!-- <div class="ion-text-center" *ngIf="!mobile">--> + <!-- <ng-container *ngTemplateOutlet="buttons"></ng-container>--> + <!-- </div>--> + <ion-toolbar class="ion-text-center" [color]="'transparent'"> + <ng-container *ngTemplateOutlet="buttons"></ng-container> + </ion-toolbar> </ion-content> <!--<ion-footer *ngIf="mobile" class="ion-text-center">--> @@ -118,17 +104,14 @@ <!--</ion-footer>--> <ng-template #buttons> - <ion-button (click)="cancel($event)" color="light" - [disabled]="loading" *ngIf="!mobile"> + <ion-button (click)="cancel()" color="light" [disabled]="loading" *ngIf="!mobile"> <ion-label translate>COMMON.BTN_CANCEL</ion-label> </ion-button> - <ion-button (click)="qrCodeModal.present()" - [disabled]="loading||invalid"> + <ion-button (click)="qrCodeModal.present()" [disabled]="loading || invalid"> <ion-icon slot="start" name="qr-code"></ion-icon> <ion-label translate>TRANSFER.BTN_QR_CODE</ion-label> </ion-button> - <ion-button (click)="submit($event)" [disabled]="loading||invalid" - color="tertiary"> + <ion-button (click)="submit()" [disabled]="loading || invalid" color="tertiary"> <ion-icon slot="start" name="paper-plane"></ion-icon> <ion-label translate>TRANSFER.BTN_SEND</ion-label> </ion-button> @@ -138,12 +121,7 @@ <ion-skeleton-text [animated]="true" style="width: 60%"></ion-skeleton-text> </ng-template> -<ion-modal - #qrCodeModal - [backdropDismiss]="true" - [presentingElement]="_presentingElement" - [canDismiss]="true" -> +<ion-modal #qrCodeModal [backdropDismiss]="true" [presentingElement]="_presentingElement" [canDismiss]="true"> <ng-template> <ion-content> <ion-header> @@ -160,19 +138,13 @@ </ion-item> <ion-item> <ion-icon slot="start" name="warning" color="danger" lines="none"></ion-icon> - <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED'|translate"></ion-label> + <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED' | translate"></ion-label> </ion-item> </ion-content> </ng-template> </ion-modal> - -<ion-modal - #notImplementedModal - [backdropDismiss]="true" - [presentingElement]="_presentingElement" - [canDismiss]="true" -> +<ion-modal #notImplementedModal [backdropDismiss]="true" [presentingElement]="_presentingElement" [canDismiss]="true"> <ng-template> <ion-content> <ion-header> @@ -184,7 +156,7 @@ </ion-header> <ion-item> <ion-icon slot="start" name="warning" color="danger" lines="none"></ion-icon> - <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED'|translate"></ion-label> + <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED' | translate"></ion-label> </ion-item> </ion-content> </ng-template> diff --git a/src/app/transfer/transfer.page.ts b/src/app/transfer/transfer.page.ts index 9530600288a619f368807a2f95064cc89afed1db..253780e617766e1c2da4e46fbe08954f8f580741 100644 --- a/src/app/transfer/transfer.page.ts +++ b/src/app/transfer/transfer.page.ts @@ -1,18 +1,10 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - Injector, - Input, - OnDestroy, OnInit, - ViewChild -} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, ViewChild} from '@angular/core'; import {AppPage, AppPageState} from "@app/shared/pages/base-page.class"; import {Account} from "@app/account/account.model"; -import {ActionSheetOptions, IonModal, IonTextarea, ModalController, Platform, PopoverOptions} from "@ionic/angular"; +import {ActionSheetOptions, IonModal, ModalController, Platform, PopoverOptions} from "@ionic/angular"; import {mergeMap, Observable, tap} from "rxjs"; import {isNotEmptyArray, isNotNilOrBlank} from "@app/shared/functions"; -import {filter, first} from "rxjs/operators"; +import {filter} from "rxjs/operators"; import {NetworkService} from "@app/network/network.service"; import {Currency} from "@app/network/currency.model"; import {NavigationEnd, Router} from "@angular/router"; @@ -22,7 +14,7 @@ import {Capacitor} from "@capacitor/core"; import {CapacitorPlugins} from "@app/shared/capacitor/plugins"; import {RxState} from "@rx-angular/state"; import {AccountsService} from "@app/account/accounts.service"; -import {FormBuilder, Validators} from "@angular/forms"; +import {WotController} from "@app/wot/wot.controller"; export interface TransferState extends AppPageState { currency: Currency; @@ -77,7 +69,6 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe @Input() dismissOnSubmit: boolean = false; // True is modal @Input() showToastOnSubmit: boolean = true; - @ViewChild('wotModal') wotModal: IonModal; @ViewChild('qrCodeModal') qrCodeModal: IonModal; get valid(): boolean { @@ -99,21 +90,15 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe protected accountService: AccountsService, protected networkService: NetworkService, protected modalCtrl: ModalController, - protected router: Router, - formBuilder: FormBuilder + protected wotCtrl: WotController, + protected router: Router ) { super({name: 'transfer', loadDueTime: 250, initialState: { recipient: {address: null, meta: null} } - }, - formBuilder.group({ - 'account': [null, Validators.required], - 'recipient': [null, Validators.required], - 'amount': [null, Validators.required], - //'comment': [null, Validators.maxLength(255)], - })); + }); this._state.connect('accounts', this.accountService.watchAll({positiveBalanceFirst: true}) // DEBUG @@ -162,7 +147,6 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe async ngOnDestroy() { super.ngOnDestroy(); - await this.wotModal?.dismiss(); await this.qrCodeModal?.dismiss(); } @@ -171,11 +155,10 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe this._enableScan = this.ionicPlatform.is('capacitor') && Capacitor.isPluginAvailable(CapacitorPlugins.BarcodeScanner); - return { - }; + return {}; } - async selectAccount(event?: Event) { + async selectAccount() { const account = await this.accountService.selectAccount({ minBalance: this.amount || 0, positiveBalanceFirst: true, @@ -186,63 +169,21 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe } } - - protected async focusFrom(event: UIEvent, textarea?: IonTextarea) { - - if (this._autoOpenWotModal) { - await this.showWotModal(event); - - if (textarea) { - const el = await textarea.getInputElement(); - setTimeout( () => el.focus(), 250); - } - } - - } - - - protected async showWotModal(event: UIEvent, breakpoint?: number) { - breakpoint = breakpoint || 0.25; - - this._initialWotModalBreakpoint = breakpoint; - - if (!this.wotModal.isCmpOpen) { - await this.wotModal.present(); - } - - // Set breakpoint - if (breakpoint > 0.25){ - const currentBreakpoint = await this.wotModal.getCurrentBreakpoint(); - if (breakpoint > currentBreakpoint) { - await this.wotModal.setCurrentBreakpoint(breakpoint); - } - } - } - - protected async hideWotModal(event?: UIEvent) { - if (this.wotModal && this.wotModal.isCmpOpen) { - this._autoOpenWotModal = false; - await this.wotModal.dismiss(); - } - } - - - setRecipient(recipient: string|Account): boolean { + setRecipient(recipient: string|Account) { + console.log(this._logPrefix + 'Selected recipient: ', recipient) if (typeof recipient === 'object') { this.recipient = recipient; } else { this.recipient = {address: recipient, meta: null}; } - this.markForCheck(); - return true; } - cancel(event?: UIEvent) { + cancel() { this.close(); } - async submit(event?: UIEvent) { + async submit() { // Check valid if (!this.recipient || !this.account) return; // Skip @@ -265,7 +206,7 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe } } - async close(data?: any) { + async close(data?: string) { // As a page if (this.routerOutlet) { console.debug('[transfer] Closing page with result: ', data); @@ -278,9 +219,6 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe } // As a modal else { - // First close wot modal (if opened) - await this.hideWotModal(); - const modal = await this.modalCtrl.getTop(); const hasModal = !!modal; if (hasModal) { @@ -291,9 +229,25 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe return this.unload(); } - async scanRecipient(event: UIEvent) { + async openWotModal(event: Event) { + event.preventDefault(); + + const searchText = this.recipient?.address; + const data = await this.wotCtrl.select({searchText}); + + if (!data) { + console.log('TODO cancelled') + return; // User cancelled + } + + this.recipient = data; + } + + async scanRecipient(event: Event) { if (!this._enableScan) return; // SKip + event.preventDefault(); + await BarcodeScanner.hideBackground(); // make background of WebView transparent const result = await BarcodeScanner.startScan(); // start scanning and wait for a result @@ -310,7 +264,6 @@ export class TransferPage extends AppPage<TransferState> implements OnInit, OnDe protected async ngOnUnload() { this.showComment = false; - await this.wotModal?.dismiss(); await this.qrCodeModal?.dismiss(); return { ...(await super.ngOnUnload()), diff --git a/src/app/wallet/wallet-routing.module.ts b/src/app/wallet/wallet-routing.module.ts index 6cd1bcbadd217e7b6e0c9ca6e2da6e0aefeaba86..47052ad22d984dbd8f9fc2c5aceff9a2241a0c9d 100644 --- a/src/app/wallet/wallet-routing.module.ts +++ b/src/app/wallet/wallet-routing.module.ts @@ -1,7 +1,7 @@ -import {inject, NgModule} from '@angular/core'; -import {Routes, RouterModule, ActivatedRouteSnapshot, RouterStateSnapshot} from '@angular/router'; +import {NgModule} from '@angular/core'; +import {RouterModule, Routes} from '@angular/router'; -import { WalletPage } from './wallet.page'; +import {WalletPage} from './wallet.page'; import {AuthGuardService} from "@app/account/auth-guard.service"; const routes: Routes = [ diff --git a/src/app/wallet/wallet.module.ts b/src/app/wallet/wallet.module.ts index b6832435eb82adb82c005dcb5dfefa1f71fd1a06..0d1d9f47439b280c5208a0c58908a4d20ec4c7d1 100644 --- a/src/app/wallet/wallet.module.ts +++ b/src/app/wallet/wallet.module.ts @@ -4,8 +4,6 @@ import {WalletPage} from './wallet.page'; import {WalletPageRoutingModule} from "./wallet-routing.module"; import {AppSharedModule} from "@app/shared/shared.module"; import {TranslateModule} from "@ngx-translate/core"; -import {AppAuthModule} from "@app/account/auth/auth.module"; -import {AppUnlockModule} from "@app/account/unlock/unlock.module"; import {NgxJdenticonModule} from "ngx-jdenticon"; import {AppAccountModule} from "@app/account/account.module"; diff --git a/src/app/wallet/wallet.page.html b/src/app/wallet/wallet.page.html index 485f433a5f75550cf0b347f63491975bafd8a1a0..7569becd50a06408380d79d500bbcb835b8f34e3 100644 --- a/src/app/wallet/wallet.page.html +++ b/src/app/wallet/wallet.page.html @@ -5,18 +5,18 @@ </ion-buttons> <ion-title translate>ACCOUNT.TITLE</ion-title> - <ion-select slot="end" - [(ngModel)]="account" - [interface]="mobile ? 'action-sheet' : 'popover'" - [interfaceOptions]="mobile ? actionSheetOptions : popoverOptions" - [okText]="'COMMON.BTN_OK'|translate" - [cancelText]="'COMMON.BTN_CANCEL'|translate"> - <ion-select-option *rxFor="let account of accounts$" - [value]="account"> - {{account|accountName}} + <ion-select + slot="end" + [(ngModel)]="account" + [interface]="mobile ? 'action-sheet' : 'popover'" + [interfaceOptions]="mobile ? actionSheetOptions : popoverOptions" + [okText]="'COMMON.BTN_OK' | translate" + [cancelText]="'COMMON.BTN_CANCEL' | translate" + > + <ion-select-option *rxFor="let account of accounts$" [value]="account"> + {{ account | accountName }} </ion-select-option> - <ion-select-option [value]="new" - translate>ACCOUNT.WALLET_LIST.BTN_NEW_DOTS</ion-select-option> + <ion-select-option [value]="new" translate>ACCOUNT.WALLET_LIST.BTN_NEW_DOTS</ion-select-option> </ion-select> </ion-toolbar> <ion-progress-bar type="indeterminate" *rxIf="loading$"></ion-progress-bar> @@ -24,93 +24,94 @@ <ion-content [fullscreen]="true"> <ion-header> - <ion-item color="secondary" > + <ion-item color="secondary"> <ion-avatar slot="start" (click)="avatarModal.present()" [style.background-color]="'white'"> <ng-container *rxIf="account$; let account"> - <ion-img *ngIf="account.meta?.avatar; let avatar; else svgIcon" - [src]="avatar"></ion-img> + <ion-img *ngIf="account.meta?.avatar; let avatar; else: svgIcon" [src]="avatar"></ion-img> <ng-template #svgIcon> - <svg width="40" width="40" - [data-jdenticon-value]="account.data?.randomId||account.address"></svg> + <svg width="40" width="40" [data-jdenticon-value]="account.data?.randomId || account.address"></svg> </ng-template> </ng-container> </ion-avatar> <ion-label class="ion-text-center ion-margin-end"> <h2 translate>ACCOUNT.BALANCE</h2> - <p><b *rxIf="account$; let account">{{ account | balance | amountFormat }}</b></p> + <p> + <b *rxIf="account$; let account">{{ account | balance | amountFormat }}</b> + </p> </ion-label> </ion-item> </ion-header> <div id="container"> - <ion-list> <ion-item *rxIf="error$; let error" lines="none" color="light"> <ion-icon slot="start" name="alert-circle" color="danger"></ion-icon> - <ion-label color="danger">{{error|translate}}</ion-label> + <ion-label color="danger">{{ error | translate }}</ion-label> </ion-item> <!-- pubkey --> - <ion-item *ngIf="(account$ | async)?.meta.publicKeyV1; let pubkey" - (click)="copyToClipboard($event, pubkey)"> - + <ion-item *ngIf="(account$ | async)?.meta.publicKeyV1; let pubkey" (click)="copyToClipboard($event, pubkey)"> <ion-icon name="key" slot="start"></ion-icon> <ion-label> <h2 translate>COMMON.PUBKEY</h2> <p class="ion-text-wrap"> - <span>{{pubkey}}</span> + <span>{{ pubkey }}</span> </p> </ion-label> <ion-button - [disabled]="loading$|async" - (click)="showQrCode($event, pubkey, 'COMMON.PUBKEY')" slot="end" fill="clear" shape="round"> + [disabled]="loading$ | async" + (click)="showQrCode($event, pubkey, 'COMMON.PUBKEY')" + slot="end" + fill="clear" + shape="round" + > <ion-icon slot="icon-only" name="qr-code"></ion-icon> </ion-button> </ion-item> <!-- address --> - <ion-item *rxIf="account$; let account" - (click)="copyToClipboard($event, account.address)"> - + <ion-item *rxIf="account$; let account" (click)="copyToClipboard($event, account.address)"> <ion-chip slot="start"> - <ion-icon name="key" ></ion-icon> + <ion-icon name="key"></ion-icon> <ion-text>v2</ion-text> </ion-chip> <ion-label> <h2 translate>COMMON.ADDRESS</h2> <p class="ion-text-wrap"> - <span>{{account.address}}</span> + <span>{{ account.address }}</span> </p> </ion-label> <ion-button - [disabled]="loading$|async" - (click)="showQrCode($event, account.address, 'COMMON.ADDRESS')" slot="end" fill="clear" shape="round"> + [disabled]="loading$ | async" + (click)="showQrCode($event, account.address, 'COMMON.ADDRESS')" + slot="end" + fill="clear" + shape="round" + > <ion-icon slot="icon-only" name="qr-code"></ion-icon> </ion-button> </ion-item> -<!-- <ion-item detail [routerLink]="['/wot/cert/', data?.address]">--> -<!-- <ion-icon slot="start" name="ribbon"></ion-icon>--> -<!-- <ion-label translate>ACCOUNT.BALANCE_ACCOUNT</ion-label>--> -<!-- <ion-badge color="success">{{(data?.data.free || 0) | amountFormat}} {{currency}}</ion-badge>--> -<!-- </ion-item>--> + <!-- <ion-item detail [routerLink]="['/wot/cert/', data?.address]">--> + <!-- <ion-icon slot="start" name="ribbon"></ion-icon>--> + <!-- <ion-label translate>ACCOUNT.BALANCE_ACCOUNT</ion-label>--> + <!-- <ion-badge color="success">{{(data?.data.free || 0) | amountFormat}} {{currency}}</ion-badge>--> + <!-- </ion-item>--> -<!-- <ion-item detail --> -<!-- [routerLink]="['/wot/cert/', data?.address]">--> + <!-- <ion-item detail --> + <!-- [routerLink]="['/wot/cert/', data?.address]">--> <ion-item detail (click)="notImplementedModal.present()"> - <ion-icon slot="start" name="ribbon"></ion-icon> - <ion-label translate>ACCOUNT.CERTIFICATION_COUNT</ion-label> + <ion-icon slot="start" name="ribbon"></ion-icon> + <ion-label translate>ACCOUNT.CERTIFICATION_COUNT</ion-label> <ion-badge color="success">0</ion-badge> </ion-item> - </ion-list> <div class="ion-text-center ion-padding-top"> - <ion-button [routerLink]="['/transfer', 'from', (account$|push)?.address]" - [disabled]="loading"> + <ion-button [routerLink]="['/transfer', 'from', (account$ | push)?.address]" [disabled]="loading"> <ion-icon slot="start" name="paper-plane"></ion-icon> <ion-label translate>COMMON.BTN_SEND_MONEY</ion-label> </ion-button> @@ -118,9 +119,7 @@ </div> </ion-content> -<ion-modal - #authModal - [backdropDismiss]="false"> +<ion-modal #authModal [backdropDismiss]="false"> <ng-template> <ion-content scrollY="false"> <app-auth-modal></app-auth-modal> @@ -128,17 +127,11 @@ </ng-template> </ion-modal> - -<ion-modal - #qrCodeModal - [backdropDismiss]="true" - [presentingElement]="_presentingElement" - [canDismiss]="true" -> +<ion-modal #qrCodeModal [backdropDismiss]="true" [presentingElement]="_presentingElement" [canDismiss]="true"> <ng-template> <ion-header> <ion-toolbar color="secondary"> - <ion-title translate>{{qrCodeTitle}}</ion-title> + <ion-title translate>{{ qrCodeTitle }}</ion-title> <ion-buttons slot="end"> <ion-button (click)="qrCodeModal.dismiss()" translate>COMMON.BTN_CLOSE</ion-button> </ion-buttons> @@ -146,7 +139,6 @@ </ion-header> <ion-content [scrollY]="mobile"> - <ion-list> <!-- QR code --> <ion-item> @@ -156,21 +148,14 @@ <!-- public key --> <ion-item lines="none" (click)="copyToClipboard($event, qrCodeValue)" tappable> <ion-icon slot="start" name="key"></ion-icon> - <ion-label class="ion-text-wrap">{{qrCodeValue}}</ion-label> + <ion-label class="ion-text-wrap">{{ qrCodeValue }}</ion-label> </ion-item> </ion-list> </ion-content> - </ng-template> </ion-modal> - -<ion-modal - #avatarModal - [backdropDismiss]="true" - [presentingElement]="_presentingElement" - [canDismiss]="true" -> +<ion-modal #avatarModal [backdropDismiss]="true" [presentingElement]="_presentingElement" [canDismiss]="true"> <ng-template> <ion-content [scrollY]="mobile"> <ion-header> @@ -184,20 +169,13 @@ <ion-item> <ion-icon slot="start" name="warning" color="danger" lines="none"></ion-icon> - <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED'|translate"></ion-label> + <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED' | translate"></ion-label> </ion-item> </ion-content> </ng-template> </ion-modal> - - -<ion-modal - #notImplementedModal - [backdropDismiss]="true" - [presentingElement]="_presentingElement" - [canDismiss]="true" -> +<ion-modal #notImplementedModal [backdropDismiss]="true" [presentingElement]="_presentingElement" [canDismiss]="true"> <ng-template> <ion-content> <ion-header> @@ -209,7 +187,7 @@ </ion-header> <ion-item> <ion-icon slot="start" name="warning" color="danger" lines="none"></ion-icon> - <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED'|translate"></ion-label> + <ion-label class="ion-text-wrap" [innerHTML]="'INFO.FEATURES_NOT_IMPLEMENTED' | translate"></ion-label> </ion-item> </ion-content> </ng-template> diff --git a/src/app/wallet/wallet.page.ts b/src/app/wallet/wallet.page.ts index d79c865b447b8dd9130472f90862813597baa02a..90ca1166897b6d73746a1606a872f0b8ad40a985 100644 --- a/src/app/wallet/wallet.page.ts +++ b/src/app/wallet/wallet.page.ts @@ -92,7 +92,7 @@ export class WalletPage extends AppPage<WalletState> implements OnInit { // Add new wallet this._state.hold(this.account$.pipe(filter(account => account === WalletPage.NEW)), - _ => this.addNewWallet()); + () => this.addNewWallet()); // Load by address this._state.connect('account', this._state.$.pipe( diff --git a/src/app/wot/wot-details.page.html b/src/app/wot/wot-details.page.html index 4c61f824af81db0c4a492e672abc8b10eb51ddc2..9dd83fdd4466f762e95ed9723f03d2d3d1991248 100644 --- a/src/app/wot/wot-details.page.html +++ b/src/app/wot/wot-details.page.html @@ -4,26 +4,25 @@ <ion-menu-button></ion-menu-button> <ion-back-button></ion-back-button> </ion-buttons> - <ion-title size="large">{{data|accountName}}</ion-title> + <ion-title size="large">{{ data | accountName }}</ion-title> </ion-toolbar> </ion-header> <ion-content [fullscreen]="true"> <ion-header collapse="condense" *ngIf="showToolbar"> <ion-toolbar> - <ion-title size="large">{{data|accountName}}</ion-title> + <ion-title size="large">{{ data | accountName }}</ion-title> </ion-toolbar> </ion-header> <div id="container"> - <ion-header> <ion-toolbar class="ion-text-end" color="secondary"> <ion-avatar slot="start" *ngIf="data?.meta?.avatar"> <ion-img [src]="data.meta.avatar"></ion-img> </ion-avatar> <ion-title size="large" *ngIf="loaded; else loadingText" class="balance"> - {{ data|balance|amountFormat }} + {{ data | balance | amountFormat }} </ion-title> <ng-template #loadingText> <ion-title translate>COMMON.LOADING</ion-title> @@ -35,29 +34,26 @@ <ion-item> <ion-icon slot="start" name="key"></ion-icon> <ion-label color="medium" translate>COMMON.PUBKEY</ion-label> - <ion-input *ngIf="loaded; else skeleton60" - class="ion-text-end" - [value]="data?.address" - readonly> - </ion-input> - - <ion-button slot="end" (click)="copyAddress()" - [disabled]="loading" - fill="clear" [title]="'COMMON.COPY'|translate"> + <ion-input *ngIf="loaded; else skeleton60" class="ion-text-end" [value]="data?.address" readonly></ion-input> + + <ion-button + slot="end" + (click)="copyAddress()" + [disabled]="loading" + fill="clear" + [title]="'COMMON.COPY' | translate" + > <ion-icon slot="icon-only" name="copy"></ion-icon> </ion-button> </ion-item> - </ion-list> <div class="ion-text-center"> - <ion-button [routerLink]="['/transfer', 'to', data?.address]" - [disabled]="loading"> + <ion-button [routerLink]="['/transfer', 'to', data?.address]" [disabled]="loading"> <ion-icon slot="start" name="paper-plane"></ion-icon> <ion-label translate>COMMON.BTN_SEND_MONEY</ion-label> </ion-button> </div> - </div> </ion-content> diff --git a/src/app/wot/wot-details.page.ts b/src/app/wot/wot-details.page.ts index f02e3db6bc31189792f6667e0f9d3404dae9c343..0c78b6c7d29bf9f6d245482d94c949eaca089de4 100644 --- a/src/app/wot/wot-details.page.ts +++ b/src/app/wot/wot-details.page.ts @@ -1,4 +1,4 @@ -import {ChangeDetectionStrategy, Component, Injector, Input, OnInit} from '@angular/core'; +import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core'; import {AppPage, AppPageState} from "@app/shared/pages/base-page.class"; import {Account} from "@app/account/account.model"; diff --git a/src/app/wot/wot-lookup.page.html b/src/app/wot/wot-lookup.page.html index 923fd9b0790521056b4519cd4030bbbbe432b464..b55a82892519bec639ada420a7ebb560b6486ff5 100644 --- a/src/app/wot/wot-lookup.page.html +++ b/src/app/wot/wot-lookup.page.html @@ -1,11 +1,16 @@ <ion-header [translucent]="true" *ngIf="showToolbar"> <ion-toolbar [color]="toolbarColor"> - <ion-buttons slot="start"> + <ion-buttons slot="start" *ngIf="!modal"> <ion-menu-button></ion-menu-button> </ion-buttons> <ion-title translate>MENU.WOT</ion-title> <ion-buttons slot="end"> <ng-content select="[toolbar-end]"></ng-content> + + <!-- close --> + <ion-button fill="clear" *ngIf="closeClick.observed" (click)="closeClick.emit($event)" translate> + COMMON.BTN_CLOSE + </ion-button> </ion-buttons> </ion-toolbar> </ion-header> @@ -18,26 +23,24 @@ </ion-header> <div id="container"> - - <ion-searchbar *ngIf="showSearchBar" - #searchBar - inputmode="search" - autocomplete="off" - animated="true" - showClearButton="true" - [debounce]="debounceTime" - (ionClear)="markAsLoading()" - (ionInput)="markAsLoading()" - (ionChange)="searchChanged($event, $event.detail.value)" - (search)="searchChanged($event, searchBar.value)" - [placeholder]="'WOT.SEARCH_HELP'|translate" - (click)="searchClick.emit($event)"> - </ion-searchbar> + <ion-searchbar + *ngIf="showSearchBar" + #searchBar + inputmode="search" + autocomplete="off" + animated="true" + showClearButton="true" + [debounce]="debounceTime" + (ionClear)="markAsLoading()" + (ionInput)="markAsLoading()" + (ionChange)="searchChanged($event, $event.detail.value)" + (search)="searchChanged($event, searchBar.value)" + [placeholder]="'WOT.SEARCH_HELP' | translate" + (click)="searchClick.emit($event)" + ></ion-searchbar> <ion-list> - <ng-container *ngIf="loading; else items"> - <ng-template [ngTemplateOutlet]="itemSkeleton"></ng-template> <ng-template [ngTemplateOutlet]="itemSkeleton"></ng-template> <ng-template [ngTemplateOutlet]="itemSkeleton"></ng-template> @@ -51,37 +54,40 @@ </ion-avatar> <ng-template #iconPerson> <ion-avatar slot="start"> - <svg width="40" width="40" [data-jdenticon-value]="item.data?.randomId||item.address"></svg> + <svg width="40" width="40" [data-jdenticon-value]="item.data?.randomId || item.address"></svg> </ion-avatar> </ng-template> <ion-label> - <h2>{{item.meta?.name}}</h2> - <p>{{item.address|addressFormat}}</p> + <h2>{{ item.meta?.name }}</h2> + <p>{{ item.address | addressFormat }}</p> </ion-label> - <ion-button slot="end" *ngIf="showItemActions && !mobile" (click)="transfer(item)" [title]="'BTN_SEND_MONEY'|translate"> + <ion-button + slot="end" + *ngIf="showItemActions && !mobile" + (click)="transfer(item)" + [title]="'BTN_SEND_MONEY' | translate" + > <ion-icon slot="icon-only" name="paper-plane"></ion-icon> </ion-button> </ion-item> <ion-item-options *ngIf="mobile && showItemActions"> - <ion-item-option (click)="transfer(item)" [title]="'BTN_SEND_MONEY'|translate"> + <ion-item-option (click)="transfer(item)" [title]="'BTN_SEND_MONEY' | translate"> <ion-icon slot="icon-only" name="paper-plane"></ion-icon> </ion-item-option> </ion-item-options> </ion-item-sliding> </ng-template> - </ion-list> </div> </ion-content> - <ng-template #itemSkeleton> <ion-item> <ion-icon slot="start" name="person"></ion-icon> <ion-label> - <h2><ion-skeleton-text animated style="width: 20%;"></ion-skeleton-text></h2> - <p><ion-skeleton-text animated style="width: 50%;"></ion-skeleton-text></p> + <h2><ion-skeleton-text animated style="width: 20%"></ion-skeleton-text></h2> + <p><ion-skeleton-text animated style="width: 50%"></ion-skeleton-text></p> </ion-label> </ion-item> </ng-template> diff --git a/src/app/wot/wot-lookup.page.spec.ts b/src/app/wot/wot-lookup.page.spec.ts index 530a8a45cb1a0bb721a36931ccd1c7673de1c11f..64284a69c527cca3396d7c7ed6fded14de69e6ec 100644 --- a/src/app/wot/wot-lookup.page.spec.ts +++ b/src/app/wot/wot-lookup.page.spec.ts @@ -1,9 +1,9 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing'; import { IonicModule } from '@ionic/angular'; import { RouterModule } from '@angular/router'; -import { WotLookupPage } from './wallet.page'; +import { WotLookupPage } from './wot-lookup.page'; -describe('FolderPage', () => { +describe('WotLookupPage', () => { let component: WotLookupPage; let fixture: ComponentFixture<WotLookupPage>; diff --git a/src/app/wot/wot-lookup.page.ts b/src/app/wot/wot-lookup.page.ts index 09542ae26ed27f49a81d6bbd7ba8f066a23c0d70..e28af5cf58078d94248265ec8bdf0128c3b54e39 100644 --- a/src/app/wot/wot-lookup.page.ts +++ b/src/app/wot/wot-lookup.page.ts @@ -4,11 +4,15 @@ import {AppPage, AppPageState} from "@app/shared/pages/base-page.class"; import {Account} from "@app/account/account.model"; import {Router} from "@angular/router"; import {WotService} from "@app/wot/wot.service"; -import {WotSearchFilter} from "@app/wot/wot.model"; -import {toBoolean} from "@app/shared/functions"; -import {debounceTime, mergeMap, Observable} from "rxjs"; +import {WotSearchFilter, WotSearchFilterUtils} from "@app/wot/wot.model"; +import {isNilOrBlank, isNotNilOrBlank, toBoolean} from "@app/shared/functions"; +import {Observable} from "rxjs"; +import {filter, switchMap, tap, debounceTime, distinctUntilChanged, mergeMap} from "rxjs/operators"; + import {PredefinedColors} from "@app/shared/colors/colors.utils"; -import {RxStateSelect} from "@app/shared/decorator/state.decorator"; +import {RxStateProperty, RxStateSelect} from "@app/shared/decorator/state.decorator"; +import {RxState} from "@rx-angular/state"; +import {ModalController} from "@ionic/angular"; export interface WotLookupState extends AppPageState { @@ -17,41 +21,65 @@ export interface WotLookupState extends AppPageState { items: Account[]; } +export interface WotLookupOptions { + debounceTime?: number; + showToolbar?: boolean; + showSearchBar?: boolean; + showItemActions?: boolean; + toolbarColor?: PredefinedColors; + searchText?: string; + filter?: WotSearchFilter; +} + @Component({ selector: 'app-wot-lookup', templateUrl: './wot-lookup.page.html', styleUrls: ['./wot-lookup.page.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + providers: [RxState] }) -export class WotLookupPage extends AppPage<WotLookupState> implements OnInit { +export class WotLookupPage extends AppPage<WotLookupState> implements OnInit, WotLookupOptions { + + @RxStateSelect() protected items$: Observable<Account[]>; + @Input() modal = false; @Input() debounceTime = 650; @Input() showToolbar = true; @Input() showSearchBar = true; - @Output() searchClick = new EventEmitter<Event>(); - @Output() itemClick = new EventEmitter<Account>(); - @Input() showItemActions: boolean; @Input() toolbarColor: PredefinedColors = 'primary'; + @Input() @RxStateProperty() filter: WotSearchFilter; + @Input() @RxStateProperty() searchText: string; + + @Output() searchClick = new EventEmitter<Event>(); + @Output() itemClick = new EventEmitter<Account>(); + @Output() closeClick = new EventEmitter<Account>(); - @RxStateSelect() items$: Observable<Account[]>; constructor(private router: Router, - private wotService: WotService + private wotService: WotService, + private modalCtrl: ModalController ) { super({name: 'wot-lookup-page'}); this._state.connect('filter', - this._state.select('searchText') - .pipe(debounceTime(this.debounceTime)), + this._state.select('loading').pipe( + filter(loading => loading === false), + switchMap(() => this._state.select('searchText')), + distinctUntilChanged(), + tap(() => this.markAsLoading()), + debounceTime(this.debounceTime), + ), (s, text) => { - return { - ...s.filter, - text - } - }); + return { + ...s.filter, + text: isNilOrBlank(text) ? undefined : text, + last: isNilOrBlank(text) ? s.filter?.last : undefined + } + }); this._state.connect('items', this._state.select('filter').pipe( + distinctUntilChanged(WotSearchFilterUtils.isEquals), mergeMap(filter => this.search(filter)) )); } @@ -59,25 +87,31 @@ export class WotLookupPage extends AppPage<WotLookupState> implements OnInit { ngOnInit() { super.ngOnInit(); this.showItemActions = toBoolean(this.showItemActions, !this.itemClick.observed); + + if (this.modal) { + this.registerSubscription( + this.itemClick.subscribe(item => this.modalCtrl.dismiss(item)) + ) + + this.registerSubscription( + this.closeClick.subscribe(() => this.modalCtrl.dismiss()) + ) + } } protected async ngOnLoad(): Promise<WotLookupState> { await this.wotService.ready(); - const items = await this.search({last: true}) + const filter = !WotSearchFilterUtils.isEmpty(this.filter) && this.filter + || (isNotNilOrBlank(this.searchText) && {text:this.searchText}) + || {last: true}; - return <WotLookupState>{ - searchText: null, - filter: {}, - items - }; + return <WotLookupState>{ filter }; } async search(filter?: WotSearchFilter): Promise<Account[]> { - this.log('search:', arguments); - - this.markAsLoading(); + this.log('search:', filter); try { return await this.wotService.search(filter); @@ -87,7 +121,6 @@ export class WotLookupPage extends AppPage<WotLookupState> implements OnInit { } finally { this.markAsLoaded(); - this.markForCheck(); } } @@ -111,11 +144,11 @@ export class WotLookupPage extends AppPage<WotLookupState> implements OnInit { super.markAsLoading(); } - async searchChanged(event: CustomEvent<any>, value: string) { + async searchChanged(event: CustomEvent, value: string) { if (!event || event.defaultPrevented) return; event.preventDefault(); event.stopPropagation(); - this._state.set('searchText', (_) => value); + this.searchText = value; } } diff --git a/src/app/wot/wot.controller.ts b/src/app/wot/wot.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..562b84ccf75573395708391de33e0caec8ace6e4 --- /dev/null +++ b/src/app/wot/wot.controller.ts @@ -0,0 +1,31 @@ +import {Injectable} from "@angular/core"; +import {ModalController} from "@ionic/angular"; +import {Account} from "@app/account/account.model"; +import {WotLookupOptions, WotLookupPage} from "@app/wot/wot-lookup.page"; + + +@Injectable({providedIn: 'root'}) +export class WotController { + + constructor( + protected modalCtrl: ModalController + ) { + + } + + async select(options?: WotLookupOptions): Promise<Account> { + const modal = await this.modalCtrl.create({ + component: WotLookupPage, + componentProps: <WotLookupOptions>{ + ...options, + modal: true + } + }); + await modal.present(); + const {data} = await modal.onDidDismiss(); + + if (!data) return; // User cancelled + + return data as Account; + } +} diff --git a/src/app/wot/wot.model.ts b/src/app/wot/wot.model.ts index 8ddaa09dc73c04630074562979ded29e5c1e1326..34134da38c2d517fc1bb2e6c7b9efb2c53ab515f 100644 --- a/src/app/wot/wot.model.ts +++ b/src/app/wot/wot.model.ts @@ -1,7 +1,21 @@ +import {equals, isNil, isNilOrBlank, isNotNilOrBlank} from "@app/shared/functions"; export interface WotSearchFilter { - address?: string; text?: string; last?: boolean; } + + +export class WotSearchFilterUtils { + + static isEquals(f1: WotSearchFilter, f2: WotSearchFilter) { + return f1 === f2 || equals(f1, f2) + } + + static isEmpty(filter: WotSearchFilter) { + return !filter || (isNilOrBlank(filter.text) && isNil(filter.last) && isNotNilOrBlank(filter.address)); + } + +} + diff --git a/src/app/wot/wot.module.ts b/src/app/wot/wot.module.ts index 1618f3927e242c14a0edd2dc553b6cef32db9312..2d2b31aa5c5319d4023e002eb010eaad117775f0 100644 --- a/src/app/wot/wot.module.ts +++ b/src/app/wot/wot.module.ts @@ -14,11 +14,11 @@ import {NgxJdenticonModule} from "ngx-jdenticon"; NgxJdenticonModule, WotRoutingModule ], - exports: [ + declarations: [ WotLookupPage, WotDetailsPage ], - declarations: [ + exports: [ WotLookupPage, WotDetailsPage ] diff --git a/src/polyfills.ts b/src/polyfills.ts index a986dba2fdee78f79c1deefb2c6f59c26fd4aea3..39ac0e9892f2ff22e60f5b79f297d799222adaa9 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -55,6 +55,7 @@ import 'zone.js'; // Included with Angular CLI. */ // Polkadot API augment import '@polkadot/api-augment' +// eslint-disable-next-line @typescript-eslint/no-explicit-any (window as any).global = window; // Force moment-timezone to be loaded, otherwise moment().tz() will failed