<commit_before>angular
    .module('gnap-app', ['gnap',
                         'ui.bootstrap',
                         'ngBootbox',
                         'localytics.directives',
                         'datatables',
                         'ngSanitize'])

    .config([
        '$tooltipProvider', function ($tooltipProvider) {
            $tooltipProvider.options({
                appendToBody: true
            });
        }
    ])

    .controller('main', ['$scope',
                         'sidebarService',
                         'breadcrumbsService',
                         function ($scope, sidebarService, breadcrumbsService) {

            $scope.search = function () {
                alert('Searching for ' + $scope.keywords);
            }

            // configure breadcrumbs
            breadcrumbsService.setBreadcrumbs([
                {
                    title: 'Home',
                    click: function () {
                        alert('Going to the home page ...');
                    }
                },
                {
                    title: 'Examples',
                    click: function () {
                        alert('Going to the examples page ...');
                    }
                }
            ]);

            // configure shortcuts
            sidebarService.settings.shortcuts = [
                {
                    title: 'Statistics',
                    buttonClass: 'btn btn-success',
                    icon: 'icon-signal',
                    click: function () {
                        alert('Going to the statistics page ...');
                    }
                },
                {
                    title: 'Edit',
                    buttonClass: 'btn btn-info',
                    icon: 'icon-pencil',
                    click: function () {
                        alert('Going to the edit page ...');
                    }
                },
                {
                    title: 'Profile',
                    buttonClass: 'btn btn-warning',
                    icon: 'icon-group',
                    click: function () {
                        alert('Going to the profile page ...');
                    }
                },
                {
                    title: 'Administration',
                    buttonClass: 'btn btn-danger',
                    icon: 'icon-cogs',
                    click: function () {
                        alert('Going to the administration page ...');
                    }
                }
            ];

            // configure menu items
            sidebarService.settings.items = [
                {
                    key: 'about',
                    title: 'About',
                    icon: 'icon-lightbulb',
                    click: function () {
                        alert('Going to the about page ...');
                        sidebarService.setActive('about');
                    }
                },
                {
                    key: 'examples',
                    title: 'Examples',
                    icon: 'icon-heart',
                    active: true,
                    click: function () {
                        alert('Going to the examples page ...');
                        sidebarService.setActive('examples');
                    }
                },
                {
                    key: 'typography',
                    title: 'Typography',
                    icon: 'icon-text-width',
                    click: function () {
                        alert('Going to the typography page ...');
                        sidebarService.setActive('typography');
                    }
                },
                {
                    key: 'error-404',
                    title: 'Error 404',
                    icon: 'icon-circle',
                    click: function () {
                        alert('Going to the error 404 page ...');
                        sidebarService.setActive('error-404');
                    }
                },
                {
                    key: 'error-500',
                    title: 'Error 500',
                    icon: 'icon-circle-blank',
                    click: function () {
                        alert('Going to the error 500 page ...');
                        sidebarService.setActive('error-500');
                    }
                },
                {
                    key: 'login',
                    title: 'Login',
                    icon: 'icon-user',
                    click: function () {
                        alert('Going to the Login page ...');
                        sidebarService.setActive('login');
                    }
                },
                {
                    key: 'multi-level-menu',
                    title: 'Multi Level Menu',
                    icon: 'icon-globe',
                    items: [
                        {
                            key: 'level-2',
                            title: 'Level 2',
                            icon: 'icon-leaf',
                            click: function () {
                                alert('Going to the level2 page ...');
                                sidebarService.setActive('multi-level-menu/level-2');
                            }
                        },
                        {
                            key: 'level-2-with-sub',
                            title: 'Level 2 with Sub',
                            icon: 'icon-pencil',
                            items: [
                                {
                                    key: 'level-3',
                                    title: 'Level 3',
                                    icon: 'icon-plus',
                                    click: function () {
                                        alert('Going to the Level 3 page ...');
                                        sidebarService.setActive('multi-level-menu/level-2-with-sub/level-3');
                                    }
                                },
                                {
                                    key: 'next-level-3',
                                    title: 'Next Level 3',
                                    icon: 'icon-eye-open',
                                    click: function () {
                                        alert('Going to the Next Level 3 page ...');
                                        sidebarService.setActive('multi-level-menu/level-2-with-sub/next-level-3');
                                    }
                                }
                            ]
                        }
                    ]
                },
                {
                    key: 'my-settings',
                    title: 'My Settings',
                    icon: 'icon-globe',
                    items: [
                        {
                            key: 'my-name',
                            title: 'My Name',
                            icon: 'icon-leaf',
                            click: function () {
                                alert('Going to the level2 page ...');
                                sidebarService.setActive('multi-level-menu/level-2');
                            }
                        }
                    ]
                }
            ];

            sidebarService.setActive('multi-level-menu/level-2-with-sub/next-level-3');
        }
    ])
    .controller('gnap-default-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                title: 'This is a default notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-progressbar', function ($scope) {
        $scope.progress = 40;
        $scope.$watch('progress', function (progress) { $scope.workLeft = 100 - progress; });
    })
    .controller('gnap-stacked-progressbar', function ($scope) {
        $scope.collection = [];

        $scope.collection.push({
            value: 20,
            type: 'success'
        });

        $scope.collection.push({
            value: 15,
            type: 'warning'
        });

        $scope.collection.push({
            value: 25,
            type: 'error'
        });
    })
    .controller('gnap-confirm-dialog', function ($scope) {
        $scope.confirm = function () {
            alert('You were sure!');
        };

        $scope.cancel = function () {
            alert('You weren\'t sure!');
        };
    })
    .controller('gnap-prompt-dialog', function ($scope) {
        $scope.accept = function (name) {
            alert('Hello ' + name);
        };

        $scope.cancel = function () {
            alert('You cancelled!');
        };
    })
    .controller('gnap-custom-dialog', function ($scope, $modal) {
        $scope.items = ['item1', 'item2', 'item3'];

        $scope.open = function (size) {
            var modalInstance = $modal.open({
                templateUrl: 'myModalContent.html',
                controller: 'gnap-custom-dialog-instance',
                resolve: {
                    items: function () {
                        return $scope.items;
                    }
                }
            });

            modalInstance.result.then(function (result) {
                alert('You selected: ' + result);
            }, function () {
                alert('You dismissed the dialog!');
            });
        };

    })
    .controller('gnap-custom-dialog-instance', function ($scope, $modalInstance, items) {
        $scope.items = items;

        $scope.selected = {
            item: $scope.items[0]
        };

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

        $scope.cancel = function () {
            $modalInstance.dismiss('cancel');
        };
    })
    .controller('gnap-alerts', function ($scope, $sce) {
        $scope.alerts = [
            { type: 'danger', msg: $sce.trustAsHtml('Oh snap! Change a few things up and try submitting again.') },
            { type: 'success', msg: $sce.trustAsHtml('Well done! You successfully read this important alert message.') },
            { type: 'warning', msg: $sce.trustAsHtml('Warning! Best check your self, you\'re not looking too good.') },
            { type: 'info', msg: $sce.trustAsHtml('Heads up! This alert needs your attention, but it\'s not super important.') }
        ];

        $scope.closeAlert = function (index) {
            $scope.alerts.splice(index, 1);
        };
    })
    .controller('gnap-pagination', function ($scope) {
        $scope.totalItems = 164;
        $scope.currentPage = 4;
    })
    .controller('gnap-collapse', function ($scope) {
        $scope.isCollapsed = false;
    })
    .controller('gnap-default-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                title: 'This is a default notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-success-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                type: 'success',
                title: 'This is a success notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-error-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                type: 'error',
                title: 'This is an error notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-date-picker', function ($scope) {
        $scope.date = new Date();
    })
    .controller('gnap-date-range-picker', [
        '$scope', function ($scope) {
            $scope.range = {
                dateStart: new Date(2014, 0, 7),
                dateEnd: new Date(2014, 7, 14)
            };

            $scope.iconPosition = "left";
        }
    ])
    .controller('gnap-time-picker', function ($scope) {
        $scope.sometime = new Date(2014, 1, 1, 10, 30, 0);
    })
    .controller('gnap-choice-toggle', function ($scope) {
        $scope.value = true;
    })
    .directive('exampleLink', function () {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                element.addClass("label");
                element.addClass("label-default");
                element.attr('target', '_blank');
                element.prepend('  View on ' + attrs['source']);

                if (!attrs['href']) {
                    var url = '#';
                    switch (attrs['source']) {
                        case 'ui-bootstrap':
                            {
                                url = 'http://angular-ui.github.io/bootstrap';
                                break;
                            }
                        case 'ngBootbox':
                            {
                                url = 'https://github.com/eriktufvesson/ngbootbox';
                                break;
                            }
                        case 'gnap-theme':
                            {
                                url = 'https://github.com/infrabel/GNaP.Web.Themes';
                                break;
                            }
                        case 'gnap-theme-angular':
                            {
                                url = 'https://github.com/infrabel/GNaP.Web.Themes';
                                break;
                            }
                    }
                    element.attr('href', url);
                }
            }
        };
    });<commit_msg>Removed second submenu<commit_after>angular
    .module('gnap-app', ['gnap',
                         'ui.bootstrap',
                         'ngBootbox',
                         'localytics.directives',
                         'datatables',
                         'ngSanitize'])

    .config([
        '$tooltipProvider', function ($tooltipProvider) {
            $tooltipProvider.options({
                appendToBody: true
            });
        }
    ])

    .controller('main', ['$scope',
                         'sidebarService',
                         'breadcrumbsService',
                         function ($scope, sidebarService, breadcrumbsService) {

            $scope.search = function () {
                alert('Searching for ' + $scope.keywords);
            }

            // configure breadcrumbs
            breadcrumbsService.setBreadcrumbs([
                {
                    title: 'Home',
                    click: function () {
                        alert('Going to the home page ...');
                    }
                },
                {
                    title: 'Examples',
                    click: function () {
                        alert('Going to the examples page ...');
                    }
                }
            ]);

            // configure shortcuts
            sidebarService.settings.shortcuts = [
                {
                    title: 'Statistics',
                    buttonClass: 'btn btn-success',
                    icon: 'icon-signal',
                    click: function () {
                        alert('Going to the statistics page ...');
                    }
                },
                {
                    title: 'Edit',
                    buttonClass: 'btn btn-info',
                    icon: 'icon-pencil',
                    click: function () {
                        alert('Going to the edit page ...');
                    }
                },
                {
                    title: 'Profile',
                    buttonClass: 'btn btn-warning',
                    icon: 'icon-group',
                    click: function () {
                        alert('Going to the profile page ...');
                    }
                },
                {
                    title: 'Administration',
                    buttonClass: 'btn btn-danger',
                    icon: 'icon-cogs',
                    click: function () {
                        alert('Going to the administration page ...');
                    }
                }
            ];

            // configure menu items
            sidebarService.settings.items = [
                {
                    key: 'about',
                    title: 'About',
                    icon: 'icon-lightbulb',
                    click: function () {
                        alert('Going to the about page ...');
                        sidebarService.setActive('about');
                    }
                },
                {
                    key: 'examples',
                    title: 'Examples',
                    icon: 'icon-heart',
                    active: true,
                    click: function () {
                        alert('Going to the examples page ...');
                        sidebarService.setActive('examples');
                    }
                },
                {
                    key: 'typography',
                    title: 'Typography',
                    icon: 'icon-text-width',
                    click: function () {
                        alert('Going to the typography page ...');
                        sidebarService.setActive('typography');
                    }
                },
                {
                    key: 'error-404',
                    title: 'Error 404',
                    icon: 'icon-circle',
                    click: function () {
                        alert('Going to the error 404 page ...');
                        sidebarService.setActive('error-404');
                    }
                },
                {
                    key: 'error-500',
                    title: 'Error 500',
                    icon: 'icon-circle-blank',
                    click: function () {
                        alert('Going to the error 500 page ...');
                        sidebarService.setActive('error-500');
                    }
                },
                {
                    key: 'login',
                    title: 'Login',
                    icon: 'icon-user',
                    click: function () {
                        alert('Going to the Login page ...');
                        sidebarService.setActive('login');
                    }
                },
                {
                    key: 'multi-level-menu',
                    title: 'Multi Level Menu',
                    icon: 'icon-globe',
                    items: [
                        {
                            key: 'level-2',
                            title: 'Level 2',
                            icon: 'icon-leaf',
                            click: function () {
                                alert('Going to the level2 page ...');
                                sidebarService.setActive('multi-level-menu/level-2');
                            }
                        },
                        {
                            key: 'level-2-with-sub',
                            title: 'Level 2 with Sub',
                            icon: 'icon-pencil',
                            items: [
                                {
                                    key: 'level-3',
                                    title: 'Level 3',
                                    icon: 'icon-plus',
                                    click: function () {
                                        alert('Going to the Level 3 page ...');
                                        sidebarService.setActive('multi-level-menu/level-2-with-sub/level-3');
                                    }
                                },
                                {
                                    key: 'next-level-3',
                                    title: 'Next Level 3',
                                    icon: 'icon-eye-open',
                                    click: function () {
                                        alert('Going to the Next Level 3 page ...');
                                        sidebarService.setActive('multi-level-menu/level-2-with-sub/next-level-3');
                                    }
                                }
                            ]
                        }
                    ]
                }
            ];

            sidebarService.setActive('multi-level-menu/level-2-with-sub/next-level-3');
        }
    ])
    .controller('gnap-default-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                title: 'This is a default notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-progressbar', function ($scope) {
        $scope.progress = 40;
        $scope.$watch('progress', function (progress) { $scope.workLeft = 100 - progress; });
    })
    .controller('gnap-stacked-progressbar', function ($scope) {
        $scope.collection = [];

        $scope.collection.push({
            value: 20,
            type: 'success'
        });

        $scope.collection.push({
            value: 15,
            type: 'warning'
        });

        $scope.collection.push({
            value: 25,
            type: 'error'
        });
    })
    .controller('gnap-confirm-dialog', function ($scope) {
        $scope.confirm = function () {
            alert('You were sure!');
        };

        $scope.cancel = function () {
            alert('You weren\'t sure!');
        };
    })
    .controller('gnap-prompt-dialog', function ($scope) {
        $scope.accept = function (name) {
            alert('Hello ' + name);
        };

        $scope.cancel = function () {
            alert('You cancelled!');
        };
    })
    .controller('gnap-custom-dialog', function ($scope, $modal) {
        $scope.items = ['item1', 'item2', 'item3'];

        $scope.open = function (size) {
            var modalInstance = $modal.open({
                templateUrl: 'myModalContent.html',
                controller: 'gnap-custom-dialog-instance',
                resolve: {
                    items: function () {
                        return $scope.items;
                    }
                }
            });

            modalInstance.result.then(function (result) {
                alert('You selected: ' + result);
            }, function () {
                alert('You dismissed the dialog!');
            });
        };

    })
    .controller('gnap-custom-dialog-instance', function ($scope, $modalInstance, items) {
        $scope.items = items;

        $scope.selected = {
            item: $scope.items[0]
        };

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

        $scope.cancel = function () {
            $modalInstance.dismiss('cancel');
        };
    })
    .controller('gnap-alerts', function ($scope, $sce) {
        $scope.alerts = [
            { type: 'danger', msg: $sce.trustAsHtml('Oh snap! Change a few things up and try submitting again.') },
            { type: 'success', msg: $sce.trustAsHtml('Well done! You successfully read this important alert message.') },
            { type: 'warning', msg: $sce.trustAsHtml('Warning! Best check your self, you\'re not looking too good.') },
            { type: 'info', msg: $sce.trustAsHtml('Heads up! This alert needs your attention, but it\'s not super important.') }
        ];

        $scope.closeAlert = function (index) {
            $scope.alerts.splice(index, 1);
        };
    })
    .controller('gnap-pagination', function ($scope) {
        $scope.totalItems = 164;
        $scope.currentPage = 4;
    })
    .controller('gnap-collapse', function ($scope) {
        $scope.isCollapsed = false;
    })
    .controller('gnap-default-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                title: 'This is a default notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-success-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                type: 'success',
                title: 'This is a success notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-error-notification', function ($scope, notification) {
        $scope.notify = function () {
            notification.show({
                type: 'error',
                title: 'This is an error notification',
                text: 'This will fade out after a certain amount of time. Vivamus eget tincidunt velit. Cum sociis natoque penatibus et, nascetur ridiculus mus.'
            });
        };

        $scope.remove = function () {
            notification.removeAll();
        };
    })
    .controller('gnap-date-picker', function ($scope) {
        $scope.date = new Date();
    })
    .controller('gnap-date-range-picker', [
        '$scope', function ($scope) {
            $scope.range = {
                dateStart: new Date(2014, 0, 7),
                dateEnd: new Date(2014, 7, 14)
            };

            $scope.iconPosition = "left";
        }
    ])
    .controller('gnap-time-picker', function ($scope) {
        $scope.sometime = new Date(2014, 1, 1, 10, 30, 0);
    })
    .controller('gnap-choice-toggle', function ($scope) {
        $scope.value = true;
    })
    .directive('exampleLink', function () {
        return {
            restrict: 'A',
            link: function (scope, element, attrs) {
                element.addClass("label");
                element.addClass("label-default");
                element.attr('target', '_blank');
                element.prepend('  View on ' + attrs['source']);

                if (!attrs['href']) {
                    var url = '#';
                    switch (attrs['source']) {
                        case 'ui-bootstrap':
                            {
                                url = 'http://angular-ui.github.io/bootstrap';
                                break;
                            }
                        case 'ngBootbox':
                            {
                                url = 'https://github.com/eriktufvesson/ngbootbox';
                                break;
                            }
                        case 'gnap-theme':
                            {
                                url = 'https://github.com/infrabel/GNaP.Web.Themes';
                                break;
                            }
                        case 'gnap-theme-angular':
                            {
                                url = 'https://github.com/infrabel/GNaP.Web.Themes';
                                break;
                            }
                    }
                    element.attr('href', url);
                }
            }
        };
    });<|endoftext|>


