<commit_before>/**
 * User controller.
 *
 * @desc This controller is the user controller.
 * @param {!angular.Scope} $scope
 * @constructor
 */
angular.module('app')
    .controller('User', ['$scope', '$interval', '$routeParams', '$location', '$rootScope', 'Connection', '$route', '$timeout',
        function($scope, $interval, $routeParams, $location, $rootScope, Connection, $route, $timeout) {

            $scope.page_loading = true;
            $scope.path = $location.path();

            $scope.$emit('sidebar', true);

            $scope.guid = $routeParams.userId;

            /**
             * Establish message handlers
             * @msg - message from websocket to pass on to handler
             */

            $scope.$evalAsync( function( $scope ) {

                    var listeners = Connection.$$listeners;

                    listeners.load_page = [];
                    Connection.$on('load_page', function(e, msg){ $scope.load_page(msg); });
                    listeners.store_contracts = [];
                    Connection.$on('store_contracts', function(e, msg){ $scope.parse_store_listings(msg); });
                    listeners.store_contract = [];
                    Connection.$on('store_contract', function(e, msg){ $scope.parse_store_contract(msg); });
                    listeners.page = [];
                    Connection.$on('page', function(e, msg){ $scope.parse_page(msg); });
                    Connection.$on('store_products', function(e, msg){ $scope.parse_store_products(msg); });
                    //if(!listeners.hasOwnProperty('new_listing')) {
                    //    Connection.$on('new_listing', function(e, msg){ $scope.parse_new_listing(msg); });
                    //}

                    listeners.no_listings_found = [];
                    Connection.$on('no_listings_found', function(e, msg){ $scope.handle_no_listings(); });

                    listeners.reputation_pledge_update = [];
                    Connection.$on('reputation_pledge_update', function(e, msg){ $scope.parse_reputation_pledge_update(msg); });

            });

            $scope.load_page = function(msg) {
                switch($scope.path) {

                    case "/user/"+$scope.guid+"/products":
                        $scope.queryShop($scope.guid);
                        $scope.store_listings = [];
                        $scope.storeProductsPanel = true;
                        $scope.showStorePanel('storeProducts');
                        break;
                    case "/user/"+$scope.guid+"/services":
                        $scope.queryShop($scope.guid);
                        $scope.storeServicesPanel = true;
                        $scope.showStorePanel('storeServices');
                        break;
                    default:
                        $scope.queryShop($scope.guid);
                        $scope.showStorePanel('storeInfo');
                }

            };

            $scope.parse_reputation_pledge_update = function(msg) {
                $scope.page.reputation_pledge = (msg.value) ? msg.value : 0;
            };

            $scope.compose_message = function(size, myself, address, subject) {
                $rootScope.$broadcast("compose_message", {
                    size: size,
                    myself: myself,
                    bm_address: address,
                    subject: subject
                });
            };

            $scope.compose_inbox_message = function(size, myself, guid, subject) {
                console.log('Composing Inbox Message');
                $rootScope.$broadcast("compose_inbox_message", {
                    size: size,
                    myself: myself,
                    guid: guid,
                    subject: subject
                });
            };

            /**
             * Query the network for a merchant and then
             * show the page
             * @guid - GUID of page to load
             */
            $scope.queryShop = function(guid) {

                $scope.awaitingShop = guid;

                console.log('Querying for shop: ', guid);

                // Tell the user store is probably offline if no response
                setTimeout(function() {
                    if($scope.page_loading) {
                        $scope.page_loading = false;
                        $scope.page_unreachable = true;
                        $scope.$apply();
                    }
                }, 15000);

                var query = {
                    'type': 'query_store_listings',
                    'findGUID': guid
                };

                Connection.send('query_page', query);

            };

            $scope.parse_page = function(msg) {

                console.log('scope', $scope);
                $scope.page_loading = false;

                console.log('Parsing Store Page: ', msg);

                msg.senderNick = msg.senderNick.substring(0, 120);
                msg.text = msg.text.substring(0, 2048);

                if (!$scope.reviews.hasOwnProperty(msg.pubkey)) {
                    $scope.reviews[msg.pubkey] = [];
                }

                $.each($scope.settings.notaries, function(idx, val) {
                    if (val.guid == msg.senderGUID) {
                       msg.isTrustedNotary = true;
                    }
                });

                if (!$scope.dashboard) {
                    $scope.currentReviews = $scope.reviews[msg.pubkey];
                    $scope.page = msg;
                    $scope.page.reputation_pledge = 0;

                    if(('subpanel' in $scope) && $scope.subpanel == 'listings') {
                        $scope.queryStoreProducts($scope.guid);
                    }

                }
                console.log($scope.page);

            };

            // A listing has shown up from the network
            $scope.store_listings = [];
            $scope.parse_new_listing = function(msg) {
                console.log(msg.data);
                var contract_data = msg.data;
                contract_data.key = msg.key;
                contract_data.rawContract = msg.rawContract;
                contract_data.nickname = msg.nickname;
                $scope.store_listings.push(contract_data);
                $scope.store_listings = jQuery.unique($scope.store_listings);
                $.each($scope.store_listings, function(index, contract) {
                    if (jQuery.isEmptyObject(contract.Contract.item_images)) {
                        console.log('empty object');
                        contract.Contract.item_images = "img/no-photo.png";
                    }
                });
                $('#listing-loader').hide();
                console.log('New Listing', $scope.store_listings);
                $scope.$apply();
                $scope.no_listings = false;
            };

            $scope.no_listings = null;
            $scope.handle_no_listings = function() {
                $('#listing-loader').hide();
                $scope.no_listings = true;
            };

            $scope.parse_store_contract = function(msg) {
                var contract = msg.contract;
                var contract_exists = false;

                $.each($scope.store_listings, function(i, listing) {
                    console.log(listing.key, msg.contract.key);
                    if(listing.key == msg.contract.key) {
                        contract_exists = true;
                    }
                });

                if('item_images' in contract) {
                    if (jQuery.isEmptyObject(contract.item_images)) {
                        contract.item_images = "img/no-photo.png";
                    }
                } else {
                    contract.item_images = "img/no-photo.png";
                }

                if(!contract_exists && $scope.guid == contract.senderGUID) {
                    $scope.store_listings.push(contract);
                }

                $('#listing-loader').hide();
                console.log('New Listing', $scope.store_listings);
                $scope.no_listings = false;
            };

            $scope.parse_store_listings = function(msg) {
                var contracts = msg.product;

                $scope.store_listings = [];
                $.each(contracts, function(key, value) {
                    console.log('value', value);
                    $scope.store_listings.push(value.contract_body);
                });

                //$scope.store_listings = jQuery.unique($scope.store_listings);
                $.each($scope.store_listings, function(index, contract) {
                    if (jQuery.isEmptyObject(contract.Contract.item_images)) {
                        contract.Contract.item_images = "img/no-photo.png";
                    }
                });

                $('#listing-loader').hide();
                console.log('New Listing', $scope.store_listings);

            };

            $scope.store_products = {};
            $scope.parse_store_products = function(msg) {

                console.log(msg);
                $scope.store_products = msg.products;
                // $scope.store_products.forEach(function(product) {
                //   console.log(product);
                //
                // })

            };
            $scope.parse_listing_results = function(msg) {
                $scope.store_products = msg.contracts;
            };

            function resetStorePanels() {
                $scope.storeInfoPanel = false;
                $scope.storeProductsPanel = false;
                $scope.storeReviewsPanel = false;
                $scope.storeOrderHistoryPanel = false;
                $scope.storeServicesPanel = false;
            }

            $scope.showStorePanel = function(panelName) {

                resetStorePanels();
                $scope.dashboard = false;

                switch (panelName) {
                    case 'storeInfo':
                        $scope.storeInfoPanel = true;
                        break;
                    case 'storeProducts':
                        $('#listing-loader').show();
                        $scope.store_listings = [];
                        $scope.subpanel = 'listings';
                        Connection.send('get_notaries', {});
                        break;
                    case 'storeOrders':
                        //$scope.storeOrdersPanel = true;
                        break;
                    case 'storeReviews':
                        $scope.storeReviewsPanel = true;
                        break;
                    case 'storeServices':
                        $scope.storeServicesPanel = true;
                        break;

                }

            };

            // Query for product listings from this store
            $scope.queryStoreProducts = function(storeID) {

                console.log('Querying for contracts in store: ' + storeID);
                $scope.storeProductsPanel = true;
                var query = {
                    'type': 'query_store_products',
                    'key': storeID
                };

                //setTimeout(function() {
                Connection.send('query_store_products', query);
                //}, 2000);

            };

            $scope.addNotary = function(guid, nickname) {

                $scope.page.isTrustedNotary = true;

                Connection.send('add_trusted_notary', { 'type': 'add_trusted_notary',
                    'guid': guid,
                    'nickname': nickname
                    }
                );

                Connection.send('refresh_settings', {});
                Notifier.success('Success', 'Notary added successfully.');

            };

            $scope.BuyItemCtrl = function($scope, $modal, $log) {

                $scope.open = function(size, myself, merchantPubkey, listing,
                    notaries, arbiters, btc_pubkey) {

                    // Send socket a request for order info
                    //Connection.send('query_order', { orderId: orderId } )

                    notaries = [];
                    console.log('notaries', $scope.settings.notaries);
                    for(var i in $scope.settings.notaries) {
                        console.log('notary', $scope.settings.notaries[i]);
                        notary = $scope.settings.notaries[i];
                        if(notary.online) {
                            notaries.push(notary);
                        }
                    }

                    var modalInstance = $modal.open({
                        templateUrl: 'partials/modal/buyItem.html',
                        controller: $scope.BuyItemInstanceCtrl,
                        resolve: {
                            merchantPubkey: function() {
                                return merchantPubkey;
                            },
                            myself: function() {
                                return myself;
                            },
                            btc_pubkey: function() {
                                return btc_pubkey;
                            },
                            notaries: function() {
                                return notaries;
                            },
                            arbiters: function() {
                                return arbiters;
                            },
                            listing: function() {
                                return listing;
                            },
                            scope: function() {
                                return $scope;
                            }
                        },
                        size: size
                    });

                    modalInstance.result.then(function() {

                        $scope.showDashboardPanel('orders_purchases');

                        $('#pill-orders').addClass('active').siblings().removeClass('active').blur();
                        $("#orderSuccessAlert").alert();
                        window.setTimeout(function() {
                            $("#orderSuccessAlert").alert('close');
                        }, 5000);

                    }, function() {
                        $log.info('Modal dismissed at: ' + new Date());

                    });
                };
            };

            $scope.BuyItemInstanceCtrl = function($scope, $modalInstance, myself, merchantPubkey, listing,
                notaries,
                arbiters,
                btc_pubkey,
                scope) {

                console.log('Listing Info: ', listing);

                $scope.listing = listing;

                $scope.myself = myself;
                $scope.merchantPubkey = merchantPubkey;
                $scope.productTitle = listing.contract_body.Contract.item_title;
                $scope.productPrice = (listing.contract_body.Contract.item_price !== "") ? +listing.contract_body.Contract.item_price : 0;
                $scope.productDescription = listing.contract_body.Contract.item_desc;
                $scope.productImageData = listing.contract_body.Contract.item_images;
                $scope.productRemoteImages = listing.contract_body.Contract.item_remote_images;
                $scope.shippingPrice = (listing.contract_body.Contract.item_delivery.hasOwnProperty('shipping_price')) ? listing.contract_body.Contract.item_delivery.shipping_price : 0;
                $scope.totalPrice = +(parseFloat($scope.productPrice) + parseFloat($scope.shippingPrice)).toPrecision(8);
                $scope.productQuantity = 1;
                $scope.rawContract = listing.signed_contract_body;
                $scope.guid = listing.contract_body.Seller.seller_GUID;
                $scope.arbiters = arbiters;

                $scope.notaries = notaries;

                $scope.key = listing.key;

                $scope.update = function(user) {
                    console.log('Updated');
                };

                $scope.ok = function() {
                    $modalInstance.close();
                };

                $scope.cancel = function() {
                    $modalInstance.dismiss('cancel');
                };

                $scope.updateTotal = function() {
                    var newPrice = $('#itemQuantity').val() * $scope.productPrice;
                    newPrice = Math.round(newPrice * 100000) / 100000;
                    $('#totalPrice').html(+(parseFloat(newPrice) + parseFloat($scope.shippingPrice)).toPrecision(8));
                };

                $scope.gotoStep3 = function() {
                    $scope.order.step3 = 1;
                    $scope.order.step2 = '';
                };

                $scope.gotoStep2 = function() {
                    $scope.order.step2 = 1;
                    $scope.order.step3 = '';
                };

                $scope.gotoStep1 = function() {
                    $scope.order.step2 = '';
                };

                $scope.order = {
                    message: '',
                    tx: '',
                    listingKey: listing.key,
                    listingTotal: '',
                    productTotal: '',
                    productQuantity: 1,
                    rawContract: listing.signed_contract_body,
                    btc_pubkey: btc_pubkey
                };
                $scope.order.notary = ($scope.notaries.length > 0) ? $scope.notaries[0].guid : "";
                $scope.order.arbiter = $scope.arbiters[0];

                $scope.submitOrder = function() {

                    $scope.creatingOrder = false;
                    $scope.order.step3 = '';
                    $scope.order.step2 = '';
                    $scope.order.step1 = '';
                    $scope.order.confirmation = true;

                    var newOrder = {
                        'message': $scope.order.message,
                        'state': 'New',
                        'buyer': $scope.myself.pubkey,
                        'seller': $scope.merchantPubkey,
                        'sellerGUID': $scope.guid,
                        'listingKey': $scope.key,
                        'orderTotal': $('#totalPrice').html(),

                        'rawContract': $scope.rawContract,
                        'notary': $scope.order.notary,
                        'btc_pubkey': $scope.order.btc_pubkey,
                        'arbiter': $scope.order.arbiter,
                        'buyerRefundAddress': $scope.order.buyerRefundAddress
                    };
                    console.log(newOrder);
                    Connection.send('order', newOrder);
                    $scope.sentOrder = true;

                };

                $scope.closeConfirmation = function() {
                    $modalInstance.close();
                    window.location = '#/orders/purchases';
                };

            };

            if (Connection.websocket.readyState == 1) {
                $scope.load_page({});
            }

        }
    ]);
