├── .gitignore ├── dist ├── kscope.zip └── kscope │ ├── js │ ├── kscope.min.js │ ├── apex_pages │ │ ├── p2.min.js │ │ └── p2.js │ ├── kscope.js │ ├── bundle.min.js │ └── bundle.js │ └── apex_pages │ └── js │ ├── p2.min.js │ └── p2.js ├── config.json ├── src └── js │ ├── applications │ └── kscope │ │ ├── kscope.js │ │ └── apex_pages │ │ └── p2.js │ ├── modules │ ├── keyboardShortcuts.js │ └── message.js │ └── widgets │ └── customReport.js ├── gulpfile.js ├── gulp-tasks ├── deploy.js ├── scripts.js └── bundle.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | -------------------------------------------------------------------------------- /dist/kscope.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mennooo/apex-modular-js/HEAD/dist/kscope.zip -------------------------------------------------------------------------------- /dist/kscope/js/kscope.min.js: -------------------------------------------------------------------------------- 1 | !function(){apex.jQuery(document).ready(function(){$(".t-IRR-region").customReport(),kscope.keyboardShortcuts.defaultShortcuts()})}(); -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "applications": [ 3 | { 4 | "alias": "kscope", 5 | "modules": ["message", "keyboardShortcuts"], 6 | "widgets": ["customReport"] 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /dist/kscope/apex_pages/js/p2.min.js: -------------------------------------------------------------------------------- 1 | !function(){apex.jQuery(document).ready(function(){$("#MESSAGE").on("click",function(){kscope.message.info({title:"Hi Kscope",text:"Now I'm loaded by the page.js"})})})}(); -------------------------------------------------------------------------------- /dist/kscope/js/apex_pages/p2.min.js: -------------------------------------------------------------------------------- 1 | !function(){apex.jQuery(document).ready(function(){$("#MESSAGE").on("click",function(){kscope.message.info({title:"Hi Kscope",text:"Now I'm loaded by the page.js"})})})}(); -------------------------------------------------------------------------------- /src/js/applications/kscope/kscope.js: -------------------------------------------------------------------------------- 1 | apex.jQuery(document).ready(function() { 2 | 3 | // Make row clickable 4 | $('.t-IRR-region').customReport(); 5 | 6 | // Set defaultShortcuts 7 | kscope.keyboardShortcuts.defaultShortcuts(); 8 | 9 | }); 10 | -------------------------------------------------------------------------------- /dist/kscope/js/kscope.js: -------------------------------------------------------------------------------- 1 | (function localScope() {apex.jQuery(document).ready(function() { 2 | 3 | // Make row clickable 4 | $('.t-IRR-region').customReport(); 5 | 6 | // Set defaultShortcuts 7 | kscope.keyboardShortcuts.defaultShortcuts(); 8 | 9 | }); 10 | })(); -------------------------------------------------------------------------------- /src/js/applications/kscope/apex_pages/p2.js: -------------------------------------------------------------------------------- 1 | apex.jQuery(document).ready(function(){ 2 | 3 | // Show message on button click 4 | $('#MESSAGE').on('click', function() { 5 | kscope.message.info({ 6 | title: "Hi Kscope", 7 | text: "Now I'm loaded by the page.js" 8 | }); 9 | }); 10 | 11 | }); 12 | -------------------------------------------------------------------------------- /dist/kscope/apex_pages/js/p2.js: -------------------------------------------------------------------------------- 1 | (function localScope() {apex.jQuery(document).ready(function(){ 2 | 3 | // Show message on button click 4 | $('#MESSAGE').on('click', function() { 5 | kscope.message.info({ 6 | title: "Hi Kscope", 7 | text: "Now I'm loaded by the page.js" 8 | }); 9 | }); 10 | 11 | }); 12 | })(); -------------------------------------------------------------------------------- /dist/kscope/js/apex_pages/p2.js: -------------------------------------------------------------------------------- 1 | (function localScope() {apex.jQuery(document).ready(function(){ 2 | 3 | // Show message on button click 4 | $('#MESSAGE').on('click', function() { 5 | kscope.message.info({ 6 | title: "Hi Kscope", 7 | text: "Now I'm loaded by the page.js" 8 | }); 9 | }); 10 | 11 | }); 12 | })(); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var requireDir = require('require-dir'); 3 | 4 | // relative file paths 5 | require('app-module-path').addPath(__dirname); 6 | 7 | requireDir('gulp-tasks'); 8 | 9 | gulp.task('bundle', ['bundle:watch']); 10 | 11 | gulp.task('scripts', ['scripts:watch']); 12 | 13 | gulp.task('start', ['bundle:bundleAll', 'scripts:apps', 'scripts:pages', 'deploy']) 14 | 15 | gulp.task('default', ['start', 'bundle', 'scripts']); 16 | -------------------------------------------------------------------------------- /gulp-tasks/deploy.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var notify = require('gulp-notify'); 3 | var zip = require('gulp-zip'); 4 | 5 | gulp.task('deploy', ['bundle:bundleAll'],function() { 6 | return gulp.src(['dist/**/*', '!dist/*.zip'], { 7 | base: 'dist' 8 | }) 9 | .pipe(zip('kscope.zip')) 10 | .pipe(gulp.dest('dist')) 11 | /* 12 | In this sample we only create a zipfile to upload to APEX Static Application files manually 13 | You might want to use ftp to upload files to your webserver like this: 14 | 15 | 16 | 17 | */ 18 | .pipe(notify('Please upload the file dist/kscope/kscope.zip to APEX Static Application Files in the sample application.')); 19 | }); 20 | -------------------------------------------------------------------------------- /src/js/modules/keyboardShortcuts.js: -------------------------------------------------------------------------------- 1 | var key = require("keymaster"); 2 | var message = require("modules/message"); 3 | 4 | // Add shortcut coe 5 | var _addShortcut = function(selectedKey,cb) { 6 | key(selectedKey, cb); 7 | } 8 | 9 | var _clickButton = function(selector) { 10 | $(selector).trigger('click'); 11 | return false; 12 | } 13 | 14 | // Add shortcuts for selectors 15 | var defaultShortcuts = function() { 16 | _addShortcut('ctrl+s', function(event,handler){ 17 | _clickButton('.t-Button[onclick*="\'CREATE\'"],.t-Button#CREATE'); 18 | }); 19 | _addShortcut('ctrl+s', function(event,handler){ 20 | _clickButton('.t-Button[onclick*="\'SAVE\'"],.t-Button#SAVE'); 21 | }); 22 | _addShortcut('ctrl+d', function(event,handler){ 23 | _clickButton('.t-Button[onclick*="\'DELETE\'"],.t-Button#DELETE'); 24 | }); 25 | _addShortcut('ctrl+m', function(event,handler){ 26 | message.info({ 27 | title: "A key was pressed", 28 | text: "ctrl+m" 29 | }); 30 | }); 31 | }; 32 | 33 | module.exports = { 34 | defaultShortcuts: defaultShortcuts 35 | } 36 | -------------------------------------------------------------------------------- /src/js/modules/message.js: -------------------------------------------------------------------------------- 1 | var $ = require("jquery"); // being shimmed 2 | var PNotify = require("pnotify"); 3 | 4 | 5 | // Button defaults 6 | var defaults = { 7 | hide: false, 8 | closer: true, 9 | buttons: { 10 | closer_hover: false, 11 | sticker: false, 12 | labels: { 13 | close: 'Sluit melding' 14 | } 15 | } 16 | }; 17 | 18 | 19 | var getSettings = function(options) { 20 | 21 | switch (typeof options) { 22 | case "string": 23 | return $.extend({ 24 | title: options 25 | }, defaults); 26 | case "object": 27 | return $.extend(options, defaults); 28 | default: 29 | return defaults; 30 | }; 31 | 32 | }; 33 | 34 | var info = function(options) { 35 | return new PNotify($.extend(getSettings(options), { 36 | type: 'info', 37 | hide: true 38 | })); 39 | }; 40 | 41 | var success = function(options) { 42 | return new PNotify($.extend(getSettings(options), { 43 | type: 'success', 44 | hide: true 45 | })); 46 | }; 47 | 48 | var warning = function(options) { 49 | return new PNotify($.extend(getSettings(options), { 50 | type: 'warning' 51 | })); 52 | }; 53 | 54 | var error = function(options) { 55 | return new PNotify($.extend(getSettings(options), { 56 | type: 'error' 57 | })); 58 | }; 59 | 60 | module.exports = { 61 | info: info, 62 | success: success, 63 | warning: warning, 64 | error: error 65 | }; 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apex-modular-js", 3 | "version": "1.0.0", 4 | "description": "Using modular JavaScript with Oracle Application Express", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "gulp" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mennooo/apex-modular-js.git" 12 | }, 13 | "keywords": [ 14 | "orclapex" 15 | ], 16 | "author": "M.Hoogendijk (iAdvise)", 17 | "license": "ISC", 18 | "devDependencies": { 19 | "app-module-path": "^1.0.6", 20 | "async": "^1.5.2", 21 | "browserify": "^13.0.1", 22 | "browserify-global-shim": "^1.0.3", 23 | "browserify-shim": "^3.8.12", 24 | "event-stream": "^3.3.2", 25 | "gulp": "^3.9.1", 26 | "gulp-filesize": "0.0.6", 27 | "gulp-insert": "^0.5.0", 28 | "gulp-notify": "^2.2.0", 29 | "gulp-plumber": "^1.1.0", 30 | "gulp-rename": "^1.2.2", 31 | "gulp-sourcemaps": "^1.6.0", 32 | "gulp-uglify": "^1.5.3", 33 | "gulp-util": "^3.0.7", 34 | "gulp-watch": "^4.3.5", 35 | "gulp-zip": "^3.2.0", 36 | "lodash.assign": "^4.0.8", 37 | "remapify": "^2.1.0", 38 | "require-dir": "^0.3.0", 39 | "require-reload": "^0.2.2", 40 | "underscore": "^1.8.3", 41 | "vinyl": "^1.1.1", 42 | "vinyl-buffer": "^1.0.0", 43 | "vinyl-source-stream": "^1.1.0", 44 | "watchify": "^3.7.0" 45 | }, 46 | "dependencies": { 47 | "keymaster": "^1.6.2", 48 | "pnotify": "^3.0.0" 49 | }, 50 | "bugs": { 51 | "url": "https://github.com/mennooo/apex-modular-js/issues" 52 | }, 53 | "homepage": "https://github.com/mennooo/apex-modular-js#readme" 54 | } 55 | -------------------------------------------------------------------------------- /gulp-tasks/scripts.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var uglify = require('gulp-uglify'); 4 | var rename = require('gulp-rename'); 5 | var plumber = require('gulp-plumber'); 6 | var insert = require('gulp-insert'); 7 | 8 | 9 | // relative file paths 10 | require('app-module-path').addPath(__dirname); 11 | 12 | 13 | var onError = function(error) { 14 | gutil.log(error); 15 | this.emit('end'); 16 | } 17 | 18 | var jsConfig = { 19 | base: 'src/js/applications', 20 | build: 'dist/', 21 | apps: 'src/js/applications/*/*.js', 22 | pages: 'src/js/applications/*/apex_pages/*.js' 23 | }; 24 | 25 | // Two kinds of tasks: original JavaScript or minified JavaScript 26 | function execTask(options) { 27 | 28 | return gulp.src([ 29 | options.source 30 | ], { 31 | base: jsConfig.base 32 | }) 33 | .pipe(plumber({ 34 | errorHandler: onError 35 | })) 36 | .pipe(insert.wrap('(function localScope() {', '})();')) // create local scope for these files 37 | .pipe(rename(function(path){ 38 | // add js folder in dirname 39 | path.dirname = function(){ 40 | var slugs = path.dirname.split('/'); 41 | slugs.splice(1, 0, 'js'); 42 | return slugs.join('/'); 43 | }(); 44 | })) 45 | .pipe(gulp.dest(jsConfig.build)) 46 | .pipe(uglify()) 47 | .pipe(rename({ 48 | suffix: '.min' 49 | })) 50 | .pipe(gulp.dest(jsConfig.build)); 51 | 52 | }; 53 | 54 | gulp.task('scripts:apps', function() { 55 | 56 | return execTask({ 57 | source: jsConfig.apps 58 | }); 59 | 60 | }); 61 | 62 | gulp.task('scripts:pages', function() { 63 | 64 | return execTask({ 65 | source: jsConfig.pages 66 | }); 67 | 68 | }); 69 | 70 | gulp.task('scripts:watch', function() { 71 | 72 | gulp.watch(jsConfig.apps, ['scripts:apps', 'deploy']); 73 | gulp.watch(jsConfig.pages, ['scripts:pages', 'deploy']); 74 | 75 | }); 76 | -------------------------------------------------------------------------------- /src/js/widgets/customReport.js: -------------------------------------------------------------------------------- 1 | $.widget("custom.customReport", { 2 | options: { 3 | exceptClass: 'no-row-link', 4 | activeClass: 'active', 5 | columns: [], 6 | rowclick: function(e, data) { 7 | $(this).customReport('openLink', e); 8 | } 9 | }, 10 | 11 | _create: function() { 12 | var self = this; 13 | // Check if report may have a row link 14 | if (this._rowLinkAllowed) { 15 | this._initRowClick(); 16 | } 17 | 18 | $(this.element).on('apexafterrefresh',function(e){ 19 | self._apexafterrefresh(); 20 | }); 21 | 22 | }, 23 | 24 | _apexafterrefresh: function() { 25 | // Check if report may have a row link 26 | if (this._rowLinkAllowed) { 27 | this._initRowClick(); 28 | } 29 | }, 30 | 31 | _initRowClick: function(cb) { 32 | 33 | var self = this; 34 | var data; 35 | cb = $.proxy(cb, self); 36 | 37 | // Remove previous handlers 38 | this._off(this.element, 'click tr td:not(:has(a))'); 39 | this._off(this.element, 'hover tr td:not(:has(a))'); 40 | 41 | // Add new handler 42 | this._on(this.element, { 43 | 'mouseenter tr td:not(:has(a))': function(e) { 44 | $(e.target).css('cursor', 'pointer'); 45 | } 46 | }); 47 | 48 | // Add new handler 49 | this._on(this.element, { 50 | 'click tr td:not(:has(a))': function(e) { 51 | self._trigger('rowclick', e, data); 52 | e.stopImmediatePropagation(); 53 | } 54 | }); 55 | 56 | }, 57 | 58 | // Use an a href value to redirect on row click in report 59 | openLink: function(event, options) { 60 | 61 | var self = this; 62 | 63 | var defaults = { 64 | aPos: 0 // which "a" element contains the link 65 | } 66 | 67 | var settings = $.extend(options, defaults); 68 | 69 | // Get link 70 | var $linkElem = self._getLinkElement(settings, event.currentTarget); 71 | var href = $linkElem.attr('href'); 72 | 73 | if (href != undefined) { 74 | window.location.href = href; 75 | } 76 | }, 77 | 78 | _getLinkElement: function(options, target) { 79 | 80 | var links = $(target).closest('tr').find('td:has(a)'); 81 | 82 | // Raise exception if position of a element is invalid 83 | if (links.length < options.aPos) { 84 | apex.debug.error('Exception: ', options.aPos + 'th "a" element is not found in report row.'); 85 | } else { 86 | return $(links[options.aPos]).find('a'); 87 | } 88 | 89 | }, 90 | 91 | // _rowLinkAllowed returns boolean 92 | _rowLinkAllowed: function() { 93 | return !$(this.element).hasClass(this.options.exceptClass); 94 | }, 95 | 96 | _setActiveRow: function($row) { 97 | $($row).closest('table').find('td.'+this.options.activeClass).removeClass(this.options.activeClass); 98 | $($row).find('td').addClass(this.options.activeClass); 99 | }, 100 | 101 | activeRow: function() { 102 | return $(this.element).find('td.'+this.options.activeClass).closest('tr'); 103 | } 104 | 105 | }); 106 | -------------------------------------------------------------------------------- /gulp-tasks/bundle.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var gutil = require('gulp-util'); 3 | var browserify = require('browserify'); 4 | var assign = require('lodash.assign'); 5 | var remapify = require('remapify'); 6 | var File = require("vinyl"); 7 | var buffer = require('vinyl-buffer'); 8 | var plumber = require('gulp-plumber'); 9 | var source = require('vinyl-source-stream'); 10 | var sourcemaps = require('gulp-sourcemaps'); 11 | var rename = require('gulp-rename'); 12 | var _ = require('underscore'); 13 | var uglify = require('gulp-uglify'); 14 | var async = require('async'); 15 | var es = require('event-stream'); 16 | var reload = require('require-reload')(require); 17 | var config = reload('config.json'); 18 | 19 | // relative file paths 20 | require('app-module-path').addPath(__dirname); 21 | 22 | // jQuery shim 23 | var globalShim = require('browserify-global-shim').configure({ 24 | 'jQuery': '$', 25 | 'jquery': '$' 26 | }); 27 | 28 | var onError = function(error) { 29 | gutil.log(error); 30 | this.emit('end'); 31 | } 32 | 33 | var bundleTask = function(app, resolve, reject) { 34 | 35 | gutil.log(app.alias, 'requires these modules: ', app.modules); 36 | 37 | // add custom browserify options here 38 | var customOpts = { 39 | debug: true 40 | }; 41 | 42 | // Create new file to add modules & widgets to namespace on global scope 43 | var dynamicJs = [ 44 | "var " + app.alias + " = {};", 45 | "window." + app.alias + " = " + app.alias + ";" 46 | ]; 47 | 48 | // Add modules to new file 49 | app.modules.forEach(function(module) { 50 | dynamicJs.push(app.alias + "." + module + " = require('modules/"+ module + "');"); 51 | }); 52 | 53 | // Add widgets to new file 54 | app.widgets.forEach(function(widget) { 55 | dynamicJs.push("require('widgets/" + widget + "');"); 56 | }); 57 | 58 | gutil.log(dynamicJs.join('')); 59 | 60 | var temp = new File({ 61 | contents: new Buffer(dynamicJs.join('')) 62 | }); 63 | 64 | var b = browserify(temp, customOpts); 65 | 66 | // shims 67 | b.transform(globalShim, { 68 | global: true 69 | }); 70 | 71 | // Browserify plugins 72 | b.plugin(remapify, [{ 73 | cwd: 'src/js/modules', // set the directory to look in 74 | src: '*.js', // glob for the files to remap 75 | expose: 'modules' 76 | }, { 77 | cwd: 'src/js/widgets', // set the directory to look in 78 | src: '*.js', // glob for the files to remap 79 | expose: 'widgets' 80 | }]); 81 | 82 | return b.bundle() 83 | // log errors if they happen 84 | .on('error', onError) 85 | .pipe(plumber({ 86 | errorHandler: onError 87 | })) 88 | .pipe(source('bundle.js')) 89 | .pipe(buffer()) 90 | .pipe(sourcemaps.write('./')) 91 | .pipe(gulp.dest('dist/' + app.alias + '/js')) 92 | .pipe(uglify()) 93 | .pipe(rename({ 94 | suffix: '.min' 95 | })) 96 | .pipe(gulp.dest('dist/' + app.alias + '/js')) 97 | .on('end', function() { 98 | resolve(); 99 | }); 100 | 101 | }; 102 | 103 | gulp.task('bundle:bundleAll', function() { 104 | 105 | config = reload('config.json'); 106 | 107 | var tasks = config.applications.map(function(app) { 108 | return new Promise(function(resolve, reject) { 109 | bundleTask(app, resolve, reject); 110 | }) 111 | }) 112 | 113 | // Return bundled streams after all bundles have been created 114 | return Promise.all(tasks).then(function(results) { 115 | }).catch(function(err) { 116 | console.log(err); 117 | }); 118 | 119 | }); 120 | 121 | 122 | 123 | //moduleTasks 124 | gulp.task('bundle:watch', function() { 125 | gulp.watch(['src/js/modules/*.js', 'src/js/widgets/*.js', 'config.json'], ['bundle:bundleAll', 'deploy']); 126 | }); 127 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # apex-modular-js 2 | Using modular JavaScript with Oracle Application Express 3 | 4 | This project is created for sample purposes only. It demonstrates how to create and use reusable JavaScript modules & widgets in an APEX application. It's developed with a combination of Node.js, Gulp and Browserify. 5 | 6 | The project was presented at the Kscope16 conference. 7 | 8 | Demo: https://apex.oracle.com/pls/apex/f?p=78682 9 | 10 | Blogpost: [https://blog.iadvise.eu/2016/08/12/apex-javascript-the-modular-way/](https://blog.iadvise.eu/2016/08/12/apex-javascript-the-modular-way/) 11 | 12 | Take a look at all the files to see how it works. 13 | 14 | ## System Requirements 15 | - [Node.js](https://nodejs.org) 16 | - [Oracle APEX](https://apex.oracle.com) 17 | - [Git](https://git-scm.com/downloads) 18 | 19 | ## Install this project 20 | - Open the command line 21 | - Go to your desired directory 22 | - Execute 23 | ```bash 24 | git clone https://github.com/mennooo/apex-modular-js.git 25 | cd apex-modular-js 26 | npm install 27 | ``` 28 | 29 | ## Install APEX Sample application 30 | You can import the demo application in your APEX workspace. It works immediate without any further actions and is located at: 31 | ``` 32 | apex/f103.sql 33 | ``` 34 | 35 | ## Run 36 | **On the command line:** 37 | ```bash 38 | npm start 39 | ``` 40 | ## Usage 41 | You can change the content of the JavaScript files to see how it affects the sample application. 42 | From the `src` folder you can create, edit or delete any files in: 43 | ``` 44 | |-/src/ 45 | |-js 46 | |-applications 47 | |-kscope 48 | |-modules 49 | |-widgets 50 | ``` 51 | As you can see the `modules` and `widgets` folders are not placed inside the `applications` folder because we want to be able to use them in multiple applications. 52 | New modules are not added to your application immediate. If you want to include any module or widget in the sample application then change the following config file: 53 | ``` 54 | config.json 55 | ``` 56 | The names of modules and widgets must be the same as the corresponding `.js` files. 57 | 58 | Saving changes to file in the `src` folder will automatically compile your files to this folder structure: 59 | ``` 60 | |-/dist/ 61 | |-kscope 62 | ``` 63 | 64 | A zip file called `kscope.zip` will be created every time you save a change: 65 | ``` 66 | dist/kscope/kscope.zip 67 | ``` 68 | Please upload the file `dist/kscope/kscope.zip` to APEX **Static Application Files** in the sample application. At this point you can use the new code in the application. 69 | 70 | In a real world project you can tell Gulp to upload the content of the dist folder to your webserver via sftp (most likely /i/ folder). 71 | 72 | ## About the APEX sample application 73 | 74 | The application includes 4 examples about using the Modular JavaScript approach. It's also available on apex.oracle.com: 75 | https://apex.oracle.com/pls/apex/f?p=78682 76 | 77 | It's based around integrating the pNotify plugin. 78 | 79 | Examples: 80 | - 1: Load nessecary files in page, put code inside dynamic action (not recommended) 81 | - 2: Encapsulate pNotify in kscope.message module, put code in p2.js file (a bit better) 82 | - 3: Encapsulate kscope.message module in dynamic action plugin, add dynamic action to page 3 (preferred solution) 83 | - 4: Encapsulate kscope.message module inside keyboardShortcuts module (advanced usage) 84 | 85 | In example 4, it's not longer needed to include the message module in the `config.json` because it will be bundled as part of the keyboardShortcuts module. Feel free to test this out. 86 | 87 | The JavaScript files from this project are added under **User Interface Details** in the **JavaScript File URLs**. The filenames are **Substitution Variables** defined in the **Application Definition**. 88 | 89 | ## How the project works 90 | 91 | ### Bundling all modules and widgets 92 | 93 | It's developed to demonstrate how to bundle required modules and widgets into a single file to add to your APEX application. 94 | A module dependency loader loads required client side modules as part of the module. In this case a module dependency loader called Browserify was used. 95 | 96 | For instance: 97 | - The module message.js requires jQuery and pNotify in order to work when bundled. 98 | - The module keyboardShortcuts.js requires keymaster and our message module in order to work when bundled. 99 | 100 | The external plugins are preinstalled and they are available as NPM packages. The installation was done as follows. 101 | Go to the root folder of this project and execute these commands: 102 | ``` 103 | npm install --save pnotify 104 | npm install --save keymaster 105 | ``` 106 | 107 | If you would like to add other external plugins, make sure they are available is NPM package at npmjs.com. 108 | 109 | The tasks to bundle the modules and widgets and place all files for deployment in the `dist` folder is done via an automated task runner called Gulp. 110 | 111 | Gulp itself is a Node.js module which works well together with Browserify. Feel free to take a look at the Gulp config in: 112 | ``` 113 | gulpfile.js 114 | gulp-tasks/bundle.js 115 | gulp-tasks/deploy.js 116 | gulp-tasks/scripts.js 117 | ``` 118 | I had to tweak the Browserify task in Gulp a little bit because Browserify typically creates one big bundle where all functions are only accessible within the file. In APEX we need to access the modules from dynamic actions and other external JavaScript files, so I've attached the modules and widgets to a global namespace called `kscope`. 119 | -------------------------------------------------------------------------------- /dist/kscope/js/bundle.min.js: -------------------------------------------------------------------------------- 1 | !function t(i,e,o){function n(r,a){if(!e[r]){if(!i[r]){var c="function"==typeof require&&require;if(!a&&c)return c(r,!0);if(s)return s(r,!0);var h=new Error("Cannot find module '"+r+"'");throw h.code="MODULE_NOT_FOUND",h}var l=e[r]={exports:{}};i[r][0].call(l.exports,function(t){var e=i[r][1][t];return n(e?e:t)},l,l.exports,t,i,e,o)}return e[r].exports}for(var s="function"==typeof require&&require,r=0;r0;for(s in k)(!k[s]&&e(o.mods,+s)>-1||k[s]&&e(o.mods,+s)==-1)&&(a=!1);(0!=o.mods.length||k[16]||k[18]||k[17]||k[91])&&!a||o.method(t,o)===!1&&(t.preventDefault?t.preventDefault():t.returnValue=!1,t.stopPropagation&&t.stopPropagation(),t.cancelBubble&&(t.cancelBubble=!0))}}function r(t){var i,o=t.keyCode,n=e(R,o);if(n>=0&&R.splice(n,1),93!=o&&224!=o||(o=91),o in k){k[o]=!1;for(i in C)C[i]==o&&(c[i]=!1)}}function a(){for(w in k)k[w]=!1;for(w in C)c[w]=!1}function c(t,i,e){var o,n;o=y(t),void 0===e&&(e=i,i="all");for(var s=0;s1&&(n=g(t),t=[t[t.length-1]]),t=t[0],t=E(t),t in _||(_[t]=[]),_[t].push({shortcut:o[s],scope:i,method:e,key:o[s],mods:n})}function h(t,i){var e,n,s,r,a,c=[];for(e=y(t),r=0;r1&&(c=g(n),t=n[n.length-1]),t=E(t),void 0===i&&(i=f()),!_[t])return;for(s=0;s<_[t].length;s++)a=_[t][s],a.scope===i&&o(a.mods,c)&&(_[t][s]={})}}function l(t){return"string"==typeof t&&(t=E(t)),e(R,t)!=-1}function p(){return R.slice(0)}function u(t){var i=(t.target||t.srcElement).tagName;return!("INPUT"==i||"SELECT"==i||"TEXTAREA"==i)}function d(t){b=t||"all"}function f(){return b||"all"}function m(t){var i,e,o;for(i in _)for(e=_[i],o=0;o",{"class":"ui-pnotify-modal-overlay"});return e.prependTo(i.context),i.overlay_close&&e.click(function(){h.removeStack(i)}),e},h=function(t){this.parseOptions(t),this.init()};return t.extend(h.prototype,{version:"3.0.0",options:{title:!1,title_escape:!1,text:!1,text_escape:!1,styling:"brighttheme",addclass:"",cornerclass:"",auto_display:!0,width:"300px",min_height:"16px",type:"notice",icon:!0,animation:"fade",animate_speed:"normal",shadow:!0,hide:!0,delay:8e3,mouse_reset:!0,remove:!0,insert_brs:!0,destroy:!0,stack:s},modules:{},runModules:function(t,i){var e,o;for(o in this.modules)e="object"==typeof i&&o in i?i[o]:i,"function"==typeof this.modules[o][t]&&(this.modules[o].notice=this,this.modules[o].options="object"==typeof this.options[o]?this.options[o]:{},this.modules[o][t](this,"object"==typeof this.options[o]?this.options[o]:{},e))},state:"initializing",timer:null,animTimer:null,styles:null,elem:null,container:null,title_container:null,text_container:null,animating:!1,timerHide:!1,init:function(){var i=this;return this.modules={},t.extend(!0,this.modules,h.prototype.modules),this.styles="object"==typeof this.options.styling?this.options.styling:h.styling[this.options.styling],this.elem=t("
",{"class":"ui-pnotify "+this.options.addclass,css:{display:"none"},"aria-live":"assertive","aria-role":"alertdialog",mouseenter:function(t){if(i.options.mouse_reset&&"out"===i.animating){if(!i.timerHide)return;i.cancelRemove()}i.options.hide&&i.options.mouse_reset&&i.cancelRemove()},mouseleave:function(t){i.options.hide&&i.options.mouse_reset&&"out"!==i.animating&&i.queueRemove(),h.positionAll()}}),"fade"===this.options.animation&&this.elem.addClass("ui-pnotify-fade-"+this.options.animate_speed),this.container=t("
",{"class":this.styles.container+" ui-pnotify-container "+("error"===this.options.type?this.styles.error:"info"===this.options.type?this.styles.info:"success"===this.options.type?this.styles.success:this.styles.notice),role:"alert"}).appendTo(this.elem),""!==this.options.cornerclass&&this.container.removeClass("ui-corner-all").addClass(this.options.cornerclass),this.options.shadow&&this.container.addClass("ui-pnotify-shadow"),!1!==this.options.icon&&t("
",{"class":"ui-pnotify-icon"}).append(t("",{"class":!0===this.options.icon?"error"===this.options.type?this.styles.error_icon:"info"===this.options.type?this.styles.info_icon:"success"===this.options.type?this.styles.success_icon:this.styles.notice_icon:this.options.icon})).prependTo(this.container),this.title_container=t("

",{"class":"ui-pnotify-title"}).appendTo(this.container),!1===this.options.title?this.title_container.hide():this.options.title_escape?this.title_container.text(this.options.title):this.title_container.html(this.options.title),this.text_container=t("
",{"class":"ui-pnotify-text","aria-role":"alert"}).appendTo(this.container),!1===this.options.text?this.text_container.hide():this.options.text_escape?this.text_container.text(this.options.text):this.text_container.html(this.options.insert_brs?String(this.options.text).replace(/\n/g,"
"):this.options.text),"string"==typeof this.options.width&&this.elem.css("width",this.options.width),"string"==typeof this.options.min_height&&this.container.css("min-height",this.options.min_height),h.notices="top"===this.options.stack.push?t.merge([this],h.notices):t.merge(h.notices,[this]),"top"===this.options.stack.push&&this.queuePosition(!1,1),this.options.stack.animation=!1,this.runModules("init"),this.options.auto_display&&this.open(),this},update:function(i){var e=this.options;return this.parseOptions(e,i),this.elem.removeClass("ui-pnotify-fade-slow ui-pnotify-fade-normal ui-pnotify-fade-fast"),"fade"===this.options.animation&&this.elem.addClass("ui-pnotify-fade-"+this.options.animate_speed),this.options.cornerclass!==e.cornerclass&&this.container.removeClass("ui-corner-all "+e.cornerclass).addClass(this.options.cornerclass),this.options.shadow!==e.shadow&&(this.options.shadow?this.container.addClass("ui-pnotify-shadow"):this.container.removeClass("ui-pnotify-shadow")),!1===this.options.addclass?this.elem.removeClass(e.addclass):this.options.addclass!==e.addclass&&this.elem.removeClass(e.addclass).addClass(this.options.addclass),!1===this.options.title?this.title_container.slideUp("fast"):this.options.title!==e.title&&(this.options.title_escape?this.title_container.text(this.options.title):this.title_container.html(this.options.title),!1===e.title&&this.title_container.slideDown(200)),!1===this.options.text?this.text_container.slideUp("fast"):this.options.text!==e.text&&(this.options.text_escape?this.text_container.text(this.options.text):this.text_container.html(this.options.insert_brs?String(this.options.text).replace(/\n/g,"
"):this.options.text),!1===e.text&&this.text_container.slideDown(200)),this.options.type!==e.type&&this.container.removeClass(this.styles.error+" "+this.styles.notice+" "+this.styles.success+" "+this.styles.info).addClass("error"===this.options.type?this.styles.error:"info"===this.options.type?this.styles.info:"success"===this.options.type?this.styles.success:this.styles.notice),(this.options.icon!==e.icon||!0===this.options.icon&&this.options.type!==e.type)&&(this.container.find("div.ui-pnotify-icon").remove(),!1!==this.options.icon&&t("
",{"class":"ui-pnotify-icon"}).append(t("",{"class":!0===this.options.icon?"error"===this.options.type?this.styles.error_icon:"info"===this.options.type?this.styles.info_icon:"success"===this.options.type?this.styles.success_icon:this.styles.notice_icon:this.options.icon})).prependTo(this.container)),this.options.width!==e.width&&this.elem.animate({width:this.options.width}),this.options.min_height!==e.min_height&&this.container.animate({minHeight:this.options.min_height}),this.options.hide?e.hide||this.queueRemove():this.cancelRemove(),this.queuePosition(!0),this.runModules("update",e),this},open:function(){this.state="opening",this.runModules("beforeOpen");var t=this;return this.elem.parent().length||this.elem.appendTo(this.options.stack.context?this.options.stack.context:n),"top"!==this.options.stack.push&&this.position(!0),this.animateIn(function(){t.queuePosition(!0),t.options.hide&&t.queueRemove(),t.state="open",t.runModules("afterOpen")}),this},remove:function(e){this.state="closing",this.timerHide=!!e,this.runModules("beforeClose");var o=this;return this.timer&&(i.clearTimeout(this.timer),this.timer=null),this.animateOut(function(){if(o.state="closed",o.runModules("afterClose"),o.queuePosition(!0),o.options.remove&&o.elem.detach(),o.runModules("beforeDestroy"),o.options.destroy&&null!==h.notices){var i=t.inArray(o,h.notices);-1!==i&&h.notices.splice(i,1)}o.runModules("afterDestroy")}),this},get:function(){return this.elem},parseOptions:function(i,e){this.options=t.extend(!0,{},h.prototype.options),this.options.stack=h.prototype.options.stack;for(var o,n=[i,e],s=0;s(i.context.is(n)?r.height():i.context.prop("scrollHeight"))||"up"===i.dir1&&i.nextpos1+e.height()>(i.context.is(n)?r.height():i.context.prop("scrollHeight"))||"left"===i.dir1&&i.nextpos1+e.width()>(i.context.is(n)?r.width():i.context.prop("scrollWidth"))||"right"===i.dir1&&i.nextpos1+e.width()>(i.context.is(n)?r.width():i.context.prop("scrollWidth")))&&(i.nextpos1=i.firstpos1,i.nextpos2+=i.addpos2+("undefined"==typeof i.spacing2?25:i.spacing2),i.addpos2=0),"number"==typeof i.nextpos2&&(i.animation?e.css(a,i.nextpos2+"px"):(e.removeClass("ui-pnotify-move"),e.css(a,i.nextpos2+"px"),e.css(a),e.addClass("ui-pnotify-move"))),i.dir2){case"down":case"up":e.outerHeight(!0)>i.addpos2&&(i.addpos2=e.height());break;case"left":case"right":e.outerWidth(!0)>i.addpos2&&(i.addpos2=e.width())}switch("number"==typeof i.nextpos1&&(i.animation?e.css(s,i.nextpos1+"px"):(e.removeClass("ui-pnotify-move"),e.css(s,i.nextpos1+"px"),e.css(s),e.addClass("ui-pnotify-move"))),i.dir1){case"down":case"up":i.nextpos1+=e.height()+("undefined"==typeof i.spacing1?25:i.spacing1);break;case"left":case"right":i.nextpos1+=e.width()+("undefined"==typeof i.spacing1?25:i.spacing1)}}return this}},queuePosition:function(t,i){return o&&clearTimeout(o),i||(i=10),o=setTimeout(function(){h.positionAll(t)},i),this},cancelRemove:function(){return this.timer&&i.clearTimeout(this.timer),this.animTimer&&i.clearTimeout(this.animTimer),"closing"===this.state&&(this.state="open",this.animating=!1,this.elem.addClass("ui-pnotify-in"),"fade"===this.options.animation&&this.elem.addClass("ui-pnotify-fade-in")),this},queueRemove:function(){var t=this;return this.cancelRemove(),this.timer=i.setTimeout(function(){t.remove(!0)},isNaN(this.options.delay)?0:this.options.delay),this}}),t.extend(h,{notices:[],reload:e,removeAll:function(){t.each(h.notices,function(){this.remove&&this.remove(!1)})},removeStack:function(i){t.each(h.notices,function(){this.remove&&this.options.stack===i&&this.remove(!1)})},positionAll:function(i){if(o&&clearTimeout(o),o=null,h.notices&&h.notices.length)t.each(h.notices,function(){var t=this.options.stack;t&&(t.overlay&&t.overlay.hide(),t.nextpos1=t.firstpos1,t.nextpos2=t.firstpos2,t.addpos2=0,t.animation=i)}),t.each(h.notices,function(){this.position()});else{var e=h.prototype.options.stack;e&&(delete e.nextpos1,delete e.nextpos2)}},styling:{brighttheme:{container:"brighttheme",notice:"brighttheme-notice",notice_icon:"brighttheme-icon-notice",info:"brighttheme-info",info_icon:"brighttheme-icon-info",success:"brighttheme-success",success_icon:"brighttheme-icon-success",error:"brighttheme-error",error_icon:"brighttheme-icon-error"},jqueryui:{container:"ui-widget ui-widget-content ui-corner-all",notice:"ui-state-highlight",notice_icon:"ui-icon ui-icon-info",info:"",info_icon:"ui-icon ui-icon-info",success:"ui-state-default",success_icon:"ui-icon ui-icon-circle-check",error:"ui-state-error",error_icon:"ui-icon ui-icon-alert"},bootstrap3:{container:"alert",notice:"alert-warning",notice_icon:"glyphicon glyphicon-exclamation-sign",info:"alert-info",info_icon:"glyphicon glyphicon-info-sign",success:"alert-success",success_icon:"glyphicon glyphicon-ok-sign",error:"alert-danger",error_icon:"glyphicon glyphicon-warning-sign"}}}),h.styling.fontawesome=t.extend({},h.styling.bootstrap3),t.extend(h.styling.fontawesome,{notice_icon:"fa fa-exclamation-circle",info_icon:"fa fa-info",success_icon:"fa fa-check",error_icon:"fa fa-warning"}),i.document.body?a():t(a),h};return e(i)})}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],4:[function(t,i,e){var o=t("keymaster"),n=t("modules/message"),s=function(t,i){o(t,i)},r=function(t){return $(t).trigger("click"),!1},a=function(){s("ctrl+s",function(t,i){r(".t-Button[onclick*=\"'CREATE'\"],.t-Button#CREATE")}),s("ctrl+s",function(t,i){r(".t-Button[onclick*=\"'SAVE'\"],.t-Button#SAVE")}),s("ctrl+d",function(t,i){r(".t-Button[onclick*=\"'DELETE'\"],.t-Button#DELETE")}),s("ctrl+m",function(t,i){n.info({title:"A key was pressed",text:"ctrl+m"})})};i.exports={defaultShortcuts:a}},{keymaster:2,"modules/message":5}],5:[function(t,i,e){var o=window.$,n=t("pnotify"),s={hide:!1,closer:!0,buttons:{closer_hover:!1,sticker:!1,labels:{close:"Sluit melding"}}},r=function(t){switch(typeof t){case"string":return o.extend({title:t},s);case"object":return o.extend(t,s);default:return s}},a=function(t){return new n(o.extend(r(t),{type:"info",hide:!0}))},c=function(t){return new n(o.extend(r(t),{type:"success",hide:!0}))},h=function(t){return new n(o.extend(r(t),{type:"warning"}))},l=function(t){return new n(o.extend(r(t),{type:"error"}))};i.exports={info:a,success:c,warning:h,error:l}},{pnotify:3}],6:[function(t,i,e){$.widget("custom.customReport",{options:{exceptClass:"no-row-link",activeClass:"active",columns:[],rowclick:function(t,i){$(this).customReport("openLink",t)}},_create:function(){var t=this;this._rowLinkAllowed&&this._initRowClick(),$(this.element).on("apexafterrefresh",function(i){t._apexafterrefresh()})},_apexafterrefresh:function(){this._rowLinkAllowed&&this._initRowClick()},_initRowClick:function(t){var i,e=this;t=$.proxy(t,e),this._off(this.element,"click tr td:not(:has(a))"),this._off(this.element,"hover tr td:not(:has(a))"),this._on(this.element,{"mouseenter tr td:not(:has(a))":function(t){$(t.target).css("cursor","pointer")}}),this._on(this.element,{"click tr td:not(:has(a))":function(t){e._trigger("rowclick",t,i),t.stopImmediatePropagation()}})},openLink:function(t,i){var e=this,o={aPos:0},n=$.extend(i,o),s=e._getLinkElement(n,t.currentTarget),r=s.attr("href");void 0!=r&&(window.location.href=r)},_getLinkElement:function(t,i){var e=$(i).closest("tr").find("td:has(a)");return e.length property to true and return 78 | if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko 79 | if(key in _mods) { 80 | _mods[key] = true; 81 | // 'assignKey' from inside this closure is exported to window.key 82 | for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true; 83 | return; 84 | } 85 | updateModifierKey(event); 86 | 87 | // see if we need to ignore the keypress (filter() can can be overridden) 88 | // by default ignore key presses if a select, textarea, or input is focused 89 | if(!assignKey.filter.call(this, event)) return; 90 | 91 | // abort if no potentially matching shortcuts found 92 | if (!(key in _handlers)) return; 93 | 94 | scope = getScope(); 95 | 96 | // for each potential shortcut 97 | for (i = 0; i < _handlers[key].length; i++) { 98 | handler = _handlers[key][i]; 99 | 100 | // see if it's in the current scope 101 | if(handler.scope == scope || handler.scope == 'all'){ 102 | // check if modifiers match if any 103 | modifiersMatch = handler.mods.length > 0; 104 | for(k in _mods) 105 | if((!_mods[k] && index(handler.mods, +k) > -1) || 106 | (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false; 107 | // call the handler and stop the event if neccessary 108 | if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){ 109 | if(handler.method(event, handler)===false){ 110 | if(event.preventDefault) event.preventDefault(); 111 | else event.returnValue = false; 112 | if(event.stopPropagation) event.stopPropagation(); 113 | if(event.cancelBubble) event.cancelBubble = true; 114 | } 115 | } 116 | } 117 | } 118 | }; 119 | 120 | // unset modifier keys on keyup 121 | function clearModifier(event){ 122 | var key = event.keyCode, k, 123 | i = index(_downKeys, key); 124 | 125 | // remove key from _downKeys 126 | if (i >= 0) { 127 | _downKeys.splice(i, 1); 128 | } 129 | 130 | if(key == 93 || key == 224) key = 91; 131 | if(key in _mods) { 132 | _mods[key] = false; 133 | for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false; 134 | } 135 | }; 136 | 137 | function resetModifiers() { 138 | for(k in _mods) _mods[k] = false; 139 | for(k in _MODIFIERS) assignKey[k] = false; 140 | }; 141 | 142 | // parse and assign shortcut 143 | function assignKey(key, scope, method){ 144 | var keys, mods; 145 | keys = getKeys(key); 146 | if (method === undefined) { 147 | method = scope; 148 | scope = 'all'; 149 | } 150 | 151 | // for each shortcut 152 | for (var i = 0; i < keys.length; i++) { 153 | // set modifier keys if any 154 | mods = []; 155 | key = keys[i].split('+'); 156 | if (key.length > 1){ 157 | mods = getMods(key); 158 | key = [key[key.length-1]]; 159 | } 160 | // convert to keycode and... 161 | key = key[0] 162 | key = code(key); 163 | // ...store handler 164 | if (!(key in _handlers)) _handlers[key] = []; 165 | _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods }); 166 | } 167 | }; 168 | 169 | // unbind all handlers for given key in current scope 170 | function unbindKey(key, scope) { 171 | var multipleKeys, keys, 172 | mods = [], 173 | i, j, obj; 174 | 175 | multipleKeys = getKeys(key); 176 | 177 | for (j = 0; j < multipleKeys.length; j++) { 178 | keys = multipleKeys[j].split('+'); 179 | 180 | if (keys.length > 1) { 181 | mods = getMods(keys); 182 | key = keys[keys.length - 1]; 183 | } 184 | 185 | key = code(key); 186 | 187 | if (scope === undefined) { 188 | scope = getScope(); 189 | } 190 | if (!_handlers[key]) { 191 | return; 192 | } 193 | for (i = 0; i < _handlers[key].length; i++) { 194 | obj = _handlers[key][i]; 195 | // only clear handlers if correct scope and mods match 196 | if (obj.scope === scope && compareArray(obj.mods, mods)) { 197 | _handlers[key][i] = {}; 198 | } 199 | } 200 | } 201 | }; 202 | 203 | // Returns true if the key with code 'keyCode' is currently down 204 | // Converts strings into key codes. 205 | function isPressed(keyCode) { 206 | if (typeof(keyCode)=='string') { 207 | keyCode = code(keyCode); 208 | } 209 | return index(_downKeys, keyCode) != -1; 210 | } 211 | 212 | function getPressedKeyCodes() { 213 | return _downKeys.slice(0); 214 | } 215 | 216 | function filter(event){ 217 | var tagName = (event.target || event.srcElement).tagName; 218 | // ignore keypressed in any elements that support keyboard data input 219 | return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA'); 220 | } 221 | 222 | // initialize key. to false 223 | for(k in _MODIFIERS) assignKey[k] = false; 224 | 225 | // set current scope (default 'all') 226 | function setScope(scope){ _scope = scope || 'all' }; 227 | function getScope(){ return _scope || 'all' }; 228 | 229 | // delete all handlers for a given scope 230 | function deleteScope(scope){ 231 | var key, handlers, i; 232 | 233 | for (key in _handlers) { 234 | handlers = _handlers[key]; 235 | for (i = 0; i < handlers.length; ) { 236 | if (handlers[i].scope === scope) handlers.splice(i, 1); 237 | else i++; 238 | } 239 | } 240 | }; 241 | 242 | // abstract key logic for assign and unassign 243 | function getKeys(key) { 244 | var keys; 245 | key = key.replace(/\s/g, ''); 246 | keys = key.split(','); 247 | if ((keys[keys.length - 1]) == '') { 248 | keys[keys.length - 2] += ','; 249 | } 250 | return keys; 251 | } 252 | 253 | // abstract mods logic for assign and unassign 254 | function getMods(key) { 255 | var mods = key.slice(0, key.length - 1); 256 | for (var mi = 0; mi < mods.length; mi++) 257 | mods[mi] = _MODIFIERS[mods[mi]]; 258 | return mods; 259 | } 260 | 261 | // cross-browser events 262 | function addEvent(object, event, method) { 263 | if (object.addEventListener) 264 | object.addEventListener(event, method, false); 265 | else if(object.attachEvent) 266 | object.attachEvent('on'+event, function(){ method(window.event) }); 267 | }; 268 | 269 | // set the handlers globally on document 270 | addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48 271 | addEvent(document, 'keyup', clearModifier); 272 | 273 | // reset modifiers to false whenever the window is (re)focused. 274 | addEvent(window, 'focus', resetModifiers); 275 | 276 | // store previously defined key 277 | var previousKey = global.key; 278 | 279 | // restore previously defined key and return reference to our key object 280 | function noConflict() { 281 | var k = global.key; 282 | global.key = previousKey; 283 | return k; 284 | } 285 | 286 | // set window.key and window.key.set/get/deleteScope, and the default filter 287 | global.key = assignKey; 288 | global.key.setScope = setScope; 289 | global.key.getScope = getScope; 290 | global.key.deleteScope = deleteScope; 291 | global.key.filter = filter; 292 | global.key.isPressed = isPressed; 293 | global.key.getPressedKeyCodes = getPressedKeyCodes; 294 | global.key.noConflict = noConflict; 295 | global.key.unbind = unbindKey; 296 | 297 | if(typeof module !== 'undefined') module.exports = assignKey; 298 | 299 | })(this); 300 | 301 | },{}],3:[function(require,module,exports){ 302 | (function (global){ 303 | /* 304 | PNotify 3.0.0 sciactive.com/pnotify/ 305 | (C) 2015 Hunter Perrin; Google, Inc. 306 | license Apache-2.0 307 | */ 308 | (function(b,k){"function"===typeof define&&define.amd?define("pnotify",["jquery"],function(q){return k(q,b)}):"object"===typeof exports&&"undefined"!==typeof module?module.exports=k((window.$),global||b):b.PNotify=k(b.jQuery,b)})(this,function(b,k){var q=function(l){var k={dir1:"down",dir2:"left",push:"bottom",spacing1:36,spacing2:36,context:b("body"),modal:!1},g,h,n=b(l),r=function(){h=b("body");d.prototype.options.stack.context=h;n=b(l);n.bind("resize",function(){g&&clearTimeout(g);g=setTimeout(function(){d.positionAll(!0)}, 309 | 10)})},s=function(c){var a=b("
",{"class":"ui-pnotify-modal-overlay"});a.prependTo(c.context);c.overlay_close&&a.click(function(){d.removeStack(c)});return a},d=function(c){this.parseOptions(c);this.init()};b.extend(d.prototype,{version:"3.0.0",options:{title:!1,title_escape:!1,text:!1,text_escape:!1,styling:"brighttheme",addclass:"",cornerclass:"",auto_display:!0,width:"300px",min_height:"16px",type:"notice",icon:!0,animation:"fade",animate_speed:"normal",shadow:!0,hide:!0,delay:8E3,mouse_reset:!0, 310 | remove:!0,insert_brs:!0,destroy:!0,stack:k},modules:{},runModules:function(c,a){var p,b;for(b in this.modules)p="object"===typeof a&&b in a?a[b]:a,"function"===typeof this.modules[b][c]&&(this.modules[b].notice=this,this.modules[b].options="object"===typeof this.options[b]?this.options[b]:{},this.modules[b][c](this,"object"===typeof this.options[b]?this.options[b]:{},p))},state:"initializing",timer:null,animTimer:null,styles:null,elem:null,container:null,title_container:null,text_container:null,animating:!1, 311 | timerHide:!1,init:function(){var c=this;this.modules={};b.extend(!0,this.modules,d.prototype.modules);this.styles="object"===typeof this.options.styling?this.options.styling:d.styling[this.options.styling];this.elem=b("
",{"class":"ui-pnotify "+this.options.addclass,css:{display:"none"},"aria-live":"assertive","aria-role":"alertdialog",mouseenter:function(a){if(c.options.mouse_reset&&"out"===c.animating){if(!c.timerHide)return;c.cancelRemove()}c.options.hide&&c.options.mouse_reset&&c.cancelRemove()}, 312 | mouseleave:function(a){c.options.hide&&c.options.mouse_reset&&"out"!==c.animating&&c.queueRemove();d.positionAll()}});"fade"===this.options.animation&&this.elem.addClass("ui-pnotify-fade-"+this.options.animate_speed);this.container=b("
",{"class":this.styles.container+" ui-pnotify-container "+("error"===this.options.type?this.styles.error:"info"===this.options.type?this.styles.info:"success"===this.options.type?this.styles.success:this.styles.notice),role:"alert"}).appendTo(this.elem);""!== 313 | this.options.cornerclass&&this.container.removeClass("ui-corner-all").addClass(this.options.cornerclass);this.options.shadow&&this.container.addClass("ui-pnotify-shadow");!1!==this.options.icon&&b("
",{"class":"ui-pnotify-icon"}).append(b("",{"class":!0===this.options.icon?"error"===this.options.type?this.styles.error_icon:"info"===this.options.type?this.styles.info_icon:"success"===this.options.type?this.styles.success_icon:this.styles.notice_icon:this.options.icon})).prependTo(this.container); 314 | this.title_container=b("

",{"class":"ui-pnotify-title"}).appendTo(this.container);!1===this.options.title?this.title_container.hide():this.options.title_escape?this.title_container.text(this.options.title):this.title_container.html(this.options.title);this.text_container=b("
",{"class":"ui-pnotify-text","aria-role":"alert"}).appendTo(this.container);!1===this.options.text?this.text_container.hide():this.options.text_escape?this.text_container.text(this.options.text):this.text_container.html(this.options.insert_brs? 315 | String(this.options.text).replace(/\n/g,"
"):this.options.text);"string"===typeof this.options.width&&this.elem.css("width",this.options.width);"string"===typeof this.options.min_height&&this.container.css("min-height",this.options.min_height);d.notices="top"===this.options.stack.push?b.merge([this],d.notices):b.merge(d.notices,[this]);"top"===this.options.stack.push&&this.queuePosition(!1,1);this.options.stack.animation=!1;this.runModules("init");this.options.auto_display&&this.open();return this}, 316 | update:function(c){var a=this.options;this.parseOptions(a,c);this.elem.removeClass("ui-pnotify-fade-slow ui-pnotify-fade-normal ui-pnotify-fade-fast");"fade"===this.options.animation&&this.elem.addClass("ui-pnotify-fade-"+this.options.animate_speed);this.options.cornerclass!==a.cornerclass&&this.container.removeClass("ui-corner-all "+a.cornerclass).addClass(this.options.cornerclass);this.options.shadow!==a.shadow&&(this.options.shadow?this.container.addClass("ui-pnotify-shadow"):this.container.removeClass("ui-pnotify-shadow")); 317 | !1===this.options.addclass?this.elem.removeClass(a.addclass):this.options.addclass!==a.addclass&&this.elem.removeClass(a.addclass).addClass(this.options.addclass);!1===this.options.title?this.title_container.slideUp("fast"):this.options.title!==a.title&&(this.options.title_escape?this.title_container.text(this.options.title):this.title_container.html(this.options.title),!1===a.title&&this.title_container.slideDown(200));!1===this.options.text?this.text_container.slideUp("fast"):this.options.text!== 318 | a.text&&(this.options.text_escape?this.text_container.text(this.options.text):this.text_container.html(this.options.insert_brs?String(this.options.text).replace(/\n/g,"
"):this.options.text),!1===a.text&&this.text_container.slideDown(200));this.options.type!==a.type&&this.container.removeClass(this.styles.error+" "+this.styles.notice+" "+this.styles.success+" "+this.styles.info).addClass("error"===this.options.type?this.styles.error:"info"===this.options.type?this.styles.info:"success"===this.options.type? 319 | this.styles.success:this.styles.notice);if(this.options.icon!==a.icon||!0===this.options.icon&&this.options.type!==a.type)this.container.find("div.ui-pnotify-icon").remove(),!1!==this.options.icon&&b("
",{"class":"ui-pnotify-icon"}).append(b("",{"class":!0===this.options.icon?"error"===this.options.type?this.styles.error_icon:"info"===this.options.type?this.styles.info_icon:"success"===this.options.type?this.styles.success_icon:this.styles.notice_icon:this.options.icon})).prependTo(this.container); 320 | this.options.width!==a.width&&this.elem.animate({width:this.options.width});this.options.min_height!==a.min_height&&this.container.animate({minHeight:this.options.min_height});this.options.hide?a.hide||this.queueRemove():this.cancelRemove();this.queuePosition(!0);this.runModules("update",a);return this},open:function(){this.state="opening";this.runModules("beforeOpen");var c=this;this.elem.parent().length||this.elem.appendTo(this.options.stack.context?this.options.stack.context:h);"top"!==this.options.stack.push&& 321 | this.position(!0);this.animateIn(function(){c.queuePosition(!0);c.options.hide&&c.queueRemove();c.state="open";c.runModules("afterOpen")});return this},remove:function(c){this.state="closing";this.timerHide=!!c;this.runModules("beforeClose");var a=this;this.timer&&(l.clearTimeout(this.timer),this.timer=null);this.animateOut(function(){a.state="closed";a.runModules("afterClose");a.queuePosition(!0);a.options.remove&&a.elem.detach();a.runModules("beforeDestroy");if(a.options.destroy&&null!==d.notices){var c= 322 | b.inArray(a,d.notices);-1!==c&&d.notices.splice(c,1)}a.runModules("afterDestroy")});return this},get:function(){return this.elem},parseOptions:function(c,a){this.options=b.extend(!0,{},d.prototype.options);this.options.stack=d.prototype.options.stack;for(var p=[c,a],m,f=0;f(a.context.is(h)?n.height():a.context.prop("scrollHeight"))||"up"===a.dir1&&a.nextpos1+b.height()> 327 | (a.context.is(h)?n.height():a.context.prop("scrollHeight"))||"left"===a.dir1&&a.nextpos1+b.width()>(a.context.is(h)?n.width():a.context.prop("scrollWidth"))||"right"===a.dir1&&a.nextpos1+b.width()>(a.context.is(h)?n.width():a.context.prop("scrollWidth")))a.nextpos1=a.firstpos1,a.nextpos2+=a.addpos2+("undefined"===typeof a.spacing2?25:a.spacing2),a.addpos2=0;"number"===typeof a.nextpos2&&(a.animation?b.css(e,a.nextpos2+"px"):(b.removeClass("ui-pnotify-move"),b.css(e,a.nextpos2+"px"),b.css(e),b.addClass("ui-pnotify-move"))); 328 | switch(a.dir2){case "down":case "up":b.outerHeight(!0)>a.addpos2&&(a.addpos2=b.height());break;case "left":case "right":b.outerWidth(!0)>a.addpos2&&(a.addpos2=b.width())}"number"===typeof a.nextpos1&&(a.animation?b.css(f,a.nextpos1+"px"):(b.removeClass("ui-pnotify-move"),b.css(f,a.nextpos1+"px"),b.css(f),b.addClass("ui-pnotify-move")));switch(a.dir1){case "down":case "up":a.nextpos1+=b.height()+("undefined"===typeof a.spacing1?25:a.spacing1);break;case "left":case "right":a.nextpos1+=b.width()+("undefined"=== 329 | typeof a.spacing1?25:a.spacing1)}}return this}},queuePosition:function(b,a){g&&clearTimeout(g);a||(a=10);g=setTimeout(function(){d.positionAll(b)},a);return this},cancelRemove:function(){this.timer&&l.clearTimeout(this.timer);this.animTimer&&l.clearTimeout(this.animTimer);"closing"===this.state&&(this.state="open",this.animating=!1,this.elem.addClass("ui-pnotify-in"),"fade"===this.options.animation&&this.elem.addClass("ui-pnotify-fade-in"));return this},queueRemove:function(){var b=this;this.cancelRemove(); 330 | this.timer=l.setTimeout(function(){b.remove(!0)},isNaN(this.options.delay)?0:this.options.delay);return this}});b.extend(d,{notices:[],reload:q,removeAll:function(){b.each(d.notices,function(){this.remove&&this.remove(!1)})},removeStack:function(c){b.each(d.notices,function(){this.remove&&this.options.stack===c&&this.remove(!1)})},positionAll:function(c){g&&clearTimeout(g);g=null;if(d.notices&&d.notices.length)b.each(d.notices,function(){var a=this.options.stack;a&&(a.overlay&&a.overlay.hide(),a.nextpos1= 331 | a.firstpos1,a.nextpos2=a.firstpos2,a.addpos2=0,a.animation=c)}),b.each(d.notices,function(){this.position()});else{var a=d.prototype.options.stack;a&&(delete a.nextpos1,delete a.nextpos2)}},styling:{brighttheme:{container:"brighttheme",notice:"brighttheme-notice",notice_icon:"brighttheme-icon-notice",info:"brighttheme-info",info_icon:"brighttheme-icon-info",success:"brighttheme-success",success_icon:"brighttheme-icon-success",error:"brighttheme-error",error_icon:"brighttheme-icon-error"},jqueryui:{container:"ui-widget ui-widget-content ui-corner-all", 332 | notice:"ui-state-highlight",notice_icon:"ui-icon ui-icon-info",info:"",info_icon:"ui-icon ui-icon-info",success:"ui-state-default",success_icon:"ui-icon ui-icon-circle-check",error:"ui-state-error",error_icon:"ui-icon ui-icon-alert"},bootstrap3:{container:"alert",notice:"alert-warning",notice_icon:"glyphicon glyphicon-exclamation-sign",info:"alert-info",info_icon:"glyphicon glyphicon-info-sign",success:"alert-success",success_icon:"glyphicon glyphicon-ok-sign",error:"alert-danger",error_icon:"glyphicon glyphicon-warning-sign"}}}); 333 | d.styling.fontawesome=b.extend({},d.styling.bootstrap3);b.extend(d.styling.fontawesome,{notice_icon:"fa fa-exclamation-circle",info_icon:"fa fa-info",success_icon:"fa fa-check",error_icon:"fa fa-warning"});l.document.body?r():b(r);return d};return q(k)}); 334 | 335 | }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) 336 | 337 | },{}],4:[function(require,module,exports){ 338 | var key = require("keymaster"); 339 | var message = require("modules/message"); 340 | 341 | // Add shortcut coe 342 | var _addShortcut = function(selectedKey,cb) { 343 | key(selectedKey, cb); 344 | } 345 | 346 | var _clickButton = function(selector) { 347 | $(selector).trigger('click'); 348 | return false; 349 | } 350 | 351 | // Add shortcuts for selectors 352 | var defaultShortcuts = function() { 353 | _addShortcut('ctrl+s', function(event,handler){ 354 | _clickButton('.t-Button[onclick*="\'CREATE\'"],.t-Button#CREATE'); 355 | }); 356 | _addShortcut('ctrl+s', function(event,handler){ 357 | _clickButton('.t-Button[onclick*="\'SAVE\'"],.t-Button#SAVE'); 358 | }); 359 | _addShortcut('ctrl+d', function(event,handler){ 360 | _clickButton('.t-Button[onclick*="\'DELETE\'"],.t-Button#DELETE'); 361 | }); 362 | _addShortcut('ctrl+m', function(event,handler){ 363 | message.info({ 364 | title: "A key was pressed", 365 | text: "ctrl+m" 366 | }); 367 | }); 368 | }; 369 | 370 | module.exports = { 371 | defaultShortcuts: defaultShortcuts 372 | } 373 | 374 | },{"keymaster":2,"modules/message":5}],5:[function(require,module,exports){ 375 | var $ = (window.$); // being shimmed 376 | var PNotify = require("pnotify"); 377 | 378 | 379 | // Button defaults 380 | var defaults = { 381 | hide: false, 382 | closer: true, 383 | buttons: { 384 | closer_hover: false, 385 | sticker: false, 386 | labels: { 387 | close: 'Sluit melding' 388 | } 389 | } 390 | }; 391 | 392 | 393 | var getSettings = function(options) { 394 | 395 | switch (typeof options) { 396 | case "string": 397 | return $.extend({ 398 | title: options 399 | }, defaults); 400 | case "object": 401 | return $.extend(options, defaults); 402 | default: 403 | return defaults; 404 | }; 405 | 406 | }; 407 | 408 | var info = function(options) { 409 | return new PNotify($.extend(getSettings(options), { 410 | type: 'info', 411 | hide: true 412 | })); 413 | }; 414 | 415 | var success = function(options) { 416 | return new PNotify($.extend(getSettings(options), { 417 | type: 'success', 418 | hide: true 419 | })); 420 | }; 421 | 422 | var warning = function(options) { 423 | return new PNotify($.extend(getSettings(options), { 424 | type: 'warning' 425 | })); 426 | }; 427 | 428 | var error = function(options) { 429 | return new PNotify($.extend(getSettings(options), { 430 | type: 'error' 431 | })); 432 | }; 433 | 434 | module.exports = { 435 | info: info, 436 | success: success, 437 | warning: warning, 438 | error: error 439 | }; 440 | 441 | },{"pnotify":3}],6:[function(require,module,exports){ 442 | $.widget("custom.customReport", { 443 | options: { 444 | exceptClass: 'no-row-link', 445 | activeClass: 'active', 446 | columns: [], 447 | rowclick: function(e, data) { 448 | $(this).customReport('openLink', e); 449 | } 450 | }, 451 | 452 | _create: function() { 453 | var self = this; 454 | // Check if report may have a row link 455 | if (this._rowLinkAllowed) { 456 | this._initRowClick(); 457 | } 458 | 459 | $(this.element).on('apexafterrefresh',function(e){ 460 | self._apexafterrefresh(); 461 | }); 462 | 463 | }, 464 | 465 | _apexafterrefresh: function() { 466 | // Check if report may have a row link 467 | if (this._rowLinkAllowed) { 468 | this._initRowClick(); 469 | } 470 | }, 471 | 472 | _initRowClick: function(cb) { 473 | 474 | var self = this; 475 | var data; 476 | cb = $.proxy(cb, self); 477 | 478 | // Remove previous handlers 479 | this._off(this.element, 'click tr td:not(:has(a))'); 480 | this._off(this.element, 'hover tr td:not(:has(a))'); 481 | 482 | // Add new handler 483 | this._on(this.element, { 484 | 'mouseenter tr td:not(:has(a))': function(e) { 485 | $(e.target).css('cursor', 'pointer'); 486 | } 487 | }); 488 | 489 | // Add new handler 490 | this._on(this.element, { 491 | 'click tr td:not(:has(a))': function(e) { 492 | self._trigger('rowclick', e, data); 493 | e.stopImmediatePropagation(); 494 | } 495 | }); 496 | 497 | }, 498 | 499 | // Use an a href value to redirect on row click in report 500 | openLink: function(event, options) { 501 | 502 | var self = this; 503 | 504 | var defaults = { 505 | aPos: 0 // which "a" element contains the link 506 | } 507 | 508 | var settings = $.extend(options, defaults); 509 | 510 | // Get link 511 | var $linkElem = self._getLinkElement(settings, event.currentTarget); 512 | var href = $linkElem.attr('href'); 513 | 514 | if (href != undefined) { 515 | window.location.href = href; 516 | } 517 | }, 518 | 519 | _getLinkElement: function(options, target) { 520 | 521 | var links = $(target).closest('tr').find('td:has(a)'); 522 | 523 | // Raise exception if position of a element is invalid 524 | if (links.length < options.aPos) { 525 | apex.debug.error('Exception: ', options.aPos + 'th "a" element is not found in report row.'); 526 | } else { 527 | return $(links[options.aPos]).find('a'); 528 | } 529 | 530 | }, 531 | 532 | // _rowLinkAllowed returns boolean 533 | _rowLinkAllowed: function() { 534 | return !$(this.element).hasClass(this.options.exceptClass); 535 | }, 536 | 537 | _setActiveRow: function($row) { 538 | $($row).closest('table').find('td.'+this.options.activeClass).removeClass(this.options.activeClass); 539 | $($row).find('td').addClass(this.options.activeClass); 540 | }, 541 | 542 | activeRow: function() { 543 | return $(this.element).find('td.'+this.options.activeClass).closest('tr'); 544 | } 545 | 546 | }); 547 | 548 | },{}]},{},[1]) 549 | //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJfc3RyZWFtXzAuanMiLCJub2RlX21vZHVsZXMva2V5bWFzdGVyL2tleW1hc3Rlci5qcyIsIm5vZGVfbW9kdWxlcy9wbm90aWZ5L2Rpc3QvcG5vdGlmeS5qcyIsInNyYy9qcy9tb2R1bGVzL2tleWJvYXJkU2hvcnRjdXRzLmpzIiwic3JjL2pzL21vZHVsZXMvbWVzc2FnZS5qcyIsInNyYy9qcy93aWRnZXRzL2N1c3RvbVJlcG9ydC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTtBQ0FBOztBQ0FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FDeFNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7QUMvQkE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25DQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDakVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsInZhciBrc2NvcGUgPSB7fTt3aW5kb3cua3Njb3BlID0ga3Njb3BlO2tzY29wZS5tZXNzYWdlID0gcmVxdWlyZSgnbW9kdWxlcy9tZXNzYWdlJyk7a3Njb3BlLmtleWJvYXJkU2hvcnRjdXRzID0gcmVxdWlyZSgnbW9kdWxlcy9rZXlib2FyZFNob3J0Y3V0cycpO3JlcXVpcmUoJ3dpZGdldHMvY3VzdG9tUmVwb3J0Jyk7IiwiLy8gICAgIGtleW1hc3Rlci5qc1xuLy8gICAgIChjKSAyMDExLTIwMTMgVGhvbWFzIEZ1Y2hzXG4vLyAgICAga2V5bWFzdGVyLmpzIG1heSBiZSBmcmVlbHkgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIE1JVCBsaWNlbnNlLlxuXG47KGZ1bmN0aW9uKGdsb2JhbCl7XG4gIHZhciBrLFxuICAgIF9oYW5kbGVycyA9IHt9LFxuICAgIF9tb2RzID0geyAxNjogZmFsc2UsIDE4OiBmYWxzZSwgMTc6IGZhbHNlLCA5MTogZmFsc2UgfSxcbiAgICBfc2NvcGUgPSAnYWxsJyxcbiAgICAvLyBtb2RpZmllciBrZXlzXG4gICAgX01PRElGSUVSUyA9IHtcbiAgICAgICfih6cnOiAxNiwgc2hpZnQ6IDE2LFxuICAgICAgJ+KMpSc6IDE4LCBhbHQ6IDE4LCBvcHRpb246IDE4LFxuICAgICAgJ+KMgyc6IDE3LCBjdHJsOiAxNywgY29udHJvbDogMTcsXG4gICAgICAn4oyYJzogOTEsIGNvbW1hbmQ6IDkxXG4gICAgfSxcbiAgICAvLyBzcGVjaWFsIGtleXNcbiAgICBfTUFQID0ge1xuICAgICAgYmFja3NwYWNlOiA4LCB0YWI6IDksIGNsZWFyOiAxMixcbiAgICAgIGVudGVyOiAxMywgJ3JldHVybic6IDEzLFxuICAgICAgZXNjOiAyNywgZXNjYXBlOiAyNywgc3BhY2U6IDMyLFxuICAgICAgbGVmdDogMzcsIHVwOiAzOCxcbiAgICAgIHJpZ2h0OiAzOSwgZG93bjogNDAsXG4gICAgICBkZWw6IDQ2LCAnZGVsZXRlJzogNDYsXG4gICAgICBob21lOiAzNiwgZW5kOiAzNSxcbiAgICAgIHBhZ2V1cDogMzMsIHBhZ2Vkb3duOiAzNCxcbiAgICAgICcsJzogMTg4LCAnLic6IDE5MCwgJy8nOiAxOTEsXG4gICAgICAnYCc6IDE5MiwgJy0nOiAxODksICc9JzogMTg3LFxuICAgICAgJzsnOiAxODYsICdcXCcnOiAyMjIsXG4gICAgICAnWyc6IDIxOSwgJ10nOiAyMjEsICdcXFxcJzogMjIwXG4gICAgfSxcbiAgICBjb2RlID0gZnVuY3Rpb24oeCl7XG4gICAgICByZXR1cm4gX01BUFt4XSB8fCB4LnRvVXBwZXJDYXNlKCkuY2hhckNvZGVBdCgwKTtcbiAgICB9LFxuICAgIF9kb3duS2V5cyA9IFtdO1xuXG4gIGZvcihrPTE7azwyMDtrKyspIF9NQVBbJ2YnK2tdID0gMTExK2s7XG5cbiAgLy8gSUUgZG9lc24ndCBzdXBwb3J0IEFycmF5I2luZGV4T2YsIHNvIGhhdmUgYSBzaW1wbGUgcmVwbGFjZW1lbnRcbiAgZnVuY3Rpb24gaW5kZXgoYXJyYXksIGl0ZW0pe1xuICAgIHZhciBpID0gYXJyYXkubGVuZ3RoO1xuICAgIHdoaWxlKGktLSkgaWYoYXJyYXlbaV09PT1pdGVtKSByZXR1cm4gaTtcbiAgICByZXR1cm4gLTE7XG4gIH1cblxuICAvLyBmb3IgY29tcGFyaW5nIG1vZHMgYmVmb3JlIHVuYXNzaWdubWVudFxuICBmdW5jdGlvbiBjb21wYXJlQXJyYXkoYTEsIGEyKSB7XG4gICAgaWYgKGExLmxlbmd0aCAhPSBhMi5sZW5ndGgpIHJldHVybiBmYWxzZTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGExLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGlmIChhMVtpXSAhPT0gYTJbaV0pIHJldHVybiBmYWxzZTtcbiAgICB9XG4gICAgcmV0dXJuIHRydWU7XG4gIH1cblxuICB2YXIgbW9kaWZpZXJNYXAgPSB7XG4gICAgICAxNjonc2hpZnRLZXknLFxuICAgICAgMTg6J2FsdEtleScsXG4gICAgICAxNzonY3RybEtleScsXG4gICAgICA5MTonbWV0YUtleSdcbiAgfTtcbiAgZnVuY3Rpb24gdXBkYXRlTW9kaWZpZXJLZXkoZXZlbnQpIHtcbiAgICAgIGZvcihrIGluIF9tb2RzKSBfbW9kc1trXSA9IGV2ZW50W21vZGlmaWVyTWFwW2tdXTtcbiAgfTtcblxuICAvLyBoYW5kbGUga2V5ZG93biBldmVudFxuICBmdW5jdGlvbiBkaXNwYXRjaChldmVudCkge1xuICAgIHZhciBrZXksIGhhbmRsZXIsIGssIGksIG1vZGlmaWVyc01hdGNoLCBzY29wZTtcbiAgICBrZXkgPSBldmVudC5rZXlDb2RlO1xuXG4gICAgaWYgKGluZGV4KF9kb3duS2V5cywga2V5KSA9PSAtMSkge1xuICAgICAgICBfZG93bktleXMucHVzaChrZXkpO1xuICAgIH1cblxuICAgIC8vIGlmIGEgbW9kaWZpZXIga2V5LCBzZXQgdGhlIGtleS48bW9kaWZpZXJrZXluYW1lPiBwcm9wZXJ0eSB0byB0cnVlIGFuZCByZXR1cm5cbiAgICBpZihrZXkgPT0gOTMgfHwga2V5ID09IDIyNCkga2V5ID0gOTE7IC8vIHJpZ2h0IGNvbW1hbmQgb24gd2Via2l0LCBjb21tYW5kIG9uIEdlY2tvXG4gICAgaWYoa2V5IGluIF9tb2RzKSB7XG4gICAgICBfbW9kc1trZXldID0gdHJ1ZTtcbiAgICAgIC8vICdhc3NpZ25LZXknIGZyb20gaW5zaWRlIHRoaXMgY2xvc3VyZSBpcyBleHBvcnRlZCB0byB3aW5kb3cua2V5XG4gICAgICBmb3IoayBpbiBfTU9ESUZJRVJTKSBpZihfTU9ESUZJRVJTW2tdID09IGtleSkgYXNzaWduS2V5W2tdID0gdHJ1ZTtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgdXBkYXRlTW9kaWZpZXJLZXkoZXZlbnQpO1xuXG4gICAgLy8gc2VlIGlmIHdlIG5lZWQgdG8gaWdub3JlIHRoZSBrZXlwcmVzcyAoZmlsdGVyKCkgY2FuIGNhbiBiZSBvdmVycmlkZGVuKVxuICAgIC8vIGJ5IGRlZmF1bHQgaWdub3JlIGtleSBwcmVzc2VzIGlmIGEgc2VsZWN0LCB0ZXh0YXJlYSwgb3IgaW5wdXQgaXMgZm9jdXNlZFxuICAgIGlmKCFhc3NpZ25LZXkuZmlsdGVyLmNhbGwodGhpcywgZXZlbnQpKSByZXR1cm47XG5cbiAgICAvLyBhYm9ydCBpZiBubyBwb3RlbnRpYWxseSBtYXRjaGluZyBzaG9ydGN1dHMgZm91bmRcbiAgICBpZiAoIShrZXkgaW4gX2hhbmRsZXJzKSkgcmV0dXJuO1xuXG4gICAgc2NvcGUgPSBnZXRTY29wZSgpO1xuXG4gICAgLy8gZm9yIGVhY2ggcG90ZW50aWFsIHNob3J0Y3V0XG4gICAgZm9yIChpID0gMDsgaSA8IF9oYW5kbGVyc1trZXldLmxlbmd0aDsgaSsrKSB7XG4gICAgICBoYW5kbGVyID0gX2hhbmRsZXJzW2tleV1baV07XG5cbiAgICAgIC8vIHNlZSBpZiBpdCdzIGluIHRoZSBjdXJyZW50IHNjb3BlXG4gICAgICBpZihoYW5kbGVyLnNjb3BlID09IHNjb3BlIHx8IGhhbmRsZXIuc2NvcGUgPT0gJ2FsbCcpe1xuICAgICAgICAvLyBjaGVjayBpZiBtb2RpZmllcnMgbWF0Y2ggaWYgYW55XG4gICAgICAgIG1vZGlmaWVyc01hdGNoID0gaGFuZGxlci5tb2RzLmxlbmd0aCA+IDA7XG4gICAgICAgIGZvcihrIGluIF9tb2RzKVxuICAgICAgICAgIGlmKCghX21vZHNba10gJiYgaW5kZXgoaGFuZGxlci5tb2RzLCAraykgPiAtMSkgfHxcbiAgICAgICAgICAgIChfbW9kc1trXSAmJiBpbmRleChoYW5kbGVyLm1vZHMsICtrKSA9PSAtMSkpIG1vZGlmaWVyc01hdGNoID0gZmFsc2U7XG4gICAgICAgIC8vIGNhbGwgdGhlIGhhbmRsZXIgYW5kIHN0b3AgdGhlIGV2ZW50IGlmIG5lY2Nlc3NhcnlcbiAgICAgICAgaWYoKGhhbmRsZXIubW9kcy5sZW5ndGggPT0gMCAmJiAhX21vZHNbMTZdICYmICFfbW9kc1sxOF0gJiYgIV9tb2RzWzE3XSAmJiAhX21vZHNbOTFdKSB8fCBtb2RpZmllcnNNYXRjaCl7XG4gICAgICAgICAgaWYoaGFuZGxlci5tZXRob2QoZXZlbnQsIGhhbmRsZXIpPT09ZmFsc2Upe1xuICAgICAgICAgICAgaWYoZXZlbnQucHJldmVudERlZmF1bHQpIGV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG4gICAgICAgICAgICAgIGVsc2UgZXZlbnQucmV0dXJuVmFsdWUgPSBmYWxzZTtcbiAgICAgICAgICAgIGlmKGV2ZW50LnN0b3BQcm9wYWdhdGlvbikgZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG4gICAgICAgICAgICBpZihldmVudC5jYW5jZWxCdWJibGUpIGV2ZW50LmNhbmNlbEJ1YmJsZSA9IHRydWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9O1xuXG4gIC8vIHVuc2V0IG1vZGlmaWVyIGtleXMgb24ga2V5dXBcbiAgZnVuY3Rpb24gY2xlYXJNb2RpZmllcihldmVudCl7XG4gICAgdmFyIGtleSA9IGV2ZW50LmtleUNvZGUsIGssXG4gICAgICAgIGkgPSBpbmRleChfZG93bktleXMsIGtleSk7XG5cbiAgICAvLyByZW1vdmUga2V5IGZyb20gX2Rvd25LZXlzXG4gICAgaWYgKGkgPj0gMCkge1xuICAgICAgICBfZG93bktleXMuc3BsaWNlKGksIDEpO1xuICAgIH1cblxuICAgIGlmKGtleSA9PSA5MyB8fCBrZXkgPT0gMjI0KSBrZXkgPSA5MTtcbiAgICBpZihrZXkgaW4gX21vZHMpIHtcbiAgICAgIF9tb2RzW2tleV0gPSBmYWxzZTtcbiAgICAgIGZvcihrIGluIF9NT0RJRklFUlMpIGlmKF9NT0RJRklFUlNba10gPT0ga2V5KSBhc3NpZ25LZXlba10gPSBmYWxzZTtcbiAgICB9XG4gIH07XG5cbiAgZnVuY3Rpb24gcmVzZXRNb2RpZmllcnMoKSB7XG4gICAgZm9yKGsgaW4gX21vZHMpIF9tb2RzW2tdID0gZmFsc2U7XG4gICAgZm9yKGsgaW4gX01PRElGSUVSUykgYXNzaWduS2V5W2tdID0gZmFsc2U7XG4gIH07XG5cbiAgLy8gcGFyc2UgYW5kIGFzc2lnbiBzaG9ydGN1dFxuICBmdW5jdGlvbiBhc3NpZ25LZXkoa2V5LCBzY29wZSwgbWV0aG9kKXtcbiAgICB2YXIga2V5cywgbW9kcztcbiAgICBrZXlzID0gZ2V0S2V5cyhrZXkpO1xuICAgIGlmIChtZXRob2QgPT09IHVuZGVmaW5lZCkge1xuICAgICAgbWV0aG9kID0gc2NvcGU7XG4gICAgICBzY29wZSA9ICdhbGwnO1xuICAgIH1cblxuICAgIC8vIGZvciBlYWNoIHNob3J0Y3V0XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBrZXlzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAvLyBzZXQgbW9kaWZpZXIga2V5cyBpZiBhbnlcbiAgICAgIG1vZHMgPSBbXTtcbiAgICAgIGtleSA9IGtleXNbaV0uc3BsaXQoJysnKTtcbiAgICAgIGlmIChrZXkubGVuZ3RoID4gMSl7XG4gICAgICAgIG1vZHMgPSBnZXRNb2RzKGtleSk7XG4gICAgICAgIGtleSA9IFtrZXlba2V5Lmxlbmd0aC0xXV07XG4gICAgICB9XG4gICAgICAvLyBjb252ZXJ0IHRvIGtleWNvZGUgYW5kLi4uXG4gICAgICBrZXkgPSBrZXlbMF1cbiAgICAgIGtleSA9IGNvZGUoa2V5KTtcbiAgICAgIC8vIC4uLnN0b3JlIGhhbmRsZXJcbiAgICAgIGlmICghKGtleSBpbiBfaGFuZGxlcnMpKSBfaGFuZGxlcnNba2V5XSA9IFtdO1xuICAgICAgX2hhbmRsZXJzW2tleV0ucHVzaCh7IHNob3J0Y3V0OiBrZXlzW2ldLCBzY29wZTogc2NvcGUsIG1ldGhvZDogbWV0aG9kLCBrZXk6IGtleXNbaV0sIG1vZHM6IG1vZHMgfSk7XG4gICAgfVxuICB9O1xuXG4gIC8vIHVuYmluZCBhbGwgaGFuZGxlcnMgZm9yIGdpdmVuIGtleSBpbiBjdXJyZW50IHNjb3BlXG4gIGZ1bmN0aW9uIHVuYmluZEtleShrZXksIHNjb3BlKSB7XG4gICAgdmFyIG11bHRpcGxlS2V5cywga2V5cyxcbiAgICAgIG1vZHMgPSBbXSxcbiAgICAgIGksIGosIG9iajtcblxuICAgIG11bHRpcGxlS2V5cyA9IGdldEtleXMoa2V5KTtcblxuICAgIGZvciAoaiA9IDA7IGogPCBtdWx0aXBsZUtleXMubGVuZ3RoOyBqKyspIHtcbiAgICAgIGtleXMgPSBtdWx0aXBsZUtleXNbal0uc3BsaXQoJysnKTtcblxuICAgICAgaWYgKGtleXMubGVuZ3RoID4gMSkge1xuICAgICAgICBtb2RzID0gZ2V0TW9kcyhrZXlzKTtcbiAgICAgICAga2V5ID0ga2V5c1trZXlzLmxlbmd0aCAtIDFdO1xuICAgICAgfVxuXG4gICAgICBrZXkgPSBjb2RlKGtleSk7XG5cbiAgICAgIGlmIChzY29wZSA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHNjb3BlID0gZ2V0U2NvcGUoKTtcbiAgICAgIH1cbiAgICAgIGlmICghX2hhbmRsZXJzW2tleV0pIHtcbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgZm9yIChpID0gMDsgaSA8IF9oYW5kbGVyc1trZXldLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIG9iaiA9IF9oYW5kbGVyc1trZXldW2ldO1xuICAgICAgICAvLyBvbmx5IGNsZWFyIGhhbmRsZXJzIGlmIGNvcnJlY3Qgc2NvcGUgYW5kIG1vZHMgbWF0Y2hcbiAgICAgICAgaWYgKG9iai5zY29wZSA9PT0gc2NvcGUgJiYgY29tcGFyZUFycmF5KG9iai5tb2RzLCBtb2RzKSkge1xuICAgICAgICAgIF9oYW5kbGVyc1trZXldW2ldID0ge307XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gIH07XG5cbiAgLy8gUmV0dXJucyB0cnVlIGlmIHRoZSBrZXkgd2l0aCBjb2RlICdrZXlDb2RlJyBpcyBjdXJyZW50bHkgZG93blxuICAvLyBDb252ZXJ0cyBzdHJpbmdzIGludG8ga2V5IGNvZGVzLlxuICBmdW5jdGlvbiBpc1ByZXNzZWQoa2V5Q29kZSkge1xuICAgICAgaWYgKHR5cGVvZihrZXlDb2RlKT09J3N0cmluZycpIHtcbiAgICAgICAga2V5Q29kZSA9IGNvZGUoa2V5Q29kZSk7XG4gICAgICB9XG4gICAgICByZXR1cm4gaW5kZXgoX2Rvd25LZXlzLCBrZXlDb2RlKSAhPSAtMTtcbiAgfVxuXG4gIGZ1bmN0aW9uIGdldFByZXNzZWRLZXlDb2RlcygpIHtcbiAgICAgIHJldHVybiBfZG93bktleXMuc2xpY2UoMCk7XG4gIH1cblxuICBmdW5jdGlvbiBmaWx0ZXIoZXZlbnQpe1xuICAgIHZhciB0YWdOYW1lID0gKGV2ZW50LnRhcmdldCB8fCBldmVudC5zcmNFbGVtZW50KS50YWdOYW1lO1xuICAgIC8vIGlnbm9yZSBrZXlwcmVzc2VkIGluIGFueSBlbGVtZW50cyB0aGF0IHN1cHBvcnQga2V5Ym9hcmQgZGF0YSBpbnB1dFxuICAgIHJldHVybiAhKHRhZ05hbWUgPT0gJ0lOUFVUJyB8fCB0YWdOYW1lID09ICdTRUxFQ1QnIHx8IHRhZ05hbWUgPT0gJ1RFWFRBUkVBJyk7XG4gIH1cblxuICAvLyBpbml0aWFsaXplIGtleS48bW9kaWZpZXI+IHRvIGZhbHNlXG4gIGZvcihrIGluIF9NT0RJRklFUlMpIGFzc2lnbktleVtrXSA9IGZhbHNlO1xuXG4gIC8vIHNldCBjdXJyZW50IHNjb3BlIChkZWZhdWx0ICdhbGwnKVxuICBmdW5jdGlvbiBzZXRTY29wZShzY29wZSl7IF9zY29wZSA9IHNjb3BlIHx8ICdhbGwnIH07XG4gIGZ1bmN0aW9uIGdldFNjb3BlKCl7IHJldHVybiBfc2NvcGUgfHwgJ2FsbCcgfTtcblxuICAvLyBkZWxldGUgYWxsIGhhbmRsZXJzIGZvciBhIGdpdmVuIHNjb3BlXG4gIGZ1bmN0aW9uIGRlbGV0ZVNjb3BlKHNjb3BlKXtcbiAgICB2YXIga2V5LCBoYW5kbGVycywgaTtcblxuICAgIGZvciAoa2V5IGluIF9oYW5kbGVycykge1xuICAgICAgaGFuZGxlcnMgPSBfaGFuZGxlcnNba2V5XTtcbiAgICAgIGZvciAoaSA9IDA7IGkgPCBoYW5kbGVycy5sZW5ndGg7ICkge1xuICAgICAgICBpZiAoaGFuZGxlcnNbaV0uc2NvcGUgPT09IHNjb3BlKSBoYW5kbGVycy5zcGxpY2UoaSwgMSk7XG4gICAgICAgIGVsc2UgaSsrO1xuICAgICAgfVxuICAgIH1cbiAgfTtcblxuICAvLyBhYnN0cmFjdCBrZXkgbG9naWMgZm9yIGFzc2lnbiBhbmQgdW5hc3NpZ25cbiAgZnVuY3Rpb24gZ2V0S2V5cyhrZXkpIHtcbiAgICB2YXIga2V5cztcbiAgICBrZXkgPSBrZXkucmVwbGFjZSgvXFxzL2csICcnKTtcbiAgICBrZXlzID0ga2V5LnNwbGl0KCcsJyk7XG4gICAgaWYgKChrZXlzW2tleXMubGVuZ3RoIC0gMV0pID09ICcnKSB7XG4gICAgICBrZXlzW2tleXMubGVuZ3RoIC0gMl0gKz0gJywnO1xuICAgIH1cbiAgICByZXR1cm4ga2V5cztcbiAgfVxuXG4gIC8vIGFic3RyYWN0IG1vZHMgbG9naWMgZm9yIGFzc2lnbiBhbmQgdW5hc3NpZ25cbiAgZnVuY3Rpb24gZ2V0TW9kcyhrZXkpIHtcbiAgICB2YXIgbW9kcyA9IGtleS5zbGljZSgwLCBrZXkubGVuZ3RoIC0gMSk7XG4gICAgZm9yICh2YXIgbWkgPSAwOyBtaSA8IG1vZHMubGVuZ3RoOyBtaSsrKVxuICAgIG1vZHNbbWldID0gX01PRElGSUVSU1ttb2RzW21pXV07XG4gICAgcmV0dXJuIG1vZHM7XG4gIH1cblxuICAvLyBjcm9zcy1icm93c2VyIGV2ZW50c1xuICBmdW5jdGlvbiBhZGRFdmVudChvYmplY3QsIGV2ZW50LCBtZXRob2QpIHtcbiAgICBpZiAob2JqZWN0LmFkZEV2ZW50TGlzdGVuZXIpXG4gICAgICBvYmplY3QuYWRkRXZlbnRMaXN0ZW5lcihldmVudCwgbWV0aG9kLCBmYWxzZSk7XG4gICAgZWxzZSBpZihvYmplY3QuYXR0YWNoRXZlbnQpXG4gICAgICBvYmplY3QuYXR0YWNoRXZlbnQoJ29uJytldmVudCwgZnVuY3Rpb24oKXsgbWV0aG9kKHdpbmRvdy5ldmVudCkgfSk7XG4gIH07XG5cbiAgLy8gc2V0IHRoZSBoYW5kbGVycyBnbG9iYWxseSBvbiBkb2N1bWVudFxuICBhZGRFdmVudChkb2N1bWVudCwgJ2tleWRvd24nLCBmdW5jdGlvbihldmVudCkgeyBkaXNwYXRjaChldmVudCkgfSk7IC8vIFBhc3NpbmcgX3Njb3BlIHRvIGEgY2FsbGJhY2sgdG8gZW5zdXJlIGl0IHJlbWFpbnMgdGhlIHNhbWUgYnkgZXhlY3V0aW9uLiBGaXhlcyAjNDhcbiAgYWRkRXZlbnQoZG9jdW1lbnQsICdrZXl1cCcsIGNsZWFyTW9kaWZpZXIpO1xuXG4gIC8vIHJlc2V0IG1vZGlmaWVycyB0byBmYWxzZSB3aGVuZXZlciB0aGUgd2luZG93IGlzIChyZSlmb2N1c2VkLlxuICBhZGRFdmVudCh3aW5kb3csICdmb2N1cycsIHJlc2V0TW9kaWZpZXJzKTtcblxuICAvLyBzdG9yZSBwcmV2aW91c2x5IGRlZmluZWQga2V5XG4gIHZhciBwcmV2aW91c0tleSA9IGdsb2JhbC5rZXk7XG5cbiAgLy8gcmVzdG9yZSBwcmV2aW91c2x5IGRlZmluZWQga2V5IGFuZCByZXR1cm4gcmVmZXJlbmNlIHRvIG91ciBrZXkgb2JqZWN0XG4gIGZ1bmN0aW9uIG5vQ29uZmxpY3QoKSB7XG4gICAgdmFyIGsgPSBnbG9iYWwua2V5O1xuICAgIGdsb2JhbC5rZXkgPSBwcmV2aW91c0tleTtcbiAgICByZXR1cm4gaztcbiAgfVxuXG4gIC8vIHNldCB3aW5kb3cua2V5IGFuZCB3aW5kb3cua2V5LnNldC9nZXQvZGVsZXRlU2NvcGUsIGFuZCB0aGUgZGVmYXVsdCBmaWx0ZXJcbiAgZ2xvYmFsLmtleSA9IGFzc2lnbktleTtcbiAgZ2xvYmFsLmtleS5zZXRTY29wZSA9IHNldFNjb3BlO1xuICBnbG9iYWwua2V5LmdldFNjb3BlID0gZ2V0U2NvcGU7XG4gIGdsb2JhbC5rZXkuZGVsZXRlU2NvcGUgPSBkZWxldGVTY29wZTtcbiAgZ2xvYmFsLmtleS5maWx0ZXIgPSBmaWx0ZXI7XG4gIGdsb2JhbC5rZXkuaXNQcmVzc2VkID0gaXNQcmVzc2VkO1xuICBnbG9iYWwua2V5LmdldFByZXNzZWRLZXlDb2RlcyA9IGdldFByZXNzZWRLZXlDb2RlcztcbiAgZ2xvYmFsLmtleS5ub0NvbmZsaWN0ID0gbm9Db25mbGljdDtcbiAgZ2xvYmFsLmtleS51bmJpbmQgPSB1bmJpbmRLZXk7XG5cbiAgaWYodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcpIG1vZHVsZS5leHBvcnRzID0gYXNzaWduS2V5O1xuXG59KSh0aGlzKTtcbiIsIi8qXG5QTm90aWZ5IDMuMC4wIHNjaWFjdGl2ZS5jb20vcG5vdGlmeS9cbihDKSAyMDE1IEh1bnRlciBQZXJyaW47IEdvb2dsZSwgSW5jLlxubGljZW5zZSBBcGFjaGUtMi4wXG4qL1xuKGZ1bmN0aW9uKGIsayl7XCJmdW5jdGlvblwiPT09dHlwZW9mIGRlZmluZSYmZGVmaW5lLmFtZD9kZWZpbmUoXCJwbm90aWZ5XCIsW1wianF1ZXJ5XCJdLGZ1bmN0aW9uKHEpe3JldHVybiBrKHEsYil9KTpcIm9iamVjdFwiPT09dHlwZW9mIGV4cG9ydHMmJlwidW5kZWZpbmVkXCIhPT10eXBlb2YgbW9kdWxlP21vZHVsZS5leHBvcnRzPWsoKHdpbmRvdy4kKSxnbG9iYWx8fGIpOmIuUE5vdGlmeT1rKGIualF1ZXJ5LGIpfSkodGhpcyxmdW5jdGlvbihiLGspe3ZhciBxPWZ1bmN0aW9uKGwpe3ZhciBrPXtkaXIxOlwiZG93blwiLGRpcjI6XCJsZWZ0XCIscHVzaDpcImJvdHRvbVwiLHNwYWNpbmcxOjM2LHNwYWNpbmcyOjM2LGNvbnRleHQ6YihcImJvZHlcIiksbW9kYWw6ITF9LGcsaCxuPWIobCkscj1mdW5jdGlvbigpe2g9YihcImJvZHlcIik7ZC5wcm90b3R5cGUub3B0aW9ucy5zdGFjay5jb250ZXh0PWg7bj1iKGwpO24uYmluZChcInJlc2l6ZVwiLGZ1bmN0aW9uKCl7ZyYmY2xlYXJUaW1lb3V0KGcpO2c9c2V0VGltZW91dChmdW5jdGlvbigpe2QucG9zaXRpb25BbGwoITApfSxcbjEwKX0pfSxzPWZ1bmN0aW9uKGMpe3ZhciBhPWIoXCI8ZGl2IC8+XCIse1wiY2xhc3NcIjpcInVpLXBub3RpZnktbW9kYWwtb3ZlcmxheVwifSk7YS5wcmVwZW5kVG8oYy5jb250ZXh0KTtjLm92ZXJsYXlfY2xvc2UmJmEuY2xpY2soZnVuY3Rpb24oKXtkLnJlbW92ZVN0YWNrKGMpfSk7cmV0dXJuIGF9LGQ9ZnVuY3Rpb24oYyl7dGhpcy5wYXJzZU9wdGlvbnMoYyk7dGhpcy5pbml0KCl9O2IuZXh0ZW5kKGQucHJvdG90eXBlLHt2ZXJzaW9uOlwiMy4wLjBcIixvcHRpb25zOnt0aXRsZTohMSx0aXRsZV9lc2NhcGU6ITEsdGV4dDohMSx0ZXh0X2VzY2FwZTohMSxzdHlsaW5nOlwiYnJpZ2h0dGhlbWVcIixhZGRjbGFzczpcIlwiLGNvcm5lcmNsYXNzOlwiXCIsYXV0b19kaXNwbGF5OiEwLHdpZHRoOlwiMzAwcHhcIixtaW5faGVpZ2h0OlwiMTZweFwiLHR5cGU6XCJub3RpY2VcIixpY29uOiEwLGFuaW1hdGlvbjpcImZhZGVcIixhbmltYXRlX3NwZWVkOlwibm9ybWFsXCIsc2hhZG93OiEwLGhpZGU6ITAsZGVsYXk6OEUzLG1vdXNlX3Jlc2V0OiEwLFxucmVtb3ZlOiEwLGluc2VydF9icnM6ITAsZGVzdHJveTohMCxzdGFjazprfSxtb2R1bGVzOnt9LHJ1bk1vZHVsZXM6ZnVuY3Rpb24oYyxhKXt2YXIgcCxiO2ZvcihiIGluIHRoaXMubW9kdWxlcylwPVwib2JqZWN0XCI9PT10eXBlb2YgYSYmYiBpbiBhP2FbYl06YSxcImZ1bmN0aW9uXCI9PT10eXBlb2YgdGhpcy5tb2R1bGVzW2JdW2NdJiYodGhpcy5tb2R1bGVzW2JdLm5vdGljZT10aGlzLHRoaXMubW9kdWxlc1tiXS5vcHRpb25zPVwib2JqZWN0XCI9PT10eXBlb2YgdGhpcy5vcHRpb25zW2JdP3RoaXMub3B0aW9uc1tiXTp7fSx0aGlzLm1vZHVsZXNbYl1bY10odGhpcyxcIm9iamVjdFwiPT09dHlwZW9mIHRoaXMub3B0aW9uc1tiXT90aGlzLm9wdGlvbnNbYl06e30scCkpfSxzdGF0ZTpcImluaXRpYWxpemluZ1wiLHRpbWVyOm51bGwsYW5pbVRpbWVyOm51bGwsc3R5bGVzOm51bGwsZWxlbTpudWxsLGNvbnRhaW5lcjpudWxsLHRpdGxlX2NvbnRhaW5lcjpudWxsLHRleHRfY29udGFpbmVyOm51bGwsYW5pbWF0aW5nOiExLFxudGltZXJIaWRlOiExLGluaXQ6ZnVuY3Rpb24oKXt2YXIgYz10aGlzO3RoaXMubW9kdWxlcz17fTtiLmV4dGVuZCghMCx0aGlzLm1vZHVsZXMsZC5wcm90b3R5cGUubW9kdWxlcyk7dGhpcy5zdHlsZXM9XCJvYmplY3RcIj09PXR5cGVvZiB0aGlzLm9wdGlvbnMuc3R5bGluZz90aGlzLm9wdGlvbnMuc3R5bGluZzpkLnN0eWxpbmdbdGhpcy5vcHRpb25zLnN0eWxpbmddO3RoaXMuZWxlbT1iKFwiPGRpdiAvPlwiLHtcImNsYXNzXCI6XCJ1aS1wbm90aWZ5IFwiK3RoaXMub3B0aW9ucy5hZGRjbGFzcyxjc3M6e2Rpc3BsYXk6XCJub25lXCJ9LFwiYXJpYS1saXZlXCI6XCJhc3NlcnRpdmVcIixcImFyaWEtcm9sZVwiOlwiYWxlcnRkaWFsb2dcIixtb3VzZWVudGVyOmZ1bmN0aW9uKGEpe2lmKGMub3B0aW9ucy5tb3VzZV9yZXNldCYmXCJvdXRcIj09PWMuYW5pbWF0aW5nKXtpZighYy50aW1lckhpZGUpcmV0dXJuO2MuY2FuY2VsUmVtb3ZlKCl9Yy5vcHRpb25zLmhpZGUmJmMub3B0aW9ucy5tb3VzZV9yZXNldCYmYy5jYW5jZWxSZW1vdmUoKX0sXG5tb3VzZWxlYXZlOmZ1bmN0aW9uKGEpe2Mub3B0aW9ucy5oaWRlJiZjLm9wdGlvbnMubW91c2VfcmVzZXQmJlwib3V0XCIhPT1jLmFuaW1hdGluZyYmYy5xdWV1ZVJlbW92ZSgpO2QucG9zaXRpb25BbGwoKX19KTtcImZhZGVcIj09PXRoaXMub3B0aW9ucy5hbmltYXRpb24mJnRoaXMuZWxlbS5hZGRDbGFzcyhcInVpLXBub3RpZnktZmFkZS1cIit0aGlzLm9wdGlvbnMuYW5pbWF0ZV9zcGVlZCk7dGhpcy5jb250YWluZXI9YihcIjxkaXYgLz5cIix7XCJjbGFzc1wiOnRoaXMuc3R5bGVzLmNvbnRhaW5lcitcIiB1aS1wbm90aWZ5LWNvbnRhaW5lciBcIisoXCJlcnJvclwiPT09dGhpcy5vcHRpb25zLnR5cGU/dGhpcy5zdHlsZXMuZXJyb3I6XCJpbmZvXCI9PT10aGlzLm9wdGlvbnMudHlwZT90aGlzLnN0eWxlcy5pbmZvOlwic3VjY2Vzc1wiPT09dGhpcy5vcHRpb25zLnR5cGU/dGhpcy5zdHlsZXMuc3VjY2Vzczp0aGlzLnN0eWxlcy5ub3RpY2UpLHJvbGU6XCJhbGVydFwifSkuYXBwZW5kVG8odGhpcy5lbGVtKTtcIlwiIT09XG50aGlzLm9wdGlvbnMuY29ybmVyY2xhc3MmJnRoaXMuY29udGFpbmVyLnJlbW92ZUNsYXNzKFwidWktY29ybmVyLWFsbFwiKS5hZGRDbGFzcyh0aGlzLm9wdGlvbnMuY29ybmVyY2xhc3MpO3RoaXMub3B0aW9ucy5zaGFkb3cmJnRoaXMuY29udGFpbmVyLmFkZENsYXNzKFwidWktcG5vdGlmeS1zaGFkb3dcIik7ITEhPT10aGlzLm9wdGlvbnMuaWNvbiYmYihcIjxkaXYgLz5cIix7XCJjbGFzc1wiOlwidWktcG5vdGlmeS1pY29uXCJ9KS5hcHBlbmQoYihcIjxzcGFuIC8+XCIse1wiY2xhc3NcIjohMD09PXRoaXMub3B0aW9ucy5pY29uP1wiZXJyb3JcIj09PXRoaXMub3B0aW9ucy50eXBlP3RoaXMuc3R5bGVzLmVycm9yX2ljb246XCJpbmZvXCI9PT10aGlzLm9wdGlvbnMudHlwZT90aGlzLnN0eWxlcy5pbmZvX2ljb246XCJzdWNjZXNzXCI9PT10aGlzLm9wdGlvbnMudHlwZT90aGlzLnN0eWxlcy5zdWNjZXNzX2ljb246dGhpcy5zdHlsZXMubm90aWNlX2ljb246dGhpcy5vcHRpb25zLmljb259KSkucHJlcGVuZFRvKHRoaXMuY29udGFpbmVyKTtcbnRoaXMudGl0bGVfY29udGFpbmVyPWIoXCI8aDQgLz5cIix7XCJjbGFzc1wiOlwidWktcG5vdGlmeS10aXRsZVwifSkuYXBwZW5kVG8odGhpcy5jb250YWluZXIpOyExPT09dGhpcy5vcHRpb25zLnRpdGxlP3RoaXMudGl0bGVfY29udGFpbmVyLmhpZGUoKTp0aGlzLm9wdGlvbnMudGl0bGVfZXNjYXBlP3RoaXMudGl0bGVfY29udGFpbmVyLnRleHQodGhpcy5vcHRpb25zLnRpdGxlKTp0aGlzLnRpdGxlX2NvbnRhaW5lci5odG1sKHRoaXMub3B0aW9ucy50aXRsZSk7dGhpcy50ZXh0X2NvbnRhaW5lcj1iKFwiPGRpdiAvPlwiLHtcImNsYXNzXCI6XCJ1aS1wbm90aWZ5LXRleHRcIixcImFyaWEtcm9sZVwiOlwiYWxlcnRcIn0pLmFwcGVuZFRvKHRoaXMuY29udGFpbmVyKTshMT09PXRoaXMub3B0aW9ucy50ZXh0P3RoaXMudGV4dF9jb250YWluZXIuaGlkZSgpOnRoaXMub3B0aW9ucy50ZXh0X2VzY2FwZT90aGlzLnRleHRfY29udGFpbmVyLnRleHQodGhpcy5vcHRpb25zLnRleHQpOnRoaXMudGV4dF9jb250YWluZXIuaHRtbCh0aGlzLm9wdGlvbnMuaW5zZXJ0X2Jycz9cblN0cmluZyh0aGlzLm9wdGlvbnMudGV4dCkucmVwbGFjZSgvXFxuL2csXCI8YnIgLz5cIik6dGhpcy5vcHRpb25zLnRleHQpO1wic3RyaW5nXCI9PT10eXBlb2YgdGhpcy5vcHRpb25zLndpZHRoJiZ0aGlzLmVsZW0uY3NzKFwid2lkdGhcIix0aGlzLm9wdGlvbnMud2lkdGgpO1wic3RyaW5nXCI9PT10eXBlb2YgdGhpcy5vcHRpb25zLm1pbl9oZWlnaHQmJnRoaXMuY29udGFpbmVyLmNzcyhcIm1pbi1oZWlnaHRcIix0aGlzLm9wdGlvbnMubWluX2hlaWdodCk7ZC5ub3RpY2VzPVwidG9wXCI9PT10aGlzLm9wdGlvbnMuc3RhY2sucHVzaD9iLm1lcmdlKFt0aGlzXSxkLm5vdGljZXMpOmIubWVyZ2UoZC5ub3RpY2VzLFt0aGlzXSk7XCJ0b3BcIj09PXRoaXMub3B0aW9ucy5zdGFjay5wdXNoJiZ0aGlzLnF1ZXVlUG9zaXRpb24oITEsMSk7dGhpcy5vcHRpb25zLnN0YWNrLmFuaW1hdGlvbj0hMTt0aGlzLnJ1bk1vZHVsZXMoXCJpbml0XCIpO3RoaXMub3B0aW9ucy5hdXRvX2Rpc3BsYXkmJnRoaXMub3BlbigpO3JldHVybiB0aGlzfSxcbnVwZGF0ZTpmdW5jdGlvbihjKXt2YXIgYT10aGlzLm9wdGlvbnM7dGhpcy5wYXJzZU9wdGlvbnMoYSxjKTt0aGlzLmVsZW0ucmVtb3ZlQ2xhc3MoXCJ1aS1wbm90aWZ5LWZhZGUtc2xvdyB1aS1wbm90aWZ5LWZhZGUtbm9ybWFsIHVpLXBub3RpZnktZmFkZS1mYXN0XCIpO1wiZmFkZVwiPT09dGhpcy5vcHRpb25zLmFuaW1hdGlvbiYmdGhpcy5lbGVtLmFkZENsYXNzKFwidWktcG5vdGlmeS1mYWRlLVwiK3RoaXMub3B0aW9ucy5hbmltYXRlX3NwZWVkKTt0aGlzLm9wdGlvbnMuY29ybmVyY2xhc3MhPT1hLmNvcm5lcmNsYXNzJiZ0aGlzLmNvbnRhaW5lci5yZW1vdmVDbGFzcyhcInVpLWNvcm5lci1hbGwgXCIrYS5jb3JuZXJjbGFzcykuYWRkQ2xhc3ModGhpcy5vcHRpb25zLmNvcm5lcmNsYXNzKTt0aGlzLm9wdGlvbnMuc2hhZG93IT09YS5zaGFkb3cmJih0aGlzLm9wdGlvbnMuc2hhZG93P3RoaXMuY29udGFpbmVyLmFkZENsYXNzKFwidWktcG5vdGlmeS1zaGFkb3dcIik6dGhpcy5jb250YWluZXIucmVtb3ZlQ2xhc3MoXCJ1aS1wbm90aWZ5LXNoYWRvd1wiKSk7XG4hMT09PXRoaXMub3B0aW9ucy5hZGRjbGFzcz90aGlzLmVsZW0ucmVtb3ZlQ2xhc3MoYS5hZGRjbGFzcyk6dGhpcy5vcHRpb25zLmFkZGNsYXNzIT09YS5hZGRjbGFzcyYmdGhpcy5lbGVtLnJlbW92ZUNsYXNzKGEuYWRkY2xhc3MpLmFkZENsYXNzKHRoaXMub3B0aW9ucy5hZGRjbGFzcyk7ITE9PT10aGlzLm9wdGlvbnMudGl0bGU/dGhpcy50aXRsZV9jb250YWluZXIuc2xpZGVVcChcImZhc3RcIik6dGhpcy5vcHRpb25zLnRpdGxlIT09YS50aXRsZSYmKHRoaXMub3B0aW9ucy50aXRsZV9lc2NhcGU/dGhpcy50aXRsZV9jb250YWluZXIudGV4dCh0aGlzLm9wdGlvbnMudGl0bGUpOnRoaXMudGl0bGVfY29udGFpbmVyLmh0bWwodGhpcy5vcHRpb25zLnRpdGxlKSwhMT09PWEudGl0bGUmJnRoaXMudGl0bGVfY29udGFpbmVyLnNsaWRlRG93bigyMDApKTshMT09PXRoaXMub3B0aW9ucy50ZXh0P3RoaXMudGV4dF9jb250YWluZXIuc2xpZGVVcChcImZhc3RcIik6dGhpcy5vcHRpb25zLnRleHQhPT1cbmEudGV4dCYmKHRoaXMub3B0aW9ucy50ZXh0X2VzY2FwZT90aGlzLnRleHRfY29udGFpbmVyLnRleHQodGhpcy5vcHRpb25zLnRleHQpOnRoaXMudGV4dF9jb250YWluZXIuaHRtbCh0aGlzLm9wdGlvbnMuaW5zZXJ0X2Jycz9TdHJpbmcodGhpcy5vcHRpb25zLnRleHQpLnJlcGxhY2UoL1xcbi9nLFwiPGJyIC8+XCIpOnRoaXMub3B0aW9ucy50ZXh0KSwhMT09PWEudGV4dCYmdGhpcy50ZXh0X2NvbnRhaW5lci5zbGlkZURvd24oMjAwKSk7dGhpcy5vcHRpb25zLnR5cGUhPT1hLnR5cGUmJnRoaXMuY29udGFpbmVyLnJlbW92ZUNsYXNzKHRoaXMuc3R5bGVzLmVycm9yK1wiIFwiK3RoaXMuc3R5bGVzLm5vdGljZStcIiBcIit0aGlzLnN0eWxlcy5zdWNjZXNzK1wiIFwiK3RoaXMuc3R5bGVzLmluZm8pLmFkZENsYXNzKFwiZXJyb3JcIj09PXRoaXMub3B0aW9ucy50eXBlP3RoaXMuc3R5bGVzLmVycm9yOlwiaW5mb1wiPT09dGhpcy5vcHRpb25zLnR5cGU/dGhpcy5zdHlsZXMuaW5mbzpcInN1Y2Nlc3NcIj09PXRoaXMub3B0aW9ucy50eXBlP1xudGhpcy5zdHlsZXMuc3VjY2Vzczp0aGlzLnN0eWxlcy5ub3RpY2UpO2lmKHRoaXMub3B0aW9ucy5pY29uIT09YS5pY29ufHwhMD09PXRoaXMub3B0aW9ucy5pY29uJiZ0aGlzLm9wdGlvbnMudHlwZSE9PWEudHlwZSl0aGlzLmNvbnRhaW5lci5maW5kKFwiZGl2LnVpLXBub3RpZnktaWNvblwiKS5yZW1vdmUoKSwhMSE9PXRoaXMub3B0aW9ucy5pY29uJiZiKFwiPGRpdiAvPlwiLHtcImNsYXNzXCI6XCJ1aS1wbm90aWZ5LWljb25cIn0pLmFwcGVuZChiKFwiPHNwYW4gLz5cIix7XCJjbGFzc1wiOiEwPT09dGhpcy5vcHRpb25zLmljb24/XCJlcnJvclwiPT09dGhpcy5vcHRpb25zLnR5cGU/dGhpcy5zdHlsZXMuZXJyb3JfaWNvbjpcImluZm9cIj09PXRoaXMub3B0aW9ucy50eXBlP3RoaXMuc3R5bGVzLmluZm9faWNvbjpcInN1Y2Nlc3NcIj09PXRoaXMub3B0aW9ucy50eXBlP3RoaXMuc3R5bGVzLnN1Y2Nlc3NfaWNvbjp0aGlzLnN0eWxlcy5ub3RpY2VfaWNvbjp0aGlzLm9wdGlvbnMuaWNvbn0pKS5wcmVwZW5kVG8odGhpcy5jb250YWluZXIpO1xudGhpcy5vcHRpb25zLndpZHRoIT09YS53aWR0aCYmdGhpcy5lbGVtLmFuaW1hdGUoe3dpZHRoOnRoaXMub3B0aW9ucy53aWR0aH0pO3RoaXMub3B0aW9ucy5taW5faGVpZ2h0IT09YS5taW5faGVpZ2h0JiZ0aGlzLmNvbnRhaW5lci5hbmltYXRlKHttaW5IZWlnaHQ6dGhpcy5vcHRpb25zLm1pbl9oZWlnaHR9KTt0aGlzLm9wdGlvbnMuaGlkZT9hLmhpZGV8fHRoaXMucXVldWVSZW1vdmUoKTp0aGlzLmNhbmNlbFJlbW92ZSgpO3RoaXMucXVldWVQb3NpdGlvbighMCk7dGhpcy5ydW5Nb2R1bGVzKFwidXBkYXRlXCIsYSk7cmV0dXJuIHRoaXN9LG9wZW46ZnVuY3Rpb24oKXt0aGlzLnN0YXRlPVwib3BlbmluZ1wiO3RoaXMucnVuTW9kdWxlcyhcImJlZm9yZU9wZW5cIik7dmFyIGM9dGhpczt0aGlzLmVsZW0ucGFyZW50KCkubGVuZ3RofHx0aGlzLmVsZW0uYXBwZW5kVG8odGhpcy5vcHRpb25zLnN0YWNrLmNvbnRleHQ/dGhpcy5vcHRpb25zLnN0YWNrLmNvbnRleHQ6aCk7XCJ0b3BcIiE9PXRoaXMub3B0aW9ucy5zdGFjay5wdXNoJiZcbnRoaXMucG9zaXRpb24oITApO3RoaXMuYW5pbWF0ZUluKGZ1bmN0aW9uKCl7Yy5xdWV1ZVBvc2l0aW9uKCEwKTtjLm9wdGlvbnMuaGlkZSYmYy5xdWV1ZVJlbW92ZSgpO2Muc3RhdGU9XCJvcGVuXCI7Yy5ydW5Nb2R1bGVzKFwiYWZ0ZXJPcGVuXCIpfSk7cmV0dXJuIHRoaXN9LHJlbW92ZTpmdW5jdGlvbihjKXt0aGlzLnN0YXRlPVwiY2xvc2luZ1wiO3RoaXMudGltZXJIaWRlPSEhYzt0aGlzLnJ1bk1vZHVsZXMoXCJiZWZvcmVDbG9zZVwiKTt2YXIgYT10aGlzO3RoaXMudGltZXImJihsLmNsZWFyVGltZW91dCh0aGlzLnRpbWVyKSx0aGlzLnRpbWVyPW51bGwpO3RoaXMuYW5pbWF0ZU91dChmdW5jdGlvbigpe2Euc3RhdGU9XCJjbG9zZWRcIjthLnJ1bk1vZHVsZXMoXCJhZnRlckNsb3NlXCIpO2EucXVldWVQb3NpdGlvbighMCk7YS5vcHRpb25zLnJlbW92ZSYmYS5lbGVtLmRldGFjaCgpO2EucnVuTW9kdWxlcyhcImJlZm9yZURlc3Ryb3lcIik7aWYoYS5vcHRpb25zLmRlc3Ryb3kmJm51bGwhPT1kLm5vdGljZXMpe3ZhciBjPVxuYi5pbkFycmF5KGEsZC5ub3RpY2VzKTstMSE9PWMmJmQubm90aWNlcy5zcGxpY2UoYywxKX1hLnJ1bk1vZHVsZXMoXCJhZnRlckRlc3Ryb3lcIil9KTtyZXR1cm4gdGhpc30sZ2V0OmZ1bmN0aW9uKCl7cmV0dXJuIHRoaXMuZWxlbX0scGFyc2VPcHRpb25zOmZ1bmN0aW9uKGMsYSl7dGhpcy5vcHRpb25zPWIuZXh0ZW5kKCEwLHt9LGQucHJvdG90eXBlLm9wdGlvbnMpO3RoaXMub3B0aW9ucy5zdGFjaz1kLnByb3RvdHlwZS5vcHRpb25zLnN0YWNrO2Zvcih2YXIgcD1bYyxhXSxtLGY9MDtmPHAubGVuZ3RoO2YrKyl7bT1wW2ZdO2lmKFwidW5kZWZpbmVkXCI9PT10eXBlb2YgbSlicmVhaztpZihcIm9iamVjdFwiIT09dHlwZW9mIG0pdGhpcy5vcHRpb25zLnRleHQ9bTtlbHNlIGZvcih2YXIgZSBpbiBtKXRoaXMubW9kdWxlc1tlXT9iLmV4dGVuZCghMCx0aGlzLm9wdGlvbnNbZV0sbVtlXSk6dGhpcy5vcHRpb25zW2VdPW1bZV19fSxhbmltYXRlSW46ZnVuY3Rpb24oYyl7dGhpcy5hbmltYXRpbmc9XG5cImluXCI7dmFyIGE9dGhpcztjPWZ1bmN0aW9uKCl7YS5hbmltVGltZXImJmNsZWFyVGltZW91dChhLmFuaW1UaW1lcik7XCJpblwiPT09YS5hbmltYXRpbmcmJihhLmVsZW0uaXMoXCI6dmlzaWJsZVwiKT8odGhpcyYmdGhpcy5jYWxsKCksYS5hbmltYXRpbmc9ITEpOmEuYW5pbVRpbWVyPXNldFRpbWVvdXQoYyw0MCkpfS5iaW5kKGMpO1wiZmFkZVwiPT09dGhpcy5vcHRpb25zLmFuaW1hdGlvbj8odGhpcy5lbGVtLm9uZShcIndlYmtpdFRyYW5zaXRpb25FbmQgbW96VHJhbnNpdGlvbkVuZCBNU1RyYW5zaXRpb25FbmQgb1RyYW5zaXRpb25FbmQgdHJhbnNpdGlvbmVuZFwiLGMpLmFkZENsYXNzKFwidWktcG5vdGlmeS1pblwiKSx0aGlzLmVsZW0uY3NzKFwib3BhY2l0eVwiKSx0aGlzLmVsZW0uYWRkQ2xhc3MoXCJ1aS1wbm90aWZ5LWZhZGUtaW5cIiksdGhpcy5hbmltVGltZXI9c2V0VGltZW91dChjLDY1MCkpOih0aGlzLmVsZW0uYWRkQ2xhc3MoXCJ1aS1wbm90aWZ5LWluXCIpLGMoKSl9LGFuaW1hdGVPdXQ6ZnVuY3Rpb24oYyl7dGhpcy5hbmltYXRpbmc9XG5cIm91dFwiO3ZhciBhPXRoaXM7Yz1mdW5jdGlvbigpe2EuYW5pbVRpbWVyJiZjbGVhclRpbWVvdXQoYS5hbmltVGltZXIpO1wib3V0XCI9PT1hLmFuaW1hdGluZyYmKFwiMFwiIT1hLmVsZW0uY3NzKFwib3BhY2l0eVwiKSYmYS5lbGVtLmlzKFwiOnZpc2libGVcIik/YS5hbmltVGltZXI9c2V0VGltZW91dChjLDQwKTooYS5lbGVtLnJlbW92ZUNsYXNzKFwidWktcG5vdGlmeS1pblwiKSx0aGlzJiZ0aGlzLmNhbGwoKSxhLmFuaW1hdGluZz0hMSkpfS5iaW5kKGMpO1wiZmFkZVwiPT09dGhpcy5vcHRpb25zLmFuaW1hdGlvbj8odGhpcy5lbGVtLm9uZShcIndlYmtpdFRyYW5zaXRpb25FbmQgbW96VHJhbnNpdGlvbkVuZCBNU1RyYW5zaXRpb25FbmQgb1RyYW5zaXRpb25FbmQgdHJhbnNpdGlvbmVuZFwiLGMpLnJlbW92ZUNsYXNzKFwidWktcG5vdGlmeS1mYWRlLWluXCIpLHRoaXMuYW5pbVRpbWVyPXNldFRpbWVvdXQoYyw2NTApKToodGhpcy5lbGVtLnJlbW92ZUNsYXNzKFwidWktcG5vdGlmeS1pblwiKSxjKCkpfSxwb3NpdGlvbjpmdW5jdGlvbihjKXt2YXIgYT1cbnRoaXMub3B0aW9ucy5zdGFjayxiPXRoaXMuZWxlbTtcInVuZGVmaW5lZFwiPT09dHlwZW9mIGEuY29udGV4dCYmKGEuY29udGV4dD1oKTtpZihhKXtcIm51bWJlclwiIT09dHlwZW9mIGEubmV4dHBvczEmJihhLm5leHRwb3MxPWEuZmlyc3Rwb3MxKTtcIm51bWJlclwiIT09dHlwZW9mIGEubmV4dHBvczImJihhLm5leHRwb3MyPWEuZmlyc3Rwb3MyKTtcIm51bWJlclwiIT09dHlwZW9mIGEuYWRkcG9zMiYmKGEuYWRkcG9zMj0wKTt2YXIgZD0hYi5oYXNDbGFzcyhcInVpLXBub3RpZnktaW5cIik7aWYoIWR8fGMpe2EubW9kYWwmJihhLm92ZXJsYXk/YS5vdmVybGF5LnNob3coKTphLm92ZXJsYXk9cyhhKSk7Yi5hZGRDbGFzcyhcInVpLXBub3RpZnktbW92ZVwiKTt2YXIgZjtzd2l0Y2goYS5kaXIxKXtjYXNlIFwiZG93blwiOmY9XCJ0b3BcIjticmVhaztjYXNlIFwidXBcIjpmPVwiYm90dG9tXCI7YnJlYWs7Y2FzZSBcImxlZnRcIjpmPVwicmlnaHRcIjticmVhaztjYXNlIFwicmlnaHRcIjpmPVwibGVmdFwifWM9cGFyc2VJbnQoYi5jc3MoZikucmVwbGFjZSgvKD86XFwuLip8W14wLTkuXSkvZyxcblwiXCIpKTtpc05hTihjKSYmKGM9MCk7XCJ1bmRlZmluZWRcIiE9PXR5cGVvZiBhLmZpcnN0cG9zMXx8ZHx8KGEuZmlyc3Rwb3MxPWMsYS5uZXh0cG9zMT1hLmZpcnN0cG9zMSk7dmFyIGU7c3dpdGNoKGEuZGlyMil7Y2FzZSBcImRvd25cIjplPVwidG9wXCI7YnJlYWs7Y2FzZSBcInVwXCI6ZT1cImJvdHRvbVwiO2JyZWFrO2Nhc2UgXCJsZWZ0XCI6ZT1cInJpZ2h0XCI7YnJlYWs7Y2FzZSBcInJpZ2h0XCI6ZT1cImxlZnRcIn1jPXBhcnNlSW50KGIuY3NzKGUpLnJlcGxhY2UoLyg/OlxcLi4qfFteMC05Ll0pL2csXCJcIikpO2lzTmFOKGMpJiYoYz0wKTtcInVuZGVmaW5lZFwiIT09dHlwZW9mIGEuZmlyc3Rwb3MyfHxkfHwoYS5maXJzdHBvczI9YyxhLm5leHRwb3MyPWEuZmlyc3Rwb3MyKTtpZihcImRvd25cIj09PWEuZGlyMSYmYS5uZXh0cG9zMStiLmhlaWdodCgpPihhLmNvbnRleHQuaXMoaCk/bi5oZWlnaHQoKTphLmNvbnRleHQucHJvcChcInNjcm9sbEhlaWdodFwiKSl8fFwidXBcIj09PWEuZGlyMSYmYS5uZXh0cG9zMStiLmhlaWdodCgpPlxuKGEuY29udGV4dC5pcyhoKT9uLmhlaWdodCgpOmEuY29udGV4dC5wcm9wKFwic2Nyb2xsSGVpZ2h0XCIpKXx8XCJsZWZ0XCI9PT1hLmRpcjEmJmEubmV4dHBvczErYi53aWR0aCgpPihhLmNvbnRleHQuaXMoaCk/bi53aWR0aCgpOmEuY29udGV4dC5wcm9wKFwic2Nyb2xsV2lkdGhcIikpfHxcInJpZ2h0XCI9PT1hLmRpcjEmJmEubmV4dHBvczErYi53aWR0aCgpPihhLmNvbnRleHQuaXMoaCk/bi53aWR0aCgpOmEuY29udGV4dC5wcm9wKFwic2Nyb2xsV2lkdGhcIikpKWEubmV4dHBvczE9YS5maXJzdHBvczEsYS5uZXh0cG9zMis9YS5hZGRwb3MyKyhcInVuZGVmaW5lZFwiPT09dHlwZW9mIGEuc3BhY2luZzI/MjU6YS5zcGFjaW5nMiksYS5hZGRwb3MyPTA7XCJudW1iZXJcIj09PXR5cGVvZiBhLm5leHRwb3MyJiYoYS5hbmltYXRpb24/Yi5jc3MoZSxhLm5leHRwb3MyK1wicHhcIik6KGIucmVtb3ZlQ2xhc3MoXCJ1aS1wbm90aWZ5LW1vdmVcIiksYi5jc3MoZSxhLm5leHRwb3MyK1wicHhcIiksYi5jc3MoZSksYi5hZGRDbGFzcyhcInVpLXBub3RpZnktbW92ZVwiKSkpO1xuc3dpdGNoKGEuZGlyMil7Y2FzZSBcImRvd25cIjpjYXNlIFwidXBcIjpiLm91dGVySGVpZ2h0KCEwKT5hLmFkZHBvczImJihhLmFkZHBvczI9Yi5oZWlnaHQoKSk7YnJlYWs7Y2FzZSBcImxlZnRcIjpjYXNlIFwicmlnaHRcIjpiLm91dGVyV2lkdGgoITApPmEuYWRkcG9zMiYmKGEuYWRkcG9zMj1iLndpZHRoKCkpfVwibnVtYmVyXCI9PT10eXBlb2YgYS5uZXh0cG9zMSYmKGEuYW5pbWF0aW9uP2IuY3NzKGYsYS5uZXh0cG9zMStcInB4XCIpOihiLnJlbW92ZUNsYXNzKFwidWktcG5vdGlmeS1tb3ZlXCIpLGIuY3NzKGYsYS5uZXh0cG9zMStcInB4XCIpLGIuY3NzKGYpLGIuYWRkQ2xhc3MoXCJ1aS1wbm90aWZ5LW1vdmVcIikpKTtzd2l0Y2goYS5kaXIxKXtjYXNlIFwiZG93blwiOmNhc2UgXCJ1cFwiOmEubmV4dHBvczErPWIuaGVpZ2h0KCkrKFwidW5kZWZpbmVkXCI9PT10eXBlb2YgYS5zcGFjaW5nMT8yNTphLnNwYWNpbmcxKTticmVhaztjYXNlIFwibGVmdFwiOmNhc2UgXCJyaWdodFwiOmEubmV4dHBvczErPWIud2lkdGgoKSsoXCJ1bmRlZmluZWRcIj09PVxudHlwZW9mIGEuc3BhY2luZzE/MjU6YS5zcGFjaW5nMSl9fXJldHVybiB0aGlzfX0scXVldWVQb3NpdGlvbjpmdW5jdGlvbihiLGEpe2cmJmNsZWFyVGltZW91dChnKTthfHwoYT0xMCk7Zz1zZXRUaW1lb3V0KGZ1bmN0aW9uKCl7ZC5wb3NpdGlvbkFsbChiKX0sYSk7cmV0dXJuIHRoaXN9LGNhbmNlbFJlbW92ZTpmdW5jdGlvbigpe3RoaXMudGltZXImJmwuY2xlYXJUaW1lb3V0KHRoaXMudGltZXIpO3RoaXMuYW5pbVRpbWVyJiZsLmNsZWFyVGltZW91dCh0aGlzLmFuaW1UaW1lcik7XCJjbG9zaW5nXCI9PT10aGlzLnN0YXRlJiYodGhpcy5zdGF0ZT1cIm9wZW5cIix0aGlzLmFuaW1hdGluZz0hMSx0aGlzLmVsZW0uYWRkQ2xhc3MoXCJ1aS1wbm90aWZ5LWluXCIpLFwiZmFkZVwiPT09dGhpcy5vcHRpb25zLmFuaW1hdGlvbiYmdGhpcy5lbGVtLmFkZENsYXNzKFwidWktcG5vdGlmeS1mYWRlLWluXCIpKTtyZXR1cm4gdGhpc30scXVldWVSZW1vdmU6ZnVuY3Rpb24oKXt2YXIgYj10aGlzO3RoaXMuY2FuY2VsUmVtb3ZlKCk7XG50aGlzLnRpbWVyPWwuc2V0VGltZW91dChmdW5jdGlvbigpe2IucmVtb3ZlKCEwKX0saXNOYU4odGhpcy5vcHRpb25zLmRlbGF5KT8wOnRoaXMub3B0aW9ucy5kZWxheSk7cmV0dXJuIHRoaXN9fSk7Yi5leHRlbmQoZCx7bm90aWNlczpbXSxyZWxvYWQ6cSxyZW1vdmVBbGw6ZnVuY3Rpb24oKXtiLmVhY2goZC5ub3RpY2VzLGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmUmJnRoaXMucmVtb3ZlKCExKX0pfSxyZW1vdmVTdGFjazpmdW5jdGlvbihjKXtiLmVhY2goZC5ub3RpY2VzLGZ1bmN0aW9uKCl7dGhpcy5yZW1vdmUmJnRoaXMub3B0aW9ucy5zdGFjaz09PWMmJnRoaXMucmVtb3ZlKCExKX0pfSxwb3NpdGlvbkFsbDpmdW5jdGlvbihjKXtnJiZjbGVhclRpbWVvdXQoZyk7Zz1udWxsO2lmKGQubm90aWNlcyYmZC5ub3RpY2VzLmxlbmd0aCliLmVhY2goZC5ub3RpY2VzLGZ1bmN0aW9uKCl7dmFyIGE9dGhpcy5vcHRpb25zLnN0YWNrO2EmJihhLm92ZXJsYXkmJmEub3ZlcmxheS5oaWRlKCksYS5uZXh0cG9zMT1cbmEuZmlyc3Rwb3MxLGEubmV4dHBvczI9YS5maXJzdHBvczIsYS5hZGRwb3MyPTAsYS5hbmltYXRpb249Yyl9KSxiLmVhY2goZC5ub3RpY2VzLGZ1bmN0aW9uKCl7dGhpcy5wb3NpdGlvbigpfSk7ZWxzZXt2YXIgYT1kLnByb3RvdHlwZS5vcHRpb25zLnN0YWNrO2EmJihkZWxldGUgYS5uZXh0cG9zMSxkZWxldGUgYS5uZXh0cG9zMil9fSxzdHlsaW5nOnticmlnaHR0aGVtZTp7Y29udGFpbmVyOlwiYnJpZ2h0dGhlbWVcIixub3RpY2U6XCJicmlnaHR0aGVtZS1ub3RpY2VcIixub3RpY2VfaWNvbjpcImJyaWdodHRoZW1lLWljb24tbm90aWNlXCIsaW5mbzpcImJyaWdodHRoZW1lLWluZm9cIixpbmZvX2ljb246XCJicmlnaHR0aGVtZS1pY29uLWluZm9cIixzdWNjZXNzOlwiYnJpZ2h0dGhlbWUtc3VjY2Vzc1wiLHN1Y2Nlc3NfaWNvbjpcImJyaWdodHRoZW1lLWljb24tc3VjY2Vzc1wiLGVycm9yOlwiYnJpZ2h0dGhlbWUtZXJyb3JcIixlcnJvcl9pY29uOlwiYnJpZ2h0dGhlbWUtaWNvbi1lcnJvclwifSxqcXVlcnl1aTp7Y29udGFpbmVyOlwidWktd2lkZ2V0IHVpLXdpZGdldC1jb250ZW50IHVpLWNvcm5lci1hbGxcIixcbm5vdGljZTpcInVpLXN0YXRlLWhpZ2hsaWdodFwiLG5vdGljZV9pY29uOlwidWktaWNvbiB1aS1pY29uLWluZm9cIixpbmZvOlwiXCIsaW5mb19pY29uOlwidWktaWNvbiB1aS1pY29uLWluZm9cIixzdWNjZXNzOlwidWktc3RhdGUtZGVmYXVsdFwiLHN1Y2Nlc3NfaWNvbjpcInVpLWljb24gdWktaWNvbi1jaXJjbGUtY2hlY2tcIixlcnJvcjpcInVpLXN0YXRlLWVycm9yXCIsZXJyb3JfaWNvbjpcInVpLWljb24gdWktaWNvbi1hbGVydFwifSxib290c3RyYXAzOntjb250YWluZXI6XCJhbGVydFwiLG5vdGljZTpcImFsZXJ0LXdhcm5pbmdcIixub3RpY2VfaWNvbjpcImdseXBoaWNvbiBnbHlwaGljb24tZXhjbGFtYXRpb24tc2lnblwiLGluZm86XCJhbGVydC1pbmZvXCIsaW5mb19pY29uOlwiZ2x5cGhpY29uIGdseXBoaWNvbi1pbmZvLXNpZ25cIixzdWNjZXNzOlwiYWxlcnQtc3VjY2Vzc1wiLHN1Y2Nlc3NfaWNvbjpcImdseXBoaWNvbiBnbHlwaGljb24tb2stc2lnblwiLGVycm9yOlwiYWxlcnQtZGFuZ2VyXCIsZXJyb3JfaWNvbjpcImdseXBoaWNvbiBnbHlwaGljb24td2FybmluZy1zaWduXCJ9fX0pO1xuZC5zdHlsaW5nLmZvbnRhd2Vzb21lPWIuZXh0ZW5kKHt9LGQuc3R5bGluZy5ib290c3RyYXAzKTtiLmV4dGVuZChkLnN0eWxpbmcuZm9udGF3ZXNvbWUse25vdGljZV9pY29uOlwiZmEgZmEtZXhjbGFtYXRpb24tY2lyY2xlXCIsaW5mb19pY29uOlwiZmEgZmEtaW5mb1wiLHN1Y2Nlc3NfaWNvbjpcImZhIGZhLWNoZWNrXCIsZXJyb3JfaWNvbjpcImZhIGZhLXdhcm5pbmdcIn0pO2wuZG9jdW1lbnQuYm9keT9yKCk6YihyKTtyZXR1cm4gZH07cmV0dXJuIHEoayl9KTtcbiIsInZhciBrZXkgPSByZXF1aXJlKFwia2V5bWFzdGVyXCIpO1xyXG52YXIgbWVzc2FnZSA9IHJlcXVpcmUoXCJtb2R1bGVzL21lc3NhZ2VcIik7XHJcblxyXG4vLyBBZGQgc2hvcnRjdXQgY29lXHJcbnZhciBfYWRkU2hvcnRjdXQgPSBmdW5jdGlvbihzZWxlY3RlZEtleSxjYikge1xyXG4gIGtleShzZWxlY3RlZEtleSwgY2IpO1xyXG59XHJcblxyXG52YXIgX2NsaWNrQnV0dG9uID0gZnVuY3Rpb24oc2VsZWN0b3IpIHtcclxuICAkKHNlbGVjdG9yKS50cmlnZ2VyKCdjbGljaycpO1xyXG4gIHJldHVybiBmYWxzZTtcclxufVxyXG5cclxuLy8gQWRkIHNob3J0Y3V0cyBmb3Igc2VsZWN0b3JzXHJcbnZhciBkZWZhdWx0U2hvcnRjdXRzID0gZnVuY3Rpb24oKSB7XHJcbiAgX2FkZFNob3J0Y3V0KCdjdHJsK3MnLCBmdW5jdGlvbihldmVudCxoYW5kbGVyKXtcclxuICAgIF9jbGlja0J1dHRvbignLnQtQnV0dG9uW29uY2xpY2sqPVwiXFwnQ1JFQVRFXFwnXCJdLC50LUJ1dHRvbiNDUkVBVEUnKTtcclxuICB9KTtcclxuICBfYWRkU2hvcnRjdXQoJ2N0cmwrcycsIGZ1bmN0aW9uKGV2ZW50LGhhbmRsZXIpe1xyXG4gICAgX2NsaWNrQnV0dG9uKCcudC1CdXR0b25bb25jbGljayo9XCJcXCdTQVZFXFwnXCJdLC50LUJ1dHRvbiNTQVZFJyk7XHJcbiAgfSk7XHJcbiAgX2FkZFNob3J0Y3V0KCdjdHJsK2QnLCBmdW5jdGlvbihldmVudCxoYW5kbGVyKXtcclxuICAgIF9jbGlja0J1dHRvbignLnQtQnV0dG9uW29uY2xpY2sqPVwiXFwnREVMRVRFXFwnXCJdLC50LUJ1dHRvbiNERUxFVEUnKTtcclxuICB9KTtcclxuICBfYWRkU2hvcnRjdXQoJ2N0cmwrbScsIGZ1bmN0aW9uKGV2ZW50LGhhbmRsZXIpe1xyXG4gICAgbWVzc2FnZS5pbmZvKHtcclxuICAgICAgdGl0bGU6IFwiQSBrZXkgd2FzIHByZXNzZWRcIixcclxuICAgICAgdGV4dDogXCJjdHJsK21cIlxyXG4gICAgfSk7XHJcbiAgfSk7XHJcbn07XHJcblxyXG5tb2R1bGUuZXhwb3J0cyA9IHtcclxuICBkZWZhdWx0U2hvcnRjdXRzOiBkZWZhdWx0U2hvcnRjdXRzXHJcbn1cclxuIiwidmFyICQgPSAod2luZG93LiQpOyAvLyBiZWluZyBzaGltbWVkXHJcbnZhciBQTm90aWZ5ID0gcmVxdWlyZShcInBub3RpZnlcIik7XHJcblxyXG5cclxuLy8gQnV0dG9uIGRlZmF1bHRzXHJcbnZhciBkZWZhdWx0cyA9IHtcclxuICBoaWRlOiBmYWxzZSxcclxuICBjbG9zZXI6IHRydWUsXHJcbiAgYnV0dG9uczoge1xyXG4gICAgY2xvc2VyX2hvdmVyOiBmYWxzZSxcclxuICAgIHN0aWNrZXI6IGZhbHNlLFxyXG4gICAgbGFiZWxzOiB7XHJcbiAgICAgIGNsb3NlOiAnU2x1aXQgbWVsZGluZydcclxuICAgIH1cclxuICB9XHJcbn07XHJcblxyXG5cclxudmFyIGdldFNldHRpbmdzID0gZnVuY3Rpb24ob3B0aW9ucykge1xyXG5cclxuICBzd2l0Y2ggKHR5cGVvZiBvcHRpb25zKSB7XHJcbiAgICBjYXNlIFwic3RyaW5nXCI6XHJcbiAgICAgIHJldHVybiAkLmV4dGVuZCh7XHJcbiAgICAgICAgdGl0bGU6IG9wdGlvbnNcclxuICAgICAgfSwgZGVmYXVsdHMpO1xyXG4gICAgY2FzZSBcIm9iamVjdFwiOlxyXG4gICAgICByZXR1cm4gJC5leHRlbmQob3B0aW9ucywgZGVmYXVsdHMpO1xyXG4gICAgZGVmYXVsdDpcclxuICAgICAgcmV0dXJuIGRlZmF1bHRzO1xyXG4gIH07XHJcblxyXG59O1xyXG5cclxudmFyIGluZm8gPSBmdW5jdGlvbihvcHRpb25zKSB7XHJcbiAgcmV0dXJuIG5ldyBQTm90aWZ5KCQuZXh0ZW5kKGdldFNldHRpbmdzKG9wdGlvbnMpLCB7XHJcbiAgICB0eXBlOiAnaW5mbycsXHJcbiAgICBoaWRlOiB0cnVlXHJcbiAgfSkpO1xyXG59O1xyXG5cclxudmFyIHN1Y2Nlc3MgPSBmdW5jdGlvbihvcHRpb25zKSB7XHJcbiAgcmV0dXJuIG5ldyBQTm90aWZ5KCQuZXh0ZW5kKGdldFNldHRpbmdzKG9wdGlvbnMpLCB7XHJcbiAgICB0eXBlOiAnc3VjY2VzcycsXHJcbiAgICBoaWRlOiB0cnVlXHJcbiAgfSkpO1xyXG59O1xyXG5cclxudmFyIHdhcm5pbmcgPSBmdW5jdGlvbihvcHRpb25zKSB7XHJcbiAgcmV0dXJuIG5ldyBQTm90aWZ5KCQuZXh0ZW5kKGdldFNldHRpbmdzKG9wdGlvbnMpLCB7XHJcbiAgICB0eXBlOiAnd2FybmluZydcclxuICB9KSk7XHJcbn07XHJcblxyXG52YXIgZXJyb3IgPSBmdW5jdGlvbihvcHRpb25zKSB7XHJcbiAgcmV0dXJuIG5ldyBQTm90aWZ5KCQuZXh0ZW5kKGdldFNldHRpbmdzKG9wdGlvbnMpLCB7XHJcbiAgICB0eXBlOiAnZXJyb3InXHJcbiAgfSkpO1xyXG59O1xyXG5cclxubW9kdWxlLmV4cG9ydHMgPSB7XHJcbiAgaW5mbzogaW5mbyxcclxuICBzdWNjZXNzOiBzdWNjZXNzLFxyXG4gIHdhcm5pbmc6IHdhcm5pbmcsXHJcbiAgZXJyb3I6IGVycm9yXHJcbn07XHJcbiIsIiQud2lkZ2V0KFwiY3VzdG9tLmN1c3RvbVJlcG9ydFwiLCB7XHJcbiAgb3B0aW9uczoge1xyXG4gICAgZXhjZXB0Q2xhc3M6ICduby1yb3ctbGluaycsXHJcbiAgICBhY3RpdmVDbGFzczogJ2FjdGl2ZScsXHJcbiAgICBjb2x1bW5zOiBbXSxcclxuICAgIHJvd2NsaWNrOiBmdW5jdGlvbihlLCBkYXRhKSB7XHJcbiAgICAgICQodGhpcykuY3VzdG9tUmVwb3J0KCdvcGVuTGluaycsIGUpO1xyXG4gICAgfVxyXG4gIH0sXHJcblxyXG4gIF9jcmVhdGU6IGZ1bmN0aW9uKCkge1xyXG4gICAgdmFyIHNlbGYgPSB0aGlzO1xyXG4gICAgLy8gQ2hlY2sgaWYgcmVwb3J0IG1heSBoYXZlIGEgcm93IGxpbmtcclxuICAgIGlmICh0aGlzLl9yb3dMaW5rQWxsb3dlZCkge1xyXG4gICAgICB0aGlzLl9pbml0Um93Q2xpY2soKTtcclxuICAgIH1cclxuXHJcbiAgICAkKHRoaXMuZWxlbWVudCkub24oJ2FwZXhhZnRlcnJlZnJlc2gnLGZ1bmN0aW9uKGUpe1xyXG4gICAgICBzZWxmLl9hcGV4YWZ0ZXJyZWZyZXNoKCk7XHJcbiAgICB9KTtcclxuXHJcbiAgfSxcclxuXHJcbiAgX2FwZXhhZnRlcnJlZnJlc2g6IGZ1bmN0aW9uKCkge1xyXG4gICAgLy8gQ2hlY2sgaWYgcmVwb3J0IG1heSBoYXZlIGEgcm93IGxpbmtcclxuICAgIGlmICh0aGlzLl9yb3dMaW5rQWxsb3dlZCkge1xyXG4gICAgICB0aGlzLl9pbml0Um93Q2xpY2soKTtcclxuICAgIH1cclxuICB9LFxyXG5cclxuICBfaW5pdFJvd0NsaWNrOiBmdW5jdGlvbihjYikge1xyXG5cclxuICAgIHZhciBzZWxmID0gdGhpcztcclxuICAgIHZhciBkYXRhO1xyXG4gICAgY2IgPSAkLnByb3h5KGNiLCBzZWxmKTtcclxuXHJcbiAgICAvLyBSZW1vdmUgcHJldmlvdXMgaGFuZGxlcnNcclxuICAgIHRoaXMuX29mZih0aGlzLmVsZW1lbnQsICdjbGljayB0ciB0ZDpub3QoOmhhcyhhKSknKTtcclxuICAgIHRoaXMuX29mZih0aGlzLmVsZW1lbnQsICdob3ZlciB0ciB0ZDpub3QoOmhhcyhhKSknKTtcclxuXHJcbiAgICAvLyBBZGQgbmV3IGhhbmRsZXJcclxuICAgIHRoaXMuX29uKHRoaXMuZWxlbWVudCwge1xyXG4gICAgICAnbW91c2VlbnRlciB0ciB0ZDpub3QoOmhhcyhhKSknOiBmdW5jdGlvbihlKSB7XHJcbiAgICAgICAgJChlLnRhcmdldCkuY3NzKCdjdXJzb3InLCAncG9pbnRlcicpO1xyXG4gICAgICB9XHJcbiAgICB9KTtcclxuXHJcbiAgICAvLyBBZGQgbmV3IGhhbmRsZXJcclxuICAgIHRoaXMuX29uKHRoaXMuZWxlbWVudCwge1xyXG4gICAgICAnY2xpY2sgdHIgdGQ6bm90KDpoYXMoYSkpJzogZnVuY3Rpb24oZSkge1xyXG4gICAgICAgIHNlbGYuX3RyaWdnZXIoJ3Jvd2NsaWNrJywgZSwgZGF0YSk7XHJcbiAgICAgICAgZS5zdG9wSW1tZWRpYXRlUHJvcGFnYXRpb24oKTtcclxuICAgICAgfVxyXG4gICAgfSk7XHJcblxyXG4gIH0sXHJcblxyXG4gIC8vIFVzZSBhbiBhIGhyZWYgdmFsdWUgdG8gcmVkaXJlY3Qgb24gcm93IGNsaWNrIGluIHJlcG9ydFxyXG4gIG9wZW5MaW5rOiBmdW5jdGlvbihldmVudCwgb3B0aW9ucykge1xyXG5cclxuICAgIHZhciBzZWxmID0gdGhpcztcclxuXHJcbiAgICB2YXIgZGVmYXVsdHMgPSB7XHJcbiAgICAgIGFQb3M6IDAgLy8gd2hpY2ggXCJhXCIgZWxlbWVudCBjb250YWlucyB0aGUgbGlua1xyXG4gICAgfVxyXG5cclxuICAgIHZhciBzZXR0aW5ncyA9ICQuZXh0ZW5kKG9wdGlvbnMsIGRlZmF1bHRzKTtcclxuXHJcbiAgICAvLyBHZXQgbGlua1xyXG4gICAgdmFyICRsaW5rRWxlbSA9IHNlbGYuX2dldExpbmtFbGVtZW50KHNldHRpbmdzLCBldmVudC5jdXJyZW50VGFyZ2V0KTtcclxuICAgIHZhciBocmVmID0gJGxpbmtFbGVtLmF0dHIoJ2hyZWYnKTtcclxuXHJcbiAgICBpZiAoaHJlZiAhPSB1bmRlZmluZWQpIHtcclxuICAgICAgd2luZG93LmxvY2F0aW9uLmhyZWYgPSBocmVmO1xyXG4gICAgfVxyXG4gIH0sXHJcblxyXG4gIF9nZXRMaW5rRWxlbWVudDogZnVuY3Rpb24ob3B0aW9ucywgdGFyZ2V0KSB7XHJcblxyXG4gICAgdmFyIGxpbmtzID0gJCh0YXJnZXQpLmNsb3Nlc3QoJ3RyJykuZmluZCgndGQ6aGFzKGEpJyk7XHJcblxyXG4gICAgLy8gUmFpc2UgZXhjZXB0aW9uIGlmIHBvc2l0aW9uIG9mIGEgZWxlbWVudCBpcyBpbnZhbGlkXHJcbiAgICBpZiAobGlua3MubGVuZ3RoIDwgb3B0aW9ucy5hUG9zKSB7XHJcbiAgICAgIGFwZXguZGVidWcuZXJyb3IoJ0V4Y2VwdGlvbjogJywgb3B0aW9ucy5hUG9zICsgJ3RoIFwiYVwiIGVsZW1lbnQgaXMgbm90IGZvdW5kIGluIHJlcG9ydCByb3cuJyk7XHJcbiAgICB9IGVsc2Uge1xyXG4gICAgICByZXR1cm4gJChsaW5rc1tvcHRpb25zLmFQb3NdKS5maW5kKCdhJyk7XHJcbiAgICB9XHJcblxyXG4gIH0sXHJcblxyXG4gIC8vIF9yb3dMaW5rQWxsb3dlZCByZXR1cm5zIGJvb2xlYW5cclxuICBfcm93TGlua0FsbG93ZWQ6IGZ1bmN0aW9uKCkge1xyXG4gICAgcmV0dXJuICEkKHRoaXMuZWxlbWVudCkuaGFzQ2xhc3ModGhpcy5vcHRpb25zLmV4Y2VwdENsYXNzKTtcclxuICB9LFxyXG5cclxuICBfc2V0QWN0aXZlUm93OiBmdW5jdGlvbigkcm93KSB7XHJcbiAgICAkKCRyb3cpLmNsb3Nlc3QoJ3RhYmxlJykuZmluZCgndGQuJyt0aGlzLm9wdGlvbnMuYWN0aXZlQ2xhc3MpLnJlbW92ZUNsYXNzKHRoaXMub3B0aW9ucy5hY3RpdmVDbGFzcyk7XHJcbiAgICAkKCRyb3cpLmZpbmQoJ3RkJykuYWRkQ2xhc3ModGhpcy5vcHRpb25zLmFjdGl2ZUNsYXNzKTtcclxuICB9LFxyXG5cclxuICBhY3RpdmVSb3c6IGZ1bmN0aW9uKCkge1xyXG4gICAgcmV0dXJuICQodGhpcy5lbGVtZW50KS5maW5kKCd0ZC4nK3RoaXMub3B0aW9ucy5hY3RpdmVDbGFzcykuY2xvc2VzdCgndHInKTtcclxuICB9XHJcblxyXG59KTtcclxuIl19 550 | --------------------------------------------------------------------------------