<commit_before>import Ember from 'ember';
import { getNativeXhr } from './native-xhr';
import { maybeDisableMockjax, maybeResetMockjax } from './mockjax-wrapper';

function getDoctype() {
  let doctypeNode = document.doctype;
  if (!doctypeNode || !doctypeNode.name) {
    return '<!DOCTYPE html>';
  }
  let doctype = "<!DOCTYPE " +
    doctypeNode.name +
    (doctypeNode.publicId ? ' PUBLIC "' + doctypeNode.publicId + '"' : '') +
    (!doctypeNode.publicId && doctypeNode.systemId ? ' SYSTEM' : '') +
    (doctypeNode.systemId ? ' "' + doctypeNode.systemId + '"' : '') +
    '>';
  return doctype;
}

export function percySnapshot(name, options) {
  // Skip if Testem is not available (we're probably running from `ember server` and Percy is not
  // enabled anyway).
  if (!window.Testem) {
    return;
  }

  // Automatic name generation for QUnit tests by passing in the `assert` object.
  if (name.test && name.test.module && name.test.module.name && name.test.testName) {
    name = `${name.test.module.name} | ${name.test.testName}`;
  } else if (name.fullTitle) {
    // Automatic name generation for Mocha tests by passing in the `this.test` object.
    name = name.fullTitle();
  }

  let snapshotHtml;
  options = options || {};
  let scope = options.scope;

  // Create a full-page DOM snapshot from the current testing page.
  // TODO(fotinakis): more memory-efficient way to do this?
  let domCopy = Ember.$('html').clone();
  let testingContainer = domCopy.find('#ember-testing-container');

  if (scope) {
    snapshotHtml = Ember.$('#ember-testing-container').find(scope).html();
  } else {
    snapshotHtml = testingContainer.html();
  }

  // Hoist the testing container contents up to the body.
  // We need to use the original DOM to keep the head stylesheet around.
  domCopy.find('body').html(snapshotHtml);

  Ember.run(function() {
    maybeDisableMockjax();
    Ember.$.ajax('/_percy/snapshot', {
      xhr: getNativeXhr,
      method: 'POST',
      contentType: 'application/json; charset=utf-8',
      data: JSON.stringify({
        name: name,
        content: getDoctype() + domCopy[0].outerHTML,
        widths: options.widths,
        breakpoints: options.breakpoints,
      }),
      statusCode: {
        400: function(jqXHR) {
          // Bubble up 400 errors, ie. when given options are invalid.
          throw jqXHR.responseText;
        },
      }
    });
    maybeResetMockjax();
  });
}
<commit_msg>Fix DOM snapshot depth bug.<commit_after>import Ember from 'ember';
import { getNativeXhr } from './native-xhr';
import { maybeDisableMockjax, maybeResetMockjax } from './mockjax-wrapper';