<commit_msg>Refresh notaries on buy contract<commit_after>/**
 * User controller.
 *
 * @desc This controller is the user controller.
 * @param {!angular.Scope} $scope
 * @constructor
 */
angular.module('app')
    .controller('User', ['$scope', '$interval', '$routeParams', '$location', '$rootScope', 'Connection', '$route', '$timeout',
        function($scope, $interval, $routeParams, $location, $rootScope, Connection, $route, $timeout) {

            $scope.page_loading = true;
            $scope.path = $location.path();

            $scope.$emit('sidebar', true);

            $scope.guid = $routeParams.userId;

            /**
             * Establish message handlers
             * @msg - message from websocket to pass on to handler
             */

            $scope.$evalAsync( function( $scope ) {

                    var listeners = Connection.$$listeners;

                    listeners.load_page = [];
                    Connection.$on('load_page', function(e, msg){ $scope.load_page(msg); });
                    listeners.store_contracts = [];
                    Connection.$on('store_contracts', function(e, msg){ $scope.parse_store_listings(msg); });
                    listeners.store_contract = [];
                    Connection.$on('store_contract', function(e, msg){ $scope.parse_store_contract(msg); });
                    listeners.page = [];
                    Connection.$on('page', function(e, msg){ $scope.parse_page(msg); });
                    Connection.$on('store_products', function(e, msg){ $scope.parse_store_products(msg); });
                    //if(!listeners.hasOwnProperty('new_listing')) {
                    //    Connection.$on('new_listing', function(e, msg){ $scope.parse_new_listing(msg); });
                    //}

                    listeners.no_listings_found = [];
                    Connection.$on('no_listings_found', function(e, msg){ $scope.handle_no_listings(); });

                    listeners.reputation_pledge_update = [];
                    Connection.$on('reputation_pledge_update', function(e, msg){ $scope.parse_reputation_pledge_update(msg); });

            });

            $scope.load_page = function(msg) {
                switch($scope.path) {

                    case "/user/"+$scope.guid+"/products":
                        $scope.queryShop($scope.guid);
                        $scope.store_listings = [];
                        $scope.storeProductsPanel = true;
                        $scope.showStorePanel('storeProducts');
                        break;
                    case "/user/"+$scope.guid+"/services":
                        $scope.queryShop($scope.guid);
                        $scope.storeServicesPanel = true;
                        $scope.showStorePanel('storeServices');
                        break;
                    default:
                        $scope.queryShop($scope.guid);
                        $scope.showStorePanel('storeInfo');
                }

            };

            $scope.parse_reputation_pledge_update = function(msg) {
                $scope.page.reputation_pledge = (msg.value) ? msg.value : 0;
            };

            $scope.compose_message = function(size, myself, address, subject) {
                $rootScope.$broadcast("compose_message", {
                    size: size,
                    myself: myself,
                    bm_address: address,
                    subject: subject
                });
            };

            $scope.compose_inbox_message = function(size, myself, guid, subject) {
                console.log('Composing Inbox Message');
                $rootScope.$broadcast("compose_inbox_message", {
                    size: size,
                    myself: myself,
                    guid: guid,
                    subject: subject
                });
            };

            /**
             * Query the network for a merchant and then
             * show the page
             * @guid - GUID of page to load
             */
            $scope.queryShop = function(guid) {

                $scope.awaitingShop = guid;

                console.log('Querying for shop: ', guid);

                // Tell the user store is probably offline if no response
                setTimeout(function() {
                    if($scope.page_loading) {
                        $scope.page_loading = false;
                        $scope.page_unreachable = true;
                        $scope.$apply();
                    }
                }, 15000);

                var query = {
                    'type': 'query_store_listings',
                    'findGUID': guid
                };

                Connection.send('query_page', query);

            };

            $scope.parse_page = function(msg) {

                console.log('scope', $scope);
                $scope.page_loading = false;

                console.log('Parsing Store Page: ', msg);

                msg.senderNick = msg.senderNick.substring(0, 120);
                msg.text = msg.text.substring(0, 2048);

                if (!$scope.reviews.hasOwnProperty(msg.pubkey)) {
                    $scope.reviews[msg.pubkey] = [];
                }

                $.each($scope.settings.notaries, function(idx, val) {
                    if (val.guid == msg.senderGUID) {
                       msg.isTrustedNotary = true;
                    }
                });

                if (!$scope.dashboard) {
                    $scope.currentReviews = $scope.reviews[msg.pubkey];
                    $scope.page = msg;
                    $scope.page.reputation_pledge = 0;

                    if(('subpanel' in $scope) && $scope.subpanel == 'listings') {
                        $scope.queryStoreProducts($scope.guid);
                    }

                }
                console.log($scope.page);

            };

            // A listing has shown up from the network
            $scope.store_listings = [];
            $scope.parse_new_listing = function(msg) {
                console.log(msg.data);
                var contract_data = msg.data;
                contract_data.key = msg.key;
                contract_data.rawContract = msg.rawContract;
                contract_data.nickname = msg.nickname;
                $scope.store_listings.push(contract_data);
                $scope.store_listings = jQuery.unique($scope.store_listings);
                $.each($scope.store_listings, function(index, contract) {
                    if (jQuery.isEmptyObject(contract.Contract.item_images)) {
                        console.log('empty object');
                        contract.Contract.item_images = "img/no-photo.png";
                    }
                });
                $('#listing-loader').hide();
                console.log('New Listing', $scope.store_listings);
                $scope.$apply();
                $scope.no_listings = false;
            };

            $scope.no_listings = null;
            $scope.handle_no_listings = function() {
                $('#listing-loader').hide();
                $scope.no_listings = true;
            };

            $scope.parse_store_contract = function(msg) {
                var contract = msg.contract;
                var contract_exists = false;

                $.each($scope.store_listings, function(i, listing) {
                    console.log(listing.key, msg.contract.key);
                    if(listing.key == msg.contract.key) {
                        contract_exists = true;
                    }
                });

                if('item_images' in contract) {
                    if (jQuery.isEmptyObject(contract.item_images)) {
                        contract.item_images = "img/no-photo.png";
                    }
                } else {
                    contract.item_images = "img/no-photo.png";
                }

                if(!contract_exists && $scope.guid == contract.senderGUID) {
                    $scope.store_listings.push(contract);
                }

                $('#listing-loader').hide();
                console.log('New Listing', $scope.store_listings);
                $scope.no_listings = false;
            };

            $scope.parse_store_listings = function(msg) {
                var contracts = msg.product;

                $scope.store_listings = [];
                $.each(contracts, function(key, value) {
                    console.log('value', value);
                    $scope.store_listings.push(value.contract_body);
                });

                //$scope.store_listings = jQuery.unique($scope.store_listings);
                $.each($scope.store_listings, function(index, contract) {
                    if (jQuery.isEmptyObject(contract.Contract.item_images)) {
                        contract.Contract.item_images = "img/no-photo.png";
                    }
                });

                $('#listing-loader').hide();
                console.log('New Listing', $scope.store_listings);

            };

            $scope.store_products = {};
            $scope.parse_store_products = function(msg) {

                console.log(msg);
                $scope.store_products = msg.products;
                // $scope.store_products.forEach(function(product) {
                //   console.log(product);
                //
                // })

            };
            $scope.parse_listing_results = function(msg) {
                $scope.store_products = msg.contracts;
            };

            function resetStorePanels() {
                $scope.storeInfoPanel = false;
                $scope.storeProductsPanel = false;
                $scope.storeReviewsPanel = false;
                $scope.storeOrderHistoryPanel = false;
                $scope.storeServicesPanel = false;
            }

            $scope.showStorePanel = function(panelName) {

                resetStorePanels();
                $scope.dashboard = false;

                switch (panelName) {
                    case 'storeInfo':
                        $scope.storeInfoPanel = true;
                        break;
                    case 'storeProducts':
                        $('#listing-loader').show();
                        $scope.store_listings = [];
                        $scope.subpanel = 'listings';
                        Connection.send('get_notaries', {});
                        break;
                    case 'storeOrders':
                        //$scope.storeOrdersPanel = true;
                        break;
                    case 'storeReviews':
                        $scope.storeReviewsPanel = true;
                        break;
                    case 'storeServices':
                        $scope.storeServicesPanel = true;
                        break;

                }

            };

            // Query for product listings from this store
            $scope.queryStoreProducts = function(storeID) {

                console.log('Querying for contracts in store: ' + storeID);
                $scope.storeProductsPanel = true;
                var query = {
                    'type': 'query_store_products',
                    'key': storeID
                };

                //setTimeout(function() {
                Connection.send('query_store_products', query);
                //}, 2000);

            };

            $scope.addNotary = function(guid, nickname) {

                $scope.page.isTrustedNotary = true;

                Connection.send('add_trusted_notary', { 'type': 'add_trusted_notary',
                    'guid': guid,
                    'nickname': nickname
                    }
                );

                Connection.send('refresh_settings', {});
                Notifier.success('Success', 'Notary added successfully.');

            };

            $scope.BuyItemCtrl = function($scope, $modal, $log) {

                $scope.open = function(size, myself, merchantPubkey, listing,
                    notaries, arbiters, btc_pubkey) {

                    // Send socket a request for order info
                    //Connection.send('query_order', { orderId: orderId } )
                    Connection.send('get_notaries', {});

                    notaries = [];
                    console.log('notaries', $scope.settings.notaries);
                    for(var i in $scope.settings.notaries) {
                        console.log('notary', $scope.settings.notaries[i]);
                        notary = $scope.settings.notaries[i];
                        if(notary.online) {
                            notaries.push(notary);
                        }
                    }

                    var modalInstance = $modal.open({
                        templateUrl: 'partials/modal/buyItem.html',
                        controller: $scope.BuyItemInstanceCtrl,
                        resolve: {
                            merchantPubkey: function() {
                                return merchantPubkey;
                            },
                            myself: function() {
                                return myself;
                            },
                            btc_pubkey: function() {
                                return btc_pubkey;
                            },
                            notaries: function() {
                                return notaries;
                            },
                            arbiters: function() {
                                return arbiters;
                            },
                            listing: function() {
                                return listing;
                            },
                            scope: function() {
                                return $scope;
                            }
                        },
                        size: size
                    });

                    modalInstance.result.then(function() {

                        $scope.showDashboardPanel('orders_purchases');

                        $('#pill-orders').addClass('active').siblings().removeClass('active').blur();
                        $("#orderSuccessAlert").alert();
                        window.setTimeout(function() {
                            $("#orderSuccessAlert").alert('close');
                        }, 5000);

                    }, function() {
                        $log.info('Modal dismissed at: ' + new Date());

                    });
                };
            };

            $scope.BuyItemInstanceCtrl = function($scope, $modalInstance, myself, merchantPubkey, listing,
                notaries,
                arbiters,
                btc_pubkey,
                scope) {

                console.log('Listing Info: ', listing);

                $scope.listing = listing;

                $scope.myself = myself;
                $scope.merchantPubkey = merchantPubkey;
                $scope.productTitle = listing.contract_body.Contract.item_title;
                $scope.productPrice = (listing.contract_body.Contract.item_price !== "") ? +listing.contract_body.Contract.item_price : 0;
                $scope.productDescription = listing.contract_body.Contract.item_desc;
                $scope.productImageData = listing.contract_body.Contract.item_images;
                $scope.productRemoteImages = listing.contract_body.Contract.item_remote_images;
                $scope.shippingPrice = (listing.contract_body.Contract.item_delivery.hasOwnProperty('shipping_price')) ? listing.contract_body.Contract.item_delivery.shipping_price : 0;
                $scope.totalPrice = +(parseFloat($scope.productPrice) + parseFloat($scope.shippingPrice)).toPrecision(8);
                $scope.productQuantity = 1;
                $scope.rawContract = listing.signed_contract_body;
                $scope.guid = listing.contract_body.Seller.seller_GUID;
                $scope.arbiters = arbiters;

                $scope.notaries = notaries;

                $scope.key = listing.key;

                $scope.update = function(user) {
                    console.log('Updated');
                };

                $scope.ok = function() {
                    $modalInstance.close();
                };

                $scope.cancel = function() {
                    $modalInstance.dismiss('cancel');
                };

                $scope.updateTotal = function() {
                    var newPrice = $('#itemQuantity').val() * $scope.productPrice;
                    newPrice = Math.round(newPrice * 100000) / 100000;
                    $('#totalPrice').html(+(parseFloat(newPrice) + parseFloat($scope.shippingPrice)).toPrecision(8));
                };

                $scope.gotoStep3 = function() {
                    $scope.order.step3 = 1;
                    $scope.order.step2 = '';
                };

                $scope.gotoStep2 = function() {
                    $scope.order.step2 = 1;
                    $scope.order.step3 = '';
                };

                $scope.gotoStep1 = function() {
                    $scope.order.step2 = '';
                };

                $scope.order = {
                    message: '',
                    tx: '',
                    listingKey: listing.key,
                    listingTotal: '',
                    productTotal: '',
                    productQuantity: 1,
                    rawContract: listing.signed_contract_body,
                    btc_pubkey: btc_pubkey
                };
                $scope.order.notary = ($scope.notaries.length > 0) ? $scope.notaries[0].guid : "";
                $scope.order.arbiter = $scope.arbiters[0];

                $scope.submitOrder = function() {

                    $scope.creatingOrder = false;
                    $scope.order.step3 = '';
                    $scope.order.step2 = '';
                    $scope.order.step1 = '';
                    $scope.order.confirmation = true;

                    var newOrder = {
                        'message': $scope.order.message,
                        'state': 'New',
                        'buyer': $scope.myself.pubkey,
                        'seller': $scope.merchantPubkey,
                        'sellerGUID': $scope.guid,
                        'listingKey': $scope.key,
                        'orderTotal': $('#totalPrice').html(),

                        'rawContract': $scope.rawContract,
                        'notary': $scope.order.notary,
                        'btc_pubkey': $scope.order.btc_pubkey,
                        'arbiter': $scope.order.arbiter,
                        'buyerRefundAddress': $scope.order.buyerRefundAddress
                    };
                    console.log(newOrder);
                    Connection.send('order', newOrder);
                    $scope.sentOrder = true;

                };

                $scope.closeConfirmation = function() {
                    $modalInstance.close();
                    window.location = '#/orders/purchases';
                };

            };

            if (Connection.websocket.readyState == 1) {
                $scope.load_page({});
            }

        }
    ]);
