Разработка модуля для NEM кошелька — Nano Wallet

0

Предисловие

NEM — социально-ориентированная криптовалюта второго поколения, с философией финансовой свободы, децентрализации и равенства возможностей.

NEM платформа предоставляет пользователям следующие виды кошельков для безопасного хранения токенов — XEM:

  • NCC Standalone (Nem Comunity Client) — автономный клиент. Рекомендуется использовать, если Вы желаете поднять «cуперноду» или полноценный узел. Для установки и запуска необходимы хотя бы минимальные навыки использования терминала;
  • Nano Wallet — легкий клиент. Не требует установки дополнительного программного обеспечения, скачивания блокчейна. Все запросы обслуживают удаленные узлы. Разработан при помощи JavaScript-фреймворка — AngularJS;
  • Android NEM Wallet — мобильный кошелек для системы Android.

NCC в своем функционале содержит очень нужный для кошелька модуль — «Адресная книга», который отсутствует в Nano Wallet.

Статья покажет вам, как создать модуль — «Адресная книга», содержащий в себе следующий функционал:

  • Создание контакта
  • Редактирование контакта
  • Удаление контакта
  • Сортировка списка контактов
  • Экспорт адресной книги
  • Импорт адресной книги

Введение

Первое, с чем сталкивается разработчик-новичок, никогда не занимавшийся написанием модулей для Nano Wallet, это вопрос — «С чего же начать?».

И так, для начала проверьте, установлена ли в вашей системе платформа Node.js, если нет, то скачиваем и устанавливаем.

Это вам понадобиться для сборки проекта.

Далее скачиваем официальный репозиторий кошелька.

Для сборки проекта, открываем терминал, переходим в корневую директорию проекта и запускаем следующие команды:

> npm install
> npm gulp

Теперь всё готово, и можно приступить непосредственно к разработке самого модуля.

Разработка модуля

Модули кошелька хранятся в следующей директории:

NanoWallet/
  src/
    app/
      modules/

Итак, назовем наш модуль без лишних хитростей — addressBook. Все модули кошелька имеют следующую структуру файлов:

modules/
  addressBook/
    addressBook.config.js
    addressBook.controller.js
    index.js
    addressBook.html

addressBook.config.js — прописываем конфигурацию модуля. Здесь необходимо указать:

  • url — url-адрес, по которому модуль будет доступен;
  • controller — название контролера, обслуживающего модуль;
  • controllerAs — псевдоним контролера;
  • templateUrl — путь к файлу, в котором содержится представление (html-код) модуля;
  • title — название модуля.

 

AddressBookConfig($stateProvider) {
    'ngInject';

    $stateProvider
        .state('app.addressBook', {
            url: '/address-book',
            controller: 'AddressBookCtrl', 
            controllerAs: '$ctrl',
            templateUrl: 'modules/addressBook/addressBook.html',
            title: 'Address book'
        });

};

export default AddressBookConfig;

addressBook.controller.js — здесь программируем логику нашего модуля. Контролер модуля «Адресная книга» будет содержать следующие основные свойства и методы:

  • contacts — список контактов, хранящихся в локальном хранилище браузера;
  • addContact(), add() — добавление в адресную книгу нового контакта;
  • editContact(), edit() — редактирование контакта;
  • removeContact(), remove() — удаление контакта;
  • sortBy() -сортировка списка контактов;
  • exportAddressBook() — экспорт экспорт й книги;
  • importAddressBook() — импорт адресной книги;

 

import helpers from '../../utils/helpers';
import CryptoHelpers from '../../utils/CryptoHelpers';

class AddressBookCtrl {
    constructor($localStorage, DataBridge, Wallet, Alert, $location, $filter, $q, $rootScope) {
        'ngInject';

        this.contacts = this._storage.contacts;
    }

