diff --git a/bower.json b/bower.json index 8992e37e4dee6397a721bc038e2cbce5ceaced11..498c7e07368e8d2d6a741b959171c81338922bb6 100644 --- a/bower.json +++ b/bower.json @@ -14,7 +14,8 @@ "angular": "1.5.3", "angular-bind-notifier": "^1.1.7", "angular-image-crop": "^2.0.0", - "ng-idle": "^1.3.2" + "ng-idle": "^1.3.2", + "chart.js": "chartjs#^2.6.0" }, "resolutions": { "angular-sanitize": "1.5.3", diff --git a/doc/fr/development_tutorial-02.md b/doc/fr/development_tutorial-02.md index 1af9e3ca2e69cc6bbf6248f337c69fadcca7c884..596b7e60d3e907eab1fc2b8cd4e5d72d463bf847 100644 --- a/doc/fr/development_tutorial-02.md +++ b/doc/fr/development_tutorial-02.md @@ -18,7 +18,7 @@ Pour mettre en place votre environnement de développement, suivez le [1er tutor ## Niveau VI : Afficher un paramètre monétaire manquant -__Objectif :__ Dans ce niveau, l'objectif est d'afficher dans la page [`Monnaie`](http://cesium.duniter.fr/#/app/currency/view/lg/) le paramètre monétaire `stepMax`. +__Objectif :__ Dans ce niveau, l'objectif est d'afficher dans la page [`Monnaie`](http://g1.duniter.fr/#/app/currency/view/lg/) le paramètre monétaire `stepMax`. > Pour rappel, `stepMax` est la distance maximale entre un membre et un postulant, pour que ce dernier puisse rentrer dans la toile de confiance. diff --git a/doc/fr/development_tutorial-04.md b/doc/fr/development_tutorial-04.md index b83cb085e9368aef505ec4423a0e59e92f8d1664..dad2751631c8a764166944ba3b5a70c60c3c5e7e 100644 --- a/doc/fr/development_tutorial-04.md +++ b/doc/fr/development_tutorial-04.md @@ -15,36 +15,35 @@ Avant de faire ce tutoriel, vous devez : L'objectif ici est de réaliser un graphique représentant l'évolution de montant du dividende universel. -Quand l'utilisateur cliquera sur le champ "dividende universel" de la page suivante : http://cesium.duniter.fr/#/app/currency/view/lg/ +Quand l'utilisateur cliquera sur le champ "dividende universel" de la page suivante : http://g1.duniter.fr/#/app/currency/view/lg/ ### Récupérer le code (tag rml8) Passez sur la branche du code #rml8 : https://github.com/duniter/cesium/tree/rml8 -### Démarrer Cesium -Lancer Cesium : +### Ajout de librairie dans Cesium -```bash -cd cesium -ionic serve -``` -### Démarrer le noeud ElasticSearch +#### librairie Chart.js -Démarrer votre noeud ES : +[Chart.js](chartjs.org) est une librairie JS qui permet de faire de magnifiques graphiques. -```bash -cd duniter4j -mvn install -DskipTests -mvn install -Prun -pl duniter4j-elasticsearch +Vérifier que cette librairie est installé dans Cesium, en ouvrant la page principale de l'application `www/index.html` et en repérant la ligne : +```html +<script src="js/vendor/Chart.js"></script> +``` + +Si ce n'est pas le cas, ajouté là avec la commande : +``` +bower install chartjs --save ``` -### Ajout de la librairie D3.js +Puis ajouter la librairie dans `www/index.html`. -D3.js est une puissante librairie JS qui permet de faire de magnifiques graphiques. +#### librairie Angular Chart -Vous pouvez utiliser `bower` pour installer la dépendance. -Puis ajouter la librairie dans la page principale de l'application : `www/index.html` +[Angular Chart](https://jtblin.github.io/angular-chart.js/) est une librairie qui intègre pleinement `Chart.js` dans Angular JS, utilisé par Cesium. +Cela permet de définir plus facilement un graphique. ### Gestion du controlleur @@ -59,14 +58,35 @@ Editez le fichier `www/js/controllers/currency-charts-controllers.js`. A vous de jouer ! Il faut : -- Remplir la requete POST vers le noeud ES sur l'index `/test_net/block/_search`; cf méthode `$scope.loadUds()'; +- Remplir la requete POST vers le noeud ES sur l'index `/g1/block/_search`; cf méthode `$scope.loadUds()'; - Traiter le retour de la requête, pour la transformer dans le format attendu par D3.js. ### Template -Editez le template HTML, dans le fichier `www/templates/currency/charts/ud.html` +Editez le template HTML. + +Regardez la documentation Chart.js pour savoir comment faire la suite ! + +### Testez ! + +#### Démarrer le noeud ElasticSearch -Regardez la documentation D3.js pour savoir comment faire la suite ! +Démarrer votre noeud ES : + +```bash +cd duniter4j +mvn install -DskipTests +mvn install -Prun -pl duniter4j-es-assembly +``` + +#### Démarrer Cesium + +Lancer Cesium : + +```bash +cd cesium +ionic serve +``` ## La suite ? diff --git a/doc/fr/development_tutorial-05.md b/doc/fr/development_tutorial-05.md new file mode 100644 index 0000000000000000000000000000000000000000..182b15364a9b26872e9cc8a53d01bf4e73b93e3d --- /dev/null +++ b/doc/fr/development_tutorial-05.md @@ -0,0 +1,72 @@ +## Introduction + +Cet article est un tutoriel pour développer un plugin Cesium. + +## Prérequis + +Avant de faire ce tutoriel, vous devez : + + - Avoir suivi les tutoriels sur Cesium [jusqu'au niveau VII](./development_tutorial-02.md) + +## Niveau XIII + +### Objectif + +L'objectif ici est d'inésrer des fonctionnalités à Cesium, en exploitant le mécanisme de plugin. +Dans la page `Mes opérations`, nous allons ajouter un bouton pour télécharger l'historique des opérations du compte. + +### Récupérer le code (tag rml9) + +Passez sur la branche du code #rml9 : https://github.com/duniter/cesium/tree/rml9 + + +### Activation du plugin + +Editer le fichier `www/index.html`, et décommenter LES DEUX lignes : +```html + (...) + <link href="dist/dist_css/plugins/graph/css/style.css" rel="stylesheet"> + (...) + <script src="dist/dist_js/plugins/rml9/plugin.js"></script> + (...) +``` + +Editer le fichier `www/js/plugins.js`, et décommenter la ligne : +```json + (...) + ,'cesium.rml9.plugin' + (...) +``` + +Editer le fichier `www/js/config.js`, et ajouter ces lignes : +```json + (...) + "plugins": { + (...) + // --- DEBUT des lignes à ajouter + "rml9": { + "enable": true + } + // --- FIN des lignes à ajouter + (...) + }, +``` + +Le plugin RML9 est maintenan activer. Il ne reste plus qu'à lancer Cesium pour vérifier : +```bash +ionic serve +``` + +### Développement du plugin + +Dans le fichier `www/plugins/rml9/plugins.js`, identifier la méthode `onButtonClick`. + + + + +## La suite ? + +Il ne vous reste qu'à publier le résultat ! ;) + +- sur le forum duniter, +- ou mieux via un `pull request` sur github. \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js index 6f08d841149ecc200ae0e2da0e24008ac66e33ca..cbc1d7314936a558c70903cbcb90cd4fb48ea116 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -43,7 +43,7 @@ var paths = { // plugins: templatecache_plugin: ['./www/plugins/*/templates/**/*.html'], ng_translate_plugin: ['./www/plugins/*/i18n/locale-*.json'], - ng_annotate_plugin: ['./www/plugins/*/js/**/*.js', '!./www/plugins/*/js/vendor/*.js'], + ng_annotate_plugin: ['./www/plugins/*/**/*.js', '!./www/plugins/*/js/vendor/*.js'], css_plugin: ['./www/plugins/*/css/**/*.css'] }; @@ -188,7 +188,6 @@ gulp.task('templatecache_plugin', function (done) { root: "plugins/" })) .pipe(gulp.dest('./www/dist/dist_js/plugins')) - //.pipe(gulp.dest('./www/plugins')) .on('end', done); }); @@ -203,7 +202,6 @@ gulp.task('ng_translate_plugin', function() { return gulp.src(paths.ng_translate_plugin) .pipe(ngTranslate({standalone:true, module: 'cesium.plugins.translations'})) .pipe(gulp.dest('www/dist/dist_js/plugins')); - //.pipe(gulp.dest('www/plugins')); }); gulp.task('css_plugin', function (done) { diff --git a/www/i18n/locale-en-GB.json b/www/i18n/locale-en-GB.json index ade6d11f79abc88ecb6a39c9d5ada890dcfd308d..f4a7da74cd12da0b8ce36eb0a356e5f9dcbb33a5 100644 --- a/www/i18n/locale-en-GB.json +++ b/www/i18n/locale-en-GB.json @@ -31,6 +31,7 @@ "BTN_ADD": "Add", "BTN_SEARCH": "Search", "BTN_REFRESH": "Refresh", + "BTN_START": "Start", "BTN_CONTINUE": "Continue", "BTN_UNDERSTOOD": "I understood", "BTN_OPTIONS": "Options", @@ -197,17 +198,13 @@ } }, "CURRENCY": { - "SELECT": { - "TITLE": "Currencies", - "CURRENCIES": "Known currencies", - "MEMBERS_COUNT": "{{membersCount}} members" - }, "VIEW": { "TITLE": "Currency", "TAB_CURRENCY": "Currency", "TAB_WOT": "Web of trust", "TAB_NETWORK": "Network", "TAB_BLOCKS": "Blocks", + "CURRENCY_SHORT_DESCRIPTION": "{{currency|capitalize}} is a <b>libre money</b>, started {{firstBlockTime | formatFromNow}}. It currently counts <b>{{N}} members </b>, who produce and collect a <a ng-click=\"showHelpModal('ud')\">Universal Dividend</a> (DU), each {{dt | formatPeriod}}.", "NETWORK_RULES_DIVIDER": "Network rules", "CURRENCY_NAME": "Currency name", "MEMBERS": "Members count", @@ -253,7 +250,7 @@ "VIEW": { "MEDIAN_TIME": "Blockchain time", "LOADING_PEERS": "Loading peers...", - "NODE_ADDRESS": "Node address", + "NODE_ADDRESS": "Peer address", "ENDPOINTS": { "BMAS": "Secure endpoint (SSL)", "BMATOR": "TOR endpoint", @@ -278,7 +275,7 @@ "POPOVER_FILTER_TITLE": "Filter", "OFFLINE": "Offline peers", "VIEW": { - "TITLE": "Node", + "TITLE": "Peer", "OWNER": "Owned by ", "SHOW_RAW_PEERING": "See peering document", "KNOWN_PEERS": "Known peers :", @@ -405,8 +402,13 @@ }, "NEW": { "TITLE": "Registration", - "SLIDE_1_TITLE": "Select a currency:", - "SLIDE_2_TITLE": "Kind of account:", + "INTRO_WARNING_TIME": "Creating an account on {{name|capitalize}} is very simple. Please take sufficient time to do this correctly (not to forget the usernames, passwords, etc.).", + "INTRO_WARNING_SECURITY": "Check that the hardware you are currently using (computer, tablet, phone) <b>is secure and trustworthy </b>.", + "INTRO_WARNING_SECURITY_HELP": "Up-to-date anti-virus, firewall enabled, session protected by password or pin code...", + "INTRO_HELP": "Click <b> {{'COMMON.BTN_START'|translate}}</b> to begin creating an account. You will be guided step by step.", + "REGISTRATION_NODE": "Your registration will be registered via the Duniter peer <b>{{server}}</b> node, which will then be distributed to the rest of the currency network.", + "REGISTRATION_NODE_HELP": "If you do not trust this peer, please change <a ng-click=\"doQuickFix('settings')\">in the settings</a> of Cesium.", + "SELECT_ACCOUNT_TYPE": "Choose the type of account to create:", "MEMBER_ACCOUNT": "Member account", "MEMBER_ACCOUNT_HELP": "If you are not yet registered as an individual (one account possible per individual).", "WALLET_ACCOUNT": "Simple wallet", @@ -425,8 +427,9 @@ "LAST_SLIDE_CONGRATULATION": "You completed all required fields.<br/><b>You can send the account creation request</b>.<br/><br/>For information, the public key below identifies your future account.<br/>It can be communicated to third parties to receive their payment.<br/>Once your account has been approved, you can find this key under <b>{{'ACCOUNT.TITLE'|translate}}</b>.", "CONFIRMATION_MEMBER_ACCOUNT": "<b class=\"assertive\">Warning:</b> your secret identifier, password and pseudonym can not be changed.<br/><b>Make sure you always remember it!</b><br/><b>Are you sure</b> you want to send this account creation request?", "CONFIRMATION_WALLET_ACCOUNT": "<b class=\"assertive\">Warning:</b> your password and pseudonym can not be changed.<br/><b>Make sure you always remember it!</b><br/><b>Are you sure</b> you want to continue?", - "PSEUDO_AVAILABLE": "This nickname is available", - "PSEUDO_NOT_AVAILABLE": "This nickname is not available", + "CHECKING_PSEUDO": "Checking...", + "PSEUDO_AVAILABLE": "This pseudonym is available", + "PSEUDO_NOT_AVAILABLE": "This pseudonym is not available", "INFO_LICENSE": "To be able to adhere to the currency, we ask you to kindly read and accept this license.", "BTN_ACCEPT": "I accept", "BTN_ACCEPT_LICENSE": "I accept the license" @@ -596,7 +599,7 @@ "FIX_IDENTITY": "The pseudonym <b>{{uid}}</b> will be published again, replacing the old publication that has expired.<br/></br/><b>Are you sure</b> you want to continue?", "FIX_MEMBERSHIP": "Your application for membership will be sent.<br/></br/><b>Are you sure?</b>", "RENEW_MEMBERSHIP": "Your membership will be renewed.<br/></br/><b>Are you sure?</b>", - "REVOKE_IDENTITY": "You will <b>definitely revoke this identity</b>.<br/><br/>The public key and the associated nickname <b>will never be used again</b> (for a member account).<br/></br/><b>Are you sure</b> you want to revoke this identity?", + "REVOKE_IDENTITY": "You will <b>definitely revoke this identity</b>.<br/><br/>The public key and the associated pseudonym <b>will never be used again</b> (for a member account).<br/></br/><b>Are you sure</b> you want to revoke this identity?", "REVOKE_IDENTITY_2": "This operation is <b>irreversible</b>!<br/><br/>Are you sure you want to <b>revoke this identity</b>?", "NOT_NEED_RENEW_MEMBERSHIP": "Your membership does not need to be renewed (it will only expire in {{membershipExpiresIn|formatDuration}}).<br/></br/><b>Are you sure you</b> want to renew your membership?", "SAVE_BEFORE_LEAVE": "Do you want to <b>save your changes</b> before leaving the page?", diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json index 813d4ffdb503906d5051017ab9d4db51ebd7b1dc..979d581f5c724b9e2f6d0d8bc1c4e26aa21690fe 100644 --- a/www/i18n/locale-en.json +++ b/www/i18n/locale-en.json @@ -31,6 +31,7 @@ "BTN_ADD": "Add", "BTN_SEARCH": "Search", "BTN_REFRESH": "Refresh", + "BTN_START": "Start", "BTN_CONTINUE": "Continue", "BTN_UNDERSTOOD": "I understood", "BTN_OPTIONS": "Options", @@ -197,17 +198,13 @@ } }, "CURRENCY": { - "SELECT": { - "TITLE": "Currencies", - "CURRENCIES": "Known currencies", - "MEMBERS_COUNT": "{{membersCount}} members" - }, "VIEW": { "TITLE": "Currency", "TAB_CURRENCY": "Currency", "TAB_WOT": "Web of trust", "TAB_NETWORK": "Network", "TAB_BLOCKS": "Blocks", + "CURRENCY_SHORT_DESCRIPTION": "{{currency|capitalize}} is a <b>libre money</b>, started {{firstBlockTime | formatFromNow}}. It currently counts <b>{{N}} members </b>, who produce and collect a <a ng-click=\"showHelpModal('ud')\">Universal Dividend</a> (DU), each {{dt | formatPeriod}}.", "NETWORK_RULES_DIVIDER": "Network rules", "CURRENCY_NAME": "Currency name", "MEMBERS": "Members count", @@ -253,7 +250,7 @@ "VIEW": { "MEDIAN_TIME": "Blockchain time", "LOADING_PEERS": "Loading peers...", - "NODE_ADDRESS": "Node address", + "NODE_ADDRESS": "Peer address", "ENDPOINTS": { "BMAS": "Secure endpoint (SSL)", "BMATOR": "TOR endpoint", @@ -278,7 +275,7 @@ "POPOVER_FILTER_TITLE": "Filter", "OFFLINE": "Offline peers", "VIEW": { - "TITLE": "Node", + "TITLE": "Peer", "OWNER": "Owned by ", "SHOW_RAW_PEERING": "See peering document", "KNOWN_PEERS": "Known peers :", @@ -405,8 +402,13 @@ }, "NEW": { "TITLE": "Registration", - "SLIDE_1_TITLE": "Select a currency:", - "SLIDE_2_TITLE": "Kind of account:", + "INTRO_WARNING_TIME": "Creating an account on {{name|capitalize}} is very simple. Please take sufficient time to do this correctly (not to forget the usernames, passwords, etc.).", + "INTRO_WARNING_SECURITY": "Check that the hardware you are currently using (computer, tablet, phone) <b>is secure and trustworthy </b>.", + "INTRO_WARNING_SECURITY_HELP": "Up-to-date anti-virus, firewall enabled, session protected by password or pin code...", + "INTRO_HELP": "Click <b> {{'COMMON.BTN_START'|translate}}</b> to begin creating an account. You will be guided step by step.", + "REGISTRATION_NODE": "Your registration will be registered via the Duniter peer <b>{{server}}</b> node, which will then be distributed to the rest of the currency network.", + "REGISTRATION_NODE_HELP": "If you do not trust this peer, please change <a ng-click=\"doQuickFix('settings')\">in the settings</a> of Cesium.", + "SELECT_ACCOUNT_TYPE": "Choose the type of account to create:", "MEMBER_ACCOUNT": "Member account", "MEMBER_ACCOUNT_HELP": "If you are not yet registered as an individual (one account possible per individual).", "WALLET_ACCOUNT": "Simple wallet", @@ -425,8 +427,9 @@ "LAST_SLIDE_CONGRATULATION": "You completed all required fields.<br/><b>You can send the account creation request</b>.<br/><br/>For information, the public key below identifies your future account.<br/>It can be communicated to third parties to receive their payment.<br/>Once your account has been approved, you can find this key under <b>{{'ACCOUNT.TITLE'|translate}}</b>.", "CONFIRMATION_MEMBER_ACCOUNT": "<b class=\"assertive\">Warning:</b> your secret identifier, password and pseudonym can not be changed.<br/><b>Make sure you always remember it!</b><br/><b>Are you sure</b> you want to send this account creation request?", "CONFIRMATION_WALLET_ACCOUNT": "<b class=\"assertive\">Warning:</b> your password and pseudonym can not be changed.<br/><b>Make sure you always remember it!</b><br/><b>Are you sure</b> you want to continue?", - "PSEUDO_AVAILABLE": "This nickname is available", - "PSEUDO_NOT_AVAILABLE": "This nickname is not available", + "CHECKING_PSEUDO": "Checking...", + "PSEUDO_AVAILABLE": "This pseudonym is available", + "PSEUDO_NOT_AVAILABLE": "This pseudonym is not available", "INFO_LICENSE": "To be able to adhere to the currency, we ask you to kindly read and accept this license.", "BTN_ACCEPT": "I accept", "BTN_ACCEPT_LICENSE": "I accept the license" @@ -596,7 +599,7 @@ "FIX_IDENTITY": "The pseudonym <b>{{uid}}</b> will be published again, replacing the old publication that has expired.<br/></br/><b>Are you sure</b> you want to continue?", "FIX_MEMBERSHIP": "Your application for membership will be sent.<br/></br/><b>Are you sure?</b>", "RENEW_MEMBERSHIP": "Your membership will be renewed.<br/></br/><b>Are you sure?</b>", - "REVOKE_IDENTITY": "You will <b>definitely revoke this identity</b>.<br/><br/>The public key and the associated nickname <b>will never be used again</b> (for a member account).<br/></br/><b>Are you sure</b> you want to revoke this identity?", + "REVOKE_IDENTITY": "You will <b>definitely revoke this identity</b>.<br/><br/>The public key and the associated pseudonym <b>will never be used again</b> (for a member account).<br/></br/><b>Are you sure</b> you want to revoke this identity?", "REVOKE_IDENTITY_2": "This operation is <b>irreversible</b>!<br/><br/>Are you sure you want to <b>revoke this identity</b>?", "NOT_NEED_RENEW_MEMBERSHIP": "Your membership does not need to be renewed (it will only expire in {{membershipExpiresIn|formatDuration}}).<br/></br/><b>Are you sure you</b> want to renew your membership?", "SAVE_BEFORE_LEAVE": "Do you want to <b>save your changes</b> before leaving the page?", diff --git a/www/i18n/locale-es-ES.json b/www/i18n/locale-es-ES.json index a29e26658140c4614d37582e5d970489a9a0dbde..29f30038ca200b50733242faae067282c2e10868 100644 --- a/www/i18n/locale-es-ES.json +++ b/www/i18n/locale-es-ES.json @@ -31,6 +31,7 @@ "BTN_ADD": "Añadir", "BTN_SEARCH": "Buscar", "BTN_REFRESH": "Actualisar", + "BTN_START": "Empezar", "BTN_CONTINUE": "Continuar", "BTN_UNDERSTOOD": "He entendido", "BTN_OPTIONS": "Opciónes", @@ -108,11 +109,12 @@ "MESSAGE_SHORT": "Sigue sus cuentas <a href=\"http://duniter.org\" target=\"_blank\">Duniter</a><br/>en tiempo real !", "BTN_REGISTRY": "Anuario", "BTN_MARKET": "Ofertas/demandas", - "BTN_CURRENCIES": "Explorar las monedas", "BTN_CURRENCY": "Explorar la moneda", "BTN_ABOUT": "A propósito", "BTN_HELP": "Ayuda en lÃnea", - "REPORT_ISSUE": "anomalÃa" + "REPORT_ISSUE": "anomalÃa", + "NOT_YOUR_ACCOUNT_QUESTION" : "Usted no es dueño de la cuenta <<b><i class=\"ion-key\"></i> {{pubkey|formatPubkey}}</b>?", + "BTN_CHANGE_ACCOUNT": "Desconectar esta cuenta" }, "SETTINGS": { "TITLE": "Configuraciónes", @@ -195,17 +197,13 @@ } }, "CURRENCY": { - "SELECT": { - "TITLE": "Monedas", - "CURRENCIES": "Monedas conocidas", - "MEMBERS_COUNT": "{{membersCount}} miembros" - }, "VIEW": { "TITLE": "Moneda", "TAB_CURRENCY": "Moneda", "TAB_WOT": "Red de confianza", "TAB_NETWORK": "Red", "TAB_BLOCKS": "Bloques", + "CURRENCY_SHORT_DESCRIPTION": "{{currency|capitalizar}} es un <b>moneda libre</b>, iniciado {{firstBlockTime|formatFromNow}}. Ella actualmente <b>{{N}} miembros</b>, que producen y recibir un <a ng-click=\"showHelpModal('ud')\">Dividendo universal</a> (DU), cada {{dt|formatPeriod}}.", "NETWORK_RULES_DIVIDER": "Reglas de la red", "CURRENCY_NAME": "Nombre de la moneda", "MEMBERS": "Número de miembros", @@ -403,8 +401,13 @@ }, "NEW": { "TITLE": "Inscripción", - "SLIDE_1_TITLE": "Elección de la moneda :", - "SLIDE_2_TITLE": "Tipo de cuenta :", + "INTRO_WARNING_TIME": "Crear una cuenta en {{name|capitalize}} es muy simple. Sin embargo, por favor tome el tiempo suficiente para tomar correctamente este paso (no olvidar los identificadores, contraseñas, etc.).", + "INTRO_WARNING_SECURITY": "Asegúrate de que el equipo que se utiliza actualmente (ordenador, tableta, teléfono) <b>es seguro y digno de confianza</b>.", + "INTRO_WARNING_SECURITY_HELP": "Actualizaciones de antivirus, firewall activado, sesión protegidos por contraseña o código PIN, etc.", + "INTRO_HELP": "Haga clic en <b>{{'COMMON.BTN_START'|translate}}</b> para iniciar la creación de la cuenta. Se le guiará paso a paso.", + "REGISTRATION_NODE": "Su registro será grabado a través del nodo Duniter <b>{{server}}</b>, que luego se transmitió al resto del sistema de moneda.", + "REGISTRATION_NODE_HELP": "Si usted no confÃa en este nodo, <a ng-click=\"doQuickFix('settings')\">cambie la configuración</a> de Cesium.", + "SELECT_ACCOUNT_TYPE": "Elegir el tipo de cuenta para crear:", "MEMBER_ACCOUNT": "Cuenta miembro", "MEMBER_ACCOUNT_HELP": "Si ya no está inscrito como un individuo (Solamente una cuenta posible por individuo).", "WALLET_ACCOUNT": "Simple monedero", @@ -423,6 +426,7 @@ "LAST_SLIDE_CONGRATULATION": "<b>Bravo !</b> Ha introducido todas las informaciónes necesarias.<br/><b>Puede mandar la solicitud</b> de creación de su cuenta.</b><br/><br/>Por su información, la llave pública más abajo identificará su cuenta futura.<br/>Podrá estar comunicada a terceros para recibir sus pagos.<br/>Sin embargo, <b>no es útil</b> anotarla aquÃ.", "CONFIRMATION_MEMBER_ACCOUNT": "<b class=\"assertive\">Advertencia :</b> el identificador secreto, la contraseña y el seudónimo no podrán estar modificados.<br/><b>Asegurase siempre se los recordar !</b><br/><br/><b>Está usted seguro</b> querer mandar esta solicitud de inscripción ?", "CONFIRMATION_WALLET_ACCOUNT": "<b class=\"assertive\">Advertencia :</b> el identificador secreto y la contraseña no podrán estar modificados.<br/><b>Asegurase siempre se los recordar !</b><br/><br/><b>Está usted seguro</b> querer continuar con estos identificadores ?", + "CHECKING_PSEUDO": "Comprobación de disponibilidad...", "PSEUDO_AVAILABLE": "Este nombre está disponible", "PSEUDO_NOT_AVAILABLE": "Este nombre de usuario no está disponible", "INFO_LICENSE": "Para unirse a la moneda, le pedimos que leer y aceptar esta licencia.", diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json index 4cb3046c6b2ff2767aef38e7e8a2363ddff1cd11..74ffce440435caba87bf5ad8a48cb526c5ae213f 100644 --- a/www/i18n/locale-fr-FR.json +++ b/www/i18n/locale-fr-FR.json @@ -31,6 +31,7 @@ "BTN_ADD": "Ajouter", "BTN_SEARCH": "Rechercher", "BTN_REFRESH": "Actualiser", + "BTN_START": "Commencer", "BTN_CONTINUE": "Continuer", "BTN_UNDERSTOOD": "J'ai compris", "BTN_OPTIONS": "Options", @@ -104,12 +105,11 @@ }, "HOME": { "TITLE": "Cesium", - "MESSAGE": "Bienvenue dans l'application Cesium !<br/>Suivez vos comptes <a href=\"http://duniter.org\" target=\"_blank\">Duniter</a> en temps réel.", - "MESSAGE_SHORT": "Suivez vos comptes <a href=\"http://duniter.org\" target=\"_blank\">Duniter</a><br/>en temps réel !", + "MESSAGE": "Bienvenue dans l'application Cesium !<br/>Suivez vos comptes en monnaie libre <a href=\"http://duniter.org\" target=\"_blank\">Duniter</a>.", + "MESSAGE_SHORT": "Suivez vos comptes en monnaie libre <a href=\"http://duniter.org\" target=\"_blank\">Duniter</a> !", "BTN_REGISTRY": "Annuaire", "BTN_MARKET": "Offres/demandes", - "BTN_CURRENCIES": "Explorer les monnaies", - "BTN_CURRENCY": "Explorer la monnaie", + "BTN_CURRENCY": "Explorer la monnaie {{name|abbreviate}}", "BTN_ABOUT": "à propos", "BTN_HELP": "Aide en ligne", "REPORT_ISSUE": "anomalie", @@ -197,17 +197,13 @@ } }, "CURRENCY": { - "SELECT": { - "TITLE": "Monnaies", - "CURRENCIES": "Monnaies connues", - "MEMBERS_COUNT": "{{membersCount}} membres" - }, "VIEW": { "TITLE": "Monnaie", "TAB_CURRENCY": "Monnaie", "TAB_WOT": "Toile de confiance", "TAB_NETWORK": "Réseau", "TAB_BLOCKS": "Blocs", + "CURRENCY_SHORT_DESCRIPTION": "{{currency|abbreviate}} est une <b>monnaie libre</b>, démarrée {{firstBlockTime|formatFromNow}}. Elle compte actuellement <b>{{N}} membres</b>, qui produisent et perçoivent un <a ng-click=\"showHelpModal('ud')\">Dividende Universel</a> (DU), chaque {{dt|formatPeriod}}.", "NETWORK_RULES_DIVIDER": "Règles du réseau", "CURRENCY_NAME": "Nom de la monnaie", "MEMBERS": "Nombre de membres", @@ -405,15 +401,20 @@ }, "NEW": { "TITLE": "Inscription", - "SLIDE_1_TITLE": "Choix de la monnaie :", - "SLIDE_2_TITLE": "Type de compte :", + "INTRO_WARNING_TIME": "La création d'un compte sur {{name|capitalize}} est très simple. Veuillez néanmoins prendre suffisament de temps pour faire correctement cette formalité (pour ne pas oublier les identifiants, mots de passe, etc.).", + "INTRO_WARNING_SECURITY": "Vérifier que le matériel que vous utilisez actuellement (ordinateur, tablette, téléphone) <b>est sécurisé et digne de confiance</b>.", + "INTRO_WARNING_SECURITY_HELP": "Anti-virus à jour, pare-feu activé, session protégée par mot de passe ou code pin, etc.", + "INTRO_HELP": "Cliquez sur <b>{{'COMMON.BTN_START'|translate}}</b> pour débuter la création de compte. Vous serez guidé étape par étape.", + "REGISTRATION_NODE": "Votre inscription sera enregistrée via le noeud Duniter <b>{{server}}</b>, qui le diffusera ensuite au reste du réseau de la monnaie.", + "REGISTRATION_NODE_HELP": "Si vous ne faites pas confiance en ce noeud, veuillez en changer <a ng-click=\"doQuickFix('settings')\">dans les paramètres</a> de Cesium.", + "SELECT_ACCOUNT_TYPE": "Choisissez le type de compte à créer :", "MEMBER_ACCOUNT": "Compte membre", - "MEMBER_ACCOUNT_HELP": "Si vous n'etes pas encore inscrit en tant qu'individu (Un seul compte possible par individu).", + "MEMBER_ACCOUNT_HELP": "Si vous n'êtes pas encore inscrit en tant qu'individu (un seul compte possible par individu). Ce compte permet de co-produire la monnaie, en recevant un <b>dividende universel</b> chaque {{parameters.dt|formatPeriod}}.", "WALLET_ACCOUNT": "Simple portefeuille", "WALLET_ACCOUNT_HELP": "Pour tous les autres cas, par exemple si vous avez besoin d'un compte supplémentaire.<br/>Aucun dividende universel ne sera créé par ce compte.", "SALT_WARNING": "Choisissez votre identifiant secret.<br/>Il vous sera demandé à chaque connexion sur ce compte.<br/><br/><b>Retenez le bien</b>.<br/>En cas de perte, plus personne ne pourra accéder à votre compte !", "PASSWORD_WARNING": "Choisissez un mot de passe.<br/>Il vous sera demandé à chaque connexion sur ce compte.<br/><br/><b>Retenez bien ce mot de passe</b>.<br/>En cas de perte, plus personne ne pourra accéder à votre compte !", - "PSEUDO_WARNING": "Choisissez un pseudonyme.<br/>Il sert aux autres membres, pour vous identifier plus facilement.<br/><br/>Il ne doit contenir <b>ni espace, ni de caractère accentué</b>.<div class='hidden-xs'><br/>Exemple : <span class='gray'>SophieDupond, MarcelChemin, etc.</span>", + "PSEUDO_WARNING": "Choisissez un pseudonyme.<br/>Il sert aux autres membres, pour vous identifier plus facilement.<div class='hidden-xs'><br/>Il <b>ne pourra pas être modifié</b>, sans refaire un compte.</div><br/><br/>Il ne doit contenir <b>ni espace, ni de caractère accentué</b>.<div class='hidden-xs'><br/>Exemple : <span class='gray'>SophieDupond, MarcelChemin, etc.</span>", "PSEUDO": "Pseudonyme", "PSEUDO_HELP": "Pseudonyme", "SALT_CONFIRM": "Confirmation", @@ -425,6 +426,7 @@ "LAST_SLIDE_CONGRATULATION": "<b>Bravo !</b> Vous avez saisi toutes les informations nécessaires.<br/><b>Vous pouvez envoyer la demande</b> de création de compte.</b><br/><br/>Pour information, la clé publique ci-dessous identifiera votre futur compte.<br/>Elle pourra être communiquée à des tiers pour recevoir leur paiement.<br/>Cependant, <b>il n'est pas utile</b> de la noter ici.", "CONFIRMATION_MEMBER_ACCOUNT": "<b class=\"assertive\">Avertissement :</b> l'identifiant secret, le mot de passe et le pseudonyme ne pourront plus être modifiés.<br/><br/><b>Assurez-vous de toujours vous en rappeller !</b><br/><br/><b>Etes-vous sûr</b> de vouloir envoyer cette demande d'inscription ?", "CONFIRMATION_WALLET_ACCOUNT": "<b class=\"assertive\">Avertissement :</b> l'identifiant secret et le mot de passe ne pourront plus être modifiés.<br/><br/><b>Assurez-vous de toujours vous en rappeller !</b><br/><br/><b>Etes-vous sûr</b> de vouloir continuer avec ces identifiants ?", + "CHECKING_PSEUDO": "Vérification...", "PSEUDO_AVAILABLE": "Pseudonyme disponible", "PSEUDO_NOT_AVAILABLE": "Pseudonyme non disponible", "INFO_LICENSE": "Avant de créer un compte membre, <b>veuillez lire et accepter la licence</b> d'usage de la monnaie :", diff --git a/www/index.html b/www/index.html index 4596ffbcc4c5f10ed0223ad7c33e6d6a6febaf1b..e3a2e99adf110124eb14b0fa31bd6f0f76e87a44 100644 --- a/www/index.html +++ b/www/index.html @@ -19,6 +19,7 @@ <!--removeIf(no-plugin)--> <link href="dist/dist_css/plugins/es/css/style.css" rel="stylesheet"> + <link href="dist/dist_css/plugins/graph/css/style.css" rel="stylesheet"> <!--endRemoveIf(no-plugin)--> <!-- endbuild --> <!-- build:js dist_js/vendor.js --> @@ -159,9 +160,15 @@ <script src="dist/dist_js/plugins/graph/js/plugin.js"></script> <script src="dist/dist_js/plugins/graph/js/services.js"></script> <script src="dist/dist_js/plugins/graph/js/services/data-services.js"></script> + <script src="dist/dist_js/plugins/graph/js/services/color-services.js"></script> + <script src="dist/dist_js/plugins/graph/js/controllers/common-controllers.js"></script> <script src="dist/dist_js/plugins/graph/js/controllers/blockchain-controllers.js"></script> <script src="dist/dist_js/plugins/graph/js/controllers/network-controllers.js"></script> <script src="dist/dist_js/plugins/graph/js/controllers/currency-controllers.js"></script> + <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> <!--endRemoveIf(no-plugin)--> diff --git a/www/js/app.js b/www/js/app.js index 9ec6877c9b55cd96747e8160da5e78c945fd37e9..56616c9b87f89ae6d8d88807b2f4368c3e5712b7 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -225,21 +225,32 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht }) .filter('abbreviate', function() { + var _cache = {}; return function(input) { var currency = input || ''; + if (_cache[currency]) return _cache[currency]; if (currency.length > 3) { var unit = '', sepChars = ['-', '_', ' ']; for (var i = 0; i < currency.length; i++) { var c = currency[i]; - if (i === 0 || (i > 0 && sepChars.indexOf(currency[i-1]) != -1)) { + if (i === 0) { + unit = (c === 'g' || c === 'G') ? 'Äž' : c ; + } + else if (i > 0 && sepChars.indexOf(currency[i-1]) != -1) { unit += c; } } - return unit.toUpperCase(); + currency = unit.toUpperCase(); } else { - return currency.toUpperCase(); + currency = currency.toUpperCase(); + if (currency.charAt(0) === 'G') { + currency = 'Äž' + (currency.length > 1 ? currency.substr(1) : ''); + } } + + _cache[input] = currency; + return currency; }; }) @@ -364,11 +375,12 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht IdleProvider.timeout(csConfig.logoutTimeout||15); // display warning during 15s }) -.run(function($rootScope, $translate, $state, $window, ionicReady, Device, UIUtils, $ionicConfig, PluginService, csWallet, csSettings, csConfig) { +.run(function($rootScope, $translate, $state, $window, ionicReady, Device, UIUtils, $ionicConfig, PluginService, csWallet, csSettings, csConfig, csCurrency) { 'ngInject'; $rootScope.config = csConfig; $rootScope.settings = csSettings.data; + $rootScope.currency = csCurrency.data; $rootScope.walletData = csWallet.data; $rootScope.device = Device; @@ -477,7 +489,13 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht return csSettings.ready(); }) - // Trying to restore default wallet + // Load currency + .then(csCurrency.get) + .then(function(currency){ + $rootScope.currency = currency; + }) + + // Trying to restore wallet .then(csWallet.restore) // Storing wallet to root scope @@ -485,7 +503,6 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'pascalprecht $rootScope.walletData = walletData; }); - }) ; diff --git a/www/js/config.js b/www/js/config.js index 08361de8bf372271e3f5152e389e52e11de73706..0cab57479bc07009d56d5613d68e06d4d1f1fe07 100644 --- a/www/js/config.js +++ b/www/js/config.js @@ -11,26 +11,21 @@ angular.module("cesium.config", []) .constant("csConfig", { "cacheTimeMs": 60000, "fallbackLanguage": "en", - "rememberMe": false, + "rememberMe": true, "showUDHistory": false, "timeout": 10000, "timeWarningExpireMembership": 5184000, "timeWarningExpire": 7776000, - "logoutIlde": 600, "useLocalStorage": true, "useRelative": false, "initPhase": false, - "expertMode": false, + "expertMode": true, "decimalCount": 2, "httpsMode": false, "helptip": { - "enable": true, + "enable": false, "installDocUrl": "https://github.com/duniter/duniter/blob/master/doc/install-a-node.md" }, - "license": { - "fr-FR": "license/license_g1-fr-FR.txt", - "en": "license/license_g1-en.txt" - }, "node": { "host": "g1.duniter.org", "port": "443" @@ -39,19 +34,23 @@ angular.module("cesium.config", []) "es": { "enable": true, "askEnable": false, - "host": "g1.data.duniter.fr", - "port": "443", - "notifications": { - "txSent": true, - "txReceived": true, - "certSent": true, - "certReceived": true - } - } + "host": "localhost", + "port": "9200", + "wsPort": "9400" + }, + "graph": { + "enable": true + }, + "neo4j": { + "enable": true + }, + "rml9": { + "enable": true + } }, "version": "0.12.6", - "build": "2017-05-22T16:39:21.668Z", + "build": "2017-05-23T08:45:46.819Z", "newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug" }) -; \ No newline at end of file +; diff --git a/www/js/controllers/app-controllers.js b/www/js/controllers/app-controllers.js index a4187af495d70558b33b9d880cbb9e08d4fe5db2..e52206a821d3dcd4645c9f649fb08303af9ca487 100644 --- a/www/js/controllers/app-controllers.js +++ b/www/js/controllers/app-controllers.js @@ -436,7 +436,6 @@ function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, $q, $ $scope.doMotion = function(options) { return $scope.motion.show(options); }; - } @@ -448,12 +447,11 @@ function AboutController($scope, csConfig) { function HomeController($scope) { 'ngInject'; - } + function PassCodeController($scope) { 'ngInject'; - } diff --git a/www/js/controllers/blockchain-controllers.js b/www/js/controllers/blockchain-controllers.js index 87c96e52c428dbf2ce7e3d38cb266ad05d09d160..e64455399434cc5262c96384c9f41411b3e5d507 100644 --- a/www/js/controllers/blockchain-controllers.js +++ b/www/js/controllers/blockchain-controllers.js @@ -101,11 +101,11 @@ function BlockLookupController($scope, $timeout, $focus, $filter, $state, $ancho } // Load currency if need if (!$scope.currency) { - csCurrency.default() + csCurrency.get() .then(function(currency) { $scope.currency = currency ? currency.name : null; - $scope.node = !BMA.node.same(currency.peer.host, currency.peer.port) ? - BMA.instance(currency.peer.host, currency.peer.port) : BMA; + $scope.node = !BMA.node.same(currency.node.host, currency.node.port) ? + BMA.instance(currency.node.host, currency.node.port) : BMA; if (!$scope.currency) { UIUtils.alert.error('ERROR.GET_CURRENCY_FAILED'); @@ -434,12 +434,11 @@ function BlockViewController($scope, $ionicPopover, $state, UIUtils, BMA, csCurr $scope.hash = state && state.stateParams && state.stateParams.hash ? state.stateParams.hash : undefined; if (!$scope.currency) { - csCurrency.default() + csCurrency.get() .then(function (currency) { if (currency) { $scope.currency = currency.name; - $scope.node = !BMA.node.same(currency.peer.host, currency.peer.port) ? - BMA.instance(currency.peer.host, currency.peer.port) : BMA; + $scope.node = currency.node; $scope.load(); } }) diff --git a/www/js/controllers/currency-controllers.js b/www/js/controllers/currency-controllers.js index 907ebb1bf0e0e29468581601872c9fb34d967ec6..863d2158e660cf786720fed678fdbd02515cff0d 100644 --- a/www/js/controllers/currency-controllers.js +++ b/www/js/controllers/currency-controllers.js @@ -6,29 +6,6 @@ angular.module('cesium.currency.controllers', ['cesium.services']) $stateProvider - .state('app.currency_lookup', { - url: "/currencies?q", - views: { - 'menuContent': { - templateUrl: "templates/currency/lookup.html", - controller: 'CurrencyLookupCtrl' - } - } - }) - - .state('app.currency_name', { - url: "/currencies/:currency", - views: { - 'menuContent': { - templateUrl: "templates/currency/view_currency.html", - controller: 'CurrencyViewCtrl' - } - }, - data: { - large: 'app.currency_name_lg' - } - }) - .state('app.currency', { url: "/currency", views: { @@ -80,17 +57,6 @@ angular.module('cesium.currency.controllers', ['cesium.services']) } }) - .state('app.currency_name_lg', { - url: "/currency/lg/:name", - cache: false, - views: { - 'menuContent': { - templateUrl: "templates/currency/view_currency_lg.html", - controller: 'CurrencyViewCtrl' - } - } - }) - .state('app.currency_lg', { url: "/currency/lg", cache: false, @@ -105,63 +71,12 @@ angular.module('cesium.currency.controllers', ['cesium.services']) }) -.controller('CurrencyLookupCtrl', CurrencyLookupController) - -.controller('CurrencyViewCtrl', CurrencyViewController) + .controller('CurrencyViewCtrl', CurrencyViewController) .controller('CurrencyLicenseModalCtrl', CurrencyLicenseModalController) ; -function CurrencyLookupController($scope, $state, $q, UIUtils, BMA, csCurrency) { - 'ngInject'; - - $scope.selectedCurrency = ''; - $scope.knownCurrencies = []; - $scope.search = { - loading: true, - results: [] - }; - $scope.entered = false; - - $scope.$on('$ionicView.enter', function(e, state) { - if (!$scope.entered) { - if (state && state.stateParams && state.stateParams.q) { - $scope.search.text = state.stateParams.q; - } - - csCurrency.all() - .then(function (currencies) { - - $q.all(currencies.map(function(currency) { - var bma = BMA.lightInstance(currency.peer.host,currency.peer.port); - return bma.blockchain.current() - .then(function(block) { - currency.membersCount = block.membersCount; - }); - })) - .then(function() { - $scope.search.results = currencies; - $scope.search.loading = false; - if (!!currencies && currencies.length == 1) { - $scope.selectedCurrency = currencies[0].id; - } - // Set Ink - UIUtils.ink({selector: 'a.item'}); - }); - - - }); - } - }); - - // Called to navigate to the main app - $scope.selectCurrency = function(currency) { - $scope.selectedCurrency = currency; - $state.go('app.currency_name', {currency: currency.name}); - }; -} - -function CurrencyViewController($scope, $q, $timeout, $ionicPopover, BMA, UIUtils, csSettings, csCurrency, csNetwork, ModalUtils) { +function CurrencyViewController($scope, $q, $timeout, $ionicPopover, Modals, BMA, UIUtils, csSettings, csCurrency, csNetwork, ModalUtils) { $scope.formData = { useRelative: csSettings.data.useRelative, @@ -195,25 +110,23 @@ function CurrencyViewController($scope, $q, $timeout, $ionicPopover, BMA, UIUtil csSettings.data.expertMode, licenseUrl: csSettings.getLicenseUrl() }; - $scope.node = null; $scope.loading = true; $scope.screen = UIUtils.screen; $scope.enter = function(e, state) { if ($scope.loading) { // run only once (first enter) - if (state.stateParams && state.stateParams.currency) { // Load by name - csCurrency.searchByName(state.stateParams.currency) - .then(function(currency){ - $scope.init(currency); - }); - } - else { - csCurrency.default() - .then(function (currency) { - $scope.init(currency); + + UIUtils.loading.show(); + + csCurrency.get() + .then($scope.load) + .then(function() { + // Show help tip, if login + if ($scope.isLogin()) { + $scope.showHelpTip(); + } }) .catch(UIUtils.onError('ERROR.GET_CURRENCY_FAILED')); - } csNetwork.api.data.on.mainBlockChanged($scope, function(mainBlock) { if ($scope.loading) return; @@ -228,34 +141,13 @@ function CurrencyViewController($scope, $q, $timeout, $ionicPopover, BMA, UIUtil }; $scope.$on('$ionicView.enter', $scope.enter); - $scope.init = function(currency) { - $scope.formData.currency = currency.name; - $scope.node = !BMA.node.same(currency.peer.host, currency.peer.port) ? - BMA.instance(currency.peer.host, currency.peer.port) : BMA; - - UIUtils.loading.show(); - - // Load data - $scope.load() - .then(function() { - // Show help tip, if login - if ($scope.isLogin()) { - $scope.showHelpTip(); - } - }); - }; - $scope.load = function() { - if (!$scope.node) { - return; - } - // Load data from node var data = {}, M, lastUDTime, now = new Date().getTime(); return $q.all([ // Get the currency parameters - $scope.node.blockchain.parameters() + BMA.blockchain.parameters() .then(function(json){ data.currency = json.currency; data.c = json.c; @@ -279,7 +171,7 @@ function CurrencyViewController($scope, $q, $timeout, $ionicPopover, BMA, UIUtil }), // Get the current block informations - $scope.node.blockchain.current() + BMA.blockchain.current() .then(function(block){ M = block.monetaryMass; data.N = block.membersCount; @@ -299,11 +191,11 @@ function CurrencyViewController($scope, $q, $timeout, $ionicPopover, BMA, UIUtil }), // Get the UD informations - $scope.node.blockchain.stats.ud() + BMA.blockchain.stats.ud() .then(function(res){ if (res.result.blocks.length) { var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; - return $scope.node.blockchain.block({ block: lastBlockWithUD }) + return BMA.blockchain.block({ block: lastBlockWithUD }) .then(function(block){ data.currentUD = (block.unitbase > 0) ? block.dividend * Math.pow(10, block.unitbase) : block.dividend; lastUDTime = block.medianTime; @@ -314,7 +206,7 @@ function CurrencyViewController($scope, $q, $timeout, $ionicPopover, BMA, UIUtil else { lastUDTime=0; data.Nprev=0; - return $scope.node.blockchain.parameters() + return BMA.blockchain.parameters() .then(function(json){ data.currentUD = json.ud0; }); @@ -423,6 +315,10 @@ function CurrencyViewController($scope, $q, $timeout, $ionicPopover, BMA, UIUtil return ModalUtils.show('templates/currency/modal_license.html','CurrencyLicenseModalCtrl'); }; + $scope.showHelpModal = function(helpAnchor) { + Modals.showHelp({anchor: helpAnchor}); + }; + /* -- popover -- */ $scope.showActionsPopover = function(event) { diff --git a/www/js/controllers/help-controllers.js b/www/js/controllers/help-controllers.js index 6cc430a0462cbbbceb5ce9d8e5152c0e3de3ecaa..a76a801f310c28bbbe4fe89a01a3ed783510fdc5 100644 --- a/www/js/controllers/help-controllers.js +++ b/www/js/controllers/help-controllers.js @@ -60,6 +60,7 @@ function HelpController($scope, $state, $timeout, $anchorScroll, csSettings) { $scope.$on('$ionicView.enter', function(e) { $scope.locale = csSettings.data.locale.id; if ($state.stateParams && $state.stateParams.anchor) { + $scope.anchor = $state.stateParams.anchor; $timeout(function () { $anchorScroll($state.stateParams.anchor); }, 100); @@ -70,13 +71,21 @@ function HelpController($scope, $state, $timeout, $anchorScroll, csSettings) { function HelpModalController($scope, $timeout, $anchorScroll, csSettings, parameters) { 'ngInject'; + $scope.itemsClass = {}; $scope.locale = csSettings.data.locale.id; if (parameters && parameters.anchor) { + $timeout(function() { $anchorScroll(parameters.anchor); }, 100); + + // Change CSS classes + $scope.itemsClass = {}; + $scope.itemsClass[parameters.anchor] = 'positive'; + $scope.listClass = 'gray'; } + } @@ -370,7 +379,7 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe ]; // Get currency parameters, with currentUD - return csCurrency.default().then(function(currency) { + return csCurrency.get().then(function(currency) { contentParams = currency.parameters; // Launch steps return $scope.executeStep('currency', steps, startIndex); @@ -481,8 +490,8 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe ]; // Get currency parameters, with currentUD - return csCurrency.default().then(function(currency) { - contentParams = currency.parameters; + return csCurrency.parameters().then(function(parameters) { + contentParams = parameters; // Launch steps return $scope.executeStep('network', steps, startIndex); }); @@ -581,9 +590,9 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe ]; // Get currency parameters, with currentUD - return csCurrency.default().then(function(currency) { + return csCurrency.get().then(function(currency) { contentParams = currency.parameters; - contentParams.currentUD = $rootScope.walletData.currentUD; + contentParams.currentUD = currency.currentUD; // Launch steps return $scope.executeStep('wot', steps, startIndex); }); @@ -732,10 +741,10 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe ]; // Get currency parameters, with currentUD - return csCurrency.default() + return csCurrency.get() .then(function(currency) { contentParams = currency.parameters; - contentParams.currentUD = $rootScope.walletData.currentUD; + contentParams.currentUD = currency.currentUD; // Launch steps return $scope.executeStep('wallet', steps, startIndex); }); @@ -844,8 +853,8 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe }*/ ]; - return csCurrency.default().then(function(currency) { - contentParams = currency.parameters; + return csCurrency.parameters().then(function(parameters) { + contentParams = parameterss; return $scope.executeStep('certs', steps, startIndex); }); }; @@ -908,10 +917,10 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe ]; // Get currency parameters, with currentUD - return csCurrency.default() + return csCurrency.get() .then(function(currency) { contentParams = currency.parameters; - contentParams.currentUD = $rootScope.walletData.currentUD; + contentParams.currentUD = currency.currentUD; // Launch steps return $scope.executeStep('tx', steps, startIndex); }); @@ -1036,9 +1045,9 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe } ]; - return csCurrency.default() - .then(function(currency) { - contentParams = currency.parameters; + return csCurrency.parameters() + .then(function(parameters) { + contentParams = parameters; return $scope.executeStep('settings', steps, startIndex); }); }; @@ -1072,7 +1081,7 @@ function HelpTipController($scope, $rootScope, $state, $window, $ionicSideMenuDe return $q.all([ $scope.showHome(), - csCurrency.default() + csCurrency.parameters() .then(function(parameters) { contentParams = parameters; }) diff --git a/www/js/controllers/join-controllers.js b/www/js/controllers/join-controllers.js index 77e469e15cdb647c60796275c9dd99356a49562d..cc099bfe274962eabd66e3701943b173f2cef4ed 100644 --- a/www/js/controllers/join-controllers.js +++ b/www/js/controllers/join-controllers.js @@ -32,7 +32,7 @@ function JoinController($timeout, Modals) { }, 100); } -function JoinChooseAccountTypeModalController($scope, $timeout, UIUtils, csCurrency) { +function JoinChooseAccountTypeModalController($scope, $state, Modals, UIUtils, csCurrency) { 'ngInject'; $scope.formData = {}; @@ -48,10 +48,11 @@ function JoinChooseAccountTypeModalController($scope, $timeout, UIUtils, csCurre $scope.load = function() { if ($scope.loading) { - return csCurrency.load() - .then(function (data) { - if (!data) return; - $scope.currencies = data.currencies; + return csCurrency.get() + .then(function (currency) { + if (!currency) return; + $scope.currency = currency; + $scope.formData.currency = currency.name; $scope.loading = false; }) .catch(UIUtils.onError('ERROR.GET_CURRENCIES_FAILED')); @@ -76,16 +77,26 @@ function JoinChooseAccountTypeModalController($scope, $timeout, UIUtils, csCurre $scope.slides.slider.lockSwipes(); }; - $scope.selectCurrency = function(currency) { - $scope.formData.currency = currency; - $scope.slideNext(); - }; - $scope.selectAccountTypeAndClose = function(type) { $scope.formData.accountType = type; $scope.closeModal($scope.formData); }; + /** + * Catch click for quick fix + * @param fix + */ + $scope.doQuickFix = function(event) { + if (event == 'settings') { + $scope.closeModal(); + $state.go('app.settings'); + } + }; + + $scope.showHelpModal = function(helpAnchor) { + Modals.showHelp({anchor: helpAnchor}); + }; + // TODO DEV only //$timeout(function() { //$scope.selectCurrency('g1'); @@ -94,7 +105,7 @@ function JoinChooseAccountTypeModalController($scope, $timeout, UIUtils, csCurre } -function JoinModalController($scope, $state, $interval, $timeout, UIUtils, CryptoUtils, csSettings, Modals, csWallet, csConfig, BMA, parameters) { +function JoinModalController($scope, $state, $interval, $timeout, $focus, UIUtils, CryptoUtils, csSettings, Modals, csWallet, BMA, parameters) { 'ngInject'; $scope.formData = { @@ -127,11 +138,11 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt if ($scope.loading) { $scope.licenseFileUrl = csSettings.getLicenseUrl(); - - $scope.startListenLicenseBottom(); + if ($scope.licenseFileUrl) { + $scope.startListenLicenseBottom(); + } $scope.slideBehavior = $scope.computeSlideBehavior(); - $scope.loading = false; } }; @@ -156,17 +167,18 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt $scope.showAccountPubkey = function() { $scope.formData.computing=true; + CryptoUtils.connect($scope.formData.username, $scope.formData.password) - .then(function(keypair) { - $scope.formData.pubkey = CryptoUtils.util.encode_base58(keypair.signPk); - $scope.formData.computing=false; - $scope.checkAccountAvailable(); - }) - .catch(function(err) { - $scope.formData.computing=false; - console.error('>>>>>>>' , err); - UIUtils.alert.error('ERROR.CRYPTO_UNKNOWN_ERROR'); - }); + .then(function(keypair) { + $scope.formData.pubkey = CryptoUtils.util.encode_base58(keypair.signPk); + $scope.formData.computing=false; + $scope.checkAccountAvailable(); + }) + .catch(function(err) { + $scope.formData.computing=false; + console.error('>>>>>>>' , err); + UIUtils.alert.error('ERROR.CRYPTO_UNKNOWN_ERROR'); + }); }; $scope.formDataChanged = function() { @@ -177,6 +189,7 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt $scope.getCurrentFormName = function() { var index = $scope.slides.slider.activeIndex; if($scope.accountType === 'member') { + index += ($scope.licenseFileUrl ? 0 : 1); // skip index 0, when no license file if (index === 0) return "licenseForm"; if (index === 1) return "pseudoForm"; if (index === 2) return "saltForm"; @@ -193,45 +206,62 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt $scope.computeSlideBehavior = function() { var formName = $scope.getCurrentFormName(); + var behavior; if (formName == "licenseForm") { - return { + behavior = { hasPreviousButton: false, hasNextButton: false, hasAcceptButton: true }; } else if (formName == "pseudoForm") { - return { + behavior = { helpAnchor: 'join-pseudo', - hasPreviousButton: true, - hasNextButton: true + hasPreviousButton: $scope.licenseFileUrl && true, + hasNextButton: true, + focus: 'pseudo' }; } else if (formName == "saltForm") { - return { + behavior = { helpAnchor: 'join-salt', hasPreviousButton: true, - hasNextButton: true + hasNextButton: true, + focus: 'salt' }; } else if (formName == "passwordForm") { - return { + behavior = { helpAnchor: 'join-password', hasPreviousButton: true, - hasNextButton: true + hasNextButton: true, + focus: 'password' }; } else if (formName == "confirmForm") { - return { + behavior = { hasPreviousButton: true, hasNextButton: false, hasSendButton: true }; } - return { - hasPreviousButton: false, - hasNextButton: true - }; + else { + behavior = { + hasPreviousButton: false, + hasNextButton: true + }; + } + + // removeIf(device) + // Focus input text (only if NOT device, to avoid keyboard opening) + if (behavior.focus) { + $timeout(function(){ + $focus(behavior.focus); + }, 100); + } + // endRemoveIf(device) + + return behavior; }; @@ -376,8 +406,9 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt }; $scope.checkUid = function(){ - if (!$scope.formData.pseudo || !$scope.formData.pseudo.length) { - $scope.uiAlreadyUsed = undefined; + if (!$scope.formData.pseudo || $scope.formData.pseudo.length < 3) { + $scope.formData.computing=false; + delete $scope.uiAlreadyUsed; return; } @@ -395,6 +426,7 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt $scope.formData.computing=false; }) .catch(function(err){ + console.error(err); $scope.formData.computing=false; $scope.uiAlreadyUsed = false; }); @@ -402,9 +434,9 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt $scope.$watch('formData.pseudo', $scope.checkUid, true); $scope.checkAccountAvailable = function() { - var pub = $scope.formData.pubkey; - $scope.accountAvailable = false; - BMA.tx.sources({ pubkey: pub }) // search on pubkey + delete $scope.accountAvailable; + // Search for tx source, from pubkey + BMA.tx.sources({ pubkey: $scope.formData.pubkey }) .then(function(res) { if(!res.sources.length) { $scope.formData.computing=false; @@ -412,7 +444,13 @@ function JoinModalController($scope, $state, $interval, $timeout, UIUtils, Crypt } else{ $scope.formData.computing=false; + $scope.accountAvailable = false; } + }) + .catch(function(err) { + console.error(err); + $scope.formData.computing=false; + $scope.accountAvailable = false; }); }; diff --git a/www/js/controllers/network-controllers.js b/www/js/controllers/network-controllers.js index 47758b5b3d7be5f05cb96b068b13a78071cf69f7..f2c90afe495921982e8f3ddc46e5b9c9d30d0a2f 100644 --- a/www/js/controllers/network-controllers.js +++ b/www/js/controllers/network-controllers.js @@ -65,11 +65,11 @@ function NetworkLookupController($scope, $state, $ionicHistory, $ionicPopover, if ($scope.networkStarted) return; $scope.networkStarted = true; $scope.search.loading = true; - csCurrency.default() + csCurrency.get() .then(function (currency) { if (currency) { - $scope.node = !BMA.node.same(currency.peer.host, currency.peer.port) ? - BMA.instance(currency.peer.host, currency.peer.port) : BMA; + $scope.node = !BMA.node.same(currency.node.host, currency.node.port) ? + BMA.instance(currency.node.host, currency.node.port) : BMA; if (state && state.stateParams) { if (state.stateParams.type && ['mirror', 'member', 'offline'].indexOf(state.stateParams.type) != -1) { $scope.search.type = state.stateParams.type; diff --git a/www/js/controllers/settings-controllers.js b/www/js/controllers/settings-controllers.js index 9b87e7f1c9c1de9b211cc9ab4437cc8d383052e3..85158edd54a69d226c5da825d9c6885e61406b37 100644 --- a/www/js/controllers/settings-controllers.js +++ b/www/js/controllers/settings-controllers.js @@ -20,7 +20,7 @@ angular.module('cesium.settings.controllers', ['cesium.services', 'cesium.curren .controller('SettingsCtrl', SettingsController) ; -function SettingsController($scope, $q, $ionicPopup, $timeout, $translate, csHttp, +function SettingsController($scope, $q, $ionicHistory, $ionicPopup, $timeout, $translate, csHttp, UIUtils, BMA, csSettings, $ionicPopover, Modals) { 'ngInject'; @@ -121,6 +121,9 @@ function SettingsController($scope, $q, $ionicPopup, $timeout, $translate, csHtt UIUtils.loading.hide(); $scope.formData.node = newNode; BMA.copy(nodeBMA); + + // Reset history cache + return $ionicHistory.clearCache(); }); }); }; diff --git a/www/js/plugins.js b/www/js/plugins.js index cbe68800eb9d6e49bf05049cd6bf074beb593243..cfc0d177e463a9975dd38fe3ddff3bba430b7e19 100644 --- a/www/js/plugins.js +++ b/www/js/plugins.js @@ -4,6 +4,7 @@ angular.module('cesium.plugins', [ 'cesium.plugins.templates', // Plugins 'cesium.es.plugin', - 'cesium.graph.plugin' + 'cesium.graph.plugin', + 'cesium.rml9.plugin' ]) ; diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js index dffc4a47127c873e0772e61bbdb9c77580ecc8bf..9c0351fe1b3c52c9c47bd7e87757e53d294cbfe0 100644 --- a/www/js/services/bma-services.js +++ b/www/js/services/bma-services.js @@ -337,7 +337,7 @@ angular.module('cesium.bma.services', ['ngResource', 'ngApi', 'cesium.http.servi node: { summary: get('/node/summary', csHttp.cache.LONG), same: function(host2, port2) { - return host2 == host && ((!port && !port2) || (port == port2)); + return host2 == that.host && ((!that.port && !port2) || (that.port == port2||80)); } }, network: { @@ -615,7 +615,7 @@ angular.module('cesium.bma.services', ['ngResource', 'ngApi', 'cesium.http.servi } }) .catch(function(err){ - if (err && err.ucode === errorCodes.HTTP_LIMITATION) { + if (err && err.ucode === exports.errorCodes.HTTP_LIMITATION) { resolve(result); } else { @@ -625,6 +625,19 @@ angular.module('cesium.bma.services', ['ngResource', 'ngApi', 'cesium.http.servi }); }; + exports.raw.getHttpWithRetryIfLimitation = function(exec) { + return exec() + .catch(function(err){ + // When too many request, retry in 3s + if (err && err.ucode == exports.errorCodes.HTTP_LIMITATION) { + return $timeout(function() { + // retry + return exports.raw.getHttpWithRetryIfLimitation(exec); + }, exports.constants.LIMIT_REQUEST_DELAY); + } + }); + }; + exports.blockchain.lastUd = function() { return exports.blockchain.stats.ud() .then(function(res) { diff --git a/www/js/services/currency-services.js b/www/js/services/currency-services.js index 086b898ba6b06261e5e21e2c562166a791e7e08b..c2818e68513c587e9a48e8c3de7dd1645df47952 100644 --- a/www/js/services/currency-services.js +++ b/www/js/services/currency-services.js @@ -1,107 +1,190 @@ angular.module('cesium.currency.services', ['ngResource', 'ngApi', 'cesium.bma.services']) -.factory('csCurrency', function($q, BMA, Api, $rootScope) { +.factory('csCurrency', function($q, BMA, Api) { 'ngInject'; - factory = function(id) { - + function factory(id, BMA) { var - data = { - loaded: false, - currencies: null, - cache: { - loadPromise: null + constants = { + // Avoid to many call on well known currencies + WELL_KNOWN_CURRENCIES: { + g1: { + firstBlockTime: 1488987127 + } } }, - api = new Api(this, "csCurrency-" + id), - loadData = function() { + data = {}, + api = new Api(this, "csCurrency-" + id); + + function powBase(amount, base) { + return base <= 0 ? amount : amount * Math.pow(10, base); + } + + function resetData() { + data.loaded = false; + data.name = null; + data.parameters = null; + data.firstBlockTime = null; + data.membersCount = null; + data.cache = {}; + data.node = BMA; + data.currentUD = null; + api.data.raise.reset(data); + } + + function loadData() { + + console.debug('[currency] Starting...'); + var now = new Date().getTime(); + + // Load currency from default node + data.cache.loadPromise = $q.all([ + + // get parameters + loadParameters() + .then(function(parameters) { + // load first block info + return loadFirstBlock(parameters.currency); + }), + + // get current data (e.g. UD, members count) + loadCurrentData(), + + // call extensions + api.data.raisePromise.load(data) + ]) + .then(function() { + console.debug('[currency] Loaded in ' + (new Date().getTime() - now) + 'ms'); + data.loaded = true; + delete data.cache.loadPromise; + return data; + }) + .catch(function(err) { + resetData(); + throw err; + }); + + return data.cache.loadPromise; + } + + function loadParameters() { + return BMA.blockchain.parameters() + .then(function(res){ + data.name = res.currency; + data.parameters = res; + return res; + }); + } + + function loadFirstBlock(currencyName) { + // Well known currencies + if (constants.WELL_KNOWN_CURRENCIES[currencyName]){ + angular.merge(data, constants.WELL_KNOWN_CURRENCIES[currencyName]); + return $q.when(); + } + + return BMA.blockchain.block({block:0}) + .then(function(json) { + // Need by graph plugin + data.firstBlockTime = json.medianTime; + }) + .catch(function(err) { + // Special case, when currency not started yet + if (err && err.ucode === BMA.errorCodes.BLOCK_NOT_FOUND) { + data.firstBlockTime = 0; + return; + } + throw err; + }); + } + + function loadCurrentData() { + return BMA.blockchain.stats.ud() + .then(function(res){ + // Special case for currency init + if (!res.result.blocks.length) { + data.currentUD = data.parameters ? data.parameters.ud0 : -1; + return data.currentUD ; + } + else { + var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; + return BMA.blockchain.block({ block: lastBlockWithUD }) + .then(function(block){ + data.currentUD = powBase(block.dividend, block.unitbase); + data.membersCount = block.membersCount; + return data.currentUD; + }) + .catch(function(err) { + data.currentUD = null; + throw err; + }); + } + }) + .catch(function(err) { + data.currentUD = null; + throw err; + }); + } + + function getData() { + if (data.loaded) { // load only once + return $q.when(data); + } + + // Previous load not finished: return the existing promise - fix #452 + if (data.cache.loadPromise) { // load only once + return $q.when(data.cache.loadPromise); + } + + return loadData(); + } + + function getDataField(field) { + return function() { if (data.loaded) { // load only once - return $q.when(data); + return $q.when(data[field]); } // Previous load not finished: return the existing promise - fix #452 if (data.cache.loadPromise) { // load only once - return $q.when(data.cache.loadPromise); + return $q.when(data.cache.loadPromise) + .then(function(){ + return data[field]; + }); } - data.currencies = []; - - var now = new Date().getTime(); - - // Load currency from default node - var promise = BMA.blockchain.parameters() - .then(function(res){ - data.currencies.push({ - name: res.currency, - peer: { - host: BMA.host, - port: BMA.port, - server: BMA.server - }, - parameters: res - }); - - // API extension point - return api.data.raisePromise.load(data); - }) - .then(function() { - console.debug('[currency] Loaded in ' + (new Date().getTime() - now) + 'ms'); - data.loaded = true; - delete data.cache.loadJobPromise; - return data; - }) - .catch(function(err) { - data.loaded = false; - data.currencies = []; - delete data.cache.loadJobPromise; - throw err; - }); - - data.cache.loadPromise = promise; - return promise; - }, - - getAll = function() { - return loadData() - .then(function(data){ - return data.currencies; + return loadData().then(function(){ + return data[field]; }); - }, - - getDefault = function() { - return loadData() - .then(function(data){ - if (!data || !data.currencies || !data.currencies.length) throw new Error('No default currency'); - return data.currencies[0]; - }); - }, + }; + } - searchByName = function(name) { - return loadData() - .then(function(data){ - return _.findWhere(data.currencies, {name: name}); - }); - } - ; + // TODO register new block event, to get new UD value // Register extension points api.registerEvent('data', 'load'); + api.registerEvent('data', 'reset'); + + // init data + resetData(); return { - id: id, - load: loadData, - all: getAll, - default: getDefault, - searchByName: searchByName, + get: getData, + parameters: getDataField('parameters'), + currentUD: getDataField('currentUD'), // api extension - api: api + api: api, + // deprecated methods + default: function() { + console.warn('[currency] \'csCurrency.default()\' has been DEPRECATED - Please use \'csCurrency.get()\' instead.'); + return getData(); + } }; - }; - - var service = factory('default'); + } + 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 4d289cdd690c01916213137f04e3b11fc9658bfc..1af89938fb991061e717160e4dc44829b5d308cb 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -21,10 +21,10 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser }, data = {}, - api = new Api(this, 'csWallet-' + id), resetData = function(init) { + data.loaded = false; data.pubkey= null; data.keypair = { signSk: null, @@ -48,7 +48,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser data.sigDate = null; data.isMember = false; data.events = []; - data.loaded = false; if (init) { api.data.raise.init(data); } @@ -316,12 +315,9 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Call extend api api.data.raisePromise.login(data), - // Load parameters + // Load currency (e.g parameters) // This prevent timeout error, when loading a market record after a browser refresh (e.g. F5) - loadParameters(), - - // Load current UD is need by features tour - loadCurrentUD() + loadCurrency() ]); } else */if (storedData && storedData.keypair && storedData.pubkey) { @@ -340,18 +336,15 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Call extend api api.data.raisePromise.login(data), - // Load parameters - // This prevent timeout error, when loading a market record after a browser refresh (e.g. F5) - loadParameters(), - - // Load current UD is need by features tour - loadCurrentUD() + // Load currency + // This prevent timeout error, when loading record after a browser refresh (e.g. F5) + loadCurrency() ]); } else { - // Load parameters + // Load currency // This prevent timeout error, when loading a market record after a browser refresh (e.g. F5) - return loadParameters(); + return loadCurrency(); } }) .then(function(){ @@ -686,49 +679,23 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser }); }, - loadParameters = function() { + loadCurrency = function() { if (data.parameters && data.currency) return $q.when(); - return BMA.blockchain.parameters() - .then(function(json){ - data.currency = json.currency; - data.parameters = json; - if (data.currentUD == -1) data.currentUD = data.parameters.ud0; + return csCurrency.get() + .then(function(currency){ + data.currency = currency.name; + data.parameters =currency.parameters; + data.currentUD = currency.currentUD; }) .catch(function(err) { data.currency = null; data.parameters = null; + data.currentUD = -1; throw err; }); }, - loadCurrentUD = function() { - return BMA.blockchain.stats.ud() - .then(function(res){ - // Special case for currency init - if (!res.result.blocks.length) { - data.currentUD = data.parameters ? data.parameters.ud0 : -1; - return data.currentUD ; - } - else { - var lastBlockWithUD = res.result.blocks[res.result.blocks.length - 1]; - return BMA.blockchain.block({ block: lastBlockWithUD }) - .then(function(block){ - data.currentUD = powBase(block.dividend, block.unitbase); - return data.currentUD; - }) - .catch(function(err) { - data.currentUD = null; - throw err; - }); - } - }) - .catch(function(err) { - data.currentUD = null; - throw err; - }); - }, - - // Must be call after loadParameters() and loadRequirements() + // Must be call after loadCurrency() and loadRequirements() finishLoadRequirements = function() { data.requirements.needCertificationCount = (!data.requirements.needMembership && (data.requirements.certificationCount < data.parameters.sigQty)) ? (data.parameters.sigQty - data.requirements.certificationCount) : 0; @@ -804,10 +771,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser return $q.all([ // Get currency parameters - loadParameters(), - - // Get current UD - loadCurrentUD(), + loadCurrency(), // Get requirements loadRequirements(), @@ -833,7 +797,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser return processTransactionsAndSources(); }) .then(function() { - finishLoadRequirements(); // must be call after loadParameters() and loadRequirements() + finishLoadRequirements(); // must be call after loadCurrency() and loadRequirements() return api.data.raisePromise.finishLoad(data) .catch(function(err) { console.error('Error while finishing wallet data load, on extension point. Try to continue'); @@ -884,11 +848,8 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Reset events cleanEventsByContext('requirements'); - // Get parameters - if (options.parameters) jobs.push(loadParameters()); - - // Get current UD - if (options.currentUd) jobs.push(loadCurrentUD()); + // Get currency (e.g parameters) + if (options.parameters || options.currentUd) jobs.push(loadCurrency()); // Get requirements if (options.requirements) { @@ -1291,8 +1252,8 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser var block; return $q.all([ - // Load parameters (need to known the currency) - loadParameters(), + // Load currency (e.g parameters) + loadCurrency(), // Get th current block BMA.blockchain.current() diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json index 3d2878f322a714ad273321f7dba469d86a5ea44d..c09d707abc137907ccc6d331899b4b79cd392be4 100644 --- a/www/plugins/es/i18n/locale-fr-FR.json +++ b/www/plugins/es/i18n/locale-fr-FR.json @@ -474,6 +474,8 @@ "NODE_BMA_UP": "Le noeud <b>{{params[0]}}:{{params[1]}}</b> est à nouveau accessible.", "MEMBER_JOIN": "Vous êtes maintenant <b>membre</b> de la monnaie <b>{{params[0]}}</b> !", "MEMBER_LEAVE": "Vous n'êtes <b>plus membre</b> de la monnaie <b>{{params[0]}}</b>!", + "MEMBER_EXCLUDE": "Vous n'êtes <b>plus membre</b> de la monnaie <b>{{params[0]}}</b>, faute de non renouvellement ou par manque de certifications.", + "MEMBER_REVOKE": "La révocation de votre compte a été effectuée. Il ne pourra plus être un compte membre de la monnaie <b>{{params[0]}}</b>.", "MEMBER_ACTIVE": "Votre renouvellement d'adhésion à la monnaie <b>{{params[0]}}</b> a été <b>pris en compte</b>.", "TX_SENT": "Votre <b>paiement</b> à <span ng-class=\"{'gray': !notification.uid, 'positive':notification.uid}\" ><i class=\"icon\" ng-class=\"{'ion-person': notification.uid, 'ion-key': !notification.uid}\"></i> {{name||uid||params[1]}}</span> a été effectué.", "TX_SENT_MULTI": "Votre <b>paiement</b> à <b>{{params[1]}}</b> a été effectué.", diff --git a/www/plugins/es/js/controllers/market-controllers.js b/www/plugins/es/js/controllers/market-controllers.js index 69194fa8653f96831c5b63a523fc075add84ba39..e545deb4c9b3cc588d8132f586f416af61a53e23 100644 --- a/www/plugins/es/js/controllers/market-controllers.js +++ b/www/plugins/es/js/controllers/market-controllers.js @@ -618,7 +618,7 @@ function ESMarketRecordEditController($scope, $q, $timeout, $state, $ionicPopove $scope.formData.type=state.stateParams.type; } // Set the default currency - csCurrency.default() + csCurrency.get() .then(function(currency){ $scope.formData.currency = currency.name; $scope.loading = false; diff --git a/www/plugins/es/js/services/market-services.js b/www/plugins/es/js/services/market-services.js index f328be3e9f46904c6f1f90a38025f71cbdf06cb5..20ff1f1f4d82772f3412eff96343371a7ab5b2eb 100644 --- a/www/plugins/es/js/services/market-services.js +++ b/www/plugins/es/js/services/market-services.js @@ -191,7 +191,7 @@ angular.module('cesium.es.market.services', ['ngResource', 'cesium.services', 'c // Make sure currency if present (fix old data) if (!record.currency) { - return csCurrency.default() + return csCurrency.get() .then(function (currency) { record.currency = currency.name; return data; diff --git a/www/plugins/graph/css/style.css b/www/plugins/graph/css/style.css new file mode 100644 index 0000000000000000000000000000000000000000..0d97c8161095b32e210b86648283f901df7acfd3 --- /dev/null +++ b/www/plugins/graph/css/style.css @@ -0,0 +1,9 @@ + +/********** + Graph currency popover +**********/ + +.popover-graph-currency { + height: 300px !important; + max-width: 250px !important; +} diff --git a/www/plugins/graph/i18n/locale-en-GB.json b/www/plugins/graph/i18n/locale-en-GB.json index b758ed2fb4dd4362c8d1bd68f72da3501222bd3a..1a64beadbf60d937bb2bd42246536f07b320125e 100644 --- a/www/plugins/graph/i18n/locale-en-GB.json +++ b/www/plugins/graph/i18n/locale-en-GB.json @@ -11,6 +11,16 @@ "BTN_SHOW_STATS": "See statistics", "BTN_SHOW_DETAILED_STATS": "Detailed statistics" }, + "ACCOUNT": { + "TITLE": "Statistics", + "BTN_SHOW_STATS": "View account Statistics", + "BALANCE_DIVIDER": "Account status", + "BALANCE_TITLE": "Evolution of the account {{pubkey|formatPubkey}}", + "TX_RECEIVED_LABEL": "Receipts", + "TX_SENT_LABEL": "Spending", + "UD_LABEL": "UD", + "BALANCE_LABEL": "Balance" + }, "BLOCKCHAIN": { "TITLE": "Statistics", "BLOCKS_ISSUERS_DIVIDER": "Written blocks by members", @@ -24,6 +34,7 @@ "TX_COUNT_TITLE": "Number of written transactions", "TX_COUNT_LABEL": "Number of transactions", "TX_AVG_BY_BLOCK": "Average per block", + "TX_RANGE_DURATION_DIVIDER": "Step unit:", "TX_RANGE_DURATION": { "HOUR": "Group by <b>hour</b>", "DAY": "Group by <b>day</b>", diff --git a/www/plugins/graph/i18n/locale-en.json b/www/plugins/graph/i18n/locale-en.json index b758ed2fb4dd4362c8d1bd68f72da3501222bd3a..1a64beadbf60d937bb2bd42246536f07b320125e 100644 --- a/www/plugins/graph/i18n/locale-en.json +++ b/www/plugins/graph/i18n/locale-en.json @@ -11,6 +11,16 @@ "BTN_SHOW_STATS": "See statistics", "BTN_SHOW_DETAILED_STATS": "Detailed statistics" }, + "ACCOUNT": { + "TITLE": "Statistics", + "BTN_SHOW_STATS": "View account Statistics", + "BALANCE_DIVIDER": "Account status", + "BALANCE_TITLE": "Evolution of the account {{pubkey|formatPubkey}}", + "TX_RECEIVED_LABEL": "Receipts", + "TX_SENT_LABEL": "Spending", + "UD_LABEL": "UD", + "BALANCE_LABEL": "Balance" + }, "BLOCKCHAIN": { "TITLE": "Statistics", "BLOCKS_ISSUERS_DIVIDER": "Written blocks by members", @@ -24,6 +34,7 @@ "TX_COUNT_TITLE": "Number of written transactions", "TX_COUNT_LABEL": "Number of transactions", "TX_AVG_BY_BLOCK": "Average per block", + "TX_RANGE_DURATION_DIVIDER": "Step unit:", "TX_RANGE_DURATION": { "HOUR": "Group by <b>hour</b>", "DAY": "Group by <b>day</b>", diff --git a/www/plugins/graph/i18n/locale-fr-FR.json b/www/plugins/graph/i18n/locale-fr-FR.json index 7e781d9af9d27b844e56894e71940e1742a4b8e4..99c5115c3a78c498254ac5fa0d94ef13df747aef 100644 --- a/www/plugins/graph/i18n/locale-fr-FR.json +++ b/www/plugins/graph/i18n/locale-fr-FR.json @@ -11,6 +11,24 @@ "BTN_SHOW_STATS": "Voir les statistiques", "BTN_SHOW_DETAILED_STATS": "Statistiques détaillées" }, + "ACCOUNT": { + "TITLE": "Statistiques", + "BTN_SHOW_STATS": "Voir les statistiques du compte", + "BALANCE_DIVIDER": "Situation du compte", + "BALANCE_TITLE": "Evolution du compte {{pubkey|formatPubkey}}", + "TX_RECEIVED_LABEL": "Recettes", + "TX_SENT_LABEL": "Dépenses", + "TX_ACCUMULATION_LABEL": "Bilan des transactions", + "UD_LABEL": "DU", + "UD_ACCUMULATION_LABEL": "Bilan des DU", + "BALANCE_LABEL": "Solde", + "WOT_DIVIDER": "Toile de confiance", + "CERTIFICATION_TITLE": "Nombre de certifications - {{pubkey|formatPubkey}}", + "RECEIVED_CERT_LABEL": "Total reçues", + "RECEIVED_CERT_DELTA_LABEL": "Variation reçues", + "GIVEN_CERT_LABEL": "Total envoyées", + "GIVEN_CERT_DELTA_LABEL": "Variation envoyées" + }, "BLOCKCHAIN": { "TITLE": "Statistiques", "BLOCKS_ISSUERS_DIVIDER": "Analyse de la répartition du calcul", @@ -24,10 +42,11 @@ "TX_COUNT_TITLE": "Nombre de transactions écrites", "TX_COUNT_LABEL": "Nombre de transactions", "TX_AVG_BY_BLOCK": "Nombre moyen de transactions / bloc", + "TX_RANGE_DURATION_DIVIDER": "Unité de temps :", "TX_RANGE_DURATION": { - "HOUR": "Regrouper par <b>heure</b>", - "DAY": "Regrouper par <b>jour</b>", - "MONTH": "Regrouper par <b>mois</b>" + "HOUR": "Heure", + "DAY": "Jour", + "MONTH": "Mois" } }, "CURRENCY": { diff --git a/www/plugins/graph/js/controllers/account-controllers.js b/www/plugins/graph/js/controllers/account-controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..85c54ffaba105f878869a5d1cfb922826b8d6fc6 --- /dev/null +++ b/www/plugins/graph/js/controllers/account-controllers.js @@ -0,0 +1,477 @@ + +angular.module('cesium.graph.account.controllers', ['chart.js', 'cesium.graph.services']) + + .config(function($stateProvider, PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.es; + if (enable) { + + PluginServiceProvider + .extendState('app.view_wallet_tx', { + points: { + 'buttons': { + templateUrl: "plugins/graph/templates/account/view_wallet_tx_extend.html", + controller: 'GpExtendCtrl' + } + } + }) + + .extendState('app.wot_identity', { + points: { + 'buttons': { + templateUrl: "plugins/graph/templates/account/view_identity_extend.html", + controller: 'GpExtendCtrl' + } + } + }) + ; + + $stateProvider + .state('app.view_wallet_stats', { + url: "/wallet/stats?t&stepUnit", + views: { + 'menuContent': { + templateUrl: "plugins/graph/templates/account/view_stats.html" + } + }, + data: { + auth: true + } + }) + + .state('app.wot_identity_stats', { + url: "/wot/:pubkey/stats?t&stepUnit", + views: { + 'menuContent': { + templateUrl: "plugins/graph/templates/account/view_stats.html" + } + } + }); + } + }) + + .controller('GpExtendCtrl', GpExtendController) + + .controller('GpAccountBalanceCtrl', GpAccountBalanceController) + + .controller('GpAccountCertificationCtrl', GpAccountCertificationController) + +; + +function GpExtendController($scope, PluginService, esSettings, $state, csWallet) { + 'ngInject'; + + $scope.extensionPoint = PluginService.extensions.points.current.get(); + $scope.enable = esSettings.isEnable(); + + esSettings.api.state.on.changed($scope, function(enable) { + $scope.enable = enable; + }); + + $scope.showIdentityStats = function() { + if ($scope.formData && $scope.formData.pubkey) { + $state.go('app.wot_identity_stats', {pubkey: $scope.formData.pubkey}); + } + }; + + $scope.showWalletStats = function() { + if (csWallet.isLogin()) { + $state.go('app.wot_identity_stats', {pubkey: csWallet.data.pubkey}); + } + }; +} + + +function GpAccountBalanceController($scope, $controller, $q, $state, $filter, $translate, csWot, gpData, gpColor, csWallet) { + 'ngInject'; + + // Initialize the super class and extend it. + angular.extend(this, $controller('GpCurrencyAbstractCtrl', {$scope: $scope})); + + $scope.init = function(e, state) { + + if (state && state.stateParams && state.stateParams.pubkey) { // Currency parameter + $scope.formData.pubkey = state.stateParams.pubkey; + } + else if(csWallet.isLogin()) { + $scope.formData.pubkey = csWallet.data.pubkey; + } + + }; + + $scope.inheritedSetScale = $scope.setScale; + $scope.setScale = function(scale) { + // linear scale: sent values as negative + if (scale === 'linear') { + $scope.data[$scope.data.length-2] = _.map($scope.data[$scope.data.length-2], function(value) { + return -1 * Math.abs(value); + }); + } + // log scale: sent values as positive + else { + $scope.data[$scope.data.length-2] = _.map($scope.data[$scope.data.length-2], function(value) { + return Math.abs(value); + }); + } + + $scope.inheritedSetScale(scale); + }; + + $scope.load = function(updateTimePct) { + + updateTimePct = angular.isDefined(updateTimePct) ? updateTimePct : true; + + var withUD = true; + + return csWot.load($scope.formData.pubkey) + .then(function(identity) { + $scope.identity = identity; + withUD = $scope.identity.isMember || $scope.identity.wasMember; + + return $q.all([ + + $translate('GRAPH.ACCOUNT.BALANCE_TITLE', $scope.formData), + + // translate i18n keys + $translate(['GRAPH.ACCOUNT.UD_LABEL', + 'GRAPH.ACCOUNT.TX_RECEIVED_LABEL', + 'GRAPH.ACCOUNT.TX_SENT_LABEL', + 'GRAPH.ACCOUNT.UD_ACCUMULATION_LABEL', + 'GRAPH.ACCOUNT.TX_ACCUMULATION_LABEL', + 'GRAPH.ACCOUNT.BALANCE_LABEL', + 'COMMON.DATE_PATTERN', + 'COMMON.DATE_SHORT_PATTERN', + 'COMMON.DATE_MONTH_YEAR_PATTERN']), + + // get data + gpData.blockchain.movement($scope.formData.currency, angular.copy($scope.formData)) + ]); + }) + .then(function(result) { + var title = result[0]; + var translations = result[1]; + result = result[2]; + + if (!result || !result.times) return; // no data + $scope.times = result.times; + + var formatInteger = $filter('formatInteger'); + var formatAmount = $filter('formatDecimal'); + $scope.currencySymbol = $filter('currencySymbolNoHtml')($scope.formData.currency, $scope.formData.useRelative); + + // Data + $scope.data = [ + result.ud, + result.received, + result.sent, + result.balance + ]; + + var displayFormats = { + hour: translations['COMMON.DATE_PATTERN'], + day: translations['COMMON.DATE_SHORT_PATTERN'], + month: translations['COMMON.DATE_MONTH_YEAR_PATTERN'] + }; + var displayFormat = displayFormats[$scope.formData.rangeDuration]; + // Labels + $scope.labels = result.times.reduce(function(res, time) { + return res.concat(moment.unix(time).local().format(displayFormat)); + }, []); + + // Colors + $scope.colors = gpColor.scale.fix(result.times.length); + + // Update range with received values + $scope.updateRange(result.times[0], result.times[result.times.length-1], updateTimePct); + + // Options + $scope.options = { + responsive: true, + maintainAspectRatio: true, + title: { + display: true, + text: title + }, + scales: { + yAxes: [ + { + id: 'y-axis-left', + type: 'linear', + position: 'left', + stacked: true + } + ] + }, + legend: { + display: true + }, + tooltips: { + enabled: true, + mode: 'index', + callbacks: { + label: function(tooltipItems, data) { + return data.datasets[tooltipItems.datasetIndex].label + + ': ' + + (!tooltipItems.yLabel ? '0' : + (formatAmount(tooltipItems.yLabel) + ' ' + $scope.currencySymbol)); + } + } + } + }; + $scope.setScale($scope.scale); + + $scope.datasetOverride = [ + { + yAxisID: 'y-axis-left', + type: 'bar', + label: translations['GRAPH.ACCOUNT.UD_LABEL'], + backgroundColor: gpColor.rgba.energized(0.3), + hoverBackgroundColor: gpColor.rgba.energized(0.5), + borderWidth: 1 + }, + { + yAxisID: 'y-axis-left', + type: 'bar', + label: translations['GRAPH.ACCOUNT.TX_RECEIVED_LABEL'], + backgroundColor: gpColor.rgba.positive(0.4), + hoverBackgroundColor: gpColor.rgba.positive(0.6), + borderWidth: 1 + }, + { + yAxisID: 'y-axis-left', + type: 'bar', + label: translations['GRAPH.ACCOUNT.TX_SENT_LABEL'], + backgroundColor: gpColor.rgba.assertive(0.4), + hoverBackgroundColor: gpColor.rgba.assertive(0.6), + borderWidth: 1 + }, + { + yAxisID: 'y-axis-left', + type: 'line', + label: translations['GRAPH.ACCOUNT.BALANCE_LABEL'], + fill: 'origin', + borderColor: gpColor.rgba.calm(0.5), + borderWidth: 2, + pointBackgroundColor: gpColor.rgba.calm(0.5), + pointBorderColor: gpColor.rgba.white(), + pointHoverBackgroundColor: gpColor.rgba.calm(1), + pointHoverBorderColor: 'rgba(0,0,0,0)', + pointRadius: 3, + lineTension: 0.1 + } + ]; + + + if (!withUD) { + // remove UD + $scope.data.splice(0,1); + $scope.datasetOverride.splice(0,1); + } + else { + // FIXME: fund why UD data not working well + // remove UD + /*$scope.data.splice(0,1); + $scope.datasetOverride.splice(0,1);*/ + } + }); + }; + + $scope.onChartClick = function(data, e, item) { + if (!item) return; + var from = $scope.times[item._index]; + var to = moment.unix(from).utc().add(1, $scope.formData.rangeDuration).unix(); + var query = '_exists_:transactions AND medianTime:>={0} AND medianTime:<{1}'.format(from, to); + if ($scope.formData.pubkey) { + query += ' AND (transactions.issuers:' + $scope.formData.pubkey + ' OR transactions.outputs:*' + $scope.formData.pubkey + ')'; + } + $state.go('app.blockchain_search', {q: query}); + }; +} + + +/** + * Graph that display received/sent certification + */ +function GpAccountCertificationController($scope, $controller, $q, $state, $filter, $translate, gpData, gpColor, csWallet) { + 'ngInject'; + + // Initialize the super class and extend it. + angular.extend(this, $controller('GpCurrencyAbstractCtrl', {$scope: $scope})); + + $scope.init = function(e, state) { + if (state && state.stateParams && state.stateParams.pubkey) { // Currency parameter + $scope.formData.pubkey = state.stateParams.pubkey; + } + else if(csWallet.isLogin()) { + $scope.formData.pubkey = csWallet.data.pubkey; + } + + // for DEV only + //$scope.formData.pubkey = '38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE'; + }; + + $scope.load = function(updateTimePct) { + + var formData = $scope.formData; + + return $q.all([ + + $translate('GRAPH.ACCOUNT.CERTIFICATION_TITLE', formData), + + // translate i18n keys + $translate(['GRAPH.ACCOUNT.GIVEN_CERT_LABEL', + 'GRAPH.ACCOUNT.RECEIVED_CERT_LABEL', + 'GRAPH.ACCOUNT.GIVEN_CERT_DELTA_LABEL', + 'GRAPH.ACCOUNT.RECEIVED_CERT_DELTA_LABEL', + 'COMMON.DATE_PATTERN', + 'COMMON.DATE_SHORT_PATTERN', + 'COMMON.DATE_MONTH_YEAR_PATTERN']), + + // get data + gpData.wot.certifications(formData) + ]) + .then(function(result) { + + var title = result[0]; + var translations = result[1]; + result = result[2]; + + if (!result || !result.times) return; // no data + $scope.times = result.times; + + var formatInteger = $filter('formatInteger'); + + // Data + $scope.data = [ + result.deltaReceived, + result.received, + result.deltaGiven, + result.given + ]; + + // Labels + $scope.labels = result.labels; + + var displayFormats = { + hour: translations['COMMON.DATE_PATTERN'], + day: translations['COMMON.DATE_SHORT_PATTERN'], + month: translations['COMMON.DATE_MONTH_YEAR_PATTERN'] + }; + var displayFormat = displayFormats[$scope.formData.rangeDuration]; + // Labels + $scope.labels = result.times.reduce(function(res, time) { + return res.concat(moment.unix(time).local().format(displayFormat)); + }, []); + + // Colors + $scope.colors = gpColor.scale.fix(result.times.length); + + // Update options with received values + $scope.updateRange(result.times[0], result.times[result.times.length-1], updateTimePct); + + // Options + $scope.options = { + responsive: true, + maintainAspectRatio: true, + title: { + display: true, + text: title + }, + scales: { + yAxes: [ + { + id: 'y-axis-left', + type: 'linear', + position: 'left' + }, + { + id: 'y-axis-hide', + type: 'linear', + display: false, + position: 'right' + } + ] + }, + legend: { + display: true + }, + tooltips: { + enabled: true, + mode: 'index', + callbacks: { + label: function(tooltipItems, data) { + // Should add a '+' before value ? + var addPlus = (tooltipItems.datasetIndex === 0 || tooltipItems.datasetIndex === 2) && tooltipItems.yLabel > 0; + return data.datasets[tooltipItems.datasetIndex].label + + ': ' + + (addPlus ? '+' : '') + + !tooltipItems.yLabel ? '0' : formatInteger(tooltipItems.yLabel); + } + } + } + }; + + $scope.datasetOverride = [ + { + yAxisID: 'y-axis-left', + type: 'bar', + label: translations['GRAPH.ACCOUNT.RECEIVED_CERT_DELTA_LABEL'], + borderColor: gpColor.rgba.positive(0.6), + borderWidth: 1, + backgroundColor: gpColor.rgba.positive(0.4), + hoverBackgroundColor: gpColor.rgba.positive(0.6) + }, + { + yAxisID: 'y-axis-left', + type: 'line', + label: translations['GRAPH.ACCOUNT.RECEIVED_CERT_LABEL'], + fill: false, + borderColor: gpColor.rgba.positive(0.5), + borderWidth: 2, + backgroundColor: gpColor.rgba.positive(1), + pointBackgroundColor: gpColor.rgba.positive(0.5), + pointBorderColor: gpColor.rgba.white(), + pointHoverBackgroundColor: gpColor.rgba.positive(1), + pointHoverBorderColor: 'rgba(0,0,0,0)', + pointRadius: 3 + }, + { + yAxisID: 'y-axis-left', + type: 'bar', + label: translations['GRAPH.ACCOUNT.GIVEN_CERT_DELTA_LABEL'], + borderColor: gpColor.rgba.assertive(0.6), + borderWidth: 1, + backgroundColor: gpColor.rgba.assertive(0.4), + hoverBackgroundColor: gpColor.rgba.assertive(0.6) + }, + { + yAxisID: 'y-axis-left', + type: 'line', + label: translations['GRAPH.ACCOUNT.GIVEN_CERT_LABEL'], + fill: false, + borderColor: gpColor.rgba.assertive(0.4), + borderWidth: 2, + backgroundColor: gpColor.rgba.assertive(1), + pointBackgroundColor: gpColor.rgba.assertive(0.4), + pointBorderColor: gpColor.rgba.white(), + pointHoverBackgroundColor: gpColor.rgba.assertive(1), + pointHoverBorderColor: 'rgba(0,0,0,0)', + pointRadius: 3, + lineTension: 0.1 + } + ]; + }); + }; + + $scope.onChartClick = function(data, e, item) { + if (!item) return; + var from = $scope.times[item._index]; + var to = moment.unix(from).utc().add(1, $scope.formData.rangeDuration).unix(); + var query = '_exists_:transactions AND medianTime:>={0} AND medianTime:<{1}'.format(from, to); + if ($scope.formData.pubkey) { + query += ' AND (transactions.issuers:' + $scope.formData.pubkey + ' OR transactions.outputs:*' + $scope.formData.pubkey + ')'; + } + $state.go('app.blockchain_search', {q: query}); + }; +} diff --git a/www/plugins/graph/js/controllers/blockchain-controllers.js b/www/plugins/graph/js/controllers/blockchain-controllers.js index a7f0ba1a97ef964a0789d38010c4b4c931dbe983..f1befd6b58af37a47f69fec8aefc8e1d0af35d6b 100644 --- a/www/plugins/graph/js/controllers/blockchain-controllers.js +++ b/www/plugins/graph/js/controllers/blockchain-controllers.js @@ -31,61 +31,21 @@ angular.module('cesium.graph.blockchain.controllers', ['chart.js', 'cesium.servi ; -function GpBlockchainTxCountController($scope, $q, $state, $filter, $translate, $ionicPopover, csCurrency, BMA, esHttp, gpData) { +function GpBlockchainTxCountController($scope, $controller, $q, $state, $filter, $translate, gpData, gpColor) { 'ngInject'; - $scope.loading = true; - $scope.height=undefined; - $scope.width=undefined; - $scope.formData = { - timePct: 100, - useRelative: false /*csSettings.data.useRelative*/ - }; - - // Default TX range duration - $scope.txOptions = { - rangeDuration: 'day' - }; - - $scope.enter = function(e, state) { - if ($scope.loading) { - - if (state && state.stateParams && state.stateParams.currency) { // Currency parameter - $scope.currency = state.stateParams.currency; - } - - // Make sure there is currency, or load it not - if (!$scope.currency) { - return csCurrency.default() - .then(function(currency) { - $scope.currency = currency ? currency.name : null; - return $scope.enter(e, state); - }); - } - - $scope.load() - .then(function() { - $scope.loading = false; - }); - } - }; - $scope.$on('$ionicParentView.enter', $scope.enter); + // Initialize the super class and extend it. + angular.extend(this, $controller('GpCurrencyAbstractCtrl', {$scope: $scope})); $scope.load = function(updateTimePct) { - updateTimePct = angular.isDefined(updateTimePct) ? updateTimePct : true; - - var truncDate = function(time) { - return moment.unix(time).utc().startOf($scope.txOptions.rangeDuration).unix(); - }; - - var txOptions = $scope.txOptions; + var formData = $scope.formData; return $q.all([ - $translate($scope.txOptions.issuer? + $translate($scope.formData.issuer? 'GRAPH.BLOCKCHAIN.TX_AMOUNT_PUBKEY_TITLE': - 'GRAPH.BLOCKCHAIN.TX_AMOUNT_TITLE', txOptions), + 'GRAPH.BLOCKCHAIN.TX_AMOUNT_TITLE', formData), // translate i18n keys $translate(['GRAPH.BLOCKCHAIN.TX_AMOUNT_LABEL', @@ -95,38 +55,25 @@ function GpBlockchainTxCountController($scope, $q, $state, $filter, $translate, 'COMMON.DATE_SHORT_PATTERN', 'COMMON.DATE_MONTH_YEAR_PATTERN']), - // get block #0 - $scope.firstBlockTime ? - $q.when({medianTime: $scope.firstBlockTime}) : - BMA.blockchain.block({block: 0}) - .catch(function(err) { - if (err && err.ucode == BMA.errorCodes.BLOCK_NOT_FOUND) { - return {medianTime: esHttp.date.now()}; - } - }), - // get data - gpData.blockchain.txCount($scope.currency, txOptions) + gpData.blockchain.txCount($scope.formData.currency, formData) ]) .then(function(result) { var title = result[0]; var translations = result[1]; - $scope.firstBlockTime = $scope.firstBlockTime || result[2].medianTime; - $scope.formData.firstBlockTime = $scope.formData.firstBlockTime || truncDate($scope.firstBlockTime); - $scope.formData.currencyAge = truncDate(esHttp.date.now()) - $scope.formData.firstBlockTime; - result = result[3]; + result = result[2]; if (!result || !result.times) return; // no data $scope.times = result.times; var formatInteger = $filter('formatInteger'); var formatAmount = $filter('formatDecimal'); - $scope.currencySymbol = $filter('currencySymbolNoHtml')($scope.currency, false/*$scope.formData.useRelative*/); + $scope.currencySymbol = $filter('currencySymbolNoHtml')($scope.formData.currency, $scope.formData.useRelative); // Data - if ($scope.txOptions.rangeDuration != 'hour') { + if ($scope.formData.rangeDuration != 'hour') { $scope.data = [ result.amount, result.count, @@ -148,27 +95,17 @@ function GpBlockchainTxCountController($scope, $q, $state, $filter, $translate, day: translations['COMMON.DATE_SHORT_PATTERN'], month: translations['COMMON.DATE_MONTH_YEAR_PATTERN'] }; - var displayFormat = displayFormats[$scope.txOptions.rangeDuration]; + var displayFormat = displayFormats[$scope.formData.rangeDuration]; // Labels $scope.labels = result.times.reduce(function(res, time) { return res.concat(moment.unix(time).local().format(displayFormat)); }, []); // Colors - $scope.colors = result.times.reduce(function(res) { - return res.concat('rgba(17,193,243,0.5)'); - }, []); + $scope.colors = gpColor.scale.fix(result.times.length); - // Update options with received values - $scope.txOptions.startTime = result.times[0]; - $scope.txOptions.endTime = result.times[result.times.length-1]; - $scope.formData.timeWindow = $scope.formData.timeWindow || $scope.txOptions.endTime - $scope.txOptions.startTime; - $scope.formData.rangeDuration = $scope.formData.rangeDuration || $scope.formData.timeWindow / result.times.length; - - if (updateTimePct) { - $scope.formData.timePct = Math.ceil(($scope.txOptions.startTime - $scope.formData.firstBlockTime) * 100 / - ($scope.formData.currencyAge - $scope.formData.timeWindow)); - } + // Update range options with received values + $scope.updateRange(result.times[0], result.times[result.times.length-1], updateTimePct); // Options $scope.options = { @@ -183,31 +120,19 @@ function GpBlockchainTxCountController($scope, $q, $state, $filter, $translate, { id: 'y-axis-amount', type: 'linear', - position: 'left', - ticks: { - beginAtZero:true, - callback: function(value) { - return formatInteger(value); - } - } + position: 'left' }, { id: 'y-axis-count', display: false, type: 'linear', - position: 'right', - ticks: { - beginAtZero:true - } + position: 'right' }, { id: 'y-axis-avg', display: false, type: 'linear', - position: 'right', - ticks: { - beginAtZero:true - } + position: 'right' } ] }, @@ -228,23 +153,25 @@ function GpBlockchainTxCountController($scope, $q, $state, $filter, $translate, } }; + $scope.setScale($scope.scale); + $scope.datasetOverride = [ { yAxisID: 'y-axis-amount', type: 'bar', label: translations['GRAPH.BLOCKCHAIN.TX_AMOUNT_LABEL'], - hoverBackgroundColor: 'rgba(17,193,243,0.6)' + hoverBackgroundColor: gpColor.rgba.calm(0.6) }, { yAxisID: 'y-axis-count', type: 'line', label: translations['GRAPH.BLOCKCHAIN.TX_COUNT_LABEL'], fill: false, - borderColor: 'rgba(150,150,150,0.5)', + borderColor: gpColor.rgba.gray(0.5), borderWidth: 2, - pointBackgroundColor: 'rgba(150,150,150,0.5)', - pointBorderColor: 'rgba(255,255,255,1)', - pointHoverBackgroundColor: 'rgba(150,150,150,1)', + pointBackgroundColor: gpColor.rgba.gray(0.5), + pointBorderColor: gpColor.rgba.white(), + pointHoverBackgroundColor: gpColor.rgba.gray(1), pointHoverBorderColor: 'rgba(0,0,0,0)', pointRadius: 3 }, @@ -264,122 +191,25 @@ function GpBlockchainTxCountController($scope, $q, $state, $filter, $translate, }); }; - $scope.setSize = function(height, width, maintainAspectRatio) { - $scope.height = height; - $scope.width = width; - $scope.maintainAspectRatio = angular.isDefined(maintainAspectRatio) ? maintainAspectRatio : $scope.maintainAspectRatio; - }; - - $scope.showTxRange = function(data, e, item) { - if (!item) return + $scope.onChartClick = function(data, e, item) { + if (!item) return; var from = $scope.times[item._index]; - var to = moment.unix(from).utc().add(1, $scope.txOptions.rangeDuration).unix(); + var to = moment.unix(from).utc().add(1, $scope.formData.rangeDuration).unix(); var query = '_exists_:transactions AND medianTime:>={0} AND medianTime:<{1}'.format(from, to); - if ($scope.txOptions.issuer) { - query += ' AND issuer:' + $scope.txOptions.issuer; + if ($scope.formData.issuer) { + query += ' AND issuer:' + $scope.formData.issuer; } $state.go('app.blockchain_search', {q: query}); }; - $scope.setTxRangeDuration = function(txRangeDuration) { - $scope.hideActionsPopover(); - if ($scope.txOptions && txRangeDuration == $scope.txOptions.rangeDuration) return; - - $scope.txOptions.rangeDuration = txRangeDuration; - - // Restore default values - delete $scope.txOptions.startTime; - delete $scope.txOptions.endTime; - $scope.formData = { - timePct: 100 - }; - - // Reload TX data - $scope.load(); - }; - - $scope.loadPreviousTx = function() { - $scope.txOptions.startTime -= $scope.times.length * $scope.formData.rangeDuration; - if ($scope.txOptions.startTime < $scope.firstBlockTime) { - $scope.txOptions.startTime = $scope.firstBlockTime; - } - $scope.txOptions.endTime = $scope.txOptions.startTime + $scope.times.length * $scope.formData.rangeDuration; - // Reload TX data - $scope.load(); - }; - - $scope.loadNextTx = function() { - $scope.txOptions.startTime += $scope.times.length * $scope.formData.rangeDuration; - if ($scope.txOptions.startTime > $scope.firstBlockTime + $scope.formData.currencyAge - $scope.formData.timeWindow) { - $scope.txOptions.startTime = $scope.firstBlockTime + $scope.formData.currencyAge - $scope.formData.timeWindow; - } - $scope.txOptions.endTime = $scope.txOptions.startTime + $scope.times.length * $scope.formData.rangeDuration; - // Reload TX data - $scope.load(); - }; - - $scope.onTxTimeChanged = function() { - $scope.txOptions.startTime = $scope.firstBlockTime + (parseFloat($scope.formData.timePct) / 100) * ($scope.formData.currencyAge - $scope.formData.timeWindow) ; - $scope.txOptions.endTime = $scope.txOptions.startTime + $scope.times.length * $scope.formData.rangeDuration; - - // Reload TX data - $scope.load(false); - }; - - /* -- Popover -- */ - - $scope.showTxActionsPopover = function(event) { - $scope.hideActionsPopover(); - $ionicPopover.fromTemplateUrl('plugins/graph/templates/blockchain/popover_tx_actions.html', { - scope: $scope - }).then(function(popover) { - $scope.actionsPopover = popover; - //Cleanup the popover when we're done with it! - $scope.$on('$destroy', function() { - $scope.actionsPopover.remove(); - }); - $scope.actionsPopover.show(event); - }); - }; - - $scope.hideActionsPopover = function() { - if ($scope.actionsPopover) { - $scope.actionsPopover.hide(); - } - }; - } -function GpBlockchainIssuersController($scope, $q, $state, $translate, csCurrency, gpData) { +function GpBlockchainIssuersController($scope, $controller, $q, $state, $translate, gpColor, gpData) { 'ngInject'; - $scope.loading = true; - $scope.height = undefined; - $scope.width = undefined; - - $scope.enter = function(e, state) { - if ($scope.loading) { - - if (state && state.stateParams && state.stateParams.currency) { // Currency parameter - $scope.currency = state.stateParams.currency; - } - - // Make sure there is currency, or load it not - if (!$scope.currency) { - return csCurrency.default() - .then(function(currency) { - $scope.currency = currency ? currency.name : null; - return $scope.enter(e, state); - }); - } - - $scope.load() - .then(function() { - $scope.loading = false; - }); - } - }; - $scope.$on('$ionicParentView.enter', $scope.enter); + + // Initialize the super class and extend it. + angular.extend(this, $controller('GpCurrencyAbstractCtrl', {$scope: $scope})); $scope.load = function() { return $q.all([ @@ -387,7 +217,7 @@ function GpBlockchainIssuersController($scope, $q, $state, $translate, csCurrenc 'GRAPH.BLOCKCHAIN.BLOCKS_ISSUERS_TITLE', 'GRAPH.BLOCKCHAIN.BLOCKS_ISSUERS_LABEL' ]), - gpData.blockchain.countByIssuer($scope.currency) + gpData.blockchain.countByIssuer($scope.formData.currency) ]) .then(function(result) { var translations = result[0]; @@ -423,18 +253,12 @@ function GpBlockchainIssuersController($scope, $q, $state, $translate, csCurrenc }; // Colors - $scope.colors = gpData.util.colors.custom(result.data.length); + $scope.colors = gpColor.scale.custom(result.data.length); }); }; - $scope.setSize = function(height, width, maintainAspectRatio) { - $scope.height = height; - $scope.width = width; - $scope.maintainAspectRatio = angular.isDefined(maintainAspectRatio) ? maintainAspectRatio : $scope.maintainAspectRatio; - }; - - $scope.showBlockIssuer = function(data, e, item) { + $scope.onChartClick = function(data, e, item) { if (!item) return; var issuer = $scope.issuers[item._index]; $state.go('app.wot_identity', issuer); diff --git a/www/plugins/graph/js/controllers/common-controllers.js b/www/plugins/graph/js/controllers/common-controllers.js new file mode 100644 index 0000000000000000000000000000000000000000..69c80fd5b0c6d0d13d6dadf063f2ffa20133a920 --- /dev/null +++ b/www/plugins/graph/js/controllers/common-controllers.js @@ -0,0 +1,254 @@ + +angular.module('cesium.graph.common.controllers', ['cesium.services']) + + .controller('GpCurrencyAbstractCtrl', GpCurrencyAbstractController) +; + +function GpCurrencyAbstractController($scope, $filter, $ionicPopover, $ionicHistory, $state, csSettings, csCurrency, esHttp) { + 'ngInject'; + + $scope.loading = true; + $scope.formData = $scope.formData || { + useRelative: csSettings.data.useRelative, + timePct: 100, + rangeDuration: 'day', + firstBlockTime: 0 + }; + $scope.formData.useRelative = false; /*angular.isDefined($scope.formData.useRelative) ? + $scope.formData.useRelative : csSettings.data.useRelative;*/ + $scope.scale = 'linear'; + $scope.height = undefined; + $scope.width = undefined; + $scope.maintainAspectRatio = true; + $scope.times = []; + + function _truncDate(time) { + return moment.unix(time).utc().startOf($scope.formData.rangeDuration).unix(); + } + + $scope.enter = function (e, state) { + if ($scope.loading) { + + if (state && state.stateParams) { + // remember state, to be able to refresh location + $scope.stateName = state && state.stateName; + $scope.stateParams = angular.copy(state && state.stateParams||{}); + + if (!$scope.formData.currency && state && state.stateParams && state.stateParams.currency) { // Currency parameter + $scope.formData.currency = state.stateParams.currency; + } + if (state.stateParams.timePct) { + $scope.formData.timePct = state.stateParams.timePct; + } + if (state.stateParams.group) { + $scope.formData.rangeDuration = state.stateParams.group; + } + } + + $scope.init(e, state); + + // Make sure there is currency, or load it not + if (!$scope.formData.currency) { + return csCurrency.get() + .then(function (currency) { + $scope.formData.currency = currency ? currency.name : null; + $scope.formData.firstBlockTime = currency ? _truncDate(currency.firstBlockTime) : 0; + if (!$scope.formData.firstBlockTime){ + console.warn('[graph] currency.firstBlockTime not loaded ! Should have been loaded by currrency service!'); + } + $scope.formData.currencyAge = _truncDate(esHttp.date.now()) - $scope.formData.firstBlockTime; + return $scope.enter(e, state); + }); + } + + $scope.load() + .then(function () { + $scope.loading = false; + }); + } + }; + $scope.$on('$csExtension.enter', $scope.enter); + $scope.$on('$ionicParentView.enter', $scope.enter); + + $scope.updateLocation = function() { + $ionicHistory.nextViewOptions({ + disableAnimate: true, + disableBack: true, + historyRoot: true + }); + + $scope.stateParams = $scope.stateParams || {}; + $scope.stateParams.t = $scope.formData.timePct < 100 || $scope.formData.timePct >= 0 ? $scope.formData.timePct : undefined; + $scope.stateParams.stepUnit = $scope.formData.rangeDuration != 'day' ? $scope.formData.rangeDuration : undefined; + + $state.go($scope.stateName, $scope.stateParams, { + reload: false, + inherit: true, + notify: false} + ); + }; + + // Allow to fixe size, form a template (e.g. in a 'ng-init' tag) + $scope.setSize = function(height, width, maintainAspectRatio) { + $scope.height = height; + $scope.width = width; + $scope.maintainAspectRatio = angular.isDefined(maintainAspectRatio) ? maintainAspectRatio : $scope.maintainAspectRatio; + }; + + // When parent view execute a refresh action + $scope.$on('csView.action.refresh', function(event, context) { + if (!context || context == 'currency') { + return $scope.load(); + } + }); + + $scope.init = function(stateParams) { + // Should be override by subclasses + }; + + $scope.load = function() { + // Should be override by subclasses + }; + + $scope.toggleScale = function() { + $scope.setScale($scope.scale === 'linear' ? 'logarithmic' : 'linear'); + }; + + $scope.setScale = function(scale) { + $scope.hideActionsPopover(); + $scope.scale = scale; + + var format = $filter('formatInteger'); + + _.forEach($scope.options.scales.yAxes, function(yAxe) { + yAxe.type = scale; + yAxe.ticks = yAxe.ticks || {}; + if (scale == 'linear') { + yAxe.ticks.beginAtZero = true; + delete yAxe.ticks.min; + yAxe.ticks.callback = function(value) { + return format(value); + }; + } + else { + //yAxe.ticks.min = 0; + delete yAxe.ticks.beginAtZero; + delete yAxe.ticks.callback; + yAxe.ticks.callback = function(value, index) { + if (!value) return; + if (Math.log10(value)%1 === 0 || Math.log10(value/3)%1 === 0) { + return format(value); + } + return ''; + }; + } + }); + }; + + $scope.setRangeDuration = function(rangeDuration) { + $scope.hideActionsPopover(); + if ($scope.formData && rangeDuration == $scope.formData.rangeDuration) return; + + $scope.formData.rangeDuration = rangeDuration; + + // Restore default values + delete $scope.formData.startTime; + delete $scope.formData.endTime; + delete $scope.formData.rangeDurationSec; + //$scope.formData.timePct = 100; + + // Reload data + $scope.load(); + // Update location + $scope.updateLocation(); + }; + + $scope.goPreviousRange = function() { + if ($scope.loading) return; + $scope.loading = true; + + $scope.formData.startTime -= $scope.times.length * $scope.formData.rangeDurationSec; + if ($scope.formData.startTime < $scope.formData.firstBlockTime) { + $scope.formData.startTime = $scope.formData.firstBlockTime; + } + $scope.formData.endTime = $scope.formData.startTime + $scope.times.length * $scope.formData.rangeDurationSec; + + // Reload data + $scope.load().then(function(){ + // Update location + $scope.updateLocation(); + + $scope.loading = false; + }); + }; + + $scope.goNextRange = function() { + if ($scope.loading) return; + $scope.loading = true; + $scope.formData.startTime += $scope.times.length * $scope.formData.rangeDurationSec; + if ($scope.formData.startTime > $scope.formData.firstBlockTime + $scope.formData.currencyAge - $scope.formData.timeWindow) { + $scope.formData.startTime = $scope.formData.firstBlockTime + $scope.formData.currencyAge - $scope.formData.timeWindow; + } + $scope.formData.endTime = $scope.formData.startTime + $scope.times.length * $scope.formData.rangeDurationSec; + + // Reload data + $scope.load().then(function(){ + // Update location + $scope.updateLocation(); + + $scope.loading = false; + }); + }; + + $scope.onRangeChanged = function() { + if ($scope.loading) return; + $scope.loading = true; + + $scope.formData.startTime = $scope.formData.firstBlockTime + (parseFloat($scope.formData.timePct) / 100) * ($scope.formData.currencyAge - $scope.formData.timeWindow) ; + $scope.formData.endTime = $scope.formData.startTime + $scope.times.length * $scope.formData.rangeDurationSec; + + // Reload data + $scope.load().then(function(){ + // Update location + $scope.updateLocation(); + + $scope.loading = false; + }); + }; + + $scope.updateRange = function(startTime, endTime, updateTimePct) { + updateTimePct = angular.isDefined(updateTimePct) ? updateTimePct : true; + + $scope.formData.startTime = startTime; + $scope.formData.endTime = endTime; + $scope.formData.timeWindow = $scope.formData.timeWindow || $scope.formData.endTime - $scope.formData.startTime; + $scope.formData.rangeDurationSec = $scope.formData.rangeDurationSec || $scope.formData.timeWindow / ($scope.times.length-1); + + if (updateTimePct) { + $scope.formData.timePct = Math.ceil(($scope.formData.startTime - $scope.formData.firstBlockTime) * 100 / + ($scope.formData.currencyAge - $scope.formData.timeWindow)); + } + }; + + /* -- Popover -- */ + + $scope.showActionsPopover = function(event) { + $scope.hideActionsPopover(); + $ionicPopover.fromTemplateUrl('plugins/graph/templates/common/popover_range_actions.html', { + scope: $scope + }).then(function(popover) { + $scope.actionsPopover = popover; + //Cleanup the popover when we're done with it! + $scope.$on('$destroy', function() { + $scope.actionsPopover.remove(); + }); + $scope.actionsPopover.show(event); + }); + }; + + $scope.hideActionsPopover = function() { + if ($scope.actionsPopover) { + $scope.actionsPopover.hide(); + } + }; +} diff --git a/www/plugins/graph/js/controllers/currency-controllers.js b/www/plugins/graph/js/controllers/currency-controllers.js index 056588024fcd84c5e42f33e97f8894e6c0e353fd..aaf200e9b7ef3034ae93efa5fc0f3968010a6f96 100644 --- a/www/plugins/graph/js/controllers/currency-controllers.js +++ b/www/plugins/graph/js/controllers/currency-controllers.js @@ -1,5 +1,5 @@ -angular.module('cesium.graph.currency.controllers', ['chart.js', 'cesium.graph.services', 'cesium.graph.blockchain.controllers']) +angular.module('cesium.graph.currency.controllers', ['chart.js', 'cesium.graph.services', 'cesium.graph.common.controllers']) .config(function($stateProvider, PluginServiceProvider, csConfig) { 'ngInject'; @@ -82,8 +82,6 @@ angular.module('cesium.graph.currency.controllers', ['chart.js', 'cesium.graph.s .controller('GpCurrencyViewExtendCtrl', GpCurrencyViewExtendController) - .controller('GpCurrencyAbstractCtrl', GpCurrencyAbstractController) - .controller('GpCurrencyMonetaryMassCtrl', GpCurrencyMonetaryMassController) .controller('GpCurrencyDUCtrl', GpCurrencyDUController) @@ -103,63 +101,7 @@ function GpCurrencyViewExtendController($scope, PluginService, UIUtils, esSettin }); } - - -function GpCurrencyAbstractController($scope, csCurrency) { - 'ngInject'; - - $scope.loading = true; - $scope.formData = $scope.formData || {}; - $scope.height = undefined; - $scope.width = undefined; - $scope.maintainAspectRatio = true; - - $scope.enter = function (e, state) { - if ($scope.loading) { - - if (!$scope.formData.currency && state && state.stateParams && state.stateParams.currency) { // Currency parameter - $scope.formData.currency = state.stateParams.currency; - } - - // Make sure there is currency, or load it not - if (!$scope.formData.currency) { - return csCurrency.default() - .then(function (currency) { - $scope.formData.currency = currency ? currency.name : null; - return $scope.enter(e, state); - }); - } - - $scope.load() - .then(function () { - $scope.loading = false; - }); - } - }; - $scope.$on('$csExtension.enter', $scope.enter); - $scope.$on('$ionicParentView.enter', $scope.enter); - - // Allow to fixe size, form a template (e.g. in a 'ng-init' tag) - $scope.setSize = function(height, width, maintainAspectRatio) { - $scope.height = height; - $scope.width = width; - $scope.maintainAspectRatio = angular.isDefined(maintainAspectRatio) ? maintainAspectRatio : $scope.maintainAspectRatio; - }; - - // When parent view execute a refresh action - $scope.$on('csView.action.refresh', function(event, context) { - if (!context || context == 'currency') { - return $scope.load(); - } - }); - - $scope.load = function() { - // Should be override by subclasses - }; - -} - -function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $translate, $ionicPopover, gpData, $filter, csSettings) { +function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $translate, $ionicPopover, gpColor, gpData, $filter, csSettings) { 'ngInject'; // Initialize the super class and extend it. @@ -168,7 +110,6 @@ function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $tran $scope.formData.useRelative = angular.isDefined($scope.formData.useRelative) ? $scope.formData.useRelative : csSettings.data.useRelative; - $scope.scale = 'linear'; $scope.displayShareAxis = true; $scope.onUseRelativeChanged = function() { @@ -198,10 +139,11 @@ function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $tran .then(function(result) { var translations = result[0]; result = result[1]; - if (!result || !result.blocks) return; + if (!result || !result.times) return; + $scope.times = result.times; // Choose a date formatter, depending on the blocks period - var blocksPeriod = result.blocks[result.blocks.length-1].medianTime - result.blocks[0].medianTime; + var blocksPeriod = result.times[result.times.length-1] - result.times[0]; var formatDate; if (blocksPeriod < 15778800/* less than 6 months*/) { formatDate = $filter('formatDateShort'); @@ -247,14 +189,12 @@ function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $tran $scope.data = data; // Labels - $scope.labels = result.labels.reduce(function(res, time) { + $scope.labels = result.times.reduce(function(res, time) { return res.concat(formatDate(time)); }, []); // Colors - $scope.colors = result.blocks.reduce(function(res) { - return res.concat('rgba(17,193,243,0.5)'); - }, []); + $scope.colors = gpColor.scale.fix(result.times.length); // Options $scope.options = { @@ -264,6 +204,9 @@ function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $tran display: true, text: translations['GRAPH.CURRENCY.MONETARY_MASS_TITLE'] }, + legend: { + display: $scope.displayShareAxis + }, scales: { yAxes: [ { @@ -293,9 +236,10 @@ function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $tran $scope.datasetOverride = [ { yAxisID: 'y-axis-mass', - type: 'bar', + type: 'line', label: translations['GRAPH.CURRENCY.MONETARY_MASS_LABEL'], - hoverBackgroundColor: 'rgba(17,193,243,0.6)' + hoverBackgroundColor: gpColor.rgba.calm(0.6), + borderWidth: 1 }, { yAxisID: 'y-axis-mn', @@ -305,6 +249,7 @@ function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $tran showLine: true, borderColor: 'rgba(255,201,0,1)', borderWidth: 2, + backgroundColor: 'rgba(255,201,0,1)', pointBackgroundColor: 'rgba(255,201,0,1)', pointBorderColor: 'rgba(255,255,255,1)', pointHoverBackgroundColor: 'rgba(255,201,0,1)', @@ -320,44 +265,12 @@ function GpCurrencyMonetaryMassController($scope, $controller, $q, $state, $tran }; - $scope.showBlock = function(data, e, item) { + $scope.onChartClick = function(data, e, item) { if (!item) return; var number = $scope.blocks[item._index]; $state.go('app.view_block', {number: number}); }; - $scope.setScale = function(scale) { - $scope.hideActionsPopover(); - $scope.scale = scale; - - var format = $filter('formatInteger'); - - _.forEach($scope.options.scales.yAxes, function(yAxe) { - yAxe.type = scale; - yAxe.ticks = yAxe.ticks || {}; - if (scale == 'linear') { - yAxe.ticks.beginAtZero = true; - delete yAxe.ticks.min; - yAxe.ticks.callback = function(value) { - return format(value); - }; - } - else { - yAxe.ticks.min = 0; - delete yAxe.ticks.beginAtZero; - delete yAxe.ticks.callback; - yAxe.ticks.callback = function(value, index) { - if (!value) return; - //console.log(value + '->' + Math.log10(value)%1); - if (Math.log10(value)%1 === 0 || Math.log10(value/3)%1 === 0) { - return format(value); - } - return ''; - }; - } - }); - }; - /* -- Popover -- */ $scope.showActionsPopover = function(event) { @@ -403,10 +316,11 @@ function GpCurrencyDUController($scope, $q, $controller, $translate, gpData, $fi .then(function(result) { var translations = result[0]; result = result[1]; - if (!result || !result.blocks) return; + if (!result || !result.times) return; + $scope.times = result.times; // Choose a date formatter, depending on the blocks period - var blocksPeriod = result.blocks[result.blocks.length-1].medianTime - result.blocks[0].medianTime; + var blocksPeriod = result.times[result.times.length-1] - result.times[0]; var dateFilter; if (blocksPeriod < 15778800/* less than 6 months*/) { dateFilter = $filter('formatDateShort'); @@ -426,7 +340,7 @@ function GpCurrencyDUController($scope, $q, $controller, $translate, gpData, $fi ]; // Labels - $scope.labels = result.labels.reduce(function(res, time) { + $scope.labels = result.times.reduce(function(res, time) { return res.concat(dateFilter(time)); }, []); @@ -485,7 +399,7 @@ function GpCurrencyDUController($scope, $q, $controller, $translate, gpData, $fi } -function GpCurrencyMembersCountController($scope, $controller, $q, $state, $translate, gpData, $filter) { +function GpCurrencyMembersCountController($scope, $controller, $q, $state, $translate, gpColor, gpData, $filter) { 'ngInject'; // Initialize the super class and extend it. @@ -506,10 +420,12 @@ function GpCurrencyMembersCountController($scope, $controller, $q, $state, $tran .then(function(result) { var translations = result[0]; result = result[1]; - if (!result || !result.blocks) return; + + if (!result || !result.times) return; + $scope.times = result.times; // Choose a date formatter, depending on the blocks period - var blocksPeriod = result.blocks[result.blocks.length-1].medianTime - result.blocks[0].medianTime; + var blocksPeriod = result.times[result.blocks.length-1] - result.times[0].medianTime; var dateFormat; if (blocksPeriod < 15778800/* less than 6 months*/) { dateFormat = $filter('formatDateShort'); @@ -519,7 +435,7 @@ function GpCurrencyMembersCountController($scope, $controller, $q, $state, $tran } // Format time - $scope.labels = result.labels.reduce(function(res, time) { + $scope.labels = result.times.reduce(function(res, time) { return res.concat(dateFormat(time)); }, []); @@ -558,24 +474,17 @@ function GpCurrencyMembersCountController($scope, $controller, $q, $state, $tran ]; // Colors - $scope.colors = result.blocks.reduce(function(res) { - return res.concat('rgba(17,193,243,0.5)'); - }, []); - - // Keep times (need for click) - $scope.blockTimes = result.blocks.reduce(function(res, block) { - return res.concat(block.medianTime); - }, []); + $scope.colors = gpColor.scale.fix(result.blocks.length); }); }; - $scope.showBlock = function(data, e, item) { + $scope.onChartClick = function(data, e, item) { if (!item) return; if (!item._index) { $state.go('app.view_block', {number: 0}); return; } - var from = $scope.blockTimes[item._index-1]; + var from = $scope.times[item._index-1]; var to = moment.unix(from).utc().add(1, 'day').unix(); $state.go('app.blockchain_search', { q: '(_exists_:joiners OR _exists_:leavers OR _exists_:revoked OR _exists_:excluded) AND medianTime:>{0} AND medianTime:<={1}'.format(from, to) diff --git a/www/plugins/graph/js/controllers/network-controllers.js b/www/plugins/graph/js/controllers/network-controllers.js index 89e9ff08db1dd513ca03fb40a1d488c79f63376c..5610d4a28989bcb2b8cff2dc6e721558fb5c074a 100644 --- a/www/plugins/graph/js/controllers/network-controllers.js +++ b/www/plugins/graph/js/controllers/network-controllers.js @@ -59,7 +59,7 @@ function GpNetworkViewExtendController($scope, PluginService, esSettings) { }); } -function GpPeerViewExtendController($scope, $q, $timeout, PluginService, esSettings, csCurrency, gpData) { +function GpPeerViewExtendController($scope, $timeout, PluginService, esSettings, csCurrency, gpData) { 'ngInject'; $scope.extensionPoint = PluginService.extensions.points.current.get(); @@ -84,7 +84,7 @@ function GpPeerViewExtendController($scope, $q, $timeout, PluginService, esSetti // Make sure there is currency, or load if not if (!$scope.node.currency) { - return csCurrency.default() + return csCurrency.get() .then(function(currency) { $scope.node.currency = currency ? currency.name : null; return $scope.enter(e, state); @@ -133,7 +133,7 @@ function GpPeerStatsController($scope, $controller, csCurrency) { // Make sure there is currency, or load it not if (!$scope.currency) { - return csCurrency.default() + return csCurrency.get() .then(function(currency) { $scope.currency = currency ? currency.name : null; return $scope.enter(e, state); diff --git a/www/plugins/graph/js/plugin.js b/www/plugins/graph/js/plugin.js index e2d4965ea4e3b7f547e7fa709061be3bee4cbf48..baa6ca01317f4d4f46d539cc1ed0bbb2a5d299c2 100644 --- a/www/plugins/graph/js/plugin.js +++ b/www/plugins/graph/js/plugin.js @@ -3,8 +3,10 @@ angular.module('cesium.graph.plugin', [ // Services 'cesium.graph.services', // Controllers + 'cesium.graph.common.controllers', 'cesium.graph.blockchain.controllers', 'cesium.graph.network.controllers', - 'cesium.graph.currency.controllers' + 'cesium.graph.currency.controllers', + 'cesium.graph.account.controllers' ]) ; diff --git a/www/plugins/graph/js/services.js b/www/plugins/graph/js/services.js index 6cae46c6948f4d5cb752c78b6c91e03699951e61..ac325d0c5568336f71bb25e2010cb8a76e8af2be 100644 --- a/www/plugins/graph/js/services.js +++ b/www/plugins/graph/js/services.js @@ -1,6 +1,7 @@ angular.module('cesium.graph.services', [ // Services + 'cesium.graph.color.services', 'cesium.graph.data.services' ]) ; diff --git a/www/plugins/graph/js/services/color-services.js b/www/plugins/graph/js/services/color-services.js new file mode 100644 index 0000000000000000000000000000000000000000..d123e545ae14f463c9717faf8b0fd0f2531d8e4f --- /dev/null +++ b/www/plugins/graph/js/services/color-services.js @@ -0,0 +1,138 @@ +angular.module('cesium.graph.color.services', []) + + .factory('gpColor', function($rootScope) { + 'ngInject'; + + var + constants = { + css2Rgb: { + 'white': [255, 255, 255], + 'assertive': [239, 71, 58], // ok + 'calm': [17, 193, 243], // ok + 'positive': [56, 126, 245], // ok + 'balanced': [51, 205, 95], // ok + 'energized': [255, 201, 0], // ok + 'royal': [136, 106, 234], // ok + 'gray': [150, 150, 150], // ok + 'stable': [248, 248, 248] // ok + } + }, + exports = { + scale: {} + }; + + + /** + * Compute colors scale + * @param count + * @param opacity + * @param startColor + * @param startState + * @returns {Array} + */ + exports.scale.custom = function (count, opacity, startColor, startState) { + + function _state2side(state) { + switch (state) { + case 0: + return 0; + case 1: + return -1; + case 2: + return 0; + case 3: + return 1; + } + } + + // From [0,1] + opacity = opacity>0 && opacity|| '0.55'; + + var defaultStateSize = Math.round(count / 2.5/*=4 states max*/); + + // Start color [r,v,b] + var color = startColor ? angular.copy(startColor) : [255, 0, 0]; // Red + + // Colors state: 0=keep, 1=decrease, 2=keep, 3=increase + var states = startState ? angular.copy(startState) : [0, 2, 3]; // R=keep, V=keep, B=increase + + var steps = startColor ? [ + Math.round(255 / defaultStateSize), + Math.round(255 / defaultStateSize), + Math.round(255 / defaultStateSize) + ] : [ + Math.round((color[0] - 50) / defaultStateSize), + Math.round((255 - color[1]) / defaultStateSize), + Math.round((255 - color[2]) / defaultStateSize) + ]; + + + // Compute start sides (1=increase, 0=flat, -1=decrease) + var sides = [ + _state2side(states[0]), + _state2side(states[1]), + _state2side(states[2])]; + + // Use to detect when need to change a 'flat' state (when state = 0 or 2) + var stateCounters = [0, 0, 0]; + + var result = []; + for (var i = 0; i < count; i++) { + for (var j = 0; j < 3; j++) { + color[j] += sides[j] * steps[j]; + stateCounters[j]++; + // color has reach a limit + if (((color[j] <= 0 || color[j] >= 255) && sides[j] !== 0) || + (sides[j] === 0 && stateCounters[j] == defaultStateSize)) { + // Max sure not overflow limit + if (color[j] <= 0) { + color[j] = 0; + } + else if (color[j] >= 255) { + color[j] = 255; + } + // Go to the next state, in [0..3] + states[j] = (states[j] + 1) % 4; + + // Update side from this new state + sides[j] = _state2side(states[j]); + + // Reset state counter + stateCounters[j] = 0; + } + } + + // Add the color to result + result.push('rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + opacity + ')'); + + } + return result; + }; + + exports.scale.default = function () { + return exports.scale.custom(25); + }; + + /** + * Create a array with the given color + **/ + exports.scale.fix = function (length, color) { + return Array.apply(null, Array(length||25)) + .map(String.prototype.valueOf, color||exports.rgba.calm(0.5)); + }; + + // Create a function to generate a rgba string, from + exports.rgba = _.mapObject(constants.css2Rgb, function(rgbArray){ + var prefix = 'rgba(' + rgbArray.join(',') + ','; + return function(opacity){ + if (!opacity || opacity < 0) { + return 'rgb(' + rgbArray.join(',') + ')'; + } + return prefix + opacity + ')'; + }; + }); + + return exports; + }) + +; diff --git a/www/plugins/graph/js/services/data-services.js b/www/plugins/graph/js/services/data-services.js index ddd2d7733e9e3c70f410147fd9ae27e70e9dc312..b2c6677bb9a96cd91851506f551a6ec03be67ec2 100644 --- a/www/plugins/graph/js/services/data-services.js +++ b/www/plugins/graph/js/services/data-services.js @@ -1,119 +1,72 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es.http.services']) - .factory('gpData', function($rootScope, $q, $timeout, esHttp, BMA, csWot, csCache) { + .factory('gpData', function($rootScope, $q, $timeout, esHttp, BMA, csWot, csCache, csCurrency) { 'ngInject'; var currencyCache = csCache.get('gpData-currency-', csCache.constants.SHORT), exports = { node: {}, + wot: {}, blockchain: {}, - util: { - colors: {} - }, raw: { block: { search: esHttp.post('/:currency/block/_search') }, blockstat: { - search: esHttp.post('/:currency/blockstat/_search?pretty') + search: esHttp.post('/:currency/blockstat/_search') + }, + movement: { + search: esHttp.post('/:currency/movement/_search') + }, + user: { + event: esHttp.post('/user/event/_search?pretty') } }, regex: { } }; + function onCurrencyLoad(data, deferred) { + deferred = deferred || $q.defer(); + var currency = data.currencies[data.currencies.length-1]; - /** - * Compute colors scale - * @param count - * @param opacity - * @param startColor - * @param startState - * @returns {Array} - */ - exports.util.colors.custom = function(count, opacity, startColor, startState) { - - function _state2side(state) { - switch(state) { - case 0: - return 0; - case 1: - return -1; - case 2: - return 0; - case 3: - return 1; + if (currency.firstBlockTime) { + deferred.resolve(); + return deferred.promise; } + // Fill first block time value + BMA.blockchain.block({block: 0}) + .then(function(block) { + currency.firstBlockTime = block.medianTime; + deferred.resolve(); + }) + .catch(function(err) { + if (err && err.ucode == BMA.errorCodes.BLOCK_NOT_FOUND) { + currency.firstBlockTime = esHttp.date.now(); + deferred.resolve(); + } + deferred.reject(err); + }); + return deferred.promise; } - // From [0,1] - opacity = opacity || '0.55'; - - var defaultStateSize = Math.round(count / 2.5/*=4 states max*/); - - // Start color [r,v,b] - var color = startColor ? angular.copy(startColor) : [255,0,0]; // Red - - // Colors state: 0=keep, 1=decrease, 2=keep, 3=increase - var states = startState ? angular.copy(startState) : [0,2,3]; // R=keep, V=keep, B=increase - - var steps = startColor ? [ - Math.round(255 / defaultStateSize), - Math.round(255 / defaultStateSize), - Math.round(255 / defaultStateSize) - ] : [ - Math.round((color[0]-50) / defaultStateSize), - Math.round((255-color[1]) / defaultStateSize), - Math.round((255-color[2]) / defaultStateSize) - ]; - - - // Compute start sides (1=increase, 0=flat, -1=decrease) - var sides = [ - _state2side(states[0]), - _state2side(states[1]), - _state2side(states[2])]; - - // Use to detect when need to change a 'flat' state (when state = 0 or 2) - var stateCounters = [0,0,0]; - - var result = []; - for (var i = 0; i<count; i++) { - for (var j=0; j<3;j++) { - color[j] += sides[j] * steps[j]; - stateCounters[j]++; - // color has reach a limit - if (((color[j] <= 0 || color[j] >= 255) && sides[j] !== 0) || - (sides[j] === 0 && stateCounters[j] == defaultStateSize)) { - // Max sure not overflow limit - if (color[j] <= 0) { - color[j] = 0; - } - else if (color[j] >= 255) { - color[j] = 255; - } - // Go to the next state, in [0..3] - states[j] = (states[j] + 1) % 4; - - // Update side from this new state - sides[j] = _state2side(states[j]); - - // Reset state counter - stateCounters[j] = 0; - } - } - // Add the color to result - result.push('rgba(' + color[0] + ',' + color[1] + ',' + color[2] + ',' + opacity+')'); + function _powBase(amount, base) { + return base <= 0 ? amount : amount * Math.pow(10, base); + } - } - return result; - }; + function _initRangeOptions(options) { + options = options || {}; + options.maxRangeSize = options.maxRangeSize || 30; + options.defaultTotalRangeCount = options.defaultTotalRangeCount || options.maxRangeSize*2; - exports.util.colors.default = function() { - return exports.util.colors.custom(25); - }; + options.rangeDuration = options.rangeDuration || 'day'; + options.endTime = options.endTime || moment().utc().add(1, options.rangeDuration).unix(); + options.startTime = options.startTime || + moment.unix(options.endTime).utc().subtract(options.defaultTotalRangeCount, options.rangeDuration).unix(); + return options; + } /** * Graph: "blocks count by issuer" @@ -188,10 +141,6 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. } } - function _powBase(amount, base) { - return base <= 0 ? amount : amount * Math.pow(10, base); - } - var request = { query: { filtered: { @@ -255,7 +204,7 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. } } - result.labels = result.blocks.reduce(function(res, block){ + result.times = result.blocks.reduce(function(res, block){ return res.concat(block.medianTime); }, []); @@ -275,20 +224,14 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. */ exports.blockchain.txCount = function(currency, options) { - var maxRangeSize = 30; - var defaultTotalRangeCount = maxRangeSize*2; - - options = options || {}; - options.rangeDuration = options.rangeDuration || 'day'; - options.endTime = options.endTime || moment.unix(esHttp.date.now()).utc().add(1, options.rangeDuration).unix(); - options.startTime = options.startTime || - moment.unix(options.endTime).utc().subtract(defaultTotalRangeCount, options.rangeDuration).unix(); + options = _initRangeOptions(options); var jobs = []; var from = moment.unix(options.startTime).utc().startOf(options.rangeDuration); + var to = moment.unix(options.endTime).utc(); var ranges = []; - while(from.unix() < options.endTime) { + while(from.isBefore(to)) { ranges.push({ from: from.unix(), @@ -296,7 +239,7 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. }); // Do not exceed max range count - if (ranges.length == maxRangeSize) { + if (ranges.length == options.maxRangeSize) { var request = { size: 0, aggs: { @@ -329,8 +272,11 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. // prepare next loop ranges = []; - if (jobs.length < 10) { - + if (jobs.length == 10) { + console.error('Too many parallel jobs!'); + from = moment.unix(options.endTime).utc(); // stop while + } + else { jobs.push( exports.raw.blockstat.search(request, {currency: currency}) .then(function (res) { @@ -349,11 +295,6 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. }) ); } - else { - console.error('Too many call of txCount request ! '); - from = moment.unix(options.endTime).utc(); - } - } } @@ -366,23 +307,15 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. res = _.sortBy(res, 'from'); - var result = {}; - result.count = res.reduce(function(res, hit){ - return res.concat(hit.count); - }, []); - result.avgByBlock = res.reduce(function(res, hit){ - return res.concat(hit.avgByBlock); - }, []); - result.maxByBlock = res.reduce(function(res, hit){ - return res.concat(hit.maxByBlock); - }, []); - result.amount = res.reduce(function(res, hit){ - return res.concat(hit.amount/100); - }, []); - result.times = res.reduce(function(res, hit){ - return res.concat(hit.from); - }, []); - return result; + return { + count: _.pluck(res, 'count'), + avgByBlock: _.pluck(res, 'avgByBlock'), + maxByBlock: _.pluck(res, 'maxByBlock'), + amount: res.reduce(function(res, hit){ + return res.concat(hit.amount/100); + }, []), + times: _.pluck(res, 'from') + }; }); }; @@ -391,9 +324,7 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. * @param currency * @returns {*} */ - exports.node.blockCount = function(currency, pubkey, options) { - - options = options || {}; + exports.node.blockCount = function(currency, pubkey) { var request = { size: 0, @@ -406,6 +337,353 @@ angular.module('cesium.graph.data.services', ['cesium.wot.services', 'cesium.es. }); }; + + exports.raw.movement.getByRange = function(currency, pubkey, ranges) { + if (!pubkey) { + throw new Error('Missing \'pubkey\' argument!'); + } + var request = { + size: 0, + query: { + bool: { + should: [ + {term: {recipient: pubkey}}, + {term: {issuer: pubkey}} + ] + } + }, + aggs: { + tx: { + range: { + field: "medianTime", + ranges: ranges + }, + aggs: { + received: { + filter: {term: {recipient: pubkey}}, + aggs: { + received_stats: { + stats: { + field: "amount" + } + } + } + }, + sent: { + filter: {term: {issuer: pubkey}}, + aggs: { + sent_stats: { + stats: { + field: "amount" + } + } + } + } + } + + } + } + }; + + return exports.raw.movement.search(request, {currency: currency}) + .then(function(res) { + var aggs = res.aggregations; + if (!aggs.tx || !aggs.tx.buckets || !aggs.tx.buckets.length) return; + return (aggs.tx.buckets || []).reduce(function (res, agg) { + var sent = agg.sent.sent_stats; + var received = agg.received.received_stats; + return res.concat({ + from: agg.from, + to: agg.to, + sent: sent.sum ? (-sent.sum / 100) : 0, + received: received.sum ? (received.sum / 100) : 0 + }); + }, []); + }); + }; + + exports.raw.movement.getUds = function(currency, ranges, fromMapping) { + var request = { + size: 0, + query: { + bool: { + should: [ + {exists: {field: 'dividend'}} + ] + } + }, + aggs: { + ud: { + range: { + field: 'medianTime', + ranges: ranges + }, + aggs: { + ud_stats: { + stats: { + field: 'dividend' + } + }, + unitbase_stats: { + stats: { + field: 'unitbase' + } + } + } + } + } + }; + + return exports.raw.block.search(request, {currency: currency}) + .then(function(res) { + var aggs = res.aggregations; + if (!aggs.ud || !aggs.ud.buckets || !aggs.ud.buckets.length) return; + return (aggs.ud.buckets || []).reduce(function (res, agg) { + var from = fromMapping[agg.from]; + res[from] = _powBase(agg.ud_stats.sum, agg.unitbase_stats.min) / 100; + return res; + }, {}); + }); + }; + + /** + * Graph: "tx count" + * @param currency + * @returns {*} + */ + exports.blockchain.movement = function(currency, options) { + + options = _initRangeOptions(options); + options.withUD = angular.isDefined(options.withUD) ? options.withUD : true; + + var jobs = []; + + // If need and missing: load membership periods + if (options.withUD && !options.memberships) { + return exports.wot.memberships(options) + .then(function(res) { + options.memberships = res || []; + return exports.blockchain.movement(currency, options); + }); + } + + var from = moment.unix(options.startTime).utc().startOf(options.rangeDuration); + var to = moment.unix(options.endTime).utc(); + + var ranges = []; + var udRanges = []; + var udFromMapping = {}; + var memberships = angular.copy(options.memberships).reverse(); + var membership = memberships.pop(); + + function addRange(range) { + ranges.push(range); + var member = membership && membership.joinTime < range.to; + if (member) { + var udRange = { + from: Math.max(membership.joinTime, range.from), + to: Math.min(membership.leaveTime, range.to) + }; + udRanges.push(udRange); + udFromMapping[udRange.from] = range.from; + while (membership && (membership.leaveTime && membership.leaveTime < range.to)) { + membership = memberships.pop(); + } + } + } + + // Add a range to get TX before startTime + addRange({ + from: 0, + to: from.unix() + }); + + while(from.isBefore(to)) { + + addRange({ + from: from.unix(), + to: from.add(1, options.rangeDuration).unix() + }); + + // Do not exceed max range count + if ((!jobs.length && ranges.length == options.maxRangeSize+1) || (jobs.length && ranges.length == options.maxRangeSize)) { + + if (udRanges.length) { + jobs.push($q.all([ + exports.raw.movement.getUds(currency, udRanges, udFromMapping), + exports.raw.movement.getByRange(currency, options.pubkey, ranges) + ]) + .then(function(res){ + var udsMap = res[0]; + res = res[1]; + // fill UD + res.forEach(function(hit){ + hit.ud = (udsMap[hit.from]) || 0; + }); + return res; + })); + } + else { + jobs.push(exports.raw.movement.getByRange(currency, options.pubkey, ranges) + .then(function(res){ + // fill UD + res.forEach(function(hit){ + hit.ud = 0; + }); + return res; + })); + } + + // reset ranges for the next loop + ranges = []; + } + } // loop + + return $q.all(jobs) + .then(function(res) { + // concat all results + res = res.reduce(function(res, hits){ + if (!hits || !hits.length) return res; + return res.concat(hits); + }, []); + + if (!res.length) return; + + // Sort by 'from' field + res = _.sortBy(res, 'from'); + + // First item should be history (tx before startTime) + var history = res.splice(0,1)[0]; + var balance = history.received + history.sent + history.ud; + + return { + times: _.pluck(res, 'from'), + ud: _.pluck(res, 'ud'), + sent: _.pluck(res, 'sent'), + received: _.pluck(res, 'received'), + balance: res.reduce(function(res, hit){ + balance += hit.received + hit.sent + hit.ud; + return res.concat(balance); + }, []) + }; + }); + }; + + + /** + * Graph: "tx count" + * @param currency + * @returns {*} + */ + exports.wot.certifications = function(options) { + + options = _initRangeOptions(options); + + return csWot.load(options.pubkey) + .then(function(idty) { + if (!idty) return; + var res = {}; + _.forEach(idty.given_cert||[], function(cert){ + var truncTime = moment.unix(cert.time).utc().startOf(options.rangeDuration).unix(); + res[truncTime] = res[truncTime] || {time:truncTime,given:0,received:0}; + res[truncTime].given++; + }); + _.forEach(idty.received_cert||[], function(cert){ + var truncTime = moment.unix(cert.time).utc().startOf(options.rangeDuration).unix(); + res[truncTime] = res[truncTime] || {time:truncTime,given:0,received:0}; + res[truncTime].received++; + }); + + // Sort by time + res = _.sortBy(_.values(res), 'time'); + + // create final result + var result = { + times: _.pluck(res, 'time'), + deltaGiven: _.pluck(res, 'given'), + deltaReceived: _.pluck(res, 'received') + }; + var sum = 0; + result.given = result.deltaGiven.reduce(function(res, delta) { + sum += delta; + return res.concat(sum); + }, []); + sum = 0; + result.received = result.deltaReceived.reduce(function(res, delta) { + sum += delta; + return res.concat(sum); + }, []); + return result; + + }); + }; + + + exports.wot.memberships = function(options) { + + options = options || {}; + + // Get user events on membership state + var request = { + "size": 1000, + "query": { + "bool": { + "filter": [ + {"term": {"recipient" : options.pubkey }}, + {"terms": {"code" : ["MEMBER_JOIN","MEMBER_ACTIVE","MEMBER_LEAVE","MEMBER_EXCLUDE","MEMBER_REVOKE"] }} + ] + } + }, + "sort" : [ + { "time" : {"order" : "asc"}} + ], + _source: ["code", "time"] + }; + + return exports.raw.user.event(request) + + .then(function(res) { + if (!res.hits || !res.hits.total) return; + + // Compute member periods + var lastJoinTime; + var result = res.hits.hits.reduce(function(res, hit){ + var isMember = hit._source.code == 'MEMBER_JOIN' || hit._source.code == 'MEMBER_ACTIVE'; + // If join + if (isMember && !lastJoinTime) { + lastJoinTime = hit._source.time; + } + // If leave + else if (!isMember && lastJoinTime) { + // Add an entry + res = res.concat({ + joinTime: lastJoinTime, + leaveTime: hit._source.time + }); + lastJoinTime = 0; // reset + } + return res; + }, []); + + if (lastJoinTime) { + // Add last entry if need + result.push({ + joinTime: lastJoinTime, + leaveTime: moment().utc().unix() + }); + } + + return result; + }); + }; + + + // register listener +// csCurrency.api.data.on.load($rootScope, onCurrencyLoad, this); + return exports; }) + + + ; diff --git a/www/plugins/graph/templates/account/graph_balance.html b/www/plugins/graph/templates/account/graph_balance.html new file mode 100644 index 0000000000000000000000000000000000000000..7939969f663c4fb884147d4536794ceac7cef274 --- /dev/null +++ b/www/plugins/graph/templates/account/graph_balance.html @@ -0,0 +1,24 @@ + + <!-- button bar --> + <div class="button-bar-inline " + style="top: 33px; margin-top:-33px; position: relative;"> + <button + class="button button-stable button-clear no-padding-xs pull-right" + ng-click="showActionsPopover($event)"> + <i class="icon ion-navicon-round"></i> + </button> + </div> + + <div class="padding-left padding-right"> + <canvas id="account-balance" class="chart-bar" + height="{{height}}" width="{{width}}" + chart-data="data" + chart-dataset-override="datasetOverride" + chart-colors="colors" + chart-options="options" + chart-labels="labels" + chart-click="onChartClick"> + </canvas> + </div> + + <ng-include src="'plugins/graph/templates/common/graph_range_bar.html'"></ng-include> diff --git a/www/plugins/graph/templates/account/graph_certifications.html b/www/plugins/graph/templates/account/graph_certifications.html new file mode 100644 index 0000000000000000000000000000000000000000..6e4816e12f11abf0994697373a5ddc6d06e93b09 --- /dev/null +++ b/www/plugins/graph/templates/account/graph_certifications.html @@ -0,0 +1,12 @@ + + <div class="padding-left padding-right"> + <canvas id="account-certifications" class="chart-bar" + height="{{height}}" width="{{width}}" + chart-data="data" + chart-dataset-override="datasetOverride" + chart-colors="colors" + chart-options="options" + chart-labels="labels" + chart-click="onChartClick"> + </canvas> + </div> diff --git a/www/plugins/graph/templates/account/view_identity_extend.html b/www/plugins/graph/templates/account/view_identity_extend.html new file mode 100644 index 0000000000000000000000000000000000000000..65ff216dbc2b9417ebb031c94e8af2aa22f0f7cb --- /dev/null +++ b/www/plugins/graph/templates/account/view_identity_extend.html @@ -0,0 +1,9 @@ +<!-- Buttons section --> +<ng-if ng-if="extensionPoint === 'buttons'"> + + <button class="button button-stable button-small-padding icon ion-stats-bars" + ng-click="showIdentityStats()" + title="{{'GRAPH.ACCOUNT.BTN_SHOW_STATS' | translate}}"> + </button> + +</ng-if> diff --git a/www/plugins/graph/templates/account/view_stats.html b/www/plugins/graph/templates/account/view_stats.html new file mode 100644 index 0000000000000000000000000000000000000000..8b21f2ddcbda4652510d4a500904face8d79daec --- /dev/null +++ b/www/plugins/graph/templates/account/view_stats.html @@ -0,0 +1,39 @@ +<ion-view left-buttons="leftButtons" + cache-view="false"> + <ion-nav-title> + {{'GRAPH.ACCOUNT.TITLE' | translate}}{{id}} + </ion-nav-title> + + <ion-content scroll="true" class="no-padding"> + + <div class="center padding" ng-if="loading"> + <ion-spinner icon="android"></ion-spinner> + </div> + + <div class="list" ng-if="!loading"> + + <!-- - - - - Balance - - - - --> + <div class="item item-divider" translate> + GRAPH.ACCOUNT.BALANCE_DIVIDER + </div> + + <div class="item no-padding-xs" + ng-include="'plugins/graph/templates/account/graph_balance.html'" + ng-controller="GpAccountBalanceCtrl" + ng-init="setSize(350, 1000)"> + </div> + + <!-- - - - - WOT - - - - --> + <!--<div class="item item-divider" translate> + GRAPH.ACCOUNT.WOT_DIVIDER + </div> + + <div class="item no-padding-xs" + ng-include="'plugins/graph/templates/account/graph_certifications.html'" + ng-controller="GpAccountCertificationCtrl" + ng-init="setSize(350, 1000)"> + </div>--> + + </ion-content> + +</ion-view> diff --git a/www/plugins/graph/templates/account/view_wallet_tx_extend.html b/www/plugins/graph/templates/account/view_wallet_tx_extend.html new file mode 100644 index 0000000000000000000000000000000000000000..b8cd1c04adaf3a3830be19e9902bec5c9dc7ec7d --- /dev/null +++ b/www/plugins/graph/templates/account/view_wallet_tx_extend.html @@ -0,0 +1,9 @@ +<!-- Buttons section --> +<ng-if ng-if="extensionPoint === 'buttons'"> + + <button class="button button-stable button-small-padding icon ion-stats-bars" + ng-click="showWalletStats()" + title="{{'GRAPH.ACCOUNT.BTN_SHOW_STATS' | translate}}"> + </button> + +</ng-if> diff --git a/www/plugins/graph/templates/blockchain/graph_block_issuers.html b/www/plugins/graph/templates/blockchain/graph_block_issuers.html index 81ef7618a48a95fc40f396c33d63e60426977b49..c97178f57a54362cd3abb6ccf35b1a5ecb0d8e53 100644 --- a/www/plugins/graph/templates/blockchain/graph_block_issuers.html +++ b/www/plugins/graph/templates/blockchain/graph_block_issuers.html @@ -9,7 +9,7 @@ chart-labels="labels" chart-colors="colors" chart-options="barOptions" - chart-click="showBlockIssuer"> + chart-click="onChartClick"> </canvas> </div> @@ -19,7 +19,7 @@ chart-data="data" chart-labels="labels" chart-colors="colors" - chart-click="showBlockIssuer"> + chart-click="onChartClick"> </canvas> <div class="gray padding-top text-center"> diff --git a/www/plugins/graph/templates/blockchain/graph_tx_count.html b/www/plugins/graph/templates/blockchain/graph_tx_count.html index 93ea3734bd20d56567ec3feae2277451c91f640a..def80ecccdd8a248237444117435d4476ed015f0 100644 --- a/www/plugins/graph/templates/blockchain/graph_tx_count.html +++ b/www/plugins/graph/templates/blockchain/graph_tx_count.html @@ -4,7 +4,7 @@ style="top: 33px; margin-top:-33px; position: relative;"> <button class="button button-stable button-clear no-padding-xs pull-right" - ng-click="showTxActionsPopover($event)"> + ng-click="showActionsPopover($event)"> <i class="icon ion-navicon-round"></i> </button> </div> @@ -17,26 +17,8 @@ chart-colors="colors" chart-options="options" chart-labels="labels" - chart-click="showTxRange"> + chart-click="onChartClick"> </canvas> </div> - <div class="range range-positive no-padding-left no-padding-right"> - <a - class="button button-stable button-clear no-padding pull-left" - ng-click="loadPreviousTx()"> - <i class="icon ion-chevron-left"></i> - </a> - <input type="range" - ng-model="formData.timePct" - name="timePct" - min="0" max="100" - value="{{formData.timePct}}" - ng-change="onTxTimeChanged();" - ng-model-options="{ debounce: 250 }"> - <a - class="button button-stable button-clear no-padding pull-right" - ng-click="loadNextTx($event)"> - <i class="icon ion-chevron-right"></i> - </a> - </div> + <ng-include src="'plugins/graph/templates/common/graph_range_bar.html'"></ng-include> diff --git a/www/plugins/graph/templates/blockchain/popover_tx_actions.html b/www/plugins/graph/templates/blockchain/popover_tx_actions.html deleted file mode 100644 index 970a901d9fa6602797c1b0a03d18d280b2cb6aa3..0000000000000000000000000000000000000000 --- a/www/plugins/graph/templates/blockchain/popover_tx_actions.html +++ /dev/null @@ -1,31 +0,0 @@ -<ion-popover-view class="fit has-header"> - <ion-header-bar> - <h1 class="title" translate>COMMON.POPOVER_ACTIONS_TITLE</h1> - </ion-header-bar> - <ion-content scroll="false"> - <div class="list item-text-wrap"> - - <!-- duration: hour --> - <a class="item item-icon-right ink" - ng-click="setTxRangeDuration('hour')"> - <span ng-bind-html="'GRAPH.BLOCKCHAIN.TX_RANGE_DURATION.HOUR' | translate"></span> - <i class="icon ion-ios-checkmark-empty" ng-show="txOptions.rangeDuration=='hour'"></i> - </a> - - <!-- duration: day --> - <a class="item item-icon-right ink" - ng-click="setTxRangeDuration('day')"> - <span ng-bind-html="'GRAPH.BLOCKCHAIN.TX_RANGE_DURATION.DAY' | translate"></span> - <i class="icon ion-ios-checkmark-empty" ng-show="txOptions.rangeDuration=='day'"></i> - </a> - - <!-- duration: month --> - <a class="item item-icon-right ink" - ng-click="setTxRangeDuration('month')"> - <span ng-bind-html="'GRAPH.BLOCKCHAIN.TX_RANGE_DURATION.MONTH' | translate"></span> - <i class="icon ion-ios-checkmark-empty" ng-show="txOptions.rangeDuration=='month'"></i> - </a> - - </div> - </ion-content> -</ion-popover-view> diff --git a/www/plugins/graph/templates/common/graph_range_bar.html b/www/plugins/graph/templates/common/graph_range_bar.html new file mode 100644 index 0000000000000000000000000000000000000000..3a7c75a0648e5a6707e313eec8bfbdc8d40a74a0 --- /dev/null +++ b/www/plugins/graph/templates/common/graph_range_bar.html @@ -0,0 +1,20 @@ + + <div class="range range-positive no-padding-left no-padding-right"> + <a + class="button button-stable button-clear no-padding pull-left" + ng-click="goPreviousRange($event)"> + <i class="icon ion-chevron-left"></i> + </a> + <input type="range" + ng-model="formData.timePct" + name="timePct" + min="0" max="100" + value="{{formData.timePct}}" + ng-change="onRangeChanged();" + ng-model-options="{ debounce: 250 }"> + <a + class="button button-stable button-clear no-padding pull-right" + ng-click="goNextRange($event)"> + <i class="icon ion-chevron-right"></i> + </a> + </div> diff --git a/www/plugins/graph/templates/common/popover_range_actions.html b/www/plugins/graph/templates/common/popover_range_actions.html new file mode 100644 index 0000000000000000000000000000000000000000..31745aa69cad20d2ea164c0f3f4ba60b945c5915 --- /dev/null +++ b/www/plugins/graph/templates/common/popover_range_actions.html @@ -0,0 +1,43 @@ +<ion-popover-view class="has-header popover-graph-currency"> + <ion-header-bar> + <h1 class="title" translate>COMMON.POPOVER_ACTIONS_TITLE</h1> + </ion-header-bar> + <ion-content scroll="false"> + <div class="list item-text-wrap"> + + <!-- scale --> + <a class="item item-icon-left ink" + ng-click="toggleScale()"> + <i class="icon ion-ios-checkmark-empty" ng-show="scale=='logarithmic'"></i> + <span ng-bind-html="'GRAPH.COMMON.LOGARITHMIC_SCALE' | translate"></span> + </a> + + <!-- duration divider --> + <div class="item item-divider"> + {{'GRAPH.BLOCKCHAIN.TX_RANGE_DURATION_DIVIDER'|translate}} + </div> + + <!-- duration: hour --> + <a class="item item-icon-left ink" + ng-click="setRangeDuration('hour')"> + <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration=='hour'"></i> + <span ng-bind-html="'GRAPH.BLOCKCHAIN.TX_RANGE_DURATION.HOUR' | translate"></span> + </a> + + <!-- duration: day --> + <a class="item item-icon-left ink" + ng-click="setRangeDuration('day')"> + <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration=='day'"></i> + <span ng-bind-html="'GRAPH.BLOCKCHAIN.TX_RANGE_DURATION.DAY' | translate"></span> + </a> + + <!-- duration: month --> + <a class="item item-icon-left ink" + ng-click="setRangeDuration('month')"> + <i class="icon ion-ios-checkmark-empty" ng-show="formData.rangeDuration=='month'"></i> + <span ng-bind-html="'GRAPH.BLOCKCHAIN.TX_RANGE_DURATION.MONTH' | translate"></span> + </a> + + </div> + </ion-content> +</ion-popover-view> diff --git a/www/plugins/graph/templates/currency/graph_members_count.html b/www/plugins/graph/templates/currency/graph_members_count.html index 2b26b0a4cb9760a31ff528350ce430bb7aeb6620..0b261fb47c03da64debe4bf15307864060e163ee 100644 --- a/www/plugins/graph/templates/currency/graph_members_count.html +++ b/www/plugins/graph/templates/currency/graph_members_count.html @@ -6,5 +6,5 @@ chart-colors="colors" chart-options="options" chart-dataset-override="datasetOverride" - chart-click="showBlock"> + chart-click="onChartClick"> </canvas> diff --git a/www/plugins/graph/templates/currency/graph_monetary_mass.html b/www/plugins/graph/templates/currency/graph_monetary_mass.html index 2b34a02ac999b5c537c9ce34b5e15ea3bd8dd750..894e82b841d4b7375a9ca917d3d88d845ed95797 100644 --- a/www/plugins/graph/templates/currency/graph_monetary_mass.html +++ b/www/plugins/graph/templates/currency/graph_monetary_mass.html @@ -17,5 +17,5 @@ chart-colors="colors" chart-dataset-override="datasetOverride" chart-options="options" - chart-click="showBlock"> + chart-click="onChartClick"> </canvas> diff --git a/www/plugins/rml9/i18n/locale-fr-FR.json b/www/plugins/rml9/i18n/locale-fr-FR.json new file mode 100644 index 0000000000000000000000000000000000000000..0dfad640d78c19e4bd81e0125c1c7978343d6c19 --- /dev/null +++ b/www/plugins/rml9/i18n/locale-fr-FR.json @@ -0,0 +1,8 @@ +{ + "RML9": { + "BTN_EXPORT": "Exporter les transactions", + "VIEW": { + "TITLE": "RML9" + } + } +} diff --git a/www/plugins/rml9/plugin.js b/www/plugins/rml9/plugin.js new file mode 100644 index 0000000000000000000000000000000000000000..d8766423fc546728d9c06729fc30c38767d301ea --- /dev/null +++ b/www/plugins/rml9/plugin.js @@ -0,0 +1,106 @@ + +angular.module('cesium.rml9.plugin', ['cesium.services']) + + .config(function($stateProvider, PluginServiceProvider, csConfig) { + 'ngInject'; + + var enable = csConfig.plugins && csConfig.plugins.rml9; + if (enable) { + + // Extend existing view + PluginServiceProvider + .extendState('app.view_wallet_tx', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/button.html", + controller: 'Rml9ButtonCtrl' + } + } + }) + + .extendState('app.wot_identity', { + points: { + 'buttons': { + templateUrl: "plugins/rml9/templates/button.html", + controller: 'Rml9ButtonCtrl' + } + } + }) + ; + + // Add new view + $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 la page 'Mes opérations' + */ + .controller('Rml9ButtonCtrl', function($scope, $state, PluginService, FileSaver, BMA, csWallet) { + 'ngInject'; + + $scope.extensionPoint = PluginService.extensions.points.current.get(); + + // Manage click on the export button + $scope.onButtonClick = function() { + console.debug("[RML9] calling onButtonClick()"); + + 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'); + }); + }; + }) + + + /** + * 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/button.html b/www/plugins/rml9/templates/button.html new file mode 100644 index 0000000000000000000000000000000000000000..43dd317b43d6c511d2bea4fffe38735da8b1f798 --- /dev/null +++ b/www/plugins/rml9/templates/button.html @@ -0,0 +1,9 @@ +<!-- Buttons section --> +<ng-if ng-if="extensionPoint === 'buttons'"> + + <button class="button button-stable button-small-padding icon ion-android-archive" + ng-click="onButtonClick()" + title="{{'RML9.BTN_EXPORT' | translate}}"> + </button> + +</ng-if> diff --git a/www/plugins/rml9/templates/view.html b/www/plugins/rml9/templates/view.html new file mode 100644 index 0000000000000000000000000000000000000000..665f08658b7493e329198117c1ba174d04bcafe8 --- /dev/null +++ b/www/plugins/rml9/templates/view.html @@ -0,0 +1,23 @@ +<ion-view left-buttons="leftButtons"> + + <ion-nav-title> + {{'RML9.VIEW.TITLE' | translate}} + </ion-nav-title> + + <ion-content> + + <!-- items container --> + <div class="list"> + + <!-- an item --> + <div class="item" ng-repeat="item in items"> + <h3>{{item.time|formatDate}}</h3> + <h4>{{item.issuer|formatPubkey}}</h4> + <div class="badge">{{item.amount}}</div> + </div> + + </div> + + </ion-content> + +</ion-view> diff --git a/www/templates/currency/view_currency_lg.html b/www/templates/currency/view_currency_lg.html index 5b2d96e74ac60d8ff4efcdb124e54e97cf4884f0..227d8a3b9ec19ca19c4166d5e14110f1e8d45383 100644 --- a/www/templates/currency/view_currency_lg.html +++ b/www/templates/currency/view_currency_lg.html @@ -1,11 +1,8 @@ <ion-view left-buttons="leftButtons" cache-view="false"> <ion-nav-title bind-notifier="{ rebind:formData.useRelative }"> - <span> - {{'CURRENCY.VIEW.TITLE' | translate}} {{id}} - </span> <span ng-if="!loading"> - {{formData.currency}} (<span ng-bind-html=":rebind:formData.currency | currencySymbol:formData.useRelative"></span>) + {{'CURRENCY.VIEW.TITLE' | translate}} {{formData.currency|abbreviate}} </span> </ion-nav-title> @@ -21,6 +18,8 @@ <!-- Buttons bar--> <div class="hidden-xs hidden-sm padding text-center"> + + <button class="button button-stable icon-right ink" ng-if="formData.licenseUrl" ng-click="showLicenseModal()"> @@ -36,7 +35,16 @@ <button class="button button-stable button-small-padding icon ion-android-more-vertical ink" ng-click="showActionsPopover($event)"> </button> - + + </div> + + <div class="item item-text-wrap no-border no-padding pull-left" + ng-if="!loading"> + <div class="item-icon-left card padding stable-900-bg"> + <i class="icon ion-help-circled calm"></i> + <div class="item-icon-left-padding" trust-as-html=":rebind:'CURRENCY.VIEW.CURRENCY_SHORT_DESCRIPTION'|translate:formData"> + </div> + </div> </div> <div class="row responsive-sm"> diff --git a/www/templates/help/help.html b/www/templates/help/help.html index 2926d62dfe42eed49b3132ab3f8d5c9dc1437b28..0eaf332633d0ba4a67c408c391a3f2eb8846527b 100644 --- a/www/templates/help/help.html +++ b/www/templates/help/help.html @@ -3,20 +3,23 @@ <h2 translate>HELP.JOIN.SECTION</h2> <a name="join-salt"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>LOGIN.SALT</div> + <div class="row responsive-sm" + ng-class="itemsClass['join-salt']"> + <div class="col col-20" translate>LOGIN.SALT</div> <div class="col" translate>HELP.JOIN.SALT</div> </div> <a name="join-password"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>LOGIN.PASSWORD</div> + <div class="row responsive-sm" + ng-class="itemsClass['join-password']"> + <div class="col col-20" translate>LOGIN.PASSWORD</div> <div class="col" translate>HELP.JOIN.PASSWORD</div> </div> <a name="join-pseudo"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>ACCOUNT.NEW.PSEUDO</div> + <div class="row responsive-sm" + ng-class="itemsClass['join-pseudo']"> + <div class="col col-20" translate>ACCOUNT.NEW.PSEUDO</div> <div class="col" translate>HELP.JOIN.PSEUDO</div> </div> @@ -24,33 +27,37 @@ <h2 translate>HELP.GLOSSARY.SECTION</h2> <a name="pubkey"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>COMMON.PUBKEY</div> + <div class="row responsive-sm" + ng-class="itemsClass.pubkey"> + <div class="col col-20" translate>COMMON.PUBKEY</div> <div class="col" translate>HELP.GLOSSARY.PUBKEY_DEF</div> </div> <a name="blockchain"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>HELP.GLOSSARY.BLOCKCHAIN</div> + <div class="row responsive-sm" + ng-class="itemsClass.blockchain"> + <div class="col col-20" translate>HELP.GLOSSARY.BLOCKCHAIN</div> <div class="col" translate>HELP.GLOSSARY.BLOCKCHAIN_DEF</div> </div> <a name="universal_dividend"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>COMMON.UNIVERSAL_DIVIDEND</div> + <a name="ud"></a> + <div class="row responsive-sm" + ng-class="itemsClass.ud"> + <div class="col col-20" translate>COMMON.UNIVERSAL_DIVIDEND</div> <div class="col" translate>HELP.GLOSSARY.UNIVERSAL_DIVIDEND_DEF</div> </div> <a name="member"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>HELP.GLOSSARY.MEMBER</div> + <div class="row responsive-sm" + ng-class="itemsClass.member"> + <div class="col col-20" translate>HELP.GLOSSARY.MEMBER</div> <div class="col" translate>HELP.GLOSSARY.MEMBER_DEF</div> </div> - <a name="currency_rules"></a> - <div class="row responsive-sm"> - <div class="col col-20 gray" translate>HELP.GLOSSARY.CURRENCY_RULES</div> + <div class="row responsive-sm" + ng-class="itemsClass.currency_rules"> + <div class="col col-20" translate>HELP.GLOSSARY.CURRENCY_RULES</div> <div class="col" translate>HELP.GLOSSARY.CURRENCY_RULES_DEF</div> </div> - diff --git a/www/templates/help/modal_help.html b/www/templates/help/modal_help.html index a0cfe9a64cd7bfccab12ea5ba4769e6727f56670..d0361b0951dac54a9d211261085aba3d7e13d7c0 100644 --- a/www/templates/help/modal_help.html +++ b/www/templates/help/modal_help.html @@ -1,4 +1,4 @@ -<ion-view class="modal slide-in-up ng-enter active ng-enter-active"> +<ion-modal-view class="modal-full-height modal-help"> <ion-header-bar class="bar-positive"> <button class="button button-clear" ng-click="closeModal()" translate>COMMON.BTN_CLOSE @@ -7,9 +7,11 @@ <h1 class="title" translate>HELP.TITLE</h1> </ion-header-bar> - <ion-content scroll="true" class="padding"> + <ion-content croll="false" class="padding"> - <ng-include src="'templates/help/help.html'"></ng-include> + <div ng-class="listClass"> + <ng-include src="'templates/help/help.html'"></ng-include> + </div> <div class="padding hidden-xs text-center"> <button class="button button-positive ink" type="submit" @@ -19,4 +21,4 @@ </div> </ion-content> -</ion-view> +</ion-modal-view> diff --git a/www/templates/home/home.html b/www/templates/home/home.html index 1f2175f0b8c8ef97486611c0eb70131edd58984a..a9795e2d3ae271bd3f7e81923f01cf6e3e735cb5 100644 --- a/www/templates/home/home.html +++ b/www/templates/home/home.html @@ -9,37 +9,30 @@ <h4 class="hidden-xs" translate>HOME.MESSAGE</h4> <h4 class="visible-xs" translate>HOME.MESSAGE_SHORT</h4> + <!-- Help tour (NOT ready yet for small device) --> <div class="center"> - <br class="hidden-xs"/> - - <!-- Help tour (NOT ready yet for small device) --> <button type="button" class="button button-block button-stable button-raised icon-left icon ion-easel ink-dark hidden-xs" ng-click="startHelpTour()" > {{'COMMON.BTN_HELP_TOUR'|translate}} </button> - <!-- CURRENCY --> - <button type="button" - class="button button-block button-stable button-raised icon icon-left ion-ios-world-outline ink-dark" - ng-if="options.registry.enable" - ui-sref="app.currency_lookup" translate>HOME.BTN_CURRENCIES</button> + <!-- Currency--> <button type="button" - class="button button-block button-stable button-raised icon icon-left ion-ios-world-outline ink-dark hidden-sm hidden-xs" - ng-if="!options.registry || !options.registry.enable" - ui-sref="app.currency" translate>HOME.BTN_CURRENCY</button> + class="item button button-block button-stable button-raised icon icon-left ion-ios-world-outline ink-dark hidden-sm hidden-xs" + ui-sref="app.currency">{{'HOME.BTN_CURRENCY'|translate:$root.currency }}</button> <button type="button" - class="button button-block button-positive button-raised icon icon-left ion-locked ink-dark" + class="item button button-block button-positive button-raised icon icon-left ion-locked ink-dark" ng-click="loginAndGo('app.view_wallet')" ng-show="!login" translate>COMMON.BTN_LOGIN</button> <button type="button" - class="button button-block button-positive button-raised icon icon-left ion-person ink-dark" + class="item button button-block button-positive button-raised icon icon-left ion-person ink-dark" ui-sref="app.view_wallet" ng-show="login" translate>MENU.ACCOUNT</button> <button type="button" - class="button button-block button-positive button-raised icon icon-left ion-card ink-dark visible-xs" + class="item button button-block button-positive button-raised icon icon-left ion-card ink-dark visible-xs" ui-sref="app.view_wallet_tx" ng-show="login" translate>MENU.TRANSACTIONS</button> <br class="visible-xs visible-sm"/> @@ -80,9 +73,9 @@ <!-- about --> <a href="#" ng-click="showAboutModal()" translate>HOME.BTN_ABOUT</a> </div> - </div> - + </div> </ion-content> </ion-view> + diff --git a/www/templates/join/modal_choose_account_type.html b/www/templates/join/modal_choose_account_type.html index d2203fbf0e313c1ab0f5ecdec2cd39626b16e271..1de048038a6aad79caa8c7d54b61a7cedcf11b2b 100644 --- a/www/templates/join/modal_choose_account_type.html +++ b/www/templates/join/modal_choose_account_type.html @@ -10,15 +10,12 @@ ng-click="slidePrev()" ng-if="slides.slider.activeIndex"> </button> - <button class="button button-icon button-clear icon ion-ios-help-outline visible-xs" - ng-if="slides.slider.activeIndex > 1 && !isLastSlide" - ng-click="showHelpModal()"></button> <h1 class="title" translate>ACCOUNT.NEW.TITLE</h1> <button class="button button-clear icon-right visible-xs" - ng-if="!isLastSlide && slides.slider.activeIndex > 1" - ng-click="doNext()"> + ng-if="slides.slider.activeIndex === 0" + ng-click="slideNext()"> <span translate>COMMON.BTN_NEXT</span> <i class="icon ion-ios-arrow-right"></i> </button> @@ -30,25 +27,50 @@ <!-- STEP 1: currency --> <ion-slide-page> <ion-content class="has-header padding"> - <h3 translate>ACCOUNT.NEW.SLIDE_1_TITLE</h3> - <div class="list"> - <div class="item text-center" ng-if="loading"> - <ion-spinner class="icon" icon="android"></ion-spinner> - </div> - <div ng-repeat="currency in currencies" - ng-if="!loading" - class="item card item-icon-right stable-bg padding ink" - ng-class="{ selected: selectedCurrency == currency }" - ng-click="selectCurrency(currency.name, true)"> - <h2>{{::currency.name}}</h2> - <h4 class="gray">{{::currency.peer.server}}</h4> - <i class="icon dark ion-ios-arrow-right"></i> + <div class="text-center" ng-if="loading"> + <ion-spinner class="icon" icon="android"></ion-spinner> + </div> + + <div ng-if="!loading"> + + + <p ng-bind-html="'ACCOUNT.NEW.INTRO_WARNING_TIME'|translate:currency"></p> + + <div class="row responsive-sm"> + <div class="col"> + <div class="item card item-icon-left padding item-text-wrap stable-bg"> + <i class="icon ion-android-warning assertive"></i> + + <p class="item-content item-icon-left-padding "> + <span class="dark" translate>ACCOUNT.NEW.INTRO_WARNING_SECURITY</span><br/> + <small translate>ACCOUNT.NEW.INTRO_WARNING_SECURITY_HELP</small> + </p> + </div> + </div> + + <div class="col"> + <div class="item card item-icon-left padding item-text-wrap stable-bg"> + <i class="icon ion-information-circled positive"></i> + <p class="item-content item-icon-left-padding "> + <span class="dark" trust-as-html="'ACCOUNT.NEW.REGISTRATION_NODE'|translate:currency.node"></span><br/> + <small trust-as-html="'ACCOUNT.NEW.REGISTRATION_NODE_HELP'|translate:currency.node"></small> + </p> + </div> + </div> </div> + + </div> + <p class="hidden-xs hidden-sm" ng-bind-html="'ACCOUNT.NEW.INTRO_HELP'|translate"></p> + <div class="padding hidden-xs text-right"> <button class="button button-clear button-dark ink" ng-click="closeModal()" type="button" translate>COMMON.BTN_CANCEL </button> + <button class="button button-positive icon-right ion-chevron-right ink" ng-click="slideNext()" + ng-disabled="loading" type="button" translate> + COMMON.BTN_START + </button> </div> </ion-content> </ion-slide-page> @@ -56,7 +78,7 @@ <!-- STEP 2: account type --> <ion-slide-page> <ion-content class="has-header padding"> - <h3 translate>ACCOUNT.NEW.SLIDE_2_TITLE</h3> + <p translate>ACCOUNT.NEW.SELECT_ACCOUNT_TYPE</p> <div class="list"> <!-- member account --> <div class="item item-complex card stable-bg item-icon-left item-icon-right ink" @@ -64,7 +86,7 @@ <div class="item-content item-text-wrap"> <i class="item-image icon dark ion-person"></i> <h2 translate>ACCOUNT.NEW.MEMBER_ACCOUNT</h2> - <h4 class="gray" translate>ACCOUNT.NEW.MEMBER_ACCOUNT_HELP</h4> + <h4 class="gray" ng-bind-html="'ACCOUNT.NEW.MEMBER_ACCOUNT_HELP'|translate:currency"></h4> <i class="icon dark ion-ios-arrow-right"></i> </div> </div> diff --git a/www/templates/join/modal_join_member.html b/www/templates/join/modal_join_member.html index c4ab0c157c9f0ccec1f393484035eb27213a1932..b2ad690fe6d24f02d9174b42b997e6b548a172b3 100644 --- a/www/templates/join/modal_join_member.html +++ b/www/templates/join/modal_join_member.html @@ -44,7 +44,7 @@ <ion-slides options="slides.options" slider="slides.slider" ng-init="console.log('START SLIDE');"> <!-- STEP 1: license --> - <ion-slide-page> + <ion-slide-page ng-if="licenseFileUrl"> <ion-content class="has-header" scroll="false"> <div class="padding" translate>ACCOUNT.NEW.INFO_LICENSE</div> @@ -75,8 +75,8 @@ <ion-content class="has-header" scroll="false"> <form name="pseudoForm" novalidate="" ng-submit="doNext('pseudoForm')"> - <div class="item item-text-wrap text-center padding hidden-xs" > - <a class="pull-right icon-help" ng-click="showHelpModal('join-pseudo')"></a> + <div class="item item-text-wrap text-center padding" > + <a class="pull-right icon-help hidden-xs" ng-click="showHelpModal('join-pseudo')"></a> <span translate>ACCOUNT.NEW.PSEUDO_WARNING</span> </div> @@ -87,7 +87,7 @@ <div class="item item-input" ng-class="{'item-input-error': (pseudoForm.$submitted && pseudoForm.pseudo.$invalid) || (uiAlreadyUsed && formData.pseudo)}"> <span class="input-label" translate>ACCOUNT.NEW.PSEUDO</span> - <input name="pseudo" type="text" placeholder="{{'ACCOUNT.NEW.PSEUDO_HELP' | translate}}" + <input id="pseudo" name="pseudo" type="text" placeholder="{{'ACCOUNT.NEW.PSEUDO_HELP' | translate}}" ng-model="formData.pseudo" ng-minlength="3" ng-maxlength="100" @@ -113,18 +113,23 @@ </div> <!-- Show if valid pseudo--> - <div class="item item-button-right left" ng-if="formData.computing && formData.pseudo"> - <ion-spinner icon="android"></ion-spinner> - </div> - <div class="text-right" ng-if="!formData.computing"> - <div class="form-error balanced" ng-if="!uiAlreadyUsed && formData.pseudo"> - <i class="icon ion-checkmark balanced"></i> - <span translate>ACCOUNT.NEW.PSEUDO_AVAILABLE</span> - </div> - <div class="form-error" ng-if="uiAlreadyUsed && formData.pseudo"> - <i class="icon ion-close-circled assertive"></i> - <span translate>ACCOUNT.NEW.PSEUDO_NOT_AVAILABLE</span> + <div class="text-right" style="min-height: 18px;"> + <div class="form-error gray" ng-if="formData.computing && formData.pseudo"> + <ion-spinner class="icon ion-spinner-small" icon="android" ng-if="formData.computing && formData.pseudo"></ion-spinner> + <span translate>ACCOUNT.NEW.CHECKING_PSEUDO</span> </div> + + <ng-if ng-if="!formData.computing && formData.pseudo"> + <div class="form-error balanced" ng-if="!uiAlreadyUsed "> + <i class="icon ion-checkmark balanced"></i> + <span translate>ACCOUNT.NEW.PSEUDO_AVAILABLE</span> + </div> + <div class="form-error" ng-if="uiAlreadyUsed"> + <i class="icon ion-close-circled assertive"></i> + <span translate>ACCOUNT.NEW.PSEUDO_NOT_AVAILABLE</span> + </div> + </ng-if> + </div> <div class="padding hidden-xs text-right"> @@ -326,11 +331,9 @@ <ion-content class="has-header" scroll="false"> <!-- Computing --> - <div class="padding text-center" ng-if="formData.computing"> <ion-spinner icon="android"></ion-spinner> </div> - <!-- ng-if="formData.computing" --> <!-- Account available --> <div ng-if="accountAvailable && !formData.computing"> @@ -339,14 +342,8 @@ <div class="list"> <ion-item class="item item-text-wrap item-border"> - <div class="dark pull-right padding-right" ng-if="formData.computing"> - <ion-spinner icon="android"></ion-spinner> - </div> <span class="input-label" translate>COMMON.PUBKEY</span> - <span class="gray text-no-wrap" ng-if="formData.computing" translate> - ACCOUNT.NEW.COMPUTING_PUBKEY - </span> - <span class="gray text-no-wrap" ng-if="formData.pubkey"> + <span class="gray text-no-wrap" copy-on-click="{{formData.pubkey}}"> {{formData.pubkey}} </span> </ion-item> @@ -367,20 +364,14 @@ <ion-item class="item-icon-left item-text-wrap text-center"> <i class="icon ion-minus-circled assertive"></i> - <span id="modal-license" translate>ERROR.EXISTING_ACCOUNT</span> + <span id="modal-license" trust-as-html="'ERROR.EXISTING_ACCOUNT'|translate"></span> </ion-item> <div class="list"> <ion-item class="item item-text-wrap item-border"> - <div class="dark pull-right padding-right" ng-if="formData.computing"> - <ion-spinner icon="android"></ion-spinner> - </div> - <span class="gray text-no-wrap" ng-if="formData.computing" translate> - ACCOUNT.NEW.COMPUTING_PUBKEY - </span> <div class="padding text-center"> - <span class="gray text-no-wrap" ng-if="formData.pubkey"> + <span class="gray text-no-wrap"> {{formData.pubkey}} </span> </div> diff --git a/www/templates/network/view_peer.html b/www/templates/network/view_peer.html index c8b1ec992c509ffb75a0b90bf8eea2954d013e22..2f52296f5794763aa23e78e2f423ecfd48f1bdb3 100644 --- a/www/templates/network/view_peer.html +++ b/www/templates/network/view_peer.html @@ -3,9 +3,9 @@ <span translate>PEER.VIEW.TITLE</span> </ion-nav-title> - <ion-content class="has-header padding" scroll="true"> + <ion-content class="has-header" scroll="true"> - <div class="row"> + <div class="row no-padding"> <div class="col col-20 hidden-xs hidden-sm"> </div> diff --git a/www/templates/settings/popup_node.html b/www/templates/settings/popup_node.html index 33e23fdf1b1093b39cc5d5397cf80bd4fe1c4665..77756efaae19ef1ff2d745416b72cda93e7e62a0 100644 --- a/www/templates/settings/popup_node.html +++ b/www/templates/settings/popup_node.html @@ -22,12 +22,13 @@ </div> </div> - <small ng-bind-html="'SETTINGS.POPUP_PEER.BTN_SHOW_LIST_HELP' | translate"></small> - - <button class="button button-positive button-clear positive button-outline button-full button-small-padding icon-left ink" ng-click="showNodeList()"> - <i class="icon ion-wifi "></i> - {{'SETTINGS.POPUP_PEER.BTN_SHOW_LIST' | translate}} - </button> + <button type="submit" class="hide"></button> </form> +<small ng-bind-html="'SETTINGS.POPUP_PEER.BTN_SHOW_LIST_HELP' | translate"></small> +<button class="button button-positive button-clear positive button-outline button-full button-small-padding icon-left ink" ng-click="showNodeList()"> + <i class="icon ion-wifi "></i> + {{'SETTINGS.POPUP_PEER.BTN_SHOW_LIST' | translate}} +</button> + diff --git a/www/templates/wallet/view_wallet_tx.html b/www/templates/wallet/view_wallet_tx.html index 4de31b92dea0d911fa7c0f2a9f297ade2e0c5bcc..a2be1f21f9dc2069635edfc3f5f7b35bb99587d9 100644 --- a/www/templates/wallet/view_wallet_tx.html +++ b/www/templates/wallet/view_wallet_tx.html @@ -5,6 +5,8 @@ </ion-nav-title> <ion-nav-buttons side="secondary"> + <cs-extension-point name="nav-buttons"></cs-extension-point> + <button class="button button-icon button-clear icon ion-loop visible-xs visible-sm" ng-click="doUpdate()"> </button> </ion-nav-buttons> @@ -40,6 +42,8 @@ title="{{'COMMON.BTN_REFRESH' | translate}}"> </button> + <cs-extension-point name="buttons"></cs-extension-point> + <button class="button button-calm ink"