<|endoftext|>


<commit_before>define(module, function(exports, require, make) {

  var http = require('http');
  var fs = require('fs');
  var domain = require('domain');
  var useragent = require('useragent');
  var mustache = require('mustache');
  var mime = require('mime');
  var qp = require('qp-utility');
  var fss = require('qp-library/fss');
  var fso = require('qp-library/fso');
  var url = require('qp-library/url');
  var exit = require('qp-library/exit');
  var log = require('qp-library/log');

  make('qp-library/http_server', {

    name: '',
    port: 80,
    www: '',
    external: {},
    favicon: 'none',
    headers: {},
    cors: {
      preflight: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Credentials': 'true',
        'Access-Control-Allow-Methods': 'GET,POST',
        'Access-Control-Allow-Headers': 'SessionId, Content-Type, Accept, X-Requested-With'
      },
      request: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Credentials': 'true'
      }
    },
    templates: {},
    server: null,

    mime_types: {
      text: mime.lookup('txt'),
      html: mime.lookup('html'),
      css: mime.lookup('css'),
      js: mime.lookup('js'),
      json: mime.lookup('json'),
      ico: mime.lookup('ico')
    },

    handlers: {

      template: function(send, id, data, headers) {
        var template = this.templates[id];
        if (template) {
          send.data(mustache.render(fss.read(template.path), data), this.mime(template.type), headers);
        }
      },

      text: function(send, msg, headers) {
        send.data(msg, this.mime('text'), headers);
      },

      data: function(send, data, mime, headers) {
        send(200, { mime: mime, size: Buffer.byteLength(data) }, data, headers);
      },

      file: function(send, file, headers) {
        if (file.is_file && file.exists) {
          send(200, file, fs.createReadStream(file.fullname), headers);
        }
      },

      json: function(send, o, headers) {
        var data = JSON.stringify(o);
        send(200, { mime: this.mime('json'), size: Buffer.byteLength(data) }, data, headers);
      }

    },

    init: function(config) {
      log.clear();
      exit.handler(this.on_stop);
      if (config.on_request) this.on_request = config.on_request.bind(this);
      this.server = this.create_server();
      this.server.listen(this.port);
      this.on_start();
    },

    create_server: function() {
      return http.createServer(function(req, res) {
        var d = domain.create();
        d.on('error', this.on_error.bind(this, req, res));
        d.add(req);
        d.add(res);
        d.run(this.run_request.bind(this, req, res));
      }.bind(this));
    },

    mime: function(type) {
      return this.mime_types[type] || (this.mime_types[type] = mime.lookup(type));
    },

    read_json: function(req, done) {
      var json = '';
      req.on('data', function(data) { json += data; });
      req.on('end', function() { done(null, JSON.parse(json)); });
    },

    read_agent: function(req) {
      req.ua = useragent.parse(req.headers['user-agent']);
    },

    run_request: function(req, res) {
      var req_url = url.create({ url: req.url });
      var send = this.send.bind(this, req_url, req, res);
      qp.each(this.handlers, function(handler, key) {
        send[key] = handler.bind(this, send);
      }, this);
      if (req.method === 'GET' && req_url.equals('/favicon.ico') && this.favicon === 'none') {
        send(200, { mime: this.mime('ico'), size: 0 }, '');
      } else {
        this.on_request(req.method, req_url, send, req, res);
      }
    },

    send: function(req_url, req, res, status, stat, data, headers) {
      if (!res.done) {
        res.done = true;
        log(log.magenta(res.statusCode), log.blue(qp.rpad(req.method, 4)), req_url.fullname);
        if (arguments.length === 4 && !data) {
          res.writeHead(204, this.headers);
          res.end();
        } else {
          res.writeHead(status, this.create_headers(stat, headers));
          if (qp.is(data, 'string')) {
            res.write(data);
            res.end();
          } else if (qp.is(data, 'readstream')) {
            data.pipe(res);
          } else {
            res.end();
          }
        }
      }
    },

    create_headers: function(stat, headers) {
      if (!stat) { return this.headers; }
      stat.mtime = stat.mtime || qp.now();
      return qp.assign({
        'ETag': JSON.stringify([stat.ino || 'x', stat.size, stat.mtime.getTime()].join('-')),
        'Last-Modified': stat.mtime.toUTCString(),
        'Content-Type': stat.mime,
        'Content-Length': stat.size
      }, this.headers, headers);
    },

    on_request: function(method, url, send) { send(204); },

    on_start: function() {
      log(log.blue_white(' *** %s:%s *** '), this.name, this.port);
    },

    on_stop: function() {
      log('Stopped');
      process.exit(1);
    },

    on_error: function(http_request, http_response, error) {
      try {
        this.server.close();
        log('Exception:', http_request.url);
        log(error.stack);
        http_response.statusCode = 500;
        http_response.setHeader('content-type', 'text/plain');
        http_response.end(error.stack);
      } catch (error1) {
        log(error1.stack);
      } finally {
        log('Terminated');
        process.exit(1);
      }
    }

  });

});
<commit_msg>http_server - improve logging<commit_after>define(module, function(exports, require, make) {

  var http = require('http');
  var fs = require('fs');
  var domain = require('domain');
  var useragent = require('useragent');
  var mustache = require('mustache');
  var mime = require('mime');
  var qp = require('qp-utility');
  var fss = require('qp-library/fss');
  var fso = require('qp-library/fso');
  var url = require('qp-library/url');
  var exit = require('qp-library/exit');
  var log = require('qp-library/log');

  make('qp-library/http_server', {

    name: '',
    port: 80,
    www: '',
    external: {},
    favicon: 'none',
    headers: {},
    cors: {
      preflight: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Credentials': 'true',
        'Access-Control-Allow-Methods': 'GET,POST',
        'Access-Control-Allow-Headers': 'SessionId, Content-Type, Accept, X-Requested-With'
      },
      request: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Credentials': 'true'
      }
    },
    templates: {},
    server: null,

    mime_types: {
      text: mime.lookup('txt'),
      html: mime.lookup('html'),
      css: mime.lookup('css'),
      js: mime.lookup('js'),
      json: mime.lookup('json'),
      ico: mime.lookup('ico')
    },

    handlers: {

      template: function(send, id, data, headers) {
        var template = this.templates[id];
        if (template) {
          send.data(mustache.render(fss.read(template.path), data), this.mime(template.type), headers);
        }
      },

      text: function(send, msg, headers) {
        send.data(msg, this.mime('text'), headers);
      },

      data: function(send, data, mime, headers) {
        send(200, { mime: mime, size: Buffer.byteLength(data) }, data, headers);
      },

      file: function(send, file, headers) {
        if (file.is_file && file.exists) {
          send(200, file, fs.createReadStream(file.fullname), headers);
        }
      },

      json: function(send, o, headers) {
        var data = JSON.stringify(o, null, 2);
        send(200, { mime: this.mime('json'), size: Buffer.byteLength(data) }, data, headers);
      }

    },

    init: function(config) {
      log.clear();
      exit.handler(this.on_stop);
      if (config.on_request) this.on_request = config.on_request.bind(this);
      this.server = this.create_server();
      this.server.listen(this.port);
      this.on_start();
    },

    create_server: function() {
      return http.createServer(function(req, res) {
        var d = domain.create();
        d.on('error', this.on_error.bind(this, req, res));
        d.add(req);
        d.add(res);
        d.run(this.run_request.bind(this, req, res));
      }.bind(this));
    },

    mime: function(type) {
      return this.mime_types[type] || (this.mime_types[type] = mime.lookup(type));
    },

    read_json: function(req, done) {
      var json = '';
      req.on('data', function(data) { json += data; });
      req.on('end', function() { done(null, JSON.parse(json)); });
    },

    read_agent: function(req) {
      req.ua = useragent.parse(req.headers['user-agent']);
    },

    run_request: function(req, res) {
      var req_url = url.create({ url: req.url });
      var send = this.send.bind(this, req_url, req, res);
      qp.each(this.handlers, function(handler, key) {
        send[key] = handler.bind(this, send);
      }, this);
      if (req.method === 'GET' && req_url.equals('/favicon.ico') && this.favicon === 'none') {
        send(200, { mime: this.mime('ico'), size: 0 }, '');
      } else {
        this.on_request(req.method, req_url, send, req, res);
      }
    },

    send: function(req_url, req, res, status, stat, data, headers) {
      if (!res.done) {
        res.done = true;
        log(log.magenta(res.statusCode), log.blue(qp.rpad(req.method, 4)), req_url.fullname);
        if (arguments.length === 4 && !data) {
          res.writeHead(204, this.headers);
          res.end();
        } else {
          res.writeHead(status, this.create_headers(stat, headers));
          if (qp.is(data, 'string')) {
            res.write(data);
            res.end();
          } else if (qp.is(data, 'readstream')) {
            data.pipe(res);
          } else {
            res.end();
          }
        }
      }
    },

    create_headers: function(stat, headers) {
      if (!stat) { return this.headers; }
      stat.mtime = stat.mtime || qp.now();
      return qp.assign({
        'ETag': JSON.stringify([stat.ino || 'x', stat.size, stat.mtime.getTime()].join('-')),
        'Last-Modified': stat.mtime.toUTCString(),
        'Content-Type': stat.mime,
        'Content-Length': stat.size
      }, this.headers, headers);
    },

    on_request: function(method, url, send) { send(204); },

    on_start: function() {
      log(log.blue_white(' *** %s:%s *** '), this.name, this.port);
    },

    on_stop: function() {
      log('Stopped');
      process.exit(1);
    },

    on_error: function(http_request, http_response, error) {
      try {
        this.server.close();
        log('Exception:', http_request.url);
        log(error.stack);
        http_response.statusCode = 500;
        http_response.setHeader('content-type', 'text/plain');
        http_response.end(error.stack);
      } catch (error1) {
        log(error1.stack);
      } finally {
        log('Terminated');
        process.exit(1);
      }
    }

  });

});
<|endoftext|>