function getDoctype() {
  let doctypeNode = document.doctype;
  if (!doctypeNode || !doctypeNode.name) {
    return '<!DOCTYPE html>';
  }
  let doctype = "<!DOCTYPE " +
    doctypeNode.name +
    (doctypeNode.publicId ? ' PUBLIC "' + doctypeNode.publicId + '"' : '') +
    (!doctypeNode.publicId && doctypeNode.systemId ? ' SYSTEM' : '') +
    (doctypeNode.systemId ? ' "' + doctypeNode.systemId + '"' : '') +
    '>';
  return doctype;
}

export function percySnapshot(name, options) {
  // Skip if Testem is not available (we're probably running from `ember server` and Percy is not
  // enabled anyway).
  if (!window.Testem) {
    return;
  }

  // Automatic name generation for QUnit tests by passing in the `assert` object.
  if (name.test && name.test.module && name.test.module.name && name.test.testName) {
    name = `${name.test.module.name} | ${name.test.testName}`;
  } else if (name.fullTitle) {
    // Automatic name generation for Mocha tests by passing in the `this.test` object.
    name = name.fullTitle();
  }

  let snapshotHtml;
  options = options || {};
  let scope = options.scope;

  // Create a full-page DOM snapshot from the current testing page.
  // TODO(fotinakis): more memory-efficient way to do this?
  let domCopy = Ember.$('html').clone();
  let testingContainer = domCopy.find('#ember-testing');

  if (scope) {
    snapshotHtml = Ember.$('#ember-testing').find(scope).html();
  } else {
    snapshotHtml = testingContainer.html();
  }

  // Hoist the testing container contents up to the body.
  // We need to use the original DOM to keep the head stylesheet around.
  domCopy.find('body').html(snapshotHtml);

  Ember.run(function() {
    maybeDisableMockjax();
    Ember.$.ajax('/_percy/snapshot', {
      xhr: getNativeXhr,
      method: 'POST',
      contentType: 'application/json; charset=utf-8',
      data: JSON.stringify({
        name: name,
        content: getDoctype() + domCopy[0].outerHTML,
        widths: options.widths,
        breakpoints: options.breakpoints,
      }),
      statusCode: {
        400: function(jqXHR) {
          // Bubble up 400 errors, ie. when given options are invalid.
          throw jqXHR.responseText;
        },
      }
    });
    maybeResetMockjax();
  });
}
<|endoftext|>


<commit_before>const msg = document.getElementById('msg');
function output(message) {
  msg.innerHTML = message;
  console.log(message);
}

const currency = document.getElementById('currency');
const value = document.getElementById('value');
function updateAmount(currencyUpdate, valueUpdate) {
  currency.innerHTML = currencyUpdate;
  value.innerHTML = valueUpdate;
}

const parts = window.location.href.split('#');
let id = 'N/A';
if (parts.length === 4) {
  id = parts[1];
  updateAmount(parts[2], parts[3]);
} else {
  output('Could not parse the Payment Request ID, total currency, and total value from the URL');
}

const pleasewait = document.getElementById('pleasewait');
let paymentManager = null;
function init() {
  pleasewait.style.display = 'block';
  navigator.serviceWorker.getRegistration('app.js').then((registration) => {
    if (!registration) {
      output('Service worker not installed');
    } else if (!registration.paymentManager) {
      output('Payment manager not found');
    } else {
      paymentManager = registration.paymentManager;
    }
    pleasewait.style.display = 'none';
  });
}
init();

const button = document.getElementById('confirm');
button.addEventListener('click', (evt) => {
  button.style.display = 'none';
  pleasewait.style.display = 'block';
  navigator.serviceWorker.controller.postMessage('confirm');
});

function firePaymentMethodChangeEvent(details) {
  if (!paymentManager) {
    output('Payment manager not found');
    return;
  }
  if (!paymentManager.fireEvent) {
    output('No event firing feature in the payment manager');
    return;
  }
  pleasewait.style.display = 'block';
  paymentManager.fireEvent(id, 'paymentmethodchange', details).then((total) => {
    updateAmount(total.currency, total.value);
    pleasewait.style.display = 'none';
  }).catch((error) => {
    output(error);
    pleasewait.style.display = 'none';
  });
}

const billingAddress2 = document.getElementById('billing-address-1');
billingAddress1.addEventListener('click', (evt) => {
  firePaymentMethodChangeEvent({
    billingAddress: {
        city: 'Venice',
        country: 'US',
        postalCode: '20191',
        region: 'VA',
    },
    cardNumber: '****1111',
    cardNetwork: 'visa',
  });
});

const billingAddress1 = document.getElementById('billing-address-1');
billingAddress2.addEventListener('click', (evt) => {
  firePaymentMethodChangeEvent({
    billingAddress: {
        city: 'London',
        country: 'GB',
        postalCode: 'WC2H 8AG',
    },
    cardNumber: '****5454',
    cardNetwork: 'mastercard',
  });
});
<commit_msg>Fix typo<commit_after>const msg = document.getElementById('msg');
function output(message) {
  msg.innerHTML = message;
  console.log(message);
}

const currency = document.getElementById('currency');
const value = document.getElementById('value');
function updateAmount(currencyUpdate, valueUpdate) {
  currency.innerHTML = currencyUpdate;
  value.innerHTML = valueUpdate;
}

const parts = window.location.href.split('#');
let id = 'N/A';
if (parts.length === 4) {
  id = parts[1];
  updateAmount(parts[2], parts[3]);
} else {
  output('Could not parse the Payment Request ID, total currency, and total value from the URL');
}

const pleasewait = document.getElementById('pleasewait');
let paymentManager = null;
function init() {
  pleasewait.style.display = 'block';
  navigator.serviceWorker.getRegistration('app.js').then((registration) => {
    if (!registration) {
      output('Service worker not installed');
    } else if (!registration.paymentManager) {
      output('Payment manager not found');
    } else {
      paymentManager = registration.paymentManager;
    }
    pleasewait.style.display = 'none';
  });
}
init();

const button = document.getElementById('confirm');
button.addEventListener('click', (evt) => {
  button.style.display = 'none';
  pleasewait.style.display = 'block';
  navigator.serviceWorker.controller.postMessage('confirm');
});

function firePaymentMethodChangeEvent(details) {
  if (!paymentManager) {
    output('Payment manager not found');
    return;
  }
  if (!paymentManager.fireEvent) {
    output('No event firing feature in the payment manager');
    return;
  }
  pleasewait.style.display = 'block';
  paymentManager.fireEvent(id, 'paymentmethodchange', details).then((total) => {
    updateAmount(total.currency, total.value);
    pleasewait.style.display = 'none';
  }).catch((error) => {
    output(error);
    pleasewait.style.display = 'none';
  });
}

const billingAddress1 = document.getElementById('billing-address-1');
billingAddress1.addEventListener('click', (evt) => {
  firePaymentMethodChangeEvent({
    billingAddress: {
        city: 'Venice',
        country: 'US',
        postalCode: '20191',
        region: 'VA',
    },
    cardNumber: '****1111',
    cardNetwork: 'visa',
  });
});

const billingAddress2 = document.getElementById('billing-address-1');
billingAddress2.addEventListener('click', (evt) => {
  firePaymentMethodChangeEvent({
    billingAddress: {
        city: 'London',
        country: 'GB',
        postalCode: 'WC2H 8AG',
    },
    cardNumber: '****5454',
    cardNetwork: 'mastercard',
  });
});
<|endoftext|>


<commit_before>// engn.js (vDevel) - A simple game utilities library.
// Licensed under the MIT license.
// Copyright (C) 2015 Podium
// GitHub Repository: podiumnet/engn.js
(function(window, document, undefined){
  // Create the EngnJS object if it doesn't exist.
  window.engn = window.engn || {};

  // Checks for intersection. Adapted from
  // http://stackoverflow.com/questions/2752349/fast-rectangle-to-rectangle-intersection.
  window.engn.checkIntersection = function (xa, ya, wa, ha, xb, yb, wb, hb) {
    return !(xb > xa + wa || xb + wb < xa || yb > ya + ha || yb + hb < ya);
  };

  // Create the Loop class.
  window.engn.Loop = function (fps) {
    var tUpdate = []; // Bindings for game updates.
    var tRender = []; // Bindings for game renders.
    // Trigger the bindings for game renders.
    var render = function () {
      tRender.forEach(function(callback) {
        callback();
      });
    };
    // Trigger the bindings for game updates.
    var update = function () {
      tUpdate.forEach(function(callback) {
        callback();
      });
    };
    // Reset data about the game loops for a fresh start.
    var resetLoopData = function () {
      skipped = 0;
      interval = 1000 / fps;
      skipUntil = 10;
      next = new Date().getTime();
      frozen = false;
    };
    // This is the loop function that is called repeatedly forever (or until frozen.)
    var loop = function () {
      // Run the update bindings if the time set in next has come, and it will not permit any more
      // skipped renders.
      while (new Date().getTime() > next && skipped < skipUntil) {
        update();
        next += interval;
        skipped++;
      }
      // Render if an update has been performed.
      if (skipped) render();
      skipped = 0;
      // Request to run loop() again unless the game has been frozen.
      if (!frozen) requestAnimationFrame(loop);
    };
    // Freezes (pauses) the game loop.
    this.freeze = function () {
      frozen = true;
      return this;
    };
    // Unfreezes (plays) the game loop.
    this.unfreeze = function () {
      resetLoopData();
      requestAnimationFrame(loop);
      return this;
    };
    // Bind a new function to the update event.
    this.onUpdate = function (callback) {
      tUpdate.push(callback);
      return this;
    };
    // Bind a new function to the render event.
    this.onRender = function (callback) {
      tRender.push(callback);
      return this;
    };
  };
})(this, this.document);
<commit_msg>Simplify code through small change.<commit_after>// engn.js (vDevel) - A simple game utilities library.
// Licensed under the MIT license.
// Copyright (C) 2015 Podium
// GitHub Repository: podiumnet/engn.js
(function(window, document, undefined){
  // Create the EngnJS object if it doesn't exist.
  window.engn = window.engn || {};
  var engn = window.engn;

  // Checks for intersection. Adapted from
  // http://stackoverflow.com/questions/2752349/fast-rectangle-to-rectangle-intersection.
  engn.checkIntersection = function (xa, ya, wa, ha, xb, yb, wb, hb) {
    return !(xb > xa + wa || xb + wb < xa || yb > ya + ha || yb + hb < ya);
  };

  // Create the Loop class.
  engn.Loop = function (fps) {
    var tUpdate = []; // Bindings for game updates.
    var tRender = []; // Bindings for game renders.
    // Trigger the bindings for game renders.
    var render = function () {
      tRender.forEach(function(callback) {
        callback();
      });
    };
    // Trigger the bindings for game updates.
    var update = function () {
      tUpdate.forEach(function(callback) {
        callback();
      });
    };
    // Reset data about the game loops for a fresh start.
    var resetLoopData = function () {
      skipped = 0;
      interval = 1000 / fps;
      skipUntil = 10;
      next = new Date().getTime();
      frozen = false;
    };
    // This is the loop function that is called repeatedly forever (or until frozen.)
    var loop = function () {
      // Run the update bindings if the time set in next has come, and it will not permit any more
      // skipped renders.
      while (new Date().getTime() > next && skipped < skipUntil) {
        update();
        next += interval;
        skipped++;
      }
      // Render if an update has been performed.
      if (skipped) render();
      skipped = 0;
      // Request to run loop() again unless the game has been frozen.
      if (!frozen) requestAnimationFrame(loop);
    };
    // Freezes (pauses) the game loop.
    this.freeze = function () {
      frozen = true;
      return this;
    };
    // Unfreezes (plays) the game loop.
    this.unfreeze = function () {
      resetLoopData();
      requestAnimationFrame(loop);
      return this;
    };
    // Bind a new function to the update event.
    this.onUpdate = function (callback) {
      tUpdate.push(callback);
      return this;
    };
    // Bind a new function to the render event.
    this.onRender = function (callback) {
      tRender.push(callback);
      return this;
    };
  };
})(this, this.document);
<|endoftext|>


