From 978431f524b1b9548031dac69f8f09ed461cb378 Mon Sep 17 00:00:00 2001 From: blavenie <benoit.lavenier@e-is.pro> Date: Fri, 19 Aug 2016 20:02:31 +0200 Subject: [PATCH] - Simplify configuration syntax - New Settings service - new csNetwork service, to manage network and peers access - TX in protocole v0.3 --- app/config.json | 62 ++-- ...o.large.blue.sand-dune-250\303\227250.png" | Bin 30885 -> 0 bytes gulpfile.js | 8 +- package.json | 19 + www/i18n/locale-en.json | 6 +- www/i18n/locale-fr-FR.json | 10 +- www/index.html | 2 + www/js/app.js | 8 +- www/js/config.js | 29 +- www/js/controllers/app-controllers.js | 16 +- www/js/controllers/currency-controllers.js | 154 ++------- www/js/controllers/login-controllers.js | 4 +- www/js/controllers/peer-controllers.js | 11 +- www/js/controllers/settings-controllers.js | 40 ++- www/js/controllers/transfer-controllers.js | 9 +- www/js/controllers/wallet-controllers.js | 20 +- www/js/controllers/wot-controllers.js | 7 +- www/js/services.js | 2 + www/js/services/bma-services.js | 91 +++-- www/js/services/http-services.js | 4 +- www/js/services/network-services.js | 148 ++++++++ www/js/services/settings-services.js | 96 ++++++ www/js/services/utils-services.js | 2 +- www/js/services/wallet-services.js | 326 ++++++++---------- www/js/services/wot-services.js | 13 +- .../es/js/controllers/common-controllers.js | 8 +- .../es/js/controllers/settings-controllers.js | 14 +- www/plugins/es/js/services/http-services.js | 4 +- www/plugins/es/js/services/user-services.js | 4 +- www/templates/currency/tabs/view_network.html | 4 +- www/templates/currency/view_currency.html | 21 +- www/templates/currency/view_currency_lg.html | 2 - www/templates/settings/settings.html | 2 +- www/templates/wallet/view_wallet.html | 20 +- .../wallet/view_wallet_tx_error.html | 16 +- www/templates/wot/view_certifications.html | 4 +- 36 files changed, 675 insertions(+), 511 deletions(-) delete mode 100644 "cesium-logo.large.blue.sand-dune-250\303\227250.png" create mode 100644 www/js/services/network-services.js create mode 100644 www/js/services/settings-services.js diff --git a/app/config.json b/app/config.json index 0ff11f63..25bd48bc 100644 --- a/app/config.json +++ b/app/config.json @@ -1,37 +1,43 @@ { "default": { - "APP_CONFIG": { - "DUNITER_NODE": "test-net.duniter.fr:9201", - "DUNITER_NODE_ES": "test-net.duniter.fr:9203", - "NEW_ISSUE_LINK": "https://github.com/duniter/cesium/issues/new?labels=bug", - "TIMEOUT": 4000, - "DEBUG": false - } - }, - - "simple": { - "APP_CONFIG": { - "DUNITER_NODE": "test-net.duniter.fr:9201", - "NEW_ISSUE_LINK": "https://github.com/duniter/cesium/issues/new?labels=bug", - "TIMEOUT": 6000, - "DEBUG": false + "timeout": 4000, + "useRelative": true, + "timeWarningExpireMembership": 5184000, + "timeWarningExpire": 7776000, + "useLocalStorage": false, + "rememberMe": false, + "showUDHistory": false, + "node": { + "host": "test-net.duniter.fr", + "port": "9201" + }, + "plugins":{ + "es": { + "enable": "false", + "host": "test-net.duniter.fr", + "port": "9203" + } } }, "dev": { - "APP_CONFIG": { - "DUNITER_NODE": "localhost:9201", - "TIMEOUT": 6000, - "DEBUG": true - } - }, - - "dev-es": { - "APP_CONFIG": { - "DUNITER_NODE": "test-net.duniter.fr:9201", - "DUNITER_NODE_ES": "192.168.0.28:9200", - "TIMEOUT": 6000, - "DEBUG": true + "timeout": 6000, + "useRelative": true, + "timeWarningExpireMembership": 5184000, + "timeWarningExpire": 7776000, + "useLocalStorage": true, + "rememberMe": true, + "showUDHistory": false, + "node": { + "host": "test-net.duniter.fr", + "port": "9201" + }, + "plugins":{ + "es": { + "enable": "true", + "host": "test-net.duniter.fr", + "port": "9203" + } } } } diff --git "a/cesium-logo.large.blue.sand-dune-250\303\227250.png" "b/cesium-logo.large.blue.sand-dune-250\303\227250.png" deleted file mode 100644 index 68fe13e1b3916829585550000d6bc4f1f0dadb42..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30885 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 b2=M;_E-t)@6aTf~00000NkvXXu0mjf<H7PQ diff --git a/gulpfile.js b/gulpfile.js index 6a22c5ea..9a839a42 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -117,14 +117,14 @@ gulp.task('config', function (done) { gutil.log(gutil.colors.green("Building `www/js/config.js` for `" + env + "` environment...")); var version = JSON.parse(fs.readFileSync('./package.json', 'utf8')).version; - config['APP_CONFIG']['VERSION'] = version; - config['APP_CONFIG']['BUILD_DATE'] = (new Date()).toJSON(); + config['version'] = version; + config['build'] = (new Date()).toJSON(); - // TODO : change version config.xml file + // TODO : change version in config.xml file return ngConstant({ name: 'cesium.config', - constants: config, + constants: {"csConfig": config}, stream: true, dest: 'config.js' }) diff --git a/package.json b/package.json index e9a56de3..cfd8a19e 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,25 @@ "name": "cesium", "version": "0.1.28", "description": "Unhosted webapp client for Duniter network", + "repository": { + "type": "git", + "url": "git@github.com:duniter/cesium.git" + }, + "keywords": [ + "duniter", + "cesium", + "ionic", + "angular", + "cordova", + "crypto-currency" + ], + "author": "Duniter team", + "license": "GPLv3", + "readmeFilename": "README.md", + "bugs": { + "url": "https://github.com/duniter/cesium/issues", + "new": "https://github.com/duniter/cesium/issues/new?labels=bug" + }, "dependencies": { "delete-empty": "^0.1.3", "gulp": "^3.9.1", diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json index d1c23bdc..82d35fc4 100644 --- a/www/i18n/locale-en.json +++ b/www/i18n/locale-en.json @@ -224,6 +224,9 @@ "PASSWORD_NOT_CONFIRMED": "Must match previous password.", "SEND_IDENTITY_FAILED": "Error while trying to register.", "SEND_CERTIFICATION_FAILED": "Could not certify identity.", + "NEED_MEMBER_ACCOUNT_TO_CERTIFY": "You could not send certification, because your account is <b>not</b> a member account.", + "NEED_MEMBER_ACCOUNT_TO_CERTIFY_HAS_SELF": "You could not send certification now, because your are a member yet.<br/>Be patient ! ;-)", + "NOT_MEMBER_FOR_CERTIFICATION": "Votre compte n'est pas encore membre.", "LOGIN_FAILED": "Error while sign in.", "LOAD_IDENTITY_FAILED": "Could not load identity.", "LOAD_REQUIREMENTS_FAILED": "Could not load identity requirements.", @@ -252,7 +255,8 @@ "INVALID_NODE_SUMMARY": "Unreachable node or invalid address", "INVALID_USER_ID": "Field 'pseudonym' must not contains spaces or special caracters.", "INVALID_COMMENT": "Field 'reference' has a bad format.", - "INVALID_PUBKEY": "Public key has a bad format." + "INVALID_PUBKEY": "Public key has a bad format.", + "IDENTITY_SANDBOX_FULL": "Duniter node could not received new identity, beacause it's sandbox is full.<br/><br/>Please retry later or change your node (in <b>Settings</b>)." }, "INFO": { "POPUP_TITLE": "Information", diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json index 110ff1b7..fc48e267 100644 --- a/www/i18n/locale-fr-FR.json +++ b/www/i18n/locale-fr-FR.json @@ -222,8 +222,11 @@ "FIELD_TOO_LONG": "Valeur trop longue", "FIELD_ACCENT": "Caractères accentués et virgules non autorisés", "PASSWORD_NOT_CONFIRMED": "Ne correspond pas au mot de passe.", - "SEND_IDENTITY_FAILED": "Erreur pendant l'inscription.", - "SEND_CERTIFICATION_FAILED": "Erreur lors de la certification de l'identité.", + "SEND_IDENTITY_FAILED": "Echec de l'inscription.", + "SEND_CERTIFICATION_FAILED": "Echec de la certification.", + "NEED_MEMBER_ACCOUNT_TO_CERTIFY": "Vous ne pouvez pas effectuer de certification, car votre compte n'est <b>pas membre</b>.", + "NEED_MEMBER_ACCOUNT_TO_CERTIFY_HAS_SELF": "Vous ne pouvez pas certifier quelqu'un, car vous n'etes pas encore devenu membre.<br/>Patience ! ;-)", + "NOT_MEMBER_FOR_CERTIFICATION": "Votre compte n'est pas encore membre.", "LOGIN_FAILED": "Erreur lors de l'authentification.", "LOAD_IDENTITY_FAILED": "Erreur de chargement de l'identité.", "LOAD_REQUIREMENTS_FAILED": "Erreur de chargement des prérequis de l'ientité.", @@ -252,7 +255,8 @@ "INVALID_NODE_SUMMARY": "Noeud injoignable ou adresse invalide.", "INVALID_USER_ID": "Le champ 'pseudonyme' ne doit contenir ni espace ni caractère spécial ou accentué.", "INVALID_COMMENT": "Le champ 'référence' ne doit pas contenir de caractères accentués.", - "INVALID_PUBKEY": "La clé publique n'a pas le format attendu." + "INVALID_PUBKEY": "La clé publique n'a pas le format attendu.", + "IDENTITY_SANDBOX_FULL": "Le noeud Duniter utilisé par Cesium ne peut plus recevoir de nouvelles identités, car sa file d'attente est pleine.<br/><br/>Veuillez réessayer ultérieurement ou changer de noeud (via le menu <b>Paramètres</b>)." }, "INFO": { "POPUP_TITLE": "Information", diff --git a/www/index.html b/www/index.html index 752c2b4e..bae90aeb 100644 --- a/www/index.html +++ b/www/index.html @@ -51,6 +51,8 @@ <!-- endbuild --> <!-- build:js dist_js/cesium.js --> <!-- services --> + <script src="dist/dist_js/app/services/settings-services.js"></script> + <script src="dist/dist_js/app/services/network-services.js"></script> <script src="dist/dist_js/app/services/crypto-services.js"></script> <script src="dist/dist_js/app/services/utils-services.js"></script> <script src="dist/dist_js/app/services/modal-services.js"></script> diff --git a/www/js/app.js b/www/js/app.js index fe544df6..9c9922cb 100644 --- a/www/js/app.js +++ b/www/js/app.js @@ -119,10 +119,10 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngAnimate', .useLoaderCache(true); }) - .config(function($httpProvider, APP_CONFIG) { + .config(function($httpProvider, csConfig) { 'ngInject'; // Set default timeout - $httpProvider.defaults.timeout = !!APP_CONFIG.TIMEOUT ? APP_CONFIG.TIMEOUT : 4000 /* default timeout */; + $httpProvider.defaults.timeout = !!csConfig.TIMEOUT ? csConfig.TIMEOUT : 4000 /* default timeout */; //Enable cross domain calls $httpProvider.defaults.useXDomain = true; @@ -131,10 +131,10 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngAnimate', delete $httpProvider.defaults.headers.common['X-Requested-With']; }) - .config(function($compileProvider, APP_CONFIG) { + .config(function($compileProvider, csConfig) { 'ngInject'; - $compileProvider.debugInfoEnabled(!!APP_CONFIG.DEBUG); + $compileProvider.debugInfoEnabled(!!csConfig.DEBUG); }) .config(function($animateProvider) { diff --git a/www/js/config.js b/www/js/config.js index cc193730..29394a2c 100644 --- a/www/js/config.js +++ b/www/js/config.js @@ -8,14 +8,27 @@ angular.module("cesium.config", []) -.constant("APP_CONFIG", { - "DUNITER_NODE": "test-net.duniter.fr:9201", - "DUNITER_NODE_ES": "test-net.duniter.fr:9203", - "NEW_ISSUE_LINK": "https://github.com/duniter/cesium/issues/new?labels=bug", - "TIMEOUT": 4000, - "DEBUG": false, - "VERSION": "0.1.28", - "BUILD_DATE": "2016-08-18T22:15:59.099Z" +.constant("csConfig", { + "timeout": 6000, + "useRelative": true, + "timeWarningExpireMembership": 5184000, + "timeWarningExpire": 7776000, + "useLocalStorage": true, + "rememberMe": true, + "showUDHistory": false, + "node": { + "host": "test-net.duniter.fr", + "port": "9201" + }, + "plugins": { + "es": { + "enable": "true", + "host": "test-net.duniter.fr", + "port": "9203" + } + }, + "version": "0.1.28", + "build": "2016-08-19T10:52:06.309Z" }) ; \ No newline at end of file diff --git a/www/js/controllers/app-controllers.js b/www/js/controllers/app-controllers.js index a4da9715..ed46950a 100644 --- a/www/js/controllers/app-controllers.js +++ b/www/js/controllers/app-controllers.js @@ -53,17 +53,19 @@ function PluginExtensionPointController($scope, PluginService) { /** * Abstract controller (inherited by other controllers) */ -function AppController($scope, $rootScope, $ionicModal, $state, $ionicSideMenuDelegate, UIUtils, $q, $timeout, - CryptoUtils, BMA, Wallet, APP_CONFIG, $ionicHistory, Device, $ionicPopover, $translate, $filter, - Modals +function AppController($scope, $rootScope, $state, $ionicSideMenuDelegate, UIUtils, $q, $timeout, + BMA, Wallet, csConfig, $ionicHistory, Device, Modals, csSettings ) { 'ngInject'; $scope.search = { text: '', results: {} }; - $scope.config = APP_CONFIG; + $scope.config = csConfig; if (!$rootScope.walletData) { $rootScope.walletData = Wallet.data; } + if (!$rootScope.settings) { + $rootScope.settings = csSettings.data; + } //////////////////////////////////////// // Load currencies @@ -195,9 +197,9 @@ function AppController($scope, $rootScope, $ionicModal, $state, $ionicSideMenuDe return Modals.showLogin() .then(function(formData){ if (!formData) return; - Wallet.data.settings.rememberMe = formData.rememberMe; - if (Wallet.data.settings.rememberMe) { - Wallet.data.settings.useLocalStorage = true; + var rememberMeChanged = (csSettings.data.rememberMe !== formData.rememberMe); + if (rememberMeChanged) { + csSettings.data.useLocalStorage = csSettings.data.rememberMe ? true : csSettings.data.useLocalStorage; Wallet.store(); } return Wallet.login(formData.username, formData.password); diff --git a/www/js/controllers/currency-controllers.js b/www/js/controllers/currency-controllers.js index 6c1880f1..18a33685 100644 --- a/www/js/controllers/currency-controllers.js +++ b/www/js/controllers/currency-controllers.js @@ -59,14 +59,14 @@ angular.module('cesium.currency.controllers', ['cesium.services']) ; -function CurrencyLookupController($scope, $state, $q, $timeout, UIUtils, APP_CONFIG, BMA) { +function CurrencyLookupController($scope, $state, UIUtils) { 'ngInject'; $scope.selectedCurrency = ''; $scope.knownCurrencies = []; $scope.search.looking = true; - $scope.$on('$ionicView.enter', function(e, $state) { + $scope.$on('$ionicView.enter', function() { $scope.loadCurrencies() .then(function (res) { $scope.knownCurrencies = res; @@ -91,15 +91,13 @@ function CurrencyLookupController($scope, $state, $q, $timeout, UIUtils, APP_CON }; } -function CurrencyViewController($scope, $rootScope, $state, BMA, $q, UIUtils, $interval, $timeout, Wallet, $translate) { - - PeersController.call(this, $scope, $rootScope, BMA, UIUtils, $q, $interval, $timeout); +function CurrencyViewController($scope, $q, $translate, $timeout, BMA, UIUtils, csSettings, csNetwork) { - $scope.search = {}; + $scope.loadingPeers = true; + $scope.peers = csNetwork.data.peers; $scope.formData = { - useRelative: Wallet.data.settings.useRelative + useRelative: csSettings.data.useRelative }; - $scope.knownBlocks = []; $scope.node = null; $scope.loading = true; @@ -158,11 +156,11 @@ function CurrencyViewController($scope, $rootScope, $state, BMA, $q, UIUtils, $i } $scope.node.websocket.block().on('block', function(block) { - var theFPR = fpr(block); - if ($scope.knownBlocks.indexOf(theFPR) === -1) { - $scope.knownBlocks.push(theFPR); + var buid = csNetwork.buid(block); + if (csNetwork.data.knownBlocks.indexOf(buid) === -1) { + csNetwork.data.knownBlocks.push(buid); // We wait 2s when a new block is received, just to wait for network propagation - var wait = $scope.knownBlocks.length === 1 ? 0 : 2000; + var wait = csNetwork.data.knownBlocks.length === 1 ? 0 : 2000; $timeout(function() { $scope.refresh(); }, wait); @@ -266,128 +264,16 @@ function CurrencyViewController($scope, $rootScope, $state, BMA, $q, UIUtils, $i }) .then(function(){ // Network - $scope.searchPeers(); - }); - }; -} - - -function PeersController($scope, $rootScope, BMA, UIUtils, $q, $interval, $timeout) { - - var newPeers = [], interval, lookingForPeers; - $scope.search.lookingForPeers = false; - $scope.search.peers = []; - - $scope.overviewPeers = function() { - var currents = {}, block; - for (var i = 0, len = $scope.search.peers.length; i < len; i++) { - block = $scope.search.peers[i].current; - if (block) { - var bid = fpr(block); - currents[bid] = currents[bid] || 0; - currents[bid]++; - } - } - var fprs = _.keys(currents).map(function(key) { - return { fpr: key, qty: currents[key] }; - }); - var best = _.max(fprs, function(obj) { - return obj.qty; - }); - var p; - for (var j = 0, len2 = $scope.search.peers.length; j < len2; j++) { - p = $scope.search.peers[j]; - p.hasMainConsensusBlock = fpr(p.current) == best.fpr; - p.hasConsensusBlock = !p.hasMainConsensusBlock && currents[fpr(p.current)] > 1; - } - $scope.search.peers = _.uniq($scope.search.peers, false, function(peer) { - return peer.pubkey; - }); - $scope.search.peers = _.sortBy($scope.search.peers, function(p) { - var score = 1; - score += (10000 * (p.online ? 1 : 0)); - score += (1000 * (p.hasMainConsensusBlock ? 1 : 0)); - score += (100 * (p.uid ? 1 : 0)); - return -score; + $scope.loadingPeers = true; + csNetwork.getPeers() + .then(function(peers) { + $scope.peers = peers; + $scope.loadingPeers = false; + }) + .catch(function(err) { + $scope.peers = []; + $scope.loadingPeers = false; + }) }); }; - - $scope.searchPeers = function() { - - if (interval) { - $interval.cancel(interval); - } - - interval = $interval(function() { - if (newPeers.length) { - $scope.search.peers = $scope.search.peers.concat(newPeers.splice(0)); - $scope.overviewPeers(); - } else if (lookingForPeers && !$scope.search.lookingForPeers) { - // The peer lookup endend, we can make a clean final report - $timeout(function(){ - lookingForPeers = false; - $scope.overviewPeers(); - }, 1000); - } - }, 1000); - - var known = {}; - $rootScope.memberUidsByPubkeys = {}; - $scope.search.peers = []; - $scope.search.lookingForPeers = true; - lookingForPeers = true; - return BMA.network.peering.peers({ leaves: true }) - .then(function(res){ - return BMA.wot.member.uids(true/*cache*/) - .then(function(uids){ - $rootScope.memberUidsByPubkeys = uids; - return res; - }); - }) - .then(function(res){ - return $q.all(res.leaves.map(function(leaf) { - return BMA.network.peering.peers({ leaf: leaf }) - .then(function(subres){ - var peer = subres.leaf.value; - if (peer) { - peer = new Peer(peer); - // Test each peer only once - if (!known[peer.getURL()]) { - peer.dns = peer.getDns(); - peer.blockNumber = peer.block.replace(/-.+$/, ''); - peer.uid = $rootScope.memberUidsByPubkeys[peer.pubkey]; - newPeers.push(peer); - var node = BMA.instance(peer.getURL()); - return node.blockchain.current() - .then(function(block){ - peer.current = block; - peer.online = true; - peer.server = peer.getURL(); - if ($scope.knownBlocks.indexOf(fpr(block)) === -1) { - $scope.knownBlocks.push(fpr(block)); - } - }) - .catch(function(err) { - }); - } - } - }); - })) - .then(function(){ - $scope.search.lookingForPeers = false; - }); - }) - .catch(function(err) { - //console.log(err); - $scope.search.lookingForPeers = false; - }); - }; - - $scope.viewPeer = function() { - - }; -} - -function fpr(block) { - return block && [block.number, block.hash].join('-'); } diff --git a/www/js/controllers/login-controllers.js b/www/js/controllers/login-controllers.js index e7c8357f..dc4c03fe 100644 --- a/www/js/controllers/login-controllers.js +++ b/www/js/controllers/login-controllers.js @@ -4,13 +4,13 @@ angular.module('cesium.login.controllers', ['cesium.services']) .controller('LoginModalCtrl', LoginModalController) ; -function LoginModalController($scope, $rootScope, $ionicModal, Wallet, CryptoUtils, UIUtils, $q, $state, $timeout, $ionicSideMenuDelegate, $ionicHistory, Modals) { +function LoginModalController($scope, $timeout, CryptoUtils, UIUtils, Modals, csSettings) { 'ngInject'; $scope.computing = false; $scope.pubkey = null; $scope.formData = { - rememberMe: Wallet.data.settings.rememberMe + rememberMe: csSettings.data.rememberMe }; // Login form submit diff --git a/www/js/controllers/peer-controllers.js b/www/js/controllers/peer-controllers.js index ee129522..fd423f2b 100644 --- a/www/js/controllers/peer-controllers.js +++ b/www/js/controllers/peer-controllers.js @@ -3,7 +3,16 @@ function PeerController($scope, $rootScope, $ionicSlideBoxDelegate, $ionicModal, 'ngInject'; $scope.$on('$ionicView.enter', function(e, $state) { - $scope.showPeer($state.stateParams.server); + if (!$rootScope.memberUidsByPubkeys) { + BMA.wot.member.uids(true/*cache*/) + .then(function(uids){ + $rootScope.memberUidsByPubkeys = uids; + $scope.showPeer($state.stateParams.server); + }); + } + else { + $scope.showPeer($state.stateParams.server); + } }); $scope.showPeer = function(server) { diff --git a/www/js/controllers/settings-controllers.js b/www/js/controllers/settings-controllers.js index b01b86b5..5ddf4b8b 100644 --- a/www/js/controllers/settings-controllers.js +++ b/www/js/controllers/settings-controllers.js @@ -20,19 +20,19 @@ angular.module('cesium.settings.controllers', ['cesium.services', 'cesium.curren .controller('SettingsCtrl', SettingsController) ; -function SettingsController($scope, $state, UIUtils, Wallet, $translate, BMA, $q, $ionicPopup, $timeout, localStorage) { +function SettingsController($scope, $q, $ionicPopup, $timeout, $translate, UIUtils, BMA, csSettings) { 'ngInject'; - $scope.formData = angular.copy(Wallet.defaultSettings); + $scope.formData = angular.copy(csSettings.data); $scope.loading = true; $scope.$on('$ionicView.enter', function(e, $state) { $scope.loading = true; // to avoid the call of Wallet.store() $scope.locales = UIUtils.locales; $scope.formData.locale = _.findWhere($scope.locales, {id: $translate.use()}); - angular.merge($scope.formData, Wallet.data.settings); - if (Wallet.data.settings.locale && Wallet.data.settings.locale.id) { - $scope.formData.locale = _.findWhere($scope.locales, {id: Wallet.data.settings.locale.id}); + angular.merge($scope.formData, csSettings.data); + if (csSettings.data.locale && csSettings.data.locale.id) { + $scope.formData.locale = _.findWhere($scope.locales, {id: csSettings.data.locale.id}); } UIUtils.loading.hide(); $scope.loading = false; @@ -54,27 +54,26 @@ function SettingsController($scope, $state, UIUtils, Wallet, $translate, BMA, $q // Change node $scope.changeNode= function(node) { - if (!node) { - node = $scope.formData.node; - } - $scope.showNodePopup(node) - .then(function(node) { - if (node == $scope.formData.node) { + $scope.showNodePopup(node || $scope.formData.node) + .then(function(newNode) { + + if (newNode.host === $scope.formData.node.host && + newNode.port === $scope.formData.node.port) { return; // same node = nothing to do } UIUtils.loading.show(); - var nodeBMA = BMA.instance(node); + var nodeBMA = BMA.instance(newNode.host, newNode.port); nodeBMA.node.summary() // ping the node .then(function() { UIUtils.loading.hide(); - $scope.formData.node = node; + $scope.formData.node = newNode; BMA.copy(nodeBMA); }) .catch(function(err){ UIUtils.loading.hide(); UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY') .then(function(){ - $scope.changeNode(node); // loop + $scope.changeNode(newNode); // loop }); }); }); @@ -83,7 +82,7 @@ function SettingsController($scope, $state, UIUtils, Wallet, $translate, BMA, $q // Show node popup $scope.showNodePopup = function(node) { return $q(function(resolve, reject) { - $scope.formData.newNode = node; + $scope.formData.newNode = [node.host, node.port].join(':'); if (!!$scope.settingsForm) { $scope.settingsForm.$setPristine(); } @@ -113,12 +112,15 @@ function SettingsController($scope, $state, UIUtils, Wallet, $translate, BMA, $q ] }) .then(function(node) { - delete $scope.formData.newNode; if (!node) { // user cancel UIUtils.loading.hide(); return; } - resolve(node); + var parts = node.split(':'); + resolve({ + host: parts[0], + port: parts[1] || 80 + }); }); }); }); @@ -127,8 +129,8 @@ function SettingsController($scope, $state, UIUtils, Wallet, $translate, BMA, $q $scope.onSettingsChanged = function() { if (!$scope.loading) { $scope.loading = true; - angular.merge(Wallet.data.settings, $scope.formData); - Wallet.store(); + angular.merge(csSettings.data, $scope.formData); + csSettings.store(); $scope.loading = false; } }; diff --git a/www/js/controllers/transfer-controllers.js b/www/js/controllers/transfer-controllers.js index 62b265f2..151d343b 100644 --- a/www/js/controllers/transfer-controllers.js +++ b/www/js/controllers/transfer-controllers.js @@ -44,7 +44,7 @@ angular.module('cesium.transfer.controllers', ['cesium.services', 'cesium.curren function TransferController($scope, $rootScope, $state, BMA, Wallet, UIUtils, $timeout, Device, $ionicPopover, $translate, $filter, $q, Modals, $ionicHistory) { 'ngInject'; - TransferModalController.call(this, $scope, $rootScope, $state, BMA, Wallet, UIUtils, $timeout, Device, $ionicPopover, $translate, $filter, $q, Modals); + TransferModalController.call(this, $scope, $rootScope, $state, BMA, Wallet, UIUtils, $timeout, Device, $ionicPopover, $translate, $filter, $q, Modals, csSettings); $scope.$on('$ionicView.enter', function(e, $state) { if (!!$state.stateParams && !!$state.stateParams.pubkey) { @@ -62,7 +62,7 @@ function TransferController($scope, $rootScope, $state, BMA, Wallet, UIUtils, $t $scope.loadWallet() .then(function(walletData) { $scope.walletData = walletData; - $scope.formData.useRelative = walletData.settings.useRelative; + $scope.formData.useRelative = csSettings.data.useRelative; $scope.onUseRelativeChanged(); UIUtils.loading.hide(); }); @@ -78,7 +78,8 @@ function TransferController($scope, $rootScope, $state, BMA, Wallet, UIUtils, $t } -function TransferModalController($scope, $rootScope, $state, BMA, Wallet, UIUtils, $timeout, Device, $ionicPopover, $translate, $filter, $q, Modals, parameters) { +function TransferModalController($scope, $rootScope, $ionicPopover, $translate, $filter, $q, BMA, Wallet, UIUtils, Modals, + csSettings, parameters) { 'ngInject'; $scope.walletData = $rootScope.walletData; @@ -87,7 +88,7 @@ function TransferModalController($scope, $rootScope, $state, BMA, Wallet, UIUtil destPub: null, amount: null, comment: null, - useRelative: Wallet.defaultSettings.useRelative, + useRelative: csSettings.data.useRelative, useComment: false }; $scope.udAmount = null; diff --git a/www/js/controllers/wallet-controllers.js b/www/js/controllers/wallet-controllers.js index c455fd6b..1396eb18 100644 --- a/www/js/controllers/wallet-controllers.js +++ b/www/js/controllers/wallet-controllers.js @@ -32,8 +32,8 @@ angular.module('cesium.wallet.controllers', ['cesium.services', 'cesium.currency .controller('WalletTxErrorCtrl', WalletTxErrorController) ; -function WalletController($scope, $rootScope, $state, $q, $ionicPopup, $ionicActionSheet, $timeout, - UIUtils, Wallet, BMA, $translate, Device, $ionicPopover, Modals) { +function WalletController($scope, $q, $ionicPopup, $timeout, + UIUtils, Wallet, BMA, $translate, $ionicPopover, Modals, csSettings) { 'ngInject'; $scope.walletData = null; @@ -73,7 +73,7 @@ function WalletController($scope, $rootScope, $state, $q, $ionicPopup, $ionicAct if (!$scope.walletData) { return; } - if ($scope.walletData.settings.useRelative) { + if (csSettings.data.useRelative) { $scope.convertedBalance = $scope.walletData.balance ? ($scope.walletData.balance / $scope.walletData.currentUD) : 0; } else { var balance = $scope.walletData.balance; @@ -83,7 +83,7 @@ function WalletController($scope, $rootScope, $state, $q, $ionicPopup, $ionicAct $scope.convertedBalance = balance; } }; - $scope.$watch('walletData.settings.useRelative', $scope.refreshConvertedBalance, true); + csSettings.api.data.on.changed($scope, $scope.refreshConvertedBalance); $scope.$watch('walletData.balance', $scope.refreshConvertedBalance, true); // Update view @@ -241,8 +241,10 @@ function WalletController($scope, $rootScope, $state, $q, $ionicPopup, $ionicAct }) .catch(function(err){ UIUtils.loading.hide(); - UIUtils.alert.info(err); - $scope.self(); // loop + UIUtils.onError('ERROR.SEND_IDENTITY_FAILED')(err) + .then(function() { + $scope.self(); // loop + }); }); }); }; @@ -251,9 +253,6 @@ function WalletController($scope, $rootScope, $state, $q, $ionicPopup, $ionicAct $scope.membershipIn= function() { var doMembershipIn = function(retryCount) { Wallet.membership.inside() - .then(function() { - $scope.doUpdate(); - }) .catch(function(err) { if (!retryCount || retryCount <= 2) { $timeout(function() { @@ -295,9 +294,6 @@ function WalletController($scope, $rootScope, $state, $q, $ionicPopup, $ionicAct $scope.membershipOut = function() { UIUtils.loading.show(); Wallet.membership.out() - .then(function() { - $scope.doUpdate(); - }) .catch(UIUtils.onError('ERROR.SEND_MEMBERSHIP_OUT_FAILED')); }; diff --git a/www/js/controllers/wot-controllers.js b/www/js/controllers/wot-controllers.js index 1b6cd124..0acf6dc1 100644 --- a/www/js/controllers/wot-controllers.js +++ b/www/js/controllers/wot-controllers.js @@ -234,7 +234,7 @@ function WotCertificationsViewController($scope, $state, BMA, Wallet, UIUtils, $ WotService.load(pubkey) .then(function(identity){ $scope.formData = identity; - $scope.canCertify = Wallet.isLogin() && !Wallet.isUserPubkey(pubkey) && Wallet.data.isMember; + $scope.canCertify = $scope.formData.hasSelf && (!Wallet.isLogin() || (!Wallet.isUserPubkey(pubkey))); $scope.alreadyCertified = $scope.canCertify ? !!_.findWhere(identity.certifications, { uid: Wallet.data.uid, valid: true }) : false; $scope.loading = false; @@ -254,6 +254,11 @@ function WotCertificationsViewController($scope, $state, BMA, Wallet, UIUtils, $ $scope.certify = function() { $scope.loadWallet() .then(function(walletData) { + if (!walletData.isMember) { + UIUtils.alert.error(walletData.requirements.needSelf ? + 'ERROR.NEED_MEMBER_ACCOUNT_TO_CERTIFY' : 'ERROR.NEED_MEMBER_ACCOUNT_TO_CERTIFY_HAS_SELF'); + return; + } UIUtils.loading.hide(); UIUtils.alert.confirm('CONFIRM.CERTIFY_RULES') .then(function(confirm){ diff --git a/www/js/services.js b/www/js/services.js index 2f85d603..fa593fda 100644 --- a/www/js/services.js +++ b/www/js/services.js @@ -1,6 +1,8 @@ angular.module('cesium.services', [ 'cesium.config', + 'cesium.settings.services', 'cesium.http.services', + 'cesium.network.services', 'cesium.bma.services', 'cesium.crypto.services', 'cesium.utils.services', diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js index 08867c85..9bc368d5 100644 --- a/www/js/services/bma-services.js +++ b/www/js/services/bma-services.js @@ -1,12 +1,11 @@ //var Base58, Base64, scrypt_module_factory = null, nacl_factory = null; -angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', - 'cesium.config']) +angular.module('cesium.bma.services', ['ngResource', 'cesium.http.services', 'cesium.settings.services']) -.factory('BMA', function($q, APP_CONFIG, HttpUtils) { +.factory('BMA', function($q, csSettings, HttpUtils, $rootScope) { 'ngInject'; - function BMA(server) { + function BMA(host, port) { var instance = this, @@ -15,7 +14,8 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', UID_ALREADY_USED: 2003, NO_MATCHING_MEMBER: 2004, NO_IDTY_MATCHING_PUB_OR_UID: 2021, - MEMBERSHRIP_ALREADY_SEND: 2007 + MEMBERSHIP_ALREADY_SEND: 2007, + IDENTITY_SANDBOX_FULL: 1007 }, regex = { USER_ID: "[A-Za-z0-9_-]+", @@ -29,7 +29,13 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', constants = { CACHE_TIME_MS: 60000 }, + protocol = (port === 443 ? 'https' : 'http'), + server = protocol + '://' + host + (port ? ':' + port : ''), data = { + node: { + host: host, + port: port + }, blockchain: { current: null }, @@ -57,7 +63,7 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', function getBlockchainCurrent(cache) { - var getBlockchainCurrentNoCache = HttpUtils.get('http://' + server + '/blockchain/current'); + var getBlockchainCurrentNoCache = HttpUtils.get(server + '/blockchain/current'); return $q(function(resolve, reject) { var now = new Date(); if (cache && data.blockchain.current !== null && @@ -72,13 +78,14 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', timestamp: now.getTime() }; resolve(block); - }); + }) + .catch(function(err){reject(err);}); }); } function getMembers(cache) { - var getMembersNoCache = HttpUtils.get('http://' + server + '/wot/members'); + var getMembersNoCache = HttpUtils.get(server + '/wot/members'); return $q(function(resolve, reject) { var now = new Date(); if (cache && data.wot && data.wot.members && data.wot.members.length > 0 && @@ -146,8 +153,8 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', } function getBlockchainLastUd(cache) { - var getBlockchainWithUd = HttpUtils.get('http://' + server + '/blockchain/with/ud'); - var getBlockchainBlock = HttpUtils.get('http://' + server + '/blockchain/block/:block'); + var getBlockchainWithUd = HttpUtils.get(server + '/blockchain/with/ud'); + var getBlockchainBlock = HttpUtils.get(server + '/blockchain/block/:block'); return $q(function(resolve, reject) { var now = new Date(); if (cache && data.blockchain && data.blockchain.lastUd && (now.getTime() - data.blockchain.lastUdTimestamp) <= constants.CACHE_TIME_MS){ @@ -216,7 +223,7 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', if (!currency){ if (host) { - HttpUtils.get('http://' + host + '/blockchain/parameters')() + HttpUtils.get(host + '/blockchain/parameters')() .then(function(parameters){ resolve({ uid: uid, @@ -242,7 +249,7 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', } // Check if currency are the same (between node and uri) - return HttpUtils.get('http://' + host + '/blockchain/parameters')() + return HttpUtils.get(host + '/blockchain/parameters')() .then(function(parameters){ if (parameters.currency !== currency) { throw {message: "Node's currency ["+parameters.currency+"] does not matched URI's currency ["+currency+"]."}; @@ -286,55 +293,57 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', return { node: { - summary: HttpUtils.get('http://' + server + '/node/summary'), - url: server + summary: HttpUtils.get(server + '/node/summary'), + url: server, + host: host, + port: port }, wot: { - lookup: HttpUtils.get('http://' + server + '/wot/lookup/:search'), + lookup: HttpUtils.get(server + '/wot/lookup/:search'), member: { all: getMembers, uids: getMemberUidsByPubkey, get: getMemberByPubkey }, - requirements: HttpUtils.get('http://' + server + '/wot/requirements/:pubkey'), - add: HttpUtils.post('http://' + server + '/wot/add'), - certify: HttpUtils.post('http://' + server + '/wot/certify') + requirements: HttpUtils.get(server + '/wot/requirements/:pubkey'), + add: HttpUtils.post(server + '/wot/add'), + certify: HttpUtils.post(server + '/wot/certify') }, network: { peering: { - peers: HttpUtils.get('http://' + server + '/network/peering/peers') + peers: HttpUtils.get(server + '/network/peering/peers') }, - peers: HttpUtils.get('http://' + server + '/network/peers') + peers: HttpUtils.get(server + '/network/peers') }, blockchain: { - parameters: HttpUtils.get('http://' + server + '/blockchain/parameters'), + parameters: HttpUtils.get(server + '/blockchain/parameters'), current: getBlockchainCurrent, - block: HttpUtils.get('http://' + server + '/blockchain/block/:block'), - membership: HttpUtils.post('http://' + server + '/blockchain/membership'), + block: HttpUtils.get(server + '/blockchain/block/:block'), + membership: HttpUtils.post(server + '/blockchain/membership'), stats: { - ud: HttpUtils.get('http://' + server + '/blockchain/with/ud'), - tx: HttpUtils.get('http://' + server + '/blockchain/with/tx') + ud: HttpUtils.get(server + '/blockchain/with/ud'), + tx: HttpUtils.get(server + '/blockchain/with/tx') }, lastUd: getBlockchainLastUd }, tx: { - sources: HttpUtils.get('http://' + server + '/tx/sources/:pubkey'), - process: HttpUtils.post('http://' + server + '/tx/process'), + sources: HttpUtils.get(server + '/tx/sources/:pubkey'), + process: HttpUtils.post(server + '/tx/process'), history: { - all: HttpUtils.get('http://' + server + '/tx/history/:pubkey'), - times: HttpUtils.get('http://' + server + '/tx/history/:pubkey/times/:from/:to'), - blocks: HttpUtils.get('http://' + server + '/tx/history/:pubkey/blocks/:from/:to') + all: HttpUtils.get(server + '/tx/history/:pubkey'), + times: HttpUtils.get(server + '/tx/history/:pubkey/times/:from/:to'), + blocks: HttpUtils.get(server + '/tx/history/:pubkey/blocks/:from/:to') } }, ud: { - history: HttpUtils.get('http://' + server + '/ud/history/:pubkey') + history: HttpUtils.get(server + '/ud/history/:pubkey') }, websocket: { block: function() { - return HttpUtils.ws('ws://' + server + '/ws/block'); + return HttpUtils.ws('ws://' + host + ':' + port + '/ws/block'); }, peer: function() { - return HttpUtils.ws('ws://' + server + '/ws/peer'); + return HttpUtils.ws('ws://' + host + ':' + port + '/ws/peer'); }, close : HttpUtils.closeAllWs }, @@ -361,8 +370,22 @@ angular.module('cesium.bma.services', ['cesium.http.services', 'ngResource', }; } - var service = BMA(APP_CONFIG.DUNITER_NODE, APP_CONFIG.TIMEOUT); + var service = BMA(csSettings.data.node.host, csSettings.data.node.port); service.instance = BMA; + + // Listen settings changes + csSettings.api.data.on.changed($rootScope, function(settings) { + + var nodeChanged = + (settings.node.host && settings.node.host != service.node.host) || + (settings.node.port && settings.node.port != service.node.port); + + if (nodeChanged) { + service.copy(BMA(settings.node.host, settings.node.port)); // reload BMA + } + + }); + return service; }) ; diff --git a/www/js/services/http-services.js b/www/js/services/http-services.js index 0c91f95e..0fe2275c 100644 --- a/www/js/services/http-services.js +++ b/www/js/services/http-services.js @@ -1,6 +1,6 @@ angular.module('cesium.http.services', ['ngResource']) -.factory('HttpUtils', function($http, $q, APP_CONFIG) { +.factory('HttpUtils', function($http, $q, csSettings) { 'ngInject'; function HttpUtils(timeout) { @@ -165,7 +165,7 @@ angular.module('cesium.http.services', ['ngResource']) }; } - var service = HttpUtils(APP_CONFIG.TIMEOUT); + var service = HttpUtils(csSettings.data.timeout); service.instance = HttpUtils; return service; }) diff --git a/www/js/services/network-services.js b/www/js/services/network-services.js new file mode 100644 index 00000000..3c399d84 --- /dev/null +++ b/www/js/services/network-services.js @@ -0,0 +1,148 @@ + +angular.module('cesium.network.services', ['ngResource', 'ngApi', 'cesium.bma.services']) + +.factory('csNetwork', function($rootScope, $q, $interval, $timeout, BMA, Api) { + 'ngInject'; + + CSNetwork = function(id) { + + var + data = { + peers: [], + newPeers: [], + knownBlocks: [] + }, + interval, + updatingPeers = false, + searchingPeersOnNetwork = false, + api = new Api(this, "csNetwork-" + id), + + buid = function(block) { + return block && [block.number, block.hash].join('-'); + }, + + processPeers = function() { + // Count peer by current block uid + var currents = {}, block; + _.forEach(data.peers, function(peer){ + if (peer.buid) { + currents[peer.buid] = currents[peer.buid] || 0; + currents[peer.buid]++; + } + }); + var buids = _.keys(currents).map(function(key) { + return { buid: key, count: currents[key] }; + }); + var mainBuid = _.max(buids, function(obj) { + return obj.count; + }).buid; + var p; + _.forEach(data.peers, function(peer){ + peer.hasMainConsensusBlock = peer.buid == mainBuid; + peer.hasConsensusBlock = !peer.hasMainConsensusBlock && currents[peer.buid] > 1; + }); + data.peers = _.uniq(data.peers, false, function(peer) { + return peer.pubkey; + }); + data.peers = _.sortBy(data.peers, function(peer) { + var score = 1; + score += (10000000 * (peer.online ? 1 : 0)); + score += (1000000 * (peer.hasMainConsensusBlock ? 1 : 0)); + score += (100 * (peer.hasConsensusBlock ? currents[peer.buid] : 0)); + score += (10 * (peer.uid ? 1 : 0)); + return -score; + }); + }, + + getPeers = function() { + return $q(function(resolve, reject){ + + if (interval) { + $interval.cancel(interval); + } + + interval = $interval(function() { + if (data.newPeers.length) { + data.peers = data.peers.concat(data.newPeers.splice(0)); + processPeers(); + } else if (updatingPeers && !searchingPeersOnNetwork) { + // The peer lookup endend, we can make a clean final report + $timeout(function(){ + updatingPeers = false; + processPeers(); + resolve(data.peers); + $interval.cancel(interval); + }, 1000); + } + }, 1000); + + var known = {}; + data.peers = []; + searchingPeersOnNetwork = true; + updatingPeers = true; + var uidsByPubkeys; + return BMA.wot.member.uids(true/*cache*/) + .then(function(uids){ + uidsByPubkeys = uids; + return BMA.network.peering.peers({ leaves: true }) + }) + .then(function(res){ + return $q.all(res.leaves.map(function(leaf) { + return BMA.network.peering.peers({ leaf: leaf }) + .then(function(subres){ + var peer = subres.leaf.value; + if (peer) { + peer = new Peer(peer); + // Test each peer only once + if (!known[peer.getURL()]) { + peer.dns = peer.getDns(); + peer.blockNumber = peer.block.replace(/-.+$/, ''); + data.newPeers.push(peer); + var node = BMA.instance(peer.getHost(), peer.getPort()); + return node.blockchain.current() + .then(function(block){ + peer.current = block; + peer.online = true; + peer.server = peer.getURL(); + peer.buid = buid(block); + peer.uid = uidsByPubkeys[peer.pubkey]; + if (data.knownBlocks.indexOf(peer.buid) === -1) { + data.knownBlocks.push(peer.buid); + } + }) + .catch(function(err) { + // nothing to do (node is DOWN ?) + }); + } + } + }); + })) + .then(function(){ + searchingPeersOnNetwork = false; + }); + }) + .catch(function(err) { + //console.log(err); + searchingPeersOnNetwork = false; + }); + }); + }; + + // Register extension points + //api.registerEvent('data', 'load'); + + return { + id: id, + buid: buid, + data: data, + getPeers: getPeers, + // api extension + api: api + }; + }; + + var service = CSNetwork('default'); + + service.instance = CSNetwork; + return service; +}); diff --git a/www/js/services/settings-services.js b/www/js/services/settings-services.js new file mode 100644 index 00000000..56568ba6 --- /dev/null +++ b/www/js/services/settings-services.js @@ -0,0 +1,96 @@ + +angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.config']) + +.factory('csSettings', function($q, csConfig, Api, localStorage, $translate) { + 'ngInject'; + + CSSettings = function(id) { + + var + constants = { + STORAGE_KEY: 'CESIUM_SETTINGS' + }, + + defaultSettings = angular.merge({ + useRelative: true, + timeWarningExpireMembership: 2592000 * 2 /*=2 mois*/, + timeWarningExpire: 2592000 * 3 /*=3 mois*/, + useLocalStorage: false, + rememberMe: false, + showUDHistory: true, + locale: { + id: $translate.use() + } + }, csConfig), + + data = angular.copy(defaultSettings), + + api = new Api(this, "csSettings-" + id), + + reset = function() { + angular.merge(data, defaultSettings); + }, + + store = function(options) { + if (data.useLocalStorage) { + localStorage.setObject(constants.STORAGE_KEY, data); + } + else { + localStorage.setObject(constants.STORAGE_KEY, null); + } + + // Emit event on changed + api.data.raise.changed(data); + + }, + + restore = function() { + return $q(function(resolve, reject){ + var storedData = localStorage.getObject(constants.STORAGE_KEY); + if (!storedData) { + resolve(); + return; + } + + var localeChanged = storedData.locale && storedData.locale.id && (data.locale.id !== storedData.locale.id); + angular.merge(data, storedData); + + // Always force the usage of deffault settings + // This is a workaround for DEV (TODO: implement edition in settings) + data.timeWarningExpire = defaultSettings.timeWarningExpire; + data.timeWarningExpireMembership = defaultSettings.timeWarningExpireMembership; + delete data.DUNITER_NODE; + delete data.DUNITER_NODE_ES; + + if (localeChanged) { + $translate.use(data.locale.id); + } + + + // Emit event on changed + api.data.raisePromise.changed(data); + //resolve(); + }); + }; + + api.registerEvent('data', 'changed'); + + return { + id: id, + data: data, + reset: reset, + store: store, + restore: restore, + defaultSettings: defaultSettings, + // api extension + api: api + }; + }; + + var service = CSSettings('default'); + + service.restore(); + + service.instance = CSSettings; + return service; +}); diff --git a/www/js/services/utils-services.js b/www/js/services/utils-services.js index b3897e57..6ce78ee2 100644 --- a/www/js/services/utils-services.js +++ b/www/js/services/utils-services.js @@ -146,7 +146,7 @@ angular.module('cesium.utils.services', ['ngResource']) else { console.error(err); hideLoading(10); // timeout, to avoid bug on transfer (when error on reference) - alertError(fullMsg, subtitle); + return alertError(fullMsg, subtitle); } }; } diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js index 3f2859be..d76d5b57 100644 --- a/www/js/services/wallet-services.js +++ b/www/js/services/wallet-services.js @@ -1,27 +1,17 @@ -angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.services', 'cesium.crypto.services', 'cesium.utils.services']) +angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.services', 'cesium.crypto.services', 'cesium.utils.services', + 'cesium.settings.services']) -.factory('Wallet', function($q, $rootScope, CryptoUtils, BMA, $translate, localStorage, $filter, Api, UIUtils) { +.factory('Wallet', function($q, $rootScope, CryptoUtils, BMA, $translate, localStorage, $filter, Api, csSettings) { 'ngInject'; Wallet = function(id) { var - events = { - SETTINGS: 'wallet-settings-changed' - }, - - defaultSettings = { - useRelative: true, - timeWarningExpireMembership: 2592000 * 2 /*=2 mois*/, - timeWarningExpire: 2592000 * 3 /*=3 mois*/, - useLocalStorage: false, - rememberMe: false, - node: BMA.node.url, - showUDHistory: true - }, - + constants = { + STORAGE_KEY: "CESIUM_DATA" + } data = { pubkey: null, keypair: { @@ -46,16 +36,6 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser loaded: false, blockUid: null, avatar: null, - settings: { - useRelative: defaultSettings.useRelative, - timeWarningExpireMembership: defaultSettings.timeWarningExpireMembership, - timeWarningExpire: defaultSettings.timeWarningExpire, - locale: {id: $translate.use()}, - useLocalStorage: defaultSettings.useLocalStorage, - rememberMe: defaultSettings.rememberMe, - node: defaultSettings.node, - showUDHistory: defaultSettings.showUDHistory - } }, api = new Api(this, 'WalletService-' + id), @@ -84,17 +64,8 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser data.loaded = false; data.blockUid = null; data.avatar = null; - if (!data.settings.useLocalStorage) { - data.settings = { - useRelative: defaultSettings.useRelative, - timeWarningExpireMembership: defaultSettings.timeWarningExpireMembership, - timeWarningExpire: defaultSettings.timeWarningExpire, - locale: {id: $translate.use()}, - useLocalStorage: defaultSettings.useLocalStorage, - rememberMe: defaultSettings.rememberMe, - node: BMA.node.url, // If changed, use the updated url - showUDHistory: defaultSettings.showUDHistory - }; + if (!csSettings.data.useLocalStorage) { + csSettings.reset(); } }, @@ -177,7 +148,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Copy result to properties data.pubkey = CryptoUtils.util.encode_base58(keypair.signPk); data.keypair = keypair; - if (data.settings.useLocalStorage) { + if (csSettings.data.useLocalStorage) { store(); } resolve(data); @@ -204,99 +175,64 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser return isLogin() && data.pubkey === pubkey; }, - store = function(options) { - if (data.settings.useLocalStorage) { - - if (!options || options.settings) { - localStorage.setObject('CESIUM_SETTINGS', data.settings); - // Send event when settings changed - $rootScope.$broadcast(events.SETTINGS); - } + store = function() { + if (csSettings.data.useLocalStorage) { - if (!options || options.data) { - if (isLogin() && data.settings.rememberMe) { - var dataToStore = { - keypair: data.keypair, - pubkey: data.pubkey - }; + if (isLogin() && csSettings.data.rememberMe) { + var dataToStore = { + keypair: data.keypair, + pubkey: data.pubkey + }; - if (data.tx && data.tx.pendings && data.tx.pendings.length>0) { - var pendings = data.tx.pendings.reduce(function(res, tx){ - return tx.time ? res.concat({ - amount: tx.amount, - time: tx.time, - hash: tx.hash - }) : res; - }, []); - if (pendings.length) { - dataToStore.tx = { - pendings: pendings - }; - } + if (data.tx && data.tx.pendings && data.tx.pendings.length>0) { + var pendings = data.tx.pendings.reduce(function(res, tx){ + return tx.time ? res.concat({ + amount: tx.amount, + time: tx.time, + hash: tx.hash + }) : res; + }, []); + if (pendings.length) { + dataToStore.tx = { + pendings: pendings + }; } - - localStorage.setObject('CESIUM_DATA', dataToStore); - } - else { - localStorage.setObject('CESIUM_DATA', null); } + + localStorage.setObject(constants.STORAGE_KEY, dataToStore); + } + else { + localStorage.setObject(constants.STORAGE_KEY, null); } } else { - localStorage.setObject('CESIUM_SETTINGS', null); - localStorage.setObject('CESIUM_DATA', null); - // Send event when settings changed - $rootScope.$broadcast(events.SETTINGS); + localStorage.setObject(constants.STORAGE_KEY, null); } - }, restore = function() { return $q(function(resolve, reject){ - var settings = localStorage.getObject('CESIUM_SETTINGS'); - var dataStr = localStorage.get('CESIUM_DATA'); - if (!settings && !dataStr) { + var dataStr = localStorage.get(constants.STORAGE_KEY); + if (!dataStr) { resolve(); return; } - var nodeChanged = (settings && settings.node) && (data.settings.node != settings.node); - if (nodeChanged) { - BMA.copy(BMA.instance(settings.node)); // reload BMA - data.loaded = false; - } - if (settings) { - var refreshLocale = settings.locale && settings.locale.id && (data.settings.locale.id !== settings.locale.id); - data.settings = settings; - - // TODO : This is a workaround for DEV (Need to implement edition in settings) - data.settings.timeWarningExpire = defaultSettings.timeWarningExpire; - data.settings.timeWarningExpireMembership = defaultSettings.timeWarningExpireMembership; - - if (refreshLocale) { - $translate.use(data.settings.locale.id); - } - } - if (dataStr) { - fromJson(dataStr, false) - .then(function(storedData){ - if (storedData && storedData.keypair && storedData.pubkey) { - data.keypair = storedData.keypair; - data.pubkey = storedData.pubkey; - if (storedData.tx && storedData.tx.pendings) { - data.tx.pendings = storedData.tx.pendings; - } - data.loaded = false; + fromJson(dataStr, false) + .then(function(storedData){ + if (storedData && storedData.keypair && storedData.pubkey) { + data.keypair = storedData.keypair; + data.pubkey = storedData.pubkey; + if (storedData.tx && storedData.tx.pendings) { + data.tx.pendings = storedData.tx.pendings; } + data.loaded = false; + } - // Load parameters - // This prevent timeout error, when loading a market record after a browser refresh (e.g. F5) - return loadParameters(); - }) - .catch(function(err){reject(err);}); - } - else { - resolve(); - } + // Load parameters + // This prevent timeout error, when loading a market record after a browser refresh (e.g. F5) + return loadParameters(); + }) + .catch(function(err){reject(err);}); }); }, @@ -356,13 +292,13 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser data.requirements.needMembership = (data.requirements.membershipExpiresIn === 0 && data.requirements.membershipPendingExpiresIn <= 0 ); data.requirements.needRenew = (!data.requirements.needMembership && - data.requirements.membershipExpiresIn <= data.settings.timeWarningExpireMembership && + data.requirements.membershipExpiresIn <= csSettings.data.timeWarningExpireMembership && data.requirements.membershipPendingExpiresIn <= 0); data.requirements.canMembershipOut = (data.requirements.membershipExpiresIn > 0); data.requirements.pendingMembership = (data.requirements.membershipPendingExpiresIn > 0); data.requirements.certificationCount = (idty.certifications) ? idty.certifications.length : 0; data.requirements.willExpireCertificationCount = idty.certifications ? idty.certifications.reduce(function(count, cert){ - if (cert.expiresIn <= data.settings.timeWarningExpire) { + if (cert.expiresIn <= csSettings.data.timeWarningExpire) { return count + 1; } return count; @@ -435,7 +371,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser reduceTxAndPush(res.history.pending, txPendings, processedTxMap, false/*exclude pending*/); })); // get UD history - if (data.settings.showUDHistory) { + if (csSettings.data.showUDHistory) { jobs.push( BMA.ud.history({pubkey: data.pubkey}) .then(function(res){ @@ -529,21 +465,23 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser }, loadParameters = function() { - if (!data.parameters || !data.currency) { - return $q(function(resolve, reject) { - BMA.blockchain.parameters() - .then(function(json){ - data.currency = json.currency; - data.parameters = json; - resolve(); - }) - .catch(function(err) { - data.currency = null; - data.parameters = null; - reject(err); - }); + return $q(function(resolve, reject) { + if (data.parameters && data.currency) { + resolve(); + return; + } + BMA.blockchain.parameters() + .then(function(json){ + data.currency = json.currency; + data.parameters = json; + resolve(); + }) + .catch(function(err) { + data.currency = null; + data.parameters = null; + reject(err); }); - } + }); }, loadUDs = function() { @@ -627,25 +565,40 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser }); }, - refreshData = function() { + refreshData = function(options) { + if (!options) { + options = { + uds: true, + requirements: true, + sources: true, + tx: true + } + } return $q(function(resolve, reject){ - $q.all([ - - // Get UDs - loadUDs(), - - // Get requirements - loadRequirements() - .then(function() { - finishLoadRequirements(); - }), - - // Get sources - loadSources(), + var jobs = []; + // Get UDs + if (options.uds) { + jobs.push(loadUDs()); + } + // Get requirements + if (options.uds) { + jobs.push(loadRequirements() + .then(function() { + finishLoadRequirements(); + })); + } + // Get sources + if (options.source) { + jobs.push(loadSources()); + } + // Get transactions + if (options.tx) { + jobs.push(loadTransactions()); + } + // API extension + jobs.push(api.data.raisePromise.load(data)); - // Get transactions - loadTransactions() - ]) + $q.all(jobs) .then(function() { // Process transactions and sources processTransactionsAndSources() @@ -794,18 +747,19 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser return; } - var tx = "Version: 2\n"; - tx += "Type: Transaction\n"; - tx += "Currency: " + data.currency + "\n"; - tx += "Locktime: 0" + "\n"; // no lock - tx += "Issuers:\n"; - tx += data.pubkey + "\n"; - tx += "Inputs:\n"; + var tx = 'Version: 3\n' + + 'Type: Transaction\n' + + 'Currency: ' + data.currency + '\n' + + 'Blockstamp: ' + block.number + '-' + block.hash + '\n' + + 'Locktime: 0\n' + // no lock + 'Issuers:\n' + + data.pubkey + '\n' + + 'Inputs:\n'; _.forEach(inputs.sources, function(source) { - // if D : D:PUBLIC_KEY:BLOCK_ID - // if T : T:T_HASH:T_INDEX - tx += source.type+":"+source.identifier+":"+source.noffset+"\n"; + // if D : AMOUNT:BASE:D:PUBLIC_KEY:BLOCK_ID + // if T : AMOUNT:BASE:T:T_HASH:T_INDEX + tx += [source.amount, source.base, source.type, source.identifier,source.noffset].join(':')+"\n"; }); tx += 'Unlocks:\n'; @@ -899,22 +853,27 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser var signedIdentity = identity + signature + '\n'; // Send signed identity BMA.wot.add({identity: signedIdentity}) - .then(function (result) { - if (!!requirements) { - // Refresh membership data - loadRequirements() - .then(function () { - resolve(); - }).catch(function (err) { - reject(err); - }); - } - else { - data.uid = uid; - data.blockUid = block.number + '-' + block.hash; - resolve(); - } - }).catch(function (err) { + .then(function (result) { + if (!!requirements) { + // Refresh membership data + loadRequirements() + .then(function () { + resolve(); + }).catch(function (err) { + reject(err); + }); + } + else { + data.uid = uid; + data.blockUid = block.number + '-' + block.hash; + resolve(); + } + }) + .catch(function (err) { + if (err && err.ucode === BMA.errorCodes.IDENTITY_SANDBOX_FULL) { + reject({ucode: BMA.errorCodes.IDENTITY_SANDBOX_FULL, message: 'ERROR.IDENTITY_SANDBOX_FULL'}); + return; + } reject(err); }); }).catch(function (err) { @@ -953,11 +912,14 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Send signed membership BMA.blockchain.membership({membership: signedMembership}) .then(function(result) { - // Refresh membership data - loadRequirements() - .then(function() { - resolve(); - }).catch(function(err){reject(err);}); + $timeout(function() { + loadRequirements() + .then(function() { + finishLoadRequirements(); + resolve(); + }) + .catch(function(err){reject(err);}); + }, 200); }).catch(function(err){reject(err);}); }).catch(function(err){reject(err);}); }).catch(function(err){reject(err);}); @@ -1047,6 +1009,8 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // Register extension points api.registerEvent('data', 'load'); + csSettings.api.data.on.changed($rootScope, store); + return { id: id, data: data, @@ -1071,9 +1035,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser // serialization toJson: toJson, fromJson: fromJson, - defaultSettings: defaultSettings, - api: api, - events: events + api: api }; }; diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js index 68f303e6..19f11b9c 100644 --- a/www/js/services/wot-services.js +++ b/www/js/services/wot-services.js @@ -1,7 +1,8 @@ -angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.services', 'cesium.crypto.services', 'cesium.utils.services']) +angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.services', 'cesium.crypto.services', 'cesium.utils.services', + 'cesium.settings.services']) -.factory('WotService', function($q, BMA, Api, Wallet) { +.factory('WotService', function($q, BMA, Api, csSettings) { 'ngInject'; WotService = function(id) { @@ -28,19 +29,18 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic return -score; }); } - var timeWarningExpire = Wallet.isLogin() ? Wallet.data.settings.timeWarningExpire : Wallet.defaultSettings.timeWarningExpire; var requirements = res.identities[0]; // Add useful custom fields requirements.hasSelf = true; requirements.needMembership = (requirements.membershipExpiresIn === 0 && requirements.membershipPendingExpiresIn <= 0 ); - requirements.needRenew = !requirements.needMembership && (requirements.membershipExpiresIn <= timeWarningExpire && + requirements.needRenew = !requirements.needMembership && (requirements.membershipExpiresIn <= csSettings.data.timeWarningExpire && requirements.membershipPendingExpiresIn <= 0 ); requirements.canMembershipOut = (requirements.membershipExpiresIn > 0); requirements.pendingMembership = (requirements.membershipPendingExpiresIn > 0); requirements.certificationCount = (requirements.certifications) ? requirements.certifications.length : 0; requirements.willExpireCertificationCount = requirements.certifications ? requirements.certifications.reduce(function(count, cert){ - if (cert.expiresIn <= timeWarningExpire) { + if (cert.expiresIn <= csSettings.data.timeWarningExpire) { return count + 1; } return count; @@ -95,7 +95,6 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic identity.hasSelf = !!(identity.uid && identity.timestamp && identity.sig); // Retrieve certifications - var timeWarningExpire = Wallet.isLogin() ? Wallet.data.settings.timeWarningExpire : Wallet.defaultSettings.timeWarningExpire; var expiresInByPub = requirements.certifications.reduce(function(map, cert){ map[cert.from]=cert.expiresIn; return map; @@ -112,7 +111,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic uid: cert.uids[0], block: (cert.meta && cert.meta.block_number) ? cert.meta.block_number : 0, expiresIn: expiresIn, - willExpire: (expiresIn && expiresIn <= timeWarningExpire), + willExpire: (expiresIn && expiresIn <= csSettings.data.timeWarningExpire), valid: (expiresIn && expiresIn > 0), isMember: cert.isMember }); diff --git a/www/plugins/es/js/controllers/common-controllers.js b/www/plugins/es/js/controllers/common-controllers.js index f6b76222..33f42e71 100644 --- a/www/plugins/es/js/controllers/common-controllers.js +++ b/www/plugins/es/js/controllers/common-controllers.js @@ -1,10 +1,10 @@ angular.module('cesium.es.common.controllers', ['ngResource', 'cesium.es.services']) // Configure menu items - .config(function(PluginServiceProvider, APP_CONFIG) { + .config(function(PluginServiceProvider, csConfig) { 'ngInject'; - var enable = !!APP_CONFIG.DUNITER_NODE_ES; + var enable = !!csConfig.DUNITER_NODE_ES; if (enable) { // Menu extension points PluginServiceProvider.extendState('app', { @@ -36,14 +36,14 @@ angular.module('cesium.es.common.controllers', ['ngResource', 'cesium.es.service /** * Control menu extension */ -function ESMenuExtendController($scope, $rootScope, PluginService, Wallet, APP_CONFIG) { +function ESMenuExtendController($scope, $rootScope, PluginService, Wallet, csConfig) { 'ngInject'; $scope.extensionPoint = PluginService.extensions.points.current.get(); $scope.refreshEnable = function() { $scope.enable = Wallet.data && Wallet.data.settings.plugins && Wallet.data.settings.plugins.es ? Wallet.data.settings.plugins.es.enable : - !!APP_CONFIG.DUNITER_NODE_ES; + !!csConfig.DUNITER_NODE_ES; }; $rootScope.$on(Wallet.events.SETTINGS, $scope.refreshEnable); diff --git a/www/plugins/es/js/controllers/settings-controllers.js b/www/plugins/es/js/controllers/settings-controllers.js index 954188b8..1f9a6f6c 100644 --- a/www/plugins/es/js/controllers/settings-controllers.js +++ b/www/plugins/es/js/controllers/settings-controllers.js @@ -1,10 +1,10 @@ angular.module('cesium.es.settings.controllers', ['cesium.es.services']) // Configure menu items - .config(function(PluginServiceProvider, $stateProvider, APP_CONFIG) { + .config(function(PluginServiceProvider, $stateProvider, csConfig) { 'ngInject'; - var enable = !!APP_CONFIG.DUNITER_NODE_ES; + var enable = !!csConfig.DUNITER_NODE_ES; if (enable) { // Extend settings via extension points PluginServiceProvider.extendState('app.settings', { @@ -38,7 +38,7 @@ angular.module('cesium.es.settings.controllers', ['cesium.es.services']) /* * Settings extend controller */ -function ESExtendSettingsController ($scope, $rootScope, Wallet, PluginService, APP_CONFIG) { +function ESExtendSettingsController ($scope, $rootScope, Wallet, PluginService, csConfig) { 'ngInject'; $scope.extensionPoint = PluginService.extensions.points.current.get(); @@ -47,7 +47,7 @@ function ESExtendSettingsController ($scope, $rootScope, Wallet, PluginService, // Update settings if need $scope.onSettingsLoaded = function() { if ($scope.loading) { - var enable = !!APP_CONFIG.DUNITER_NODE_ES; + var enable = !!csConfig.DUNITER_NODE_ES; if (enable && Wallet.data.settings && Wallet.data.settings.plugins && Wallet.data.settings.plugins.es) { enable = Wallet.data.settings.plugins.es.enable; } @@ -60,7 +60,7 @@ function ESExtendSettingsController ($scope, $rootScope, Wallet, PluginService, /* * Settings extend controller */ -function ESPluginSettingsController ($scope, $rootScope, $q, $translate, $ionicPopup, $ionicHistory, UIUtils, APP_CONFIG, esHttp, esMarket, +function ESPluginSettingsController ($scope, $rootScope, $q, $translate, $ionicPopup, $ionicHistory, UIUtils, csConfig, esHttp, esMarket, esRegistry, esUser, Wallet) { 'ngInject'; @@ -73,11 +73,11 @@ function ESPluginSettingsController ($scope, $rootScope, $q, $translate, $ionic angular.merge($scope.formData, Wallet.data.settings.plugins.es); } else { - $scope.formData.enable = !!APP_CONFIG.DUNITER_NODE_ES; + $scope.formData.enable = !!csConfig.DUNITER_NODE_ES; } if (!$scope.formData.node) { - $scope.formData.node = APP_CONFIG.DUNITER_NODE_ES; + $scope.formData.node = csConfig.DUNITER_NODE_ES; } } $scope.loading = false; diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js index acbcfef3..b8bd39df 100644 --- a/www/plugins/es/js/services/http-services.js +++ b/www/plugins/es/js/services/http-services.js @@ -3,7 +3,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces /** * Elastic Search Http */ -.factory('esHttp', function($q, CryptoUtils, HttpUtils, $rootScope, APP_CONFIG, Wallet) { +.factory('esHttp', function($q, CryptoUtils, HttpUtils, $rootScope, csConfig, Wallet) { 'ngInject'; function esHttp(server) { @@ -203,7 +203,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces }; } - var service = esHttp(APP_CONFIG.DUNITER_NODE_ES); + var service = esHttp(csConfig.DUNITER_NODE_ES); service.instance = esHttp; return service; }) diff --git a/www/plugins/es/js/services/user-services.js b/www/plugins/es/js/services/user-services.js index 57cef594..9cd82e7b 100644 --- a/www/plugins/es/js/services/user-services.js +++ b/www/plugins/es/js/services/user-services.js @@ -1,8 +1,8 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.services']) -.config(function(PluginServiceProvider, APP_CONFIG) { +.config(function(PluginServiceProvider, csConfig) { 'ngInject'; - var enable = !!APP_CONFIG.DUNITER_NODE_ES; + var enable = !!csConfig.DUNITER_NODE_ES; if (enable) { // Will force to load this service PluginServiceProvider.registerEagerLoadingService('esUser'); diff --git a/www/templates/currency/tabs/view_network.html b/www/templates/currency/tabs/view_network.html index ee491a9e..c0c34cee 100644 --- a/www/templates/currency/tabs/view_network.html +++ b/www/templates/currency/tabs/view_network.html @@ -3,11 +3,11 @@ <div class="item item-divider item-icon-right"> {{'PEER.PEERS'|translate}} - <ion-spinner class="icon" icon="android" ng-if="search.lookingForPeers"></ion-spinner> + <ion-spinner class="icon" icon="android" ng-if="loadingPeers"></ion-spinner> </div> <a class="peer-item item item-icon-left" - collection-repeat="peer in search.peers" + collection-repeat="peer in peers" ng-class="{ assertive: !peer.online, balanced: (peer.online && peer.hasMainConsensusBlock), energized: (peer.online && !peer.hasMainConsensusBlock) }" ui-sref="app.view_peer(peer)"> <i class="icon ion-android-globe"></i> diff --git a/www/templates/currency/view_currency.html b/www/templates/currency/view_currency.html index c0bae04c..5288c7d7 100644 --- a/www/templates/currency/view_currency.html +++ b/www/templates/currency/view_currency.html @@ -3,17 +3,15 @@ cache-view="false" > <ion-nav-title> - {{id}} + {{currency}} </ion-nav-title> <ion-nav-buttons side="secondary"> <button class="button button-icon button-clear icon ion-loop" ng-click="refresh()"> </button> </ion-nav-buttons> - <!-- - TODO : use a ng-if to show/hide when small/large - <div class="scroll visible-xs">--> - <div class="scroll"> + + <ion-content scroll="false"> <ion-tabs class="tabs-icon-top tabs-positive has-header"> <ion-tab title="{{'CURRENCY.VIEW.TAB_CURRENCY'|translate}}" icon="ion-stats-bars"> @@ -32,17 +30,6 @@ </ion-tab> </ion-tabs> - </div> + </ion-content> - <!--<ion-content class="has-header hidden-xs"> - <div class="row scroll"> - <div class="col-50 "> - <ng-include src="'templates/explore/tabs/explore_tab_currency.html'"/> - </div> - <div class="col-50 "> - <ng-include src="'templates/explore/tabs/explore_tab_network.html'"/> - </div> - </div> - <div class="scroll-bar scroll-bar-v"></div> - </ion-content>--> </ion-view> diff --git a/www/templates/currency/view_currency_lg.html b/www/templates/currency/view_currency_lg.html index 033bec7e..5c128098 100644 --- a/www/templates/currency/view_currency_lg.html +++ b/www/templates/currency/view_currency_lg.html @@ -11,8 +11,6 @@ <ion-content> <div class="row"> - </div> - </div> <div class="row responsive-sm"> <div class="col col-50 "> <ng-include src="'templates/currency/tabs/view_parameters.html'"></ng-include> diff --git a/www/templates/settings/settings.html b/www/templates/settings/settings.html index a26847a4..ea8f2e47 100644 --- a/www/templates/settings/settings.html +++ b/www/templates/settings/settings.html @@ -79,7 +79,7 @@ <div class="input-label"> {{'SETTINGS.NODE' | translate}} </div> - <span class="item-note dark">{{formData.node}}</span> + <span class="item-note dark">{{formData.node.host}}<ng-if ng-if="formData.node.port && formData.node.port !== 80">:{{formData.node.port}}</ng-if></span> </div> <!-- Allow extension here --> <cs-extension-point name="network"></cs-extension-point> diff --git a/www/templates/wallet/view_wallet.html b/www/templates/wallet/view_wallet.html index e9224854..2f89e958 100644 --- a/www/templates/wallet/view_wallet.html +++ b/www/templates/wallet/view_wallet.html @@ -26,8 +26,8 @@ <h3 class="light" ng-if="!walletData.name && walletData.isMember">{{walletData.uid}}</h3> <h3 class="light" ng-if="!walletData.name && !walletData.isMember">{{::walletData.pubkey | formatPubkey}}</h3> <h4 class="light"> - <span ng-if="!walletData.settings.useRelative">{{convertedBalance | formatInteger}} {{walletData.parameters.currency | abbreviate}}</span> - <span ng-if="walletData.settings.useRelative">{{convertedBalance | formatDecimal}} {{'COMMON.UD' | translate}}<sub>{{walletData.parameters.currency | abbreviate}}</sub></span> + <span ng-if="!$root.settings.useRelative">{{convertedBalance | formatInteger}} {{walletData.parameters.currency | abbreviate}}</span> + <span ng-if="$root.settings.useRelative">{{convertedBalance | formatDecimal}} {{'COMMON.UD' | translate}}<sub>{{walletData.parameters.currency | abbreviate}}</sub></span> </h4> </div> <h4 class="content light" ng-if="loading"> @@ -123,8 +123,8 @@ <span class="item item-pending item-divider" ng-if="walletData.tx.pendings && walletData.tx.pendings.length"> <span translate>ACCOUNT.PENDING_TX</span> <div class="badge item-note"> - <span ng-if="!walletData.settings.useRelative">({{walletData.parameters.currency | abbreviate}})</span> - <span ng-if="walletData.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.parameters.currency | abbreviate}}</sub>)</span> + <span ng-if="!$root.settings.useRelative">({{walletData.parameters.currency | abbreviate}})</span> + <span ng-if="$root.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.parameters.currency | abbreviate}}</sub>)</span> </div> </span> @@ -158,8 +158,8 @@ </h3> <div class="badge badge-pending item-note" ng-class="{'badge-balanced': tx.amount > 0}"> - <span ng-if="!walletData.settings.useRelative">{{::tx.amount | formatInteger}}</span> - <span ng-if="walletData.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> + <span ng-if="!$root.settings.useRelative">{{::tx.amount | formatInteger}}</span> + <span ng-if="$root.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> </div > </span> @@ -167,8 +167,8 @@ <span class="item item-divider" ng-if="!loading"> <span translate>ACCOUNT.LAST_TX</span> <div class="badge item-note"> - <span ng-if="!walletData.settings.useRelative">({{walletData.parameters.currency | abbreviate}})</span> - <span ng-if="walletData.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.parameters.currency | abbreviate}}</sub>)</span> + <span ng-if="!$root.settings.useRelative">({{walletData.parameters.currency | abbreviate}})</span> + <span ng-if="$root.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.parameters.currency | abbreviate}}</sub>)</span> </div> </span> @@ -217,8 +217,8 @@ </h3> <div class="badge item-note" ng-class="{'badge-balanced': tx.amount > 0}"> - <span ng-if="!walletData.settings.useRelative">{{::tx.amount | formatInteger}}</span> - <span ng-if="walletData.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> + <span ng-if="!$root.settings.useRelative">{{::tx.amount | formatInteger}}</span> + <span ng-if="$root.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> </div > </span> </div> diff --git a/www/templates/wallet/view_wallet_tx_error.html b/www/templates/wallet/view_wallet_tx_error.html index 4d0d76c6..c02db0fd 100644 --- a/www/templates/wallet/view_wallet_tx_error.html +++ b/www/templates/wallet/view_wallet_tx_error.html @@ -34,8 +34,8 @@ <span class="item item-divider"> <span translate>ACCOUNT.ERROR_TX_SENDED</span> <div class="badge item-note"> - <span ng-if="!walletData.settings.useRelative">({{walletData.currency | abbreviate}})</span> - <span ng-if="walletData.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.currency | abbreviate}}</sub>)</span> + <span ng-if="!$root.settings.useRelative">({{walletData.currency | abbreviate}})</span> + <span ng-if="$root.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.currency | abbreviate}}</sub>)</span> </div> </span> @@ -66,8 +66,8 @@ {{::tx.comment}}<br/> </h3> <div class="badge item-note assertive"> - <span ng-if="!walletData.settings.useRelative">{{::tx.amount | formatInteger}}</span> - <span ng-if="walletData.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> + <span ng-if="!$root.settings.useRelative">{{::tx.amount | formatInteger}}</span> + <span ng-if="$root.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> </div > </span> @@ -75,8 +75,8 @@ <span class="item item-divider"> <span translate>ACCOUNT.ERROR_TX_RECEIVED</span> <div class="badge item-note"> - <span ng-if="!walletData.settings.useRelative">({{walletData.currency | abbreviate}})</span> - <span ng-if="walletData.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.currency | abbreviate}}</sub>)</span> + <span ng-if="!$root.settings.useRelative">({{walletData.currency | abbreviate}})</span> + <span ng-if="$root.settings.useRelative">({{'COMMON.UD' | translate}}<sub>{{walletData.currency | abbreviate}}</sub>)</span> </div> </span> @@ -107,8 +107,8 @@ {{::tx.comment}}<br/> </h3> <div class="badge badge-assertive item-note"> - <span ng-if="!walletData.settings.useRelative">{{::tx.amount | formatInteger}}</span> - <span ng-if="walletData.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> + <span ng-if="!$root.settings.useRelative">{{::tx.amount | formatInteger}}</span> + <span ng-if="$root.settings.useRelative">{{::tx.amount/walletData.currentUD | formatDecimal}}</span> </div > </span> </div> diff --git a/www/templates/wot/view_certifications.html b/www/templates/wot/view_certifications.html index 1b8667be..e013f73c 100644 --- a/www/templates/wot/view_certifications.html +++ b/www/templates/wot/view_certifications.html @@ -82,8 +82,8 @@ <!-- fab button --> <div class="visible-xs visible-sm"> <button id="fab-certify" class="button button-fab button-fab-bottom-right button-energized-900 spin" - ng-click="certifyIdentity()" - ng-if="canCertify && !alreadyCertified"> + ng-click="certify()" + ng-if="formData.hasSelf && canCertify && !alreadyCertified"> <i class="icon ion-ribbon-b"></i></button> </div> -- GitLab