Merge pull request #959 from cmgustavo/feature/01-newdesign-v2

Feature/01 newdesign v2
This commit is contained in:
Yemel Jardi 2014-07-28 17:08:59 -03:00
commit 6418f5f8b1
50 changed files with 2341 additions and 2132 deletions

1
.gitignore vendored
View File

@ -47,7 +47,6 @@ browser-extensions/chrome/copay-chrome-extension.zip
browser-extensions/firefox/firefox-addon
browser-extensions/firefox/data
browser-extensions/firefox/copay.xpi
version.js
android/package

5
app.js
View File

@ -2,6 +2,11 @@ var express = require('express');
var http = require('http');
var app = express();
app.use('/', express.static(__dirname + '/'));
app.get('*', function(req, res) {
return res.sendfile('index.html');
});
app.start = function(port, callback) {
app.set('port', port);

View File

@ -120,8 +120,6 @@ var defaultConfig = {
storageSalt: 'mjuBtGybi/4=',
},
// theme list
themes: ['default'],
disableVideo: true,
verbose: 1,
};

File diff suppressed because it is too large Load Diff

View File

@ -1,50 +1,106 @@
/*
*
* Copay mobile CSS
*
*/
@media (max-width: 1024px) {
.logo {
background-size: 90px 44px !important;
height: 41px;
}
.header-content {
font-size: 70%;
line-height: 120%;
font-weight: normal;
}
.line-dashed-v {
border: none !important;
.line-dashed-setup-v,
.line-dashed-v,
.line-dashed-h {
border: none;
}
.top-bar-section ul li>a {
font-size: 70%;
}
}
.logo-setup {
margin: 20px 0;
}
@media (max-width: 640px) {
.hide_menu {
.home, .open, .join, .waiting-copayers, .setup, .import, .settings {
margin-top: 0;
}
.sidebar {
display: none;
}
.show_menu {
display: block;
}
.top-bar-section ul li {
width: 100%;
}
.top-bar-section ul li>a {
padding: 0 0 0 15px;
}
.top-bar {
background: #1ABC9C;
}
.header-content .small-7 {
text-align: right !important;
padding-top: 0;
.page, .main {
height: auto;
}
.main {
height: 100%;
margin-top: 40px;
margin-left: 0;
margin-bottom: -40px;
padding-bottom: 60px;
}
.tab-bar {
display: block;
position: fixed;
width: 100%;
z-index: 5;
background: #3C4E60;
}
.left-off-canvas-menu {
background: #2C3E50;
}
.off-canvas-wrap,.inner-wrap{
height:100%;
}
.page{
height:100%;
}
.box-copayers figure {
height: 71px;
width: 71px;
.copayers {
position: relative;
padding: 0;
overflow-y: none;
}
ul.copayer-list img {
width: 30px;
height: 30px;
}
.tab-bar-section {
text-align: left;
}
.setup-page {
height: 100%;
}
.hide-tab-bar {
display: none;
}
.tab-bar h1 {
font-weight: 100;
}
ul.off-canvas-list li a {
border-bottom: 1px solid #425568;
padding: 0.66667rem 1rem;
color: #fff;
}
ul.off-canvas-list li a i {
opacity: 0.6;
}
.box-founds {
background-color: #213140;
}
.left-small {
border-right: 1px solid #425568;
}
}

View File

@ -1,14 +0,0 @@
.logo {
background: transparent url('../img/logo.png') no-repeat;
}
#footer {
background: #333;
color: #fff;
}
.dr-notification {
background-color: #000;
color: #fff;
border: 1px solid #eee;
}

View File

@ -1,349 +0,0 @@
/*
*
* Copay Default Template
*
*/
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 300;
src: local('Ubuntu Light'), local('Ubuntu-Light'), url(../font/ubuntu-light.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 400;
src: local('Ubuntu'), url(../font/ubuntu.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: normal;
font-weight: 700;
src: local('Ubuntu Bold'), local('Ubuntu-Bold'), url(../font/ubuntu-bold.woff) format('woff');
}
@font-face {
font-family: 'Ubuntu';
font-style: italic;
font-weight: 700;
src: local('Ubuntu Bold Italic'), local('Ubuntu-BoldItalic'), url(../font/ubuntu-bold-italic.woff) format('woff');
}
* {
font-family: 'Ubuntu', Helvetica, sans-serif !important;
}
body {
background: #F8F8FB;
}
.logo {
background: transparent url('../img/logo-negative-beta.svg') no-repeat;
background-size: 130px 51px;
}
.top-bar-section li:not(.has-form) a:not(.button) {
background: #1ABC9C;
color: #fff;
}
.top-bar-section li.active:not(.has-form) a:not(.button) {
background: #F8F8FB;
color: #111;
}
.top-bar-section li.active:not(.has-form) a:not(.button):hover, .top-bar-section li:not(.has-form) a:not(.button):hover {
background: #16A085;
color: white;
}
.top-bar-section ul li>a {
color: #111;
}
.header {
background: #2C3E50;
color: white;
}
.header h1, h5, p {
color: #93A9BD;
}
.header h6 {
color: #fff;
}
.header .line-dashed-v {
border-right: 1px dashed #5A6B7D;
}
.header a.button.small-icon {
background: white;
color: #2C3E50;
}
.header a.button.small-icon:hover {
background: #16A085;
color: #fff;
}
.header-content a {
color: #fff;
}
.panel {
color: #333;
background: #FFFFFF;
border: 1px solid #EFEFEF;
}
.transactions .panel {
background: #f5f5f5;
}
.transactions .panel.pending {
background-color: #fff;
}
.btransactions .panel {
background: #fff;
}
.addresses .panel:hover, .addresses .panel.selected {
background: #efefef;
}
a.box-backup {
color: #111;
}
a.box-backup:hover {
background-color: #16A085;
}
a.box-backup:hover i, a.box-backup:hover p {
color: #fff;
}
.panel-sign {
color: #111;
background: #FAE448;
}
.panel-ignore {
color: #fff;
background: #111;
}
.share-wallet.panel {
background-color: #111;
color: #FBE500;
}
.alert-box.success {
background-color: #CDEFE6;
color: #16A085;
border:none;
}
.alert-box.info {
background-color: #DEE6EF;
border:none;
color: #2C3E50;
}
.alert-box.error {
background-color: #E8D7D7;
border:none;
color: #C0392B;
}
.text-warning {
color:#C0392A;
}
small.is-valid {
color: #04B404;
}
small.has-error {
color: #f04124;
}
.radius {
-webkit-border-radius: 10px;
border-radius: 10px;
}
button.radius, .button.radius {
-webkit-border-radius: 5px;
border-radius: 5px;
}
/* SECONDARY */
button.secondary,
.button.secondary {
background-color: #1ABC9C;
color: #fff;
}
button.secondary:hover,
button.secondary:focus,
.button.secondary:hover,
.button.secondary:focus {
background-color: #16A085;
color: #e6e6e6;
}
button.disabled.secondary,
button[disabled].secondary,
.button.disabled.secondary,
.button[disabled].secondary,
button.disabled.secondary:hover,
button.disabled.secondary:focus,
button[disabled].secondary:hover,
button[disabled].secondary:focus,
.button.disabled.secondary:hover,
.button.disabled.secondary:focus,
.button[disabled].secondary:hover,
.button[disabled].secondary:focus {
background-color: #1ABC9C;
color: #E6E6E6;
}
/* PRIMARY */
button.primary,
.button.primary {
background-color: #E67E22;
color: #fff;
}
button.primary:hover,
button.primary:focus,
.button.primary:hover,
.button.primary:focus {
background-color: #D86601;
color: #e6e6e6;
}
button.disabled.primary,
button[disabled].primary,
.button.disabled.primary,
.button[disabled].primary,
button.disabled.primary:hover,
button.disabled.primary:focus,
button[disabled].primary:hover,
button[disabled].primary:focus,
.button.disabled.primary:hover,
.button.disabled.primary:focus,
.button[disabled].primary:hover,
.button[disabled].primary:focus {
background-color: #E67E22;
color: #E6E6E6;
}
/* WARNING */
button.warning,
.button.warning {
background-color: #C0392A;
color: #fff;
}
button.warning:hover,
button.warning:focus,
.button.warning:hover,
.button.warning:focus {
background-color: #82251A;
color: #e6e6e6;
}
button.disabled.warning,
button[disabled].warning,
.button.disabled.warning,
.button[disabled].warning,
button.disabled.warning:hover,
button.disabled.warning:focus,
button[disabled].warning:hover,
button[disabled].warning:focus,
.button.disabled.warning:hover,
.button.disabled.warning:focus,
.button[disabled].warning:hover,
.button[disabled].warning:focus {
background-color: #C0392A;
color: #E6E6E6;
}
.text-gray { color: #999 !important;}
#footer {
background: #2C3E50;
color: #fff;
}
fieldset legend {
background: #F8F8FB;
}
input.ng-invalid-wallet-secret {
background: #FFB6C1;
}
.dr-notification {
background-color: #2C3E50;
color: #bfe2de;
border: 1px solid rgba(4, 94, 123, 0.85);
opacity: 0.9;
}
.dr-notification-close-btn {
background-color: #2C3E50;
color: #fff;
border: 1px solid rgba(4, 94, 123, 0.85);
}
.dr-notification-image.dr-notification-type-info {
color: #FFF;
}
.dr-notification-image.dr-notification-type-warning {
color: #FFA226;
}
.dr-notification-image.dr-notification-type-error {
color: #FF4B4F;
}
.dr-notification-image.dr-notification-type-success {
color: #B4D455;
}
.dr-notification-image.success {
color: #B4D455;
}
.success {
color: #3FBC9C;
}
.box-setup fieldset {
background: #fff;
}
.tooltip {
background: #16A085;
color: #fff;
font-weight: normal;
font-size: 14px;
padding: 3px 5px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
border: 1px solid #16A085;
}
.tooltip>.nub {
border-color:transparent transparent #16A085 transparent;
}
.tooltip.tip-top>.nub {
border-color:#16A085 transparent transparent transparent;
}
.tooltip.tip-right>.nub {
border-color:transparent #16A085 transparent transparent;
}
.tooltip.tip-left>.nub {
border-color:transparent transparent transparent #16A085;
}

1114
index.html

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('AddressesController',
function($scope, $rootScope, $timeout, controllerUtils) {
function($scope, $rootScope, $timeout, $modal, controllerUtils) {
$scope.loading = false;
var w = $rootScope.wallet;
@ -16,8 +16,22 @@ angular.module('copayApp.controllers').controller('AddressesController',
});
};
$scope.selectAddress = function(addr) {
$scope.selectedAddr = addr;
$scope.openAddressModal = function(address) {
var ModalInstanceCtrl = function ($scope, $modalInstance, address) {
$scope.address = address;
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
$modal.open({
templateUrl: 'views/modals/qr-address.html',
windowClass: 'tiny',
controller: ModalInstanceCtrl,
resolve: {
address: function() { return address; }
}
});
};
$rootScope.$watch('addrInfos', function() {
@ -37,9 +51,7 @@ angular.module('copayApp.controllers').controller('AddressesController',
'owned': addrinfo.owned
});
}
$scope.selectedAddr = $scope.addresses[0];
$scope.addrWithFund = $rootScope.receivedFund ? $rootScope.receivedFund[1] : null;
$rootScope.receivedFund = null;
}
}
});
}
);

View File

@ -2,9 +2,11 @@
angular.module('copayApp.controllers').controller('BackupController',
function($scope, $rootScope, backupService, walletFactory, controllerUtils) {
$scope.download = function() {
backupService.download($rootScope.wallet);
};
$scope.downloadBackup = function() {
var w = $rootScope.wallet;
backupService.download(w);
}
$scope.deleteWallet = function() {
var w = $rootScope.wallet;

View File

@ -0,0 +1,21 @@
'use strict';
angular.module('copayApp.controllers').controller('CopayersController',
function($scope, $rootScope, $location, backupService) {
$scope.backup = function() {
var w = $rootScope.wallet;
w.setBackupReady();
backupService.download(w);
};
$scope.downloadBackup = function() {
var w = $rootScope.wallet;
backupService.download(w);
}
$scope.goToWallet = function() {
$location.path('/addresses');
};
});

View File

@ -1,37 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('FooterController', function($rootScope, $sce, $scope, $http) {
$scope.networkName = config.networkName;
if (config.themes && Array.isArray(config.themes) && config.themes[0]) {
$scope.themes = config.themes;
} else {
$scope.themes = ['default'];
}
$scope.theme = 'css/tpl-' + $scope.themes[0] + '.css';
$scope.change_theme = function(name) {
$scope.theme = 'css/tpl-' + name + '.css';
};
$scope.version = copay.version;
$scope.getVideoURL = function(copayer) {
if (config.disableVideo) return;
var vi = $rootScope.videoInfo[copayer]
if (!vi) return;
if ($rootScope.wallet.getOnlinePeerIDs().indexOf(copayer) === -1) {
// peer disconnected, remove his video
delete $rootScope.videoInfo[copayer]
return;
}
var encoded = vi.url;
var url = decodeURI(encoded);
var trusted = $sce.trustAsResourceUrl(url);
return trusted;
};
});

10
js/controllers/home.js Normal file
View File

@ -0,0 +1,10 @@
'use strict';
angular.module('copayApp.controllers').controller('HomeController',
function($scope, $rootScope, walletFactory, notification) {
$scope.loading = false;
if ($rootScope.pendingPayment) {
notification.info('Login Required', 'Please open wallet to complete payment');
}
$scope.hasWallets = walletFactory.getWallets().length > 0 ? true : false;
});

37
js/controllers/join.js Normal file
View File

@ -0,0 +1,37 @@
'use strict';
angular.module('copayApp.controllers').controller('JoinController',
function($scope, $rootScope, walletFactory, controllerUtils, Passphrase, notification) {
$scope.loading = false;
$scope.join = function(form) {
if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields');
return;
}
$scope.loading = true;
walletFactory.network.on('badSecret', function() {});
Passphrase.getBase64Async($scope.joinPassword, function(passphrase) {
walletFactory.joinCreateSession($scope.connectionId, $scope.nickname, passphrase, function(err, w) {
$scope.loading = false;
if (err || !w) {
if (err === 'joinError')
notification.error('Can\'t find peer.');
else if (err === 'walletFull')
notification.error('The wallet is full');
else if (err === 'badNetwork')
notification.error('Network Error', 'The wallet your are trying to join uses a different Bitcoin Network. Check your settings.');
else if (err === 'badSecret')
notification.error('Bad secret', 'The secret string you entered is invalid');
else
notification.error('Unknown error');
controllerUtils.onErrorDigest();
} else {
controllerUtils.startNetwork(w, $scope);
}
});
});
}
});

43
js/controllers/open.js Normal file
View File

@ -0,0 +1,43 @@
'use strict';
angular.module('copayApp.controllers').controller('OpenController',
function($scope, $rootScope, walletFactory, controllerUtils, Passphrase, notification) {
var cmp = function(o1, o2) {
var v1 = o1.show.toLowerCase(),
v2 = o2.show.toLowerCase();
return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0;
};
$scope.loading = false;
$scope.wallets = walletFactory.getWallets().sort(cmp);
$scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null;
$scope.openPassword = '';
$scope.open = function(form) {
if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields');
return;
}
$scope.loading = true;
var password = form.openPassword.$modelValue;
Passphrase.getBase64Async(password, function(passphrase) {
var w, errMsg;
try {
var w = walletFactory.open($scope.selectedWalletId, {
passphrase: passphrase
});
} catch (e) {
errMsg = e.message;
};
if (!w) {
$scope.loading = false;
notification.error('Error', errMsg || 'Wrong password');
$rootScope.$digest();
return;
}
controllerUtils.startNetwork(w, $scope);
});
};
});

View File

@ -2,7 +2,7 @@
var bitcore = require('bitcore');
angular.module('copayApp.controllers').controller('SendController',
function($scope, $rootScope, $window, $location, $timeout, $anchorScroll, $modal, isMobile, notification) {
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification) {
$scope.title = 'Send';
$scope.loading = false;
var satToUnit = 1 / config.unitToSatoshi;
@ -226,7 +226,7 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.openAddressBookModal = function() {
var modalInstance = $modal.open({
templateUrl: 'addressBookModal.html',
templateUrl: 'views/modals/address-book.html',
windowClass: 'tiny',
controller: function($scope, $modalInstance) {

View File

@ -74,7 +74,7 @@ angular.module('copayApp.controllers').controller('SettingsController',
unitToSatoshi: $scope.selectedUnit.value,
}));
var target = ($window.location.origin !== 'null' ? $window.location.origin : '') + $window.location.pathname;
var target = ($window.location.origin !== 'null' ? $window.location.origin : '');
$window.location.href = target;
};

View File

@ -85,4 +85,10 @@ angular.module('copayApp.controllers').controller('SetupController',
});
};
$scope.isSetupWalletPage = 0;
$scope.setupWallet = function() {
$scope.isSetupWalletPage = !$scope.isSetupWalletPage;
};
});

View File

@ -1,25 +1,63 @@
'use strict';
angular.module('copayApp.controllers').controller('HeaderController',
function($scope, $rootScope, $location, notification, $http, $sce, controllerUtils, backupService, walletFactory) {
angular.module('copayApp.controllers').controller('SidebarController',
function($scope, $rootScope, $sce, $location, $http, notification, controllerUtils) {
$scope.version = copay.version;
$scope.networkName = config.networkName;
$scope.menu = [{
'title': 'Addresses',
'icon': 'fi-address-book',
'link': '#/addresses'
'link': 'addresses'
}, {
'title': 'Transactions',
'icon': 'fi-clipboard-pencil',
'link': '#/transactions'
'link': 'transactions'
}, {
'title': 'Send',
'icon': 'fi-arrow-right',
'link': '#/send'
'link': 'send'
}, {
'title': 'More',
'icon': 'fi-download',
'link': '#/backup'
'link': 'backup'
}];
$scope.signout = function() {
logout();
};
// Ensures a graceful disconnect
window.onbeforeunload = logout;
$scope.$on('$destroy', function() {
window.onbeforeunload = undefined;
});
$scope.refresh = function() {
var w = $rootScope.wallet;
w.connectToAll();
if ($rootScope.addrInfos.length > 0) {
controllerUtils.updateBalance(function() {
$rootScope.$digest();
});
}
};
$scope.isActive = function(item) {
return item.link && item.link == $location.path().split('/')[1];
};
function logout() {
var w = $rootScope.wallet;
if (w) {
w.disconnect();
controllerUtils.logout();
}
}
// ng-repeat defined number of times instead of repeating over array?
$scope.getNumber = function(num) {
return new Array(num);
}
@ -41,87 +79,7 @@ angular.module('copayApp.controllers').controller('HeaderController',
}
});
// Init socket handlers (with no wallet yet)
controllerUtils.setSocketHandlers();
$scope.isActive = function(item) {
if (item.link && item.link.replace('#', '') == $location.path()) {
return true;
}
return false;
};
$scope.signout = function() {
logout();
};
$scope.refresh = function() {
var w = $rootScope.wallet;
w.connectToAll();
if ($rootScope.addrInfos.length > 0) {
controllerUtils.updateBalance(function() {
$rootScope.$digest();
});
}
};
$rootScope.isCollapsed = true;
$scope.toggleCollapse = function() {
$rootScope.isCollapsed = !$rootScope.isCollapsed;
};
function logout() {
var w = $rootScope.wallet;
if (w) {
w.disconnect();
controllerUtils.logout();
}
}
// Ensures a graceful disconnect
window.onbeforeunload = logout;
$scope.$on('$destroy', function() {
window.onbeforeunload = undefined;
});
$scope.backup = function() {
var w = $rootScope.wallet;
w.setBackupReady();
backupService.download(w);
};
$scope.dowloadBackup = function() {
var w = $rootScope.wallet;
backupService.download(w);
};
$scope.deleteWallet = function() {
var w = $rootScope.wallet;
w.disconnect();
walletFactory.delete(w.id, function() {
controllerUtils.logout();
});
};
$scope.getVideoURL = function(copayer) {
if (config.disableVideo) return;
var vi = $rootScope.videoInfo[copayer]
if (!vi) return;
if ($rootScope.wallet.getOnlinePeerIDs().indexOf(copayer) === -1) {
// peer disconnected, remove his video
delete $rootScope.videoInfo[copayer]
return;
}
var encoded = vi.url;
var url = decodeURI(encoded);
var trusted = $sce.trustAsResourceUrl(url);
return trusted;
};
});

View File

@ -1,78 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('SigninController',
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, backupService, notification) {
var cmp = function(o1, o2) {
var v1 = o1.show.toLowerCase(),
v2 = o2.show.toLowerCase();
return v1 > v2 ? 1 : (v1 < v2) ? -1 : 0;
};
$rootScope.videoInfo = {};
$scope.loading = false;
$scope.wallets = walletFactory.getWallets().sort(cmp);
$scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null;
$scope.openPassword = '';
if ($rootScope.pendingPayment) {
notification.info('Login Required', 'Please open wallet to complete payment');
}
$scope.open = function(form) {
if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields');
return;
}
$scope.loading = true;
var password = form.openPassword.$modelValue;
Passphrase.getBase64Async(password, function(passphrase) {
var w, errMsg;
try {
var w = walletFactory.open($scope.selectedWalletId, {
passphrase: passphrase
});
} catch (e) {
errMsg = e.message;
};
if (!w) {
$scope.loading = false;
notification.error('Error', errMsg || 'Wrong password');
$rootScope.$digest();
return;
}
controllerUtils.startNetwork(w, $scope);
});
};
$scope.join = function(form) {
if (form && form.$invalid) {
notification.error('Error', 'Please enter the required fields');
return;
}
$scope.loading = true;
walletFactory.network.on('badSecret', function() {});
Passphrase.getBase64Async($scope.joinPassword, function(passphrase) {
walletFactory.joinCreateSession($scope.connectionId, $scope.nickname, passphrase, function(err, w) {
$scope.loading = false;
if (err || !w) {
if (err === 'joinError')
notification.error('Can\'t find peer.');
else if (err === 'walletFull')
notification.error('The wallet is full');
else if (err === 'badNetwork')
notification.error('Network Error', 'The wallet your are trying to join uses a different Bitcoin Network. Check your settings.');
else if (err === 'badSecret')
notification.error('Bad secret', 'The secret string you entered is invalid');
else
notification.error('Unknown error');
controllerUtils.onErrorDigest();
} else {
controllerUtils.startNetwork(w, $scope);
}
});
});
}
});