<commit_before>///////////////////////
// dom manipulation
///////////////////////

function addBeamIsotope(){
    //insert the html for a new row in the beam isotope table

    document.getElementById('beamCocktailTable').innerHTML += Mustache.to_html(
        document.getElementById('cocktailRow').innerHTML,
        {
            'id': dataStore.isotopeIndex
        }
    );

    dataStore.isotopeIndex += 1;
    repopulateBeamList()
}

function removeBeamIsotope(targetRowID){
    //remove a beam isotope row

    var node = document.getElementById(targetRowID);
    node.parentNode.removeChild(node);

    parseBeamList()
}

////////////////////////
// data extraction
////////////////////////

function parseBeamList(){
    //parse the beam cocktail table into dataStore.beamSpecies, and replot

    var isotopes = document.getElementById('beamCocktailTable').getElementsByTagName('tr'),
        i, index, properties, halfLife, halfLifeUnit;

    dataStore.beamSpecies = [];

    for(i=1; i<isotopes.length; i++){
        index = isotopes[i].id.slice(0,-3)

        halfLife = parseFloat(document.getElementById(index + 'halfLife').value);
        halfLifeUnit = parseInt(getSelected(index + 'halfLifeUnit'), 10);

        dataStore.beamSpecies.push({
            "name": document.getElementById(index + 'name').value,
            "halfLife": halfLife,
            "halfLifeUnit": halfLifeUnit,
            "lifetime": Math.log(2) / halfLife / halfLifeUnit, // in s-1
            "yield": parseFloat(document.getElementById(index + 'yield').value),
            "enabled": document.getElementById(index + 'enabled').checked
        })
    }

    repaint();
}

function parseConfigTable(){
    //parse the config table into dataStore.config

    dataStore.config = {
        "region": checkedRadio('region').value,
        "beamOn": parseInt(document.getElementById('beamOn').value, 10),
        "beamOnUnit": parseInt(getSelected('beamOnUnit'), 10),
        "beamOff": parseInt(document.getElementById('beamOff').value, 10),
        "beamOffUnit": parseInt(getSelected('beamOffUnit'), 10),
        "exptDuration": parseInt(document.getElementById('exptDuration').value, 10),
        "exptDurationUnit": parseInt(getSelected('exptDurationUnit'), 10)
    }

    if(dataStore.config.exptDurationUnit == 1)
        dataStore.config.exptDurationScale = 'h';
    else if(dataStore.config.exptDurationUnit == 24)
        dataStore.config.exptDurationScale = 'days';
    else
        dataStore.config.exptDurationScale = 'shifts';

    repaint();

}

function repopulateBeamList(){
    //repopulate the beam cocktail list with whatever is in the dataStore
    //for use after manipulating the html of the table

    var i, index,
        rows = document.getElementById('beamCocktailTable').getElementsByTagName('tr')

    for(i=0; i<dataStore.beamSpecies.length; i++){
        index = rows[i+1].id.slice(0,-3);

        document.getElementById(index + 'name').value = dataStore.beamSpecies[i].name;
        document.getElementById(index + 'halfLife').value = dataStore.beamSpecies[i].halfLife;
        document.getElementById(index + 'halfLifeUnit').value = dataStore.beamSpecies[i].halfLifeUnit;
        document.getElementById(index + 'yield').value = dataStore.beamSpecies[i].yield;
        document.getElementById(index + 'enabled').checked = dataStore.beamSpecies[i].enabled

    }
}

///////////////////////////////////
// physics / data generation
///////////////////////////////////

function generatePeakData(){
    var data, time,
        key, i, j, nTransitions,
        foundAnIsotope = false,
        propTime,
        nStep,
        beamOn = dataStore.config.beamOn*dataStore.config.beamOnUnit, // in ms
        beamOff = dataStore.config.beamOff*dataStore.config.beamOffUnit; // in ms

    //when plotting activity for the full experiment duration, aliasing can become a problem when cycle times are short
    //compared to experiment duration and half lives are comparable to the cycle time.  It's not feasible to sufficiently
    //sample this periodic behavior over the whole experiment, so in this case we plot peak activity, which is monotonically
    //increasing and not periodic.  This can be achieved by invoking activitySteps, and considering a sample of the odd indexed
    //values, which correspond to activities at the moment beamOn transitions to beamOff.

    data = {
        "time" : []
    }

    //set up arrays for all beam species of interest
    for(i=0; i<dataStore.beamSpecies.length; i++){
        if(dataStore.beamSpecies[i].enabled){
            data[dataStore.beamSpecies[i].name] = [];
        }
    }

    //decide how many points to sample - shoot for about 1000 maxima spread over the experiment
    nTransitions = Math.floor(dataStore.config.exptDuration*dataStore.config.exptDurationUnit*3600000 / (beamOn + beamOff))*2
    nStep = nTransitions / 2; // == how many maxima
    nStep = Math.max(1, Math.floor(nStep/1000)); //skip this many maxima between points to cover the experiment in about 1000 points.
    nPoints = Math.floor(nTransitions / 2 / nStep);

    for(i=0; i<nPoints; i++){
        //add the x-value to the list:
        time = beamOn + i*nStep*(beamOn + beamOff);
        data.time.push(time / 3600000 / dataStore.config.exptDurationUnit);

        //add a y-value for each isotope:
        for(j=0; j<dataStore.beamSpecies.length; j++){
            if(dataStore.beamSpecies[j].enabled){
                data[dataStore.beamSpecies[j].name].push(   nthMax(i*nStep*2 + 1,
                                                                dataStore.beamSpecies[j].yield,
                                                                beamOn,
                                                                beamOff,
                                                                dataStore.beamSpecies[j].lifetime/1000)
                                                            + chamberOffset(time, key)
                                                        );
            }
        }
    }

    return data;
}

function generateFirstThreeCyclesData(){

    return [[0,1,2,3], [100,101,102,103]]
}

function generateLastThreeCyclesData(){

    return [[0,1,2,3], [100,101,102,103]]
}

function generateAfterExperimentData(){

    return [[0,1,2,3], [100,101,102,103]]
}

function nthMax(N, rate, t_on, t_off, lifetime){
    // activity at <N>th beam transition (initial beam on is N=0) for production <rate>, beam on <t_on>[ms], beam off <t_off>[ms], <lifetime>[ms-1]
    var max = rate * (1 - Math.exp(-lifetime*t_on)),
        numerator, denominator;

    numerator = 1 - Math.pow(Math.exp(-lifetime*(t_on+t_off)), (N-1)/2 + 1);
    denominator = 1 - Math.exp(-lifetime*(t_on+t_off));

    return max*numerator/denominator;
}

function chamberOffset(time, index){
    //the chamber sees not only the 1% activity that backscatters into it,
    //but some extra activity from the 89% that is deposited on the tape in the
    //beamspot; this extra term is independent of cycle number though, since
    //the activity that builds up there is reset to 0 when the tape is cycled into the
    //tape box between beam off -> beam on.  Thus we can just caluclate it as a
    //simple correction to be tacked on at the end, that doesn't depend on where in the
    //experiment we are.
    //<time>[ms] into experiment, <index> from dataStore.beamSpecies indicating which isotope we're doing this for

    var propTime, tempTerm,
        beamOn = dataStore.config.beamOn*dataStore.config.beamOnUnit,
        beamOff = dataStore.config.beamOff*dataStore.config.beamOffUnit;

    if(dataStore.config.region != 'chamber') return 0;

    propTime = time % (beamOn + beamOff) //time into this cycle
    if (propTime < beamOn) //still in irradiation step:
        tempTerm = stepActivity(0, 0.89*dataStore.beamSpecies[i].yield, dataStore.beamSpecies[i].lifetime, propTime/1000);
    else{ //completed irradiation step, now in decay step
        tempTerm = stepActivity(0, 0.89*dataStore.beamSpecies[i].yield, dataStore.beamSpecies[i].lifetime, beamOn/1000);
        tempTerm = stepActivity(tempTerm, 0, dataStore.beamSpecies[i].lifetime, (propTime - beamOn)/1000);
    }

    return tempTerm;
}

function stepActivity(A0, rate, lifetime, time){
    //starting with initial activity <A0>, return Activity after <time>, under beam <rate> for isotope with <lifetime>
    return rate * (1 - Math.exp(-1*lifetime*time)) + A0 * Math.exp(-1*lifetime*time);
}

////////////////
//plotting
////////////////