<commit_before>var fw = require('knockout/build/output/knockout-latest');
var _ = require('footwork-lodash');

var objectTools = require('./object-tools');
var regExpIsEqual = objectTools.regExpIsEqual;
var sortOfEqual = objectTools.sortOfEqual;

var makeOrGetRequest = require('../misc/ajax').makeOrGetRequest;
var privateDataSymbol = require('../misc/util').getSymbol('footwork');

function collectionSync () {
  return fw.sync.apply(this, arguments);
}

function get (id) {
  var collection = this;
  return _.find(collection(), function findModelWithId (model) {
    var found = _.result(model, collection[privateDataSymbol].getIdAttribute()) === id;
    if(fw.isDataModel(model) && !found) {
      found = model[privateDataSymbol].idAttributeObservable() === id;
    }
    return found;
  });
}

function getData () {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  return _.reduce(collection(), function (models, model) {
    models.push(castAsModelData(model));
    return models;
  }, []);
}

function toJSON () {
  return this.getData();
}

function pluck (attribute) {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  return _.reduce(collection(), function (pluckedValues, model) {
    pluckedValues.push(castAsModelData(model, attribute));
    return pluckedValues;
  }, []);
}

function set (newCollection, options) {
  if (!_.isArray(newCollection)) {
    throw Error('collection.set() must be passed an array of data/dataModels');
  }

  var collection = this;
  var collectionStore = collection();
  var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  var idAttribute = collection[privateDataSymbol].getIdAttribute();
  var affectedModels = [];
  var absentModels = [];
  var addedModels = [];
  options = options || {};

  _.each(newCollection, function checkModelPresence (modelData) {
    var modelPresent = false;
    modelData = castAsModelData(modelData);

    if (!_.isUndefined(modelData)) {
      _.each(collectionStore, function lookForModel (model, indexOfModel) {
        var collectionModelData = castAsModelData(model);

        if (!_.isUndefined(modelData[idAttribute]) && !_.isNull(modelData[idAttribute]) && modelData[idAttribute] === collectionModelData[idAttribute]) {
          modelPresent = true;
          if (options.merge !== false && !sortOfEqual(collectionModelData, modelData)) {
            // found model, but needs an update
            if (_.isFunction(model.set)) {
              model.set.call(model, modelData);
            } else {
              collectionStore[indexOfModel] = modelData;
            }
            collection.$namespace.publish('_.change', model);
            affectedModels.push(model);
          }
        }
      });

      if (!modelPresent && options.add !== false) {
        // not found in collection, we have to add this model
        var newModel = castAsDataModel(modelData);
        collectionStore.push(newModel);
        affectedModels.push(newModel);
        addedModels.push(newModel);
        collection.$namespace.publish('_.add', newModel);
      }
    }
  });

  if (options.remove !== false) {
    _.each(collectionStore, function checkForRemovals (model, indexOfModel) {
      var collectionModelData = castAsModelData(model);
      var modelPresent = false;

      if (collectionModelData) {
        modelPresent = _.reduce(newCollection, function (isPresent, modelData) {
          var bothValuesAreFalsey = !_.result(modelData, idAttribute) && !collectionModelData[idAttribute];
          return isPresent || (_.result(modelData, idAttribute) === collectionModelData[idAttribute] || bothValuesAreFalsey);
        }, false);
      }

      if (!modelPresent) {
        // model currently in collection not found in the supplied newCollection so we need to mark it for removal
        absentModels.push(model);
        affectedModels.push(model);
      }
    });

    if (absentModels.length) {
      _.each(absentModels, function (modelToRemove) {
        var indexOfModelToRemove = collectionStore.indexOf(modelToRemove);
        if (indexOfModelToRemove > -1) {
          collectionStore.splice(indexOfModelToRemove, 1);
        }
      });
      collection.$namespace.publish('_.remove', absentModels);
    }
  }

  // re-sort based on the newCollection
  var reSorted = [];
  var wasResorted = false;
  _.each(newCollection, function (newModelData, modelIndex) {
    newModelData = castAsModelData(newModelData);
    var foundAtIndex = null;
    var currentModel = _.find(collectionStore, function (model, theIndex) {
      if (sortOfEqual(castAsModelData(model), newModelData)) {
        foundAtIndex = theIndex;
        return true;
      }
    });
    reSorted.push(currentModel);
    wasResorted = wasResorted || foundAtIndex !== modelIndex;
  });

  wasResorted = (wasResorted && reSorted.length && _.every(reSorted));

  if (wasResorted) {
    Array.prototype.splice.apply(collectionStore, [0, reSorted.length].concat(reSorted));
  }

  if (wasResorted || addedModels.length || absentModels.length || affectedModels.length) {
    collection.notifySubscribers();
  }

  return affectedModels;
}

function reset (newCollection) {
  var collection = this;
  var oldModels = collection.removeAll();
  var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;

  collection(_.reduce(newCollection, function (newModels, modelData) {
    newModels.push(castAsDataModel(modelData));
    return newModels;
  }, []));

  collection.$namespace.publish('_.reset', { newModels: collection(), oldModels: oldModels });

  return collection();
}

