diff --git a/package-lock.json b/package-lock.json index 0207babf78d91f7381ae832cb146824968f45f86..57beb6ff47621b0f08f947488564c4f2f9cdf928 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cesium", - "version": "2.0.0-alpha45", + "version": "2.0.0-alpha46", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cesium", - "version": "2.0.0-alpha45", + "version": "2.0.0-alpha46", "license": "AGPL-3.0", "dependencies": { "@angular/animations": "^18.2.13", @@ -131,7 +131,7 @@ "typescript": "~5.4.5" }, "engines": { - "node": ">= 20.17.2", + "node": ">= 20.17.0", "npm": ">= 10.7.0", "yarn": ">= 1.22.19" }, diff --git a/src/app/account/accounts.service.ts b/src/app/account/accounts.service.ts index f5feca466854d93728dff500d11695090e9c60fb..c6f0aa8d82d435e3a0132b84124ff756c3b72cc8 100644 --- a/src/app/account/accounts.service.ts +++ b/src/app/account/accounts.service.ts @@ -277,11 +277,11 @@ export class AccountsService extends RxStartableService<AccountsState> { return true; // ok } - const hasPassword = this.settings.get('hasPassword') || false; - if (!hasPassword) { - console.error(`${this._logPrefix}No password defined. Cannot auth`); - return false; // KO - } + // const hasPassword = this.settings.get('hasPassword') || false; //TODO: TO BE VALIDED + // if (!hasPassword) { + // console.error(`${this._logPrefix}No password defined. Cannot auth`); + // return false; // KO + // } console.debug(`${this._logPrefix}Not auth: opening unlock modal...`); diff --git a/src/app/account/auth/address/address.form.html b/src/app/account/auth/address/address.form.html index a73d28803b4ca46efdfc705aaa598109f255ca52..c1ffb7bd80611fb3a3500b8c714353fc4fa8b042 100644 --- a/src/app/account/auth/address/address.form.html +++ b/src/app/account/auth/address/address.form.html @@ -9,9 +9,16 @@ <!-- Address --> <ion-item> <ion-input formControlName="address" [label]="'LOGIN.ADDRESS_HELP' | translate" labelPlacement="floating" autocomplete="off" required> - <ion-button fill="clear" slot="end" [attr.aria-label]="'COMMON.BTN_SEARCH'" (click)="showWotModal($event)"> - <ion-icon slot="icon-only" name="search" aria-hidden="true"></ion-icon> - </ion-button> + <ion-buttons slot="end"> + <!-- Copy from clipboard --> + <ion-button fill="clear" (click)="pasteFromClipboard()"> + <ion-icon slot="icon-only" name="clipboard" aria-hidden="true"></ion-icon> + </ion-button> + + <ion-button fill="clear" [attr.aria-label]="'COMMON.BTN_SEARCH'" (click)="showWotModal($event)"> + <ion-icon slot="icon-only" name="search" aria-hidden="true"></ion-icon> + </ion-button> + </ion-buttons> </ion-input> </ion-item> </ion-list> diff --git a/src/app/account/auth/address/address.form.ts b/src/app/account/auth/address/address.form.ts index 583e1209b8c474cba6cf3dbb0119fe4ba9a2c943..61239916babb02af77a5fdf3f14b5d415dcf32f3 100644 --- a/src/app/account/auth/address/address.form.ts +++ b/src/app/account/auth/address/address.form.ts @@ -4,11 +4,12 @@ import { AppForm } from '@app/shared/form.class'; import { SettingsService } from '@app/settings/settings.service'; import { environment } from '@environments/environment'; import { FormUtils } from '@app/shared/forms'; -import { isNil } from '@app/shared/functions'; +import { isNil, isNotNilOrBlank } from '@app/shared/functions'; import { setTimeout } from '@rx-angular/cdk/zone-less/browser'; import { AuthData } from '@app/account/auth/auth.model'; import { SharedValidators } from '@app/shared/form/form-validators'; import { APP_WOT_CONTROLLER, IWotController } from '@app/wot/wot.model'; +import { Clipboard } from '@capacitor/clipboard'; @Component({ selector: 'app-address-form', @@ -82,6 +83,18 @@ export class AddressForm extends AppForm<AuthData> implements OnInit { }; } + async pasteFromClipboard() { + try { + const { value } = await Clipboard.read(); + + if (isNotNilOrBlank(value)) { + this.form.patchValue({ address: value.trim() }); + } + } catch (err) { + console.error('Can not read clipboard', err); + } + } + // get address corresponding to form input get address(): string { const data = this.form.value; diff --git a/src/app/account/auth/auth.modal.ts b/src/app/account/auth/auth.modal.ts index 7b7eafdab23742f59c0cc6e21441ac098c781ef2..70af2b4c07e00bbbaaebd3d3166d881470473d6b 100644 --- a/src/app/account/auth/auth.modal.ts +++ b/src/app/account/auth/auth.modal.ts @@ -10,6 +10,7 @@ import { SettingsService } from '@app/settings/settings.service'; import { AppForm } from '@app/shared/form.class'; import { toBoolean } from '@app/shared/functions'; import { DerivationSelectionComponent } from '@app/account/auth/derivation-selection/derivation-selection.component'; +import { HelpPage } from '@app/home/help/help.page'; export interface AuthModalOptions { auth?: boolean; @@ -77,6 +78,7 @@ export class AuthModal implements OnInit, AuthModalOptions { try { data = data || this.form.value; + data.v2.mnemonic = data.v2.mnemonic.trim(); // Disable the form this.form.disable(); @@ -152,8 +154,16 @@ export class AuthModal implements OnInit, AuthModalOptions { this.markForCheck(); } - showHelpModal(anchor?: string) { - console.info('TODO Opening help modal to anchor: ' + anchor); + async showHelpModal(anchor?: string) { + const modal = await this.modalCtrl.create({ + id: 'help-modal', + component: HelpPage, + componentProps: { + highlightedDefinition: anchor, + }, + canDismiss: true, + }); + await modal.present(); } protected register() { diff --git a/src/app/account/auth/mnemonic/mnemonic.form.html b/src/app/account/auth/mnemonic/mnemonic.form.html index eede827ab9de52ae38a905dec22308f7f90654c0..96b31fcd4e32ea524eac0778979316b0792d4919 100644 --- a/src/app/account/auth/mnemonic/mnemonic.form.html +++ b/src/app/account/auth/mnemonic/mnemonic.form.html @@ -8,29 +8,19 @@ <!-- Mnemonic --> <ion-item [class.invalid]="form.get('mnemonic').invalid && form.get('mnemonic').touched"> - @if (showMnemonic) { - <ion-textarea - formControlName="mnemonic" - [label]="'LOGIN.MNEMONIC' | translate" - labelPlacement="floating" - autocomplete="off" - required - ></ion-textarea> - } @else { - <ion-textarea - formControlName="mnemonic" - type="password" - [label]="'LOGIN.MNEMONIC' | translate" - labelPlacement="floating" - autocomplete="off" - required - ></ion-textarea> - } + <ion-label position="floating" translate>LOGIN.MNEMONIC</ion-label> + <ion-textarea formControlName="mnemonic" [class.password-mode]="!showMnemonic" autocomplete="off" required></ion-textarea> - <!-- 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> + <ion-buttons slot="end"> + <!-- Copy from clipboard --> + <ion-button (click)="pasteFromClipboard()" fill="clear" color="medium" [tabindex]="-1"> + <ion-icon slot="icon-only" name="clipboard-outline"></ion-icon> + </ion-button> + <!-- show/hide button --> + <ion-button (click)="toggleShowMnemonic($event)" fill="clear" color="medium" [tabindex]="-1"> + <ion-icon slot="icon-only" [name]="showMnemonic ? 'eye-off' : 'eye'"></ion-icon> + </ion-button> + </ion-buttons> </ion-item> <!-- Address --> @@ -38,5 +28,4 @@ <ion-textarea [value]="generatedAddress" [label]="'COMMON.ADDRESS' | translate" labelPlacement="floating" [disabled]="true"></ion-textarea> </ion-item> </ion-list> - </form> diff --git a/src/app/account/auth/mnemonic/mnemonic.form.scss b/src/app/account/auth/mnemonic/mnemonic.form.scss new file mode 100644 index 0000000000000000000000000000000000000000..99e8c8fb53dfc256ab76cb11e3508ce592d43576 --- /dev/null +++ b/src/app/account/auth/mnemonic/mnemonic.form.scss @@ -0,0 +1,3 @@ +.password-mode { + -webkit-text-security: disc; +} diff --git a/src/app/account/auth/mnemonic/mnemonic.form.ts b/src/app/account/auth/mnemonic/mnemonic.form.ts index 5ef065b9680b2b5df8c99724ad44ebda11129a85..25c5ebf2a047da16e7e51e3ab12618d993eafae3 100644 --- a/src/app/account/auth/mnemonic/mnemonic.form.ts +++ b/src/app/account/auth/mnemonic/mnemonic.form.ts @@ -11,10 +11,13 @@ import { debounceTime } from 'rxjs/operators'; import { AccountsService } from '@app/account/accounts.service'; import { formatAddress } from '@app/shared/currencies'; import { SharedValidators } from '@app/shared/form/form-validators'; +import { Clipboard } from '@capacitor/clipboard'; +import { isNotNilOrBlank } from '@app/shared/functions'; @Component({ selector: 'app-mnemonic-form', templateUrl: 'mnemonic.form.html', + styleUrls: ['mnemonic.form.scss'], animations: [slideUpDownAnimation], changeDetection: ChangeDetectionStrategy.OnPush, }) @@ -92,6 +95,18 @@ export class MnemonicForm extends AppForm<AuthData> implements OnInit { setTimeout(() => this.validate.emit(data)); } + async pasteFromClipboard() { + try { + const { value } = await Clipboard.read(); + + if (isNotNilOrBlank(value)) { + this.form.patchValue({ mnemonic: value.trim() }); + } + } catch (err) { + console.error('Can not read clipboard', err); + } + } + get value(): AuthData { const data = this.form.value; return { diff --git a/src/app/account/auth/pubkey/pubkey.form.html b/src/app/account/auth/pubkey/pubkey.form.html index 1b5906f09ea47c7e96d1da5799fded91050bc153..f4358da7c4e8ae268ff1fba72326a85f09d65513 100644 --- a/src/app/account/auth/pubkey/pubkey.form.html +++ b/src/app/account/auth/pubkey/pubkey.form.html @@ -9,16 +9,21 @@ <!-- Pubkey --> <ion-item> <ion-input type="text" formControlName="pubkey" [label]="'LOGIN.PUBKEY_HELP' | translate" labelPlacement="floating" autocomplete="off" required> - <!-- search button--> - <ion-button - fill="clear" - slot="end" - [attr.aria-label]="'COMMON.BTN_SEARCH'" - (click)="showWotModal($event)" - [disabled]="form | formGetValue: 'pubkey' | isNilOrBlank" - > - <ion-icon slot="icon-only" name="search" aria-hidden="true"></ion-icon> - </ion-button> + <ion-buttons slot="end"> + <!-- Copy from clipboard --> + <ion-button fill="clear" (click)="pasteFromClipboard()"> + <ion-icon slot="icon-only" name="clipboard" aria-hidden="true"></ion-icon> + </ion-button> + <!-- search button--> + <ion-button + fill="clear" + [attr.aria-label]="'COMMON.BTN_SEARCH'" + (click)="showWotModal($event)" + [disabled]="form | formGetValue: 'pubkey' | isNilOrBlank" + > + <ion-icon slot="icon-only" name="search" aria-hidden="true"></ion-icon> + </ion-button> + </ion-buttons> </ion-input> </ion-item> </ion-list> diff --git a/src/app/account/auth/pubkey/pubkey.form.ts b/src/app/account/auth/pubkey/pubkey.form.ts index 2fc60f85fb5d9dd00bee0b7e407d21c1d67915fd..c03aecd1719089331b4e5145c5407114f2d9de7d 100644 --- a/src/app/account/auth/pubkey/pubkey.form.ts +++ b/src/app/account/auth/pubkey/pubkey.form.ts @@ -4,11 +4,12 @@ import { AppForm } from '@app/shared/form.class'; import { SettingsService } from '@app/settings/settings.service'; import { environment } from '@environments/environment'; import { FormUtils } from '@app/shared/forms'; -import { isNil } from '@app/shared/functions'; +import { isNil, isNotNilOrBlank } from '@app/shared/functions'; import { setTimeout } from '@rx-angular/cdk/zone-less/browser'; import { AuthData } from '@app/account/auth/auth.model'; import { SharedValidators } from '@app/shared/form/form-validators'; import { APP_WOT_CONTROLLER, IWotController } from '@app/wot/wot.model'; +import { Clipboard } from '@capacitor/clipboard'; @Component({ selector: 'app-pubkey-form', @@ -92,6 +93,18 @@ export class PubkeyForm extends AppForm<AuthData> implements OnInit { return ''; } + async pasteFromClipboard() { + try { + const { value } = await Clipboard.read(); + + if (isNotNilOrBlank(value)) { + this.form.patchValue({ pubkey: value.trim() }); + } + } catch (err) { + console.error('Can not read clipboard', err); + } + } + /* -- protected functions -- */ async showWotModal(event: Event) { diff --git a/src/app/home/help/help.page.html b/src/app/home/help/help.page.html index 743f8d7db6724885af9379fc79c399489c5f423d..8f7bdedad868716d8deb3ce3f5ff2b398b34c15a 100644 --- a/src/app/home/help/help.page.html +++ b/src/app/home/help/help.page.html @@ -95,10 +95,10 @@ </ion-row> </ion-grid> </ion-item> - <ion-item lines="none"> - <ion-grid> + <ion-item id="login-method" lines="none"> + <ion-grid [ngClass]="{ highlight: isLoginMethodHelp() }"> <ion-row> - <ion-col size="4" size-lg="4" size-sm="12"> + <ion-col size="4" size-lg="4" size-sm="12" [ngClass]="{ 'text-bold': isLoginMethodHelp() }"> <ion-label translate>HELP.LOGIN.METHOD</ion-label> </ion-col> <ion-col size="8" size-lg="8" size-sm="12"> diff --git a/src/app/home/help/help.page.ts b/src/app/home/help/help.page.ts index aefbcc768988de93b30bbed21c2ed0fe7173e6d8..c41ace4358bd7d36fa631158a0f8a57eb9570e9b 100644 --- a/src/app/home/help/help.page.ts +++ b/src/app/home/help/help.page.ts @@ -12,7 +12,7 @@ import { isNotNilOrBlank } from '@app/shared/functions'; export interface HelpPageState extends AppPageState, Settings {} -export declare type HighlightedDefinition = 'GLOSSARY_DISTANCE_RULE' | 'DEMO_MODE_HELP' | 'READONLY_MODE_HELP' | 'UD' | ''; +export declare type HighlightedDefinition = 'GLOSSARY_DISTANCE_RULE' | 'DEMO_MODE_HELP' | 'READONLY_MODE_HELP' | 'UD' | 'login-method' | ''; @Component({ selector: 'app-help', @@ -64,6 +64,10 @@ export class HelpPage extends AppPage<HelpPageState> implements OnInit { return this.highlightedDefinition === 'UD'; } + isLoginMethodHelp() { + return this.highlightedDefinition === 'login-method'; + } + async scrollToDefinition(definitionId: string) { const targetElement = document.getElementById(definitionId); if (targetElement) {