function plotActivity(data, timeUnits, divID, title){

    var dim = document.getElementById('peakPlot').offsetWidth,
        layout = {
            xaxis:{
                title: 'time (' + timeUnits + ')'
            },
            yaxis:{
                title: 'Activity',
            },
            autosize: false,
            width: dim,
            height: dim,
            title: title,
            showlegend: true
        },
        keys = Object.keys(data),
        //construct the plotly data object
        points = [], i;

        for(i=0; i<keys.length; i++){
            if (keys[i] == 'time') continue;

            points.push({
                x: data.time,
                y: data[keys[i]],
                type: 'scatter',
                name: keys[i]
            })
        }

    Plotly.newPlot(divID, points, layout);
}

function repaint(){
    //repaint all plots

    if(dataStore.beamSpecies.length == 0) return;

    plotActivity(generatePeakData(), dataStore.config.exptDurationScale, 'peakPlot', 'Peak Activity During Experiment');
    //plotWaveform(generateFirstThreeCyclesData(), dataStore.config.exptDurationScale, 'firstThreeCyclesPlot', 'Activity Over First Three Cycles');
    //plotWaveform(generateLastThreeCyclesData(), dataStore.config.exptDurationScale, 'lastThreeCyclesPlot', 'Activity Over Last Three Cycles');
    //plotWaveform(generateAfterExperimentData(), dataStore.config.exptDurationScale, 'afterPlot', 'Activity After Experiment');
}

////////////////
// helpers
////////////////

function getSelected(id){
    //return the current value selected by the select element with id.
    //thx http://stackoverflow.com/questions/1085801/get-selected-value-in-dropdown-list-using-javascript

    var e = document.getElementById(id);
    return e.options[e.selectedIndex].value;
}

function checkedRadio(name){
    //given the name of a radio group, return the checked radio

    var i, radios = document.getElementsByName(name);

    for (i = 0; i < radios.length; i++) {
        if (radios[i].checked) {
            return radios[i];
        }
    }

    return null
}<commit_msg>first three cycles plot complete<commit_after>///////////////////////
// dom manipulation
///////////////////////

function addBeamIsotope(){
    //insert the html for a new row in the beam isotope table

    document.getElementById('beamCocktailTable').innerHTML += Mustache.to_html(
        document.getElementById('cocktailRow').innerHTML,
        {
            'id': dataStore.isotopeIndex
        }
    );

    dataStore.isotopeIndex += 1;
    repopulateBeamList()
}

function removeBeamIsotope(targetRowID){
    //remove a beam isotope row

    var node = document.getElementById(targetRowID);
    node.parentNode.removeChild(node);

    parseBeamList()
}

////////////////////////
// data extraction
////////////////////////

function parseBeamList(){
    //parse the beam cocktail table into dataStore.beamSpecies, and replot

    var isotopes = document.getElementById('beamCocktailTable').getElementsByTagName('tr'),
        i, index, properties, halfLife, halfLifeUnit;

    dataStore.beamSpecies = [];

    for(i=1; i<isotopes.length; i++){
        index = isotopes[i].id.slice(0,-3)

        halfLife = parseFloat(document.getElementById(index + 'halfLife').value);
        halfLifeUnit = parseInt(getSelected(index + 'halfLifeUnit'), 10);

        dataStore.beamSpecies.push({
            "name": document.getElementById(index + 'name').value,
            "halfLife": halfLife,
            "halfLifeUnit": halfLifeUnit,
            "lifetime": Math.log(2) / halfLife / halfLifeUnit, // in s-1
            "yield": parseFloat(document.getElementById(index + 'yield').value),
            "enabled": document.getElementById(index + 'enabled').checked
        })
    }

    repaint();
}

function parseConfigTable(){
    //parse the config table into dataStore.config

    dataStore.config = {
        "region": checkedRadio('region').value,
        "beamOn": parseInt(document.getElementById('beamOn').value, 10),
        "beamOnUnit": parseInt(getSelected('beamOnUnit'), 10),
        "beamOff": parseInt(document.getElementById('beamOff').value, 10),
        "beamOffUnit": parseInt(getSelected('beamOffUnit'), 10),
        "exptDuration": parseInt(document.getElementById('exptDuration').value, 10),
        "exptDurationUnit": parseInt(getSelected('exptDurationUnit'), 10)
    }

    if(dataStore.config.exptDurationUnit == 1)
        dataStore.config.exptDurationScale = 'h';
    else if(dataStore.config.exptDurationUnit == 24)
        dataStore.config.exptDurationScale = 'days';
    else
        dataStore.config.exptDurationScale = 'shifts';

    repaint();

}

function repopulateBeamList(){
    //repopulate the beam cocktail list with whatever is in the dataStore
    //for use after manipulating the html of the table

    var i, index,
        rows = document.getElementById('beamCocktailTable').getElementsByTagName('tr')

    for(i=0; i<dataStore.beamSpecies.length; i++){
        index = rows[i+1].id.slice(0,-3);

        document.getElementById(index + 'name').value = dataStore.beamSpecies[i].name;
        document.getElementById(index + 'halfLife').value = dataStore.beamSpecies[i].halfLife;
        document.getElementById(index + 'halfLifeUnit').value = dataStore.beamSpecies[i].halfLifeUnit;
        document.getElementById(index + 'yield').value = dataStore.beamSpecies[i].yield;
        document.getElementById(index + 'enabled').checked = dataStore.beamSpecies[i].enabled

    }
}

///////////////////////////////////
// physics / data generation
///////////////////////////////////

function generatePeakData(){
    var data, time,
        i, j, nTransitions,
        nStep,
        beamOn = dataStore.config.beamOn*dataStore.config.beamOnUnit, // in ms
        beamOff = dataStore.config.beamOff*dataStore.config.beamOffUnit; // in ms

    //when plotting activity for the full experiment duration, aliasing can become a problem when cycle times are short
    //compared to experiment duration and half lives are comparable to the cycle time.  It's not feasible to sufficiently
    //sample this periodic behavior over the whole experiment, so in this case we plot peak activity, which is monotonically
    //increasing and not periodic.  This can be achieved by invoking activitySteps, and considering a sample of the odd indexed
    //values, which correspond to activities at the moment beamOn transitions to beamOff.

    data = {
        "time" : []
    }

    //set up arrays for all beam species of interest
    for(i=0; i<dataStore.beamSpecies.length; i++){
        if(dataStore.beamSpecies[i].enabled){
            data[dataStore.beamSpecies[i].name] = [];
        }
    }

    //decide how many points to sample - shoot for about 1000 maxima spread over the experiment
    nTransitions = Math.floor(dataStore.config.exptDuration*dataStore.config.exptDurationUnit*3600000 / (beamOn + beamOff))*2
    nStep = nTransitions / 2; // == how many maxima
    nStep = Math.max(1, Math.floor(nStep/1000)); //skip this many maxima between points to cover the experiment in about 1000 points.
    nPoints = Math.floor(nTransitions / 2 / nStep);

    for(i=0; i<nPoints; i++){
        //add the x-value to the list:
        time = beamOn + i*nStep*(beamOn + beamOff);
        data.time.push(time / 3600000 / dataStore.config.exptDurationUnit);

        //add a y-value for each isotope:
        for(j=0; j<dataStore.beamSpecies.length; j++){
            if(dataStore.beamSpecies[j].enabled){
                data[dataStore.beamSpecies[j].name].push(   nthMax(i*nStep*2 + 1,
                                                                dataStore.beamSpecies[j].yield,
                                                                beamOn,
                                                                beamOff,
                                                                dataStore.beamSpecies[j].lifetime/1000)
                                                            + chamberOffset(time, dataStore.beamSpecies[j].lifetime, dataStore.beamSpecies[j].yield)
                                                        );
            }
        }
    }

    return data;
}

function generateFirstThreeCyclesData(){

    var data, time,
        nPoints = 800,
        foundAnIsotope = false,
        key, i, nextline,
        propTime,
        tempTerm = 0,
        nStep,
        beamOn = dataStore.config.beamOn*dataStore.config.beamOnUnit, // in ms
        beamOff = dataStore.config.beamOff*dataStore.config.beamOffUnit; // in ms

    data = {
        "time" : []
    }

    //set up arrays for all beam species of interest
    for(i=0; i<dataStore.beamSpecies.length; i++){
        if(dataStore.beamSpecies[i].enabled){
            data[dataStore.beamSpecies[i].name] = [];
        }
    }

    for(i=0; i<nPoints; i++){
        //add the x-value to the list:
        time = (3*(beamOn + beamOff)/nPoints)*i;
        data.time.push(time / 3600000 / dataStore.config.exptDurationUnit);

        //add a y-value for each isotope:
        for(j=0; j<dataStore.beamSpecies.length; j++){
            if(dataStore.beamSpecies[j].enabled){
                data[dataStore.beamSpecies[j].name].push(   Activity(
                                                                dataStore.beamSpecies[j].yield,
                                                                dataStore.beamSpecies[j].lifetime/1000,
                                                                time,
                                                                dataStore.config.region
                                                            )
                                                            + chamberOffset(time, dataStore.beamSpecies[j].lifetime, dataStore.beamSpecies[j].yield)
                                                        );
            }
        }
    }

    return data;
}

function generateLastThreeCyclesData(){

    return [[0,1,2,3], [100,101,102,103]]
}

function generateAfterExperimentData(){

    return [[0,1,2,3], [100,101,102,103]]
}

function nthMax(N, rate, t_on, t_off, lifetime){
    // activity at <N>th beam transition (initial beam on is N=0) for production <rate>, beam on <t_on>[ms], beam off <t_off>[ms], <lifetime>[ms-1]
    var max = rate * (1 - Math.exp(-lifetime*t_on)),
        numerator, denominator;

    numerator = 1 - Math.pow(Math.exp(-lifetime*(t_on+t_off)), (N-1)/2 + 1);
    denominator = 1 - Math.exp(-lifetime*(t_on+t_off));

    return max*numerator/denominator;
}

function chamberOffset(time, lifetime, yield){
    //the chamber sees not only the 1% activity that backscatters into it,
    //but some extra activity from the 89% that is deposited on the tape in the
    //beamspot; this extra term is independent of cycle number though, since
    //the activity that builds up there is reset to 0 when the tape is cycled into the
    //tape box between beam off -> beam on.  Thus we can just caluclate it as a
    //simple correction to be tacked on at the end, that doesn't depend on where in the
    //experiment we are.
    //<time>[ms] into experiment, yield s-1, lifetime s-1

    var propTime, tempTerm,
        beamOn = dataStore.config.beamOn*dataStore.config.beamOnUnit,
        beamOff = dataStore.config.beamOff*dataStore.config.beamOffUnit;

    if(dataStore.config.region != 'chamber') return 0;

    propTime = time % (beamOn + beamOff) //time into this cycle
    if (propTime < beamOn) //still in irradiation step:
        tempTerm = stepActivity(0, 0.89*yield, lifetime, propTime/1000);
    else{ //completed irradiation step, now in decay step
        tempTerm = stepActivity(0, 0.89*yield, lifetime, beamOn/1000);
        tempTerm = stepActivity(tempTerm, 0, lifetime, (propTime - beamOn)/1000);
    }

    return tempTerm;
}