function fetch (options) {
  var ajax = require('../misc/ajax');
  var collection = this;
  var configParams = collection[privateDataSymbol].configParams;
  options = options ? _.clone(options) : {};

  var requestInfo = {
    requestRunning: collection.isFetching,
    requestLull: configParams.requestLull,
    entity: collection,
    createRequest: function () {
      if (_.isUndefined(options.parse)) {
        options.parse = true;
      }

      var xhr = collection.sync('read', collection, options);

      ajax.handleJsonResponse(xhr)
        .then(function handleResponseData (data) {
          if(data) {
            var method = options.reset ? 'reset' : 'set';
            data = configParams.parse(data);
            var touchedModels = collection[method](data, options);

            collection.$namespace.publish('_.change', {
              touched: touchedModels,
              serverResponse: data,
              options: options
            });
          }
        });

      return xhr;
    }
  };

  return makeOrGetRequest('fetch', requestInfo);
}

function where (modelData, options) {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  options = options || {};
  modelData = castAsModelData(modelData);

  return _.reduce(collection(), function findModel (foundModels, model) {
    var thisModelData = castAsModelData(model);
    if (regExpIsEqual(modelData, thisModelData, options.isEqual)) {
      foundModels.push(options.getData ? thisModelData : model);
    }
    return foundModels;
  }, []);
}

function findWhere (modelData, options) {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  options = options || {};
  modelData = castAsModelData(modelData);

  return _.reduce(collection(), function findModel (foundModel, model) {
    var thisModelData = castAsModelData(model);
    if (_.isNull(foundModel) && regExpIsEqual(modelData, thisModelData, options.isEqual)) {
      return options.getData ? thisModelData : model;
    }
    return foundModel;
  }, null);
}

function addModel (models, options) {
  var collection = this;
  var affectedModels = [];
  options = options || {};

  if (_.isObject(models)) {
    models = [models];
  }
  if (!_.isArray(models)) {
    models = !_.isUndefined(models) && !_.isNull(models) ? [models] : [];
  }

  if (models.length) {
    var collectionData = collection();
    var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;
    var castAsModelData = collection[privateDataSymbol].castAs.modelData;
    var idAttribute = collection[privateDataSymbol].getIdAttribute();

    if (_.isNumber(options.at)) {
      var newModels = _.map(models, castAsDataModel);

      collectionData.splice.apply(collectionData, [options.at, 0].concat(newModels));
      affectedModels.concat(newModels);
      collection.$namespace.publish('_.add', newModels);

      collection.valueHasMutated();
    } else {
      _.each(models, function checkModelPresence (modelData) {
        var modelPresent = false;
        var theModelData = castAsModelData(modelData);

        _.each(collectionData, function lookForModel (model) {
          var collectionModelData = castAsModelData(model);

          if (!_.isUndefined(theModelData[idAttribute]) && !_.isNull(theModelData[idAttribute]) && theModelData[idAttribute] === collectionModelData[idAttribute]) {
            modelPresent = true;
            if (options.merge && !sortOfEqual(theModelData, collectionModelData)) {
              // found model, but needs an update
              (model.set || noop).call(model, theModelData);
              collection.$namespace.publish('_.change', model);
              affectedModels.push(model);
            }
          }
        });

        if (!modelPresent) {
          // not found in collection, we have to add this model
          var newModel = castAsDataModel(modelData);
          collection.push(newModel);
          affectedModels.push(newModel);
        }
      });
    }
  }

  return affectedModels;
}

function create (model, options) {
  var ajax = require('../misc/ajax');
  var collection = this;
  var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;
  var configParams = collection[privateDataSymbol].configParams;
  options = options ? _.clone(options) : {};

  var requestInfo = {
    requestRunning: collection.isCreating,
    requestLull: configParams.requestLull,
    entity: collection,
    allowConcurrent: true,
    createRequest: function () {
      var newModel = castAsDataModel(model);
      var xhr;

      if (fw.isDataModel(newModel)) {
        xhr = newModel.save();

        if (options.wait) {
          ajax.handleJsonResponse(xhr)
            .then(function (responseData) {
              responseData && collection.addModel(newModel);
            });
        } else {
          collection.addModel(newModel)
        }
      } else {
        return newModel;
      }

      return xhr;
    }
  };

  if (!_.isFunction(configParams.dataModel)) {
    throw Error('No dataModel specified, cannot create() a new collection item');
  }

  return makeOrGetRequest('create', requestInfo);
}

function removeModel (models) {
  var collection = this;
  var affectedModels = [];

  if (_.isObject(models)) {
    models = [models];
  }
  if (!_.isArray(models)) {
    models = !_.isUndefined(models) && !_.isNull(models) ? [models] : [];
  }

  return _.reduce(models, function (removedModels, model) {
    var removed = null;
    if (fw.isDataModel(model)) {
      removed = collection.remove(model);
    } else {
      var modelsToRemove = collection.where(model);
      if (!_.isNull(modelsToRemove)) {
        removed = collection.removeAll(modelsToRemove);
      }
    }

    if (!_.isNull(removed)) {
      return removedModels.concat(removed);
    }
    return removedModels;
  }, []);
}

module.exports = {
  sync: collectionSync,
  get: get,
  getData: getData,
  toJSON: toJSON,
  pluck: pluck,
  set: set,
  reset: reset,
  fetch: fetch,
  where: where,
  findWhere: findWhere,
  addModel: addModel,
  create: create,
  removeModel: removeModel
};
<commit_msg>now relying on collection idAttribute configuration only<commit_after>var fw = require('knockout/build/output/knockout-latest');
var _ = require('footwork-lodash');

var objectTools = require('./object-tools');
var regExpIsEqual = objectTools.regExpIsEqual;
var sortOfEqual = objectTools.sortOfEqual;

var makeOrGetRequest = require('../misc/ajax').makeOrGetRequest;
var privateDataSymbol = require('../misc/util').getSymbol('footwork');

function collectionSync () {
  return fw.sync.apply(this, arguments);
}

function get (id) {
  var collection = this;
  return _.find(collection(), function findModelWithId (model) {
    return _.result(model, collection[privateDataSymbol].getIdAttribute()) === id;
  });
}

function getData () {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  return _.reduce(collection(), function (models, model) {
    models.push(castAsModelData(model));
    return models;
  }, []);
}

function toJSON () {
  return this.getData();
}

function pluck (attribute) {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  return _.reduce(collection(), function (pluckedValues, model) {
    pluckedValues.push(castAsModelData(model, attribute));
    return pluckedValues;
  }, []);
}

function set (newCollection, options) {
  if (!_.isArray(newCollection)) {
    throw Error('collection.set() must be passed an array of data/dataModels');
  }

  var collection = this;
  var collectionStore = collection();
  var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  var idAttribute = collection[privateDataSymbol].getIdAttribute();
  var affectedModels = [];
  var absentModels = [];
  var addedModels = [];
  options = options || {};

  _.each(newCollection, function checkModelPresence (modelData) {
    var modelPresent = false;
    modelData = castAsModelData(modelData);

    if (!_.isUndefined(modelData)) {
      _.each(collectionStore, function lookForModel (model, indexOfModel) {
        var collectionModelData = castAsModelData(model);

        if (!_.isUndefined(modelData[idAttribute]) && !_.isNull(modelData[idAttribute]) && modelData[idAttribute] === collectionModelData[idAttribute]) {
          modelPresent = true;
          if (options.merge !== false && !sortOfEqual(collectionModelData, modelData)) {
            // found model, but needs an update
            if (_.isFunction(model.set)) {
              model.set.call(model, modelData);
            } else {
              collectionStore[indexOfModel] = modelData;
            }
            collection.$namespace.publish('_.change', model);
            affectedModels.push(model);
          }
        }
      });

      if (!modelPresent && options.add !== false) {
        // not found in collection, we have to add this model
        var newModel = castAsDataModel(modelData);
        collectionStore.push(newModel);
        affectedModels.push(newModel);
        addedModels.push(newModel);
        collection.$namespace.publish('_.add', newModel);
      }
    }
  });

  if (options.remove !== false) {
    _.each(collectionStore, function checkForRemovals (model, indexOfModel) {
      var collectionModelData = castAsModelData(model);
      var modelPresent = false;

      if (collectionModelData) {
        modelPresent = _.reduce(newCollection, function (isPresent, modelData) {
          var bothValuesAreFalsey = !_.result(modelData, idAttribute) && !collectionModelData[idAttribute];
          return isPresent || (_.result(modelData, idAttribute) === collectionModelData[idAttribute] || bothValuesAreFalsey);
        }, false);
      }

      if (!modelPresent) {
        // model currently in collection not found in the supplied newCollection so we need to mark it for removal
        absentModels.push(model);
        affectedModels.push(model);
      }
    });

    if (absentModels.length) {
      _.each(absentModels, function (modelToRemove) {
        var indexOfModelToRemove = collectionStore.indexOf(modelToRemove);
        if (indexOfModelToRemove > -1) {
          collectionStore.splice(indexOfModelToRemove, 1);
        }
      });
      collection.$namespace.publish('_.remove', absentModels);
    }
  }

  // re-sort based on the newCollection
  var reSorted = [];
  var wasResorted = false;
  _.each(newCollection, function (newModelData, modelIndex) {
    newModelData = castAsModelData(newModelData);
    var foundAtIndex = null;
    var currentModel = _.find(collectionStore, function (model, theIndex) {
      if (sortOfEqual(castAsModelData(model), newModelData)) {
        foundAtIndex = theIndex;
        return true;
      }
    });
    reSorted.push(currentModel);
    wasResorted = wasResorted || foundAtIndex !== modelIndex;
  });

  wasResorted = (wasResorted && reSorted.length && _.every(reSorted));

  if (wasResorted) {
    Array.prototype.splice.apply(collectionStore, [0, reSorted.length].concat(reSorted));
  }

  if (wasResorted || addedModels.length || absentModels.length || affectedModels.length) {
    collection.notifySubscribers();
  }

  return affectedModels;
}

function reset (newCollection) {
  var collection = this;
  var oldModels = collection.removeAll();
  var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;

  collection(_.reduce(newCollection, function (newModels, modelData) {
    newModels.push(castAsDataModel(modelData));
    return newModels;
  }, []));

  collection.$namespace.publish('_.reset', { newModels: collection(), oldModels: oldModels });

  return collection();
}

function fetch (options) {
  var ajax = require('../misc/ajax');
  var collection = this;
  var configParams = collection[privateDataSymbol].configParams;
  options = options ? _.clone(options) : {};

  var requestInfo = {
    requestRunning: collection.isFetching,
    requestLull: configParams.requestLull,
    entity: collection,
    createRequest: function () {
      if (_.isUndefined(options.parse)) {
        options.parse = true;
      }

      var xhr = collection.sync('read', collection, options);

      ajax.handleJsonResponse(xhr)
        .then(function handleResponseData (data) {
          if(data) {
            var method = options.reset ? 'reset' : 'set';
            data = configParams.parse(data);
            var touchedModels = collection[method](data, options);

            collection.$namespace.publish('_.change', {
              touched: touchedModels,
              serverResponse: data,
              options: options
            });
          }
        });

      return xhr;
    }
  };

  return makeOrGetRequest('fetch', requestInfo);
}

