diff --git a/app/config.json b/app/config.json index 77ff020fee885256384d4df95f43e7398093fc76..82f75e0d7e530eef941e0d894ebdbbc9c3f78168 100644 --- a/app/config.json +++ b/app/config.json @@ -168,6 +168,9 @@ }, "neo4j": { "enable": true + }, + "rml9": { + "enable": true } } }, @@ -200,7 +203,7 @@ }, "plugins":{ "es": { - "enable": true, + "enable": false, "askEnable": false, "host": "localhost", "port": "9200", @@ -211,6 +214,9 @@ }, "neo4j": { "enable": true + }, + "rml9": { + "enable": true } } }, diff --git a/doc/fr/development_tutorial-04-add_plugin.md b/doc/fr/development_tutorial-04-add_plugin.md index 6928c3822203178a7674a4b1d8b3892bfadd410c..a1fb5c54650d239178cafee9639fb58b348b5b20 100644 --- a/doc/fr/development_tutorial-04-add_plugin.md +++ b/doc/fr/development_tutorial-04-add_plugin.md @@ -1,3 +1,5 @@ +ATTENTION : ne pas lire AVANT les RML9 (en cours de rédaction !) + ## Introduction Cet article est un tutoriel pour développer un plugin Cesium. @@ -18,38 +20,42 @@ __Objectif :__ Ce niveau a pour objectif d'activer un nouveau plugin, minimalist Passez sur la branche du code #rml9 : https://github.com/duniter/cesium/tree/rml9 -Une fois récupérer, vous devriez voir les nouveaux fichiers suivant : +Une fois la branche récupérée, vous devriez voir les _nouveaux_ fichiers suivant : -```pre +```java www \-- plugins |-- (...) // plugins existants \-- rml9 // le nouveau plugin |-- i18n // ici, les traductions |-- templates // ici, les fichiers HTML content les interfaces graphiques - | |-- buttons.html + | |-- button.html | \-- view.html - \-- plugin.js // le code du plugin (= le controlleur Angular JS, etc.) + |-- plugin.js // Code final du plugin (à garder pour la fin !) + |-- plugin-01-add_button.js // 1ère étape du tuto + \-- plugin-02-(...).js // etc. ``` -> Pour simplifier, nous avons ici regrouper tout le code dans un seul fichier (`plugin.js`). Les plugins existants, plus important, ont cependant un déoupage plus fin, en plusieurs fichiers. +> Pour simplifier, nous avons regroupé tout le code dans un seul fichier `plugin.js`. Dans les autres plugins, généralement, on a préfèré déoupé le code en plusieurs fichiers (controllers, services, etc). + +### Activation du plugin (en version `01`) -### Activation du plugin +Nous allons activer une 1ère version du plugin. -Editer le fichier `www/index.html`, et ajouter les **deux** lignes suiavntes : +Editer le fichier `www/index.html`, et ajouter la ligne suivante, vers la fin du fichier : ```html - (...) - <!-- dans la partie CSS, vers le haut du fichier --> - <link href="dist/dist_css/plugins/graph/css/style.css" rel="stylesheet"> - (...) - - <!-- dans la partie JS, vers le bas du fichier --> - <script src="dist/dist_js/plugins/rml9/plugin.js"></script> - (...) + <!--removeIf(no-plugin)--> + (...) + + <!-- Ajout du fichier JS du plugin (à mettre vers le bas de la section <head>) --> + <script src="dist/dist_js/plugins/rml9/plugin-01-add_button.js"></script> + + (...) + <!--endRemoveIf(no-plugin)--> ``` -Editer le fichier `www/js/plugins.js`, et ajouter la ligne suivante : -```js +Editer le fichier `www/js/plugins.js`, et ajoutez une entrée dans la liste, comme indiqué ci-dessous : +``` (...) // Plugins 'cesium.graph.plugin', // Ligne déjà existante, mais n'oubliez pas la virgule à la fin ! ;) @@ -57,9 +63,9 @@ Editer le fichier `www/js/plugins.js`, et ajouter la ligne suivante : (...) ``` -Editer le fichier `www/js/config.js`, et ajouter les lignes : +Editer le fichier `www/js/config.js`, et ajouter les lignes suivantes dans sous la balise `plugins` : -```js +```JSON // (...) "plugins": { // (...) @@ -67,7 +73,6 @@ Editer le fichier `www/js/config.js`, et ajouter les lignes : "rml9": { "enable": true } - // (...) }, ``` @@ -80,16 +85,19 @@ cd cesium ionic serve ``` -Ouvrez Cesium dans un navigateur, à l'adresse [localhost:8100](http://localhost:8100) + - Ouvrez un navigateur à l'adresse [http://localhost:8100](http://localhost:8100) + - Dans le menu de gauche, cliquez sur `Annuaire` + - puis choisissez un compte au hasard. + - Vous devriez voir un nouveau bouton, de couleur verte : +<img src="https://forum.duniter.org/uploads/default/original/2X/4/4e28c8487f380ac6229a735e01ac206d13fd9a21.png" width="690" height="225"> -Allez dans `Annuaire`, puis choisissez une personne. Vous devriez voir un nouveau bouton, vert : - -<img src="/uploads/default/original/2X/4/4e28c8487f380ac6229a735e01ac206d13fd9a21.png" width="690" height="225"> - -Le nouveau plugin est opérationnel ! + - Ouvrez la console Javascript de votre navigateur; + - Si vous cliquez sur le bouton vert, vous devriez voir le log suivant dans la console : +<img src="https://forum.duniter.org/uploads/default/original/2X/d/db5575c48fe0cd136a8949e5336e26ced95e08ec.png" width="676" height="77"> -> Pour le moment, ce plugin ne fait rien d'utile, mais nous allons pouvoir l'enrichir tranquillement. ;) +Bravo ! Le nouveau plugin est opérationnel ! +> Pour le moment, ce plugin ne fait rien d'utile, mais nous allons pouvoir l'enrichir tranquillement : patience ! ;) ## Niveau XIII : Etendre l'interface graphique @@ -97,43 +105,46 @@ __Objectif :__ Grâce à ce niveau, vous allez savoir étendre un écran existan ### Se repérer dans le code -Ouvrez le fichier `www/plugins/rml9/plugins.js`. Dans le partie, haute, identifier la partie qui permet la définition du point d'insertion du plugin `rml9` : +Ouvrez le fichier `www/plugins/rml9/plugin-01-add_button.js`, qui contient le code du plugin. + +En haut du fichier, identifiez la partie qui définit le point d'insertion du plugin, dans l'écran : -```js +```javascript var enable = csConfig.plugins && csConfig.plugins.rml9; if (enable) { - // Extension de la vue d'une identité + // Extension de la vue d'une identité: ajout d'un bouton PluginServiceProvider .extendState('app.wot_identity', { points: { 'buttons': { - templateUrl: "plugins/rml9/templates/buttons.html", + templateUrl: "plugins/rml9/templates/button.html", controller: 'Rml9ButtonsCtrl' } } }); - - // ... } ``` -Vous y voyez qu'une page (appelée `state` dans AngularJS) est étendue : la page d'une identité, dont state est `app.wot_identity` +Vous y voyez qu'une page (ou `state` dans AngularJS) est étendue : celle d'une identité dont `state` vaut `app.wot_identity` -> Le nom de cette page (`state`) provient du code de Cesium du fichier `www/js/controllers/wot-controllers.js`. +> L'identifant de cette page (son `state`) provient du code existant de Cesium, dans le fichier `www/js/controllers/wot-controllers.js` . Vous pouvez l'ouvrir pour mieux comprendre... -Dans le code du plugin, notons aussi qu'un point d'extension utilisé est nommé. Il s'agit de l'emplacement où notre code sera injecté dans Cesium. Ici, le point d'extension est nommé `buttons` : -```js +Dans le code de notre plugin, notons aussi qu'un point d'extension est nommé. Il s'agit de l'emplacement où notre code sera injecté dans Cesium. Ici, le point d'extension est nommé `buttons` : +``` points: { 'buttons': { // <- nom du point d'extension, où va s'insérer notre plugin // ... ``` -Ce point d'extension a été défini dans le template HTML de la page affichant une identité : le fichier `www/templates/wot/view_identity.html`. Le formalisme utilisé pour le définir est très simple : +Ce point d'extension a été défini dans le template HTML de la page affichant une identité. +Ouvrez le fichier `www/templates/wot/view_identity.html` : + ```html <!-- Définition d'un point d'extension nommé "buttons", dans le template de la page à étendre --> <cs-extension-point name="buttons"></cs-extension-point> ``` +Vous voyez que le formalisme utilisé pour le définir l'emplacement d'un point d'extension est très simple. Ouvrez maintenant le fichier `www/plugins/rml9/templates/buttons.html` : @@ -145,20 +156,22 @@ Ouvrez maintenant le fichier `www/plugins/rml9/templates/buttons.html` : </button> ``` -Ce fichier contient le contenu visuel de notre plugin. Ici, simplement le bouton ajouté à l'écran par notre plugin... +Ce fichier contient le **contenu visuel de notre plugin**. +Ici, il s'agit simplement d'un bouton, avec l'appel d'une fonction à chaque clic. + Simple comme un `Hello world`, non ? ;) -### Ajout du bouton dans une autre page +### Ajouter du bouton dans une autre page -Vous allez maintenant ajouter le bouton de notre plugin (ce même bouton qui ne fait rien !) dans une autre page bien connue : `Mes opérations`. +Vous allez maintenant pouvoir ajouter le bouton (le même, celui de notre plugin) dans une autre page : celle accessible par le memnu de gauche : `Mes opérations`. -Editer le fichier `www/plugins/rml9/plugins.js`. Puis, sous la première extension définie, ajouter les lignes suivantes : +Editer le fichier `www/plugins/rml9/plugin-01-add_button.js`. Puis, sous la première extension définie, ajouter les lignes suivantes : -```js +``` - // Extension de 'Mes opérations' + // Extension de la page 'Mes opérations': insertion du même bouton PluginServiceProvider .extendState('app.view_wallet_tx', { points: { @@ -174,32 +187,32 @@ Editer le fichier `www/plugins/rml9/plugins.js`. Puis, sous la première extens ``` > La page `Mes opérations` a été étendue à partir du nom de son état (`state`) `app.view_wallet_tx`. -> Cet état est visible en ouvrant le fichier `www/js/controllers/wallet-controllers.js` (dans la partie haute du fichier). +> Cet état est visible en ouvrant le fichier `www/js/controllers/wallet-controllers.js` (dans la partie haute du fichier). Ouvrez le si besoin, pour mieux comprendre. -> __Attention :__ Bien que le nom du point d'extension soit encore `buttons`, il s'agit _d'un autre point d'extension_, distinct, et défini cette fois dans un template : `www/templates/wallet/view_wallet_tx.html` +> __Attention :__ Bien que le nom du point d'extension soit encore nommé `buttons`, il s'agit _d'un autre point d'extension_, distinct du premier, et défini dans un autre template : le fichier `www/templates/wallet/view_wallet_tx.html` Vérifier maintenant que le résultat est celui attendu : - - Ouvrez Cesium, puis connectez-vous (à n'importe quel compte) - - Dans la page `Mes opérations`; notre même bouton vert vient d'apparaitre ! -<img src="/uploads/default/original/2X/e/e02307b86cf43dc7c6dcc4f8a77b797e50b45d82.png" width="518" height="196"> + - Ouvrez Cesium, puis connectez-vous (à n'importe quel compte) + - Dans la page `Mes opérations`; notre même bouton vert vient d'apparaitre : +<img src="https://forum.duniter.org/uploads/default/original/2X/e/e02307b86cf43dc7c6dcc4f8a77b797e50b45d82.png" width="518" height="196"> -### Etendre, n'importe où ! +### Etendre, n'importe où ! Récapitulons... Maintenant que vous comprenez comment étendre Cesium, vous comprenez aussi qu'on peut étendre n'importe quelle partie de l'interface de Cesium ! La méthologie est toujours la même : - Chercher dans les `templates` HTML, la page (ou le composant) que vous voulez étendre; - - S'il n'y en a pas déjà , à l'emplacement qui vous inéteresse, ajouter dans ce template, à l'emplacement précis de votre choix, un nouveau point d'extension. Vous devrez lui choisir un nom. Par exemple : + - S'il n'y en a pas déjà , à l'emplacement qui vous inéteresse, ajouter dans ce template (à l'emplacement précis de votre choix) le nouveau point d'extension. Vous devrez lui choisir un nom, unique dans la page. Par exemple : ```html <cs-extension-point name="this-is-a-good-extension-place"></cs-extension-point> ``` - - Dans les controlleurs de Cesium, chercher le nom de la page (state) concernée; + - Dans le code des controlleurs (fichiers du répertoire `www/js/controllers`) recherchez le nom de la page (state) concernée; - Dans le code du plugin, étendre le point d'extension de la manière suivante : -```js +```javascript PluginServiceProvider - .extendState('app.a_state_name', { // ici, le nom de la page (=state), à identifier dans les controlleurs + .extendState('app.a_state_name', { // ici, le nom de la page (state), à identifier dans les controlleurs points: { 'this-is-a-good-extension-place': { // ici, le nom du point d'extension concerné templateUrl: "plugins/rml9/templates/buttons.html", @@ -209,39 +222,248 @@ La méthologie est toujours la même : }); ``` +##Niveau XIV : Ajouter une page +__Objectif :__ Ce niveau a pour objectif de vous apprendre à ajouter une nouvelle page dans Cesium. il s'agira ensuite utilise run service d'accès aux données pour afficher les transactions d'un compte. -Editer le fichier `www/plugins/rml9/templates/buttons.html` +### Activation du plugin (en version `02`) +Editez le fichier `www/index.html` pour activer cette fois le plugin en version 2 : + ``` + <script src="dist/dist_js/plugins/rml9/plugin-02-add_view.js"></script> + ``` -Dans le fichier `www/plugins/rml9/plugins.js`, identifier la méthode `onButtonClick`. +Dans votre navigateur, vérifiez que le bouton est toujours présent (page d'une identité ou `Mes opérations`). +Cliquez sur le bouton. Une nouvelle page s'ouvre alors : +<img src="https://forum.duniter.org/uploads/default/original/2X/5/5a8b4eb0c09d8125b1d6f92551256377a700e128.png" width="690" height="299"> -## Niveau XIII : Développer un export de fichier +### Se repérer dans le code -__Objectif :__ Ce niveau a pour objectif de développer un fonctionnalité d'export des comptes, en fichier. +Ouvrez le code du plugin. +En haut u fichier, repérez le code suivant : -### Se repérer dans le code +``` + // [NEW] Ajout d'une nouvelle page #/app/rml9 + $stateProvider + .state('app.rml9', { + url: "/rml9/:pubkey", + views: { + 'menuContent': { + templateUrl: "plugins/rml9/templates/view.html", + controller: 'Rml9ViewCtrl' + } + } + }); +``` + +Cette déclaration ajoute une nouvelle page (state) `app.rml9`, utilisant le template HTML `view.html` et un nouveau controlleur associé. -Dans le fichier `www/plugins/rml9/plugins.js`, identifier la méthode `onButtonClick`. +Le code du controlleur de cette page est sité un peu plus bas : +``` + // Manage events from the page #/app/rml9 + .controller('Rml9ViewCtrl', function($scope) { + 'ngInject'; + + // When opening the view + $scope.$on('$ionicView.enter', function(e, state) { + console.log("[RML9] Opening the view..."); + + // Get the pubkey (from URL params) and store it in the page context ($scope) + $scope.pubkey = (state && state.stateParams && state.stateParams.pubkey); + if (!$scope.pubkey) return; + + // Create some data to display + $scope.items = [ + {amount: 64, time: 1493391431, pubkey:'2RFPQGxYraKTFKKBXgp...'}, + {amount: -500, time: 1493373164, pubkey:'2RFPQGxYraKTFKKBXgp...'}, + {amount: 100, time: 1493363131, pubkey:'5U2xuAUEPFeUQ4zpns6...'} + ]; + }); + }); +``` +> Le reste du code provient du plugin réalisé lors du niveau précédent. +> Le nouveau code est identifié par des commentaire précédé de la balise "`[NEW]`" -## Niveau XIII +Ouvrez maintenant le fichier `www/plugins/rml9/templates/view.html` qui contient le template HTML. +Observez notamment l'affichage des données que nous avons stockées dans la variable `$scope.items` : -__Objectif :__ L'objectif est d'enrichir le plugin, en y ajoutant un nouvel écran (ou `view`). -Cette écran permettra l'affichage de transactions d'un compte (quelconque, et pas seulement le votre). +```html + <!-- Iterate on each TX --> + <div class="item" ng-repeat="item in items"> + <h3>{{item.time|formatDate}}</h3> + <h4>{{item.pubkey|formatPubkey}}</h4> -### Ajout d'une vue (`view`) + <div class="badge">{{item.amount|formatAmount}}</div> + </div> +``` +> L'attribut `ng-repeat` (directive native d'Angular JS) permet de boucler simplement sur chaque élément d'une collection. +### Utiliser un service d'accès aux données -### Ajout d'un bouton d'accès +Nous allons maintenant remplacer les données "en dures" dans le code, par l'appel à un "service" existant de Cesium. +> Les "services" d'AngularJS sont des objets indépendant des interfaces, et donc réutiliables entre plusieurs écrans ou compasant graphiques. Typiquement, dans Cesium, ils executent les requêtes HTTP vers le noeud Duniter (le noeud configuré dans vos paramètres Cesium). -## La suite ? +Dans le fichier `plugin-02-add_view.js`, remplacez l'initialisation du tableau `$scope.items` par cet appel au service `csTx` : -Vous pouvez maintenant poursuivre avec les niveaux qui suivent. Nous y verrons comment **ajouter un graphique**. +``` + // Load account TX data + csTx.load($scope.pubkey) + .then(function(result) { + console.log(result); // Allow to discover data structure + if (result && result.tx && result.tx.history) { + $scope.items = result.tx.history; + } + }); +``` +> Le `console.log()` est un moyen simple pour découvrir la structure des données renvoyées par le service. +> Un point d'arrêt dans la fonction aura le même effet. + +Notez bien le formalisme de traitement du retour de la méthode, propre aux méthodes asynchrones : +``` + monService.maMethode() + .then(function(result) { + // ici, traitement du résultat + }); +``` -[Voir la suite ici >>](./development_tutorial-05-add_chart.md) +> Pour ne pas pénaliser les performances de la navigation, les services utilisent le plus souvent une execution _asynchrone_. Il est donc indespensable de bien maitriser l'usage de telles méthodes. + +#### Déclaration du service utilisé + +A ce stade, si vous testez votre code. +Vous devriez avoir une erreur dans la console Javascript : + +<img src="https://forum.duniter.org/uploads/default/original/2X/a/a0d809e20b59af5786ce486dbf8c4f2ad2a0c4c8.png" width="690" height="87"> + +pas de panique : ce type d'erreur est fréquent ! il indique simplement que le service utilisé, `csTx`, n'a pas été déclaré comme dépendence du controlleur de la page. +Pour corriger l'erreur, ajouté le simplement dans la fonction du controlleur : +``` + .controller('Rml9ViewCtrl', function($scope, csTx /*ICI, ajouter la décalration du service*/) { + 'ngInject'; + // ... +``` +> C'est la chaine de caratère `ngInject` qui permet de gérer automatiquement l'ajout de dépendances dans AngularJS, par injection à partir du nom. Il est donc indispensable que les variables portent ici le même nom que les services définis. + +Vous pouvez maintenant tester ! +<img src="https://forum.duniter.org/uploads/default/original/2X/7/7c9416bf1ee97605d1859ef0b5cc9af075555e64.png" width="690" height="470"> + +#### Amélioration de la page + +Cette nouvelle page est correspond vraiment à une demande : celle de Galuel de pouvoir consultr un compte sans se connecter. + +A vous d'imaginer comme réaliser cette fonctionnalités! + +Voici simplement quelques pistes d'améliorations : + +- Calculer et afficher la balance du compte +- Ajouter le pseudo (UID) de l'utilisateur, acessible par `item.uid` +- Afficher son nom de profile et son avatar Cesium+ (`item.name` et `item.avatar`) + +> Attention: dans cette dernière proposition, vous devrez penser que le plugin Cesium+ peut être désactiver, et prévoir un affichage correct (dégradé) le cas échéant ;) + +#### Quels services utiliser ? + +Lors du développement d'un plugin Cesium, vous devrez savoir quel service utiliser. Rassurez vous, ils sont tous regrouper dans le répertoire `www/js/services`. + +Voici les plus importants : + +```java + \-- www/js/services + |-- bma-services.js // accès complet à l'API BMA du noeud (résultats brutes) + |-- crypto-services.js // fonctions de crypotographie + |-- currency-services.js // nom et paramètres de la monnaie, + |-- device-services.js // accès au capteurs (appareil photo, etc) - pour les téléphones/tablettes + |-- modal-services.js // utilitaires pour gérer les fenêtres modales simplement + |-- network-services.js // accès aux peers - utilisé par la page Réseau + |-- settings-services.js // accès aux paramètres de l'utilisateur + |-- tx-services.js // accès à l'historique des transaction et aux sources d'un compte + |-- utils-services.js // fonctions utiliaraires, comme les popup d'erreur ou de confirmation + |-- wallet-services.js // Le portefeuille de l'utilisateur connecté (envoi de paiement, etc.) + \-- wot-services.js // accès aux données de la WoT - utilisé par la page 'Annuaire' et sous-pages +``` + +## Niveau XV : Développer un export fichier d'un compte + +__Objectif :__ Ce niveau a pour objectif de développer un fonctionnalité d'export des transactions d'un comptes, dans un fichier. + +### Activation du plugin (en version `03`) + +Editez le fichier `www/index.html` pour activer cette fois le plugin en version 3 : + ``` + <script src="dist/dist_js/plugins/rml9/plugin-03-file_export.js"></script> + ``` + +Notre page RML9 a maintenant un bouton de téléchargement : +<img src="https://forum.duniter.org/uploads/default/original/2X/3/32f330d76431c16c56d865cb4629558c13c16410.png" width="690" height="384"> + +Si vous cliquez sur le bouton, un fichier (au contenu presque vide) est téléchargé. + +### Utilisation d'un plugin AngularJS + +Cette fois-ci, nous allons utiliser un plugin AngularJS. +La communauté AngularJS est très active : de nombreux plugins, de tous genres, sont disponibles ! + +Généralement, installer un nouveau plugin AngularJS est très simple. Il suffit d'executer la commande suivante pour que le téléchargement de la librairie soit fait : + +```bower install <nom_du_plugin_AngularJS>```` + +Le chemin de la librairie installée doit ensuite être ajouté à la main, dans le fichier `www/index.html` : +```html +<script src="lib/ionic/js/angular/angular-file-saver.bundle.js"></script> +``` + +### 5 min de coding ! + +Allez, une petite fonction facile à coder : le remplissage du fichier ! ;) + +Editez maintenant le code du plugin (version `03`), et identifiez la méthode `onExportButtonClick()` : + +``` + // [NEW] Manage click on the export button + $scope.onExportButtonClick = function() { + console.debug("[RML9] call method onExportButtonClick() on pubkey: " + $scope.pubkey); + + // [NEW] Load account TX data + var fromTime = -1; // all TX (full history) + csTx.load($scope.pubkey, fromTime) + .then(function(result) { + if (!result || !result.tx || !result.tx.history) return; // no TX: stop here + + // TODO: replace this ! + // You can choose any format (CSV, TXT, JSON, ...) and test it ! + var content = [ + "Hello Libre World !\n", + "Cesium rock's !\n" + ]; + + var file = new Blob(content, {type: 'text/plain; charset=utf-8'}); + var filename = $scope.pubkey+'-history.txt'; + FileSaver.saveAs(file, filename); + }); + + }; + +``` + + +## Niveau XVI : + +__Objectif :__ + +### Activation du plugin (en version `04`) + + + + + + +## La suite ? + +Vous pouvez maintenant poursuivre avec les niveaux qui suivent. Nous y verrons comment **ajouter un graphique**. +[Voir la suite ici >>](./development_tutorial-05-add_chart.md) \ No newline at end of file diff --git a/www/index.html b/www/index.html index e3a2e99adf110124eb14b0fa31bd6f0f76e87a44..2b8e5985f91db12a79b0464326ec3709eab9dbec 100644 --- a/www/index.html +++ b/www/index.html @@ -84,8 +84,9 @@ <script src="dist/dist_js/app/services/device-services.js"></script> <script src="dist/dist_js/app/services/currency-services.js"></script> <script src="dist/dist_js/app/services/bma-services.js"></script> - <script src="dist/dist_js/app/services/wallet-services.js"></script> <script src="dist/dist_js/app/services/wot-services.js"></script> + <script src="dist/dist_js/app/services/tx-services.js"></script> + <script src="dist/dist_js/app/services/wallet-services.js"></script> <script src="dist/dist_js/app/services/plugin-services.js"></script> <script src="dist/dist_js/app/services.js"></script> @@ -168,7 +169,10 @@ <script src="dist/dist_js/plugins/graph/js/controllers/account-controllers.js"></script> <!-- RML9 plugin --> - <script src="dist/dist_js/plugins/rml9/plugin.js"></script> + <!--<script src="dist/dist_js/plugins/rml9/plugin.js"></script>--> + <!--<script src="dist/dist_js/plugins/rml9/plugin-01-add_button.js"></script>--> + <!--<script src="dist/dist_js/plugins/rml9/plugin-02-add_view.js"></script>--> + <script src="dist/dist_js/plugins/rml9/plugin-03-export_to_file.js"></script> <!--endRemoveIf(no-plugin)--> diff --git a/www/js/services.js b/www/js/services.js index fa593fda3bce0fa8d9255ba2ddd58e52a644ec1a..56c84de8cb4e2845a0e942409744019ce73d04c9 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -1,17 +1,18 @@ angular.module('cesium.services', [ - 'cesium.config', - 'cesium.settings.services', - 'cesium.http.services', - 'cesium.network.services', - 'cesium.bma.services', - 'cesium.crypto.services', - 'cesium.utils.services', - 'cesium.modal.services', - 'cesium.storage.services', - 'cesium.device.services', - 'cesium.currency.services', - 'cesium.wallet.services', - 'cesium.wot.services', - 'cesium.plugin.services' - ]) + 'cesium.config', + 'cesium.settings.services', + 'cesium.http.services', + 'cesium.network.services', + 'cesium.bma.services', + 'cesium.crypto.services', + 'cesium.utils.services', + 'cesium.modal.services', + 'cesium.storage.services', + 'cesium.device.services', + 'cesium.currency.services', + 'cesium.wot.services', + 'cesium.tx.services', + 'cesium.wallet.services', + 'cesium.plugin.services' + ]) ; diff --git a/www/js/services/tx-services.js b/www/js/services/tx-services.js new file mode 100644 index 0000000000000000000000000000000000000000..590caf27a09589e6bd8c1f830fae7c867419918f --- /dev/null +++ b/www/js/services/tx-services.js @@ -0,0 +1,354 @@ + +angular.module('cesium.tx.services', ['ngResource', 'ngApi', 'cesium.bma.services', + 'cesium.settings.services', 'cesium.wot.services' ]) + +.factory('csTx', function($q, $timeout, BMA, Api, csConfig, csSettings, csWot) { + 'ngInject'; + + function factory(id, BMA) { + + var + api = new Api(this, "csTx-" + id), + + _reduceTxAndPush = function(pubkey, txArray, result, processedTxMap, allowPendings) { + if (!txArray || txArray.length === 0) { + return; + } + + _.forEach(txArray, function(tx) { + if (tx.block_number || allowPendings) { + var walletIsIssuer = false; + var otherIssuer = tx.issuers.reduce(function(issuer, res) { + walletIsIssuer = (res === pubkey) ? true : walletIsIssuer; + return issuer + ((res !== pubkey) ? ', ' + res : ''); + }, ''); + if (otherIssuer.length > 0) { + otherIssuer = otherIssuer.substring(2); + } + var otherReceiver; + var outputBase; + var sources = []; + var lockedOutputs; + var amount = tx.outputs.reduce(function(sum, output, noffset) { + var outputArray = output.split(':',3); + outputBase = parseInt(outputArray[1]); + var outputAmount = powBase(parseInt(outputArray[0]), outputBase); + var outputCondition = outputArray[2]; + var sigMatches = BMA.regexp.TX_OUTPUT_SIG.exec(outputCondition); + + // Simple unlock condition + if (sigMatches) { + var outputPubkey = sigMatches[1]; + if (outputPubkey == pubkey) { // output is for the wallet + if (!walletIsIssuer) { + return sum + outputAmount; + } + // If pending: use output as new sources + else if (tx.block_number === null) { + sources.push({ + amount: parseInt(outputArray[0]), + base: outputBase, + type: 'T', + identifier: tx.hash, + noffset: noffset, + consumed: false + }); + } + } + else { // output is for someone else + if (outputPubkey !== '' && outputPubkey != otherIssuer) { + otherReceiver = outputPubkey; + } + if (walletIsIssuer) { + return sum - outputAmount; + } + } + } + + // Complex unlock condition, on the issuer pubkey + else if (outputCondition.indexOf('SIG('+pubkey+')') != -1) { + var lockedOutput = BMA.tx.parseUnlockCondition(outputCondition); + if (lockedOutput) { + // Add a source + // FIXME: should be uncomment when filtering source on transfer() + /*sources.push(angular.merge({ + amount: parseInt(outputArray[0]), + base: outputBase, + type: 'T', + identifier: tx.hash, + noffset: noffset, + consumed: false + }, lockedOutput)); + */ + lockedOutput.amount = outputAmount; + lockedOutputs = lockedOutputs || []; + lockedOutputs.push(lockedOutput); + console.debug('[BMA] [TX] has locked output:', lockedOutput); + + return sum + outputAmount; + } + } + return sum; + }, 0); + + var txPubkey = amount > 0 ? otherIssuer : otherReceiver; + var time = tx.time || tx.blockstampTime; + + // Avoid duplicated tx, or tx to him self + var txKey = amount + ':' + tx.hash + ':' + time; + if (!processedTxMap[txKey] && amount !== 0) { + processedTxMap[txKey] = true; + var newTx = { + time: time, + amount: amount, + pubkey: txPubkey, + comment: tx.comment, + isUD: false, + hash: tx.hash, + locktime: tx.locktime, + block_number: tx.block_number + }; + // If pending: store sources and inputs for a later use - see method processTransactionsAndSources() + if (walletIsIssuer && tx.block_number === null) { + newTx.inputs = tx.inputs; + newTx.sources = sources; + } + if (lockedOutputs) { + newTx.lockedOutputs = lockedOutputs; + } + result.push(newTx); + } + } + }); + }, + + loadTx = function(pubkey, fromTime, existingPendings) { + return $q(function(resolve, reject) { + var txHistory = []; + var udHistory = []; + var txPendings = []; + + var nowInSec = Math.trunc(new Date().getTime() / 1000); // TODO test to replace using moment().utc().unix() + fromTime = fromTime || (nowInSec - csSettings.data.walletHistoryTimeSecond); + var processedTxMap = {}; + var tx = { + pendings: [] + }; + + var _reduceTx = function(res){ + _reduceTxAndPush(pubkey, res.history.sent, txHistory, processedTxMap); + _reduceTxAndPush(pubkey, res.history.received, txHistory, processedTxMap); + _reduceTxAndPush(pubkey, res.history.sending, txHistory, processedTxMap); + _reduceTxAndPush(pubkey, res.history.pending, txPendings, processedTxMap, true /*allow pendings*/); + }; + + var jobs = [ + // get pendings history + BMA.tx.history.pending({pubkey: pubkey}) + .then(_reduceTx) + ]; + + // get TX history since + if (fromTime !== -1) { + var sliceTime = csSettings.data.walletHistorySliceSecond; + for(var i = fromTime - (fromTime % sliceTime); i - sliceTime < nowInSec; i += sliceTime) { + jobs.push(BMA.tx.history.times({pubkey: pubkey, from: i, to: i+sliceTime-1}) + .then(_reduceTx) + ); + } + + jobs.push(BMA.tx.history.timesNoCache({pubkey: pubkey, from: nowInSec - (nowInSec % sliceTime), to: nowInSec+999999999}) + .then(_reduceTx)); + } + + // get all TX + else { + jobs.push(BMA.tx.history.all({pubkey: pubkey}) + .then(_reduceTx) + ); + } + + // get UD history + if (csSettings.data.showUDHistory) { + jobs.push( + BMA.ud.history({pubkey: pubkey}) + .then(function(res){ + udHistory = !res.history || !res.history.history ? [] : + res.history.history.reduce(function(res, ud){ + if (ud.time < fromTime) return res; // skip to old UD + var amount = powBase(ud.amount, ud.base); + return res.concat({ + time: ud.time, + amount: amount, + isUD: true, + block_number: ud.block_number + }); + }, []); + })); + } + + // Execute jobs + $q.all(jobs) + .then(function(){ + // sort by time desc + tx.history = txHistory.concat(udHistory).sort(function(tx1, tx2) { + return (tx2.time - tx1.time); + }); + tx.pendings = txPendings; + + tx.fromTime = fromTime; + tx.toTime = tx.history.length ? tx.history[0].time /*=max(tx.time)*/: fromTime; + + + resolve(tx); + }) + .catch(function(err) { + tx.history = []; + tx.pendings = []; + tx.errors = []; + delete tx.fromTime; + delete tx.toTime; + reject(err); + }); + }); + }, + + powBase = function(amount, base) { + return base <= 0 ? amount : amount * Math.pow(10, base); + }, + + addSource = function(src, sources, sourcesIndexByKey) { + var srcKey = src.type+':'+src.identifier+':'+src.noffset; + if (angular.isUndefined(sourcesIndexByKey[srcKey])) { + sources.push(src); + sourcesIndexByKey[srcKey] = sources.length - 1; + } + }, + + addSources = function(result, sources) { + _(sources).forEach(function(src) { + addSource(src, result.sources, result.sourcesIndexByKey); + }); + }, + + loadSourcesAndBalance = function(pubkey) { + return BMA.tx.sources({pubkey: pubkey}) + .then(function(res){ + var result = { + sources: [], + sourcesIndexByKey: [], + balance: 0 + }; + if (res.sources && res.sources.length) { + _.forEach(res.sources, function(src) { + src.consumed = false; + result.balance += powBase(src.amount, src.base); + }); + addSources(result, res.sources); + } + return result; + }); + }, + + loadData = function(pubkey, fromTime) { + var now = new Date().getTime(); + + var data = {}; + return $q.all([ + // Load Sources + loadSourcesAndBalance(pubkey), + + // Load Tx + loadTx(pubkey, fromTime) + ]) + + .then(function(res) { + angular.merge(data, res[0]); + data.tx = res[1]; + + var txPendings = []; + var txErrors = []; + var balance = data.balance; + + function _processPendingTx(tx) { + var consumedSources = []; + var valid = true; + if (tx.amount > 0) { // do not check sources from received TX + valid = false; + // TODO get sources from the issuer ? + } + else { + _.forEach(tx.inputs, function(input) { + var inputKey = input.split(':').slice(2).join(':'); + var srcIndex = data.sourcesIndexByKey[inputKey]; + if (angular.isDefined(srcIndex)) { + consumedSources.push(data.sources[srcIndex]); + } + else { + valid = false; + return false; // break + } + }); + if (tx.sources) { // add source output + addSources(data, tx.sources); + } + delete tx.sources; + delete tx.inputs; + } + if (valid) { + balance += tx.amount; // update balance + txPendings.push(tx); + _.forEach(consumedSources, function(src) { + src.consumed=true; + }); + } + else { + txErrors.push(tx); + } + } + + var txs = data.tx.pendings; + var retry = true; + while(txs && txs.length > 0) { + // process TX pendings + _.forEach(txs, _processPendingTx); + + // Retry once (TX could be chained and processed in a wrong order) + if (txErrors.length > 0 && txPendings.length > 0 && retry) { + txs = txErrors; + txErrors = []; + retry = false; + } + else { + txs = null; + } + } + + data.tx.pendings = txPendings; + data.tx.errors = txErrors; + data.balance = balance; + + // Will add uid (+ plugin will add name, avatar, etc. if enable) + return csWot.extendAll((data.tx.history || []).concat(data.tx.pendings||[]), 'pubkey'); + }) + .then(function() { + console.debug('[tx] TX and sources loaded in '+ (new Date().getTime()-now) +'ms'); + return data; + }); + } + + ; + + return { + id: id, + load: loadData, + // api extension + api: api + }; + } + + var service = factory('default', BMA); + + service.instance = factory; + return service; +}); diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js index 1af89938fb991061e717160e4dc44829b5d308cb..f85e43b8fd266089a4f32db8c29eb86e1914adb9 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -4,10 +4,10 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser .factory('csWallet', function($q, $rootScope, $timeout, $translate, $filter, Api, localStorage, - CryptoUtils, BMA, csConfig, csSettings, FileSaver, Blob, csWot, csCurrency) { + CryptoUtils, BMA, csConfig, csSettings, FileSaver, Blob, csWot, csTx, csCurrency) { 'ngInject'; - factory = function(id) { + function factory(id, BMA) { var constants = { @@ -32,22 +32,19 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser }; data.uid = null; data.isNew = null; - data.balance = 0; - data.sources = null; data.sourcesIndexByKey = null; data.currency= null; data.parameters = null; data.currentUD = null; data.medianTime = null; - data.tx = data.tx || {}; - data.tx.history = []; - data.tx.pendings = []; - data.tx.errors = []; data.requirements = {}; data.blockUid = null; data.sigDate = null; data.isMember = false; data.events = []; + + resetTxAndSources(); + if (init) { api.data.raise.init(data); } @@ -59,130 +56,18 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser } }, - reduceTxAndPush = function(txArray, result, processedTxMap, excludePending) { - if (!txArray || txArray.length === 0) { - return; - } - var txPendingsTimeByKey = excludePending ? [] : data.tx.pendings.reduce(function(res, tx) { - if (tx.time) { - res[tx.amount+':'+tx.hash] = tx.time; - } - return res; - }, []); - - _.forEach(txArray, function(tx) { - if (!excludePending || tx.block_number !== null) { - var walletIsIssuer = false; - var otherIssuer = tx.issuers.reduce(function(issuer, res) { - walletIsIssuer = (res === data.pubkey) ? true : walletIsIssuer; - return issuer + ((res !== data.pubkey) ? ', ' + res : ''); - }, ''); - if (otherIssuer.length > 0) { - otherIssuer = otherIssuer.substring(2); - } - var otherReceiver; - var outputBase; - var sources = []; - var lockedOutputs; - var amount = tx.outputs.reduce(function(sum, output, noffset) { - var outputArray = output.split(':',3); - outputBase = parseInt(outputArray[1]); - var outputAmount = powBase(parseInt(outputArray[0]), outputBase); - var outputCondition = outputArray[2]; - var sigMatches = BMA.regexp.TX_OUTPUT_SIG.exec(outputCondition); - - // Simple unlock condition - if (sigMatches) { - var outputPubkey = sigMatches[1]; - if (outputPubkey == data.pubkey) { // output is for the wallet - if (!walletIsIssuer) { - return sum + outputAmount; - } - // If pending: use output as new sources - else if (tx.block_number === null) { - sources.push({ - amount: parseInt(outputArray[0]), - base: outputBase, - type: 'T', - identifier: tx.hash, - noffset: noffset, - consumed: false - }); - } - } - else { // output is for someone else - if (outputPubkey !== '' && outputPubkey != otherIssuer) { - otherReceiver = outputPubkey; - } - if (walletIsIssuer) { - return sum - outputAmount; - } - } - } - - // Complex unlock condition, on the issuer pubkey - else if (outputCondition.indexOf('SIG('+data.pubkey+')') != -1) { - var lockedOutput = BMA.tx.parseUnlockCondition(outputCondition); - if (lockedOutput) { - // Add a source - // FIXME: should be uncomment when filtering source on transfer() - /*sources.push(angular.merge({ - amount: parseInt(outputArray[0]), - base: outputBase, - type: 'T', - identifier: tx.hash, - noffset: noffset, - consumed: false - }, lockedOutput)); - */ - lockedOutput.amount = outputAmount; - lockedOutputs = lockedOutputs || []; - lockedOutputs.push(lockedOutput); - console.debug('[BMA] [TX] has locked output:', lockedOutput); - - return sum + outputAmount; - } - } - return sum; - }, 0); - - var pubkey = amount > 0 ? otherIssuer : otherReceiver; - var time = tx.time; - if (tx.block_number === null) { - time = tx.blockstampTime || txPendingsTimeByKey[amount + ':' + tx.hash]; - } - - // Avoid duplicated tx, or tx to him self - var txKey = amount + ':' + tx.hash + ':' + time; - if (!processedTxMap[txKey] && amount !== 0) { - processedTxMap[txKey] = true; - var newTx = { - time: time, - amount: amount, - pubkey: pubkey, - comment: tx.comment, - isUD: false, - hash: tx.hash, - locktime: tx.locktime, - block_number: tx.block_number - }; - // If pending: store sources and inputs for a later use - see method processTransactionsAndSources() - if (walletIsIssuer && tx.block_number === null) { - newTx.inputs = tx.inputs; - newTx.sources = sources; - } - if (lockedOutputs) { - newTx.lockedOutputs = lockedOutputs; - } - result.push(newTx); - } - } - }); - }, - - resetSources = function(){ + resetTxAndSources = function(){ + // reset sources data data.sources = []; data.sourcesIndexByKey = {}; + data.balance = 0; + // reset TX data + data.tx = data.tx || {}; + data.tx.history = []; + data.tx.pendings = []; + data.tx.errors = []; + delete data.tx.fromTime; + delete data.tx.toTime; }, addSource = function(src, sources, sourcesIndexByKey) { @@ -273,21 +158,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser version: csConfig.version }; - if (data.tx && data.tx.pendings && data.tx.pendings.length>0) { - var pendings = data.tx.pendings.reduce(function(res, tx){ - return tx.time ? res.concat({ - amount: tx.amount, - time: tx.time, - hash: tx.hash - }) : res; - }, []); - if (pendings.length) { - dataToStore.tx = { - pendings: pendings - }; - } - } - localStorage.setObject(constants.STORAGE_KEY, dataToStore); } else { @@ -326,10 +196,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // FOR DEV ONLY - on crosschain // console.error('TODO REMOVE this code - dev only'); data.pubkey = '36j6pCNzKDPo92m7UXJLFpgDbcLFAZBgThD2TCwTwGrd'; - - if (storedData.tx && storedData.tx.pendings) { - data.tx.pendings = storedData.tx.pendings; - } data.loaded = false; return $q.all([ @@ -489,193 +355,14 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser }); }, - loadSources = function() { - return $q(function(resolve, reject) { - // Get transactions - BMA.tx.sources({pubkey: data.pubkey}) + loadTxAndSources = function(fromTime) { + return csTx.load(data.pubkey, fromTime) .then(function(res){ - resetSources(); - var balance = 0; - if (res.sources) { - _.forEach(res.sources, function(src) { - src.consumed = false; - balance += powBase(src.amount, src.base); - }); - addSources(res.sources); - } - data.balance = balance; - resolve(); + angular.merge(data, res); }) .catch(function(err) { - resetSources(); - reject(err); - }); - }); - }, - - loadTransactions = function(fromTime) { - return $q(function(resolve, reject) { - var txHistory = []; - var udHistory = []; - var txPendings = []; - - var now = new Date().getTime(); - var nowInSec = Math.trunc(now / 1000); - fromTime = fromTime || (nowInSec - csSettings.data.walletHistoryTimeSecond); - var processedTxMap = {}; - - var reduceTx = function(res){ - reduceTxAndPush(res.history.sent, txHistory, processedTxMap, true/*exclude pending*/); - reduceTxAndPush(res.history.received, txHistory, processedTxMap, true/*exclude pending*/); - reduceTxAndPush(res.history.sending, txHistory, processedTxMap, true/*exclude pending*/); - reduceTxAndPush(res.history.pending, txPendings, processedTxMap, false/*include pending*/); - }; - - var jobs = [ - // get pendings history - BMA.tx.history.pending({pubkey: data.pubkey}) - .then(reduceTx) - ]; - - // get TX history since - if (fromTime !== -1) { - var sliceTime = csSettings.data.walletHistorySliceSecond; - for(var i = fromTime - (fromTime % sliceTime); i - sliceTime < nowInSec; i += sliceTime) { - jobs.push(BMA.tx.history.times({pubkey: data.pubkey, from: i, to: i+sliceTime-1}) - .then(reduceTx) - ); - } - - jobs.push(BMA.tx.history.timesNoCache({pubkey: data.pubkey, from: nowInSec - (nowInSec % sliceTime), to: nowInSec+999999999}) - .then(reduceTx)); - } - - // get all TX - else { - jobs.push(BMA.tx.history.all({pubkey: data.pubkey}) - .then(reduceTx) - ); - } - - // get UD history - if (csSettings.data.showUDHistory) { - jobs.push( - BMA.ud.history({pubkey: data.pubkey}) - .then(function(res){ - udHistory = !res.history || !res.history.history ? [] : - res.history.history.reduce(function(res, ud){ - if (ud.time < fromTime) return res; // skip to old UD - var amount = powBase(ud.amount, ud.base); - return res.concat({ - time: ud.time, - amount: amount, - isUD: true, - block_number: ud.block_number - }); - }, []); - })); - } - - // Execute jobs - $q.all(jobs) - .then(function(){ - // sort by time desc - data.tx.history = txHistory.concat(udHistory).sort(function(tx1, tx2) { - return (tx2.time - tx1.time); - }); - data.tx.pendings = txPendings; - data.tx.fromTime = fromTime; - data.tx.toTime = data.tx.history.length ? data.tx.history[0].time /*=max(tx.time)*/: fromTime; - - // Call extend api - return api.data.raisePromise.loadTx(data.tx) - .then(function() { - console.debug('[wallet] TX history loaded in '+ (new Date().getTime()-now) +'ms'); - resolve(); - }); - }) - .catch(function(err) { - data.tx.history = []; - data.tx.pendings = []; - data.tx.errors = []; - delete data.tx.fromTime; - delete data.tx.toTime; - reject(err); - }); - }); - }, - - processTransactionsAndSources = function() { - return BMA.wot.member.uids() - .then(function(uids){ - var txPendings = []; - var txErrors = []; - var balance = data.balance; - - // process TX history - _.forEach(data.tx.history, function(tx) { - tx.uid = uids[tx.pubkey] || null; - }); - - var processPendingTx = function(tx) { - tx.uid = uids[tx.pubkey] || null; - - var consumedSources = []; - var valid = true; - if (tx.amount > 0) { // do not check sources from received TX - valid = false; - // TODO get sources from the issuer ? - } - else { - _.forEach(tx.inputs, function(input) { - var inputKey = input.split(':').slice(2).join(':'); - var srcIndex = data.sourcesIndexByKey[inputKey]; - if (angular.isDefined(srcIndex)) { - consumedSources.push(data.sources[srcIndex]); - } - else { - valid = false; - return false; // break - } - }); - if (tx.sources) { // add source output - addSources(tx.sources); - } - delete tx.sources; - delete tx.inputs; - } - if (valid) { - balance += tx.amount; // update balance - txPendings.push(tx); - _.forEach(consumedSources, function(src) { - src.consumed=true; - }); - } - else { - txErrors.push(tx); - } - }; - - var txs = data.tx.pendings; - var retry = true; - while(txs && txs.length > 0) { - // process TX pendings - _.forEach(txs, processPendingTx); - - // Retry once (TX could be chained and processed in a wrong order) - if (txErrors.length > 0 && txPendings.length > 0 && retry) { - txs = txErrors; - txErrors = []; - retry = false; - } - else { - txs = null; - } - } - - data.tx.pendings = txPendings; - data.tx.errors = txErrors; - data.balance = balance; + resetTxAndSources(); + throw err; }); }, @@ -776,11 +463,8 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Get requirements loadRequirements(), - // Get sources - loadSources(), - - // Get transactions - loadTransactions(), + // Get TX and sources + loadTxAndSources(), // Load sigStock loadSigStock(), @@ -792,10 +476,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser console.error(err); }) ]) - .then(function() { - // Process transactions and sources - return processTransactionsAndSources(); - }) .then(function() { finishLoadRequirements(); // must be call after loadCurrency() and loadRequirements() return api.data.raisePromise.finishLoad(data) @@ -860,11 +540,8 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser } if (options.sources || (options.tx && options.tx.enable)) { - // Get sources - jobs.push(loadSources()); - - // Get transactions - jobs.push(loadTransactions(options.tx.fromTime)); + // Get TX and sources + jobs.push(loadTxAndSources()); } // Load sigStock @@ -874,12 +551,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser if (!jobs.length || options.api) jobs.push(api.data.raisePromise.load(data, options)); return $q.all(jobs) - .then(function() { - if (options.sources || (options.tx && options.tx.enable)) { - // Process transactions and sources - return processTransactionsAndSources(); - } - }) .then(function(){ return api.data.raisePromise.finishLoad(data); }) @@ -1065,7 +736,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser csWot.extendAll([pendingTx], 'pubkey') .then(function() { data.tx.pendings.unshift(pendingTx); - store(); // save pendings in local storage resolve(); }).catch(function(err){reject(err);}); }).catch(function(err){reject(err);}); @@ -1697,7 +1367,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser api.registerEvent('data', 'finishLoad'); api.registerEvent('data', 'logout'); api.registerEvent('data', 'reset'); - api.registerEvent('data', 'loadTx'); api.registerEvent('action', 'certify'); csSettings.api.data.on.changed($rootScope, store); @@ -1744,9 +1413,9 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser fromJson: fromJson, api: api }; - }; + } - var service = factory('default'); + var service = factory('default', BMA); service.instance = factory; return service; diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js index 8aefc0a0882c5fe67f0d36d349624be9796795c9..ce2f414a7d54c3ffd5597d6da082d7df9b19320d 100644 --- a/www/js/services/wot-services.js +++ b/www/js/services/wot-services.js @@ -5,7 +5,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic .factory('csWot', function($q, $timeout, BMA, Api, CacheFactory, csConfig, csSettings, csCache) { 'ngInject'; - factory = function(id) { + function factory(id) { var api = new Api(this, "csWot-" + id), @@ -489,29 +489,6 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic } }, - loadSources = function(pubkey) { - return BMA.tx.sources({pubkey: pubkey}) - .then(function(res){ - var sources = []; - var sourcesIndexByKey = []; - var balance = 0; - if (!!res.sources && res.sources.length > 0) { - _.forEach(res.sources, function(src) { - var srcKey = src.type+':'+src.identifier+':'+src.noffset; - src.consumed = false; - balance += (src.base > 0) ? (src.amount * Math.pow(10, src.base)) : src.amount; - sources.push(src); - sourcesIndexByKey[srcKey] = sources.length -1 ; - }); - } - return { - sources: sources, - sourcesIndexByKey: sourcesIndexByKey, - balance: balance - }; - }); - }, - loadData = function(pubkey, withCache, uid, force) { var data; @@ -602,14 +579,6 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic data.given_cert_pending = res.pending; data.given_cert_error = res.error; }) - - // Get sources - // NOT NEED for now - /*loadSources(pubkey) - .then(function (sources) { - data.sources = sources; - }) - */ ]); }) .then(function() { @@ -997,9 +966,9 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic // api extension api: api }; - }; + } - var service = factory('default'); + var service = factory('default', BMA); service.instance = factory; return service; diff --git a/www/plugins/es/js/services/user-services.js b/www/plugins/es/js/services/user-services.js index 0da0941a62de0612eeca8aeb78991bb140f3696b..841068e60011b1a950ffacc4f2996f0e06de93cc 100644 --- a/www/plugins/es/js/services/user-services.js +++ b/www/plugins/es/js/services/user-services.js @@ -179,17 +179,6 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se return deferred.promise; } - function onWalletLoadTx(tx, deferred) { - fillAvatars((tx.history || []).concat(tx.pendings||[]), 'pubkey') - .then(function() { - deferred.resolve(); - }) - .catch(function(err) { - console.error(err); - deferred.resolve(); // silent - }); - } - function onWotSearch(text, datas, pubkeyAtributeName, deferred) { deferred = deferred || $q.defer(); if (!text && (!datas || !datas.length)) { @@ -383,7 +372,6 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se csWallet.api.data.on.finishLoad($rootScope, onWalletFinishLoad, this), csWallet.api.data.on.init($rootScope, onWalletReset, this), csWallet.api.data.on.reset($rootScope, onWalletReset, this), - csWallet.api.data.on.loadTx($rootScope, onWalletLoadTx, this), csWot.api.data.on.load($rootScope, onWotLoad, this), csWot.api.data.on.search($rootScope, onWotSearch, this) ]; diff --git a/www/plugins/rml9/i18n/locale-fr-FR.json b/www/plugins/rml9/i18n/locale-fr-FR.json index eee9848e3e18af74cad8015f49a422d1d1bcb2f0..152945116524bea8ad5ec8031d4c81e1357dc2c4 100644 --- a/www/plugins/rml9/i18n/locale-fr-FR.json +++ b/www/plugins/rml9/i18n/locale-fr-FR.json @@ -1,9 +1,11 @@ { "RML9": { - "BTN_EXPORT": "Exporter les transactions", + "BTN_EXPORT": "Télécharger", "BTN_OPEN": "Ouvrir la page RML9", "VIEW": { - "TITLE": "RML9" + "TITLE": "RML9", + "DIVIDER": "Dernières transactions :", + "BALANCE": "Solde du compte" } } } diff --git a/www/plugins/rml9/plugin-01-add_button.js b/www/plugins/rml9/plugin-01-add_button.js new file mode 100644 index 0000000000000000000000000000000000000000..7ad1d846f07b91e8cd07b6ae15f11d6bf0ed423f --- /dev/null +++ b/www/plugins/rml9/plugin-01-add_button.js @@ -0,0 +1,40 @@ + +angular.module('cesium.rml9.plugin', ['cesium.services']) + + .config(function($stateProvider, PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.rml9; + if (enable) { + + // [01] Extension de la vue d'une identité: ajout d'un bouton + PluginServiceProvider + .extendState('app.wot_identity', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/button.html", + controller: 'Rml9ButtonCtrl' + } + } + }); + } + + }) + + // [01] Manage events from the plugin button + .controller('Rml9ButtonCtrl', function($scope) { + 'ngInject'; + + // [01] Manage click event, on the plugin button + $scope.onButtonClick = function() { + + // [01] Get the public key, from the page context ($scope.formData) + var pubkey = $scope.formData.pubkey; + if (!pubkey) return; + console.debug("[RML9] call method onButtonClick() on pubkey: " + pubkey); + + }; + }) +; + + diff --git a/www/plugins/rml9/plugin-02-add_view.js b/www/plugins/rml9/plugin-02-add_view.js new file mode 100644 index 0000000000000000000000000000000000000000..8d77179d59d5fb6b2eef9aa1a0d9cd0a296b6389 --- /dev/null +++ b/www/plugins/rml9/plugin-02-add_view.js @@ -0,0 +1,84 @@ + +angular.module('cesium.rml9.plugin', ['cesium.services']) + + .config(function($stateProvider, PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.rml9; + if (enable) { + + // Extension de la vue d'une identité: ajout d'un bouton + PluginServiceProvider + .extendState('app.wot_identity', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/button.html", + controller: 'Rml9ButtonCtrl' + } + } + }); + + // Extension de 'Mes opérations' : insertion d'un bouton + PluginServiceProvider.extendState('app.view_wallet_tx', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/button.html", + controller: 'Rml9ButtonCtrl' + } + } + }); + + // [NEW] Ajout d'une nouvelle page #/app/rml9 + $stateProvider + .state('app.rml9', { + url: "/rml9/:pubkey", + views: { + 'menuContent': { + templateUrl: "plugins/rml9/templates/view.html", + controller: 'Rml9ViewCtrl' + } + } + }); + } + + }) + + // Manage events from the plugin button + .controller('Rml9ButtonCtrl', function($scope, $state) { + 'ngInject'; + + // Manage click event, on the plugin button + $scope.onButtonClick = function() { + + // [Get the public key, from the page context ($scope.formData) + var pubkey = $scope.formData.pubkey; + if (!pubkey) return; + console.debug("[RML9] call method onButtonClick() on pubkey: " + pubkey); + + // [NEW] Open the RML9 view (#/app/rml9) + $state.go('app.rml9', {pubkey: pubkey}); + }; + }) + + // [NEW] Manage events from the page #/app/rml9 + .controller('Rml9ViewCtrl', function($scope) { + 'ngInject'; + + // [NEW] When opening the view + $scope.$on('$ionicView.enter', function(e, state) { + console.log("[RML9] Opening the view..."); + + // [NEW] Get the pubkey (from URL params) and store it in the page context ($scope) + $scope.pubkey = (state && state.stateParams && state.stateParams.pubkey); + if (!$scope.pubkey) return; + + // [NEW] Create some data to display + $scope.items = [ + {amount: 64, time: 1493391431, pubkey:'2RFPQGxYraKTFKKBXgpNn1QDEPdFM7rHNu7HdbmmF43v'}, + {amount: -500, time: 1493373164, pubkey:'2RFPQGxYraKTFKKBXgpNn1QDEPdFM7rHNu7HdbmmF43v'}, + {amount: 100, time: 1493363131, pubkey:'5U2xuAUEPFeUQ4zpns6Zn33Q1ZWaHxEd3sPx689ZpaZV'} + ]; + }); + }); + + diff --git a/www/plugins/rml9/plugin-03-export_to_file.js b/www/plugins/rml9/plugin-03-export_to_file.js new file mode 100644 index 0000000000000000000000000000000000000000..600912e45f34630192e49cbd2e7e0e30d3931ed5 --- /dev/null +++ b/www/plugins/rml9/plugin-03-export_to_file.js @@ -0,0 +1,117 @@ + +angular.module('cesium.rml9.plugin', ['cesium.services']) + + .config(function($stateProvider, PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.rml9; + if (enable) { + + // Extension de la vue d'une identité: ajout d'un bouton + PluginServiceProvider + .extendState('app.wot_identity', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/button.html", + controller: 'Rml9ButtonCtrl' + } + } + }); + + // Extension de 'Mes opérations' : insertion d'un bouton + PluginServiceProvider.extendState('app.view_wallet_tx', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/button.html", + controller: 'Rml9ButtonCtrl' + } + } + }); + + // Ajout de la page #/app/rml9 + $stateProvider + .state('app.rml9', { + url: "/rml9/:pubkey", + views: { + 'menuContent': { + templateUrl: "plugins/rml9/templates/view-03.html", + controller: 'Rml9ViewCtrl' + } + } + }); + } + + }) + + // Manage events from the plugin button + .controller('Rml9ButtonCtrl', function($scope, $state) { + 'ngInject'; + + // Manage click event, on the plugin button + $scope.onButtonClick = function() { + + // [Get the public key, from the page context ($scope.formData) + var pubkey = $scope.formData.pubkey; + if (!pubkey) return; + console.debug("[RML9] call method onButtonClick() on pubkey: " + pubkey); + + // [NEW] Open the RML9 view (#/app/rml9) + $state.go('app.rml9', {pubkey: pubkey}); + }; + }) + + // [NEW] Manage events from the page #/app/rml9 + .controller('Rml9ViewCtrl', function($scope, csTx, + // [NEW] declare an AngularJS plugin, useful to create file + FileSaver) { + 'ngInject'; + + // When opening the view + $scope.$on('$ionicView.enter', function(e, state) { + console.log("[RML9] Opening the view..."); + + // Get the pubkey (from URL params) and store it in the page context ($scope) + $scope.pubkey = (state && state.stateParams && state.stateParams.pubkey); + if (!$scope.pubkey) return; + + // Load account TX data + csTx.load($scope.pubkey) + .then(function(result) { + console.log(result); // Allow to discover data structure + if (result && result.tx && result.tx.history) { + $scope.items = result.tx.history; + $scope.items = result && result.tx && result.tx.history || []; + + } + // [NEW] store the account balance + $scope.balance = (result && result.balance) || 0; + }); + }); + + // [NEW] Manage click on the export button + $scope.onExportButtonClick = function() { + console.debug("[RML9] call method onExportButtonClick() on pubkey: " + $scope.pubkey); + + // Load account TX data + var fromTime = -1; // all TX (full history) + csTx.load($scope.pubkey, fromTime) + .then(function(result) { + if (!result || !result.tx || !result.tx.history) return; // no TX + + + // TODO: replace this ! + // You can choose any format (CSV, TXT, JSON, ...) and test it ! + var content = [ + "Hello Libre World !\n", + "Cesium rock's !\n" + ]; + + var file = new Blob(content, {type: 'text/plain; charset=utf-8'}); + var filename = $scope.pubkey+'-history.txt'; + FileSaver.saveAs(file, filename); + }); + + }; + }); + + diff --git a/www/plugins/rml9/plugin-step-03.js b/www/plugins/rml9/plugin-step-03.js new file mode 100644 index 0000000000000000000000000000000000000000..9220cbd209c398fae0958ab69be06d50098650aa --- /dev/null +++ b/www/plugins/rml9/plugin-step-03.js @@ -0,0 +1,121 @@ + +angular.module('cesium.rml9.plugin', ['cesium.services']) + + .config(function($stateProvider, PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.rml9; + if (enable) { + + // Extension de la vue d'une identité + PluginServiceProvider + .extendState('app.wot_identity', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/buttons.html", + controller: 'Rml9ButtonsCtrl' + } + } + }); + + // Extension de 'Mes opérations' + /*PluginServiceProvider + .extendState('app.view_wallet_tx', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/buttons.html", + controller: 'Rml9ButtonsCtrl' + } + } + });*/ + + // Ajout d'une nouvelle vue #/app/rml9 + $stateProvider + .state('app.rml9', { + url: "/rml9?pubkey", + views: { + 'menuContent': { + templateUrl: "plugins/rml9/templates/view.html", + controller: 'Rml9ViewCtrl' + } + } + }); + } + + + }) + + /** + * Les controlleurs sont chargés de gérer faire la liaison entre les services d'accès aux données, et l'interface graphique. + * + * Celui-ci sert à étendre les vues 'Mes opérations' et celle d'une identité + */ + .controller('Rml9ButtonsCtrl', function($scope, $state, PluginService, FileSaver, BMA, csWallet) { + 'ngInject'; + + $scope.extensionPoint = PluginService.extensions.points.current.get(); + + /** + * Manage click event, on the export button + */ + $scope.onExportButtonClick = function() { + console.debug("[RML9] calling onExportButtonClick()"); + + var pubkey = $scope.formData.pubkey || csWallet.isLogin() && csWallet.data.pubkey; + if (!pubkey) return; + + BMA.tx.history.all({pubkey: pubkey}) + .then(function(res){ + if (!res || !res.history) return; + + console.debug("[RML9] TODO: process the TX history:", res.history); + + var fileContent = ["Hello Libre World !\n", "Second line example\n"]; + var file = new Blob(fileContent, {type: 'text/plain; charset=utf-8'}); + FileSaver.saveAs(file, 'transactions.txt'); + }); + }; + + /** + * Manage click event, on the export button + */ + $scope.onOpenButtonClick = function() { + console.debug("[RML9] calling onOpenButtonClick()"); + + // Get the pubkey from the extended view + var pubkey = $scope.formData.pubkey || csWallet.isLogin() && csWallet.data.pubkey; + + // Open the RML9 view (#/app/rml9) + $state.go('app.rml9', {pubkey: pubkey}); + }; + }) + + + /** + * Ce controlleur gère la page #/app/rml9 + */ + .controller('Rml9ViewCtrl', function($scope, csWallet) { + + // Call when enter into the view + $scope.$on('$ionicView.enter', function(e, state) { + + console.log("[RML9] entering RML9 view..."); + + // If need, a pubkey could be pass by URL params : #/app/rml9?pubkey=... + /* + var pubkey = (state && state.stateParams && state.stateParams.pubkey) || (csWallet.isLogin() && csWallet.data.pubkey); + if (!pubkey) return; + */ + + $scope.items = [ + {amount: 100, time: 125454702, issuer:'5U2xuAUEPFeUQ4zpns6Zn33Q1ZWaHxEd3sPx689ZpaZV'}, + {amount: -500, time: 125404702, issuer:'2RFPQGxYraKTFKKBXgpNn1QDEPdFM7rHNu7HdbmmF43v'} + ]; + + + }); + + + }); + + diff --git a/www/plugins/rml9/plugin.js b/www/plugins/rml9/plugin.js index 9220cbd209c398fae0958ab69be06d50098650aa..b44b4d14991740823d6e9767dea8e2332fef8ad9 100644 --- a/www/plugins/rml9/plugin.js +++ b/www/plugins/rml9/plugin.js @@ -17,32 +17,8 @@ angular.module('cesium.rml9.plugin', ['cesium.services']) } } }); - - // Extension de 'Mes opérations' - /*PluginServiceProvider - .extendState('app.view_wallet_tx', { - points: { - 'buttons': { - templateUrl: "plugins/rml9/templates/buttons.html", - controller: 'Rml9ButtonsCtrl' - } - } - });*/ - - // Ajout d'une nouvelle vue #/app/rml9 - $stateProvider - .state('app.rml9', { - url: "/rml9?pubkey", - views: { - 'menuContent': { - templateUrl: "plugins/rml9/templates/view.html", - controller: 'Rml9ViewCtrl' - } - } - }); } - }) /** @@ -59,63 +35,14 @@ angular.module('cesium.rml9.plugin', ['cesium.services']) * Manage click event, on the export button */ $scope.onExportButtonClick = function() { - console.debug("[RML9] calling onExportButtonClick()"); - var pubkey = $scope.formData.pubkey || csWallet.isLogin() && csWallet.data.pubkey; + // Récupération de la clé publique, stockée dans le contexte ($scope.formData) de la page + var pubkey = $scope.formData.pubkey; if (!pubkey) return; - BMA.tx.history.all({pubkey: pubkey}) - .then(function(res){ - if (!res || !res.history) return; - - console.debug("[RML9] TODO: process the TX history:", res.history); - - var fileContent = ["Hello Libre World !\n", "Second line example\n"]; - var file = new Blob(fileContent, {type: 'text/plain; charset=utf-8'}); - FileSaver.saveAs(file, 'transactions.txt'); - }); - }; - - /** - * Manage click event, on the export button - */ - $scope.onOpenButtonClick = function() { - console.debug("[RML9] calling onOpenButtonClick()"); - - // Get the pubkey from the extended view - var pubkey = $scope.formData.pubkey || csWallet.isLogin() && csWallet.data.pubkey; - - // Open the RML9 view (#/app/rml9) - $state.go('app.rml9', {pubkey: pubkey}); + console.debug("[RML9] calling onExportButtonClick()"); }; }) - - - /** - * Ce controlleur gère la page #/app/rml9 - */ - .controller('Rml9ViewCtrl', function($scope, csWallet) { - - // Call when enter into the view - $scope.$on('$ionicView.enter', function(e, state) { - - console.log("[RML9] entering RML9 view..."); - - // If need, a pubkey could be pass by URL params : #/app/rml9?pubkey=... - /* - var pubkey = (state && state.stateParams && state.stateParams.pubkey) || (csWallet.isLogin() && csWallet.data.pubkey); - if (!pubkey) return; - */ - - $scope.items = [ - {amount: 100, time: 125454702, issuer:'5U2xuAUEPFeUQ4zpns6Zn33Q1ZWaHxEd3sPx689ZpaZV'}, - {amount: -500, time: 125404702, issuer:'2RFPQGxYraKTFKKBXgpNn1QDEPdFM7rHNu7HdbmmF43v'} - ]; - - - }); - - - }); +; diff --git a/www/plugins/rml9/templates/buttons.html b/www/plugins/rml9/templates/button.html similarity index 57% rename from www/plugins/rml9/templates/buttons.html rename to www/plugins/rml9/templates/button.html index 2deff657b40763d322858b1c746cc6390ab8a9b5..013af9f9d0ab8d223049c6b5ddf572f804efce98 100644 --- a/www/plugins/rml9/templates/buttons.html +++ b/www/plugins/rml9/templates/button.html @@ -1,14 +1,8 @@ -<!-- Button: export --> +<!-- Button: call a method from the state controller --> <button class="button button-balanced button-small-padding icon ion-android-archive" - ng-click="onExportButtonClick()" - title="{{'RML9.BTN_EXPORT' | translate}}"> -</button> - -<!-- Button: Open a view, using a function -<<button class="button button-balanced button-small-padding icon ion-plus-circled" - ng-click="onOpenButtonClick()" + ng-click="onButtonClick()" title="{{'RML9.BTN_OPEN' | translate}}"> -</button> --> +</button> <!-- Button: Open a view, using `ui-sref` attribute <button class="button button-balanced button-small-padding icon ion-plus-circled" diff --git a/www/plugins/rml9/templates/view-03.html b/www/plugins/rml9/templates/view-03.html new file mode 100644 index 0000000000000000000000000000000000000000..8a1c06a5e307192006d2a41f8da42bc0caa028a4 --- /dev/null +++ b/www/plugins/rml9/templates/view-03.html @@ -0,0 +1,53 @@ +<ion-view left-buttons="leftButtons"> + <ion-nav-title> + {{'RML9.VIEW.TITLE' | translate}} + </ion-nav-title> + + <ion-content> + <div class="list"> + + <!-- buttons bar --> + <div class="center padding"> + <div class="buttons"> + <button class="button button-balanced icon-left icon ion-archive" + ng-click="onExportButtonClick()">{{'RML9.BTN_EXPORT' | translate}} + </button> + </div> + </div> + + <!-- the pubkey --> + <div class="item"> + {{'COMMON.PUBKEY'|translate}} + <div class="badge">{{pubkey|formatPubkey}}</div> + </div> + + <!-- the balance --> + <div class="item"> + {{'RML9.VIEW.BALANCE'|translate}} + <div class="badge badge-calm"> + {{balance|formatAmount}} <span ng-bind-html="$root.currency.name|currencySymbol"></span> + </div> + </div> + + <!-- a text divider--> + <div class="item item-divider">{{'RML9.VIEW.DIVIDER'|translate:{pubkey: pubkey} }}</div> + + <!-- iterate on each TX --> + <div class="item item-text-wrap" ng-repeat="item in items"> + + <h3> + {{item.time|formatDate}} + <span ng-if="item.comment" class="gray"> | {{item.comment}}</span> + </h3> + + <h4 ng-if="item.uid" class="positive"><i class="icon ion-person"></i> {{item.name||item.uid}}</h4> + <h4 ng-if="!item.uid" class="gray"><i class="icon ion-key"></i> {{item.pubkey|formatPubkey}}</h4> + + <div class="badge" + ng-class="{'badge-balanced': item.amount > 0}"> + {{item.amount|formatAmount}} <span ng-bind-html="$root.currency.name|currencySymbol"></span> + </div> + </div> + </div> + </ion-content> +</ion-view> diff --git a/www/plugins/rml9/templates/view.html b/www/plugins/rml9/templates/view.html index 665f08658b7493e329198117c1ba174d04bcafe8..8d52ce5eeb152e1ddc52047d6acc422973293d8a 100644 --- a/www/plugins/rml9/templates/view.html +++ b/www/plugins/rml9/templates/view.html @@ -1,23 +1,26 @@ <ion-view left-buttons="leftButtons"> - <ion-nav-title> {{'RML9.VIEW.TITLE' | translate}} </ion-nav-title> <ion-content> - - <!-- items container --> <div class="list"> - <!-- an item --> + <!-- the pubkey --> + <div class="item"> + {{'COMMON.PUBKEY'|translate}} + <div class="badge">{{pubkey|formatPubkey}}</div> + </div> + + <!-- a text divider--> + <div class="item item-divider">{{'RML9.VIEW.DIVIDER'|translate:{pubkey: pubkey} }}</div> + + <!-- iterate on each TX --> <div class="item" ng-repeat="item in items"> <h3>{{item.time|formatDate}}</h3> - <h4>{{item.issuer|formatPubkey}}</h4> - <div class="badge">{{item.amount}}</div> + <h4>{{item.pubkey|formatPubkey}}</h4> + <div class="badge">{{item.amount|formatAmount}}</div> </div> - </div> - </ion-content> - </ion-view>