function stepActivity(A0, rate, lifetime, time){
    //starting with initial activity <A0>, return Activity after <time>, under beam <rate> for isotope with <lifetime>
    return rate * (1 - Math.exp(-1*lifetime*time)) + A0 * Math.exp(-1*lifetime*time);
}

function Activity(rate, lifetime, time, region){
    // activity [s-1] at <time [ms]>, for an isotope with <lifetime [ms-1]>, initial activity <A0 [s-1]> and production <rate [s-1]>

    var N, timeSinceLastMax, activityAtLastMax, activity,
        Rate = regionScale(region) * rate,
        beamOn = dataStore.config.beamOn*dataStore.config.beamOnUnit,
        beamOff = dataStore.config.beamOff*dataStore.config.beamOffUnit;

    //if we're in the very first implantation, things are simple:
    if(time < beamOn){
        return stepActivity(0, Rate, lifetime, time);
    }

    //how many full cycles have completed before <time>?
    N = Math.floor(time / (beamOn + beamOff));
    //how much time has elapsed in the final cycle before <time>?
    timeSinceLastMax = time - N*(beamOn + beamOff);

    if(timeSinceLastMax < beamOn){
        N = 2*N-1;
        timeSinceLastMax += beamOff;
    } else {
        N = 2*N+1;
        timeSinceLastMax -= beamOn;
    }

    activityAtLastMax = nthMax(N, Rate, beamOn, beamOff, lifetime);

    //finish last beam off
    activity = stepActivity(activityAtLastMax, 0, lifetime, Math.min(timeSinceLastMax, beamOff));
    if(timeSinceLastMax < beamOff)
        return activity;

    //finish last beam on
    activity = stepActivity(activity, Rate, lifetime, Math.min(timeSinceLastMax-beamOff, beamOn) );

    return activity;
}

function regionScale(region){
    //return the appropriate scale constant as a function of <region>:
    if(region == 'box') return 0.89;
    else if(region == 'chamber') return 0.01;
    else if(region == 'beam') return 0.1;
}

////////////////
//plotting
////////////////

function plotActivity(data, timeUnits, divID, title){

    var dim = document.getElementById('peakPlot').offsetWidth,
        layout = {
            xaxis:{
                title: 'time (' + timeUnits + ')'
            },
            yaxis:{
                title: 'Activity',
            },
            autosize: false,
            width: dim,
            height: dim,
            title: title,
            showlegend: true
        },
        keys = Object.keys(data),
        //construct the plotly data object
        points = [], i;

        for(i=0; i<keys.length; i++){
            if (keys[i] == 'time') continue;

            points.push({
                x: data.time,
                y: data[keys[i]],
                type: 'scatter',
                name: keys[i]
            })
        }

    Plotly.newPlot(divID, points, layout);
}

function repaint(){
    //repaint all plots

    if(dataStore.beamSpecies.length == 0) return;

    plotActivity(generatePeakData(), dataStore.config.exptDurationScale, 'peakPlot', 'Peak Activity During Experiment');
    plotActivity(generateFirstThreeCyclesData(), dataStore.config.exptDurationScale, 'firstThreeCyclesPlot', 'Activity Over First Three Cycles');
    //plotWaveform(generateLastThreeCyclesData(), dataStore.config.exptDurationScale, 'lastThreeCyclesPlot', 'Activity Over Last Three Cycles');
    //plotWaveform(generateAfterExperimentData(), dataStore.config.exptDurationScale, 'afterPlot', 'Activity After Experiment');
}

////////////////
// helpers
////////////////

function getSelected(id){
    //return the current value selected by the select element with id.
    //thx http://stackoverflow.com/questions/1085801/get-selected-value-in-dropdown-list-using-javascript

    var e = document.getElementById(id);
    return e.options[e.selectedIndex].value;
}

function checkedRadio(name){
    //given the name of a radio group, return the checked radio

    var i, radios = document.getElementsByName(name);

    for (i = 0; i < radios.length; i++) {
        if (radios[i].checked) {
            return radios[i];
        }
    }

    return null
}<|endoftext|>


<commit_before>
require('babel-register');

const expect = require('chai').expect;
const RG = require('../client/src/battles');
const Keys = require('../client/src/keymap');
const fs = require('fs');

const restKey = {code: Keys.KEY.REST};

const RNG = RG.Random.getRNG();
const ROT = require('../lib/rot');

RNG.setSeed(0);
ROT.RNG.setSeed(0);

console.log('RNG uniform: ' + RNG.getUniform());
console.log('ROT.RNG uniform: ' + ROT.RNG.getUniform());

const gameConf = {
    cols: 60,
    rows: 30,
    levels: 2,

    playerLevel: 'Medium',
    levelSize: 'Medium',
    playerClass: RG.ACTOR_CLASSES[0],
    playerRace: RG.ACTOR_RACES[0],

    sqrPerActor: 120,
    sqrPerItem: 120,
    playMode: 'Arena',
    loadedPlayer: null,
    loadedLevel: null,
    playerName: 'Player'
};
const gameFact = new RG.Factory.Game();
let game = gameFact.createNewGame(gameConf);

// Simulate 1st serialisation in worker thread
let gameJSON = game.toJSON();
let fromJSON = new RG.Game.FromJSON();
game = fromJSON.createGame(gameJSON);

// Used with expect() later
const saveFunc = () => {
    fromJSON = new RG.Game.FromJSON();
    gameJSON = game.toJSON();
    game = fromJSON.createGame(gameJSON);
};

const updateFunc = () => {
    game.update(restKey);
};

const simulSpellOn1stTurn = () => {
    game.update({code: Keys.KEY.POWER});
    game.update({code: Keys.VK_h});
};
// expect(simulSpellOn1stTurn).not.to.throw(Error);

const timeStart = new Date().getTime();
const numTurns = 10000;
for (let i = 1; i <= numTurns; i++) {
    // expect(updateFunc).not.to.throw(Error);
    updateFunc();

    if (i === 10) {
        expect(simulSpellOn1stTurn).not.to.throw(Error);
    }
    if (i % 100 === 0) {
        console.log(`Saving game after ${i}/${numTurns} turns`);
        // expect(saveFunc).not.to.throw(Error);
        saveFunc();
    }
    if (i % 10 === 0) {
        console.log('Finished turn ' + i);
    }
}
const timeEnd = new Date().getTime();
const dur = timeEnd - timeStart;
console.log('Execution took ' + dur + ' ms');

const fps = numTurns / (dur / 1000);
console.log('FPS: ' + fps);

fromJSON = new RG.Game.FromJSON();
gameJSON = game.toJSON();
fs.writeFileSync('results/debug-game.json',
    JSON.stringify(gameJSON));

<commit_msg>Added printing of fps after each save. fps reduces to half after the first save.<commit_after>
require('babel-register');

const expect = require('chai').expect;
const RG = require('../client/src/battles');
const Keys = require('../client/src/keymap');
const fs = require('fs');

const restKey = {code: Keys.KEY.REST};

const RNG = RG.Random.getRNG();
const ROT = require('../lib/rot');

RNG.setSeed(0);
ROT.RNG.setSeed(0);

console.log('RNG uniform: ' + RNG.getUniform());
console.log('ROT.RNG uniform: ' + ROT.RNG.getUniform());

const gameConf = {
    cols: 60,
    rows: 30,
    levels: 2,

    playerLevel: 'Medium',
    levelSize: 'Medium',
    playerClass: RG.ACTOR_CLASSES[0],
    playerRace: RG.ACTOR_RACES[0],

    sqrPerActor: 120,
    sqrPerItem: 120,
    playMode: 'Arena',
    loadedPlayer: null,
    loadedLevel: null,
    playerName: 'Player',
    seed: 0
};
const gameFact = new RG.Factory.Game();
let game = gameFact.createNewGame(gameConf);

// Simulate 1st serialisation in worker thread
let gameJSON = game.toJSON();
let fromJSON = new RG.Game.FromJSON();
game = fromJSON.createGame(gameJSON);

// Used with expect() later
const saveFunc = () => {
    fromJSON = new RG.Game.FromJSON();
    gameJSON = game.toJSON();
    game = fromJSON.createGame(gameJSON);
    console.log(`saveFunc RG.POOL listeners: ${RG.POOL.getNumListeners()}`);
};

const updateFunc = () => {
    game.update(restKey);
};

const simulSpellOn1stTurn = () => {
    game.update({code: Keys.KEY.POWER});
    game.update({code: Keys.VK_h});
};
// expect(simulSpellOn1stTurn).not.to.throw(Error);

const timeStart = new Date().getTime();
const numTurns = 500;
const saveInterval = 200;

let turnOfLastSave = 0;
let timeSaveFinished = Date.now();

console.log(`Start RG.POOL listeners: ${RG.POOL.getNumListeners()}`);

for (let i = 1; i <= numTurns; i++) {
    // expect(updateFunc).not.to.throw(Error);
    updateFunc();

    if (i === 10) {
        expect(simulSpellOn1stTurn).not.to.throw(Error);
    }
    if (i % saveInterval === 0) {
        const timeNow = Date.now();
        const dur = timeNow - timeSaveFinished;
        const fps = (i - turnOfLastSave) / (dur / 1000);
        console.log(`FPS: ${fps} Saving game after ${i}/${numTurns} turns`);
        // expect(saveFunc).not.to.throw(Error);
        saveFunc();
        turnOfLastSave = i;
        timeSaveFinished = Date.now();
    }
    if (i % 10 === 0) {
        console.log('Finished turn ' + i);
    }
}
const timeEnd = new Date().getTime();
const dur = timeEnd - timeStart;
console.log('Execution took ' + dur + ' ms');

const fps = numTurns / (dur / 1000);
console.log('FPS: ' + fps);

fromJSON = new RG.Game.FromJSON();
gameJSON = game.toJSON();
fs.writeFileSync('results/debug-game.json',
    JSON.stringify(gameJSON));

<|endoftext|>


<commit_before>Modules.VERSION = '1.2.6';