function where (modelData, options) {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  options = options || {};
  modelData = castAsModelData(modelData);

  return _.reduce(collection(), function findModel (foundModels, model) {
    var thisModelData = castAsModelData(model);
    if (regExpIsEqual(modelData, thisModelData, options.isEqual)) {
      foundModels.push(options.getData ? thisModelData : model);
    }
    return foundModels;
  }, []);
}

function findWhere (modelData, options) {
  var collection = this;
  var castAsModelData = collection[privateDataSymbol].castAs.modelData;
  options = options || {};
  modelData = castAsModelData(modelData);

  return _.reduce(collection(), function findModel (foundModel, model) {
    var thisModelData = castAsModelData(model);
    if (_.isNull(foundModel) && regExpIsEqual(modelData, thisModelData, options.isEqual)) {
      return options.getData ? thisModelData : model;
    }
    return foundModel;
  }, null);
}

function addModel (models, options) {
  var collection = this;
  var affectedModels = [];
  options = options || {};

  if (_.isObject(models)) {
    models = [models];
  }
  if (!_.isArray(models)) {
    models = !_.isUndefined(models) && !_.isNull(models) ? [models] : [];
  }

  if (models.length) {
    var collectionData = collection();
    var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;
    var castAsModelData = collection[privateDataSymbol].castAs.modelData;
    var idAttribute = collection[privateDataSymbol].getIdAttribute();

    if (_.isNumber(options.at)) {
      var newModels = _.map(models, castAsDataModel);

      collectionData.splice.apply(collectionData, [options.at, 0].concat(newModels));
      affectedModels.concat(newModels);
      collection.$namespace.publish('_.add', newModels);

      collection.valueHasMutated();
    } else {
      _.each(models, function checkModelPresence (modelData) {
        var modelPresent = false;
        var theModelData = castAsModelData(modelData);

        _.each(collectionData, function lookForModel (model) {
          var collectionModelData = castAsModelData(model);

          if (!_.isUndefined(theModelData[idAttribute]) && !_.isNull(theModelData[idAttribute]) && theModelData[idAttribute] === collectionModelData[idAttribute]) {
            modelPresent = true;
            if (options.merge && !sortOfEqual(theModelData, collectionModelData)) {
              // found model, but needs an update
              (model.set || noop).call(model, theModelData);
              collection.$namespace.publish('_.change', model);
              affectedModels.push(model);
            }
          }
        });

        if (!modelPresent) {
          // not found in collection, we have to add this model
          var newModel = castAsDataModel(modelData);
          collection.push(newModel);
          affectedModels.push(newModel);
        }
      });
    }
  }

  return affectedModels;
}

function create (model, options) {
  var ajax = require('../misc/ajax');
  var collection = this;
  var castAsDataModel = collection[privateDataSymbol].castAs.dataModel;
  var configParams = collection[privateDataSymbol].configParams;
  options = options ? _.clone(options) : {};

  var requestInfo = {
    requestRunning: collection.isCreating,
    requestLull: configParams.requestLull,
    entity: collection,
    allowConcurrent: true,
    createRequest: function () {
      var newModel = castAsDataModel(model);
      var xhr;

      if (fw.isDataModel(newModel)) {
        xhr = newModel.save();

        if (options.wait) {
          ajax.handleJsonResponse(xhr)
            .then(function (responseData) {
              responseData && collection.addModel(newModel);
            });
        } else {
          collection.addModel(newModel)
        }
      } else {
        return newModel;
      }

      return xhr;
    }
  };

  if (!_.isFunction(configParams.dataModel)) {
    throw Error('No dataModel specified, cannot create() a new collection item');
  }

  return makeOrGetRequest('create', requestInfo);
}

function removeModel (models) {
  var collection = this;
  var affectedModels = [];

  if (_.isObject(models)) {
    models = [models];
  }
  if (!_.isArray(models)) {
    models = !_.isUndefined(models) && !_.isNull(models) ? [models] : [];
  }

  return _.reduce(models, function (removedModels, model) {
    var removed = null;
    if (fw.isDataModel(model)) {
      removed = collection.remove(model);
    } else {
      var modelsToRemove = collection.where(model);
      if (!_.isNull(modelsToRemove)) {
        removed = collection.removeAll(modelsToRemove);
      }
    }

    if (!_.isNull(removed)) {
      return removedModels.concat(removed);
    }
    return removedModels;
  }, []);
}

module.exports = {
  sync: collectionSync,
  get: get,
  getData: getData,
  toJSON: toJSON,
  pluck: pluck,
  set: set,
  reset: reset,
  fetch: fetch,
  where: where,
  findWhere: findWhere,
  addModel: addModel,
  create: create,
  removeModel: removeModel
};
<|endoftext|>


<commit_before>/* jslint node: true */
/* global window */
'use strict';

var _ = require('lodash');
var FontProvider = require('./fontProvider');
var LayoutBuilder = require('./layoutBuilder');
var PdfKit = require('pdfkit');
var sizes = require('./standardPageSizes');
var ImageMeasure = require('./imageMeasure');
var textDecorator = require('./textDecorator');

_.noConflict();

////////////////////////////////////////
// PdfPrinter

/**
 * @class Creates an instance of a PdfPrinter which turns document definition into a pdf
 *
 * @param {Object} fontDescriptors font definition dictionary
 *
 * @example
 * var fontDescriptors = {
 *	Roboto: {
 *		normal: 'fonts/Roboto-Regular.ttf',
 *		bold: 'fonts/Roboto-Medium.ttf',
 *		italics: 'fonts/Roboto-Italic.ttf',
 *		bolditalics: 'fonts/Roboto-Italic.ttf'
 *	}
 * };
 *
 * var printer = new PdfPrinter(fontDescriptors);
 */
function PdfPrinter(fontDescriptors) {
	this.fontDescriptors = fontDescriptors;
}

/**
 * Executes layout engine for the specified document and renders it into a pdfkit document
 * ready to be saved.
 *
 * @param {Object} docDefinition document definition
 * @param {Object} docDefinition.content an array describing the pdf structure (for more information take a look at the examples in the /examples folder)
 * @param {Object} [docDefinition.defaultStyle] default (implicit) style definition
 * @param {Object} [docDefinition.styles] dictionary defining all styles which can be used in the document
 * @param {Object} [docDefinition.pageSize] page size (pdfkit units, A4 dimensions by default)
 * @param {Number} docDefinition.pageSize.width width
 * @param {Number} docDefinition.pageSize.height height
 * @param {Object} [docDefinition.pageMargins] page margins (pdfkit units)
 *
 * @example
 *
 * var docDefinition = {
 * 	info: {
 *		title: 'awesome Document',
 *		author: 'john doe',
 *		subject: 'subject of document',
 *		keywords: 'keywords for document',
 * 	},
 *	content: [
 *		'First paragraph',
 *		'Second paragraph, this time a little bit longer',
 *		{ text: 'Third paragraph, slightly bigger font size', fontSize: 20 },
 *		{ text: 'Another paragraph using a named style', style: 'header' },
 *		{ text: ['playing with ', 'inlines' ] },
 *		{ text: ['and ', { text: 'restyling ', bold: true }, 'them'] },
 *	],
 *	styles: {
 *		header: { fontSize: 30, bold: true }
 *	}
 * }
 *
 * var pdfDoc = printer.createPdfKitDocument(docDefinition);
 *
 * pdfDoc.pipe(fs.createWriteStream('sample.pdf'));
 * pdfDoc.end();
 *
 * @return {Object} a pdfKit document object which can be saved or encode to data-url
 */
PdfPrinter.prototype.createPdfKitDocument = function (docDefinition, options) {
	options = options || {};

	var pageSize = pageSize2widthAndHeight(docDefinition.pageSize || 'a4');

	if (docDefinition.pageOrientation === 'landscape') {
		pageSize = {width: pageSize.height, height: pageSize.width};
	}
	pageSize.orientation = docDefinition.pageOrientation === 'landscape' ? docDefinition.pageOrientation : 'portrait';

	this.pdfKitDoc = new PdfKit({size: [pageSize.width, pageSize.height], compress: false});
	this.pdfKitDoc.info.Producer = 'pdfmake';
	this.pdfKitDoc.info.Creator = 'pdfmake';

	// pdf kit maintains the uppercase fieldnames from pdf spec
	// to keep the pdfmake api consistent, the info field are defined lowercase
	if (docDefinition.info) {
		var info = docDefinition.info;
		// check for falsey an set null, so that pdfkit always get either null or value
		this.pdfKitDoc.info.Title = info.title ? info.title : null;
		this.pdfKitDoc.info.Author = info.author ? info.author : null;
		this.pdfKitDoc.info.Subject = info.subject ? info.subject : null;
		this.pdfKitDoc.info.Keywords = info.keywords ? info.keywords : null;
		this.pdfKitDoc.info.CreationDate = info.creationDate ? info.creationDate : null;
	}

	this.fontProvider = new FontProvider(this.fontDescriptors, this.pdfKitDoc);

	docDefinition.images = docDefinition.images || {};

	var builder = new LayoutBuilder(
		pageSize,
		fixPageMargins(docDefinition.pageMargins || 40),
		new ImageMeasure(this.pdfKitDoc, docDefinition.images));

	registerDefaultTableLayouts(builder);
	if (options.tableLayouts) {
		builder.registerTableLayouts(options.tableLayouts);
	}

	var pages = builder.layoutDocument(docDefinition.content, this.fontProvider, docDefinition.styles || {}, docDefinition.defaultStyle || {fontSize: 12, font: 'Roboto'}, docDefinition.background, docDefinition.header, docDefinition.footer, docDefinition.images, docDefinition.watermark, docDefinition.pageBreakBefore);

	renderPages(pages, this.fontProvider, this.pdfKitDoc);

	if (options.autoPrint) {
		var printActionRef = this.pdfKitDoc.ref({
			Type: 'Action',
			S: 'Named',
			N: 'Print'
		});
		this.pdfKitDoc._root.data.OpenAction = printActionRef;
		printActionRef.end();
	}
	return this.pdfKitDoc;
};

function fixPageMargins(margin) {
	if (!margin) {
		return null;
	}

	if (typeof margin === 'number' || margin instanceof Number) {
		margin = {left: margin, right: margin, top: margin, bottom: margin};
	} else if (Array.isArray(margin)) {
		if (margin.length === 2) {
			margin = {left: margin[0], top: margin[1], right: margin[0], bottom: margin[1]};
		} else if (margin.length === 4) {
			margin = {left: margin[0], top: margin[1], right: margin[2], bottom: margin[3]};
		} else
			throw 'Invalid pageMargins definition';
	}

	return margin;
}