    sortBy(propertyName) { // code };

    addContact() { // code }

    add() { // code }

    editContact(elem) { // code }

    edit() { // code }

    removeContact(elem) { // code }

    remove() { // code }

    saveAddressBook() { // code }

    exportAddressBook() { // code }

    uploadAddressBook() { // code }

    importAddressBook($fileContent) { // code }

    transferTransaction(address) { // code }
}

export default AddressBookCtrl;

index.js — в этом файле создаем экземпляр нашего модуля с заранее определенными конфигурацией и контролером.

import angular from 'angular';

// Create the module where our functionality can attach to
let addressBookModule = angular.module('app.addressBook', []);

// Include our UI-Router config settings
import AddressBookConfig from './addressBook.config';
addressBookModule.config(AddressBookConfig);

// Controllers
import AddressBookCtrl from './addressBook.controller';
addressBookModule.controller('AddressBookCtrl', AddressBookCtrl);

export default addressBookModule;

addressBook.html — в этом файле делаем html-разметку модуля.

Сначала сделаем разметку бокового меню:

<div class="col-sm-3 sidebar" style="margin-top: 10px;">
    <div class="panel panel-default">
        <div class="panel-heading" style="background-color: rgb(68, 68, 68); color: white;border-radius: 0px;">
          <i class="fa fa-chevron-right"></i>
          <span>{{ 'ADDRESS_BOOK_NAVIGATION' | translate }}</span>
        </div>
        <div class="panel-body">
            <ul class="nav nav-pills nav-stacked">
                <li>
                    <button type="button" class="btn btn-operations" ng-click="$ctrl.addContact()">{{ 'ADDRESS_BOOK_NEW' | translate }}</button>
                </li>
                <li>
                    <button type="button" ng-disabled="!$ctrl.contacts.items.length" class="btn btn-operations" ng-click="$ctrl.exportAddressBook()">{{ 'ADDRESS_BOOK_EXPORT_BTN' | translate }}</button>
                    <a id="exportAddressBook" class="hidden" target="_blank"></a>
                </li>
                <li>
                    <button type="button" class="btn btn-operations" ng-click="$ctrl.uploadAddressBook()">{{ 'ADDRESS_BOOK_IMPORT_BTN' | translate }}</button>
                    <input id="uploadAddressBook" accept=".adb" style="visibility:hidden;position:absolute;" import-address-book-file="$ctrl.importAddressBook($fileContent)" type="file">
                </li>
            </ul>
        </div>
    </div>
</div>

Далее добавим разметку для вывода списка контактов и кнопок управления контактами:

<div class="col-sm-9 noPaddingLeft" style="margin-top: 10px;">
    <div class="panel panel-default">
        <div class="panel-heading" style="background-color: rgb(68, 68, 68); color: white;border-radius: 0px;">
          <i class="fa fa-group"></i>  {{ 'ADDRESS_BOOK_LIST' | translate }}
          <div style="float: right; position: relative; display: block;" ng-show="$ctrl.contacts.items.length > $ctrl.pageSize"><button class="buttonStyle" ng-disabled="$ctrl.currentPage == 0" ng-click="$ctrl.currentPage = $ctrl.currentPage-1" style="background-color: transparent; border: medium none;"><span class="fa fa-chevron-left" aria-hidden="true"></span></button><b>{{$ctrl.currentPage+1}}/{{$ctrl.numberOfPages()}}</b><button class="buttonStyle" ng-disabled="$ctrl.currentPage+1 >= $ctrl.numberOfPages()" ng-click="$ctrl.currentPage = $ctrl.currentPage+1" style="background-color: transparent; border: medium none;"> <span class="fa fa-chevron-right" aria-hidden="true"></span></button></div>
        </div>
        <table  class="table table-bordered table-hover table-striped table-condensed" style="border:1px solid #444;table-layout:fixed;word-break:break-all;">
            <thead style="color:white;">
                <tr>
                    <th style="width: 20%;">
                        <a href="javascript:;" ng-click="$ctrl.sortBy('label')">
                            <i class="fa" ng-show="$ctrl.propertyName === 'label'" ng-class="{'fa-caret-down': $ctrl.revers, 'fa-caret-up': !$ctrl.revers}"></i>  {{ 'ADDRESS_BOOK_CONTACT_LABEL' | translate }}
                        </a>
                    </th>
                    <th>
                        <a href="javascript:;" ng-click="$ctrl.sortBy('address')">
                            <i class="fa" ng-show="$ctrl.propertyName === 'address'" ng-class="{'fa-caret-down': $ctrl.revers, 'fa-caret-up': !$ctrl.revers}"></i>  {{ 'ADDRESS_BOOK_ACCOUNT_ADDRESS' | translate }}
                        </a>
                    </th>
                    <th style="width: 200px;">{{ 'ADDRESS_BOOK_ACTIONS' | translate }}</th>
                </tr>
            </thead>
            <tbody style="background-color:white;text-align:center">
                <tr ng-repeat="contact in $ctrl.contacts.items | orderBy:$ctrl.propertyName:$ctrl.revers | startFrom:$ctrl.currentPage*$ctrl.pageSize | limitTo:$ctrl.pageSize">
                    <td style="vertical-align: middle;border-bottom: 1px solid #444">{{contact.label}}</td>
                    <td style="vertical-align: middle;border-bottom: 1px solid #444">{{contact.address}}</td>
                    <td class="actions" style="vertical-align: middle;border-bottom: 1px solid #444;white-space:nowrap;">
                        <button type="button" class="btn btn-xs" ng-click="$ctrl.transferTransaction(contact.address)">Send XEM</button>
                        <button type="button" class="btn btn-xs" ng-click="$ctrl.editContact(contact)">Edit</button>
                        <button type="button" class="btn btn-xs" ng-click="$ctrl.removeContact(contact)">Remove</button>
                    </td>
                </tr>
            </tbody>
        </table>
        <div class="panel-body" ng-show="!$ctrl.contacts.items.length" style="border: 1px solid #444;">
            <p style="margin:0;">{{ 'GENERAL_NO_RESULTS' | translate }}</p>
        </div>
        <div class="panel-footer text-center" style="background-color: #e3e0cf; color: #444;padding:0;">
          <small><b><i>{{ 'ADDRESS_BOOK_MAX_NUMBER' | translate }} {{ $ctrl.pageSize }}</i></b></small>
        </div>
    </div>
</div>

И добавим модальные окна, позволяющие добавлять/редактировать/удалять контакты:

<!-- Add new account modal -->
<div id="contactModal" class="modal fade" role="dialog">
    <div class="modal-dialog">
        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">×</button>
                <h4 class="modal-title" ng-show="!$ctrl.is_edit">{{ 'ADDRESS_BOOK_NEW' | translate }}</h4>
                <h4 class="modal-title" ng-show="$ctrl.is_edit">{{ 'ADDRESS_BOOK_EDIT' | translate }}</h4>
            </div>
            <div class="modal-body">
                <fieldset class="form-group">
                    <input class="form-control form-control-lg"
                           type="password"
                           placeholder="{{ 'FORM_PASSWORD_FIELD_PLACEHOLDER' | translate }}"
                           ng-model="$ctrl.common.password"/>
                </fieldset>
                <fieldset class="form-group">
                    <input class="form-control form-control-lg"
                           type="text"
                           placeholder="{{ 'ADDRESS_BOOK_CONTACT_LABEL' | translate }}"
                           ng-model="$ctrl.formData.label"/>
                </fieldset>

                <fieldset class="form-group">
                    <input class="form-control form-control-lg"
                           type="text"
                           placeholder="{{ 'FORM_ADDRESS_FIELD_PLACEHOLDER' | translate }}"
                           ng-model="$ctrl.formData.address"/>
                </fieldset>
                <button class="btn btn-success" style="border-radius:0px;"
                        type="submit" ng-show="!$ctrl.is_edit" ng-disabled="$ctrl.okPressed || !$ctrl.common.password.length || !$ctrl.formData.label || !$ctrl.formData.address" ng-click="$ctrl.add()">
                    <i class="fa fa-plus"></i> {{ 'ADDRESS_BOOK_NEW_BTN' | translate }}
                </button>
                <button class="btn btn-success" style="border-radius:0px;"
                        type="submit" ng-show="$ctrl.is_edit" ng-disabled="$ctrl.okPressed || !$ctrl.common.password.length || !$ctrl.formData.label || !$ctrl.formData.address" ng-click="$ctrl.edit()">
                    <i class="fa fa-edit"></i> {{ 'ADDRESS_BOOK_EDIT_BTN' | translate }}
                </button>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default"  data-dismiss="modal">{{ 'GENERAL_CLOSE' | translate }}</button>
            </div>
        </div>
    </div>
</div>

<!-- Add new account modal -->
<div id="removeContactModal" class="modal fade" role="dialog">
    <div class="modal-dialog">
        <!-- Modal content-->
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">×</button>
                <h4 class="modal-title">{{ 'ADDRESS_BOOK_REMOVE' | translate }}</h4>
            </div>
            <div class="modal-body">
                <fieldset class="form-group">
                    <input class="form-control form-control-lg"
                           type="password"
                           placeholder="{{ 'FORM_PASSWORD_FIELD_PLACEHOLDER' | translate }}"
                           ng-model="$ctrl.common.password"/>
                </fieldset>
                <button class="btn btn-danger" style="border-radius:0px;" type="submit" ng-disabled="$ctrl.okPressed || !$ctrl.common.password.length" ng-click="$ctrl.remove()">
                    <i class="fa fa-remove"></i> {{ 'ADDRESS_BOOK_REMOVE_BTN' | translate }}
                </button>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default"  data-dismiss="modal">{{ 'GENERAL_CLOSE' | translate }}</button>
            </div>
        </div>
    </div>
</div>

Нам осталось написать стили для модуля. Основной css код кошелька содержится в файле:

NanoWallet/
  src/
    css/
      nano.css

Стили для нашего модуля:

.address-book-page .panel-default .panel-heading {
	border-bottom: 3px solid #dfa82f;
}

.address-book-page .sidebar .nav li .btn-operations {
	width: 100%;
	border-top: 1px solid #444;
	border-bottom: 1px solid #444;
	border-radius: 0;
	margin-top: 7px;
	color: #444;
	background-color: #e3e0cf;
}

.address-book-page .sidebar .nav li:first-child .btn-operations { margin-top: 0; }
.address-book-page .sidebar .nav li .btn-operations:hover { background-color: #dad6bf; }
.address-book-page .sidebar .nav li .btn-operations:focus { outline: none; }

.address-book-page .sidebar .panel-body { padding: 10px 0; }

.address-book-page table th a { color: #FFF; }
.address-book-page table th a:hover,
.address-book-page table th a:focus,
.address-book-page table th a:active { color: #FFF; text-decoration: none; }
.address-book-page table .actions .btn { border: 1px solid rgba(153, 153, 153, 0.52); background: #E3E0CF; }

.address-book-page table .actions .btn:hover,
.address-book-page table .actions .btn:focus,
#addressBookModal table .btn:hover,
#addressBookModal table .btn:focus {
	background-color: #dad6bf !important;
	text-decoration: none;
}

#addressBookModal .modal-body { padding: 0; }
#addressBookModal .modal-footer { background: #444; }
#addressBookModal .modal-footer .btn {
	border-radius: 3px;
	background-color: #FFF;
	color: #333;
	border: none;
}

#addressBookModal .modal-footer .custom-pagination { color: #FFF; margin-top: 5px; }
#addressBookModal .modal-footer .custom-pagination button { opacity: 1; transition: .3s ease opacity; }
#addressBookModal .modal-footer .custom-pagination button[disabled="disabled"] { opacity: 0.3; }

#addressBookModal table th,
#addressBookModal table td { padding-left: 15px; padding-right: 15px; }
#addressBookModal table th:first-child,
#addressBookModal table td:first-child { border-right: 1px solid #DDDDDD; }
#addressBookModal table tr:first-child td { padding-top: 10px; }
#addressBookModal table tr:last-child td { padding-bottom: 15px; }
#addressBookModal table th { border-bottom: none; }

На этом разработка модуля окончена.

Теперь необходимо импортировать созданный модуль в приложение. Открываем следующий файл:

NanoWallet/
  src/
    app/
      app.js

Прописываем следующий код, чтобы импортировать наш модуль:

<// Import our app modules
import './modules/addressBook';

// Create and bootstrap application
const requires = [
    'app.addressBook'
];

Заключение

Вот собственно и получился готовый модуль.

Как мы увидели, разработка модулей для кошелька Nano Wallet не такое уж и сложное дело. Надеюсь, эта статья кому-нибудь поможет.

Удачи в разработке собственных модулей!

photo_2017-01-02_23-01-41

alexl2

alexl1

Share.