this.uiBackup = {};

this.handleUIHighlightBackground = function() {
	uiBackup.textHighlightBackground = Prefs.textHighlightBackground;

	// This triggers a re-color
	var original = Prefs.highlightColor;
	if(Prefs.textHighlightBackground) {
		Prefs.highlightColor = Prefs.textHighlightBackground;
	} else {
		Prefs.reset('highlightColor');
	}
	// Make sure we trigger a re-color when it's not actually been changed
	if(original == Prefs.highlightColor) {
		changeHighlightColor();
	}
};

// This won't trigger a re-color
this.handleUIHighlightForeground = function() {
	uiBackup.textHighlightForeground = Prefs.textHighlightForeground;
};

this.handleUISelectBackground = function() {
	uiBackup.textSelectBackgroundAttention = Prefs.textSelectBackgroundAttention;

	// This triggers a re-color
	var original = Prefs.selectColor;
	if(Prefs.textSelectBackgroundAttention) {
		Prefs.selectColor = Prefs.textSelectBackgroundAttention;
	} else {
		Prefs.reset('selectColor');
	}
	// Make sure we trigger a re-color when it's not actually been changed
	if(original == Prefs.selectColor) {
		changeSelectColor();
	}
};

// This won't trigger a re-color
this.handleUISelectForeground = function() {
	uiBackup.textSelectForeground = Prefs.textSelectForeground;
};

this.getRGBfromString = function(m) {
	if(m[1].length === 6) { // 6-char notation
		var rgb = {
			r: parseInt(m[1].substr(0,2),16),
			g: parseInt(m[1].substr(2,2),16),
			b: parseInt(m[1].substr(4,2),16)
		};
	} else { // 3-char notation
		var rgb = {
			r: parseInt(m[1].charAt(0)+m[1].charAt(0),16),
			g: parseInt(m[1].charAt(1)+m[1].charAt(1),16),
			b: parseInt(m[1].charAt(2)+m[1].charAt(2),16)
		};
	}
	return rgb;
};

this.darkBackgroundRGB = function(rgb) {
	return (0.213 *rgb.r /255 + 0.715 *rgb.g /255 + 0.072 *rgb.b /255 < 0.7);
};