View File

@ -10,7 +10,7 @@ angular.module('copayApp.controllers').controller('UriPaymentController', functi
$scope.message = $rootScope.pendingPayment.message;
$timeout(function() {
$location.path('signin');
$location.path('/');
}, 1000);

46
js/controllers/video.js Normal file
View File

@ -0,0 +1,46 @@
'use strict';
angular.module('copayApp.controllers').controller('VideoController',
function($scope, $rootScope, $sce) {
$rootScope.videoInfo = {};
// Cached list of copayers
$scope.copayers = $rootScope.wallet.getRegisteredPeerIds();
$scope.copayersList = function() {
return $rootScope.wallet.getRegisteredPeerIds();
}
$scope.hasVideo = function(copayer) {
return $rootScope.videoInfo[copayer.peerId];
}
$scope.isConnected = function(copayer) {
return $rootScope.wallet.getOnlinePeerIDs().indexOf(copayer.peerId) != -1;
}
$scope.isBackupReady = function(copayer) {
return $rootScope.wallet.publicKeyRing.isBackupReady(copayer.copayerId);
}
$scope.getVideoURL = function(copayer) {
if (config.disableVideo) return;
var vi = $scope.videoInfo[copayer.peerId];
if (!vi) return;
if ($scope.isConnected(copayer)) {
// peer disconnected, remove his video
delete $rootScope.videoInfo[copayer.peerId];
return;
}
var encoded = vi.url;
var url = decodeURI(encoded);
var trusted = $sce.trustAsResourceUrl(url);
return trusted;
};
});

View File

@ -23,18 +23,6 @@ angular.module('copayApp.directives')
};
}
])
.directive('notification', ['$rootScope',
function($rootScope) {
return {
restrict: 'A',
link: function(scope, element, attrs, ctrl) {
setTimeout(function() {
scope.$apply(function() {});
}, 5000);
}
};
}
])
.directive('enoughAmount', ['$rootScope',
function($rootScope) {
var bitcore = require('bitcore');
@ -228,4 +216,20 @@ angular.module('copayApp.directives')
}
])
.directive('match', function () {
return {
require: 'ngModel',
restrict: 'A',
scope: {
match: '='
},
link: function(scope, elem, attrs, ctrl) {
scope.$watch(function() {
return (ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)) || scope.match === ctrl.$modelValue;
}, function(currentValue) {
ctrl.$setValidity('match', currentValue);
});
}
};
})
;