function registerDefaultTableLayouts(layoutBuilder) {
	layoutBuilder.registerTableLayouts({
		noBorders: {
			hLineWidth: function (i) {
				return 0;
			},
			vLineWidth: function (i) {
				return 0;
			},
			paddingLeft: function (i) {
				return i && 4 || 0;
			},
			paddingRight: function (i, node) {
				return (i < node.table.widths.length - 1) ? 4 : 0;
			},
		},
		headerLineOnly: {
			hLineWidth: function (i, node) {
				if (i === 0 || i === node.table.body.length) {
					return 0;
				}
				return (i === node.table.headerRows) ? 2 : 0;
			},
			vLineWidth: function (i) {
				return 0;
			},
			paddingLeft: function (i) {
				return i === 0 ? 0 : 8;
			},
			paddingRight: function (i, node) {
				return (i === node.table.widths.length - 1) ? 0 : 8;
			}
		},
		lightHorizontalLines: {
			hLineWidth: function (i, node) {
				if (i === 0 || i === node.table.body.length) {
					return 0;
				}
				return (i === node.table.headerRows) ? 2 : 1;
			},
			vLineWidth: function (i) {
				return 0;
			},
			hLineColor: function (i) {
				return i === 1 ? 'black' : '#aaa';
			},
			paddingLeft: function (i) {
				return i === 0 ? 0 : 8;
			},
			paddingRight: function (i, node) {
				return (i === node.table.widths.length - 1) ? 0 : 8;
			}
		}
	});
}

var defaultLayout = {
	hLineWidth: function (i, node) {
		return 1;
	}, //return node.table.headerRows && i === node.table.headerRows && 3 || 0; },
	vLineWidth: function (i, node) {
		return 1;
	},
	hLineColor: function (i, node) {
		return 'black';
	},
	vLineColor: function (i, node) {
		return 'black';
	},
	paddingLeft: function (i, node) {
		return 4;
	}, //i && 4 || 0; },
	paddingRight: function (i, node) {
		return 4;
	}, //(i < node.table.widths.length - 1) ? 4 : 0; },
	paddingTop: function (i, node) {
		return 2;
	},
	paddingBottom: function (i, node) {
		return 2;
	},
	defaultBorder: true
};

function pageSize2widthAndHeight(pageSize) {
	if (typeof pageSize == 'string' || pageSize instanceof String) {
		var size = sizes[pageSize.toUpperCase()];
		if (!size) {
			throw ('Page size ' + pageSize + ' not recognized');
		}
		return {width: size[0], height: size[1]};
	}

	return pageSize;
}

function StringObject(str) {
	this.isString = true;
	this.toString = function () {
		return str;
	};
}

function updatePageOrientationInOptions(currentPage, pdfKitDoc) {
	var previousPageOrientation = pdfKitDoc.options.size[0] > pdfKitDoc.options.size[1] ? 'landscape' : 'portrait';

	if (currentPage.pageSize.orientation !== previousPageOrientation) {
		var width = pdfKitDoc.options.size[0];
		var height = pdfKitDoc.options.size[1];
		pdfKitDoc.options.size = [height, width];
	}
}

function renderPages(pages, fontProvider, pdfKitDoc) {
	pdfKitDoc._pdfMakePages = pages;
	for (var i = 0; i < pages.length; i++) {
		if (i > 0) {
			updatePageOrientationInOptions(pages[i], pdfKitDoc);
			pdfKitDoc.addPage(pdfKitDoc.options);
		}

		var page = pages[i];
		for (var ii = 0, il = page.items.length; ii < il; ii++) {
			var item = page.items[ii];
			switch (item.type) {
				case 'vector':
					renderVector(item.item, pdfKitDoc);
					break;
				case 'line':
					renderLine(item.item, item.item.x, item.item.y, pdfKitDoc);
					break;
				case 'image':
					renderImage(item.item, item.item.x, item.item.y, pdfKitDoc);
					break;
			}
		}
		if (page.watermark) {
			renderWatermark(page, pdfKitDoc);
		}
	}
}

function renderLine(line, x, y, pdfKitDoc) {
	x = x || 0;
	y = y || 0;

	textDecorator.drawBackground(line, x, y, pdfKitDoc);

	//TODO: line.optimizeInlines();
	for (var i = 0, l = line.inlines.length; i < l; i++) {
		var inline = line.inlines[i];

		pdfKitDoc.fill(inline.color || 'black');

		pdfKitDoc._font = inline.font;
		pdfKitDoc.fontSize(inline.fontSize);
		pdfKitDoc.text(inline.text, x + inline.x, y, {
			lineBreak: false,
			link: inline.link
		});
	}

	textDecorator.drawDecorations(line, x, y, pdfKitDoc);
}

function renderWatermark(page, pdfKitDoc) {
	var watermark = page.watermark;

	pdfKitDoc.fill(watermark.color);
	pdfKitDoc.opacity(watermark.opacity);

	pdfKitDoc.save();

	var angle = Math.atan2(pdfKitDoc.page.height, pdfKitDoc.page.width) * -180 / Math.PI;
	pdfKitDoc.rotate(angle, {origin: [pdfKitDoc.page.width / 2, pdfKitDoc.page.height / 2]});

	var x = pdfKitDoc.page.width / 2 - watermark.size.size.width / 2;
	var y = pdfKitDoc.page.height / 2 - watermark.size.size.height / 4;

	pdfKitDoc._font = watermark.font;
	pdfKitDoc.fontSize(watermark.size.fontSize);
	pdfKitDoc.text(watermark.text, x, y, {lineBreak: false});

	pdfKitDoc.restore();
}

function renderVector(vector, pdfDoc) {
	//TODO: pdf optimization (there's no need to write all properties everytime)
	pdfDoc.lineWidth(vector.lineWidth || 1);
	if (vector.dash) {
		pdfDoc.dash(vector.dash.length, {space: vector.dash.space || vector.dash.length});
	} else {
		pdfDoc.undash();
	}
	pdfDoc.fillOpacity(vector.fillOpacity || 1);
	pdfDoc.strokeOpacity(vector.strokeOpacity || 1);
	pdfDoc.lineJoin(vector.lineJoin || 'miter');

	//TODO: clipping

	switch (vector.type) {
		case 'ellipse':
			pdfDoc.ellipse(vector.x, vector.y, vector.r1, vector.r2);
			break;
		case 'rect':
			if (vector.r) {
				pdfDoc.roundedRect(vector.x, vector.y, vector.w, vector.h, vector.r);
			} else {
				pdfDoc.rect(vector.x, vector.y, vector.w, vector.h);
			}
			break;
		case 'line':
			pdfDoc.moveTo(vector.x1, vector.y1);
			pdfDoc.lineTo(vector.x2, vector.y2);
			break;
		case 'polyline':
			if (vector.points.length === 0) {
				break;
			}

			pdfDoc.moveTo(vector.points[0].x, vector.points[0].y);
			for (var i = 1, l = vector.points.length; i < l; i++) {
				pdfDoc.lineTo(vector.points[i].x, vector.points[i].y);
			}

			if (vector.points.length > 1) {
				var p1 = vector.points[0];
				var pn = vector.points[vector.points.length - 1];

				if (vector.closePath || p1.x === pn.x && p1.y === pn.y) {
					pdfDoc.closePath();
				}
			}
			break;
	}

	if (vector.color && vector.lineColor) {
		pdfDoc.fillAndStroke(vector.color, vector.lineColor);
	} else if (vector.color) {
		pdfDoc.fill(vector.color);
	} else {
		pdfDoc.stroke(vector.lineColor || 'black');
	}
}

function renderImage(image, x, y, pdfKitDoc) {
	pdfKitDoc.image(image.image, image.x, image.y, {width: image._width, height: image._height});
}

module.exports = PdfPrinter;

/* temporary browser extension */
PdfPrinter.prototype.fs = require('fs');
<commit_msg>Compression is enabled by default. Can be disabled by compress: false.<commit_after>/* jslint node: true */
/* global window */
'use strict';

var _ = require('lodash');
var FontProvider = require('./fontProvider');
var LayoutBuilder = require('./layoutBuilder');
var PdfKit = require('pdfkit');
var sizes = require('./standardPageSizes');
var ImageMeasure = require('./imageMeasure');
var textDecorator = require('./textDecorator');

_.noConflict();

////////////////////////////////////////
// PdfPrinter

/**
 * @class Creates an instance of a PdfPrinter which turns document definition into a pdf
 *
 * @param {Object} fontDescriptors font definition dictionary
 *
 * @example
 * var fontDescriptors = {
 *	Roboto: {
 *		normal: 'fonts/Roboto-Regular.ttf',
 *		bold: 'fonts/Roboto-Medium.ttf',
 *		italics: 'fonts/Roboto-Italic.ttf',
 *		bolditalics: 'fonts/Roboto-Italic.ttf'
 *	}
 * };
 *
 * var printer = new PdfPrinter(fontDescriptors);
 */
function PdfPrinter(fontDescriptors) {
	this.fontDescriptors = fontDescriptors;
}

/**
 * Executes layout engine for the specified document and renders it into a pdfkit document
 * ready to be saved.
 *
 * @param {Object} docDefinition document definition
 * @param {Object} docDefinition.content an array describing the pdf structure (for more information take a look at the examples in the /examples folder)
 * @param {Object} [docDefinition.defaultStyle] default (implicit) style definition
 * @param {Object} [docDefinition.styles] dictionary defining all styles which can be used in the document
 * @param {Object} [docDefinition.pageSize] page size (pdfkit units, A4 dimensions by default)
 * @param {Number} docDefinition.pageSize.width width
 * @param {Number} docDefinition.pageSize.height height
 * @param {Object} [docDefinition.pageMargins] page margins (pdfkit units)
 *
 * @example
 *
 * var docDefinition = {
 * 	info: {
 *		title: 'awesome Document',
 *		author: 'john doe',
 *		subject: 'subject of document',
 *		keywords: 'keywords for document',
 * 	},
 *	content: [
 *		'First paragraph',
 *		'Second paragraph, this time a little bit longer',
 *		{ text: 'Third paragraph, slightly bigger font size', fontSize: 20 },
 *		{ text: 'Another paragraph using a named style', style: 'header' },
 *		{ text: ['playing with ', 'inlines' ] },
 *		{ text: ['and ', { text: 'restyling ', bold: true }, 'them'] },
 *	],
 *	styles: {
 *		header: { fontSize: 30, bold: true }
 *	}
 * }
 *
 * var pdfDoc = printer.createPdfKitDocument(docDefinition);
 *
 * pdfDoc.pipe(fs.createWriteStream('sample.pdf'));
 * pdfDoc.end();
 *
 * @return {Object} a pdfKit document object which can be saved or encode to data-url
 */
