1'use strict'; 2 3var elixir_front = angular.module('elixir_front', [ 4 'elixir_front.filters', 5 'elixir_front.services', 6 'elixir_front.directives', 7 'elixir_front.controllers', 8 'ui.bootstrap', 9 'ngCookies', 10 'ngRoute', 11 'ngSanitize', 12 'ngAnimate', 13 'ngResource', 14 'ui.router', 15 'angularBootstrapNavTree', 16 'treeControl', 17 '720kb.tooltips', 18 'yaru22.angular-timeago', 19 'angularDjangoRegistrationAuthApp', 20 'ngTagsInput', 21 'pasvaz.bindonce', 22 'ui.grid', 23 'ui.grid.resizeColumns', 24 'ui.grid.autoResize', 25 'ui.grid.saveState', 26 'ui.grid.moveColumns', 27 'chart.js', 28 'ngMeta' 29], function($rootScopeProvider) {}) 30 31// setting up router and states 32.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) { 33 // default state 34 $urlRouterProvider.when('', '/'); 35 // for any unmatched url, send to 404 36 $urlRouterProvider.otherwise('/404'); 37 $stateProvider 38 .state('404', { 39 url: "/404", 40 templateUrl: "partials/404.html", 41 data: { 42 meta: { 43 'title': 'Page not found' 44 }, 45 roles: [] 46 }, 47 resolve: {} 48 }) 49 .state('orcid_callback', { 50 url: "/orcid/callback/", 51 templateUrl: "partials/orcidCallback.html", 52 controller: 'OrcidCallbackController' 53 }) 54 .state('home', { 55 url: "/", 56 templateUrl: "components/home/home.html", 57 data: { 58 meta: { 59 'title': 'bio.tools · Bioinformatics Tools and Services Discovery Portal', 60 'description': 'A registry of bioinformatics software resources including biological databases, analytical tools and data services', 61 'titleSuffix': '', 62 'og:title': 'bio.tools · Bioinformatics Tools and Services Discovery Portal', 63 'og:description': 'A registry of bioinformatics software resources including biological databases, analytical tools and data services', 64 'og:image': 'https://bio.tools/img/ELIXIR_logo_white_background_small.png' 65 }, 66 roles: [] 67 }, 68 resolve: {} 69 }) 70 .state('about', { 71 url: "/about", 72 templateUrl: "components/about/about.html", 73 data: { 74 meta: { 75 'title': 'About', 76 'description': 'A registry of bioinformatics software resources including biological databases, analytical tools and data services' 77 }, 78 roles: [] 79 }, 80 resolve: {} 81 }) 82 .state('community', { 83 url: "/community", 84 templateUrl: "components/community/community.html", 85 data: { 86 meta: { 87 'title': 'Communities in bio.tools', 88 'description': 'bio.tools and communities' 89 }, 90 roles: [] 91 }, 92 resolve: {} 93 }) 94 .state('communities', { 95 url: "/communities", 96 templateUrl: "components/community/community.html", 97 data: { 98 meta: { 99 'title': 'Communities', 100 'description': 'bio.tools and communities' 101 }, 102 roles: [] 103 }, 104 resolve: {} 105 }) 106 .state('schema', { 107 url: "/schema", 108 templateUrl: "partials/schema.html", 109 data: { 110 roles: [] 111 }, 112 resolve: {} 113 }) 114 .state('stats', { 115 url: "/stats", 116 templateUrl: "components/stats/stats.html", 117 data: { 118 meta: { 119 'title': 'Stats', 120 'description': 'bio.tools content statistics.' 121 }, 122 roles: [] 123 }, 124 controller: "StatsController", 125 resolve: {} 126 }) 127 .state('search', { 128 url: "/t?page&q&biotoolsID&name&topic&function&operation&input&inputDataFormat&inputDataType&output&outputDataFormat&outputDataType&homepage&description&version&accessibility&toolType&collectionID&maturity&operatingSystem&language&cost&license&documentation&link&download&publication&credit&owner&sort&ord&domain&topicID&operationID&dataType&dataTypeID&dataFormat&dataFormatID&inputID&inputDataTypeID&inputDataFormatID&outputID&outputDataTypeID&outputDataFormatID&creditName&creditTypeRole&creditTypeEntity&creditOrcidID&publicationID&publicationType&publicationVersion&linkType&documentationType&downloadType&downloadVersion&otherID&otherIDType&otherIDVersion&otherIDValue&elixirPlatform&elixirNode&elixirCommunity&creditGridID&creditRORID&creditFundRefID", 129 templateUrl: "partials/search_results.html", 130 data: { 131 meta: { 132 'title': 'bio.tools · Bioinformatics Tools and Services Discovery Portal', 133 'description': 'A registry of bioinformatics software resources including biological databases, analytical tools and data services', 134 'titleSuffix': '', 135 'og:title': 'bio.tools · Bioinformatics Tools and Services Discovery Portal', 136 'og:description': 'A registry of bioinformatics software resources including biological databases, analytical tools and data services', 137 'og:image': 'https://bio.tools/img/ELIXIR_logo_white_background_small.png' 138 }, 139 roles: [] 140 }, 141 controller: "SearchResultController" 142 }) 143 .state('domains', { 144 url: "/domains", 145 templateUrl: "partials/subdomains/domains.html", 146 data: { 147 meta: { 148 'title': 'bio.tools domains', 149 'description': 'Explore and search all bio.tools domains and manage your own domains.', 150 'og:title': 'bio.tools domains', 151 'og:description': 'Explore and search all bio.tools domains and manage your own domains.' 152 }, 153 roles: [] 154 }, 155 controller: "DomainListController", 156 resolve: {} 157 }) 158 .state('register', { 159 url: "/register", 160 templateUrl: "partials/toolEdit.html", 161 data: { 162 meta: { 163 'title': 'Register new resource', 164 'description': 'Register a tool or database in bio.tools.', 165 'og:title': 'Register new resource', 166 'og:description': 'Register a tool or database in bio.tools.' 167 }, 168 roles: ['User'] 169 }, 170 controller: "ToolCreateController", 171 resolve: {} 172 }) 173 .state('edit-subdomain', { 174 url: "/edit-subdomain/:id", 175 templateUrl: "partials/subdomains/subdomainEdit.html", 176 data: { 177 meta: { 178 'title': 'Edit domain', 179 'og:title': 'Edit domain' 180 }, 181 roles: ['User'] 182 }, 183 controller: "SubdomainController", 184 resolve: {} 185 }) 186 .state('edit-domain', { 187 url: "/edit-domain/:id", 188 templateUrl: "partials/subdomains/subdomainEdit.html", 189 data: { 190 meta: { 191 'title': 'Edit domain', 192 'og:title': 'Edit domain' 193 }, 194 roles: ['User'] 195 }, 196 controller: "SubdomainController", 197 resolve: {} 198 }) 199 .state('admin-subdomain', { 200 url: "/subdomain", 201 templateUrl: "partials/subdomains/subdomainPage.html", 202 data: { 203 meta: { 204 'title': 'Domain', 205 'og:title': 'Domain' 206 }, 207 roles: ['User'] 208 }, 209 controller: "SubdomainAdminController", 210 resolve: {} 211 }) 212 .state('admin-domain', { 213 url: "/domain-manager", 214 templateUrl: "partials/subdomains/subdomainPage.html", 215 data: { 216 meta: { 217 'title': 'Domain', 218 'og:title': 'Domain' 219 }, 220 roles: ['User'] 221 }, 222 controller: "SubdomainAdminController", 223 resolve: {} 224 }) 225 .state('login', { 226 url: "/login", 227 templateUrl: "partials/login.html", 228 data: { 229 meta: { 230 'title': 'Log in', 231 'description': 'Log in to bio.tools.', 232 'og:title': 'Log in', 233 'og:description': 'Log in to bio.tools.' 234 }, 235 roles: [] 236 }, 237 controller: "LoginController", 238 resolve: {} 239 }) 240 .state('signup', { 241 url: "/signup", 242 templateUrl: "partials/signup.html", 243 data: { 244 meta: { 245 'title': 'Sign up', 246 'description': 'Sign up for bio.tools.', 247 'og:title': 'Sign up', 248 'og:description': 'Sign up for bio.tools.' 249 }, 250 roles: [] 251 }, 252 controller: "SignupController" 253 }) 254 .state('signup.success', { 255 url: "/success", 256 templateUrl: "partials/signupSuccess.html", 257 data: { 258 meta: { 259 'title': 'Sign up success' 260 }, 261 roles: [] 262 } 263 }) 264 .state('signup.verify-email', { 265 abstract: true, 266 url: "/verify-email",
267 template: "<ui-view/>", 268 data: { 269 meta: { 270 'title': 'Verify email' 271 }, 272 roles: [] 273 } 274 }) 275 .state('signup.verify-email.success', { 276 url: "/success", 277 templateUrl: "partials/signupVerifyEmailSuccess.html", 278 data: { 279 meta: { 280 'title': 'Verification success' 281 }, 282 roles: [] 283 } 284 }) 285 .state('signup.verify-email.key', { 286 url: "/:key", 287 templateUrl: "partials/signupVerifyEmailKey.html", 288 data: { 289 meta: { 290 'title': 'Verify email' 291 }, 292 roles: [] 293 }, 294 controller: "SignupVerifyEmailKeyController" 295 }) 296 .state('reset-password', { 297 url: "/reset-password", 298 templateUrl: "partials/resetPassword.html", 299 data: { 300 meta: { 301 'title': 'Reset password' 302 }, 303 roles: [] 304 }, 305 controller: "ResetPasswordController" 306 }) 307 .state('reset-password.confirm', { 308 url: "/confirm?uid&token", 309 templateUrl: "partials/resetPasswordConfirm.html", 310 data: { 311 meta: { 312 'title': 'Reset password' 313 }, 314 roles: [] 315 }, 316 controller: "ResetPasswordConfirmController" 317 }) 318 .state('reset-password.confirm.success', { 319 url: "/success", 320 templateUrl: "partials/resetPasswordConfirmSuccess.html", 321 data: { 322 meta: { 323 'title': 'Reset password success' 324 }, 325 roles: [] 326 } 327 }) 328 .state('profile', { 329 url: "/profile", 330 templateUrl: "components/profile/profile.html", 331 data: { 332 meta: { 333 'title': 'My profile' 334 }, 335 roles: [] 336 }, 337 controller: "ProfileController" 338 }) 339 .state('subdomain', { 340 url: "/t?:domain" 341 }) 342 .state('requests', { 343 url: "/requests", 344 templateUrl: "partials/requests.html", 345 data: { 346 roles: [] 347 }, 348 controller: "RequestsController" 349 }) 350 .state('workflows', { 351 url: "/workflows", 352 templateUrl: "partials/workflows/workflows.html", 353 data: { 354 roles: [] 355 }, 356 controller: "WorkflowController" 357 }) 358 .state('tool-explicit', { 359 url: "/tool/:id", 360 templateUrl: "components/toolPage/toolPage.html", 361 data: { 362 roles: [] 363 }, 364 controller: "ToolPageController", 365 resolve: {} 366 }) 367 .state('tool-explicit-short', { 368 url: "/t/:id", 369 templateUrl: "components/toolPage/toolPage.html", 370 data: { 371 roles: [] 372 }, 373 controller: "ToolPageController", 374 resolve: {} 375 }) 376 .state('tool', { 377 url: "/:id", 378 templateUrl: "components/toolPage/toolPage.html", 379 data: { 380 roles: [] 381 }, 382 controller: "ToolPageController", 383 resolve: {} 384 }) 385 .state('tool.edit', { 386 url: "/edit", 387 templateUrl: "partials/toolEdit.html", 388 data: { 389 roles: ['User'] 390 }, 391 controller: "ToolUpdateController", 392 resolve: {} 393 }) 394 // redirects 395 .state('governance', { 396 url: '/governance', 397 redirect: 'http://biotools.readthedocs.io/en/latest/governance.html', 398 external: true, 399 data: { 400 roles: [] 401 }, 402 resolve: {} 403 }) 404 .state('events', { 405 url: '/events', 406 redirect: 'https://docs.google.com/document/d/1K-6IG_7a-4amstSSOxYjjJ3uDUEg4-WGSSt9jvvO7Ik/edit#heading=h.twop51kvyu80', 407 external: true, 408 data: { 409 roles: [] 410 }, 411 resolve: {} 412 }) 413 .state('mail', { 414 url: '/mail', 415 redirect: 'http://elixirmail.cbs.dtu.dk/mailman/listinfo', 416 external: true, 417 data: { 418 roles: [] 419 }, 420 resolve: {} 421 }) 422 .state('faq', { 423 url: '/faq', 424 redirect: 'https://docs.google.com/document/d/1WdcY0WQJVdRHz0BAhII7s8gygxKRJaHZC4gOnAC1eqs/edit', 425 external: true, 426 data: { 427 roles: [] 428 }, 429 resolve: {} 430 }) 431 .state('apidoc', { 432 url: '/apidoc', 433 redirect: 'https://docs.google.com/document/d/1c1zridWLBNSrWcYyR2fnBfgjWfrUSrT7HJ6Ql6ZCkS4/edit#heading=h.9a2zbl9ka0z0', 434 external: true, 435 data: { 436 roles: [] 437 }, 438 resolve: {} 439 }) 440 .state('changelog', { 441 url: '/changelog', 442 redirect: 'https://docs.google.com/document/d/1C5KFu2t4OIAuUpx6dS7Djsa9y3zgicd8naOsAs9QPAY/edit#heading=h.eu5jkfa77et', 443 external: true, 444 data: { 445 roles: [] 446 }, 447 resolve: {} 448 }) 449 .state('helpdesk', { 450 url: '/helpdesk', 451 redirect: 'https://elixir-registry.atlassian.net/servicedesk/customer/portal/2/user/login?destination=portal%2F2', 452 external: true, 453 data: { 454 roles: [] 455 }, 456 resolve: {} 457 }) 458 .state('projects', { 459 url: '/projects', 460 redirect: 'https://docs.google.com/document/d/1QBIZHBzHxxmvSnOG3xK6t3SCeft5g4AH2rVICLRLmDw/edit#heading=h.a6i4b9p7xdw7', 461 external: true, 462 data: { 463 roles: [] 464 }, 465 resolve: {} 466 }) 467 .state('roadmap', { 468 url: '/roadmap', 469 redirect: 'https://docs.google.com/document/d/17iH72hmwVo205QVEkptv7GTMlqFojatIwMmxVOyuPy8/edit#heading=h.m1jhkecru8xq', 470 external: true, 471 data: { 472 roles: [] 473 }, 474 resolve: {} 475 }) 476}]) 477 478// enable HTML5 mode which removes the leading # from URL 479.config(function($locationProvider) { 480 $locationProvider.html5Mode(true).hashPrefix('!') 481}) 482 483// default settings for tooltips 484.config(function(tooltipsConfigProvider) { 485 tooltipsConfigProvider.options({ 486 speed: 'fast', 487 try: 0 488 }) 489}) 490 491// make redirect states work 492.run(function($rootScope, $window) { 493 $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { 494 if (toState.external) { 495 event.preventDefault(); 496 $window.open(toState.redirect, '_self'); 497 } 498 }); 499}) 500 501// make ngMeta run and configure 502.run(['ngMeta', function(ngMeta) { 503 ngMeta.init(); 504}]) 505.config(function(ngMetaProvider) { 506 507 ngMetaProvider.useTitleSuffix(true); 508 ngMetaProvider.setDefaultTitle('bio.tools'); 509 // ngMetaProvider.setDefaultTitleSuffix(' · bio.tools'); 510 // ngMetaProvider.setDefaultTag('author', 'John Smith'); 511}) 512 513// user authentication 514.run(['$rootScope', '$state', '$timeout', 'User', 'djangoAuth', '$http', function($rootScope, $state, $timeout, User, djangoAuth, $http) { 515 User.authenticated = false;
516 517 // what happens on User login 518 var logInCallback = function(){ 519 User.authenticated = true; 520 // get user profile 521 djangoAuth.profile().then(function (response) { 522 User.current = response; 523 }); 524 }; 525 526 // what happens on authentication errors 527 var wrongAuthCallback = function() { 528 delete $http.defaults.headers.common.Authorization; 529 delete localStorage.token; 530 User.authenticated = false; 531 User.current = {}; 532 }; 533 534 // what happens on User logout 535 var logOutCallback = function() { 536 wrongAuthCallback(); 537 $state.go('home'); 538 } 539 540 // get initial authentication status 541 var authenticationPromise = djangoAuth.authenticationStatus(true).then(logInCallback, wrongAuthCallback); 542 543 // set relevant callbacks for log in/out events 544 $rootScope.$on('djangoAuth.logged_in', logInCallback); 545 $rootScope.$on('djangoAuth.logged_out', logOutCallback); 546 547 // checking authentication on state change 548 $rootScope.$on('$stateChangeStart', function(event, toState, toStateParams, fromState, fromStateParams) { 549 // save current state transitions 550 if (toState.name != 'login') { 551 $rootScope.toState = toState; 552 $rootScope.toStateParams = toStateParams; 553 } 554 $rootScope.fromState = fromState; 555 556 // pausing the state change, so that the user authentication has a chance to proceed before state change 557 if (typeof toState.resolve == 'undefined') { toState.resolve = {} }; 558 toState.resolve.pauseStateChange = ['$q', function($q) { 559 var defer = $q.defer(); 560 authenticationPromise.then(function () { 561 if (!User.authenticated && _.includes(toState.data.roles, 'User')) { 562 event.preventDefault(); 563 // save destination states to return to them on proper login 564 $rootScope.returnToState = $rootScope.toState; 565 $rootScope.returnToStateParams = $rootScope.toStateParams; 566 // direct unauthenticated user to login 567 $state.go('login'); 568 } 569 defer.resolve(); 570 }); 571 return defer.promise; 572 } 573 ] 574 }); 575 // on error go to 404 576 $rootScope.$on('$stateChangeError', function(event) { 577 $state.go('404'); 578 }); 579}]) 580 581// filling in title and description meta attributes in header 582.run(["$rootScope", "$timeout", "$state", function($rootScope, $timeout, $state) { 583 function getTextValue(text) { 584 return angular.isFunction(text) ? text() : text; 585 } 586 587 $rootScope.$on("$stateChangeSuccess", function() { 588 var title = getTextValue($state.$current.locals.globals.$title); 589 var description = getTextValue($state.$current.locals.globals.$description); 590 if(title) { 591 $timeout(function() { 592 $rootScope.$title = title; 593 }); 594 } 595 if(description) { 596 $timeout(function() { 597 $rootScope.$description = description; 598 }); 599 } 600 }); 601}]) 602 603// Configuration file for environment-specific settings 604angular.module('elixir_front').constant('AppConfig', { 605 ORCID_CLIENT_ID: 'APP-YNOM1N01SXS79JZ9', 606 ORCID_REDIRECT_URI: 'https://bio.tools/orcid/callback/', 607 ORCID_BASE_URL: 'https://orcid.org' 608}); 609 610'use strict'; 611 612/* Controllers */ 613 614angular 615 .module('elixir_front.controllers', []) 616 .controller('ToolGridController', [ 617 '$scope', 618 '$timeout', 619 'ToolList', 620 'ToolTableDataSource', 621 function ($scope, $timeout, ToolList, ToolTableDataSource) { 622 $scope.ToolList = ToolList; 623 $scope.savedStateExists = function () { 624 return localStorage.getItem('gridState') ? true : false; 625 }; 626 $scope.saveState = function () { 627 var state = $scope.gridApi.saveState.save(); 628 localStorage.setItem('gridState', JSON.stringify(state)); 629 }; 630 $scope.restoreState = function () { 631 $timeout(function () { 632 var state = localStorage.getItem('gridState'); 633 if (state) $scope.gridApi.saveState.restore($scope, JSON.parse(state)); 634 }); 635 }; 636 $scope.gridOptions = { 637 enableSorting: false, 638 enableColumnMenus: false, 639 enableColumnResizing: true, 640 enableVerticalScrollbar: 1, 641 enableHorizontalScrollbar: true, 642 columnVirtualizationThreshold: ToolTableDataSource.columnsDescription([]).length, 643 data: ToolList.list, 644 columnDefs: ToolTableDataSource.columnsDescription(
645 $scope.savedStateExists() == true 646 ? [] 647 : [ 648 'Name', 649 'Description', 650 'Homepage', 651 'Credits', 652 'Operation', 653 'Topic', 654 'Input', 655 'Output', 656 ] 657 ), 658 rowHeight: 135, 659 enableGridMenu: true, 660 onRegisterApi: function (gridApi) { 661 $scope.gridApi = gridApi; 662 $scope.restoreState(); 663 // Setup events so we're notified when grid state changes. 664 $scope.gridApi.core.on.columnVisibilityChanged($scope, function (column) { 665 $scope.saveState(); 666 }); 667 $scope.gridApi.colResizable.on.columnSizeChanged($scope, function (column) { 668 $scope.saveState(); 669 }); 670 $scope.gridApi.colMovable.on.columnPositionChanged($scope, function (column) { 671 $scope.saveState(); 672 }); 673 }, 674 }; 675 $scope.tableHeight = function () { 676 return { 677 height: 678 Math.min($scope.ToolList.count, 4) * $scope.gridOptions.rowHeight + 679 33 + 680 'px', 681 }; 682 }; 683 }, 684 ]) 685 .controller('ToolGridCellController', [ 686 '$scope', 687 function ($scope) { 688 $scope.init = function (columnName, rowIndex) { 689 $scope.columnName = columnName; 690 $scope.rowIndex = rowIndex; 691 }; 692 $scope.RowIdentifier = function () { 693 return $scope.columnName + $scope.rowIndex; 694 }; 695 $scope.RowName = function () { 696 return $scope.columnName; 697 }; 698 $scope.RowHeight = function () { 699 var element = document.getElementById($scope.RowIdentifier()); 700 return element ? element.offsetHeight : 0; 701 }; 702 $scope.DefaultRowHeight = function () { 703 return 135; 704 }; 705 $scope.CellWidth = function () { 706 return document.getElementById($scope.RowIdentifier()).offsetWidth; 707 }; 708 $scope.CellStyle = function () { 709 return { 710 width: CellWidthDescription(), 711 'background-color': CellColorDescription(), 712 }; 713 }; 714 function CellWidthDescription() { 715 return $scope.CellWidth() + 2 + 'px'; 716 } 717 function CellColorDescription() { 718 return $scope.rowIndex % 2 != 0 ? '#f8f8f8' : 'white'; 719 } 720 }, 721 ]) 722 .controller('SearchResultController', [ 723 '$scope', 724 '$state', 725 'ToolList', 726 'ToolTableDataSource', 727 'DisplayModeSelector', 728 'Domain', 729 function ($scope, $state, ToolList, ToolTableDataSource, DisplayModeSelector, Domain) { 730 function quoteQueryStringValue(v) { 731 return '"' + v + '"'; 732 } 733 734 function stripEdam(t) { 735 return t.replace('http://edamontology.org/', ''); 736 } 737 738 $scope.topicNameClicked = function (topic) { 739 //$state.go('search', {'topic': topic.term}, {reload: true}); 740 $state.transitionTo( 741 'search', 742 { topicID: quoteQueryStringValue(stripEdam(topic.uri)) }, 743 { 744 reload: true, 745 inherit: false, 746 notify: true, 747 } 748 ); 749 }; 750 751 $scope.operationNameClicked = function (operation) { 752 //$state.go('search', {'topic': topic.term}, {reload: true}); 753 $state.transitionTo( 754 'search', 755 { operationID: quoteQueryStringValue(stripEdam(operation.uri)) }, 756 { 757 reload: true, 758 inherit: false, 759 notify: true, 760 } 761 ); 762 }; 763 $scope.collectionNameClicked = function (collection) { 764 //$state.go('search', {'topic': topic.term}, {reload: true}); 765 $state.transitionTo( 766 'search', 767 { collectionID: quoteQueryStringValue(collection) }, 768 { 769 reload: true, 770 inherit: false, 771 notify: true, 772 } 773 ); 774 }; 775 776 $scope.shouldLicenseBeALink = function (license) { 777 return !_.includes(['Freeware', 'Proprietary', 'Other', 'Not licensed'], license); 778 }; 779 780 $scope.getFlatOperations = function (functions) { 781 var operations = {}; 782 for (var i = 0; i < functions.length; i++) { 783 for (var j = 0; j < functions[i].operation.length; j++) { 784 var o = functions[i].operation[j]; 785 operations[o.term] = o; 786 } 787 } 788 789 var arr = Object.keys(operations); 790 var r = []; 791 for (var i = 0; i < arr.length; i++) { 792 r.push(operations[arr[i]]); 793 } 794 795 return r; 796 }; 797 798 $scope.Domain = Domain; 799 $scope.ToolTableDataSource = ToolTableDataSource; 800 $scope.ToolList = ToolList; 801 $scope.DisplayModeSelector = DisplayModeSelector; 802 // Get data initially 803 ToolList.refresh(); 804 }, 805 ]) 806 .controller('AlertsController', [ 807 '$scope', 808 'Alert', 809 'EnvironmentChecker', 810 function ($scope, Alert, EnvironmentChecker) { 811 $scope.Alert = Alert; 812 $scope.dev_alert = false;
813 814 // check if the current setup id dev or prod. 815 EnvironmentChecker.getEnvironment().then(function (data) { 816 if (data == 'Development') { 817 $scope.dev_alert = true; 818 } 819 }); 820 821 // check if welcome message was disabled for this user 822 if ('welcome_message' in localStorage) { 823 $scope.welcome_message = localStorage.welcome_message === 'true'; 824 } else { 825 $scope.welcome_message = true; 826 } 827 828 $scope.closeWelcomeMessageButtonClick = function () { 829 $scope.welcome_message = false; 830 localStorage.welcome_message = false; 831 }; 832 }, 833 ]) 834 .controller('ToolEditController', [ 835 '$scope', 836 '$controller', 837 '$state', 838 '$stateParams', 839 'Ontology', 840 'Attribute', 841 'CheckUserEditingRights', 842 'User', 843 '$timeout', 844 'UsedTerms', 845 '$q', 846 '$uibModal', 847 function ( 848 $scope, 849 $controller, 850 $state, 851 $stateParams, 852 Ontology, 853 Attribute, 854 CheckUserEditingRights, 855 User, 856 $timeout, 857 UsedTerms, 858 $q, 859 $uibModal 860 ) { 861 // reference the service 862 $scope.Attribute = Attribute; 863 $scope.CheckUserEditingRights = CheckUserEditingRights; 864 $scope.$state = $state; 865 $scope.form = {}; 866 $scope.canEditTool = false;
867 $scope.canEditToolPermissions = false; 868 $scope.User = User; 869 $scope.orderby = 'text'; 870 871 $scope.registeringInProgress = false; 872 873 // for storing validation and saving progess 874 ($scope.validationProgress = {}), 875 ($scope.savingProgress = {}), 876 ($scope.deletingProgress = {}); 877 878 $scope.initializePermissions = function () { 879 $scope.canEditTool = false; 880 $scope.canEditToolPermissions = false; 881 // Owner can edit anything. 882 if (!_.isEmpty($scope.software)) { 883 if ($scope.software.owner == $scope.User.getUsername()) { 884 $scope.canEditTool = true; 885 $scope.canEditToolPermissions = true; 886 } else if ($scope.software.editPermission != undefined) { 887 $scope.canEditTool = $scope.CheckUserEditingRights.canEdit($scope.software); 888 $scope.canEditToolPermissions = false; 889 delete $scope.software.editPermission; 890 } 891 } 892 }; 893 894 // handle sending the resource to either validation or saving endpoints 895 $scope.sendResource = function (service, progress, isRemoval, action) { 896 progress.success = false; 897 progress.error = false; 898 progress.inProgress = true; 899 $scope.registrationErrorPayload = null; 900 901 service( 902 $stateParams, 903 $scope.software, 904 function (response) { 905 // handle success 906 progress.inProgress = false; 907 progress.success = true; 908 if (isRemoval) { 909 alert('Resource removed succesfully.'); 910 $state.go('search'); 911 } 912 913 if (action == 'create') { 914 $state.go('tool.edit', { id: response.biotoolsID }); 915 } 916 }, 917 function (response) { 918 // handle error 919 progress.error = true; 920 progress.inProgress = false; 921 $scope.registrationErrorPayload = response.data; 922 } 923 ); 924 }; 925 926 // modals 927 $scope.openModal = function (edam, type, suggestions) { 928 var ontoMap = { 929 data: $scope.EDAM_data, 930 format: $scope.EDAM_format, 931 operation: $scope.EDAM_operation, 932 }; 933 var onto = ontoMap[type] || $scope.EDAM_data; 934 935 var modalInstance = $uibModal.open({ 936 templateUrl: 'partials/tool_edit/toolEditEdamModal.html', 937 controllerAs: 'vm', 938 controller: [ 939 '$uibModalInstance', 940 'edam', 941 'onto', 942 'type', 943 'suggestions', 944 EdamModalCtrl, 945 ], 946 resolve: { 947 edam: function () { 948 return edam; 949 }, 950 onto: function () { 951 return onto; 952 }, 953 type: function () { 954 return type; 955 }, 956 suggestions: function () { 957 return suggestions; 958 }, 959 }, 960 }); 961 962 modalInstance.result.then( 963 function (updatedEdam) { 964 angular.copy(updatedEdam, edam); 965 }, 966 function () {} 967 ); 968 969 return modalInstance.result; 970 }; 971 972 $scope.findObjectByUri = function (obj, targetUri) { 973 function search(current) { 974 if (current.data && current.data.uri === targetUri) { 975 return current; 976 } 977 if (current.children) { 978 for (var i = 0; i < current.children.length; i++) { 979 var result = search(current.children[i]); 980 if (result) return result; 981 } 982 } 983 return null; 984 } 985 986 for (var i = 0; i < obj.length; i++) { 987 var result = search(obj[i]); 988 if (result) return result; 989 } 990 return null; 991 }; 992 993 $scope.flattenObject = function (obj) { 994 var result = []; 995 var stack = [obj]; 996 997 while (stack.length > 0) { 998 var current = stack.pop(); 999 for (var key in current) { 1000 if (current.hasOwnProperty(key)) {
1001 var value = current[key]; 1002 if (typeof value === 'object' && value !== null) { 1003 stack.push(value); 1004 } else { 1005 result.push(value); 1006 } 1007 } 1008 } 1009 } 1010 return result; 1011 }; 1012 1013 $scope.recommend_terms = function (edam, type) { 1014 var edamArray = $scope.flattenObject(edam).filter(function (word) { 1015 return ( 1016 typeof word === 'string' && word.indexOf('http://edamontology.org/') !== -1 1017 ); 1018 }); 1019 1020 if (type === 'operation') return null; // No recommendations for operations 1021 1022 var ontoMap = { 1023 format: $scope.EDAM_data, 1024 input: $scope.EDAM_operation, 1025 output: $scope.EDAM_operation, 1026 }; 1027 var onto = ontoMap[type] || $scope.EDAM_operation; 1028 1029 var suggestions = []; 1030 1031 for (var i = 0; i < edamArray.length; i++) { 1032 var element = edamArray[i]; 1033 var edamObj = $scope.findObjectByUri(onto, element); 1034 if (!edamObj) continue; 1035 1036 var appendSuggestions = getSuggestions(type, edamObj, edam); 1037 if (appendSuggestions.length) { 1038 suggestions = suggestions.concat(appendSuggestions); 1039 } 1040 } 1041 1042 return mapSuggestions(suggestions, type); 1043 }; 1044 1045 function getSuggestions(type, edamObj, edam) { 1046 switch (type) { 1047 case 'input': 1048 return getInputSuggestions(edamObj, edam); 1049 case 'output': 1050 return getOutputSuggestions(edamObj, edam); 1051 default: 1052 return []; 1053 } 1054 } 1055 1056 function getInputSuggestions(edamObj, edam) { 1057 if (!edamObj.has_input) return []; 1058 if (!edam.hasOwnProperty('input')) return edamObj.has_input; 1059 return edamObj.has_input.filter(function (input) { 1060 return !edam.input.some(function (existingInput) { 1061 return existingInput.data.uri === input; 1062 }); 1063 }); 1064 } 1065 1066 function getOutputSuggestions(edamObj, edam) { 1067 if (!edamObj.has_output) return []; 1068 if (!edam.hasOwnProperty('output')) return edamObj.has_output; 1069 return edamObj.has_output.filter(function (output) { 1070 return !edam.output.some(function (existingOutput) { 1071 return existingOutput.data.uri === output; 1072 }); 1073 }); 1074 } 1075 1076 function mapSuggestions(suggestions, type) { 1077 var ontoMap = { 1078 format: $scope.EDAM_format, 1079 output: $scope.EDAM_data, 1080 input: $scope.EDAM_data, 1081 }; 1082 var onto = ontoMap[type]; 1083 1084 return suggestions.map(function (element) { 1085 var edamObj = $scope.findObjectByUri(onto, element); 1086 return { 1087 uri: element, 1088 term: edamObj ? edamObj.text : '', 1089 }; 1090 }); 1091 } 1092 1093 $scope.addWithModal = function (type, edam) { 1094 var pickerTypeMap = { 1095 input: 'data', 1096 output: 'data', 1097 format: 'format', 1098 function: 'operation', 1099 operation: 'operation', 1100 }; 1101 var pickertype = pickerTypeMap[type] || ''; 1102 1103 var suggestions = $scope.recommend_terms(edam, type); 1104 1105 var modalPromise = $scope.openModal({}, pickertype, suggestions); 1106 1107 modalPromise.then( 1108 function (newEdam) { 1109 handleModalResult(type, edam, newEdam); 1110 }, 1111 function () {} 1112 ); 1113 }; 1114 1115 function handleModalResult(type, edam, newEdam) { 1116 switch (type) { 1117 case 'format': 1118 $scope.addButtonClick('format', edam, true, true); 1119 edam.format[edam.format.length - 1] = newEdam; 1120 break; 1121 case 'output': 1122 $scope.addButtonClick('output', edam, true, true); 1123 edam.output[edam.output.length - 1] = newEdam; 1124 break; 1125 case 'input': 1126 $scope.addButtonClick('input', edam, true, true); 1127 edam.input[edam.input.length - 1] = newEdam; 1128 break; 1129 case 'function': 1130 $scope.addButtonClick('function', edam, true, true); 1131 edam.function[edam.function.length - 1].operation = [newEdam]; 1132 break; 1133 case 'operation': 1134 $scope.addButtonClick('function', edam, true, true); 1135 edam.push(newEdam); 1136 break; 1137 } 1138 } 1139 1140 // reset success flags when changes are made 1141 $scope.$watch( 1142 'software', 1143 function (newVal, oldVal) { 1144 if (newVal !== oldVal) {
1145 $scope.savingProgress.success = false; 1146 $scope.validationProgress.success = false; 1147 } 1148 }, 1149 true 1150 ); 1151 1152 // used terms (biotoolsID) for searching in relations 1153 function getBiotoolsIDs() { 1154 var d = $q.defer(); 1155 var params = { 1156 usedTermName: 'biotoolsID', 1157 }; 1158 UsedTerms.get(params, function (response) { 1159 d.resolve(response.data); 1160 }); 1161 return d.promise; 1162 } 1163 1164 $scope.loadBiotoolsIDs = function (query) { 1165 return getBiotoolsIDs().then(function (list) { 1166 return list 1167 .filter(function (str) { 1168 return str.toLowerCase().includes(query.toLowerCase()); 1169 }) 1170 .slice(0, 50) 1171 .sort(); 1172 }); 1173 }; 1174 1175 // used terms (collectionID) for searching in collections 1176 function getCollectionIDs() { 1177 var d = $q.defer(); 1178 var params = { 1179 usedTermName: 'collectionID', 1180 }; 1181 UsedTerms.get(params, function (response) { 1182 d.resolve(response.data); 1183 }); 1184 return d.promise; 1185 } 1186 1187 $scope.loadCollectionIDs = function (query) { 1188 return getCollectionIDs().then(function (list) { 1189 return list 1190 .filter(function (str) { 1191 return str.toLowerCase().includes(query.toLowerCase()); 1192 }) 1193 .slice(0, 10) 1194 .sort(); 1195 }); 1196 }; 1197 1198 // used terms (credit names) for searching in credits 1199 function getCreditNames() { 1200 var d = $q.defer(); 1201 var params = { 1202 usedTermName: 'credit', 1203 }; 1204 UsedTerms.get(params, function (response) { 1205 d.resolve(response.data); 1206 }); 1207 return d.promise; 1208 } 1209 1210 $scope.loadCreditNames = function (query) { 1211 return getCreditNames().then(function (list) { 1212 return list 1213 .filter(function (str) { 1214 return str.toLowerCase().includes(query.toLowerCase()); 1215 }) 1216 .slice(0, 10) 1217 .sort(); 1218 }); 1219 }; 1220 1221 // add attribute or list entry 1222 $scope.addButtonClick = function (_what, _where, _isList, _isObject) { 1223 if (_isList) { 1224 // if array does not exist create it 1225 if (typeof _where[_what] == 'undefined') { 1226 _where[_what] = []; 1227 } 1228 // add either an object or string to array 1229 _where[_what].push(_isObject ? {} : ''); 1230 } else { 1231 // if object does not exist create it 1232 if (typeof _where[_what] == 'undefined') { 1233 _where[_what] = _isObject ? {} : ''; 1234 } 1235 } 1236 }; 1237 1238 $scope.addObjectClick = function (_what, _parent, _where) { 1239 _parent[_where] = {}; 1240 _parent[_where][_what] = {}; 1241 }; 1242 1243 $scope.removeObjectClick = function (_what, _parent, _where) { 1244 var message = _parent[_what][_index].term 1245 ? `Are you sure you want to remove ${_parent[_what][_index].term}?` 1246 : 'Are you sure you want to remove this element?'; 1247 1248 if (confirm(message)) { 1249 if (_parent[_where][_what]) { 1250 delete _parent[_where][_what]; 1251 } 1252 if (Object.keys(_parent[_where]).length === 0) { 1253 delete _parent[_where]; 1254 } 1255 } 1256 }; 1257 1258 // remove attribute or list entry 1259 $scope.removeButtonClick = function (_what, _parent, _index, _event) { 1260 var message = _parent[_what][_index].term 1261 ? `Are you sure you want to remove ${_parent[_what][_index].term}?` 1262 : 'Are you sure you want to remove this element?'; 1263 1264 if (_parent[_what][_index] ? confirm(message) : true) { 1265 // remove jstree if exists 1266 if (_event) { 1267 $(_event.target).closest('div').find('.jstree').jstree('destroy').remove(); 1268 } 1269 1270 _parent[_what].splice(_index, 1); 1271 1272 // if last instance in array delete entire attribute from the parent object 1273 if (_parent[_what].length == 0) { 1274 delete _parent[_what]; 1275 1276 // If we're removing an operation and it's the last one, remove the entire function 1277 if (_what === 'operation') { 1278 var functionIndex = $scope.software.function.indexOf(_parent); 1279 if (functionIndex > -1) { 1280 $scope.software.function.splice(functionIndex, 1); 1281 } 1282 } 1283 } 1284 } 1285 }; 1286 1287 $scope.moveItem = function (key, index, direction) { 1288 var array = $scope.software[key]; 1289 var newIndex = index + direction; 1290 if (newIndex < 0 || newIndex >= array.length) return; 1291 // Swap the elements 1292 var temp = array[newIndex]; 1293 array[newIndex] = array[index]; 1294 array[index] = temp; 1295 }; 1296 1297 // create connections between entries 1298 $scope.errorConnections = { 1299 name: ['id'], 1300 }; 1301 1302 // reset error on change 1303 $scope.resetError = function (_what, _parent, _index) { 1304 // select appropriate error type handling 1305 if (_index != undefined && _index >= 0) { 1306 $scope.resetListError(_what, _parent, _index); 1307 } else {
1308 // remove all connected error warnings 1309 var whatConnections = $scope.errorConnections[_what]; 1310 if (whatConnections) { 1311 whatConnections.forEach(function (connection) { 1312 if (_parent != undefined && _parent[connection]) { 1313 delete _parent[connection]; 1314 } 1315 }); 1316 } 1317 // remove the actual error 1318 if (_parent != undefined && _parent[_what]) { 1319 delete _parent[_what]; 1320 } 1321 } 1322 }; 1323 1324 $scope.resetListError = function (_what, _parent, _index) { 1325 if (_parent[_what] && _parent[_what][_index]) { 1326 _parent[_what][_index] = {}; 1327 } 1328 }; 1329 1330 $scope.isString = function (value) { 1331 if (typeof value === 'string') { 1332 return true; 1333 } else { 1334 return false; 1335 } 1336 }; 1337 1338 // get ontology objects for the various widgets 1339 Ontology.get({ name: 'EDAM_topic' }, function (response) { 1340 $scope.EDAM_topic = response.data.children; 1341 }); 1342 Ontology.get({ name: 'EDAM_data' }, function (response) { 1343 $scope.EDAM_data = response.data.children; 1344 }); 1345 Ontology.get({ name: 'EDAM_format' }, function (response) { 1346 $scope.EDAM_format = response.data.children; 1347 }); 1348 Ontology.get({ name: 'EDAM_operation' }, function (response) { 1349 $scope.EDAM_operation = response.data.children; 1350 }); 1351 1352 // populate the JSON edit textarea 1353 $scope.$watch( 1354 'software', 1355 function (newVal, oldVal) { 1356 $scope.jsonEdit.model = angular.toJson($scope.software, 2); 1357 // Check permissions 1358 if ($scope.canEditTool == false) { 1359 $scope.initializePermissions(); 1360 } 1361 }, 1362 true 1363 ); 1364 1365 // parse the edited JSON for errors 1366 $scope.jsonEdit = {}; 1367 var initializing = true; 1368 $scope.$watch('jsonEdit.model', function (newVal, oldVal) { 1369 if (!initializing) { 1370 try { 1371 $scope.software = angular.fromJson($scope.jsonEdit.model); 1372 $scope.jsonEdit.error = null; 1373 } catch (exp) { 1374 $scope.jsonEdit.error = exp.message; 1375 } 1376 } else { 1377 initializing = false; 1378 } 1379 }); 1380 1381 // download JSON from the editor 1382 $scope.downloadTool = function (_pretty) { 1383 var hiddenElement = document.createElement('a'); 1384 if (_pretty) { 1385 hiddenElement.href = 1386 'data:attachment/json,' + encodeURI(angular.toJson($scope.software, 2)); 1387 } else { 1388 hiddenElement.href = 1389 'data:attachment/json,' + encodeURI(angular.toJson($scope.software)); 1390 } 1391 hiddenElement.target = '_blank'; 1392 hiddenElement.download = 1393 ($scope.software.name ? $scope.software.name : 'resource') + '.json'; 1394 document.body.appendChild(hiddenElement); 1395 hiddenElement.click(); 1396 }; 1397 1398 // settings for the EDAM tree widget 1399 $scope.treeOptions = { 1400 nodeChildren: 'children', 1401 dirSelectable: true, 1402 }; 1403 1404 // set term and uri when picked from EDAM widget 1405 $scope.ontologyOnSelect = function (_object, _index, _node) { 1406 // if _object[_index] is not an object, make it one 1407 if (!(_object[_index] === Object(_object[_index]))) { 1408 _object[_index] = {}; 1409 } 1410 _object[_index].term = _node.text; 1411 _object[_index].uri = _node.data.uri; 1412 }; 1413 1414 // fetch data from Europe PMC and update the publication object 1415 function fetchEuropePMCData(pub, id_type, identifier) { 1416 var params = []; 1417 1418 if (id_type == 'doi') params.push('query=DOI:' + identifier); 1419 else if (id_type == 'pmid') params.push('query=EXT_ID:' + identifier); 1420 else if (id_type == 'pmcid') params.push('query=PMC:' + identifier); 1421 else return; 1422 1423 var url = 1424 'https://www.ebi.ac.uk/europepmc/webservices/rest/search?' + 1425 params.join('&') + 1426 '&format=json'; 1427 fetch(url) 1428 .then(function (response) { 1429 return response.json(); 1430 }) 1431 .then(function (data) { 1432 if ( 1433 data.resultList && 1434 data.resultList.result && 1435 data.resultList.result.length > 0 1436 ) { 1437 var rec = data.resultList.result[0]; 1438 if (rec.doi) pub.doi = rec.doi; 1439 if (rec.pmid) pub.pmid = rec.pmid; 1440 if (rec.pmcid) pub.pmcid = rec.pmcid; 1441 $scope.$apply(); 1442 } 1443 }) 1444 .catch(function (err) { 1445 /* handle error */ 1446 }); 1447 } 1448 1449 $scope.onPublicationIdChange = function (idType, value, index) { 1450 if (value && value.trim() !== '') { 1451 fetchEuropePMCData($scope.software.publication[index], idType, value.trim()); 1452 } 1453 }; 1454 1455 $scope.latestOptions = [ 1456 { value: 1, text: 'Yes' }, 1457 { value: 0, text: 'No' }, 1458 ]; 1459 1460 $scope.contactRoleOptions = [ 1461 { value: 'General', text: 'General' }, 1462 { value: 'Developer', text: 'Developer' }, 1463 { value: 'Technical', text: 'Technical' }, 1464 { value: 'Scientific', text: 'Scientific' }, 1465 { value: 'Helpdesk', text: 'Helpdesk' }, 1466 { value: 'Maintainer', text: 'Maintainer' }, 1467 ]; 1468 1469 $scope.licenseOptions = [ 1470 { value: 'Freeware', text: 'Freeware' }, 1471 { value: 'Proprietary', text: 'Proprietary' }, 1472 { value: 'Other', text: 'Other' }, 1473 { value: 'Not licensed', text: 'Not licensed' }, 1474 { value: '0BSD', text: '0BSD' }, 1475 { value: 'AAL', text: 'AAL' }, 1476 { value: 'Abstyles', text: 'Abstyles' }, 1477 { value: 'Adobe-2006', text: 'Adobe-2006' }, 1478 { value: 'Adobe-Glyph', text: 'Adobe-Glyph' }, 1479 { value: 'ADSL', text: 'ADSL' }, 1480 { value: 'AFL-1.1', text: 'AFL-1.1' }, 1481 { value: 'AFL-1.2', text: 'AFL-1.2' }, 1482 { value: 'AFL-2.0', text: 'AFL-2.0' }, 1483 { value: 'AFL-2.1', text: 'AFL-2.1' }, 1484 { value: 'AFL-3.0', text: 'AFL-3.0' }, 1485 { value: 'Afmparse', text: 'Afmparse' }, 1486 { value: 'AGPL-1.0', text: 'AGPL-1.0' }, 1487 { value: 'AGPL-1.0-or-later', text: 'AGPL-1.0-or-later' }, 1488 { value: 'AGPL-3.0', text: 'AGPL-3.0' }, 1489 { value: 'AGPL-3.0-or-later', text: 'AGPL-3.0-or-later' }, 1490 { value: 'Aladdin', text: 'Aladdin' }, 1491 { value: 'AMDPLPA', text: 'AMDPLPA' }, 1492 { value: 'AML', text: 'AML' }, 1493 { value: 'AMPAS', text: 'AMPAS' }, 1494 { value: 'ANTLR-PD', text: 'ANTLR-PD' }, 1495 { value: 'ANTLR-PD-fallback', text: 'ANTLR-PD-fallback' }, 1496 { value: 'Apache-1.0', text: 'Apache-1.0' }, 1497 { value: 'Apache-1.1', text: 'Apache-1.1' }, 1498 { value: 'Apache-2.0', text: 'Apache-2.0' }, 1499 { value: 'APAFML', text: 'APAFML' }, 1500 { value: 'APL-1.0', text: 'APL-1.0' }, 1501 { value: 'APSL-1.0', text: 'APSL-1.0' }, 1502 { value: 'APSL-1.1', text: 'APSL-1.1' }, 1503 { value: 'APSL-1.2', text: 'APSL-1.2' }, 1504 { value: 'APSL-2.0', text: 'APSL-2.0' }, 1505 { value: 'Artistic-1.0', text: 'Artistic-1.0' }, 1506 { value: 'Artistic-1.0-cl8', text: 'Artistic-1.0-cl8' }, 1507 { value: 'Artistic-1.0-Perl', text: 'Artistic-1.0-Perl' }, 1508 { value: 'Artistic-2.0', text: 'Artistic-2.0' }, 1509 { value: 'Bahyph', text: 'Bahyph' }, 1510 { value: 'Barr', text: 'Barr' }, 1511 { value: 'Beerware', text: 'Beerware' }, 1512 { value: 'BitTorrent-1.0', text: 'BitTorrent-1.0' }, 1513 { value: 'BitTorrent-1.1', text: 'BitTorrent-1.1' }, 1514 { value: 'blessing', text: 'blessing' }, 1515 { value: 'BlueOak-1.0.0', text: 'BlueOak-1.0.0' }, 1516 { value: 'Borceux', text: 'Borceux' }, 1517 { value: 'BSD-1-Clause', text: 'BSD-1-Clause' }, 1518 { value: 'BSD-2-Clause', text: 'BSD-2-Clause' }, 1519 { value: 'BSD-2-Clause-FreeBSD', text: 'BSD-2-Clause-FreeBSD' }, 1520 { value: 'BSD-2-Clause-NetBSD', text: 'BSD-2-Clause-NetBSD' }, 1521 { value: 'BSD-2-Clause-Patent', text: 'BSD-2-Clause-Patent' }, 1522 { value: 'BSD-2-Clause-Views', text: 'BSD-2-Clause-Views' }, 1523 { value: 'BSD-3-Clause', text: 'BSD-3-Clause' }, 1524 { value: 'BSD-3-Clause-Attribution', text: 'BSD-3-Clause-Attribution' }, 1525 { value: 'BSD-3-Clause-Clear', text: 'BSD-3-Clause-Clear' }, 1526 { value: 'BSD-3-Clause-LBNL', text: 'BSD-3-Clause-LBNL' }, 1527 { value: 'BSD-3-Clause-Modification', text: 'BSD-3-Clause-Modification' }, 1528 { value: 'BSD-3-Clause-No-Military-License', text: 'BSD-3-Clause-No-Military-License' }, 1529 { value: 'BSD-3-Clause-No-Nuclear-License', text: 'BSD-3-Clause-No-Nuclear-License' }, 1530 { value: 'BSD-3-Clause-No-Nuclear-License-2014', text: 'BSD-3-Clause-No-Nuclear-License-2014' }, 1531 { value: 'BSD-3-Clause-No-Nuclear-Warranty', text: 'BSD-3-Clause-No-Nuclear-Warranty' }, 1532 { value: 'BSD-3-Clause-Open-MPI', text: 'BSD-3-Clause-Open-MPI' }, 1533 { value: 'BSD-4-Clause', text: 'BSD-4-Clause' }, 1534 { value: 'BSD-4-Clause-Shortened', text: 'BSD-4-Clause-Shortened' }, 1535 { value: 'BSD-4-Clause-UC', text: 'BSD-4-Clause-UC' }, 1536 { value: 'BSD-Protection', text: 'BSD-Protection' }, 1537 { value: 'BSD-Source-Code', text: 'BSD-Source-Code' }, 1538 { value: 'BSL-1.0', text: 'BSL-1.0' }, 1539 { value: 'BUSL-1.1', text: 'BUSL-1.1' }, 1540 { value: 'bzip2-1.0.5', text: 'bzip2-1.0.5' }, 1541 { value: 'bzip2-1.0.6', text: 'bzip2-1.0.6' }, 1542 { value: 'C-UDA-1.0', text: 'C-UDA-1.0' }, 1543 { value: 'CAL-1.0', text: 'CAL-1.0' }, 1544 { value: 'CAL-1.0-Combined-Work-Exception', text: 'CAL-1.0-Combined-Work-Exception' }, 1545 { value: 'Caldera', text: 'Caldera' }, 1546 { value: 'CATOSL-1.1', text: 'CATOSL-1.1' },
1547 { value: 'CC-BY-1.0', text: 'CC-BY-1.0' }, 1548 { value: 'CC-BY-2.0', text: 'CC-BY-2.0' }, 1549 { value: 'CC-BY-2.5', text: 'CC-BY-2.5' }, 1550 { value: 'CC-BY-3.0', text: 'CC-BY-3.0' }, 1551 { value: 'CC-BY-3.0-AT', text: 'CC-BY-3.0-AT' }, 1552 { value: 'CC-BY-3.0-US', text: 'CC-BY-3.0-US' }, 1553 { value: 'CC-BY-4.0', text: 'CC-BY-4.0' }, 1554 { value: 'CC-BY-NC-1.0', text: 'CC-BY-NC-1.0' }, 1555 { value: 'CC-BY-NC-2.0', text: 'CC-BY-NC-2.0' }, 1556 { value: 'CC-BY-NC-2.5', text: 'CC-BY-NC-2.5' }, 1557 { value: 'CC-BY-NC-3.0', text: 'CC-BY-NC-3.0' }, 1558 { value: 'CC-BY-NC-4.0', text: 'CC-BY-NC-4.0' }, 1559 { value: 'CC-BY-NC-ND-1.0', text: 'CC-BY-NC-ND-1.0' }, 1560 { value: 'CC-BY-NC-ND-2.0', text: 'CC-BY-NC-ND-2.0' }, 1561 { value: 'CC-BY-NC-ND-2.5', text: 'CC-BY-NC-ND-2.5' }, 1562 { value: 'CC-BY-NC-ND-3.0', text: 'CC-BY-NC-ND-3.0' }, 1563 { value: 'CC-BY-NC-ND-3.0-IGO', text: 'CC-BY-NC-ND-3.0-IGO' }, 1564 { value: 'CC-BY-NC-ND-4.0', text: 'CC-BY-NC-ND-4.0' }, 1565 { value: 'CC-BY-NC-SA-1.0', text: 'CC-BY-NC-SA-1.0' }, 1566 { value: 'CC-BY-NC-SA-2.0', text: 'CC-BY-NC-SA-2.0' }, 1567 { value: 'CC-BY-NC-SA-2.5', text: 'CC-BY-NC-SA-2.5' }, 1568 { value: 'CC-BY-NC-SA-3.0', text: 'CC-BY-NC-SA-3.0' }, 1569 { value: 'CC-BY-NC-SA-4.0', text: 'CC-BY-NC-SA-4.0' }, 1570 { value: 'CC-BY-ND-1.0', text: 'CC-BY-ND-1.0' }, 1571 { value: 'CC-BY-ND-2.0', text: 'CC-BY-ND-2.0' }, 1572 { value: 'CC-BY-ND-2.5', text: 'CC-BY-ND-2.5' }, 1573 { value: 'CC-BY-ND-3.0', text: 'CC-BY-ND-3.0' }, 1574 { value: 'CC-BY-ND-4.0', text: 'CC-BY-ND-4.0' }, 1575 { value: 'CC-BY-SA-1.0', text: 'CC-BY-SA-1.0' }, 1576 { value: 'CC-BY-SA-2.0', text: 'CC-BY-SA-2.0' }, 1577 { value: 'CC-BY-SA-2.0-UK', text: 'CC-BY-SA-2.0-UK' }, 1578 { value: 'CC-BY-SA-2.1-JP', text: 'CC-BY-SA-2.1-JP' }, 1579 { value: 'CC-BY-SA-2.5', text: 'CC-BY-SA-2.5' }, 1580 { value: 'CC-BY-SA-3.0', text: 'CC-BY-SA-3.0' }, 1581 { value: 'CC-BY-SA-3.0-AT', text: 'CC-BY-SA-3.0-AT' }, 1582 { value: 'CC-BY-SA-4.0', text: 'CC-BY-SA-4.0' }, 1583 { value: 'CC-PDDC', text: 'CC-PDDC' }, 1584 { value: 'CC0-1.0', text: 'CC0-1.0' }, 1585 { value: 'CDDL-1.0', text: 'CDDL-1.0' }, 1586 { value: 'CDDL-1.1', text: 'CDDL-1.1' }, 1587 { value: 'CDL-1.0', text: 'CDL-1.0' }, 1588 { value: 'CDLA-Permissive-1.0', text: 'CDLA-Permissive-1.0' }, 1589 { value: 'CDLA-Sharing-1.0', text: 'CDLA-Sharing-1.0' }, 1590 { value: 'CECILL-1.0', text: 'CECILL-1.0' }, 1591 { value: 'CECILL-1.1', text: 'CECILL-1.1' }, 1592 { value: 'CECILL-2.0', text: 'CECILL-2.0' }, 1593 { value: 'CECILL-2.1', text: 'CECILL-2.1' }, 1594 { value: 'CECILL-B', text: 'CECILL-B' }, 1595 { value: 'CECILL-C', text: 'CECILL-C' }, 1596 { value: 'CERN-OHL-1.1', text: 'CERN-OHL-1.1' }, 1597 { value: 'CERN-OHL-1.2', text: 'CERN-OHL-1.2' }, 1598 { value: 'CERN-OHL-P-2.0', text: 'CERN-OHL-P-2.0' }, 1599 { value: 'CERN-OHL-S-2.0', text: 'CERN-OHL-S-2.0' }, 1600 { value: 'CERN-OHL-W-2.0', text: 'CERN-OHL-W-2.0' }, 1601 { value: 'ClArtistic', text: 'ClArtistic' }, 1602 { value: 'CNRI-Jython', text: 'CNRI-Jython' }, 1603 { value: 'CNRI-Python', text: 'CNRI-Python' }, 1604 { value: 'CNRI-Python-GPL-Compatible', text: 'CNRI-Python-GPL-Compatible' }, 1605 { value: 'Condor-1.1', text: 'Condor-1.1' }, 1606 { value: 'copyleft-next-0.3.0', text: 'copyleft-next-0.3.0' }, 1607 { value: 'copyleft-next-0.3.1', text: 'copyleft-next-0.3.1' }, 1608 { value: 'CPAL-1.0', text: 'CPAL-1.0' }, 1609 { value: 'CPL-1.0', text: 'CPL-1.0' }, 1610 { value: 'CPOL-1.02', text: 'CPOL-1.02' }, 1611 { value: 'Crossword', text: 'Crossword' }, 1612 { value: 'CrystalStacker', text: 'CrystalStacker' }, 1613 { value: 'CUA-OPL-1.0', text: 'CUA-OPL-1.0' }, 1614 { value: 'Cube', text: 'Cube' }, 1615 { value: 'curl', text: 'curl' }, 1616 { value: 'D-FSL-1.0', text: 'D-FSL-1.0' }, 1617 { value: 'diffmark', text: 'diffmark' }, 1618 { value: 'DOC', text: 'DOC' }, 1619 { value: 'Dotseqn', text: 'Dotseqn' }, 1620 { value: 'DRL-1.0', text: 'DRL-1.0' }, 1621 { value: 'DSDP', text: 'DSDP' }, 1622 { value: 'dvipdfm', text: 'dvipdfm' }, 1623 { value: 'ECL-1.0', text: 'ECL-1.0' }, 1624 { value: 'ECL-2.0', text: 'ECL-2.0' }, 1625 { value: 'EFL-1.0', text: 'EFL-1.0' }, 1626 { value: 'EFL-2.0', text: 'EFL-2.0' }, 1627 { value: 'eGenix', text: 'eGenix' }, 1628 { value: 'Entessa', text: 'Entessa' }, 1629 { value: 'EPICS', text: 'EPICS' }, 1630 { value: 'EPL-1.0', text: 'EPL-1.0' }, 1631 { value: 'EPL-2.0', text: 'EPL-2.0' }, 1632 { value: 'ErlPL-1.1', text: 'ErlPL-1.1' }, 1633 { value: 'etalab-2.0', text: 'etalab-2.0' }, 1634 { value: 'EUDatagrid', text: 'EUDatagrid' }, 1635 { value: 'EUPL-1.0', text: 'EUPL-1.0' }, 1636 { value: 'EUPL-1.1', text: 'EUPL-1.1' }, 1637 { value: 'EUPL-1.2', text: 'EUPL-1.2' }, 1638 { value: 'Eurosym', text: 'Eurosym' }, 1639 { value: 'Fair', text: 'Fair' }, 1640 { value: 'Frameworx-1.0', text: 'Frameworx-1.0' }, 1641 { value: 'FreeBSD-DOC', text: 'FreeBSD-DOC' }, 1642 { value: 'FreeImage', text: 'FreeImage' }, 1643 { value: 'FSFAP', text: 'FSFAP' }, 1644 { value: 'FSFUL', text: 'FSFUL' }, 1645 { value: 'FSFULLR', text: 'FSFULLR' }, 1646 { value: 'FTL', text: 'FTL' }, 1647 { value: 'GD', text: 'GD' }, 1648 { value: 'GFDL-1.1', text: 'GFDL-1.1' }, 1649 { value: 'GFDL-1.1-invariants-only', text: 'GFDL-1.1-invariants-only' }, 1650 { value: 'GFDL-1.1-invariants-or-later', text: 'GFDL-1.1-invariants-or-later' }, 1651 { value: 'GFDL-1.1-no-invariants-only', text: 'GFDL-1.1-no-invariants-only' }, 1652 { value: 'GFDL-1.1-no-invariants-or-later', text: 'GFDL-1.1-no-invariants-or-later' }, 1653 { value: 'GFDL-1.1-or-later', text: 'GFDL-1.1-or-later' }, 1654 { value: 'GFDL-1.2', text: 'GFDL-1.2' }, 1655 { value: 'GFDL-1.2-invariants-only', text: 'GFDL-1.2-invariants-only' }, 1656 { value: 'GFDL-1.2-invariants-or-later', text: 'GFDL-1.2-invariants-or-later' }, 1657 { value: 'GFDL-1.2-no-invariants-only', text: 'GFDL-1.2-no-invariants-only' }, 1658 { value: 'GFDL-1.2-no-invariants-or-later', text: 'GFDL-1.2-no-invariants-or-later' }, 1659 { value: 'GFDL-1.2-or-later', text: 'GFDL-1.2-or-later' }, 1660 { value: 'GFDL-1.3', text: 'GFDL-1.3' }, 1661 { value: 'GFDL-1.3-invariants-only', text: 'GFDL-1.3-invariants-only' }, 1662 { value: 'GFDL-1.3-invariants-or-later', text: 'GFDL-1.3-invariants-or-later' }, 1663 { value: 'GFDL-1.3-no-invariants-only', text: 'GFDL-1.3-no-invariants-only' }, 1664 { value: 'GFDL-1.3-no-invariants-or-later', text: 'GFDL-1.3-no-invariants-or-later' }, 1665 { value: 'GFDL-1.3-or-later', text: 'GFDL-1.3-or-later' }, 1666 { value: 'Giftware', text: 'Giftware' }, 1667 { value: 'GL2PS', text: 'GL2PS' }, 1668 { value: 'Glide', text: 'Glide' }, 1669 { value: 'Glulxe', text: 'Glulxe' }, 1670 { value: 'GLWTPL', text: 'GLWTPL' }, 1671 { value: 'gnuplot', text: 'gnuplot' }, 1672 { value: 'GPL-1.0', text: 'GPL-1.0' }, 1673 { value: 'GPL-1.0-or-later', text: 'GPL-1.0-or-later' }, 1674 { value: 'GPL-2.0', text: 'GPL-2.0' }, 1675 { value: 'GPL-2.0-or-later', text: 'GPL-2.0-or-later' }, 1676 { value: 'GPL-3.0', text: 'GPL-3.0' }, 1677 { value: 'GPL-3.0-or-later', text: 'GPL-3.0-or-later' }, 1678 { value: 'gSOAP-1.3b', text: 'gSOAP-1.3b' }, 1679 { value: 'HaskellReport', text: 'HaskellReport' }, 1680 { value: 'Hippocratic-2.1', text: 'Hippocratic-2.1' }, 1681 { value: 'HPND', text: 'HPND' }, 1682 { value: 'HPND-sell-variant', text: 'HPND-sell-variant' }, 1683 { value: 'HTMLTIDY', text: 'HTMLTIDY' }, 1684 { value: 'IBM-pibs', text: 'IBM-pibs' }, 1685 { value: 'ICU', text: 'ICU' }, 1686 { value: 'IJG', text: 'IJG' }, 1687 { value: 'ImageMagick', text: 'ImageMagick' }, 1688 { value: 'iMatix', text: 'iMatix' }, 1689 { value: 'Imlib2', text: 'Imlib2' }, 1690 { value: 'Info-ZIP', text: 'Info-ZIP' }, 1691 { value: 'Intel', text: 'Intel' }, 1692 { value: 'Intel-ACPI', text: 'Intel-ACPI' }, 1693 { value: 'Interbase-1.0', text: 'Interbase-1.0' }, 1694 { value: 'IPA', text: 'IPA' }, 1695 { value: 'IPL-1.0', text: 'IPL-1.0' }, 1696 { value: 'ISC', text: 'ISC' }, 1697 { value: 'JasPer-2.0', text: 'JasPer-2.0' }, 1698 { value: 'JPNIC', text: 'JPNIC' }, 1699 { value: 'JSON', text: 'JSON' }, 1700 { value: 'LAL-1.2', text: 'LAL-1.2' }, 1701 { value: 'LAL-1.3', text: 'LAL-1.3' }, 1702 { value: 'Latex2e', text: 'Latex2e' }, 1703 { value: 'Leptonica', text: 'Leptonica' }, 1704 { value: 'LGPL-2.0', text: 'LGPL-2.0' }, 1705 { value: 'LGPL-2.0-or-later', text: 'LGPL-2.0-or-later' }, 1706 { value: 'LGPL-2.1', text: 'LGPL-2.1' }, 1707 { value: 'LGPL-2.1-or-later', text: 'LGPL-2.1-or-later' }, 1708 { value: 'LGPL-3.0', text: 'LGPL-3.0' }, 1709 { value: 'LGPL-3.0-or-later', text: 'LGPL-3.0-or-later' }, 1710 { value: 'LGPLLR', text: 'LGPLLR' }, 1711 { value: 'Libpng', text: 'Libpng' }, 1712 { value: 'libpng-2.0', text: 'libpng-2.0' }, 1713 { value: 'libselinux-1.0', text: 'libselinux-1.0' }, 1714 { value: 'libtiff', text: 'libtiff' }, 1715 { value: 'LiLiQ-P-1.1', text: 'LiLiQ-P-1.1' }, 1716 { value: 'LiLiQ-R-1.1', text: 'LiLiQ-R-1.1' }, 1717 { value: 'LiLiQ-Rplus-1.1', text: 'LiLiQ-Rplus-1.1' }, 1718 { value: 'Linux-OpenIB', text: 'Linux-OpenIB' }, 1719 { value: 'LPL-1.0', text: 'LPL-1.0' }, 1720 { value: 'LPL-1.02', text: 'LPL-1.02' }, 1721 { value: 'LPPL-1.0', text: 'LPPL-1.0' }, 1722 { value: 'LPPL-1.1', text: 'LPPL-1.1' }, 1723 { value: 'LPPL-1.2', text: 'LPPL-1.2' }, 1724 { value: 'LPPL-1.3a', text: 'LPPL-1.3a' }, 1725 { value: 'LPPL-1.3c', text: 'LPPL-1.3c' }, 1726 { value: 'MakeIndex', text: 'MakeIndex' }, 1727 { value: 'MirOS', text: 'MirOS' }, 1728 { value: 'MIT', text: 'MIT' },
1729 { value: 'MIT-0', text: 'MIT-0' }, 1730 { value: 'MIT-advertising', text: 'MIT-advertising' }, 1731 { value: 'MIT-CMU', text: 'MIT-CMU' }, 1732 { value: 'MIT-enna', text: 'MIT-enna' }, 1733 { value: 'MIT-feh', text: 'MIT-feh' }, 1734 { value: 'MIT-Modern-Variant', text: 'MIT-Modern-Variant' }, 1735 { value: 'MIT-open-group', text: 'MIT-open-group' }, 1736 { value: 'MITNFA', text: 'MITNFA' }, 1737 { value: 'Motosoto', text: 'Motosoto' }, 1738 { value: 'mpich2', text: 'mpich2' }, 1739 { value: 'MPL-1.0', text: 'MPL-1.0' }, 1740 { value: 'MPL-1.1', text: 'MPL-1.1' }, 1741 { value: 'MPL-2.0', text: 'MPL-2.0' }, 1742 { value: 'MPL-2.0-no-copyleft-exception', text: 'MPL-2.0-no-copyleft-exception' }, 1743 { value: 'MS-PL', text: 'MS-PL' }, 1744 { value: 'MS-RL', text: 'MS-RL' }, 1745 { value: 'MTLL', text: 'MTLL' }, 1746 { value: 'MulanPSL-1.0', text: 'MulanPSL-1.0' }, 1747 { value: 'MulanPSL-2.0', text: 'MulanPSL-2.0' }, 1748 { value: 'Multics', text: 'Multics' }, 1749 { value: 'Mup', text: 'Mup' }, 1750 { value: 'NAIST-2003', text: 'NAIST-2003' }, 1751 { value: 'NASA-1.3', text: 'NASA-1.3' }, 1752 { value: 'Naumen', text: 'Naumen' }, 1753 { value: 'NBPL-1.0', text: 'NBPL-1.0' }, 1754 { value: 'NCGL-UK-2.0', text: 'NCGL-UK-2.0' }, 1755 { value: 'NCSA', text: 'NCSA' }, 1756 { value: 'Net-SNMP', text: 'Net-SNMP' }, 1757 { value: 'NetCDF', text: 'NetCDF' }, 1758 { value: 'Newsletr', text: 'Newsletr' }, 1759 { value: 'NGPL', text: 'NGPL' }, 1760 { value: 'NIST-PD', text: 'NIST-PD' }, 1761 { value: 'NIST-PD-fallback', text: 'NIST-PD-fallback' }, 1762 { value: 'NLOD-1.0', text: 'NLOD-1.0' }, 1763 { value: 'NLPL', text: 'NLPL' }, 1764 { value: 'Nokia', text: 'Nokia' }, 1765 { value: 'NOSL', text: 'NOSL' }, 1766 { value: 'Noweb', text: 'Noweb' }, 1767 { value: 'NPL-1.0', text: 'NPL-1.0' }, 1768 { value: 'NPL-1.1', text: 'NPL-1.1' }, 1769 { value: 'NPOSL-3.0', text: 'NPOSL-3.0' }, 1770 { value: 'NRL', text: 'NRL' }, 1771 { value: 'NTP', text: 'NTP' }, 1772 { value: 'Nunit', text: 'Nunit' }, 1773 { value: 'O-UDA-1.0', text: 'O-UDA-1.0' }, 1774 { value: 'OCCT-PL', text: 'OCCT-PL' }, 1775 { value: 'OCLC-2.0', text: 'OCLC-2.0' }, 1776 { value: 'ODbL-1.0', text: 'ODbL-1.0' }, 1777 { value: 'ODC-By-1.0', text: 'ODC-By-1.0' }, 1778 { value: 'OFL-1.0', text: 'OFL-1.0' }, 1779 { value: 'OFL-1.0-no-RFN', text: 'OFL-1.0-no-RFN' }, 1780 { value: 'OFL-1.0-RFN', text: 'OFL-1.0-RFN' }, 1781 { value: 'OFL-1.1', text: 'OFL-1.1' }, 1782 { value: 'OFL-1.1-no-RFN', text: 'OFL-1.1-no-RFN' }, 1783 { value: 'OFL-1.1-RFN', text: 'OFL-1.1-RFN' }, 1784 { value: 'OGC-1.0', text: 'OGC-1.0' }, 1785 { value: 'OGDL-Taiwan-1.0', text: 'OGDL-Taiwan-1.0' }, 1786 { value: 'OGL-Canada-2.0', text: 'OGL-Canada-2.0' }, 1787 { value: 'OGL-UK-1.0', text: 'OGL-UK-1.0' }, 1788 { value: 'OGL-UK-2.0', text: 'OGL-UK-2.0' }, 1789 { value: 'OGL-UK-3.0', text: 'OGL-UK-3.0' }, 1790 { value: 'OGTSL', text: 'OGTSL' }, 1791 { value: 'OLDAP-1.1', text: 'OLDAP-1.1' }, 1792 { value: 'OLDAP-1.2', text: 'OLDAP-1.2' }, 1793 { value: 'OLDAP-1.3', text: 'OLDAP-1.3' }, 1794 { value: 'OLDAP-1.4', text: 'OLDAP-1.4' }, 1795 { value: 'OLDAP-2.0', text: 'OLDAP-2.0' }, 1796 { value: 'OLDAP-2.0.1', text: 'OLDAP-2.0.1' }, 1797 { value: 'OLDAP-2.1', text: 'OLDAP-2.1' }, 1798 { value: 'OLDAP-2.2', text: 'OLDAP-2.2' }, 1799 { value: 'OLDAP-2.2.1', text: 'OLDAP-2.2.1' }, 1800 { value: 'OLDAP-2.2.2', text: 'OLDAP-2.2.2' }, 1801 { value: 'OLDAP-2.3', text: 'OLDAP-2.3' }, 1802 { value: 'OLDAP-2.4', text: 'OLDAP-2.4' }, 1803 { value: 'OLDAP-2.5', text: 'OLDAP-2.5' }, 1804 { value: 'OLDAP-2.6', text: 'OLDAP-2.6' }, 1805 { value: 'OLDAP-2.7', text: 'OLDAP-2.7' }, 1806 { value: 'OLDAP-2.8', text: 'OLDAP-2.8' }, 1807 { value: 'OML', text: 'OML' }, 1808 { value: 'OpenSSL', text: 'OpenSSL' }, 1809 { value: 'OPL-1.0', text: 'OPL-1.0' }, 1810 { value: 'OSET-PL-2.1', text: 'OSET-PL-2.1' }, 1811 { value: 'OSL-1.0', text: 'OSL-1.0' }, 1812 { value: 'OSL-1.1', text: 'OSL-1.1' }, 1813 { value: 'OSL-2.0', text: 'OSL-2.0' }, 1814 { value: 'OSL-2.1', text: 'OSL-2.1' }, 1815 { value: 'OSL-3.0', text: 'OSL-3.0' }, 1816 { value: 'Parity-6.0.0', text: 'Parity-6.0.0' }, 1817 { value: 'Parity-7.0.0', text: 'Parity-7.0.0' }, 1818 { value: 'PDDL-1.0', text: 'PDDL-1.0' }, 1819 { value: 'PHP-3.0', text: 'PHP-3.0' }, 1820 { value: 'PHP-3.01', text: 'PHP-3.01' }, 1821 { value: 'Plexus', text: 'Plexus' }, 1822 { value: 'PolyForm-Noncommercial-1.0.0', text: 'PolyForm-Noncommercial-1.0.0' }, 1823 { value: 'PolyForm-Small-Business-1.0.0', text: 'PolyForm-Small-Business-1.0.0' }, 1824 { value: 'PostgreSQL', text: 'PostgreSQL' }, 1825 { value: 'PSF-2.0', text: 'PSF-2.0' }, 1826 { value: 'psfrag', text: 'psfrag' }, 1827 { value: 'psutils', text: 'psutils' }, 1828 { value: 'Python-2.0', text: 'Python-2.0' }, 1829 { value: 'Qhull', text: 'Qhull' }, 1830 { value: 'QPL-1.0', text: 'QPL-1.0' }, 1831 { value: 'Rdisc', text: 'Rdisc' }, 1832 { value: 'RHeCos-1.1', text: 'RHeCos-1.1' }, 1833 { value: 'RPL-1.1', text: 'RPL-1.1' }, 1834 { value: 'RPL-1.5', text: 'RPL-1.5' }, 1835 { value: 'RPSL-1.0', text: 'RPSL-1.0' }, 1836 { value: 'RSA-MD', text: 'RSA-MD' }, 1837 { value: 'RSCPL', text: 'RSCPL' }, 1838 { value: 'Ruby', text: 'Ruby' }, 1839 { value: 'SAX-PD', text: 'SAX-PD' }, 1840 { value: 'Saxpath', text: 'Saxpath' }, 1841 { value: 'SCEA', text: 'SCEA' }, 1842 { value: 'Sendmail', text: 'Sendmail' }, 1843 { value: 'Sendmail-8.23', text: 'Sendmail-8.23' }, 1844 { value: 'SGI-B-1.0', text: 'SGI-B-1.0' }, 1845 { value: 'SGI-B-1.1', text: 'SGI-B-1.1' }, 1846 { value: 'SGI-B-2.0', text: 'SGI-B-2.0' }, 1847 { value: 'SHL-0.5', text: 'SHL-0.5' }, 1848 { value: 'SHL-0.51', text: 'SHL-0.51' }, 1849 { value: 'SimPL-2.0', text: 'SimPL-2.0' }, 1850 { value: 'SISSL', text: 'SISSL' }, 1851 { value: 'SISSL-1.2', text: 'SISSL-1.2' }, 1852 { value: 'Sleepycat', text: 'Sleepycat' }, 1853 { value: 'SMLNJ', text: 'SMLNJ' }, 1854 { value: 'SMPPL', text: 'SMPPL' }, 1855 { value: 'SNIA', text: 'SNIA' }, 1856 { value: 'Spencer-86', text: 'Spencer-86' }, 1857 { value: 'Spencer-94', text: 'Spencer-94' }, 1858 { value: 'Spencer-99', text: 'Spencer-99' }, 1859 { value: 'SPL-1.0', text: 'SPL-1.0' }, 1860 { value: 'SSH-OpenSSH', text: 'SSH-OpenSSH' }, 1861 { value: 'SSH-short', text: 'SSH-short' }, 1862 { value: 'SSPL-1.0', text: 'SSPL-1.0' }, 1863 { value: 'SugarCRM-1.1.3', text: 'SugarCRM-1.1.3' }, 1864 { value: 'SWL', text: 'SWL' }, 1865 { value: 'TAPR-OHL-1.0', text: 'TAPR-OHL-1.0' }, 1866 { value: 'TCL', text: 'TCL' }, 1867 { value: 'TCP-wrappers', text: 'TCP-wrappers' }, 1868 { value: 'TMate', text: 'TMate' }, 1869 { value: 'TORQUE-1.1', text: 'TORQUE-1.1' }, 1870 { value: 'TOSL', text: 'TOSL' }, 1871 { value: 'TU-Berlin-1.0', text: 'TU-Berlin-1.0' }, 1872 { value: 'TU-Berlin-2.0', text: 'TU-Berlin-2.0' }, 1873 { value: 'UCL-1.0', text: 'UCL-1.0' }, 1874 { value: 'Unicode-DFS-2015', text: 'Unicode-DFS-2015' }, 1875 { value: 'Unicode-DFS-2016', text: 'Unicode-DFS-2016' }, 1876 { value: 'Unicode-TOU', text: 'Unicode-TOU' }, 1877 { value: 'Unlicense', text: 'Unlicense' }, 1878 { value: 'UPL-1.0', text: 'UPL-1.0' }, 1879 { value: 'Vim', text: 'Vim' }, 1880 { value: 'VOSTROM', text: 'VOSTROM' }, 1881 { value: 'VSL-1.0', text: 'VSL-1.0' }, 1882 { value: 'W3C', text: 'W3C' }, 1883 { value: 'W3C-19980720', text: 'W3C-19980720' }, 1884 { value: 'Watcom-1.0', text: 'Watcom-1.0' }, 1885 { value: 'Wsuipa', text: 'Wsuipa' }, 1886 { value: 'WTFPL', text: 'WTFPL' }, 1887 { value: 'X11', text: 'X11' }, 1888 { value: 'Xerox', text: 'Xerox' }, 1889 { value: 'XFree86-1.1', text: 'XFree86-1.1' }, 1890 { value: 'xinetd', text: 'xinetd' }, 1891 { value: 'Xnet', text: 'Xnet' }, 1892 { value: 'xpp', text: 'xpp' }, 1893 { value: 'XSkat', text: 'XSkat' }, 1894 { value: 'YPL-1.0', text: 'YPL-1.0' }, 1895 { value: 'YPL-1.1', text: 'YPL-1.1' }, 1896 { value: 'Zed', text: 'Zed' }, 1897 { value: 'Zend-2.0', text: 'Zend-2.0' }, 1898 { value: 'Zimbra-1.3', text: 'Zimbra-1.3' }, 1899 { value: 'Zimbra-1.4', text: 'Zimbra-1.4' }, 1900 { value: 'Zlib', text: 'Zlib' }, 1901 { value: 'zlib-acknowledgement', text: 'zlib-acknowledgement' }, 1902 { value: 'ZPL-1.1', text: 'ZPL-1.1' }, 1903 { value: 'ZPL-2.0', text: 'ZPL-2.0' }, 1904 { value: 'ZPL-2.1', text: 'ZPL-2.1' }, 1905 ]; 1906 1907 $scope.costOptions = [ 1908 { value: 'Free of charge', text: 'Free of charge' }, 1909 { 1910 value: 'Free of charge (with restrictions)', 1911 text: 'Free of charge (with restrictions)', 1912 }, 1913 { value: 'Commercial', text: 'Commercial' }, 1914 ]; 1915 1916 $scope.languageOptions = [ 1917 { value: 'ActionScript', text: 'ActionScript' }, 1918 { value: 'Ada', text: 'Ada' }, 1919 { value: 'AppleScript', text: 'AppleScript' }, 1920 { value: 'Assembly language', text: 'Assembly language' }, 1921 { value: 'AWK', text: 'AWK' }, 1922 { value: 'Bash', text: 'Bash' }, 1923 { value: 'C', text: 'C' }, 1924 { value: 'C#', text: 'C#' }, 1925 { value: 'C++', text: 'C++' }, 1926 { value: 'Clojure', text: 'Clojure' }, 1927 { value: 'COBOL', text: 'COBOL' }, 1928 { value: 'ColdFusion', text: 'ColdFusion' }, 1929 { value: 'Cython', text: 'Cython' }, 1930 { value: 'CUDA', text: 'CUDA' }, 1931 { value: 'CWL', text: 'CWL' }, 1932 { value: 'D', text: 'D' }, 1933 { value: 'Delphi', text: 'Delphi' }, 1934 { value: 'Dylan', text: 'Dylan' }, 1935 { value: 'Eiffel', text: 'Eiffel' }, 1936 { value: 'Elm', text: 'Elm' }, 1937 { value: 'F#', text: 'F#' }, 1938 { value: 'Forth', text: 'Forth' }, 1939 { value: 'Fortran', text: 'Fortran' }, 1940 { value: 'Go', text: 'Go' }, 1941 { value: 'Groovy', text: 'Groovy' }, 1942 { value: 'Haskell', text: 'Haskell' }, 1943 { value: 'Java', text: 'Java' }, 1944 { value: 'JavaScript', text: 'JavaScript' }, 1945 { value: 'JSP', text: 'JSP' }, 1946 { value: 'Julia', text: 'Julia' }, 1947 { value: 'Jython', text: 'Jython' },
1948 { value: 'Kotlin', text: 'Kotlin' }, 1949 { value: 'LabVIEW', text: 'LabVIEW' }, 1950 { value: 'Lisp', text: 'Lisp' }, 1951 { value: 'Lua', text: 'Lua' }, 1952 { value: 'Maple', text: 'Maple' }, 1953 { value: 'Mathematica', text: 'Mathematica' }, 1954 { value: 'MATLAB', text: 'MATLAB' }, 1955 { value: 'MLXTRAN', text: 'MLXTRAN' }, 1956 { value: 'NMTRAN', text: 'NMTRAN' }, 1957 { value: 'OCaml', text: 'OCaml' }, 1958 { value: 'Pascal', text: 'Pascal' }, 1959 { value: 'Perl', text: 'Perl' }, 1960 { value: 'PHP', text: 'PHP' }, 1961 { value: 'Prolog', text: 'Prolog' }, 1962 { value: 'PyMOL', text: 'PyMOL' }, 1963 { value: 'Python', text: 'Python' }, 1964 { value: 'Q#', text: 'Q#' }, 1965 { value: 'QCL', text: 'QCL' }, 1966 { value: 'R', text: 'R' }, 1967 { value: 'Racket', text: 'Racket' }, 1968 { value: 'REXX', text: 'REXX' }, 1969 { value: 'Ruby', text: 'Ruby' }, 1970 { value: 'Rust', text: 'Rust' }, 1971 { value: 'SAS', text: 'SAS' }, 1972 { value: 'Scala', text: 'Scala' }, 1973 { value: 'Scheme', text: 'Scheme' }, 1974 { value: 'Shell', text: 'Shell' }, 1975 { value: 'Smalltalk', text: 'Smalltalk' }, 1976 { value: 'SQL', text: 'SQL' }, 1977 { value: 'Swift', text: 'Swift' }, 1978 { value: 'Turing', text: 'Turing' }, 1979 { value: 'TypeScript', text: 'TypeScript' }, 1980 { value: 'Verilog', text: 'Verilog' }, 1981 { value: 'VHDL', text: 'VHDL' }, 1982 { value: 'Visual Basic', text: 'Visual Basic' }, 1983 { value: 'XAML', text: 'XAML' }, 1984 { value: 'Other', text: 'Other' }, 1985 ]; 1986 1987 $scope.platformOptions = [ 1988 { value: 'Mac', text: 'Mac' }, 1989 { value: 'Linux', text: 'Linux' }, 1990 { value: 'Windows', text: 'Windows' }, 1991 { value: 'Android', text: 'Android' }, 1992 { value: 'iOS', text: 'iOS' }, 1993 ]; 1994 1995 $scope.accessibilityOptions = [ 1996 { value: 'Open access', text: 'Open access' }, 1997 { 1998 value: 'Open access (with restrictions)', 1999 text: 'Open access (with restrictions)', 2000 }, 2001 { value: 'Restricted access', text: 'Restricted access' }, 2002 ]; 2003 2004 $scope.maturityOptions = [ 2005 { value: 'Emerging', text: 'Emerging' }, 2006 { value: 'Mature', text: 'Mature' }, 2007 { value: 'Legacy', text: 'Legacy' }, 2008 ]; 2009 2010 $scope.toolTypeOptions = [ 2011 { value: 'Bioinformatics portal', text: 'Bioinformatics portal' }, 2012 { value: 'Command-line tool', text: 'Command-line tool' }, 2013 { value: 'Database portal', text: 'Database portal' }, 2014 { value: 'Desktop application', text: 'Desktop application' }, 2015 { value: 'Library', text: 'Library' }, 2016 { value: 'Mobile application', text: 'Mobile application' }, 2017 { value: 'Ontology', text: 'Ontology' }, 2018 { value: 'Plug-in', text: 'Plug-in' }, 2019 { value: 'Script', text: 'Script' }, 2020 { value: 'SPARQL endpoint', text: 'SPARQL endpoint' }, 2021 { value: 'Suite', text: 'Suite' }, 2022 { value: 'Web application', text: 'Web application' }, 2023 { value: 'Web API', text: 'Web API' }, 2024 { value: 'Web service', text: 'Web service' }, 2025 { value: 'Workbench', text: 'Workbench' }, 2026 { value: 'Workflow', text: 'Workflow' }, 2027 ]; 2028 2029 $scope.linkTypeOptions = [ 2030 { value: 'Discussion forum', text: 'Discussion forum' }, 2031 { value: 'Galaxy service', text: 'Galaxy service' }, 2032 { value: 'Helpdesk', text: 'Helpdesk' }, 2033 { value: 'Issue tracker', text: 'Issue tracker' }, 2034 { value: 'Mailing list', text: 'Mailing list' }, 2035 { value: 'Mirror', text: 'Mirror' }, 2036 { value: 'Repository', text: 'Repository' }, 2037 { value: 'Service', text: 'Service' }, 2038 { value: 'Social media', text: 'Social media' }, 2039 { value: 'Software catalogue', text: 'Software catalogue' }, 2040 { value: 'Technical monitoring', text: 'Technical monitoring' }, 2041 { value: 'Other', text: 'Other' }, 2042 ]; 2043 2044 $scope.downloadTypeOptions = [ 2045 { value: 'Downloads page', text: 'Downloads page' }, 2046 { value: 'API specification', text: 'API specification' }, 2047 { value: 'Biological data', text: 'Biological data' }, 2048 { value: 'Binaries', text: 'Binaries' }, 2049 { value: 'Command-line specification', text: 'Command-line specification' }, 2050 { value: 'Container file', text: 'Container file' }, 2051 { value: 'Icon', text: 'Icon' }, 2052 { value: 'Screenshot', text: 'Screenshot' }, 2053 { value: 'Software package', text: 'Software package' }, 2054 { value: 'Source code', text: 'Source code' }, 2055 { value: 'Test data', text: 'Test data' }, 2056 { value: 'Test script', text: 'Test script' }, 2057 { value: 'Tool wrapper (CWL)', text: 'Tool wrapper (CWL)' }, 2058 { value: 'Tool wrapper (Galaxy)', text: 'Tool wrapper (Galaxy)' }, 2059 { value: 'Tool wrapper (Taverna)', text: 'Tool wrapper (Taverna)' }, 2060 { value: 'Tool wrapper (Other)', text: 'Tool wrapper (Other)' }, 2061 { value: 'VM Image', text: 'VM Image' }, 2062 { value: 'Other', text: 'Other' }, 2063 ]; 2064 2065 $scope.documentationTypeOptions = [ 2066 { value: 'API documentation', text: 'API documentation' }, 2067 { value: 'Citation instructions', text: 'Citation instructions' }, 2068 { value: 'Code of conduct', text: 'Code of conduct' }, 2069 { value: 'Command-line options', text: 'Command-line options' }, 2070 { value: 'Contributions policy', text: 'Contributions policy' }, 2071 { value: 'FAQ', text: 'FAQ' }, 2072 { value: 'General', text: 'General' }, 2073 { value: 'Governance', text: 'Governance' }, 2074 { value: 'Installation instructions', text: 'Installation instructions' }, 2075 { value: 'Quick start guide', text: 'Quick start guide' }, 2076 { value: 'Release notes', text: 'Release notes' }, 2077 { value: 'Terms of use', text: 'Terms of use' }, 2078 { value: 'Training material', text: 'Training material' }, 2079 { value: 'User manual', text: 'User manual' }, 2080 { value: 'Other', text: 'Other' }, 2081 ]; 2082 2083 $scope.publicationTypeOptions = [ 2084 { value: 'Primary', text: 'Primary' }, 2085 { value: 'Benchmarking study', text: 'Benchmarking study' }, 2086 { value: 'Method', text: 'Method' }, 2087 { value: 'Usage', text: 'Usage' }, 2088 { value: 'Review', text: 'Review' }, 2089 { value: 'Preprint', text: 'Preprint' }, 2090 { value: 'Other', text: 'Other' }, 2091 ]; 2092 2093 $scope.entityTypeOptions = [ 2094 { value: 'Person', text: 'Person' }, 2095 { value: 'Project', text: 'Project' }, 2096 { value: 'Division', text: 'Division' }, 2097 { value: 'Institute', text: 'Institute' }, 2098 { value: 'Consortium', text: 'Consortium' },
2099 { value: 'Funding agency', text: 'Funding agency' }, 2100 ]; 2101 2102 $scope.roleTypeOptions = [ 2103 { value: 'Primary contact', text: 'Primary contact' }, 2104 { value: 'Contributor', text: 'Contributor' }, 2105 { value: 'Developer', text: 'Developer' }, 2106 { value: 'Documentor', text: 'Documentor' }, 2107 { value: 'Maintainer', text: 'Maintainer' }, 2108 { value: 'Provider', text: 'Provider' }, 2109 { value: 'Support', text: 'Support' }, 2110 ]; 2111 2112 $scope.elixirPlatformOptions = [ 2113 { value: 'Data', text: 'Data' }, 2114 { value: 'Tools', text: 'Tools' }, 2115 { value: 'Compute', text: 'Compute' }, 2116 { value: 'Interoperability', text: 'Interoperability' }, 2117 { value: 'Training', text: 'Training' }, 2118 ]; 2119 2120 $scope.elixirNodeOptions = [ 2121 { value: 'Belgium', text: 'Belgium' }, 2122 { value: 'Czech Republic', text: 'Czech Republic' }, 2123 { value: 'Denmark', text: 'Denmark' }, 2124 { value: 'EMBL', text: 'EMBL' }, 2125 { value: 'Estonia', text: 'Estonia' }, 2126 { value: 'Finland', text: 'Finland' }, 2127 { value: 'France', text: 'France' }, 2128 { value: 'Germany', text: 'Germany' }, 2129 { value: 'Greece', text: 'Greece' }, 2130 { value: 'Hungary', text: 'Hungary' }, 2131 { value: 'Ireland', text: 'Ireland' }, 2132 { value: 'Israel', text: 'Israel' }, 2133 { value: 'Italy', text: 'Italy' }, 2134 { value: 'Luxembourg', text: 'Luxembourg' }, 2135 { value: 'Netherlands', text: 'Netherlands' }, 2136 { value: 'Norway', text: 'Norway' }, 2137 { value: 'Portugal', text: 'Portugal' }, 2138 { value: 'Slovenia', text: 'Slovenia' }, 2139 { value: 'Spain', text: 'Spain' }, 2140 { value: 'Sweden', text: 'Sweden' }, 2141 { value: 'Switzerland', text: 'Switzerland' }, 2142 { value: 'UK', text: 'UK' }, 2143 ]; 2144 2145 $scope.elixirCommunityOptions = [ 2146 { value: '3D-BioInfo', text: '3D-BioInfo', link: '3d-bioinfo' }, 2147 { value: 'Biodiversity', text: 'Biodiversity', link: 'biodiversity' }, 2148 { value: 'Cancer Data', text: 'Cancer Data', link: 'cancer-data' }, 2149 { value: 'Federated Human Data', text: 'Federated Human Data', link: 'human-data' }, 2150 { 2151 value: 'Food and Nutrition', 2152 text: 'Food and Nutrition', 2153 link: 'food-and-nutrition', 2154 }, 2155 { value: 'Galaxy', text: 'Galaxy', link: 'galaxy' }, 2156 { 2157 value: 'Human Copy Number Variation', 2158 text: 'Human Copy Number Variation', 2159 link: 'hcnv', 2160 }, 2161 { 2162 value: 'Intrinsically Disordered Proteins', 2163 text: 'Intrinsically Disordered Proteins', 2164 link: 'intrinsically-disordered-proteins', 2165 }, 2166 { 2167 value: 'Marine Metagenomics', 2168 text: 'Marine Metagenomics', 2169 link: 'marine-metagenomics', 2170 }, 2171 { value: 'Metabolomics', text: 'Metabolomics', link: 'metabolomics' }, 2172 { 2173 value: 'Microbial Biotechnology', 2174 text: 'Microbial Biotechnology', 2175 link: 'microbial-biotechnology', 2176 }, 2177 { value: 'Microbiome', text: 'Microbiome', link: 'microbiome' }, 2178 { value: 'Plant Sciences', text: 'Plant Sciences', link: 'plant-sciences' }, 2179 { value: 'Proteomics', text: 'Proteomics', link: 'proteomics' }, 2180 { value: 'Rare Diseases', text: 'Rare Diseases', link: 'rare-diseases' }, 2181 { 2182 value: 'Research Data Management',
2183 text: 'Research Data Management', 2184 link: 'research-data-management', 2185 }, 2186 { 2187 value: 'Single-cell Omics', 2188 text: 'Single-cell Omics', 2189 link: 'single-cell-omics', 2190 }, 2191 { value: 'Systems Biology', text: 'Systems Biology', link: 'systems-biology' }, 2192 { value: 'Toxicology', text: 'Toxicology', link: 'toxicology' }, 2193 ]; 2194 2195 $scope.otherIdTypeOptions = [ 2196 { value: 'doi', text: 'doi' }, 2197 { value: 'rrid', text: 'rrid' }, 2198 { value: 'cpe', text: 'cpe' }, 2199 ]; 2200 2201 $scope.relationTypeOptions = [ 2202 { value: 'isNewVersionOf', text: 'isNewVersionOf' }, 2203 { value: 'hasNewVersion', text: 'hasNewVersion' }, 2204 { value: 'uses', text: 'uses' }, 2205 { value: 'usedBy', text: 'usedBy' }, 2206 { value: 'includes', text: 'includes' }, 2207 { value: 'includedIn', text: 'includedIn' }, 2208 ]; 2209 2210 $scope.confidenceOptions = [ 2211 { value: 'tool', text: 'tool' }, 2212 { value: 'high', text: 'high' }, 2213 { value: 'medium', text: 'medium' }, 2214 { value: 'low', text: 'low' }, 2215 { value: 'very low', text: 'very low' }, 2216 ]; 2217 2218 $scope.$watch( 2219 'software', 2220 function () { 2221 angular.forEach($scope.software, function (value, key) { 2222 if (value === null || value === '') { 2223 delete $scope.software[key]; 2224 } 2225 }); 2226 }, 2227 true 2228 ); 2229 }, 2230 ]) 2231 .controller('ToolUpdateController', [ 2232 '$scope', 2233 '$controller', 2234 '$timeout', 2235 '$state', 2236 '$stateParams', 2237 'Tool', 2238 'ToolUpdateValidator', 2239 'CommunityCollection', 2240 function ( 2241 $scope, 2242 $controller, 2243 $timeout, 2244 $state, 2245 $stateParams, 2246 Tool, 2247 ToolUpdateValidator, 2248 CommunityCollection 2249 ) { 2250 // inherit common controller 2251 $controller('ToolEditController', { $scope: $scope }); 2252 2253 // sets which controller is in use, so the HTML can adapt 2254 $scope.controller = 'update'; 2255 2256 // set the ID to not autoupdate when name is changed 2257 $scope.autoUpdateId = false; 2258 $scope.CommunityCollection = CommunityCollection; 2259 $scope.validateButtonClick = function () { 2260 $timeout(function () { 2261 $scope.sendResource( 2262 ToolUpdateValidator.update, 2263 $scope.validationProgress, 2264 false, 2265 'update-validate' 2266 ); 2267 }, 100); 2268 }; 2269 2270 $scope.registerButtonClick = function () { 2271 $timeout(function () { 2272 if (confirm('Are you sure you want to update the resource? ')) { 2273 $scope.sendResource(Tool.update, $scope.savingProgress, false, 'update'); 2274 } 2275 }, 100); 2276 }; 2277 2278 $scope.deleteButtonClick = function () { 2279 $timeout(function () { 2280 if (confirm('Are you sure you want to remove the resource? ')) { 2281 if ( 2282 confirm( 2283 'This will remove the resource and cannot be undone. Are you sure you want to continue? ' 2284 ) 2285 ) { 2286 $scope.sendResource( 2287 Tool.remove, 2288 $scope.deletingProgress, 2289 true, 2290 'delete' 2291 ); 2292 } 2293 } 2294 }, 100); 2295 }; 2296 2297 $scope.naviagateToTool = function (biotoolsID) { 2298 $timeout(function () { 2299 if ( 2300 confirm( 2301 'Make sure you save before navigating away! Are you sure you want to leave? ' 2302 ) 2303 ) { 2304 $state.go('tool', { id: biotoolsID }, { reload: true }); 2305 } 2306 }, 100); 2307 }; 2308 2309 // when a tool is being updated, display the current URL 2310 // $scope.$watch('software', function() { 2311 // $scope.setURL(); 2312 // }) 2313 }, 2314 ]) 2315 .controller('ToolCreateController', [ 2316 '$scope', 2317 '$controller', 2318 '$timeout', 2319 'ToolListConnection', 2320 'ToolCreateValidator', 2321 'User', 2322 '$stateParams', 2323 'CommunityCollection', 2324 function ( 2325 $scope, 2326 $controller, 2327 $timeout, 2328 ToolListConnection, 2329 ToolCreateValidator, 2330 User, 2331 $stateParams, 2332 CommunityCollection 2333 ) { 2334 // inherit common controller 2335 $controller('ToolEditController', { $scope: $scope }); 2336 $scope.orderby = 'text'; 2337 // sets which controller is in use, so the HTML can adapt 2338 $scope.controller = 'create'; 2339 2340 // initially set the ID to change automatically when name is modified 2341 $scope.biotoolsIDDisabled = true; 2342 $scope.editIdButtonText = 'Edit ID'; 2343 $scope.CommunityCollection = CommunityCollection; 2344 // remove or replace all URL unsafe characters and set softw
2344are.id 2345 $scope.makeIdURLSafe = function (value) { 2346 if (typeof value != 'undefined' && $scope.biotoolsIDDisabled) { 2347 $scope.software.biotoolsID = value 2348 .replace(/[^a-zA-Z0-9_~ .-]*/g, '') 2349 .replace(/[ ]+/g, '_') 2350 .toLowerCase(); 2351 } else if ($scope.biotoolsIDDisabled) { 2352 $scope.software.biotoolsID = ''; 2353 } 2354 }; 2355 2356 $scope.editIdToggleButtonClick = function () { 2357 $scope.biotoolsIDDisabled = !$scope.biotoolsIDDisabled; 2358 $scope.makeIdURLSafe($scope.software.name); 2359 2360 if ($scope.biotoolsIDDisabled) { 2361 $scope.editIdButtonText = 'Edit ID'; 2362 } else { 2363 $scope.editIdButtonText = 'From Name'; 2364 } 2365 }; 2366 2367 $scope.validateButtonClick = function () { 2368 $timeout(function () { 2369 $scope.sendResource( 2370 ToolCreateValidator.save, 2371 $scope.validationProgress, 2372 false, 2373 'create-validate' 2374 ); 2375 }, 100); 2376 }; 2377 2378 $scope.registerButtonClick = function () { 2379 if ( 2380 confirm( 2381 'Are you sure you want to save the resource?\nOnce saved the tool ID cannot be changed!' 2382 ) 2383 ) { 2384 $timeout(function () { 2385 $scope.sendResource( 2386 ToolListConnection.save, 2387 $scope.savingProgress, 2388 false, 2389 'create' 2390 ); 2391 }, 100); 2392 } 2393 }; 2394 2395 // TODO: needs to keep it DRY and in a service 2396 // function to clean all nulls from tool gotten from API 2397 function cleanNulls(object) { 2398 for (var key in object) { 2399 if (object[key] == null) { 2400 delete object[key]; 2401 } else if (object[key].constructor === Array) { 2402 if (object[key].length == 0) { 2403 delete object[key]; 2404 } else { 2405 for (var i in object[key]) { 2406 cleanNulls(object[key][i]); 2407 } 2408 } 2409 } else if (typeof object[key] === 'object') { 2410 cleanNulls(object[key]); 2411 } 2412 } 2413 } 2414 2415 if (typeof $stateParams.newVersionOf !== 'undefined') { 2416 $scope.newVersion = true; 2417 $scope.software = ToolLatest.get( 2418 { id: $stateParams.newVersionOf }, 2419 function (response) { 2420 // success handler 2421 cleanNulls($scope.software); 2422 $scope.software.version = null; 2423 }, 2424 function (response) { 2425 // error handler 2426 if (response.status == 404) { 2427 $scope.notFound = true; 2428 } 2429 } 2430 ); 2431 } else { 2432 $scope.newVersion = false; 2433 // create the 'empty' software object 2434 $timeout(function () { 2435 $scope.software = { 2436 owner: $scope.User.getUsername(), 2437 name: '', 2438 description: '', 2439 homepage: '', 2440 }; 2441 }, 100); 2442 } 2443 }, 2444 ]) 2445 .controller('OrcidCallbackController', [ 2446 '$scope', 2447 '$state', 2448 'djangoAuth', 2449 '$location', 2450 'OrcidAuth', 2451 function ($scope, $state, djangoAuth, $location, OrcidAuth) { 2452 var code = $location.search().code; 2453 const stateParams = $location.search().state; 2454 2455 const stateCheck = OrcidAuth.consumeState(stateParams); 2456 if (!stateCheck.ok) { 2457 $scope.loginErrors = 'ORCID authentication failed: invali
2457d state parameter'; 2458 return; 2459 } 2460 2461 function goAfterAuth() { 2462 if (stateCheck.payload && stateCheck.payload.name) { 2463 $state.go(stateCheck.payload.name, stateCheck.payload.params || {}); 2464 } else { 2465 $state.go('search'); 2466 } 2467 } 2468 2469 // if user is authenticated call djangoAuth.orcidConnect 2470 if (djangoAuth.authenticated) { 2471 djangoAuth.orcidConnect(code).then( 2472 function () { 2473 $state.go('profile'); 2474 }, 2475 function () { 2476 $scope.loginErrors = 'ORCID connection failed'; 2477 } 2478 ); 2479 } else { 2480 djangoAuth.orcidLogin(code).then( 2481 function () { 2482 goAfterAuth(); 2483 }, 2484 function () { 2485 var error_description = $location.search().error_description; 2486 $scope.loginErrors = error_description || 'ORCID login failed'; 2487 } 2488 ); 2489 } 2490 }, 2491 ]) 2492 .controller('LoginController', [ 2493 '$scope', 2494 '$state', 2495 'djangoAuth', 2496 '$rootScope', 2497 'OrcidAuth', 2498 function ($scope, $state, djangoAuth, $rootScope, OrcidAuth) { 2499 $scope.credentials = {}; 2500 2501 $scope.loginButtonClick = function () { 2502 djangoAuth.login($scope.credentials.username, $scope.credentials.password).then( 2503 function (response) { 2504 // go to states set before redirection to login 2505 if ( 2506 typeof $rootScope.toState == 'undefined' || 2507 /signup/.test($rootScope.toState.name) || 2508 /reset-password/.test($rootScope.toState.name) 2509 ) { 2510 $state.go('search'); 2511 } else { 2512 $state.go($rootScope.toState.name, $rootScope.toStateParams); 2513 } 2514 }, 2515 function (response) { 2516 $scope.loginErrors = response.general_errors; 2517 } 2518 ); 2519 }; 2520 2521 // clean errors when credentials are changed 2522 $scope.$watch( 2523 'credentials', 2524 function () { 2525 if ($scope.loginErrors) { 2526 $scope.loginErrors.pop(); 2527 delete $scope.loginErrors; 2528 } 2529 }, 2530 true 2531 ); 2532 2533 $scope.orcidLoginButtonClick = function () { 2534 OrcidAuth.start(); 2535 }; 2536 }, 2537 ]) 2538 .controller('SignupController', [ 2539 '$scope', 2540 '$state', 2541 'djangoAuth', 2542 '$rootScope', 2543 '$timeout', 2544 'OrcidAuth', 2545 function ($scope, $state, djangoAuth, $rootScope, $timeout, OrcidAuth) { 2546 $scope.credentials = {}; 2547 $scope.error_message = {}; 2548 $scope.error_message.username = ''; 2549 $scope.error_message.email = ''; 2550 $scope.error_message.creation = ''; 2551 2552 // check if username is taken 2553 var initializing_username = true; 2554 $scope.$watch('credentials.username', function (newValue, oldValue, scope) { 2555 if (!initializing_username) { 2556 $scope.error_message.username = ''; 2557 djangoAuth.register($scope.credentials.username, null, null, null, null).then( 2558 function (response) { 2559 // success never happens since parameters are missing 2560 }, 2561 function (response) { 2562 if (response.hasOwnProperty('username')) { 2563 $scope.error_message.username = 'Username is already taken.'; 2564 } 2565 } 2566 ); 2567 } else { 2568 initializing_username = false;
2569 } 2570 }); 2571 2572 // check if email is taken 2573 var initializing_email = true; 2574 $scope.$watch('credentials.email', function (newValue, oldValue, scope) { 2575 if (!initializing_email) { 2576 $scope.error_message.email = ''; 2577 djangoAuth.register(null, null, null, $scope.credentials.email, null).then( 2578 function (response) { 2579 // success never happens since parameters are missing 2580 }, 2581 function (response) { 2582 if (response.hasOwnProperty('email')) { 2583 $scope.error_message.email = 'Email is invalid or already taken.'; 2584 } 2585 } 2586 ); 2587 } else { 2588 initializing_email = false; 2589 } 2590 }); 2591 2592 $scope.signupButtonClick = function () { 2593 $scope.loading = true; 2594 $timeout(function () { 2595 djangoAuth 2596 .register( 2597 $scope.credentials.username, 2598 $scope.credentials.password, 2599 $scope.credentials.password, 2600 $scope.credentials.email, 2601 null 2602 ) 2603 .then( 2604 function (response) { 2605 $state.go('signup.success'); 2606 $scope.loading = false; 2607 }, 2608 function (response) { 2609 $scope.error_message.email = response.email.join(); 2610 $scope.error_message.username = response.username.join(); 2611 $scope.error_message.creation = response.message; 2612 $scope.loading = false; 2613 } 2614 ); 2615 }, 100); 2616 }; 2617 2618 $scope.orcidSignupButtonClick = function () { 2619 OrcidAuth.start(); 2620 }; 2621 }, 2622 ]) 2623 .controller('SignupVerifyEmailKeyController', [ 2624 '$scope', 2625 '$state', 2626 '$stateParams', 2627 'djangoAuth', 2628 function ($scope, $state, $stateParams, djangoAuth) { 2629 $scope.error_message = ''; 2630 2631 djangoAuth.verify($stateParams.key).then( 2632 function (response) { 2633 $state.go('signup.verify-email.success'); 2634 }, 2635 function (response) { 2636 $scope.error_message = response.message; 2637 } 2638 ); 2639 }, 2640 ]) 2641 .controller('ResetPasswordController', [ 2642 '$scope', 2643 '$state', 2644 '$stateParams', 2645 'djangoAuth', 2646 function ($scope, $state, $stateParams, djangoAuth) { 2647 $scope.credentials = {}; 2648 $scope.error_message = ''; 2649 $scope.success_message = ''; 2650 2651 $scope.loading = false; 2652 $scope.resetButtonClick = function () { 2653 $scope.error_message = ''; 2654 $scope.success_message = ''; 2655 $scope.loading = true; 2656 djangoAuth.resetPassword($scope.credentials.email).then( 2657 function (response) { 2658 $scope.success_message = 2659 'If this email is linked to an account, a reset link will be sent.'; 2660 $scope.loading = false; 2661 }, 2662 function (response) { 2663 $scope.error_message = response; 2664 $scope.loading = false; 2665 } 2666 ); 2667 }; 2668 }, 2669 ]) 2670 .controller('ResetPasswordConfirmController', [ 2671 '$scope', 2672 '$state', 2673 '$stateParams', 2674 'djangoAuth', 2675 function ($scope, $state, $stateParams, djangoAuth) { 2676 $scope.credentials = {}; 2677 $scope.error_message = false;
2678 var uid = $stateParams.uid; 2679 var token = $stateParams.token; 2680 2681 $scope.loading = false; 2682 $scope.resetButtonClick = function () { 2683 $scope.error_message = false; 2684 $scope.loading = true; 2685 djangoAuth 2686 .confirmReset( 2687 uid, 2688 token, 2689 $scope.credentials.password, 2690 $scope.credentials.password 2691 ) 2692 .then( 2693 function (response) { 2694 $state.go('reset-password.confirm.success'); 2695 $scope.loading = false; 2696 }, 2697 function (response) { 2698 $scope.error_message = true; 2699 $scope.loading = false; 2700 } 2701 ); 2702 }; 2703 }, 2704 ]); 2705 2706function EdamModalCtrl($uibModalInstance, edam, onto, type, suggestions) { 2707 var vm = this; 2708 vm.data = angular.copy(edam); 2709 vm.onto = onto; 2710 vm.self = $uibModalInstance; 2711 vm.type = type; 2712 vm.suggestions = suggestions; 2713 2714 vm.saveData = function () { 2715 if (isEmptyObject(vm.data)) { 2716 $uibModalInstance.dismiss('cancel'); 2717 return; 2718 } 2719 $uibModalInstance.close(vm.data); 2720 }; 2721 2722 vm.apply_suggestion = function (suggestion) { 2723 vm.predicate = suggestion.term; 2724 }; 2725 2726 vm.customOrder = function (node) { 2727 if (!vm.suggestions) { 2728 return node.text.toLowerCase(); 2729 } 2730 2731 var isSuggested = containsSuggestion(node); 2732 return (isSuggested ? '0' : '1') + node.text.toLowerCase(); 2733 }; 2734 2735 vm.isSuggested = function (node) { 2736 return vm.suggestions && containsSuggestion(node); 2737 }; 2738 2739 vm.cancel = function () { 2740 $uibModalInstance.dismiss('cancel'); 2741 }; 2742 2743 function isEmptyObject(obj) { 2744 return angular.equals(obj, {}); 2745 } 2746 2747 function containsSuggestion(node) { 2748 return vm.suggestions.some(function (suggestion) { 2749 return suggestion.term === node.text; 2750 }); 2751 } 2752} 2753 2754'use strict'; 2755 2756/* Directives */ 2757 2758angular.module('elixir_front.directives', []) 2759.directive('toolList', ['ToolList', 'Highlighting', function(ToolList, Highlighting) { 2760 return { 2761 restrict: 'A', 2762 templateUrl: 'partials/directives/toolList.html', 2763 link: function(scope, element, attrs) { 2764 // reference the ToolList in the scope 2765 scope.ToolList = ToolList; 2766 // reference Highlighting in the scope 2767 scope.Highlighting = Highlighting; 2768 } 2769 } 2770}]) 2771.directive('showMore', 2772 [function(){ 2773 return { 2774 templateUrl: 'showMore.html', 2775 restrict: 'A', 2776 transclude: true, 2777 scope:{ 2778 'showMoreHeight': '@' 2779 }, 2780 controller: ['$scope', '$element', '$interval', function($scope, $element, $interval) { 2781 2782 $scope.expanded = false; 2783 2784 $interval(function(){ 2785 renderStyles(); 2786 }, 300); 2787 2788 $scope.expandable = false;
2789 function renderStyles(){ 2790 if($element.height() >= $scope.showMoreHeight && $scope.expanded === false){ 2791 $scope.expandable = true; 2792 } 2793 } 2794 2795 $scope.showLessStyle = { 2796 'max-height': $scope.showMoreHeight + 'px', 2797 'overflow': 'hidden' 2798 }; 2799 2800 }] 2801 }; 2802 }]) 2803.directive('toolListTable', ['ToolList', 'ToolTableDataSource', 'Highlighting', function(ToolList, ToolTableDataSource, Highlighting) { 2804 return { 2805 restrict: 'A', 2806 templateUrl: 'partials/directives/toolListTable.html', 2807 link: function(scope, element, attrs) { 2808 // reference Highlighting in the scope 2809 scope.Highlighting = Highlighting; 2810 scope.ToolTableDataSource = ToolTableDataSource; 2811 } 2812 } 2813}]) 2814.directive('toolSorter', ['ToolList', '$state','$stateParams', '$timeout', 'ToolSorter', 'Query', '$rootScope', function(ToolList, $state, $stateParams, $timeout, ToolSorter, Query, $rootScope) { 2815 return { 2816 restrict: 'A', 2817 templateUrl: 'partials/directives/sorter.html', 2818 link: function(scope, element, attrs) { 2819 // reference the ToolPaginator in the scope 2820 scope.ToolSorter = ToolSorter; 2821 2822 // put sorting attributes in the url 2823 scope.addToURL = function() { 2824 var params = $stateParams; 2825 // set the sorting url parameters 2826 if (ToolSorter.sortBy.attrName == 'lastUpdate' && ToolSorter.order == true) { 2827 // set defaults as null 2828 params['sort'] = null; 2829 params['ord'] = null; 2830 } else { 2831 params['sort'] = ToolSorter.sortBy.attrName; 2832 params['ord'] = ToolSorter.order ? 'desc' : 'asc'; 2833 } 2834 $state.transitionTo('search', params, { notify: false }); 2835 } 2836 2837 // ran on changing the dropdown value 2838 scope.sortingChangedByDropdown = function() { 2839 scope.addToURL(); 2840 $timeout( function() { 2841 ToolList.refresh(); 2842 }); 2843 } 2844 2845 scope.validateSelectedSortOption = function() { 2846 if (Query.current.length > 0) { 2847 ToolSorter.addScore(); 2848 } else { 2849 ToolSorter.removeScore(); 2850 } 2851 ToolSorter.setSortOption($stateParams['sort']); 2852 } 2853 2854 // read current sorting attribute from url 2855 if ($stateParams['sort'] != null) { 2856 if ($stateParams['sort'] == 'score') { 2857 ToolSorter.addScore(); 2858 } 2859 ToolSorter.sortBy = ToolSorter.list[_.findIndex(ToolSorter.list, {"attrName": $stateParams['sort']}) || 0]; 2860 } 2861 // read current sorting order from url 2862 if ($stateParams['ord'] != null) { 2863 if ($stateParams['ord'] == 'desc') { 2864 ToolSorter.order = true; 2865 } else if($stateParams['ord'] == 'asc'){ 2866 ToolSorter.order = false; 2867 } 2868 } 2869 2870 $rootScope.$on('$stateChangeSuccess', function(event, toState, toStateParams, fromState, fromStateParams) { 2871 scope.validateSelectedSortOption(); 2872 }); 2873 2874 $rootScope.$on('search-bar-tags-changed', function handler() { 2875 scope.validateSelectedSortOption(); 2876 }); 2877 2878 2879 } 2880 } 2881}]) 2882.directive('displayModeSelector', ['ToolList', '$state','$stateParams', '$timeout', 'DisplayModeSelector', function(ToolList, $state, $stateParams, $timeout, DisplayModeSelector) { 2883 return { 2884 restrict: 'A', 2885 templateUrl: 'partials/directives/displaySelector.html', 2886 link: function(scope, element, attrs) { 2887 scope.DisplayModeSelector = DisplayModeSelector; 2888 2889 // put sorting attributes in the url 2890 scope.modeChanged = function() { 2891 $timeout( function() { 2892 ToolList.refresh(); 2893 }); 2894 } 2895 } 2896 } 2897}]) 2898.directive('toolPaginator', ['ToolList', '$state','$stateParams', 'Query', '$timeout', 'ToolPaginator', function(ToolList, $state, $stateParams, Query, $timeout, ToolPaginator) { 2899 return { 2900 restrict: 'A', 2901 template: '<div style="display: flex; align-items: center; margin-top: 25px; margin-bottom: 25px;"><ul uib-pagination total-items="ToolList.count" ng-model="ToolPaginator.currentPage" items-per-page="ToolPaginator.pageSize" max-size="ToolPaginator.maxSize" ng-change="pageChanged()" class="pagination-sm pagination-top" boundary-links="true" rotate="true" ng-disabled="ToolList.loading"></ul><div style="margin-left: 15px; display: flex; align-items: center;"><label for="pageInput" style="margin-right: 5px;">Go to page:</label><input type="number" id="pageInput" ng-model="inputPage" min="1" max="{{Math.ceil(ToolList.count / ToolPaginator.pageSize)}}" ng-keypress="handlePageInput($event)" style="width: 60px;" /><label style="margin-left: 5px; margin-right: 5px;">Page size:</label><select ng-model="ToolPaginator.pageSize" ng-change="pageSizeChanged()"><option ng-repeat="size in ToolPaginator.availablePageSizes" ng-value="size">{{size}}</option></select></div></div>', 2902 link: function(scope, element, attrs) { 2903 // reference the Query in the scope 2904 scope.Query = Query; 2905 // reference the ToolPaginator in the scope 2906 scope.ToolPaginator = ToolPaginator; 2907 2908 // read current page from url 2909 if ($stateParams['page'] != null) { 2910 ToolPaginator.currentPage = parseInt($stateParams['page']); 2911 } else { 2912 ToolPaginator.currentPage = 1; 2913 } 2914 2915 // when page changes, store the new page in the url and refresh the list of tools
2916 scope.pageChanged = function() { 2917 var params = $stateParams; 2918 $timeout( function() { 2919 // set the 'page' URL parameter according to currentPage 2920 params['page'] = ToolPaginator.currentPage != 1 ? ToolPaginator.currentPage : null; 2921 $state.transitionTo('search', params, { notify: false }); 2922 ToolList.refresh(); 2923 }); 2924 } 2925 2926 // Allow direct page navigation via input 2927 scope.handlePageInput = function(event) { 2928 if (event.key === 'Enter') { // Navigate on pressing Enter 2929 if ( 2930 scope.inputPage >= 1 && 2931 scope.inputPage <= Math.ceil(ToolList.count / ToolPaginator.pageSize) 2932 ) { 2933 ToolPaginator.currentPage = scope.inputPage; 2934 scope.pageChanged(); 2935 } else { 2936 alert('Invalid page number.'); 2937 } 2938 } 2939 }; 2940 2941 // page size 2942 scope.pageSizeChanged = function() { 2943 ToolPaginator.currentPage = 1; 2944 var params = $stateParams; 2945 params['per_page'] = ToolPaginator.pageSize; 2946 params['page'] = null; 2947 $state.transitionTo('search', params, { notify: false }); 2948 ToolList.refresh(); 2949 }; 2950 2951 // riddiculous hax, otherwise it keeps changing to 1 2952 var initializing = true; 2953 scope.$watch('ToolPaginator.currentPage', function(newVal, oldVal) { 2954 if (initializing) { 2955 if (newVal != oldVal && newVal == 1) {ToolPaginator.currentPage = oldVal}; 2956 $timeout(function() { initializing = false; }); 2957 } 2958 }); 2959 2960 // reset page to 1 when query changes 2961 scope.$watch('Query.current', function() { 2962 ToolPaginator.currentPage = 1; 2963 }, true); 2964 2965 } 2966 } 2967}]) 2968 2969// inserts a link to a publication resolving page, depending on the type of publication 2970.directive("insertPublicationLink", function($compile){ 2971 return { 2972 restrict: 'A', 2973 scope: { 2974 publication: '=' 2975 }, 2976 compile: function CompilingFunction($templateElement, $templateAttributes) { 2977 return function LinkingFunction($scope, $element, $attrs) { 2978 var html = ''; 2979 // PUBMED 2980 if (/^\d{7,8}$/.test($scope.publication)) { 2981 html = '<span>PUBMED:</span> <a href="http://www.ncbi.nlm.nih.gov/pubmed/' + $scope.publication + '">' + $scope.publication + '</a>'; 2982 // PMC 2983 } else if (/^PMC\d{6,8}$/.test($scope.publication)) { 2984 html = '<span>PMC:</span> <a href="http://www.ncbi.nlm.nih.gov/pmc/' + $scope.publication + '">' + $scope.publication + '</a>'; 2985 // DOI 2986 } else if (/^(doi:)?\d{2}.\d{4}/.test($scope.publication)) { 2987 var publication = $scope.publication.replace('doi:',''); 2988 html = '<span>DOI:</span> <a href="https://dx.doi.org/' + publication + '">' + publication + '</a>'; 2989 } 2990 var e = $compile(html)($scope); 2991 $element.replaceWith(e); 2992 }; 2993 } 2994} 2995}) 2996// inserts a link to a publication resolving page, depending on the type of publication 2997.directive("insertPublicationLinkIcon", function($compile){ 2998 return { 2999 restrict: 'A', 3000 scope: { 3001 publication: '=' 3002 }, 3003 compile: function CompilingFunction($templateElement, $templateAttributes) { 3004 return function LinkingFunction($scope, $element, $attrs) { 3005 var html = ''; 3006 // PUBMED 3007 if (/^\d{7,8}$/.test($scope.publication)) { 3008 html = '<a href="http://www.ncbi.nlm.nih.gov/pubmed/' + $scope.publication + '"><i class="fa fa-external-link" aria-hidden="true" uib-tooltip="PUBMED" tooltip-append-to-body="true"></i></a>'; 3009 // PMC 3010 } else if (/^PMC\d{6,8}$/.test($scope.publication)) { 3011 html = '<a href="http://www.ncbi.nlm.nih.gov/pmc/' + $scope.publication + '"><i class="fa fa-external-link" aria-hidden="true" uib-tooltip="PMC" tooltip-append-to-body="true"></i></a>'; 3012 // DOI 3013 } else if (/^(doi:)?\d{2}.\d{4}/.test($scope.publication)) { 3014 var publication = $scope.publication.replace('doi:',''); 3015 html = '<a href="https://dx.doi.org/' + publication + '"><i class="fa fa-external-link" aria-hidden="true" uib-tooltip="DOI" tooltip-append-to-body="true"></i></a>'; 3016 } 3017 var e = $compile(html)($scope); 3018 $element.replaceWith(e); 3019 }; 3020 } 3021} 3022}) 3023// makes this element focused 3024.directive('focus', function () { 3025 return { 3026 restrict: 'A', 3027 link: function(scope, iElm, iAttrs, controller) { 3028 iElm[0].focus(); 3029 } 3030 } 3031}) 3032// creates an animated spinner 3033.directive('spinner', function() { 3034 return { 3035 restrict: 'A', 3036 replace: 'true', 3037 template: '<div class="adjust"><div class="spinner"></div></div>' 3038 3039 } 3040}) 3041// creates a resource not found message 3042.directive('resourceNotFoundDialog', function() { 3043 return { 3044 restrict: 'A', 3045 replace: 'true', 3046 template: '<div class="col-md-12 info-dialog-background info-dialog info-dialog-text">We\'re sorry but we couldn\'t find what you were looking for. You can try searching for the resource you need <a ui-sref="search()">here</a>.</div>' 3047 } 3048}) 3049// creates an error message 3050.directive('errorDialog', function() { 3051 return { 3052 restrict: 'A', 3053 replace: true, 3054 template: function(elem, attr){ 3055 return '<div class="col-md-offset-2 col-md-8 bs-callout bs-callout-danger" style="margin-top: 80px;"><h4><i class="fa fa-exclamation-circle" aria-hidden="true"></i> Error {{' + attr.errorcode + '}}</h4>{{' + attr.errormessage + '}}</div>' 3056 }, 3057 transclude: true 3058 } 3059}) 3060// make this element ignore pressing the 'Enter' key 3061.directive('ignoreEnter', function() { 3062 return function (scope, element, attrs) { 3063 element.bind("keydown keypress", function (event) { 3064 if (event.which === 13) { 3065 event.preventDefault(); 3066 } 3067 }); 3068 } 3069}) 3070.directive('validateEditResourceField', function () { 3071 return { 3072 restrict: 'A', 3073 require: ['^form','ngModel'], 3074 link: function (scope, element, attrs, ctrls) { 3075 var ngForm = ctrls[0]; 3076 var ngModel = ctrls[1]; 3077 // Initialization - create the data path based on ng-model path. 3078 var dataPath = attrs.ngModel.replace("software.", "").replace("$parent.$index", attrs.parentIndex).replace("$index", attrs.index).replace("[", ".").replace("]", "").split("."); 3079 // Remove errors once values have been edited. 3080 ngModel.$parsers.push(function(value) { 3081 _.set(scope.registrationErrorPayload, dataPath, null); 3082 ngModel.errorMessages = []; 3083 ngModel.$setValidity('server_validation_passed', true); 3084 return value; 3085 }); 3086 ngModel.$viewChangeListeners.push(function() { 3087 // Prevent non-mandatory fields from becoming mandatory, 3088 // by assigning empty strings to the keys in the object to be 3089 // sent to the server. 3090 var value = _.get(scope.software, dataPath); 3091 if (value === "") { 3092 _.unset(scope.software, dataPath); 3093 } 3094 }); 3095 // Observe for changes in the server-side validation 3096 // of a resource appeared - i.e. user pressed validate in resource edit 3097 scope.$watch('registrationErrorPayload', function(date) { 3098 // Find the error and set the input validity based on the results. 3099 var error = _.result(scope.registrationErrorPayload, dataPath); 3100 if (_.isEmpty(error) == false) { 3101 // Error exist - server validation failed. 3102 ngModel.$setValidity('server_validation_passed', false); 3103 ngModel.errorMessages = error; 3104 } 3105 else { 3106 // Error does not exist - server validation passed. 3107 ngModel.$setValidity('server_validation_passed', true); 3108 ngModel.errorMessages = []; 3109 } 3110 // Find the error and set the form validity based on the results. 3111 var formError = extractFormErrors(); 3112 // There are errors in the form. 3113 if (formError.length > 0) { 3114 ngForm.$setValidity('server_validation_passed', false); 3115 ngForm.errorMessages = _.union(ngForm.errorMessages, formError); 3116 } 3117 else { 3118 ngForm.$setValidity('server_validation_passed', true); 3119 ngForm.errorMessages = []; 3120 } 3121 }); 3122 var extractFormErrors = function() { 3123 var formErrors = []; 3124 for (var i = 1; i < dataPath.length; i++) { 3125 var subDataPath = dataPath.slice(0,i); 3126 subDataPath.push("general_errors"); 3127 var error = _.result(scope.registrationErrorPayload, subDataPath); 3128 if (_.isEmpty(error) == false) { 3129 formErrors = formErrors.concat(error); 3130 } 3131 } 3132 return formErrors; 3133 } 3134 } 3135 } 3136}) 3137.directive("dividerLabel", function(){ 3138 return { 3139 restrict: 'A', 3140 transclude: true, 3141 template: function(elem, attr){ 3142 var callout = 'class="tool-page-diver-label"'; 3143 return callout; 3144 }, 3145 replace: true 3146 }; 3147}) 3148// creates an animated small spinner 3149.directive('smallSpinner', function() { 3150 return { 3151 restrict: 'A', 3152 replace: 'true', 3153 template: '<div style="text-align:center; color: #7f7f7f; font-size: 12px;"><i class="fa fa-circle-o-notch fa-spin"></i> Processing...</div>' 3154 } 3155}) 3156.directive('largeSpinner', function() { 3157 return { 3158 restrict: 'A', 3159 replace: 'true', 3160 template: '<div style="text-align:center; color: #cccccc; font-size: 60px;"><i class="fa fa-circle-o-notch fa-spin"></i></div>' 3161 } 3162}); 3163 3164 3165 3166 3167'use strict'; 3168 3169/* Filters */ 3170 3171angular.module('elixir_front.filters', []). 3172// necessary for paging 3173filter('startFrom', function() { 3174 return function(input, start) { 3175 start = +start; //parse to int 3176 if (input) { 3177 return input.slice(start); 3178 } else { 3179 return; 3180 } 3181 } 3182}). 3183// filtering of a list of object by an object - used in filters in query interface 3184filter('filterByObject', function($filter) { 3185 return function (list, filter) { 3186 // recursively go through list entry and filter objects to find a match 3187 function filterMatch(entry, filter) { 3188 if (typeof filter === 'undefined') { 3189 return true; 3190 } else if (entry == null) { 3191 return false;
3192 } 3193 var result = false; 3194 for (var _key in filter) { 3195 result = false; 3196 // return true if filter empty 3197 if (typeof filter[_key] === 'undefined' || filter[_key] == '') { 3198 result = true; 3199 // skip if entry does not have the key from the filter 3200 } else if (typeof entry[_key] === 'undefined') { 3201 result = false; 3202 // if filter is not a deeper object nor an array just perform normal comparison 3203 } else if (typeof filter[_key] !== 'object') { 3204 // if entry is not a deeper object nor an array just perform normal comparison 3205 if (typeof entry[_key] !== 'object') { 3206 result = entry[_key].toLowerCase().indexOf(filter[_key].toLowerCase()) > -1 3207 // if entry is a deeper object perform deeper checking 3208 } else { 3209 // if entry is an array 3210 if (Array.isArray(entry[_key])) { 3211 // need a boolean to store whether any of the former array elements matched the filter; need this otherwise result could be overwritten with latter elements, even if the former ones matched 3212 var result_inner = false; 3213 // go through the whole entry array and check each element against the filter 3214 for (var j = 0; j < entry[_key].length; j++) { 3215 // if entry is a string, just perform normal comparison 3216 if (typeof entry[_key][j] == "string") { 3217 result_inner = entry[_key][j].toLowerCase().indexOf(filter[_key].toLowerCase()) > -1 3218 // if entry is an ontology only compare the term and not the URI to the filter 3219 } else if ('term' in entry[_key][j]) { 3220 result_inner = entry[_key][j].term.toLowerCase().indexOf(filter[_key].toLowerCase()) > -1 3221 // if entry is a different object then search for filter within it 3222 } else { 3223 // need to go through all properties of an entry to see if any matches are found 3224 for (var property in entry[_key][j]) { 3225 if(entry[_key][j].hasOwnProperty(property)) { 3226 if (JSON.stringify(entry[_key][j][property]).toLowerCase().indexOf(filter[_key].toLowerCase()) > -1) { 3227 result_inner = true; 3228 break; 3229 } 3230 } 3231 } 3232 } 3233 // if any of array elements matches, set result to true; this way even if the former elements match, the unmatching latter ones will not overwrite result 3234 if (result_inner) { 3235 result = true; 3236 break; 3237 } 3238 } 3239 // if entry is not an array 3240 } else { 3241 // if entry is an ontology only compare the term and not the URI to the filter 3242 if ('term' in entry[_key]) { 3243 result = entry[_key].term.toLowerCase().indexOf(filter[_key].toLowerCase()) > -1 3244 // if entry is a different object then search for filter within it 3245 } else { 3246 // need to go through all properties of an entry to see if any matches are found 3247 for (var property in entry[_key]) { 3248 if(entry[_key].hasOwnProperty(property)) { 3249 if (JSON.stringify(entry[_key][property]).toLowerCase().indexOf(filter[_key].toLowerCase()) > -1) { 3250 result = true; 3251 break; 3252 } 3253 } 3254 } 3255 } 3256 } 3257 } 3258 // if entry is an array and not an object 3259 } else if (Array.isArray(entry[_key])) { 3260 // go through the array and run the filter recursively 3261 for (var j = 0; j < entry[_key].length; j++) { 3262 if (filterMatch(entry[_key][j], filter[_key])) { 3263 result = true; 3264 break; 3265 } 3266 } 3267 } else { 3268 // if filter is a deeper object, call the same function recursively 3269 result = filterMatch(entry[_key], filter[_key]); 3270 } 3271 // if one of the filters didn't match return false 3272 if (!result) { 3273 return false; 3274 } 3275 } 3276 // return true only if all the elements matched 3277 return true; 3278 } 3279 3280 // list of matching elements 3281 var return_list = []; 3282 if (list) { 3283 // go through all elements 3284 for (var i=0; i < list.length; i++) { 3285 // add element to list if filter matches 3286 if ( filterMatch(list[i], filter) == true ) { 3287 return_list.push(list[i]); 3288 } 3289 } 3290 } 3291 return return_list; 3292 }; 3293}). 3294// query fuzzy matching which produces the relevance score 3295filter('filterQuery', function() { 3296 return function (list, filter) { 3297 // if filter empty return all resources 3298 if (filter == "" || filter == null) { 3299 return list; 3300 } 3301 3302 // list of matching elements 3303 var return_list = []; 3304 if (list && list.length > 0) { 3305 // go through all elements 3306 for (var i=0; i < list.length; i++) { 3307 if (!_.isEmpty(list[i])){ 3308 // calculate the fuzzy score of some of the attributes of each resource vs the filter 3309 var search_score = (list[i].name + " " + list[i].description + " " + list[i].topic + " " + list[i].function + " " + list[i].affiliation).score(filter); 3310 // if there is a non-zero match 3311 if ( search_score > 0 ) { 3312 // invert the score for sorting purposes 3313 list[i].search_score = 1.0 - search_score; 3314 return_list.push(list[i]); 3315 } 3316 } 3317 } 3318 } 3319 return return_list; 3320 } 3321}). 3322// filter that highlights matches 3323filter('highlight', function($sce) { 3324 return function(str, termsToHighlight) { 3325 if (str && termsToHighlight.length > 0) { 3326 // Sort terms by length 3327 termsToHighlight.sort(function(a, b) { 3328 return b.length - a.length; 3329 }); 3330 // Regex to simultaneously replace terms 3331 var regex = new RegExp('(' + termsToHighlight.join('|') + ')', 'gi'); 3332 return $sce.trustAsHtml(str.replace(regex, '<span class="highlightedTerm">$&</span>')); 3333 } else { 3334 return str; 3335 } 3336 }; 3337}); 3338// Controllers 3339angular.module('elixir_front.controllers').controller('LoginMangerController', ['$scope', '$uibModal', '$rootScope', '$state', function($scope, $uibModal, $rootScope, $state) { 3340 var vm = this; 3341 3342 vm.openLoginModal = function() { 3343 $uibModal.open({ 3344 templateUrl: 'partials/modals/loginModal.html', 3345 controller: ['$uibModalInstance', 'djangoAuth', '$rootScope', '$state', 'OrcidAuth', LoginModalController], 3346 controllerAs: 'vm', 3347 windowClass: 'login-modal-window' 3348 }); 3349 }; 3350}]); 3351 3352function LoginModalController($uibModalInstance, djangoAuth, $rootScope, $state, OrcidAuth) { 3353 var vm = this; 3354 vm.error = {}; 3355 3356 vm.loginButtonPressed = function(username, password) { 3357 vm.error.username = ""; 3358 vm.error.password = ""; 3359 vm.error.general = []; 3360 if (!username && !password) { 3361 vm.error.username = "Username is required"; 3362 vm.error.password = "Password is required"; 3363 vm.error.general = ["Username and password are required"]; 3364 } 3365 else if (!username) { 3366 vm.error.username = "Username is required"; 3367 vm.error.general = ["Username is required"]; 3368 } 3369 else if (!password) { 3370 vm.error.password = "Password is required"; 3371 vm.error.general = ["Password is required"]; 3372 } 3373 else { 3374 djangoAuth.login(username, password).then(function (response) { 3375 // go to states set before redirection to login 3376 if(typeof $rootScope.toState == 'undefined' || /signup/.test($rootScope.toState.name) || /reset-password/.test($rootScope.toState.name)) { 3377 $state.go('search'); 3378 } else { 3379 $state.go($rootScope.toState.name, $rootScope.toStateParams); 3380 } 3381 $uibModalInstance.close(); 3382 }, function (response) { 3383 vm.error.general = response.general_errors; 3384 }); 3385 } 3386 } 3387 3388 vm.registerButtonPressed = function() { 3389 $uibModalInstance.close(); 3390 $state.go('signup'); 3391 } 3392 3393 vm.forgotButtonPressed = function() { 3394 $uibModalInstance.close(); 3395 $state.go('reset-password'); 3396 } 3397 3398 vm.orcidLoginPressed = function() { 3399 OrcidAuth.start(); 3400 } 3401} 3402 3403(function() { 3404 'use strict'; 3405 3406 angular.module('elixir_front') 3407 .service('OrcidAuth', ['AppConfig', '$window', '$rootScope', function(AppConfig, $window, $rootScope) { 3408 3409 function randomState() {
3410 try { 3411 var arr = new Uint8Array(16); 3412 ($window.crypto || $window.msCrypto).getRandomValues(arr); 3413 return Array.prototype.map.call(arr, function(b){ return ('00' + b.toString(16)).slice(-2); }).join(''); 3414 } catch (e) { 3415 return Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2); 3416 } 3417 } 3418 3419 function saveState(stateToken) { 3420 // Persist state + intended return route 3421 sessionStorage.setItem('orcid_oauth_state', stateToken); 3422 3423 var payload = null; 3424 if ($rootScope.toState && $rootScope.toState.name && 3425 !/signup/.test($rootScope.toState.name) && 3426 !/reset-password/.test($rootScope.toState.name)) { 3427 payload = { name: $rootScope.toState.name, params: $rootScope.toStateParams || {} }; 3428 } 3429 sessionStorage.setItem('orcid_oauth_state_payload', JSON.stringify(payload)); 3430 } 3431 3432 function buildAuthorizeUrl(stateToken) { 3433 var base = AppConfig.ORCID_BASE_URL || 'https://orcid.org'; 3434 var params = new URLSearchParams({ 3435 client_id: AppConfig.ORCID_CLIENT_ID || '', 3436 response_type: 'code', 3437 scope: '/authenticate', 3438 redirect_uri: AppConfig.ORCID_REDIRECT_URI || '', 3439 state: stateToken 3440 }); 3441 return base.replace(/\/+$/,'') + '/oauth/authorize?' + params.toString(); 3442 } 3443 3444 this.start = function() { 3445 var state = randomState(); 3446 saveState(state); 3447 var url = buildAuthorizeUrl(state); 3448 $window.location.replace(url); 3449 }; 3450 3451 this.consumeState = function(received) { 3452 var expected = sessionStorage.getItem('orcid_oauth_state'); 3453 var payload = sessionStorage.getItem('orcid_oauth_state_payload'); 3454 3455 // cleanup 3456 sessionStorage.removeItem('orcid_oauth_state'); 3457 sessionStorage.removeItem('orcid_oauth_state_payload'); 3458 3459 return { 3460 ok: !!received && received === expected, 3461 payload: payload ? JSON.parse(payload) : null 3462 }; 3463 }; 3464 }]); 3465})(); 3466// Controllers 3467angular.module('elixir_front.controllers').controller('PermissionsController', ['$scope', 'UserSuggestionsProvider', function($scope, UserSuggestionsProvider) { 3468 var vm = this; 3469 3470 // Typeahead Support 3471 $scope.userSuggestions = function(prefix) { 3472 return UserSuggestionsProvider.getSuggestions(prefix).then(function(data) { 3473 var suggestions = _.map(data, function(obj){ 3474 return obj.username; 3475 }); 3476 return _.difference(suggestions, $scope.software.editPermission.authors); 3477 }); 3478 }; 3479 3480 $scope.userSelected = function($item, $model, $label) { 3481 // Initialize authors if not present. 3482 if ($scope.software.editPermission.authors == undefined) { 3483 $scope.software.editPermission.authors = []; 3484 } 3485 // Clear the input field on selection. 3486 $scope.userSuggestion = ''; 3487 $scope.software.editPermission.authors.push($model); 3488 }; 3489 3490 $scope.isSoftwareOwner = function() { 3491 if ($scope.software.owner == $scope.User.getUsername()) { 3492 return true; 3493 } 3494 return false; 3495 } 3496 3497 $scope.deleteUser = function(index) { 3498 $scope.software.editPermission.authors.splice(index, 1); 3499 } 3500}]); 3501 3502// Services and Factories 3503var elixir_front = angular.module('elixir_front'); 3504elixir_front.factory('UserSuggestionsProvider', ['$http', function ($http) { 3505 return { 3506 getSuggestions: function(prefix) { 3507 return $http({ 3508 method: 'GET', 3509 url: '/api/user-list', 3510 params: {term: prefix} 3511 }).then(function successCallback(response) { 3512 return response.data; 3513 }, function errorCallback(response) { 3514 return {}; 3515 }) 3516 } 3517 }; 3518}]); 3519 3520// Controllers 3521angular.module('elixir_front.controllers').controller('RequestsController', ['$scope', 'ResourceRequestProvider', 'User', function($scope, ResourceRequestProvider, User) { 3522 var vm = this; 3523 $scope.requests = {}; 3524 $scope.received = []; 3525 $scope.sent = []; 3526 $scope.loading = true; 3527 $scope.error = {} 3528 $scope.error.status = 0; 3529 $scope.error.message = ""; 3530 $scope.filters = {}; 3531 $scope.filters.showCompletedReceived = false; 3532 $scope.filters.showCompletedSent = false; 3533 3534 // Data feteching 3535 vm.loadRequests = function($item, $model, $label) { 3536 $scope.loading = true; 3537 ResourceRequestProvider.getRequests().then(function successCallback(response) { 3538 $scope.requests = response.data.requests; 3539 $scope.filterSentRequests(); 3540 $scope.filterReceivedRequests(); 3541 $scope.loading = false;
3542 }, function errorCallback(response) { 3543 $scope.error.status = response.status; 3544 $scope.error.message = "There was an error communicating with the server. Try refreshing this website."; 3545 $scope.loading = false; 3546 }); 3547 }; 3548 3549 $scope.filterSentRequests = function() { 3550 if ($scope.filters.showCompletedSent == false) { 3551 $scope.sent = $scope.requests.sent.filter(vm.checkIfNotCompleted); 3552 } 3553 else { 3554 $scope.sent = $scope.requests.sent; 3555 } 3556 } 3557 3558 $scope.filterReceivedRequests = function() { 3559 if ($scope.filters.showCompletedReceived == false) { 3560 $scope.received = $scope.requests.received.filter(vm.checkIfNotCompleted); 3561 } 3562 else { 3563 $scope.received = $scope.requests.received; 3564 } 3565 } 3566 3567 //Operations 3568 vm.checkIfNotCompleted = function(request) { 3569 return !request.completed; 3570 } 3571 3572 vm.requestsAffectedByAcceptingRequest = function(request) { 3573 // Accept ownership for not own request. 3574 if (request.type == "ownership" && request.username != User.current.username) { 3575 var affectedRequests = []; 3576 for (var index = 0; index < $scope.requests.received.length; ++index) { 3577 var checkRequest = $scope.requests.received[index]; 3578 if (checkRequest.completed == false && checkRequest.requestId != request.requestId && checkRequest.resourceId == request.resourceId) { 3579 affectedRequests.push(checkRequest); 3580 } 3581 } 3582 return affectedRequests; 3583 } 3584 return []; 3585 } 3586 3587 $scope.typeDescription = function(type) { 3588 if (type == "editing") { 3589 return "Editing rights"; 3590 } 3591 return "Resource ownership"; 3592 } 3593 3594 $scope.acceptRequest = function(request) { 3595 var affectedRequests = vm.requestsAffectedByAcceptingRequest(request); 3596 var resourceIndex = $scope.requests.received.indexOf(request); 3597 if (affectedRequests.length == 0) { 3598 vm.processAcceptRequest(resourceIndex, request, affectedRequests); 3599 } 3600 else { 3601 if (confirm("Are you sure you want to accept this request? " + affectedRequests.length + " of the exisitng requests will be sent to the new owner.")) { 3602 vm.processAcceptRequest(resourceIndex, request, affectedRequests); 3603 } 3604 } 3605 } 3606 3607 vm.processAcceptRequest = function(index, request, affectedRequests) { 3608 var resourceIndex = index; 3609 $scope.requests.received[resourceIndex].processing = true 3610 request.processing = false 3611 ResourceRequestProvider.acceptRequest(request.requestId).then(function successCallback(response) { 3612 $scope.requests.received[resourceIndex].processing = false 3613 $scope.requests.received[resourceIndex].completed = true 3614 $scope.requests.received[resourceIndex].accepted = true 3615 $scope.requests.received = _.difference($scope.requests.received, affectedRequests); 3616 $scope.received = _.difference($scope.received, affectedRequests); 3617 request.processing = false 3618 request.completed = true 3619 request.accepted = true 3620 User.current.requests_count = User.current.requests_count - 1 - affectedRequests.length; 3621 }, function errorCallback(response) { 3622 vm.loadRequests(); 3623 }); 3624 } 3625 3626 $scope.declineRequest = function(request) { 3627 var resourceIndex = $scope.requests.received.indexOf(request); 3628 request.processing = false 3629 $scope.requests.received[resourceIndex].processing = true 3630 ResourceRequestProvider.declineRequest(request.requestId).then(function successCallback(response) { 3631 $scope.requests.received[resourceIndex].processing = false 3632 $scope.requests.received[resourceIndex].completed = true 3633 $scope.requests.received[resourceIndex].accepted = false 3634 request.processing = false 3635 request.completed = true 3636 request.accepted = false 3637 User.current.requests_count = User.current.requests_count - 1; 3638 }, function errorCallback(response) { 3639 vm.loadRequests(); 3640 }); 3641 } 3642 3643 // Initialization 3644 vm.loadRequests(); 3645}]); 3646 3647// Services and Factories 3648var elixir_front = angular.module('elixir_front'); 3649elixir_front.factory('ResourceRequestProvider', ['$http', function ($http) { 3650 return { 3651 getRequests: function() { 3652 return $http({ 3653 method: 'GET', 3654 url: '/api/request', 3655 params: {} 3656 }) 3657 }, 3658 acceptRequest: function(requestId) { 3659 return $http({ 3660 method: 'POST', 3661 url: '/api/request/conclude', 3662 data: {'requestId': requestId, accept: true} 3663 }) 3664 }, 3665 declineRequest: function(requestId) { 3666 return $http({ 3667 method: 'POST', 3668 url: '/api/request/conclude', 3669 data: {'requestId': requestId, accept: false} 3670 }) 3671 }, 3672 requestOwnership: function(resourceId) { 3673 return $http({ 3674 method: 'PUT', 3675 url: '/api/request', 3676 data: {'resourceId': resourceId, type: 'ownership'} 3677 }) 3678 }, 3679 requestEditingRights: function(resourceId) { 3680 return $http({ 3681 method: 'PUT', 3682 url: '/api/request', 3683 data: {'resourceId': resourceId, type: 'editing'} 3684 }) 3685 } 3686 }; 3687}]); 3688'use strict'; 3689 3690/* Services */ 3691 3692angular.module('elixir_front.services', []) 3693.service('Alert', function(){ 3694 this.list = []; 3695 this.add = function(_message, _type) { 3696 this.list.push({msg: _message, type: _type, ttl:2}); 3697 }; 3698 this.remove = function(_index) { 3699 this.list.splice(_index, 1); 3700 } 3701}) 3702.service('User', [function(){ 3703 this.current = {}; 3704 return { 3705 isLoggedIn: function () { 3706 return this.authenticated == true; 3707 }, 3708 getUsername: function () { 3709 if (!_.isEmpty(this.current)) { 3710 return this.current.username; 3711 } 3712 }, 3713 isSuperuser: function () { 3714 if (!_.isEmpty(this.current)) { 3715 return this.current.is_superuser; 3716 } 3717 }, 3718 3719 } 3720}]) 3721.service('Query', [function(){ 3722 this.current = []; 3723}]) 3724.service('DomainConnection', ['$resource', function($resource){ 3725 return $resource('/api/d/', null, { 3726 'query': { 3727 isArray: true, 3728 method:'GET' 3729 }, 3730 'create': { 3731 method:'POST' 3732 } 3733 }) 3734}]) 3735.service('DomainDetailConnection', ['$resource', function($resource){ 3736 return $resource('/api/d/:domain', {'domain': '@domain'}, { 3737 'query': { 3738 isArray:false, 3739 method:'GET' 3740 }, 3741 'update': { 3742 method:'PUT' 3743 }, 3744 'delete': { 3745 method:'DELETE' 3746 } 3747 }) 3748}]) 3749.service('Domain', ['DomainDetailConnection', function(DomainDetailConnection){ 3750 var _this = this; 3751 this.current = {}; 3752 this.loaded = false;
3753 this.set = function(domain) { 3754 _this.current = domain; 3755 } 3756 this.load = function(domain) { 3757 if (typeof domain != 'undefined') { 3758 var response = DomainDetailConnection.query({'domain':domain}, function(response) { 3759 _this.set(response.data); 3760 _this.loaded = true; 3761 }); 3762 } else { 3763 _this.loaded = false; 3764 } 3765 } 3766 this.isLoaded = function() { 3767 return _this.loaded; 3768 } 3769 this.hasSubdomain = function() { 3770 return !_.isEmpty(_this.current); 3771 } 3772 this.hasTitle = function() { 3773 return _this.current.title !== undefined; 3774 } 3775 this.hasSubTitle = function() { 3776 return _this.current.sub_title !== undefined; 3777 } 3778 this.hasDescription = function() { 3779 return _this.current.description !== undefined; 3780 } 3781}]) 3782.service('Highlighting', [function(){ 3783 var _this = this; 3784 this.terms = []; 3785 this.purge = function() { 3786 while (_this.terms.length > 0) { 3787 _this.terms.pop(); 3788 } 3789 } 3790 this.set = function(query_array) { 3791 _this.purge(); 3792 for (var i in query_array) { 3793 _this.terms.push(query_array[i].text.replace(/['"]/g,"")); 3794 } 3795 } 3796}]) 3797.service('ToolListConnection', ['$resource', function($resource){ 3798 return $resource('/api/t/', null, { 3799 'query': { 3800 isArray:false, 3801 method:'GET' 3802 }, 3803 'update': { 3804 method:'PUT' 3805 } 3806 }) 3807}]) 3808.service('ToolListOverviewConnection', ['$resource', function($resource){ 3809 return $resource('/api/tool-list/', null, { 3810 'query': { 3811 isArray:true, 3812 method:'GET' 3813 } 3814 }) 3815}]) 3816.service('ToolList', ['$stateParams', 'ToolListConnection', 'ToolPaginator', function($stateParams, ToolListConnection, ToolPaginator){ 3817 var _this = this; 3818 this.count = 0; 3819 this.list = []; 3820 this.loading = true; 3821 3822 this.refresh = function() { 3823 // enable spinner 3824 this.loading = true; 3825 3826 // prepare API parameters including per_page 3827 var apiParams = angular.copy($stateParams); 3828 if (ToolPaginator.pageSize && ToolPaginator.pageSize !== 50) { 3829 apiParams.per_page = ToolPaginator.pageSize; 3830 } 3831 3832 // get tools 3833 var response = ToolListConnection.query(apiParams, function() { 3834 // disable spinner 3835 _this.loading = false; 3836 _this.count = response.count; 3837 _this.list = response.list; 3838 }); 3839 } 3840}]) 3841.factory('MetaService', function($document, $window) { 3842 function setAlternate(type, href, title) { 3843 if (!href) return; 3844 // Remove any existing for the same type 3845 var selector = 'link[rel="alternate"][type="' + type + '"]'; 3846 var existing = $document[0].head.querySelectorAll(selector); 3847 existing.forEach ? existing.forEach(function(n){ n.remove(); }) 3848 : Array.prototype.forEach.call(existing, function(n){ n.parentNode.removeChild(n); }); 3849 var link = $document[0].createElement('link'); 3850 link.setAttribute('rel', 'alternate'); 3851 link.setAttribute('type', type); 3852 link.setAttribute('href', href); 3853 if (title) link.setAttribute('title', title); 3854 $document[0].head.appendChild(link); 3855 } 3856 return { 3857 setAlternatesForTool: function(biotoolsID) { 3858 if (!biotoolsID) return; 3859 var base = $window.location.origin; 3860 setAlternate('application/json', base + '/api/' + biotoolsID + '?format=json', 'JSON'); 3861 setAlternate('application/xml', base + '/api/' + biotoolsID + '?format=xml', 'XML'); 3862 } 3863 }; 3864}) 3865.factory('ToolTableDataSource', function() { 3866 var columnDescriptionForKey = function(key, hidden) { 3867 var widthCharMultiplier = 12; 3868 var columnDescription = {}; 3869 if (key == 'Name') { 3870 columnDescription = {field: 'name', pinnedLeft: true, displayName: 'Name', cellTemplate: '/partials/grid_cells/nameCell.html', width: '150', resizable: true}; 3871 } 3872 else if (key == 'Operating System') { 3873 columnDescription = {field: 'operatingSystem', displayName: 'Operating system', cellTemplate: '/partials/grid_cells/platformCell.html', width: '100', resizable: true}; 3874 } 3875 else if (key == 'Description') { 3876 columnDescription = {field: 'description', displayName: 'Description', width: '*', resizable: true, cellTemplate: '/partials/grid_cells/defaultCell.html'}; 3877 } 3878 else if (key == 'Operation') { 3879 columnDescription = {field: 'function', displayName: 'Operation', width: '220', resizable: true, cellTemplate: '/partials/grid_cells/functionCell.html'}; 3880 } 3881 else if (key == 'Input') { 3882 columnDescription = {field: 'function', displayName: 'Input', width: '240', resizable: true, cellTemplate: '/partials/grid_cells/inputCell.html'}; 3883 } 3884 else if (key == 'Output') { 3885 columnDescription = {field: 'function', displayName: 'Output', width: '240', resizable: true, cellTemplate: '/partials/grid_cells/outputCell.html'}; 3886 } 3887 else if (key == 'Topic') { 3888 columnDescription = {field: 'topic', displayName: 'Topic', width: '200', resizable: true, cellTemplate: '/partials/grid_cells/topicCell.html'}; 3889 } 3890 else if (key == 'Homepage') { 3891 columnDescription = {field: 'homepage', displayName: 'Homepage', resizable: true, width: '100', cellTemplate: '/partials/grid_cells/urlCell.html'}; 3892 } 3893 else if (key == 'Tool Type') { 3894 columnDescription = {field: 'toolType', displayName: 'Tool type', width: '150', resizable: true, cellTemplate: '/partials/grid_cells/listCell.html'}; 3895 } 3896 else if (key == 'Publications') { 3897 columnDescription = {field: 'publication', displayName: 'Publications', width: '250', resizable: true, cellTemplate: '/partials/grid_cells/publicationsCell.html'}; 3898 } 3899 else if (key == 'Collection') { 3900 columnDescription = {field: 'collectionID', displayName: 'Collection', width: '150', resizable: true, cellTemplate: '/partials/grid_cells/listCell.html'}; 3901 } 3902 else if (key == 'bio.tools ID') { 3903 columnDescription = {field: 'biotoolsID', displayName: 'bio.tools ID', width: '120', resizable: true, cellTemplate: '/partials/grid_cells/defaultCell.html'}; 3904 } 3905 else if (key == 'Documentation') { 3906 columnDescription = {field: 'documentation', displayName: 'Documentation', width: '160', resizable: true, cellTemplate: '/partials/grid_cells/linkListCell.html'}; 3907 } 3908 else if (key == 'Link') { 3909 columnDescription = {field: 'link', displayName: 'Links', width: '160', resizable: true, cellTemplate: '/partials/grid_cells/linkListCell.html'}; 3910 } 3911 else if (key == 'Download') { 3912 columnDescription = {field: 'download', displayName: 'Downloads', width: '160', resizable: true, cellTemplate: '/partials/grid_cells/linkListCell.html'}; 3913 } 3914 else if (key == 'Language') { 3915 columnDescription = {field: 'language', displayName: 'Language', width: '120', resizable: true, cellTemplate: '/partials/grid_cells/listCell.html'}; 3916 } 3917 else if (key == 'Credits') { 3918 columnDescription = {field: 'credit', displayName: 'Credits & Support', width: '150', resizable: true, cellTemplate: '/partials/grid_cells/creditsCell.html'}; 3919 } 3920 else if (key == 'Accessibility') { 3921 columnDescription = {field: key.toLowerCase(), displayName: key, width: '150', resizable: true, cellTemplate: '/partials/grid_cells/defaultCell.html'}; 3922 } 3923 else if (key == 'Cost') { 3924 columnDescription = {field: key.toLowerCase(), displayName: key, width: '90', resizable: true, cellTemplate: '/partials/grid_cells/defaultCell.html'}; 3925 } 3926 else if (key == 'Version') { 3927 columnDescription = {field: key.toLowerCase(), displayName: key, width: '90', resizable: true, cellTemplate: '/partials/grid_cells/listCell.html'}; 3928 } 3929 else if (key == 'Added') { 3930 columnDescription = {field: 'additionDate', displayName: key, width: '100', resizable: true, cellTemplate: '/partials/grid_cells/dateCell.html'}; 3931 } 3932 else if (key == 'Updated') { 3933 columnDescription = {field: 'lastUpdate', displayName: key, width: '100', resizable: true, cellTemplate: '/partials/grid_cells/dateCell.html'}; 3934 } 3935 else {
3936 columnDescription = {field: key.toLowerCase(), displayName: key, width: key.length * widthCharMultiplier, resizable: true, cellTemplate: '/partials/grid_cells/defaultCell.html'}; 3937 } 3938 columnDescription.enableHiding = true; 3939 columnDescription.visible = hidden; 3940 columnDescription["minWidth"] = 120; 3941 return columnDescription; 3942 } 3943 3944 return { 3945 columnsDescription : function(visibleColumns) { 3946 return [ 3947 columnDescriptionForKey('Name', visibleColumns.indexOf('Name') != -1), 3948 columnDescriptionForKey('Description', visibleColumns.indexOf('Description') != -1), 3949 columnDescriptionForKey('Homepage', visibleColumns.indexOf('Homepage') != -1), 3950 columnDescriptionForKey('Version', visibleColumns.indexOf('Homepage') != -1), 3951 columnDescriptionForKey('bio.tools ID', visibleColumns.indexOf('bio.tools ID') != -1), 3952 columnDescriptionForKey('Tool Type', visibleColumns.indexOf('Tool Type') != -1), 3953 columnDescriptionForKey('Topic', visibleColumns.indexOf('Topic') != -1), 3954 columnDescriptionForKey('Publications', visibleColumns.indexOf('Publications') != -1), 3955 columnDescriptionForKey('Credits', visibleColumns.indexOf('Credits') != -1), 3956 columnDescriptionForKey('Operation', visibleColumns.indexOf('Operation') != -1), 3957 columnDescriptionForKey('Input', visibleColumns.indexOf('Input') != -1), 3958 columnDescriptionForKey('Output', visibleColumns.indexOf('Output') != -1), 3959 columnDescriptionForKey('Documentation', visibleColumns.indexOf('Documentation') != -1), 3960 columnDescriptionForKey('Operating System', visibleColumns.indexOf('Operating System') != -1), 3961 columnDescriptionForKey('Language', visibleColumns.indexOf('Language') != -1), 3962 columnDescriptionForKey('License', visibleColumns.indexOf('License') != -1), 3963 columnDescriptionForKey('Link', visibleColumns.indexOf('Link') != -1), 3964 columnDescriptionForKey('Accessibility', visibleColumns.indexOf('Accessibility') != -1), 3965 columnDescriptionForKey('Download', visibleColumns.indexOf('Download') != -1), 3966 columnDescriptionForKey('Collection', visibleColumns.indexOf('Collection') != -1), 3967 columnDescriptionForKey('Cost', visibleColumns.indexOf('Cost') != -1), 3968 columnDescriptionForKey('Maturity', visibleColumns.indexOf('Maturity') != -1), 3969 columnDescriptionForKey('Added', visibleColumns.indexOf('Added') != -1), 3970 columnDescriptionForKey('Updated', visibleColumns.indexOf('Updated') != -1) 3971 ]; 3972 } 3973 } 3974}) 3975.service('ToolPaginator', function(){ 3976 this.currentPage = 1; 3977 // pagination settings 3978 this.maxSize = 5; 3979 this.pageSize = 50; 3980 this.availablePageSizes = [10, 25, 50, 100]; // New option 3981}) 3982.service('ToolSorter', function(){ 3983 var _this = this; 3984 this.order = true; 3985 this.list = [ 3986 {"attrName": "lastUpdate", "text": "Updated"}, 3987 {"attrName": "additionDate", "text": "Added"}, 3988 {"attrName": "name", "text": "Name"}, 3989 {"attrName": "citationCount", "text": "Citation Count"}, 3990 {"attrName": "citationDate", "text": "Publication Date"} 3991 ]; 3992 this.sortBy = this.list[0]; 3993 3994 var scoreEntry = {"attrName": "score", "text": "Score"}; 3995 3996 this.setSortOption = function(parameter) { 3997 if (parameter == undefined) { 3998 this.sortBy = this.list[0]; 3999 } 4000 else { 4001 for (var i = 0; i < this.list.length; i++) { 4002 if (this.list[i]['attrName'] == parameter) { 4003 this.sortBy = this.list[i]; 4004 } 4005 } 4006 } 4007 } 4008 4009 // add 'Score' to the list of sortable attributes 4010 this.addScore = function () { 4011 if (!_.isEqual(scoreEntry, _this.list[0])) { 4012 _this.list.unshift(scoreEntry); 4013 _this.sortBy = _this.list[0]; 4014 } 4015 } 4016 4017 // remove 'Score' from the list of sortable attributes 4018 this.removeScore = function () { 4019 if (_.isEqual(scoreEntry, _this.list[0])) { 4020 _this.list.splice(0,1); 4021 _this.sortBy = _this.list[0]; 4022 } 4023 } 4024}) 4025.service('DisplayModeSelector', function(){ 4026 this.list = [ 4027 {"attrName": "cards", "text": "Cards"}, 4028 {"attrName": "grid", "text": "Table"} 4029 ]; 4030 this.mode = this.list[0]; 4031}) 4032.factory('Tool', ['$resource', function($resource){ 4033 return $resource('/api/t/:id', null, { 4034 'query': { 4035 isArray:false, 4036 method:'GET' 4037 }, 4038 'update': { 4039 method:'PUT' 4040 } 4041 }) 4042}]) 4043.factory('DisownToolService', ['$resource', function($resource){ 4044 return $resource('/api/t/:id/disown', {'id': '@id'}, { 4045 'disown': { 4046 method:'POST' 4047 } 4048 }) 4049}]) 4050.factory('ToolCreateValidator', ['$resource', function($resource){ 4051 return $resource('/api/t/:id/validate', null, { 4052 'update': { 4053 method:'PUT' 4054 } 4055 }) 4056}]) 4057.factory('ToolUpdateValidator', ['$resource', function($resource){ 4058 return $resource('/api/t/:id/validate', null, { 4059 'update': { 4060 method:'PUT' 4061 } 4062 }) 4063}]) 4064.factory('Ontology', ['$resource', function($resource){ 4065 return $resource('/api/o/:name', null, {}) 4066}]) 4067.factory('UsedTerms', ['$resource', function($resource){ 4068 return $resource('/api/used-terms/:usedTermName', null, { 4069 'query': { 4070 isArray:true, 4071 method:'GET' 4072 }, 4073 }) 4074}]) 4075.service('Attribute', function() { 4076 this.description = { 4077 everything: { 4078 label: "Everything" 4079 }, 4080 name: { 4081 description: "Tool name.", 4082 label: "Name" 4083 }, 4084 biotoolsID: { 4085 description: "Unique, persistent bio.tools identifier.", 4086 label: "biotoolsID" 4087 }, 4088 toolType: { 4089 description: "A type of application software: a tool can have more than one type.", 4090 label: "Tool type" 4091 }, 4092 version: { 4093 description: "Tool version information. Use 'Add version' to enter individual version numbers (if practical).", 4094 label: "Current version" 4095 }, 4096 description: { 4097 description: "Tool description, e.g. a few sentences adapted from the tool publication abstract or homepage.", 4098 label: "Description" 4099 }, 4100 operation: { 4101 description: "A basic operation performed by this tool function.", 4102 label: "Operation" 4103 }, 4104 functionNote: { 4105 description: "Concise comment about this function, if not apparent from the description and EDAM annotations.", 4106 label: "Function description" 4107 }, 4108 functionName: { 4109 description: "Name of the function (EDAM term)", 4110 label: "Function Name" 4111 }, 4112 input: { 4113 description: "Details of input data for this function.", 4114 label: "Input" 4115 }, 4116 inputDataType: { 4117 description: "Type of input data.", 4118 label: "Data type" 4119 }, 4120 inputDataFormat: { 4121 description: "Allowed format(s) of the input data.", 4122 label: "Data format" 4123 }, 4124 outputDataType: { 4125 description: "Type of output data.", 4126 label: "Data type" 4127 }, 4128 outputDataFormat: { 4129 description: "Allowed format(s) of the output data.", 4130 label: "Data format" 4131 }, 4132 dataDescription: { 4133 description: "Free text description of the data.", 4134 label: "Data description" 4135 }, 4136 output: { 4137 description: "Details of output data for this function.", 4138 label: "Output" 4139 }, 4140 topic: { 4141 description: "A scientific domain that the tool serves, or another general category.", 4142 label: "Topic" 4143 }, 4144 homepage: { 4145 description: "Homepage of the tool, or some URL that best serves this purpose.", 4146 label: "Homepage URL" 4147 }, 4148 contact: { 4149 description: "Primary points of contact, e.g. helpdesk or an individual", 4150 label: "Contact" 4151 }, 4152 contactName: { 4153 description: "Name of contact", 4154 label: "Name" 4155 }, 4156 contactEmail: { 4157 description: "Email address of contact", 4158 label: "E-mail" 4159 }, 4160 resourceType: { 4161 description: "Basic resource type: Tool, Database etc.", 4162 label: "Resource type" 4163 }, 4164 interface: { 4165 description: "Resource interfaces: Web UI, Command line etc.", 4166 label: "Interface" 4167 }, 4168 publications: { 4169 description: "Publications relevant to the resource (PMCID, PMID or DOI)", 4170 label: "Publications" 4171 }, 4172 publicationsPrimaryID: { 4173 description: "PMCID, PMID or DOI of the publication", 4174 label: "ID of the primary publication" 4175 }, 4176 publicationsOtherID: { 4177 description: "PMCID, PMID or DOI of other relevant publications", 4178 label: "ID of other publications" 4179 }, 4180 docs: { 4181 description: "Links to the documentation", 4182 label: "Documentation" 4183 }, 4184 docsHome: { 4185 description: "Main page of documentation", 4186 label: "Main page" 4187 }, 4188 docsTermsOfUse: { 4189 description: "Link to the Terms Of Use", 4190 label: "Terms of Use" 4191 }, 4192 docsDownload: { 4193 description: "Link to the download instructions", 4194 label: "Download" 4195 }, 4196 docsCitationInstructions: { 4197 description: "Link to the citation instructions", 4198 label: "Citation instructions" 4199 }, 4200 docsDownloadSource: { 4201 description: "Source code downloads page (URL)", 4202 label: "Download source" 4203 }, 4204 docsDownloadBinaries: { 4205 description: "Software binaries downloads page (URL)", 4206 label: "Download binaries" 4207 }, 4208 docsGithub: { 4209 description: "Github page (URL)", 4210 label: "Github page" 4211 }, 4212 affiliation: { 4213 description: "Entry owner", 4214 label: "Affiliation" 4215 }, 4216 mirror: { 4217 description: "Mirror homepage (URL)", 4218 label: "Mirror" 4219 }, 4220 collectionID: { 4221 description: "A tag which assigns the tool to a collection in bio.tools.", 4222 label: "Collection" 4223 }, 4224 sourceRegistry: { 4225 description: "Link to the registry (or other collection) from which the tool was imported (URL)", 4226 label: "Source registry" 4227 }, 4228 canonicalID: { 4229 description: "Canonical Identifier (typically a URI) of the resource, if one is available", 4230 label: "Canonical ID" 4231 }, 4232 cost: { 4233 description: "Monetary cost of acquiring the tool.", 4234 label: "Cost" 4235 }, 4236 elixirInfo: { 4237 description: "Information specific to ELIXIR services", 4238 label: "ELIXIR info" 4239 }, 4240 elixirStatus: { 4241 description: "ELIXIR Core Service", 4242 label: "ELIXIR status" 4243 }, 4244 elixirNode: { 4245 description: "Name of one of countries participating in ELIXIR", 4246 label: "ELIXIR node" 4247 }, 4248 maturity: {
4249 description: "Development stage of the software product.", 4250 label: "Maturity" 4251 }, 4252 platform: { 4253 description: "Platforms supported by a downloadable software package", 4254 label: "Platform" 4255 }, 4256 language: { 4257 description: "Name of a programming language, e.g. used for the tool source code.", 4258 label: "Language" 4259 }, 4260 license: { 4261 description: "Software or data usage license. Use \"Unlicensed\", \"Proprietary\" (for commercial licenses) or \"Other\" in special cases. ", 4262 label: "License" 4263 }, 4264 credit: { 4265 description: "Entities that should be credited", 4266 label: "Credit" 4267 }, 4268 creditsDeveloper: { 4269 description: "Name of person that developed the resource", 4270 label: "Developer" 4271 }, 4272 creditsContributor: { 4273 description: "Name of person contributing to the resource", 4274 label: "Contributor" 4275 }, 4276 creditsInstitution: { 4277 description: "Name of the institution that developed or provide the resource", 4278 label: "Institution" 4279 }, 4280 creditsInfrastructure: { 4281 description: "Research infrastructure in which the resource was developed or provided", 4282 label: "Infrastructure" 4283 }, 4284 creditsFunding: { 4285 description: "Details of grant funding supporting the resource", 4286 label: "Funding" 4287 }, 4288 uses: { 4289 description: "Other resources this resource uses, e.g. as a data source, or auxillary program", 4290 label: "Uses" 4291 }, 4292 usesName: { 4293 description: "Name of a resource that this resource uses", 4294 label: "Name" 4295 }, 4296 usesHomepage: { 4297 description: "Homepage of a resource that this resource uses", 4298 label: "Homepage" 4299 }, 4300 usesVersion: { 4301 description: "Version number of a resource that this resources uses", 4302 label: "Version" 4303 }, 4304 operatingSystem: { 4305 description: "The operating system supported by a downloadable software.", 4306 label: "Operating system" 4307 }, 4308 accessibility: { 4309 description: "How freely available the software is for use.", 4310 label: "Accessibility" 4311 }, 4312 cmd: { 4313 description: "Relevant command, command-line fragment or option for executing this function or running the tool in this mode.", 4314 label: "Command" 4315 }, 4316 linkURL: { 4317 description: "A link of some relevance to the tool (URL).", 4318 label: "linkURL" 4319 }, 4320 linkType: { 4321 description: "The type of data, information or system that is obtained when the link is resolved.", 4322 label: "linkType" 4323 }, 4324 linkNote: { 4325 description: "Comment about the link.", 4326 label: "linkNote" 4327 }, 4328 downloadURL: { 4329 description: "Link to download (or repository providing a download) for the tool.", 4330 label: "downloadURL" 4331 }, 4332 downloadType: { 4333 description: "Type of downloadable resource.", 4334 label: "downloadType" 4335 }, 4336 downloadNote: { 4337 description: "Comment about the download.", 4338 label: "downloadNote" 4339 }, 4340 downloadVersion: { 4341 description: "Version information (typically a version number) of the software applicable to this download.", 4342 label: "linkNote" 4343 }, 4344 documentationURL: { 4345 description: "Link to documentation for the tool (URL).", 4346 label: "documentationURL" 4347 }, 4348 documentationType: { 4349 description: "Type of documentation that is linked to.", 4350 label: "documentationType" 4351 }, 4352 documentationNote: { 4353 description: "Comment about the documentation.", 4354 label: "documentationNote" 4355 }, 4356 publicationPMCID: { 4357 description: "PubMed Central Identifier (PMCID) of a publication about the tool.", 4358 label: "publicationPMCID" 4359 }, 4360 publicationPMID: { 4361 description: "PubMed Identifier (PMID) of a publication about the tool.", 4362 label: "publicationPMID" 4363 }, 4364 publicationDOI: { 4365 description: "Digital Object Identifier (DOI) of a publication about the tool.", 4366 label: "publicationDOI" 4367 }, 4368 publicationType: { 4369 description: "Type of publication.", 4370 label: "publicationType" 4371 }, 4372 publicationVersion: { 4373 description: "Version information (typically a version number) of the software applicable to this publication.", 4374 label: "publicationVersion" 4375 }, 4376 publicationNote: { 4377 description: "Comment about the publication.", 4378 label: "publicationNote" 4379 }, 4380 creditName: { 4381 description: "Name of the entity that is credited.", 4382 label: "creditName" 4383 }, 4384 creditEmail: { 4385 description: "Email address of the entity that is credited.", 4386 label: "creditEmail" 4387 }, 4388 creditURL: { 4389 description: "URL for the entity that is credited, e.g. homepage of an institute.", 4390 label: "creditURL" 4391 }, 4392 creditOrcidID: { 4393 description: "Unique identifier (ORCID iD) of a person that is credited.", 4394 label: "creditOrcidID" 4395 }, 4396 creditGridID: { 4397 description: "Unique identifier (GRID ID) of an organisation that is credited.", 4398 label: "creditGridID" 4399 }, 4400 creditRORID: { 4401 description: "Unique identifier (ROR ID) of an organisation that is credited.", 4402 label: "creditRORID" 4403 }, 4404 creditFundRefID: { 4405 description: "Unique identifier (FundRef ID) of an organisation that is credited.", 4406 label: "creditFundRefID" 4407 }, 4408 creditEntityType: { 4409 description: "Type of entity that is credited.", 4410 label: "creditEntityType" 4411 }, 4412 creditEntityRole: { 4413 description: "Role performed by entity that is credited.", 4414 label: "creditEntityRole" 4415 }, 4416 creditNote: { 4417 description: "A comment about the credit.", 4418 label: "creditNote" 4419 }, 4420 elixirPlatform: { 4421 description: "Name of one of countries participating in ELIXIR", 4422 label: "elixirPlatform" 4423 }, 4424 elixirCommunity: { 4425 description: "Name of the community participating in ELIXIR", 4426 label: "elixirCommunity" 4427 }, 4428 otherID: { 4429 description: "A unique identifier of the tool, typically assigned by an ID-assignment authority other than bio.tools, e.g. âRRID:SCR_015644â", 4430 label: "otherID" 4431 }, 4432 otherIDValue: { 4433 description: "Value of tool identifier, e.g. âRRID:SCR_001156â", 4434 label: "otherIDValue" 4435 }, 4436 otherIDType: { 4437 description: "Type of tool identifier, e.g. ârridâ", 4438 label: "otherIDType" 4439 }, 4440 otherIDVersion: { 4441 description: "Version information (typically a version number) of the softw
4441are applicable to this identifier, e.g. â1.4â", 4442 label: "otherIDVersion" 4443 }, 4444 relationBiotoolsID: { 4445 description: 'bio.tools ID of an existing bio.tools entry to which this tool is related, e.g. "needle"', 4446 label: "relationBiotoolsID" 4447 }, 4448 relationType: { 4449 description: 'Type of relation between this and another registered tool, e.g. "isNewVersionOf"', 4450 label: "relationType" 4451 }, 4452 confidence: { 4453 description: 'Related to automatically generated entries from publications: describes the confidence score of an entry being a valid bio.tools entry.', 4454 label: "Tool confidence" 4455 }, 4456 bioLibAppName: { 4457 description: 'Application Name of an existing BioLib app', 4458 label: "App name" 4459 }, 4460 bioLibAuthorUsername: { 4461 description: 'Username of the user that created the BioLib app', 4462 label: "Author username" 4463 }, 4464 bioLibAuthorName: { 4465 description: 'The display name of the author that created the BioLib app', 4466 label: "Author name" 4467 } 4468 }; 4469}) 4470.factory('CheckUserEditingRights', ['User', function(User) { 4471 return { 4472 canEdit: function(resource) { 4473 4474 if (User.isSuperuser()) return true; 4475 4476 if (User.getUsername() == resource.owner) { 4477 return true; 4478 } 4479 else if (resource.editPermission != undefined) { 4480 if (resource.editPermission.type == "public") { 4481 return true; 4482 } 4483 else if (resource.editPermission.type == "group") { 4484 for (var index in resource.editPermission.authors) { 4485 if (User.getUsername() == resource.editPermission.authors[index]) { 4486 return true; 4487 } 4488 } 4489 } 4490 } 4491 return false; 4492 }, 4493 isOwner: function(resource) { 4494 if (User.getUsername() == resource.owner) { 4495 return true; 4496 } 4497 return false; 4498 } 4499 }; 4500}]) 4501.factory('ToolVersionProvider', ['$http', function ($http) { 4502 return { 4503 getVersions: function(toolId) { 4504 return $http({ 4505 method: 'GET', 4506 url: '/api/tool/' + toolId + '/version' 4507 }).then(function successCallback(response) { 4508 return response.data; 4509 }, function errorCallback(response) { 4510 return []; 4511 }) 4512 } 4513 }; 4514}]) 4515.factory('EnvironmentChecker', ['$http', function ($http) { 4516 return { 4517 getEnvironment: function() { 4518 return $http({ 4519 method: 'GET', 4520 url: '/api/env/' 4521 }).then(function successCallback(response) { 4522 return response.data; 4523 }, function errorCallback(response) { 4524 return []; 4525 }) 4526 } 4527 }; 4528}]) 4529.service('CommunityCollection', function(){ 4530 this.hasCommunityCollection = function(collectionList, communityCollection){ 4531 if (collectionList && Array.isArray(collectionList) && collectionList.length > 0){ 4532 return collectionList.includes(communityCollection); 4533 } 4534 return false; 4535 } 4536 4537 this.addCommunityCollection = function(collectionList, communityCollection){ 4538 if (!this.hasCommunityCollection(collectionList, communityCollection)){ 4539 4540 // if the collection property is not set or is set but is not an array 4541 if (!collectionList || (collectionList && !Array.isArray(collectionList))){ 4542 return [communityCollection]; 4543 } 4544 if (Array.isArray(collectionList)){ 4545 collectionList.push(communityCollection); 4546 return collectionList; 4547 } 4548 } 4549 } 4550 4551 this.removeCommunityCollection = function(collectionList, communityCollection){ 4552 var result = []; 4553 if (this.hasCommunityCollection(collectionList, communityCollection)){ 4554 result = collectionList.filter(function(c){ 4555 return c != communityCollection; 4556 }); 4557 } 4558 if (result.length == 0){ 4559 return undefined 4560 } 4561 return result; 4562 } 4563}); 4564 4565// Controllers 4566angular.module('elixir_front.controllers') 4567.controller('SubdomainAdminController', ['$scope', '$stateParams', 'DomainConnection', 'DomainDetailConnection', 'User', function($scope, $stateParams, DomainConnection, DomainDetailConnection, User) { 4568 var vm = this; 4569 $scope.User = User; 4570 $scope.isLoadingSubdomains = true; 4571 $scope.subdomains = []; 4572 4573 $scope.deleteSubdomain = function(name){ 4574 if (confirm("Are you sure you want to remove this subdomain?")){ 4575 // COVID-19 subdomain hack 4576 if (name.toLowerCase() == 'covid-19'){ 4577 alert('Cannot delete the covid-19 subdomain'); 4578 return; 4579 } 4580 4581 var deleteResponse = DomainDetailConnection.delete({'domain': name}, function(data) { 4582 $scope.subdomains.splice($scope.subdomains.findIndex(function(d) { return d.domain === name; }), 1); 4583 // TODO: Handle responses 4584 }, function(error) { 4585 // TODO: Handle errors 4586 }); 4587 } 4588 } 4589 4590 4591 vm.loadSubdomains = function() { 4592 $scope.isLoadingSubdomains = true; 4593 $scope.subdomains = []; 4594 var domainResponse = DomainConnection.query({}, function(data) { 4595 $scope.subdomains = data; 4596 $scope.isLoadingSubdomains = false;
4597 }, function(errorData) { 4598 // TODO: Handle errors 4599 }); 4600 }; 4601 4602 // Initialization 4603 vm.loadSubdomains() 4604}]) 4605.controller('DomainListController', ['$scope', 'DomainDetailConnection', '$state', function($scope, DomainDetailConnection, $state){ 4606 var vm = this; 4607 $scope.domains = []; 4608 $scope.domainSearchText = ""; 4609 4610 $scope.search_title = true; 4611 $scope.search_subtitle = true; 4612 $scope.search_collection = true; 4613 $scope.search_tags = true; 4614 $scope.search_description = false; 4615 $scope.search_tools = false; 4616 4617 $scope.updateKeys = function(){ 4618 $scope.initialKeys = [ 4619 { 4620 "name":"domain", 4621 "weight": 1 4622 }, 4623 { 4624 "name":"title", 4625 "weight": $scope.search_title ? 0.9 : 0 4626 }, 4627 { 4628 "name":"sub_title", 4629 "weight": $scope.search_subtitle ? 0.7 : 0 4630 }, 4631 { 4632 "name":"tag", 4633 "weight": $scope.search_tags ? 0.9 : 0 4634 }, 4635 { 4636 "name":"collection", 4637 "weight": $scope.search_collection ? 0.9 : 0 4638 }, 4639 { 4640 "name":"description", 4641 "weight": $scope.search_description ? 0.3 : 0 4642 }, 4643 { 4644 "name":"resources.name", 4645 "weight": $scope.search_tools ? 0.2 : 0 4646 }, 4647 { 4648 "name":"resources.biotoolsID", 4649 "weight": $scope.search_tools ? 0.2 : 0 4650 } 4651 ]; 4652 4653 $scope.fuseSearchKeys = $scope.initialKeys.filter(function(x){ 4654 return x.weight > 0; 4655 }); 4656 4657 $scope.fuseOptions = { 4658 threshold: 0.2, 4659 shouldSort: true, 4660 minMatchCharLength: 2, 4661 // location: 0, default 0 4662 tokenize: true, 4663 // matchAllTokens: true, 4664 keys: $scope.fuseSearchKeys 4665 4666 }; 4667 } 4668 4669 $scope.updateKeys(); 4670 4671 DomainDetailConnection.query({'domain':'all'}, function(response) { 4672 $scope.domains = response.data; 4673 $scope.domains.sort(function(a,b){ 4674 var d_a = a.domain.toLowerCase(); 4675 var d_b = b.domain.toLowerCase(); 4676 4677 if (d_a < d_b) { 4678 return -1; 4679 } 4680 if (d_a > d_b) { 4681 return 1; 4682 } 4683 return 0; 4684 }) 4685 $scope.filteredDomains = $scope.domains; 4686 $scope.fuse = new Fuse($scope.domains, $scope.fuseOptions); 4687 }); 4688 4689 $scope.collectionNameClicked = function(collectionName) { 4690 $state.go('search', {'collectionID': '"'+ collectionName + '"'}); 4691 } 4692 4693 $scope.domainSearch = function(){ 4694 if ($scope.domainSearchText.length == 0){ 4695 $scope.filteredDomains = $scope.domains; 4696 } 4697 else { 4698 $scope.updateKeys(); 4699 $scope.fuse = new Fuse($scope.domains, $scope.fuseOptions); 4700 console.log($scope.fuseOptions); 4701 $scope.filteredDomains = $scope.fuse.search($scope.domainSearchText); 4702 } 4703 } 4704 4705 4706}]) 4707.controller('SubdomainController', ['$scope', '$state', '$stateParams', 'ToolListOverviewConnection', 'DomainDetailConnection', 'DomainConnection', '$q', 'UsedTerms', 'UserSuggestionsProvider', 'User', function($scope, $state, $stateParams, ToolListOverviewConnection, DomainDetailConnection, DomainConnection, $q, UsedTerms, UserSuggestionsProvider, User) { 4708 var vm = this; 4709 $scope.ToolListOverviewConnection = ToolListOverviewConnection; 4710 $scope.User = User; 4711 $scope.updating = false; 4712 $scope.loading = ($stateParams.id != ""); 4713 $scope.toolsPerPageCount = 10; 4714 4715 $scope.search = {};
4716 $scope.search.searchOption = 'name'; 4717 $scope.search.loadingSearchResults = false; 4718 $scope.search.searchResults = []; 4719 $scope.search.searchString = ''; 4720 $scope.search.noResults = false; 4721 $scope.search.currentPage = 1; 4722 $scope.search.totalPages = 1; 4723 4724 $scope.subdomain = {}; 4725 $scope.subdomain.exists = ($stateParams.id != ""); 4726 $scope.subdomain.domain = $stateParams.id; 4727 $scope.subdomain.title = ''; 4728 $scope.subdomain.subtitle = ''; 4729 $scope.subdomain.description = ''; 4730 $scope.subdomain.editors = []; 4731 $scope.subdomain.toolList = []; 4732 $scope.subdomain.resources = []; 4733 $scope.subdomain.tag = []; 4734 $scope.subdomain.collection = []; 4735 $scope.subdomain.is_private = true; 4736 $scope.subdomain.currentPage = 1; 4737 $scope.subdomain.totalPages = 1; 4738 4739 $scope.errors = {}; 4740 $scope.errors.general = ''; 4741 $scope.errors.domain = ''; 4742 $scope.errors.loading = ''; 4743 $scope.errors.data = {} 4744 4745 4746 $scope.response = {}; 4747 $scope.response.general = ''; 4748 4749 4750 $scope.gotoDomain = function(d){ 4751 if (confirm('Make sure to save before leaving the page. Leave?')){ 4752 $state.go('subdomain', {domain: d}); 4753 } 4754 4755 } 4756 4757 // Handle users search 4758 $scope.userSuggestions = function(prefix) { 4759 return UserSuggestionsProvider.getSuggestions(prefix).then(function(data) { 4760 var suggestions = _.map(data, function(obj){ 4761 return obj.username; 4762 }); 4763 return _.difference(suggestions, $scope.subdomain.editors); 4764 }); 4765 }; 4766 4767 $scope.userSelected = function($item, $model, $label) { 4768 // Initialize authors if not present. 4769 if ($scope.subdomain.editors == undefined) { 4770 $scope.subdomain.editors = []; 4771 } 4772 // Clear the input field on selection. 4773 $scope.userSuggestion = ''; 4774 $scope.subdomain.editors.push($model); 4775 }; 4776 4777 $scope.isDomainOwner = function() { 4778 if ($scope.subdomain.owner == User.current.username) { 4779 return true; 4780 } 4781 return false; 4782 } 4783 4784 $scope.deleteUser = function(index) { 4785 $scope.subdomain.editors.splice(index, 1); 4786 } 4787 4788 // Handle tool search 4789 $scope.clearButtonPressed = function() { 4790 $scope.search.currentPage = 1; 4791 var searchQuery = {} 4792 searchQuery[$scope.search.searchOption] = $scope.search.searchString; 4793 $scope.search.searchResults = []; 4794 }; 4795 4796 $scope.searchButtonPressed = function() { 4797 $scope.search.currentPage = 1; 4798 var searchQuery = {} 4799 searchQuery[$scope.search.searchOption] = $scope.search.searchString; 4800 vm.performSearch(searchQuery); 4801 }; 4802 4803 $scope.toolInSubdomain = function(toolID, versionID) { 4804 for (var index in $scope.subdomain.resources) { 4805 var currentTool = $scope.subdomain.resources[index]; 4806 4807 if (currentTool['biotoolsID'] == toolID){// && currentTool['versionId'] == versionID) { 4808 return true; 4809 } 4810 } 4811 return false; 4812 } 4813 4814 $scope.addToSubdomain = function(index) { 4815 var selectedTool = $scope.search.searchResults[index]; 4816 4817 var selectedDict = { "biotoolsID": selectedTool.biotoolsID, "versionId": selectedTool.versionId }; 4818 $scope.subdomain.toolList.push(selectedTool); 4819 $scope.subdomain.resources.push(selectedDict); 4820 $scope.subdomain.totalPages = ($scope.subdomain.toolList.length / $scope.toolsPerPageCount) * 10; 4821 } 4822 4823 $scope.domainAddButtonClick = function (_what, _where, _isList, _isObject) { 4824 if (_isList) { 4825 // if array does not exist create it 4826 if (typeof _where[_what] == 'undefined') { 4827 _where[_what] = []; 4828 } 4829 // add either an object or string to array 4830 _where[_what].push(_isObject ? {} : ''); 4831 } else { 4832 // if object does not exist create it 4833 if (typeof _where[_what] == 'undefined') { 4834 _where[_what] = _isObject ? {} : ''; 4835 } 4836 } 4837 } 4838 4839 $scope.removeButtonClick = function (_what, _parent, _index, _event){ 4840 $scope.removeButtonClickAux(_what, _parent, _index, _event); 4841 4842 if ($scope.errors.data[_what] && $scope.errors.data[_what][_index]){ 4843 $scope.removeButtonClickAux(_what, $scope.errors.data, _index, _event); 4844 } 4845 4846 } 4847 4848 // remove attribute or list entry 4849 $scope.removeButtonClickAux = function (_what, _parent, _index, _event) { 4850 if (_parent[_what][_index] ? confirm("Are you sure you want to remove this element?") : 1) { 4851 // remove jstree if exists 4852 if (_event) { 4853 $(_event.target).closest('div').find('.jstree').jstree("destroy").remove(); 4854 } 4855 _parent[_what].splice(_index, 1); 4856 // if last instance in array delete entire attribute from the softw
4856are object 4857 if (_parent[_what].length == 0) { 4858 delete _parent[_what]; 4859 } 4860 } 4861 } 4862 4863 // used terms (collectionID) for searching in collections 4864 function getCollectionIDs(){ 4865 var d = $q.defer(); 4866 var params = { 4867 "usedTermName": "collectionID" 4868 }; 4869 UsedTerms.get(params, function(response) { 4870 d.resolve(response.data); 4871 }); 4872 return d.promise; 4873 } 4874 4875 $scope.loadCollectionIDs = function(query) { 4876 return getCollectionIDs().then(function(list) { 4877 return list.filter(function (str) { return str.toLowerCase().includes(query.toLowerCase()); }).slice(0,10).sort(); 4878 }); 4879 } 4880 4881 $scope.addAllToSubdomain = function() { 4882 for (var tool in $scope.search.searchResults) { 4883 var selectedTool = $scope.search.searchResults[tool]; 4884 if (selectedTool.biotoolsID && $scope.toolInSubdomain(selectedTool.biotoolsID, selectedTool.versionId) == false) { 4885 $scope.subdomain.toolList.push(selectedTool); 4886 $scope.subdomain.resources.push({ "biotoolsID": selectedTool.biotoolsID, "versionId": selectedTool.versionId }); 4887 } 4888 } 4889 $scope.subdomain.totalPages = ($scope.subdomain.toolList.length / $scope.toolsPerPageCount) * 10; 4890 } 4891 4892 $scope.removeFromSubdomain = function(index) { 4893 $scope.subdomain.toolList.splice(index, 1); 4894 $scope.subdomain.resources.splice(index, 1); 4895 $scope.subdomain.totalPages = ($scope.subdomain.toolList.length / $scope.toolsPerPageCount) * 10; 4896 } 4897 4898 $scope.saveSubdomain = function() { 4899 $scope.updating = true; 4900 $scope.errors.general = ''; 4901 $scope.response.general = ''; 4902 var createResponse = DomainConnection.create(vm.subdomainQuery(), function(data) { 4903 $scope.subdomain.exists = true; 4904 $scope.subdomain.owner = data.owner; 4905 4906 $scope.response.general = "Domain '" + $scope.subdomain.domain + "' was created."; 4907 $scope.updating = false; 4908 }, function(error) { 4909 $scope.errors.general = error.data.detail; 4910 console.log(error); 4911 $scope.updating = false; 4912 $scope.errors.data = error.data; 4913 }); 4914 } 4915 4916 $scope.deleteSubdomain = function(name){ 4917 if (confirm("Are you sure you want to remove this subdomain?")){ 4918 // COVID-19 subdomain hack 4919 if (name.toLowerCase() == 'covid-19'){ 4920 alert('Cannot delete the covid-19 subdomain'); 4921 return; 4922 } 4923 4924 var deleteResponse = DomainDetailConnection.delete({'domain': name}, function(data) { 4925 $scope.response.general = "Domain '" + name + "' was deleted."; 4926 $state.go('domains'); 4927 }, function(error) { 4928 $scope.errors.general = error.data.detail || "Failed to delete domain."; 4929 }); 4930 } 4931 } 4932 4933 vm.fetchSubdomain = function() { 4934 $scope.errors.general = ''; 4935 $scope.response.general = ''; 4936 var updateResponse = DomainDetailConnection.query({'domain': $scope.subdomain.domain}, function(data) { 4937 $scope.subdomain.title = data.data['title']; 4938 $scope.subdomain.subtitle = data.data['sub_title']; 4939 $scope.subdomain.description = data.data['description']; 4940 for (var index in data.data.resources) { 4941 var tool = data.data.resources[index]; 4942 tool['biotoolsID'] = tool['biotoolsID']; 4943 $scope.subdomain.toolList.push(tool); 4944 $scope.subdomain.resources.push(tool); 4945 } 4946 4947 for (var i in data.data.tag){ 4948 $scope.subdomain.tag.push(data.data.tag[i]); 4949 } 4950 4951 for (var i in data.data.collection){ 4952 $scope.subdomain.collection.push(data.data.collection[i]); 4953 } 4954 4955 $scope.subdomain.is_private = data.data.is_private; 4956 4957 if (data.data.editors) { 4958 $scope.subdomain.editors = data.data.editors; 4959 } 4960 if (data.data.owner) { 4961 $scope.subdomain.owner = data.data.owner; 4962 } 4963 4964 $scope.loading = false; 4965 $scope.updating = false; 4966 $scope.subdomain.totalPages = ($scope.subdomain.toolList.length / $scope.toolsPerPageCount) * 10; 4967 }, function(error) { 4968 // Handle error 4969 $scope.errors.loading = 'Data failed to load. Please try to refresh this website. ' + error.data.details; 4970 }); 4971 } 4972 4973 $scope.updateSubdomain = function() { 4974 $scope.updating = true; 4975 $scope.errors.general = ''; 4976 $scope.response.general = ''; 4977 var updateResponse = DomainDetailConnection.update(vm.subdomainQuery(), function(data) { 4978 $scope.response.general = "Domain '" + $scope.subdomain.domain + "' was updated with " + $scope.subdomain.resources.length + " entries." ; 4979 $scope.updating = false;
4980 }, function(error) { 4981 $scope.errors.general = error.data.detail; 4982 $scope.updating = false; 4983 $scope.errors.data = error.data; 4984 }); 4985 } 4986 4987 // remove or replace characters not allowed for subdomains 4988 // See https://tools.ietf.org/html/rfc952 for guidelines 4989 $scope.makeSubdomainURLSafe = function(value) { 4990 $scope.errors.domain = ""; 4991 if (typeof value != 'undefined') { 4992 var id = value.replace(/[^a-zA-Z0-9_-]*/g,'').replace(/[ ]+/g, '-').replace(/[_]+/g, '-').toLowerCase(); 4993 } 4994 if (value != id) { 4995 $scope.errors.domain = "Entered value '" + value + "' has been modified. Only alphanumeric characters and dashes can be used for the subdomain name."; 4996 } 4997 $scope.subdomain.domain = id; 4998 } 4999 5000 5001 vm.performSearch = function(query) { 5002 $scope.search.loadingSearchResults = true; 5003 $scope.search.noResults = false; 5004 var response = ToolListOverviewConnection.query(query, function() { 5005 $scope.search.currentPage = 1; 5006 $scope.search.searchResults = response; 5007 $scope.search.loadingSearchResults = false; 5008 $scope.search.totalPages = ($scope.search.searchResults.length / $scope.toolsPerPageCount) * 10; 5009 if (response.length == 0) { 5010 $scope.search.noResults = true; 5011 } 5012 }); 5013 }; 5014 5015 vm.subdomainQuery = function() { 5016 return {'domain': $scope.subdomain.domain, 5017 'title': $scope.subdomain.title, 5018 'sub_title': $scope.subdomain.subtitle, 5019 'description': $scope.subdomain.description, 5020 'resources': $scope.subdomain.resources, 5021 'tag': $scope.subdomain.tag, 5022 'collection': $scope.subdomain.collection, 5023 'is_private': $scope.subdomain.is_private, 5024 'editors': $scope.subdomain.editors, 5025 }; 5026} 5027 5028 // Initialization 5029 if ($scope.loading) { 5030 vm.fetchSubdomain() 5031 } 5032}]); 5033 5034 5035// Services and Factories 5036angular.module('elixir_front').factory('UserSuggestionsProvider', ['$http', function ($http) { 5037 return { 5038 getSuggestions: function(prefix) { 5039 return $http({ 5040 method: 'GET', 5041 url: '/api/user-list', 5042 params: {term: prefix} 5043 }).then(function successCallback(response) { 5044 return response.data; 5045 }, function errorCallback(response) { 5046 return {}; 5047 }) 5048 } 5049 }; 5050}]); 5051 5052// Controllers 5053angular.module('elixir_front.controllers') 5054.controller('WorkflowController', ['$scope', '$stateParams', '$uibModal', 'WorkflowProvider', function($scope, $stateParams, $uibModal, WorkflowProvider) { 5055 var vm = this; 5056 $scope.loading = true 5057 $scope.filteredWorkflows = [] 5058 $scope.workflows = [] 5059 5060 // Data feteching 5061 vm.loadRequests = function($item, $model, $label) { 5062 $scope.loading = true; 5063 WorkflowProvider.getWorkflows().then(function successCallback(response) { 5064 $scope.workflows = response.data; 5065 $scope.filterWorkflows(); 5066 $scope.loading = false; 5067 }, function errorCallback(response) { 5068 //TODO: Handle error 5069 }); 5070 }; 5071 5072 $scope.filterWorkflows = function() { 5073 var searchString = $scope.search.searchString; 5074 if (searchString == undefined || searchString == '') { 5075 $scope.filteredWorkflows = $scope.workflows; 5076 } 5077 else { 5078 $scope.filteredWorkflows = $scope.workflows.filter(function(x) { 5079 var foundAnnotations = x.annotations.filter(function(annotation) { 5080 var regularExpression = new RegExp(searchString,"i"); 5081 if (annotation.title.match(regularExpression) || annotation.title.match(regularExpression) || annotation.edam_term.match(regularExpression)) { 5082 return true; 5083 } 5084 return false; 5085 }); 5086 return foundAnnotations.length > 0; 5087 }); 5088 } 5089 } 5090 5091 $scope.openModal = function(workflow) { 5092 $uibModal.open({ 5093 templateUrl: 'partials/workflows/modal.html', 5094 controller: ['$uibModalInstance', 'workflow', WorkflowModalCtrl], 5095 controllerAs: 'vm', 5096 windowClass: 'workflow-modal-window', 5097 resolve: { 5098 workflow: function () { return workflow }, 5099 } 5100 }); 5101 }; 5102 5103 $scope.handleAnnotationClick = function(annotation) { 5104 console.log(annotation); 5105 } 5106 5107 $scope.search = function() { 5108 console.log(string); 5109 }; 5110 5111 // Initialization 5112 vm.loadRequests(); 5113}]); 5114 5115function WorkflowModalCtrl($uibModalInstance, workflow) { 5116 var vm = this; 5117 vm.workflow = workflow; 5118 vm.currentTitle = ""; 5119 vm.currentDescription = ""; 5120 vm.currentEdam = ""; 5121 5122 vm.mapDimensions = function(annotation) { 5123 return vm.mapXDimension(annotation.startX) + "," + vm.mapYDimension(annotation.startY) + "," + vm.mapXDimension(annotation.endX) + "," + vm.mapYDimension(annotation.endY); 5124 } 5125 5126 vm.imageHeight = function() { 5127 var img = document.getElementById('image'); 5128 return img.clientHeight; 5129 } 5130 5131 vm.imageWidth = function() { 5132 var img = document.getElementById('image'); 5133 return img.clientWidth; 5134 } 5135 5136 vm.mapXDimension = function(dim_x) { 5137 var multiplier_x = vm.imageWidth() / vm.workflow.width 5138 return dim_x * multiplier_x 5139 } 5140 5141 vm.mapYDimension = function(dim_y) { 5142 var multiplier_y = vm.imageHeight() / vm.workflow.height 5143 return dim_y * multiplier_y 5144 } 5145 5146 vm.hover = function(annotation) { 5147 vm.currentTitle = annotation.title; 5148 vm.currentDescription = annotation.description; 5149 vm.currentEdam = annotation.edam_term; 5150 } 5151 5152 vm.endHover = function(annotation) { 5153 vm.currentTitle = ""; 5154 vm.currentDescription = ""; 5155 vm.currentEdam = ""; 5156 } 5157} 5158 5159// Services and Factories 5160var elixir_front = angular.module('elixir_front'); 5161elixir_front.factory('WorkflowProvider', ['$http', function ($http) { 5162 return { 5163 getWorkflows: function() { 5164 return $http({ 5165 method: 'GET', 5166 url: '/api/w', 5167 params: {} 5168 }) 5169 } 5170 }; 5171}]); 5172(function() { 5173 var module, 5174 __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; 5175 5176 module = angular.module('angularBootstrapNavTree', []); 5177 5178 module.directive('abnTree', [ 5179 '$timeout', function($timeout) { 5180 return { 5181 restrict: 'E', 5182 template: "<ul class=\"nav nav-list nav-pills nav-stacked abn-tree\">\n <li tooltips tooltip-side=\"right\" tooltip-content=\"{{ resourceModel[row.branch.name].description }}\" ng-repeat=\"row in tree_rows | filter:{visible:true} track by row.branch.uid\" ng-animate=\"'abn-tree-animate'\" ng-class=\"'level-' + {{ row.level }} + (row.branch.selected ? ' active':'') + ' ' +row.classes.join(' ')\" class=\"abn-tree-row\"><a ng-click=\"user_clicks_branch(row.branch)\"><i ng-class=\"row.tree_icon\" ng-click=\"row.branch.expanded = !row.branch.expanded\" class=\"indented tree-icon\"> </i><span class=\"indented tree-label\"> {{ resourceModel[row.branch.name].label }} </span></a></li>\n</ul>", 5183 replace: true, 5184 scope: { 5185 treeData: '=', 5186 onSelect: '&', 5187 initialSelection: '@', 5188 treeControl: '=', 5189 resourceModel: '=' 5190 }, 5191 link: function(scope, element, attrs) { 5192 var error, expand_all_parents, expand_level, for_all_ancestors, for_each_branch, get_parent, n, on_treeData_change, select_branch, selected_branch, tree; 5193 error = function(s) { 5194 5195 debugger; 5196 return void 0; 5197 }; 5198 if (attrs.iconExpand == null) { 5199 attrs.iconExpand = 'icon-plus glyphicon glyphicon-plus fa fa-plus'; 5200 } 5201 if (attrs.iconCollapse == null) { 5202 attrs.iconCollapse = 'icon-minus glyphicon glyphicon-minus fa fa-minus'; 5203 } 5204 if (attrs.iconLeaf == null) { 5205 attrs.iconLeaf = 'icon-file glyphicon glyphicon-file fa fa-file'; 5206 } 5207 if (attrs.expandLevel == null) { 5208 attrs.expandLevel = '3'; 5209 } 5210 expand_level = parseInt(attrs.expandLevel, 10); 5211 if (!scope.treeData) { 5212 alert('no treeData defined for the tree!'); 5213 return; 5214 } 5215 if (scope.treeData.length == null) { 5216 if (treeData.label != null) { 5217 scope.treeData = [treeData]; 5218 } else { 5219 alert('treeData should be an array of root branches'); 5220 return; 5221 } 5222 } 5223 for_each_branch = function(f) { 5224 var do_f, root_branch, _i, _len, _ref, _results; 5225 do_f = function(branch, level) { 5226 var child, _i, _len, _ref, _results; 5227 f(branch, level); 5228 if (branch.children != null) { 5229 _ref = branch.children; 5230 _results = []; 5231 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 5232 child = _ref[_i];
5233 _results.push(do_f(child, level + 1)); 5234 } 5235 return _results; 5236 } 5237 }; 5238 _ref = scope.treeData; 5239 _results = []; 5240 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 5241 root_branch = _ref[_i]; 5242 _results.push(do_f(root_branch, 1)); 5243 } 5244 return _results; 5245 }; 5246 selected_branch = null; 5247 select_branch = function(branch) { 5248 if (!branch) { 5249 if (selected_branch != null) { 5250 selected_branch.selected = false; 5251 } 5252 selected_branch = null; 5253 return; 5254 } 5255 if (branch !== selected_branch) { 5256 if (selected_branch != null) { 5257 selected_branch.selected = false; 5258 } 5259 branch.selected = true; 5260 selected_branch = branch; 5261 expand_all_parents(branch); 5262 if (branch.onSelect != null) { 5263 return $timeout(function() { 5264 return branch.onSelect(branch); 5265 }); 5266 } else { 5267 if (scope.onSelect != null) { 5268 return $timeout(function() { 5269 return scope.onSelect({ 5270 branch: branch 5271 }); 5272 }); 5273 } 5274 } 5275 } 5276 }; 5277 scope.user_clicks_branch = function(branch) { 5278 if (branch !== selected_branch) { 5279 return select_branch(branch); 5280 } 5281 }; 5282 get_parent = function(child) { 5283 var parent; 5284 parent = void 0; 5285 if (child.parent_uid) { 5286 for_each_branch(function(b) { 5287 if (b.uid === child.parent_uid) { 5288 return parent = b; 5289 } 5290 }); 5291 } 5292 return parent; 5293 }; 5294 for_all_ancestors = function(child, fn) { 5295 var parent; 5296 parent = get_parent(child); 5297 if (parent != null) { 5298 fn(parent); 5299 return for_all_ancestors(parent, fn); 5300 } 5301 }; 5302 expand_all_parents = function(child) { 5303 return for_all_ancestors(child, function(b) { 5304 return b.expanded = true; 5305 }); 5306 }; 5307 scope.tree_rows = []; 5308 on_treeData_change = function() { 5309 var add_branch_to_list, root_branch, _i, _len, _ref, _results; 5310 for_each_branch(function(b, level) { 5311 if (!b.uid) { 5312 return b.uid = "" + Math.random(); 5313 } 5314 }); 5315 5316 for_each_branch(function(b) { 5317 var child, _i, _len, _ref, _results; 5318 if (angular.isArray(b.children)) { 5319 _ref = b.children; 5320 _results = []; 5321 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 5322 child = _ref[_i]; 5323 _results.push(child.parent_uid = b.uid); 5324 } 5325 return _results; 5326 } 5327 }); 5328 scope.tree_rows = []; 5329 for_each_branch(function(branch) { 5330 var child, f; 5331 if (branch.children) { 5332 if (branch.children.length > 0) { 5333 f = function(e) { 5334 if (typeof e === 'string') { 5335 return { 5336 label: e, 5337 children: [] 5338 }; 5339 } else { 5340 return e; 5341 } 5342 }; 5343 return branch.children = (function() { 5344 var _i, _len, _ref, _results; 5345 _ref = branch.children; 5346 _results = []; 5347 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 5348 child = _ref[_i]; 5349 _results.push(f(child)); 5350 } 5351 return _results; 5352 })(); 5353 } 5354 } else { 5355 return branch.children = []; 5356 } 5357 }); 5358 add_branch_to_list = function(level, branch, visible) { 5359 var child, child_visible, tree_icon, _i, _len, _ref, _results; 5360 if (branch.expanded == null) { 5361 branch.expanded = false;
5362 } 5363 if (branch.classes == null) { 5364 branch.classes = []; 5365 } 5366 if (!branch.noLeaf && (!branch.children || branch.children.length === 0)) { 5367 tree_icon = attrs.iconLeaf; 5368 if (__indexOf.call(branch.classes, "leaf") < 0) { 5369 branch.classes.push("leaf"); 5370 } 5371 } else { 5372 if (branch.expanded) { 5373 tree_icon = attrs.iconCollapse; 5374 } else { 5375 tree_icon = attrs.iconExpand; 5376 } 5377 } 5378 scope.tree_rows.push({ 5379 level: level, 5380 branch: branch, 5381 label: branch.label, 5382 classes: branch.classes, 5383 tree_icon: tree_icon, 5384 visible: visible 5385 }); 5386 if (branch.children != null) { 5387 _ref = branch.children; 5388 _results = []; 5389 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 5390 child = _ref[_i]; 5391 child_visible = visible && branch.expanded; 5392 _results.push(add_branch_to_list(level + 1, child, child_visible)); 5393 } 5394 return _results; 5395 } 5396 }; 5397 _ref = scope.treeData; 5398 _results = []; 5399 for (_i = 0, _len = _ref.length; _i < _len; _i++) { 5400 root_branch = _ref[_i]; 5401 _results.push(add_branch_to_list(1, root_branch, true)); 5402 } 5403 return _results; 5404 }; 5405 scope.$watch('treeData', on_treeData_change, true); 5406 if (attrs.initialSelection != null) { 5407 for_each_branch(function(b) { 5408 if (b.label === attrs.initialSelection) { 5409 return $timeout(function() { 5410 return select_branch(b); 5411 }); 5412 } 5413 }); 5414 } 5415 n = scope.treeData.length; 5416 5417 for_each_branch(function(b, level) { 5418 b.level = level; 5419 return b.expanded = b.level < expand_level; 5420 }); 5421 if (scope.treeControl != null) { 5422 if (angular.isObject(scope.treeControl)) { 5423 tree = scope.treeControl; 5424 tree.expand_all = function() { 5425 return for_each_branch(function(b, level) { 5426 return b.expanded = true; 5427 }); 5428 }; 5429 tree.collapse_all = function() { 5430 return for_each_branch(function(b, level) { 5431 return b.expanded = false; 5432 }); 5433 }; 5434 tree.get_first_branch = function() { 5435 n = scope.treeData.length; 5436 if (n > 0) { 5437 return scope.treeData[0]; 5438 } 5439 }; 5440 tree.select_first_branch = function() { 5441 var b; 5442 b = tree.get_first_branch(); 5443 return tree.select_branch(b); 5444 }; 5445 tree.get_selected_branch = function() { 5446 return selected_branch; 5447 }; 5448 tree.get_parent_branch = function(b) { 5449 return get_parent(b); 5450 }; 5451 tree.select_branch = function(b) { 5452 select_branch(b); 5453 return b; 5454 }; 5455 tree.get_children = function(b) { 5456 return b.children; 5457 }; 5458 tree.select_parent_branch = function(b) { 5459 var p; 5460 if (b == null) { 5461 b = tree.get_selected_branch(); 5462 } 5463 if (b != null) { 5464 p = tree.get_parent_branch(b); 5465 if (p != null) { 5466 tree.select_branch(p); 5467 return p; 5468 } 5469 } 5470 }; 5471 tree.add_branch = function(parent, new_branch) { 5472 if (parent != null) { 5473 parent.children.push(new_branch); 5474 parent.expanded = true; 5475 } else { 5476 scope.treeData.push(new_branch); 5477 } 5478 return new_branch; 5479 }; 5480 tree.add_root_branch = function(new_branch) { 5481 tree.add_branch(null, new_branch); 5482 return new_branch; 5483 }; 5484 tree.expand_branch = function(b) { 5485 if (b == null) { 5486 b = tree.get_selected_branch(); 5487 } 5488 if (b != null) { 5489 b.expanded = true; 5490 return b; 5491 } 5492 }; 5493 tree.collapse_branch = function(b) { 5494 if (b == null) { 5495 b = selected_branch; 5496 } 5497 if (b != null) { 5498 b.expanded = false;
5499 return b; 5500 } 5501 }; 5502 tree.get_siblings = function(b) { 5503 var p, siblings; 5504 if (b == null) { 5505 b = selected_branch; 5506 } 5507 if (b != null) { 5508 p = tree.get_parent_branch(b); 5509 if (p) { 5510 siblings = p.children; 5511 } else { 5512 siblings = scope.treeData; 5513 } 5514 return siblings; 5515 } 5516 }; 5517 tree.get_next_sibling = function(b) { 5518 var i, siblings; 5519 if (b == null) { 5520 b = selected_branch; 5521 } 5522 if (b != null) { 5523 siblings = tree.get_siblings(b); 5524 n = siblings.length; 5525 i = siblings.indexOf(b); 5526 if (i < n) { 5527 return siblings[i + 1]; 5528 } 5529 } 5530 }; 5531 tree.get_prev_sibling = function(b) { 5532 var i, siblings; 5533 if (b == null) { 5534 b = selected_branch; 5535 } 5536 siblings = tree.get_siblings(b); 5537 n = siblings.length; 5538 i = siblings.indexOf(b); 5539 if (i > 0) { 5540 return siblings[i - 1]; 5541 } 5542 }; 5543 tree.select_next_sibling = function(b) { 5544 var next; 5545 if (b == null) { 5546 b = selected_branch; 5547 } 5548 if (b != null) { 5549 next = tree.get_next_sibling(b); 5550 if (next != null) { 5551 return tree.select_branch(next); 5552 } 5553 } 5554 }; 5555 tree.select_prev_sibling = function(b) { 5556 var prev; 5557 if (b == null) { 5558 b = selected_branch; 5559 } 5560 if (b != null) { 5561 prev = tree.get_prev_sibling(b); 5562 if (prev != null) { 5563 return tree.select_branch(prev); 5564 } 5565 } 5566 }; 5567 tree.get_first_child = function(b) { 5568 var _ref; 5569 if (b == null) { 5570 b = selected_branch; 5571 } 5572 if (b != null) { 5573 if (((_ref = b.children) != null ? _ref.length : void 0) > 0) { 5574 return b.children[0]; 5575 } 5576 } 5577 }; 5578 tree.get_closest_ancestor_next_sibling = function(b) { 5579 var next, parent; 5580 next = tree.get_next_sibling(b); 5581 if (next != null) { 5582 return next; 5583 } else { 5584 parent = tree.get_parent_branch(b); 5585 return tree.get_closest_ancestor_next_sibling(parent); 5586 } 5587 }; 5588 tree.get_next_branch = function(b) { 5589 var next; 5590 if (b == null) { 5591 b = selected_branch; 5592 } 5593 if (b != null) { 5594 next = tree.get_first_child(b); 5595 if (next != null) { 5596 return next; 5597 } else { 5598 next = tree.get_closest_ancestor_next_sibling(b); 5599 return next; 5600 } 5601 } 5602 }; 5603 tree.select_next_branch = function(b) { 5604 var next; 5605 if (b == null) { 5606 b = selected_branch; 5607 } 5608 if (b != null) { 5609 next = tree.get_next_branch(b); 5610 if (next != null) { 5611 tree.select_branch(next); 5612 return next; 5613 } 5614 } 5615 }; 5616 tree.last_descendant = function(b) { 5617 var last_child; 5618 if (b == null) { 5619 debugger; 5620 } 5621 n = b.children.length; 5622 if (n === 0) { 5623 return b; 5624 } else { 5625 last_child = b.children[n - 1]; 5626 return tree.last_descendant(last_child); 5627 } 5628 }; 5629 tree.get_prev_branch = function(b) { 5630 var parent, prev_sibling; 5631 if (b == null) { 5632 b = selected_branch; 5633 } 5634 if (b != null) { 5635 prev_sibling = tree.get_prev_sibling(b); 5636 if (prev_sibling != null) { 5637 return tree.last_descendant(prev_sibling); 5638 } else { 5639 parent = tree.get_parent_branch(b); 5640 return parent; 5641 } 5642 } 5643 }; 5644 return tree.select_prev_branch = function(b) { 5645 var prev; 5646 if (b == null) { 5647 b = selected_branch; 5648 } 5649 if (b != null) { 5650 prev = tree.get_prev_branch(b); 5651 if (prev != null) { 5652 tree.select_branch(prev); 5653 return prev; 5654 } 5655 } 5656 }; 5657 } 5658 } 5659 } 5660 }; 5661 } 5662 ]); 5663 5664}).call(this); 5665 5666'use strict'; 5667 5668angular.module('angularDjangoRegistrationAuthApp', ['ngCookies', 'ngResource', 'ngSanitize', 'ngRoute']) 5669.run(function(djangoAuth){ 5670 djangoAuth.initialize('/api/rest-auth', false); 5671}); 5672 5673angular.module('angularDjangoRegistrationAuthApp')
5674 .service('djangoAuth', function djangoAuth($q, $http, $cookies, $rootScope) { 5675 // AngularJS will instantiate a singleton by calling "new" on this function 5676 var service = { 5677 /* START CUSTOMIZATION HERE */ 5678 // Change this to point to your Django REST Auth API 5679 // e.g. /api/rest-auth (DO NOT INCLUDE ENDING SLASH) 5680 'API_URL': '/api/rest-auth', 5681 // Set use_session to true to use Django sessions to store security token. 5682 // Set use_session to false to store the security token locally and transmit it as a custom header. 5683 'use_session': false, 5684 /* END OF CUSTOMIZATION */ 5685 'authenticated': null, 5686 'authPromise': null, 5687 'request': function(args) { 5688 // Let's retrieve the token from the cookie, if available 5689 if(localStorage.token){ 5690 $http.defaults.headers.common.Authorization = 'Token ' + localStorage.token; 5691 } 5692 // Continue 5693 params = args.params || {} 5694 args = args || {}; 5695 var deferred = $q.defer(), 5696 url = this.API_URL + args.url, 5697 method = args.method || "GET", 5698 params = params, 5699 data = args.data || {}; 5700 // Fire the request, as configured. 5701 $http({ 5702 url: url, 5703 withCredentials: this.use_session, 5704 method: method.toUpperCase(), 5705 headers: {'X-CSRFToken': $cookies['csrftoken']}, 5706 params: params, 5707 data: data 5708 }) 5709 .success(angular.bind(this,function(data, status, headers, config) { 5710 deferred.resolve(data, status); 5711 })) 5712 .error(angular.bind(this,function(data, status, headers, config) { 5713 console.log("error syncing with: " + url); 5714 // Set request status 5715 if(data){ 5716 data.status = status; 5717 } 5718 if(status == 0){ 5719 if(data == ""){ 5720 data = {}; 5721 data['status'] = 0; 5722 data['non_field_errors'] = ["Could not connect. Please try again."]; 5723 } 5724 // or if the data is null, then there was a timeout. 5725 if(data == null){ 5726 // Inject a non field error alerting the user 5727 // that there's been a timeout error. 5728 data = {}; 5729 data['status'] = 0; 5730 data['non_field_errors'] = ["Server timed out. Please try again."]; 5731 } 5732 } 5733 deferred.reject(data, status, headers, config); 5734 })); 5735 return deferred.promise; 5736 }, 5737 'register': function(username,password1,password2,email,more){ 5738 var data = { 5739 'username':username, 5740 'password1':password1, 5741 'password2':password2, 5742 'email':email 5743 } 5744 data = angular.extend(data,more); 5745 return this.request({ 5746 'method': "POST", 5747 'url': "/registration/", 5748 'data' :data 5749 }); 5750 }, 5751 'login': function(username,password){ 5752 var djangoAuth = this; 5753 return this.request({ 5754 'method': "POST", 5755 'url': "/login/", 5756 'data':{ 5757 'username':username, 5758 'password':password 5759 } 5760 }).then(function(data){ 5761 if(!djangoAuth.use_session){ 5762 $http.defaults.headers.common.Authorization = 'Token ' + data.key; 5763 localStorage.token = data.key; 5764 } 5765 djangoAuth.authenticated = true; 5766 $rootScope.$broadcast("djangoAuth.logged_in", data); 5767 }); 5768 }, 5769 'orcidLogin': function(code){ 5770 // POST request at rest-auth/orcid/login/ 5771 var djangoAuth = this; 5772 return this.request({ 5773 'method': "POST", 5774 'url': "/orcid/", 5775 'data': { 5776 'code': code 5777 } 5778 }).then(function(data){ 5779 // Handle successful ORCID login 5780 if(!djangoAuth.use_session){ 5781 $http.defaults.headers.common.Authorization = 'Token ' + data.key; 5782 localStorage.token = data.key; 5783 } 5784 djangoAuth.authenticated = true; 5785 $rootScope.$broadcast("djangoAuth.logged_in", data); 5786 return data; 5787 }, function(error) { 5788 console.error("ORCID login failed:", error); 5789 }); 5790 }, 5791 'orcidConnect': function(code){ //TODO 5792 var djangoAuth = this; 5793 return this.request({ 5794 'method': "POST", 5795 'url': "/orcid/connect/", 5796 'data': { 5797 'code': code 5798 } 5799 }).then(function(data){ 5800 // Handle successful ORCID connection 5801 $rootScope.$broadcast("djangoAuth.orcid_connected", data); 5802 return data; 5803 }, function(error) { 5804 console.error("ORCID connection failed:", error); 5805 }); 5806 }, 5807 'disconnectSocial': function(id){ 5808 return this.request({ 5809 'method': "POST", 5810 'url': "/socialaccounts/"+id+"/disconnect/" 5811 }); 5812 }, 5813 'logout': function(){ 5814 var djangoAuth = this; 5815 return this.request({ 5816 'method': "POST", 5817 'url': "/logout/" 5818 }).then(function(data){ 5819 delete $http.defaults.headers.common.Authorization; 5820 delete localStorage.token; 5821 djangoAuth.authenticated = false;
5822 $rootScope.$broadcast("djangoAuth.logged_out"); 5823 }); 5824 }, 5825 'changePassword': function(old_password, password1, password2){ 5826 return this.request({ 5827 'method': "POST", 5828 'url': "/password/change/", 5829 'data':{ 5830 'old_password':old_password, 5831 'new_password1':password1, 5832 'new_password2':password2 5833 } 5834 }); 5835 }, 5836 'resetPassword': function(email){ 5837 return this.request({ 5838 'method': "POST", 5839 'url': "/password/reset/", 5840 'data':{ 5841 'email':email 5842 } 5843 }); 5844 }, 5845 'profile': function(){ 5846 return this.request({ 5847 'method': "GET", 5848 'url': "/user/" 5849 }); 5850 }, 5851 'updateProfile': function(data){ 5852 return this.request({ 5853 'method': "PATCH", 5854 'url': "/user/", 5855 'data':data 5856 }); 5857 }, 5858 'verify': function(key){ 5859 return this.request({ 5860 'method': "POST", 5861 'url': "/registration/verify-email/", 5862 'data': {'key': key} 5863 }); 5864 }, 5865 'resendEmail': function(email){ 5866 return this.request({ 5867 'method': "POST", 5868 'url': "/registration/resend-email/", 5869 'data': {'email': email} 5870 }); 5871 }, 5872 'confirmReset': function(uid,token,password1,password2){ 5873 return this.request({ 5874 'method': "POST", 5875 'url': "/password/reset/confirm/", 5876 'data':{ 5877 'uid': uid, 5878 'token': token, 5879 'new_password1':password1, 5880 'new_password2':password2 5881 } 5882 }); 5883 }, 5884 'authenticationStatus': function(restrict, force){ 5885 // Set restrict to true to reject the promise if not logged in 5886 // Set to false or omit to resolve when status is known 5887 // Set force to true to ignore stored value and query API 5888 restrict = restrict || false; 5889 force = force || false; 5890 if(this.authPromise == null || force){ 5891 this.authPromise = this.request({ 5892 'method': "GET", 5893 'url': "/user/" 5894 }) 5895 } 5896 var da = this; 5897 var getAuthStatus = $q.defer(); 5898 if(this.authenticated != null && !force){ 5899 // We have a stored value which means we can pass it back right away. 5900 if(this.authenticated == false && restrict){ 5901 getAuthStatus.reject("User is not logged in."); 5902 }else{ 5903 getAuthStatus.resolve(); 5904 } 5905 }else{ 5906 // There isn't a stored value, or we're forcing a request back to 5907 // the API to get the authentication status. 5908 this.authPromise.then(function(){ 5909 da.authenticated = true; 5910 getAuthStatus.resolve(); 5911 },function(){ 5912 da.authenticated = false; 5913 if(restrict){ 5914 getAuthStatus.reject("User is not logged in."); 5915 }else{ 5916 getAuthStatus.resolve(); 5917 } 5918 }); 5919 } 5920 return getAuthStatus.promise; 5921 }, 5922 'initialize': function(url, sessions){ 5923 this.API_URL = url; 5924 this.use_session = sessions; 5925 return this.authenticationStatus(); 5926 } 5927 5928 } 5929 return service; 5930 }); 5931 5932/** 5933 * @license 5934 * Fuse - Lightweight fuzzy-search 5935 * 5936 * Copyright (c) 2012-2016 Kirollos Risk <[email protected]>. 5937 * All Rights Reserved. Apache Software License 2.0 5938 * 5939 * Licensed under the Apache License, Version 2.0 (the "License") 5940 * you may not use this file except in compliance with the License. 5941 * You may obtain a copy of the License at 5942 * 5943 * http://www.apache.org/licenses/LICENSE-2.0 5944 * 5945 * Unless required by applicable law or agreed to in writing, software 5946 * distributed under the License is distributed on an "AS IS" BASIS, 5947 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5948 * See the License for the specific language governing permissions and 5949 * limitations under the License. 5950 */ 5951;(function (global) { 5952 'use strict' 5953 5954 /** @type {function(...*)} */ 5955 function log () { 5956 console.log.apply(console, arguments) 5957 } 5958 5959 var defaultOptions = { 5960 // The name of the identifier property. If specified, the returned result will be a list 5961 // of the items' dentifiers, otherwise it will be a list of the items. 5962 id: null, 5963 5964 // Indicates whether comparisons should be case sensitive. 5965 5966 caseSensitive: false, 5967 5968 // An array of values that should be included from the searcher's output. When this array 5969 // contains elements, each result in the list will be of the form `{ item: ..., include1: ..., include2: ... }`.
5970 // Values you can include are `score`, `matchedLocations` 5971 include: [], 5972 5973 // Whether to sort the result list, by score 5974 shouldSort: true, 5975 5976 // The search function to use 5977 // Note that the default search function ([[Function]]) must conform to the following API: 5978 // 5979 // @param pattern The pattern string to search 5980 // @param options The search option 5981 // [[Function]].constructor = function(pattern, options) 5982 // 5983 // @param text: the string to search in for the pattern 5984 // @return Object in the form of: 5985 // - isMatch: boolean 5986 // - score: Int 5987 // [[Function]].prototype.search = function(text) 5988 searchFn: BitapSearcher, 5989 5990 // Default sort function 5991 sortFn: function (a, b) { 5992 return a.score - b.score 5993 }, 5994 5995 // The get function to use when fetching an object's properties. 5996 // The default will search nested paths *ie foo.bar.baz* 5997 getFn: deepValue, 5998 5999 // List of properties that will be searched. This also supports nested properties. 6000 keys: [], 6001 6002 // Will print to the console. Useful for debugging.
6003 verbose: false, 6004 6005 // When true, the search algorithm will search individual words **and** the full string, 6006 // computing the final score as a function of both. Note that when `tokenize` is `true`, 6007 // the `threshold`, `distance`, and `location` are inconsequential for individual tokens. 6008 tokenize: false, 6009 6010 // When true, the result set will only include records that match all tokens. Will only work 6011 // if `tokenize` is also true. 6012 matchAllTokens: false, 6013 6014 // Regex used to separate words when searching. Only applicable when `tokenize` is `true`. 6015 tokenSeparator: / +/g, 6016 6017 // Minimum number of characters that must be matched before a result is considered a match 6018 minMatchCharLength: 1, 6019 6020 // When true, the algorithm continues searching to the end of the input even if a perfect 6021 // match is found before the end of the same input. 6022 findAllMatches: false 6023 } 6024 6025 /** 6026 * @constructor 6027 * @param {!Array} list 6028 * @param {!Object<string, *>} options 6029 */ 6030 function Fuse (list, options) { 6031 var key 6032 6033 this.list = list 6034 this.options = options = options || {} 6035 6036 for (key in defaultOptions) { 6037 if (!defaultOptions.hasOwnProperty(key)) { 6038 continue; 6039 } 6040 // Add boolean type options 6041 if (typeof defaultOptions[key] === 'boolean') { 6042 this.options[key] = key in options ? options[key] : defaultOptions[key]; 6043 // Add all other options 6044 } else { 6045 this.options[key] = options[key] || defaultOptions[key] 6046 } 6047 } 6048 } 6049 6050 Fuse.VERSION = '2.6.2' 6051 6052 /** 6053 * Sets a new list for Fuse to match against. 6054 * @param {!Array} list 6055 * @return {!Array} The newly set list 6056 * @public 6057 */ 6058 Fuse.prototype.set = function (list) { 6059 this.list = list 6060 return list 6061 } 6062 6063 Fuse.prototype.search = function (pattern) { 6064 if (this.options.verbose) log('\nSearch term:', pattern, '\n') 6065 6066 this.pattern = pattern 6067 this.results = [] 6068 this.resultMap = {} 6069 this._keyMap = null 6070 6071 this._prepareSearchers() 6072 this._startSearch() 6073 this._computeScore() 6074 this._sort() 6075 6076 var output = this._format() 6077 return output 6078 } 6079 6080 Fuse.prototype._prepareSearchers = function () { 6081 var options = this.options 6082 var pattern = this.pattern 6083 var searchFn = options.searchFn 6084 var tokens = pattern.split(options.tokenSeparator) 6085 var i = 0 6086 var len = tokens.length 6087 6088 if (this.options.tokenize) { 6089 this.tokenSearchers = [] 6090 for (; i < len; i++) { 6091 this.tokenSearchers.push(new searchFn(tokens[i], options)) 6092 } 6093 } 6094 this.fullSeacher = new searchFn(pattern, options) 6095 } 6096 6097 Fuse.prototype._startSearch = function () { 6098 var options = this.options 6099 var getFn = options.getFn 6100 var list = this.list 6101 var listLen = list.length 6102 var keys = this.options.keys 6103 var keysLen = keys.length 6104 var key 6105 var weight 6106 var item = null 6107 var i 6108 var j 6109 6110 // Check the first item in the list, if it's a string, then we assume 6111 // that every item in the list is also a string, and thus it's a flattened array. 6112 if (typeof list[0] === 'string') { 6113 // Iterate over every item 6114 for (i = 0; i < listLen; i++) { 6115 this._analyze('', list[i], i, i) 6116 } 6117 } else { 6118 this._keyMap = {} 6119 // Otherwise, the first item is an Object (hopefully), and thus the searching 6120 // is done on the values of the keys of each item. 6121 // Iterate over every item 6122 for (i = 0; i < listLen; i++) { 6123 item = list[i] 6124 // Iterate over every key 6125 for (j = 0; j < keysLen; j++) { 6126 key = keys[j] 6127 if (typeof key !== 'string') { 6128 weight = (1 - key.weight) || 1 6129 this._keyMap[key.name] = { 6130 weight: weight 6131 } 6132 if (key.weight <= 0 || key.weight > 1) { 6133 throw new Error('Key weight has to be > 0 and <= 1') 6134 } 6135 key = key.name 6136 } else { 6137 this._keyMap[key] = { 6138 weight: 1 6139 } 6140 } 6141 this._analyze(key, getFn(item, key, []), item, i) 6142 } 6143 } 6144 } 6145 } 6146 6147 Fuse.prototype._analyze = function (key, text, entity, index) { 6148 var options = this.options 6149 var words 6150 var scores 6151 var exists = false 6152 var existingResult 6153 var averageScore 6154 var finalScore 6155 var scoresLen 6156 var mainSearchResult 6157 var tokenSearcher 6158 var termScores 6159 var word 6160 var tokenSearchResult 6161 var hasMatchInText 6162 var checkTextMatches 6163 var i 6164 var j 6165 6166 // Check if the text can be searched 6167 if (text === undefined || text === null) { 6168 return 6169 } 6170 6171 scores = [] 6172 6173 var numTextMatches = 0 6174 6175 if (typeof text === 'string') { 6176 words = text.split(options.tokenSeparator) 6177 6178 if (options.verbose) log('---------\nKey:', key) 6179 6180 if (this.options.tokenize) { 6181 for (i = 0; i < this.tokenSearchers.length; i++) { 6182 tokenSearcher = this.tokenSearchers[i] 6183 6184 if (options.verbose) log('Pattern:', tokenSearcher.pattern) 6185 6186 termScores = [] 6187 hasMatchInText = false 6188 6189 for (j = 0; j < words.length; j++) { 6190 word = words[j] 6191 tokenSearchResult = tokenSearcher.search(word) 6192 var obj = {} 6193 if (tokenSearchResult.isMatch) { 6194 obj[word] = tokenSearchResult.score 6195 exists = true 6196 hasMatchInText = true 6197 scores.push(tokenSearchResult.score) 6198 } else { 6199 obj[word] = 1 6200 if (!this.options.matchAllTokens) { 6201 scores.push(1) 6202 } 6203 } 6204 termScores.push(obj) 6205 } 6206 6207 if (hasMatchInText) { 6208 numTextMatches++ 6209 } 6210 6211 if (options.verbose) log('Token scores:', termScores) 6212 } 6213 6214 averageScore = scores[0] 6215 scoresLen = scores.length 6216 for (i = 1; i < scoresLen; i++) { 6217 averageScore += scores[i] 6218 } 6219 averageScore = averageScore / scoresLen 6220 6221 if (options.verbose) log('Token score average:', averageScore) 6222 } 6223 6224 mainSearchResult = this.fullSeacher.search(text) 6225 if (options.verbose) log('Full text score:', mainSearchResult.score) 6226 6227 finalScore = mainSearchResult.score 6228 if (averageScore !== undefined) { 6229 finalScore = (finalScore + averageScore) / 2 6230 } 6231 6232 if (options.verbose) log('Score average:', finalScore) 6233 6234 checkTextMatches = (this.options.tokenize && this.options.matchAllTokens) ? numTextMatches >= this.tokenSearchers.length : true 6235 6236 if (options.verbose) log('Check Matches', checkTextMatches) 6237 6238 // If a match is found, add the item to <rawResults>, including its score 6239 if ((exists || mainSearchResult.isMatch) && checkTextMatches) { 6240 // Check if the item already exists in our results 6241 existingResult = this.resultMap[index] 6242 6243 if (existingResult) { 6244 // Use the lowest score 6245 // existingResult.score, bitapResult.score 6246 existingResult.output.push({ 6247 key: key, 6248 score: finalScore, 6249 matchedIndices: mainSearchResult.matchedIndices 6250 }) 6251 } else { 6252 // Add it to the raw result list 6253 this.resultMap[index] = { 6254 item: entity, 6255 output: [{ 6256 key: key, 6257 score: finalScore, 6258 matchedIndices: mainSearchResult.matchedIndices 6259 }] 6260 } 6261 6262 this.results.push(this.resultMap[index]) 6263 } 6264 } 6265 } else if (isArray(text)) { 6266 for (i = 0; i < text.length; i++) { 6267 this._analyze(key, text[i], entity, index) 6268 } 6269 } 6270 } 6271 6272 Fuse.prototype._computeScore = function () { 6273 var i 6274 var j 6275 var keyMap = this._keyMap 6276 var totalScore 6277 var output 6278 var scoreLen 6279 var score 6280 var weight 6281 var results = this.results 6282 var bestScore 6283 var nScore 6284 6285 if (this.options.verbose) log('\n\nComputing score:\n') 6286 6287 for (i = 0; i < results.length; i++) { 6288 totalScore = 0 6289 output = results[i].output 6290 scoreLen = output.length 6291 6292 bestScore = 1 6293 6294 for (j = 0; j < scoreLen; j++) { 6295 score = output[j].score 6296 weight = keyMap ? keyMap[output[j].key].weight : 1 6297 6298 nScore = score * weight 6299 6300 if (weight !== 1) { 6301 bestScore = Math.min(bestScore, nScore) 6302 } else {
6303 totalScore += nScore 6304 output[j].nScore = nScore 6305 } 6306 } 6307 6308 if (bestScore === 1) { 6309 results[i].score = totalScore / scoreLen 6310 } else { 6311 results[i].score = bestScore 6312 } 6313 6314 if (this.options.verbose) log(results[i]) 6315 } 6316 } 6317 6318 Fuse.prototype._sort = function () { 6319 var options = this.options 6320 if (options.shouldSort) { 6321 if (options.verbose) log('\n\nSorting....') 6322 this.results.sort(options.sortFn) 6323 } 6324 } 6325 6326 Fuse.prototype._format = function () { 6327 var options = this.options 6328 var getFn = options.getFn 6329 var finalOutput = [] 6330 var item 6331 var i 6332 var len 6333 var results = this.results 6334 var replaceValue 6335 var getItemAtIndex 6336 var include = options.include 6337 6338 if (options.verbose) log('\n\nOutput:\n\n', results) 6339 6340 // Helper function, here for speed-up, which replaces the item with its value, 6341 // if the options specifies it, 6342 replaceValue = options.id ? function (index) { 6343 results[index].item = getFn(results[index].item, options.id, [])[0] 6344 } : function () {} 6345 6346 getItemAtIndex = function (index) { 6347 var record = results[index] 6348 var data 6349 var j 6350 var output 6351 var _item 6352 var _result 6353 6354 // If `include` has values, put the item in the result 6355 if (include.length > 0) { 6356 data = { 6357 item: record.item 6358 } 6359 if (include.indexOf('matches') !== -1) { 6360 output = record.output 6361 data.matches = [] 6362 for (j = 0; j < output.length; j++) { 6363 _item = output[j] 6364 _result = { 6365 indices: _item.matchedIndices 6366 } 6367 if (_item.key) { 6368 _result.key = _item.key 6369 } 6370 data.matches.push(_result) 6371 } 6372 } 6373 6374 if (include.indexOf('score') !== -1) { 6375 data.score = results[index].score 6376 } 6377 6378 } else { 6379 data = record.item 6380 } 6381 6382 return data 6383 } 6384 6385 // From the results, push into a new array only the item identifier (if specified) 6386 // of the entire item. This is because we don't want to return the <results>, 6387 // since it contains other metadata 6388 for (i = 0, len = results.length; i < len; i++) { 6389 replaceValue(i) 6390 item = getItemAtIndex(i) 6391 finalOutput.push(item) 6392 } 6393 6394 return finalOutput 6395 } 6396 6397 // Helpers 6398 6399 function deepValue (obj, path, list) { 6400 var firstSegment 6401 var remaining 6402 var dotIndex 6403 var value 6404 var i 6405 var len 6406 6407 if (!path) { 6408 // If there's no path left, we've gotten to the object we care about. 6409 list.push(obj) 6410 } else { 6411 dotIndex = path.indexOf('.') 6412 6413 if (dotIndex !== -1) { 6414 firstSegment = path.slice(0, dotIndex) 6415 remaining = path.slice(dotIndex + 1) 6416 } else { 6417 firstSegment = path 6418 } 6419 6420 value = obj[firstSegment] 6421 if (value !== null && value !== undefined) { 6422 if (!remaining && (typeof value === 'string' || typeof value === 'number')) { 6423 list.push(value) 6424 } else if (isArray(value)) { 6425 // Search each item in the array. 6426 for (i = 0, len = value.length; i < len; i++) { 6427 deepValue(value[i], remaining, list) 6428 } 6429 } else if (remaining) { 6430 // An object. Recurse further. 6431 deepValue(value, remaining, list) 6432 } 6433 } 6434 } 6435 6436 return list 6437 } 6438 6439 function isArray (obj) { 6440 return Object.prototype.toString.call(obj) === '[object Array]' 6441 } 6442 6443 /** 6444 * Adapted from "Diff, Match and Patch", by Google 6445 * 6446 * http://code.google.com/p/google-diff-match-patch/ 6447 * 6448 * Modified by: Kirollos Risk <[email protected]> 6449 * ----------------------------------------------- 6450 * Details: the algorithm and structure was modified to allow the creation of 6451 * <Searcher> instances with a <search> method which does the actual 6452 * bitap search. The <pattern> (the string that is searched for) is only defined 6453 * once per instance and thus it eliminates redundant re-creation when searching 6454 * over a list of strings. 6455 * 6456 * Licensed under the Apache License, Version 2.0 (the "License")
6457 * you may not use this file except in compliance with the License. 6458 * 6459 * @constructor 6460 */ 6461 function BitapSearcher (pattern, options) { 6462 options = options || {} 6463 this.options = options 6464 this.options.location = options.location || BitapSearcher.defaultOptions.location 6465 this.options.distance = 'distance' in options ? options.distance : BitapSearcher.defaultOptions.distance 6466 this.options.threshold = 'threshold' in options ? options.threshold : BitapSearcher.defaultOptions.threshold 6467 this.options.maxPatternLength = options.maxPatternLength || BitapSearcher.defaultOptions.maxPatternLength 6468 6469 this.pattern = options.caseSensitive ? pattern : pattern.toLowerCase() 6470 this.patternLen = pattern.length 6471 6472 if (this.patternLen <= this.options.maxPatternLength) { 6473 this.matchmask = 1 << (this.patternLen - 1) 6474 this.patternAlphabet = this._calculatePatternAlphabet() 6475 } 6476 } 6477 6478 BitapSearcher.defaultOptions = { 6479 // Approximately where in the text is the pattern expected to be found? 6480 location: 0, 6481 6482 // Determines how close the match must be to the fuzzy location (specified above). 6483 // An exact letter match which is 'distance' characters away from the fuzzy location 6484 // would score as a complete mismatch. A distance of '0' requires the match be at 6485 // the exact location specified, a threshold of '1000' would require a perfect match 6486 // to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. 6487 distance: 100, 6488 6489 // At what point does the match algorithm give up. A threshold of '0.0' requires a perfect match 6490 // (of both letters and location), a threshold of '1.0' would match anything. 6491 threshold: 0.6, 6492 6493 // Machine word size 6494 maxPatternLength: 32 6495 } 6496 6497 /** 6498 * Initialize the alphabet for the Bitap algorithm. 6499 * @return {Object} Hash of character locations. 6500 * @private 6501 */ 6502 BitapSearcher.prototype._calculatePatternAlphabet = function () { 6503 var mask = {}, 6504 i = 0 6505 6506 for (i = 0; i < this.patternLen; i++) { 6507 mask[this.pattern.charAt(i)] = 0 6508 } 6509 6510 for (i = 0; i < this.patternLen; i++) { 6511 mask[this.pattern.charAt(i)] |= 1 << (this.pattern.length - i - 1) 6512 } 6513 6514 return mask 6515 } 6516 6517 /** 6518 * Compute and return the score for a match with `e` errors and `x` location. 6519 * @param {number} errors Number of errors in match. 6520 * @param {number} location Location of match. 6521 * @return {number} Overall score for match (0.0 = good, 1.0 = bad). 6522 * @private 6523 */ 6524 BitapSearcher.prototype._bitapScore = function (errors, location) { 6525 var accuracy = errors / this.patternLen, 6526 proximity = Math.abs(this.options.location - location) 6527 6528 if (!this.options.distance) { 6529 // Dodge divide by zero error. 6530 return proximity ? 1.0 : accuracy 6531 } 6532 return accuracy + (proximity / this.options.distance) 6533 } 6534 6535 /** 6536 * Compute and return the result of the search 6537 * @param {string} text The text to search in 6538 * @return {{isMatch: boolean, score: number}} Literal containing: 6539 * isMatch - Whether the text is a match or not 6540 * score - Overall score for the match 6541 * @public 6542 */ 6543 BitapSearcher.prototype.search = function (text) { 6544 var options = this.options 6545 var i 6546 var j 6547 var textLen 6548 var findAllMatches 6549 var location 6550 var threshold 6551 var bestLoc 6552 var binMin 6553 var binMid 6554 var binMax 6555 var start, finish 6556 var bitArr 6557 var lastBitArr 6558 var charMatch 6559 var score 6560 var locations 6561 var matches 6562 var isMatched 6563 var matchMask 6564 var matchedIndices 6565 var matchesLen 6566 var match 6567 6568 text = options.caseSensitive ? text : text.toLowerCase() 6569 6570 if (this.pattern === text) { 6571 // Exact match 6572 return { 6573 isMatch: true, 6574 score: 0, 6575 matchedIndices: [[0, text.length - 1]] 6576 } 6577 } 6578 6579 // When pattern length is greater than the machine word length, just do a a regex comparison 6580 if (this.patternLen > options.maxPatternLength) { 6581 matches = text.match(new RegExp(this.pattern.replace(options.tokenSeparator, '|'))) 6582 isMatched = !!matches 6583 6584 if (isMatched) { 6585 matchedIndices = [] 6586 for (i = 0, matchesLen = matches.length; i < matchesLen; i++) { 6587 match = matches[i] 6588 matchedIndices.push([text.indexOf(match), match.length - 1]) 6589 } 6590 } 6591 6592 return {
6593 isMatch: isMatched, 6594 // TODO: revisit this score 6595 score: isMatched ? 0.5 : 1, 6596 matchedIndices: matchedIndices 6597 } 6598 } 6599 6600 findAllMatches = options.findAllMatches 6601 6602 location = options.location 6603 // Set starting location at beginning text and initialize the alphabet. 6604 textLen = text.length 6605 // Highest score beyond which we give up. 6606 threshold = options.threshold 6607 // Is there a nearby exact match? (speedup) 6608 bestLoc = text.indexOf(this.pattern, location) 6609 6610 // a mask of the matches 6611 matchMask = [] 6612 for (i = 0; i < textLen; i++) { 6613 matchMask[i] = 0 6614 } 6615 6616 if (bestLoc != -1) { 6617 threshold = Math.min(this._bitapScore(0, bestLoc), threshold) 6618 // What about in the other direction? (speed up) 6619 bestLoc = text.lastIndexOf(this.pattern, location + this.patternLen) 6620 6621 if (bestLoc != -1) { 6622 threshold = Math.min(this._bitapScore(0, bestLoc), threshold) 6623 } 6624 } 6625 6626 bestLoc = -1 6627 score = 1 6628 locations = [] 6629 binMax = this.patternLen + textLen 6630 6631 for (i = 0; i < this.patternLen; i++) { 6632 // Scan for the best match; each iteration allows for one more error. 6633 // Run a binary search to determine how far from the match location we can stray 6634 // at this error level. 6635 binMin = 0 6636 binMid = binMax 6637 while (binMin < binMid) { 6638 if (this._bitapScore(i, location + binMid) <= threshold) { 6639 binMin = binMid 6640 } else { 6641 binMax = binMid 6642 } 6643 binMid = Math.floor((binMax - binMin) / 2 + binMin) 6644 } 6645 6646 // Use the result from this iteration as the maximum for the next. 6647 binMax = binMid 6648 start = Math.max(1, location - binMid + 1) 6649 if (findAllMatches) { 6650 finish = textLen; 6651 } else { 6652 finish = Math.min(location + binMid, textLen) + this.patternLen 6653 } 6654 6655 // Initialize the bit array 6656 bitArr = Array(finish + 2) 6657 6658 bitArr[finish + 1] = (1 << i) - 1 6659 6660 for (j = finish; j >= start; j--) { 6661 charMatch = this.patternAlphabet[text.charAt(j - 1)] 6662 6663 if (charMatch) { 6664 matchMask[j - 1] = 1 6665 } 6666 6667 if (i === 0) { 6668 // First pass: exact match. 6669 bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch 6670 } else { 6671 // Subsequent passes: fuzzy match. 6672 bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch | (((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1) | lastBitArr[j + 1] 6673 } 6674 if (bitArr[j] & this.matchmask) { 6675 score = this._bitapScore(i, j - 1) 6676 6677 // This match will almost certainly be better than any existing match. 6678 // But check anyway. 6679 if (score <= threshold) { 6680 // Indeed it is 6681 threshold = score 6682 bestLoc = j - 1 6683 locations.push(bestLoc) 6684 6685 if (bestLoc > location) { 6686 // When passing loc, don't exceed our current distance from loc. 6687 start = Math.max(1, 2 * location - bestLoc) 6688 } else { 6689 // Already passed loc, downhill from here on in. 6690 break 6691 } 6692 } 6693 } 6694 } 6695 6696 // No hope for a (better) match at greater error levels. 6697 if (this._bitapScore(i + 1, location) > threshold) { 6698 break 6699 } 6700 lastBitArr = bitArr 6701 } 6702 6703 matchedIndices = this._getMatchedIndices(matchMask) 6704 6705 // Count exact matches (those with a score of 0) to be "almost" exact 6706 return { 6707 isMatch: bestLoc >= 0, 6708 score: score === 0 ? 0.001 : score, 6709 matchedIndices: matchedIndices 6710 } 6711 } 6712 6713 BitapSearcher.prototype._getMatchedIndices = function (matchMask) { 6714 var matchedIndices = [] 6715 var start = -1 6716 var end = -1 6717 var i = 0 6718 var match 6719 var len = matchMask.length 6720 for (; i < len; i++) { 6721 match = matchMask[i] 6722 if (match && start === -1) { 6723 start = i 6724 } else if (!match && start !== -1) { 6725 end = i - 1 6726 if ((end - start) + 1 >= this.options.minMatchCharLength) { 6727 matchedIndices.push([start, end]) 6728 } 6729 start = -1 6730 } 6731 } 6732 if (matchMask[i - 1]) { 6733 if ((i-1 - start) + 1 >= this.options.minMatchCharLength) { 6734 matchedIndices.push([start, i - 1]) 6735 } 6736 } 6737 return matchedIndices 6738 } 6739
6740 // Export to Common JS Loader 6741 if (typeof exports === 'object') { 6742 // Node. Does not work with strict CommonJS, but 6743 // only CommonJS-like environments that support module.exports, 6744 // like Node. 6745 module.exports = Fuse 6746 } else if (typeof define === 'function' && define.amd) { 6747 // AMD. Register as an anonymous module. 6748 define(function () { 6749 return Fuse 6750 }) 6751 } else { 6752 // Browser globals (root is window) 6753 global.Fuse = Fuse 6754 } 6755 6756})(this); 6757 6758/*! 6759 * ngTagsInput v3.0.0 6760 * http://mbenford.github.io/ngTagsInput 6761 * 6762 * Copyright (c) 2013-2015 Michael Benford 6763 * License: MIT 6764 * 6765 * Generated at 2015-07-13 02:08:11 -0300 6766 */ 6767(function() { 6768'use strict'; 6769 6770var KEYS = { 6771 backspace: 8, 6772 tab: 9, 6773 enter: 13, 6774 escape: 27, 6775 space: 32, 6776 up: 38, 6777 down: 40, 6778 left: 37, 6779 right: 39, 6780 delete: 46, 6781 comma: 188 6782}; 6783 6784var MAX_SAFE_INTEGER = 9007199254740991; 6785var SUPPORTED_INPUT_TYPES = ['text', 'email', 'url']; 6786 6787var tagsInput = angular.module('ngTagsInput', []); 6788 6789/** 6790 * @ngdoc directive 6791 * @name tagsInput 6792 * @module ngTagsInput 6793 * 6794 * @description 6795 * Renders an input box with tag editing support. 6796 * 6797 * @param {string} ngModel Assignable Angular expression to data-bind to. 6798 * @param {string=} [template=NA] URL or id of a custom template for rendering each tag. 6799 * @param {string=} [displayProperty=text] Property to be rendered as the tag label. 6800 * @param {string=} [keyProperty=text] Property to be used as a unique identifier for the tag. 6801 * @param {string=} [type=text] Type of the input element. Only 'text', 'email' and 'url' are supported values. 6802 * @param {string=} [text=NA] Assignable Angular expression for data-binding to the element's text. 6803 * @param {number=} tabindex Tab order of the control. 6804 * @param {string=} [placeholder=Add a tag] Placeholder text for the control. 6805 * @param {number=} [minLength=3] Minimum length for a new tag. 6806 * @param {number=} [maxLength=MAX_SAFE_INTEGER] Maximum length allowed for a new tag. 6807 * @param {number=} [minTags=0] Sets minTags validation error key if the number of tags added is less than minTags. 6808 * @param {number=} [maxTags=MAX_SAFE_INTEGER] Sets maxTags validation error key if the number of tags added is greater 6809 * than maxTags. 6810 * @param {boolean=} [allowLeftoverText=false] Sets leftoverText validation error key if there is any leftover text in 6811 * the input element when the directive loses focus. 6812 * @param {string=} [removeTagSymbol=Ã] (Obsolete) Symbol character for the remove tag button. 6813 * @param {boolean=} [addOnEnter=true] Flag indicating that a new tag will be added on pressing the ENTER key. 6814 * @param {boolean=} [addOnSpace=false] Flag indicating that a new tag will be added on pressing the SPACE key. 6815 * @param {boolean=} [addOnComma=true] Flag indicating that a new tag will be added on pressing the COMMA key. 6816 * @param {boolean=} [addOnBlur=true] Flag indicating that a new tag will be added when the input field loses focus. 6817 * @param {boolean=} [addOnPaste=false] Flag indicating that the text pasted into the input field will be split into tags. 6818 * @param {string=} [pasteSplitPattern=,] Regular expression used to split the pasted text into tags. 6819 * @param {boolean=} [replaceSpacesWithDashes=true] Flag indicating that spaces will be replaced with dashes. 6820 * @param {string=} [allowedTagsPattern=.+] Regular expression that determines whether a new tag is valid. 6821 * @param {boolean=} [enableEditingLastTag=false] Flag indicating that the last tag will be moved back into the new tag 6822 * input box instead of being removed when the backspace key is pressed and the input box is empty. 6823 * @param {boolean=} [addFromAutocompleteOnly=false] Flag indicating that only tags coming from the autocomplete list 6824 * will be allowed. When this flag is true, addOnEnter, addOnComma, addOnSpace and addOnBlur values are ignored. 6825 * @param {boolean=} [spellcheck=true] Flag indicating whether the browser's spellcheck is enabled for the input field or not. 6826 * @param {expression=} [onTagAdding=NA] Expression to evaluate that will be invoked before adding a new tag. The new 6827 * tag is available as $tag. This method must return either true or false. If false, the tag will not be added. 6828 * @param {expression=} [onTagAdded=NA] Expression to evaluate upon adding a new tag. The new tag is available as $tag. 6829 * @param {expression=}
6829 [onInvalidTag=NA] Expression to evaluate when a tag is invalid. The invalid tag is available as $tag. 6830 * @param {expression=} [onTagRemoving=NA] Expression to evaluate that will be invoked before removing a tag. The tag 6831 * is available as $tag. This method must return either true or false. If false, the tag will not be removed. 6832 * @param {expression=} [onTagRemoved=NA] Expression to evaluate upon removing an existing tag. The removed tag is 6833 * available as $tag. 6834 * @param {expression=} [onTagClicked=NA] Expression to evaluate upon clicking an existing tag. The clicked tag is available as $tag. 6835 */ 6836tagsInput.directive('tagsInput', ["$timeout", "$document", "$window", "tagsInputConfig", "tiUtil", function($timeout, $document, $window, tagsInputConfig, tiUtil) { 6837 function TagList(options, events, onTagAdding, onTagRemoving) { 6838 var self = {}, getTagText, setTagText, tagIsValid; 6839 6840 getTagText = function(tag) { 6841 return tiUtil.safeToString(tag[options.displayProperty]); 6842 }; 6843 6844 setTagText = function(tag, text) { 6845 tag[options.displayProperty] = text; 6846 }; 6847 6848 tagIsValid = function(tag) { 6849 var tagText = getTagText(tag); 6850 6851 return tagText && 6852 tagText.length >= options.minLength && 6853 tagText.length <= options.maxLength && 6854 options.allowedTagsPattern.test(tagText) && 6855 !tiUtil.findInObjectArray(self.items, tag, options.keyProperty || options.displayProperty) && 6856 onTagAdding({ $tag: tag }); 6857 }; 6858 6859 self.items = []; 6860 6861 self.addText = function(text) { 6862 var tag = {}; 6863 setTagText(tag, text); 6864 return self.add(tag); 6865 }; 6866 6867 self.add = function(tag) { 6868 var tagText = getTagText(tag); 6869 6870 if (options.replaceSpacesWithDashes) { 6871 tagText = tiUtil.replaceSpacesWithDashes(tagText); 6872 } 6873 6874 setTagText(tag, tagText); 6875 6876 if (tagIsValid(tag)) { 6877 self.items.push(tag); 6878 events.trigger('tag-added', { $tag: tag }); 6879 } 6880 else if (tagText) { 6881 events.trigger('invalid-tag', { $tag: tag }); 6882 } 6883 6884 return tag; 6885 }; 6886 6887 self.remove = function(index) { 6888 var tag = self.items[index]; 6889 6890 if (onTagRemoving({ $tag: tag })) { 6891 self.items.splice(index, 1); 6892 self.clearSelection(); 6893 events.trigger('tag-removed', { $tag: tag }); 6894 return tag; 6895 } 6896 }; 6897 6898 self.select = function(index) { 6899 if (index < 0) { 6900 index = self.items.length - 1; 6901 } 6902 else if (index >= self.items.length) { 6903 index = 0; 6904 } 6905 6906 self.index = index; 6907 self.selected = self.items[index]; 6908 }; 6909 6910 self.selectPrior = function() { 6911 self.select(--self.index); 6912 }; 6913 6914 self.selectNext = function() { 6915 self.select(++self.index); 6916 }; 6917 6918 self.removeSelected = function() { 6919 return self.remove(self.index); 6920 }; 6921 6922 self.clearSelection = function() { 6923 self.selected = null; 6924 self.index = -1; 6925 }; 6926 6927 self.clearSelection(); 6928 6929 return self; 6930 } 6931 6932 function validateType(type) { 6933 return SUPPORTED_INPUT_TYPES.indexOf(type) !== -1; 6934 } 6935 6936 return { 6937 restrict: 'E', 6938 require: 'ngModel', 6939 scope: { 6940 tags: '=ngModel', 6941 text: '=?', 6942 onTagAdding: '&', 6943 onTagAdded: '&', 6944 onInvalidTag: '&', 6945 onTagRemoving: '&', 6946 onTagRemoved: '&', 6947 onTagClicked: '&', 6948 attributeList: '=' 6949 }, 6950 replace: false, 6951 transclude: true, 6952 templateUrl: 'ngTagsInput/tags-input.html', 6953 controller: ["$scope", "$attrs", "$element", function($scope, $attrs, $element) { 6954 $scope.events = tiUtil.simplePubSub(); 6955 6956 tagsInputConfig.load('tagsInput', $scope, $attrs, { 6957 template: [String, 'ngTagsInput/tag-item.html'], 6958 type: [String, 'text', validateType], 6959 placeholder: [String, 'Add a tag'], 6960 tabindex: [Number, null], 6961 removeTagSymbol: [String, String.fromCharCode(215)], 6962 replaceSpacesWithDashes: [Boolean, true], 6963 minLength: [Number, 3],
6964 maxLength: [Number, MAX_SAFE_INTEGER], 6965 addOnEnter: [Boolean, true], 6966 addOnSpace: [Boolean, false], 6967 addOnComma: [Boolean, true], 6968 addOnBlur: [Boolean, true], 6969 addOnPaste: [Boolean, false], 6970 pasteSplitPattern: [RegExp, /,/], 6971 allowedTagsPattern: [RegExp, /.+/], 6972 enableEditingLastTag: [Boolean, false], 6973 minTags: [Number, 0], 6974 maxTags: [Number, MAX_SAFE_INTEGER], 6975 displayProperty: [String, 'text'], 6976 keyProperty: [String, ''], 6977 allowLeftoverText: [Boolean, false], 6978 addFromAutocompleteOnly: [Boolean, false], 6979 spellcheck: [Boolean, true] 6980 }); 6981 6982 $scope.tagList = new TagList($scope.options, $scope.events, 6983 tiUtil.handleUndefinedResult($scope.onTagAdding, true), 6984 tiUtil.handleUndefinedResult($scope.onTagRemoving, true)); 6985 6986 this.registerAutocomplete = function() { 6987 var input = $element.find('input'); 6988 6989 return { 6990 addTag: function(tag) { 6991 return $scope.tagList.add(tag); 6992 }, 6993 focusInput: function() { 6994 input[0].focus(); 6995 }, 6996 getTags: function() { 6997 return $scope.tagList.items; 6998 }, 6999 getCurrentTagText: function() { 7000 return $scope.newTag.text(); 7001 }, 7002 getOptions: function() { 7003 return $scope.options; 7004 }, 7005 on: function(name, handler) { 7006 $scope.events.on(name, handler); 7007 return this; 7008 } 7009 }; 7010 }; 7011 7012 this.registerTagItem = function() { 7013 return { 7014 getOptions: function() { 7015 return $scope.options; 7016 }, 7017 removeTag: function(index) { 7018 if ($scope.disabled) { 7019 return; 7020 } 7021 $scope.tagList.remove(index); 7022 } 7023 }; 7024 }; 7025 }], 7026 link: function(scope, element, attrs, ngModelCtrl) { 7027 var hotkeys = [KEYS.enter, KEYS.comma, KEYS.space, KEYS.backspace, KEYS.delete, KEYS.left, KEYS.right], 7028 tagList = scope.tagList, 7029 events = scope.events, 7030 options = scope.options, 7031 input = element.find('input'), 7032 validationOptions = ['minTags', 'maxTags', 'allowLeftoverText'], 7033 setElementValidity; 7034 7035 setElementValidity = function() { 7036 ngModelCtrl.$setValidity('maxTags', tagList.items.length <= options.maxTags); 7037 ngModelCtrl.$setValidity('minTags', tagList.items.length >= options.minTags); 7038 ngModelCtrl.$setValidity('leftoverText', scope.hasFocus || options.allowLeftoverText ? true : !scope.newTag.text()); 7039 }; 7040 7041 ngModelCtrl.$isEmpty = function(value) { 7042 return !value || !value.length; 7043 }; 7044 7045 scope.newTag = { 7046 text: function(value) { 7047 if (angular.isDefined(value)) { 7048 scope.text = value; 7049 events.trigger('input-change', value); 7050 } 7051 else { 7052 return scope.text || ''; 7053 } 7054 }, 7055 invalid: null 7056 }; 7057 7058 scope.track = function(tag) { 7059 return tag[options.keyProperty || options.displayProperty]; 7060 }; 7061 7062 scope.$watch('tags', function(value) { 7063 if (value) { 7064 tagList.items = tiUtil.makeObjectArray(value, options.displayProperty); 7065 scope.tags = tagList.items; 7066 } 7067 else { 7068 tagList.items = []; 7069 } 7070 }); 7071 7072 scope.$watch('tags.length', function() { 7073 setElementValidity(); 7074 7075 // ngModelController won't trigger validators when the model changes (because it's an array), 7076 // so we need to do it ourselves. Unfortunately this won't trigger any registered formatter. 7077 ngModelCtrl.$validate(); 7078 }); 7079 7080 attrs.$observe('disabled', function(value) { 7081 scope.disabled = value; 7082 }); 7083 7084 scope.eventHandlers = { 7085 input: { 7086 keydown: function($event) { 7087 events.trigger('input-keydown', $event); 7088 }, 7089 focus: function() { 7090 if (scope.hasFocus) { 7091 return; 7092 } 7093 7094 scope.hasFocus = true; 7095 events.trigger('input-focus'); 7096 }, 7097 blur: function() { 7098 $timeout(function() { 7099 var activeElement = $document.prop('activeElement'), 7100 lostFocusToBrowserWindow = activeElement === input[0], 7101 lostFocusToChildElement = element[0].contains(activeElement); 7102 7103 if (lostFocusToBrowserWindow || !lostFocusToChildElement) { 7104 scope.hasFocus = false;
vendor: 14,422 bytes, lines 7105-7458
7105 events.trigger('input-blur'); 7106 } 7107 }); 7108 }, 7109 paste: function($event) { 7110 $event.getTextData = function() { 7111 var clipboardData = $event.clipboardData || ($event.originalEvent && $event.originalEvent.clipboardData); 7112 return clipboardData ? clipboardData.getData('text/plain') : $window.clipboardData.getData('Text'); 7113 }; 7114 events.trigger('input-paste', $event); 7115 } 7116 }, 7117 host: { 7118 click: function() { 7119 if (scope.disabled) { 7120 return; 7121 } 7122 input[0].focus(); 7123 } 7124 }, 7125 tag: { 7126 click: function(tag) { 7127 events.trigger('tag-clicked', { $tag: tag }); 7128 } 7129 } 7130 }; 7131 7132 events 7133 .on('tag-added', scope.onTagAdded) 7134 .on('invalid-tag', scope.onInvalidTag) 7135 .on('tag-removed', scope.onTagRemoved) 7136 .on('tag-clicked', scope.onTagClicked) 7137 .on('tag-added', function() { 7138 scope.newTag.text(''); 7139 }) 7140 .on('tag-added tag-removed', function() { 7141 scope.tags = tagList.items; 7142 // Ideally we should be able call $setViewValue here and let it in turn call $setDirty and $validate 7143 // automatically, but since the model is an array, $setViewValue does nothing and it's up to us to do it. 7144 // Unfortunately this won't trigger any registered $parser and there's no safe way to do it. 7145 ngModelCtrl.$setDirty(); 7146 }) 7147 .on('invalid-tag', function() { 7148 scope.newTag.invalid = true; 7149 }) 7150 .on('option-change', function(e) { 7151 if (validationOptions.indexOf(e.name) !== -1) { 7152 setElementValidity(); 7153 } 7154 }) 7155 .on('input-change', function() { 7156 tagList.clearSelection(); 7157 scope.newTag.invalid = null; 7158 }) 7159 .on('input-focus', function() { 7160 element.triggerHandler('focus'); 7161 ngModelCtrl.$setValidity('leftoverText', true); 7162 }) 7163 .on('input-blur', function() { 7164 if (options.addOnBlur && !options.addFromAutocompleteOnly) { 7165 tagList.addText(scope.newTag.text()); 7166 } 7167 element.triggerHandler('blur'); 7168 setElementValidity(); 7169 }) 7170 .on('input-keydown', function(event) { 7171 var key = event.keyCode, 7172 addKeys = {}, 7173 shouldAdd, shouldRemove, shouldSelect, shouldEditLastTag; 7174 7175 if (tiUtil.isModifierOn(event) || hotkeys.indexOf(key) === -1) { 7176 return; 7177 } 7178 7179 addKeys[KEYS.enter] = options.addOnEnter; 7180 addKeys[KEYS.comma] = options.addOnComma; 7181 addKeys[KEYS.space] = options.addOnSpace; 7182 7183 shouldAdd = !options.addFromAutocompleteOnly && addKeys[key]; 7184 shouldRemove = (key === KEYS.backspace || key === KEYS.delete) && tagList.selected; 7185 shouldEditLastTag = key === KEYS.backspace && scope.newTag.text().length === 0 && options.enableEditingLastTag; 7186 shouldSelect = (key === KEYS.backspace || key === KEYS.left || key === KEYS.right) && scope.newTag.text().length === 0 && !options.enableEditingLastTag; 7187 7188 if (shouldAdd) { 7189 tagList.addText(scope.newTag.text()); 7190 } 7191 else if (shouldEditLastTag) { 7192 var tag; 7193 7194 tagList.selectPrior(); 7195 tag = tagList.removeSelected(); 7196 7197 if (tag) { 7198 scope.newTag.text(tag[options.displayProperty]); 7199 } 7200 } 7201 else if (shouldRemove) { 7202 tagList.removeSelected(); 7203 } 7204 else if (shouldSelect) { 7205 if (key === KEYS.left || key === KEYS.backspace) { 7206 tagList.selectPrior(); 7207 } 7208 else if (key === KEYS.right) { 7209 tagList.selectNext(); 7210 } 7211 } 7212 7213 if (shouldAdd || shouldSelect || shouldRemove || shouldEditLastTag) { 7214 event.preventDefault(); 7215 } 7216 }) 7217 .on('input-paste', function(event) { 7218 if (options.addOnPaste) { 7219 var data = event.getTextData(); 7220 var tags = data.split(options.pasteSplitPattern); 7221 7222 if (tags.length > 1) { 7223 tags.forEach(function(tag) { 7224 tagList.addText(tag); 7225 }); 7226 event.preventDefault(); 7227 } 7228 } 7229 }); 7230 } 7231 }; 7232}]); 7233 7234 7235/** 7236 * @ngdoc directive 7237 * @name tiTagItem 7238 * @module ngTagsInput 7239 * 7240 * @description 7241 * Represents a tag item. Used internally by the tagsInput directive. 7242 */ 7243tagsInput.directive('tiTagItem', ["tiUtil", function(tiUtil) { 7244 return { 7245 restrict: 'E', 7246 require: '^tagsInput', 7247 template: '<ng-include src="$$template"></ng-include>', 7248 scope: { data: '=' }, 7249 link: function(scope, element, attrs, tagsInputCtrl) { 7250 var tagsInput = tagsInputCtrl.registerTagItem(), 7251 options = tagsInput.getOptions(); 7252 7253 scope.$$template = options.template; 7254 scope.$$removeTagSymbol = options.removeTagSymbol; 7255 scope.$getDisplayText = function() { 7256 return tiUtil.safeToString(scope.data[options.displayProperty]); 7257 }; 7258 scope.$removeTag = function() { 7259 tagsInput.removeTag(scope.$index); 7260 }; 7261 7262 scope.$watch('$parent.$index', function(value) { 7263 scope.$index = value; 7264 }); 7265 } 7266 }; 7267}]); 7268 7269 7270/** 7271 * @ngdoc directive 7272 * @name autoComplete 7273 * @module ngTagsInput 7274 * 7275 * @description 7276 * Provides autocomplete support for the tagsInput directive. 7277 * 7278 * @param {expression} source Expression to evaluate upon changing the input content. The input value is available as 7279 * $query. The result of the expression must be a promise that eventually resolves to an array of strings. 7280 * @param {string=} [template=NA] URL or id of a custom template for rendering each element of the autocomplete list. 7281 * @param {string=} [displayProperty=tagsInput.displayText] Property to be rendered as the autocomplete label. 7282 * @param {number=} [debounceDelay=100] Amount of time, in milliseconds, to wait before evaluating the expression in 7283 * the source option after the last keystroke. 7284 * @param {number=} [minLength=3] Minimum number of characters that must be entered before evaluating the expression 7285 * in the source option. 7286 * @param {boolean=} [highlightMatchedText=true] Flag indicating that the matched text will be highlighted in the 7287 * suggestions list. 7288 * @param {number=} [maxResultsToShow=10] Maximum number of results to be displayed at a time. 7289 * @param {boolean=} [loadOnDownArrow=false] Flag indicating that the source option will be evaluated when the down arrow 7290 * key is pressed and the suggestion list is closed. The current input value is available as $query. 7291 * @param {boolean=} [loadOnEmpty=false] Flag indicating that the source option will be evaluated when the input content 7292 * becomes empty. The $query variable will be passed to the expression as an empty string. 7293 * @param {boolean=} [loadOnFocus=false] Flag indicating that the source option will be evaluated when the input element 7294 * gains focus. The current input value is available as $query. 7295 * @param {boolean=} [selectFirstMatch=true] Flag indicating that the first match will be automatically selected once 7296 * the suggestion list is shown. 7297 */ 7298tagsInput.directive('autoComplete', ["$document", "$timeout", "$sce", "$q", "tagsInputConfig", "tiUtil", function($document, $timeout, $sce, $q, tagsInputConfig, tiUtil) { 7299 function SuggestionList(loadFn, options, events) { 7300 var self = {}, getDifference, lastPromise, getTagId; 7301 7302 getTagId = function() { 7303 return options.tagsInput.keyProperty || options.tagsInput.displayProperty; 7304 }; 7305 7306 getDifference = function(array1, array2) { 7307 return array1.filter(function(item) { 7308 return !tiUtil.findInObjectArray(array2, item, getTagId(), function(a, b) { 7309 if (options.tagsInput.replaceSpacesWithDashes) { 7310 a = tiUtil.replaceSpacesWithDashes(a); 7311 b = tiUtil.replaceSpacesWithDashes(b); 7312 } 7313 return tiUtil.defaultComparer(a, b); 7314 }); 7315 }); 7316 }; 7317 7318 self.reset = function() { 7319 lastPromise = null; 7320 7321 self.items = []; 7322 self.visible = false; 7323 self.index = -1; 7324 self.selected = null; 7325 self.query = null; 7326 }; 7327 self.show = function() { 7328 if (options.selectFirstMatch) { 7329 self.select(0); 7330 } 7331 else { 7332 self.selected = null; 7333 } 7334 self.visible = true; 7335 }; 7336 self.load = tiUtil.debounce(function(query, tags) { 7337 self.query = query; 7338 7339 var promise = $q.when(loadFn({ $query: query })); 7340 lastPromise = promise; 7341 7342 promise.then(function(items) { 7343 if (promise !== lastPromise) { 7344 return; 7345 } 7346 7347 items = tiUtil.makeObjectArray(items.data || items, getTagId()); 7348 items = getDifference(items, tags); 7349 self.items = items.slice(0, options.maxResultsToShow); 7350 7351 if (self.items.length > 0) { 7352 self.show(); 7353 } 7354 else { 7355 self.reset(); 7356 self.visible = true; 7357 } 7358 }); 7359 }, options.debounceDelay); 7360 7361 self.selectNext = function() { 7362 self.select(++self.index); 7363 }; 7364 self.selectPrior = function() { 7365 self.select(--self.index); 7366 }; 7367 self.select = function(index) { 7368 if (index < 0) { 7369 index = self.items.length - 1; 7370 } 7371 else if (index >= self.items.length) { 7372 index = 0; 7373 } 7374 self.index = index; 7375 self.selected = self.items[index]; 7376 events.trigger('suggestion-selected', index); 7377 }; 7378 7379 self.reset(); 7380 7381 return self; 7382 } 7383 7384 function scrollToElement(root, index) { 7385 var element = root.find('li').eq(index), 7386 parent = element.parent(), 7387 elementTop = element.prop('offsetTop'), 7388 elementHeight = element.prop('offsetHeight'), 7389 parentHeight = parent.prop('clientHeight'), 7390 parentScrollTop = parent.prop('scrollTop'); 7391 7392 if (elementTop < parentScrollTop) { 7393 parent.prop('scrollTop', elementTop); 7394 } 7395 else if (elementTop + elementHeight > parentHeight + parentScrollTop) { 7396 parent.prop('scrollTop', elementTop + elementHeight - parentHeight); 7397 } 7398 } 7399 7400 return { 7401 restrict: 'E', 7402 require: '^tagsInput', 7403 scope: { 7404 source: '&', 7405 filter: '=', 7406 attributeList: '=' 7407 }, 7408 templateUrl: 'ngTagsInput/auto-complete.html', 7409 controller: ["$scope", "$element", "$attrs", function($scope, $element, $attrs) { 7410 $scope.events = tiUtil.simplePubSub(); 7411 7412 tagsInputConfig.load('autoComplete', $scope, $attrs, { 7413 template: [String, 'ngTagsInput/auto-complete-match.html'], 7414 debounceDelay: [Number, 100], 7415 minLength: [Number, 3], 7416 highlightMatchedText: [Boolean, true], 7417 maxResultsToShow: [Number, 10], 7418 loadOnDownArrow: [Boolean, false], 7419 loadOnEmpty: [Boolean, false], 7420 loadOnFocus: [Boolean, false], 7421 selectFirstMatch: [Boolean, true], 7422 displayProperty: [String, ''] 7423 }); 7424 7425 $scope.suggestionList = new SuggestionList($scope.source, $scope.options, $scope.events); 7426 7427 this.registerAutocompleteMatch = function() { 7428 return { 7429 getOptions: function() { 7430 return $scope.options; 7431 }, 7432 getQuery: function() { 7433 return $scope.suggestionList.query; 7434 } 7435 }; 7436 }; 7437 }], 7438 link: function(scope, element, attrs, tagsInputCtrl) { 7439 var hotkeys = [KEYS.enter, KEYS.tab, KEYS.escape, KEYS.up, KEYS.down], 7440 suggestionList = scope.suggestionList, 7441 tagsInput = tagsInputCtrl.registerAutocomplete(), 7442 options = scope.options, 7443 events = scope.events, 7444 shouldLoadSuggestions; 7445 7446 options.tagsInput = tagsInput.getOptions(); 7447 7448 shouldLoadSuggestions = function(value) { 7449 return value && value.length >= options.minLength || !value && options.loadOnEmpty; 7450 }; 7451 7452 scope.addSuggestionByIndex = function(index) { 7453 suggestionList.select(index); 7454 scope.addSuggestion(); 7455 }; 7456 7457 scope.addSuggestion = function() { 7458 var added = false;
7459 7460 if (suggestionList.selected) { 7461 suggestionList.selected.text = '\'' + suggestionList.selected.text + '\''; 7462 tagsInput.addTag(angular.copy(suggestionList.selected)); 7463 suggestionList.reset(); 7464 tagsInput.focusInput(); 7465 7466 added = true; 7467 } 7468 return added; 7469 }; 7470 7471 scope.track = function(item) { 7472 return item[options.tagsInput.keyProperty || options.tagsInput.displayProperty]; 7473 }; 7474 7475 tagsInput 7476 .on('tag-added tag-removed invalid-tag input-blur', function() { 7477 suggestionList.reset(); 7478 }) 7479 .on('input-change', function(value) { 7480 if (shouldLoadSuggestions(value)) { 7481 suggestionList.load(value, tagsInput.getTags()); 7482 } 7483 else { 7484 suggestionList.reset(); 7485 } 7486 }) 7487 .on('input-focus', function() { 7488 var value = tagsInput.getCurrentTagText(); 7489 if (options.loadOnFocus && shouldLoadSuggestions(value)) { 7490 suggestionList.load(value, tagsInput.getTags()); 7491 } 7492 }) 7493 .on('input-keydown', function(event) { 7494 var key = event.keyCode, 7495 handled = false; 7496 7497 if (tiUtil.isModifierOn(event) || hotkeys.indexOf(key) === -1) { 7498 return; 7499 } 7500 7501 if (suggestionList.visible) { 7502 7503 if (key === KEYS.down) { 7504 suggestionList.selectNext(); 7505 handled = true; 7506 } 7507 else if (key === KEYS.up) { 7508 suggestionList.selectPrior(); 7509 handled = true; 7510 } 7511 else if (key === KEYS.escape) { 7512 suggestionList.reset(); 7513 handled = true; 7514 } 7515 else if (key === KEYS.enter || key === KEYS.tab) { 7516 handled = scope.addSuggestion(); 7517 } 7518 } 7519 else { 7520 if (key === KEYS.down && scope.options.loadOnDownArrow) { 7521 suggestionList.load(tagsInput.getCurrentTagText(), tagsInput.getTags()); 7522 handled = true; 7523 } 7524 } 7525 7526 if (handled) { 7527 event.preventDefault(); 7528 event.stopImmediatePropagation(); 7529 return false; 7530 } 7531 }); 7532 7533 events.on('suggestion-selected', function(index) { 7534 scrollToElement(element, index); 7535 }); 7536 7537 var initializing = true; 7538 scope.$watch('filter.selected', function() { 7539 if (initializing) { 7540 $timeout(function() { initializing = false; }); 7541 } else { 7542 $timeout(function() { 7543 suggestionList.load(tagsInput.getCurrentTagText(), tagsInput.getTags()); 7544 }); 7545 } 7546 }); 7547 } 7548 }; 7549}]); 7550 7551 7552/** 7553 * @ngdoc directive 7554 * @name tiAutocompleteMatch 7555 * @module ngTagsInput 7556 * 7557 * @description 7558 * Represents an autocomplete match. Used internally by the autoComplete directive. 7559 */ 7560tagsInput.directive('tiAutocompleteMatch', ["$sce", "tiUtil", function($sce, tiUtil) { 7561 return { 7562 restrict: 'E', 7563 require: '^autoComplete', 7564 template: '<ng-include src="$$template"></ng-include>', 7565 scope: { data: '='
vendor: 7,646 bytes, lines 7565-7791
7565 }, 7566 link: function(scope, element, attrs, autoCompleteCtrl) { 7567 var autoComplete = autoCompleteCtrl.registerAutocompleteMatch(), 7568 options = autoComplete.getOptions(); 7569 7570 scope.$$template = options.template; 7571 scope.$index = scope.$parent.$index; 7572 7573 scope.$highlight = function(text) { 7574 if (options.highlightMatchedText) { 7575 text = tiUtil.safeHighlight(text, autoComplete.getQuery()); 7576 } 7577 return $sce.trustAsHtml(text); 7578 }; 7579 scope.$getDisplayText = function() { 7580 return tiUtil.safeToString(scope.data[options.displayProperty || options.tagsInput.displayProperty]); 7581 }; 7582 } 7583 }; 7584}]); 7585 7586 7587/** 7588 * @ngdoc directive 7589 * @name tiTranscludeAppend 7590 * @module ngTagsInput 7591 * 7592 * @description 7593 * Re-creates the old behavior of ng-transclude. Used internally by tagsInput directive. 7594 */ 7595tagsInput.directive('tiTranscludeAppend', function() { 7596 return function(scope, element, attrs, ctrl, transcludeFn) { 7597 transcludeFn(function(clone) { 7598 element.append(clone); 7599 }); 7600 }; 7601}); 7602 7603/** 7604 * @ngdoc directive 7605 * @name tiAutosize 7606 * @module ngTagsInput 7607 * 7608 * @description 7609 * Automatically sets the input's width so its content is always visible. Used internally by tagsInput directive. 7610 */ 7611tagsInput.directive('tiAutosize', ["tagsInputConfig", function(tagsInputConfig) { 7612 return { 7613 restrict: 'A', 7614 require: 'ngModel', 7615 link: function(scope, element, attrs, ctrl) { 7616 var threshold = tagsInputConfig.getTextAutosizeThreshold(), 7617 span, resize; 7618 7619 span = angular.element('<span class="input"></span>'); 7620 span.css('display', 'none') 7621 .css('visibility', 'hidden') 7622 .css('width', 'auto') 7623 .css('white-space', 'pre'); 7624 7625 element.parent().append(span); 7626 7627 resize = function(originalValue) { 7628 var value = originalValue, width; 7629 7630 if (angular.isString(value) && value.length === 0) { 7631 value = attrs.placeholder; 7632 } 7633 7634 if (value) { 7635 span.text(value); 7636 span.css('display', ''); 7637 width = span.prop('offsetWidth'); 7638 span.css('display', 'none'); 7639 } 7640 7641 element.css('width', width ? width + threshold + 'px' : ''); 7642 7643 return originalValue; 7644 }; 7645 7646 ctrl.$parsers.unshift(resize); 7647 ctrl.$formatters.unshift(resize); 7648 7649 attrs.$observe('placeholder', function(value) { 7650 if (!ctrl.$modelValue) { 7651 resize(value); 7652 } 7653 }); 7654 } 7655 }; 7656}]); 7657 7658/** 7659 * @ngdoc directive 7660 * @name tiBindAttrs 7661 * @module ngTagsInput 7662 * 7663 * @description 7664 * Binds attributes to expressions. Used internally by tagsInput directive. 7665 */ 7666tagsInput.directive('tiBindAttrs', function() { 7667 return function(scope, element, attrs) { 7668 scope.$watch(attrs.tiBindAttrs, function(value) { 7669 angular.forEach(value, function(value, key) { 7670 attrs.$set(key, value); 7671 }); 7672 }, true); 7673 }; 7674}); 7675 7676/** 7677 * @ngdoc service 7678 * @name tagsInputConfig 7679 * @module ngTagsInput 7680 * 7681 * @description 7682 * Sets global configuration settings for both tagsInput and autoComplete directives. It's also used internally to parse and 7683 * initialize options from HTML attributes. 7684 */ 7685tagsInput.provider('tagsInputConfig', function() { 7686 var globalDefaults = {}, 7687 interpolationStatus = {}, 7688 autosizeThreshold = 3; 7689 7690 /** 7691 * @ngdoc method 7692 * @name tagsInputConfig#setDefaults 7693 * @description Sets the default configuration option for a directive. 7694 * 7695 * @param {string} directive Name of the directive to be configured. Must be either 'tagsInput' or 'autoComplete'. 7696 * @param {object} defaults Object containing options and their values. 7697 * 7698 * @returns {object} The service itself for chaining purposes. 7699 */ 7700 this.setDefaults = function(directive, defaults) { 7701 globalDefaults[directive] = defaults; 7702 return this; 7703 }; 7704 7705 /** 7706 * @ngdoc method 7707 * @name tagsInputConfig#setActiveInterpolation 7708 * @description Sets active interpolation for a set of options. 7709 * 7710 * @param {string} directive Name of the directive to be configured. Must be either 'tagsInput' or 'autoComplete'. 7711 * @param {object} options Object containing which options should have interpolation turned on at all times. 7712 * 7713 * @returns {object} The service itself for chaining purposes. 7714 */ 7715 this.setActiveInterpolation = function(directive, options) { 7716 interpolationStatus[directive] = options; 7717 return this; 7718 }; 7719 7720 /** 7721 * @ngdoc method 7722 * @name tagsInputConfig#setTextAutosizeThreshold 7723 * @description Sets the threshold used by the tagsInput directive to re-size the inner input field element based on its contents. 7724 * 7725 * @param {number} threshold Threshold value, in pixels. 7726 * 7727 * @returns {object} The service itself for chaining purposes. 7728 */ 7729 this.setTextAutosizeThreshold = function(threshold) { 7730 autosizeThreshold = threshold; 7731 return this; 7732 }; 7733 7734 this.$get = ["$interpolate", function($interpolate) { 7735 var converters = {}; 7736 converters[String] = function(value) { return value; }; 7737 converters[Number] = function(value) { return parseInt(value, 10); }; 7738 converters[Boolean] = function(value) { return value.toLowerCase() === 'true'; }; 7739 converters[RegExp] = function(value) { return new RegExp(value); }; 7740 7741 return { 7742 load: function(directive, scope, attrs, options) { 7743 var defaultValidator = function() { return true; }; 7744 7745 scope.options = {}; 7746 7747 angular.forEach(options, function(value, key) { 7748 var type, localDefault, validator, converter, getDefault, updateValue; 7749 7750 type = value[0]; 7751 localDefault = value[1]; 7752 validator = value[2] || defaultValidator; 7753 converter = converters[type]; 7754 7755 getDefault = function() { 7756 var globalValue = globalDefaults[directive] && globalDefaults[directive][key]; 7757 return angular.isDefined(globalValue) ? globalValue : localDefault; 7758 }; 7759 7760 updateValue = function(value) { 7761 scope.options[key] = value && validator(value) ? converter(value) : getDefault(); 7762 }; 7763 7764 if (interpolationStatus[directive] && interpolationStatus[directive][key]) { 7765 attrs.$observe(key, function(value) { 7766 updateValue(value); 7767 scope.events.trigger('option-change', { name: key, newValue: value }); 7768 }); 7769 } 7770 else { 7771 updateValue(attrs[key] && $interpolate(attrs[key])(scope.$parent)); 7772 } 7773 }); 7774 }, 7775 getTextAutosizeThreshold: function() { 7776 return autosizeThreshold; 7777 } 7778 }; 7779 }]; 7780}); 7781 7782 7783/*** 7784 * @ngdoc service 7785 * @name tiUtil 7786 * @module ngTagsInput 7787 * 7788 * @description 7789 * Helper methods used internally by the directive. Should not be called directly from user code. 7790 */ 7791tagsInput.factory('tiUtil', ["$timeout",
vendor: 2,218 bytes, lines 7791-7865
7791function($timeout) { 7792 var self = {}; 7793 7794 self.debounce = function(fn, delay) { 7795 var timeoutId; 7796 return function() { 7797 var args = arguments; 7798 $timeout.cancel(timeoutId); 7799 timeoutId = $timeout(function() { fn.apply(null, args); }, delay); 7800 }; 7801 }; 7802 7803 self.makeObjectArray = function(array, key) { 7804 if (!angular.isArray(array) || array.length === 0 || angular.isObject(array[0])) { 7805 return array; 7806 } 7807 7808 var newArray = []; 7809 array.forEach(function(item) { 7810 var obj = {}; 7811 obj[key] = item; 7812 newArray.push(obj); 7813 }); 7814 return newArray; 7815 }; 7816 7817 self.findInObjectArray = function(array, obj, key, comparer) { 7818 var item = null; 7819 comparer = comparer || self.defaultComparer; 7820 7821 array.some(function(element) { 7822 if (comparer(element[key], obj[key])) { 7823 item = element; 7824 return true; 7825 } 7826 }); 7827 7828 return item; 7829 }; 7830 7831 self.defaultComparer = function(a, b) { 7832 // I'm aware of the internationalization issues regarding toLowerCase() 7833 // but I couldn't come up with a better solution right now 7834 return self.safeToString(a).toLowerCase() === self.safeToString(b).toLowerCase(); 7835 }; 7836 7837 self.safeHighlight = function(str, value) { 7838 if (!value) { 7839 return str; 7840 } 7841 7842 function escapeRegexChars(str) { 7843 return str.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); 7844 } 7845 7846 str = self.encodeHTML(str); 7847 value = self.encodeHTML(value); 7848 7849 var expression = new RegExp('&[^;]+;|' + escapeRegexChars(value), 'gi'); 7850 return str.replace(expression, function(match) { 7851 return match.toLowerCase() === value.toLowerCase() ? '<em>' + match + '</em>' : match; 7852 }); 7853 }; 7854 7855 self.safeToString = function(value) { 7856 return angular.isUndefined(value) || value == null ? '' : value.toString().trim(); 7857 }; 7858 7859 self.encodeHTML = function(value) { 7860 return self.safeToString(value) 7861 .replace(/&/g, '&') 7862 .replace(/</g, '<') 7863 .replace(/>/g, '>'); 7864 }; 7865
7866 self.handleUndefinedResult = function(fn, valueIfUndefined) { 7867 return function() { 7868 var result = fn.apply(null, arguments); 7869 return angular.isUndefined(result) ? valueIfUndefined : result; 7870 }; 7871 }; 7872 7873 self.replaceSpacesWithDashes = function(str) { 7874 return self.safeToString(str).replace(/\s/g, '-'); 7875 }; 7876 7877 self.isModifierOn = function(event) { 7878 return event.shiftKey || event.ctrlKey || event.altKey || event.metaKey; 7879 }; 7880 7881 self.simplePubSub = function() { 7882 var events = {}; 7883 return { 7884 on: function(names, handler) { 7885 names.split(' ').forEach(function(name) { 7886 if (!events[name]) { 7887 events[name] = []; 7888 } 7889 events[name].push(handler); 7890 }); 7891 return this; 7892 }, 7893 trigger: function(name, args) { 7894 var handlers = events[name] || []; 7895 handlers.every(function(handler) { 7896 return self.handleUndefinedResult(handler, true)(args); 7897 }); 7898 return this; 7899 } 7900 }; 7901 }; 7902 7903 return self; 7904}]); 7905 7906/* HTML templates */ 7907tagsInput.run(["$templateCache", function($templateCache) { 7908 $templateCache.put('ngTagsInput/tags-input.html', 7909 "<div class=\"host\" tabindex=\"-1\" ng-click=\"eventHandlers.host.click()\" ti-transclude-append><div class=\"tags\" ng-class=\"{focused: hasFocus}\"><ul class=\"tag-list\"><li class=\"tag-item\" ng-repeat=\"tag in tagList.items track by track(tag)\" ng-class=\"{ selected: tag == tagList.selected }\" ng-click=\"eventHandlers.tag.click(tag)\"><ti-tag-item data=\"::tag\"></ti-tag-item></li></ul><input class=\"input\" autocomplete=\"off\" ng-model=\"newTag.text\" ng-model-options=\"{getterSetter: true}\" ng-keydown=\"eventHandlers.input.keydown($event)\" ng-focus=\"eventHandlers.input.focus($event)\" ng-blur=\"eventHandlers.input.blur($event)\" ng-paste=\"eventHandlers.input.paste($event)\" ng-trim=\"false\" ng-class=\"{'invalid-tag': newTag.invalid}\" ng-disabled=\"disabled\" ti-bind-attrs=\"{type: options.type, placeholder: options.placeholder, tabindex: options.tabindex, spellcheck: options.spellcheck}\" ti-autosize></div></div>" 7910 ); 7911 7912 $templateCache.put('ngTagsInput/tag-item.html', 7913 "<span ng-bind=\"$getDisplayText()\"></span> <a class=\"remove-button\" ng-click=\"$removeTag()\" ng-bind=\"::$$removeTagSymbol\"></a>" 7914 ); 7915 7916 $templateCache.put('ngTagsInput/auto-complete.html', 7917 "<div class=\"autocomplete\" ng-if=\"suggestionList.visible\"><ul class=\"filter-list\"><li class=\"filter-item\" ng-class=\"{selected: $parent.filter.selected == filter}\" ng-repeat=\"filter in filter.list\" ng-click=\"$parent.filter.selected=filter\">{{attributeList[filter].label}}</li></ul><hr class=\"hr-slim\"><div class=\"clearfix\" style=\"height:10px;\"></div><ul class=\"suggestion-list\"><li class=\"suggestion-item\" ng-show=\"suggestionList.items.length < 1\">No suggestions</li></ul><ul class=\"suggestion-list\"><li class=\"suggestion-item\" ng-repeat=\"item in suggestionList.items track by track(item)\" ng-class=\"{selected: item == suggestionList.selected}\" ng-click=\"addSuggestionByIndex($index)\" ng-mouseenter=\"suggestionList.select($index)\"><ti-autocomplete-match data=\"::item\"></ti-autocomplete-match></li></ul></div>" 7918 ); 7919 7920 $templateCache.put('ngTagsInput/auto-complete-match.html', 7921 "<span ng-bind-html=\"$highlight($getDisplayText())\"></span>" 7922 ); 7923}]); 7924 7925}()); 7926'use strict'; 7927 7928angular.module('angularDjangoRegistrationAuthApp')
7929 .service('Validate', function Validate() { 7930 return { 7931 'message': { 7932 'minlength': 'This value is not long enough.', 7933 'maxlength': 'This value is too long.', 7934 'email': 'A properly formatted email address is required.', 7935 'required': 'This field is required.' 7936 }, 7937 'more_messages': { 7938 'demo': { 7939 'required': 'Here is a sample alternative required message.' 7940 } 7941 }, 7942 'check_more_messages': function(name,error){ 7943 return (this.more_messages[name] || [])[error] || null; 7944 }, 7945 validation_messages: function(field,form,error_bin){ 7946 var messages = []; 7947 for(var e in form[field].$error){ 7948 if(form[field].$error[e]){ 7949 var special_message = this.check_more_messages(field,e); 7950 if(special_message){ 7951 messages.push(special_message); 7952 }else if(this.message[e]){ 7953 messages.push(this.message[e]); 7954 }else{ 7955 messages.push("Error: " + e) 7956 } 7957 } 7958 } 7959 var deduped_messages = []; 7960 angular.forEach(messages, function(el, i){ 7961 if(deduped_messages.indexOf(el) === -1) deduped_messages.push(el); 7962 }); 7963 if(error_bin){ 7964 error_bin[field] = deduped_messages; 7965 } 7966 }, 7967 'form_validation': function(form,error_bin){ 7968 for(var field in form){ 7969 if(field.substr(0,1) != "$"){ 7970 this.validation_messages(field,form,error_bin); 7971 } 7972 } 7973 } 7974 } 7975}); 7976angular.module('elixir_front') 7977.controller('AboutController', ['$scope', function($scope) { 7978 var vm = this; 7979 window.scrollTo(0, 0); 7980}]); 7981 7982 7983angular.module('elixir_front') 7984.directive('homeInfo', ['$state','$stateParams', function($state, $stateParams) { 7985 return { 7986 restrict: 'EA', 7987 templateUrl: 'components/about/aboutContact.html', 7988 scope: true, 7989 link: function(scope, element, attrs) { 7990 scope.name = attrs["name"]; 7991 scope.initials = attrs["initials"]; 7992 scope.role = attrs["role"]; 7993 scope.image = attrs["image"]; 7994 7995 } 7996 } 7997}]); 7998angular.module('elixir_front') 7999.controller('CommunityController', ['$scope', function($scope) { 8000 var vm = this; 8001 window.scrollTo(0, 0); 8002}]); 8003angular.module('elixir_front') 8004.directive('detailsBar', [function() { 8005 return { 8006 restrict: 'A', 8007 templateUrl: 'components/detailsBar/detailsBar.html', 8008 link: function(scope, element, attrs) { 8009 scope.showToolbar = scope.toState.name == "search"; 8010 scope.$watch(function(){ 8011 return scope.toState.name 8012 }, function(newVal, oldVal){ 8013 scope.showToolbar = (scope.toState.name == "search"); 8014 }) 8015 } 8016 } 8017}]) 8018.controller('DetailsBarController', ['$rootScope', '$scope', '$state', '$stateParams', function($rootScope, $scope, $state, $stateParams) { 8019 var vm = this; 8020}]); 8021angular.module('elixir_front') 8022.controller('DomainBarController', ['$scope', 'djangoAuth', '$stateParams', '$timeout', 'Domain', function($scope, djangoAuth, $stateParams, $timeout, Domain) { 8023 // reference the service 8024 var vm = this; 8025 vm.Domain = Domain; 8026 8027 $timeout(function() { 8028 Domain.load($stateParams['domain']); 8029 }, 500); 8030}]) 8031.service('DomainConnection', ['$resource', function($resource){ 8032 return $resource('/api/d/', null, { 8033 'query': { 8034 isArray: true, 8035 method:'GET' 8036 }, 8037 'create': { 8038 method:'POST' 8039 } 8040 }) 8041}]) 8042.service('DomainDetailConnection', ['$resource', function($resource){ 8043 return $resource('/api/d/:domain', {'domain': '@domain'}, { 8044 'query': { 8045 isArray:false, 8046 method:'GET' 8047 }, 8048 'update': { 8049 method:'PUT' 8050 }, 8051 'delete': { 8052 method:'DELETE' 8053 } 8054 }) 8055}]) 8056.service('Domain', ['DomainDetailConnection', function(DomainDetailConnection){ 8057 var _this = this; 8058 this.current = {}; 8059 this.loaded = false; 8060 this.set = function(domain) { 8061 _this.current = domain; 8062 } 8063 this.load = function(domain) { 8064 if (typeof domain != 'undefined') { 8065 var response = DomainDetailConnection.query({'domain':domain}, function(response) { 8066 _this.set(response.data); 8067 _this.loaded = true; 8068 }); 8069 } else { 8070 _this.loaded = false;
8071 } 8072 } 8073 this.isLoaded = function() { 8074 return _this.loaded; 8075 } 8076 this.hasSubdomain = function() { 8077 return !_.isEmpty(_this.current); 8078 } 8079 this.hasTitle = function() { 8080 return _this.current.title !== undefined; 8081 } 8082 this.hasSubTitle = function() { 8083 return _this.current.sub_title !== undefined; 8084 } 8085 this.hasDescription = function() { 8086 return _this.current.description !== undefined; 8087 } 8088}]); 8089angular.module('elixir_front') 8090.service('DomainConnection', ['$resource', function($resource){ 8091 return $resource('/api/d/', null, { 8092 'query': { 8093 isArray: true, 8094 method:'GET' 8095 }, 8096 'create': { 8097 method:'POST' 8098 } 8099 }) 8100}]) 8101.service('DomainDetailConnection', ['$resource', function($resource){ 8102 return $resource('/api/d/:domain', {'domain': '@domain'}, { 8103 'query': { 8104 isArray:false, 8105 method:'GET' 8106 }, 8107 'update': { 8108 method:'PUT' 8109 }, 8110 'delete': { 8111 method:'DELETE' 8112 } 8113 }) 8114}]) 8115.service('Domain', ['DomainDetailConnection', '$rootScope', function(DomainDetailConnection, $rootScope){ 8116 var _this = this; 8117 this.current = {}; 8118 this.loaded = false; 8119 this.set = function(domain) { 8120 _this.current = domain; 8121 } 8122 this.load = function(domain) { 8123 if (typeof domain != 'undefined') { 8124 var response = DomainDetailConnection.query({'domain':domain}, function(response) { 8125 _this.set(response.data); 8126 _this.loaded = true; 8127 }); 8128 } else { 8129 _this.loaded = false; 8130 } 8131 } 8132 this.isLoaded = function() { 8133 return _this.loaded; 8134 } 8135 this.hasSubdomain = function() { 8136 return !_.isEmpty(_this.current); 8137 } 8138 this.hasTitle = function() { 8139 return _this.current.title !== undefined; 8140 } 8141 this.hasSubTitle = function() { 8142 return _this.current.sub_title !== undefined; 8143 } 8144 this.hasDescription = function() { 8145 return _this.current.description !== undefined; 8146 } 8147 this.unload = function() { 8148 if (this.isLoaded() == true) { 8149 this.current = {}; 8150 this.loaded = false; 8151 } 8152 } 8153}]); 8154angular.module('elixir_front') 8155.controller('HomeController', ['$scope','$state', '$stateParams', function($scope, $state, $stateParams) { 8156 var vm = this; 8157 window.scrollTo(0, 0); 8158}]); 8159 8160 angular.module('elixir_front') 8161.directive('homeSearch', ['Query', 'ToolList', 'Highlighting', 'Attribute', '$state', '$stateParams', '$timeout', 'UsedTerms', '$q', 'filterFilter', 'Domain', '$rootScope', '$location', '$anchorScroll', function(Query, ToolList, Highlighting, Attribute, $state, $stateParams, $timeout, UsedTerms, $q, filterFilter, Domain, $rootScope, $location, $anchorScroll) { 8162 return { 8163 restrict: 'A', 8164 templateUrl: 'components/home/homeSearch.html', 8165 link: function(scope, element, attrs) { 8166 // reference the Query in the scope 8167 scope.Query = Query; 8168 scope.params = null; 8169 scope.lastParam = null; 8170 // reference the Attribute in the scope 8171 scope.Attribute = Attribute; 8172 scope.ToolList = ToolList; 8173 8174 var initializing = true 8175 8176 /* function updateQuery() { 8177 Query.current = []; 8178 for (var prop in $stateParams) { 8179 // iterate through all parameters, skip 'page' 8180 if ($stateParams.hasOwnProperty(prop) 8181 && !_.includes(['page', 'sort', 'ord', 'domain'], prop) 8182 && typeof $stateParams[prop] != 'undefined') { 8183 // Remove tool id from the query 8184 if (prop == 'q') { 8185 var list = $stateParams[prop].split('+'); 8186 for (var i in list) { 8187 if (_.isEmpty(list[i]) == false) { 8188 Query.current.push({text:list[i], filter:'everything'}); 8189 } 8190 } 8191 } else { 8192 Query.current.push({text:$stateParams[prop], filter:prop}); 8193 } 8194 } 8195 } 8196 // initial highlighting 8197 Highlighting.set(Query.current); 8198 }*/ 8199 8200 8201 /*$rootScope.$on('$stateChangeSuccess', function(event, toState, toStateParams, fromState, fromStateParams) { 8202 if (toState.name === 'tool' || toState.name === 'home') { 8203 Query.current = []; 8204 } 8205 else if (toState.name === 'search') { 8206 updateQuery(); 8207 } 8208 });*/ 8209 8210 //updateQuery(); 8211 8212 function quoteQueryStringValue(v){ 8213 return '"' + v + '"' 8214 } 8215 8216 scope.topicNameClicked = function(topic) { 8217 $state.transitionTo('search', 8218 {'topic': quoteQueryStringValue(topic), 8219 'page': 1, 8220 'sort':'score' 8221 }, 8222 { 8223 reload: true, 8224 inherit: false, 8225 notify: true 8226 }); 8227 } 8228 8229 scope.operationNameClicked = function(operation) { 8230 $state.transitionTo('search', 8231 {'operation': quoteQueryStringValue(operation), 8232 'page': 1, 8233 'sort':'score' 8234 }, 8235 { 8236 reload: true, 8237 inherit: false, 8238 notify: true 8239 }); 8240 } 8241 8242 scope.navElement = function(elementid) { 8243 $anchorScroll.yOffset = 75; 8244 // set the location.hash 8245 $location.hash(elementid); 8246 8247 $anchorScroll(); 8248 $location.hash(null); 8249 } 8250 8251 // helper function for getting used terms 8252 function getUsedTerms(term, params) { 8253 var d = $q.defer(); 8254 params["usedTermName"] = term; 8255 UsedTerms.get(params, function(response) { 8256 d.resolve(response.data); 8257 }); 8258 return d.promise; 8259 } 8260 8261 // necessary for filtering suggestions for the autocomplete 8262 scope.loadSuggestions = function(term, query) { 8263 var deferred = $q.defer(); 8264 // fetch suggestions if not loaded 8265 scope.getSuggestionsFor(term); 8266 scope.usedTerms[term].then(function(list) { 8267 deferred.resolve(filterFilter(list, query)); 8268 }); 8269 return deferred.promise; 8270 } 8271 8272 // get used terms for typeahead 8273 scope.usedTerms = {}; 8274 scope.usedTermsLoaded = {}; 8275 8276 scope.getSuggestionsFor = function(term) { 8277 // only fetch if not already loaded 8278 if (!scope.usedTermsLoaded[term]) { 8279 var params = _.clone($stateParams); 8280 params['page'] = null; 8281 params['sort'] = null; 8282 params['ord'] = null; 8283 8284 scope.usedTerms[term] = getUsedTerms(term === 'everything' ? 'all' : term, params); 8285 scope.usedTermsLoaded[term] = true; 8286 } 8287 }; 8288 8289 // custom object necessary for ngTagsInput 8290 scope.filter = {}; 8291 // initially selected filter 8292 scope.filter.selected = 'everything'; 8293 // list of filters to display 8294 scope.filter.list = [ 8295 "everything", 8296 "topic", 8297 "operation", 8298 "input", 8299 "output", 8300 "toolType", 8301 "language", 8302 "accessibility", 8303 "cost", 8304 "license", 8305 "credit", 8306 "collectionID", 8307 "name" 8308 ]; 8309 8310 // get initial suggestions 8311 // getSuggestions(); 8312 8313 // when tag is added save the filter and reset it to 'everything' 8314 scope.tagAdded = function(tag) { 8315 tag['text'] = scope.normalizeTag(tag); 8316 tag['filter'] = scope.filter.selected; 8317 scope.filter.selected = 'everything'; 8318 } 8319 8320 // remove any unwanted characters from the entered tag 8321 scope.normalizeTag = function(tag) { 8322 return tag['text'].replace(/\+/g, "") 8323 } 8324 8325 scope.doHomeSearch = function() { 8326 var params = scope.params; 8327 var no_params = scope.Query.current.length; 8328 if (no_params > 0 && scope.Query.current[no_params-1].text == scope.lastParam && params != null & params.page != undefined){ 8329 $state.transitionTo('search', params, { notify: true }); 8330 } 8331 } 8332 8333 scope.myFunct = function(keyEvent) { 8334 if (keyEvent.which === 13){ 8335 scope.doHomeSearch(); 8336 } 8337 } 8338 8339 // saving the query and filtering to url 8340 scope.tagsChanged = function() { 8341 $rootScope.$emit('search-bar-tags-changed', {}); 8342 $timeout(function() { 8343 var params = {}; 8344 scope.params = null; 8345 params['page'] = 1; 8346 params['sort'] = $stateParams['sort']; 8347 if (params['sort'] == undefined) { 8348 params['sort'] = 'score'; 8349 }; 8350 if (scope.Query.current.length == 0) { 8351 params['sort'] = undefined; 8352 params['page'] = undefined; 8353 }; 8354 params['ord'] = $stateParams['ord']; 8355 params['domain'] = $stateParams['domain']; 8356 // add each tag from the search bar to the url 8357 for (var i in scope.Query.current) { 8358 scope.lastParam = scope.Query.current[i].text; 8359 // if tag is 'everything' put it under q 8360 if (scope.Query.current[i].filter == 'everything') { 8361 // if there already is a 'everything' tag, add new ones separating them with a dash 8362 if (params['q'] && params['q'].length > 0) { 8363 params['q'] += "+" + scope.Query.current[i].text; 8364 } else { 8365 params['q'] = scope.Query.current[i].text 8366 } 8367 } else { 8368 params[scope.Query.current[i].filter] = scope.Query.current[i].text; 8369 } 8370 } 8371 8372 scope.params = params; 8373 // update url 8374 //$state.transitionTo('search', params, { notify: true }); 8375 // add query elements to list of terms to highlight 8376 Highlighting.set(Query.current); 8377 $timeout(function() { 8378 // get more refined suggestions 8379 getSuggestions(); 8380 // refresh tool list 8381 ToolList.refresh(); 8382 }) 8383 }) 8384 } 8385 } 8386 } 8387}]); 8388angular.module('elixir_front') 8389.directive('navigationBar', [function() { 8390 return { 8391 restrict: 'A', 8392 templateUrl: 'components/navigationBar/navigationBar.html', 8393 link: function(scope, element, attrs) { 8394 scope.showSearch = scope.toState.name != "home"; 8395 scope.$watch(function(){ 8396 return scope.toState.name 8397 }, function(newVal, oldVal){ 8398 scope.showSearch = (scope.toState.name != "home"); 8399 }) 8400 } 8401 } 8402}])
8403.controller('NavbarController', ['$scope', 'djangoAuth', 'User', '$stateParams', '$timeout', 'Domain', '$state', function($scope, djangoAuth, User, $stateParams, $timeout, Domain, $state) { 8404 // reference the service 8405 var vm = this; 8406 8407 $scope.User = User; 8408 $scope.Domain = Domain; 8409 8410 $timeout(function() { 8411 Domain.load($stateParams['domain']); 8412 }, 500); 8413 8414 $scope.logoutButtonClick = function() { 8415 djangoAuth.logout(); 8416 }; 8417 8418 $scope.homeButtonClicked = function() { 8419 $scope.Domain.unload(); 8420 $state.go('home'); 8421 }; 8422 8423 $scope.aboutButtonClicked = function() { 8424 $scope.Domain.unload(); 8425 $state.go('about'); 8426 }; 8427 8428 8429}]); 8430angular.module('elixir_front') 8431.directive('pageFooter', [function() { 8432 return { 8433 restrict: 'A', 8434 templateUrl: 'components/pageFooter/pageFooter.html', 8435 link: function(scope, element, attrs) { 8436 8437 } 8438 } 8439}]) 8440angular.module('elixir_front') 8441.controller('ChangeEmailController', ['$scope', '$uibModalInstance', 'djangoAuth', function($scope, $uibModalInstance, djangoAuth) { 8442 $scope.submitEmail = function() { 8443 djangoAuth.updateProfile({email: $scope.newEmail}) 8444 .then(function(response) { 8445 alert('Email updated successfully!'); 8446 $uibModalInstance.close(response); 8447 }, function(error) { 8448 alert('Failed to update email: ' + error.email); 8449 }); 8450 }; 8451}]); 8452angular.module('elixir_front') 8453.controller('ChangePasswordController', ['$scope', '$uibModalInstance', 'djangoAuth', function($scope, $uibModalInstance, djangoAuth) { 8454 8455 $scope.submitPassword = function() { 8456 var token = localStorage.getItem("authToken"); // Store and retrieve auth token securely 8457 var oldPassword = $scope.oldPassword; 8458 var newPassword1 = $scope.newPassword; 8459 var newPassword2 = $scope.confirmPassword; 8460 8461 djangoAuth.changePassword(oldPassword, newPassword1, newPassword2) 8462 .then(function(response) { 8463 alert('Password updated successfully!'); 8464 $uibModalInstance.close(); 8465 }) 8466 .catch(function(error) { 8467 console.error("error: ", error); 8468 let firstKey = Object.keys(error)[0]; // First error key (old_password, new_password2...) 8469 alert('Failed to update password: ' + error[firstKey][0]); 8470 }); 8471 }; 8472}]); 8473angular.module('elixir_front') 8474.controller('ProfileController', ['$scope', '$state', 'djangoAuth', 'DisownToolService', 'User', '$uibModal', 'AppConfig', function($scope, $state, djangoAuth, DisownToolService, User, $uibModal, AppConfig) { 8475 $scope.profile = {}; 8476 $scope.loading = true; 8477 $scope.User = User; 8478 djangoAuth.profile() 8479 .then(function (response) { 8480 $scope.profile = response; 8481 $scope.loading = false; 8482 }, function (response) { 8483 $scope.loading = false; 8484 }); 8485 8486 $scope.disownEntry = function(entry) { 8487 if (!confirm("Are you sure you want to disown this resource?")){ 8488 return; 8489 } 8490 8491 DisownToolService.disown({id: entry.id, version: entry.versionId}, function(response) { 8492 $scope.profile.resources = _.difference($scope.profile.resources, [entry]); 8493 }, function(response) { 8494 alert("Failed to disown " + entry.name + ". Please check your connection and try again later."); 8495 }); 8496 }; 8497 $scope.openChangePasswordModal = function() { 8498 $uibModal.open({ 8499 templateUrl: 'components/profile/changePasswordModal.html', 8500 controller: 'ChangePasswordController' 8501 }); 8502 }; 8503 8504 $scope.openChangeEmailModal = function() { 8505 var modalInstance = $uibModal.open({ 8506 templateUrl: 'components/profile/changeEmailModal.html', 8507 controller: 'ChangeEmailController' 8508 }); 8509 8510 modalInstance.result.then(function(updatedProfile) { 8511 // Update the profile with new email 8512 $scope.profile.email = updatedProfile.email; 8513 $scope.profile.email_verified = updatedProfile.email_verified; 8514 }); 8515 }; 8516 8517 $scope.resendVerificationEmail = function() { 8518 // If email ends with @biotools.local, do not attempt to resend 8519 if ($scope.profile.email.endsWith('@biotools.local')) { 8520 alert('Cannot send verification email to local test accounts.'); 8521 return; 8522 } 8523 djangoAuth.resendEmail($scope.profile.email) 8524 .then(function(response) { 8525 alert('Verification email sent successfully!'); 8526 }, function(error) { 8527 alert('Failed to send verification email: ' + (error.data && error.data.message ? error.data.message : 'Please try again later.')); 8528 }); 8529 }; 8530 8531 $scope.orcidConnect = function() { 8532 var client_id = AppConfig.ORCID_CLIENT_ID; 8533 var redirect_uri = AppConfig.ORCID_REDIRECT_URI; 8534 var orcid_base_url = AppConfig.ORCID_BASE_URL; 8535 8536 window.location.href = orcid_base_url + '/oauth/authorize?client_id=' + client_id + '&response_type=code&scope=/authenticate&redirect_uri='
8536+ redirect_uri; 8537 }; 8538 8539 $scope.disconnectSocial = function(provider, id) { 8540 if (!confirm("Are you sure you want to disconnect your " + provider + " account?")){ 8541 return; 8542 } 8543 djangoAuth.disconnectSocial(id) 8544 .then(function(response) { 8545 // Remove the disconnected account from the profile.socialAccounts array 8546 $scope.profile.socialAccounts = $scope.profile.socialAccounts.filter(function(account) { 8547 return account.id !== id; 8548 }); 8549 }, function(error) { 8550 alert("Failed to disconnect your " + provider + " account. Please try again later."); 8551 }); 8552 }; 8553 8554 // check if social accounts are connected 8555 $scope.hasOrcidAccount = function() { 8556 if (!$scope.profile.socialAccounts) return false; 8557 return $scope.profile.socialAccounts.some(function(account) { 8558 return account.provider === 'orcid'; 8559 }); 8560 }; 8561}]); 8562 angular.module('elixir_front') 8563.directive('searchBar', ['Query', 'ToolList', 'Highlighting', 'Attribute', '$state', '$stateParams', '$timeout', 'UsedTerms', '$q', 'filterFilter', 'Domain', '$rootScope', function(Query, ToolList, Highlighting, Attribute, $state, $stateParams, $timeout, UsedTerms, $q, filterFilter, Domain, $rootScope) { 8564 return { 8565 restrict: 'A', 8566 templateUrl: 'components/searchBar/searchBar.html', 8567 link: function(scope, element, attrs) { 8568 8569 // reference the Query in the scope 8570 scope.Query = Query; 8571 // reference the Attribute in the scope 8572 scope.Attribute = Attribute; 8573 scope.ToolList = ToolList; 8574 8575 var initializing = true 8576 8577 function updateQuery() { 8578 Query.current = []; 8579 for (var prop in $stateParams) { 8580 // iterate through all parameters, skip 'page' 8581 if ($stateParams.hasOwnProperty(prop) 8582 && !_.includes(['page', 'sort', 'ord', 'domain'], prop) 8583 && typeof $stateParams[prop] != 'undefined') { 8584 // Remove tool id from the query 8585 if (prop == 'q') { 8586 var list = $stateParams[prop].split('+'); 8587 for (var i in list) { 8588 if (_.isEmpty(list[i]) == false) { 8589 Query.current.push({text:list[i], filter:'everything'}); 8590 } 8591 } 8592 } else { 8593 Query.current.push({text:$stateParams[prop], filter:prop}); 8594 } 8595 } 8596 } 8597 // initial highlighting 8598 Highlighting.set(Query.current); 8599 } 8600 8601 8602 $rootScope.$on('$stateChangeSuccess', function(event, toState, toStateParams, fromState, fromStateParams) { 8603 if (toState.name !== 'search') { 8604 Query.current = []; 8605 ToolList.refresh(); 8606 } 8607 else if (toState.name === 'search') { 8608 updateQuery(); 8609 } 8610 }); 8611 8612 updateQuery(); 8613 8614 // helper function for getting used terms 8615 function getUsedTerms(term, params) { 8616 var d = $q.defer(); 8617 var list = [] 8618 params["usedTermName"] = term; 8619 UsedTerms.get(params, function(response) { 8620 d.resolve(response.data); 8621 }); 8622 return d.promise; 8623 } 8624 8625 // necessary for filtering suggestions for the autocomplete 8626 scope.loadSuggestions = function(term, query) { 8627 var deferred = $q.defer(); 8628 scope.usedTerms[term].then(function(list) { 8629 deferred.resolve(filterFilter(list,query)); 8630 }) 8631 return deferred.promise; 8632 } 8633 8634 function getSuggestions() { 8635 // get used terms for typeahead 8636 scope.usedTerms = {}; 8637 8638 // add query and filtering parameters to refine suggestions 8639 var params = _.clone($stateParams); 8640 params['page'] = null; 8641 params['sort'] = null; 8642 params['ord'] = null; 8643 8644 // create promises for all suggestions 8645 scope.usedTerms['everything'] = getUsedTerms('all', params); 8646 scope.usedTerms['topic'] = getUsedTerms('topic', params); 8647 scope.usedTerms['operation'] = getUsedTerms('operation', params); 8648 scope.usedTerms['input'] = getUsedTerms('input', params); 8649 scope.usedTerms['output'] = getUsedTerms('output', params); 8650 scope.usedTerms['toolType'] = getUsedTerms('toolType', params); 8651 scope.usedTerms['language'] = getUsedTerms('language', params); 8652 scope.usedTerms['accessibility'] = getUsedTerms('accessibility', params); 8653 scope.usedTerms['cost'] = getUsedTerms('cost', params); 8654 scope.usedTerms['license'] = getUsedTerms('license', params); 8655 scope.usedTerms['credit'] = getUsedTerms('credit', params); 8656 scope.usedTerms['collectionID'] = getUsedTerms('collectionID', params); 8657 scope.usedTerms['name'] = getUsedTerms('name', params); 8658 } 8659 8660 // custom object necessary for ngTagsInput 8661 scope.filter = {}; 8662 // initially selected filter 8663 scope.filter.selected = 'everything'; 8664 // list of filters to display 8665 scope.filter.list = [ 8666 "everything", 8667 "topic", 8668 "operation", 8669 "input", 8670 "output", 8671 "toolType", 8672 "language", 8673 "accessibility", 8674 "cost", 8675 "license", 8676 "credit", 8677 "collectionID", 8678 "name" 8679 ]; 8680 8681 // get initial suggestions 8682 getSuggestions(); 8683 8684 // when tag is added save the filter and reset it to 'everything' 8685 scope.tagAdded = function(tag) { 8686 tag['text'] = scope.normalizeTag(tag); 8687 tag['filter'] = scope.filter.selected; 8688 scope.filter.selected = 'everything'; 8689 } 8690 8691 // remove any unwanted characters from the entered tag 8692 scope.normalizeTag = function(tag) { 8693 return tag['text'].replace(/\+/g, "") 8694 } 8695 8696 // saving the query and filtering to url 8697 scope.tagsChanged = function() { 8698 $rootScope.$emit('search-bar-tags-changed', {}); 8699 $timeout(function() { 8700 var params = {}; 8701 params['page'] = 1; 8702 params['sort'] = $stateParams['sort']; 8703 if (params['sort'] == undefined) { 8704 params['sort'] = 'score'; 8705 }; 8706 if (scope.Query.current.length == 0) { 8707 params['sort'] = undefined; 8708 params['page'] = undefined; 8709 }; 8710 params['ord'] = $stateParams['ord']; 8711 params['domain'] = $stateParams['domain']; 8712 // add each tag from the search bar to the url 8713 for (var i in scope.Query.current) { 8714 // if tag is 'everything' put it under q 8715 if (scope.Query.current[i].filter == 'everything') { 8716 // if there already is a 'everything' tag, add new ones separating them with a dash 8717 if (params['q'] && params['q'].length > 0) { 8718 params['q'] += "+" + scope.Query.current[i].text; 8719 } else { 8720 params['q'] = scope.Query.current[i].text 8721 } 8722 } else { 8723 params[scope.Query.current[i].filter] = scope.Query.current[i].text; 8724 } 8725 } 8726 // update url 8727 $state.transitionTo('search', params, { notify: true }); 8728 // add query elements to list of terms to highlight 8729 Highlighting.set(Query.current); 8730 $timeout(function() { 8731 // get more refined suggestions 8732 getSuggestions(); 8733 // refresh tool list 8734 ToolList.refresh(); 8735 }) 8736 }) 8737 } 8738 } 8739 } 8740}]); 8741angular.module('elixir_front.services') 8742// Gets stats info from the server. 8743.factory('Stats', ['$resource', function($resource){ 8744 return $resource('/api/stats', null, { 8745 'query': { 8746 isArray:false, 8747 method:'GET' 8748 } 8749 }) 8750}]) 8751.factory('StatsTotalEntries', ['$resource', function($resource){ 8752 return $resource('/api/stats/total-entries', null, { 8753 'query': { 8754 isArray:true, 8755 method:'GET' 8756 } 8757 }) 8758}]) 8759.factory('StatsUserData', ['$resource', function($resource){ 8760 return $resource('/api/stats/users', null, { 8761 'query': { 8762 isArray:true, 8763 method:'GET' 8764 } 8765 }) 8766}]) 8767.factory('TotalAnnotationsStatsData', ['$resource', function($resource){ 8768 return $resource('/api/stats/annotation-count', null, { 8769 'query': { 8770 isArray:true, 8771 method:'GET' 8772 } 8773 }) 8774}]) 8775// Get and manage the stats data from the server. 8776.service('StatsDataSource', ['$stateParams', 'Stats', 'StatsTotalEntries', 'StatsUserData', 'TotalAnnotationsStatsData', function($stateParams, Stats, StatsTotalEntries, StatsUserData, TotalAnnotationsStatsData) { 8777 var _this = this; 8778 this.isLoadingData = true; 8779 this.isLoadingTotalEntriesData = true; 8780 this.isLoadingUserStatsData = true; 8781 this.isLoadingTotalAnnotationsStats = true; 8782 this.statsData = {}; 8783 this.totalEntriesData = {}; 8784 this.userStatsData = {}; 8785 this.totalAnnotationsStatsData = {}; 8786 this.totalAnnotationsCount = function() { 8787 var totalCount = 0; 8788 for (var key in this.statsData.totalAnnotationCount) { 8789 var value = this.statsData.totalAnnotationCount[key]; 8790 totalCount += value; 8791 } 8792 return totalCount; 8793 } 8794 // Operations 8795 this.fetchStatsData = function(completion) { 8796 this.isLoadingData = true; 8797 var response = Stats.query($stateParams, function() { 8798 _this.statsData = response; 8799 _this.isLoadingData = false;
8800 completion() 8801 }); 8802 } 8803 this.fetchTotalEntriesData = function(completion) { 8804 this.isLoadingTotalEntriesData = true; 8805 var response = StatsTotalEntries.query($stateParams, function() { 8806 _this.totalEntriesData = response; 8807 _this.isLoadingTotalEntriesData = false; 8808 completion() 8809 }); 8810 } 8811 this.fetchUserStatsData = function(completion) { 8812 this.isLoadingUserStatsData = true; 8813 var response = StatsUserData.query($stateParams, function() { 8814 _this.userStatsData = response; 8815 _this.isLoadingUserStatsData = false; 8816 completion(); 8817 }); 8818 } 8819 this.fetchTotalAnnotationsStatsData = function(completion) { 8820 this.isLoadingTotalAnnotationsStats = true; 8821 var response = TotalAnnotationsStatsData.query($stateParams, function() { 8822 _this.totalAnnotationsStatsData = response; 8823 _this.isLoadingTotalAnnotationsStats = false; 8824 completion(); 8825 }); 8826 } 8827}]) 8828// Controller for the stats website. 8829angular.module('elixir_front.controllers').controller('StatsController', ['$scope', '$http', 'StatsDataSource', function($scope, $http, StatsDataSource) { 8830 // Initialization 8831 var _this = this; 8832 $scope.labels = []; 8833 $scope.labelsUsers = []; 8834 $scope.series = ['Total entries']; 8835 $scope.seriesEDAM = ['EDAM annotations']; 8836 $scope.seriesEDAMBreakdown = ['Data Type annotations', 'Topic annotations', 'Function annotations', 'Format annotations']; 8837 $scope.seriesUsers = ['Total contributors count', 'New contributors this month']; 8838 $scope.seriesTotalAnnotationDetail = ['Name', 'Description', 'Homepage', 'Tool Type', 'Unique Id', 'Topic', 'Publication', 'Contact', 'Operation', 'Documentation', 'Operating System', 'Input output', 'Code availability', 'Accessibility', 'Data format', 'Community', 'Downloads', 'Total']; 8839 $scope.seriesTotalAnnotation = ['Total']; 8840 $scope.data = [[]]; 8841 $scope.dataEDAM = [[]]; 8842 $scope.dataEDAMBreakdown = [[]]; 8843 $scope.dataUsers = [[]]; 8844 $scope.dataTotalAnnotation = [[]]; 8845 $scope.dataTotalAnnotationDetail = [[]]; 8846 $scope.totalAnnotationOptions = { 8847 tooltips: { 8848 titleFontSize: 10, 8849 bodyFontSize: 9, 8850 bodySpacing: 0 8851 } 8852 }; 8853 $scope.statsDataSource = StatsDataSource; 8854 $scope.statsDataSource.fetchStatsData(function(){ 8855 _this.initializeTopContributorsStatsGraph(); 8856 }); 8857 $scope.statsDataSource.fetchTotalEntriesData(function() { 8858 _this.initializeTotalEntriesGraph(); 8859 }); 8860 $scope.statsDataSource.fetchUserStatsData(function() { 8861 _this.initializeUserStatsGraph(); 8862 }); 8863 $scope.statsDataSource.fetchTotalAnnotationsStatsData(function() { 8864 _this.initializeTotalAnnotationsStatsGraph(); 8865 _this.initializeTotalAnnotationsDetailStatsGraph(); 8866 }); 8867 $scope.topContributorsLabels = []; 8868 $scope.topContributorsData = []; 8869 $scope.topContributorsOptions = {legend: {display: true}}; 8870 // Total Entries Grpah handling 8871 this.initializeTotalEntriesGraph = function() { 8872 var data = $scope.statsDataSource.totalEntriesData; 8873 $scope.labels = _.map(data, function(x) { 8874 var date = x.date.split('T')[0]; 8875 return moment(date).format("MMM YY"); 8876 }); 8877 $scope.data = [_.map(data, function(x) { 8878 return x.entriesCount; 8879 })]; 8880 $scope.dataEDAM = [_.map(data, function(x) { 8881 return x.edamAnnotationsCount; 8882 })]; 8883 $scope.dataEDAMBreakdown = [_.map(data, function(x) { 8884 return x.dataTypeAnnotationsCount; 8885 }), 8886 _.map(data, function(x) { 8887 return x.topicAnnotationsCount; 8888 }), 8889 _.map(data, function(x) { 8890 return x.functionAnnotationsCount; 8891 }), 8892 _.map(data, function(x) { 8893 return x.formatAnnotationsCount; 8894 })]; 8895 }; 8896 // Total Entries Grpah handling 8897 this.initializeUserStatsGraph = function() { 8898 var data = $scope.statsDataSource.userStatsData; 8899 $scope.labelsUsers = _.map(data, function(x) { 8900 var date = x.date.split('T')[0]; 8901 return moment(date).format("MMM YY"); 8902 }); 8903 $scope.dataUsers = [_.map(data, function(x) { 8904 return x.totalUsersCount; 8905 }), 8906 _.map(data, function(x) { 8907 return x.newUsersCount; 8908 })]; 8909 }; 8910 // Top contributors data. 8911 this.initializeTopContributorsStatsGraph = function() { 8912 var contributorData = $scope.statsDataSource.statsData.topContributors; 8913 $scope.topContributorsLabels = _.map(contributorData, function(x) { 8914 return x.domain; 8915 }); 8916 $scope.topContributorsData = _.map(contributorData, function(x) { 8917 return x.count; 8918 }); 8919 }; 8920 // Total annotations data. 8921 this.initializeTotalAnnotationsStatsGraph = function() { 8922 var annotationData = $scope.statsDataSource.totalAnnotationsStatsData 8923 $scope.dataTotalAnnotation = [_.map(annotationData, function(x) { 8924 var data = x; 8925 var totalCount = 0; 8926 for (var key in data) { 8927 if (key != 'date' && !isNaN(data[key])) { 8928 totalCount += data[key]; 8929 } 8930 } 8931 return totalCount; 8932 })]; 8933 }; 8934 this.initializeTotalAnnotationsDetailStatsGraph = function() { 8935 var annotationData = $scope.statsDataSource.totalAnnotationsStatsData 8936 $scope.dataTotalAnnotationDetail = [_.map(annotationData, function(x) { 8937 return x.nameAnnotationCount; 8938 }), 8939 _.map(annotationData, function(x) { 8940 return x.descriptionAnnotationCount; 8941 }), 8942 _.map(annotationData, function(x) { 8943 return x.homepageAnnotationCount; 8944 }), 8945 _.map(annotationData, function(x) { 8946 return x.toolTypeAnnotationCount; 8947 }), 8948 _.map(annotationData, function(x) { 8949 return x.uniqueIDAnnotationCount; 8950 }), 8951 _.map(annotationData, function(x) { 8952 return x.topicAnnotationCount; 8953 }), 8954 _.map(annotationData, function(x) { 8955 return x.publicationAnnotationCount; 8956 }), 8957 _.map(annotationData, function(x) { 8958 return x.contactAnnotationCount; 8959 }), 8960 _.map(annotationData, function(x) { 8961 return x.operationAnnotationCount; 8962 }), 8963 _.map(annotationData, function(x) { 8964 return x.documentationAnnotationCount; 8965 }), 8966 _.map(annotationData, function(x) { 8967 return x.operatingSystemAnnotationCount; 8968 }), 8969 _.map(annotationData, function(x) { 8970 return x.inputOutputAnnotationCount; 8971 }), 8972 _.map(annotationData, function(x) { 8973 return x.codeAvailabilityAnnotationCount; 8974 }), 8975 _.map(annotationData, function(x) { 8976 return x.accessibilityAnnotationCount; 8977 }), 8978 _.map(annotationData, function(x) { 8979 return x.dataFormatsAnnotationCount; 8980 }), 8981 _.map(annotationData, function(x) { 8982 return x.communityAnnotationCount; 8983 }), 8984 _.map(annotationData, function(x) { 8985 return x.downloadsAnnotationCount; 8986 })]; 8987 }; 8988}]); 8989 8990 8991 8992 8993angular.module('elixir_front') 8994.controller('ToolPageController', ['$scope', '$state', '$timeout', '$stateParams', 'Tool', 'User', 'CheckUserEditingRights', 'ResourceRequestProvider', 'ngMeta', 'Query', '$http', 'MetaService', function($scope, $state, $timeout, $stateParams, Tool, User, CheckUserEditingRights, ResourceRequestProvider, ngMeta, Query, $http, MetaService) { 8995 $scope.notFound = false;
8996 $scope.versions = []; 8997 $scope.CheckUserEditingRights = CheckUserEditingRights; 8998 $scope.editingRequestedSuccess = null; 8999 9000 9001 9002 $scope.User = User; 9003 $scope.elixirCommunityIndex = { 9004 "3D-BioInfo": "3d-bioinfo", 9005 "Biodiversity": "biodiversity", 9006 "Federated Human Data": "human-data", 9007 "Galaxy": "galaxy", 9008 "Human Copy Number Variation": "hcnv", 9009 "Intrinsically Disordered Proteins": "intrinsically-disordered-proteins", 9010 "Marine Metagenomics": "marine-metagenomics", 9011 "Metabolomics": "metabolomics", 9012 "Microbial Biotechnology": "microbial-biotechnology", 9013 "Plant Sciences": "plant-sciences", 9014 "Proteomics": "proteomics", 9015 "Rare Diseases": "rare-diseases", 9016 "Systems Biology": "systems-biology" 9017 } 9018 9019 $scope.hasRRID = function(){ 9020 for (var index in $scope.software.otherID) { 9021 if ($scope.software.otherID[index].type.toLowerCase() == 'rrid') { 9022 return true 9023 } 9024 } 9025 9026 return false; 9027 } 9028 9029 $scope.confidenceClass = function() { 9030 if ($scope.software.confidence_flag === 'high'){ 9031 return 'alert-info' 9032 } 9033 9034 if ($scope.software.confidence_flag === 'medium'){ 9035 return 'alert-warning' 9036 } 9037 9038 return 'alert-danger'; 9039 } 9040 9041 function quoteQueryStringValue(v){ 9042 return '"' + v + '"' 9043 } 9044 9045 function stripEdam(t){ 9046 return t.replace("http://edamontology.org/", ""); 9047 } 9048 // go to 404 in special case when URL is /tool/ 9049 if ($stateParams.id.length == 0) { 9050 $state.go('404'); 9051 } 9052 9053 $scope.topicNameClicked = function(topic) { 9054 $state.go('search', {'topicID': quoteQueryStringValue(stripEdam(topic.uri))}); 9055 } 9056 9057 $scope.functionNameClicked = function(functionName) { 9058 $state.go('search', {'operationID': quoteQueryStringValue(stripEdam(functionName.uri))}); 9059 } 9060 9061 $scope.inputNameClicked = function(inputName) { 9062 //$state.go('search', {'inputDataType': inputName.data.term}); 9063 $state.go('search', {'inputDataTypeID': quoteQueryStringValue(stripEdam(inputName.data.uri)) }); 9064 } 9065 9066 $scope.inputFormatNameClicked = function(formatName) { 9067 //$state.go('search', {'inputDataFormat': formatName.term}); 9068 $state.go('search', {'inputDataFormatID': quoteQueryStringValue(stripEdam(formatName.uri)) }); 9069 } 9070 9071 $scope.outputFormatNameClicked = function(formatName) { 9072 $state.go('search', {'outputDataFormatID': quoteQueryStringValue(stripEdam(formatName.uri))}); 9073 } 9074 9075 $scope.outputNameClicked = function(outputName) { 9076 $state.go('search', {'outputDataTypeID': quoteQueryStringValue(stripEdam(outputName.data.uri))}); 9077 } 9078 9079 $scope.toolsNameClicked = function(functionName) { 9080 $state.go('search', {'name': $scope.software.name}); 9081 } 9082 9083 $scope.collectionNameClicked = function(collectionName) { 9084 $state.go('search', {'collectionID': '"'+ collectionName + '"'}); 9085 } 9086 9087 $scope.creditNameClicked = function(creditName) { 9088 var url = $state.href('search', {'credit': '"'+ creditName + '"'}); 9089 window.open(url,'_blank'); 9090 } 9091 9092 $scope.publicationDetailsExist = function() { 9093 for (var index in $scope.software.publication) { 9094 if ($scope.software.publication[index].metadata != null) { 9095 return true; 9096 } 9097 } 9098 return false; 9099 } 9100 9101 // TODO: needs to keep it DRY and in a service 9102 // function to clean all nulls from tool gotten from API 9103 function cleanNulls (object) { 9104 for (var key in object) { 9105 if (object[key] == null) { 9106 delete object[key] 9107 } else if (object[key].constructor === Array) { 9108 if (object[key].length == 0) { 9109 delete object[key] 9110 } else { 9111 for (var i in object[key]) { 9112 cleanNulls(object[key][i]) 9113 } 9114 } 9115 } else if (typeof object[key] === 'object') { 9116 cleanNulls(object[key]); 9117 } 9118 } 9119 } 9120 9121 var initAltmetricsScore = function() { 9122 var script = document.createElement('script'); 9123 script.type = 'text/javascript'; 9124 script.src = "https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"; 9125 document.body.appendChild(script); 9126 } 9127 9128 var initBipScripts = function() { 9129 var script = document.createElement('script'); 9130 script.type = 'text/javascript'; 9131 script.src = "https://cdn.jsdelivr.net/gh/athenarc/bip-plugin@main/embed.min.js"; 9132 document.body.appendChild(script); 9133 } 9134 9135 var initDimensionsAI = function() { 9136 var script = document.createElement('script'); 9137 script.type = 'text/javascript'; 9138 script.src = "https://badge.dimensions.ai/badge.js"; 9139 document.body.appendChild(script); 9140 } 9141 9142 $scope.altMetricsScorePublication = function() { 9143 var publication = "" 9144 for (var index in $scope.software.publication) { 9145 var publication = $scope.software.publication[index]; 9146 if (publication.type == "Primary" && publication.doi) { 9147 publication = publication; 9148 break; 9149 } 9150 else { 9151 publication = publication; 9152 } 9153 } 9154 return publication; 9155 } 9156 9157 $scope.versionSelected = function(versionId) { 9158 $state.go('tool', {id: $scope.software.id, version: versionId}); 9159 } 9160
9161 $scope.shouldLicenseBeALink = function(license) { 9162 return !_.includes(['Freeware','Proprietary', 'Other','Not licensed'], license); 9163 } 9164 9165 $scope.setMetadataForSoftware = function(software) { 9166 ngMeta.setTitle(software.name, ' · bio.tools'); 9167 ngMeta.setTag('description', software.description); 9168 ngMeta.setTag('og:title', software.name); 9169 ngMeta.setTag('og:description', software.description); 9170 MetaService.setAlternatesForTool(software.biotoolsID); 9171 9172 const published = software.additionDate; 9173 const modified = software.lastUpdate; 9174 ngMeta.setTag('datePublished', published, false); 9175 ngMeta.setTag('dateModified', modified, false); 9176 } 9177 9178 // get tool 9179 $scope.software = Tool.get($stateParams, function(response) { 9180 // success handler 9181 initAltmetricsScore(); 9182 initBipScripts(); 9183 initDimensionsAI(); 9184 //window.__dimensions_embed.addBadges(); 9185 //window.__dimensions_embed.addBadges(); 9186 cleanNulls($scope.software); 9187 $scope.setMetadataForSoftware($scope.software); 9188 //$timeout(function(){ window.OpEB_widgets.OpEB.apply(); }, 100);*/ 9189 9190 $timeout(function(){ window.__dimensions_embed.addBadges(); }, 100); 9191 /*$timeout(function(){ window.OpEB_widgets.OpEB.apply(); }, 100);*/ 9192 }, function(response) { 9193 // error handler 9194 if (response.status == 404) { 9195 $scope.notFound = true; 9196 } 9197 }); 9198 9199 $scope.requestOwnership = function(resourceId) { 9200 if (confirm("We recommend that only the tool developers/maintainers request ownership.\nAll other users should request edit-rights.\nAre you sure you want to request ownership of this tool?")){ 9201 $scope.ownershipRequestedSuccess = null; 9202 ResourceRequestProvider.requestOwnership(resourceId).then(function successCallback(response) { 9203 $scope.ownershipRequestedSuccess = true; 9204 }, function errorCallback(response) { 9205 $scope.ownershipRequestedSuccess = false; 9206 }); 9207 } 9208 } 9209 9210 $scope.requestEditing = function(resourceId) { 9211 $scope.editingRequestedSuccess = null; 9212 ResourceRequestProvider.requestEditingRights(resourceId).then(function successCallback(response) { 9213 $scope.editingRequestedSuccess = true; 9214 }, function errorCallback(response) { 9215 $scope.editingRequestedSuccess = false; 9216 }); 9217 } 9218 9219 // Call the API t retrieve and inject JSON-LD semantic annotations (Bioschemas tool profile) 9220 $scope.software.$promise.then(function(data) { 9221 $http.get("/api/"+data.biotoolsID+"?format=jsonld").then(function(response) { 9222 var script = document.createElement('script'); 9223 script.type = 'application/ld+json'; 9224 script.innerHTML = JSON.stringify(response.data); 9225 9226 document.getElementsByTagName('head')[0].appendChild(script); 9227 9228 }); 9229 }, function (reason) { 9230 console.log('ERROR: '+JSON.stringify(reason)); 9231 }); 9232 9233}]) 9234.directive("publicationDetailCallout", function(){ 9235 return { 9236 restrict: 'A', 9237 transclude: true, 9238 templateUrl: 'components/toolPage/partials/publicationDetailCallout.html', 9239 controller: ['$scope', function($scope) { 9240 $scope.status = "... More"; 9241 $scope.expandable_status = "expandable-summary"; 9242 9243 var initDimensionsAI = function() { 9244 var script = document.createElement('script'); 9245 script.type = 'text/javascript'; 9246 script.src = "https://badge.dimensions.ai/badge.js"; 9247 document.body.appendChild(script); 9248 } 9249 9250 //initDimensionsAI(); 9251 $scope.toggleStatus = function(){ 9252 if ($scope.status == "... More"){ 9253 $scope.status = "Less"; 9254 $scope.expandable_status = ""; 9255 }else{ 9256 $scope.status = "... More"; 9257 $scope.expandable_status = "expandable-summary"; 9258 } 9259 } 9260 9261 $scope.getDoi = function(){ 9262 return $scope.publication.doi; 9263 } 9264 }], 9265 link: function($scope, element, attrs) { 9266 9267 //window.__dimensions_embed.addBadges(); 9268 9269 } 9270 }; 9271}) 9272.directive("toolPageLinkCallout", function(){ 9273 return { 9274 restrict: 'A', 9275 transclude: true, 9276 template: function(elem, attr){ 9277 return '<div class="bs-callout-sm bs-callout-primary" style="border-left-color: ' + attr.color + ';">' + 9278 '<div class="tool-page-callout-header" style="color: black;">' + 9279 '<a target="_blank" href="' + attr.url + '" tooltips tooltip-side="top" tooltip-content="' + attr.url + '">' + attr.name + ' ⺠</a>' +
9280 '<i ng-show="' + attr.toshow + '" class="fa fa-question-circle" aria-hidden="true" style="font-size: 100%; margin-left: 0.5em; color: ' + attr.color + ';" tooltips tooltip-side="top" tooltip-content="' + attr.comment + '"></i>' + 9281 '</div>' + 9282 '</div>'; 9283 }, 9284 replace: true 9285 }; 9286}) 9287.directive("toolPageNewLinkCallout", function(){ 9288 return { 9289 restrict: 'A', 9290 scope:{ 9291 'url': '=?', 9292 'types':'=?', 9293 'toshow':'@?', 9294 'color':'@?', 9295 'cssclass':"@?", 9296 'note':'=?', 9297 'hovertext':'@?' 9298 }, 9299 templateUrl: 'components/toolPage/partials/toolPageNewLinkCallout.html', 9300 replace: true 9301 }; 9302}) 9303.directive("toolPagePublicationCallout", function(){ 9304 return { 9305 restrict: 'A', 9306 scope:{ 9307 'pmcid': '=?', 9308 'doi':'=?', 9309 'pmid':'=?', 9310 'types':'=?', 9311 'color':'@?', 9312 'note':'=?' 9313 }, 9314 templateUrl: 'components/toolPage/partials/toolPagePublicationCalloutHTML.html', 9315 replace: true 9316 }; 9317}) 9318.directive("toolPageContactCallout", function(){ 9319 return { 9320 restrict: 'A', 9321 transclude: true, 9322 template: function(elem, attr){ 9323 var callout = '<div class="bs-callout-sm bs-callout-primary" style="border-left-color: ' + attr.color + ';">'; 9324 callout += '<div ng-show="' + attr.name + '" class="tool-page-callout-header" style="color: black;">{{::' + attr.name + '}}</div>'; 9325 callout += '<div class="tool-page-callout-text">'; 9326 callout += '<span ng-show="' + attr.phone + '">{{::' + attr.phone + '}}'; 9327 callout += '<span ng-show="' + attr.email + ' || ' + attr.url + '" style="color: #CCCCCC;"> | </span></span>'; 9328 callout += '<span ng-show="' + attr.email + '"><i class="fa fa-envelope-o" aria-hidden="true"></i> {{' + attr.email +'.replace(\'@\', \' at \') }}'; 9329 callout += '<span ng-show="' + attr.url + '" style="color: #CCCCCC;"> | </span></span>'; 9330 callout += '<span ng-show="' + attr.url + '"><a href="{{::' + attr.url + '}}">Link ⺠</a></span>'; 9331 callout += '</div>'; 9332 callout += '</div>'; 9333 return callout; 9334 }, 9335 replace: true 9336 }; 9337}) 9338.directive("toolPageCreditCallout", function(){ 9339 return { 9340 restrict: 'A', 9341 transclude: true, 9342 template: function(elem, attr){ 9343 var callout = '<div class="bs-callout-sm bs-callout-primary" style="border-left-color: ' + attr.color + ';">'; 9344 callout += '<div ng-show="' + attr.name + '" class="tool-page-callout-header" style="color: black;"><a href="" ng-click="creditNameClicked(credit.name)">{{' + attr.name + '}}</a>'; 9345 callout += '<i ng-show="' + attr.toshow + '" class="fa fa-question-circle" aria-hidden="true" style="font-size: 100%; margin-left: 0.5em; color: ' + attr.color + ';" tooltips tooltip-side="top" tooltip-content="' + attr.comment + '"></i>'; 9346 callout += '<span ng-show="' + attr.typeentity + ' && ' + attr.typeentity + ' != \'Person\'">'; 9347 callout += '<span ng-show="' + attr.typeentity + '" style="color: #CCCCCC;"> | </span>'; 9348 callout += '<span ng-show="' + attr.typeentity + '">{{' + attr.typeentity + '}}</span>'; 9349 callout += '</div>'; 9350 callout += '<div class="tool-page-callout-text">'; 9351 callout += '<span ng-show="' + attr.typerole.count + ' != 0" ng-repeat="role in ' + attr.typerole + '">{{role}}{{$last ? "" : ", "}}</span>'; 9352 callout += '<span ng-show="' + attr.email + ' || ' + attr.url + ' || ' + attr.orcidid + ' || ' + attr.gridid + '" style="color: #CCCCCC;"> | </span>'; 9353 callout += '<span ng-show="' + attr.email + '"><i class="fa fa-envelope-o" aria-hidden="true"></i> {{' + attr.email +'.replace(\'@\', \' at \') }}'; 9354 callout += '<span ng-show="' + attr.url + ' || ' + attr.orcidid + ' || ' + attr.gridid + '" style="color: #CCCCCC;"> | </span></span>'; 9355 callout += '<span ng-show="' + attr.url + '"><a href="{{' + attr.url + '}}" target="_blank">Link ⺠</a>'; 9356 callout += '<span ng-show="' + attr.orcidid + ' || ' + attr.gridid + '" style="color: #CCCCCC;"> | </span></span>'; 9357 callout += '<span ng-show="' + attr.orcidid + '"><a href="{{' + attr.orcidid + '}}" target="_blank">ORCID âº</a></span>'; 9358 callout += '<span ng-show="' + attr.gridid + '" style="color: #CCCCCC;"> | </span></span>'; 9359 callout += '<span ng-show="' + attr.gridid + '"><a href="https://www.grid.ac/institutes/{{' + attr.gridid + '}}" target="_blank">gridid âº</a></span>'; 9360 callout += '<span ng-show="' + attr.rorid + '" style="color: #CCCCCC;"> | </span></span>'; 9361 callout += '<span ng-show="' + attr.rorid + '"><a href="https://ror.org/{{' + attr.rorid + '}}" target="_blank">rorid âº</a></span>'; 9362 callout += '<span ng-show="' + attr.fundrefid + '" style="color: #CCCCCC;"> | </span></span>'; 9363 callout += '<span ng-show="' + attr.fundrefid + '"><a href="https://dx.doi.org/{{' + attr.fundrefid + '}}" target="_blank">fundrefid âº</a></span>'; 9364 callout += '</div>'; 9365 callout += '</div>'; 9366 return callout; 9367 }, 9368 replace: true 9369 }; 9370});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.