View File

@ -7,49 +7,58 @@ angular
$routeProvider
.when('/', {
templateUrl: 'signin.html',
templateUrl: 'views/home.html',
validate: false
})
.when('/signin', {
templateUrl: 'signin.html',
.when('/open', {
templateUrl: 'views/open.html',
validate: false
})
.when('/join', {
templateUrl: 'views/join.html',
validate: false
})
.when('/import', {
templateUrl: 'import.html',
templateUrl: 'views/import.html',
validate: false
})
.when('/setup', {
templateUrl: 'setup.html',
templateUrl: 'views/setup.html',
validate: false
})
.when('/copayers', {
templateUrl: 'views/copayers.html',
validate: true
})
.when('/addresses', {
templateUrl: 'addresses.html',
templateUrl: 'views/addresses.html',
validate: true
})
.when('/transactions', {
templateUrl: 'transactions.html',
templateUrl: 'views/transactions.html',
validate: true
})
.when('/send', {
templateUrl: 'send.html',
templateUrl: 'views/send.html',
validate: true
})
.when('/backup', {
templateUrl: 'backup.html',
templateUrl: 'views/backup.html',
validate: true
})
.when('/settings', {
templateUrl: 'settings.html',
templateUrl: 'views/settings.html',
validate: false
})
.when('/unsupported', {
templateUrl: 'unsupported.html'
templateUrl: 'views/unsupported.html'
})
.when('/uri_payment/:data', {
templateUrl: 'uri_payment.html'
.when('/uri-payment/:data', {
templateUrl: 'views/uri-payment.html'
})
.otherwise({
templateUrl: '404.html'
templateUrl: 'views/errors/404.html',
title: 'Error'
});
});
@ -58,8 +67,8 @@ angular
.module('copayApp')
.config(function($locationProvider) {
$locationProvider
.html5Mode(false);
//.hashPrefix('!');
.html5Mode(true)
.hashPrefix('!');
})
.run(function($rootScope, $location) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
@ -67,7 +76,10 @@ angular
$location.path('unsupported');
} else {
if ((!$rootScope.wallet || !$rootScope.wallet.id) && next.validate) {
$location.path('signin');
$location.path('/');
}
if ($rootScope.wallet && !$rootScope.wallet.isReady()) {
$location.path('/copayers');
}
}
});

View File