PdfPrinter.prototype.createPdfKitDocument = function (docDefinition, options) {
	options = options || {};

	var pageSize = pageSize2widthAndHeight(docDefinition.pageSize || 'a4');

	if (docDefinition.pageOrientation === 'landscape') {
		pageSize = {width: pageSize.height, height: pageSize.width};
	}
	pageSize.orientation = docDefinition.pageOrientation === 'landscape' ? docDefinition.pageOrientation : 'portrait';

	this.pdfKitDoc = new PdfKit({size: [pageSize.width, pageSize.height], compress: docDefinition.compress || true});
	this.pdfKitDoc.info.Producer = 'pdfmake';
	this.pdfKitDoc.info.Creator = 'pdfmake';

	// pdf kit maintains the uppercase fieldnames from pdf spec
	// to keep the pdfmake api consistent, the info field are defined lowercase
	if (docDefinition.info) {
		var info = docDefinition.info;
		// check for falsey an set null, so that pdfkit always get either null or value
		this.pdfKitDoc.info.Title = info.title ? info.title : null;
		this.pdfKitDoc.info.Author = info.author ? info.author : null;
		this.pdfKitDoc.info.Subject = info.subject ? info.subject : null;
		this.pdfKitDoc.info.Keywords = info.keywords ? info.keywords : null;
		this.pdfKitDoc.info.CreationDate = info.creationDate ? info.creationDate : null;
	}

	this.fontProvider = new FontProvider(this.fontDescriptors, this.pdfKitDoc);

	docDefinition.images = docDefinition.images || {};

	var builder = new LayoutBuilder(
		pageSize,
		fixPageMargins(docDefinition.pageMargins || 40),
		new ImageMeasure(this.pdfKitDoc, docDefinition.images));

	registerDefaultTableLayouts(builder);
	if (options.tableLayouts) {
		builder.registerTableLayouts(options.tableLayouts);
	}

	var pages = builder.layoutDocument(docDefinition.content, this.fontProvider, docDefinition.styles || {}, docDefinition.defaultStyle || {fontSize: 12, font: 'Roboto'}, docDefinition.background, docDefinition.header, docDefinition.footer, docDefinition.images, docDefinition.watermark, docDefinition.pageBreakBefore);

	renderPages(pages, this.fontProvider, this.pdfKitDoc);

	if (options.autoPrint) {
		var printActionRef = this.pdfKitDoc.ref({
			Type: 'Action',
			S: 'Named',
			N: 'Print'
		});
		this.pdfKitDoc._root.data.OpenAction = printActionRef;
		printActionRef.end();
	}
	return this.pdfKitDoc;
};

function fixPageMargins(margin) {
	if (!margin) {
		return null;
	}

	if (typeof margin === 'number' || margin instanceof Number) {
		margin = {left: margin, right: margin, top: margin, bottom: margin};
	} else if (Array.isArray(margin)) {
		if (margin.length === 2) {
			margin = {left: margin[0], top: margin[1], right: margin[0], bottom: margin[1]};
		} else if (margin.length === 4) {
			margin = {left: margin[0], top: margin[1], right: margin[2], bottom: margin[3]};
		} else
			throw 'Invalid pageMargins definition';
	}

	return margin;
}

function registerDefaultTableLayouts(layoutBuilder) {
	layoutBuilder.registerTableLayouts({
		noBorders: {
			hLineWidth: function (i) {
				return 0;
			},
			vLineWidth: function (i) {
				return 0;
			},
			paddingLeft: function (i) {
				return i && 4 || 0;
			},
			paddingRight: function (i, node) {
				return (i < node.table.widths.length - 1) ? 4 : 0;
			},
		},
		headerLineOnly: {
			hLineWidth: function (i, node) {
				if (i === 0 || i === node.table.body.length) {
					return 0;
				}
				return (i === node.table.headerRows) ? 2 : 0;
			},
			vLineWidth: function (i) {
				return 0;
			},
			paddingLeft: function (i) {
				return i === 0 ? 0 : 8;
			},
			paddingRight: function (i, node) {
				return (i === node.table.widths.length - 1) ? 0 : 8;
			}
		},
		lightHorizontalLines: {
			hLineWidth: function (i, node) {
				if (i === 0 || i === node.table.body.length) {
					return 0;
				}
				return (i === node.table.headerRows) ? 2 : 1;
			},
			vLineWidth: function (i) {
				return 0;
			},
			hLineColor: function (i) {
				return i === 1 ? 'black' : '#aaa';
			},
			paddingLeft: function (i) {
				return i === 0 ? 0 : 8;
			},
			paddingRight: function (i, node) {
				return (i === node.table.widths.length - 1) ? 0 : 8;
			}
		}
	});
}

var defaultLayout = {
	hLineWidth: function (i, node) {
		return 1;
	}, //return node.table.headerRows && i === node.table.headerRows && 3 || 0; },
	vLineWidth: function (i, node) {
		return 1;
	},
	hLineColor: function (i, node) {
		return 'black';
	},
	vLineColor: function (i, node) {
		return 'black';
	},
	paddingLeft: function (i, node) {
		return 4;
	}, //i && 4 || 0; },
	paddingRight: function (i, node) {
		return 4;
	}, //(i < node.table.widths.length - 1) ? 4 : 0; },
	paddingTop: function (i, node) {
		return 2;
	},
	paddingBottom: function (i, node) {
		return 2;
	},
	defaultBorder: true
};

function pageSize2widthAndHeight(pageSize) {
	if (typeof pageSize == 'string' || pageSize instanceof String) {
		var size = sizes[pageSize.toUpperCase()];
		if (!size) {
			throw ('Page size ' + pageSize + ' not recognized');
		}
		return {width: size[0], height: size[1]};
	}

	return pageSize;
}

function StringObject(str) {
	this.isString = true;
	this.toString = function () {
		return str;
	};
}

function updatePageOrientationInOptions(currentPage, pdfKitDoc) {
	var previousPageOrientation = pdfKitDoc.options.size[0] > pdfKitDoc.options.size[1] ? 'landscape' : 'portrait';

	if (currentPage.pageSize.orientation !== previousPageOrientation) {
		var width = pdfKitDoc.options.size[0];
		var height = pdfKitDoc.options.size[1];
		pdfKitDoc.options.size = [height, width];
	}
}

function renderPages(pages, fontProvider, pdfKitDoc) {
	pdfKitDoc._pdfMakePages = pages;
	for (var i = 0; i < pages.length; i++) {
		if (i > 0) {
			updatePageOrientationInOptions(pages[i], pdfKitDoc);
			pdfKitDoc.addPage(pdfKitDoc.options);
		}

		var page = pages[i];
		for (var ii = 0, il = page.items.length; ii < il; ii++) {
			var item = page.items[ii];
			switch (item.type) {
				case 'vector':
					renderVector(item.item, pdfKitDoc);
					break;
				case 'line':
					renderLine(item.item, item.item.x, item.item.y, pdfKitDoc);
					break;
				case 'image':
					renderImage(item.item, item.item.x, item.item.y, pdfKitDoc);
					break;
			}
		}
		if (page.watermark) {
			renderWatermark(page, pdfKitDoc);
		}
	}
}

function renderLine(line, x, y, pdfKitDoc) {
	x = x || 0;
	y = y || 0;

	textDecorator.drawBackground(line, x, y, pdfKitDoc);

	//TODO: line.optimizeInlines();
	for (var i = 0, l = line.inlines.length; i < l; i++) {
		var inline = line.inlines[i];

		pdfKitDoc.fill(inline.color || 'black');

		pdfKitDoc._font = inline.font;
		pdfKitDoc.fontSize(inline.fontSize);
		pdfKitDoc.text(inline.text, x + inline.x, y, {
			lineBreak: false,
			link: inline.link
		});
	}

	textDecorator.drawDecorations(line, x, y, pdfKitDoc);
}

function renderWatermark(page, pdfKitDoc) {
	var watermark = page.watermark;

	pdfKitDoc.fill(watermark.color);
	pdfKitDoc.opacity(watermark.opacity);

	pdfKitDoc.save();

	var angle = Math.atan2(pdfKitDoc.page.height, pdfKitDoc.page.width) * -180 / Math.PI;
	pdfKitDoc.rotate(angle, {origin: [pdfKitDoc.page.width / 2, pdfKitDoc.page.height / 2]});

	var x = pdfKitDoc.page.width / 2 - watermark.size.size.width / 2;
	var y = pdfKitDoc.page.height / 2 - watermark.size.size.height / 4;

	pdfKitDoc._font = watermark.font;
	pdfKitDoc.fontSize(watermark.size.fontSize);
	pdfKitDoc.text(watermark.text, x, y, {lineBreak: false});

	pdfKitDoc.restore();
}

function renderVector(vector, pdfDoc) {
	//TODO: pdf optimization (there's no need to write all properties everytime)
	pdfDoc.lineWidth(vector.lineWidth || 1);
	if (vector.dash) {
		pdfDoc.dash(vector.dash.length, {space: vector.dash.space || vector.dash.length});
	} else {
		pdfDoc.undash();
	}
	pdfDoc.fillOpacity(vector.fillOpacity || 1);
	pdfDoc.strokeOpacity(vector.strokeOpacity || 1);
	pdfDoc.lineJoin(vector.lineJoin || 'miter');

	//TODO: clipping

	switch (vector.type) {
		case 'ellipse':
			pdfDoc.ellipse(vector.x, vector.y, vector.r1, vector.r2);
			break;
		case 'rect':
			if (vector.r) {
				pdfDoc.roundedRect(vector.x, vector.y, vector.w, vector.h, vector.r);
			} else {
				pdfDoc.rect(vector.x, vector.y, vector.w, vector.h);
			}
			break;
		case 'line':
			pdfDoc.moveTo(vector.x1, vector.y1);
			pdfDoc.lineTo(vector.x2, vector.y2);
			break;
		case 'polyline':
			if (vector.points.length === 0) {
				break;
			}

			pdfDoc.moveTo(vector.points[0].x, vector.points[0].y);
			for (var i = 1, l = vector.points.length; i < l; i++) {
				pdfDoc.lineTo(vector.points[i].x, vector.points[i].y);
			}

			if (vector.points.length > 1) {
				var p1 = vector.points[0];
				var pn = vector.points[vector.points.length - 1];

				if (vector.closePath || p1.x === pn.x && p1.y === pn.y) {
					pdfDoc.closePath();
				}
			}
			break;
	}

	if (vector.color && vector.lineColor) {
		pdfDoc.fillAndStroke(vector.color, vector.lineColor);
	} else if (vector.color) {
		pdfDoc.fill(vector.color);
	} else {
		pdfDoc.stroke(vector.lineColor || 'black');
	}
}

function renderImage(image, x, y, pdfKitDoc) {
	pdfKitDoc.image(image.image, image.x, image.y, {width: image._width, height: image._height});
}

module.exports = PdfPrinter;

/* temporary browser extension */
PdfPrinter.prototype.fs = require('fs');
<|endoftext|>