this.changeHighlightColor = function() {
	var m = Prefs.highlightColor.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
	if(!m) { return; }
	var rgb = getRGBfromString(m);

	// Have to remove the listeners and add them back after, so the backups aren't overwritten with our values
	Prefs.unlisten('textHighlightBackground', handleUIHighlightBackground);
	Prefs.unlisten('textHighlightForeground', handleUIHighlightForeground);

	Prefs.textHighlightBackground = Prefs.highlightColor;
	Prefs.textHighlightForeground = (darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000';

	Prefs.listen('textHighlightBackground', handleUIHighlightBackground);
	Prefs.listen('textHighlightForeground', handleUIHighlightForeground);

	setHighlightColorStyleSheet(rgb);

	Observers.notify('ReHighlightAll');
};

this.setHighlightColorStyleSheet = function(rgb) {
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);\n';
	sscode += '@-moz-document\n';
	sscode += '	url("chrome://browser/content/browser.xul"),\n';
	sscode += '	url("chrome://global/content/viewSource.xul"),\n';
	sscode += '	url("chrome://global/content/viewPartialSource.xul"),\n';
	sscode += '	url("chrome://findbartweak/content/findInTabsFull.xul") {\n';
	sscode += '		.findInTabs-list label[highlight]:not([current]):not(:hover) {\n';
	sscode += '			background-color: '+Prefs.highlightColor+';\n';
	sscode += '			color: '+((darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000')+';\n';
	sscode += '		}\n';
	sscode += '		vbox[anonid="findGrid"] vbox[highlight]:not([current]):not([hover]) {\n';
	sscode += '			background-color: '+Prefs.highlightColor+';\n';
	sscode += '		}\n';
	sscode += '}';

	Styles.load('highlightColorStyleSheet', sscode, true);

	// For PDF.JS
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.w3.org/1999/xhtml);\n';
	sscode += 'body #outerContainer #mainContainer #viewerContainer .textLayer .highlight:not(.selected) { background-color: rgba('+rgb.r+','+rgb.g+','+rgb.b+',0.3) !important; }\n';

	// For grids in frames
	sscode += 'div[ownedByFindBarTweak][anonid="gridBox"] div[anonid="findGrid"] div[highlight]:not([current]):not([hover]) {\n';
	sscode += '	background-color: '+Prefs.highlightColor+';\n';
	sscode += '}\n';

	Styles.load('otherHighlightColorStyleSheet', sscode, true);
};

this.changeSelectColor = function() {
	var m = Prefs.selectColor.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
	if(!m) { return; }
	var rgb = getRGBfromString(m);

	// Have to remove the listeners and add them back after, so the backups aren't overwritten with our values
	Prefs.unlisten('textSelectBackgroundAttention', handleUISelectBackground);
	Prefs.unlisten('textSelectForeground', handleUISelectForeground);

	Prefs.textSelectBackgroundAttention = Prefs.selectColor;

	if(!Prefs.keepSelectContrast) { Prefs.textSelectForeground = (darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000'; }
	else if(uiBackup.textSelectForeground) { Prefs.textSelectForeground = uiBackup.textSelectForeground; }
	else { Prefs.reset('textSelectForeground'); }

	Prefs.listen('textSelectBackgroundAttention', handleUISelectBackground);
	Prefs.listen('textSelectForeground', handleUISelectForeground);

	setSelectColorStyleSheet(rgb);

	Observers.notify('ReHighlightAll');
};

this.setSelectColorStyleSheet = function(rgb) {
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);\n';
	sscode += '@-moz-document\n';
	sscode += '	url("chrome://browser/content/browser.xul"),\n';
	sscode += '	url("chrome://global/content/viewSource.xul"),\n';
	sscode += '	url("chrome://global/content/viewPartialSource.xul"),\n';
	sscode += '	url("chrome://findbartweak/content/findInTabsFull.xul") {\n';
	sscode += '		.findInTabs-list label[highlight][current],\n';
	sscode += '		.findInTabs-list label[highlight]:hover {\n';
	sscode += '			background-color: '+Prefs.selectColor+';\n';
	sscode += '			color: '+((darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000')+';\n';
	sscode += '		}\n';
	sscode += '		.findInTabs-list richlistitem:hover {\n';
	sscode += '			background-color: rgba('+rgb.r+','+rgb.g+','+rgb.b+',0.03);\n';
	sscode += '			box-shadow: inset 0 0 2px 1px rgba('+rgb.r+','+rgb.g+','+rgb.b+',0.2);\n';
	sscode += '		}\n';
	sscode += '		vbox[anonid="findGrid"] vbox[highlight][current],\n';
	sscode += '		vbox[anonid="findGrid"] vbox[highlight][hover] {\n';
	sscode += '			background-color: '+Prefs.selectColor+';\n';
	sscode += '		}\n';
	sscode += '}';

	Styles.load('selectColorStyleSheet', sscode, true);

	// For PDF.JS
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.w3.org/1999/xhtml);\n';
	sscode += 'body #outerContainer #mainContainer #viewerContainer .textLayer .highlight.selected { background-color: rgba('+rgb.r+','+rgb.g+','+rgb.b+',0.3) !important; }\n';

	// For grids in frames
	sscode += 'div[ownedByFindBarTweak][anonid="gridBox"] div[anonid="findGrid"] div[highlight][current],\n';
	sscode += 'div[ownedByFindBarTweak][anonid="gridBox"] div[anonid="findGrid"] div[highlight][hover] {\n';
	sscode += '	background-color: '+Prefs.selectColor+';\n';
	sscode += '}\n';

	Styles.load('otherSelectColorStyleSheet', sscode, true);
};

this.resetColorPrefs = function() {
	Prefs.unlisten('textHighlightBackground', handleUIHighlightBackground);
	Prefs.unlisten('textHighlightForeground', handleUIHighlightForeground);
	Prefs.unlisten('textSelectBackgroundAttention', handleUISelectBackground);
	Prefs.unlisten('textSelectForeground', handleUISelectForeground);

	if(!Prefs.resetNative && uiBackup.textHighlightBackground) { Prefs.textHighlightBackground = uiBackup.textHighlightBackground; }
	else { Prefs.reset('textHighlightBackground'); }
	if(!Prefs.resetNative && uiBackup.textHighlightForeground) { Prefs.textHighlightForeground = uiBackup.textHighlightForeground; }
	else { Prefs.reset('textHighlightForeground'); }

	if(!Prefs.resetNative && uiBackup.textSelectBackgroundAttention) { Prefs.textSelectBackgroundAttention = uiBackup.textSelectBackgroundAttention; }
	else { Prefs.reset('textSelectBackgroundAttention'); }
	if(!Prefs.resetNative && uiBackup.textSelectForeground) { Prefs.textSelectForeground = uiBackup.textSelectForeground; }
	else { Prefs.reset('textSelectForeground'); }
};

Modules.LOADMODULE = function() {
	Prefs.setDefaults({
		textHighlightBackground: '',
		textHighlightForeground: '',
		textSelectBackgroundAttention: '',
		textSelectForeground: '',
	}, 'ui', '');

	uiBackup = {
		textHighlightBackground: Prefs.textHighlightBackground,
		textHighlightForeground: Prefs.textHighlightForeground,
		textSelectBackgroundAttention: Prefs.textSelectBackgroundAttention,
		textSelectForeground: Prefs.textSelectForeground
	};

	Prefs.listen('highlightColor', changeHighlightColor);
	Prefs.listen('selectColor', changeSelectColor);
	Prefs.listen('keepSelectContrast', changeSelectColor);

	alwaysRunOnShutdown.push(resetColorPrefs);

	changeHighlightColor();
	changeSelectColor();
};

Modules.UNLOADMODULE = function() {
	Styles.unload('highlightColorStyleSheet');
	Styles.unload('otherHighlightColorStyleSheet');
	Styles.unload('selectColorStyleSheet');
	Styles.unload('otherSelectColorStyleSheet');

	Prefs.unlisten('highlightColor', changeHighlightColor);
	Prefs.unlisten('selectColor', changeSelectColor);
	Prefs.unlisten('keepSelectContrast', changeSelectColor);

	resetColorPrefs();
};
<commit_msg>Closes #172: faint highlights in PDFs<commit_after>Modules.VERSION = '1.2.7';

this.uiBackup = {};

this.handleUIHighlightBackground = function() {
	uiBackup.textHighlightBackground = Prefs.textHighlightBackground;

	// This triggers a re-color
	var original = Prefs.highlightColor;
	if(Prefs.textHighlightBackground) {
		Prefs.highlightColor = Prefs.textHighlightBackground;
	} else {
		Prefs.reset('highlightColor');
	}
	// Make sure we trigger a re-color when it's not actually been changed
	if(original == Prefs.highlightColor) {
		changeHighlightColor();
	}
};

// This won't trigger a re-color
this.handleUIHighlightForeground = function() {
	uiBackup.textHighlightForeground = Prefs.textHighlightForeground;
};

this.handleUISelectBackground = function() {
	uiBackup.textSelectBackgroundAttention = Prefs.textSelectBackgroundAttention;

	// This triggers a re-color
	var original = Prefs.selectColor;
	if(Prefs.textSelectBackgroundAttention) {
		Prefs.selectColor = Prefs.textSelectBackgroundAttention;
	} else {
		Prefs.reset('selectColor');
	}
	// Make sure we trigger a re-color when it's not actually been changed
	if(original == Prefs.selectColor) {
		changeSelectColor();
	}
};

// This won't trigger a re-color
this.handleUISelectForeground = function() {
	uiBackup.textSelectForeground = Prefs.textSelectForeground;
};

this.getRGBfromString = function(m) {
	if(m[1].length === 6) { // 6-char notation
		var rgb = {
			r: parseInt(m[1].substr(0,2),16),
			g: parseInt(m[1].substr(2,2),16),
			b: parseInt(m[1].substr(4,2),16)
		};
	} else { // 3-char notation
		var rgb = {
			r: parseInt(m[1].charAt(0)+m[1].charAt(0),16),
			g: parseInt(m[1].charAt(1)+m[1].charAt(1),16),
			b: parseInt(m[1].charAt(2)+m[1].charAt(2),16)
		};
	}
	return rgb;
};

this.darkBackgroundRGB = function(rgb) {
	return (0.213 *rgb.r /255 + 0.715 *rgb.g /255 + 0.072 *rgb.b /255 < 0.7);
};

this.changeHighlightColor = function() {
	var m = Prefs.highlightColor.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
	if(!m) { return; }
	var rgb = getRGBfromString(m);

	// Have to remove the listeners and add them back after, so the backups aren't overwritten with our values
	Prefs.unlisten('textHighlightBackground', handleUIHighlightBackground);
	Prefs.unlisten('textHighlightForeground', handleUIHighlightForeground);

	Prefs.textHighlightBackground = Prefs.highlightColor;
	Prefs.textHighlightForeground = (darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000';

	Prefs.listen('textHighlightBackground', handleUIHighlightBackground);
	Prefs.listen('textHighlightForeground', handleUIHighlightForeground);

	setHighlightColorStyleSheet(rgb);

	Observers.notify('ReHighlightAll');
};

this.setHighlightColorStyleSheet = function(rgb) {
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);\n';
	sscode += '@-moz-document\n';
	sscode += '	url("chrome://browser/content/browser.xul"),\n';
	sscode += '	url("chrome://global/content/viewSource.xul"),\n';
	sscode += '	url("chrome://global/content/viewPartialSource.xul"),\n';
	sscode += '	url("chrome://findbartweak/content/findInTabsFull.xul") {\n';
	sscode += '		.findInTabs-list label[highlight]:not([current]):not(:hover) {\n';
	sscode += '			background-color: '+Prefs.highlightColor+';\n';
	sscode += '			color: '+((darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000')+';\n';
	sscode += '		}\n';
	sscode += '		vbox[anonid="findGrid"] vbox[highlight]:not([current]):not([hover]) {\n';
	sscode += '			background-color: '+Prefs.highlightColor+';\n';
	sscode += '		}\n';
	sscode += '}';

	Styles.load('highlightColorStyleSheet', sscode, true);

	// For PDF.JS
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.w3.org/1999/xhtml);\n';
	sscode += 'body #outerContainer #mainContainer #viewerContainer .textLayer .highlight:not(.selected) { background-color: rgb('+rgb.r+','+rgb.g+','+rgb.b+'); }\n';

	// For grids in frames
	sscode += 'div[ownedByFindBarTweak][anonid="gridBox"] div[anonid="findGrid"] div[highlight]:not([current]):not([hover]) {\n';
	sscode += '	background-color: '+Prefs.highlightColor+';\n';
	sscode += '}\n';

	Styles.load('otherHighlightColorStyleSheet', sscode, true);
};

this.changeSelectColor = function() {
	var m = Prefs.selectColor.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i);
	if(!m) { return; }
	var rgb = getRGBfromString(m);

	// Have to remove the listeners and add them back after, so the backups aren't overwritten with our values
	Prefs.unlisten('textSelectBackgroundAttention', handleUISelectBackground);
	Prefs.unlisten('textSelectForeground', handleUISelectForeground);

	Prefs.textSelectBackgroundAttention = Prefs.selectColor;

	if(!Prefs.keepSelectContrast) { Prefs.textSelectForeground = (darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000'; }
	else if(uiBackup.textSelectForeground) { Prefs.textSelectForeground = uiBackup.textSelectForeground; }
	else { Prefs.reset('textSelectForeground'); }

	Prefs.listen('textSelectBackgroundAttention', handleUISelectBackground);
	Prefs.listen('textSelectForeground', handleUISelectForeground);

	setSelectColorStyleSheet(rgb);

	Observers.notify('ReHighlightAll');
};

this.setSelectColorStyleSheet = function(rgb) {
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul);\n';
	sscode += '@-moz-document\n';
	sscode += '	url("chrome://browser/content/browser.xul"),\n';
	sscode += '	url("chrome://global/content/viewSource.xul"),\n';
	sscode += '	url("chrome://global/content/viewPartialSource.xul"),\n';
	sscode += '	url("chrome://findbartweak/content/findInTabsFull.xul") {\n';
	sscode += '		.findInTabs-list label[highlight][current],\n';
	sscode += '		.findInTabs-list label[highlight]:hover {\n';
	sscode += '			background-color: '+Prefs.selectColor+';\n';
	sscode += '			color: '+((darkBackgroundRGB(rgb)) ? '#FFFFFF' : '#000000')+';\n';
	sscode += '		}\n';
	sscode += '		.findInTabs-list richlistitem:hover {\n';
	sscode += '			background-color: rgba('+rgb.r+','+rgb.g+','+rgb.b+',0.03);\n';
	sscode += '			box-shadow: inset 0 0 2px 1px rgba('+rgb.r+','+rgb.g+','+rgb.b+',0.2);\n';
	sscode += '		}\n';
	sscode += '		vbox[anonid="findGrid"] vbox[highlight][current],\n';
	sscode += '		vbox[anonid="findGrid"] vbox[highlight][hover] {\n';
	sscode += '			background-color: '+Prefs.selectColor+';\n';
	sscode += '		}\n';
	sscode += '}';

	Styles.load('selectColorStyleSheet', sscode, true);

	// For PDF.JS
	var sscode = '/*FindBar Tweak CSS declarations of variable values*/\n';
	sscode += '@namespace url(http://www.w3.org/1999/xhtml);\n';
	sscode += 'body #outerContainer #mainContainer #viewerContainer .textLayer .highlight.selected { background-color: rgb('+rgb.r+','+rgb.g+','+rgb.b+'); }\n';

	// For grids in frames
	sscode += 'div[ownedByFindBarTweak][anonid="gridBox"] div[anonid="findGrid"] div[highlight][current],\n';
	sscode += 'div[ownedByFindBarTweak][anonid="gridBox"] div[anonid="findGrid"] div[highlight][hover] {\n';
	sscode += '	background-color: '+Prefs.selectColor+';\n';
	sscode += '}\n';

	Styles.load('otherSelectColorStyleSheet', sscode, true);
};

this.resetColorPrefs = function() {
	Prefs.unlisten('textHighlightBackground', handleUIHighlightBackground);
	Prefs.unlisten('textHighlightForeground', handleUIHighlightForeground);
	Prefs.unlisten('textSelectBackgroundAttention', handleUISelectBackground);
	Prefs.unlisten('textSelectForeground', handleUISelectForeground);

	if(!Prefs.resetNative && uiBackup.textHighlightBackground) { Prefs.textHighlightBackground = uiBackup.textHighlightBackground; }
	else { Prefs.reset('textHighlightBackground'); }
	if(!Prefs.resetNative && uiBackup.textHighlightForeground) { Prefs.textHighlightForeground = uiBackup.textHighlightForeground; }
	else { Prefs.reset('textHighlightForeground'); }

	if(!Prefs.resetNative && uiBackup.textSelectBackgroundAttention) { Prefs.textSelectBackgroundAttention = uiBackup.textSelectBackgroundAttention; }
	else { Prefs.reset('textSelectBackgroundAttention'); }
	if(!Prefs.resetNative && uiBackup.textSelectForeground) { Prefs.textSelectForeground = uiBackup.textSelectForeground; }
	else { Prefs.reset('textSelectForeground'); }
};

Modules.LOADMODULE = function() {
	Prefs.setDefaults({
		textHighlightBackground: '',
		textHighlightForeground: '',
		textSelectBackgroundAttention: '',
		textSelectForeground: '',
	}, 'ui', '');

	uiBackup = {
		textHighlightBackground: Prefs.textHighlightBackground,
		textHighlightForeground: Prefs.textHighlightForeground,
		textSelectBackgroundAttention: Prefs.textSelectBackgroundAttention,
		textSelectForeground: Prefs.textSelectForeground
	};

	Prefs.listen('highlightColor', changeHighlightColor);
	Prefs.listen('selectColor', changeSelectColor);
	Prefs.listen('keepSelectContrast', changeSelectColor);

	alwaysRunOnShutdown.push(resetColorPrefs);

	changeHighlightColor();
	changeSelectColor();
};

Modules.UNLOADMODULE = function() {
	Styles.unload('highlightColorStyleSheet');
	Styles.unload('otherHighlightColorStyleSheet');
	Styles.unload('selectColorStyleSheet');
	Styles.unload('otherSelectColorStyleSheet');

	Prefs.unlisten('highlightColor', changeHighlightColor);
	Prefs.unlisten('selectColor', changeSelectColor);
	Prefs.unlisten('keepSelectContrast', changeSelectColor);

	resetColorPrefs();
};
<|endoftext|>