@ -27,7 +27,7 @@ angular.module('copayApp.services')
}
}
$location.path('signin');
$location.path('/');
};
root.onError = function(scope) {

View File

@ -242,24 +242,6 @@ directive('notifications', function(notification, $compile) {
* Finally, the directive should have its own controller for
* handling all of the notifications from the notification service
*/
var html =
'<div class="dr-notification-wrapper" ng-repeat="noti in queue">' +
'<div class="dr-notification-close-btn" ng-click="removeNotification(noti)">' +
'<i class="fi-x"></i>' +
'</div>' +
'<div class="dr-notification">' +
'<div class="dr-notification-image dr-notification-type-{{noti.type}}" ng-switch on="noti.image">' +
'<i class="fi-{{noti.icon}}" ng-switch-when="false"></i>' +
'<img ng-src="{{noti.image}}" ng-switch-default />' +
'</div>' +
'<div class="dr-notification-content">' +
'<h3 class="dr-notification-title">{{noti.title}}</h3>' +
'<p class="dr-notification-text">{{noti.content}}</p>' +
'</div>' +
'</div>' +
'</div>';
function link(scope, element, attrs) {
var position = attrs.notifications;
position = position.split(' ');
@ -269,11 +251,10 @@ directive('notifications', function(notification, $compile) {
}
}
return {
restrict: 'A',
scope: {},
template: html,
templateUrl: 'views/includes/notifications.html',
link: link,
controller: ['$scope',
function NotificationsCtrl($scope) {

View File

@ -4,8 +4,8 @@ var UriHandler = function() {};
UriHandler.prototype.register = function() {
var base = window.location.origin + '/';
var url = base + '#/uri_payment/%s';
// navigator.registerProtocolHandler('bitcoin', url, 'Copay');
var url = base + '#/uri-payment/%s';
navigator.registerProtocolHandler('bitcoin', url, 'Copay');
};
angular.module('copayApp.services').value('uriHandler', new UriHandler());

View File

@ -44,7 +44,7 @@ describe("Unit: Controllers", function() {
it('Backup controller #download', function() {
scope.wallet.setEnc('1234567');
expect(saveAsLastCall).equal(null);
scope.download();
scope.downloadBackup();
expect(saveAsLastCall.size).equal(7);
expect(saveAsLastCall.type).equal('text/plain;charset=utf-8');
});
@ -82,6 +82,7 @@ describe("Unit: Controllers", function() {
describe('Address Controller', function() {
var addressCtrl;
beforeEach(angular.mock.module('copayApp'));
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
addressCtrl = $controller('AddressesController', {
@ -92,13 +93,6 @@ describe("Unit: Controllers", function() {
it('should have a AddressesController controller', function() {
expect(scope.loading).equal(false);
});
it('selectedAddr should modify scope', function() {
expect(scope.selectedAddress).equal(undefined);
scope.selectAddress('hola');
expect(scope.selectedAddr).equal('hola');
});
});
describe('Transactions Controller', function() {
@ -220,7 +214,7 @@ describe("Unit: Controllers", function() {
});
describe("Unit: Header Controller", function() {
describe("Unit: Sidebar Controller", function() {
var scope, $httpBackendOut;
var GH = 'https://api.github.com/repos/bitpay/copay/tags';
beforeEach(inject(function($controller, $injector) {
@ -241,7 +235,7 @@ describe("Unit: Controllers", function() {
beforeEach(inject(function($controller, $rootScope) {
rootScope = $rootScope;
scope = $rootScope.$new();
headerCtrl = $controller('HeaderController', {
headerCtrl = $controller('SidebarController', {
$scope: scope,
});
}));
@ -342,11 +336,11 @@ describe("Unit: Controllers", function() {
});
});
describe('Signin Controller', function() {
describe('Open Controller', function() {
var what;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
what = $controller('SigninController', {
what = $controller('OpenController', {
$scope: scope,
});
}));
@ -359,6 +353,20 @@ describe("Unit: Controllers", function() {
scope.open(invalidForm);
});
});
});
describe('Join Controller', function() {
var what;
beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new();
what = $controller('JoinController', {
$scope: scope,
});
}));
it('should exist', function() {
should.exist(what);
});
describe('#join', function() {
it('should work with invalid form', function() {
scope.join(invalidForm);

View File

@ -202,4 +202,37 @@ describe("Unit: Testing Directives", function() {
});
});
describe('Match Password Inputs', function() {
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
$rootScope.availableBalance = 1000;
var element = angular.element(
'<form name="form">' +
'<input type="password" ng-model="walletPassword" name="walletPassword" required>' +
'<input type="password" ng-model="walletPasswordConfirm" name="walletPasswordConfirm" match="walletPassword" required>' +
'</form>'
);
$scope.model = {
walletPassword: null,
walletPasswordConfirm: null
};
$compile(element)($scope);
$scope.$digest();
form = $scope.form;
}));
it('should not validate', function() {
form.walletPassword.$setViewValue('mysecretpassword');
form.walletPasswordConfirm.$setViewValue('mySecretPassword');
$scope.$digest();
expect(form.walletPasswordConfirm.$invalid).to.equal(true);
});
it('should validate', function() {
form.walletPassword.$setViewValue('mysecretpassword123');
form.walletPasswordConfirm.$setViewValue('mysecretpassword123');
$scope.$digest();
expect(form.walletPasswordConfirm.$invalid).to.equal(false);
});
});
});

41
views/addresses.html Normal file
View File

@ -0,0 +1,41 @@
<div class="addresses" ng-controller="AddressesController">
<div ng-show='$root.wallet.isReady()'>
<h1>
Addresses
<span class="button primary small side-bar" ng-click="newAddr()" ng-disabled="loading" loading="Creating"><i class="fi-plus"></i></span>
</h1>
<div class="large-12 medium-12" ng-if="!!(addresses|removeEmpty).length">
<div class="large-12 medium-12" ng-init="showAll=0">
<ul>
<li class="panel radius oh" ng-repeat="addr in addresses|removeEmpty|limitAddress:showAll">
<div class="large-8 small-6 column">
<div class="ellipsis">
<a href="#" ng-click="openAddressModal(addr)"><i
class="fi-thumbnails">&nbsp;</i></a>
<span><contact address="{{addr.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/></span>
<small ng-if="addr.isChange">change</small>
</div>
</div>
<div class="large-4 small-6 column text-right">
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="!$root.updatingBalance">
{{addr.balance || 0|noFractionNumber}} {{$root.unitName}}
</span>
</div>
</li>
</ul>
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|removeEmpty).length != (addresses|removeEmpty|limitAddress).length">
<span ng-if="!showAll">Show all</span>
<span ng-if="showAll">Show less</span>
</a>
</div>
</div>
</div>
</div>

21
views/backup.html Normal file
View File

@ -0,0 +1,21 @@
<div class="backup" ng-controller="BackupController">
<h1>Backup & Delete </h1>
<div class="oh large-12 columns panel">
<h3><i class="fi-download m10r"></i> Backup </h3>
<p class="large-8 columns text-gray"> Its important to back up your wallet so that you can recover your wallet in case of disaster </p>
<div class="large-4 columns">
<a class="button primary expand" ng-click="downloadBackup()">Download File</a>
</div>
</div>
<div class="large-12 columns line-dashed-h m15b"> </div>
<div>
<div class="oh large-12 columns panel">
<h3><i class="fi-minus-circle m10r"></i> Delete Wallet </h3>
<p class="large-8 columns text-gray"> If all funds have been removed from your wallet and you do not wish to have the wallet data stored on your computer anymore, you can delete your wallet. </p>
<div class="large-4 columns">
<a class="button warning expand" ng-really-message="Are you sure to delete this wallet from this computer?" ng-really-click="deleteWallet()"> Delete</a>
</div>
</div>
</div>
</div>

107
views/copayers.html Normal file
View File

@ -0,0 +1,107 @@
<div class="waiting-copayers" ng-controller="CopayersController">
<div ng-if='$root.wallet && $root.wallet.isReady()' ng-init="goToWallet()"></div>
<div class="row" ng-if='$root.wallet && !$root.wallet.isReady() && !loading'>
<div class="large-4 columns logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<div class="large-8 columns line-dashed-setup-v">
<div class="box-setup oh">
<div ng-if="!$root.wallet.publicKeyRing.isComplete()">
<h1 class="text-primary line-sidebar-b">Waiting copayers</h1>
<h3>Share this secret with your other copayers</h3>
<div class="panel">
<div class="ellipsis text-gray
size-14">{{$root.wallet.getSecret()}}</div>
</div>
</div>
<h1 class="text-white line-sidebar-b" ng-if="$root.wallet && $root.wallet.publicKeyRing.isComplete()">New Wallet Created </h1>
<div class="row" ng-show="$root.wallet.publicKeyRing.isComplete()">
<div class="large-4 small-6 columns text-left">
<h3>Download Backup</h3>
</div>
<div class="large-8 small-6 columns text-right">
<h3 class="ellipsis">
<small class="text-gray">
<b>{{$root.wallet.getName()}}</b> :
{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}}
</small>
</h3>
</div>
</div>
<div class="row">
<div class="large-10 small-9 columns text-left">
<h4 class="ellipsis" ng-show="!$root.wallet.publicKeyRing.isComplete()">
Waiting Copayers for {{$root.wallet.getName()}}
</h4>
</div>
<div class="large-2 small-3 columns text-right">
<h4 ng-show="$root.wallet && !$root.wallet.publicKeyRing.isComplete()">
<small class="text-gray">
{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}} wallet
</small>
</h4>
</div>
</div>
<div class="box-setup-copayers p20">
<div class="oh">
<div ng-include="'views/includes/video.html'"></div>
<div ng-if="!$root.wallet.publicKeyRing.isComplete()">
<img
class="waiting br100 no-video"
ng-if="!hasVideo(copayer)"
src="./img/satoshi.gif"
alt="Waiting Copayer"
width="70">
<p class="size-12 text-white text-light m0">
<i class="fi-loop icon-rotate spinner"></i>
Waiting...
</p>
</div>
</div>
<div ng-show="$root.wallet.publicKeyRing.remainingCopayers() > 1">
<div class="line-sidebar-b" ng-if="$root.wallet && $root.wallet.publicKeyRing.isComplete()"></div>
<div class="text-gray m10t" ng-if="$root.wallet && $root.wallet.publicKeyRing.isComplete()">
<i class="text-white fi-loop icon-rotate spinner"></i> Waiting for other
copayers to make a Backup
</div>
</div>
</div>
<div class="text-right">
<a class="text-primary m20r" ng-click="downloadBackup()"
ng-show="!$root.wallet.publicKeyRing.isComplete()">Download seed backup</a>
<button class="button primary m0"
ng-click="backup()"
ng-show="!$root.wallet.publicKeyRing.isBackupReady()"
ng-disabled="!$root.wallet.publicKeyRing.isComplete()">
<span ng-show="$root.wallet.publicKeyRing.isComplete()" >
Backup wallet
</span>
<span ng-show="!$root.wallet.publicKeyRing.isComplete()" >
<span ng-show="$root.wallet.publicKeyRing.remainingCopayers() > 1">
{{ $root.wallet.publicKeyRing.remainingCopayers() }} people have
</span>
<span ng-show="$root.wallet.publicKeyRing.remainingCopayers() == 1">
One person has
</span>
yet to join.
</span>
</button>
<button class="button primary"
disabled="disabled"
ng-show="$root.wallet.publicKeyRing.isBackupReady()">
<span ng-show="$root.wallet.publicKeyRing.remainingBackups() > 1">
{{ $root.wallet.publicKeyRing.remainingBackups() }} people have
</span>
<span ng-show="$root.wallet.publicKeyRing.remainingBackups() == 1">
One person has
</span>
yet to backup the wallet.
</button>
</div>
</div>
</div>
</div> <!-- end of row -->
</div>

7
views/errors/404.html Normal file
View File

@ -0,0 +1,7 @@
<div class="text-center logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<h1 class="text-center text-white">404</h1>
<h3 class="text-center">Page not found</h3>
<p class="text-center"><a href="/">go back...</a></p>

28
views/home.html Normal file
View File

@ -0,0 +1,28 @@
<div class="home" ng-controller="HomeController">
<div class="row">
<div class="large-4 columns logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<div class="large-8 columns line-dashed-setup-v">
<div class="button-setup" ng-show="hasWallets">
<a class="text-white" href="/open">Open a wallet</a>
</div>
<div class="button-setup" ng-show="!hasWallets">
<a class="text-secondary" href="/setup">Create a new wallet</a>
</div>
<div class="button-setup">
<a class="text-primary" href="/join">Join a Wallet in Creation</a>
</div>
<div class="button-setup" ng-show="hasWallets">
<a class="text-secondary" href="/setup">Create a wallet</a>
</div>
<div class="footer-setup">
<a class="right size-12 text-gray" href="/settings"><i class="m10r size-14 fi-wrench"></i>Settings</a>
<a class="left size-12 text-gray" href="/import"><i class="m10r size-14 fi-upload"></i>Import a backup</a>
</div>
</div>
</div>
</div>

33
views/import.html Normal file
View File

@ -0,0 +1,33 @@
<div class="import" ng-controller="ImportController">
<div data-alert class="loading-screen" ng-show="loading">
<i class="size-60 fi-bitcoin-circle icon-rotate spinner"></i>
{{ importStatus }}
</div>
<div class="row" ng-init="choosefile=0; pastetext=0" ng-show="!loading">
<div class="large-4 columns logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<div class="large-8 columns line-dashed-setup-v">
<div class="box-setup">
<h1 class="text-white line-sidebar-b">{{title}}</h1>
<form name="importForm" ng-submit="import(importForm)" novalidate>
<fieldset>
<legend for="backupFile" class="m10b"> Choose backup file from your computer <i class="fi-laptop"></i></legend>
<input type="file" class="form-control" placeholder="Select a backup file" name="backupFile" ng-model="backupFile" ng-file-select>
</fieldset>
<label for="password">Password <small>Required</small></label>
<input type="password" class="form-control" placeholder="Your wallet password" name="password" ng-model="password" required>
<div class="text-right">
<a class="back-button text-white m20r" href="/">&laquo; Back</a>
<button type="submit" class="button primary m0" ng-disabled="importForm.$invalid" loading="Importing">
Import backup
</button>
</div>
</form>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
<div class="dr-notification-wrapper" ng-repeat="noti in queue">
<div class="dr-notification-close-btn" ng-click="removeNotification(noti)">
<i class="fi-x"></i>
</div>
<div class="dr-notification">
<div class="dr-notification-image dr-notification-type-{{noti.type}}" ng-switch on="noti.image">
<i class="fi-{{noti.icon}}" ng-switch-when="false"></i>
<img ng-src="{{noti.image}}" ng-switch-default />
</div>
<div class="dr-notification-content">
<h3 class="dr-notification-title">{{noti.title}}</h3>
<div class="dr-notification-text">{{noti.content}}</div>
</div>
</div>
</div>

View File

@ -0,0 +1,34 @@
<div ng-controller="VideoController" class="copayers">
<div>
<h3>
<i class="fi-torsos-all size-21 m20r"></i>
Copayers
<small class="m15l">
{{$root.wallet.requiredCopayers}} of {{$root.wallet.totalCopayers}}
</small>
</h3>
</div>
<ul class="copayer-list" ng-repeat="copayer in copayers">
<li tooltip="ID: {{copayer.peerId}}" tooltip-placement="top">
<video
ng-if="hasVideo(copayer)"
peer="{{copayer}}" avatar autoplay
ng-class="isConnected(copayer) ? 'online' : 'offline'"
ng-src="{{getVideoURL(copayer)}}"></video>
<img
class="br100 oh no-video m20r"
ng-if="!hasVideo(copayer)"
ng-class="isConnected(copayer) ? 'online' : 'offline'"
src="./img/satoshi.gif"
alt="{{copayer}}">
<span>
<span ng-show="copayer.index == 0">you</span>
<span ng-show="copayer.index > 0">{{copayer.nick}}</span>
</span>
</li>
</ul>
</div>

View File

@ -0,0 +1,55 @@
<div ng-controller="SidebarController">
<header class="text-center">
<div class="text-white m10v">
<a href="/" class="db">
<img src="../img/logo-negative-beta.svg" alt="" width="80">
</a>
<small>v{{version}}</small>
<small ng-if="$root.wallet.getNetworkName()=='livenet'">LIVENET</small>
<small ng-if="$root.wallet.getNetworkName()=='testnet'">TESTNET</small>
</div>
<div class="line-sidebar-b"></div>
<div class="founds size-12 text-center box-founds p10t">
<div class="m10b">
Balance
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="!$root.updatingBalance"
data-options="disable_for_touch:true"
tooltip="{{totalBalanceBTC || 0 |noFractionNumber:8}} BTC"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{totalBalance || 0
|noFractionNumber}} {{$root.unitName}}
</span>
</div>
<div>
Available
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-show="!$root.updatingBalance"
data-options="disable_for_touch:true"
tooltip="{{availableBalanceBTC || 0 |noFractionNumber:8}} BTC"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{availableBalance || 0|noFractionNumber}} {{$root.unitName}}
</span>
</div>
<div class="line-sidebar-b"></div>
</div>
</header>
<ul class="off-canvas-list">
<li data-ng-repeat="item in menu" ui-route="/{{item.link}}" class="nav-item" data-ng-class="{active: isActive(item)}">
<a href="{{item.link}}" ng-click="toggleCollapse()" class="db p20h">
<i class="size-24 m20r {{item.icon}}"></i> {{item.title}}
<span class="label alert round" ng-if="item.link=='transactions' && $root.pendingTxCount > 0">{{$root.pendingTxCount}}</span>
</a>
</li>
<li>
<a href="#" class="db p20h" title="Signout"
ng-click="signout()"><i class="size-24 m20r fi-power"></i> Signout</a>
</li>
</ul>
</div>

View File

@ -0,0 +1,64 @@
<div ng-controller="SidebarController">
<header class="p20">
<div class="text-center">
<a href="/" class="db">
<img src="../img/logo-negative-beta.svg" alt="" width="100">
</a>
<div>
<small>v{{version}}</small>
<small ng-if="$root.wallet.getNetworkName()=='livenet'">LIVENET</small>
<small ng-if="$root.wallet.getNetworkName()=='testnet'">TESTNET</small>
</div>
</div>
<div class="line-sidebar"></div>
<div>
<a href="/addresses" class="name-wallet" tooltip-placement="bottom" tooltip="ID: {{$root.wallet.id}}">
<span>{{$root.wallet.getName()}}</span>
</a>
<a class="button gray small side-bar right" title="Manual Refresh"
ng-disabled="$root.loading"
ng-click="refresh()"><i class="size-16 fi-refresh"></i></a>
</div>
<div class="founds size-14 m10v">
Balance
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="!$root.updatingBalance"
data-options="disable_for_touch:true"
tooltip="{{totalBalanceBTC || 0 |noFractionNumber:8}} BTC"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{totalBalance || 0
|noFractionNumber}} {{$root.unitName}}
</span>
<br>
Available
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-show="!$root.updatingBalance"
data-options="disable_for_touch:true"
tooltip="{{availableBalanceBTC || 0 |noFractionNumber:8}} BTC"
tooltip-trigger="mouseenter"
tooltip-placement="bottom">{{availableBalance || 0|noFractionNumber}} {{$root.unitName}}
</span>
</div>
<div class="line-sidebar"></div>
</header>
<ul class="side-nav">
<li data-ng-repeat="item in menu" ui-route="/{{item.link}}" class="nav-item" data-ng-class="{active: isActive(item)}">
<a href="{{item.link}}" ng-click="toggleCollapse()" class="db p20h">
<i class="size-21 m20r {{item.icon}}"></i> {{item.title}}
<span class="label alert round" ng-if="item.link=='transactions' && $root.pendingTxCount > 0">{{$root.pendingTxCount}}</span>
</a>
</li>
<li>
<a href="#" class="db p20h" title="Signout"
ng-click="signout()"><i class="size-21 m20r fi-power"></i> Signout</a>
</li>
</ul>
<div ng-include="'views/includes/peer-list.html'"></div>
</div>

29
views/includes/video.html Normal file
View File

@ -0,0 +1,29 @@
<div ng-controller="VideoController">
<div class="video-box" ng-repeat="copayer in copayersList()">
<video
ng-if="hasVideo(copayer)"
peer="{{copayer}}" avatar autoplay
ng-class="isConnected(copayer) ? 'online' : 'offline'"
ng-src="{{getVideoURL(copayer)}}"></video>
<img
class="br100 no-video"
ng-if="!hasVideo(copayer)"
ng-class="isConnected(copayer) ? 'online' : 'offline'"
src="./img/satoshi.gif"
alt="{{copayer}}"
width="70">
<div
class="ellipsis"
tooltip="ID: {{copayer.peerId}}"
tooltip-placement="bottom">
<small class="text-gray" ng-show="copayer.index == 0"><i class="fi-check m5r"></i>you</small>
<small class="text-gray" ng-show="copayer.index > 0"><i class="fi-check m5r"></i>{{copayer.nick}}</small>
</div>
<div class="success label m10t" ng-show="isBackupReady(copayer)">
Backup ready
</div>
</div>
</div>

41
views/join.html Normal file
View File

@ -0,0 +1,41 @@
<div class="join" ng-controller="JoinController">
<div data-alert class="loading-screen" ng-show="loading && !failure">
<i class="size-60 fi-bitcoin-circle icon-rotate spinner"></i>
Authenticating and looking for peers...
</div>
<div class="row" ng-show="!loading">
<div class="large-4 columns logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<div class="large-8 columns line-dashed-setup-v">
<div class="box-setup">
<h1 class="text-primary line-sidebar-b">Join a Wallet in Creation</h1>
<form name="joinForm" ng-submit="join(joinForm)" novalidate>
<label for="connectionId"> Wallet Setting </label>
<input id="connectionId" type="text" class="form-control" placeholder="Paste wallet secret here" name="connectionId" ng-model="connectionId" wallet-secret required>
<label for="joinPassword"> User info</label>
<input id="joinPassword" type="text" class="form-control" placeholder="Your name (optional)" name="nickname" ng-model="nickname">
<input type="password" class="form-control"
placeholder="Choose your password" name="joinPassword"
ng-model="$parent.joinPassword"
check-strength="passwordStrength"
tooltip-html-unsafe="Password strength:
<i>{{passwordStrength}}</i><br/><span class='size-12'>Tip: Use lower and uppercase,
numbers and symbols</span>" tooltip-trigger="focus" required>
<input type="password"
placeholder="Repeat password"
name="joinPasswordConfirm"
ng-model="joinPasswordConfirm"
match="joinPassword" required>
<div class="text-right">
<a href="/" class="back-button text-primary m20r">&laquo; Back</a>
<button type="submit" class="button primary m0" ng-disabled="joinForm.$invalid || loading" loading="Joining">Join</button>
</div>
</form>
</div>
</div>
</div> <!-- End !loading -->
</div>

View File

@ -0,0 +1,20 @@
<h2>Add Address Book Entry</h2>
<form name="addressBookForm" ng-submit="submitAddressBook(addressBookForm)" novalidate>
<label for="newaddress">Address
<small ng-hide="!addressBookForm.newaddress.$pristine || newaddress">required</small>
<small class="is-valid" ng-show="!addressBookForm.newaddress.$invalid && newaddress">Valid</small>
<small class="has-error" ng-show="addressBookForm.newaddress.$invalid && newaddress">
Not valid</small>
<input type="text" id="newaddress" name="newaddress" ng-disabled="loading"
placeholder="Address" ng-model="newaddress" valid-address required>
</label>
<label for="newlabel">Label
<small ng-hide="!addressBookForm.newlabel.$pristine || newlabel">required</small>
<input type="text" id="newlabel" name="newlabel" ng-disabled="loading"
placeholder="Label" ng-model="newlabel" required>
</label>
<a class="button warning small default" ng-click="cancel()">Cancel</a>
<input type="submit" class="button small primary right" ng-disabled="addressBookForm.$invalid || loading" value="Add Address">
</form>
<a class="close-reveal-modal" ng-click="cancel()">&#215;</a>

View File

@ -0,0 +1,13 @@
<div class="text-center">
<qrcode size="160" data="{{address.address}}"></qrcode>
<h4>{{address.address}}</h4>
<div>
<span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span>
<span ng-if="!$root.updatingBalance">
{{address.balance || 0|noFractionNumber}} {{$root.unitName}}
</span>
</div>
</div>
<a class="close-reveal-modal" ng-click="cancel()">&#215;</a>

27
views/open.html Normal file
View File

@ -0,0 +1,27 @@
<div class="open" ng-controller="OpenController">
<div data-alert class="loading-screen" ng-show="loading && !failure">
<i class="size-60 fi-bitcoin-circle icon-rotate spinner"></i>
Authenticating and looking for peers...
</div>
<div class="row" ng-show="!loading">
<div class="large-4 columns logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<div class="large-8 columns line-dashed-setup-v">
<div class="box-setup">
<h1 class="text-white line-sidebar-b">Open Wallet</h1>
<form name="openForm" ng-submit="open(openForm)" novalidate>
<select class="form-control" ng-model="selectedWalletId" ng-options="w.id as w.show for w in wallets" required>
</select>
<input type="password" class="form-control" placeholder="Your password" name="openPassword" ng-model="openPassword" required>
<div class="text-right">
<a href="/" class="back-button text-white m20r">&laquo; Back</a>
<button type="submit" class="button white m0" ng-disabled="openForm.$invalid || loading" loading="Opening">Open</button>
</div>
</form>
</div>
</div>
</div> <!-- End !loading -->
</div>

156
views/send.html Normal file
View File

@ -0,0 +1,156 @@
<div class="send" data-ng-controller="SendController">
<div ng-show='$root.wallet.isReady()'>
<h1>{{title}}</h1>
<div class="large-6 columns">
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
<div class="row">
<div class="large-12 columns">
<div class="row collapse">
<label for="address">To address
<small ng-hide="!sendForm.address.$pristine || address">required</small>
<small class="is-valid" ng-show="!sendForm.address.$invalid && address">valid!</small>
<small class="has-error" ng-show="sendForm.address.$invalid && address">
not valid</small>
</label>
<div class="small-10 columns">
<input type="text" id="address" name="address" ng-disabled="loading"
placeholder="Send to" ng-model="address" valid-address required>
<small class="icon-input" ng-show="!sendForm.address.$invalid && address"><i class="fi-check"></i></small>
<small class="icon-input" ng-show="sendForm.address.$invalid && address"><i class="fi-x"></i></small>
</div>
<div class="small-2 columns" ng-hide="showScanner || !isHttp">
<a class="postfix button black" ng-click="openScanner()"><i class="fi-camera"></i></a>
</div>
<div class="small-2 columns" ng-show="showScanner && isHttp">
<a class="postfix button warning" ng-click="cancelScanner()">Cancel</a>
</div>
</div>
<div id="scanner" class="row" ng-if="showScanner">
<div class="text-centered">
<canvas id="qr-canvas" width="200" height="150"></canvas>
<div ng-show="isMobile">
<div id="file-input-wrapper" class="btn btn-primary">
<span class="pull-left text-centered">
<i class="glyphicon glyphicon-refresh icon-rotate"></i>
Get QR code
</span>
<input id="qrcode-camera" type="file" capture="camera" accept="image/*">
</div>
</div>
<div ng-hide="isMobile">
<video id="qrcode-scanner-video" width="300" height="225" ng-hide="isMobile"></video>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<div class="large-6 medium-6 columns">
<div class="row collapse">
<label for="amount">Amount
<small ng-hide="!sendForm.amount.$pristine">required</small>
<small class="is-valid" ng-show="!sendForm.amount.$invalid && !sendForm.amount.$pristine">Valid</small>
<small class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount">
Not valid
</small>
<small ng-show="notEnoughAmount" class="has-error">Insufficient funds</small>
</label>
<div class="small-9 columns">
<input type="number" id="amount" ng-disabled="loading"
name="amount" placeholder="Amount" ng-model="amount"
min="0.0001" max="10000000000" enough-amount required
autocomplete="off"
>
<small class="icon-input" ng-show="!sendForm.amount.$invalid && amount"><i class="fi-check"></i></small>
<small class="icon-input" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount"><i class="fi-x"></i></small>
<a class="small input-note" title="Send all funds"
ng-show="$root.availableBalance > 0"
ng-click="topAmount(sendForm)">
Use all funds ({{getAvailableAmount()}} {{$root.unitName}})
</a>
</div>
<div class="small-3 columns">
<span class="postfix">{{$root.unitName}}</span>
</div>
</div>
</div>
</div>
<div class="row" ng-show="wallet.totalCopayers > 1">
<div class="large-12 columns">
<div class="row collapse">
<label for="comment">Note
<small ng-hide="!sendForm.comment.$pristine">optional</small>
<small class="has-error" ng-show="sendForm.comment.$invalid && !sendForm.comment.$pristine">too long!</small>
</label>
<div class="large-12 columns">
<textarea id="comment" ng-disabled="loading"
name="comment" placeholder="Leave a private message to your copayers" ng-model="commentText" ng-maxlength="100"></textarea>
</div>
</div>
</div>
</div>
<div class="row">
<div class="large-5 medium-3 small-4 columns">
<button type="submit" class="button primary expand text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
Send
</button>
</div>
</div>
</form>
</div>
<div class="large-6 columns show-for-large-up">
<div class="send-note">
<h6>Send to</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.address.$invalid || !address}">
{{address}}&nbsp;
</p>
<h6>Total amount for this transaction:</h6>
<p class="text-gray" ng-class="{'hidden': sendForm.amount.$invalid || !amount > 0}">
<b>{{amount + defaultFee |noFractionNumber}}</b> {{$root.unitName}}
<small>
{{ ((amount + defaultFee) * unitToBtc)|noFractionNumber:8}} BTC <br/>
Including fee of {{defaultFee|noFractionNumber}} {{$root.unitName}}
</small>
</p>
<div ng-show="wallet.totalCopayers > 1">
<h6>Note</h6>
<p ng-class="{'hidden': !commentText}">{{commentText}}</p>
</div>
</div>
</div>
<div class="large-12 columns line-dashed">
<h2>Address Book</h2>
<p class="text-gray" ng-hide="showAddressBook()">Empty. Create an alias for your addresses</p>
<table ng-show="showAddressBook()">
<thead>
<tr>
<th>Label</th>
<th>Address</th>
<th>Creator</th>
<th>Date</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
<tr
ng-repeat="(addr, info) in $root.wallet.addressBook"
ng-class="{'addressbook-disabled': info.hidden}">
<td><a ng-click="copyAddress(addr)" title="Copy address">{{info.label}}</a></td>
<td width="100" class="ellipsis">{{addr}}</td>
<td>{{$root.wallet.publicKeyRing.nicknameForCopayer(info.copayerId)}}</td>
<td><time>{{info.createdTs | amCalendar}}</time></td>
<td><a ng-click="toggleAddressBookEntry(addr)">{{info.hidden ?
'Enable' : 'Disable'}}</a></td>
</tr>
</tbody>
</table>
<button class="button tiny primary text-center" ng-click="openAddressBookModal()">Add New Entry</button>
</div>
</div>
</div>

76
views/settings.html Normal file
View File

@ -0,0 +1,76 @@
<div class="settings" ng-controller="SettingsController">
<div class="row">
<div class="large-4 columns logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<div class="large-8 columns line-dashed-setup-v">
<div class="box-setup">
<h1 class="text-white line-sidebar-b">{{title}}</h1>
<form name="settingsForm">
<fieldset>
<legend>Bitcoin Network</legend>
<input id="network-name" type="checkbox" ng-model="networkName"
ng-true-value="livenet" ng-false-value="testnet" class="form-control" ng-click="changeNetwork()"
ng-disabled="forceNetwork"
ng-checked="networkName == 'livenet' ? true : false">
<label for="network-name">Livenet</label>
<div ng-show="forceNetwork">
Network has been fixed to
<strong>{{networkName}}</strong> in this setup. See <a href="https://copay.io">copay.io</a>
for options to use Copay on both livenet and testnet.
</div>
</fieldset>
<fieldset>
<legend>Wallet Unit</legend>
<select class="form-control" ng-model="selectedUnit" ng-options="o.name for o in unitOpts" required>
</select>
</fieldset>
<fieldset>
<legend>Videoconferencing</legend>
<input id="disableVideo-opt" type="checkbox" ng-model="disableVideo" class="form-control">
<label for="disableVideo-opt">Disable videoconferencing (for slow networks)</label>
</fieldset>
<fieldset>
<legend>Insight API server</legend>
<label for="insight-host">Host</label>
<input type="text" ng-model="insightHost" class="form-control" name="insight-host">
<label for="insight-port">Port</label>
<input type="number" ng-model="insightPort" class="form-control" name="insight-port">
<input id="insight-secure" type="checkbox" ng-model="insightSecure" class="form-control" ng-click="changeInsightSSL()">
<label for="insight-secure">Use SSL</label>
<p class="small">
Insight API server is open-source software. You can run your own
instance, check <a href="http://insight.is" target="_blank">Insight
API Homepage</a></p>
</fieldset>
<fieldset>
<legend>PeerJS server</legend>
<label for="peerjs-key">Key</label>
<input type="text" ng-model="networkKey" class="form-control" name="peerjs-key">
<label for="peerjs-host">Host</label>
<input type="text" ng-model="networkHost" class="form-control" name="peerjs-host">
<label for="peerjs-port">Port</label>
<input type="number" ng-model="networkPort" class="form-control" name="peerjs-port">
<input id="peerjs-secure" type="checkbox" ng-model="networkSecure" class="form-control">
<label for="peerjs-secure">Use SSL</label>
<p class="small">
PeerJS Server is open-source software. You can run your own instance, or use PeerJS Server cloud. Check <a href="http://peerjs.com" target="_blank">PeerJS Server</a>
</p>
</fieldset>
<div class="text-right">
<a class="back-button text-white m20r" href="/">&laquo; Back</a>
<button type="submit" class="button primary m0 ng-binding" ng-disabled="setupForm.$invalid || loading" disabled="disabled" ng-click="save()">
Save
</button>
</div>
</form>
</div>
</div>
</div>
</div>

84
views/setup.html Normal file
View File

@ -0,0 +1,84 @@
<div ng-controller="SetupController">
<div data-alert class="loading-screen" ng-show="loading">
<i class="size-60 fi-bitcoin-circle icon-rotate spinner"></i>
Creating wallet...
</div>
<div class="setup" ng-show="!loading">
<form name="setupForm" ng-submit="create(setupForm)" novalidate>
<div class="row">
<div class="large-4 columns logo-setup text-center">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<div class="large-8 columns line-dashed-setup-v">
<div class="box-setup oh">
<h1 class="text-secondary line-sidebar-b">Create new wallet</h1>
<label ng-show="!isSetupWalletPage">Wallet name
<input type="text" placeholder="Family vacation funds" class="form-control" ng-model="walletName">
</label>
<div class="row" ng-show="isSetupWalletPage">
<div>
<label for="Name">Your name</label>
<input id="Name" type="text" placeholder="Name" class="form-control" ng-model="myNickname">
</div>
<div>
<label for="walletPassword">Your Wallet Password <small data-options="disable_for_touch:true" class="has-tip text-gray" tooltip="doesn't need to be shared" >Required</small>
</label>
<input id="walletPassword" type="password" placeholder="Choose your password" class="form-control"
ng-model="$parent.walletPassword"
name="walletPassword"
check-strength="passwordStrength"
tooltip-html-unsafe="Password strength:
<i>{{passwordStrength}}</i><br/><span
class='size-12'>Tip: Use lower and uppercase, numbers and
symbols</span>"
tooltip-trigger="focus" required
tooltip-placement="left">
<input type="password"
placeholder="Repeat password"
name="walletPasswordConfirm"
ng-model="walletPasswordConfirm"
match="walletPassword"
required>
</div>
</div>
<div class="row" ng-show="!isSetupWalletPage">
<div class="large-6 medium-6 columns">
<label>Select total number of copayers
<select ng-model="totalCopayers" ng-options="totalCopayers as totalCopayers for totalCopayers in TCValues">
</select>
</label>
</div>
<div class="large-6 medium-6 columns">
<label>Select required signatures
<select ng-model="requiredCopayers" ng-options="requiredCopayers as requiredCopayers for requiredCopayers in RCValues">
</select>
</label>
</div>
</div>
<div class="box-setup-copayers" ng-show="!isSetupWalletPage">
<div class="box-setup-copayers p10">
<img class="br100 oh box-setup-copay m10" ng-repeat="i in getNumber(totalCopayers) track by $index"
src="./img/satoshi.gif"
title="Copayer {{$index+1}}-{{totalCopayers}}"
ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}"
width="50px">
</div>
</div>
<div class="text-right">
<a ng-show="!isSetupWalletPage" class="back-button m20r" href="/">&laquo; Back</a>
<a ng-show="isSetupWalletPage" class="back-button m20r"
ng-click="setupWallet()">&laquo; Back</a>
<button ng-show="isSetupWalletPage" type="submit" class="button secondary m0" ng-disabled="setupForm.$invalid || loading">
Create {{requiredCopayers}}-of-{{totalCopayers}} wallet
</button>
<a class="button secondary m0" ng-show="!isSetupWalletPage"
ng-click="setupWallet()">Next</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>

173
views/transactions.html Normal file
View File

@ -0,0 +1,173 @@
<div class="transactions" data-ng-controller="TransactionsController">
<div ng-show='$root.wallet.isReady()'>
<h1 ng-show="wallet.totalCopayers > 1"> Transaction proposals <small>({{txs.length}})</small></h1>
<div class="large-12" ng-show="wallet.totalCopayers > 1">
<ul class="inline-list">
<li> <a class="text-gray size-12" ng-click="show(true)" ng-disabled="loading || onlyPending" loading="Updating" ng-class="{'active' : onlyPending}"> [ Pending ] </a> </li>
<li> <a class="text-gray size-12" ng-click="show()" ng-disabled="loading || !onlyPending" loading="Updating" ng-class="{'active' : !onlyPending}"> [ All ] </a> </li>
</ul>
<div class="last-transactions" ng-repeat="tx in txs | paged">
<div class="last-transactions-header">
<div class="large-8 medium-8 small-12 columns">
<div ng-repeat="out in tx.outs">
<div class="large-3 medium-3 small-3 columns">
{{out.value | noFractionNumber}} {{$root.unitName}}</div>
<div class="large-1 medium-1 small-2 columns fi-arrow-right"> </div>
<div class="large-8 medium-8 small-7 columns ellipsis">
<contact address="{{out.address}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
</div>
</div>
</div>
<div class="large-4 medium-4 small-12 columns text-right">
{{tx.createdTs | amCalendar}}
</div>
</div>
<div class="last-transactions-content">
<div class="box-note large-12" ng-show="tx.comment">
"{{tx.comment}}" - {{$root.wallet.publicKeyRing.nicknameForCopayer(tx.creator)}}
</div>
<div class="box-copayer" ng-repeat="(cId, actions) in tx.peerActions">
<a href="/transactions" class="has-tip" tooltip-popup-delay="1000" tooltip="{{cId === $root.wallet.getMyCopayerId() ? 'You' : $root.wallet.publicKeyRing.nicknameForCopayer(cId)}}">
<img class="copayer-ico br100" src="./img/satoshi.gif" alt="{{cId}}">
</a>
<div class="box-status">
<span ng-repeat="(action, ts) in actions">
<a ng-if="action == 'create'" href="/transactions" tooltip-popup-delay="1000" tooltip="Created {{ts | amTimeAgo}}">
<i class="fi-crown icon-status icon-active"></i>
</a>
<a ng-if="action == 'seen'" href="/transactions" tooltip-popup-delay="1000" tooltip="Seen {{ts | amTimeAgo}}">
<i class="fi-eye icon-status icon-active"></i>
</a>
<a ng-if="action == 'sign'" href="/transactions" tooltip-popup-delay="1000" tooltip="Signed {{ts | amTimeAgo}}">
<i class="fi-check icon-status icon-active-check"></i>
</a>
<a ng-if="action == 'rejected'" href="/transactions" tooltip-popup-delay="1000" tooltip="Rejected {{ts | amTimeAgo}}">
<i class="fi-x icon-status icon-active-x"></i>
</a>
</span>
</div>
<div class="text-center">
{{$root.wallet.publicKeyRing.nicknameForCopayer(cId)}}
</div>
</div>
</div>
<div class="last-transactions-footer">
<div class="large-5 medium-5 columns" ng-show="!tx.sentTs">
<div ng-show="!tx.signedByUs && !tx.rejectedByUs && !tx.finallyRejected && tx.missingSignatures">
<button class="secondary radius m10r" ng-click="sign(tx.ntxid)" ng-disabled="loading" loading="Signing">
<i class="fi-check"></i> Sign
</button>
<button class="warning radius" ng-click="reject(tx.ntxid)" ng-disabled="loading" loading="Rejecting">
<i class="fi-x" ></i> Reject
</button>
</div>
<div ng-show="!tx.missingSignatures && !tx.sentTs">
<button class="primary radius" ng-click="send(tx.ntxid)" ng-disabled="loading" loading="Broadcasting"> <i class=".fi-upload-cloud"></i>
Broadcast Transaction
</button>
</div>
</div>
<div class="large-7 medium-7 columns text-right">
<div ng-show="tx.finallyRejected" class="text-warning m10b">
Transaction finally rejected
</div>
<div ng-show="!tx.missingSignatures && tx.sentTs">
<div class="success m10b">
<strong>Sent</strong> <span class="text-gray" am-time-ago="tx.sentTs"></span>
</div>
<div class="ellipsis small">
Transaction ID:
<a href="http://{{getShortNetworkName()}}.insight.is/tx/{{tx.sentTxid}}" target="blank">
{{tx.sentTxid}}
</a>
</div>
</div>
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures==1">
One signature missing
</p>
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures>1">
{{tx.missingSignatures}} signatures missing</p>
<div class="ellipsis small text-gray">
<strong>Fee:</strong> {{tx.fee|noFractionNumber}} {{$root.unitName}}
<strong>Proposal ID:</strong> {{tx.ntxid}}
</div>
</div>
</div>
</div>
<p ng-show="onlyPending && txs.length == 0">No pending transactions proposals.</p>
<p ng-show="!onlyPending && txs.length == 0">No transactions proposals yet.</p>
<pagination ng-show="!onlyPending && txs.length > txpItemsPerPage" total-items="txs.length" items-per-page="txpItemsPerPage" page="txpCurrentPage" on-select-page="show()" class="pagination-small primary"></pagination>
</div>
<h1 ng-class="{'line-dashed': wallet.totalCopayers > 1}">
Last transactions
<small ng-hide="wallet.totalCopayers > 1 || !loading">
<i class="fi-bitcoin-circle icon-rotate spinner"></i>
</small>
</h1>
<div class="large-12">
<div class="m10b size-12" ng-hide="wallet.totalCopayers == 1">
<a class="text-gray active" ng-click="toogleLast()" ng-disabled="loading" loading="Loading" ng-hide="lastShowed && !loading">[ Show ]</a>
<a class="text-gray" ng-click="toogleLast()" ng-disabled="loading" loading="Loading" ng-show="lastShowed && !loading">[ Hide ]</a>
</div>
<div class="btransactions" ng-if="lastShowed">
<div ng-if="!blockchain_txs[0].txid && !loading">
No transactions yet.
</div>
<div class="last-transactions" ng-repeat="btx in blockchain_txs | orderBy: 'time':true">
<div class="last-transactions-header">
<div class="large-8 columns">
<a class="ellipsis" href="http://{{getShortNetworkName()}}.insight.is/tx/{{btx.txid}}" target="blank">
{{btx.txid}}
</a>
</div>
<div class="large-4 columns text-right">
<div data-ng-show="btx.firstSeenTs">
first seen at
<time>{{btx.firstSeenTs * 1000 | amCalendar}}</time>
</div>
<div data-ng-show="btx.time && !btx.firstSeenTs">
mined at
<time>{{btx.time * 1000 | amCalendar}}</time>
</div>
</div>
</div>
<div class="last-transactions-content">
<div class="large-5 medium-5 small-5 columns">
<div ng-repeat="vin in btx.vinSimple">
<small class="right m5t">{{vin.value| noFractionNumber}} {{$root.unitName}}</small>
<p class="ellipsis text-gray size-12">
<contact address="{{vin.addr}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
</p>
</div>
</div>
<div class="large-1 medium-1 small-1 columns text-center">
<i class="fi-arrow-right"></i>
</div>
<div class="large-6 medium-6 small-6 columns">
<div ng-repeat="vout in btx.voutSimple">
<small class="right m5t">{{vout.value| noFractionNumber}} {{$root.unitName}}</small>
<p class="ellipsis text-gray size-12">
<contact address="{{vout.addr}}" tooltip-popup-delay="500" tooltip tooltip-placement="right"/>
</p>
</div>
</div>
</div>
<div class="last-transactions-footer">
<div class="large-4 medium-4 small-4 columns">Fees: {{btx.fees | noFractionNumber}} {{$root.unitName}}</div>
<div class="large-4 medium-4 small-4 columns text-center">Confirmations: {{btx.confirmations || 0}}</div>
<div class="large-4 medium-4 small-4 columns text-right">Total: {{btx.valueOut| noFractionNumber}} {{$root.unitName}}</div>
</div>
</div>
</div>
</div>
</div>
</div>

14
views/unsupported.html Normal file
View File

@ -0,0 +1,14 @@
<div class="text-center logo-setup">
<img src="../img/logo-negative-beta.svg" alt="Copay">
</div>
<h1 class="text-center text-white">Browser unsupported</h1>
<h3 class="text-center">
Copay uses webRTC for peer-to-peer communications,
but your browser does not support it.
Please use
a current version of Google Chrome, Mozilla Firefox, or Opera.
<br><br>
For more information
on supported browsers please check <a href="http://www.webrtc.org/">http://www.webrtc.org/</a>
</h3>

11
views/uri-payment.html Normal file
View File

@ -0,0 +1,11 @@
<h3 class="text-center">
Preparing payment...
</h3>
<div data-ng-init="" data-ng-controller="UriPaymentController">
<p>Protocol: {{protocol}}</p>
<p>To: {{address}}</p>
<p>Amount: {{amount}}</p>
<p>Message:</p>
<div class="alert-box secondary radius">{{message}}</div>
</div>