From fbe291fe3a4f01e0c54c1e7aff9b6bb07e9c74fe Mon Sep 17 00:00:00 2001 From: stephen-james Date: Mon, 25 May 2015 12:18:13 +0100 Subject: [PATCH 001/131] introducing project to gulp using `gulp-starter` v0.1.1, cloned and copied from https://github.com/greypants/gulp-starter/commit/a059e93422c1f9825fb8207f5376f87284a2a47f --- .gitignore | 2 + gulp/LICENSE.md | 21 +++++++ gulp/config.js | 68 ++++++++++++++++++++ gulp/tasks/browserSync.js | 7 +++ gulp/tasks/browserify.js | 84 +++++++++++++++++++++++++ gulp/tasks/default.js | 3 + gulp/tasks/iconFont/generateIconSass.js | 25 ++++++++ gulp/tasks/iconFont/index.js | 11 ++++ gulp/tasks/iconFont/template.sass.swig | 33 ++++++++++ gulp/tasks/images.js | 13 ++++ gulp/tasks/karma.js | 13 ++++ gulp/tasks/markup.js | 9 +++ gulp/tasks/minifyCss.js | 11 ++++ gulp/tasks/production.js | 7 +++ gulp/tasks/sass.js | 18 ++++++ gulp/tasks/uglifyJs.js | 11 ++++ gulp/tasks/watch.js | 14 +++++ gulp/tasks/watchify.js | 7 +++ gulp/util/bundleLogger.js | 25 ++++++++ gulp/util/handleErrors.js | 15 +++++ gulpfile.js | 17 +++++ 21 files changed, 414 insertions(+) create mode 100644 .gitignore create mode 100644 gulp/LICENSE.md create mode 100644 gulp/config.js create mode 100644 gulp/tasks/browserSync.js create mode 100644 gulp/tasks/browserify.js create mode 100644 gulp/tasks/default.js create mode 100644 gulp/tasks/iconFont/generateIconSass.js create mode 100644 gulp/tasks/iconFont/index.js create mode 100644 gulp/tasks/iconFont/template.sass.swig create mode 100644 gulp/tasks/images.js create mode 100644 gulp/tasks/karma.js create mode 100644 gulp/tasks/markup.js create mode 100644 gulp/tasks/minifyCss.js create mode 100644 gulp/tasks/production.js create mode 100644 gulp/tasks/sass.js create mode 100644 gulp/tasks/uglifyJs.js create mode 100644 gulp/tasks/watch.js create mode 100644 gulp/tasks/watchify.js create mode 100644 gulp/util/bundleLogger.js create mode 100644 gulp/util/handleErrors.js create mode 100644 gulpfile.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..a56a7ef43 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules + diff --git a/gulp/LICENSE.md b/gulp/LICENSE.md new file mode 100644 index 000000000..6879275d9 --- /dev/null +++ b/gulp/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Daniel Tello + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/gulp/config.js b/gulp/config.js new file mode 100644 index 000000000..0a6245c8a --- /dev/null +++ b/gulp/config.js @@ -0,0 +1,68 @@ +var dest = "./build"; +var src = './src'; + +module.exports = { + browserSync: { + server: { + // Serve up our build folder + baseDir: dest + } + }, + sass: { + src: src + "/sass/**/*.{sass,scss}", + dest: dest, + settings: { + indentedSyntax: true, // Enable .sass syntax! + imagePath: 'images' // Used by the image-url helper + } + }, + images: { + src: src + "/images/**", + dest: dest + "/images" + }, + markup: { + src: src + "/htdocs/**", + dest: dest + }, + iconFonts: { + name: 'Gulp Starter Icons', + src: src + '/icons/*.svg', + dest: dest + '/fonts', + sassDest: src + '/sass', + template: './gulp/tasks/iconFont/template.sass.swig', + sassOutputName: '_icons.sass', + fontPath: 'fonts', + className: 'icon', + options: { + fontName: 'Post-Creator-Icons', + appendCodepoints: true, + normalize: false + } + }, + browserify: { + // A separate bundle will be generated for each + // bundle config in the list below + bundleConfigs: [{ + entries: src + '/javascript/global.coffee', + dest: dest, + outputName: 'global.js', + // Additional file extentions to make optional + extensions: ['.coffee', '.hbs'], + // list of modules to make require-able externally + require: ['jquery', 'backbone/node_modules/underscore'] + // See https://github.com/greypants/gulp-starter/issues/87 for note about + // why this is 'backbone/node_modules/underscore' and not 'underscore' + }, { + entries: src + '/javascript/page.js', + dest: dest, + outputName: 'page.js', + // list of externally available modules to exclude from the bundle + external: ['jquery', 'underscore'] + }] + }, + production: { + cssSrc: dest + '/*.css', + jsSrc: dest + '/*.js', + dest: dest + } +}; diff --git a/gulp/tasks/browserSync.js b/gulp/tasks/browserSync.js new file mode 100644 index 000000000..b1218e4b4 --- /dev/null +++ b/gulp/tasks/browserSync.js @@ -0,0 +1,7 @@ +var browserSync = require('browser-sync'); +var gulp = require('gulp'); +var config = require('../config').browserSync; + +gulp.task('browserSync', function() { + browserSync(config); +}); diff --git a/gulp/tasks/browserify.js b/gulp/tasks/browserify.js new file mode 100644 index 000000000..d59f7e566 --- /dev/null +++ b/gulp/tasks/browserify.js @@ -0,0 +1,84 @@ +/* browserify task + --------------- + Bundle javascripty things with browserify! + + This task is set up to generate multiple separate bundles, from + different sources, and to use Watchify when run from the default task. + + See browserify.bundleConfigs in gulp/config.js +*/ + +var browserify = require('browserify'); +var browserSync = require('browser-sync'); +var watchify = require('watchify'); +var mergeStream = require('merge-stream'); +var bundleLogger = require('../util/bundleLogger'); +var gulp = require('gulp'); +var handleErrors = require('../util/handleErrors'); +var source = require('vinyl-source-stream'); +var config = require('../config').browserify; +var _ = require('lodash'); + +var browserifyTask = function(devMode) { + + var browserifyThis = function(bundleConfig) { + + if(devMode) { + // Add watchify args and debug (sourcemaps) option + _.extend(bundleConfig, watchify.args, { debug: true }); + // A watchify require/external bug that prevents proper recompiling, + // so (for now) we'll ignore these options during development. Running + // `gulp browserify` directly will properly require and externalize. + bundleConfig = _.omit(bundleConfig, ['external', 'require']); + } + + var b = browserify(bundleConfig); + + var bundle = function() { + // Log when bundling starts + bundleLogger.start(bundleConfig.outputName); + + return b + .bundle() + // Report compile errors + .on('error', handleErrors) + // Use vinyl-source-stream to make the + // stream gulp compatible. Specify the + // desired output filename here. + .pipe(source(bundleConfig.outputName)) + // Specify the output destination + .pipe(gulp.dest(bundleConfig.dest)) + .pipe(browserSync.reload({ + stream: true + })); + }; + + if(devMode) { + // Wrap with watchify and rebundle on changes + b = watchify(b); + // Rebundle on update + b.on('update', bundle); + bundleLogger.watch(bundleConfig.outputName); + } else { + // Sort out shared dependencies. + // b.require exposes modules externally + if(bundleConfig.require) b.require(bundleConfig.require); + // b.external excludes modules from the bundle, and expects + // they'll be available externally + if(bundleConfig.external) b.external(bundleConfig.external); + } + + return bundle(); + }; + + // Start bundling with Browserify for each bundleConfig specified + return mergeStream.apply(gulp, _.map(config.bundleConfigs, browserifyThis)); + +}; + +gulp.task('browserify', function() { + return browserifyTask() +}); + +// Exporting the task so we can call it directly in our watch task, with the 'devMode' option +module.exports = browserifyTask; diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js new file mode 100644 index 000000000..e4c055911 --- /dev/null +++ b/gulp/tasks/default.js @@ -0,0 +1,3 @@ +var gulp = require('gulp'); + +gulp.task('default', ['sass', 'images', 'markup', 'watch']); diff --git a/gulp/tasks/iconFont/generateIconSass.js b/gulp/tasks/iconFont/generateIconSass.js new file mode 100644 index 000000000..6b3341b70 --- /dev/null +++ b/gulp/tasks/iconFont/generateIconSass.js @@ -0,0 +1,25 @@ +var gulp = require('gulp'); +var config = require('../../config').iconFonts; +var swig = require('gulp-swig'); +var rename = require('gulp-rename'); + +module.exports = function(codepoints, options) { + gulp.src(config.template) + .pipe(swig({ + data: { + icons: codepoints.map(function(icon) { + return { + name: icon.name, + code: icon.codepoint.toString(16) + } + }), + + fontName: config.options.fontName, + fontPath: config.fontPath, + className: config.className, + comment: 'DO NOT EDIT DIRECTLY!\n Generated by gulp/tasks/iconFont.js\n from ' + config.template + } + })) + .pipe(rename(config.sassOutputName)) + .pipe(gulp.dest(config.sassDest)); +}; diff --git a/gulp/tasks/iconFont/index.js b/gulp/tasks/iconFont/index.js new file mode 100644 index 000000000..6be8b2eb0 --- /dev/null +++ b/gulp/tasks/iconFont/index.js @@ -0,0 +1,11 @@ +var gulp = require('gulp'); +var iconfont = require('gulp-iconfont'); +var config = require('../../config').iconFonts; +var generateIconSass = require('./generateIconSass'); + +gulp.task('iconFont', function() { + return gulp.src(config.src) + .pipe(iconfont(config.options)) + .on('codepoints', generateIconSass) + .pipe(gulp.dest(config.dest)); +}); diff --git a/gulp/tasks/iconFont/template.sass.swig b/gulp/tasks/iconFont/template.sass.swig new file mode 100644 index 000000000..17cb6cb1d --- /dev/null +++ b/gulp/tasks/iconFont/template.sass.swig @@ -0,0 +1,33 @@ +// {{comment}} + +@font-face + font-family: {{fontName}} + src: url("{{fontPath}}/{{fontName}}.eot") + src: url("{{fontPath}}/{{fontName}}.eot?#iefix") format('embedded-opentype'), url("{{fontPath}}/{{fontName}}.woff") format('woff'), url("{{fontPath}}/{{fontName}}.ttf") format('truetype'), url("{{fontPath}}/{{fontName}}.svg#{{fontName}}") format('svg') + font-weight: normal + font-style: normal + +=icon($content) + &:before + -moz-osx-font-smoothing: grayscale + -webkit-font-smoothing: antialiased + content: $content + font-family: '{{fontName}}' + font-style: normal + font-variant: normal + font-weight: normal + line-height: 1 + speak: none + text-transform: none + @content + +{% for icon in icons -%} +=icon--{{icon.name}} + +icon("\{{icon.code}}") + @content + +.icon + &.-{{icon.name}} + +icon--{{icon.name}} + +{% endfor %} diff --git a/gulp/tasks/images.js b/gulp/tasks/images.js new file mode 100644 index 000000000..439b5dcda --- /dev/null +++ b/gulp/tasks/images.js @@ -0,0 +1,13 @@ +var changed = require('gulp-changed'); +var gulp = require('gulp'); +var imagemin = require('gulp-imagemin'); +var config = require('../config').images; +var browserSync = require('browser-sync'); + +gulp.task('images', function() { + return gulp.src(config.src) + .pipe(changed(config.dest)) // Ignore unchanged files + .pipe(imagemin()) // Optimize + .pipe(gulp.dest(config.dest)) + .pipe(browserSync.reload({stream:true})); +}); diff --git a/gulp/tasks/karma.js b/gulp/tasks/karma.js new file mode 100644 index 000000000..37e2c759d --- /dev/null +++ b/gulp/tasks/karma.js @@ -0,0 +1,13 @@ +var gulp = require('gulp'); +var karma = require('karma'); + +var karmaTask = function(done) { + karma.server.start({ + configFile: process.cwd() + '/karma.conf.js', + singleRun: true + }, done); +}; + +gulp.task('karma', karmaTask); + +module.exports = karmaTask; diff --git a/gulp/tasks/markup.js b/gulp/tasks/markup.js new file mode 100644 index 000000000..e43c6210c --- /dev/null +++ b/gulp/tasks/markup.js @@ -0,0 +1,9 @@ +var gulp = require('gulp'); +var config = require('../config').markup; +var browserSync = require('browser-sync'); + +gulp.task('markup', function() { + return gulp.src(config.src) + .pipe(gulp.dest(config.dest)) + .pipe(browserSync.reload({stream:true})); +}); diff --git a/gulp/tasks/minifyCss.js b/gulp/tasks/minifyCss.js new file mode 100644 index 000000000..56bfbdc02 --- /dev/null +++ b/gulp/tasks/minifyCss.js @@ -0,0 +1,11 @@ +var gulp = require('gulp'); +var config = require('../config').production; +var minifyCSS = require('gulp-minify-css'); +var size = require('gulp-filesize'); + +gulp.task('minifyCss', ['sass'], function() { + return gulp.src(config.cssSrc) + .pipe(minifyCSS({keepBreaks:true})) + .pipe(gulp.dest(config.dest)) + .pipe(size()); +}) diff --git a/gulp/tasks/production.js b/gulp/tasks/production.js new file mode 100644 index 000000000..7cab1cff6 --- /dev/null +++ b/gulp/tasks/production.js @@ -0,0 +1,7 @@ +var gulp = require('gulp'); + +// Run this to compress all the things! +gulp.task('production', ['karma'], function(){ + // This runs only if the karma tests pass + gulp.start(['markup', 'images', 'iconFont', 'minifyCss', 'uglifyJs']) +}); diff --git a/gulp/tasks/sass.js b/gulp/tasks/sass.js new file mode 100644 index 000000000..036f486d0 --- /dev/null +++ b/gulp/tasks/sass.js @@ -0,0 +1,18 @@ +var gulp = require('gulp'); +var browserSync = require('browser-sync'); +var sass = require('gulp-sass'); +var sourcemaps = require('gulp-sourcemaps'); +var handleErrors = require('../util/handleErrors'); +var config = require('../config').sass; +var autoprefixer = require('gulp-autoprefixer'); + +gulp.task('sass', function () { + return gulp.src(config.src) + .pipe(sourcemaps.init()) + .pipe(sass(config.settings)) + .on('error', handleErrors) + .pipe(sourcemaps.write()) + .pipe(autoprefixer({ browsers: ['last 2 version'] })) + .pipe(gulp.dest(config.dest)) + .pipe(browserSync.reload({stream:true})); +}); diff --git a/gulp/tasks/uglifyJs.js b/gulp/tasks/uglifyJs.js new file mode 100644 index 000000000..e5024d34f --- /dev/null +++ b/gulp/tasks/uglifyJs.js @@ -0,0 +1,11 @@ +var gulp = require('gulp'); +var config = require('../config').production; +var size = require('gulp-filesize'); +var uglify = require('gulp-uglify'); + +gulp.task('uglifyJs', ['browserify'], function() { + return gulp.src(config.jsSrc) + .pipe(uglify()) + .pipe(gulp.dest(config.dest)) + .pipe(size()); +}); diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js new file mode 100644 index 000000000..1a72d81bc --- /dev/null +++ b/gulp/tasks/watch.js @@ -0,0 +1,14 @@ +/* Notes: + - gulp/tasks/browserify.js handles js recompiling with watchify + - gulp/tasks/browserSync.js watches and reloads compiled files +*/ + +var gulp = require('gulp'); +var config = require('../config'); + +gulp.task('watch', ['watchify','browserSync'], function() { + gulp.watch(config.sass.src, ['sass']); + gulp.watch(config.images.src, ['images']); + gulp.watch(config.markup.src, ['markup']); + // Watchify will watch and recompile our JS, so no need to gulp.watch it +}); diff --git a/gulp/tasks/watchify.js b/gulp/tasks/watchify.js new file mode 100644 index 000000000..ba032fd1c --- /dev/null +++ b/gulp/tasks/watchify.js @@ -0,0 +1,7 @@ +var gulp = require('gulp'); +var browserifyTask = require('./browserify'); + +gulp.task('watchify', function() { + // Start browserify task with devMode === true + return browserifyTask(true); +}); diff --git a/gulp/util/bundleLogger.js b/gulp/util/bundleLogger.js new file mode 100644 index 000000000..ec6e61e65 --- /dev/null +++ b/gulp/util/bundleLogger.js @@ -0,0 +1,25 @@ +/* bundleLogger + ------------ + Provides gulp style logs to the bundle method in browserify.js +*/ + +var gutil = require('gulp-util'); +var prettyHrtime = require('pretty-hrtime'); +var startTime; + +module.exports = { + start: function(filepath) { + startTime = process.hrtime(); + gutil.log('Bundling', gutil.colors.green(filepath) + '...'); + }, + + watch: function(bundleName) { + gutil.log('Watching files required by', gutil.colors.yellow(bundleName)); + }, + + end: function(filepath) { + var taskTime = process.hrtime(startTime); + var prettyTime = prettyHrtime(taskTime); + gutil.log('Bundled', gutil.colors.green(filepath), 'in', gutil.colors.magenta(prettyTime)); + } +}; diff --git a/gulp/util/handleErrors.js b/gulp/util/handleErrors.js new file mode 100644 index 000000000..a9f283490 --- /dev/null +++ b/gulp/util/handleErrors.js @@ -0,0 +1,15 @@ +var notify = require("gulp-notify"); + +module.exports = function() { + + var args = Array.prototype.slice.call(arguments); + + // Send error to notification center with gulp-notify + notify.onError({ + title: "Compile Error", + message: "<%= error %>" + }).apply(this, args); + + // Keep gulp from hanging on this task + this.emit('end'); +}; \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 000000000..59686d1fd --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,17 @@ +/* + gulpfile.js + =========== + Rather than manage one giant configuration file responsible + for creating multiple tasks, each task has been broken out into + its own file in gulp/tasks. Any files in that directory get + automatically required below. + + To add a new task, simply add a new task file that directory. + gulp/tasks/default.js specifies the default set of tasks to run + when you run `gulp`. +*/ + +var requireDir = require('require-dir'); + +// Require all tasks in gulp/tasks, including subfolders +requireDir('./gulp/tasks', { recurse: true }); From 1b785610d7e006af0df481379a77f4fa1ca1c220 Mon Sep 17 00:00:00 2001 From: stephen-james Date: Mon, 25 May 2015 23:10:47 +0100 Subject: [PATCH 002/131] first step to a browserify build process, using gulp as task runner and karma for testing (this commit is scaffolding only and does not aim to split interact into modules) - move interact.js source to a `src` folder and introduce a `build` folder - get existing tests to run (mostly) with browserified karma, configured for mocha, chai and using fixture for html fixtures - add dependencies for `gulp-starter` based build process - remove icon font tasks from gulp process, while the others may be useful for docs/demo's folders, we won't be needing icons --- build/interact.js | 5864 +++++++++++++++++++++++ gulp/config.js | 43 +- gulp/tasks/default.js | 4 +- gulp/tasks/iconFont/generateIconSass.js | 25 - gulp/tasks/iconFont/index.js | 11 - gulp/tasks/iconFont/template.sass.swig | 33 - gulp/tasks/karma.js | 10 + gulp/tasks/production.js | 2 +- gulp/tasks/watch.js | 8 +- karma.conf.js | 64 + package.json | 114 +- interact.js => src/interact.js | 0 src/utils/window.js | 3 + test/fixtures/baseFixture.html | 1 + test/{ => fixtures}/data.js | 0 test/test.js | 84 +- 16 files changed, 6088 insertions(+), 178 deletions(-) create mode 100644 build/interact.js delete mode 100644 gulp/tasks/iconFont/generateIconSass.js delete mode 100644 gulp/tasks/iconFont/index.js delete mode 100644 gulp/tasks/iconFont/template.sass.swig create mode 100644 karma.conf.js rename interact.js => src/interact.js (100%) create mode 100644 src/utils/window.js create mode 100644 test/fixtures/baseFixture.html rename test/{ => fixtures}/data.js (100%) diff --git a/build/interact.js b/build/interact.js new file mode 100644 index 000000000..8ae176cd7 --- /dev/null +++ b/build/interact.js @@ -0,0 +1,5864 @@ +(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o + * Open source under the MIT License. + * https://raw.github.com/taye/interact.js/master/LICENSE + */ + +var realWindow = require('./utils/window'); + +// return early if there's no window to work with (eg. Node.js) +if (!realWindow) { return; } + +var // get wrapped window if using Shadow DOM polyfill + window = (function () { + // create a TextNode + var el = realWindow.document.createTextNode(''); + + // check if it's wrapped by a polyfill + if (el.ownerDocument !== realWindow.document + && typeof realWindow.wrap === 'function' + && realWindow.wrap(el) === el) { + // return wrapped window + return realWindow.wrap(realWindow); + } + + // no Shadow DOM polyfil or native implementation + return realWindow; + }()), + + document = window.document, + DocumentFragment = window.DocumentFragment || blank, + SVGElement = window.SVGElement || blank, + SVGSVGElement = window.SVGSVGElement || blank, + SVGElementInstance = window.SVGElementInstance || blank, + HTMLElement = window.HTMLElement || window.Element, + + PointerEvent = (window.PointerEvent || window.MSPointerEvent), + pEventTypes, + + hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); }, + + tmpXY = {}, // reduce object creation in getXY() + + documents = [], // all documents being listened to + + interactables = [], // all set interactables + interactions = [], // all interactions + + dynamicDrop = false, + +// { +// type: { +// selectors: ['selector', ...], +// contexts : [document, ...], +// listeners: [[listener, useCapture], ...] +// } +// } + delegatedEvents = {}, + + defaultOptions = { + base: { + accept : null, + actionChecker : null, + styleCursor : true, + preventDefault: 'auto', + origin : { x: 0, y: 0 }, + deltaSource : 'page', + allowFrom : null, + ignoreFrom : null, + _context : document, + dropChecker : null + }, + + drag: { + enabled: false, + manualStart: true, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + axis: 'xy', + }, + + drop: { + enabled: false, + accept: null, + overlap: 'pointer' + }, + + resize: { + enabled: false, + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + square: false, + axis: 'xy', + + // use default margin + margin: NaN, + + // object with props left, right, top, bottom which are + // true/false values to resize when the pointer is over that edge, + // CSS selectors to match the handles for each direction + // or the Elements for each handle + edges: null, + + // a value of 'none' will limit the resize rect to a minimum of 0x0 + // 'negate' will alow the rect to have negative width/height + // 'reposition' will keep the width/height positive by swapping + // the top and bottom edges and/or swapping the left and right edges + invert: 'none' + }, + + gesture: { + manualStart: false, + enabled: false, + max: Infinity, + maxPerElement: 1, + + restrict: null + }, + + perAction: { + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: { + enabled : false, + endOnly : false, + range : Infinity, + targets : null, + offsets : null, + + relativePoints: null + }, + + restrict: { + enabled: false, + endOnly: false + }, + + autoScroll: { + enabled : false, + container : null, // the item that is scrolled (Window or HTMLElement) + margin : 60, + speed : 300 // the scroll speed in pixels per second + }, + + inertia: { + enabled : false, + resistance : 10, // the lambda in exponential decay + minSpeed : 100, // target speed must be above this for inertia to start + endSpeed : 10, // the speed at which inertia is slow enough to stop + allowResume : true, // allow resuming an action in inertia phase + zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 + smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia + } + }, + + _holdDuration: 600 + }, + +// Things related to autoScroll + autoScroll = { + interaction: null, + i: null, // the handle returned by window.setInterval + x: 0, y: 0, // Direction each pulse is to scroll in + + // scroll the window by the values in scroll.x/y + scroll: function () { + var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll, + container = options.container || getWindow(autoScroll.interaction.element), + now = new Date().getTime(), + // change in time in seconds + dt = (now - autoScroll.prevTime) / 1000, + // displacement + s = options.speed * dt; + + if (s >= 1) { + if (isWindow(container)) { + container.scrollBy(autoScroll.x * s, autoScroll.y * s); + } + else if (container) { + container.scrollLeft += autoScroll.x * s; + container.scrollTop += autoScroll.y * s; + } + + autoScroll.prevTime = now; + } + + if (autoScroll.isScrolling) { + cancelFrame(autoScroll.i); + autoScroll.i = reqFrame(autoScroll.scroll); + } + }, + + isScrolling: false, + prevTime: 0, + + start: function (interaction) { + autoScroll.isScrolling = true; + cancelFrame(autoScroll.i); + + autoScroll.interaction = interaction; + autoScroll.prevTime = new Date().getTime(); + autoScroll.i = reqFrame(autoScroll.scroll); + }, + + stop: function () { + autoScroll.isScrolling = false; + cancelFrame(autoScroll.i); + } + }, + +// Does the browser support touch input? + supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch), + +// Does the browser support PointerEvents + supportsPointerEvent = !!PointerEvent, + +// Less Precision with touch input + margin = supportsTouch || supportsPointerEvent? 20: 10, + + pointerMoveTolerance = 1, + +// for ignoring browser's simulated mouse events + prevTouchTime = 0, + +// Allow this many interactions to happen simultaneously + maxInteractions = Infinity, + +// Check if is IE9 or older + actionCursors = (document.all && !window.atob) ? { + drag : 'move', + resizex : 'e-resize', + resizey : 's-resize', + resizexy: 'se-resize', + + resizetop : 'n-resize', + resizeleft : 'w-resize', + resizebottom : 's-resize', + resizeright : 'e-resize', + resizetopleft : 'se-resize', + resizebottomright: 'se-resize', + resizetopright : 'ne-resize', + resizebottomleft : 'ne-resize', + + gesture : '' + } : { + drag : 'move', + resizex : 'ew-resize', + resizey : 'ns-resize', + resizexy: 'nwse-resize', + + resizetop : 'ns-resize', + resizeleft : 'ew-resize', + resizebottom : 'ns-resize', + resizeright : 'ew-resize', + resizetopleft : 'nwse-resize', + resizebottomright: 'nwse-resize', + resizetopright : 'nesw-resize', + resizebottomleft : 'nesw-resize', + + gesture : '' + }, + + actionIsEnabled = { + drag : true, + resize : true, + gesture: true + }, + +// because Webkit and Opera still use 'mousewheel' event type + wheelEvent = 'onmousewheel' in document? 'mousewheel': 'wheel', + + eventTypes = [ + 'dragstart', + 'dragmove', + 'draginertiastart', + 'dragend', + 'dragenter', + 'dragleave', + 'dropactivate', + 'dropdeactivate', + 'dropmove', + 'drop', + 'resizestart', + 'resizemove', + 'resizeinertiastart', + 'resizeend', + 'gesturestart', + 'gesturemove', + 'gestureinertiastart', + 'gestureend', + + 'down', + 'move', + 'up', + 'cancel', + 'tap', + 'doubletap', + 'hold' + ], + + globalEvents = {}, + +// Opera Mobile must be handled differently + isOperaMobile = navigator.appName == 'Opera' && + supportsTouch && + navigator.userAgent.match('Presto'), + +// scrolling doesn't change the result of +// getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 + isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) + && /OS [1-7][^\d]/.test(navigator.appVersion)), + +// prefix matchesSelector + prefixedMatchesSelector = 'matches' in Element.prototype? + 'matches': 'webkitMatchesSelector' in Element.prototype? + 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? + 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? + 'oMatchesSelector': 'msMatchesSelector', + +// will be polyfill function if browser is IE8 + ie8MatchesSelector, + +// native requestAnimationFrame or polyfill + reqFrame = realWindow.requestAnimationFrame, + cancelFrame = realWindow.cancelAnimationFrame, + +// Events wrapper + events = (function () { + var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window), + addEvent = useAttachEvent? 'attachEvent': 'addEventListener', + removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', + on = useAttachEvent? 'on': '', + + elements = [], + targets = [], + attachedListeners = []; + + function add (element, type, listener, useCapture) { + var elementIndex = indexOf(elements, element), + target = targets[elementIndex]; + + if (!target) { + target = { + events: {}, + typeCount: 0 + }; + + elementIndex = elements.push(element) - 1; + targets.push(target); + + attachedListeners.push((useAttachEvent ? { + supplied: [], + wrapped : [], + useCount: [] + } : null)); + } + + if (!target.events[type]) { + target.events[type] = []; + target.typeCount++; + } + + if (!contains(target.events[type], listener)) { + var ret; + + if (useAttachEvent) { + var listeners = attachedListeners[elementIndex], + listenerIndex = indexOf(listeners.supplied, listener); + + var wrapped = listeners.wrapped[listenerIndex] || function (event) { + if (!event.immediatePropagationStopped) { + event.target = event.srcElement; + event.currentTarget = element; + + event.preventDefault = event.preventDefault || preventDef; + event.stopPropagation = event.stopPropagation || stopProp; + event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; + + if (/mouse|click/.test(event.type)) { + event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; + event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; + } + + listener(event); + } + }; + + ret = element[addEvent](on + type, wrapped, Boolean(useCapture)); + + if (listenerIndex === -1) { + listeners.supplied.push(listener); + listeners.wrapped.push(wrapped); + listeners.useCount.push(1); + } + else { + listeners.useCount[listenerIndex]++; + } + } + else { + ret = element[addEvent](type, listener, useCapture || false); + } + target.events[type].push(listener); + + return ret; + } + } + + function remove (element, type, listener, useCapture) { + var i, + elementIndex = indexOf(elements, element), + target = targets[elementIndex], + listeners, + listenerIndex, + wrapped = listener; + + if (!target || !target.events) { + return; + } + + if (useAttachEvent) { + listeners = attachedListeners[elementIndex]; + listenerIndex = indexOf(listeners.supplied, listener); + wrapped = listeners.wrapped[listenerIndex]; + } + + if (type === 'all') { + for (type in target.events) { + if (target.events.hasOwnProperty(type)) { + remove(element, type, 'all'); + } + } + return; + } + + if (target.events[type]) { + var len = target.events[type].length; + + if (listener === 'all') { + for (i = 0; i < len; i++) { + remove(element, type, target.events[type][i], Boolean(useCapture)); + } + return; + } else { + for (i = 0; i < len; i++) { + if (target.events[type][i] === listener) { + element[removeEvent](on + type, wrapped, useCapture || false); + target.events[type].splice(i, 1); + + if (useAttachEvent && listeners) { + listeners.useCount[listenerIndex]--; + if (listeners.useCount[listenerIndex] === 0) { + listeners.supplied.splice(listenerIndex, 1); + listeners.wrapped.splice(listenerIndex, 1); + listeners.useCount.splice(listenerIndex, 1); + } + } + + break; + } + } + } + + if (target.events[type] && target.events[type].length === 0) { + target.events[type] = null; + target.typeCount--; + } + } + + if (!target.typeCount) { + targets.splice(elementIndex, 1); + elements.splice(elementIndex, 1); + attachedListeners.splice(elementIndex, 1); + } + } + + function preventDef () { + this.returnValue = false; + } + + function stopProp () { + this.cancelBubble = true; + } + + function stopImmProp () { + this.cancelBubble = true; + this.immediatePropagationStopped = true; + } + + return { + add: add, + remove: remove, + useAttachEvent: useAttachEvent, + + _elements: elements, + _targets: targets, + _attachedListeners: attachedListeners + }; + }()); + +function blank () {} + +function isElement (o) { + if (!o || (typeof o !== 'object')) { return false; } + + var _window = getWindow(o) || window; + + return (/object|function/.test(typeof _window.Element) + ? o instanceof _window.Element //DOM2 + : o.nodeType === 1 && typeof o.nodeName === "string"); +} +function isWindow (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); } +function isDocFrag (thing) { return !!thing && thing instanceof DocumentFragment; } +function isArray (thing) { + return isObject(thing) + && (typeof thing.length !== undefined) + && isFunction(thing.splice); +} +function isObject (thing) { return !!thing && (typeof thing === 'object'); } +function isFunction (thing) { return typeof thing === 'function'; } +function isNumber (thing) { return typeof thing === 'number' ; } +function isBool (thing) { return typeof thing === 'boolean' ; } +function isString (thing) { return typeof thing === 'string' ; } + +function trySelector (value) { + if (!isString(value)) { return false; } + + // an exception will be raised if it is invalid + document.querySelector(value); + return true; +} + +function extend (dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + return dest; +} + +function copyCoords (dest, src) { + dest.page = dest.page || {}; + dest.page.x = src.page.x; + dest.page.y = src.page.y; + + dest.client = dest.client || {}; + dest.client.x = src.client.x; + dest.client.y = src.client.y; + + dest.timeStamp = src.timeStamp; +} + +function setEventXY (targetObj, pointer, interaction) { + if (!pointer) { + if (interaction.pointerIds.length > 1) { + pointer = touchAverage(interaction.pointers); + } + else { + pointer = interaction.pointers[0]; + } + } + + getPageXY(pointer, tmpXY, interaction); + targetObj.page.x = tmpXY.x; + targetObj.page.y = tmpXY.y; + + getClientXY(pointer, tmpXY, interaction); + targetObj.client.x = tmpXY.x; + targetObj.client.y = tmpXY.y; + + targetObj.timeStamp = new Date().getTime(); +} + +function setEventDeltas (targetObj, prev, cur) { + targetObj.page.x = cur.page.x - prev.page.x; + targetObj.page.y = cur.page.y - prev.page.y; + targetObj.client.x = cur.client.x - prev.client.x; + targetObj.client.y = cur.client.y - prev.client.y; + targetObj.timeStamp = new Date().getTime() - prev.timeStamp; + + // set pointer velocity + var dt = Math.max(targetObj.timeStamp / 1000, 0.001); + targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; + targetObj.page.vx = targetObj.page.x / dt; + targetObj.page.vy = targetObj.page.y / dt; + + targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; + targetObj.client.vx = targetObj.client.x / dt; + targetObj.client.vy = targetObj.client.y / dt; +} + +// Get specified X/Y coords for mouse or event.touches[0] +function getXY (type, pointer, xy) { + xy = xy || {}; + type = type || 'page'; + + xy.x = pointer[type + 'X']; + xy.y = pointer[type + 'Y']; + + return xy; +} + +function getPageXY (pointer, page, interaction) { + page = page || {}; + + if (pointer instanceof InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + interaction = interaction || pointer.interaction; + + extend(page, interaction.inertiaStatus.upCoords.page); + + page.x += interaction.inertiaStatus.sx; + page.y += interaction.inertiaStatus.sy; + } + else { + page.x = pointer.pageX; + page.y = pointer.pageY; + } + } + // Opera Mobile handles the viewport and scrolling oddly + else if (isOperaMobile) { + getXY('screen', pointer, page); + + page.x += window.scrollX; + page.y += window.scrollY; + } + else { + getXY('page', pointer, page); + } + + return page; +} + +function getClientXY (pointer, client, interaction) { + client = client || {}; + + if (pointer instanceof InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + extend(client, interaction.inertiaStatus.upCoords.client); + + client.x += interaction.inertiaStatus.sx; + client.y += interaction.inertiaStatus.sy; + } + else { + client.x = pointer.clientX; + client.y = pointer.clientY; + } + } + else { + // Opera Mobile handles the viewport and scrolling oddly + getXY(isOperaMobile? 'screen': 'client', pointer, client); + } + + return client; +} + +function getScrollXY (win) { + win = win || window; + return { + x: win.scrollX || win.document.documentElement.scrollLeft, + y: win.scrollY || win.document.documentElement.scrollTop + }; +} + +function getPointerId (pointer) { + return isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; +} + +function getActualElement (element) { + return (element instanceof SVGElementInstance + ? element.correspondingUseElement + : element); +} + +function getWindow (node) { + if (isWindow(node)) { + return node; + } + + var rootNode = (node.ownerDocument || node); + + return rootNode.defaultView || rootNode.parentWindow || window; +} + +function getElementRect (element) { + var scroll = isIOS7orLower + ? { x: 0, y: 0 } + : getScrollXY(getWindow(element)), + clientRect = (element instanceof SVGElement)? + element.getBoundingClientRect(): + element.getClientRects()[0]; + + return clientRect && { + left : clientRect.left + scroll.x, + right : clientRect.right + scroll.x, + top : clientRect.top + scroll.y, + bottom: clientRect.bottom + scroll.y, + width : clientRect.width || clientRect.right - clientRect.left, + height: clientRect.heigh || clientRect.bottom - clientRect.top + }; +} + +function getTouchPair (event) { + var touches = []; + + // array of touches is supplied + if (isArray(event)) { + touches[0] = event[0]; + touches[1] = event[1]; + } + // an event + else { + if (event.type === 'touchend') { + if (event.touches.length === 1) { + touches[0] = event.touches[0]; + touches[1] = event.changedTouches[0]; + } + else if (event.touches.length === 0) { + touches[0] = event.changedTouches[0]; + touches[1] = event.changedTouches[1]; + } + } + else { + touches[0] = event.touches[0]; + touches[1] = event.touches[1]; + } + } + + return touches; +} + +function touchAverage (event) { + var touches = getTouchPair(event); + + return { + pageX: (touches[0].pageX + touches[1].pageX) / 2, + pageY: (touches[0].pageY + touches[1].pageY) / 2, + clientX: (touches[0].clientX + touches[1].clientX) / 2, + clientY: (touches[0].clientY + touches[1].clientY) / 2 + }; +} + +function touchBBox (event) { + if (!event.length && !(event.touches && event.touches.length > 1)) { + return; + } + + var touches = getTouchPair(event), + minX = Math.min(touches[0].pageX, touches[1].pageX), + minY = Math.min(touches[0].pageY, touches[1].pageY), + maxX = Math.max(touches[0].pageX, touches[1].pageX), + maxY = Math.max(touches[0].pageY, touches[1].pageY); + + return { + x: minX, + y: minY, + left: minX, + top: minY, + width: maxX - minX, + height: maxY - minY + }; +} + +function touchDistance (event, deltaSource) { + deltaSource = deltaSource || defaultOptions.deltaSource; + + var sourceX = deltaSource + 'X', + sourceY = deltaSource + 'Y', + touches = getTouchPair(event); + + + var dx = touches[0][sourceX] - touches[1][sourceX], + dy = touches[0][sourceY] - touches[1][sourceY]; + + return hypot(dx, dy); +} + +function touchAngle (event, prevAngle, deltaSource) { + deltaSource = deltaSource || defaultOptions.deltaSource; + + var sourceX = deltaSource + 'X', + sourceY = deltaSource + 'Y', + touches = getTouchPair(event), + dx = touches[0][sourceX] - touches[1][sourceX], + dy = touches[0][sourceY] - touches[1][sourceY], + angle = 180 * Math.atan(dy / dx) / Math.PI; + + if (isNumber(prevAngle)) { + var dr = angle - prevAngle, + drClamped = dr % 360; + + if (drClamped > 315) { + angle -= 360 + (angle / 360)|0 * 360; + } + else if (drClamped > 135) { + angle -= 180 + (angle / 360)|0 * 360; + } + else if (drClamped < -315) { + angle += 360 + (angle / 360)|0 * 360; + } + else if (drClamped < -135) { + angle += 180 + (angle / 360)|0 * 360; + } + } + + return angle; +} + +function getOriginXY (interactable, element) { + var origin = interactable + ? interactable.options.origin + : defaultOptions.origin; + + if (origin === 'parent') { + origin = parentElement(element); + } + else if (origin === 'self') { + origin = interactable.getRect(element); + } + else if (trySelector(origin)) { + origin = closest(element, origin) || { x: 0, y: 0 }; + } + + if (isFunction(origin)) { + origin = origin(interactable && element); + } + + if (isElement(origin)) { + origin = getElementRect(origin); + } + + origin.x = ('x' in origin)? origin.x : origin.left; + origin.y = ('y' in origin)? origin.y : origin.top; + + return origin; +} + +// http://stackoverflow.com/a/5634528/2280888 +function _getQBezierValue(t, p1, p2, p3) { + var iT = 1 - t; + return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; +} + +function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) { + return { + x: _getQBezierValue(position, startX, cpX, endX), + y: _getQBezierValue(position, startY, cpY, endY) + }; +} + +// http://gizma.com/easing/ +function easeOutQuad (t, b, c, d) { + t /= d; + return -c * t*(t-2) + b; +} + +function nodeContains (parent, child) { + while (child) { + if (child === parent) { + return true; + } + + child = child.parentNode; + } + + return false; +} + +function closest (child, selector) { + var parent = parentElement(child); + + while (isElement(parent)) { + if (matchesSelector(parent, selector)) { return parent; } + + parent = parentElement(parent); + } + + return null; +} + +function parentElement (node) { + var parent = node.parentNode; + + if (isDocFrag(parent)) { + // skip past #shado-root fragments + while ((parent = parent.host) && isDocFrag(parent)) {} + + return parent; + } + + return parent; +} + +function inContext (interactable, element) { + return interactable._context === element.ownerDocument + || nodeContains(interactable._context, element); +} + +function testIgnore (interactable, interactableElement, element) { + var ignoreFrom = interactable.options.ignoreFrom; + + if (!ignoreFrom || !isElement(element)) { return false; } + + if (isString(ignoreFrom)) { + return matchesUpTo(element, ignoreFrom, interactableElement); + } + else if (isElement(ignoreFrom)) { + return nodeContains(ignoreFrom, element); + } + + return false; +} + +function testAllow (interactable, interactableElement, element) { + var allowFrom = interactable.options.allowFrom; + + if (!allowFrom) { return true; } + + if (!isElement(element)) { return false; } + + if (isString(allowFrom)) { + return matchesUpTo(element, allowFrom, interactableElement); + } + else if (isElement(allowFrom)) { + return nodeContains(allowFrom, element); + } + + return false; +} + +function checkAxis (axis, interactable) { + if (!interactable) { return false; } + + var thisAxis = interactable.options.drag.axis; + + return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); +} + +function checkSnap (interactable, action) { + var options = interactable.options; + + if (/^resize/.test(action)) { + action = 'resize'; + } + + return options[action].snap && options[action].snap.enabled; +} + +function checkRestrict (interactable, action) { + var options = interactable.options; + + if (/^resize/.test(action)) { + action = 'resize'; + } + + return options[action].restrict && options[action].restrict.enabled; +} + +function checkAutoScroll (interactable, action) { + var options = interactable.options; + + if (/^resize/.test(action)) { + action = 'resize'; + } + + return options[action].autoScroll && options[action].autoScroll.enabled; +} + +function withinInteractionLimit (interactable, element, action) { + var options = interactable.options, + maxActions = options[action.name].max, + maxPerElement = options[action.name].maxPerElement, + activeInteractions = 0, + targetCount = 0, + targetElementCount = 0; + + for (var i = 0, len = interactions.length; i < len; i++) { + var interaction = interactions[i], + otherAction = interaction.prepared.name, + active = interaction.interacting(); + + if (!active) { continue; } + + activeInteractions++; + + if (activeInteractions >= maxInteractions) { + return false; + } + + if (interaction.target !== interactable) { continue; } + + targetCount += (otherAction === action.name)|0; + + if (targetCount >= maxActions) { + return false; + } + + if (interaction.element === element) { + targetElementCount++; + + if (otherAction !== action.name || targetElementCount >= maxPerElement) { + return false; + } + } + } + + return maxInteractions > 0; +} + +// Test for the element that's "above" all other qualifiers +function indexOfDeepestElement (elements) { + var dropzone, + deepestZone = elements[0], + index = deepestZone? 0: -1, + parent, + deepestZoneParents = [], + dropzoneParents = [], + child, + i, + n; + + for (i = 1; i < elements.length; i++) { + dropzone = elements[i]; + + // an element might belong to multiple selector dropzones + if (!dropzone || dropzone === deepestZone) { + continue; + } + + if (!deepestZone) { + deepestZone = dropzone; + index = i; + continue; + } + + // check if the deepest or current are document.documentElement or document.rootElement + // - if the current dropzone is, do nothing and continue + if (dropzone.parentNode === dropzone.ownerDocument) { + continue; + } + // - if deepest is, update with the current dropzone and continue to next + else if (deepestZone.parentNode === dropzone.ownerDocument) { + deepestZone = dropzone; + index = i; + continue; + } + + if (!deepestZoneParents.length) { + parent = deepestZone; + while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { + deepestZoneParents.unshift(parent); + parent = parent.parentNode; + } + } + + // if this element is an svg element and the current deepest is + // an HTMLElement + if (deepestZone instanceof HTMLElement + && dropzone instanceof SVGElement + && !(dropzone instanceof SVGSVGElement)) { + + if (dropzone === deepestZone.parentNode) { + continue; + } + + parent = dropzone.ownerSVGElement; + } + else { + parent = dropzone; + } + + dropzoneParents = []; + + while (parent.parentNode !== parent.ownerDocument) { + dropzoneParents.unshift(parent); + parent = parent.parentNode; + } + + n = 0; + + // get (position of last common ancestor) + 1 + while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { + n++; + } + + var parents = [ + dropzoneParents[n - 1], + dropzoneParents[n], + deepestZoneParents[n] + ]; + + child = parents[0].lastChild; + + while (child) { + if (child === parents[1]) { + deepestZone = dropzone; + index = i; + deepestZoneParents = []; + + break; + } + else if (child === parents[2]) { + break; + } + + child = child.previousSibling; + } + } + + return index; +} + +function Interaction () { + this.target = null; // current interactable being interacted with + this.element = null; // the target element of the interactable + this.dropTarget = null; // the dropzone a drag target might be dropped into + this.dropElement = null; // the element at the time of checking + this.prevDropTarget = null; // the dropzone that was recently dragged away from + this.prevDropElement = null; // the element at the time of checking + + this.prepared = { // action that's ready to be fired on next move event + name : null, + axis : null, + edges: null + }; + + this.matches = []; // all selectors that are matched by target element + this.matchElements = []; // corresponding elements + + this.inertiaStatus = { + active : false, + smoothEnd : false, + + startEvent: null, + upCoords: {}, + + xe: 0, ye: 0, + sx: 0, sy: 0, + + t0: 0, + vx0: 0, vys: 0, + duration: 0, + + resumeDx: 0, + resumeDy: 0, + + lambda_v0: 0, + one_ve_v0: 0, + i : null + }; + + if (isFunction(Function.prototype.bind)) { + this.boundInertiaFrame = this.inertiaFrame.bind(this); + this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); + } + else { + var that = this; + + this.boundInertiaFrame = function () { return that.inertiaFrame(); }; + this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); }; + } + + this.activeDrops = { + dropzones: [], // the dropzones that are mentioned below + elements : [], // elements of dropzones that accept the target draggable + rects : [] // the rects of the elements mentioned above + }; + + // keep track of added pointers + this.pointers = []; + this.pointerIds = []; + this.downTargets = []; + this.downTimes = []; + this.holdTimers = []; + + // Previous native pointer move event coordinates + this.prevCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + // current native pointer move event coordinates + this.curCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + + // Starting InteractEvent pointer coordinates + this.startCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + + // Change in coordinates and time of the pointer + this.pointerDelta = { + page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + timeStamp: 0 + }; + + this.downEvent = null; // pointerdown/mousedown/touchstart event + this.downPointer = {}; + + this._eventTarget = null; + this._curEventTarget = null; + + this.prevEvent = null; // previous action event + this.tapTime = 0; // time of the most recent tap event + this.prevTap = null; + + this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.snapOffsets = []; + + this.gesture = { + start: { x: 0, y: 0 }, + + startDistance: 0, // distance between two touches of touchStart + prevDistance : 0, + distance : 0, + + scale: 1, // gesture.distance / gesture.startDistance + + startAngle: 0, // angle of line joining two touches + prevAngle : 0 // angle of the previous gesture event + }; + + this.snapStatus = { + x : 0, y : 0, + dx : 0, dy : 0, + realX : 0, realY : 0, + snappedX: 0, snappedY: 0, + targets : [], + locked : false, + changed : false + }; + + this.restrictStatus = { + dx : 0, dy : 0, + restrictedX: 0, restrictedY: 0, + snap : null, + restricted : false, + changed : false + }; + + this.restrictStatus.snap = this.snapStatus; + + this.pointerIsDown = false; + this.pointerWasMoved = false; + this.gesturing = false; + this.dragging = false; + this.resizing = false; + this.resizeAxes = 'xy'; + + this.mouse = false; + + interactions.push(this); +} + +Interaction.prototype = { + getPageXY : function (pointer, xy) { return getPageXY(pointer, xy, this); }, + getClientXY: function (pointer, xy) { return getClientXY(pointer, xy, this); }, + setEventXY : function (target, ptr) { return setEventXY(target, ptr, this); }, + + pointerOver: function (pointer, event, eventTarget) { + if (this.prepared.name || !this.mouse) { return; } + + var curMatches = [], + curMatchElements = [], + prevTargetElement = this.element; + + this.addPointer(pointer); + + if (this.target + && (testIgnore(this.target, this.element, eventTarget) + || !testAllow(this.target, this.element, eventTarget))) { + // if the eventTarget should be ignored or shouldn't be allowed + // clear the previous target + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; + } + + var elementInteractable = interactables.get(eventTarget), + elementAction = (elementInteractable + && !testIgnore(elementInteractable, eventTarget, eventTarget) + && testAllow(elementInteractable, eventTarget, eventTarget) + && validateAction( + elementInteractable.getAction(pointer, event, this, eventTarget), + elementInteractable)); + + if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { + elementAction = null; + } + + function pushCurMatches (interactable, selector) { + if (interactable + && inContext(interactable, eventTarget) + && !testIgnore(interactable, eventTarget, eventTarget) + && testAllow(interactable, eventTarget, eventTarget) + && matchesSelector(eventTarget, selector)) { + + curMatches.push(interactable); + curMatchElements.push(eventTarget); + } + } + + if (elementAction) { + this.target = elementInteractable; + this.element = eventTarget; + this.matches = []; + this.matchElements = []; + } + else { + interactables.forEachSelector(pushCurMatches); + + if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { + this.matches = curMatches; + this.matchElements = curMatchElements; + + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(eventTarget, + PointerEvent? pEventTypes.move : 'mousemove', + listeners.pointerHover); + } + else if (this.target) { + if (nodeContains(prevTargetElement, eventTarget)) { + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(this.element, + PointerEvent? pEventTypes.move : 'mousemove', + listeners.pointerHover); + } + else { + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; + } + } + } + }, + + // Check what action would be performed on pointerMove target if a mouse + // button were pressed and change the cursor accordingly + pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { + var target = this.target; + + if (!this.prepared.name && this.mouse) { + + var action; + + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); + + if (matches) { + action = this.validateSelector(pointer, event, matches, matchElements); + } + else if (target) { + action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); + } + + if (target && target.options.styleCursor) { + if (action) { + target._doc.documentElement.style.cursor = getActionCursor(action); + } + else { + target._doc.documentElement.style.cursor = ''; + } + } + } + else if (this.prepared.name) { + this.checkAndPreventDefault(event, target, this.element); + } + }, + + pointerOut: function (pointer, event, eventTarget) { + if (this.prepared.name) { return; } + + // Remove temporary event listeners for selector Interactables + if (!interactables.get(eventTarget)) { + events.remove(eventTarget, + PointerEvent? pEventTypes.move : 'mousemove', + listeners.pointerHover); + } + + if (this.target && this.target.options.styleCursor && !this.interacting()) { + this.target._doc.documentElement.style.cursor = ''; + } + }, + + selectorDown: function (pointer, event, eventTarget, curEventTarget) { + var that = this, + // copy event to be used in timeout for IE8 + eventCopy = events.useAttachEvent? extend({}, event) : event, + element = eventTarget, + pointerIndex = this.addPointer(pointer), + action; + + this.holdTimers[pointerIndex] = setTimeout(function () { + that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); + }, defaultOptions._holdDuration); + + this.pointerIsDown = true; + + // Check if the down event hits the current inertia target + if (this.inertiaStatus.active && this.target.selector) { + // climb up the DOM tree from the event target + while (isElement(element)) { + + // if this element is the current inertia target element + if (element === this.element + // and the prospective action is the same as the ongoing one + && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { + + // stop inertia so that the next move will be a normal one + cancelFrame(this.inertiaStatus.i); + this.inertiaStatus.active = false; + + this.collectEventTargets(pointer, event, eventTarget, 'down'); + return; + } + element = parentElement(element); + } + } + + // do nothing if interacting + if (this.interacting()) { + this.collectEventTargets(pointer, event, eventTarget, 'down'); + return; + } + + function pushMatches (interactable, selector, context) { + var elements = ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; + + if (inContext(interactable, element) + && !testIgnore(interactable, element, eventTarget) + && testAllow(interactable, element, eventTarget) + && matchesSelector(element, selector, elements)) { + + that.matches.push(interactable); + that.matchElements.push(element); + } + } + + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); + this.downEvent = event; + + while (isElement(element) && !action) { + this.matches = []; + this.matchElements = []; + + interactables.forEachSelector(pushMatches); + + action = this.validateSelector(pointer, event, this.matches, this.matchElements); + element = parentElement(element); + } + + if (action) { + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + + this.collectEventTargets(pointer, event, eventTarget, 'down'); + + return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); + } + else { + // do these now since pointerDown isn't being called from here + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + extend(this.downPointer, pointer); + + copyCoords(this.prevCoords, this.curCoords); + this.pointerWasMoved = false; + } + + this.collectEventTargets(pointer, event, eventTarget, 'down'); + }, + + // Determine action to be performed on next pointerMove and add appropriate + // style and event Listeners + pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { + if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { + this.checkAndPreventDefault(event, this.target, this.element); + + return; + } + + this.pointerIsDown = true; + this.downEvent = event; + + var pointerIndex = this.addPointer(pointer), + action; + + // If it is the second touch of a multi-touch gesture, keep the target + // the same if a target was set by the first touch + // Otherwise, set the target if there is no action prepared + if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { + + var interactable = interactables.get(curEventTarget); + + if (interactable + && !testIgnore(interactable, curEventTarget, eventTarget) + && testAllow(interactable, curEventTarget, eventTarget) + && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) + && withinInteractionLimit(interactable, curEventTarget, action)) { + this.target = interactable; + this.element = curEventTarget; + } + } + + var target = this.target, + options = target && target.options; + + if (target && (forceAction || !this.prepared.name)) { + action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); + + this.setEventXY(this.startCoords); + + if (!action) { return; } + + if (options.styleCursor) { + target._doc.documentElement.style.cursor = getActionCursor(action); + } + + this.resizeAxes = action.name === 'resize'? action.axis : null; + + if (action === 'gesture' && this.pointerIds.length < 2) { + action = null; + } + + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + + this.snapStatus.snappedX = this.snapStatus.snappedY = + this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN; + + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + extend(this.downPointer, pointer); + + this.setEventXY(this.prevCoords); + this.pointerWasMoved = false; + + this.checkAndPreventDefault(event, target, this.element); + } + // if inertia is active try to resume action + else if (this.inertiaStatus.active + && curEventTarget === this.element + && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { + + cancelFrame(this.inertiaStatus.i); + this.inertiaStatus.active = false; + + this.checkAndPreventDefault(event, target, this.element); + } + }, + + setModifications: function (coords, preEnd) { + var target = this.target, + shouldMove = true, + shouldSnap = checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), + shouldRestrict = checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); + + if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } + if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } + + if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { + shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; + } + else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) { + shouldMove = false; + } + + return shouldMove; + }, + + setStartOffsets: function (action, interactable, element) { + var rect = interactable.getRect(element), + origin = getOriginXY(interactable, element), + snap = interactable.options[this.prepared.name].snap, + restrict = interactable.options[this.prepared.name].restrict, + width, height; + + if (rect) { + this.startOffset.left = this.startCoords.page.x - rect.left; + this.startOffset.top = this.startCoords.page.y - rect.top; + + this.startOffset.right = rect.right - this.startCoords.page.x; + this.startOffset.bottom = rect.bottom - this.startCoords.page.y; + + if ('width' in rect) { width = rect.width; } + else { width = rect.right - rect.left; } + if ('height' in rect) { height = rect.height; } + else { height = rect.bottom - rect.top; } + } + else { + this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; + } + + this.snapOffsets.splice(0); + + var snapOffset = snap && snap.offset === 'startCoords' + ? { + x: this.startCoords.page.x - origin.x, + y: this.startCoords.page.y - origin.y + } + : snap && snap.offset || { x: 0, y: 0 }; + + if (rect && snap && snap.relativePoints && snap.relativePoints.length) { + for (var i = 0; i < snap.relativePoints.length; i++) { + this.snapOffsets.push({ + x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x, + y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y + }); + } + } + else { + this.snapOffsets.push(snapOffset); + } + + if (rect && restrict.elementRect) { + this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left); + this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top); + + this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right)); + this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom)); + } + else { + this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0; + } + }, + + /*\ + * Interaction.start + [ method ] + * + * Start an action with the given Interactable and Element as tartgets. The + * action must be enabled for the target Interactable and an appropriate number + * of pointers must be held down – 1 for drag/resize, 2 for gesture. + * + * Use it with `interactable.able({ manualStart: false })` to always + * [start actions manually](https://github.com/taye/interact.js/issues/114) + * + - action (object) The action to be performed - drag, resize, etc. + - interactable (Interactable) The Interactable to target + - element (Element) The DOM Element to target + = (object) interact + ** + | interact(target) + | .draggable({ + | // disable the default drag start by down->move + | manualStart: true + | }) + | // start dragging after the user holds the pointer down + | .on('hold', function (event) { + | var interaction = event.interaction; + | + | if (!interaction.interacting()) { + | interaction.start({ name: 'drag' }, + | event.interactable, + | event.currentTarget); + | } + | }); + \*/ + start: function (action, interactable, element) { + if (this.interacting() + || !this.pointerIsDown + || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { + return; + } + + // if this interaction had been removed after stopping + // add it back + if (indexOf(interactions, this) === -1) { + interactions.push(this); + } + + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + this.target = interactable; + this.element = element; + + this.setEventXY(this.startCoords); + this.setStartOffsets(action.name, interactable, element); + this.setModifications(this.startCoords.page); + + this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); + }, + + pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { + this.recordPointer(pointer); + + this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) + ? this.inertiaStatus.startEvent + : undefined); + + var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x + && this.curCoords.page.y === this.prevCoords.page.y + && this.curCoords.client.x === this.prevCoords.client.x + && this.curCoords.client.y === this.prevCoords.client.y); + + var dx, dy, + pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + + // register movement greater than pointerMoveTolerance + if (this.pointerIsDown && !this.pointerWasMoved) { + dx = this.curCoords.client.x - this.startCoords.client.x; + dy = this.curCoords.client.y - this.startCoords.client.y; + + this.pointerWasMoved = hypot(dx, dy) > pointerMoveTolerance; + } + + if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { + if (this.pointerIsDown) { + clearTimeout(this.holdTimers[pointerIndex]); + } + + this.collectEventTargets(pointer, event, eventTarget, 'move'); + } + + if (!this.pointerIsDown) { return; } + + if (duplicateMove && this.pointerWasMoved && !preEnd) { + this.checkAndPreventDefault(event, this.target, this.element); + return; + } + + // set pointer coordinate, time changes and speeds + setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + + if (!this.prepared.name) { return; } + + if (this.pointerWasMoved + // ignore movement while inertia is active + && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { + + // if just starting an action, calculate the pointer speed now + if (!this.interacting()) { + setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + + // check if a drag is in the correct axis + if (this.prepared.name === 'drag') { + var absX = Math.abs(dx), + absY = Math.abs(dy), + targetAxis = this.target.options.drag.axis, + axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); + + // if the movement isn't in the axis of the interactable + if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { + // cancel the prepared action + this.prepared.name = null; + + // then try to get a drag from another ineractable + + var element = eventTarget; + + // check element interactables + while (isElement(element)) { + var elementInteractable = interactables.get(element); + + if (elementInteractable + && elementInteractable !== this.target + && !elementInteractable.options.drag.manualStart + && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' + && checkAxis(axis, elementInteractable)) { + + this.prepared.name = 'drag'; + this.target = elementInteractable; + this.element = element; + break; + } + + element = parentElement(element); + } + + // if there's no drag from element interactables, + // check the selector interactables + if (!this.prepared.name) { + var thisInteraction = this; + + var getDraggable = function (interactable, selector, context) { + var elements = ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; + + if (interactable === thisInteraction.target) { return; } + + if (inContext(interactable, eventTarget) + && !interactable.options.drag.manualStart + && !testIgnore(interactable, element, eventTarget) + && testAllow(interactable, element, eventTarget) + && matchesSelector(element, selector, elements) + && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' + && checkAxis(axis, interactable) + && withinInteractionLimit(interactable, element, 'drag')) { + + return interactable; + } + }; + + element = eventTarget; + + while (isElement(element)) { + var selectorInteractable = interactables.forEachSelector(getDraggable); + + if (selectorInteractable) { + this.prepared.name = 'drag'; + this.target = selectorInteractable; + this.element = element; + break; + } + + element = parentElement(element); + } + } + } + } + } + + var starting = !!this.prepared.name && !this.interacting(); + + if (starting + && (this.target.options[this.prepared.name].manualStart + || !withinInteractionLimit(this.target, this.element, this.prepared))) { + this.stop(); + return; + } + + if (this.prepared.name && this.target) { + if (starting) { + this.start(this.prepared, this.target, this.element); + } + + var shouldMove = this.setModifications(this.curCoords.page, preEnd); + + // move if snapping or restriction doesn't prevent it + if (shouldMove || starting) { + this.prevEvent = this[this.prepared.name + 'Move'](event); + } + + this.checkAndPreventDefault(event, this.target, this.element); + } + } + + copyCoords(this.prevCoords, this.curCoords); + + if (this.dragging || this.resizing) { + this.autoScrollMove(pointer); + } + }, + + dragStart: function (event) { + var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); + + this.dragging = true; + this.target.fire(dragEvent); + + // reset active dropzones + this.activeDrops.dropzones = []; + this.activeDrops.elements = []; + this.activeDrops.rects = []; + + if (!this.dynamicDrop) { + this.setActiveDrops(this.element); + } + + var dropEvents = this.getDropEvents(event, dragEvent); + + if (dropEvents.activate) { + this.fireActiveDrops(dropEvents.activate); + } + + return dragEvent; + }, + + dragMove: function (event) { + var target = this.target, + dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), + draggableElement = this.element, + drop = this.getDrop(event, draggableElement); + + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; + + var dropEvents = this.getDropEvents(event, dragEvent); + + target.fire(dragEvent); + + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } + + this.prevDropTarget = this.dropTarget; + this.prevDropElement = this.dropElement; + + return dragEvent; + }, + + resizeStart: function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); + + if (this.prepared.edges) { + var startRect = this.target.getRect(this.element); + + if (this.target.options.resize.square) { + var squareEdges = extend({}, this.prepared.edges); + + squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); + squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); + squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); + squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); + + this.prepared._squareEdges = squareEdges; + } + else { + this.prepared._squareEdges = null; + } + + this.resizeRects = { + start : startRect, + current : extend({}, startRect), + restricted: extend({}, startRect), + previous : extend({}, startRect), + delta : { + left: 0, right : 0, width : 0, + top : 0, bottom: 0, height: 0 + } + }; + + resizeEvent.rect = this.resizeRects.restricted; + resizeEvent.deltaRect = this.resizeRects.delta; + } + + this.target.fire(resizeEvent); + + this.resizing = true; + + return resizeEvent; + }, + + resizeMove: function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); + + var edges = this.prepared.edges, + invert = this.target.options.resize.invert, + invertible = invert === 'reposition' || invert === 'negate'; + + if (edges) { + var dx = resizeEvent.dx, + dy = resizeEvent.dy, + + start = this.resizeRects.start, + current = this.resizeRects.current, + restricted = this.resizeRects.restricted, + delta = this.resizeRects.delta, + previous = extend(this.resizeRects.previous, restricted); + + if (this.target.options.resize.square) { + var originalEdges = edges; + + edges = this.prepared._squareEdges; + + if ((originalEdges.left && originalEdges.bottom) + || (originalEdges.right && originalEdges.top)) { + dy = -dx; + } + else if (originalEdges.left || originalEdges.right) { dy = dx; } + else if (originalEdges.top || originalEdges.bottom) { dx = dy; } + } + + // update the 'current' rect without modifications + if (edges.top ) { current.top += dy; } + if (edges.bottom) { current.bottom += dy; } + if (edges.left ) { current.left += dx; } + if (edges.right ) { current.right += dx; } + + if (invertible) { + // if invertible, copy the current rect + extend(restricted, current); + + if (invert === 'reposition') { + // swap edge values if necessary to keep width/height positive + var swap; + + if (restricted.top > restricted.bottom) { + swap = restricted.top; + + restricted.top = restricted.bottom; + restricted.bottom = swap; + } + if (restricted.left > restricted.right) { + swap = restricted.left; + + restricted.left = restricted.right; + restricted.right = swap; + } + } + } + else { + // if not invertible, restrict to minimum of 0x0 rect + restricted.top = Math.min(current.top, start.bottom); + restricted.bottom = Math.max(current.bottom, start.top); + restricted.left = Math.min(current.left, start.right); + restricted.right = Math.max(current.right, start.left); + } + + restricted.width = restricted.right - restricted.left; + restricted.height = restricted.bottom - restricted.top ; + + for (var edge in restricted) { + delta[edge] = restricted[edge] - previous[edge]; + } + + resizeEvent.edges = this.prepared.edges; + resizeEvent.rect = restricted; + resizeEvent.deltaRect = delta; + } + + this.target.fire(resizeEvent); + + return resizeEvent; + }, + + gestureStart: function (event) { + var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); + + gestureEvent.ds = 0; + + this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; + this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; + this.gesture.scale = 1; + + this.gesturing = true; + + this.target.fire(gestureEvent); + + return gestureEvent; + }, + + gestureMove: function (event) { + if (!this.pointerIds.length) { + return this.prevEvent; + } + + var gestureEvent; + + gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); + gestureEvent.ds = gestureEvent.scale - this.gesture.scale; + + this.target.fire(gestureEvent); + + this.gesture.prevAngle = gestureEvent.angle; + this.gesture.prevDistance = gestureEvent.distance; + + if (gestureEvent.scale !== Infinity && + gestureEvent.scale !== null && + gestureEvent.scale !== undefined && + !isNaN(gestureEvent.scale)) { + + this.gesture.scale = gestureEvent.scale; + } + + return gestureEvent; + }, + + pointerHold: function (pointer, event, eventTarget) { + this.collectEventTargets(pointer, event, eventTarget, 'hold'); + }, + + pointerUp: function (pointer, event, eventTarget, curEventTarget) { + var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + + clearTimeout(this.holdTimers[pointerIndex]); + + this.collectEventTargets(pointer, event, eventTarget, 'up' ); + this.collectEventTargets(pointer, event, eventTarget, 'tap'); + + this.pointerEnd(pointer, event, eventTarget, curEventTarget); + + this.removePointer(pointer); + }, + + pointerCancel: function (pointer, event, eventTarget, curEventTarget) { + var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + + clearTimeout(this.holdTimers[pointerIndex]); + + this.collectEventTargets(pointer, event, eventTarget, 'cancel'); + this.pointerEnd(pointer, event, eventTarget, curEventTarget); + + this.removePointer(pointer); + }, + + // http://www.quirksmode.org/dom/events/click.html + // >Events leading to dblclick + // + // IE8 doesn't fire down event before dblclick. + // This workaround tries to fire a tap and doubletap after dblclick + ie8Dblclick: function (pointer, event, eventTarget) { + if (this.prevTap + && event.clientX === this.prevTap.clientX + && event.clientY === this.prevTap.clientY + && eventTarget === this.prevTap.target) { + + this.downTargets[0] = eventTarget; + this.downTimes[0] = new Date().getTime(); + this.collectEventTargets(pointer, event, eventTarget, 'tap'); + } + }, + + // End interact move events and stop auto-scroll unless inertia is enabled + pointerEnd: function (pointer, event, eventTarget, curEventTarget) { + var endEvent, + target = this.target, + options = target && target.options, + inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, + inertiaStatus = this.inertiaStatus; + + if (this.interacting()) { + + if (inertiaStatus.active) { return; } + + var pointerSpeed, + now = new Date().getTime(), + inertiaPossible = false, + inertia = false, + smoothEnd = false, + endSnap = checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, + endRestrict = checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, + dx = 0, + dy = 0, + startEvent; + + if (this.dragging) { + if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } + else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } + else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } + } + else { + pointerSpeed = this.pointerDelta.client.speed; + } + + // check if inertia should be started + inertiaPossible = (inertiaOptions && inertiaOptions.enabled + && this.prepared.name !== 'gesture' + && event !== inertiaStatus.startEvent); + + inertia = (inertiaPossible + && (now - this.curCoords.timeStamp) < 50 + && pointerSpeed > inertiaOptions.minSpeed + && pointerSpeed > inertiaOptions.endSpeed); + + if (inertiaPossible && !inertia && (endSnap || endRestrict)) { + + var snapRestrict = {}; + + snapRestrict.snap = snapRestrict.restrict = snapRestrict; + + if (endSnap) { + this.setSnapping(this.curCoords.page, snapRestrict); + if (snapRestrict.locked) { + dx += snapRestrict.dx; + dy += snapRestrict.dy; + } + } + + if (endRestrict) { + this.setRestriction(this.curCoords.page, snapRestrict); + if (snapRestrict.restricted) { + dx += snapRestrict.dx; + dy += snapRestrict.dy; + } + } + + if (dx || dy) { + smoothEnd = true; + } + } + + if (inertia || smoothEnd) { + copyCoords(inertiaStatus.upCoords, this.curCoords); + + this.pointers[0] = inertiaStatus.startEvent = startEvent = + new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); + + inertiaStatus.t0 = now; + + target.fire(inertiaStatus.startEvent); + + if (inertia) { + inertiaStatus.vx0 = this.pointerDelta.client.vx; + inertiaStatus.vy0 = this.pointerDelta.client.vy; + inertiaStatus.v0 = pointerSpeed; + + this.calcInertia(inertiaStatus); + + var page = extend({}, this.curCoords.page), + origin = getOriginXY(target, this.element), + statusObject; + + page.x = page.x + inertiaStatus.xe - origin.x; + page.y = page.y + inertiaStatus.ye - origin.y; + + statusObject = { + useStatusXY: true, + x: page.x, + y: page.y, + dx: 0, + dy: 0, + snap: null + }; + + statusObject.snap = statusObject; + + dx = dy = 0; + + if (endSnap) { + var snap = this.setSnapping(this.curCoords.page, statusObject); + + if (snap.locked) { + dx += snap.dx; + dy += snap.dy; + } + } + + if (endRestrict) { + var restrict = this.setRestriction(this.curCoords.page, statusObject); + + if (restrict.restricted) { + dx += restrict.dx; + dy += restrict.dy; + } + } + + inertiaStatus.modifiedXe += dx; + inertiaStatus.modifiedYe += dy; + + inertiaStatus.i = reqFrame(this.boundInertiaFrame); + } + else { + inertiaStatus.smoothEnd = true; + inertiaStatus.xe = dx; + inertiaStatus.ye = dy; + + inertiaStatus.sx = inertiaStatus.sy = 0; + + inertiaStatus.i = reqFrame(this.boundSmoothEndFrame); + } + + inertiaStatus.active = true; + return; + } + + if (endSnap || endRestrict) { + // fire a move event at the snapped coordinates + this.pointerMove(pointer, event, eventTarget, curEventTarget, true); + } + } + + if (this.dragging) { + endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); + + var draggableElement = this.element, + drop = this.getDrop(event, draggableElement); + + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; + + var dropEvents = this.getDropEvents(event, endEvent); + + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } + if (dropEvents.deactivate) { + this.fireActiveDrops(dropEvents.deactivate); + } + + target.fire(endEvent); + } + else if (this.resizing) { + endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); + target.fire(endEvent); + } + else if (this.gesturing) { + endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); + target.fire(endEvent); + } + + this.stop(event); + }, + + collectDrops: function (element) { + var drops = [], + elements = [], + i; + + element = element || this.element; + + // collect all dropzones and their elements which qualify for a drop + for (i = 0; i < interactables.length; i++) { + if (!interactables[i].options.drop.enabled) { continue; } + + var current = interactables[i], + accept = current.options.drop.accept; + + // test the draggable element against the dropzone's accept setting + if ((isElement(accept) && accept !== element) + || (isString(accept) + && !matchesSelector(element, accept))) { + + continue; + } + + // query for new elements if necessary + var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; + + for (var j = 0, len = dropElements.length; j < len; j++) { + var currentElement = dropElements[j]; + + if (currentElement === element) { + continue; + } + + drops.push(current); + elements.push(currentElement); + } + } + + return { + dropzones: drops, + elements: elements + }; + }, + + fireActiveDrops: function (event) { + var i, + current, + currentElement, + prevElement; + + // loop through all active dropzones and trigger event + for (i = 0; i < this.activeDrops.dropzones.length; i++) { + current = this.activeDrops.dropzones[i]; + currentElement = this.activeDrops.elements [i]; + + // prevent trigger of duplicate events on same element + if (currentElement !== prevElement) { + // set current element as event target + event.target = currentElement; + current.fire(event); + } + prevElement = currentElement; + } + }, + + // Collect a new set of possible drops and save them in activeDrops. + // setActiveDrops should always be called when a drag has just started or a + // drag event happens while dynamicDrop is true + setActiveDrops: function (dragElement) { + // get dropzones and their elements that could receive the draggable + var possibleDrops = this.collectDrops(dragElement, true); + + this.activeDrops.dropzones = possibleDrops.dropzones; + this.activeDrops.elements = possibleDrops.elements; + this.activeDrops.rects = []; + + for (var i = 0; i < this.activeDrops.dropzones.length; i++) { + this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); + } + }, + + getDrop: function (event, dragElement) { + var validDrops = []; + + if (dynamicDrop) { + this.setActiveDrops(dragElement); + } + + // collect all dropzones and their elements which qualify for a drop + for (var j = 0; j < this.activeDrops.dropzones.length; j++) { + var current = this.activeDrops.dropzones[j], + currentElement = this.activeDrops.elements [j], + rect = this.activeDrops.rects [j]; + + validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) + ? currentElement + : null); + } + + // get the most appropriate dropzone based on DOM depth and order + var dropIndex = indexOfDeepestElement(validDrops), + dropzone = this.activeDrops.dropzones[dropIndex] || null, + element = this.activeDrops.elements [dropIndex] || null; + + return { + dropzone: dropzone, + element: element + }; + }, + + getDropEvents: function (pointerEvent, dragEvent) { + var dropEvents = { + enter : null, + leave : null, + activate : null, + deactivate: null, + move : null, + drop : null + }; + + if (this.dropElement !== this.prevDropElement) { + // if there was a prevDropTarget, create a dragleave event + if (this.prevDropTarget) { + dropEvents.leave = { + target : this.prevDropElement, + dropzone : this.prevDropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragleave' + }; + + dragEvent.dragLeave = this.prevDropElement; + dragEvent.prevDropzone = this.prevDropTarget; + } + // if the dropTarget is not null, create a dragenter event + if (this.dropTarget) { + dropEvents.enter = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragenter' + }; + + dragEvent.dragEnter = this.dropElement; + dragEvent.dropzone = this.dropTarget; + } + } + + if (dragEvent.type === 'dragend' && this.dropTarget) { + dropEvents.drop = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'drop' + }; + + dragEvent.dropzone = this.dropTarget; + } + if (dragEvent.type === 'dragstart') { + dropEvents.activate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropactivate' + }; + } + if (dragEvent.type === 'dragend') { + dropEvents.deactivate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropdeactivate' + }; + } + if (dragEvent.type === 'dragmove' && this.dropTarget) { + dropEvents.move = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + dragmove : dragEvent, + timeStamp : dragEvent.timeStamp, + type : 'dropmove' + }; + dragEvent.dropzone = this.dropTarget; + } + + return dropEvents; + }, + + currentAction: function () { + return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; + }, + + interacting: function () { + return this.dragging || this.resizing || this.gesturing; + }, + + clearTargets: function () { + this.target = this.element = null; + + this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null; + }, + + stop: function (event) { + if (this.interacting()) { + autoScroll.stop(); + this.matches = []; + this.matchElements = []; + + var target = this.target; + + if (target.options.styleCursor) { + target._doc.documentElement.style.cursor = ''; + } + + // prevent Default only if were previously interacting + if (event && isFunction(event.preventDefault)) { + this.checkAndPreventDefault(event, target, this.element); + } + + if (this.dragging) { + this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; + } + + this.clearTargets(); + } + + this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; + this.prepared.name = this.prevEvent = null; + this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; + + // remove pointers if their ID isn't in this.pointerIds + for (var i = 0; i < this.pointers.length; i++) { + if (indexOf(this.pointerIds, getPointerId(this.pointers[i])) === -1) { + this.pointers.splice(i, 1); + } + } + + for (i = 0; i < interactions.length; i++) { + // remove this interaction if it's not the only one of it's type + if (interactions[i] !== this && interactions[i].mouse === this.mouse) { + interactions.splice(indexOf(interactions, this), 1); + } + } + }, + + inertiaFrame: function () { + var inertiaStatus = this.inertiaStatus, + options = this.target.options[this.prepared.name].inertia, + lambda = options.resistance, + t = new Date().getTime() / 1000 - inertiaStatus.t0; + + if (t < inertiaStatus.te) { + + var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; + + if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { + inertiaStatus.sx = inertiaStatus.xe * progress; + inertiaStatus.sy = inertiaStatus.ye * progress; + } + else { + var quadPoint = getQuadraticCurvePoint( + 0, 0, + inertiaStatus.xe, inertiaStatus.ye, + inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, + progress); + + inertiaStatus.sx = quadPoint.x; + inertiaStatus.sy = quadPoint.y; + } + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.i = reqFrame(this.boundInertiaFrame); + } + else { + inertiaStatus.sx = inertiaStatus.modifiedXe; + inertiaStatus.sy = inertiaStatus.modifiedYe; + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.active = false; + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + } + }, + + smoothEndFrame: function () { + var inertiaStatus = this.inertiaStatus, + t = new Date().getTime() - inertiaStatus.t0, + duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; + + if (t < duration) { + inertiaStatus.sx = easeOutQuad(t, 0, inertiaStatus.xe, duration); + inertiaStatus.sy = easeOutQuad(t, 0, inertiaStatus.ye, duration); + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.i = reqFrame(this.boundSmoothEndFrame); + } + else { + inertiaStatus.sx = inertiaStatus.xe; + inertiaStatus.sy = inertiaStatus.ye; + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.active = false; + inertiaStatus.smoothEnd = false; + + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + } + }, + + addPointer: function (pointer) { + var id = getPointerId(pointer), + index = this.mouse? 0 : indexOf(this.pointerIds, id); + + if (index === -1) { + index = this.pointerIds.length; + } + + this.pointerIds[index] = id; + this.pointers[index] = pointer; + + return index; + }, + + removePointer: function (pointer) { + var id = getPointerId(pointer), + index = this.mouse? 0 : indexOf(this.pointerIds, id); + + if (index === -1) { return; } + + if (!this.interacting()) { + this.pointers.splice(index, 1); + } + + this.pointerIds .splice(index, 1); + this.downTargets.splice(index, 1); + this.downTimes .splice(index, 1); + this.holdTimers .splice(index, 1); + }, + + recordPointer: function (pointer) { + // Do not update pointers while inertia is active. + // The inertia start event should be this.pointers[0] + if (this.inertiaStatus.active) { return; } + + var index = this.mouse? 0: indexOf(this.pointerIds, getPointerId(pointer)); + + if (index === -1) { return; } + + this.pointers[index] = pointer; + }, + + collectEventTargets: function (pointer, event, eventTarget, eventType) { + var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + + // do not fire a tap event if the pointer was moved before being lifted + if (eventType === 'tap' && (this.pointerWasMoved + // or if the pointerup target is different to the pointerdown target + || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) { + return; + } + + var targets = [], + elements = [], + element = eventTarget; + + function collectSelectors (interactable, selector, context) { + var els = ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; + + if (interactable._iEvents[eventType] + && isElement(element) + && inContext(interactable, element) + && !testIgnore(interactable, element, eventTarget) + && testAllow(interactable, element, eventTarget) + && matchesSelector(element, selector, els)) { + + targets.push(interactable); + elements.push(element); + } + } + + while (element) { + if (interact.isSet(element) && interact(element)._iEvents[eventType]) { + targets.push(interact(element)); + elements.push(element); + } + + interactables.forEachSelector(collectSelectors); + + element = parentElement(element); + } + + // create the tap event even if there are no listeners so that + // doubletap can still be created and fired + if (targets.length || eventType === 'tap') { + this.firePointers(pointer, event, eventTarget, targets, elements, eventType); + } + }, + + firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { + var pointerIndex = this.mouse? 0 : indexOf(getPointerId(pointer)), + pointerEvent = {}, + i, + // for tap events + interval, createNewDoubleTap; + + // if it's a doubletap then the event properties would have been + // copied from the tap event and provided as the pointer argument + if (eventType === 'doubletap') { + pointerEvent = pointer; + } + else { + extend(pointerEvent, event); + if (event !== pointer) { + extend(pointerEvent, pointer); + } + + pointerEvent.preventDefault = preventOriginalDefault; + pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; + pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; + pointerEvent.interaction = this; + + pointerEvent.timeStamp = new Date().getTime(); + pointerEvent.originalEvent = event; + pointerEvent.type = eventType; + pointerEvent.pointerId = getPointerId(pointer); + pointerEvent.pointerType = this.mouse? 'mouse' : !supportsPointerEvent? 'touch' + : isString(pointer.pointerType) + ? pointer.pointerType + : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; + } + + if (eventType === 'tap') { + pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex]; + + interval = pointerEvent.timeStamp - this.tapTime; + createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap' + && this.prevTap.target === pointerEvent.target + && interval < 500); + + pointerEvent.double = createNewDoubleTap; + + this.tapTime = pointerEvent.timeStamp; + } + + for (i = 0; i < targets.length; i++) { + pointerEvent.currentTarget = elements[i]; + pointerEvent.interactable = targets[i]; + targets[i].fire(pointerEvent); + + if (pointerEvent.immediatePropagationStopped + ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { + break; + } + } + + if (createNewDoubleTap) { + var doubleTap = {}; + + extend(doubleTap, pointerEvent); + + doubleTap.dt = interval; + doubleTap.type = 'doubletap'; + + this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap'); + + this.prevTap = doubleTap; + } + else if (eventType === 'tap') { + this.prevTap = pointerEvent; + } + }, + + validateSelector: function (pointer, event, matches, matchElements) { + for (var i = 0, len = matches.length; i < len; i++) { + var match = matches[i], + matchElement = matchElements[i], + action = validateAction(match.getAction(pointer, event, this, matchElement), match); + + if (action && withinInteractionLimit(match, matchElement, action)) { + this.target = match; + this.element = matchElement; + + return action; + } + } + }, + + setSnapping: function (pageCoords, status) { + var snap = this.target.options[this.prepared.name].snap, + targets = [], + target, + page, + i; + + status = status || this.snapStatus; + + if (status.useStatusXY) { + page = { x: status.x, y: status.y }; + } + else { + var origin = getOriginXY(this.target, this.element); + + page = extend({}, pageCoords); + + page.x -= origin.x; + page.y -= origin.y; + } + + status.realX = page.x; + status.realY = page.y; + + page.x = page.x - this.inertiaStatus.resumeDx; + page.y = page.y - this.inertiaStatus.resumeDy; + + var len = snap.targets? snap.targets.length : 0; + + for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) { + var relative = { + x: page.x - this.snapOffsets[relIndex].x, + y: page.y - this.snapOffsets[relIndex].y + }; + + for (i = 0; i < len; i++) { + if (isFunction(snap.targets[i])) { + target = snap.targets[i](relative.x, relative.y, this); + } + else { + target = snap.targets[i]; + } + + if (!target) { continue; } + + targets.push({ + x: isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, + y: isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, + + range: isNumber(target.range)? target.range: snap.range + }); + } + } + + var closest = { + target: null, + inRange: false, + distance: 0, + range: 0, + dx: 0, + dy: 0 + }; + + for (i = 0, len = targets.length; i < len; i++) { + target = targets[i]; + + var range = target.range, + dx = target.x - page.x, + dy = target.y - page.y, + distance = hypot(dx, dy), + inRange = distance <= range; + + // Infinite targets count as being out of range + // compared to non infinite ones that are in range + if (range === Infinity && closest.inRange && closest.range !== Infinity) { + inRange = false; + } + + if (!closest.target || (inRange + // is the closest target in range? + ? (closest.inRange && range !== Infinity + // the pointer is relatively deeper in this target + ? distance / range < closest.distance / closest.range + // this target has Infinite range and the closest doesn't + : (range === Infinity && closest.range !== Infinity) + // OR this target is closer that the previous closest + || distance < closest.distance) + // The other is not in range and the pointer is closer to this target + : (!closest.inRange && distance < closest.distance))) { + + if (range === Infinity) { + inRange = true; + } + + closest.target = target; + closest.distance = distance; + closest.range = range; + closest.inRange = inRange; + closest.dx = dx; + closest.dy = dy; + + status.range = range; + } + } + + var snapChanged; + + if (closest.target) { + snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); + + status.snappedX = closest.target.x; + status.snappedY = closest.target.y; + } + else { + snapChanged = true; + + status.snappedX = NaN; + status.snappedY = NaN; + } + + status.dx = closest.dx; + status.dy = closest.dy; + + status.changed = (snapChanged || (closest.inRange && !status.locked)); + status.locked = closest.inRange; + + return status; + }, + + setRestriction: function (pageCoords, status) { + var target = this.target, + restrict = target && target.options[this.prepared.name].restrict, + restriction = restrict && restrict.restriction, + page; + + if (!restriction) { + return status; + } + + status = status || this.restrictStatus; + + page = status.useStatusXY + ? page = { x: status.x, y: status.y } + : page = extend({}, pageCoords); + + if (status.snap && status.snap.locked) { + page.x += status.snap.dx || 0; + page.y += status.snap.dy || 0; + } + + page.x -= this.inertiaStatus.resumeDx; + page.y -= this.inertiaStatus.resumeDy; + + status.dx = 0; + status.dy = 0; + status.restricted = false; + + var rect, restrictedX, restrictedY; + + if (isString(restriction)) { + if (restriction === 'parent') { + restriction = parentElement(this.element); + } + else if (restriction === 'self') { + restriction = target.getRect(this.element); + } + else { + restriction = closest(this.element, restriction); + } + + if (!restriction) { return status; } + } + + if (isFunction(restriction)) { + restriction = restriction(page.x, page.y, this.element); + } + + if (isElement(restriction)) { + restriction = getElementRect(restriction); + } + + rect = restriction; + + if (!restriction) { + restrictedX = page.x; + restrictedY = page.y; + } + // object is assumed to have + // x, y, width, height or + // left, top, right, bottom + else if ('x' in restriction && 'y' in restriction) { + restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left); + restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top ); + } + else { + restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left); + restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top ); + } + + status.dx = restrictedX - page.x; + status.dy = restrictedY - page.y; + + status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; + status.restricted = !!(status.dx || status.dy); + + status.restrictedX = restrictedX; + status.restrictedY = restrictedY; + + return status; + }, + + checkAndPreventDefault: function (event, interactable, element) { + if (!(interactable = interactable || this.target)) { return; } + + var options = interactable.options, + prevent = options.preventDefault; + + if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { + // do not preventDefault on pointerdown if the prepared action is a drag + // and dragging can only start from a certain direction - this allows + // a touch to pan the viewport if a drag isn't in the right direction + if (/down|start/i.test(event.type) + && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { + + return; + } + + // with manualStart, only preventDefault while interacting + if (options[this.prepared.name] && options[this.prepared.name].manualStart + && !this.interacting()) { + return; + } + + event.preventDefault(); + return; + } + + if (prevent === 'always') { + event.preventDefault(); + return; + } + }, + + calcInertia: function (status) { + var inertiaOptions = this.target.options[this.prepared.name].inertia, + lambda = inertiaOptions.resistance, + inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; + + status.x0 = this.prevEvent.pageX; + status.y0 = this.prevEvent.pageY; + status.t0 = status.startEvent.timeStamp / 1000; + status.sx = status.sy = 0; + + status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; + status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; + status.te = inertiaDur; + + status.lambda_v0 = lambda / status.v0; + status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; + }, + + autoScrollMove: function (pointer) { + if (!(this.interacting() + && checkAutoScroll(this.target, this.prepared.name))) { + return; + } + + if (this.inertiaStatus.active) { + autoScroll.x = autoScroll.y = 0; + return; + } + + var top, + right, + bottom, + left, + options = this.target.options[this.prepared.name].autoScroll, + container = options.container || getWindow(this.element); + + if (isWindow(container)) { + left = pointer.clientX < autoScroll.margin; + top = pointer.clientY < autoScroll.margin; + right = pointer.clientX > container.innerWidth - autoScroll.margin; + bottom = pointer.clientY > container.innerHeight - autoScroll.margin; + } + else { + var rect = getElementRect(container); + + left = pointer.clientX < rect.left + autoScroll.margin; + top = pointer.clientY < rect.top + autoScroll.margin; + right = pointer.clientX > rect.right - autoScroll.margin; + bottom = pointer.clientY > rect.bottom - autoScroll.margin; + } + + autoScroll.x = (right ? 1: left? -1: 0); + autoScroll.y = (bottom? 1: top? -1: 0); + + if (!autoScroll.isScrolling) { + // set the autoScroll properties to those of the target + autoScroll.margin = options.margin; + autoScroll.speed = options.speed; + + autoScroll.start(this); + } + }, + + _updateEventTargets: function (target, currentTarget) { + this._eventTarget = target; + this._curEventTarget = currentTarget; + } + +}; + +function getInteractionFromPointer (pointer, eventType, eventTarget) { + var i = 0, len = interactions.length, + mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) + // MSPointerEvent.MSPOINTER_TYPE_MOUSE + || pointer.pointerType === 4), + interaction; + + var id = getPointerId(pointer); + + // try to resume inertia with a new pointer + if (/down|start/i.test(eventType)) { + for (i = 0; i < len; i++) { + interaction = interactions[i]; + + var element = eventTarget; + + if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume + && (interaction.mouse === mouseEvent)) { + while (element) { + // if the element is the interaction element + if (element === interaction.element) { + // update the interaction's pointer + if (interaction.pointers[0]) { + interaction.removePointer(interaction.pointers[0]); + } + interaction.addPointer(pointer); + + return interaction; + } + element = parentElement(element); + } + } + } + } + + // if it's a mouse interaction + if (mouseEvent || !(supportsTouch || supportsPointerEvent)) { + + // find a mouse interaction that's not in inertia phase + for (i = 0; i < len; i++) { + if (interactions[i].mouse && !interactions[i].inertiaStatus.active) { + return interactions[i]; + } + } + + // find any interaction specifically for mouse. + // if the eventType is a mousedown, and inertia is active + // ignore the interaction + for (i = 0; i < len; i++) { + if (interactions[i].mouse && !(/down/.test(eventType) && interactions[i].inertiaStatus.active)) { + return interaction; + } + } + + // create a new interaction for mouse + interaction = new Interaction(); + interaction.mouse = true; + + return interaction; + } + + // get interaction that has this pointer + for (i = 0; i < len; i++) { + if (contains(interactions[i].pointerIds, id)) { + return interactions[i]; + } + } + + // at this stage, a pointerUp should not return an interaction + if (/up|end|out/i.test(eventType)) { + return null; + } + + // get first idle interaction + for (i = 0; i < len; i++) { + interaction = interactions[i]; + + if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) + && !interaction.interacting() + && !(!mouseEvent && interaction.mouse)) { + + interaction.addPointer(pointer); + + return interaction; + } + } + + return new Interaction(); +} + +function doOnInteractions (method) { + return (function (event) { + var interaction, + eventTarget = getActualElement(event.path + ? event.path[0] + : event.target), + curEventTarget = getActualElement(event.currentTarget), + i; + + if (supportsTouch && /touch/.test(event.type)) { + prevTouchTime = new Date().getTime(); + + for (i = 0; i < event.changedTouches.length; i++) { + var pointer = event.changedTouches[i]; + + interaction = getInteractionFromPointer(pointer, event.type, eventTarget); + + if (!interaction) { continue; } + + interaction._updateEventTargets(eventTarget, curEventTarget); + + interaction[method](pointer, event, eventTarget, curEventTarget); + } + } + else { + if (!supportsPointerEvent && /mouse/.test(event.type)) { + // ignore mouse events while touch interactions are active + for (i = 0; i < interactions.length; i++) { + if (!interactions[i].mouse && interactions[i].pointerIsDown) { + return; + } + } + + // try to ignore mouse events that are simulated by the browser + // after a touch event + if (new Date().getTime() - prevTouchTime < 500) { + return; + } + } + + interaction = getInteractionFromPointer(event, event.type, eventTarget); + + if (!interaction) { return; } + + interaction._updateEventTargets(eventTarget, curEventTarget); + + interaction[method](event, event, eventTarget, curEventTarget); + } + }); +} + +function InteractEvent (interaction, event, action, phase, element, related) { + var client, + page, + target = interaction.target, + snapStatus = interaction.snapStatus, + restrictStatus = interaction.restrictStatus, + pointers = interaction.pointers, + deltaSource = (target && target.options || defaultOptions).deltaSource, + sourceX = deltaSource + 'X', + sourceY = deltaSource + 'Y', + options = target? target.options: defaultOptions, + origin = getOriginXY(target, element), + starting = phase === 'start', + ending = phase === 'end', + coords = starting? interaction.startCoords : interaction.curCoords; + + element = element || interaction.element; + + page = extend({}, coords.page); + client = extend({}, coords.client); + + page.x -= origin.x; + page.y -= origin.y; + + client.x -= origin.x; + client.y -= origin.y; + + var relativePoints = options[action].snap && options[action].snap.relativePoints ; + + if (checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { + this.snap = { + range : snapStatus.range, + locked : snapStatus.locked, + x : snapStatus.snappedX, + y : snapStatus.snappedY, + realX : snapStatus.realX, + realY : snapStatus.realY, + dx : snapStatus.dx, + dy : snapStatus.dy + }; + + if (snapStatus.locked) { + page.x += snapStatus.dx; + page.y += snapStatus.dy; + client.x += snapStatus.dx; + client.y += snapStatus.dy; + } + } + + if (checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { + page.x += restrictStatus.dx; + page.y += restrictStatus.dy; + client.x += restrictStatus.dx; + client.y += restrictStatus.dy; + + this.restrict = { + dx: restrictStatus.dx, + dy: restrictStatus.dy + }; + } + + this.pageX = page.x; + this.pageY = page.y; + this.clientX = client.x; + this.clientY = client.y; + + this.x0 = interaction.startCoords.page.x - origin.x; + this.y0 = interaction.startCoords.page.y - origin.y; + this.clientX0 = interaction.startCoords.client.x - origin.x; + this.clientY0 = interaction.startCoords.client.y - origin.y; + this.ctrlKey = event.ctrlKey; + this.altKey = event.altKey; + this.shiftKey = event.shiftKey; + this.metaKey = event.metaKey; + this.button = event.button; + this.target = element; + this.t0 = interaction.downTimes[0]; + this.type = action + (phase || ''); + + this.interaction = interaction; + this.interactable = target; + + var inertiaStatus = interaction.inertiaStatus; + + if (inertiaStatus.active) { + this.detail = 'inertia'; + } + + if (related) { + this.relatedTarget = related; + } + + // end event dx, dy is difference between start and end points + if (ending) { + if (deltaSource === 'client') { + this.dx = client.x - interaction.startCoords.client.x; + this.dy = client.y - interaction.startCoords.client.y; + } + else { + this.dx = page.x - interaction.startCoords.page.x; + this.dy = page.y - interaction.startCoords.page.y; + } + } + else if (starting) { + this.dx = 0; + this.dy = 0; + } + // copy properties from previousmove if starting inertia + else if (phase === 'inertiastart') { + this.dx = interaction.prevEvent.dx; + this.dy = interaction.prevEvent.dy; + } + else { + if (deltaSource === 'client') { + this.dx = client.x - interaction.prevEvent.clientX; + this.dy = client.y - interaction.prevEvent.clientY; + } + else { + this.dx = page.x - interaction.prevEvent.pageX; + this.dy = page.y - interaction.prevEvent.pageY; + } + } + if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' + && !inertiaStatus.active + && options[action].inertia && options[action].inertia.zeroResumeDelta) { + + inertiaStatus.resumeDx += this.dx; + inertiaStatus.resumeDy += this.dy; + + this.dx = this.dy = 0; + } + + if (action === 'resize' && interaction.resizeAxes) { + if (options.resize.square) { + if (interaction.resizeAxes === 'y') { + this.dx = this.dy; + } + else { + this.dy = this.dx; + } + this.axes = 'xy'; + } + else { + this.axes = interaction.resizeAxes; + + if (interaction.resizeAxes === 'x') { + this.dy = 0; + } + else if (interaction.resizeAxes === 'y') { + this.dx = 0; + } + } + } + else if (action === 'gesture') { + this.touches = [pointers[0], pointers[1]]; + + if (starting) { + this.distance = touchDistance(pointers, deltaSource); + this.box = touchBBox(pointers); + this.scale = 1; + this.ds = 0; + this.angle = touchAngle(pointers, undefined, deltaSource); + this.da = 0; + } + else if (ending || event instanceof InteractEvent) { + this.distance = interaction.prevEvent.distance; + this.box = interaction.prevEvent.box; + this.scale = interaction.prevEvent.scale; + this.ds = this.scale - 1; + this.angle = interaction.prevEvent.angle; + this.da = this.angle - interaction.gesture.startAngle; + } + else { + this.distance = touchDistance(pointers, deltaSource); + this.box = touchBBox(pointers); + this.scale = this.distance / interaction.gesture.startDistance; + this.angle = touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); + + this.ds = this.scale - interaction.gesture.prevScale; + this.da = this.angle - interaction.gesture.prevAngle; + } + } + + if (starting) { + this.timeStamp = interaction.downTimes[0]; + this.dt = 0; + this.duration = 0; + this.speed = 0; + this.velocityX = 0; + this.velocityY = 0; + } + else if (phase === 'inertiastart') { + this.timeStamp = interaction.prevEvent.timeStamp; + this.dt = interaction.prevEvent.dt; + this.duration = interaction.prevEvent.duration; + this.speed = interaction.prevEvent.speed; + this.velocityX = interaction.prevEvent.velocityX; + this.velocityY = interaction.prevEvent.velocityY; + } + else { + this.timeStamp = new Date().getTime(); + this.dt = this.timeStamp - interaction.prevEvent.timeStamp; + this.duration = this.timeStamp - interaction.downTimes[0]; + + if (event instanceof InteractEvent) { + var dx = this[sourceX] - interaction.prevEvent[sourceX], + dy = this[sourceY] - interaction.prevEvent[sourceY], + dt = this.dt / 1000; + + this.speed = hypot(dx, dy) / dt; + this.velocityX = dx / dt; + this.velocityY = dy / dt; + } + // if normal move or end event, use previous user event coords + else { + // speed and velocity in pixels per second + this.speed = interaction.pointerDelta[deltaSource].speed; + this.velocityX = interaction.pointerDelta[deltaSource].vx; + this.velocityY = interaction.pointerDelta[deltaSource].vy; + } + } + + if ((ending || phase === 'inertiastart') + && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) { + + var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI, + overlap = 22.5; + + if (angle < 0) { + angle += 360; + } + + var left = 135 - overlap <= angle && angle < 225 + overlap, + up = 225 - overlap <= angle && angle < 315 + overlap, + + right = !left && (315 - overlap <= angle || angle < 45 + overlap), + down = !up && 45 - overlap <= angle && angle < 135 + overlap; + + this.swipe = { + up : up, + down : down, + left : left, + right: right, + angle: angle, + speed: interaction.prevEvent.speed, + velocity: { + x: interaction.prevEvent.velocityX, + y: interaction.prevEvent.velocityY + } + }; + } +} + +InteractEvent.prototype = { + preventDefault: blank, + stopImmediatePropagation: function () { + this.immediatePropagationStopped = this.propagationStopped = true; + }, + stopPropagation: function () { + this.propagationStopped = true; + } +}; + +function preventOriginalDefault () { + this.originalEvent.preventDefault(); +} + +function getActionCursor (action) { + var cursor = ''; + + if (action.name === 'drag') { + cursor = actionCursors.drag; + } + if (action.name === 'resize') { + if (action.axis) { + cursor = actionCursors[action.name + action.axis]; + } + else if (action.edges) { + var cursorKey = 'resize', + edgeNames = ['top', 'bottom', 'left', 'right']; + + for (var i = 0; i < 4; i++) { + if (action.edges[edgeNames[i]]) { + cursorKey += edgeNames[i]; + } + } + + cursor = actionCursors[cursorKey]; + } + } + + return cursor; +} + +function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { + // false, '', undefined, null + if (!value) { return false; } + + // true value, use pointer coords and element rect + if (value === true) { + // if dimensions are negative, "switch" edges + var width = isNumber(rect.width)? rect.width : rect.right - rect.left, + height = isNumber(rect.height)? rect.height : rect.bottom - rect.top; + + if (width < 0) { + if (name === 'left' ) { name = 'right'; } + else if (name === 'right') { name = 'left' ; } + } + if (height < 0) { + if (name === 'top' ) { name = 'bottom'; } + else if (name === 'bottom') { name = 'top' ; } + } + + if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } + if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } + + if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } + if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } + } + + // the remaining checks require an element + if (!isElement(element)) { return false; } + + return isElement(value) + // the value is an element to use as a resize handle + ? value === element + // otherwise check if element matches value as selector + : matchesUpTo(element, value, interactableElement); +} + +function defaultActionChecker (pointer, interaction, element) { + var rect = this.getRect(element), + shouldResize = false, + action = null, + resizeAxes = null, + resizeEdges, + page = extend({}, interaction.curCoords.page), + options = this.options; + + if (!rect) { return null; } + + if (actionIsEnabled.resize && options.resize.enabled) { + var resizeOptions = options.resize; + + resizeEdges = { + left: false, right: false, top: false, bottom: false + }; + + // if using resize.edges + if (isObject(resizeOptions.edges)) { + for (var edge in resizeEdges) { + resizeEdges[edge] = checkResizeEdge(edge, + resizeOptions.edges[edge], + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || margin); + } + + resizeEdges.left = resizeEdges.left && !resizeEdges.right; + resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; + + shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; + } + else { + var right = options.resize.axis !== 'y' && page.x > (rect.right - margin), + bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - margin); + + shouldResize = right || bottom; + resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); + } + } + + action = shouldResize + ? 'resize' + : actionIsEnabled.drag && options.drag.enabled + ? 'drag' + : null; + + if (actionIsEnabled.gesture + && interaction.pointerIds.length >=2 + && !(interaction.dragging || interaction.resizing)) { + action = 'gesture'; + } + + if (action) { + return { + name: action, + axis: resizeAxes, + edges: resizeEdges + }; + } + + return null; +} + +// Check if action is enabled globally and the current target supports it +// If so, return the validated action. Otherwise, return null +function validateAction (action, interactable) { + if (!isObject(action)) { return null; } + + var actionName = action.name, + options = interactable.options; + + if (( (actionName === 'resize' && options.resize.enabled ) + || (actionName === 'drag' && options.drag.enabled ) + || (actionName === 'gesture' && options.gesture.enabled)) + && actionIsEnabled[actionName]) { + + if (actionName === 'resize' || actionName === 'resizeyx') { + actionName = 'resizexy'; + } + + return action; + } + return null; +} + +var listeners = {}, + interactionListeners = [ + 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove', + 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', + 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', + 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' + ]; + +for (var i = 0, len = interactionListeners.length; i < len; i++) { + var name = interactionListeners[i]; + + listeners[name] = doOnInteractions(name); +} + +// bound to the interactable context when a DOM event +// listener is added to a selector interactable +function delegateListener (event, useCapture) { + var fakeEvent = {}, + delegated = delegatedEvents[event.type], + eventTarget = getActualElement(event.path + ? event.path[0] + : event.target), + element = eventTarget; + + useCapture = useCapture? true: false; + + // duplicate the event so that currentTarget can be changed + for (var prop in event) { + fakeEvent[prop] = event[prop]; + } + + fakeEvent.originalEvent = event; + fakeEvent.preventDefault = preventOriginalDefault; + + // climb up document tree looking for selector matches + while (isElement(element)) { + for (var i = 0; i < delegated.selectors.length; i++) { + var selector = delegated.selectors[i], + context = delegated.contexts[i]; + + if (matchesSelector(element, selector) + && nodeContains(context, eventTarget) + && nodeContains(context, element)) { + + var listeners = delegated.listeners[i]; + + fakeEvent.currentTarget = element; + + for (var j = 0; j < listeners.length; j++) { + if (listeners[j][1] === useCapture) { + listeners[j][0](fakeEvent); + } + } + } + } + + element = parentElement(element); + } +} + +function delegateUseCapture (event) { + return delegateListener.call(this, event, true); +} + +interactables.indexOfElement = function indexOfElement (element, context) { + context = context || document; + + for (var i = 0; i < this.length; i++) { + var interactable = this[i]; + + if ((interactable.selector === element + && (interactable._context === context)) + || (!interactable.selector && interactable._element === element)) { + + return i; + } + } + return -1; +}; + +interactables.get = function interactableGet (element, options) { + return this[this.indexOfElement(element, options && options.context)]; +}; + +interactables.forEachSelector = function (callback) { + for (var i = 0; i < this.length; i++) { + var interactable = this[i]; + + if (!interactable.selector) { + continue; + } + + var ret = callback(interactable, interactable.selector, interactable._context, i, this); + + if (ret !== undefined) { + return ret; + } + } +}; + +/*\ + * interact + [ method ] + * + * The methods of this variable can be used to set elements as + * interactables and also to change various default settings. + * + * Calling it as a function and passing an element or a valid CSS selector + * string returns an Interactable object which has various methods to + * configure it. + * + - element (Element | string) The HTML or SVG Element to interact with or CSS selector + = (object) An @Interactable + * + > Usage + | interact(document.getElementById('draggable')).draggable(true); + | + | var rectables = interact('rect'); + | rectables + | .gesturable(true) + | .on('gesturemove', function (event) { + | // something cool... + | }) + | .autoScroll(true); + \*/ +function interact (element, options) { + return interactables.get(element, options) || new Interactable(element, options); +} + +/*\ + * Interactable + [ property ] + ** + * Object type returned by @interact + \*/ +function Interactable (element, options) { + this._element = element; + this._iEvents = this._iEvents || {}; + + var _window; + + if (trySelector(element)) { + this.selector = element; + + var context = options && options.context; + + _window = context? getWindow(context) : window; + + if (context && (_window.Node + ? context instanceof _window.Node + : (isElement(context) || context === _window.document))) { + + this._context = context; + } + } + else { + _window = getWindow(element); + + if (isElement(element, _window)) { + + if (PointerEvent) { + events.add(this._element, pEventTypes.down, listeners.pointerDown ); + events.add(this._element, pEventTypes.move, listeners.pointerHover); + } + else { + events.add(this._element, 'mousedown' , listeners.pointerDown ); + events.add(this._element, 'mousemove' , listeners.pointerHover); + events.add(this._element, 'touchstart', listeners.pointerDown ); + events.add(this._element, 'touchmove' , listeners.pointerHover); + } + } + } + + this._doc = _window.document; + + if (!contains(documents, this._doc)) { + listenToDocument(this._doc); + } + + interactables.push(this); + + this.set(options); +} + +Interactable.prototype = { + setOnEvents: function (action, phases) { + if (action === 'drop') { + if (isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } + if (isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } + if (isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } + if (isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } + if (isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } + if (isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } + } + else { + action = 'on' + action; + + if (isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } + if (isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } + if (isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } + if (isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } + } + + return this; + }, + + /*\ + * Interactable.draggable + [ method ] + * + * Gets or sets whether drag actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of drag events + | var isDraggable = interact('ul li').draggable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) + = (object) This Interactable + | interact(element).draggable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // the axis in which the first movement must be + | // for the drag sequence to start + | // 'xy' by default - any direction + | axis: 'x' || 'y' || 'xy', + | + | // max number of drags that can happen concurrently + | // with elements of this Interactable. Infinity by default + | max: Infinity, + | + | // max number of drags that can target the same element+Interactable + | // 1 by default + | maxPerElement: 2 + | }); + \*/ + draggable: function (options) { + if (isObject(options)) { + this.options.drag.enabled = options.enabled === false? false: true; + this.setPerAction('drag', options); + this.setOnEvents('drag', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.drag.axis = options.axis; + } + else if (options.axis === null) { + delete this.options.drag.axis; + } + + return this; + } + + if (isBool(options)) { + this.options.drag.enabled = options; + + return this; + } + + return this.options.drag; + }, + + setPerAction: function (action, options) { + // for all the default per-action options + for (var option in options) { + // if this option exists for this action + if (option in defaultOptions[action]) { + // if the option in the options arg is an object value + if (isObject(options[option])) { + // duplicate the object + this.options[action][option] = extend(this.options[action][option] || {}, options[option]); + + if (isObject(defaultOptions.perAction[option]) && 'enabled' in defaultOptions.perAction[option]) { + this.options[action][option].enabled = options[option].enabled === false? false : true; + } + } + else if (isBool(options[option]) && isObject(defaultOptions.perAction[option])) { + this.options[action][option].enabled = options[option]; + } + else if (options[option] !== undefined) { + // or if it's not undefined, do a plain assignment + this.options[action][option] = options[option]; + } + } + } + }, + + /*\ + * Interactable.dropzone + [ method ] + * + * Returns or sets whether elements can be dropped onto this + * Interactable to trigger drop events + * + * Dropzones can receive the following events: + * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends + * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone + * - `dragmove` when a draggable that has entered the dropzone is moved + * - `drop` when a draggable is dropped into this dropzone + * + * Use the `accept` option to allow only elements that match the given CSS selector or element. + * + * Use the `overlap` option to set how drops are checked for. The allowed values are: + * - `'pointer'`, the pointer must be over the dropzone (default) + * - `'center'`, the draggable element's center must be over the dropzone + * - a number from 0-1 which is the `(intersection area) / (draggable area)`. + * e.g. `0.5` for drop to happen when half of the area of the + * draggable is over the dropzone + * + - options (boolean | object | null) #optional The new value to be set. + | interact('.drop').dropzone({ + | accept: '.can-drop' || document.getElementById('single-drop'), + | overlap: 'pointer' || 'center' || zeroToOne + | } + = (boolean | object) The current setting or this Interactable + \*/ + dropzone: function (options) { + if (isObject(options)) { + this.options.drop.enabled = options.enabled === false? false: true; + this.setOnEvents('drop', options); + this.accept(options.accept); + + if (/^(pointer|center)$/.test(options.overlap)) { + this.options.drop.overlap = options.overlap; + } + else if (isNumber(options.overlap)) { + this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); + } + + return this; + } + + if (isBool(options)) { + this.options.drop.enabled = options; + + return this; + } + + return this.options.drop; + }, + + dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) { + var dropped = false; + + // if the dropzone has no rect (eg. display: none) + // call the custom dropChecker or just return false + if (!(rect = rect || this.getRect(dropElement))) { + return (this.options.dropChecker + ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) + : false); + } + + var dropOverlap = this.options.drop.overlap; + + if (dropOverlap === 'pointer') { + var page = getPageXY(pointer), + origin = getOriginXY(draggable, draggableElement), + horizontal, + vertical; + + page.x += origin.x; + page.y += origin.y; + + horizontal = (page.x > rect.left) && (page.x < rect.right); + vertical = (page.y > rect.top ) && (page.y < rect.bottom); + + dropped = horizontal && vertical; + } + + var dragRect = draggable.getRect(draggableElement); + + if (dropOverlap === 'center') { + var cx = dragRect.left + dragRect.width / 2, + cy = dragRect.top + dragRect.height / 2; + + dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; + } + + if (isNumber(dropOverlap)) { + var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) + * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), + overlapRatio = overlapArea / (dragRect.width * dragRect.height); + + dropped = overlapRatio >= dropOverlap; + } + + if (this.options.dropChecker) { + dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); + } + + return dropped; + }, + + /*\ + * Interactable.dropChecker + [ method ] + * + * Gets or sets the function used to check if a dragged element is + * over this Interactable. + * + - checker (function) #optional The function that will be called when checking for a drop + = (Function | Interactable) The checker function or this Interactable + * + * The checker function takes the following arguments: + * + - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag + - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer + - dropped (boolean) The value from the default drop check + - dropzone (Interactable) The dropzone interactable + - dropElement (Element) The dropzone element + - draggable (Interactable) The Interactable being dragged + - draggableElement (Element) The actual element that's being dragged + * + > Usage: + | interact(target) + | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent + | event, // TouchEvent/PointerEvent/MouseEvent + | dropped, // result of the default checker + | dropzone, // dropzone Interactable + | dropElement, // dropzone elemnt + | draggable, // draggable Interactable + | draggableElement) {// draggable element + | + | return dropped && event.target.hasAttribute('allow-drop'); + | } + \*/ + dropChecker: function (checker) { + if (isFunction(checker)) { + this.options.dropChecker = checker; + + return this; + } + if (checker === null) { + delete this.options.getRect; + + return this; + } + + return this.options.dropChecker; + }, + + /*\ + * Interactable.accept + [ method ] + * + * Deprecated. add an `accept` property to the options object passed to + * @Interactable.dropzone instead. + * + * Gets or sets the Element or CSS selector match that this + * Interactable accepts if it is a dropzone. + * + - newValue (Element | string | null) #optional + * If it is an Element, then only that element can be dropped into this dropzone. + * If it is a string, the element being dragged must match it as a selector. + * If it is null, the accept options is cleared - it accepts any element. + * + = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable + \*/ + accept: function (newValue) { + if (isElement(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + // test if it is a valid CSS selector + if (trySelector(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.drop.accept; + + return this; + } + + return this.options.drop.accept; + }, + + /*\ + * Interactable.resizable + [ method ] + * + * Gets or sets whether resize actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of resize elements + | var isResizeable = interact('input[type=text]').resizable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) + = (object) This Interactable + | interact(element).resizable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | edges: { + | top : true, // Use pointer coords to check for resize. + | left : false, // Disable resizing from left edge. + | bottom: '.resize-s',// Resize if pointer target matches selector + | right : handleEl // Resize if pointer target is the given Element + | }, + | + | // a value of 'none' will limit the resize rect to a minimum of 0x0 + | // 'negate' will allow the rect to have negative width/height + | // 'reposition' will keep the width/height positive by swapping + | // the top and bottom edges and/or swapping the left and right edges + | invert: 'none' || 'negate' || 'reposition' + | + | // limit multiple resizes. + | // See the explanation in the @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); + \*/ + resizable: function (options) { + if (isObject(options)) { + this.options.resize.enabled = options.enabled === false? false: true; + this.setPerAction('resize', options); + this.setOnEvents('resize', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.resize.axis = options.axis; + } + else if (options.axis === null) { + this.options.resize.axis = defaultOptions.resize.axis; + } + + if (isBool(options.square)) { + this.options.resize.square = options.square; + } + + return this; + } + if (isBool(options)) { + this.options.resize.enabled = options; + + return this; + } + return this.options.resize; + }, + + /*\ + * Interactable.squareResize + [ method ] + * + * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead + * + * Gets or sets whether resizing is forced 1:1 aspect + * + = (boolean) Current setting + * + * or + * + - newValue (boolean) #optional + = (object) this Interactable + \*/ + squareResize: function (newValue) { + if (isBool(newValue)) { + this.options.resize.square = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.resize.square; + + return this; + } + + return this.options.resize.square; + }, + + /*\ + * Interactable.gesturable + [ method ] + * + * Gets or sets whether multitouch gestures can be performed on the + * Interactable's element + * + = (boolean) Indicates if this can be the target of gesture events + | var isGestureable = interact(element).gesturable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) + = (object) this Interactable + | interact(element).gesturable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // limit multiple gestures. + | // See the explanation in @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); + \*/ + gesturable: function (options) { + if (isObject(options)) { + this.options.gesture.enabled = options.enabled === false? false: true; + this.setPerAction('gesture', options); + this.setOnEvents('gesture', options); + + return this; + } + + if (isBool(options)) { + this.options.gesture.enabled = options; + + return this; + } + + return this.options.gesture; + }, + + /*\ + * Interactable.autoScroll + [ method ] + ** + * Deprecated. Add an `autoscroll` property to the options object + * passed to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets whether dragging and resizing near the edges of the + * window/container trigger autoScroll for this Interactable + * + = (object) Object with autoScroll properties + * + * or + * + - options (object | boolean) #optional + * options can be: + * - an object with margin, distance and interval properties, + * - true or false to enable or disable autoScroll or + = (Interactable) this Interactable + \*/ + autoScroll: function (options) { + if (isObject(options)) { + options = extend({ actions: ['drag', 'resize']}, options); + } + else if (isBool(options)) { + options = { actions: ['drag', 'resize'], enabled: options }; + } + + return this.setOptions('autoScroll', options); + }, + + /*\ + * Interactable.snap + [ method ] + ** + * Deprecated. Add a `snap` property to the options object passed + * to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets if and how action coordinates are snapped. By + * default, snapping is relative to the pointer coordinates. You can + * change this by setting the + * [`elementOrigin`](https://github.com/taye/interact.js/pull/72). + ** + = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled + ** + * or + ** + - options (object | boolean | null) #optional + = (Interactable) this Interactable + > Usage + | interact(document.querySelector('#thing')).snap({ + | targets: [ + | // snap to this specific point + | { + | x: 100, + | y: 100, + | range: 25 + | }, + | // give this function the x and y page coords and snap to the object returned + | function (x, y) { + | return { + | x: x, + | y: (75 + 50 * Math.sin(x * 0.04)), + | range: 40 + | }; + | }, + | // create a function that snaps to a grid + | interact.createSnapGrid({ + | x: 50, + | y: 50, + | range: 10, // optional + | offset: { x: 5, y: 10 } // optional + | }) + | ], + | // do not snap during normal movement. + | // Instead, trigger only one snapped move event + | // immediately before the end event. + | endOnly: true, + | + | relativePoints: [ + | { x: 0, y: 0 }, // snap relative to the top left of the element + | { x: 1, y: 1 }, // and also to the bottom right + | ], + | + | // offset the snap target coordinates + | // can be an object with x/y or 'startCoords' + | offset: { x: 50, y: 50 } + | } + | }); + \*/ + snap: function (options) { + var ret = this.setOptions('snap', options); + + if (ret === this) { return this; } + + return ret.drag; + }, + + setOptions: function (option, options) { + var actions = options && isArray(options.actions) + ? options.actions + : ['drag']; + + var i; + + if (isObject(options) || isBool(options)) { + for (i = 0; i < actions.length; i++) { + var action = /resize/.test(actions[i])? 'resize' : actions[i]; + + if (!isObject(this.options[action])) { continue; } + + var thisOption = this.options[action][option]; + + if (isObject(options)) { + extend(thisOption, options); + thisOption.enabled = options.enabled === false? false: true; + + if (option === 'snap') { + if (thisOption.mode === 'grid') { + thisOption.targets = [ + interact.createSnapGrid(extend({ + offset: thisOption.gridOffset || { x: 0, y: 0 } + }, thisOption.grid || {})) + ]; + } + else if (thisOption.mode === 'anchor') { + thisOption.targets = thisOption.anchors; + } + else if (thisOption.mode === 'path') { + thisOption.targets = thisOption.paths; + } + + if ('elementOrigin' in options) { + thisOption.relativePoints = [options.elementOrigin]; + } + } + } + else if (isBool(options)) { + thisOption.enabled = options; + } + } + + return this; + } + + var ret = {}, + allActions = ['drag', 'resize', 'gesture']; + + for (i = 0; i < allActions.length; i++) { + if (option in defaultOptions[allActions[i]]) { + ret[allActions[i]] = this.options[allActions[i]][option]; + } + } + + return ret; + }, + + + /*\ + * Interactable.inertia + [ method ] + ** + * Deprecated. Add an `inertia` property to the options object passed + * to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets if and how events continue to run after the pointer is released + ** + = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled + ** + * or + ** + - options (object | boolean | null) #optional + = (Interactable) this Interactable + > Usage + | // enable and use default settings + | interact(element).inertia(true); + | + | // enable and use custom settings + | interact(element).inertia({ + | // value greater than 0 + | // high values slow the object down more quickly + | resistance : 16, + | + | // the minimum launch speed (pixels per second) that results in inertia start + | minSpeed : 200, + | + | // inertia will stop when the object slows down to this speed + | endSpeed : 20, + | + | // boolean; should actions be resumed when the pointer goes down during inertia + | allowResume : true, + | + | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy + | zeroResumeDelta: false, + | + | // if snap/restrict are set to be endOnly and inertia is enabled, releasing + | // the pointer without triggering inertia will animate from the release + | // point to the snaped/restricted point in the given amount of time (ms) + | smoothEndDuration: 300, + | + | // an array of action types that can have inertia (no gesture) + | actions : ['drag', 'resize'] + | }); + | + | // reset custom settings and use all defaults + | interact(element).inertia(null); + \*/ + inertia: function (options) { + var ret = this.setOptions('inertia', options); + + if (ret === this) { return this; } + + return ret.drag; + }, + + getAction: function (pointer, event, interaction, element) { + var action = this.defaultActionChecker(pointer, interaction, element); + + if (this.options.actionChecker) { + return this.options.actionChecker(pointer, event, action, this, element, interaction); + } + + return action; + }, + + defaultActionChecker: defaultActionChecker, + + /*\ + * Interactable.actionChecker + [ method ] + * + * Gets or sets the function used to check action to be performed on + * pointerDown + * + - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. + = (Function | Interactable) The checker function or this Interactable + * + | interact('.resize-drag') + | .resizable(true) + | .draggable(true) + | .actionChecker(function (pointer, event, action, interactable, element, interaction) { + | + | if (interact.matchesSelector(event.target, '.drag-handle') { + | // force drag with handle target + | action.name = drag; + | } + | else { + | // resize from the top and right edges + | action.name = 'resize'; + | action.edges = { top: true, right: true }; + | } + | + | return action; + | }); + \*/ + actionChecker: function (checker) { + if (isFunction(checker)) { + this.options.actionChecker = checker; + + return this; + } + + if (checker === null) { + delete this.options.actionChecker; + + return this; + } + + return this.options.actionChecker; + }, + + /*\ + * Interactable.getRect + [ method ] + * + * The default function to get an Interactables bounding rect. Can be + * overridden using @Interactable.rectChecker. + * + - element (Element) #optional The element to measure. + = (object) The object's bounding rectangle. + o { + o top : 0, + o left : 0, + o bottom: 0, + o right : 0, + o width : 0, + o height: 0 + o } + \*/ + getRect: function rectCheck (element) { + element = element || this._element; + + if (this.selector && !(isElement(element))) { + element = this._context.querySelector(this.selector); + } + + return getElementRect(element); + }, + + /*\ + * Interactable.rectChecker + [ method ] + * + * Returns or sets the function used to calculate the interactable's + * element's rectangle + * + - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect + = (function | object) The checker function or this Interactable + \*/ + rectChecker: function (checker) { + if (isFunction(checker)) { + this.getRect = checker; + + return this; + } + + if (checker === null) { + delete this.options.getRect; + + return this; + } + + return this.getRect; + }, + + /*\ + * Interactable.styleCursor + [ method ] + * + * Returns or sets whether the action that would be performed when the + * mouse on the element are checked on `mousemove` so that the cursor + * may be styled appropriately + * + - newValue (boolean) #optional + = (boolean | Interactable) The current setting or this Interactable + \*/ + styleCursor: function (newValue) { + if (isBool(newValue)) { + this.options.styleCursor = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.styleCursor; + + return this; + } + + return this.options.styleCursor; + }, + + /*\ + * Interactable.preventDefault + [ method ] + * + * Returns or sets whether to prevent the browser's default behaviour + * in response to pointer events. Can be set to: + * - `'always'` to always prevent + * - `'never'` to never prevent + * - `'auto'` to let interact.js try to determine what would be best + * + - newValue (string) #optional `true`, `false` or `'auto'` + = (string | Interactable) The current setting or this Interactable + \*/ + preventDefault: function (newValue) { + if (/^(always|never|auto)$/.test(newValue)) { + this.options.preventDefault = newValue; + return this; + } + + if (isBool(newValue)) { + this.options.preventDefault = newValue? 'always' : 'never'; + return this; + } + + return this.options.preventDefault; + }, + + /*\ + * Interactable.origin + [ method ] + * + * Gets or sets the origin of the Interactable's element. The x and y + * of the origin will be subtracted from action event coordinates. + * + - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector + * OR + - origin (Element) #optional An HTML or SVG Element whose rect will be used + ** + = (object) The current origin or this Interactable + \*/ + origin: function (newValue) { + if (trySelector(newValue)) { + this.options.origin = newValue; + return this; + } + else if (isObject(newValue)) { + this.options.origin = newValue; + return this; + } + + return this.options.origin; + }, + + /*\ + * Interactable.deltaSource + [ method ] + * + * Returns or sets the mouse coordinate types used to calculate the + * movement of the pointer. + * + - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work + = (string | object) The current deltaSource or this Interactable + \*/ + deltaSource: function (newValue) { + if (newValue === 'page' || newValue === 'client') { + this.options.deltaSource = newValue; + + return this; + } + + return this.options.deltaSource; + }, + + /*\ + * Interactable.restrict + [ method ] + ** + * Deprecated. Add a `restrict` property to the options object passed to + * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead. + * + * Returns or sets the rectangles within which actions on this + * interactable (after snap calculations) are restricted. By default, + * restricting is relative to the pointer coordinates. You can change + * this by setting the + * [`elementRect`](https://github.com/taye/interact.js/pull/72). + ** + - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self' + = (object) The current restrictions object or this Interactable + ** + | interact(element).restrict({ + | // the rect will be `interact.getElementRect(element.parentNode)` + | drag: element.parentNode, + | + | // x and y are relative to the the interactable's origin + | resize: { x: 100, y: 100, width: 200, height: 200 } + | }) + | + | interact('.draggable').restrict({ + | // the rect will be the selected element's parent + | drag: 'parent', + | + | // do not restrict during normal movement. + | // Instead, trigger only one restricted move event + | // immediately before the end event. + | endOnly: true, + | + | // https://github.com/taye/interact.js/pull/72#issue-41813493 + | elementRect: { top: 0, left: 0, bottom: 1, right: 1 } + | }); + \*/ + restrict: function (options) { + if (!isObject(options)) { + return this.setOptions('restrict', options); + } + + var actions = ['drag', 'resize', 'gesture'], + ret; + + for (var i = 0; i < actions.length; i++) { + var action = actions[i]; + + if (action in options) { + var perAction = extend({ + actions: [action], + restriction: options[action] + }, options); + + ret = this.setOptions('restrict', perAction); + } + } + + return ret; + }, + + /*\ + * Interactable.context + [ method ] + * + * Gets the selector context Node of the Interactable. The default is `window.document`. + * + = (Node) The context Node of this Interactable + ** + \*/ + context: function () { + return this._context; + }, + + _context: document, + + /*\ + * Interactable.ignoreFrom + [ method ] + * + * If the target of the `mousedown`, `pointerdown` or `touchstart` + * event or any of it's parents match the given CSS selector or + * Element, no drag/resize/gesture is started. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements + = (string | Element | object) The current ignoreFrom value or this Interactable + ** + | interact(element, { ignoreFrom: document.getElementById('no-action') }); + | // or + | interact(element).ignoreFrom('input, textarea, a'); + \*/ + ignoreFrom: function (newValue) { + if (trySelector(newValue)) { // CSS selector to match event.target + this.options.ignoreFrom = newValue; + return this; + } + + if (isElement(newValue)) { // specific element + this.options.ignoreFrom = newValue; + return this; + } + + return this.options.ignoreFrom; + }, + + /*\ + * Interactable.allowFrom + [ method ] + * + * A drag/resize/gesture is started only If the target of the + * `mousedown`, `pointerdown` or `touchstart` event or any of it's + * parents match the given CSS selector or Element. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element + = (string | Element | object) The current allowFrom value or this Interactable + ** + | interact(element, { allowFrom: document.getElementById('drag-handle') }); + | // or + | interact(element).allowFrom('.handle'); + \*/ + allowFrom: function (newValue) { + if (trySelector(newValue)) { // CSS selector to match event.target + this.options.allowFrom = newValue; + return this; + } + + if (isElement(newValue)) { // specific element + this.options.allowFrom = newValue; + return this; + } + + return this.options.allowFrom; + }, + + /*\ + * Interactable.element + [ method ] + * + * If this is not a selector Interactable, it returns the element this + * interactable represents + * + = (Element) HTML / SVG Element + \*/ + element: function () { + return this._element; + }, + + /*\ + * Interactable.fire + [ method ] + * + * Calls listeners for the given InteractEvent type bound globally + * and directly to this Interactable + * + - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable + = (Interactable) this Interactable + \*/ + fire: function (iEvent) { + if (!(iEvent && iEvent.type) || !contains(eventTypes, iEvent.type)) { + return this; + } + + var listeners, + i, + len, + onEvent = 'on' + iEvent.type, + funcName = ''; + + // Interactable#on() listeners + if (iEvent.type in this._iEvents) { + listeners = this._iEvents[iEvent.type]; + + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + funcName = listeners[i].name; + listeners[i](iEvent); + } + } + + // interactable.onevent listener + if (isFunction(this[onEvent])) { + funcName = this[onEvent].name; + this[onEvent](iEvent); + } + + // interact.on() listeners + if (iEvent.type in globalEvents && (listeners = globalEvents[iEvent.type])) { + + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + funcName = listeners[i].name; + listeners[i](iEvent); + } + } + + return this; + }, + + /*\ + * Interactable.on + [ method ] + * + * Binds a listener for an InteractEvent or DOM event. + * + - eventType (string | array | object) The types of events to listen for + - listener (function) The function to be called on the given event(s) + - useCapture (boolean) #optional useCapture flag for addEventListener + = (object) This Interactable + \*/ + on: function (eventType, listener, useCapture) { + var i; + + if (isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } + + if (isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.on(eventType[i], listener, useCapture); + } + + return this; + } + + if (isObject(eventType)) { + for (var prop in eventType) { + this.on(prop, eventType[prop], listener); + } + + return this; + } + + if (eventType === 'wheel') { + eventType = wheelEvent; + } + + // convert to boolean + useCapture = useCapture? true: false; + + if (contains(eventTypes, eventType)) { + // if this type of event was never bound to this Interactable + if (!(eventType in this._iEvents)) { + this._iEvents[eventType] = [listener]; + } + else { + this._iEvents[eventType].push(listener); + } + } + // delegated event for selector + else if (this.selector) { + if (!delegatedEvents[eventType]) { + delegatedEvents[eventType] = { + selectors: [], + contexts : [], + listeners: [] + }; + + // add delegate listener functions + for (i = 0; i < documents.length; i++) { + events.add(documents[i], eventType, delegateListener); + events.add(documents[i], eventType, delegateUseCapture, true); + } + } + + var delegated = delegatedEvents[eventType], + index; + + for (index = delegated.selectors.length - 1; index >= 0; index--) { + if (delegated.selectors[index] === this.selector + && delegated.contexts[index] === this._context) { + break; + } + } + + if (index === -1) { + index = delegated.selectors.length; + + delegated.selectors.push(this.selector); + delegated.contexts .push(this._context); + delegated.listeners.push([]); + } + + // keep listener and useCapture flag + delegated.listeners[index].push([listener, useCapture]); + } + else { + events.add(this._element, eventType, listener, useCapture); + } + + return this; + }, + + /*\ + * Interactable.off + [ method ] + * + * Removes an InteractEvent or DOM event listener + * + - eventType (string | array | object) The types of events that were listened for + - listener (function) The listener function to be removed + - useCapture (boolean) #optional useCapture flag for removeEventListener + = (object) This Interactable + \*/ + off: function (eventType, listener, useCapture) { + var i; + + if (isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } + + if (isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.off(eventType[i], listener, useCapture); + } + + return this; + } + + if (isObject(eventType)) { + for (var prop in eventType) { + this.off(prop, eventType[prop], listener); + } + + return this; + } + + var eventList, + index = -1; + + // convert to boolean + useCapture = useCapture? true: false; + + if (eventType === 'wheel') { + eventType = wheelEvent; + } + + // if it is an action event type + if (contains(eventTypes, eventType)) { + eventList = this._iEvents[eventType]; + + if (eventList && (index = indexOf(eventList, listener)) !== -1) { + this._iEvents[eventType].splice(index, 1); + } + } + // delegated event + else if (this.selector) { + var delegated = delegatedEvents[eventType], + matchFound = false; + + if (!delegated) { return this; } + + // count from last index of delegated to 0 + for (index = delegated.selectors.length - 1; index >= 0; index--) { + // look for matching selector and context Node + if (delegated.selectors[index] === this.selector + && delegated.contexts[index] === this._context) { + + var listeners = delegated.listeners[index]; + + // each item of the listeners array is an array: [function, useCaptureFlag] + for (i = listeners.length - 1; i >= 0; i--) { + var fn = listeners[i][0], + useCap = listeners[i][1]; + + // check if the listener functions and useCapture flags match + if (fn === listener && useCap === useCapture) { + // remove the listener from the array of listeners + listeners.splice(i, 1); + + // if all listeners for this interactable have been removed + // remove the interactable from the delegated arrays + if (!listeners.length) { + delegated.selectors.splice(index, 1); + delegated.contexts .splice(index, 1); + delegated.listeners.splice(index, 1); + + // remove delegate function from context + events.remove(this._context, eventType, delegateListener); + events.remove(this._context, eventType, delegateUseCapture, true); + + // remove the arrays if they are empty + if (!delegated.selectors.length) { + delegatedEvents[eventType] = null; + } + } + + // only remove one listener + matchFound = true; + break; + } + } + + if (matchFound) { break; } + } + } + } + // remove listener from this Interatable's element + else { + events.remove(this._element, eventType, listener, useCapture); + } + + return this; + }, + + /*\ + * Interactable.set + [ method ] + * + * Reset the options of this Interactable + - options (object) The new settings to apply + = (object) This Interactablw + \*/ + set: function (options) { + if (!isObject(options)) { + options = {}; + } + + this.options = extend({}, defaultOptions.base); + + var i, + actions = ['drag', 'drop', 'resize', 'gesture'], + methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], + perActions = extend(extend({}, defaultOptions.perAction), options[action] || {}); + + for (i = 0; i < actions.length; i++) { + var action = actions[i]; + + this.options[action] = extend({}, defaultOptions[action]); + + this.setPerAction(action, perActions); + + this[methods[i]](options[action]); + } + + var settings = [ + 'accept', 'actionChecker', 'allowFrom', 'deltaSource', + 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', + 'rectChecker' + ]; + + for (i = 0, len = settings.length; i < len; i++) { + var setting = settings[i]; + + this.options[setting] = defaultOptions.base[setting]; + + if (setting in options) { + this[setting](options[setting]); + } + } + + return this; + }, + + /*\ + * Interactable.unset + [ method ] + * + * Remove this interactable from the list of interactables and remove + * it's drag, drop, resize and gesture capabilities + * + = (object) @interact + \*/ + unset: function () { + events.remove(this._element, 'all'); + + if (!isString(this.selector)) { + events.remove(this, 'all'); + if (this.options.styleCursor) { + this._element.style.cursor = ''; + } + } + else { + // remove delegated events + for (var type in delegatedEvents) { + var delegated = delegatedEvents[type]; + + for (var i = 0; i < delegated.selectors.length; i++) { + if (delegated.selectors[i] === this.selector + && delegated.contexts[i] === this._context) { + + delegated.selectors.splice(i, 1); + delegated.contexts .splice(i, 1); + delegated.listeners.splice(i, 1); + + // remove the arrays if they are empty + if (!delegated.selectors.length) { + delegatedEvents[type] = null; + } + } + + events.remove(this._context, type, delegateListener); + events.remove(this._context, type, delegateUseCapture, true); + + break; + } + } + } + + this.dropzone(false); + + interactables.splice(indexOf(interactables, this), 1); + + return interact; + } +}; + +function warnOnce (method, message) { + var warned = false; + + return function () { + if (!warned) { + window.console.warn(message); + warned = true; + } + + return method.apply(this, arguments); + }; +} + +Interactable.prototype.snap = warnOnce(Interactable.prototype.snap, + 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping'); +Interactable.prototype.restrict = warnOnce(Interactable.prototype.restrict, + 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction'); +Interactable.prototype.inertia = warnOnce(Interactable.prototype.inertia, + 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia'); +Interactable.prototype.autoScroll = warnOnce(Interactable.prototype.autoScroll, + 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll'); +Interactable.prototype.squareResize = warnOnce(Interactable.prototype.squareResize, + 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square'); + +/*\ + * interact.isSet + [ method ] + * + * Check if an element has been set + - element (Element) The Element being searched for + = (boolean) Indicates if the element or CSS selector was previously passed to interact + \*/ +interact.isSet = function(element, options) { + return interactables.indexOfElement(element, options && options.context) !== -1; +}; + +/*\ + * interact.on + [ method ] + * + * Adds a global listener for an InteractEvent or adds a DOM event to + * `document` + * + - type (string | array | object) The types of events to listen for + - listener (function) The function to be called on the given event(s) + - useCapture (boolean) #optional useCapture flag for addEventListener + = (object) interact + \*/ +interact.on = function (type, listener, useCapture) { + if (isString(type) && type.search(' ') !== -1) { + type = type.trim().split(/ +/); + } + + if (isArray(type)) { + for (var i = 0; i < type.length; i++) { + interact.on(type[i], listener, useCapture); + } + + return interact; + } + + if (isObject(type)) { + for (var prop in type) { + interact.on(prop, type[prop], listener); + } + + return interact; + } + + // if it is an InteractEvent type, add listener to globalEvents + if (contains(eventTypes, type)) { + // if this type of event was never bound + if (!globalEvents[type]) { + globalEvents[type] = [listener]; + } + else { + globalEvents[type].push(listener); + } + } + // If non InteractEvent type, addEventListener to document + else { + events.add(document, type, listener, useCapture); + } + + return interact; +}; + +/*\ + * interact.off + [ method ] + * + * Removes a global InteractEvent listener or DOM event from `document` + * + - type (string | array | object) The types of events that were listened for + - listener (function) The listener function to be removed + - useCapture (boolean) #optional useCapture flag for removeEventListener + = (object) interact + \*/ +interact.off = function (type, listener, useCapture) { + if (isString(type) && type.search(' ') !== -1) { + type = type.trim().split(/ +/); + } + + if (isArray(type)) { + for (var i = 0; i < type.length; i++) { + interact.off(type[i], listener, useCapture); + } + + return interact; + } + + if (isObject(type)) { + for (var prop in type) { + interact.off(prop, type[prop], listener); + } + + return interact; + } + + if (!contains(eventTypes, type)) { + events.remove(document, type, listener, useCapture); + } + else { + var index; + + if (type in globalEvents + && (index = indexOf(globalEvents[type], listener)) !== -1) { + globalEvents[type].splice(index, 1); + } + } + + return interact; +}; + +/*\ + * interact.enableDragging + [ method ] + * + * Deprecated. + * + * Returns or sets whether dragging is enabled for any Interactables + * + - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables + = (boolean | object) The current setting or interact + \*/ +interact.enableDragging = warnOnce(function (newValue) { + if (newValue !== null && newValue !== undefined) { + actionIsEnabled.drag = newValue; + + return interact; + } + return actionIsEnabled.drag; +}, 'interact.enableDragging is deprecated and will soon be removed.'); + +/*\ + * interact.enableResizing + [ method ] + * + * Deprecated. + * + * Returns or sets whether resizing is enabled for any Interactables + * + - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables + = (boolean | object) The current setting or interact + \*/ +interact.enableResizing = warnOnce(function (newValue) { + if (newValue !== null && newValue !== undefined) { + actionIsEnabled.resize = newValue; + + return interact; + } + return actionIsEnabled.resize; +}, 'interact.enableResizing is deprecated and will soon be removed.'); + +/*\ + * interact.enableGesturing + [ method ] + * + * Deprecated. + * + * Returns or sets whether gesturing is enabled for any Interactables + * + - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables + = (boolean | object) The current setting or interact + \*/ +interact.enableGesturing = warnOnce(function (newValue) { + if (newValue !== null && newValue !== undefined) { + actionIsEnabled.gesture = newValue; + + return interact; + } + return actionIsEnabled.gesture; +}, 'interact.enableGesturing is deprecated and will soon be removed.'); + +interact.eventTypes = eventTypes; + +/*\ + * interact.debug + [ method ] + * + * Returns debugging data + = (object) An object with properties that outline the current state and expose internal functions and variables + \*/ +interact.debug = function () { + var interaction = interactions[0] || new Interaction(); + + return { + interactions : interactions, + target : interaction.target, + dragging : interaction.dragging, + resizing : interaction.resizing, + gesturing : interaction.gesturing, + prepared : interaction.prepared, + matches : interaction.matches, + matchElements : interaction.matchElements, + + prevCoords : interaction.prevCoords, + startCoords : interaction.startCoords, + + pointerIds : interaction.pointerIds, + pointers : interaction.pointers, + addPointer : listeners.addPointer, + removePointer : listeners.removePointer, + recordPointer : listeners.recordPointer, + + snap : interaction.snapStatus, + restrict : interaction.restrictStatus, + inertia : interaction.inertiaStatus, + + downTime : interaction.downTimes[0], + downEvent : interaction.downEvent, + downPointer : interaction.downPointer, + prevEvent : interaction.prevEvent, + + Interactable : Interactable, + interactables : interactables, + pointerIsDown : interaction.pointerIsDown, + defaultOptions : defaultOptions, + defaultActionChecker : defaultActionChecker, + + actionCursors : actionCursors, + dragMove : listeners.dragMove, + resizeMove : listeners.resizeMove, + gestureMove : listeners.gestureMove, + pointerUp : listeners.pointerUp, + pointerDown : listeners.pointerDown, + pointerMove : listeners.pointerMove, + pointerHover : listeners.pointerHover, + + eventTypes : eventTypes, + + events : events, + globalEvents : globalEvents, + delegatedEvents : delegatedEvents + }; +}; + +// expose the functions used to calculate multi-touch properties +interact.getTouchAverage = touchAverage; +interact.getTouchBBox = touchBBox; +interact.getTouchDistance = touchDistance; +interact.getTouchAngle = touchAngle; + +interact.getElementRect = getElementRect; +interact.matchesSelector = matchesSelector; +interact.closest = closest; + +/*\ + * interact.margin + [ method ] + * + * Returns or sets the margin for autocheck resizing used in + * @Interactable.getAction. That is the distance from the bottom and right + * edges of an element clicking in which will start resizing + * + - newValue (number) #optional + = (number | interact) The current margin value or interact + \*/ +interact.margin = function (newvalue) { + if (isNumber(newvalue)) { + margin = newvalue; + + return interact; + } + return margin; +}; + +/*\ + * interact.supportsTouch + [ method ] + * + = (boolean) Whether or not the browser supports touch input + \*/ +interact.supportsTouch = function () { + return supportsTouch; +}; + +/*\ + * interact.supportsPointerEvent + [ method ] + * + = (boolean) Whether or not the browser supports PointerEvents + \*/ +interact.supportsPointerEvent = function () { + return supportsPointerEvent; +}; + +/*\ + * interact.stop + [ method ] + * + * Cancels all interactions (end events are not fired) + * + - event (Event) An event on which to call preventDefault() + = (object) interact + \*/ +interact.stop = function (event) { + for (var i = interactions.length - 1; i > 0; i--) { + interactions[i].stop(event); + } + + return interact; +}; + +/*\ + * interact.dynamicDrop + [ method ] + * + * Returns or sets whether the dimensions of dropzone elements are + * calculated on every dragmove or only on dragstart for the default + * dropChecker + * + - newValue (boolean) #optional True to check on each move. False to check only before start + = (boolean | interact) The current setting or interact + \*/ +interact.dynamicDrop = function (newValue) { + if (isBool(newValue)) { + //if (dragging && dynamicDrop !== newValue && !newValue) { + //calcRects(dropzones); + //} + + dynamicDrop = newValue; + + return interact; + } + return dynamicDrop; +}; + +/*\ + * interact.pointerMoveTolerance + [ method ] + * Returns or sets the distance the pointer must be moved before an action + * sequence occurs. This also affects tolerance for tap events. + * + - newValue (number) #optional The movement from the start position must be greater than this value + = (number | Interactable) The current setting or interact + \*/ +interact.pointerMoveTolerance = function (newValue) { + if (isNumber(newValue)) { + pointerMoveTolerance = newValue; + + return this; + } + + return pointerMoveTolerance; +}; + +/*\ + * interact.maxInteractions + [ method ] + ** + * Returns or sets the maximum number of concurrent interactions allowed. + * By default only 1 interaction is allowed at a time (for backwards + * compatibility). To allow multiple interactions on the same Interactables + * and elements, you need to enable it in the draggable, resizable and + * gesturable `'max'` and `'maxPerElement'` options. + ** + - newValue (number) #optional Any number. newValue <= 0 means no interactions. + \*/ +interact.maxInteractions = function (newValue) { + if (isNumber(newValue)) { + maxInteractions = newValue; + + return this; + } + + return maxInteractions; +}; + +interact.createSnapGrid = function (grid) { + return function (x, y) { + var offsetX = 0, + offsetY = 0; + + if (isObject(grid.offset)) { + offsetX = grid.offset.x; + offsetY = grid.offset.y; + } + + var gridx = Math.round((x - offsetX) / grid.x), + gridy = Math.round((y - offsetY) / grid.y), + + newX = gridx * grid.x + offsetX, + newY = gridy * grid.y + offsetY; + + return { + x: newX, + y: newY, + range: grid.range + }; + }; +}; + +function endAllInteractions (event) { + for (var i = 0; i < interactions.length; i++) { + interactions[i].pointerEnd(event, event); + } +} + +function listenToDocument (doc) { + if (contains(documents, doc)) { return; } + + var win = doc.defaultView || doc.parentWindow; + + // add delegate event listener + for (var eventType in delegatedEvents) { + events.add(doc, eventType, delegateListener); + events.add(doc, eventType, delegateUseCapture, true); + } + + if (PointerEvent) { + if (PointerEvent === win.MSPointerEvent) { + pEventTypes = { + up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', + out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' }; + } + else { + pEventTypes = { + up: 'pointerup', down: 'pointerdown', over: 'pointerover', + out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }; + } + + events.add(doc, pEventTypes.down , listeners.selectorDown ); + events.add(doc, pEventTypes.move , listeners.pointerMove ); + events.add(doc, pEventTypes.over , listeners.pointerOver ); + events.add(doc, pEventTypes.out , listeners.pointerOut ); + events.add(doc, pEventTypes.up , listeners.pointerUp ); + events.add(doc, pEventTypes.cancel, listeners.pointerCancel); + + // autoscroll + events.add(doc, pEventTypes.move, listeners.autoScrollMove); + } + else { + events.add(doc, 'mousedown', listeners.selectorDown); + events.add(doc, 'mousemove', listeners.pointerMove ); + events.add(doc, 'mouseup' , listeners.pointerUp ); + events.add(doc, 'mouseover', listeners.pointerOver ); + events.add(doc, 'mouseout' , listeners.pointerOut ); + + events.add(doc, 'touchstart' , listeners.selectorDown ); + events.add(doc, 'touchmove' , listeners.pointerMove ); + events.add(doc, 'touchend' , listeners.pointerUp ); + events.add(doc, 'touchcancel', listeners.pointerCancel); + + // autoscroll + events.add(doc, 'mousemove', listeners.autoScrollMove); + events.add(doc, 'touchmove', listeners.autoScrollMove); + } + + events.add(win, 'blur', endAllInteractions); + + try { + if (win.frameElement) { + var parentDoc = win.frameElement.ownerDocument, + parentWindow = parentDoc.defaultView; + + events.add(parentDoc , 'mouseup' , listeners.pointerEnd); + events.add(parentDoc , 'touchend' , listeners.pointerEnd); + events.add(parentDoc , 'touchcancel' , listeners.pointerEnd); + events.add(parentDoc , 'pointerup' , listeners.pointerEnd); + events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd); + events.add(parentWindow, 'blur' , endAllInteractions ); + } + } + catch (error) { + interact.windowParentError = error; + } + + if (events.useAttachEvent) { + // For IE's lack of Event#preventDefault + events.add(doc, 'selectstart', function (event) { + var interaction = interactions[0]; + + if (interaction.currentAction()) { + interaction.checkAndPreventDefault(event); + } + }); + + // For IE's bad dblclick event sequence + events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick')); + } + + documents.push(doc); +} + +listenToDocument(document); + +function indexOf (array, target) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === target) { + return i; + } + } + + return -1; +} + +function contains (array, target) { + return indexOf(array, target) !== -1; +} + +function matchesSelector (element, selector, nodeList) { + if (ie8MatchesSelector) { + return ie8MatchesSelector(element, selector, nodeList); + } + + // remove /deep/ from selectors if shadowDOM polyfill is used + if (window !== realWindow) { + selector = selector.replace(/\/deep\//g, ' '); + } + + return element[prefixedMatchesSelector](selector); +} + +function matchesUpTo (element, selector, limit) { + while (isElement(element)) { + if (matchesSelector(element, selector)) { + return true; + } + + element = parentElement(element); + + if (element === limit) { + return matchesSelector(element, selector); + } + } + + return false; +} + +// For IE8's lack of an Element#matchesSelector +// taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified +if (!(prefixedMatchesSelector in Element.prototype) || !isFunction(Element.prototype[prefixedMatchesSelector])) { + ie8MatchesSelector = function (element, selector, elems) { + elems = elems || element.parentNode.querySelectorAll(selector); + + for (var i = 0, len = elems.length; i < len; i++) { + if (elems[i] === element) { + return true; + } + } + + return false; + }; +} + +// requestAnimationFrame polyfill +(function() { + var lastTime = 0, + vendors = ['ms', 'moz', 'webkit', 'o']; + + for(var x = 0; x < vendors.length && !realWindow.requestAnimationFrame; ++x) { + reqFrame = realWindow[vendors[x]+'RequestAnimationFrame']; + cancelFrame = realWindow[vendors[x]+'CancelAnimationFrame'] || realWindow[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!reqFrame) { + reqFrame = function(callback) { + var currTime = new Date().getTime(), + timeToCall = Math.max(0, 16 - (currTime - lastTime)), + id = setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + } + + if (!cancelFrame) { + cancelFrame = function(id) { + clearTimeout(id); + }; + } +}()); + +// CommonJS +if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = interact; + } + exports['interact'] = interact; +} +// AMD +else if (typeof define === 'function' && define.amd) { + define('interact', function() { + return interact; + }); +}; + +// Always export on the global scope +window['interact'] = interact; +},{"./utils/window":2}],2:[function(require,module,exports){ +var interactWindow = typeof window === 'undefined' ? undefined : window; + +module.exports = interactWindow; +},{}]},{},[1]) +//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvaW50ZXJhY3QuanMiLCJzcmMvdXRpbHMvd2luZG93LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaHVMQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiJ3VzZSBzdHJpY3QnO1xuXG4vKipcbiAqIGludGVyYWN0LmpzIHYxLjIuNFxuICpcbiAqIENvcHlyaWdodCAoYykgMjAxMi0yMDE1IFRheWUgQWRleWVtaSA8ZGV2QHRheWUubWU+XG4gKiBPcGVuIHNvdXJjZSB1bmRlciB0aGUgTUlUIExpY2Vuc2UuXG4gKiBodHRwczovL3Jhdy5naXRodWIuY29tL3RheWUvaW50ZXJhY3QuanMvbWFzdGVyL0xJQ0VOU0VcbiAqL1xuXG52YXIgcmVhbFdpbmRvdyA9IHJlcXVpcmUoJy4vdXRpbHMvd2luZG93Jyk7XG5cbi8vIHJldHVybiBlYXJseSBpZiB0aGVyZSdzIG5vIHdpbmRvdyB0byB3b3JrIHdpdGggKGVnLiBOb2RlLmpzKVxuaWYgKCFyZWFsV2luZG93KSB7IHJldHVybjsgfVxuXG52YXIgLy8gZ2V0IHdyYXBwZWQgd2luZG93IGlmIHVzaW5nIFNoYWRvdyBET00gcG9seWZpbGxcbiAgICB3aW5kb3cgPSAoZnVuY3Rpb24gKCkge1xuICAgICAgICAvLyBjcmVhdGUgYSBUZXh0Tm9kZVxuICAgICAgICB2YXIgZWwgPSByZWFsV2luZG93LmRvY3VtZW50LmNyZWF0ZVRleHROb2RlKCcnKTtcblxuICAgICAgICAvLyBjaGVjayBpZiBpdCdzIHdyYXBwZWQgYnkgYSBwb2x5ZmlsbFxuICAgICAgICBpZiAoZWwub3duZXJEb2N1bWVudCAhPT0gcmVhbFdpbmRvdy5kb2N1bWVudFxuICAgICAgICAgICAgJiYgdHlwZW9mIHJlYWxXaW5kb3cud3JhcCA9PT0gJ2Z1bmN0aW9uJ1xuICAgICAgICAgICAgJiYgcmVhbFdpbmRvdy53cmFwKGVsKSA9PT0gZWwpIHtcbiAgICAgICAgICAgIC8vIHJldHVybiB3cmFwcGVkIHdpbmRvd1xuICAgICAgICAgICAgcmV0dXJuIHJlYWxXaW5kb3cud3JhcChyZWFsV2luZG93KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIG5vIFNoYWRvdyBET00gcG9seWZpbCBvciBuYXRpdmUgaW1wbGVtZW50YXRpb25cbiAgICAgICAgcmV0dXJuIHJlYWxXaW5kb3c7XG4gICAgfSgpKSxcblxuICAgIGRvY3VtZW50ICAgICAgICAgICA9IHdpbmRvdy5kb2N1bWVudCxcbiAgICBEb2N1bWVudEZyYWdtZW50ICAgPSB3aW5kb3cuRG9jdW1lbnRGcmFnbWVudCAgIHx8IGJsYW5rLFxuICAgIFNWR0VsZW1lbnQgICAgICAgICA9IHdpbmRvdy5TVkdFbGVtZW50ICAgICAgICAgfHwgYmxhbmssXG4gICAgU1ZHU1ZHRWxlbWVudCAgICAgID0gd2luZG93LlNWR1NWR0VsZW1lbnQgICAgICB8fCBibGFuayxcbiAgICBTVkdFbGVtZW50SW5zdGFuY2UgPSB3aW5kb3cuU1ZHRWxlbWVudEluc3RhbmNlIHx8IGJsYW5rLFxuICAgIEhUTUxFbGVtZW50ICAgICAgICA9IHdpbmRvdy5IVE1MRWxlbWVudCAgICAgICAgfHwgd2luZG93LkVsZW1lbnQsXG5cbiAgICBQb2ludGVyRXZlbnQgPSAod2luZG93LlBvaW50ZXJFdmVudCB8fCB3aW5kb3cuTVNQb2ludGVyRXZlbnQpLFxuICAgIHBFdmVudFR5cGVzLFxuXG4gICAgaHlwb3QgPSBNYXRoLmh5cG90IHx8IGZ1bmN0aW9uICh4LCB5KSB7IHJldHVybiBNYXRoLnNxcnQoeCAqIHggKyB5ICogeSk7IH0sXG5cbiAgICB0bXBYWSA9IHt9LCAgICAgLy8gcmVkdWNlIG9iamVjdCBjcmVhdGlvbiBpbiBnZXRYWSgpXG5cbiAgICBkb2N1bWVudHMgICAgICAgPSBbXSwgICAvLyBhbGwgZG9jdW1lbnRzIGJlaW5nIGxpc3RlbmVkIHRvXG5cbiAgICBpbnRlcmFjdGFibGVzICAgPSBbXSwgICAvLyBhbGwgc2V0IGludGVyYWN0YWJsZXNcbiAgICBpbnRlcmFjdGlvbnMgICAgPSBbXSwgICAvLyBhbGwgaW50ZXJhY3Rpb25zXG5cbiAgICBkeW5hbWljRHJvcCAgICAgPSBmYWxzZSxcblxuLy8ge1xuLy8gICAgICB0eXBlOiB7XG4vLyAgICAgICAgICBzZWxlY3RvcnM6IFsnc2VsZWN0b3InLCAuLi5dLFxuLy8gICAgICAgICAgY29udGV4dHMgOiBbZG9jdW1lbnQsIC4uLl0sXG4vLyAgICAgICAgICBsaXN0ZW5lcnM6IFtbbGlzdGVuZXIsIHVzZUNhcHR1cmVdLCAuLi5dXG4vLyAgICAgIH1cbi8vICB9XG4gICAgZGVsZWdhdGVkRXZlbnRzID0ge30sXG5cbiAgICBkZWZhdWx0T3B0aW9ucyA9IHtcbiAgICAgICAgYmFzZToge1xuICAgICAgICAgICAgYWNjZXB0ICAgICAgICA6IG51bGwsXG4gICAgICAgICAgICBhY3Rpb25DaGVja2VyIDogbnVsbCxcbiAgICAgICAgICAgIHN0eWxlQ3Vyc29yICAgOiB0cnVlLFxuICAgICAgICAgICAgcHJldmVudERlZmF1bHQ6ICdhdXRvJyxcbiAgICAgICAgICAgIG9yaWdpbiAgICAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgICAgIGRlbHRhU291cmNlICAgOiAncGFnZScsXG4gICAgICAgICAgICBhbGxvd0Zyb20gICAgIDogbnVsbCxcbiAgICAgICAgICAgIGlnbm9yZUZyb20gICAgOiBudWxsLFxuICAgICAgICAgICAgX2NvbnRleHQgICAgICA6IGRvY3VtZW50LFxuICAgICAgICAgICAgZHJvcENoZWNrZXIgICA6IG51bGxcbiAgICAgICAgfSxcblxuICAgICAgICBkcmFnOiB7XG4gICAgICAgICAgICBlbmFibGVkOiBmYWxzZSxcbiAgICAgICAgICAgIG1hbnVhbFN0YXJ0OiB0cnVlLFxuICAgICAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgICAgIG1heFBlckVsZW1lbnQ6IDEsXG5cbiAgICAgICAgICAgIHNuYXA6IG51bGwsXG4gICAgICAgICAgICByZXN0cmljdDogbnVsbCxcbiAgICAgICAgICAgIGluZXJ0aWE6IG51bGwsXG4gICAgICAgICAgICBhdXRvU2Nyb2xsOiBudWxsLFxuXG4gICAgICAgICAgICBheGlzOiAneHknLFxuICAgICAgICB9LFxuXG4gICAgICAgIGRyb3A6IHtcbiAgICAgICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICAgICAgYWNjZXB0OiBudWxsLFxuICAgICAgICAgICAgb3ZlcmxhcDogJ3BvaW50ZXInXG4gICAgICAgIH0sXG5cbiAgICAgICAgcmVzaXplOiB7XG4gICAgICAgICAgICBlbmFibGVkOiBmYWxzZSxcbiAgICAgICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICAgICBtYXhQZXJFbGVtZW50OiAxLFxuXG4gICAgICAgICAgICBzbmFwOiBudWxsLFxuICAgICAgICAgICAgcmVzdHJpY3Q6IG51bGwsXG4gICAgICAgICAgICBpbmVydGlhOiBudWxsLFxuICAgICAgICAgICAgYXV0b1Njcm9sbDogbnVsbCxcblxuICAgICAgICAgICAgc3F1YXJlOiBmYWxzZSxcbiAgICAgICAgICAgIGF4aXM6ICd4eScsXG5cbiAgICAgICAgICAgIC8vIHVzZSBkZWZhdWx0IG1hcmdpblxuICAgICAgICAgICAgbWFyZ2luOiBOYU4sXG5cbiAgICAgICAgICAgIC8vIG9iamVjdCB3aXRoIHByb3BzIGxlZnQsIHJpZ2h0LCB0b3AsIGJvdHRvbSB3aGljaCBhcmVcbiAgICAgICAgICAgIC8vIHRydWUvZmFsc2UgdmFsdWVzIHRvIHJlc2l6ZSB3aGVuIHRoZSBwb2ludGVyIGlzIG92ZXIgdGhhdCBlZGdlLFxuICAgICAgICAgICAgLy8gQ1NTIHNlbGVjdG9ycyB0byBtYXRjaCB0aGUgaGFuZGxlcyBmb3IgZWFjaCBkaXJlY3Rpb25cbiAgICAgICAgICAgIC8vIG9yIHRoZSBFbGVtZW50cyBmb3IgZWFjaCBoYW5kbGVcbiAgICAgICAgICAgIGVkZ2VzOiBudWxsLFxuXG4gICAgICAgICAgICAvLyBhIHZhbHVlIG9mICdub25lJyB3aWxsIGxpbWl0IHRoZSByZXNpemUgcmVjdCB0byBhIG1pbmltdW0gb2YgMHgwXG4gICAgICAgICAgICAvLyAnbmVnYXRlJyB3aWxsIGFsb3cgdGhlIHJlY3QgdG8gaGF2ZSBuZWdhdGl2ZSB3aWR0aC9oZWlnaHRcbiAgICAgICAgICAgIC8vICdyZXBvc2l0aW9uJyB3aWxsIGtlZXAgdGhlIHdpZHRoL2hlaWdodCBwb3NpdGl2ZSBieSBzd2FwcGluZ1xuICAgICAgICAgICAgLy8gdGhlIHRvcCBhbmQgYm90dG9tIGVkZ2VzIGFuZC9vciBzd2FwcGluZyB0aGUgbGVmdCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgICAgICAgIGludmVydDogJ25vbmUnXG4gICAgICAgIH0sXG5cbiAgICAgICAgZ2VzdHVyZToge1xuICAgICAgICAgICAgbWFudWFsU3RhcnQ6IGZhbHNlLFxuICAgICAgICAgICAgZW5hYmxlZDogZmFsc2UsXG4gICAgICAgICAgICBtYXg6IEluZmluaXR5LFxuICAgICAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICAgICAgcmVzdHJpY3Q6IG51bGxcbiAgICAgICAgfSxcblxuICAgICAgICBwZXJBY3Rpb246IHtcbiAgICAgICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICAgICBtYXhQZXJFbGVtZW50OiAxLFxuXG4gICAgICAgICAgICBzbmFwOiB7XG4gICAgICAgICAgICAgICAgZW5hYmxlZCAgICAgOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBlbmRPbmx5ICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHJhbmdlICAgICAgIDogSW5maW5pdHksXG4gICAgICAgICAgICAgICAgdGFyZ2V0cyAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIG9mZnNldHMgICAgIDogbnVsbCxcblxuICAgICAgICAgICAgICAgIHJlbGF0aXZlUG9pbnRzOiBudWxsXG4gICAgICAgICAgICB9LFxuXG4gICAgICAgICAgICByZXN0cmljdDoge1xuICAgICAgICAgICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGVuZE9ubHk6IGZhbHNlXG4gICAgICAgICAgICB9LFxuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsOiB7XG4gICAgICAgICAgICAgICAgZW5hYmxlZCAgICAgOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBjb250YWluZXIgICA6IG51bGwsICAgICAvLyB0aGUgaXRlbSB0aGF0IGlzIHNjcm9sbGVkIChXaW5kb3cgb3IgSFRNTEVsZW1lbnQpXG4gICAgICAgICAgICAgICAgbWFyZ2luICAgICAgOiA2MCxcbiAgICAgICAgICAgICAgICBzcGVlZCAgICAgICA6IDMwMCAgICAgICAvLyB0aGUgc2Nyb2xsIHNwZWVkIGluIHBpeGVscyBwZXIgc2Vjb25kXG4gICAgICAgICAgICB9LFxuXG4gICAgICAgICAgICBpbmVydGlhOiB7XG4gICAgICAgICAgICAgICAgZW5hYmxlZCAgICAgICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHJlc2lzdGFuY2UgICAgICAgOiAxMCwgICAgLy8gdGhlIGxhbWJkYSBpbiBleHBvbmVudGlhbCBkZWNheVxuICAgICAgICAgICAgICAgIG1pblNwZWVkICAgICAgICAgOiAxMDAsICAgLy8gdGFyZ2V0IHNwZWVkIG11c3QgYmUgYWJvdmUgdGhpcyBmb3IgaW5lcnRpYSB0byBzdGFydFxuICAgICAgICAgICAgICAgIGVuZFNwZWVkICAgICAgICAgOiAxMCwgICAgLy8gdGhlIHNwZWVkIGF0IHdoaWNoIGluZXJ0aWEgaXMgc2xvdyBlbm91Z2ggdG8gc3RvcFxuICAgICAgICAgICAgICAgIGFsbG93UmVzdW1lICAgICAgOiB0cnVlLCAgLy8gYWxsb3cgcmVzdW1pbmcgYW4gYWN0aW9uIGluIGluZXJ0aWEgcGhhc2VcbiAgICAgICAgICAgICAgICB6ZXJvUmVzdW1lRGVsdGEgIDogdHJ1ZSwgIC8vIGlmIGFuIGFjdGlvbiBpcyByZXN1bWVkIGFmdGVyIGxhdW5jaCwgc2V0IGR4L2R5IHRvIDBcbiAgICAgICAgICAgICAgICBzbW9vdGhFbmREdXJhdGlvbjogMzAwICAgIC8vIGFuaW1hdGUgdG8gc25hcC9yZXN0cmljdCBlbmRPbmx5IGlmIHRoZXJlJ3Mgbm8gaW5lcnRpYVxuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuXG4gICAgICAgIF9ob2xkRHVyYXRpb246IDYwMFxuICAgIH0sXG5cbi8vIFRoaW5ncyByZWxhdGVkIHRvIGF1dG9TY3JvbGxcbiAgICBhdXRvU2Nyb2xsID0ge1xuICAgICAgICBpbnRlcmFjdGlvbjogbnVsbCxcbiAgICAgICAgaTogbnVsbCwgICAgLy8gdGhlIGhhbmRsZSByZXR1cm5lZCBieSB3aW5kb3cuc2V0SW50ZXJ2YWxcbiAgICAgICAgeDogMCwgeTogMCwgLy8gRGlyZWN0aW9uIGVhY2ggcHVsc2UgaXMgdG8gc2Nyb2xsIGluXG5cbiAgICAgICAgLy8gc2Nyb2xsIHRoZSB3aW5kb3cgYnkgdGhlIHZhbHVlcyBpbiBzY3JvbGwueC95XG4gICAgICAgIHNjcm9sbDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgdmFyIG9wdGlvbnMgPSBhdXRvU2Nyb2xsLmludGVyYWN0aW9uLnRhcmdldC5vcHRpb25zW2F1dG9TY3JvbGwuaW50ZXJhY3Rpb24ucHJlcGFyZWQubmFtZV0uYXV0b1Njcm9sbCxcbiAgICAgICAgICAgICAgICBjb250YWluZXIgPSBvcHRpb25zLmNvbnRhaW5lciB8fCBnZXRXaW5kb3coYXV0b1Njcm9sbC5pbnRlcmFjdGlvbi5lbGVtZW50KSxcbiAgICAgICAgICAgICAgICBub3cgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgIC8vIGNoYW5nZSBpbiB0aW1lIGluIHNlY29uZHNcbiAgICAgICAgICAgICAgICBkdCA9IChub3cgLSBhdXRvU2Nyb2xsLnByZXZUaW1lKSAvIDEwMDAsXG4gICAgICAgICAgICAvLyBkaXNwbGFjZW1lbnRcbiAgICAgICAgICAgICAgICBzID0gb3B0aW9ucy5zcGVlZCAqIGR0O1xuXG4gICAgICAgICAgICBpZiAocyA+PSAxKSB7XG4gICAgICAgICAgICAgICAgaWYgKGlzV2luZG93KGNvbnRhaW5lcikpIHtcbiAgICAgICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbEJ5KGF1dG9TY3JvbGwueCAqIHMsIGF1dG9TY3JvbGwueSAqIHMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChjb250YWluZXIpIHtcbiAgICAgICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbExlZnQgKz0gYXV0b1Njcm9sbC54ICogcztcbiAgICAgICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbFRvcCAgKz0gYXV0b1Njcm9sbC55ICogcztcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbm93O1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoYXV0b1Njcm9sbC5pc1Njcm9sbGluZykge1xuICAgICAgICAgICAgICAgIGNhbmNlbEZyYW1lKGF1dG9TY3JvbGwuaSk7XG4gICAgICAgICAgICAgICAgYXV0b1Njcm9sbC5pID0gcmVxRnJhbWUoYXV0b1Njcm9sbC5zY3JvbGwpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuXG4gICAgICAgIGlzU2Nyb2xsaW5nOiBmYWxzZSxcbiAgICAgICAgcHJldlRpbWU6IDAsXG5cbiAgICAgICAgc3RhcnQ6IGZ1bmN0aW9uIChpbnRlcmFjdGlvbikge1xuICAgICAgICAgICAgYXV0b1Njcm9sbC5pc1Njcm9sbGluZyA9IHRydWU7XG4gICAgICAgICAgICBjYW5jZWxGcmFtZShhdXRvU2Nyb2xsLmkpO1xuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsLmludGVyYWN0aW9uID0gaW50ZXJhY3Rpb247XG4gICAgICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICBhdXRvU2Nyb2xsLmkgPSByZXFGcmFtZShhdXRvU2Nyb2xsLnNjcm9sbCk7XG4gICAgICAgIH0sXG5cbiAgICAgICAgc3RvcDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgYXV0b1Njcm9sbC5pc1Njcm9sbGluZyA9IGZhbHNlO1xuICAgICAgICAgICAgY2FuY2VsRnJhbWUoYXV0b1Njcm9sbC5pKTtcbiAgICAgICAgfVxuICAgIH0sXG5cbi8vIERvZXMgdGhlIGJyb3dzZXIgc3VwcG9ydCB0b3VjaCBpbnB1dD9cbiAgICBzdXBwb3J0c1RvdWNoID0gKCgnb250b3VjaHN0YXJ0JyBpbiB3aW5kb3cpIHx8IHdpbmRvdy5Eb2N1bWVudFRvdWNoICYmIGRvY3VtZW50IGluc3RhbmNlb2Ygd2luZG93LkRvY3VtZW50VG91Y2gpLFxuXG4vLyBEb2VzIHRoZSBicm93c2VyIHN1cHBvcnQgUG9pbnRlckV2ZW50c1xuICAgIHN1cHBvcnRzUG9pbnRlckV2ZW50ID0gISFQb2ludGVyRXZlbnQsXG5cbi8vIExlc3MgUHJlY2lzaW9uIHdpdGggdG91Y2ggaW5wdXRcbiAgICBtYXJnaW4gPSBzdXBwb3J0c1RvdWNoIHx8IHN1cHBvcnRzUG9pbnRlckV2ZW50PyAyMDogMTAsXG5cbiAgICBwb2ludGVyTW92ZVRvbGVyYW5jZSA9IDEsXG5cbi8vIGZvciBpZ25vcmluZyBicm93c2VyJ3Mgc2ltdWxhdGVkIG1vdXNlIGV2ZW50c1xuICAgIHByZXZUb3VjaFRpbWUgPSAwLFxuXG4vLyBBbGxvdyB0aGlzIG1hbnkgaW50ZXJhY3Rpb25zIHRvIGhhcHBlbiBzaW11bHRhbmVvdXNseVxuICAgIG1heEludGVyYWN0aW9ucyA9IEluZmluaXR5LFxuXG4vLyBDaGVjayBpZiBpcyBJRTkgb3Igb2xkZXJcbiAgICBhY3Rpb25DdXJzb3JzID0gKGRvY3VtZW50LmFsbCAmJiAhd2luZG93LmF0b2IpID8ge1xuICAgICAgICBkcmFnICAgIDogJ21vdmUnLFxuICAgICAgICByZXNpemV4IDogJ2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpleSA6ICdzLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXh5OiAnc2UtcmVzaXplJyxcblxuICAgICAgICByZXNpemV0b3AgICAgICAgIDogJ24tcmVzaXplJyxcbiAgICAgICAgcmVzaXplbGVmdCAgICAgICA6ICd3LXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbSAgICAgOiAncy1yZXNpemUnLFxuICAgICAgICByZXNpemVyaWdodCAgICAgIDogJ2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wbGVmdCAgICA6ICdzZS1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b21yaWdodDogJ3NlLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXRvcHJpZ2h0ICAgOiAnbmUtcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tbGVmdCA6ICduZS1yZXNpemUnLFxuXG4gICAgICAgIGdlc3R1cmUgOiAnJ1xuICAgIH0gOiB7XG4gICAgICAgIGRyYWcgICAgOiAnbW92ZScsXG4gICAgICAgIHJlc2l6ZXggOiAnZXctcmVzaXplJyxcbiAgICAgICAgcmVzaXpleSA6ICducy1yZXNpemUnLFxuICAgICAgICByZXNpemV4eTogJ253c2UtcmVzaXplJyxcblxuICAgICAgICByZXNpemV0b3AgICAgICAgIDogJ25zLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWxlZnQgICAgICAgOiAnZXctcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tICAgICA6ICducy1yZXNpemUnLFxuICAgICAgICByZXNpemVyaWdodCAgICAgIDogJ2V3LXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXRvcGxlZnQgICAgOiAnbndzZS1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b21yaWdodDogJ253c2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wcmlnaHQgICA6ICduZXN3LXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbWxlZnQgOiAnbmVzdy1yZXNpemUnLFxuXG4gICAgICAgIGdlc3R1cmUgOiAnJ1xuICAgIH0sXG5cbiAgICBhY3Rpb25Jc0VuYWJsZWQgPSB7XG4gICAgICAgIGRyYWcgICA6IHRydWUsXG4gICAgICAgIHJlc2l6ZSA6IHRydWUsXG4gICAgICAgIGdlc3R1cmU6IHRydWVcbiAgICB9LFxuXG4vLyBiZWNhdXNlIFdlYmtpdCBhbmQgT3BlcmEgc3RpbGwgdXNlICdtb3VzZXdoZWVsJyBldmVudCB0eXBlXG4gICAgd2hlZWxFdmVudCA9ICdvbm1vdXNld2hlZWwnIGluIGRvY3VtZW50PyAnbW91c2V3aGVlbCc6ICd3aGVlbCcsXG5cbiAgICBldmVudFR5cGVzID0gW1xuICAgICAgICAnZHJhZ3N0YXJ0JyxcbiAgICAgICAgJ2RyYWdtb3ZlJyxcbiAgICAgICAgJ2RyYWdpbmVydGlhc3RhcnQnLFxuICAgICAgICAnZHJhZ2VuZCcsXG4gICAgICAgICdkcmFnZW50ZXInLFxuICAgICAgICAnZHJhZ2xlYXZlJyxcbiAgICAgICAgJ2Ryb3BhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wZGVhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wbW92ZScsXG4gICAgICAgICdkcm9wJyxcbiAgICAgICAgJ3Jlc2l6ZXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZW1vdmUnLFxuICAgICAgICAncmVzaXplaW5lcnRpYXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZWVuZCcsXG4gICAgICAgICdnZXN0dXJlc3RhcnQnLFxuICAgICAgICAnZ2VzdHVyZW1vdmUnLFxuICAgICAgICAnZ2VzdHVyZWluZXJ0aWFzdGFydCcsXG4gICAgICAgICdnZXN0dXJlZW5kJyxcblxuICAgICAgICAnZG93bicsXG4gICAgICAgICdtb3ZlJyxcbiAgICAgICAgJ3VwJyxcbiAgICAgICAgJ2NhbmNlbCcsXG4gICAgICAgICd0YXAnLFxuICAgICAgICAnZG91YmxldGFwJyxcbiAgICAgICAgJ2hvbGQnXG4gICAgXSxcblxuICAgIGdsb2JhbEV2ZW50cyA9IHt9LFxuXG4vLyBPcGVyYSBNb2JpbGUgbXVzdCBiZSBoYW5kbGVkIGRpZmZlcmVudGx5XG4gICAgaXNPcGVyYU1vYmlsZSA9IG5hdmlnYXRvci5hcHBOYW1lID09ICdPcGVyYScgJiZcbiAgICAgICAgc3VwcG9ydHNUb3VjaCAmJlxuICAgICAgICBuYXZpZ2F0b3IudXNlckFnZW50Lm1hdGNoKCdQcmVzdG8nKSxcblxuLy8gc2Nyb2xsaW5nIGRvZXNuJ3QgY2hhbmdlIHRoZSByZXN1bHQgb2Zcbi8vIGdldEJvdW5kaW5nQ2xpZW50UmVjdC9nZXRDbGllbnRSZWN0cyBvbiBpT1MgPD03IGJ1dCBpdCBkb2VzIG9uIGlPUyA4XG4gICAgaXNJT1M3b3JMb3dlciA9ICgvaVAoaG9uZXxvZHxhZCkvLnRlc3QobmF2aWdhdG9yLnBsYXRmb3JtKVxuICAgICYmIC9PUyBbMS03XVteXFxkXS8udGVzdChuYXZpZ2F0b3IuYXBwVmVyc2lvbikpLFxuXG4vLyBwcmVmaXggbWF0Y2hlc1NlbGVjdG9yXG4gICAgcHJlZml4ZWRNYXRjaGVzU2VsZWN0b3IgPSAnbWF0Y2hlcycgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICdtYXRjaGVzJzogJ3dlYmtpdE1hdGNoZXNTZWxlY3RvcicgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICd3ZWJraXRNYXRjaGVzU2VsZWN0b3InOiAnbW96TWF0Y2hlc1NlbGVjdG9yJyBpbiBFbGVtZW50LnByb3RvdHlwZT9cbiAgICAgICAgJ21vek1hdGNoZXNTZWxlY3Rvcic6ICdvTWF0Y2hlc1NlbGVjdG9yJyBpbiBFbGVtZW50LnByb3RvdHlwZT9cbiAgICAgICAgJ29NYXRjaGVzU2VsZWN0b3InOiAnbXNNYXRjaGVzU2VsZWN0b3InLFxuXG4vLyB3aWxsIGJlIHBvbHlmaWxsIGZ1bmN0aW9uIGlmIGJyb3dzZXIgaXMgSUU4XG4gICAgaWU4TWF0Y2hlc1NlbGVjdG9yLFxuXG4vLyBuYXRpdmUgcmVxdWVzdEFuaW1hdGlvbkZyYW1lIG9yIHBvbHlmaWxsXG4gICAgcmVxRnJhbWUgPSByZWFsV2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZSxcbiAgICBjYW5jZWxGcmFtZSA9IHJlYWxXaW5kb3cuY2FuY2VsQW5pbWF0aW9uRnJhbWUsXG5cbi8vIEV2ZW50cyB3cmFwcGVyXG4gICAgZXZlbnRzID0gKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIHVzZUF0dGFjaEV2ZW50ID0gKCdhdHRhY2hFdmVudCcgaW4gd2luZG93KSAmJiAhKCdhZGRFdmVudExpc3RlbmVyJyBpbiB3aW5kb3cpLFxuICAgICAgICAgICAgYWRkRXZlbnQgICAgICAgPSB1c2VBdHRhY2hFdmVudD8gICdhdHRhY2hFdmVudCc6ICdhZGRFdmVudExpc3RlbmVyJyxcbiAgICAgICAgICAgIHJlbW92ZUV2ZW50ICAgID0gdXNlQXR0YWNoRXZlbnQ/ICAnZGV0YWNoRXZlbnQnOiAncmVtb3ZlRXZlbnRMaXN0ZW5lcicsXG4gICAgICAgICAgICBvbiAgICAgICAgICAgICA9IHVzZUF0dGFjaEV2ZW50PyAnb24nOiAnJyxcblxuICAgICAgICAgICAgZWxlbWVudHMgICAgICAgICAgPSBbXSxcbiAgICAgICAgICAgIHRhcmdldHMgICAgICAgICAgID0gW10sXG4gICAgICAgICAgICBhdHRhY2hlZExpc3RlbmVycyA9IFtdO1xuXG4gICAgICAgIGZ1bmN0aW9uIGFkZCAoZWxlbWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgIHZhciBlbGVtZW50SW5kZXggPSBpbmRleE9mKGVsZW1lbnRzLCBlbGVtZW50KSxcbiAgICAgICAgICAgICAgICB0YXJnZXQgPSB0YXJnZXRzW2VsZW1lbnRJbmRleF07XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ID0ge1xuICAgICAgICAgICAgICAgICAgICBldmVudHM6IHt9LFxuICAgICAgICAgICAgICAgICAgICB0eXBlQ291bnQ6IDBcbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgZWxlbWVudEluZGV4ID0gZWxlbWVudHMucHVzaChlbGVtZW50KSAtIDE7XG4gICAgICAgICAgICAgICAgdGFyZ2V0cy5wdXNoKHRhcmdldCk7XG5cbiAgICAgICAgICAgICAgICBhdHRhY2hlZExpc3RlbmVycy5wdXNoKCh1c2VBdHRhY2hFdmVudCA/IHtcbiAgICAgICAgICAgICAgICAgICAgc3VwcGxpZWQ6IFtdLFxuICAgICAgICAgICAgICAgICAgICB3cmFwcGVkIDogW10sXG4gICAgICAgICAgICAgICAgICAgIHVzZUNvdW50OiBbXVxuICAgICAgICAgICAgICAgIH0gOiBudWxsKSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0LmV2ZW50c1t0eXBlXSkge1xuICAgICAgICAgICAgICAgIHRhcmdldC5ldmVudHNbdHlwZV0gPSBbXTtcbiAgICAgICAgICAgICAgICB0YXJnZXQudHlwZUNvdW50Kys7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghY29udGFpbnModGFyZ2V0LmV2ZW50c1t0eXBlXSwgbGlzdGVuZXIpKSB7XG4gICAgICAgICAgICAgICAgdmFyIHJldDtcblxuICAgICAgICAgICAgICAgIGlmICh1c2VBdHRhY2hFdmVudCkge1xuICAgICAgICAgICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gYXR0YWNoZWRMaXN0ZW5lcnNbZWxlbWVudEluZGV4XSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVySW5kZXggPSBpbmRleE9mKGxpc3RlbmVycy5zdXBwbGllZCwgbGlzdGVuZXIpO1xuXG4gICAgICAgICAgICAgICAgICAgIHZhciB3cmFwcGVkID0gbGlzdGVuZXJzLndyYXBwZWRbbGlzdGVuZXJJbmRleF0gfHwgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFldmVudC5pbW1lZGlhdGVQcm9wYWdhdGlvblN0b3BwZWQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQudGFyZ2V0ID0gZXZlbnQuc3JjRWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQuY3VycmVudFRhcmdldCA9IGVsZW1lbnQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQgPSBldmVudC5wcmV2ZW50RGVmYXVsdCB8fCBwcmV2ZW50RGVmO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5zdG9wUHJvcGFnYXRpb24gPSBldmVudC5zdG9wUHJvcGFnYXRpb24gfHwgc3RvcFByb3A7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiA9IGV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiB8fCBzdG9wSW1tUHJvcDtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoL21vdXNlfGNsaWNrLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5wYWdlWCA9IGV2ZW50LmNsaWVudFggKyBnZXRXaW5kb3coZWxlbWVudCkuZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbExlZnQ7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5wYWdlWSA9IGV2ZW50LmNsaWVudFkgKyBnZXRXaW5kb3coZWxlbWVudCkuZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyKGV2ZW50KTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgICAgIHJldCA9IGVsZW1lbnRbYWRkRXZlbnRdKG9uICsgdHlwZSwgd3JhcHBlZCwgQm9vbGVhbih1c2VDYXB0dXJlKSk7XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKGxpc3RlbmVySW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMuc3VwcGxpZWQucHVzaChsaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMud3JhcHBlZC5wdXNoKHdyYXBwZWQpO1xuICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnVzZUNvdW50LnB1c2goMSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0rKztcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0ID0gZWxlbWVudFthZGRFdmVudF0odHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUgfHwgZmFsc2UpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHJldDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIHJlbW92ZSAoZWxlbWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgIHZhciBpLFxuICAgICAgICAgICAgICAgIGVsZW1lbnRJbmRleCA9IGluZGV4T2YoZWxlbWVudHMsIGVsZW1lbnQpLFxuICAgICAgICAgICAgICAgIHRhcmdldCA9IHRhcmdldHNbZWxlbWVudEluZGV4XSxcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnMsXG4gICAgICAgICAgICAgICAgbGlzdGVuZXJJbmRleCxcbiAgICAgICAgICAgICAgICB3cmFwcGVkID0gbGlzdGVuZXI7XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0IHx8ICF0YXJnZXQuZXZlbnRzKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodXNlQXR0YWNoRXZlbnQpIHtcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnMgPSBhdHRhY2hlZExpc3RlbmVyc1tlbGVtZW50SW5kZXhdO1xuICAgICAgICAgICAgICAgIGxpc3RlbmVySW5kZXggPSBpbmRleE9mKGxpc3RlbmVycy5zdXBwbGllZCwgbGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgIHdyYXBwZWQgPSBsaXN0ZW5lcnMud3JhcHBlZFtsaXN0ZW5lckluZGV4XTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHR5cGUgPT09ICdhbGwnKSB7XG4gICAgICAgICAgICAgICAgZm9yICh0eXBlIGluIHRhcmdldC5ldmVudHMpIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHRhcmdldC5ldmVudHMuaGFzT3duUHJvcGVydHkodHlwZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZShlbGVtZW50LCB0eXBlLCAnYWxsJyk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGFyZ2V0LmV2ZW50c1t0eXBlXSkge1xuICAgICAgICAgICAgICAgIHZhciBsZW4gPSB0YXJnZXQuZXZlbnRzW3R5cGVdLmxlbmd0aDtcblxuICAgICAgICAgICAgICAgIGlmIChsaXN0ZW5lciA9PT0gJ2FsbCcpIHtcbiAgICAgICAgICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZW1vdmUoZWxlbWVudCwgdHlwZSwgdGFyZ2V0LmV2ZW50c1t0eXBlXVtpXSwgQm9vbGVhbih1c2VDYXB0dXJlKSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRhcmdldC5ldmVudHNbdHlwZV1baV0gPT09IGxpc3RlbmVyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudFtyZW1vdmVFdmVudF0ob24gKyB0eXBlLCB3cmFwcGVkLCB1c2VDYXB0dXJlIHx8IGZhbHNlKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh1c2VBdHRhY2hFdmVudCAmJiBsaXN0ZW5lcnMpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnVzZUNvdW50W2xpc3RlbmVySW5kZXhdLS07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0gPT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zdXBwbGllZC5zcGxpY2UobGlzdGVuZXJJbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMud3JhcHBlZC5zcGxpY2UobGlzdGVuZXJJbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnQuc3BsaWNlKGxpc3RlbmVySW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAodGFyZ2V0LmV2ZW50c1t0eXBlXSAmJiB0YXJnZXQuZXZlbnRzW3R5cGVdLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdID0gbnVsbDtcbiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0LnR5cGVDb3VudC0tO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCF0YXJnZXQudHlwZUNvdW50KSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0cy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICAgICAgICAgICAgICBhdHRhY2hlZExpc3RlbmVycy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIHByZXZlbnREZWYgKCkge1xuICAgICAgICAgICAgdGhpcy5yZXR1cm5WYWx1ZSA9IGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gc3RvcFByb3AgKCkge1xuICAgICAgICAgICAgdGhpcy5jYW5jZWxCdWJibGUgPSB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gc3RvcEltbVByb3AgKCkge1xuICAgICAgICAgICAgdGhpcy5jYW5jZWxCdWJibGUgPSB0cnVlO1xuICAgICAgICAgICAgdGhpcy5pbW1lZGlhdGVQcm9wYWdhdGlvblN0b3BwZWQgPSB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGFkZDogYWRkLFxuICAgICAgICAgICAgcmVtb3ZlOiByZW1vdmUsXG4gICAgICAgICAgICB1c2VBdHRhY2hFdmVudDogdXNlQXR0YWNoRXZlbnQsXG5cbiAgICAgICAgICAgIF9lbGVtZW50czogZWxlbWVudHMsXG4gICAgICAgICAgICBfdGFyZ2V0czogdGFyZ2V0cyxcbiAgICAgICAgICAgIF9hdHRhY2hlZExpc3RlbmVyczogYXR0YWNoZWRMaXN0ZW5lcnNcbiAgICAgICAgfTtcbiAgICB9KCkpO1xuXG5mdW5jdGlvbiBibGFuayAoKSB7fVxuXG5mdW5jdGlvbiBpc0VsZW1lbnQgKG8pIHtcbiAgICBpZiAoIW8gfHwgKHR5cGVvZiBvICE9PSAnb2JqZWN0JykpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICB2YXIgX3dpbmRvdyA9IGdldFdpbmRvdyhvKSB8fCB3aW5kb3c7XG5cbiAgICByZXR1cm4gKC9vYmplY3R8ZnVuY3Rpb24vLnRlc3QodHlwZW9mIF93aW5kb3cuRWxlbWVudClcbiAgICAgICAgPyBvIGluc3RhbmNlb2YgX3dpbmRvdy5FbGVtZW50IC8vRE9NMlxuICAgICAgICA6IG8ubm9kZVR5cGUgPT09IDEgJiYgdHlwZW9mIG8ubm9kZU5hbWUgPT09IFwic3RyaW5nXCIpO1xufVxuZnVuY3Rpb24gaXNXaW5kb3cgKHRoaW5nKSB7IHJldHVybiAhISh0aGluZyAmJiB0aGluZy5XaW5kb3cpICYmICh0aGluZyBpbnN0YW5jZW9mIHRoaW5nLldpbmRvdyk7IH1cbmZ1bmN0aW9uIGlzRG9jRnJhZyAodGhpbmcpIHsgcmV0dXJuICEhdGhpbmcgJiYgdGhpbmcgaW5zdGFuY2VvZiBEb2N1bWVudEZyYWdtZW50OyB9XG5mdW5jdGlvbiBpc0FycmF5ICh0aGluZykge1xuICAgIHJldHVybiBpc09iamVjdCh0aGluZylcbiAgICAgICAgJiYgKHR5cGVvZiB0aGluZy5sZW5ndGggIT09IHVuZGVmaW5lZClcbiAgICAgICAgJiYgaXNGdW5jdGlvbih0aGluZy5zcGxpY2UpO1xufVxuZnVuY3Rpb24gaXNPYmplY3QgICAodGhpbmcpIHsgcmV0dXJuICEhdGhpbmcgJiYgKHR5cGVvZiB0aGluZyA9PT0gJ29iamVjdCcpOyB9XG5mdW5jdGlvbiBpc0Z1bmN0aW9uICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnZnVuY3Rpb24nOyB9XG5mdW5jdGlvbiBpc051bWJlciAgICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnbnVtYmVyJyAgOyB9XG5mdW5jdGlvbiBpc0Jvb2wgICAgICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnYm9vbGVhbicgOyB9XG5mdW5jdGlvbiBpc1N0cmluZyAgICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnc3RyaW5nJyAgOyB9XG5cbmZ1bmN0aW9uIHRyeVNlbGVjdG9yICh2YWx1ZSkge1xuICAgIGlmICghaXNTdHJpbmcodmFsdWUpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgLy8gYW4gZXhjZXB0aW9uIHdpbGwgYmUgcmFpc2VkIGlmIGl0IGlzIGludmFsaWRcbiAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKHZhbHVlKTtcbiAgICByZXR1cm4gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gZXh0ZW5kIChkZXN0LCBzb3VyY2UpIHtcbiAgICBmb3IgKHZhciBwcm9wIGluIHNvdXJjZSkge1xuICAgICAgICBkZXN0W3Byb3BdID0gc291cmNlW3Byb3BdO1xuICAgIH1cbiAgICByZXR1cm4gZGVzdDtcbn1cblxuZnVuY3Rpb24gY29weUNvb3JkcyAoZGVzdCwgc3JjKSB7XG4gICAgZGVzdC5wYWdlID0gZGVzdC5wYWdlIHx8IHt9O1xuICAgIGRlc3QucGFnZS54ID0gc3JjLnBhZ2UueDtcbiAgICBkZXN0LnBhZ2UueSA9IHNyYy5wYWdlLnk7XG5cbiAgICBkZXN0LmNsaWVudCA9IGRlc3QuY2xpZW50IHx8IHt9O1xuICAgIGRlc3QuY2xpZW50LnggPSBzcmMuY2xpZW50Lng7XG4gICAgZGVzdC5jbGllbnQueSA9IHNyYy5jbGllbnQueTtcblxuICAgIGRlc3QudGltZVN0YW1wID0gc3JjLnRpbWVTdGFtcDtcbn1cblxuZnVuY3Rpb24gc2V0RXZlbnRYWSAodGFyZ2V0T2JqLCBwb2ludGVyLCBpbnRlcmFjdGlvbikge1xuICAgIGlmICghcG9pbnRlcikge1xuICAgICAgICBpZiAoaW50ZXJhY3Rpb24ucG9pbnRlcklkcy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICBwb2ludGVyID0gdG91Y2hBdmVyYWdlKGludGVyYWN0aW9uLnBvaW50ZXJzKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHBvaW50ZXIgPSBpbnRlcmFjdGlvbi5wb2ludGVyc1swXTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGdldFBhZ2VYWShwb2ludGVyLCB0bXBYWSwgaW50ZXJhY3Rpb24pO1xuICAgIHRhcmdldE9iai5wYWdlLnggPSB0bXBYWS54O1xuICAgIHRhcmdldE9iai5wYWdlLnkgPSB0bXBYWS55O1xuXG4gICAgZ2V0Q2xpZW50WFkocG9pbnRlciwgdG1wWFksIGludGVyYWN0aW9uKTtcbiAgICB0YXJnZXRPYmouY2xpZW50LnggPSB0bXBYWS54O1xuICAgIHRhcmdldE9iai5jbGllbnQueSA9IHRtcFhZLnk7XG5cbiAgICB0YXJnZXRPYmoudGltZVN0YW1wID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG59XG5cbmZ1bmN0aW9uIHNldEV2ZW50RGVsdGFzICh0YXJnZXRPYmosIHByZXYsIGN1cikge1xuICAgIHRhcmdldE9iai5wYWdlLnggICAgID0gY3VyLnBhZ2UueCAgICAgIC0gcHJldi5wYWdlLng7XG4gICAgdGFyZ2V0T2JqLnBhZ2UueSAgICAgPSBjdXIucGFnZS55ICAgICAgLSBwcmV2LnBhZ2UueTtcbiAgICB0YXJnZXRPYmouY2xpZW50LnggICA9IGN1ci5jbGllbnQueCAgICAtIHByZXYuY2xpZW50Lng7XG4gICAgdGFyZ2V0T2JqLmNsaWVudC55ICAgPSBjdXIuY2xpZW50LnkgICAgLSBwcmV2LmNsaWVudC55O1xuICAgIHRhcmdldE9iai50aW1lU3RhbXAgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSAtIHByZXYudGltZVN0YW1wO1xuXG4gICAgLy8gc2V0IHBvaW50ZXIgdmVsb2NpdHlcbiAgICB2YXIgZHQgPSBNYXRoLm1heCh0YXJnZXRPYmoudGltZVN0YW1wIC8gMTAwMCwgMC4wMDEpO1xuICAgIHRhcmdldE9iai5wYWdlLnNwZWVkICAgPSBoeXBvdCh0YXJnZXRPYmoucGFnZS54LCB0YXJnZXRPYmoucGFnZS55KSAvIGR0O1xuICAgIHRhcmdldE9iai5wYWdlLnZ4ICAgICAgPSB0YXJnZXRPYmoucGFnZS54IC8gZHQ7XG4gICAgdGFyZ2V0T2JqLnBhZ2UudnkgICAgICA9IHRhcmdldE9iai5wYWdlLnkgLyBkdDtcblxuICAgIHRhcmdldE9iai5jbGllbnQuc3BlZWQgPSBoeXBvdCh0YXJnZXRPYmouY2xpZW50LngsIHRhcmdldE9iai5wYWdlLnkpIC8gZHQ7XG4gICAgdGFyZ2V0T2JqLmNsaWVudC52eCAgICA9IHRhcmdldE9iai5jbGllbnQueCAvIGR0O1xuICAgIHRhcmdldE9iai5jbGllbnQudnkgICAgPSB0YXJnZXRPYmouY2xpZW50LnkgLyBkdDtcbn1cblxuLy8gR2V0IHNwZWNpZmllZCBYL1kgY29vcmRzIGZvciBtb3VzZSBvciBldmVudC50b3VjaGVzWzBdXG5mdW5jdGlvbiBnZXRYWSAodHlwZSwgcG9pbnRlciwgeHkpIHtcbiAgICB4eSA9IHh5IHx8IHt9O1xuICAgIHR5cGUgPSB0eXBlIHx8ICdwYWdlJztcblxuICAgIHh5LnggPSBwb2ludGVyW3R5cGUgKyAnWCddO1xuICAgIHh5LnkgPSBwb2ludGVyW3R5cGUgKyAnWSddO1xuXG4gICAgcmV0dXJuIHh5O1xufVxuXG5mdW5jdGlvbiBnZXRQYWdlWFkgKHBvaW50ZXIsIHBhZ2UsIGludGVyYWN0aW9uKSB7XG4gICAgcGFnZSA9IHBhZ2UgfHwge307XG5cbiAgICBpZiAocG9pbnRlciBpbnN0YW5jZW9mIEludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgaWYgKC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbiB8fCBwb2ludGVyLmludGVyYWN0aW9uO1xuXG4gICAgICAgICAgICBleHRlbmQocGFnZSwgaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy51cENvb3Jkcy5wYWdlKTtcblxuICAgICAgICAgICAgcGFnZS54ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3g7XG4gICAgICAgICAgICBwYWdlLnkgKz0gaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy5zeTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHBhZ2UueCA9IHBvaW50ZXIucGFnZVg7XG4gICAgICAgICAgICBwYWdlLnkgPSBwb2ludGVyLnBhZ2VZO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8vIE9wZXJhIE1vYmlsZSBoYW5kbGVzIHRoZSB2aWV3cG9ydCBhbmQgc2Nyb2xsaW5nIG9kZGx5XG4gICAgZWxzZSBpZiAoaXNPcGVyYU1vYmlsZSkge1xuICAgICAgICBnZXRYWSgnc2NyZWVuJywgcG9pbnRlciwgcGFnZSk7XG5cbiAgICAgICAgcGFnZS54ICs9IHdpbmRvdy5zY3JvbGxYO1xuICAgICAgICBwYWdlLnkgKz0gd2luZG93LnNjcm9sbFk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBnZXRYWSgncGFnZScsIHBvaW50ZXIsIHBhZ2UpO1xuICAgIH1cblxuICAgIHJldHVybiBwYWdlO1xufVxuXG5mdW5jdGlvbiBnZXRDbGllbnRYWSAocG9pbnRlciwgY2xpZW50LCBpbnRlcmFjdGlvbikge1xuICAgIGNsaWVudCA9IGNsaWVudCB8fCB7fTtcblxuICAgIGlmIChwb2ludGVyIGluc3RhbmNlb2YgSW50ZXJhY3RFdmVudCkge1xuICAgICAgICBpZiAoL2luZXJ0aWFzdGFydC8udGVzdChwb2ludGVyLnR5cGUpKSB7XG4gICAgICAgICAgICBleHRlbmQoY2xpZW50LCBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLnVwQ29vcmRzLmNsaWVudCk7XG5cbiAgICAgICAgICAgIGNsaWVudC54ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3g7XG4gICAgICAgICAgICBjbGllbnQueSArPSBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLnN5O1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgY2xpZW50LnggPSBwb2ludGVyLmNsaWVudFg7XG4gICAgICAgICAgICBjbGllbnQueSA9IHBvaW50ZXIuY2xpZW50WTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgLy8gT3BlcmEgTW9iaWxlIGhhbmRsZXMgdGhlIHZpZXdwb3J0IGFuZCBzY3JvbGxpbmcgb2RkbHlcbiAgICAgICAgZ2V0WFkoaXNPcGVyYU1vYmlsZT8gJ3NjcmVlbic6ICdjbGllbnQnLCBwb2ludGVyLCBjbGllbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBjbGllbnQ7XG59XG5cbmZ1bmN0aW9uIGdldFNjcm9sbFhZICh3aW4pIHtcbiAgICB3aW4gPSB3aW4gfHwgd2luZG93O1xuICAgIHJldHVybiB7XG4gICAgICAgIHg6IHdpbi5zY3JvbGxYIHx8IHdpbi5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsTGVmdCxcbiAgICAgICAgeTogd2luLnNjcm9sbFkgfHwgd2luLmRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5zY3JvbGxUb3BcbiAgICB9O1xufVxuXG5mdW5jdGlvbiBnZXRQb2ludGVySWQgKHBvaW50ZXIpIHtcbiAgICByZXR1cm4gaXNOdW1iZXIocG9pbnRlci5wb2ludGVySWQpPyBwb2ludGVyLnBvaW50ZXJJZCA6IHBvaW50ZXIuaWRlbnRpZmllcjtcbn1cblxuZnVuY3Rpb24gZ2V0QWN0dWFsRWxlbWVudCAoZWxlbWVudCkge1xuICAgIHJldHVybiAoZWxlbWVudCBpbnN0YW5jZW9mIFNWR0VsZW1lbnRJbnN0YW5jZVxuICAgICAgICA/IGVsZW1lbnQuY29ycmVzcG9uZGluZ1VzZUVsZW1lbnRcbiAgICAgICAgOiBlbGVtZW50KTtcbn1cblxuZnVuY3Rpb24gZ2V0V2luZG93IChub2RlKSB7XG4gICAgaWYgKGlzV2luZG93KG5vZGUpKSB7XG4gICAgICAgIHJldHVybiBub2RlO1xuICAgIH1cblxuICAgIHZhciByb290Tm9kZSA9IChub2RlLm93bmVyRG9jdW1lbnQgfHwgbm9kZSk7XG5cbiAgICByZXR1cm4gcm9vdE5vZGUuZGVmYXVsdFZpZXcgfHwgcm9vdE5vZGUucGFyZW50V2luZG93IHx8IHdpbmRvdztcbn1cblxuZnVuY3Rpb24gZ2V0RWxlbWVudFJlY3QgKGVsZW1lbnQpIHtcbiAgICB2YXIgc2Nyb2xsID0gaXNJT1M3b3JMb3dlclxuICAgICAgICAgICAgPyB7IHg6IDAsIHk6IDAgfVxuICAgICAgICAgICAgOiBnZXRTY3JvbGxYWShnZXRXaW5kb3coZWxlbWVudCkpLFxuICAgICAgICBjbGllbnRSZWN0ID0gKGVsZW1lbnQgaW5zdGFuY2VvZiBTVkdFbGVtZW50KT9cbiAgICAgICAgICAgIGVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk6XG4gICAgICAgICAgICBlbGVtZW50LmdldENsaWVudFJlY3RzKClbMF07XG5cbiAgICByZXR1cm4gY2xpZW50UmVjdCAmJiB7XG4gICAgICAgICAgICBsZWZ0ICA6IGNsaWVudFJlY3QubGVmdCAgICsgc2Nyb2xsLngsXG4gICAgICAgICAgICByaWdodCA6IGNsaWVudFJlY3QucmlnaHQgICsgc2Nyb2xsLngsXG4gICAgICAgICAgICB0b3AgICA6IGNsaWVudFJlY3QudG9wICAgICsgc2Nyb2xsLnksXG4gICAgICAgICAgICBib3R0b206IGNsaWVudFJlY3QuYm90dG9tICsgc2Nyb2xsLnksXG4gICAgICAgICAgICB3aWR0aCA6IGNsaWVudFJlY3Qud2lkdGggfHwgY2xpZW50UmVjdC5yaWdodCAtIGNsaWVudFJlY3QubGVmdCxcbiAgICAgICAgICAgIGhlaWdodDogY2xpZW50UmVjdC5oZWlnaCB8fCBjbGllbnRSZWN0LmJvdHRvbSAtIGNsaWVudFJlY3QudG9wXG4gICAgICAgIH07XG59XG5cbmZ1bmN0aW9uIGdldFRvdWNoUGFpciAoZXZlbnQpIHtcbiAgICB2YXIgdG91Y2hlcyA9IFtdO1xuXG4gICAgLy8gYXJyYXkgb2YgdG91Y2hlcyBpcyBzdXBwbGllZFxuICAgIGlmIChpc0FycmF5KGV2ZW50KSkge1xuICAgICAgICB0b3VjaGVzWzBdID0gZXZlbnRbMF07XG4gICAgICAgIHRvdWNoZXNbMV0gPSBldmVudFsxXTtcbiAgICB9XG4gICAgLy8gYW4gZXZlbnRcbiAgICBlbHNlIHtcbiAgICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICd0b3VjaGVuZCcpIHtcbiAgICAgICAgICAgIGlmIChldmVudC50b3VjaGVzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudC5jaGFuZ2VkVG91Y2hlc1swXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGV2ZW50LnRvdWNoZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgdG91Y2hlc1swXSA9IGV2ZW50LmNoYW5nZWRUb3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudC5jaGFuZ2VkVG91Y2hlc1sxXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgdG91Y2hlc1sxXSA9IGV2ZW50LnRvdWNoZXNbMV07XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdG91Y2hlcztcbn1cblxuZnVuY3Rpb24gdG91Y2hBdmVyYWdlIChldmVudCkge1xuICAgIHZhciB0b3VjaGVzID0gZ2V0VG91Y2hQYWlyKGV2ZW50KTtcblxuICAgIHJldHVybiB7XG4gICAgICAgIHBhZ2VYOiAodG91Y2hlc1swXS5wYWdlWCArIHRvdWNoZXNbMV0ucGFnZVgpIC8gMixcbiAgICAgICAgcGFnZVk6ICh0b3VjaGVzWzBdLnBhZ2VZICsgdG91Y2hlc1sxXS5wYWdlWSkgLyAyLFxuICAgICAgICBjbGllbnRYOiAodG91Y2hlc1swXS5jbGllbnRYICsgdG91Y2hlc1sxXS5jbGllbnRYKSAvIDIsXG4gICAgICAgIGNsaWVudFk6ICh0b3VjaGVzWzBdLmNsaWVudFkgKyB0b3VjaGVzWzFdLmNsaWVudFkpIC8gMlxuICAgIH07XG59XG5cbmZ1bmN0aW9uIHRvdWNoQkJveCAoZXZlbnQpIHtcbiAgICBpZiAoIWV2ZW50Lmxlbmd0aCAmJiAhKGV2ZW50LnRvdWNoZXMgJiYgZXZlbnQudG91Y2hlcy5sZW5ndGggPiAxKSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdmFyIHRvdWNoZXMgPSBnZXRUb3VjaFBhaXIoZXZlbnQpLFxuICAgICAgICBtaW5YID0gTWF0aC5taW4odG91Y2hlc1swXS5wYWdlWCwgdG91Y2hlc1sxXS5wYWdlWCksXG4gICAgICAgIG1pblkgPSBNYXRoLm1pbih0b3VjaGVzWzBdLnBhZ2VZLCB0b3VjaGVzWzFdLnBhZ2VZKSxcbiAgICAgICAgbWF4WCA9IE1hdGgubWF4KHRvdWNoZXNbMF0ucGFnZVgsIHRvdWNoZXNbMV0ucGFnZVgpLFxuICAgICAgICBtYXhZID0gTWF0aC5tYXgodG91Y2hlc1swXS5wYWdlWSwgdG91Y2hlc1sxXS5wYWdlWSk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICB4OiBtaW5YLFxuICAgICAgICB5OiBtaW5ZLFxuICAgICAgICBsZWZ0OiBtaW5YLFxuICAgICAgICB0b3A6IG1pblksXG4gICAgICAgIHdpZHRoOiBtYXhYIC0gbWluWCxcbiAgICAgICAgaGVpZ2h0OiBtYXhZIC0gbWluWVxuICAgIH07XG59XG5cbmZ1bmN0aW9uIHRvdWNoRGlzdGFuY2UgKGV2ZW50LCBkZWx0YVNvdXJjZSkge1xuICAgIGRlbHRhU291cmNlID0gZGVsdGFTb3VyY2UgfHwgZGVmYXVsdE9wdGlvbnMuZGVsdGFTb3VyY2U7XG5cbiAgICB2YXIgc291cmNlWCA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgIHRvdWNoZXMgPSBnZXRUb3VjaFBhaXIoZXZlbnQpO1xuXG5cbiAgICB2YXIgZHggPSB0b3VjaGVzWzBdW3NvdXJjZVhdIC0gdG91Y2hlc1sxXVtzb3VyY2VYXSxcbiAgICAgICAgZHkgPSB0b3VjaGVzWzBdW3NvdXJjZVldIC0gdG91Y2hlc1sxXVtzb3VyY2VZXTtcblxuICAgIHJldHVybiBoeXBvdChkeCwgZHkpO1xufVxuXG5mdW5jdGlvbiB0b3VjaEFuZ2xlIChldmVudCwgcHJldkFuZ2xlLCBkZWx0YVNvdXJjZSkge1xuICAgIGRlbHRhU291cmNlID0gZGVsdGFTb3VyY2UgfHwgZGVmYXVsdE9wdGlvbnMuZGVsdGFTb3VyY2U7XG5cbiAgICB2YXIgc291cmNlWCA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgIHRvdWNoZXMgPSBnZXRUb3VjaFBhaXIoZXZlbnQpLFxuICAgICAgICBkeCA9IHRvdWNoZXNbMF1bc291cmNlWF0gLSB0b3VjaGVzWzFdW3NvdXJjZVhdLFxuICAgICAgICBkeSA9IHRvdWNoZXNbMF1bc291cmNlWV0gLSB0b3VjaGVzWzFdW3NvdXJjZVldLFxuICAgICAgICBhbmdsZSA9IDE4MCAqIE1hdGguYXRhbihkeSAvIGR4KSAvIE1hdGguUEk7XG5cbiAgICBpZiAoaXNOdW1iZXIocHJldkFuZ2xlKSkge1xuICAgICAgICB2YXIgZHIgPSBhbmdsZSAtIHByZXZBbmdsZSxcbiAgICAgICAgICAgIGRyQ2xhbXBlZCA9IGRyICUgMzYwO1xuXG4gICAgICAgIGlmIChkckNsYW1wZWQgPiAzMTUpIHtcbiAgICAgICAgICAgIGFuZ2xlIC09IDM2MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChkckNsYW1wZWQgPiAxMzUpIHtcbiAgICAgICAgICAgIGFuZ2xlIC09IDE4MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChkckNsYW1wZWQgPCAtMzE1KSB7XG4gICAgICAgICAgICBhbmdsZSArPSAzNjAgKyAoYW5nbGUgLyAzNjApfDAgKiAzNjA7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZHJDbGFtcGVkIDwgLTEzNSkge1xuICAgICAgICAgICAgYW5nbGUgKz0gMTgwICsgKGFuZ2xlIC8gMzYwKXwwICogMzYwO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuICBhbmdsZTtcbn1cblxuZnVuY3Rpb24gZ2V0T3JpZ2luWFkgKGludGVyYWN0YWJsZSwgZWxlbWVudCkge1xuICAgIHZhciBvcmlnaW4gPSBpbnRlcmFjdGFibGVcbiAgICAgICAgPyBpbnRlcmFjdGFibGUub3B0aW9ucy5vcmlnaW5cbiAgICAgICAgOiBkZWZhdWx0T3B0aW9ucy5vcmlnaW47XG5cbiAgICBpZiAob3JpZ2luID09PSAncGFyZW50Jykge1xuICAgICAgICBvcmlnaW4gPSBwYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgIH1cbiAgICBlbHNlIGlmIChvcmlnaW4gPT09ICdzZWxmJykge1xuICAgICAgICBvcmlnaW4gPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAodHJ5U2VsZWN0b3Iob3JpZ2luKSkge1xuICAgICAgICBvcmlnaW4gPSBjbG9zZXN0KGVsZW1lbnQsIG9yaWdpbikgfHwgeyB4OiAwLCB5OiAwIH07XG4gICAgfVxuXG4gICAgaWYgKGlzRnVuY3Rpb24ob3JpZ2luKSkge1xuICAgICAgICBvcmlnaW4gPSBvcmlnaW4oaW50ZXJhY3RhYmxlICYmIGVsZW1lbnQpO1xuICAgIH1cblxuICAgIGlmIChpc0VsZW1lbnQob3JpZ2luKSkgIHtcbiAgICAgICAgb3JpZ2luID0gZ2V0RWxlbWVudFJlY3Qob3JpZ2luKTtcbiAgICB9XG5cbiAgICBvcmlnaW4ueCA9ICgneCcgaW4gb3JpZ2luKT8gb3JpZ2luLnggOiBvcmlnaW4ubGVmdDtcbiAgICBvcmlnaW4ueSA9ICgneScgaW4gb3JpZ2luKT8gb3JpZ2luLnkgOiBvcmlnaW4udG9wO1xuXG4gICAgcmV0dXJuIG9yaWdpbjtcbn1cblxuLy8gaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL2EvNTYzNDUyOC8yMjgwODg4XG5mdW5jdGlvbiBfZ2V0UUJlemllclZhbHVlKHQsIHAxLCBwMiwgcDMpIHtcbiAgICB2YXIgaVQgPSAxIC0gdDtcbiAgICByZXR1cm4gaVQgKiBpVCAqIHAxICsgMiAqIGlUICogdCAqIHAyICsgdCAqIHQgKiBwMztcbn1cblxuZnVuY3Rpb24gZ2V0UXVhZHJhdGljQ3VydmVQb2ludChzdGFydFgsIHN0YXJ0WSwgY3BYLCBjcFksIGVuZFgsIGVuZFksIHBvc2l0aW9uKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgeDogIF9nZXRRQmV6aWVyVmFsdWUocG9zaXRpb24sIHN0YXJ0WCwgY3BYLCBlbmRYKSxcbiAgICAgICAgeTogIF9nZXRRQmV6aWVyVmFsdWUocG9zaXRpb24sIHN0YXJ0WSwgY3BZLCBlbmRZKVxuICAgIH07XG59XG5cbi8vIGh0dHA6Ly9naXptYS5jb20vZWFzaW5nL1xuZnVuY3Rpb24gZWFzZU91dFF1YWQgKHQsIGIsIGMsIGQpIHtcbiAgICB0IC89IGQ7XG4gICAgcmV0dXJuIC1jICogdCoodC0yKSArIGI7XG59XG5cbmZ1bmN0aW9uIG5vZGVDb250YWlucyAocGFyZW50LCBjaGlsZCkge1xuICAgIHdoaWxlIChjaGlsZCkge1xuICAgICAgICBpZiAoY2hpbGQgPT09IHBhcmVudCkge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cblxuICAgICAgICBjaGlsZCA9IGNoaWxkLnBhcmVudE5vZGU7XG4gICAgfVxuXG4gICAgcmV0dXJuIGZhbHNlO1xufVxuXG5mdW5jdGlvbiBjbG9zZXN0IChjaGlsZCwgc2VsZWN0b3IpIHtcbiAgICB2YXIgcGFyZW50ID0gcGFyZW50RWxlbWVudChjaGlsZCk7XG5cbiAgICB3aGlsZSAoaXNFbGVtZW50KHBhcmVudCkpIHtcbiAgICAgICAgaWYgKG1hdGNoZXNTZWxlY3RvcihwYXJlbnQsIHNlbGVjdG9yKSkgeyByZXR1cm4gcGFyZW50OyB9XG5cbiAgICAgICAgcGFyZW50ID0gcGFyZW50RWxlbWVudChwYXJlbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBudWxsO1xufVxuXG5mdW5jdGlvbiBwYXJlbnRFbGVtZW50IChub2RlKSB7XG4gICAgdmFyIHBhcmVudCA9IG5vZGUucGFyZW50Tm9kZTtcblxuICAgIGlmIChpc0RvY0ZyYWcocGFyZW50KSkge1xuICAgICAgICAvLyBza2lwIHBhc3QgI3NoYWRvLXJvb3QgZnJhZ21lbnRzXG4gICAgICAgIHdoaWxlICgocGFyZW50ID0gcGFyZW50Lmhvc3QpICYmIGlzRG9jRnJhZyhwYXJlbnQpKSB7fVxuXG4gICAgICAgIHJldHVybiBwYXJlbnQ7XG4gICAgfVxuXG4gICAgcmV0dXJuIHBhcmVudDtcbn1cblxuZnVuY3Rpb24gaW5Db250ZXh0IChpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICByZXR1cm4gaW50ZXJhY3RhYmxlLl9jb250ZXh0ID09PSBlbGVtZW50Lm93bmVyRG9jdW1lbnRcbiAgICAgICAgfHwgbm9kZUNvbnRhaW5zKGludGVyYWN0YWJsZS5fY29udGV4dCwgZWxlbWVudCk7XG59XG5cbmZ1bmN0aW9uIHRlc3RJZ25vcmUgKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlRWxlbWVudCwgZWxlbWVudCkge1xuICAgIHZhciBpZ25vcmVGcm9tID0gaW50ZXJhY3RhYmxlLm9wdGlvbnMuaWdub3JlRnJvbTtcblxuICAgIGlmICghaWdub3JlRnJvbSB8fCAhaXNFbGVtZW50KGVsZW1lbnQpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgaWYgKGlzU3RyaW5nKGlnbm9yZUZyb20pKSB7XG4gICAgICAgIHJldHVybiBtYXRjaGVzVXBUbyhlbGVtZW50LCBpZ25vcmVGcm9tLCBpbnRlcmFjdGFibGVFbGVtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAoaXNFbGVtZW50KGlnbm9yZUZyb20pKSB7XG4gICAgICAgIHJldHVybiBub2RlQ29udGFpbnMoaWdub3JlRnJvbSwgZWxlbWVudCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGZhbHNlO1xufVxuXG5mdW5jdGlvbiB0ZXN0QWxsb3cgKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlRWxlbWVudCwgZWxlbWVudCkge1xuICAgIHZhciBhbGxvd0Zyb20gPSBpbnRlcmFjdGFibGUub3B0aW9ucy5hbGxvd0Zyb207XG5cbiAgICBpZiAoIWFsbG93RnJvbSkgeyByZXR1cm4gdHJ1ZTsgfVxuXG4gICAgaWYgKCFpc0VsZW1lbnQoZWxlbWVudCkpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICBpZiAoaXNTdHJpbmcoYWxsb3dGcm9tKSkge1xuICAgICAgICByZXR1cm4gbWF0Y2hlc1VwVG8oZWxlbWVudCwgYWxsb3dGcm9tLCBpbnRlcmFjdGFibGVFbGVtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAoaXNFbGVtZW50KGFsbG93RnJvbSkpIHtcbiAgICAgICAgcmV0dXJuIG5vZGVDb250YWlucyhhbGxvd0Zyb20sIGVsZW1lbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBmYWxzZTtcbn1cblxuZnVuY3Rpb24gY2hlY2tBeGlzIChheGlzLCBpbnRlcmFjdGFibGUpIHtcbiAgICBpZiAoIWludGVyYWN0YWJsZSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgIHZhciB0aGlzQXhpcyA9IGludGVyYWN0YWJsZS5vcHRpb25zLmRyYWcuYXhpcztcblxuICAgIHJldHVybiAoYXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gYXhpcyk7XG59XG5cbmZ1bmN0aW9uIGNoZWNrU25hcCAoaW50ZXJhY3RhYmxlLCBhY3Rpb24pIHtcbiAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zO1xuXG4gICAgaWYgKC9ecmVzaXplLy50ZXN0KGFjdGlvbikpIHtcbiAgICAgICAgYWN0aW9uID0gJ3Jlc2l6ZSc7XG4gICAgfVxuXG4gICAgcmV0dXJuIG9wdGlvbnNbYWN0aW9uXS5zbmFwICYmIG9wdGlvbnNbYWN0aW9uXS5zbmFwLmVuYWJsZWQ7XG59XG5cbmZ1bmN0aW9uIGNoZWNrUmVzdHJpY3QgKGludGVyYWN0YWJsZSwgYWN0aW9uKSB7XG4gICAgdmFyIG9wdGlvbnMgPSBpbnRlcmFjdGFibGUub3B0aW9ucztcblxuICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgIGFjdGlvbiA9ICdyZXNpemUnO1xuICAgIH1cblxuICAgIHJldHVybiAgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0ICYmIG9wdGlvbnNbYWN0aW9uXS5yZXN0cmljdC5lbmFibGVkO1xufVxuXG5mdW5jdGlvbiBjaGVja0F1dG9TY3JvbGwgKGludGVyYWN0YWJsZSwgYWN0aW9uKSB7XG4gICAgdmFyIG9wdGlvbnMgPSBpbnRlcmFjdGFibGUub3B0aW9ucztcblxuICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgIGFjdGlvbiA9ICdyZXNpemUnO1xuICAgIH1cblxuICAgIHJldHVybiAgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwgJiYgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwuZW5hYmxlZDtcbn1cblxuZnVuY3Rpb24gd2l0aGluSW50ZXJhY3Rpb25MaW1pdCAoaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBhY3Rpb24pIHtcbiAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICBtYXhBY3Rpb25zID0gb3B0aW9uc1thY3Rpb24ubmFtZV0ubWF4LFxuICAgICAgICBtYXhQZXJFbGVtZW50ID0gb3B0aW9uc1thY3Rpb24ubmFtZV0ubWF4UGVyRWxlbWVudCxcbiAgICAgICAgYWN0aXZlSW50ZXJhY3Rpb25zID0gMCxcbiAgICAgICAgdGFyZ2V0Q291bnQgPSAwLFxuICAgICAgICB0YXJnZXRFbGVtZW50Q291bnQgPSAwO1xuXG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGludGVyYWN0aW9ucy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICB2YXIgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbnNbaV0sXG4gICAgICAgICAgICBvdGhlckFjdGlvbiA9IGludGVyYWN0aW9uLnByZXBhcmVkLm5hbWUsXG4gICAgICAgICAgICBhY3RpdmUgPSBpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgIGlmICghYWN0aXZlKSB7IGNvbnRpbnVlOyB9XG5cbiAgICAgICAgYWN0aXZlSW50ZXJhY3Rpb25zKys7XG5cbiAgICAgICAgaWYgKGFjdGl2ZUludGVyYWN0aW9ucyA+PSBtYXhJbnRlcmFjdGlvbnMpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpbnRlcmFjdGlvbi50YXJnZXQgIT09IGludGVyYWN0YWJsZSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgIHRhcmdldENvdW50ICs9IChvdGhlckFjdGlvbiA9PT0gYWN0aW9uLm5hbWUpfDA7XG5cbiAgICAgICAgaWYgKHRhcmdldENvdW50ID49IG1heEFjdGlvbnMpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpbnRlcmFjdGlvbi5lbGVtZW50ID09PSBlbGVtZW50KSB7XG4gICAgICAgICAgICB0YXJnZXRFbGVtZW50Q291bnQrKztcblxuICAgICAgICAgICAgaWYgKG90aGVyQWN0aW9uICE9PSBhY3Rpb24ubmFtZSB8fCB0YXJnZXRFbGVtZW50Q291bnQgPj0gbWF4UGVyRWxlbWVudCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBtYXhJbnRlcmFjdGlvbnMgPiAwO1xufVxuXG4vLyBUZXN0IGZvciB0aGUgZWxlbWVudCB0aGF0J3MgXCJhYm92ZVwiIGFsbCBvdGhlciBxdWFsaWZpZXJzXG5mdW5jdGlvbiBpbmRleE9mRGVlcGVzdEVsZW1lbnQgKGVsZW1lbnRzKSB7XG4gICAgdmFyIGRyb3B6b25lLFxuICAgICAgICBkZWVwZXN0Wm9uZSA9IGVsZW1lbnRzWzBdLFxuICAgICAgICBpbmRleCA9IGRlZXBlc3Rab25lPyAwOiAtMSxcbiAgICAgICAgcGFyZW50LFxuICAgICAgICBkZWVwZXN0Wm9uZVBhcmVudHMgPSBbXSxcbiAgICAgICAgZHJvcHpvbmVQYXJlbnRzID0gW10sXG4gICAgICAgIGNoaWxkLFxuICAgICAgICBpLFxuICAgICAgICBuO1xuXG4gICAgZm9yIChpID0gMTsgaSA8IGVsZW1lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGRyb3B6b25lID0gZWxlbWVudHNbaV07XG5cbiAgICAgICAgLy8gYW4gZWxlbWVudCBtaWdodCBiZWxvbmcgdG8gbXVsdGlwbGUgc2VsZWN0b3IgZHJvcHpvbmVzXG4gICAgICAgIGlmICghZHJvcHpvbmUgfHwgZHJvcHpvbmUgPT09IGRlZXBlc3Rab25lKSB7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghZGVlcGVzdFpvbmUpIHtcbiAgICAgICAgICAgIGRlZXBlc3Rab25lID0gZHJvcHpvbmU7XG4gICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGNoZWNrIGlmIHRoZSBkZWVwZXN0IG9yIGN1cnJlbnQgYXJlIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCBvciBkb2N1bWVudC5yb290RWxlbWVudFxuICAgICAgICAvLyAtIGlmIHRoZSBjdXJyZW50IGRyb3B6b25lIGlzLCBkbyBub3RoaW5nIGFuZCBjb250aW51ZVxuICAgICAgICBpZiAoZHJvcHpvbmUucGFyZW50Tm9kZSA9PT0gZHJvcHpvbmUub3duZXJEb2N1bWVudCkge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gLSBpZiBkZWVwZXN0IGlzLCB1cGRhdGUgd2l0aCB0aGUgY3VycmVudCBkcm9wem9uZSBhbmQgY29udGludWUgdG8gbmV4dFxuICAgICAgICBlbHNlIGlmIChkZWVwZXN0Wm9uZS5wYXJlbnROb2RlID09PSBkcm9wem9uZS5vd25lckRvY3VtZW50KSB7XG4gICAgICAgICAgICBkZWVwZXN0Wm9uZSA9IGRyb3B6b25lO1xuICAgICAgICAgICAgaW5kZXggPSBpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWRlZXBlc3Rab25lUGFyZW50cy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHBhcmVudCA9IGRlZXBlc3Rab25lO1xuICAgICAgICAgICAgd2hpbGUgKHBhcmVudC5wYXJlbnROb2RlICYmIHBhcmVudC5wYXJlbnROb2RlICE9PSBwYXJlbnQub3duZXJEb2N1bWVudCkge1xuICAgICAgICAgICAgICAgIGRlZXBlc3Rab25lUGFyZW50cy51bnNoaWZ0KHBhcmVudCk7XG4gICAgICAgICAgICAgICAgcGFyZW50ID0gcGFyZW50LnBhcmVudE5vZGU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBpZiB0aGlzIGVsZW1lbnQgaXMgYW4gc3ZnIGVsZW1lbnQgYW5kIHRoZSBjdXJyZW50IGRlZXBlc3QgaXNcbiAgICAgICAgLy8gYW4gSFRNTEVsZW1lbnRcbiAgICAgICAgaWYgKGRlZXBlc3Rab25lIGluc3RhbmNlb2YgSFRNTEVsZW1lbnRcbiAgICAgICAgICAgICYmIGRyb3B6b25lIGluc3RhbmNlb2YgU1ZHRWxlbWVudFxuICAgICAgICAgICAgJiYgIShkcm9wem9uZSBpbnN0YW5jZW9mIFNWR1NWR0VsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgIGlmIChkcm9wem9uZSA9PT0gZGVlcGVzdFpvbmUucGFyZW50Tm9kZSkge1xuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBwYXJlbnQgPSBkcm9wem9uZS5vd25lclNWR0VsZW1lbnQ7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBwYXJlbnQgPSBkcm9wem9uZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGRyb3B6b25lUGFyZW50cyA9IFtdO1xuXG4gICAgICAgIHdoaWxlIChwYXJlbnQucGFyZW50Tm9kZSAhPT0gcGFyZW50Lm93bmVyRG9jdW1lbnQpIHtcbiAgICAgICAgICAgIGRyb3B6b25lUGFyZW50cy51bnNoaWZ0KHBhcmVudCk7XG4gICAgICAgICAgICBwYXJlbnQgPSBwYXJlbnQucGFyZW50Tm9kZTtcbiAgICAgICAgfVxuXG4gICAgICAgIG4gPSAwO1xuXG4gICAgICAgIC8vIGdldCAocG9zaXRpb24gb2YgbGFzdCBjb21tb24gYW5jZXN0b3IpICsgMVxuICAgICAgICB3aGlsZSAoZHJvcHpvbmVQYXJlbnRzW25dICYmIGRyb3B6b25lUGFyZW50c1tuXSA9PT0gZGVlcGVzdFpvbmVQYXJlbnRzW25dKSB7XG4gICAgICAgICAgICBuKys7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgcGFyZW50cyA9IFtcbiAgICAgICAgICAgIGRyb3B6b25lUGFyZW50c1tuIC0gMV0sXG4gICAgICAgICAgICBkcm9wem9uZVBhcmVudHNbbl0sXG4gICAgICAgICAgICBkZWVwZXN0Wm9uZVBhcmVudHNbbl1cbiAgICAgICAgXTtcblxuICAgICAgICBjaGlsZCA9IHBhcmVudHNbMF0ubGFzdENoaWxkO1xuXG4gICAgICAgIHdoaWxlIChjaGlsZCkge1xuICAgICAgICAgICAgaWYgKGNoaWxkID09PSBwYXJlbnRzWzFdKSB7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmUgPSBkcm9wem9uZTtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmVQYXJlbnRzID0gW107XG5cbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGNoaWxkID09PSBwYXJlbnRzWzJdKSB7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNoaWxkID0gY2hpbGQucHJldmlvdXNTaWJsaW5nO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGluZGV4O1xufVxuXG5mdW5jdGlvbiBJbnRlcmFjdGlvbiAoKSB7XG4gICAgdGhpcy50YXJnZXQgICAgICAgICAgPSBudWxsOyAvLyBjdXJyZW50IGludGVyYWN0YWJsZSBiZWluZyBpbnRlcmFjdGVkIHdpdGhcbiAgICB0aGlzLmVsZW1lbnQgICAgICAgICA9IG51bGw7IC8vIHRoZSB0YXJnZXQgZWxlbWVudCBvZiB0aGUgaW50ZXJhY3RhYmxlXG4gICAgdGhpcy5kcm9wVGFyZ2V0ICAgICAgPSBudWxsOyAvLyB0aGUgZHJvcHpvbmUgYSBkcmFnIHRhcmdldCBtaWdodCBiZSBkcm9wcGVkIGludG9cbiAgICB0aGlzLmRyb3BFbGVtZW50ICAgICA9IG51bGw7IC8vIHRoZSBlbGVtZW50IGF0IHRoZSB0aW1lIG9mIGNoZWNraW5nXG4gICAgdGhpcy5wcmV2RHJvcFRhcmdldCAgPSBudWxsOyAvLyB0aGUgZHJvcHpvbmUgdGhhdCB3YXMgcmVjZW50bHkgZHJhZ2dlZCBhd2F5IGZyb21cbiAgICB0aGlzLnByZXZEcm9wRWxlbWVudCA9IG51bGw7IC8vIHRoZSBlbGVtZW50IGF0IHRoZSB0aW1lIG9mIGNoZWNraW5nXG5cbiAgICB0aGlzLnByZXBhcmVkICAgICAgICA9IHsgICAgIC8vIGFjdGlvbiB0aGF0J3MgcmVhZHkgdG8gYmUgZmlyZWQgb24gbmV4dCBtb3ZlIGV2ZW50XG4gICAgICAgIG5hbWUgOiBudWxsLFxuICAgICAgICBheGlzIDogbnVsbCxcbiAgICAgICAgZWRnZXM6IG51bGxcbiAgICB9O1xuXG4gICAgdGhpcy5tYXRjaGVzICAgICAgICAgPSBbXTsgICAvLyBhbGwgc2VsZWN0b3JzIHRoYXQgYXJlIG1hdGNoZWQgYnkgdGFyZ2V0IGVsZW1lbnRcbiAgICB0aGlzLm1hdGNoRWxlbWVudHMgICA9IFtdOyAgIC8vIGNvcnJlc3BvbmRpbmcgZWxlbWVudHNcblxuICAgIHRoaXMuaW5lcnRpYVN0YXR1cyA9IHtcbiAgICAgICAgYWN0aXZlICAgICAgIDogZmFsc2UsXG4gICAgICAgIHNtb290aEVuZCAgICA6IGZhbHNlLFxuXG4gICAgICAgIHN0YXJ0RXZlbnQ6IG51bGwsXG4gICAgICAgIHVwQ29vcmRzOiB7fSxcblxuICAgICAgICB4ZTogMCwgeWU6IDAsXG4gICAgICAgIHN4OiAwLCBzeTogMCxcblxuICAgICAgICB0MDogMCxcbiAgICAgICAgdngwOiAwLCB2eXM6IDAsXG4gICAgICAgIGR1cmF0aW9uOiAwLFxuXG4gICAgICAgIHJlc3VtZUR4OiAwLFxuICAgICAgICByZXN1bWVEeTogMCxcblxuICAgICAgICBsYW1iZGFfdjA6IDAsXG4gICAgICAgIG9uZV92ZV92MDogMCxcbiAgICAgICAgaSAgOiBudWxsXG4gICAgfTtcblxuICAgIGlmIChpc0Z1bmN0aW9uKEZ1bmN0aW9uLnByb3RvdHlwZS5iaW5kKSkge1xuICAgICAgICB0aGlzLmJvdW5kSW5lcnRpYUZyYW1lID0gdGhpcy5pbmVydGlhRnJhbWUuYmluZCh0aGlzKTtcbiAgICAgICAgdGhpcy5ib3VuZFNtb290aEVuZEZyYW1lID0gdGhpcy5zbW9vdGhFbmRGcmFtZS5iaW5kKHRoaXMpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdmFyIHRoYXQgPSB0aGlzO1xuXG4gICAgICAgIHRoaXMuYm91bmRJbmVydGlhRnJhbWUgPSBmdW5jdGlvbiAoKSB7IHJldHVybiB0aGF0LmluZXJ0aWFGcmFtZSgpOyB9O1xuICAgICAgICB0aGlzLmJvdW5kU21vb3RoRW5kRnJhbWUgPSBmdW5jdGlvbiAoKSB7IHJldHVybiB0aGF0LnNtb290aEVuZEZyYW1lKCk7IH07XG4gICAgfVxuXG4gICAgdGhpcy5hY3RpdmVEcm9wcyA9IHtcbiAgICAgICAgZHJvcHpvbmVzOiBbXSwgICAgICAvLyB0aGUgZHJvcHpvbmVzIHRoYXQgYXJlIG1lbnRpb25lZCBiZWxvd1xuICAgICAgICBlbGVtZW50cyA6IFtdLCAgICAgIC8vIGVsZW1lbnRzIG9mIGRyb3B6b25lcyB0aGF0IGFjY2VwdCB0aGUgdGFyZ2V0IGRyYWdnYWJsZVxuICAgICAgICByZWN0cyAgICA6IFtdICAgICAgIC8vIHRoZSByZWN0cyBvZiB0aGUgZWxlbWVudHMgbWVudGlvbmVkIGFib3ZlXG4gICAgfTtcblxuICAgIC8vIGtlZXAgdHJhY2sgb2YgYWRkZWQgcG9pbnRlcnNcbiAgICB0aGlzLnBvaW50ZXJzICAgID0gW107XG4gICAgdGhpcy5wb2ludGVySWRzICA9IFtdO1xuICAgIHRoaXMuZG93blRhcmdldHMgPSBbXTtcbiAgICB0aGlzLmRvd25UaW1lcyAgID0gW107XG4gICAgdGhpcy5ob2xkVGltZXJzICA9IFtdO1xuXG4gICAgLy8gUHJldmlvdXMgbmF0aXZlIHBvaW50ZXIgbW92ZSBldmVudCBjb29yZGluYXRlc1xuICAgIHRoaXMucHJldkNvb3JkcyA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcbiAgICAvLyBjdXJyZW50IG5hdGl2ZSBwb2ludGVyIG1vdmUgZXZlbnQgY29vcmRpbmF0ZXNcbiAgICB0aGlzLmN1ckNvb3JkcyA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcblxuICAgIC8vIFN0YXJ0aW5nIEludGVyYWN0RXZlbnQgcG9pbnRlciBjb29yZGluYXRlc1xuICAgIHRoaXMuc3RhcnRDb29yZHMgPSB7XG4gICAgICAgIHBhZ2UgICAgIDogeyB4OiAwLCB5OiAwIH0sXG4gICAgICAgIGNsaWVudCAgIDogeyB4OiAwLCB5OiAwIH0sXG4gICAgICAgIHRpbWVTdGFtcDogMFxuICAgIH07XG5cbiAgICAvLyBDaGFuZ2UgaW4gY29vcmRpbmF0ZXMgYW5kIHRpbWUgb2YgdGhlIHBvaW50ZXJcbiAgICB0aGlzLnBvaW50ZXJEZWx0YSA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAsIHZ4OiAwLCB2eTogMCwgc3BlZWQ6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAsIHZ4OiAwLCB2eTogMCwgc3BlZWQ6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcblxuICAgIHRoaXMuZG93bkV2ZW50ICAgPSBudWxsOyAgICAvLyBwb2ludGVyZG93bi9tb3VzZWRvd24vdG91Y2hzdGFydCBldmVudFxuICAgIHRoaXMuZG93blBvaW50ZXIgPSB7fTtcblxuICAgIHRoaXMuX2V2ZW50VGFyZ2V0ICAgID0gbnVsbDtcbiAgICB0aGlzLl9jdXJFdmVudFRhcmdldCA9IG51bGw7XG5cbiAgICB0aGlzLnByZXZFdmVudCA9IG51bGw7ICAgICAgLy8gcHJldmlvdXMgYWN0aW9uIGV2ZW50XG4gICAgdGhpcy50YXBUaW1lICAgPSAwOyAgICAgICAgIC8vIHRpbWUgb2YgdGhlIG1vc3QgcmVjZW50IHRhcCBldmVudFxuICAgIHRoaXMucHJldlRhcCAgID0gbnVsbDtcblxuICAgIHRoaXMuc3RhcnRPZmZzZXQgICAgPSB7IGxlZnQ6IDAsIHJpZ2h0OiAwLCB0b3A6IDAsIGJvdHRvbTogMCB9O1xuICAgIHRoaXMucmVzdHJpY3RPZmZzZXQgPSB7IGxlZnQ6IDAsIHJpZ2h0OiAwLCB0b3A6IDAsIGJvdHRvbTogMCB9O1xuICAgIHRoaXMuc25hcE9mZnNldHMgICAgPSBbXTtcblxuICAgIHRoaXMuZ2VzdHVyZSA9IHtcbiAgICAgICAgc3RhcnQ6IHsgeDogMCwgeTogMCB9LFxuXG4gICAgICAgIHN0YXJ0RGlzdGFuY2U6IDAsICAgLy8gZGlzdGFuY2UgYmV0d2VlbiB0d28gdG91Y2hlcyBvZiB0b3VjaFN0YXJ0XG4gICAgICAgIHByZXZEaXN0YW5jZSA6IDAsXG4gICAgICAgIGRpc3RhbmNlICAgICA6IDAsXG5cbiAgICAgICAgc2NhbGU6IDEsICAgICAgICAgICAvLyBnZXN0dXJlLmRpc3RhbmNlIC8gZ2VzdHVyZS5zdGFydERpc3RhbmNlXG5cbiAgICAgICAgc3RhcnRBbmdsZTogMCwgICAgICAvLyBhbmdsZSBvZiBsaW5lIGpvaW5pbmcgdHdvIHRvdWNoZXNcbiAgICAgICAgcHJldkFuZ2xlIDogMCAgICAgICAvLyBhbmdsZSBvZiB0aGUgcHJldmlvdXMgZ2VzdHVyZSBldmVudFxuICAgIH07XG5cbiAgICB0aGlzLnNuYXBTdGF0dXMgPSB7XG4gICAgICAgIHggICAgICAgOiAwLCB5ICAgICAgIDogMCxcbiAgICAgICAgZHggICAgICA6IDAsIGR5ICAgICAgOiAwLFxuICAgICAgICByZWFsWCAgIDogMCwgcmVhbFkgICA6IDAsXG4gICAgICAgIHNuYXBwZWRYOiAwLCBzbmFwcGVkWTogMCxcbiAgICAgICAgdGFyZ2V0cyA6IFtdLFxuICAgICAgICBsb2NrZWQgIDogZmFsc2UsXG4gICAgICAgIGNoYW5nZWQgOiBmYWxzZVxuICAgIH07XG5cbiAgICB0aGlzLnJlc3RyaWN0U3RhdHVzID0ge1xuICAgICAgICBkeCAgICAgICAgIDogMCwgZHkgICAgICAgICA6IDAsXG4gICAgICAgIHJlc3RyaWN0ZWRYOiAwLCByZXN0cmljdGVkWTogMCxcbiAgICAgICAgc25hcCAgICAgICA6IG51bGwsXG4gICAgICAgIHJlc3RyaWN0ZWQgOiBmYWxzZSxcbiAgICAgICAgY2hhbmdlZCAgICA6IGZhbHNlXG4gICAgfTtcblxuICAgIHRoaXMucmVzdHJpY3RTdGF0dXMuc25hcCA9IHRoaXMuc25hcFN0YXR1cztcblxuICAgIHRoaXMucG9pbnRlcklzRG93biAgID0gZmFsc2U7XG4gICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmdlc3R1cmluZyAgICAgICA9IGZhbHNlO1xuICAgIHRoaXMuZHJhZ2dpbmcgICAgICAgID0gZmFsc2U7XG4gICAgdGhpcy5yZXNpemluZyAgICAgICAgPSBmYWxzZTtcbiAgICB0aGlzLnJlc2l6ZUF4ZXMgICAgICA9ICd4eSc7XG5cbiAgICB0aGlzLm1vdXNlID0gZmFsc2U7XG5cbiAgICBpbnRlcmFjdGlvbnMucHVzaCh0aGlzKTtcbn1cblxuSW50ZXJhY3Rpb24ucHJvdG90eXBlID0ge1xuICAgIGdldFBhZ2VYWSAgOiBmdW5jdGlvbiAocG9pbnRlciwgeHkpIHsgcmV0dXJuICAgZ2V0UGFnZVhZKHBvaW50ZXIsIHh5LCB0aGlzKTsgfSxcbiAgICBnZXRDbGllbnRYWTogZnVuY3Rpb24gKHBvaW50ZXIsIHh5KSB7IHJldHVybiBnZXRDbGllbnRYWShwb2ludGVyLCB4eSwgdGhpcyk7IH0sXG4gICAgc2V0RXZlbnRYWSA6IGZ1bmN0aW9uICh0YXJnZXQsIHB0cikgeyByZXR1cm4gIHNldEV2ZW50WFkodGFyZ2V0LCBwdHIsIHRoaXMpOyB9LFxuXG4gICAgcG9pbnRlck92ZXI6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQpIHtcbiAgICAgICAgaWYgKHRoaXMucHJlcGFyZWQubmFtZSB8fCAhdGhpcy5tb3VzZSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgY3VyTWF0Y2hlcyA9IFtdLFxuICAgICAgICAgICAgY3VyTWF0Y2hFbGVtZW50cyA9IFtdLFxuICAgICAgICAgICAgcHJldlRhcmdldEVsZW1lbnQgPSB0aGlzLmVsZW1lbnQ7XG5cbiAgICAgICAgdGhpcy5hZGRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgIGlmICh0aGlzLnRhcmdldFxuICAgICAgICAgICAgJiYgKHRlc3RJZ25vcmUodGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICB8fCAhdGVzdEFsbG93KHRoaXMudGFyZ2V0LCB0aGlzLmVsZW1lbnQsIGV2ZW50VGFyZ2V0KSkpIHtcbiAgICAgICAgICAgIC8vIGlmIHRoZSBldmVudFRhcmdldCBzaG91bGQgYmUgaWdub3JlZCBvciBzaG91bGRuJ3QgYmUgYWxsb3dlZFxuICAgICAgICAgICAgLy8gY2xlYXIgdGhlIHByZXZpb3VzIHRhcmdldFxuICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gbnVsbDtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZWxlbWVudEludGVyYWN0YWJsZSA9IGludGVyYWN0YWJsZXMuZ2V0KGV2ZW50VGFyZ2V0KSxcbiAgICAgICAgICAgIGVsZW1lbnRBY3Rpb24gPSAoZWxlbWVudEludGVyYWN0YWJsZVxuICAgICAgICAgICAgJiYgIXRlc3RJZ25vcmUoZWxlbWVudEludGVyYWN0YWJsZSwgZXZlbnRUYXJnZXQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgJiYgdGVzdEFsbG93KGVsZW1lbnRJbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICYmIHZhbGlkYXRlQWN0aW9uKFxuICAgICAgICAgICAgICAgIGVsZW1lbnRJbnRlcmFjdGFibGUuZ2V0QWN0aW9uKHBvaW50ZXIsIGV2ZW50LCB0aGlzLCBldmVudFRhcmdldCksXG4gICAgICAgICAgICAgICAgZWxlbWVudEludGVyYWN0YWJsZSkpO1xuXG4gICAgICAgIGlmIChlbGVtZW50QWN0aW9uICYmICF3aXRoaW5JbnRlcmFjdGlvbkxpbWl0KGVsZW1lbnRJbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBlbGVtZW50QWN0aW9uKSkge1xuICAgICAgICAgICAgZWxlbWVudEFjdGlvbiA9IG51bGw7XG4gICAgICAgIH1cblxuICAgICAgICBmdW5jdGlvbiBwdXNoQ3VyTWF0Y2hlcyAoaW50ZXJhY3RhYmxlLCBzZWxlY3Rvcikge1xuICAgICAgICAgICAgaWYgKGludGVyYWN0YWJsZVxuICAgICAgICAgICAgICAgICYmIGluQ29udGV4dChpbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmICF0ZXN0SWdub3JlKGludGVyYWN0YWJsZSwgZXZlbnRUYXJnZXQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIHRlc3RBbGxvdyhpbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBtYXRjaGVzU2VsZWN0b3IoZXZlbnRUYXJnZXQsIHNlbGVjdG9yKSkge1xuXG4gICAgICAgICAgICAgICAgY3VyTWF0Y2hlcy5wdXNoKGludGVyYWN0YWJsZSk7XG4gICAgICAgICAgICAgICAgY3VyTWF0Y2hFbGVtZW50cy5wdXNoKGV2ZW50VGFyZ2V0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChlbGVtZW50QWN0aW9uKSB7XG4gICAgICAgICAgICB0aGlzLnRhcmdldCA9IGVsZW1lbnRJbnRlcmFjdGFibGU7XG4gICAgICAgICAgICB0aGlzLmVsZW1lbnQgPSBldmVudFRhcmdldDtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBpbnRlcmFjdGFibGVzLmZvckVhY2hTZWxlY3RvcihwdXNoQ3VyTWF0Y2hlcyk7XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnZhbGlkYXRlU2VsZWN0b3IocG9pbnRlciwgZXZlbnQsIGN1ck1hdGNoZXMsIGN1ck1hdGNoRWxlbWVudHMpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5tYXRjaGVzID0gY3VyTWF0Y2hlcztcbiAgICAgICAgICAgICAgICB0aGlzLm1hdGNoRWxlbWVudHMgPSBjdXJNYXRjaEVsZW1lbnRzO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVySG92ZXIocG9pbnRlciwgZXZlbnQsIHRoaXMubWF0Y2hlcywgdGhpcy5tYXRjaEVsZW1lbnRzKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKGV2ZW50VGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICBQb2ludGVyRXZlbnQ/IHBFdmVudFR5cGVzLm1vdmUgOiAnbW91c2Vtb3ZlJyxcbiAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmICh0aGlzLnRhcmdldCkge1xuICAgICAgICAgICAgICAgIGlmIChub2RlQ29udGFpbnMocHJldlRhcmdldEVsZW1lbnQsIGV2ZW50VGFyZ2V0KSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnBvaW50ZXJIb3Zlcihwb2ludGVyLCBldmVudCwgdGhpcy5tYXRjaGVzLCB0aGlzLm1hdGNoRWxlbWVudHMpO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuZWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgICAgIFBvaW50ZXJFdmVudD8gcEV2ZW50VHlwZXMubW92ZSA6ICdtb3VzZW1vdmUnLFxuICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnRhcmdldCA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZWxlbWVudCA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm1hdGNoRWxlbWVudHMgPSBbXTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gQ2hlY2sgd2hhdCBhY3Rpb24gd291bGQgYmUgcGVyZm9ybWVkIG9uIHBvaW50ZXJNb3ZlIHRhcmdldCBpZiBhIG1vdXNlXG4gICAgLy8gYnV0dG9uIHdlcmUgcHJlc3NlZCBhbmQgY2hhbmdlIHRoZSBjdXJzb3IgYWNjb3JkaW5nbHlcbiAgICBwb2ludGVySG92ZXI6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBtYXRjaGVzLCBtYXRjaEVsZW1lbnRzKSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldDtcblxuICAgICAgICBpZiAoIXRoaXMucHJlcGFyZWQubmFtZSAmJiB0aGlzLm1vdXNlKSB7XG5cbiAgICAgICAgICAgIHZhciBhY3Rpb247XG5cbiAgICAgICAgICAgIC8vIHVwZGF0ZSBwb2ludGVyIGNvb3JkcyBmb3IgZGVmYXVsdEFjdGlvbkNoZWNrZXIgdG8gdXNlXG4gICAgICAgICAgICB0aGlzLnNldEV2ZW50WFkodGhpcy5jdXJDb29yZHMsIHBvaW50ZXIpO1xuXG4gICAgICAgICAgICBpZiAobWF0Y2hlcykge1xuICAgICAgICAgICAgICAgIGFjdGlvbiA9IHRoaXMudmFsaWRhdGVTZWxlY3Rvcihwb2ludGVyLCBldmVudCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmICh0YXJnZXQpIHtcbiAgICAgICAgICAgICAgICBhY3Rpb24gPSB2YWxpZGF0ZUFjdGlvbih0YXJnZXQuZ2V0QWN0aW9uKHRoaXMucG9pbnRlcnNbMF0sIGV2ZW50LCB0aGlzLCB0aGlzLmVsZW1lbnQpLCB0aGlzLnRhcmdldCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMuc3R5bGVDdXJzb3IpIHtcbiAgICAgICAgICAgICAgICBpZiAoYWN0aW9uKSB7XG4gICAgICAgICAgICAgICAgICAgIHRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBnZXRBY3Rpb25DdXJzb3IoYWN0aW9uKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSAnJztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodGhpcy5wcmVwYXJlZC5uYW1lKSB7XG4gICAgICAgICAgICB0aGlzLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQsIHRhcmdldCwgdGhpcy5lbGVtZW50KTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBwb2ludGVyT3V0OiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIGlmICh0aGlzLnByZXBhcmVkLm5hbWUpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgLy8gUmVtb3ZlIHRlbXBvcmFyeSBldmVudCBsaXN0ZW5lcnMgZm9yIHNlbGVjdG9yIEludGVyYWN0YWJsZXNcbiAgICAgICAgaWYgKCFpbnRlcmFjdGFibGVzLmdldChldmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUoZXZlbnRUYXJnZXQsXG4gICAgICAgICAgICAgICAgUG9pbnRlckV2ZW50PyBwRXZlbnRUeXBlcy5tb3ZlIDogJ21vdXNlbW92ZScsXG4gICAgICAgICAgICAgICAgbGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodGhpcy50YXJnZXQgJiYgdGhpcy50YXJnZXQub3B0aW9ucy5zdHlsZUN1cnNvciAmJiAhdGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICB0aGlzLnRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSAnJztcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzZWxlY3RvckRvd246IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciB0aGF0ID0gdGhpcyxcbiAgICAgICAgLy8gY29weSBldmVudCB0byBiZSB1c2VkIGluIHRpbWVvdXQgZm9yIElFOFxuICAgICAgICAgICAgZXZlbnRDb3B5ID0gZXZlbnRzLnVzZUF0dGFjaEV2ZW50PyBleHRlbmQoe30sIGV2ZW50KSA6IGV2ZW50LFxuICAgICAgICAgICAgZWxlbWVudCA9IGV2ZW50VGFyZ2V0LFxuICAgICAgICAgICAgcG9pbnRlckluZGV4ID0gdGhpcy5hZGRQb2ludGVyKHBvaW50ZXIpLFxuICAgICAgICAgICAgYWN0aW9uO1xuXG4gICAgICAgIHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICB0aGF0LnBvaW50ZXJIb2xkKGV2ZW50cy51c2VBdHRhY2hFdmVudD8gZXZlbnRDb3B5IDogcG9pbnRlciwgZXZlbnRDb3B5LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuICAgICAgICB9LCBkZWZhdWx0T3B0aW9ucy5faG9sZER1cmF0aW9uKTtcblxuICAgICAgICB0aGlzLnBvaW50ZXJJc0Rvd24gPSB0cnVlO1xuXG4gICAgICAgIC8vIENoZWNrIGlmIHRoZSBkb3duIGV2ZW50IGhpdHMgdGhlIGN1cnJlbnQgaW5lcnRpYSB0YXJnZXRcbiAgICAgICAgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgJiYgdGhpcy50YXJnZXQuc2VsZWN0b3IpIHtcbiAgICAgICAgICAgIC8vIGNsaW1iIHVwIHRoZSBET00gdHJlZSBmcm9tIHRoZSBldmVudCB0YXJnZXRcbiAgICAgICAgICAgIHdoaWxlIChpc0VsZW1lbnQoZWxlbWVudCkpIHtcblxuICAgICAgICAgICAgICAgIC8vIGlmIHRoaXMgZWxlbWVudCBpcyB0aGUgY3VycmVudCBpbmVydGlhIHRhcmdldCBlbGVtZW50XG4gICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQgPT09IHRoaXMuZWxlbWVudFxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gYW5kIHRoZSBwcm9zcGVjdGl2ZSBhY3Rpb24gaXMgdGhlIHNhbWUgYXMgdGhlIG9uZ29pbmcgb25lXG4gICAgICAgICAgICAgICAgICAgICYmIHZhbGlkYXRlQWN0aW9uKHRoaXMudGFyZ2V0LmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgdGhpcy5lbGVtZW50KSwgdGhpcy50YXJnZXQpLm5hbWUgPT09IHRoaXMucHJlcGFyZWQubmFtZSkge1xuXG4gICAgICAgICAgICAgICAgICAgIC8vIHN0b3AgaW5lcnRpYSBzbyB0aGF0IHRoZSBuZXh0IG1vdmUgd2lsbCBiZSBhIG5vcm1hbCBvbmVcbiAgICAgICAgICAgICAgICAgICAgY2FuY2VsRnJhbWUodGhpcy5pbmVydGlhU3RhdHVzLmkpO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmluZXJ0aWFTdGF0dXMuYWN0aXZlID0gZmFsc2U7XG5cbiAgICAgICAgICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2Rvd24nKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbGVtZW50ID0gcGFyZW50RWxlbWVudChlbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGRvIG5vdGhpbmcgaWYgaW50ZXJhY3RpbmdcbiAgICAgICAgaWYgKHRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2Rvd24nKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIHB1c2hNYXRjaGVzIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICB2YXIgZWxlbWVudHMgPSBpZThNYXRjaGVzU2VsZWN0b3JcbiAgICAgICAgICAgICAgICA/IGNvbnRleHQucXVlcnlTZWxlY3RvckFsbChzZWxlY3RvcilcbiAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZDtcblxuICAgICAgICAgICAgaWYgKGluQ29udGV4dChpbnRlcmFjdGFibGUsIGVsZW1lbnQpXG4gICAgICAgICAgICAgICAgJiYgIXRlc3RJZ25vcmUoaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiB0ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBtYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVsZW1lbnRzKSkge1xuXG4gICAgICAgICAgICAgICAgdGhhdC5tYXRjaGVzLnB1c2goaW50ZXJhY3RhYmxlKTtcbiAgICAgICAgICAgICAgICB0aGF0Lm1hdGNoRWxlbWVudHMucHVzaChlbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHVwZGF0ZSBwb2ludGVyIGNvb3JkcyBmb3IgZGVmYXVsdEFjdGlvbkNoZWNrZXIgdG8gdXNlXG4gICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLmN1ckNvb3JkcywgcG9pbnRlcik7XG4gICAgICAgIHRoaXMuZG93bkV2ZW50ID0gZXZlbnQ7XG5cbiAgICAgICAgd2hpbGUgKGlzRWxlbWVudChlbGVtZW50KSAmJiAhYWN0aW9uKSB7XG4gICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hFbGVtZW50cyA9IFtdO1xuXG4gICAgICAgICAgICBpbnRlcmFjdGFibGVzLmZvckVhY2hTZWxlY3RvcihwdXNoTWF0Y2hlcyk7XG5cbiAgICAgICAgICAgIGFjdGlvbiA9IHRoaXMudmFsaWRhdGVTZWxlY3Rvcihwb2ludGVyLCBldmVudCwgdGhpcy5tYXRjaGVzLCB0aGlzLm1hdGNoRWxlbWVudHMpO1xuICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoYWN0aW9uKSB7XG4gICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgID0gYWN0aW9uLm5hbWU7XG4gICAgICAgICAgICB0aGlzLnByZXBhcmVkLmF4aXMgID0gYWN0aW9uLmF4aXM7XG4gICAgICAgICAgICB0aGlzLnByZXBhcmVkLmVkZ2VzID0gYWN0aW9uLmVkZ2VzO1xuXG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnZG93bicpO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5wb2ludGVyRG93bihwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBhY3Rpb24pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgLy8gZG8gdGhlc2Ugbm93IHNpbmNlIHBvaW50ZXJEb3duIGlzbid0IGJlaW5nIGNhbGxlZCBmcm9tIGhlcmVcbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzW3BvaW50ZXJJbmRleF0gPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbcG9pbnRlckluZGV4XSA9IGV2ZW50VGFyZ2V0O1xuICAgICAgICAgICAgZXh0ZW5kKHRoaXMuZG93blBvaW50ZXIsIHBvaW50ZXIpO1xuXG4gICAgICAgICAgICBjb3B5Q29vcmRzKHRoaXMucHJldkNvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICdkb3duJyk7XG4gICAgfSxcblxuICAgIC8vIERldGVybWluZSBhY3Rpb24gdG8gYmUgcGVyZm9ybWVkIG9uIG5leHQgcG9pbnRlck1vdmUgYW5kIGFkZCBhcHByb3ByaWF0ZVxuICAgIC8vIHN0eWxlIGFuZCBldmVudCBMaXN0ZW5lcnNcbiAgICBwb2ludGVyRG93bjogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQsIGZvcmNlQWN0aW9uKSB7XG4gICAgICAgIGlmICghZm9yY2VBY3Rpb24gJiYgIXRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgJiYgdGhpcy5wb2ludGVyV2FzTW92ZWQgJiYgdGhpcy5wcmVwYXJlZC5uYW1lKSB7XG4gICAgICAgICAgICB0aGlzLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQsIHRoaXMudGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJc0Rvd24gPSB0cnVlO1xuICAgICAgICB0aGlzLmRvd25FdmVudCA9IGV2ZW50O1xuXG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLmFkZFBvaW50ZXIocG9pbnRlciksXG4gICAgICAgICAgICBhY3Rpb247XG5cbiAgICAgICAgLy8gSWYgaXQgaXMgdGhlIHNlY29uZCB0b3VjaCBvZiBhIG11bHRpLXRvdWNoIGdlc3R1cmUsIGtlZXAgdGhlIHRhcmdldFxuICAgICAgICAvLyB0aGUgc2FtZSBpZiBhIHRhcmdldCB3YXMgc2V0IGJ5IHRoZSBmaXJzdCB0b3VjaFxuICAgICAgICAvLyBPdGhlcndpc2UsIHNldCB0aGUgdGFyZ2V0IGlmIHRoZXJlIGlzIG5vIGFjdGlvbiBwcmVwYXJlZFxuICAgICAgICBpZiAoKHRoaXMucG9pbnRlcklkcy5sZW5ndGggPCAyICYmICF0aGlzLnRhcmdldCkgfHwgIXRoaXMucHJlcGFyZWQubmFtZSkge1xuXG4gICAgICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlcy5nZXQoY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgJiYgIXRlc3RJZ25vcmUoaW50ZXJhY3RhYmxlLCBjdXJFdmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgdGVzdEFsbG93KGludGVyYWN0YWJsZSwgY3VyRXZlbnRUYXJnZXQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIChhY3Rpb24gPSB2YWxpZGF0ZUFjdGlvbihmb3JjZUFjdGlvbiB8fCBpbnRlcmFjdGFibGUuZ2V0QWN0aW9uKHBvaW50ZXIsIGV2ZW50LCB0aGlzLCBjdXJFdmVudFRhcmdldCksIGludGVyYWN0YWJsZSwgZXZlbnRUYXJnZXQpKVxuICAgICAgICAgICAgICAgICYmIHdpdGhpbkludGVyYWN0aW9uTGltaXQoaW50ZXJhY3RhYmxlLCBjdXJFdmVudFRhcmdldCwgYWN0aW9uKSkge1xuICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gaW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgICAgIHRoaXMuZWxlbWVudCA9IGN1ckV2ZW50VGFyZ2V0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHRhcmdldCA9IHRoaXMudGFyZ2V0LFxuICAgICAgICAgICAgb3B0aW9ucyA9IHRhcmdldCAmJiB0YXJnZXQub3B0aW9ucztcblxuICAgICAgICBpZiAodGFyZ2V0ICYmIChmb3JjZUFjdGlvbiB8fCAhdGhpcy5wcmVwYXJlZC5uYW1lKSkge1xuICAgICAgICAgICAgYWN0aW9uID0gYWN0aW9uIHx8IHZhbGlkYXRlQWN0aW9uKGZvcmNlQWN0aW9uIHx8IHRhcmdldC5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIGN1ckV2ZW50VGFyZ2V0KSwgdGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuXG4gICAgICAgICAgICB0aGlzLnNldEV2ZW50WFkodGhpcy5zdGFydENvb3Jkcyk7XG5cbiAgICAgICAgICAgIGlmICghYWN0aW9uKSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICBpZiAob3B0aW9ucy5zdHlsZUN1cnNvcikge1xuICAgICAgICAgICAgICAgIHRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBnZXRBY3Rpb25DdXJzb3IoYWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5yZXNpemVBeGVzID0gYWN0aW9uLm5hbWUgPT09ICdyZXNpemUnPyBhY3Rpb24uYXhpcyA6IG51bGw7XG5cbiAgICAgICAgICAgIGlmIChhY3Rpb24gPT09ICdnZXN0dXJlJyAmJiB0aGlzLnBvaW50ZXJJZHMubGVuZ3RoIDwgMikge1xuICAgICAgICAgICAgICAgIGFjdGlvbiA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSAgPSBhY3Rpb24ubmFtZTtcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuZWRnZXMgPSBhY3Rpb24uZWRnZXM7XG5cbiAgICAgICAgICAgIHRoaXMuc25hcFN0YXR1cy5zbmFwcGVkWCA9IHRoaXMuc25hcFN0YXR1cy5zbmFwcGVkWSA9XG4gICAgICAgICAgICAgICAgdGhpcy5yZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkWCA9IHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZFkgPSBOYU47XG5cbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzW3BvaW50ZXJJbmRleF0gPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbcG9pbnRlckluZGV4XSA9IGV2ZW50VGFyZ2V0O1xuICAgICAgICAgICAgZXh0ZW5kKHRoaXMuZG93blBvaW50ZXIsIHBvaW50ZXIpO1xuXG4gICAgICAgICAgICB0aGlzLnNldEV2ZW50WFkodGhpcy5wcmV2Q29vcmRzKTtcbiAgICAgICAgICAgIHRoaXMucG9pbnRlcldhc01vdmVkID0gZmFsc2U7XG5cbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIC8vIGlmIGluZXJ0aWEgaXMgYWN0aXZlIHRyeSB0byByZXN1bWUgYWN0aW9uXG4gICAgICAgIGVsc2UgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmVcbiAgICAgICAgICAgICYmIGN1ckV2ZW50VGFyZ2V0ID09PSB0aGlzLmVsZW1lbnRcbiAgICAgICAgICAgICYmIHZhbGlkYXRlQWN0aW9uKHRhcmdldC5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIHRoaXMuZWxlbWVudCksIHRhcmdldCkubmFtZSA9PT0gdGhpcy5wcmVwYXJlZC5uYW1lKSB7XG5cbiAgICAgICAgICAgIGNhbmNlbEZyYW1lKHRoaXMuaW5lcnRpYVN0YXR1cy5pKTtcbiAgICAgICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc2V0TW9kaWZpY2F0aW9uczogZnVuY3Rpb24gKGNvb3JkcywgcHJlRW5kKSB7XG4gICAgICAgIHZhciB0YXJnZXQgICAgICAgICA9IHRoaXMudGFyZ2V0LFxuICAgICAgICAgICAgc2hvdWxkTW92ZSAgICAgPSB0cnVlLFxuICAgICAgICAgICAgc2hvdWxkU25hcCAgICAgPSBjaGVja1NuYXAodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICAgICAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uc25hcC5lbmRPbmx5ICAgICB8fCBwcmVFbmQpLFxuICAgICAgICAgICAgc2hvdWxkUmVzdHJpY3QgPSBjaGVja1Jlc3RyaWN0KHRhcmdldCwgdGhpcy5wcmVwYXJlZC5uYW1lKSAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QuZW5kT25seSB8fCBwcmVFbmQpO1xuXG4gICAgICAgIGlmIChzaG91bGRTbmFwICAgICkgeyB0aGlzLnNldFNuYXBwaW5nICAgKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMuc25hcFN0YXR1cyAgICAubG9ja2VkICAgICA9IGZhbHNlOyB9XG4gICAgICAgIGlmIChzaG91bGRSZXN0cmljdCkgeyB0aGlzLnNldFJlc3RyaWN0aW9uKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlOyB9XG5cbiAgICAgICAgaWYgKHNob3VsZFNuYXAgJiYgdGhpcy5zbmFwU3RhdHVzLmxvY2tlZCAmJiAhdGhpcy5zbmFwU3RhdHVzLmNoYW5nZWQpIHtcbiAgICAgICAgICAgIHNob3VsZE1vdmUgPSBzaG91bGRSZXN0cmljdCAmJiB0aGlzLnJlc3RyaWN0U3RhdHVzLnJlc3RyaWN0ZWQgJiYgdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHNob3VsZFJlc3RyaWN0ICYmIHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCAmJiAhdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkKSB7XG4gICAgICAgICAgICBzaG91bGRNb3ZlID0gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gc2hvdWxkTW92ZTtcbiAgICB9LFxuXG4gICAgc2V0U3RhcnRPZmZzZXRzOiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIHJlY3QgPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgICAgIG9yaWdpbiA9IGdldE9yaWdpblhZKGludGVyYWN0YWJsZSwgZWxlbWVudCksXG4gICAgICAgICAgICBzbmFwID0gaW50ZXJhY3RhYmxlLm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgcmVzdHJpY3QgPSBpbnRlcmFjdGFibGUub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnJlc3RyaWN0LFxuICAgICAgICAgICAgd2lkdGgsIGhlaWdodDtcblxuICAgICAgICBpZiAocmVjdCkge1xuICAgICAgICAgICAgdGhpcy5zdGFydE9mZnNldC5sZWZ0ID0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnggLSByZWN0LmxlZnQ7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueSAtIHJlY3QudG9wO1xuXG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICA9IHJlY3QucmlnaHQgIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLng7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LmJvdHRvbSA9IHJlY3QuYm90dG9tIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnk7XG5cbiAgICAgICAgICAgIGlmICgnd2lkdGgnIGluIHJlY3QpIHsgd2lkdGggPSByZWN0LndpZHRoOyB9XG4gICAgICAgICAgICBlbHNlIHsgd2lkdGggPSByZWN0LnJpZ2h0IC0gcmVjdC5sZWZ0OyB9XG4gICAgICAgICAgICBpZiAoJ2hlaWdodCcgaW4gcmVjdCkgeyBoZWlnaHQgPSByZWN0LmhlaWdodDsgfVxuICAgICAgICAgICAgZWxzZSB7IGhlaWdodCA9IHJlY3QuYm90dG9tIC0gcmVjdC50b3A7IH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc3RhcnRPZmZzZXQubGVmdCA9IHRoaXMuc3RhcnRPZmZzZXQudG9wID0gdGhpcy5zdGFydE9mZnNldC5yaWdodCA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tID0gMDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc25hcE9mZnNldHMuc3BsaWNlKDApO1xuXG4gICAgICAgIHZhciBzbmFwT2Zmc2V0ID0gc25hcCAmJiBzbmFwLm9mZnNldCA9PT0gJ3N0YXJ0Q29vcmRzJ1xuICAgICAgICAgICAgPyB7XG4gICAgICAgICAgICB4OiB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueCAtIG9yaWdpbi54LFxuICAgICAgICAgICAgeTogdGhpcy5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueVxuICAgICAgICB9XG4gICAgICAgICAgICA6IHNuYXAgJiYgc25hcC5vZmZzZXQgfHwgeyB4OiAwLCB5OiAwIH07XG5cbiAgICAgICAgaWYgKHJlY3QgJiYgc25hcCAmJiBzbmFwLnJlbGF0aXZlUG9pbnRzICYmIHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLnNuYXBPZmZzZXRzLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICB4OiB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogc25hcC5yZWxhdGl2ZVBvaW50c1tpXS54KSArIHNuYXBPZmZzZXQueCxcbiAgICAgICAgICAgICAgICAgICAgeTogdGhpcy5zdGFydE9mZnNldC50b3AgIC0gKGhlaWdodCAqIHNuYXAucmVsYXRpdmVQb2ludHNbaV0ueSkgKyBzbmFwT2Zmc2V0LnlcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc25hcE9mZnNldHMucHVzaChzbmFwT2Zmc2V0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChyZWN0ICYmIHJlc3RyaWN0LmVsZW1lbnRSZWN0KSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogcmVzdHJpY3QuZWxlbWVudFJlY3QubGVmdCk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgLSAoaGVpZ2h0ICogcmVzdHJpY3QuZWxlbWVudFJlY3QudG9wKTtcblxuICAgICAgICAgICAgdGhpcy5yZXN0cmljdE9mZnNldC5yaWdodCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICAtICh3aWR0aCAgKiAoMSAtIHJlc3RyaWN0LmVsZW1lbnRSZWN0LnJpZ2h0KSk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tIC0gKGhlaWdodCAqICgxIC0gcmVzdHJpY3QuZWxlbWVudFJlY3QuYm90dG9tKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCA9IHRoaXMucmVzdHJpY3RPZmZzZXQucmlnaHQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IDA7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0aW9uLnN0YXJ0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFN0YXJ0IGFuIGFjdGlvbiB3aXRoIHRoZSBnaXZlbiBJbnRlcmFjdGFibGUgYW5kIEVsZW1lbnQgYXMgdGFydGdldHMuIFRoZVxuICAgICAqIGFjdGlvbiBtdXN0IGJlIGVuYWJsZWQgZm9yIHRoZSB0YXJnZXQgSW50ZXJhY3RhYmxlIGFuZCBhbiBhcHByb3ByaWF0ZSBudW1iZXJcbiAgICAgKiBvZiBwb2ludGVycyBtdXN0IGJlIGhlbGQgZG93biDigJMgMSBmb3IgZHJhZy9yZXNpemUsIDIgZm9yIGdlc3R1cmUuXG4gICAgICpcbiAgICAgKiBVc2UgaXQgd2l0aCBgaW50ZXJhY3RhYmxlLjxhY3Rpb24+YWJsZSh7IG1hbnVhbFN0YXJ0OiBmYWxzZSB9KWAgdG8gYWx3YXlzXG4gICAgICogW3N0YXJ0IGFjdGlvbnMgbWFudWFsbHldKGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL2lzc3Vlcy8xMTQpXG4gICAgICpcbiAgICAgLSBhY3Rpb24gICAgICAgKG9iamVjdCkgIFRoZSBhY3Rpb24gdG8gYmUgcGVyZm9ybWVkIC0gZHJhZywgcmVzaXplLCBldGMuXG4gICAgIC0gaW50ZXJhY3RhYmxlIChJbnRlcmFjdGFibGUpIFRoZSBJbnRlcmFjdGFibGUgdG8gdGFyZ2V0XG4gICAgIC0gZWxlbWVudCAgICAgIChFbGVtZW50KSBUaGUgRE9NIEVsZW1lbnQgdG8gdGFyZ2V0XG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICAgKipcbiAgICAgfCBpbnRlcmFjdCh0YXJnZXQpXG4gICAgIHwgICAuZHJhZ2dhYmxlKHtcbiAgICAgfCAgICAgLy8gZGlzYWJsZSB0aGUgZGVmYXVsdCBkcmFnIHN0YXJ0IGJ5IGRvd24tPm1vdmVcbiAgICAgfCAgICAgbWFudWFsU3RhcnQ6IHRydWVcbiAgICAgfCAgIH0pXG4gICAgIHwgICAvLyBzdGFydCBkcmFnZ2luZyBhZnRlciB0aGUgdXNlciBob2xkcyB0aGUgcG9pbnRlciBkb3duXG4gICAgIHwgICAub24oJ2hvbGQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgfCAgICAgdmFyIGludGVyYWN0aW9uID0gZXZlbnQuaW50ZXJhY3Rpb247XG4gICAgIHxcbiAgICAgfCAgICAgaWYgKCFpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpKSB7XG4gICAgIHwgICAgICAgaW50ZXJhY3Rpb24uc3RhcnQoeyBuYW1lOiAnZHJhZycgfSxcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5pbnRlcmFjdGFibGUsXG4gICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQuY3VycmVudFRhcmdldCk7XG4gICAgIHwgICAgIH1cbiAgICAgfCB9KTtcbiAgICAgXFwqL1xuICAgIHN0YXJ0OiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgfHwgIXRoaXMucG9pbnRlcklzRG93blxuICAgICAgICAgICAgfHwgdGhpcy5wb2ludGVySWRzLmxlbmd0aCA8IChhY3Rpb24ubmFtZSA9PT0gJ2dlc3R1cmUnPyAyIDogMSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGlmIHRoaXMgaW50ZXJhY3Rpb24gaGFkIGJlZW4gcmVtb3ZlZCBhZnRlciBzdG9wcGluZ1xuICAgICAgICAvLyBhZGQgaXQgYmFja1xuICAgICAgICBpZiAoaW5kZXhPZihpbnRlcmFjdGlvbnMsIHRoaXMpID09PSAtMSkge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb25zLnB1c2godGhpcyk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgID0gYWN0aW9uLm5hbWU7XG4gICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgdGhpcy5wcmVwYXJlZC5lZGdlcyA9IGFjdGlvbi5lZGdlcztcbiAgICAgICAgdGhpcy50YXJnZXQgICAgICAgICA9IGludGVyYWN0YWJsZTtcbiAgICAgICAgdGhpcy5lbGVtZW50ICAgICAgICA9IGVsZW1lbnQ7XG5cbiAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuc3RhcnRDb29yZHMpO1xuICAgICAgICB0aGlzLnNldFN0YXJ0T2Zmc2V0cyhhY3Rpb24ubmFtZSwgaW50ZXJhY3RhYmxlLCBlbGVtZW50KTtcbiAgICAgICAgdGhpcy5zZXRNb2RpZmljYXRpb25zKHRoaXMuc3RhcnRDb29yZHMucGFnZSk7XG5cbiAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdTdGFydCddKHRoaXMuZG93bkV2ZW50KTtcbiAgICB9LFxuXG4gICAgcG9pbnRlck1vdmU6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBwcmVFbmQpIHtcbiAgICAgICAgdGhpcy5yZWNvcmRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLmN1ckNvb3JkcywgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50KVxuICAgICAgICAgICAgPyB0aGlzLmluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudFxuICAgICAgICAgICAgOiB1bmRlZmluZWQpO1xuXG4gICAgICAgIHZhciBkdXBsaWNhdGVNb3ZlID0gKHRoaXMuY3VyQ29vcmRzLnBhZ2UueCA9PT0gdGhpcy5wcmV2Q29vcmRzLnBhZ2UueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5wYWdlLnkgPT09IHRoaXMucHJldkNvb3Jkcy5wYWdlLnlcbiAgICAgICAgJiYgdGhpcy5jdXJDb29yZHMuY2xpZW50LnggPT09IHRoaXMucHJldkNvb3Jkcy5jbGllbnQueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueSA9PT0gdGhpcy5wcmV2Q29vcmRzLmNsaWVudC55KTtcblxuICAgICAgICB2YXIgZHgsIGR5LFxuICAgICAgICAgICAgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YodGhpcy5wb2ludGVySWRzLCBnZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIC8vIHJlZ2lzdGVyIG1vdmVtZW50IGdyZWF0ZXIgdGhhbiBwb2ludGVyTW92ZVRvbGVyYW5jZVxuICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duICYmICF0aGlzLnBvaW50ZXJXYXNNb3ZlZCkge1xuICAgICAgICAgICAgZHggPSB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueCAtIHRoaXMuc3RhcnRDb29yZHMuY2xpZW50Lng7XG4gICAgICAgICAgICBkeSA9IHRoaXMuY3VyQ29vcmRzLmNsaWVudC55IC0gdGhpcy5zdGFydENvb3Jkcy5jbGllbnQueTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBoeXBvdChkeCwgZHkpID4gcG9pbnRlck1vdmVUb2xlcmFuY2U7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWR1cGxpY2F0ZU1vdmUgJiYgKCF0aGlzLnBvaW50ZXJJc0Rvd24gfHwgdGhpcy5wb2ludGVyV2FzTW92ZWQpKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duKSB7XG4gICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ21vdmUnKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghdGhpcy5wb2ludGVySXNEb3duKSB7IHJldHVybjsgfVxuXG4gICAgICAgIGlmIChkdXBsaWNhdGVNb3ZlICYmIHRoaXMucG9pbnRlcldhc01vdmVkICYmICFwcmVFbmQpIHtcbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICAvLyBzZXQgcG9pbnRlciBjb29yZGluYXRlLCB0aW1lIGNoYW5nZXMgYW5kIHNwZWVkc1xuICAgICAgICBzZXRFdmVudERlbHRhcyh0aGlzLnBvaW50ZXJEZWx0YSwgdGhpcy5wcmV2Q29vcmRzLCB0aGlzLmN1ckNvb3Jkcyk7XG5cbiAgICAgICAgaWYgKCF0aGlzLnByZXBhcmVkLm5hbWUpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgaWYgKHRoaXMucG9pbnRlcldhc01vdmVkXG4gICAgICAgICAgICAgICAgLy8gaWdub3JlIG1vdmVtZW50IHdoaWxlIGluZXJ0aWEgaXMgYWN0aXZlXG4gICAgICAgICAgICAmJiAoIXRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgfHwgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50ICYmIC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkpKSB7XG5cbiAgICAgICAgICAgIC8vIGlmIGp1c3Qgc3RhcnRpbmcgYW4gYWN0aW9uLCBjYWxjdWxhdGUgdGhlIHBvaW50ZXIgc3BlZWQgbm93XG4gICAgICAgICAgICBpZiAoIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHNldEV2ZW50RGVsdGFzKHRoaXMucG9pbnRlckRlbHRhLCB0aGlzLnByZXZDb29yZHMsIHRoaXMuY3VyQ29vcmRzKTtcblxuICAgICAgICAgICAgICAgIC8vIGNoZWNrIGlmIGEgZHJhZyBpcyBpbiB0aGUgY29ycmVjdCBheGlzXG4gICAgICAgICAgICAgICAgaWYgKHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBhYnNYID0gTWF0aC5hYnMoZHgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgYWJzWSA9IE1hdGguYWJzKGR5KSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldEF4aXMgPSB0aGlzLnRhcmdldC5vcHRpb25zLmRyYWcuYXhpcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMgPSAoYWJzWCA+IGFic1kgPyAneCcgOiBhYnNYIDwgYWJzWSA/ICd5JyA6ICd4eScpO1xuXG4gICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBtb3ZlbWVudCBpc24ndCBpbiB0aGUgYXhpcyBvZiB0aGUgaW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgICAgIGlmIChheGlzICE9PSAneHknICYmIHRhcmdldEF4aXMgIT09ICd4eScgJiYgdGFyZ2V0QXhpcyAhPT0gYXhpcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gY2FuY2VsIHRoZSBwcmVwYXJlZCBhY3Rpb25cbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSA9IG51bGw7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHRoZW4gdHJ5IHRvIGdldCBhIGRyYWcgZnJvbSBhbm90aGVyIGluZXJhY3RhYmxlXG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIGVsZW1lbnQgaW50ZXJhY3RhYmxlc1xuICAgICAgICAgICAgICAgICAgICAgICAgd2hpbGUgKGlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50SW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlcy5nZXQoZWxlbWVudCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudEludGVyYWN0YWJsZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBlbGVtZW50SW50ZXJhY3RhYmxlICE9PSB0aGlzLnRhcmdldFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhZWxlbWVudEludGVyYWN0YWJsZS5vcHRpb25zLmRyYWcubWFudWFsU3RhcnRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgZWxlbWVudEludGVyYWN0YWJsZS5nZXRBY3Rpb24odGhpcy5kb3duUG9pbnRlciwgdGhpcy5kb3duRXZlbnQsIHRoaXMsIGVsZW1lbnQpLm5hbWUgPT09ICdkcmFnJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBjaGVja0F4aXMoYXhpcywgZWxlbWVudEludGVyYWN0YWJsZSkpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gZWxlbWVudEludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZXJlJ3Mgbm8gZHJhZyBmcm9tIGVsZW1lbnQgaW50ZXJhY3RhYmxlcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIHRoZSBzZWxlY3RvciBpbnRlcmFjdGFibGVzXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoIXRoaXMucHJlcGFyZWQubmFtZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciB0aGlzSW50ZXJhY3Rpb24gPSB0aGlzO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGdldERyYWdnYWJsZSA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50cyA9IGllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPyBjb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZDtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlID09PSB0aGlzSW50ZXJhY3Rpb24udGFyZ2V0KSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChpbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYmICFpbnRlcmFjdGFibGUub3B0aW9ucy5kcmFnLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhdGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgdGVzdEFsbG93KGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBtYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVsZW1lbnRzKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgaW50ZXJhY3RhYmxlLmdldEFjdGlvbih0aGlzSW50ZXJhY3Rpb24uZG93blBvaW50ZXIsIHRoaXNJbnRlcmFjdGlvbi5kb3duRXZlbnQsIHRoaXNJbnRlcmFjdGlvbiwgZWxlbWVudCkubmFtZSA9PT0gJ2RyYWcnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBjaGVja0F4aXMoYXhpcywgaW50ZXJhY3RhYmxlKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgd2l0aGluSW50ZXJhY3Rpb25MaW1pdChpbnRlcmFjdGFibGUsIGVsZW1lbnQsICdkcmFnJykpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGlsZSAoaXNFbGVtZW50KGVsZW1lbnQpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBzZWxlY3RvckludGVyYWN0YWJsZSA9IGludGVyYWN0YWJsZXMuZm9yRWFjaFNlbGVjdG9yKGdldERyYWdnYWJsZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHNlbGVjdG9ySW50ZXJhY3RhYmxlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnRhcmdldCA9IHNlbGVjdG9ySW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc3RhcnRpbmcgPSAhIXRoaXMucHJlcGFyZWQubmFtZSAmJiAhdGhpcy5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgICAgICBpZiAoc3RhcnRpbmdcbiAgICAgICAgICAgICAgICAmJiAodGhpcy50YXJnZXQub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgfHwgIXdpdGhpbkludGVyYWN0aW9uTGltaXQodGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCwgdGhpcy5wcmVwYXJlZCkpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5zdG9wKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5uYW1lICYmIHRoaXMudGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgaWYgKHN0YXJ0aW5nKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc3RhcnQodGhpcy5wcmVwYXJlZCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgdmFyIHNob3VsZE1vdmUgPSB0aGlzLnNldE1vZGlmaWNhdGlvbnModGhpcy5jdXJDb29yZHMucGFnZSwgcHJlRW5kKTtcblxuICAgICAgICAgICAgICAgIC8vIG1vdmUgaWYgc25hcHBpbmcgb3IgcmVzdHJpY3Rpb24gZG9lc24ndCBwcmV2ZW50IGl0XG4gICAgICAgICAgICAgICAgaWYgKHNob3VsZE1vdmUgfHwgc3RhcnRpbmcpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdNb3ZlJ10oZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBjb3B5Q29vcmRzKHRoaXMucHJldkNvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgIGlmICh0aGlzLmRyYWdnaW5nIHx8IHRoaXMucmVzaXppbmcpIHtcbiAgICAgICAgICAgIHRoaXMuYXV0b1Njcm9sbE1vdmUocG9pbnRlcik7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgZHJhZ1N0YXJ0OiBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgdmFyIGRyYWdFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZHJhZycsICdzdGFydCcsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcmFnZ2luZyA9IHRydWU7XG4gICAgICAgIHRoaXMudGFyZ2V0LmZpcmUoZHJhZ0V2ZW50KTtcblxuICAgICAgICAvLyByZXNldCBhY3RpdmUgZHJvcHpvbmVzXG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHMgID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgID0gW107XG5cbiAgICAgICAgaWYgKCF0aGlzLmR5bmFtaWNEcm9wKSB7XG4gICAgICAgICAgICB0aGlzLnNldEFjdGl2ZURyb3BzKHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5hY3RpdmF0ZSkge1xuICAgICAgICAgICAgdGhpcy5maXJlQWN0aXZlRHJvcHMoZHJvcEV2ZW50cy5hY3RpdmF0ZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICBkcmFnTW92ZTogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIGRyYWdFdmVudCAgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ2RyYWcnLCAnbW92ZScsIHRoaXMuZWxlbWVudCksXG4gICAgICAgICAgICBkcmFnZ2FibGVFbGVtZW50ID0gdGhpcy5lbGVtZW50LFxuICAgICAgICAgICAgZHJvcCA9IHRoaXMuZ2V0RHJvcChldmVudCwgZHJhZ2dhYmxlRWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gZHJvcC5kcm9wem9uZTtcbiAgICAgICAgdGhpcy5kcm9wRWxlbWVudCA9IGRyb3AuZWxlbWVudDtcblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICB0YXJnZXQuZmlyZShkcmFnRXZlbnQpO1xuXG4gICAgICAgIGlmIChkcm9wRXZlbnRzLmxlYXZlKSB7IHRoaXMucHJldkRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmxlYXZlKTsgfVxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5lbnRlcikgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5lbnRlcik7IH1cbiAgICAgICAgaWYgKGRyb3BFdmVudHMubW92ZSApIHsgICAgIHRoaXMuZHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMubW92ZSApOyB9XG5cbiAgICAgICAgdGhpcy5wcmV2RHJvcFRhcmdldCAgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIHRoaXMucHJldkRyb3BFbGVtZW50ID0gdGhpcy5kcm9wRWxlbWVudDtcblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICByZXNpemVTdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciByZXNpemVFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAncmVzaXplJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5lZGdlcykge1xuICAgICAgICAgICAgdmFyIHN0YXJ0UmVjdCA9IHRoaXMudGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHRoaXMudGFyZ2V0Lm9wdGlvbnMucmVzaXplLnNxdWFyZSkge1xuICAgICAgICAgICAgICAgIHZhciBzcXVhcmVFZGdlcyA9IGV4dGVuZCh7fSwgdGhpcy5wcmVwYXJlZC5lZGdlcyk7XG5cbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy50b3AgICAgPSBzcXVhcmVFZGdlcy50b3AgICAgfHwgKHNxdWFyZUVkZ2VzLmxlZnQgICAmJiAhc3F1YXJlRWRnZXMuYm90dG9tKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5sZWZ0ICAgPSBzcXVhcmVFZGdlcy5sZWZ0ICAgfHwgKHNxdWFyZUVkZ2VzLnRvcCAgICAmJiAhc3F1YXJlRWRnZXMucmlnaHQgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5ib3R0b20gPSBzcXVhcmVFZGdlcy5ib3R0b20gfHwgKHNxdWFyZUVkZ2VzLnJpZ2h0ICAmJiAhc3F1YXJlRWRnZXMudG9wICAgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5yaWdodCAgPSBzcXVhcmVFZGdlcy5yaWdodCAgfHwgKHNxdWFyZUVkZ2VzLmJvdHRvbSAmJiAhc3F1YXJlRWRnZXMubGVmdCAgKTtcblxuICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuX3NxdWFyZUVkZ2VzID0gc3F1YXJlRWRnZXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcyA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucmVzaXplUmVjdHMgPSB7XG4gICAgICAgICAgICAgICAgc3RhcnQgICAgIDogc3RhcnRSZWN0LFxuICAgICAgICAgICAgICAgIGN1cnJlbnQgICA6IGV4dGVuZCh7fSwgc3RhcnRSZWN0KSxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkOiBleHRlbmQoe30sIHN0YXJ0UmVjdCksXG4gICAgICAgICAgICAgICAgcHJldmlvdXMgIDogZXh0ZW5kKHt9LCBzdGFydFJlY3QpLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICA6IHtcbiAgICAgICAgICAgICAgICAgICAgbGVmdDogMCwgcmlnaHQgOiAwLCB3aWR0aCA6IDAsXG4gICAgICAgICAgICAgICAgICAgIHRvcCA6IDAsIGJvdHRvbTogMCwgaGVpZ2h0OiAwXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmVzaXplRXZlbnQucmVjdCA9IHRoaXMucmVzaXplUmVjdHMucmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IHRoaXMucmVzaXplUmVjdHMuZGVsdGE7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKHJlc2l6ZUV2ZW50KTtcblxuICAgICAgICB0aGlzLnJlc2l6aW5nID0gdHJ1ZTtcblxuICAgICAgICByZXR1cm4gcmVzaXplRXZlbnQ7XG4gICAgfSxcblxuICAgIHJlc2l6ZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgcmVzaXplRXZlbnQgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ3Jlc2l6ZScsICdtb3ZlJywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICB2YXIgZWRnZXMgPSB0aGlzLnByZXBhcmVkLmVkZ2VzLFxuICAgICAgICAgICAgaW52ZXJ0ID0gdGhpcy50YXJnZXQub3B0aW9ucy5yZXNpemUuaW52ZXJ0LFxuICAgICAgICAgICAgaW52ZXJ0aWJsZSA9IGludmVydCA9PT0gJ3JlcG9zaXRpb24nIHx8IGludmVydCA9PT0gJ25lZ2F0ZSc7XG5cbiAgICAgICAgaWYgKGVkZ2VzKSB7XG4gICAgICAgICAgICB2YXIgZHggPSByZXNpemVFdmVudC5keCxcbiAgICAgICAgICAgICAgICBkeSA9IHJlc2l6ZUV2ZW50LmR5LFxuXG4gICAgICAgICAgICAgICAgc3RhcnQgICAgICA9IHRoaXMucmVzaXplUmVjdHMuc3RhcnQsXG4gICAgICAgICAgICAgICAgY3VycmVudCAgICA9IHRoaXMucmVzaXplUmVjdHMuY3VycmVudCxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkID0gdGhpcy5yZXNpemVSZWN0cy5yZXN0cmljdGVkLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICAgPSB0aGlzLnJlc2l6ZVJlY3RzLmRlbHRhLFxuICAgICAgICAgICAgICAgIHByZXZpb3VzICAgPSBleHRlbmQodGhpcy5yZXNpemVSZWN0cy5wcmV2aW91cywgcmVzdHJpY3RlZCk7XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnRhcmdldC5vcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgICAgICB2YXIgb3JpZ2luYWxFZGdlcyA9IGVkZ2VzO1xuXG4gICAgICAgICAgICAgICAgZWRnZXMgPSB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcztcblxuICAgICAgICAgICAgICAgIGlmICgob3JpZ2luYWxFZGdlcy5sZWZ0ICYmIG9yaWdpbmFsRWRnZXMuYm90dG9tKVxuICAgICAgICAgICAgICAgICAgICB8fCAob3JpZ2luYWxFZGdlcy5yaWdodCAmJiBvcmlnaW5hbEVkZ2VzLnRvcCkpIHtcbiAgICAgICAgICAgICAgICAgICAgZHkgPSAtZHg7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9yaWdpbmFsRWRnZXMubGVmdCB8fCBvcmlnaW5hbEVkZ2VzLnJpZ2h0KSB7IGR5ID0gZHg7IH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChvcmlnaW5hbEVkZ2VzLnRvcCB8fCBvcmlnaW5hbEVkZ2VzLmJvdHRvbSkgeyBkeCA9IGR5OyB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgJ2N1cnJlbnQnIHJlY3Qgd2l0aG91dCBtb2RpZmljYXRpb25zXG4gICAgICAgICAgICBpZiAoZWRnZXMudG9wICAgKSB7IGN1cnJlbnQudG9wICAgICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMuYm90dG9tKSB7IGN1cnJlbnQuYm90dG9tICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMubGVmdCAgKSB7IGN1cnJlbnQubGVmdCAgICs9IGR4OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMucmlnaHQgKSB7IGN1cnJlbnQucmlnaHQgICs9IGR4OyB9XG5cbiAgICAgICAgICAgIGlmIChpbnZlcnRpYmxlKSB7XG4gICAgICAgICAgICAgICAgLy8gaWYgaW52ZXJ0aWJsZSwgY29weSB0aGUgY3VycmVudCByZWN0XG4gICAgICAgICAgICAgICAgZXh0ZW5kKHJlc3RyaWN0ZWQsIGN1cnJlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGludmVydCA9PT0gJ3JlcG9zaXRpb24nKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHN3YXAgZWRnZSB2YWx1ZXMgaWYgbmVjZXNzYXJ5IHRvIGtlZXAgd2lkdGgvaGVpZ2h0IHBvc2l0aXZlXG4gICAgICAgICAgICAgICAgICAgIHZhciBzd2FwO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN0cmljdGVkLnRvcCA+IHJlc3RyaWN0ZWQuYm90dG9tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzd2FwID0gcmVzdHJpY3RlZC50b3A7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQudG9wID0gcmVzdHJpY3RlZC5ib3R0b207XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IHN3YXA7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3RyaWN0ZWQubGVmdCA+IHJlc3RyaWN0ZWQucmlnaHQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHN3YXAgPSByZXN0cmljdGVkLmxlZnQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCA9IHJlc3RyaWN0ZWQucmlnaHQ7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLnJpZ2h0ID0gc3dhcDtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIGlmIG5vdCBpbnZlcnRpYmxlLCByZXN0cmljdCB0byBtaW5pbXVtIG9mIDB4MCByZWN0XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC50b3AgICAgPSBNYXRoLm1pbihjdXJyZW50LnRvcCwgc3RhcnQuYm90dG9tKTtcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IE1hdGgubWF4KGN1cnJlbnQuYm90dG9tLCBzdGFydC50b3ApO1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCAgID0gTWF0aC5taW4oY3VycmVudC5sZWZ0LCBzdGFydC5yaWdodCk7XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC5yaWdodCAgPSBNYXRoLm1heChjdXJyZW50LnJpZ2h0LCBzdGFydC5sZWZ0KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzdHJpY3RlZC53aWR0aCAgPSByZXN0cmljdGVkLnJpZ2h0ICAtIHJlc3RyaWN0ZWQubGVmdDtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWQuaGVpZ2h0ID0gcmVzdHJpY3RlZC5ib3R0b20gLSByZXN0cmljdGVkLnRvcCA7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGVkZ2UgaW4gcmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgIGRlbHRhW2VkZ2VdID0gcmVzdHJpY3RlZFtlZGdlXSAtIHByZXZpb3VzW2VkZ2VdO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXNpemVFdmVudC5lZGdlcyA9IHRoaXMucHJlcGFyZWQuZWRnZXM7XG4gICAgICAgICAgICByZXNpemVFdmVudC5yZWN0ID0gcmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IGRlbHRhO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShyZXNpemVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIHJlc2l6ZUV2ZW50O1xuICAgIH0sXG5cbiAgICBnZXN0dXJlU3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSAwO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5zdGFydERpc3RhbmNlID0gdGhpcy5nZXN0dXJlLnByZXZEaXN0YW5jZSA9IGdlc3R1cmVFdmVudC5kaXN0YW5jZTtcbiAgICAgICAgdGhpcy5nZXN0dXJlLnN0YXJ0QW5nbGUgPSB0aGlzLmdlc3R1cmUucHJldkFuZ2xlID0gZ2VzdHVyZUV2ZW50LmFuZ2xlO1xuICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSAxO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyaW5nID0gdHJ1ZTtcblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKGdlc3R1cmVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIGdlc3R1cmVFdmVudDtcbiAgICB9LFxuXG4gICAgZ2VzdHVyZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBpZiAoIXRoaXMucG9pbnRlcklkcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnByZXZFdmVudDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBnZXN0dXJlRXZlbnQ7XG5cbiAgICAgICAgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ21vdmUnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSBnZXN0dXJlRXZlbnQuc2NhbGUgLSB0aGlzLmdlc3R1cmUuc2NhbGU7XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShnZXN0dXJlRXZlbnQpO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2QW5nbGUgPSBnZXN0dXJlRXZlbnQuYW5nbGU7XG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2RGlzdGFuY2UgPSBnZXN0dXJlRXZlbnQuZGlzdGFuY2U7XG5cbiAgICAgICAgaWYgKGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gSW5maW5pdHkgJiZcbiAgICAgICAgICAgIGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gbnVsbCAmJlxuICAgICAgICAgICAgZ2VzdHVyZUV2ZW50LnNjYWxlICE9PSB1bmRlZmluZWQgICYmXG4gICAgICAgICAgICAhaXNOYU4oZ2VzdHVyZUV2ZW50LnNjYWxlKSkge1xuXG4gICAgICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSBnZXN0dXJlRXZlbnQuc2NhbGU7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZ2VzdHVyZUV2ZW50O1xuICAgIH0sXG5cbiAgICBwb2ludGVySG9sZDogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCkge1xuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnaG9sZCcpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyVXA6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLm1vdXNlPyAwIDogaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGdldFBvaW50ZXJJZChwb2ludGVyKSk7XG5cbiAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcblxuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndXAnICk7XG4gICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICd0YXAnKTtcblxuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyQ2FuY2VsOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YodGhpcy5wb2ludGVySWRzLCBnZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIGNsZWFyVGltZW91dCh0aGlzLmhvbGRUaW1lcnNbcG9pbnRlckluZGV4XSk7XG5cbiAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2NhbmNlbCcpO1xuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICAvLyBodHRwOi8vd3d3LnF1aXJrc21vZGUub3JnL2RvbS9ldmVudHMvY2xpY2suaHRtbFxuICAgIC8vID5FdmVudHMgbGVhZGluZyB0byBkYmxjbGlja1xuICAgIC8vXG4gICAgLy8gSUU4IGRvZXNuJ3QgZmlyZSBkb3duIGV2ZW50IGJlZm9yZSBkYmxjbGljay5cbiAgICAvLyBUaGlzIHdvcmthcm91bmQgdHJpZXMgdG8gZmlyZSBhIHRhcCBhbmQgZG91YmxldGFwIGFmdGVyIGRibGNsaWNrXG4gICAgaWU4RGJsY2xpY2s6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQpIHtcbiAgICAgICAgaWYgKHRoaXMucHJldlRhcFxuICAgICAgICAgICAgJiYgZXZlbnQuY2xpZW50WCA9PT0gdGhpcy5wcmV2VGFwLmNsaWVudFhcbiAgICAgICAgICAgICYmIGV2ZW50LmNsaWVudFkgPT09IHRoaXMucHJldlRhcC5jbGllbnRZXG4gICAgICAgICAgICAmJiBldmVudFRhcmdldCAgID09PSB0aGlzLnByZXZUYXAudGFyZ2V0KSB7XG5cbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbMF0gPSBldmVudFRhcmdldDtcbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzWzBdID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndGFwJyk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gRW5kIGludGVyYWN0IG1vdmUgZXZlbnRzIGFuZCBzdG9wIGF1dG8tc2Nyb2xsIHVubGVzcyBpbmVydGlhIGlzIGVuYWJsZWRcbiAgICBwb2ludGVyRW5kOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgZW5kRXZlbnQsXG4gICAgICAgICAgICB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIG9wdGlvbnMgPSB0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMsXG4gICAgICAgICAgICBpbmVydGlhT3B0aW9ucyA9IG9wdGlvbnMgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5pbmVydGlhLFxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cyA9IHRoaXMuaW5lcnRpYVN0YXR1cztcblxuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhU3RhdHVzLmFjdGl2ZSkgeyByZXR1cm47IH1cblxuICAgICAgICAgICAgdmFyIHBvaW50ZXJTcGVlZCxcbiAgICAgICAgICAgICAgICBub3cgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSBmYWxzZSxcbiAgICAgICAgICAgICAgICBpbmVydGlhID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgc21vb3RoRW5kID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgZW5kU25hcCA9IGNoZWNrU25hcCh0YXJnZXQsIHRoaXMucHJlcGFyZWQubmFtZSkgJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnNuYXAuZW5kT25seSxcbiAgICAgICAgICAgICAgICBlbmRSZXN0cmljdCA9IGNoZWNrUmVzdHJpY3QodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5yZXN0cmljdC5lbmRPbmx5LFxuICAgICAgICAgICAgICAgIGR4ID0gMCxcbiAgICAgICAgICAgICAgICBkeSA9IDAsXG4gICAgICAgICAgICAgICAgc3RhcnRFdmVudDtcblxuICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ2dpbmcpIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChvcHRpb25zLmRyYWcuYXhpcyA9PT0gJ3gnICkgeyBwb2ludGVyU3BlZWQgPSBNYXRoLmFicyh0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudngpOyB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAob3B0aW9ucy5kcmFnLmF4aXMgPT09ICd5JyApIHsgcG9pbnRlclNwZWVkID0gTWF0aC5hYnModGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5KTsgfVxuICAgICAgICAgICAgICAgIGVsc2UgICAvKm9wdGlvbnMuZHJhZy5heGlzID09PSAneHknKi97IHBvaW50ZXJTcGVlZCA9IHRoaXMucG9pbnRlckRlbHRhLmNsaWVudC5zcGVlZDsgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgcG9pbnRlclNwZWVkID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnNwZWVkO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBjaGVjayBpZiBpbmVydGlhIHNob3VsZCBiZSBzdGFydGVkXG4gICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSAoaW5lcnRpYU9wdGlvbnMgJiYgaW5lcnRpYU9wdGlvbnMuZW5hYmxlZFxuICAgICAgICAgICAgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICE9PSAnZ2VzdHVyZSdcbiAgICAgICAgICAgICYmIGV2ZW50ICE9PSBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhID0gKGluZXJ0aWFQb3NzaWJsZVxuICAgICAgICAgICAgJiYgKG5vdyAtIHRoaXMuY3VyQ29vcmRzLnRpbWVTdGFtcCkgPCA1MFxuICAgICAgICAgICAgJiYgcG9pbnRlclNwZWVkID4gaW5lcnRpYU9wdGlvbnMubWluU3BlZWRcbiAgICAgICAgICAgICYmIHBvaW50ZXJTcGVlZCA+IGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkKTtcblxuICAgICAgICAgICAgaWYgKGluZXJ0aWFQb3NzaWJsZSAmJiAhaW5lcnRpYSAmJiAoZW5kU25hcCB8fCBlbmRSZXN0cmljdCkpIHtcblxuICAgICAgICAgICAgICAgIHZhciBzbmFwUmVzdHJpY3QgPSB7fTtcblxuICAgICAgICAgICAgICAgIHNuYXBSZXN0cmljdC5zbmFwID0gc25hcFJlc3RyaWN0LnJlc3RyaWN0ID0gc25hcFJlc3RyaWN0O1xuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFNuYXApIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5zZXRTbmFwcGluZyh0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzbmFwUmVzdHJpY3QpO1xuICAgICAgICAgICAgICAgICAgICBpZiAoc25hcFJlc3RyaWN0LmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZHggKz0gc25hcFJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcFJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFJlc3RyaWN0KSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc2V0UmVzdHJpY3Rpb24odGhpcy5jdXJDb29yZHMucGFnZSwgc25hcFJlc3RyaWN0KTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHNuYXBSZXN0cmljdC5yZXN0cmljdGVkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeCArPSBzbmFwUmVzdHJpY3QuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeSArPSBzbmFwUmVzdHJpY3QuZHk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAoZHggfHwgZHkpIHtcbiAgICAgICAgICAgICAgICAgICAgc21vb3RoRW5kID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhIHx8IHNtb290aEVuZCkge1xuICAgICAgICAgICAgICAgIGNvcHlDb29yZHMoaW5lcnRpYVN0YXR1cy51cENvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVyc1swXSA9IGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCA9IHN0YXJ0RXZlbnQgPVxuICAgICAgICAgICAgICAgICAgICBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgdGhpcy5wcmVwYXJlZC5uYW1lLCAnaW5lcnRpYXN0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudDAgPSBub3c7XG5cbiAgICAgICAgICAgICAgICB0YXJnZXQuZmlyZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGluZXJ0aWEpIHtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy52eDAgPSB0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudng7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudnkwID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnYwID0gcG9pbnRlclNwZWVkO1xuXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuY2FsY0luZXJ0aWEoaW5lcnRpYVN0YXR1cyk7XG5cbiAgICAgICAgICAgICAgICAgICAgdmFyIHBhZ2UgPSBleHRlbmQoe30sIHRoaXMuY3VyQ29vcmRzLnBhZ2UpLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JpZ2luID0gZ2V0T3JpZ2luWFkodGFyZ2V0LCB0aGlzLmVsZW1lbnQpLFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzT2JqZWN0O1xuXG4gICAgICAgICAgICAgICAgICAgIHBhZ2UueCA9IHBhZ2UueCArIGluZXJ0aWFTdGF0dXMueGUgLSBvcmlnaW4ueDtcbiAgICAgICAgICAgICAgICAgICAgcGFnZS55ID0gcGFnZS55ICsgaW5lcnRpYVN0YXR1cy55ZSAtIG9yaWdpbi55O1xuXG4gICAgICAgICAgICAgICAgICAgIHN0YXR1c09iamVjdCA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHVzZVN0YXR1c1hZOiB0cnVlLFxuICAgICAgICAgICAgICAgICAgICAgICAgeDogcGFnZS54LFxuICAgICAgICAgICAgICAgICAgICAgICAgeTogcGFnZS55LFxuICAgICAgICAgICAgICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICAgICAgICAgICAgICBkeTogMCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNuYXA6IG51bGxcbiAgICAgICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgICAgICBzdGF0dXNPYmplY3Quc25hcCA9IHN0YXR1c09iamVjdDtcblxuICAgICAgICAgICAgICAgICAgICBkeCA9IGR5ID0gMDtcblxuICAgICAgICAgICAgICAgICAgICBpZiAoZW5kU25hcCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHNuYXAgPSB0aGlzLnNldFNuYXBwaW5nKHRoaXMuY3VyQ29vcmRzLnBhZ2UsIHN0YXR1c09iamVjdCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzbmFwLmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHNuYXAuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcC5keTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIGlmIChlbmRSZXN0cmljdCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHJlc3RyaWN0ID0gdGhpcy5zZXRSZXN0cmljdGlvbih0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzdGF0dXNPYmplY3QpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAocmVzdHJpY3QucmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR5ICs9IHJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFhlICs9IGR4O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUgKz0gZHk7XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5pID0gcmVxRnJhbWUodGhpcy5ib3VuZEluZXJ0aWFGcmFtZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnNtb290aEVuZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMueGUgPSBkeDtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy55ZSA9IGR5O1xuXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLnN5ID0gMDtcblxuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLmkgPSByZXFGcmFtZSh0aGlzLmJvdW5kU21vb3RoRW5kRnJhbWUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuYWN0aXZlID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChlbmRTbmFwIHx8IGVuZFJlc3RyaWN0KSB7XG4gICAgICAgICAgICAgICAgLy8gZmlyZSBhIG1vdmUgZXZlbnQgYXQgdGhlIHNuYXBwZWQgY29vcmRpbmF0ZXNcbiAgICAgICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQsIHRydWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMuZHJhZ2dpbmcpIHtcbiAgICAgICAgICAgIGVuZEV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdkcmFnJywgJ2VuZCcsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgICAgIHZhciBkcmFnZ2FibGVFbGVtZW50ID0gdGhpcy5lbGVtZW50LFxuICAgICAgICAgICAgICAgIGRyb3AgPSB0aGlzLmdldERyb3AoZXZlbnQsIGRyYWdnYWJsZUVsZW1lbnQpO1xuXG4gICAgICAgICAgICB0aGlzLmRyb3BUYXJnZXQgPSBkcm9wLmRyb3B6b25lO1xuICAgICAgICAgICAgdGhpcy5kcm9wRWxlbWVudCA9IGRyb3AuZWxlbWVudDtcblxuICAgICAgICAgICAgdmFyIGRyb3BFdmVudHMgPSB0aGlzLmdldERyb3BFdmVudHMoZXZlbnQsIGVuZEV2ZW50KTtcblxuICAgICAgICAgICAgaWYgKGRyb3BFdmVudHMubGVhdmUpIHsgdGhpcy5wcmV2RHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMubGVhdmUpOyB9XG4gICAgICAgICAgICBpZiAoZHJvcEV2ZW50cy5lbnRlcikgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5lbnRlcik7IH1cbiAgICAgICAgICAgIGlmIChkcm9wRXZlbnRzLmRyb3AgKSB7ICAgICB0aGlzLmRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmRyb3AgKTsgfVxuICAgICAgICAgICAgaWYgKGRyb3BFdmVudHMuZGVhY3RpdmF0ZSkge1xuICAgICAgICAgICAgICAgIHRoaXMuZmlyZUFjdGl2ZURyb3BzKGRyb3BFdmVudHMuZGVhY3RpdmF0ZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRhcmdldC5maXJlKGVuZEV2ZW50KTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmICh0aGlzLnJlc2l6aW5nKSB7XG4gICAgICAgICAgICBlbmRFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAncmVzaXplJywgJ2VuZCcsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB0YXJnZXQuZmlyZShlbmRFdmVudCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodGhpcy5nZXN0dXJpbmcpIHtcbiAgICAgICAgICAgIGVuZEV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ2VuZCcsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB0YXJnZXQuZmlyZShlbmRFdmVudCk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnN0b3AoZXZlbnQpO1xuICAgIH0sXG5cbiAgICBjb2xsZWN0RHJvcHM6IGZ1bmN0aW9uIChlbGVtZW50KSB7XG4gICAgICAgIHZhciBkcm9wcyA9IFtdLFxuICAgICAgICAgICAgZWxlbWVudHMgPSBbXSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgZWxlbWVudCA9IGVsZW1lbnQgfHwgdGhpcy5lbGVtZW50O1xuXG4gICAgICAgIC8vIGNvbGxlY3QgYWxsIGRyb3B6b25lcyBhbmQgdGhlaXIgZWxlbWVudHMgd2hpY2ggcXVhbGlmeSBmb3IgYSBkcm9wXG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBpbnRlcmFjdGFibGVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoIWludGVyYWN0YWJsZXNbaV0ub3B0aW9ucy5kcm9wLmVuYWJsZWQpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgdmFyIGN1cnJlbnQgPSBpbnRlcmFjdGFibGVzW2ldLFxuICAgICAgICAgICAgICAgIGFjY2VwdCA9IGN1cnJlbnQub3B0aW9ucy5kcm9wLmFjY2VwdDtcblxuICAgICAgICAgICAgLy8gdGVzdCB0aGUgZHJhZ2dhYmxlIGVsZW1lbnQgYWdhaW5zdCB0aGUgZHJvcHpvbmUncyBhY2NlcHQgc2V0dGluZ1xuICAgICAgICAgICAgaWYgKChpc0VsZW1lbnQoYWNjZXB0KSAmJiBhY2NlcHQgIT09IGVsZW1lbnQpXG4gICAgICAgICAgICAgICAgfHwgKGlzU3RyaW5nKGFjY2VwdClcbiAgICAgICAgICAgICAgICAmJiAhbWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIGFjY2VwdCkpKSB7XG5cbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gcXVlcnkgZm9yIG5ldyBlbGVtZW50cyBpZiBuZWNlc3NhcnlcbiAgICAgICAgICAgIHZhciBkcm9wRWxlbWVudHMgPSBjdXJyZW50LnNlbGVjdG9yPyBjdXJyZW50Ll9jb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoY3VycmVudC5zZWxlY3RvcikgOiBbY3VycmVudC5fZWxlbWVudF07XG5cbiAgICAgICAgICAgIGZvciAodmFyIGogPSAwLCBsZW4gPSBkcm9wRWxlbWVudHMubGVuZ3RoOyBqIDwgbGVuOyBqKyspIHtcbiAgICAgICAgICAgICAgICB2YXIgY3VycmVudEVsZW1lbnQgPSBkcm9wRWxlbWVudHNbal07XG5cbiAgICAgICAgICAgICAgICBpZiAoY3VycmVudEVsZW1lbnQgPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgZHJvcHMucHVzaChjdXJyZW50KTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKGN1cnJlbnRFbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBkcm9wem9uZXM6IGRyb3BzLFxuICAgICAgICAgICAgZWxlbWVudHM6IGVsZW1lbnRzXG4gICAgICAgIH07XG4gICAgfSxcblxuICAgIGZpcmVBY3RpdmVEcm9wczogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciBpLFxuICAgICAgICAgICAgY3VycmVudCxcbiAgICAgICAgICAgIGN1cnJlbnRFbGVtZW50LFxuICAgICAgICAgICAgcHJldkVsZW1lbnQ7XG5cbiAgICAgICAgLy8gbG9vcCB0aHJvdWdoIGFsbCBhY3RpdmUgZHJvcHpvbmVzIGFuZCB0cmlnZ2VyIGV2ZW50XG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgY3VycmVudCA9IHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzW2ldO1xuICAgICAgICAgICAgY3VycmVudEVsZW1lbnQgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzIFtpXTtcblxuICAgICAgICAgICAgLy8gcHJldmVudCB0cmlnZ2VyIG9mIGR1cGxpY2F0ZSBldmVudHMgb24gc2FtZSBlbGVtZW50XG4gICAgICAgICAgICBpZiAoY3VycmVudEVsZW1lbnQgIT09IHByZXZFbGVtZW50KSB7XG4gICAgICAgICAgICAgICAgLy8gc2V0IGN1cnJlbnQgZWxlbWVudCBhcyBldmVudCB0YXJnZXRcbiAgICAgICAgICAgICAgICBldmVudC50YXJnZXQgPSBjdXJyZW50RWxlbWVudDtcbiAgICAgICAgICAgICAgICBjdXJyZW50LmZpcmUoZXZlbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcHJldkVsZW1lbnQgPSBjdXJyZW50RWxlbWVudDtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICAvLyBDb2xsZWN0IGEgbmV3IHNldCBvZiBwb3NzaWJsZSBkcm9wcyBhbmQgc2F2ZSB0aGVtIGluIGFjdGl2ZURyb3BzLlxuICAgIC8vIHNldEFjdGl2ZURyb3BzIHNob3VsZCBhbHdheXMgYmUgY2FsbGVkIHdoZW4gYSBkcmFnIGhhcyBqdXN0IHN0YXJ0ZWQgb3IgYVxuICAgIC8vIGRyYWcgZXZlbnQgaGFwcGVucyB3aGlsZSBkeW5hbWljRHJvcCBpcyB0cnVlXG4gICAgc2V0QWN0aXZlRHJvcHM6IGZ1bmN0aW9uIChkcmFnRWxlbWVudCkge1xuICAgICAgICAvLyBnZXQgZHJvcHpvbmVzIGFuZCB0aGVpciBlbGVtZW50cyB0aGF0IGNvdWxkIHJlY2VpdmUgdGhlIGRyYWdnYWJsZVxuICAgICAgICB2YXIgcG9zc2libGVEcm9wcyA9IHRoaXMuY29sbGVjdERyb3BzKGRyYWdFbGVtZW50LCB0cnVlKTtcblxuICAgICAgICB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcyA9IHBvc3NpYmxlRHJvcHMuZHJvcHpvbmVzO1xuICAgICAgICB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzICA9IHBvc3NpYmxlRHJvcHMuZWxlbWVudHM7XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgID0gW107XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5yZWN0c1tpXSA9IHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzW2ldLmdldFJlY3QodGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50c1tpXSk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgZ2V0RHJvcDogZnVuY3Rpb24gKGV2ZW50LCBkcmFnRWxlbWVudCkge1xuICAgICAgICB2YXIgdmFsaWREcm9wcyA9IFtdO1xuXG4gICAgICAgIGlmIChkeW5hbWljRHJvcCkge1xuICAgICAgICAgICAgdGhpcy5zZXRBY3RpdmVEcm9wcyhkcmFnRWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBjb2xsZWN0IGFsbCBkcm9wem9uZXMgYW5kIHRoZWlyIGVsZW1lbnRzIHdoaWNoIHF1YWxpZnkgZm9yIGEgZHJvcFxuICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICB2YXIgY3VycmVudCAgICAgICAgPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tqXSxcbiAgICAgICAgICAgICAgICBjdXJyZW50RWxlbWVudCA9IHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHMgW2pdLFxuICAgICAgICAgICAgICAgIHJlY3QgICAgICAgICAgID0gdGhpcy5hY3RpdmVEcm9wcy5yZWN0cyAgICBbal07XG5cbiAgICAgICAgICAgIHZhbGlkRHJvcHMucHVzaChjdXJyZW50LmRyb3BDaGVjayh0aGlzLnBvaW50ZXJzWzBdLCBldmVudCwgdGhpcy50YXJnZXQsIGRyYWdFbGVtZW50LCBjdXJyZW50RWxlbWVudCwgcmVjdClcbiAgICAgICAgICAgICAgICA/IGN1cnJlbnRFbGVtZW50XG4gICAgICAgICAgICAgICAgOiBudWxsKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGdldCB0aGUgbW9zdCBhcHByb3ByaWF0ZSBkcm9wem9uZSBiYXNlZCBvbiBET00gZGVwdGggYW5kIG9yZGVyXG4gICAgICAgIHZhciBkcm9wSW5kZXggPSBpbmRleE9mRGVlcGVzdEVsZW1lbnQodmFsaWREcm9wcyksXG4gICAgICAgICAgICBkcm9wem9uZSAgPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tkcm9wSW5kZXhdIHx8IG51bGwsXG4gICAgICAgICAgICBlbGVtZW50ICAgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzIFtkcm9wSW5kZXhdIHx8IG51bGw7XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGRyb3B6b25lOiBkcm9wem9uZSxcbiAgICAgICAgICAgIGVsZW1lbnQ6IGVsZW1lbnRcbiAgICAgICAgfTtcbiAgICB9LFxuXG4gICAgZ2V0RHJvcEV2ZW50czogZnVuY3Rpb24gKHBvaW50ZXJFdmVudCwgZHJhZ0V2ZW50KSB7XG4gICAgICAgIHZhciBkcm9wRXZlbnRzID0ge1xuICAgICAgICAgICAgZW50ZXIgICAgIDogbnVsbCxcbiAgICAgICAgICAgIGxlYXZlICAgICA6IG51bGwsXG4gICAgICAgICAgICBhY3RpdmF0ZSAgOiBudWxsLFxuICAgICAgICAgICAgZGVhY3RpdmF0ZTogbnVsbCxcbiAgICAgICAgICAgIG1vdmUgICAgICA6IG51bGwsXG4gICAgICAgICAgICBkcm9wICAgICAgOiBudWxsXG4gICAgICAgIH07XG5cbiAgICAgICAgaWYgKHRoaXMuZHJvcEVsZW1lbnQgIT09IHRoaXMucHJldkRyb3BFbGVtZW50KSB7XG4gICAgICAgICAgICAvLyBpZiB0aGVyZSB3YXMgYSBwcmV2RHJvcFRhcmdldCwgY3JlYXRlIGEgZHJhZ2xlYXZlIGV2ZW50XG4gICAgICAgICAgICBpZiAodGhpcy5wcmV2RHJvcFRhcmdldCkge1xuICAgICAgICAgICAgICAgIGRyb3BFdmVudHMubGVhdmUgPSB7XG4gICAgICAgICAgICAgICAgICAgIHRhcmdldCAgICAgICA6IHRoaXMucHJldkRyb3BFbGVtZW50LFxuICAgICAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLnByZXZEcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICByZWxhdGVkVGFyZ2V0OiBkcmFnRXZlbnQudGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uICA6IHRoaXMsXG4gICAgICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcmFnbGVhdmUnXG4gICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgIGRyYWdFdmVudC5kcmFnTGVhdmUgPSB0aGlzLnByZXZEcm9wRWxlbWVudDtcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQucHJldkRyb3B6b25lID0gdGhpcy5wcmV2RHJvcFRhcmdldDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIGlmIHRoZSBkcm9wVGFyZ2V0IGlzIG5vdCBudWxsLCBjcmVhdGUgYSBkcmFnZW50ZXIgZXZlbnRcbiAgICAgICAgICAgIGlmICh0aGlzLmRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgICAgICBkcm9wRXZlbnRzLmVudGVyID0ge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiB0aGlzLmRyb3BFbGVtZW50LFxuICAgICAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLmRyb3BUYXJnZXQsXG4gICAgICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgICAgIGRyYWdnYWJsZSAgICA6IGRyYWdFdmVudC5pbnRlcmFjdGFibGUsXG4gICAgICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICAgICAgdGltZVN0YW1wICAgIDogZHJhZ0V2ZW50LnRpbWVTdGFtcCxcbiAgICAgICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2RyYWdlbnRlcidcbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgZHJhZ0V2ZW50LmRyYWdFbnRlciA9IHRoaXMuZHJvcEVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgZHJhZ0V2ZW50LmRyb3B6b25lID0gdGhpcy5kcm9wVGFyZ2V0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ2VuZCcgJiYgdGhpcy5kcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICBkcm9wRXZlbnRzLmRyb3AgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogdGhpcy5kcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLmRyb3BUYXJnZXQsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2Ryb3AnXG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICBkcmFnRXZlbnQuZHJvcHpvbmUgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ3N0YXJ0Jykge1xuICAgICAgICAgICAgZHJvcEV2ZW50cy5hY3RpdmF0ZSA9IHtcbiAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIGRyb3B6b25lICAgICA6IG51bGwsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2Ryb3BhY3RpdmF0ZSdcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ2VuZCcpIHtcbiAgICAgICAgICAgIGRyb3BFdmVudHMuZGVhY3RpdmF0ZSA9IHtcbiAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIGRyb3B6b25lICAgICA6IG51bGwsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2Ryb3BkZWFjdGl2YXRlJ1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoZHJhZ0V2ZW50LnR5cGUgPT09ICdkcmFnbW92ZScgJiYgdGhpcy5kcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICBkcm9wRXZlbnRzLm1vdmUgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogdGhpcy5kcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLmRyb3BUYXJnZXQsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIGRyYWdtb3ZlICAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wbW92ZSdcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBkcmFnRXZlbnQuZHJvcHpvbmUgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZHJvcEV2ZW50cztcbiAgICB9LFxuXG4gICAgY3VycmVudEFjdGlvbjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gKHRoaXMuZHJhZ2dpbmcgJiYgJ2RyYWcnKSB8fCAodGhpcy5yZXNpemluZyAmJiAncmVzaXplJykgfHwgKHRoaXMuZ2VzdHVyaW5nICYmICdnZXN0dXJlJykgfHwgbnVsbDtcbiAgICB9LFxuXG4gICAgaW50ZXJhY3Rpbmc6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZHJhZ2dpbmcgfHwgdGhpcy5yZXNpemluZyB8fCB0aGlzLmdlc3R1cmluZztcbiAgICB9LFxuXG4gICAgY2xlYXJUYXJnZXRzOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMudGFyZ2V0ID0gdGhpcy5lbGVtZW50ID0gbnVsbDtcblxuICAgICAgICB0aGlzLmRyb3BUYXJnZXQgPSB0aGlzLmRyb3BFbGVtZW50ID0gdGhpcy5wcmV2RHJvcFRhcmdldCA9IHRoaXMucHJldkRyb3BFbGVtZW50ID0gbnVsbDtcbiAgICB9LFxuXG4gICAgc3RvcDogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIGlmICh0aGlzLmludGVyYWN0aW5nKCkpIHtcbiAgICAgICAgICAgIGF1dG9TY3JvbGwuc3RvcCgpO1xuICAgICAgICAgICAgdGhpcy5tYXRjaGVzID0gW107XG4gICAgICAgICAgICB0aGlzLm1hdGNoRWxlbWVudHMgPSBbXTtcblxuICAgICAgICAgICAgdmFyIHRhcmdldCA9IHRoaXMudGFyZ2V0O1xuXG4gICAgICAgICAgICBpZiAodGFyZ2V0Lm9wdGlvbnMuc3R5bGVDdXJzb3IpIHtcbiAgICAgICAgICAgICAgICB0YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHByZXZlbnQgRGVmYXVsdCBvbmx5IGlmIHdlcmUgcHJldmlvdXNseSBpbnRlcmFjdGluZ1xuICAgICAgICAgICAgaWYgKGV2ZW50ICYmIGlzRnVuY3Rpb24oZXZlbnQucHJldmVudERlZmF1bHQpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLmRyYWdnaW5nKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzID0gdGhpcy5hY3RpdmVEcm9wcy5yZWN0cyA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMuY2xlYXJUYXJnZXRzKCk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJc0Rvd24gPSB0aGlzLnNuYXBTdGF0dXMubG9ja2VkID0gdGhpcy5kcmFnZ2luZyA9IHRoaXMucmVzaXppbmcgPSB0aGlzLmdlc3R1cmluZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSB0aGlzLnByZXZFdmVudCA9IG51bGw7XG4gICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeCA9IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeSA9IDA7XG5cbiAgICAgICAgLy8gcmVtb3ZlIHBvaW50ZXJzIGlmIHRoZWlyIElEIGlzbid0IGluIHRoaXMucG9pbnRlcklkc1xuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMucG9pbnRlcnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpbmRleE9mKHRoaXMucG9pbnRlcklkcywgZ2V0UG9pbnRlcklkKHRoaXMucG9pbnRlcnNbaV0pKSA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnBvaW50ZXJzLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBpbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIC8vIHJlbW92ZSB0aGlzIGludGVyYWN0aW9uIGlmIGl0J3Mgbm90IHRoZSBvbmx5IG9uZSBvZiBpdCdzIHR5cGVcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbnNbaV0gIT09IHRoaXMgJiYgaW50ZXJhY3Rpb25zW2ldLm1vdXNlID09PSB0aGlzLm1vdXNlKSB7XG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb25zLnNwbGljZShpbmRleE9mKGludGVyYWN0aW9ucywgdGhpcyksIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSxcblxuICAgIGluZXJ0aWFGcmFtZTogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgaW5lcnRpYVN0YXR1cyA9IHRoaXMuaW5lcnRpYVN0YXR1cyxcbiAgICAgICAgICAgIG9wdGlvbnMgPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYSxcbiAgICAgICAgICAgIGxhbWJkYSA9IG9wdGlvbnMucmVzaXN0YW5jZSxcbiAgICAgICAgICAgIHQgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSAvIDEwMDAgLSBpbmVydGlhU3RhdHVzLnQwO1xuXG4gICAgICAgIGlmICh0IDwgaW5lcnRpYVN0YXR1cy50ZSkge1xuXG4gICAgICAgICAgICB2YXIgcHJvZ3Jlc3MgPSAgMSAtIChNYXRoLmV4cCgtbGFtYmRhICogdCkgLSBpbmVydGlhU3RhdHVzLmxhbWJkYV92MCkgLyBpbmVydGlhU3RhdHVzLm9uZV92ZV92MDtcblxuICAgICAgICAgICAgaWYgKGluZXJ0aWFTdGF0dXMubW9kaWZpZWRYZSA9PT0gaW5lcnRpYVN0YXR1cy54ZSAmJiBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUgPT09IGluZXJ0aWFTdGF0dXMueWUpIHtcbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gaW5lcnRpYVN0YXR1cy54ZSAqIHByb2dyZXNzO1xuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3kgPSBpbmVydGlhU3RhdHVzLnllICogcHJvZ3Jlc3M7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB2YXIgcXVhZFBvaW50ID0gZ2V0UXVhZHJhdGljQ3VydmVQb2ludChcbiAgICAgICAgICAgICAgICAgICAgMCwgMCxcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy54ZSwgaW5lcnRpYVN0YXR1cy55ZSxcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFhlLCBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUsXG4gICAgICAgICAgICAgICAgICAgIHByb2dyZXNzKTtcblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBxdWFkUG9pbnQueDtcbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gcXVhZFBvaW50Lnk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucG9pbnRlck1vdmUoaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50LCBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLmkgPSByZXFGcmFtZSh0aGlzLmJvdW5kSW5lcnRpYUZyYW1lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWGU7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllO1xuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMucG9pbnRlckVuZChpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc21vb3RoRW5kRnJhbWU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIGluZXJ0aWFTdGF0dXMgPSB0aGlzLmluZXJ0aWFTdGF0dXMsXG4gICAgICAgICAgICB0ID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLSBpbmVydGlhU3RhdHVzLnQwLFxuICAgICAgICAgICAgZHVyYXRpb24gPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYS5zbW9vdGhFbmREdXJhdGlvbjtcblxuICAgICAgICBpZiAodCA8IGR1cmF0aW9uKSB7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gZWFzZU91dFF1YWQodCwgMCwgaW5lcnRpYVN0YXR1cy54ZSwgZHVyYXRpb24pO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IGVhc2VPdXRRdWFkKHQsIDAsIGluZXJ0aWFTdGF0dXMueWUsIGR1cmF0aW9uKTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyTW92ZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG5cbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuaSA9IHJlcUZyYW1lKHRoaXMuYm91bmRTbW9vdGhFbmRGcmFtZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gaW5lcnRpYVN0YXR1cy54ZTtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3kgPSBpbmVydGlhU3RhdHVzLnllO1xuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc21vb3RoRW5kID0gZmFsc2U7XG5cbiAgICAgICAgICAgIHRoaXMucG9pbnRlckVuZChpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgYWRkUG9pbnRlcjogZnVuY3Rpb24gKHBvaW50ZXIpIHtcbiAgICAgICAgdmFyIGlkID0gZ2V0UG9pbnRlcklkKHBvaW50ZXIpLFxuICAgICAgICAgICAgaW5kZXggPSB0aGlzLm1vdXNlPyAwIDogaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGlkKTtcblxuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICBpbmRleCA9IHRoaXMucG9pbnRlcklkcy5sZW5ndGg7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJZHNbaW5kZXhdID0gaWQ7XG4gICAgICAgIHRoaXMucG9pbnRlcnNbaW5kZXhdID0gcG9pbnRlcjtcblxuICAgICAgICByZXR1cm4gaW5kZXg7XG4gICAgfSxcblxuICAgIHJlbW92ZVBvaW50ZXI6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIHZhciBpZCA9IGdldFBvaW50ZXJJZChwb2ludGVyKSxcbiAgICAgICAgICAgIGluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YodGhpcy5wb2ludGVySWRzLCBpZCk7XG5cbiAgICAgICAgaWYgKGluZGV4ID09PSAtMSkgeyByZXR1cm47IH1cblxuICAgICAgICBpZiAoIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgdGhpcy5wb2ludGVycy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5wb2ludGVySWRzIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB0aGlzLmRvd25UYXJnZXRzLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgIHRoaXMuZG93blRpbWVzICAuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgdGhpcy5ob2xkVGltZXJzIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgIH0sXG5cbiAgICByZWNvcmRQb2ludGVyOiBmdW5jdGlvbiAocG9pbnRlcikge1xuICAgICAgICAvLyBEbyBub3QgdXBkYXRlIHBvaW50ZXJzIHdoaWxlIGluZXJ0aWEgaXMgYWN0aXZlLlxuICAgICAgICAvLyBUaGUgaW5lcnRpYSBzdGFydCBldmVudCBzaG91bGQgYmUgdGhpcy5wb2ludGVyc1swXVxuICAgICAgICBpZiAodGhpcy5pbmVydGlhU3RhdHVzLmFjdGl2ZSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgaW5kZXggPSB0aGlzLm1vdXNlPyAwOiBpbmRleE9mKHRoaXMucG9pbnRlcklkcywgZ2V0UG9pbnRlcklkKHBvaW50ZXIpKTtcblxuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7IHJldHVybjsgfVxuXG4gICAgICAgIHRoaXMucG9pbnRlcnNbaW5kZXhdID0gcG9pbnRlcjtcbiAgICB9LFxuXG4gICAgY29sbGVjdEV2ZW50VGFyZ2V0czogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgZXZlbnRUeXBlKSB7XG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLm1vdXNlPyAwIDogaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGdldFBvaW50ZXJJZChwb2ludGVyKSk7XG5cbiAgICAgICAgLy8gZG8gbm90IGZpcmUgYSB0YXAgZXZlbnQgaWYgdGhlIHBvaW50ZXIgd2FzIG1vdmVkIGJlZm9yZSBiZWluZyBsaWZ0ZWRcbiAgICAgICAgaWYgKGV2ZW50VHlwZSA9PT0gJ3RhcCcgJiYgKHRoaXMucG9pbnRlcldhc01vdmVkXG4gICAgICAgICAgICAgICAgLy8gb3IgaWYgdGhlIHBvaW50ZXJ1cCB0YXJnZXQgaXMgZGlmZmVyZW50IHRvIHRoZSBwb2ludGVyZG93biB0YXJnZXRcbiAgICAgICAgICAgIHx8ICEodGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdICYmIHRoaXMuZG93blRhcmdldHNbcG9pbnRlckluZGV4XSA9PT0gZXZlbnRUYXJnZXQpKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHRhcmdldHMgPSBbXSxcbiAgICAgICAgICAgIGVsZW1lbnRzID0gW10sXG4gICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgZnVuY3Rpb24gY29sbGVjdFNlbGVjdG9ycyAoaW50ZXJhY3RhYmxlLCBzZWxlY3RvciwgY29udGV4dCkge1xuICAgICAgICAgICAgdmFyIGVscyA9IGllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgID8gY29udGV4dC5xdWVyeVNlbGVjdG9yQWxsKHNlbGVjdG9yKVxuICAgICAgICAgICAgICAgIDogdW5kZWZpbmVkO1xuXG4gICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlLl9pRXZlbnRzW2V2ZW50VHlwZV1cbiAgICAgICAgICAgICAgICAmJiBpc0VsZW1lbnQoZWxlbWVudClcbiAgICAgICAgICAgICAgICAmJiBpbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBlbGVtZW50KVxuICAgICAgICAgICAgICAgICYmICF0ZXN0SWdub3JlKGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgdGVzdEFsbG93KGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgbWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIHNlbGVjdG9yLCBlbHMpKSB7XG5cbiAgICAgICAgICAgICAgICB0YXJnZXRzLnB1c2goaW50ZXJhY3RhYmxlKTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgd2hpbGUgKGVsZW1lbnQpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdC5pc1NldChlbGVtZW50KSAmJiBpbnRlcmFjdChlbGVtZW50KS5faUV2ZW50c1tldmVudFR5cGVdKSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0cy5wdXNoKGludGVyYWN0KGVsZW1lbnQpKTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpbnRlcmFjdGFibGVzLmZvckVhY2hTZWxlY3Rvcihjb2xsZWN0U2VsZWN0b3JzKTtcblxuICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBjcmVhdGUgdGhlIHRhcCBldmVudCBldmVuIGlmIHRoZXJlIGFyZSBubyBsaXN0ZW5lcnMgc28gdGhhdFxuICAgICAgICAvLyBkb3VibGV0YXAgY2FuIHN0aWxsIGJlIGNyZWF0ZWQgYW5kIGZpcmVkXG4gICAgICAgIGlmICh0YXJnZXRzLmxlbmd0aCB8fCBldmVudFR5cGUgPT09ICd0YXAnKSB7XG4gICAgICAgICAgICB0aGlzLmZpcmVQb2ludGVycyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIHRhcmdldHMsIGVsZW1lbnRzLCBldmVudFR5cGUpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIGZpcmVQb2ludGVyczogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgdGFyZ2V0cywgZWxlbWVudHMsIGV2ZW50VHlwZSkge1xuICAgICAgICB2YXIgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YoZ2V0UG9pbnRlcklkKHBvaW50ZXIpKSxcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudCA9IHt9LFxuICAgICAgICAgICAgaSxcbiAgICAgICAgLy8gZm9yIHRhcCBldmVudHNcbiAgICAgICAgICAgIGludGVydmFsLCBjcmVhdGVOZXdEb3VibGVUYXA7XG5cbiAgICAgICAgLy8gaWYgaXQncyBhIGRvdWJsZXRhcCB0aGVuIHRoZSBldmVudCBwcm9wZXJ0aWVzIHdvdWxkIGhhdmUgYmVlblxuICAgICAgICAvLyBjb3BpZWQgZnJvbSB0aGUgdGFwIGV2ZW50IGFuZCBwcm92aWRlZCBhcyB0aGUgcG9pbnRlciBhcmd1bWVudFxuICAgICAgICBpZiAoZXZlbnRUeXBlID09PSAnZG91YmxldGFwJykge1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50ID0gcG9pbnRlcjtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGV4dGVuZChwb2ludGVyRXZlbnQsIGV2ZW50KTtcbiAgICAgICAgICAgIGlmIChldmVudCAhPT0gcG9pbnRlcikge1xuICAgICAgICAgICAgICAgIGV4dGVuZChwb2ludGVyRXZlbnQsIHBvaW50ZXIpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQucHJldmVudERlZmF1bHQgICAgICAgICAgID0gcHJldmVudE9yaWdpbmFsRGVmYXVsdDtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5zdG9wUHJvcGFnYXRpb24gICAgICAgICAgPSBJbnRlcmFjdEV2ZW50LnByb3RvdHlwZS5zdG9wUHJvcGFnYXRpb247XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uID0gSW50ZXJhY3RFdmVudC5wcm90b3R5cGUuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LmludGVyYWN0aW9uICAgICAgICAgICAgICA9IHRoaXM7XG5cbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC50aW1lU3RhbXAgICAgID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQub3JpZ2luYWxFdmVudCA9IGV2ZW50O1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnR5cGUgICAgICAgICAgPSBldmVudFR5cGU7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQucG9pbnRlcklkICAgICA9IGdldFBvaW50ZXJJZChwb2ludGVyKTtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5wb2ludGVyVHlwZSAgID0gdGhpcy5tb3VzZT8gJ21vdXNlJyA6ICFzdXBwb3J0c1BvaW50ZXJFdmVudD8gJ3RvdWNoJ1xuICAgICAgICAgICAgICAgIDogaXNTdHJpbmcocG9pbnRlci5wb2ludGVyVHlwZSlcbiAgICAgICAgICAgICAgICA/IHBvaW50ZXIucG9pbnRlclR5cGVcbiAgICAgICAgICAgICAgICA6IFssLCd0b3VjaCcsICdwZW4nLCAnbW91c2UnXVtwb2ludGVyLnBvaW50ZXJUeXBlXTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChldmVudFR5cGUgPT09ICd0YXAnKSB7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZHQgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XTtcblxuICAgICAgICAgICAgaW50ZXJ2YWwgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy50YXBUaW1lO1xuICAgICAgICAgICAgY3JlYXRlTmV3RG91YmxlVGFwID0gISEodGhpcy5wcmV2VGFwICYmIHRoaXMucHJldlRhcC50eXBlICE9PSAnZG91YmxldGFwJ1xuICAgICAgICAgICAgJiYgdGhpcy5wcmV2VGFwLnRhcmdldCA9PT0gcG9pbnRlckV2ZW50LnRhcmdldFxuICAgICAgICAgICAgJiYgaW50ZXJ2YWwgPCA1MDApO1xuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZG91YmxlID0gY3JlYXRlTmV3RG91YmxlVGFwO1xuXG4gICAgICAgICAgICB0aGlzLnRhcFRpbWUgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wO1xuICAgICAgICB9XG5cbiAgICAgICAgZm9yIChpID0gMDsgaSA8IHRhcmdldHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5jdXJyZW50VGFyZ2V0ID0gZWxlbWVudHNbaV07XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuaW50ZXJhY3RhYmxlID0gdGFyZ2V0c1tpXTtcbiAgICAgICAgICAgIHRhcmdldHNbaV0uZmlyZShwb2ludGVyRXZlbnQpO1xuXG4gICAgICAgICAgICBpZiAocG9pbnRlckV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZFxuICAgICAgICAgICAgICAgIHx8KHBvaW50ZXJFdmVudC5wcm9wYWdhdGlvblN0b3BwZWQgJiYgZWxlbWVudHNbaSArIDFdICE9PSBwb2ludGVyRXZlbnQuY3VycmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjcmVhdGVOZXdEb3VibGVUYXApIHtcbiAgICAgICAgICAgIHZhciBkb3VibGVUYXAgPSB7fTtcblxuICAgICAgICAgICAgZXh0ZW5kKGRvdWJsZVRhcCwgcG9pbnRlckV2ZW50KTtcblxuICAgICAgICAgICAgZG91YmxlVGFwLmR0ICAgPSBpbnRlcnZhbDtcbiAgICAgICAgICAgIGRvdWJsZVRhcC50eXBlID0gJ2RvdWJsZXRhcCc7XG5cbiAgICAgICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhkb3VibGVUYXAsIGV2ZW50LCBldmVudFRhcmdldCwgJ2RvdWJsZXRhcCcpO1xuXG4gICAgICAgICAgICB0aGlzLnByZXZUYXAgPSBkb3VibGVUYXA7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZXZlbnRUeXBlID09PSAndGFwJykge1xuICAgICAgICAgICAgdGhpcy5wcmV2VGFwID0gcG9pbnRlckV2ZW50O1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIHZhbGlkYXRlU2VsZWN0b3I6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cykge1xuICAgICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gbWF0Y2hlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgdmFyIG1hdGNoID0gbWF0Y2hlc1tpXSxcbiAgICAgICAgICAgICAgICBtYXRjaEVsZW1lbnQgPSBtYXRjaEVsZW1lbnRzW2ldLFxuICAgICAgICAgICAgICAgIGFjdGlvbiA9IHZhbGlkYXRlQWN0aW9uKG1hdGNoLmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgbWF0Y2hFbGVtZW50KSwgbWF0Y2gpO1xuXG4gICAgICAgICAgICBpZiAoYWN0aW9uICYmIHdpdGhpbkludGVyYWN0aW9uTGltaXQobWF0Y2gsIG1hdGNoRWxlbWVudCwgYWN0aW9uKSkge1xuICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gbWF0Y2g7XG4gICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gbWF0Y2hFbGVtZW50O1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIGFjdGlvbjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzZXRTbmFwcGluZzogZnVuY3Rpb24gKHBhZ2VDb29yZHMsIHN0YXR1cykge1xuICAgICAgICB2YXIgc25hcCA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgdGFyZ2V0cyA9IFtdLFxuICAgICAgICAgICAgdGFyZ2V0LFxuICAgICAgICAgICAgcGFnZSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMuc25hcFN0YXR1cztcblxuICAgICAgICBpZiAoc3RhdHVzLnVzZVN0YXR1c1hZKSB7XG4gICAgICAgICAgICBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZhciBvcmlnaW4gPSBnZXRPcmlnaW5YWSh0aGlzLnRhcmdldCwgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgcGFnZSA9IGV4dGVuZCh7fSwgcGFnZUNvb3Jkcyk7XG5cbiAgICAgICAgICAgIHBhZ2UueCAtPSBvcmlnaW4ueDtcbiAgICAgICAgICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5yZWFsWCA9IHBhZ2UueDtcbiAgICAgICAgc3RhdHVzLnJlYWxZID0gcGFnZS55O1xuXG4gICAgICAgIHBhZ2UueCA9IHBhZ2UueCAtIHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55ID0gcGFnZS55IC0gdGhpcy5pbmVydGlhU3RhdHVzLnJlc3VtZUR5O1xuXG4gICAgICAgIHZhciBsZW4gPSBzbmFwLnRhcmdldHM/IHNuYXAudGFyZ2V0cy5sZW5ndGggOiAwO1xuXG4gICAgICAgIGZvciAodmFyIHJlbEluZGV4ID0gMDsgcmVsSW5kZXggPCB0aGlzLnNuYXBPZmZzZXRzLmxlbmd0aDsgcmVsSW5kZXgrKykge1xuICAgICAgICAgICAgdmFyIHJlbGF0aXZlID0ge1xuICAgICAgICAgICAgICAgIHg6IHBhZ2UueCAtIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngsXG4gICAgICAgICAgICAgICAgeTogcGFnZS55IC0gdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24oc25hcC50YXJnZXRzW2ldKSkge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV0ocmVsYXRpdmUueCwgcmVsYXRpdmUueSwgdGhpcyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV07XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKCF0YXJnZXQpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaCh7XG4gICAgICAgICAgICAgICAgICAgIHg6IGlzTnVtYmVyKHRhcmdldC54KSA/ICh0YXJnZXQueCArIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngpIDogcmVsYXRpdmUueCxcbiAgICAgICAgICAgICAgICAgICAgeTogaXNOdW1iZXIodGFyZ2V0LnkpID8gKHRhcmdldC55ICsgdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueSkgOiByZWxhdGl2ZS55LFxuXG4gICAgICAgICAgICAgICAgICAgIHJhbmdlOiBpc051bWJlcih0YXJnZXQucmFuZ2UpPyB0YXJnZXQucmFuZ2U6IHNuYXAucmFuZ2VcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBjbG9zZXN0ID0ge1xuICAgICAgICAgICAgdGFyZ2V0OiBudWxsLFxuICAgICAgICAgICAgaW5SYW5nZTogZmFsc2UsXG4gICAgICAgICAgICBkaXN0YW5jZTogMCxcbiAgICAgICAgICAgIHJhbmdlOiAwLFxuICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICBkeTogMFxuICAgICAgICB9O1xuXG4gICAgICAgIGZvciAoaSA9IDAsIGxlbiA9IHRhcmdldHMubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIHRhcmdldCA9IHRhcmdldHNbaV07XG5cbiAgICAgICAgICAgIHZhciByYW5nZSA9IHRhcmdldC5yYW5nZSxcbiAgICAgICAgICAgICAgICBkeCA9IHRhcmdldC54IC0gcGFnZS54LFxuICAgICAgICAgICAgICAgIGR5ID0gdGFyZ2V0LnkgLSBwYWdlLnksXG4gICAgICAgICAgICAgICAgZGlzdGFuY2UgPSBoeXBvdChkeCwgZHkpLFxuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBkaXN0YW5jZSA8PSByYW5nZTtcblxuICAgICAgICAgICAgLy8gSW5maW5pdGUgdGFyZ2V0cyBjb3VudCBhcyBiZWluZyBvdXQgb2YgcmFuZ2VcbiAgICAgICAgICAgIC8vIGNvbXBhcmVkIHRvIG5vbiBpbmZpbml0ZSBvbmVzIHRoYXQgYXJlIGluIHJhbmdlXG4gICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5ICYmIGNsb3Nlc3QuaW5SYW5nZSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSkge1xuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBmYWxzZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFjbG9zZXN0LnRhcmdldCB8fCAoaW5SYW5nZVxuICAgICAgICAgICAgICAgICAgICAvLyBpcyB0aGUgY2xvc2VzdCB0YXJnZXQgaW4gcmFuZ2U/XG4gICAgICAgICAgICAgICAgICAgID8gKGNsb3Nlc3QuaW5SYW5nZSAmJiByYW5nZSAhPT0gSW5maW5pdHlcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhlIHBvaW50ZXIgaXMgcmVsYXRpdmVseSBkZWVwZXIgaW4gdGhpcyB0YXJnZXRcbiAgICAgICAgICAgICAgICAgICAgPyBkaXN0YW5jZSAvIHJhbmdlIDwgY2xvc2VzdC5kaXN0YW5jZSAvIGNsb3Nlc3QucmFuZ2VcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhpcyB0YXJnZXQgaGFzIEluZmluaXRlIHJhbmdlIGFuZCB0aGUgY2xvc2VzdCBkb2Vzbid0XG4gICAgICAgICAgICAgICAgICAgIDogKHJhbmdlID09PSBJbmZpbml0eSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSlcbiAgICAgICAgICAgICAgICAgICAgLy8gT1IgdGhpcyB0YXJnZXQgaXMgY2xvc2VyIHRoYXQgdGhlIHByZXZpb3VzIGNsb3Nlc3RcbiAgICAgICAgICAgICAgICB8fCBkaXN0YW5jZSA8IGNsb3Nlc3QuZGlzdGFuY2UpXG4gICAgICAgICAgICAgICAgICAgIC8vIFRoZSBvdGhlciBpcyBub3QgaW4gcmFuZ2UgYW5kIHRoZSBwb2ludGVyIGlzIGNsb3NlciB0byB0aGlzIHRhcmdldFxuICAgICAgICAgICAgICAgICAgICA6ICghY2xvc2VzdC5pblJhbmdlICYmIGRpc3RhbmNlIDwgY2xvc2VzdC5kaXN0YW5jZSkpKSB7XG5cbiAgICAgICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5KSB7XG4gICAgICAgICAgICAgICAgICAgIGluUmFuZ2UgPSB0cnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNsb3Nlc3QudGFyZ2V0ID0gdGFyZ2V0O1xuICAgICAgICAgICAgICAgIGNsb3Nlc3QuZGlzdGFuY2UgPSBkaXN0YW5jZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5pblJhbmdlID0gaW5SYW5nZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LmR4ID0gZHg7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5keSA9IGR5O1xuXG4gICAgICAgICAgICAgICAgc3RhdHVzLnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc25hcENoYW5nZWQ7XG5cbiAgICAgICAgaWYgKGNsb3Nlc3QudGFyZ2V0KSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IChzdGF0dXMuc25hcHBlZFggIT09IGNsb3Nlc3QudGFyZ2V0LnggfHwgc3RhdHVzLnNuYXBwZWRZICE9PSBjbG9zZXN0LnRhcmdldC55KTtcblxuICAgICAgICAgICAgc3RhdHVzLnNuYXBwZWRYID0gY2xvc2VzdC50YXJnZXQueDtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IGNsb3Nlc3QudGFyZ2V0Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IHRydWU7XG5cbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWCA9IE5hTjtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IE5hTjtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IGNsb3Nlc3QuZHg7XG4gICAgICAgIHN0YXR1cy5keSA9IGNsb3Nlc3QuZHk7XG5cbiAgICAgICAgc3RhdHVzLmNoYW5nZWQgPSAoc25hcENoYW5nZWQgfHwgKGNsb3Nlc3QuaW5SYW5nZSAmJiAhc3RhdHVzLmxvY2tlZCkpO1xuICAgICAgICBzdGF0dXMubG9ja2VkID0gY2xvc2VzdC5pblJhbmdlO1xuXG4gICAgICAgIHJldHVybiBzdGF0dXM7XG4gICAgfSxcblxuICAgIHNldFJlc3RyaWN0aW9uOiBmdW5jdGlvbiAocGFnZUNvb3Jkcywgc3RhdHVzKSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIHJlc3RyaWN0ID0gdGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QsXG4gICAgICAgICAgICByZXN0cmljdGlvbiA9IHJlc3RyaWN0ICYmIHJlc3RyaWN0LnJlc3RyaWN0aW9uLFxuICAgICAgICAgICAgcGFnZTtcblxuICAgICAgICBpZiAoIXJlc3RyaWN0aW9uKSB7XG4gICAgICAgICAgICByZXR1cm4gc3RhdHVzO1xuICAgICAgICB9XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMucmVzdHJpY3RTdGF0dXM7XG5cbiAgICAgICAgcGFnZSA9IHN0YXR1cy51c2VTdGF0dXNYWVxuICAgICAgICAgICAgPyBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfVxuICAgICAgICAgICAgOiBwYWdlID0gZXh0ZW5kKHt9LCBwYWdlQ29vcmRzKTtcblxuICAgICAgICBpZiAoc3RhdHVzLnNuYXAgJiYgc3RhdHVzLnNuYXAubG9ja2VkKSB7XG4gICAgICAgICAgICBwYWdlLnggKz0gc3RhdHVzLnNuYXAuZHggfHwgMDtcbiAgICAgICAgICAgIHBhZ2UueSArPSBzdGF0dXMuc25hcC5keSB8fCAwO1xuICAgICAgICB9XG5cbiAgICAgICAgcGFnZS54IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeTtcblxuICAgICAgICBzdGF0dXMuZHggPSAwO1xuICAgICAgICBzdGF0dXMuZHkgPSAwO1xuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlO1xuXG4gICAgICAgIHZhciByZWN0LCByZXN0cmljdGVkWCwgcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgaWYgKGlzU3RyaW5nKHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgaWYgKHJlc3RyaWN0aW9uID09PSAncGFyZW50Jykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gcGFyZW50RWxlbWVudCh0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAocmVzdHJpY3Rpb24gPT09ICdzZWxmJykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gdGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gY2xvc2VzdCh0aGlzLmVsZW1lbnQsIHJlc3RyaWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFyZXN0cmljdGlvbikgeyByZXR1cm4gc3RhdHVzOyB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNGdW5jdGlvbihyZXN0cmljdGlvbikpIHtcbiAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gcmVzdHJpY3Rpb24ocGFnZS54LCBwYWdlLnksIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNFbGVtZW50KHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgcmVzdHJpY3Rpb24gPSBnZXRFbGVtZW50UmVjdChyZXN0cmljdGlvbik7XG4gICAgICAgIH1cblxuICAgICAgICByZWN0ID0gcmVzdHJpY3Rpb247XG5cbiAgICAgICAgaWYgKCFyZXN0cmljdGlvbikge1xuICAgICAgICAgICAgcmVzdHJpY3RlZFggPSBwYWdlLng7XG4gICAgICAgICAgICByZXN0cmljdGVkWSA9IHBhZ2UueTtcbiAgICAgICAgfVxuICAgICAgICAvLyBvYmplY3QgaXMgYXNzdW1lZCB0byBoYXZlXG4gICAgICAgIC8vIHgsIHksIHdpZHRoLCBoZWlnaHQgb3JcbiAgICAgICAgLy8gbGVmdCwgdG9wLCByaWdodCwgYm90dG9tXG4gICAgICAgIGVsc2UgaWYgKCd4JyBpbiByZXN0cmljdGlvbiAmJiAneScgaW4gcmVzdHJpY3Rpb24pIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC54ICsgcmVjdC53aWR0aCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC54ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC55ICsgcmVjdC5oZWlnaHQgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC55ICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5yaWdodCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC5sZWZ0ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5ib3R0b20gLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC50b3AgICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IHJlc3RyaWN0ZWRYIC0gcGFnZS54O1xuICAgICAgICBzdGF0dXMuZHkgPSByZXN0cmljdGVkWSAtIHBhZ2UueTtcblxuICAgICAgICBzdGF0dXMuY2hhbmdlZCA9IHN0YXR1cy5yZXN0cmljdGVkWCAhPT0gcmVzdHJpY3RlZFggfHwgc3RhdHVzLnJlc3RyaWN0ZWRZICE9PSByZXN0cmljdGVkWTtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWQgPSAhIShzdGF0dXMuZHggfHwgc3RhdHVzLmR5KTtcblxuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZFggPSByZXN0cmljdGVkWDtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWRZID0gcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgcmV0dXJuIHN0YXR1cztcbiAgICB9LFxuXG4gICAgY2hlY2tBbmRQcmV2ZW50RGVmYXVsdDogZnVuY3Rpb24gKGV2ZW50LCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKCEoaW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlIHx8IHRoaXMudGFyZ2V0KSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICAgICAgcHJldmVudCA9IG9wdGlvbnMucHJldmVudERlZmF1bHQ7XG5cbiAgICAgICAgaWYgKHByZXZlbnQgPT09ICdhdXRvJyAmJiBlbGVtZW50ICYmICEvXihpbnB1dHxzZWxlY3R8dGV4dGFyZWEpJC9pLnRlc3QoZXZlbnQudGFyZ2V0Lm5vZGVOYW1lKSkge1xuICAgICAgICAgICAgLy8gZG8gbm90IHByZXZlbnREZWZhdWx0IG9uIHBvaW50ZXJkb3duIGlmIHRoZSBwcmVwYXJlZCBhY3Rpb24gaXMgYSBkcmFnXG4gICAgICAgICAgICAvLyBhbmQgZHJhZ2dpbmcgY2FuIG9ubHkgc3RhcnQgZnJvbSBhIGNlcnRhaW4gZGlyZWN0aW9uIC0gdGhpcyBhbGxvd3NcbiAgICAgICAgICAgIC8vIGEgdG91Y2ggdG8gcGFuIHRoZSB2aWV3cG9ydCBpZiBhIGRyYWcgaXNuJ3QgaW4gdGhlIHJpZ2h0IGRpcmVjdGlvblxuICAgICAgICAgICAgaWYgKC9kb3dufHN0YXJ0L2kudGVzdChldmVudC50eXBlKVxuICAgICAgICAgICAgICAgICYmIHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnICYmIG9wdGlvbnMuZHJhZy5heGlzICE9PSAneHknKSB7XG5cbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHdpdGggbWFudWFsU3RhcnQsIG9ubHkgcHJldmVudERlZmF1bHQgd2hpbGUgaW50ZXJhY3RpbmdcbiAgICAgICAgICAgIGlmIChvcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0gJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgJiYgIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChwcmV2ZW50ID09PSAnYWx3YXlzJykge1xuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBjYWxjSW5lcnRpYTogZnVuY3Rpb24gKHN0YXR1cykge1xuICAgICAgICB2YXIgaW5lcnRpYU9wdGlvbnMgPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYSxcbiAgICAgICAgICAgIGxhbWJkYSA9IGluZXJ0aWFPcHRpb25zLnJlc2lzdGFuY2UsXG4gICAgICAgICAgICBpbmVydGlhRHVyID0gLU1hdGgubG9nKGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkIC8gc3RhdHVzLnYwKSAvIGxhbWJkYTtcblxuICAgICAgICBzdGF0dXMueDAgPSB0aGlzLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgc3RhdHVzLnkwID0gdGhpcy5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIHN0YXR1cy50MCA9IHN0YXR1cy5zdGFydEV2ZW50LnRpbWVTdGFtcCAvIDEwMDA7XG4gICAgICAgIHN0YXR1cy5zeCA9IHN0YXR1cy5zeSA9IDA7XG5cbiAgICAgICAgc3RhdHVzLm1vZGlmaWVkWGUgPSBzdGF0dXMueGUgPSAoc3RhdHVzLnZ4MCAtIGluZXJ0aWFEdXIpIC8gbGFtYmRhO1xuICAgICAgICBzdGF0dXMubW9kaWZpZWRZZSA9IHN0YXR1cy55ZSA9IChzdGF0dXMudnkwIC0gaW5lcnRpYUR1cikgLyBsYW1iZGE7XG4gICAgICAgIHN0YXR1cy50ZSA9IGluZXJ0aWFEdXI7XG5cbiAgICAgICAgc3RhdHVzLmxhbWJkYV92MCA9IGxhbWJkYSAvIHN0YXR1cy52MDtcbiAgICAgICAgc3RhdHVzLm9uZV92ZV92MCA9IDEgLSBpbmVydGlhT3B0aW9ucy5lbmRTcGVlZCAvIHN0YXR1cy52MDtcbiAgICB9LFxuXG4gICAgYXV0b1Njcm9sbE1vdmU6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIGlmICghKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgJiYgY2hlY2tBdXRvU2Nyb2xsKHRoaXMudGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUpIHtcbiAgICAgICAgICAgIGF1dG9TY3JvbGwueCA9IGF1dG9TY3JvbGwueSA9IDA7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdG9wLFxuICAgICAgICAgICAgcmlnaHQsXG4gICAgICAgICAgICBib3R0b20sXG4gICAgICAgICAgICBsZWZ0LFxuICAgICAgICAgICAgb3B0aW9ucyA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5hdXRvU2Nyb2xsLFxuICAgICAgICAgICAgY29udGFpbmVyID0gb3B0aW9ucy5jb250YWluZXIgfHwgZ2V0V2luZG93KHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKGlzV2luZG93KGNvbnRhaW5lcikpIHtcbiAgICAgICAgICAgIGxlZnQgICA9IHBvaW50ZXIuY2xpZW50WCA8IGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICByaWdodCAgPSBwb2ludGVyLmNsaWVudFggPiBjb250YWluZXIuaW5uZXJXaWR0aCAgLSBhdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIGJvdHRvbSA9IHBvaW50ZXIuY2xpZW50WSA+IGNvbnRhaW5lci5pbm5lckhlaWdodCAtIGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdmFyIHJlY3QgPSBnZXRFbGVtZW50UmVjdChjb250YWluZXIpO1xuXG4gICAgICAgICAgICBsZWZ0ICAgPSBwb2ludGVyLmNsaWVudFggPCByZWN0LmxlZnQgICArIGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgcmVjdC50b3AgICAgKyBhdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIHJpZ2h0ICA9IHBvaW50ZXIuY2xpZW50WCA+IHJlY3QucmlnaHQgIC0gYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICBib3R0b20gPSBwb2ludGVyLmNsaWVudFkgPiByZWN0LmJvdHRvbSAtIGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG5cbiAgICAgICAgYXV0b1Njcm9sbC54ID0gKHJpZ2h0ID8gMTogbGVmdD8gLTE6IDApO1xuICAgICAgICBhdXRvU2Nyb2xsLnkgPSAoYm90dG9tPyAxOiAgdG9wPyAtMTogMCk7XG5cbiAgICAgICAgaWYgKCFhdXRvU2Nyb2xsLmlzU2Nyb2xsaW5nKSB7XG4gICAgICAgICAgICAvLyBzZXQgdGhlIGF1dG9TY3JvbGwgcHJvcGVydGllcyB0byB0aG9zZSBvZiB0aGUgdGFyZ2V0XG4gICAgICAgICAgICBhdXRvU2Nyb2xsLm1hcmdpbiA9IG9wdGlvbnMubWFyZ2luO1xuICAgICAgICAgICAgYXV0b1Njcm9sbC5zcGVlZCAgPSBvcHRpb25zLnNwZWVkO1xuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsLnN0YXJ0KHRoaXMpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIF91cGRhdGVFdmVudFRhcmdldHM6IGZ1bmN0aW9uICh0YXJnZXQsIGN1cnJlbnRUYXJnZXQpIHtcbiAgICAgICAgdGhpcy5fZXZlbnRUYXJnZXQgICAgPSB0YXJnZXQ7XG4gICAgICAgIHRoaXMuX2N1ckV2ZW50VGFyZ2V0ID0gY3VycmVudFRhcmdldDtcbiAgICB9XG5cbn07XG5cbmZ1bmN0aW9uIGdldEludGVyYWN0aW9uRnJvbVBvaW50ZXIgKHBvaW50ZXIsIGV2ZW50VHlwZSwgZXZlbnRUYXJnZXQpIHtcbiAgICB2YXIgaSA9IDAsIGxlbiA9IGludGVyYWN0aW9ucy5sZW5ndGgsXG4gICAgICAgIG1vdXNlRXZlbnQgPSAoL21vdXNlL2kudGVzdChwb2ludGVyLnBvaW50ZXJUeXBlIHx8IGV2ZW50VHlwZSlcbiAgICAgICAgICAgIC8vIE1TUG9pbnRlckV2ZW50Lk1TUE9JTlRFUl9UWVBFX01PVVNFXG4gICAgICAgIHx8IHBvaW50ZXIucG9pbnRlclR5cGUgPT09IDQpLFxuICAgICAgICBpbnRlcmFjdGlvbjtcblxuICAgIHZhciBpZCA9IGdldFBvaW50ZXJJZChwb2ludGVyKTtcblxuICAgIC8vIHRyeSB0byByZXN1bWUgaW5lcnRpYSB3aXRoIGEgbmV3IHBvaW50ZXJcbiAgICBpZiAoL2Rvd258c3RhcnQvaS50ZXN0KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uc1tpXTtcblxuICAgICAgICAgICAgdmFyIGVsZW1lbnQgPSBldmVudFRhcmdldDtcblxuICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuYWN0aXZlICYmIGludGVyYWN0aW9uLnRhcmdldC5vcHRpb25zW2ludGVyYWN0aW9uLnByZXBhcmVkLm5hbWVdLmluZXJ0aWEuYWxsb3dSZXN1bWVcbiAgICAgICAgICAgICAgICAmJiAoaW50ZXJhY3Rpb24ubW91c2UgPT09IG1vdXNlRXZlbnQpKSB7XG4gICAgICAgICAgICAgICAgd2hpbGUgKGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gaWYgdGhlIGVsZW1lbnQgaXMgdGhlIGludGVyYWN0aW9uIGVsZW1lbnRcbiAgICAgICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQgPT09IGludGVyYWN0aW9uLmVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgaW50ZXJhY3Rpb24ncyBwb2ludGVyXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3Rpb24ucG9pbnRlcnNbMF0pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5yZW1vdmVQb2ludGVyKGludGVyYWN0aW9uLnBvaW50ZXJzWzBdKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmFkZFBvaW50ZXIocG9pbnRlcik7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbjtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBlbGVtZW50ID0gcGFyZW50RWxlbWVudChlbGVtZW50KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBpZiBpdCdzIGEgbW91c2UgaW50ZXJhY3Rpb25cbiAgICBpZiAobW91c2VFdmVudCB8fCAhKHN1cHBvcnRzVG91Y2ggfHwgc3VwcG9ydHNQb2ludGVyRXZlbnQpKSB7XG5cbiAgICAgICAgLy8gZmluZCBhIG1vdXNlIGludGVyYWN0aW9uIHRoYXQncyBub3QgaW4gaW5lcnRpYSBwaGFzZVxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbnNbaV0ubW91c2UgJiYgIWludGVyYWN0aW9uc1tpXS5pbmVydGlhU3RhdHVzLmFjdGl2ZSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbnNbaV07XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBmaW5kIGFueSBpbnRlcmFjdGlvbiBzcGVjaWZpY2FsbHkgZm9yIG1vdXNlLlxuICAgICAgICAvLyBpZiB0aGUgZXZlbnRUeXBlIGlzIGEgbW91c2Vkb3duLCBhbmQgaW5lcnRpYSBpcyBhY3RpdmVcbiAgICAgICAgLy8gaWdub3JlIHRoZSBpbnRlcmFjdGlvblxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbnNbaV0ubW91c2UgJiYgISgvZG93bi8udGVzdChldmVudFR5cGUpICYmIGludGVyYWN0aW9uc1tpXS5pbmVydGlhU3RhdHVzLmFjdGl2ZSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Rpb247XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBjcmVhdGUgYSBuZXcgaW50ZXJhY3Rpb24gZm9yIG1vdXNlXG4gICAgICAgIGludGVyYWN0aW9uID0gbmV3IEludGVyYWN0aW9uKCk7XG4gICAgICAgIGludGVyYWN0aW9uLm1vdXNlID0gdHJ1ZTtcblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Rpb247XG4gICAgfVxuXG4gICAgLy8gZ2V0IGludGVyYWN0aW9uIHRoYXQgaGFzIHRoaXMgcG9pbnRlclxuICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBpZiAoY29udGFpbnMoaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJJZHMsIGlkKSkge1xuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uc1tpXTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIGF0IHRoaXMgc3RhZ2UsIGEgcG9pbnRlclVwIHNob3VsZCBub3QgcmV0dXJuIGFuIGludGVyYWN0aW9uXG4gICAgaWYgKC91cHxlbmR8b3V0L2kudGVzdChldmVudFR5cGUpKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIC8vIGdldCBmaXJzdCBpZGxlIGludGVyYWN0aW9uXG4gICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIGludGVyYWN0aW9uID0gaW50ZXJhY3Rpb25zW2ldO1xuXG4gICAgICAgIGlmICgoIWludGVyYWN0aW9uLnByZXBhcmVkLm5hbWUgfHwgKGludGVyYWN0aW9uLnRhcmdldC5vcHRpb25zLmdlc3R1cmUuZW5hYmxlZCkpXG4gICAgICAgICAgICAmJiAhaW50ZXJhY3Rpb24uaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgJiYgISghbW91c2VFdmVudCAmJiBpbnRlcmFjdGlvbi5tb3VzZSkpIHtcblxuICAgICAgICAgICAgaW50ZXJhY3Rpb24uYWRkUG9pbnRlcihwb2ludGVyKTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIG5ldyBJbnRlcmFjdGlvbigpO1xufVxuXG5mdW5jdGlvbiBkb09uSW50ZXJhY3Rpb25zIChtZXRob2QpIHtcbiAgICByZXR1cm4gKGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgaW50ZXJhY3Rpb24sXG4gICAgICAgICAgICBldmVudFRhcmdldCA9IGdldEFjdHVhbEVsZW1lbnQoZXZlbnQucGF0aFxuICAgICAgICAgICAgICAgID8gZXZlbnQucGF0aFswXVxuICAgICAgICAgICAgICAgIDogZXZlbnQudGFyZ2V0KSxcbiAgICAgICAgICAgIGN1ckV2ZW50VGFyZ2V0ID0gZ2V0QWN0dWFsRWxlbWVudChldmVudC5jdXJyZW50VGFyZ2V0KSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgaWYgKHN1cHBvcnRzVG91Y2ggJiYgL3RvdWNoLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICBwcmV2VG91Y2hUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG5cbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBldmVudC5jaGFuZ2VkVG91Y2hlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHZhciBwb2ludGVyID0gZXZlbnQuY2hhbmdlZFRvdWNoZXNbaV07XG5cbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiA9IGdldEludGVyYWN0aW9uRnJvbVBvaW50ZXIocG9pbnRlciwgZXZlbnQudHlwZSwgZXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKCFpbnRlcmFjdGlvbikgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uX3VwZGF0ZUV2ZW50VGFyZ2V0cyhldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb25bbWV0aG9kXShwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGlmICghc3VwcG9ydHNQb2ludGVyRXZlbnQgJiYgL21vdXNlLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICAgICAgLy8gaWdub3JlIG1vdXNlIGV2ZW50cyB3aGlsZSB0b3VjaCBpbnRlcmFjdGlvbnMgYXJlIGFjdGl2ZVxuICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBpbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCFpbnRlcmFjdGlvbnNbaV0ubW91c2UgJiYgaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJJc0Rvd24pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIC8vIHRyeSB0byBpZ25vcmUgbW91c2UgZXZlbnRzIHRoYXQgYXJlIHNpbXVsYXRlZCBieSB0aGUgYnJvd3NlclxuICAgICAgICAgICAgICAgIC8vIGFmdGVyIGEgdG91Y2ggZXZlbnRcbiAgICAgICAgICAgICAgICBpZiAobmV3IERhdGUoKS5nZXRUaW1lKCkgLSBwcmV2VG91Y2hUaW1lIDwgNTAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGludGVyYWN0aW9uID0gZ2V0SW50ZXJhY3Rpb25Gcm9tUG9pbnRlcihldmVudCwgZXZlbnQudHlwZSwgZXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICBpZiAoIWludGVyYWN0aW9uKSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICBpbnRlcmFjdGlvbi5fdXBkYXRlRXZlbnRUYXJnZXRzKGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgICAgIGludGVyYWN0aW9uW21ldGhvZF0oZXZlbnQsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuICAgICAgICB9XG4gICAgfSk7XG59XG5cbmZ1bmN0aW9uIEludGVyYWN0RXZlbnQgKGludGVyYWN0aW9uLCBldmVudCwgYWN0aW9uLCBwaGFzZSwgZWxlbWVudCwgcmVsYXRlZCkge1xuICAgIHZhciBjbGllbnQsXG4gICAgICAgIHBhZ2UsXG4gICAgICAgIHRhcmdldCAgICAgID0gaW50ZXJhY3Rpb24udGFyZ2V0LFxuICAgICAgICBzbmFwU3RhdHVzICA9IGludGVyYWN0aW9uLnNuYXBTdGF0dXMsXG4gICAgICAgIHJlc3RyaWN0U3RhdHVzICA9IGludGVyYWN0aW9uLnJlc3RyaWN0U3RhdHVzLFxuICAgICAgICBwb2ludGVycyAgICA9IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICBkZWx0YVNvdXJjZSA9ICh0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMgfHwgZGVmYXVsdE9wdGlvbnMpLmRlbHRhU291cmNlLFxuICAgICAgICBzb3VyY2VYICAgICA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZICAgICA9IGRlbHRhU291cmNlICsgJ1knLFxuICAgICAgICBvcHRpb25zICAgICA9IHRhcmdldD8gdGFyZ2V0Lm9wdGlvbnM6IGRlZmF1bHRPcHRpb25zLFxuICAgICAgICBvcmlnaW4gICAgICA9IGdldE9yaWdpblhZKHRhcmdldCwgZWxlbWVudCksXG4gICAgICAgIHN0YXJ0aW5nICAgID0gcGhhc2UgPT09ICdzdGFydCcsXG4gICAgICAgIGVuZGluZyAgICAgID0gcGhhc2UgPT09ICdlbmQnLFxuICAgICAgICBjb29yZHMgICAgICA9IHN0YXJ0aW5nPyBpbnRlcmFjdGlvbi5zdGFydENvb3JkcyA6IGludGVyYWN0aW9uLmN1ckNvb3JkcztcblxuICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IGludGVyYWN0aW9uLmVsZW1lbnQ7XG5cbiAgICBwYWdlICAgPSBleHRlbmQoe30sIGNvb3Jkcy5wYWdlKTtcbiAgICBjbGllbnQgPSBleHRlbmQoe30sIGNvb3Jkcy5jbGllbnQpO1xuXG4gICAgcGFnZS54IC09IG9yaWdpbi54O1xuICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcblxuICAgIGNsaWVudC54IC09IG9yaWdpbi54O1xuICAgIGNsaWVudC55IC09IG9yaWdpbi55O1xuXG4gICAgdmFyIHJlbGF0aXZlUG9pbnRzID0gb3B0aW9uc1thY3Rpb25dLnNuYXAgJiYgb3B0aW9uc1thY3Rpb25dLnNuYXAucmVsYXRpdmVQb2ludHMgO1xuXG4gICAgaWYgKGNoZWNrU25hcCh0YXJnZXQsIGFjdGlvbikgJiYgIShzdGFydGluZyAmJiByZWxhdGl2ZVBvaW50cyAmJiByZWxhdGl2ZVBvaW50cy5sZW5ndGgpKSB7XG4gICAgICAgIHRoaXMuc25hcCA9IHtcbiAgICAgICAgICAgIHJhbmdlICA6IHNuYXBTdGF0dXMucmFuZ2UsXG4gICAgICAgICAgICBsb2NrZWQgOiBzbmFwU3RhdHVzLmxvY2tlZCxcbiAgICAgICAgICAgIHggICAgICA6IHNuYXBTdGF0dXMuc25hcHBlZFgsXG4gICAgICAgICAgICB5ICAgICAgOiBzbmFwU3RhdHVzLnNuYXBwZWRZLFxuICAgICAgICAgICAgcmVhbFggIDogc25hcFN0YXR1cy5yZWFsWCxcbiAgICAgICAgICAgIHJlYWxZICA6IHNuYXBTdGF0dXMucmVhbFksXG4gICAgICAgICAgICBkeCAgICAgOiBzbmFwU3RhdHVzLmR4LFxuICAgICAgICAgICAgZHkgICAgIDogc25hcFN0YXR1cy5keVxuICAgICAgICB9O1xuXG4gICAgICAgIGlmIChzbmFwU3RhdHVzLmxvY2tlZCkge1xuICAgICAgICAgICAgcGFnZS54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBwYWdlLnkgKz0gc25hcFN0YXR1cy5keTtcbiAgICAgICAgICAgIGNsaWVudC54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBjbGllbnQueSArPSBzbmFwU3RhdHVzLmR5O1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGNoZWNrUmVzdHJpY3QodGFyZ2V0LCBhY3Rpb24pICYmICEoc3RhcnRpbmcgJiYgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0LmVsZW1lbnRSZWN0KSAmJiByZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkKSB7XG4gICAgICAgIHBhZ2UueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgcGFnZS55ICs9IHJlc3RyaWN0U3RhdHVzLmR5O1xuICAgICAgICBjbGllbnQueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgY2xpZW50LnkgKz0gcmVzdHJpY3RTdGF0dXMuZHk7XG5cbiAgICAgICAgdGhpcy5yZXN0cmljdCA9IHtcbiAgICAgICAgICAgIGR4OiByZXN0cmljdFN0YXR1cy5keCxcbiAgICAgICAgICAgIGR5OiByZXN0cmljdFN0YXR1cy5keVxuICAgICAgICB9O1xuICAgIH1cblxuICAgIHRoaXMucGFnZVggICAgID0gcGFnZS54O1xuICAgIHRoaXMucGFnZVkgICAgID0gcGFnZS55O1xuICAgIHRoaXMuY2xpZW50WCAgID0gY2xpZW50Lng7XG4gICAgdGhpcy5jbGllbnRZICAgPSBjbGllbnQueTtcblxuICAgIHRoaXMueDAgICAgICAgID0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54IC0gb3JpZ2luLng7XG4gICAgdGhpcy55MCAgICAgICAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueTtcbiAgICB0aGlzLmNsaWVudFgwICA9IGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54IC0gb3JpZ2luLng7XG4gICAgdGhpcy5jbGllbnRZMCAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5jbGllbnQueSAtIG9yaWdpbi55O1xuICAgIHRoaXMuY3RybEtleSAgID0gZXZlbnQuY3RybEtleTtcbiAgICB0aGlzLmFsdEtleSAgICA9IGV2ZW50LmFsdEtleTtcbiAgICB0aGlzLnNoaWZ0S2V5ICA9IGV2ZW50LnNoaWZ0S2V5O1xuICAgIHRoaXMubWV0YUtleSAgID0gZXZlbnQubWV0YUtleTtcbiAgICB0aGlzLmJ1dHRvbiAgICA9IGV2ZW50LmJ1dHRvbjtcbiAgICB0aGlzLnRhcmdldCAgICA9IGVsZW1lbnQ7XG4gICAgdGhpcy50MCAgICAgICAgPSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG4gICAgdGhpcy50eXBlICAgICAgPSBhY3Rpb24gKyAocGhhc2UgfHwgJycpO1xuXG4gICAgdGhpcy5pbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uO1xuICAgIHRoaXMuaW50ZXJhY3RhYmxlID0gdGFyZ2V0O1xuXG4gICAgdmFyIGluZXJ0aWFTdGF0dXMgPSBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzO1xuXG4gICAgaWYgKGluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7XG4gICAgICAgIHRoaXMuZGV0YWlsID0gJ2luZXJ0aWEnO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGVkKSB7XG4gICAgICAgIHRoaXMucmVsYXRlZFRhcmdldCA9IHJlbGF0ZWQ7XG4gICAgfVxuXG4gICAgLy8gZW5kIGV2ZW50IGR4LCBkeSBpcyBkaWZmZXJlbmNlIGJldHdlZW4gc3RhcnQgYW5kIGVuZCBwb2ludHNcbiAgICBpZiAoZW5kaW5nKSB7XG4gICAgICAgIGlmIChkZWx0YVNvdXJjZSA9PT0gJ2NsaWVudCcpIHtcbiAgICAgICAgICAgIHRoaXMuZHggPSBjbGllbnQueCAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54O1xuICAgICAgICAgICAgdGhpcy5keSA9IGNsaWVudC55IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMuY2xpZW50Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gcGFnZS54IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54O1xuICAgICAgICAgICAgdGhpcy5keSA9IHBhZ2UueSAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLnBhZ2UueTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLmR4ID0gMDtcbiAgICAgICAgdGhpcy5keSA9IDA7XG4gICAgfVxuICAgIC8vIGNvcHkgcHJvcGVydGllcyBmcm9tIHByZXZpb3VzbW92ZSBpZiBzdGFydGluZyBpbmVydGlhXG4gICAgZWxzZSBpZiAocGhhc2UgPT09ICdpbmVydGlhc3RhcnQnKSB7XG4gICAgICAgIHRoaXMuZHggPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHg7XG4gICAgICAgIHRoaXMuZHkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBpZiAoZGVsdGFTb3VyY2UgPT09ICdjbGllbnQnKSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gY2xpZW50LnggLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuY2xpZW50WDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBjbGllbnQueSAtIGludGVyYWN0aW9uLnByZXZFdmVudC5jbGllbnRZO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5keCA9IHBhZ2UueCAtIGludGVyYWN0aW9uLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBwYWdlLnkgLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKGludGVyYWN0aW9uLnByZXZFdmVudCAmJiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGV0YWlsID09PSAnaW5lcnRpYSdcbiAgICAgICAgJiYgIWluZXJ0aWFTdGF0dXMuYWN0aXZlXG4gICAgICAgICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhLnplcm9SZXN1bWVEZWx0YSkge1xuXG4gICAgICAgIGluZXJ0aWFTdGF0dXMucmVzdW1lRHggKz0gdGhpcy5keDtcbiAgICAgICAgaW5lcnRpYVN0YXR1cy5yZXN1bWVEeSArPSB0aGlzLmR5O1xuXG4gICAgICAgIHRoaXMuZHggPSB0aGlzLmR5ID0gMDtcbiAgICB9XG5cbiAgICBpZiAoYWN0aW9uID09PSAncmVzaXplJyAmJiBpbnRlcmFjdGlvbi5yZXNpemVBeGVzKSB7XG4gICAgICAgIGlmIChvcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneScpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR4ID0gdGhpcy5keTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuZHkgPSB0aGlzLmR4O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5heGVzID0gJ3h5JztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuYXhlcyA9IGludGVyYWN0aW9uLnJlc2l6ZUF4ZXM7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneCcpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR5ID0gMDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGludGVyYWN0aW9uLnJlc2l6ZUF4ZXMgPT09ICd5Jykge1xuICAgICAgICAgICAgICAgIHRoaXMuZHggPSAwO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2dlc3R1cmUnKSB7XG4gICAgICAgIHRoaXMudG91Y2hlcyA9IFtwb2ludGVyc1swXSwgcG9pbnRlcnNbMV1dO1xuXG4gICAgICAgIGlmIChzdGFydGluZykge1xuICAgICAgICAgICAgdGhpcy5kaXN0YW5jZSA9IHRvdWNoRGlzdGFuY2UocG9pbnRlcnMsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuYm94ICAgICAgPSB0b3VjaEJCb3gocG9pbnRlcnMpO1xuICAgICAgICAgICAgdGhpcy5zY2FsZSAgICA9IDE7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gMDtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB0b3VjaEFuZ2xlKHBvaW50ZXJzLCB1bmRlZmluZWQsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuZGEgICAgICAgPSAwO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGVuZGluZyB8fCBldmVudCBpbnN0YW5jZW9mIEludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgICAgIHRoaXMuZGlzdGFuY2UgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGlzdGFuY2U7XG4gICAgICAgICAgICB0aGlzLmJveCAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmJveDtcbiAgICAgICAgICAgIHRoaXMuc2NhbGUgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc2NhbGU7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gdGhpcy5zY2FsZSAtIDE7XG4gICAgICAgICAgICB0aGlzLmFuZ2xlICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmFuZ2xlO1xuICAgICAgICAgICAgdGhpcy5kYSAgICAgICA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnN0YXJ0QW5nbGU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmRpc3RhbmNlID0gdG91Y2hEaXN0YW5jZShwb2ludGVycywgZGVsdGFTb3VyY2UpO1xuICAgICAgICAgICAgdGhpcy5ib3ggICAgICA9IHRvdWNoQkJveChwb2ludGVycyk7XG4gICAgICAgICAgICB0aGlzLnNjYWxlICAgID0gdGhpcy5kaXN0YW5jZSAvIGludGVyYWN0aW9uLmdlc3R1cmUuc3RhcnREaXN0YW5jZTtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB0b3VjaEFuZ2xlKHBvaW50ZXJzLCBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZSwgZGVsdGFTb3VyY2UpO1xuXG4gICAgICAgICAgICB0aGlzLmRzID0gdGhpcy5zY2FsZSAtIGludGVyYWN0aW9uLmdlc3R1cmUucHJldlNjYWxlO1xuICAgICAgICAgICAgdGhpcy5kYSA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLmRvd25UaW1lc1swXTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSAwO1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IDA7XG4gICAgICAgIHRoaXMuc3BlZWQgICAgID0gMDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVggPSAwO1xuICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IDA7XG4gICAgfVxuICAgIGVsc2UgaWYgKHBoYXNlID09PSAnaW5lcnRpYXN0YXJ0Jykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHQgICAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmR0O1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IGludGVyYWN0aW9uLnByZXZFdmVudC5kdXJhdGlvbjtcbiAgICAgICAgdGhpcy5zcGVlZCAgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc3BlZWQ7XG4gICAgICAgIHRoaXMudmVsb2NpdHlYID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdGhpcy50aW1lU3RhbXAgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHVyYXRpb24gID0gdGhpcy50aW1lU3RhbXAgLSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG5cbiAgICAgICAgaWYgKGV2ZW50IGluc3RhbmNlb2YgSW50ZXJhY3RFdmVudCkge1xuICAgICAgICAgICAgdmFyIGR4ID0gdGhpc1tzb3VyY2VYXSAtIGludGVyYWN0aW9uLnByZXZFdmVudFtzb3VyY2VYXSxcbiAgICAgICAgICAgICAgICBkeSA9IHRoaXNbc291cmNlWV0gLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnRbc291cmNlWV0sXG4gICAgICAgICAgICAgICAgZHQgPSB0aGlzLmR0IC8gMTAwMDtcblxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IGh5cG90KGR4LCBkeSkgLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlYID0gZHggLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlZID0gZHkgLyBkdDtcbiAgICAgICAgfVxuICAgICAgICAvLyBpZiBub3JtYWwgbW92ZSBvciBlbmQgZXZlbnQsIHVzZSBwcmV2aW91cyB1c2VyIGV2ZW50IGNvb3Jkc1xuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIHNwZWVkIGFuZCB2ZWxvY2l0eSBpbiBwaXhlbHMgcGVyIHNlY29uZFxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0uc3BlZWQ7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udng7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udnk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoKGVuZGluZyB8fCBwaGFzZSA9PT0gJ2luZXJ0aWFzdGFydCcpXG4gICAgICAgICYmIGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCA+IDYwMCAmJiB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXAgPCAxNTApIHtcblxuICAgICAgICB2YXIgYW5nbGUgPSAxODAgKiBNYXRoLmF0YW4yKGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVksIGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVgpIC8gTWF0aC5QSSxcbiAgICAgICAgICAgIG92ZXJsYXAgPSAyMi41O1xuXG4gICAgICAgIGlmIChhbmdsZSA8IDApIHtcbiAgICAgICAgICAgIGFuZ2xlICs9IDM2MDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBsZWZ0ID0gMTM1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDIyNSArIG92ZXJsYXAsXG4gICAgICAgICAgICB1cCAgID0gMjI1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDMxNSArIG92ZXJsYXAsXG5cbiAgICAgICAgICAgIHJpZ2h0ID0gIWxlZnQgJiYgKDMxNSAtIG92ZXJsYXAgPD0gYW5nbGUgfHwgYW5nbGUgPCAgNDUgKyBvdmVybGFwKSxcbiAgICAgICAgICAgIGRvd24gID0gIXVwICAgJiYgICA0NSAtIG92ZXJsYXAgPD0gYW5nbGUgJiYgYW5nbGUgPCAxMzUgKyBvdmVybGFwO1xuXG4gICAgICAgIHRoaXMuc3dpcGUgPSB7XG4gICAgICAgICAgICB1cCAgIDogdXAsXG4gICAgICAgICAgICBkb3duIDogZG93bixcbiAgICAgICAgICAgIGxlZnQgOiBsZWZ0LFxuICAgICAgICAgICAgcmlnaHQ6IHJpZ2h0LFxuICAgICAgICAgICAgYW5nbGU6IGFuZ2xlLFxuICAgICAgICAgICAgc3BlZWQ6IGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCxcbiAgICAgICAgICAgIHZlbG9jaXR5OiB7XG4gICAgICAgICAgICAgICAgeDogaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WCxcbiAgICAgICAgICAgICAgICB5OiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZXG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgfVxufVxuXG5JbnRlcmFjdEV2ZW50LnByb3RvdHlwZSA9IHtcbiAgICBwcmV2ZW50RGVmYXVsdDogYmxhbmssXG4gICAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkID0gdGhpcy5wcm9wYWdhdGlvblN0b3BwZWQgPSB0cnVlO1xuICAgIH0sXG4gICAgc3RvcFByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMucHJvcGFnYXRpb25TdG9wcGVkID0gdHJ1ZTtcbiAgICB9XG59O1xuXG5mdW5jdGlvbiBwcmV2ZW50T3JpZ2luYWxEZWZhdWx0ICgpIHtcbiAgICB0aGlzLm9yaWdpbmFsRXZlbnQucHJldmVudERlZmF1bHQoKTtcbn1cblxuZnVuY3Rpb24gZ2V0QWN0aW9uQ3Vyc29yIChhY3Rpb24pIHtcbiAgICB2YXIgY3Vyc29yID0gJyc7XG5cbiAgICBpZiAoYWN0aW9uLm5hbWUgPT09ICdkcmFnJykge1xuICAgICAgICBjdXJzb3IgPSAgYWN0aW9uQ3Vyc29ycy5kcmFnO1xuICAgIH1cbiAgICBpZiAoYWN0aW9uLm5hbWUgPT09ICdyZXNpemUnKSB7XG4gICAgICAgIGlmIChhY3Rpb24uYXhpcykge1xuICAgICAgICAgICAgY3Vyc29yID0gIGFjdGlvbkN1cnNvcnNbYWN0aW9uLm5hbWUgKyBhY3Rpb24uYXhpc107XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoYWN0aW9uLmVkZ2VzKSB7XG4gICAgICAgICAgICB2YXIgY3Vyc29yS2V5ID0gJ3Jlc2l6ZScsXG4gICAgICAgICAgICAgICAgZWRnZU5hbWVzID0gWyd0b3AnLCAnYm90dG9tJywgJ2xlZnQnLCAncmlnaHQnXTtcblxuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCA0OyBpKyspIHtcbiAgICAgICAgICAgICAgICBpZiAoYWN0aW9uLmVkZ2VzW2VkZ2VOYW1lc1tpXV0pIHtcbiAgICAgICAgICAgICAgICAgICAgY3Vyc29yS2V5ICs9IGVkZ2VOYW1lc1tpXTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGN1cnNvciA9IGFjdGlvbkN1cnNvcnNbY3Vyc29yS2V5XTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBjdXJzb3I7XG59XG5cbmZ1bmN0aW9uIGNoZWNrUmVzaXplRWRnZSAobmFtZSwgdmFsdWUsIHBhZ2UsIGVsZW1lbnQsIGludGVyYWN0YWJsZUVsZW1lbnQsIHJlY3QsIG1hcmdpbikge1xuICAgIC8vIGZhbHNlLCAnJywgdW5kZWZpbmVkLCBudWxsXG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgIC8vIHRydWUgdmFsdWUsIHVzZSBwb2ludGVyIGNvb3JkcyBhbmQgZWxlbWVudCByZWN0XG4gICAgaWYgKHZhbHVlID09PSB0cnVlKSB7XG4gICAgICAgIC8vIGlmIGRpbWVuc2lvbnMgYXJlIG5lZ2F0aXZlLCBcInN3aXRjaFwiIGVkZ2VzXG4gICAgICAgIHZhciB3aWR0aCA9IGlzTnVtYmVyKHJlY3Qud2lkdGgpPyByZWN0LndpZHRoIDogcmVjdC5yaWdodCAtIHJlY3QubGVmdCxcbiAgICAgICAgICAgIGhlaWdodCA9IGlzTnVtYmVyKHJlY3QuaGVpZ2h0KT8gcmVjdC5oZWlnaHQgOiByZWN0LmJvdHRvbSAtIHJlY3QudG9wO1xuXG4gICAgICAgIGlmICh3aWR0aCA8IDApIHtcbiAgICAgICAgICAgIGlmICAgICAgKG5hbWUgPT09ICdsZWZ0JyApIHsgbmFtZSA9ICdyaWdodCc7IH1cbiAgICAgICAgICAgIGVsc2UgaWYgKG5hbWUgPT09ICdyaWdodCcpIHsgbmFtZSA9ICdsZWZ0JyA7IH1cbiAgICAgICAgfVxuICAgICAgICBpZiAoaGVpZ2h0IDwgMCkge1xuICAgICAgICAgICAgaWYgICAgICAobmFtZSA9PT0gJ3RvcCcgICApIHsgbmFtZSA9ICdib3R0b20nOyB9XG4gICAgICAgICAgICBlbHNlIGlmIChuYW1lID09PSAnYm90dG9tJykgeyBuYW1lID0gJ3RvcCcgICA7IH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChuYW1lID09PSAnbGVmdCcgICkgeyByZXR1cm4gcGFnZS54IDwgKCh3aWR0aCAgPj0gMD8gcmVjdC5sZWZ0OiByZWN0LnJpZ2h0ICkgKyBtYXJnaW4pOyB9XG4gICAgICAgIGlmIChuYW1lID09PSAndG9wJyAgICkgeyByZXR1cm4gcGFnZS55IDwgKChoZWlnaHQgPj0gMD8gcmVjdC50b3AgOiByZWN0LmJvdHRvbSkgKyBtYXJnaW4pOyB9XG5cbiAgICAgICAgaWYgKG5hbWUgPT09ICdyaWdodCcgKSB7IHJldHVybiBwYWdlLnggPiAoKHdpZHRoICA+PSAwPyByZWN0LnJpZ2h0IDogcmVjdC5sZWZ0KSAtIG1hcmdpbik7IH1cbiAgICAgICAgaWYgKG5hbWUgPT09ICdib3R0b20nKSB7IHJldHVybiBwYWdlLnkgPiAoKGhlaWdodCA+PSAwPyByZWN0LmJvdHRvbTogcmVjdC50b3AgKSAtIG1hcmdpbik7IH1cbiAgICB9XG5cbiAgICAvLyB0aGUgcmVtYWluaW5nIGNoZWNrcyByZXF1aXJlIGFuIGVsZW1lbnRcbiAgICBpZiAoIWlzRWxlbWVudChlbGVtZW50KSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgIHJldHVybiBpc0VsZW1lbnQodmFsdWUpXG4gICAgICAgIC8vIHRoZSB2YWx1ZSBpcyBhbiBlbGVtZW50IHRvIHVzZSBhcyBhIHJlc2l6ZSBoYW5kbGVcbiAgICAgICAgPyB2YWx1ZSA9PT0gZWxlbWVudFxuICAgICAgICAvLyBvdGhlcndpc2UgY2hlY2sgaWYgZWxlbWVudCBtYXRjaGVzIHZhbHVlIGFzIHNlbGVjdG9yXG4gICAgICAgIDogbWF0Y2hlc1VwVG8oZWxlbWVudCwgdmFsdWUsIGludGVyYWN0YWJsZUVsZW1lbnQpO1xufVxuXG5mdW5jdGlvbiBkZWZhdWx0QWN0aW9uQ2hlY2tlciAocG9pbnRlciwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpIHtcbiAgICB2YXIgcmVjdCA9IHRoaXMuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgc2hvdWxkUmVzaXplID0gZmFsc2UsXG4gICAgICAgIGFjdGlvbiA9IG51bGwsXG4gICAgICAgIHJlc2l6ZUF4ZXMgPSBudWxsLFxuICAgICAgICByZXNpemVFZGdlcyxcbiAgICAgICAgcGFnZSA9IGV4dGVuZCh7fSwgaW50ZXJhY3Rpb24uY3VyQ29vcmRzLnBhZ2UpLFxuICAgICAgICBvcHRpb25zID0gdGhpcy5vcHRpb25zO1xuXG4gICAgaWYgKCFyZWN0KSB7IHJldHVybiBudWxsOyB9XG5cbiAgICBpZiAoYWN0aW9uSXNFbmFibGVkLnJlc2l6ZSAmJiBvcHRpb25zLnJlc2l6ZS5lbmFibGVkKSB7XG4gICAgICAgIHZhciByZXNpemVPcHRpb25zID0gb3B0aW9ucy5yZXNpemU7XG5cbiAgICAgICAgcmVzaXplRWRnZXMgPSB7XG4gICAgICAgICAgICBsZWZ0OiBmYWxzZSwgcmlnaHQ6IGZhbHNlLCB0b3A6IGZhbHNlLCBib3R0b206IGZhbHNlXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gaWYgdXNpbmcgcmVzaXplLmVkZ2VzXG4gICAgICAgIGlmIChpc09iamVjdChyZXNpemVPcHRpb25zLmVkZ2VzKSkge1xuICAgICAgICAgICAgZm9yICh2YXIgZWRnZSBpbiByZXNpemVFZGdlcykge1xuICAgICAgICAgICAgICAgIHJlc2l6ZUVkZ2VzW2VkZ2VdID0gY2hlY2tSZXNpemVFZGdlKGVkZ2UsXG4gICAgICAgICAgICAgICAgICAgIHJlc2l6ZU9wdGlvbnMuZWRnZXNbZWRnZV0sXG4gICAgICAgICAgICAgICAgICAgIHBhZ2UsXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLl9ldmVudFRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgZWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgcmVjdCxcbiAgICAgICAgICAgICAgICAgICAgcmVzaXplT3B0aW9ucy5tYXJnaW4gfHwgbWFyZ2luKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzaXplRWRnZXMubGVmdCA9IHJlc2l6ZUVkZ2VzLmxlZnQgJiYgIXJlc2l6ZUVkZ2VzLnJpZ2h0O1xuICAgICAgICAgICAgcmVzaXplRWRnZXMudG9wICA9IHJlc2l6ZUVkZ2VzLnRvcCAgJiYgIXJlc2l6ZUVkZ2VzLmJvdHRvbTtcblxuICAgICAgICAgICAgc2hvdWxkUmVzaXplID0gcmVzaXplRWRnZXMubGVmdCB8fCByZXNpemVFZGdlcy5yaWdodCB8fCByZXNpemVFZGdlcy50b3AgfHwgcmVzaXplRWRnZXMuYm90dG9tO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdmFyIHJpZ2h0ICA9IG9wdGlvbnMucmVzaXplLmF4aXMgIT09ICd5JyAmJiBwYWdlLnggPiAocmVjdC5yaWdodCAgLSBtYXJnaW4pLFxuICAgICAgICAgICAgICAgIGJvdHRvbSA9IG9wdGlvbnMucmVzaXplLmF4aXMgIT09ICd4JyAmJiBwYWdlLnkgPiAocmVjdC5ib3R0b20gLSBtYXJnaW4pO1xuXG4gICAgICAgICAgICBzaG91bGRSZXNpemUgPSByaWdodCB8fCBib3R0b207XG4gICAgICAgICAgICByZXNpemVBeGVzID0gKHJpZ2h0PyAneCcgOiAnJykgKyAoYm90dG9tPyAneScgOiAnJyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBhY3Rpb24gPSBzaG91bGRSZXNpemVcbiAgICAgICAgPyAncmVzaXplJ1xuICAgICAgICA6IGFjdGlvbklzRW5hYmxlZC5kcmFnICYmIG9wdGlvbnMuZHJhZy5lbmFibGVkXG4gICAgICAgID8gJ2RyYWcnXG4gICAgICAgIDogbnVsbDtcblxuICAgIGlmIChhY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZVxuICAgICAgICAmJiBpbnRlcmFjdGlvbi5wb2ludGVySWRzLmxlbmd0aCA+PTJcbiAgICAgICAgJiYgIShpbnRlcmFjdGlvbi5kcmFnZ2luZyB8fCBpbnRlcmFjdGlvbi5yZXNpemluZykpIHtcbiAgICAgICAgYWN0aW9uID0gJ2dlc3R1cmUnO1xuICAgIH1cblxuICAgIGlmIChhY3Rpb24pIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIG5hbWU6IGFjdGlvbixcbiAgICAgICAgICAgIGF4aXM6IHJlc2l6ZUF4ZXMsXG4gICAgICAgICAgICBlZGdlczogcmVzaXplRWRnZXNcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICByZXR1cm4gbnVsbDtcbn1cblxuLy8gQ2hlY2sgaWYgYWN0aW9uIGlzIGVuYWJsZWQgZ2xvYmFsbHkgYW5kIHRoZSBjdXJyZW50IHRhcmdldCBzdXBwb3J0cyBpdFxuLy8gSWYgc28sIHJldHVybiB0aGUgdmFsaWRhdGVkIGFjdGlvbi4gT3RoZXJ3aXNlLCByZXR1cm4gbnVsbFxuZnVuY3Rpb24gdmFsaWRhdGVBY3Rpb24gKGFjdGlvbiwgaW50ZXJhY3RhYmxlKSB7XG4gICAgaWYgKCFpc09iamVjdChhY3Rpb24pKSB7IHJldHVybiBudWxsOyB9XG5cbiAgICB2YXIgYWN0aW9uTmFtZSA9IGFjdGlvbi5uYW1lLFxuICAgICAgICBvcHRpb25zID0gaW50ZXJhY3RhYmxlLm9wdGlvbnM7XG5cbiAgICBpZiAoKCAgKGFjdGlvbk5hbWUgID09PSAncmVzaXplJyAgICYmIG9wdGlvbnMucmVzaXplLmVuYWJsZWQgKVxuICAgICAgICB8fCAoYWN0aW9uTmFtZSAgICAgID09PSAnZHJhZycgICAgICYmIG9wdGlvbnMuZHJhZy5lbmFibGVkICApXG4gICAgICAgIHx8IChhY3Rpb25OYW1lICAgICAgPT09ICdnZXN0dXJlJyAgJiYgb3B0aW9ucy5nZXN0dXJlLmVuYWJsZWQpKVxuICAgICAgICAmJiBhY3Rpb25Jc0VuYWJsZWRbYWN0aW9uTmFtZV0pIHtcblxuICAgICAgICBpZiAoYWN0aW9uTmFtZSA9PT0gJ3Jlc2l6ZScgfHwgYWN0aW9uTmFtZSA9PT0gJ3Jlc2l6ZXl4Jykge1xuICAgICAgICAgICAgYWN0aW9uTmFtZSA9ICdyZXNpemV4eSc7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gYWN0aW9uO1xuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbn1cblxudmFyIGxpc3RlbmVycyA9IHt9LFxuICAgIGludGVyYWN0aW9uTGlzdGVuZXJzID0gW1xuICAgICAgICAnZHJhZ1N0YXJ0JywgJ2RyYWdNb3ZlJywgJ3Jlc2l6ZVN0YXJ0JywgJ3Jlc2l6ZU1vdmUnLCAnZ2VzdHVyZVN0YXJ0JywgJ2dlc3R1cmVNb3ZlJyxcbiAgICAgICAgJ3BvaW50ZXJPdmVyJywgJ3BvaW50ZXJPdXQnLCAncG9pbnRlckhvdmVyJywgJ3NlbGVjdG9yRG93bicsXG4gICAgICAgICdwb2ludGVyRG93bicsICdwb2ludGVyTW92ZScsICdwb2ludGVyVXAnLCAncG9pbnRlckNhbmNlbCcsICdwb2ludGVyRW5kJyxcbiAgICAgICAgJ2FkZFBvaW50ZXInLCAncmVtb3ZlUG9pbnRlcicsICdyZWNvcmRQb2ludGVyJywgJ2F1dG9TY3JvbGxNb3ZlJ1xuICAgIF07XG5cbmZvciAodmFyIGkgPSAwLCBsZW4gPSBpbnRlcmFjdGlvbkxpc3RlbmVycy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgIHZhciBuYW1lID0gaW50ZXJhY3Rpb25MaXN0ZW5lcnNbaV07XG5cbiAgICBsaXN0ZW5lcnNbbmFtZV0gPSBkb09uSW50ZXJhY3Rpb25zKG5hbWUpO1xufVxuXG4vLyBib3VuZCB0byB0aGUgaW50ZXJhY3RhYmxlIGNvbnRleHQgd2hlbiBhIERPTSBldmVudFxuLy8gbGlzdGVuZXIgaXMgYWRkZWQgdG8gYSBzZWxlY3RvciBpbnRlcmFjdGFibGVcbmZ1bmN0aW9uIGRlbGVnYXRlTGlzdGVuZXIgKGV2ZW50LCB1c2VDYXB0dXJlKSB7XG4gICAgdmFyIGZha2VFdmVudCA9IHt9LFxuICAgICAgICBkZWxlZ2F0ZWQgPSBkZWxlZ2F0ZWRFdmVudHNbZXZlbnQudHlwZV0sXG4gICAgICAgIGV2ZW50VGFyZ2V0ID0gZ2V0QWN0dWFsRWxlbWVudChldmVudC5wYXRoXG4gICAgICAgICAgICA/IGV2ZW50LnBhdGhbMF1cbiAgICAgICAgICAgIDogZXZlbnQudGFyZ2V0KSxcbiAgICAgICAgZWxlbWVudCA9IGV2ZW50VGFyZ2V0O1xuXG4gICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgLy8gZHVwbGljYXRlIHRoZSBldmVudCBzbyB0aGF0IGN1cnJlbnRUYXJnZXQgY2FuIGJlIGNoYW5nZWRcbiAgICBmb3IgKHZhciBwcm9wIGluIGV2ZW50KSB7XG4gICAgICAgIGZha2VFdmVudFtwcm9wXSA9IGV2ZW50W3Byb3BdO1xuICAgIH1cblxuICAgIGZha2VFdmVudC5vcmlnaW5hbEV2ZW50ID0gZXZlbnQ7XG4gICAgZmFrZUV2ZW50LnByZXZlbnREZWZhdWx0ID0gcHJldmVudE9yaWdpbmFsRGVmYXVsdDtcblxuICAgIC8vIGNsaW1iIHVwIGRvY3VtZW50IHRyZWUgbG9va2luZyBmb3Igc2VsZWN0b3IgbWF0Y2hlc1xuICAgIHdoaWxlIChpc0VsZW1lbnQoZWxlbWVudCkpIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgc2VsZWN0b3IgPSBkZWxlZ2F0ZWQuc2VsZWN0b3JzW2ldLFxuICAgICAgICAgICAgICAgIGNvbnRleHQgPSBkZWxlZ2F0ZWQuY29udGV4dHNbaV07XG5cbiAgICAgICAgICAgIGlmIChtYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgJiYgbm9kZUNvbnRhaW5zKGNvbnRleHQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIG5vZGVDb250YWlucyhjb250ZXh0LCBlbGVtZW50KSkge1xuXG4gICAgICAgICAgICAgICAgdmFyIGxpc3RlbmVycyA9IGRlbGVnYXRlZC5saXN0ZW5lcnNbaV07XG5cbiAgICAgICAgICAgICAgICBmYWtlRXZlbnQuY3VycmVudFRhcmdldCA9IGVsZW1lbnQ7XG5cbiAgICAgICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGxpc3RlbmVycy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgICAgICBpZiAobGlzdGVuZXJzW2pdWzFdID09PSB1c2VDYXB0dXJlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnNbal1bMF0oZmFrZUV2ZW50KTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGVsZW1lbnQgPSBwYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gZGVsZWdhdGVVc2VDYXB0dXJlIChldmVudCkge1xuICAgIHJldHVybiBkZWxlZ2F0ZUxpc3RlbmVyLmNhbGwodGhpcywgZXZlbnQsIHRydWUpO1xufVxuXG5pbnRlcmFjdGFibGVzLmluZGV4T2ZFbGVtZW50ID0gZnVuY3Rpb24gaW5kZXhPZkVsZW1lbnQgKGVsZW1lbnQsIGNvbnRleHQpIHtcbiAgICBjb250ZXh0ID0gY29udGV4dCB8fCBkb2N1bWVudDtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gdGhpc1tpXTtcblxuICAgICAgICBpZiAoKGludGVyYWN0YWJsZS5zZWxlY3RvciA9PT0gZWxlbWVudFxuICAgICAgICAgICAgJiYgKGludGVyYWN0YWJsZS5fY29udGV4dCA9PT0gY29udGV4dCkpXG4gICAgICAgICAgICB8fCAoIWludGVyYWN0YWJsZS5zZWxlY3RvciAmJiBpbnRlcmFjdGFibGUuX2VsZW1lbnQgPT09IGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgIHJldHVybiBpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiAtMTtcbn07XG5cbmludGVyYWN0YWJsZXMuZ2V0ID0gZnVuY3Rpb24gaW50ZXJhY3RhYmxlR2V0IChlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXNbdGhpcy5pbmRleE9mRWxlbWVudChlbGVtZW50LCBvcHRpb25zICYmIG9wdGlvbnMuY29udGV4dCldO1xufTtcblxuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IgPSBmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIGludGVyYWN0YWJsZSA9IHRoaXNbaV07XG5cbiAgICAgICAgaWYgKCFpbnRlcmFjdGFibGUuc2VsZWN0b3IpIHtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHJldCA9IGNhbGxiYWNrKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlLnNlbGVjdG9yLCBpbnRlcmFjdGFibGUuX2NvbnRleHQsIGksIHRoaXMpO1xuXG4gICAgICAgIGlmIChyZXQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIHJldDtcbiAgICAgICAgfVxuICAgIH1cbn07XG5cbi8qXFxcbiAqIGludGVyYWN0XG4gWyBtZXRob2QgXVxuICpcbiAqIFRoZSBtZXRob2RzIG9mIHRoaXMgdmFyaWFibGUgY2FuIGJlIHVzZWQgdG8gc2V0IGVsZW1lbnRzIGFzXG4gKiBpbnRlcmFjdGFibGVzIGFuZCBhbHNvIHRvIGNoYW5nZSB2YXJpb3VzIGRlZmF1bHQgc2V0dGluZ3MuXG4gKlxuICogQ2FsbGluZyBpdCBhcyBhIGZ1bmN0aW9uIGFuZCBwYXNzaW5nIGFuIGVsZW1lbnQgb3IgYSB2YWxpZCBDU1Mgc2VsZWN0b3JcbiAqIHN0cmluZyByZXR1cm5zIGFuIEludGVyYWN0YWJsZSBvYmplY3Qgd2hpY2ggaGFzIHZhcmlvdXMgbWV0aG9kcyB0b1xuICogY29uZmlndXJlIGl0LlxuICpcbiAtIGVsZW1lbnQgKEVsZW1lbnQgfCBzdHJpbmcpIFRoZSBIVE1MIG9yIFNWRyBFbGVtZW50IHRvIGludGVyYWN0IHdpdGggb3IgQ1NTIHNlbGVjdG9yXG4gPSAob2JqZWN0KSBBbiBASW50ZXJhY3RhYmxlXG4gKlxuID4gVXNhZ2VcbiB8IGludGVyYWN0KGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkcmFnZ2FibGUnKSkuZHJhZ2dhYmxlKHRydWUpO1xuIHxcbiB8IHZhciByZWN0YWJsZXMgPSBpbnRlcmFjdCgncmVjdCcpO1xuIHwgcmVjdGFibGVzXG4gfCAgICAgLmdlc3R1cmFibGUodHJ1ZSlcbiB8ICAgICAub24oJ2dlc3R1cmVtb3ZlJywgZnVuY3Rpb24gKGV2ZW50KSB7XG4gfCAgICAgICAgIC8vIHNvbWV0aGluZyBjb29sLi4uXG4gfCAgICAgfSlcbiB8ICAgICAuYXV0b1Njcm9sbCh0cnVlKTtcbiBcXCovXG5mdW5jdGlvbiBpbnRlcmFjdCAoZWxlbWVudCwgb3B0aW9ucykge1xuICAgIHJldHVybiBpbnRlcmFjdGFibGVzLmdldChlbGVtZW50LCBvcHRpb25zKSB8fCBuZXcgSW50ZXJhY3RhYmxlKGVsZW1lbnQsIG9wdGlvbnMpO1xufVxuXG4vKlxcXG4gKiBJbnRlcmFjdGFibGVcbiBbIHByb3BlcnR5IF1cbiAqKlxuICogT2JqZWN0IHR5cGUgcmV0dXJuZWQgYnkgQGludGVyYWN0XG4gXFwqL1xuZnVuY3Rpb24gSW50ZXJhY3RhYmxlIChlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgdGhpcy5fZWxlbWVudCA9IGVsZW1lbnQ7XG4gICAgdGhpcy5faUV2ZW50cyA9IHRoaXMuX2lFdmVudHMgfHwge307XG5cbiAgICB2YXIgX3dpbmRvdztcblxuICAgIGlmICh0cnlTZWxlY3RvcihlbGVtZW50KSkge1xuICAgICAgICB0aGlzLnNlbGVjdG9yID0gZWxlbWVudDtcblxuICAgICAgICB2YXIgY29udGV4dCA9IG9wdGlvbnMgJiYgb3B0aW9ucy5jb250ZXh0O1xuXG4gICAgICAgIF93aW5kb3cgPSBjb250ZXh0PyBnZXRXaW5kb3coY29udGV4dCkgOiB3aW5kb3c7XG5cbiAgICAgICAgaWYgKGNvbnRleHQgJiYgKF93aW5kb3cuTm9kZVxuICAgICAgICAgICAgICAgID8gY29udGV4dCBpbnN0YW5jZW9mIF93aW5kb3cuTm9kZVxuICAgICAgICAgICAgICAgIDogKGlzRWxlbWVudChjb250ZXh0KSB8fCBjb250ZXh0ID09PSBfd2luZG93LmRvY3VtZW50KSkpIHtcblxuICAgICAgICAgICAgdGhpcy5fY29udGV4dCA9IGNvbnRleHQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIF93aW5kb3cgPSBnZXRXaW5kb3coZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKGlzRWxlbWVudChlbGVtZW50LCBfd2luZG93KSkge1xuXG4gICAgICAgICAgICBpZiAoUG9pbnRlckV2ZW50KSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCBwRXZlbnRUeXBlcy5kb3duLCBsaXN0ZW5lcnMucG9pbnRlckRvd24gKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIHBFdmVudFR5cGVzLm1vdmUsIGxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAnbW91c2Vkb3duJyAsIGxpc3RlbmVycy5wb2ludGVyRG93biApO1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQodGhpcy5fZWxlbWVudCwgJ21vdXNlbW92ZScgLCBsaXN0ZW5lcnMucG9pbnRlckhvdmVyKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsICd0b3VjaHN0YXJ0JywgbGlzdGVuZXJzLnBvaW50ZXJEb3duICk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAndG91Y2htb3ZlJyAsIGxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy5fZG9jID0gX3dpbmRvdy5kb2N1bWVudDtcblxuICAgIGlmICghY29udGFpbnMoZG9jdW1lbnRzLCB0aGlzLl9kb2MpKSB7XG4gICAgICAgIGxpc3RlblRvRG9jdW1lbnQodGhpcy5fZG9jKTtcbiAgICB9XG5cbiAgICBpbnRlcmFjdGFibGVzLnB1c2godGhpcyk7XG5cbiAgICB0aGlzLnNldChvcHRpb25zKTtcbn1cblxuSW50ZXJhY3RhYmxlLnByb3RvdHlwZSA9IHtcbiAgICBzZXRPbkV2ZW50czogZnVuY3Rpb24gKGFjdGlvbiwgcGhhc2VzKSB7XG4gICAgICAgIGlmIChhY3Rpb24gPT09ICdkcm9wJykge1xuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uZHJvcCkgICAgICAgICAgKSB7IHRoaXMub25kcm9wICAgICAgICAgICA9IHBoYXNlcy5vbmRyb3AgICAgICAgICAgOyB9XG4gICAgICAgICAgICBpZiAoaXNGdW5jdGlvbihwaGFzZXMub25kcm9wYWN0aXZhdGUpICApIHsgdGhpcy5vbmRyb3BhY3RpdmF0ZSAgID0gcGhhc2VzLm9uZHJvcGFjdGl2YXRlICA7IH1cbiAgICAgICAgICAgIGlmIChpc0Z1bmN0aW9uKHBoYXNlcy5vbmRyb3BkZWFjdGl2YXRlKSkgeyB0aGlzLm9uZHJvcGRlYWN0aXZhdGUgPSBwaGFzZXMub25kcm9wZGVhY3RpdmF0ZTsgfVxuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uZHJhZ2VudGVyKSAgICAgKSB7IHRoaXMub25kcmFnZW50ZXIgICAgICA9IHBoYXNlcy5vbmRyYWdlbnRlciAgICAgOyB9XG4gICAgICAgICAgICBpZiAoaXNGdW5jdGlvbihwaGFzZXMub25kcmFnbGVhdmUpICAgICApIHsgdGhpcy5vbmRyYWdsZWF2ZSAgICAgID0gcGhhc2VzLm9uZHJhZ2xlYXZlICAgICA7IH1cbiAgICAgICAgICAgIGlmIChpc0Z1bmN0aW9uKHBoYXNlcy5vbmRyb3Btb3ZlKSAgICAgICkgeyB0aGlzLm9uZHJvcG1vdmUgICAgICAgPSBwaGFzZXMub25kcm9wbW92ZSAgICAgIDsgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgYWN0aW9uID0gJ29uJyArIGFjdGlvbjtcblxuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uc3RhcnQpICAgICAgICkgeyB0aGlzW2FjdGlvbiArICdzdGFydCcgICAgICAgICBdID0gcGhhc2VzLm9uc3RhcnQgICAgICAgICA7IH1cbiAgICAgICAgICAgIGlmIChpc0Z1bmN0aW9uKHBoYXNlcy5vbm1vdmUpICAgICAgICApIHsgdGhpc1thY3Rpb24gKyAnbW92ZScgICAgICAgICAgXSA9IHBoYXNlcy5vbm1vdmUgICAgICAgICAgOyB9XG4gICAgICAgICAgICBpZiAoaXNGdW5jdGlvbihwaGFzZXMub25lbmQpICAgICAgICAgKSB7IHRoaXNbYWN0aW9uICsgJ2VuZCcgICAgICAgICAgIF0gPSBwaGFzZXMub25lbmQgICAgICAgICAgIDsgfVxuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uaW5lcnRpYXN0YXJ0KSkgeyB0aGlzW2FjdGlvbiArICdpbmVydGlhc3RhcnQnICBdID0gcGhhc2VzLm9uaW5lcnRpYXN0YXJ0ICA7IH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmRyYWdnYWJsZVxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBHZXRzIG9yIHNldHMgd2hldGhlciBkcmFnIGFjdGlvbnMgY2FuIGJlIHBlcmZvcm1lZCBvbiB0aGVcbiAgICAgKiBJbnRlcmFjdGFibGVcbiAgICAgKlxuICAgICA9IChib29sZWFuKSBJbmRpY2F0ZXMgaWYgdGhpcyBjYW4gYmUgdGhlIHRhcmdldCBvZiBkcmFnIGV2ZW50c1xuICAgICB8IHZhciBpc0RyYWdnYWJsZSA9IGludGVyYWN0KCd1bCBsaScpLmRyYWdnYWJsZSgpO1xuICAgICAqIG9yXG4gICAgIC0gb3B0aW9ucyAoYm9vbGVhbiB8IG9iamVjdCkgI29wdGlvbmFsIHRydWUvZmFsc2Ugb3IgQW4gb2JqZWN0IHdpdGggZXZlbnQgbGlzdGVuZXJzIHRvIGJlIGZpcmVkIG9uIGRyYWcgZXZlbnRzIChvYmplY3QgbWFrZXMgdGhlIEludGVyYWN0YWJsZSBkcmFnZ2FibGUpXG4gICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5kcmFnZ2FibGUoe1xuICAgICB8ICAgICBvbnN0YXJ0OiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICB8ICAgICBvbm1vdmUgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICB8ICAgICBvbmVuZCAgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICB8XG4gICAgIHwgICAgIC8vIHRoZSBheGlzIGluIHdoaWNoIHRoZSBmaXJzdCBtb3ZlbWVudCBtdXN0IGJlXG4gICAgIHwgICAgIC8vIGZvciB0aGUgZHJhZyBzZXF1ZW5jZSB0byBzdGFydFxuICAgICB8ICAgICAvLyAneHknIGJ5IGRlZmF1bHQgLSBhbnkgZGlyZWN0aW9uXG4gICAgIHwgICAgIGF4aXM6ICd4JyB8fCAneScgfHwgJ3h5JyxcbiAgICAgfFxuICAgICB8ICAgICAvLyBtYXggbnVtYmVyIG9mIGRyYWdzIHRoYXQgY2FuIGhhcHBlbiBjb25jdXJyZW50bHlcbiAgICAgfCAgICAgLy8gd2l0aCBlbGVtZW50cyBvZiB0aGlzIEludGVyYWN0YWJsZS4gSW5maW5pdHkgYnkgZGVmYXVsdFxuICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICB8XG4gICAgIHwgICAgIC8vIG1heCBudW1iZXIgb2YgZHJhZ3MgdGhhdCBjYW4gdGFyZ2V0IHRoZSBzYW1lIGVsZW1lbnQrSW50ZXJhY3RhYmxlXG4gICAgIHwgICAgIC8vIDEgYnkgZGVmYXVsdFxuICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAyXG4gICAgIHwgfSk7XG4gICAgIFxcKi9cbiAgICBkcmFnZ2FibGU6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgIGlmIChpc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyYWcuZW5hYmxlZCA9IG9wdGlvbnMuZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlOiB0cnVlO1xuICAgICAgICAgICAgdGhpcy5zZXRQZXJBY3Rpb24oJ2RyYWcnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIHRoaXMuc2V0T25FdmVudHMoJ2RyYWcnLCBvcHRpb25zKTtcblxuICAgICAgICAgICAgaWYgKC9eeCR8XnkkfF54eSQvLnRlc3Qob3B0aW9ucy5heGlzKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcmFnLmF4aXMgPSBvcHRpb25zLmF4aXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChvcHRpb25zLmF4aXMgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmRyYWcuYXhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJhZy5lbmFibGVkID0gb3B0aW9ucztcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmRyYWc7XG4gICAgfSxcblxuICAgIHNldFBlckFjdGlvbjogZnVuY3Rpb24gKGFjdGlvbiwgb3B0aW9ucykge1xuICAgICAgICAvLyBmb3IgYWxsIHRoZSBkZWZhdWx0IHBlci1hY3Rpb24gb3B0aW9uc1xuICAgICAgICBmb3IgKHZhciBvcHRpb24gaW4gb3B0aW9ucykge1xuICAgICAgICAgICAgLy8gaWYgdGhpcyBvcHRpb24gZXhpc3RzIGZvciB0aGlzIGFjdGlvblxuICAgICAgICAgICAgaWYgKG9wdGlvbiBpbiBkZWZhdWx0T3B0aW9uc1thY3Rpb25dKSB7XG4gICAgICAgICAgICAgICAgLy8gaWYgdGhlIG9wdGlvbiBpbiB0aGUgb3B0aW9ucyBhcmcgaXMgYW4gb2JqZWN0IHZhbHVlXG4gICAgICAgICAgICAgICAgaWYgKGlzT2JqZWN0KG9wdGlvbnNbb3B0aW9uXSkpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gZHVwbGljYXRlIHRoZSBvYmplY3RcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXSA9IGV4dGVuZCh0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dIHx8IHt9LCBvcHRpb25zW29wdGlvbl0pO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChpc09iamVjdChkZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb25bb3B0aW9uXSkgJiYgJ2VuYWJsZWQnIGluIGRlZmF1bHRPcHRpb25zLnBlckFjdGlvbltvcHRpb25dKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dLmVuYWJsZWQgPSBvcHRpb25zW29wdGlvbl0uZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlIDogdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChpc0Jvb2wob3B0aW9uc1tvcHRpb25dKSAmJiBpc09iamVjdChkZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb25bb3B0aW9uXSkpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXS5lbmFibGVkID0gb3B0aW9uc1tvcHRpb25dO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChvcHRpb25zW29wdGlvbl0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgICAgICAvLyBvciBpZiBpdCdzIG5vdCB1bmRlZmluZWQsIGRvIGEgcGxhaW4gYXNzaWdubWVudFxuICAgICAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dID0gb3B0aW9uc1tvcHRpb25dO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmRyb3B6b25lXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIGVsZW1lbnRzIGNhbiBiZSBkcm9wcGVkIG9udG8gdGhpc1xuICAgICAqIEludGVyYWN0YWJsZSB0byB0cmlnZ2VyIGRyb3AgZXZlbnRzXG4gICAgICpcbiAgICAgKiBEcm9wem9uZXMgY2FuIHJlY2VpdmUgdGhlIGZvbGxvd2luZyBldmVudHM6XG4gICAgICogIC0gYGRyb3BhY3RpdmF0ZWAgYW5kIGBkcm9wZGVhY3RpdmF0ZWAgd2hlbiBhbiBhY2NlcHRhYmxlIGRyYWcgc3RhcnRzIGFuZCBlbmRzXG4gICAgICogIC0gYGRyYWdlbnRlcmAgYW5kIGBkcmFnbGVhdmVgIHdoZW4gYSBkcmFnZ2FibGUgZW50ZXJzIGFuZCBsZWF2ZXMgdGhlIGRyb3B6b25lXG4gICAgICogIC0gYGRyYWdtb3ZlYCB3aGVuIGEgZHJhZ2dhYmxlIHRoYXQgaGFzIGVudGVyZWQgdGhlIGRyb3B6b25lIGlzIG1vdmVkXG4gICAgICogIC0gYGRyb3BgIHdoZW4gYSBkcmFnZ2FibGUgaXMgZHJvcHBlZCBpbnRvIHRoaXMgZHJvcHpvbmVcbiAgICAgKlxuICAgICAqICBVc2UgdGhlIGBhY2NlcHRgIG9wdGlvbiB0byBhbGxvdyBvbmx5IGVsZW1lbnRzIHRoYXQgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvciBlbGVtZW50LlxuICAgICAqXG4gICAgICogIFVzZSB0aGUgYG92ZXJsYXBgIG9wdGlvbiB0byBzZXQgaG93IGRyb3BzIGFyZSBjaGVja2VkIGZvci4gVGhlIGFsbG93ZWQgdmFsdWVzIGFyZTpcbiAgICAgKiAgIC0gYCdwb2ludGVyJ2AsIHRoZSBwb2ludGVyIG11c3QgYmUgb3ZlciB0aGUgZHJvcHpvbmUgKGRlZmF1bHQpXG4gICAgICogICAtIGAnY2VudGVyJ2AsIHRoZSBkcmFnZ2FibGUgZWxlbWVudCdzIGNlbnRlciBtdXN0IGJlIG92ZXIgdGhlIGRyb3B6b25lXG4gICAgICogICAtIGEgbnVtYmVyIGZyb20gMC0xIHdoaWNoIGlzIHRoZSBgKGludGVyc2VjdGlvbiBhcmVhKSAvIChkcmFnZ2FibGUgYXJlYSlgLlxuICAgICAqICAgICAgIGUuZy4gYDAuNWAgZm9yIGRyb3AgdG8gaGFwcGVuIHdoZW4gaGFsZiBvZiB0aGUgYXJlYSBvZiB0aGVcbiAgICAgKiAgICAgICBkcmFnZ2FibGUgaXMgb3ZlciB0aGUgZHJvcHpvbmVcbiAgICAgKlxuICAgICAtIG9wdGlvbnMgKGJvb2xlYW4gfCBvYmplY3QgfCBudWxsKSAjb3B0aW9uYWwgVGhlIG5ldyB2YWx1ZSB0byBiZSBzZXQuXG4gICAgIHwgaW50ZXJhY3QoJy5kcm9wJykuZHJvcHpvbmUoe1xuICAgICB8ICAgYWNjZXB0OiAnLmNhbi1kcm9wJyB8fCBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc2luZ2xlLWRyb3AnKSxcbiAgICAgfCAgIG92ZXJsYXA6ICdwb2ludGVyJyB8fCAnY2VudGVyJyB8fCB6ZXJvVG9PbmVcbiAgICAgfCB9XG4gICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIGRyb3B6b25lOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcbiAgICAgICAgICAgIHRoaXMuc2V0T25FdmVudHMoJ2Ryb3AnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIHRoaXMuYWNjZXB0KG9wdGlvbnMuYWNjZXB0KTtcblxuICAgICAgICAgICAgaWYgKC9eKHBvaW50ZXJ8Y2VudGVyKSQvLnRlc3Qob3B0aW9ucy5vdmVybGFwKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLm92ZXJsYXAgPSBvcHRpb25zLm92ZXJsYXA7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChpc051bWJlcihvcHRpb25zLm92ZXJsYXApKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3Aub3ZlcmxhcCA9IE1hdGgubWF4KE1hdGgubWluKDEsIG9wdGlvbnMub3ZlcmxhcCksIDApO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0Jvb2wob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcDtcbiAgICB9LFxuXG4gICAgZHJvcENoZWNrOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGRyYWdnYWJsZSwgZHJhZ2dhYmxlRWxlbWVudCwgZHJvcEVsZW1lbnQsIHJlY3QpIHtcbiAgICAgICAgdmFyIGRyb3BwZWQgPSBmYWxzZTtcblxuICAgICAgICAvLyBpZiB0aGUgZHJvcHpvbmUgaGFzIG5vIHJlY3QgKGVnLiBkaXNwbGF5OiBub25lKVxuICAgICAgICAvLyBjYWxsIHRoZSBjdXN0b20gZHJvcENoZWNrZXIgb3IganVzdCByZXR1cm4gZmFsc2VcbiAgICAgICAgaWYgKCEocmVjdCA9IHJlY3QgfHwgdGhpcy5nZXRSZWN0KGRyb3BFbGVtZW50KSkpIHtcbiAgICAgICAgICAgIHJldHVybiAodGhpcy5vcHRpb25zLmRyb3BDaGVja2VyXG4gICAgICAgICAgICAgICAgPyB0aGlzLm9wdGlvbnMuZHJvcENoZWNrZXIocG9pbnRlciwgZXZlbnQsIGRyb3BwZWQsIHRoaXMsIGRyb3BFbGVtZW50LCBkcmFnZ2FibGUsIGRyYWdnYWJsZUVsZW1lbnQpXG4gICAgICAgICAgICAgICAgOiBmYWxzZSk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZHJvcE92ZXJsYXAgPSB0aGlzLm9wdGlvbnMuZHJvcC5vdmVybGFwO1xuXG4gICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ3BvaW50ZXInKSB7XG4gICAgICAgICAgICB2YXIgcGFnZSA9IGdldFBhZ2VYWShwb2ludGVyKSxcbiAgICAgICAgICAgICAgICBvcmlnaW4gPSBnZXRPcmlnaW5YWShkcmFnZ2FibGUsIGRyYWdnYWJsZUVsZW1lbnQpLFxuICAgICAgICAgICAgICAgIGhvcml6b250YWwsXG4gICAgICAgICAgICAgICAgdmVydGljYWw7XG5cbiAgICAgICAgICAgIHBhZ2UueCArPSBvcmlnaW4ueDtcbiAgICAgICAgICAgIHBhZ2UueSArPSBvcmlnaW4ueTtcblxuICAgICAgICAgICAgaG9yaXpvbnRhbCA9IChwYWdlLnggPiByZWN0LmxlZnQpICYmIChwYWdlLnggPCByZWN0LnJpZ2h0KTtcbiAgICAgICAgICAgIHZlcnRpY2FsICAgPSAocGFnZS55ID4gcmVjdC50b3AgKSAmJiAocGFnZS55IDwgcmVjdC5ib3R0b20pO1xuXG4gICAgICAgICAgICBkcm9wcGVkID0gaG9yaXpvbnRhbCAmJiB2ZXJ0aWNhbDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBkcmFnUmVjdCA9IGRyYWdnYWJsZS5nZXRSZWN0KGRyYWdnYWJsZUVsZW1lbnQpO1xuXG4gICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ2NlbnRlcicpIHtcbiAgICAgICAgICAgIHZhciBjeCA9IGRyYWdSZWN0LmxlZnQgKyBkcmFnUmVjdC53aWR0aCAgLyAyLFxuICAgICAgICAgICAgICAgIGN5ID0gZHJhZ1JlY3QudG9wICArIGRyYWdSZWN0LmhlaWdodCAvIDI7XG5cbiAgICAgICAgICAgIGRyb3BwZWQgPSBjeCA+PSByZWN0LmxlZnQgJiYgY3ggPD0gcmVjdC5yaWdodCAmJiBjeSA+PSByZWN0LnRvcCAmJiBjeSA8PSByZWN0LmJvdHRvbTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc051bWJlcihkcm9wT3ZlcmxhcCkpIHtcbiAgICAgICAgICAgIHZhciBvdmVybGFwQXJlYSAgPSAoTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5yaWdodCAsIGRyYWdSZWN0LnJpZ2h0ICkgLSBNYXRoLm1heChyZWN0LmxlZnQsIGRyYWdSZWN0LmxlZnQpKVxuICAgICAgICAgICAgICAgICogTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5ib3R0b20sIGRyYWdSZWN0LmJvdHRvbSkgLSBNYXRoLm1heChyZWN0LnRvcCAsIGRyYWdSZWN0LnRvcCApKSksXG4gICAgICAgICAgICAgICAgb3ZlcmxhcFJhdGlvID0gb3ZlcmxhcEFyZWEgLyAoZHJhZ1JlY3Qud2lkdGggKiBkcmFnUmVjdC5oZWlnaHQpO1xuXG4gICAgICAgICAgICBkcm9wcGVkID0gb3ZlcmxhcFJhdGlvID49IGRyb3BPdmVybGFwO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlcikge1xuICAgICAgICAgICAgZHJvcHBlZCA9IHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlcihwb2ludGVyLCBkcm9wcGVkLCB0aGlzLCBkcm9wRWxlbWVudCwgZHJhZ2dhYmxlLCBkcmFnZ2FibGVFbGVtZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBkcm9wcGVkO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmRyb3BDaGVja2VyXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB0aGUgZnVuY3Rpb24gdXNlZCB0byBjaGVjayBpZiBhIGRyYWdnZWQgZWxlbWVudCBpc1xuICAgICAqIG92ZXIgdGhpcyBJbnRlcmFjdGFibGUuXG4gICAgICpcbiAgICAgLSBjaGVja2VyIChmdW5jdGlvbikgI29wdGlvbmFsIFRoZSBmdW5jdGlvbiB0aGF0IHdpbGwgYmUgY2FsbGVkIHdoZW4gY2hlY2tpbmcgZm9yIGEgZHJvcFxuICAgICA9IChGdW5jdGlvbiB8IEludGVyYWN0YWJsZSkgVGhlIGNoZWNrZXIgZnVuY3Rpb24gb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgKlxuICAgICAqIFRoZSBjaGVja2VyIGZ1bmN0aW9uIHRha2VzIHRoZSBmb2xsb3dpbmcgYXJndW1lbnRzOlxuICAgICAqXG4gICAgIC0gcG9pbnRlciAoVG91Y2ggfCBQb2ludGVyRXZlbnQgfCBNb3VzZUV2ZW50KSBUaGUgcG9pbnRlci9ldmVudCB0aGF0IGVuZHMgYSBkcmFnXG4gICAgIC0gZXZlbnQgKFRvdWNoRXZlbnQgfCBQb2ludGVyRXZlbnQgfCBNb3VzZUV2ZW50KSBUaGUgZXZlbnQgcmVsYXRlZCB0byB0aGUgcG9pbnRlclxuICAgICAtIGRyb3BwZWQgKGJvb2xlYW4pIFRoZSB2YWx1ZSBmcm9tIHRoZSBkZWZhdWx0IGRyb3AgY2hlY2tcbiAgICAgLSBkcm9wem9uZSAoSW50ZXJhY3RhYmxlKSBUaGUgZHJvcHpvbmUgaW50ZXJhY3RhYmxlXG4gICAgIC0gZHJvcEVsZW1lbnQgKEVsZW1lbnQpIFRoZSBkcm9wem9uZSBlbGVtZW50XG4gICAgIC0gZHJhZ2dhYmxlIChJbnRlcmFjdGFibGUpIFRoZSBJbnRlcmFjdGFibGUgYmVpbmcgZHJhZ2dlZFxuICAgICAtIGRyYWdnYWJsZUVsZW1lbnQgKEVsZW1lbnQpIFRoZSBhY3R1YWwgZWxlbWVudCB0aGF0J3MgYmVpbmcgZHJhZ2dlZFxuICAgICAqXG4gICAgID4gVXNhZ2U6XG4gICAgIHwgaW50ZXJhY3QodGFyZ2V0KVxuICAgICB8IC5kcm9wQ2hlY2tlcihmdW5jdGlvbihwb2ludGVyLCAgICAgICAgICAgLy8gVG91Y2gvUG9pbnRlckV2ZW50L01vdXNlRXZlbnRcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQsICAgICAgICAgICAgIC8vIFRvdWNoRXZlbnQvUG9pbnRlckV2ZW50L01vdXNlRXZlbnRcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJvcHBlZCwgICAgICAgICAgIC8vIHJlc3VsdCBvZiB0aGUgZGVmYXVsdCBjaGVja2VyXG4gICAgIHwgICAgICAgICAgICAgICAgICAgICAgIGRyb3B6b25lLCAgICAgICAgICAvLyBkcm9wem9uZSBJbnRlcmFjdGFibGVcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJvcEVsZW1lbnQsICAgICAgIC8vIGRyb3B6b25lIGVsZW1udFxuICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUsICAgICAgICAgLy8gZHJhZ2dhYmxlIEludGVyYWN0YWJsZVxuICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGVFbGVtZW50KSB7Ly8gZHJhZ2dhYmxlIGVsZW1lbnRcbiAgICAgfFxuICAgICB8ICAgcmV0dXJuIGRyb3BwZWQgJiYgZXZlbnQudGFyZ2V0Lmhhc0F0dHJpYnV0ZSgnYWxsb3ctZHJvcCcpO1xuICAgICB8IH1cbiAgICAgXFwqL1xuICAgIGRyb3BDaGVja2VyOiBmdW5jdGlvbiAoY2hlY2tlcikge1xuICAgICAgICBpZiAoaXNGdW5jdGlvbihjaGVja2VyKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3BDaGVja2VyID0gY2hlY2tlcjtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgIGRlbGV0ZSB0aGlzLm9wdGlvbnMuZ2V0UmVjdDtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmRyb3BDaGVja2VyO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmFjY2VwdFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBEZXByZWNhdGVkLiBhZGQgYW4gYGFjY2VwdGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZCB0b1xuICAgICAqIEBJbnRlcmFjdGFibGUuZHJvcHpvbmUgaW5zdGVhZC5cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB0aGUgRWxlbWVudCBvciBDU1Mgc2VsZWN0b3IgbWF0Y2ggdGhhdCB0aGlzXG4gICAgICogSW50ZXJhY3RhYmxlIGFjY2VwdHMgaWYgaXQgaXMgYSBkcm9wem9uZS5cbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChFbGVtZW50IHwgc3RyaW5nIHwgbnVsbCkgI29wdGlvbmFsXG4gICAgICogSWYgaXQgaXMgYW4gRWxlbWVudCwgdGhlbiBvbmx5IHRoYXQgZWxlbWVudCBjYW4gYmUgZHJvcHBlZCBpbnRvIHRoaXMgZHJvcHpvbmUuXG4gICAgICogSWYgaXQgaXMgYSBzdHJpbmcsIHRoZSBlbGVtZW50IGJlaW5nIGRyYWdnZWQgbXVzdCBtYXRjaCBpdCBhcyBhIHNlbGVjdG9yLlxuICAgICAqIElmIGl0IGlzIG51bGwsIHRoZSBhY2NlcHQgb3B0aW9ucyBpcyBjbGVhcmVkIC0gaXQgYWNjZXB0cyBhbnkgZWxlbWVudC5cbiAgICAgKlxuICAgICA9IChzdHJpbmcgfCBFbGVtZW50IHwgbnVsbCB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgYWNjZXB0IG9wdGlvbiBpZiBnaXZlbiBgdW5kZWZpbmVkYCBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgYWNjZXB0OiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgaWYgKGlzRWxlbWVudChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmFjY2VwdCA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHRlc3QgaWYgaXQgaXMgYSB2YWxpZCBDU1Mgc2VsZWN0b3JcbiAgICAgICAgaWYgKHRyeVNlbGVjdG9yKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0ID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0O1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcC5hY2NlcHQ7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUucmVzaXphYmxlXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB3aGV0aGVyIHJlc2l6ZSBhY3Rpb25zIGNhbiBiZSBwZXJmb3JtZWQgb24gdGhlXG4gICAgICogSW50ZXJhY3RhYmxlXG4gICAgICpcbiAgICAgPSAoYm9vbGVhbikgSW5kaWNhdGVzIGlmIHRoaXMgY2FuIGJlIHRoZSB0YXJnZXQgb2YgcmVzaXplIGVsZW1lbnRzXG4gICAgIHwgdmFyIGlzUmVzaXplYWJsZSA9IGludGVyYWN0KCdpbnB1dFt0eXBlPXRleHRdJykucmVzaXphYmxlKCk7XG4gICAgICogb3JcbiAgICAgLSBvcHRpb25zIChib29sZWFuIHwgb2JqZWN0KSAjb3B0aW9uYWwgdHJ1ZS9mYWxzZSBvciBBbiBvYmplY3Qgd2l0aCBldmVudCBsaXN0ZW5lcnMgdG8gYmUgZmlyZWQgb24gcmVzaXplIGV2ZW50cyAob2JqZWN0IG1ha2VzIHRoZSBJbnRlcmFjdGFibGUgcmVzaXphYmxlKVxuICAgICA9IChvYmplY3QpIFRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkucmVzaXphYmxlKHtcbiAgICAgfCAgICAgb25zdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgfCAgICAgb25tb3ZlIDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgfCAgICAgb25lbmQgIDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgfFxuICAgICB8ICAgICBlZGdlczoge1xuICAgICB8ICAgICAgIHRvcCAgIDogdHJ1ZSwgICAgICAgLy8gVXNlIHBvaW50ZXIgY29vcmRzIHRvIGNoZWNrIGZvciByZXNpemUuXG4gICAgIHwgICAgICAgbGVmdCAgOiBmYWxzZSwgICAgICAvLyBEaXNhYmxlIHJlc2l6aW5nIGZyb20gbGVmdCBlZGdlLlxuICAgICB8ICAgICAgIGJvdHRvbTogJy5yZXNpemUtcycsLy8gUmVzaXplIGlmIHBvaW50ZXIgdGFyZ2V0IG1hdGNoZXMgc2VsZWN0b3JcbiAgICAgfCAgICAgICByaWdodCA6IGhhbmRsZUVsICAgIC8vIFJlc2l6ZSBpZiBwb2ludGVyIHRhcmdldCBpcyB0aGUgZ2l2ZW4gRWxlbWVudFxuICAgICB8ICAgICB9LFxuICAgICB8XG4gICAgIHwgICAgIC8vIGEgdmFsdWUgb2YgJ25vbmUnIHdpbGwgbGltaXQgdGhlIHJlc2l6ZSByZWN0IHRvIGEgbWluaW11bSBvZiAweDBcbiAgICAgfCAgICAgLy8gJ25lZ2F0ZScgd2lsbCBhbGxvdyB0aGUgcmVjdCB0byBoYXZlIG5lZ2F0aXZlIHdpZHRoL2hlaWdodFxuICAgICB8ICAgICAvLyAncmVwb3NpdGlvbicgd2lsbCBrZWVwIHRoZSB3aWR0aC9oZWlnaHQgcG9zaXRpdmUgYnkgc3dhcHBpbmdcbiAgICAgfCAgICAgLy8gdGhlIHRvcCBhbmQgYm90dG9tIGVkZ2VzIGFuZC9vciBzd2FwcGluZyB0aGUgbGVmdCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgfCAgICAgaW52ZXJ0OiAnbm9uZScgfHwgJ25lZ2F0ZScgfHwgJ3JlcG9zaXRpb24nXG4gICAgIHxcbiAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgcmVzaXplcy5cbiAgICAgfCAgICAgLy8gU2VlIHRoZSBleHBsYW5hdGlvbiBpbiB0aGUgQEludGVyYWN0YWJsZS5kcmFnZ2FibGUgZXhhbXBsZVxuICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICB8IH0pO1xuICAgICBcXCovXG4gICAgcmVzaXphYmxlOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuZW5hYmxlZCA9IG9wdGlvbnMuZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlOiB0cnVlO1xuICAgICAgICAgICAgdGhpcy5zZXRQZXJBY3Rpb24oJ3Jlc2l6ZScsIG9wdGlvbnMpO1xuICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygncmVzaXplJywgb3B0aW9ucyk7XG5cbiAgICAgICAgICAgIGlmICgvXngkfF55JHxeeHkkLy50ZXN0KG9wdGlvbnMuYXhpcykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmF4aXMgPSBvcHRpb25zLmF4aXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChvcHRpb25zLmF4aXMgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmF4aXMgPSBkZWZhdWx0T3B0aW9ucy5yZXNpemUuYXhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGlzQm9vbChvcHRpb25zLnNxdWFyZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLnNxdWFyZSA9IG9wdGlvbnMuc3F1YXJlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICBpZiAoaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLnJlc2l6ZTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5zcXVhcmVSZXNpemVcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogRGVwcmVjYXRlZC4gQWRkIGEgYHNxdWFyZTogdHJ1ZSB8fCBmYWxzZWAgcHJvcGVydHkgdG8gQEludGVyYWN0YWJsZS5yZXNpemFibGUgaW5zdGVhZFxuICAgICAqXG4gICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgcmVzaXppbmcgaXMgZm9yY2VkIDE6MSBhc3BlY3RcbiAgICAgKlxuICAgICA9IChib29sZWFuKSBDdXJyZW50IHNldHRpbmdcbiAgICAgKlxuICAgICAqIG9yXG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsXG4gICAgID0gKG9iamVjdCkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIHNxdWFyZVJlc2l6ZTogZnVuY3Rpb24gKG5ld1ZhbHVlKSB7XG4gICAgICAgIGlmIChpc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLnNxdWFyZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChuZXdWYWx1ZSA9PT0gbnVsbCkge1xuICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5yZXNpemUuc3F1YXJlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMucmVzaXplLnNxdWFyZTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5nZXN0dXJhYmxlXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB3aGV0aGVyIG11bHRpdG91Y2ggZ2VzdHVyZXMgY2FuIGJlIHBlcmZvcm1lZCBvbiB0aGVcbiAgICAgKiBJbnRlcmFjdGFibGUncyBlbGVtZW50XG4gICAgICpcbiAgICAgPSAoYm9vbGVhbikgSW5kaWNhdGVzIGlmIHRoaXMgY2FuIGJlIHRoZSB0YXJnZXQgb2YgZ2VzdHVyZSBldmVudHNcbiAgICAgfCB2YXIgaXNHZXN0dXJlYWJsZSA9IGludGVyYWN0KGVsZW1lbnQpLmdlc3R1cmFibGUoKTtcbiAgICAgKiBvclxuICAgICAtIG9wdGlvbnMgKGJvb2xlYW4gfCBvYmplY3QpICNvcHRpb25hbCB0cnVlL2ZhbHNlIG9yIEFuIG9iamVjdCB3aXRoIGV2ZW50IGxpc3RlbmVycyB0byBiZSBmaXJlZCBvbiBnZXN0dXJlIGV2ZW50cyAobWFrZXMgdGhlIEludGVyYWN0YWJsZSBnZXN0dXJhYmxlKVxuICAgICA9IChvYmplY3QpIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuZ2VzdHVyYWJsZSh7XG4gICAgIHwgICAgIG9uc3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgIHwgICAgIG9ubW92ZSA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgIHwgICAgIG9uZW5kICA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgIHxcbiAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgZ2VzdHVyZXMuXG4gICAgIHwgICAgIC8vIFNlZSB0aGUgZXhwbGFuYXRpb24gaW4gQEludGVyYWN0YWJsZS5kcmFnZ2FibGUgZXhhbXBsZVxuICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICB8IH0pO1xuICAgICBcXCovXG4gICAgZ2VzdHVyYWJsZTogZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgICAgICAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMuZ2VzdHVyZS5lbmFibGVkID0gb3B0aW9ucy5lbmFibGVkID09PSBmYWxzZT8gZmFsc2U6IHRydWU7XG4gICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbignZ2VzdHVyZScsIG9wdGlvbnMpO1xuICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygnZ2VzdHVyZScsIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0Jvb2wob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5nZXN0dXJlLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZ2VzdHVyZTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5hdXRvU2Nyb2xsXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKipcbiAgICAgKiBEZXByZWNhdGVkLiBBZGQgYW4gYGF1dG9zY3JvbGxgIHByb3BlcnR5IHRvIHRoZSBvcHRpb25zIG9iamVjdFxuICAgICAqIHBhc3NlZCB0byBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSBvciBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBpbnN0ZWFkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZHJhZ2dpbmcgYW5kIHJlc2l6aW5nIG5lYXIgdGhlIGVkZ2VzIG9mIHRoZVxuICAgICAqIHdpbmRvdy9jb250YWluZXIgdHJpZ2dlciBhdXRvU2Nyb2xsIGZvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAqXG4gICAgID0gKG9iamVjdCkgT2JqZWN0IHdpdGggYXV0b1Njcm9sbCBwcm9wZXJ0aWVzXG4gICAgICpcbiAgICAgKiBvclxuICAgICAqXG4gICAgIC0gb3B0aW9ucyAob2JqZWN0IHwgYm9vbGVhbikgI29wdGlvbmFsXG4gICAgICogb3B0aW9ucyBjYW4gYmU6XG4gICAgICogLSBhbiBvYmplY3Qgd2l0aCBtYXJnaW4sIGRpc3RhbmNlIGFuZCBpbnRlcnZhbCBwcm9wZXJ0aWVzLFxuICAgICAqIC0gdHJ1ZSBvciBmYWxzZSB0byBlbmFibGUgb3IgZGlzYWJsZSBhdXRvU2Nyb2xsIG9yXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIGF1dG9TY3JvbGw6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgIGlmIChpc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgb3B0aW9ucyA9IGV4dGVuZCh7IGFjdGlvbnM6IFsnZHJhZycsICdyZXNpemUnXX0sIG9wdGlvbnMpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgb3B0aW9ucyA9IHsgYWN0aW9uczogWydkcmFnJywgJ3Jlc2l6ZSddLCBlbmFibGVkOiBvcHRpb25zIH07XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5zZXRPcHRpb25zKCdhdXRvU2Nyb2xsJywgb3B0aW9ucyk7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuc25hcFxuICAgICBbIG1ldGhvZCBdXG4gICAgICoqXG4gICAgICogRGVwcmVjYXRlZC4gQWRkIGEgYHNuYXBgIHByb3BlcnR5IHRvIHRoZSBvcHRpb25zIG9iamVjdCBwYXNzZWRcbiAgICAgKiB0byBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSBvciBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBpbnN0ZWFkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIGlmIGFuZCBob3cgYWN0aW9uIGNvb3JkaW5hdGVzIGFyZSBzbmFwcGVkLiBCeVxuICAgICAqIGRlZmF1bHQsIHNuYXBwaW5nIGlzIHJlbGF0aXZlIHRvIHRoZSBwb2ludGVyIGNvb3JkaW5hdGVzLiBZb3UgY2FuXG4gICAgICogY2hhbmdlIHRoaXMgYnkgc2V0dGluZyB0aGVcbiAgICAgKiBbYGVsZW1lbnRPcmlnaW5gXShodHRwczovL2dpdGh1Yi5jb20vdGF5ZS9pbnRlcmFjdC5qcy9wdWxsLzcyKS5cbiAgICAgKipcbiAgICAgPSAoYm9vbGVhbiB8IG9iamVjdCkgYGZhbHNlYCBpZiBzbmFwIGlzIGRpc2FibGVkOyBvYmplY3Qgd2l0aCBzbmFwIHByb3BlcnRpZXMgaWYgc25hcCBpcyBlbmFibGVkXG4gICAgICoqXG4gICAgICogb3JcbiAgICAgKipcbiAgICAgLSBvcHRpb25zIChvYmplY3QgfCBib29sZWFuIHwgbnVsbCkgI29wdGlvbmFsXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgPiBVc2FnZVxuICAgICB8IGludGVyYWN0KGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJyN0aGluZycpKS5zbmFwKHtcbiAgICAgfCAgICAgdGFyZ2V0czogW1xuICAgICB8ICAgICAgICAgLy8gc25hcCB0byB0aGlzIHNwZWNpZmljIHBvaW50XG4gICAgIHwgICAgICAgICB7XG4gICAgIHwgICAgICAgICAgICAgeDogMTAwLFxuICAgICB8ICAgICAgICAgICAgIHk6IDEwMCxcbiAgICAgfCAgICAgICAgICAgICByYW5nZTogMjVcbiAgICAgfCAgICAgICAgIH0sXG4gICAgIHwgICAgICAgICAvLyBnaXZlIHRoaXMgZnVuY3Rpb24gdGhlIHggYW5kIHkgcGFnZSBjb29yZHMgYW5kIHNuYXAgdG8gdGhlIG9iamVjdCByZXR1cm5lZFxuICAgICB8ICAgICAgICAgZnVuY3Rpb24gKHgsIHkpIHtcbiAgICAgfCAgICAgICAgICAgICByZXR1cm4ge1xuICAgICB8ICAgICAgICAgICAgICAgICB4OiB4LFxuICAgICB8ICAgICAgICAgICAgICAgICB5OiAoNzUgKyA1MCAqIE1hdGguc2luKHggKiAwLjA0KSksXG4gICAgIHwgICAgICAgICAgICAgICAgIHJhbmdlOiA0MFxuICAgICB8ICAgICAgICAgICAgIH07XG4gICAgIHwgICAgICAgICB9LFxuICAgICB8ICAgICAgICAgLy8gY3JlYXRlIGEgZnVuY3Rpb24gdGhhdCBzbmFwcyB0byBhIGdyaWRcbiAgICAgfCAgICAgICAgIGludGVyYWN0LmNyZWF0ZVNuYXBHcmlkKHtcbiAgICAgfCAgICAgICAgICAgICB4OiA1MCxcbiAgICAgfCAgICAgICAgICAgICB5OiA1MCxcbiAgICAgfCAgICAgICAgICAgICByYW5nZTogMTAsICAgICAgICAgICAgICAvLyBvcHRpb25hbFxuICAgICB8ICAgICAgICAgICAgIG9mZnNldDogeyB4OiA1LCB5OiAxMCB9IC8vIG9wdGlvbmFsXG4gICAgIHwgICAgICAgICB9KVxuICAgICB8ICAgICBdLFxuICAgICB8ICAgICAvLyBkbyBub3Qgc25hcCBkdXJpbmcgbm9ybWFsIG1vdmVtZW50LlxuICAgICB8ICAgICAvLyBJbnN0ZWFkLCB0cmlnZ2VyIG9ubHkgb25lIHNuYXBwZWQgbW92ZSBldmVudFxuICAgICB8ICAgICAvLyBpbW1lZGlhdGVseSBiZWZvcmUgdGhlIGVuZCBldmVudC5cbiAgICAgfCAgICAgZW5kT25seTogdHJ1ZSxcbiAgICAgfFxuICAgICB8ICAgICByZWxhdGl2ZVBvaW50czogW1xuICAgICB8ICAgICAgICAgeyB4OiAwLCB5OiAwIH0sICAvLyBzbmFwIHJlbGF0aXZlIHRvIHRoZSB0b3AgbGVmdCBvZiB0aGUgZWxlbWVudFxuICAgICB8ICAgICAgICAgeyB4OiAxLCB5OiAxIH0sICAvLyBhbmQgYWxzbyB0byB0aGUgYm90dG9tIHJpZ2h0XG4gICAgIHwgICAgIF0sXG4gICAgIHxcbiAgICAgfCAgICAgLy8gb2Zmc2V0IHRoZSBzbmFwIHRhcmdldCBjb29yZGluYXRlc1xuICAgICB8ICAgICAvLyBjYW4gYmUgYW4gb2JqZWN0IHdpdGggeC95IG9yICdzdGFydENvb3JkcydcbiAgICAgfCAgICAgb2Zmc2V0OiB7IHg6IDUwLCB5OiA1MCB9XG4gICAgIHwgICB9XG4gICAgIHwgfSk7XG4gICAgIFxcKi9cbiAgICBzbmFwOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICB2YXIgcmV0ID0gdGhpcy5zZXRPcHRpb25zKCdzbmFwJywgb3B0aW9ucyk7XG5cbiAgICAgICAgaWYgKHJldCA9PT0gdGhpcykgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgIHJldHVybiByZXQuZHJhZztcbiAgICB9LFxuXG4gICAgc2V0T3B0aW9uczogZnVuY3Rpb24gKG9wdGlvbiwgb3B0aW9ucykge1xuICAgICAgICB2YXIgYWN0aW9ucyA9IG9wdGlvbnMgJiYgaXNBcnJheShvcHRpb25zLmFjdGlvbnMpXG4gICAgICAgICAgICA/IG9wdGlvbnMuYWN0aW9uc1xuICAgICAgICAgICAgOiBbJ2RyYWcnXTtcblxuICAgICAgICB2YXIgaTtcblxuICAgICAgICBpZiAoaXNPYmplY3Qob3B0aW9ucykgfHwgaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgYWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHZhciBhY3Rpb24gPSAvcmVzaXplLy50ZXN0KGFjdGlvbnNbaV0pPyAncmVzaXplJyA6IGFjdGlvbnNbaV07XG5cbiAgICAgICAgICAgICAgICBpZiAoIWlzT2JqZWN0KHRoaXMub3B0aW9uc1thY3Rpb25dKSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICAgICAgdmFyIHRoaXNPcHRpb24gPSB0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dO1xuXG4gICAgICAgICAgICAgICAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgICAgIGV4dGVuZCh0aGlzT3B0aW9uLCBvcHRpb25zKTtcbiAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi5lbmFibGVkID0gb3B0aW9ucy5lbmFibGVkID09PSBmYWxzZT8gZmFsc2U6IHRydWU7XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKG9wdGlvbiA9PT0gJ3NuYXAnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAodGhpc09wdGlvbi5tb2RlID09PSAnZ3JpZCcpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnRhcmdldHMgPSBbXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0LmNyZWF0ZVNuYXBHcmlkKGV4dGVuZCh7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQ6IHRoaXNPcHRpb24uZ3JpZE9mZnNldCB8fCB7IHg6IDAsIHk6IDAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LCB0aGlzT3B0aW9uLmdyaWQgfHwge30pKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIF07XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICBlbHNlIGlmICh0aGlzT3B0aW9uLm1vZGUgPT09ICdhbmNob3InKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi50YXJnZXRzID0gdGhpc09wdGlvbi5hbmNob3JzO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSBpZiAodGhpc09wdGlvbi5tb2RlID09PSAncGF0aCcpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnRhcmdldHMgPSB0aGlzT3B0aW9uLnBhdGhzO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoJ2VsZW1lbnRPcmlnaW4nIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnJlbGF0aXZlUG9pbnRzID0gW29wdGlvbnMuZWxlbWVudE9yaWdpbl07XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAoaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXNPcHRpb24uZW5hYmxlZCA9IG9wdGlvbnM7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciByZXQgPSB7fSxcbiAgICAgICAgICAgIGFsbEFjdGlvbnMgPSBbJ2RyYWcnLCAncmVzaXplJywgJ2dlc3R1cmUnXTtcblxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgYWxsQWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaWYgKG9wdGlvbiBpbiBkZWZhdWx0T3B0aW9uc1thbGxBY3Rpb25zW2ldXSkge1xuICAgICAgICAgICAgICAgIHJldFthbGxBY3Rpb25zW2ldXSA9IHRoaXMub3B0aW9uc1thbGxBY3Rpb25zW2ldXVtvcHRpb25dO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHJldDtcbiAgICB9LFxuXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmluZXJ0aWFcbiAgICAgWyBtZXRob2QgXVxuICAgICAqKlxuICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhbiBgaW5lcnRpYWAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZFxuICAgICAqIHRvIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIG9yIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWQuXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgaWYgYW5kIGhvdyBldmVudHMgY29udGludWUgdG8gcnVuIGFmdGVyIHRoZSBwb2ludGVyIGlzIHJlbGVhc2VkXG4gICAgICoqXG4gICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIGBmYWxzZWAgaWYgaW5lcnRpYSBpcyBkaXNhYmxlZDsgYG9iamVjdGAgd2l0aCBpbmVydGlhIHByb3BlcnRpZXMgaWYgaW5lcnRpYSBpcyBlbmFibGVkXG4gICAgICoqXG4gICAgICogb3JcbiAgICAgKipcbiAgICAgLSBvcHRpb25zIChvYmplY3QgfCBib29sZWFuIHwgbnVsbCkgI29wdGlvbmFsXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgPiBVc2FnZVxuICAgICB8IC8vIGVuYWJsZSBhbmQgdXNlIGRlZmF1bHQgc2V0dGluZ3NcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pbmVydGlhKHRydWUpO1xuICAgICB8XG4gICAgIHwgLy8gZW5hYmxlIGFuZCB1c2UgY3VzdG9tIHNldHRpbmdzXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYSh7XG4gICAgIHwgICAgIC8vIHZhbHVlIGdyZWF0ZXIgdGhhbiAwXG4gICAgIHwgICAgIC8vIGhpZ2ggdmFsdWVzIHNsb3cgdGhlIG9iamVjdCBkb3duIG1vcmUgcXVpY2tseVxuICAgICB8ICAgICByZXNpc3RhbmNlICAgICA6IDE2LFxuICAgICB8XG4gICAgIHwgICAgIC8vIHRoZSBtaW5pbXVtIGxhdW5jaCBzcGVlZCAocGl4ZWxzIHBlciBzZWNvbmQpIHRoYXQgcmVzdWx0cyBpbiBpbmVydGlhIHN0YXJ0XG4gICAgIHwgICAgIG1pblNwZWVkICAgICAgIDogMjAwLFxuICAgICB8XG4gICAgIHwgICAgIC8vIGluZXJ0aWEgd2lsbCBzdG9wIHdoZW4gdGhlIG9iamVjdCBzbG93cyBkb3duIHRvIHRoaXMgc3BlZWRcbiAgICAgfCAgICAgZW5kU3BlZWQgICAgICAgOiAyMCxcbiAgICAgfFxuICAgICB8ICAgICAvLyBib29sZWFuOyBzaG91bGQgYWN0aW9ucyBiZSByZXN1bWVkIHdoZW4gdGhlIHBvaW50ZXIgZ29lcyBkb3duIGR1cmluZyBpbmVydGlhXG4gICAgIHwgICAgIGFsbG93UmVzdW1lICAgIDogdHJ1ZSxcbiAgICAgfFxuICAgICB8ICAgICAvLyBib29sZWFuOyBzaG91bGQgdGhlIGp1bXAgd2hlbiByZXN1bWluZyBmcm9tIGluZXJ0aWEgYmUgaWdub3JlZCBpbiBldmVudC5keC9keVxuICAgICB8ICAgICB6ZXJvUmVzdW1lRGVsdGE6IGZhbHNlLFxuICAgICB8XG4gICAgIHwgICAgIC8vIGlmIHNuYXAvcmVzdHJpY3QgYXJlIHNldCB0byBiZSBlbmRPbmx5IGFuZCBpbmVydGlhIGlzIGVuYWJsZWQsIHJlbGVhc2luZ1xuICAgICB8ICAgICAvLyB0aGUgcG9pbnRlciB3aXRob3V0IHRyaWdnZXJpbmcgaW5lcnRpYSB3aWxsIGFuaW1hdGUgZnJvbSB0aGUgcmVsZWFzZVxuICAgICB8ICAgICAvLyBwb2ludCB0byB0aGUgc25hcGVkL3Jlc3RyaWN0ZWQgcG9pbnQgaW4gdGhlIGdpdmVuIGFtb3VudCBvZiB0aW1lIChtcylcbiAgICAgfCAgICAgc21vb3RoRW5kRHVyYXRpb246IDMwMCxcbiAgICAgfFxuICAgICB8ICAgICAvLyBhbiBhcnJheSBvZiBhY3Rpb24gdHlwZXMgdGhhdCBjYW4gaGF2ZSBpbmVydGlhIChubyBnZXN0dXJlKVxuICAgICB8ICAgICBhY3Rpb25zICAgICAgICA6IFsnZHJhZycsICdyZXNpemUnXVxuICAgICB8IH0pO1xuICAgICB8XG4gICAgIHwgLy8gcmVzZXQgY3VzdG9tIHNldHRpbmdzIGFuZCB1c2UgYWxsIGRlZmF1bHRzXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYShudWxsKTtcbiAgICAgXFwqL1xuICAgIGluZXJ0aWE6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgIHZhciByZXQgPSB0aGlzLnNldE9wdGlvbnMoJ2luZXJ0aWEnLCBvcHRpb25zKTtcblxuICAgICAgICBpZiAocmV0ID09PSB0aGlzKSB7IHJldHVybiB0aGlzOyB9XG5cbiAgICAgICAgcmV0dXJuIHJldC5kcmFnO1xuICAgIH0sXG5cbiAgICBnZXRBY3Rpb246IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIGFjdGlvbiA9IHRoaXMuZGVmYXVsdEFjdGlvbkNoZWNrZXIocG9pbnRlciwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpO1xuXG4gICAgICAgIGlmICh0aGlzLm9wdGlvbnMuYWN0aW9uQ2hlY2tlcikge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyKHBvaW50ZXIsIGV2ZW50LCBhY3Rpb24sIHRoaXMsIGVsZW1lbnQsIGludGVyYWN0aW9uKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBhY3Rpb247XG4gICAgfSxcblxuICAgIGRlZmF1bHRBY3Rpb25DaGVja2VyOiBkZWZhdWx0QWN0aW9uQ2hlY2tlcixcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuYWN0aW9uQ2hlY2tlclxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBHZXRzIG9yIHNldHMgdGhlIGZ1bmN0aW9uIHVzZWQgdG8gY2hlY2sgYWN0aW9uIHRvIGJlIHBlcmZvcm1lZCBvblxuICAgICAqIHBvaW50ZXJEb3duXG4gICAgICpcbiAgICAgLSBjaGVja2VyIChmdW5jdGlvbiB8IG51bGwpICNvcHRpb25hbCBBIGZ1bmN0aW9uIHdoaWNoIHRha2VzIGEgcG9pbnRlciBldmVudCwgZGVmYXVsdEFjdGlvbiBzdHJpbmcsIGludGVyYWN0YWJsZSwgZWxlbWVudCBhbmQgaW50ZXJhY3Rpb24gYXMgcGFyYW1ldGVycyBhbmQgcmV0dXJucyBhbiBvYmplY3Qgd2l0aCBuYW1lIHByb3BlcnR5ICdkcmFnJyAncmVzaXplJyBvciAnZ2VzdHVyZScgYW5kIG9wdGlvbmFsbHkgYW4gYGVkZ2VzYCBvYmplY3Qgd2l0aCBib29sZWFuICd0b3AnLCAnbGVmdCcsICdib3R0b20nIGFuZCByaWdodCBwcm9wcy5cbiAgICAgPSAoRnVuY3Rpb24gfCBJbnRlcmFjdGFibGUpIFRoZSBjaGVja2VyIGZ1bmN0aW9uIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICpcbiAgICAgfCBpbnRlcmFjdCgnLnJlc2l6ZS1kcmFnJylcbiAgICAgfCAgIC5yZXNpemFibGUodHJ1ZSlcbiAgICAgfCAgIC5kcmFnZ2FibGUodHJ1ZSlcbiAgICAgfCAgIC5hY3Rpb25DaGVja2VyKGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGludGVyYWN0aW9uKSB7XG4gICAgIHxcbiAgICAgfCAgIGlmIChpbnRlcmFjdC5tYXRjaGVzU2VsZWN0b3IoZXZlbnQudGFyZ2V0LCAnLmRyYWctaGFuZGxlJykge1xuICAgICB8ICAgICAvLyBmb3JjZSBkcmFnIHdpdGggaGFuZGxlIHRhcmdldFxuICAgICB8ICAgICBhY3Rpb24ubmFtZSA9IGRyYWc7XG4gICAgIHwgICB9XG4gICAgIHwgICBlbHNlIHtcbiAgICAgfCAgICAgLy8gcmVzaXplIGZyb20gdGhlIHRvcCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgfCAgICAgYWN0aW9uLm5hbWUgID0gJ3Jlc2l6ZSc7XG4gICAgIHwgICAgIGFjdGlvbi5lZGdlcyA9IHsgdG9wOiB0cnVlLCByaWdodDogdHJ1ZSB9O1xuICAgICB8ICAgfVxuICAgICB8XG4gICAgIHwgICByZXR1cm4gYWN0aW9uO1xuICAgICB8IH0pO1xuICAgICBcXCovXG4gICAgYWN0aW9uQ2hlY2tlcjogZnVuY3Rpb24gKGNoZWNrZXIpIHtcbiAgICAgICAgaWYgKGlzRnVuY3Rpb24oY2hlY2tlcikpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyID0gY2hlY2tlcjtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoY2hlY2tlciA9PT0gbnVsbCkge1xuICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuYWN0aW9uQ2hlY2tlcjtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5nZXRSZWN0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFRoZSBkZWZhdWx0IGZ1bmN0aW9uIHRvIGdldCBhbiBJbnRlcmFjdGFibGVzIGJvdW5kaW5nIHJlY3QuIENhbiBiZVxuICAgICAqIG92ZXJyaWRkZW4gdXNpbmcgQEludGVyYWN0YWJsZS5yZWN0Q2hlY2tlci5cbiAgICAgKlxuICAgICAtIGVsZW1lbnQgKEVsZW1lbnQpICNvcHRpb25hbCBUaGUgZWxlbWVudCB0byBtZWFzdXJlLlxuICAgICA9IChvYmplY3QpIFRoZSBvYmplY3QncyBib3VuZGluZyByZWN0YW5nbGUuXG4gICAgIG8ge1xuICAgICBvICAgICB0b3AgICA6IDAsXG4gICAgIG8gICAgIGxlZnQgIDogMCxcbiAgICAgbyAgICAgYm90dG9tOiAwLFxuICAgICBvICAgICByaWdodCA6IDAsXG4gICAgIG8gICAgIHdpZHRoIDogMCxcbiAgICAgbyAgICAgaGVpZ2h0OiAwXG4gICAgIG8gfVxuICAgICBcXCovXG4gICAgZ2V0UmVjdDogZnVuY3Rpb24gcmVjdENoZWNrIChlbGVtZW50KSB7XG4gICAgICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IHRoaXMuX2VsZW1lbnQ7XG5cbiAgICAgICAgaWYgKHRoaXMuc2VsZWN0b3IgJiYgIShpc0VsZW1lbnQoZWxlbWVudCkpKSB7XG4gICAgICAgICAgICBlbGVtZW50ID0gdGhpcy5fY29udGV4dC5xdWVyeVNlbGVjdG9yKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGdldEVsZW1lbnRSZWN0KGVsZW1lbnQpO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLnJlY3RDaGVja2VyXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgZnVuY3Rpb24gdXNlZCB0byBjYWxjdWxhdGUgdGhlIGludGVyYWN0YWJsZSdzXG4gICAgICogZWxlbWVudCdzIHJlY3RhbmdsZVxuICAgICAqXG4gICAgIC0gY2hlY2tlciAoZnVuY3Rpb24pICNvcHRpb25hbCBBIGZ1bmN0aW9uIHdoaWNoIHJldHVybnMgdGhpcyBJbnRlcmFjdGFibGUncyBib3VuZGluZyByZWN0YW5nbGUuIFNlZSBASW50ZXJhY3RhYmxlLmdldFJlY3RcbiAgICAgPSAoZnVuY3Rpb24gfCBvYmplY3QpIFRoZSBjaGVja2VyIGZ1bmN0aW9uIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIFxcKi9cbiAgICByZWN0Q2hlY2tlcjogZnVuY3Rpb24gKGNoZWNrZXIpIHtcbiAgICAgICAgaWYgKGlzRnVuY3Rpb24oY2hlY2tlcikpIHtcbiAgICAgICAgICAgIHRoaXMuZ2V0UmVjdCA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgIGRlbGV0ZSB0aGlzLm9wdGlvbnMuZ2V0UmVjdDtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5nZXRSZWN0O1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLnN0eWxlQ3Vyc29yXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIHRoZSBhY3Rpb24gdGhhdCB3b3VsZCBiZSBwZXJmb3JtZWQgd2hlbiB0aGVcbiAgICAgKiBtb3VzZSBvbiB0aGUgZWxlbWVudCBhcmUgY2hlY2tlZCBvbiBgbW91c2Vtb3ZlYCBzbyB0aGF0IHRoZSBjdXJzb3JcbiAgICAgKiBtYXkgYmUgc3R5bGVkIGFwcHJvcHJpYXRlbHlcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChib29sZWFuKSAjb3B0aW9uYWxcbiAgICAgPSAoYm9vbGVhbiB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgc3R5bGVDdXJzb3I6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoaXNCb29sKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuc3R5bGVDdXJzb3I7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUucHJldmVudERlZmF1bHRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgdG8gcHJldmVudCB0aGUgYnJvd3NlcidzIGRlZmF1bHQgYmVoYXZpb3VyXG4gICAgICogaW4gcmVzcG9uc2UgdG8gcG9pbnRlciBldmVudHMuIENhbiBiZSBzZXQgdG86XG4gICAgICogIC0gYCdhbHdheXMnYCB0byBhbHdheXMgcHJldmVudFxuICAgICAqICAtIGAnbmV2ZXInYCB0byBuZXZlciBwcmV2ZW50XG4gICAgICogIC0gYCdhdXRvJ2AgdG8gbGV0IGludGVyYWN0LmpzIHRyeSB0byBkZXRlcm1pbmUgd2hhdCB3b3VsZCBiZSBiZXN0XG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoc3RyaW5nKSAjb3B0aW9uYWwgYHRydWVgLCBgZmFsc2VgIG9yIGAnYXV0bydgXG4gICAgID0gKHN0cmluZyB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgcHJldmVudERlZmF1bHQ6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoL14oYWx3YXlzfG5ldmVyfGF1dG8pJC8udGVzdChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5wcmV2ZW50RGVmYXVsdCA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNCb29sKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLnByZXZlbnREZWZhdWx0ID0gbmV3VmFsdWU/ICdhbHdheXMnIDogJ25ldmVyJztcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5wcmV2ZW50RGVmYXVsdDtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5vcmlnaW5cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogR2V0cyBvciBzZXRzIHRoZSBvcmlnaW4gb2YgdGhlIEludGVyYWN0YWJsZSdzIGVsZW1lbnQuICBUaGUgeCBhbmQgeVxuICAgICAqIG9mIHRoZSBvcmlnaW4gd2lsbCBiZSBzdWJ0cmFjdGVkIGZyb20gYWN0aW9uIGV2ZW50IGNvb3JkaW5hdGVzLlxuICAgICAqXG4gICAgIC0gb3JpZ2luIChvYmplY3QgfCBzdHJpbmcpICNvcHRpb25hbCBBbiBvYmplY3QgZWcuIHsgeDogMCwgeTogMCB9IG9yIHN0cmluZyAncGFyZW50JywgJ3NlbGYnIG9yIGFueSBDU1Mgc2VsZWN0b3JcbiAgICAgKiBPUlxuICAgICAtIG9yaWdpbiAoRWxlbWVudCkgI29wdGlvbmFsIEFuIEhUTUwgb3IgU1ZHIEVsZW1lbnQgd2hvc2UgcmVjdCB3aWxsIGJlIHVzZWRcbiAgICAgKipcbiAgICAgPSAob2JqZWN0KSBUaGUgY3VycmVudCBvcmlnaW4gb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIG9yaWdpbjogZnVuY3Rpb24gKG5ld1ZhbHVlKSB7XG4gICAgICAgIGlmICh0cnlTZWxlY3RvcihuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5vcmlnaW4gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGlzT2JqZWN0KG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLm9yaWdpbiA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLm9yaWdpbjtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5kZWx0YVNvdXJjZVxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgdGhlIG1vdXNlIGNvb3JkaW5hdGUgdHlwZXMgdXNlZCB0byBjYWxjdWxhdGUgdGhlXG4gICAgICogbW92ZW1lbnQgb2YgdGhlIHBvaW50ZXIuXG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoc3RyaW5nKSAjb3B0aW9uYWwgVXNlICdjbGllbnQnIGlmIHlvdSB3aWxsIGJlIHNjcm9sbGluZyB3aGlsZSBpbnRlcmFjdGluZzsgVXNlICdwYWdlJyBpZiB5b3Ugd2FudCBhdXRvU2Nyb2xsIHRvIHdvcmtcbiAgICAgPSAoc3RyaW5nIHwgb2JqZWN0KSBUaGUgY3VycmVudCBkZWx0YVNvdXJjZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgZGVsdGFTb3VyY2U6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAobmV3VmFsdWUgPT09ICdwYWdlJyB8fCBuZXdWYWx1ZSA9PT0gJ2NsaWVudCcpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kZWx0YVNvdXJjZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZGVsdGFTb3VyY2U7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUucmVzdHJpY3RcbiAgICAgWyBtZXRob2QgXVxuICAgICAqKlxuICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhIGByZXN0cmljdGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZCB0b1xuICAgICAqIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlLCBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBvciBASW50ZXJhY3RhYmxlLmdlc3R1cmFibGUgaW5zdGVhZC5cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgcmVjdGFuZ2xlcyB3aXRoaW4gd2hpY2ggYWN0aW9ucyBvbiB0aGlzXG4gICAgICogaW50ZXJhY3RhYmxlIChhZnRlciBzbmFwIGNhbGN1bGF0aW9ucykgYXJlIHJlc3RyaWN0ZWQuIEJ5IGRlZmF1bHQsXG4gICAgICogcmVzdHJpY3RpbmcgaXMgcmVsYXRpdmUgdG8gdGhlIHBvaW50ZXIgY29vcmRpbmF0ZXMuIFlvdSBjYW4gY2hhbmdlXG4gICAgICogdGhpcyBieSBzZXR0aW5nIHRoZVxuICAgICAqIFtgZWxlbWVudFJlY3RgXShodHRwczovL2dpdGh1Yi5jb20vdGF5ZS9pbnRlcmFjdC5qcy9wdWxsLzcyKS5cbiAgICAgKipcbiAgICAgLSBvcHRpb25zIChvYmplY3QpICNvcHRpb25hbCBhbiBvYmplY3Qgd2l0aCBrZXlzIGRyYWcsIHJlc2l6ZSwgYW5kL29yIGdlc3R1cmUgd2hvc2UgdmFsdWVzIGFyZSByZWN0cywgRWxlbWVudHMsIENTUyBzZWxlY3RvcnMsIG9yICdwYXJlbnQnIG9yICdzZWxmJ1xuICAgICA9IChvYmplY3QpIFRoZSBjdXJyZW50IHJlc3RyaWN0aW9ucyBvYmplY3Qgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgKipcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5yZXN0cmljdCh7XG4gICAgIHwgICAgIC8vIHRoZSByZWN0IHdpbGwgYmUgYGludGVyYWN0LmdldEVsZW1lbnRSZWN0KGVsZW1lbnQucGFyZW50Tm9kZSlgXG4gICAgIHwgICAgIGRyYWc6IGVsZW1lbnQucGFyZW50Tm9kZSxcbiAgICAgfFxuICAgICB8ICAgICAvLyB4IGFuZCB5IGFyZSByZWxhdGl2ZSB0byB0aGUgdGhlIGludGVyYWN0YWJsZSdzIG9yaWdpblxuICAgICB8ICAgICByZXNpemU6IHsgeDogMTAwLCB5OiAxMDAsIHdpZHRoOiAyMDAsIGhlaWdodDogMjAwIH1cbiAgICAgfCB9KVxuICAgICB8XG4gICAgIHwgaW50ZXJhY3QoJy5kcmFnZ2FibGUnKS5yZXN0cmljdCh7XG4gICAgIHwgICAgIC8vIHRoZSByZWN0IHdpbGwgYmUgdGhlIHNlbGVjdGVkIGVsZW1lbnQncyBwYXJlbnRcbiAgICAgfCAgICAgZHJhZzogJ3BhcmVudCcsXG4gICAgIHxcbiAgICAgfCAgICAgLy8gZG8gbm90IHJlc3RyaWN0IGR1cmluZyBub3JtYWwgbW92ZW1lbnQuXG4gICAgIHwgICAgIC8vIEluc3RlYWQsIHRyaWdnZXIgb25seSBvbmUgcmVzdHJpY3RlZCBtb3ZlIGV2ZW50XG4gICAgIHwgICAgIC8vIGltbWVkaWF0ZWx5IGJlZm9yZSB0aGUgZW5kIGV2ZW50LlxuICAgICB8ICAgICBlbmRPbmx5OiB0cnVlLFxuICAgICB8XG4gICAgIHwgICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL3B1bGwvNzIjaXNzdWUtNDE4MTM0OTNcbiAgICAgfCAgICAgZWxlbWVudFJlY3Q6IHsgdG9wOiAwLCBsZWZ0OiAwLCBib3R0b206IDEsIHJpZ2h0OiAxIH1cbiAgICAgfCB9KTtcbiAgICAgXFwqL1xuICAgIHJlc3RyaWN0OiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoIWlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5zZXRPcHRpb25zKCdyZXN0cmljdCcsIG9wdGlvbnMpO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIGFjdGlvbnMgPSBbJ2RyYWcnLCAncmVzaXplJywgJ2dlc3R1cmUnXSxcbiAgICAgICAgICAgIHJldDtcblxuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHZhciBhY3Rpb24gPSBhY3Rpb25zW2ldO1xuXG4gICAgICAgICAgICBpZiAoYWN0aW9uIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICB2YXIgcGVyQWN0aW9uID0gZXh0ZW5kKHtcbiAgICAgICAgICAgICAgICAgICAgYWN0aW9uczogW2FjdGlvbl0sXG4gICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uOiBvcHRpb25zW2FjdGlvbl1cbiAgICAgICAgICAgICAgICB9LCBvcHRpb25zKTtcblxuICAgICAgICAgICAgICAgIHJldCA9IHRoaXMuc2V0T3B0aW9ucygncmVzdHJpY3QnLCBwZXJBY3Rpb24pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHJldDtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5jb250ZXh0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgdGhlIHNlbGVjdG9yIGNvbnRleHQgTm9kZSBvZiB0aGUgSW50ZXJhY3RhYmxlLiBUaGUgZGVmYXVsdCBpcyBgd2luZG93LmRvY3VtZW50YC5cbiAgICAgKlxuICAgICA9IChOb2RlKSBUaGUgY29udGV4dCBOb2RlIG9mIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICoqXG4gICAgIFxcKi9cbiAgICBjb250ZXh0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jb250ZXh0O1xuICAgIH0sXG5cbiAgICBfY29udGV4dDogZG9jdW1lbnQsXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmlnbm9yZUZyb21cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogSWYgdGhlIHRhcmdldCBvZiB0aGUgYG1vdXNlZG93bmAsIGBwb2ludGVyZG93bmAgb3IgYHRvdWNoc3RhcnRgXG4gICAgICogZXZlbnQgb3IgYW55IG9mIGl0J3MgcGFyZW50cyBtYXRjaCB0aGUgZ2l2ZW4gQ1NTIHNlbGVjdG9yIG9yXG4gICAgICogRWxlbWVudCwgbm8gZHJhZy9yZXNpemUvZ2VzdHVyZSBpcyBzdGFydGVkLlxuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKHN0cmluZyB8IEVsZW1lbnQgfCBudWxsKSAjb3B0aW9uYWwgYSBDU1Mgc2VsZWN0b3Igc3RyaW5nLCBhbiBFbGVtZW50IG9yIGBudWxsYCB0byBub3QgaWdub3JlIGFueSBlbGVtZW50c1xuICAgICA9IChzdHJpbmcgfCBFbGVtZW50IHwgb2JqZWN0KSBUaGUgY3VycmVudCBpZ25vcmVGcm9tIHZhbHVlIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICoqXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCwgeyBpZ25vcmVGcm9tOiBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbm8tYWN0aW9uJykgfSk7XG4gICAgIHwgLy8gb3JcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pZ25vcmVGcm9tKCdpbnB1dCwgdGV4dGFyZWEsIGEnKTtcbiAgICAgXFwqL1xuICAgIGlnbm9yZUZyb206IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAodHJ5U2VsZWN0b3IobmV3VmFsdWUpKSB7ICAgICAgICAgICAgLy8gQ1NTIHNlbGVjdG9yIHRvIG1hdGNoIGV2ZW50LnRhcmdldFxuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmlnbm9yZUZyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGlzRWxlbWVudChuZXdWYWx1ZSkpIHsgICAgICAgICAgICAgIC8vIHNwZWNpZmljIGVsZW1lbnRcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5pZ25vcmVGcm9tID0gbmV3VmFsdWU7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuaWdub3JlRnJvbTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5hbGxvd0Zyb21cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogQSBkcmFnL3Jlc2l6ZS9nZXN0dXJlIGlzIHN0YXJ0ZWQgb25seSBJZiB0aGUgdGFyZ2V0IG9mIHRoZVxuICAgICAqIGBtb3VzZWRvd25gLCBgcG9pbnRlcmRvd25gIG9yIGB0b3VjaHN0YXJ0YCBldmVudCBvciBhbnkgb2YgaXQnc1xuICAgICAqIHBhcmVudHMgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvciBFbGVtZW50LlxuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKHN0cmluZyB8IEVsZW1lbnQgfCBudWxsKSAjb3B0aW9uYWwgYSBDU1Mgc2VsZWN0b3Igc3RyaW5nLCBhbiBFbGVtZW50IG9yIGBudWxsYCB0byBhbGxvdyBmcm9tIGFueSBlbGVtZW50XG4gICAgID0gKHN0cmluZyB8IEVsZW1lbnQgfCBvYmplY3QpIFRoZSBjdXJyZW50IGFsbG93RnJvbSB2YWx1ZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAqKlxuICAgICB8IGludGVyYWN0KGVsZW1lbnQsIHsgYWxsb3dGcm9tOiBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZHJhZy1oYW5kbGUnKSB9KTtcbiAgICAgfCAvLyBvclxuICAgICB8IGludGVyYWN0KGVsZW1lbnQpLmFsbG93RnJvbSgnLmhhbmRsZScpO1xuICAgICBcXCovXG4gICAgYWxsb3dGcm9tOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgaWYgKHRyeVNlbGVjdG9yKG5ld1ZhbHVlKSkgeyAgICAgICAgICAgIC8vIENTUyBzZWxlY3RvciB0byBtYXRjaCBldmVudC50YXJnZXRcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5hbGxvd0Zyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGlzRWxlbWVudChuZXdWYWx1ZSkpIHsgICAgICAgICAgICAgIC8vIHNwZWNpZmljIGVsZW1lbnRcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5hbGxvd0Zyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hbGxvd0Zyb207XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuZWxlbWVudFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBJZiB0aGlzIGlzIG5vdCBhIHNlbGVjdG9yIEludGVyYWN0YWJsZSwgaXQgcmV0dXJucyB0aGUgZWxlbWVudCB0aGlzXG4gICAgICogaW50ZXJhY3RhYmxlIHJlcHJlc2VudHNcbiAgICAgKlxuICAgICA9IChFbGVtZW50KSBIVE1MIC8gU1ZHIEVsZW1lbnRcbiAgICAgXFwqL1xuICAgIGVsZW1lbnQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2VsZW1lbnQ7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuZmlyZVxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBDYWxscyBsaXN0ZW5lcnMgZm9yIHRoZSBnaXZlbiBJbnRlcmFjdEV2ZW50IHR5cGUgYm91bmQgZ2xvYmFsbHlcbiAgICAgKiBhbmQgZGlyZWN0bHkgdG8gdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgKlxuICAgICAtIGlFdmVudCAoSW50ZXJhY3RFdmVudCkgVGhlIEludGVyYWN0RXZlbnQgb2JqZWN0IHRvIGJlIGZpcmVkIG9uIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIGZpcmU6IGZ1bmN0aW9uIChpRXZlbnQpIHtcbiAgICAgICAgaWYgKCEoaUV2ZW50ICYmIGlFdmVudC50eXBlKSB8fCAhY29udGFpbnMoZXZlbnRUeXBlcywgaUV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBsaXN0ZW5lcnMsXG4gICAgICAgICAgICBpLFxuICAgICAgICAgICAgbGVuLFxuICAgICAgICAgICAgb25FdmVudCA9ICdvbicgKyBpRXZlbnQudHlwZSxcbiAgICAgICAgICAgIGZ1bmNOYW1lID0gJyc7XG5cbiAgICAgICAgLy8gSW50ZXJhY3RhYmxlI29uKCkgbGlzdGVuZXJzXG4gICAgICAgIGlmIChpRXZlbnQudHlwZSBpbiB0aGlzLl9pRXZlbnRzKSB7XG4gICAgICAgICAgICBsaXN0ZW5lcnMgPSB0aGlzLl9pRXZlbnRzW2lFdmVudC50eXBlXTtcblxuICAgICAgICAgICAgZm9yIChpID0gMCwgbGVuID0gbGlzdGVuZXJzLmxlbmd0aDsgaSA8IGxlbiAmJiAhaUV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgZnVuY05hbWUgPSBsaXN0ZW5lcnNbaV0ubmFtZTtcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnNbaV0oaUV2ZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGludGVyYWN0YWJsZS5vbmV2ZW50IGxpc3RlbmVyXG4gICAgICAgIGlmIChpc0Z1bmN0aW9uKHRoaXNbb25FdmVudF0pKSB7XG4gICAgICAgICAgICBmdW5jTmFtZSA9IHRoaXNbb25FdmVudF0ubmFtZTtcbiAgICAgICAgICAgIHRoaXNbb25FdmVudF0oaUV2ZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGludGVyYWN0Lm9uKCkgbGlzdGVuZXJzXG4gICAgICAgIGlmIChpRXZlbnQudHlwZSBpbiBnbG9iYWxFdmVudHMgJiYgKGxpc3RlbmVycyA9IGdsb2JhbEV2ZW50c1tpRXZlbnQudHlwZV0pKSAge1xuXG4gICAgICAgICAgICBmb3IgKGkgPSAwLCBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoOyBpIDwgbGVuICYmICFpRXZlbnQuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkOyBpKyspIHtcbiAgICAgICAgICAgICAgICBmdW5jTmFtZSA9IGxpc3RlbmVyc1tpXS5uYW1lO1xuICAgICAgICAgICAgICAgIGxpc3RlbmVyc1tpXShpRXZlbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUub25cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogQmluZHMgYSBsaXN0ZW5lciBmb3IgYW4gSW50ZXJhY3RFdmVudCBvciBET00gZXZlbnQuXG4gICAgICpcbiAgICAgLSBldmVudFR5cGUgIChzdHJpbmcgfCBhcnJheSB8IG9iamVjdCkgVGhlIHR5cGVzIG9mIGV2ZW50cyB0byBsaXN0ZW4gZm9yXG4gICAgIC0gbGlzdGVuZXIgICAoZnVuY3Rpb24pIFRoZSBmdW5jdGlvbiB0byBiZSBjYWxsZWQgb24gdGhlIGdpdmVuIGV2ZW50KHMpXG4gICAgIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgYWRkRXZlbnRMaXN0ZW5lclxuICAgICA9IChvYmplY3QpIFRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIFxcKi9cbiAgICBvbjogZnVuY3Rpb24gKGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgdmFyIGk7XG5cbiAgICAgICAgaWYgKGlzU3RyaW5nKGV2ZW50VHlwZSkgJiYgZXZlbnRUeXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gZXZlbnRUeXBlLnRyaW0oKS5zcGxpdCgvICsvKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0FycmF5KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBldmVudFR5cGUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uKGV2ZW50VHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc09iamVjdChldmVudFR5cGUpKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBwcm9wIGluIGV2ZW50VHlwZSkge1xuICAgICAgICAgICAgICAgIHRoaXMub24ocHJvcCwgZXZlbnRUeXBlW3Byb3BdLCBsaXN0ZW5lcik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGV2ZW50VHlwZSA9PT0gJ3doZWVsJykge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gd2hlZWxFdmVudDtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGNvbnZlcnQgdG8gYm9vbGVhblxuICAgICAgICB1c2VDYXB0dXJlID0gdXNlQ2FwdHVyZT8gdHJ1ZTogZmFsc2U7XG5cbiAgICAgICAgaWYgKGNvbnRhaW5zKGV2ZW50VHlwZXMsIGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIC8vIGlmIHRoaXMgdHlwZSBvZiBldmVudCB3YXMgbmV2ZXIgYm91bmQgdG8gdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgICAgIGlmICghKGV2ZW50VHlwZSBpbiB0aGlzLl9pRXZlbnRzKSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX2lFdmVudHNbZXZlbnRUeXBlXSA9IFtsaXN0ZW5lcl07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLl9pRXZlbnRzW2V2ZW50VHlwZV0ucHVzaChsaXN0ZW5lcik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gZGVsZWdhdGVkIGV2ZW50IGZvciBzZWxlY3RvclxuICAgICAgICBlbHNlIGlmICh0aGlzLnNlbGVjdG9yKSB7XG4gICAgICAgICAgICBpZiAoIWRlbGVnYXRlZEV2ZW50c1tldmVudFR5cGVdKSB7XG4gICAgICAgICAgICAgICAgZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0gPSB7XG4gICAgICAgICAgICAgICAgICAgIHNlbGVjdG9yczogW10sXG4gICAgICAgICAgICAgICAgICAgIGNvbnRleHRzIDogW10sXG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyczogW11cbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgLy8gYWRkIGRlbGVnYXRlIGxpc3RlbmVyIGZ1bmN0aW9uc1xuICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBkb2N1bWVudHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZChkb2N1bWVudHNbaV0sIGV2ZW50VHlwZSwgZGVsZWdhdGVMaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jdW1lbnRzW2ldLCBldmVudFR5cGUsIGRlbGVnYXRlVXNlQ2FwdHVyZSwgdHJ1ZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgZGVsZWdhdGVkID0gZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0sXG4gICAgICAgICAgICAgICAgaW5kZXg7XG5cbiAgICAgICAgICAgIGZvciAoaW5kZXggPSBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCAtIDE7IGluZGV4ID49IDA7IGluZGV4LS0pIHtcbiAgICAgICAgICAgICAgICBpZiAoZGVsZWdhdGVkLnNlbGVjdG9yc1tpbmRleF0gPT09IHRoaXMuc2VsZWN0b3JcbiAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2luZGV4XSA9PT0gdGhpcy5fY29udGV4dCkge1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbmRleCA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGRlbGVnYXRlZC5zZWxlY3RvcnMubGVuZ3RoO1xuXG4gICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5wdXNoKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5jb250ZXh0cyAucHVzaCh0aGlzLl9jb250ZXh0KTtcbiAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQubGlzdGVuZXJzLnB1c2goW10pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBrZWVwIGxpc3RlbmVyIGFuZCB1c2VDYXB0dXJlIGZsYWdcbiAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnNbaW5kZXhdLnB1c2goW2xpc3RlbmVyLCB1c2VDYXB0dXJlXSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUub2ZmXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJlbW92ZXMgYW4gSW50ZXJhY3RFdmVudCBvciBET00gZXZlbnQgbGlzdGVuZXJcbiAgICAgKlxuICAgICAtIGV2ZW50VHlwZSAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGxpc3RlbmVyIGZ1bmN0aW9uIHRvIGJlIHJlbW92ZWRcbiAgICAgLSB1c2VDYXB0dXJlIChib29sZWFuKSAjb3B0aW9uYWwgdXNlQ2FwdHVyZSBmbGFnIGZvciByZW1vdmVFdmVudExpc3RlbmVyXG4gICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIG9mZjogZnVuY3Rpb24gKGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgdmFyIGk7XG5cbiAgICAgICAgaWYgKGlzU3RyaW5nKGV2ZW50VHlwZSkgJiYgZXZlbnRUeXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gZXZlbnRUeXBlLnRyaW0oKS5zcGxpdCgvICsvKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0FycmF5KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBldmVudFR5cGUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9mZihldmVudFR5cGVbaV0sIGxpc3RlbmVyLCB1c2VDYXB0dXJlKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNPYmplY3QoZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgZm9yICh2YXIgcHJvcCBpbiBldmVudFR5cGUpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9mZihwcm9wLCBldmVudFR5cGVbcHJvcF0sIGxpc3RlbmVyKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZXZlbnRMaXN0LFxuICAgICAgICAgICAgaW5kZXggPSAtMTtcblxuICAgICAgICAvLyBjb252ZXJ0IHRvIGJvb2xlYW5cbiAgICAgICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgICAgIGlmIChldmVudFR5cGUgPT09ICd3aGVlbCcpIHtcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IHdoZWVsRXZlbnQ7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBpZiBpdCBpcyBhbiBhY3Rpb24gZXZlbnQgdHlwZVxuICAgICAgICBpZiAoY29udGFpbnMoZXZlbnRUeXBlcywgZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgZXZlbnRMaXN0ID0gdGhpcy5faUV2ZW50c1tldmVudFR5cGVdO1xuXG4gICAgICAgICAgICBpZiAoZXZlbnRMaXN0ICYmIChpbmRleCA9IGluZGV4T2YoZXZlbnRMaXN0LCBsaXN0ZW5lcikpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX2lFdmVudHNbZXZlbnRUeXBlXS5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIGRlbGVnYXRlZCBldmVudFxuICAgICAgICBlbHNlIGlmICh0aGlzLnNlbGVjdG9yKSB7XG4gICAgICAgICAgICB2YXIgZGVsZWdhdGVkID0gZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0sXG4gICAgICAgICAgICAgICAgbWF0Y2hGb3VuZCA9IGZhbHNlO1xuXG4gICAgICAgICAgICBpZiAoIWRlbGVnYXRlZCkgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgICAgICAvLyBjb3VudCBmcm9tIGxhc3QgaW5kZXggb2YgZGVsZWdhdGVkIHRvIDBcbiAgICAgICAgICAgIGZvciAoaW5kZXggPSBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCAtIDE7IGluZGV4ID49IDA7IGluZGV4LS0pIHtcbiAgICAgICAgICAgICAgICAvLyBsb29rIGZvciBtYXRjaGluZyBzZWxlY3RvciBhbmQgY29udGV4dCBOb2RlXG4gICAgICAgICAgICAgICAgaWYgKGRlbGVnYXRlZC5zZWxlY3RvcnNbaW5kZXhdID09PSB0aGlzLnNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgICYmIGRlbGVnYXRlZC5jb250ZXh0c1tpbmRleF0gPT09IHRoaXMuX2NvbnRleHQpIHtcblxuICAgICAgICAgICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gZGVsZWdhdGVkLmxpc3RlbmVyc1tpbmRleF07XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gZWFjaCBpdGVtIG9mIHRoZSBsaXN0ZW5lcnMgYXJyYXkgaXMgYW4gYXJyYXk6IFtmdW5jdGlvbiwgdXNlQ2FwdHVyZUZsYWddXG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IGxpc3RlbmVycy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGZuID0gbGlzdGVuZXJzW2ldWzBdLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZUNhcCA9IGxpc3RlbmVyc1tpXVsxXTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgaWYgdGhlIGxpc3RlbmVyIGZ1bmN0aW9ucyBhbmQgdXNlQ2FwdHVyZSBmbGFncyBtYXRjaFxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGZuID09PSBsaXN0ZW5lciAmJiB1c2VDYXAgPT09IHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgdGhlIGxpc3RlbmVyIGZyb20gdGhlIGFycmF5IG9mIGxpc3RlbmVyc1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zcGxpY2UoaSwgMSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBpZiBhbGwgbGlzdGVuZXJzIGZvciB0aGlzIGludGVyYWN0YWJsZSBoYXZlIGJlZW4gcmVtb3ZlZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHJlbW92ZSB0aGUgaW50ZXJhY3RhYmxlIGZyb20gdGhlIGRlbGVnYXRlZCBhcnJheXNcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoIWxpc3RlbmVycy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnMuc3BsaWNlKGluZGV4LCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgZGVsZWdhdGUgZnVuY3Rpb24gZnJvbSBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fY29udGV4dCwgZXZlbnRUeXBlLCBkZWxlZ2F0ZUxpc3RlbmVyKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRzLnJlbW92ZSh0aGlzLl9jb250ZXh0LCBldmVudFR5cGUsIGRlbGVnYXRlVXNlQ2FwdHVyZSwgdHJ1ZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gcmVtb3ZlIHRoZSBhcnJheXMgaWYgdGhleSBhcmUgZW1wdHlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0gPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gb25seSByZW1vdmUgb25lIGxpc3RlbmVyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0Y2hGb3VuZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBpZiAobWF0Y2hGb3VuZCkgeyBicmVhazsgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyByZW1vdmUgbGlzdGVuZXIgZnJvbSB0aGlzIEludGVyYXRhYmxlJ3MgZWxlbWVudFxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fZWxlbWVudCwgZXZlbnRUeXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5zZXRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmVzZXQgdGhlIG9wdGlvbnMgb2YgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgLSBvcHRpb25zIChvYmplY3QpIFRoZSBuZXcgc2V0dGluZ3MgdG8gYXBwbHlcbiAgICAgPSAob2JqZWN0KSBUaGlzIEludGVyYWN0YWJsd1xuICAgICBcXCovXG4gICAgc2V0OiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoIWlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICBvcHRpb25zID0ge307XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLm9wdGlvbnMgPSBleHRlbmQoe30sIGRlZmF1bHRPcHRpb25zLmJhc2UpO1xuXG4gICAgICAgIHZhciBpLFxuICAgICAgICAgICAgYWN0aW9ucyA9IFsnZHJhZycsICdkcm9wJywgJ3Jlc2l6ZScsICdnZXN0dXJlJ10sXG4gICAgICAgICAgICBtZXRob2RzID0gWydkcmFnZ2FibGUnLCAnZHJvcHpvbmUnLCAncmVzaXphYmxlJywgJ2dlc3R1cmFibGUnXSxcbiAgICAgICAgICAgIHBlckFjdGlvbnMgPSBleHRlbmQoZXh0ZW5kKHt9LCBkZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb24pLCBvcHRpb25zW2FjdGlvbl0gfHwge30pO1xuXG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgYWN0aW9uID0gYWN0aW9uc1tpXTtcblxuICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl0gPSBleHRlbmQoe30sIGRlZmF1bHRPcHRpb25zW2FjdGlvbl0pO1xuXG4gICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbihhY3Rpb24sIHBlckFjdGlvbnMpO1xuXG4gICAgICAgICAgICB0aGlzW21ldGhvZHNbaV1dKG9wdGlvbnNbYWN0aW9uXSk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc2V0dGluZ3MgPSBbXG4gICAgICAgICAgICAnYWNjZXB0JywgJ2FjdGlvbkNoZWNrZXInLCAnYWxsb3dGcm9tJywgJ2RlbHRhU291cmNlJyxcbiAgICAgICAgICAgICdkcm9wQ2hlY2tlcicsICdpZ25vcmVGcm9tJywgJ29yaWdpbicsICdwcmV2ZW50RGVmYXVsdCcsXG4gICAgICAgICAgICAncmVjdENoZWNrZXInXG4gICAgICAgIF07XG5cbiAgICAgICAgZm9yIChpID0gMCwgbGVuID0gc2V0dGluZ3MubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIHZhciBzZXR0aW5nID0gc2V0dGluZ3NbaV07XG5cbiAgICAgICAgICAgIHRoaXMub3B0aW9uc1tzZXR0aW5nXSA9IGRlZmF1bHRPcHRpb25zLmJhc2Vbc2V0dGluZ107XG5cbiAgICAgICAgICAgIGlmIChzZXR0aW5nIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICB0aGlzW3NldHRpbmddKG9wdGlvbnNbc2V0dGluZ10pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUudW5zZXRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmVtb3ZlIHRoaXMgaW50ZXJhY3RhYmxlIGZyb20gdGhlIGxpc3Qgb2YgaW50ZXJhY3RhYmxlcyBhbmQgcmVtb3ZlXG4gICAgICogaXQncyBkcmFnLCBkcm9wLCByZXNpemUgYW5kIGdlc3R1cmUgY2FwYWJpbGl0aWVzXG4gICAgICpcbiAgICAgPSAob2JqZWN0KSBAaW50ZXJhY3RcbiAgICAgXFwqL1xuICAgIHVuc2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fZWxlbWVudCwgJ2FsbCcpO1xuXG4gICAgICAgIGlmICghaXNTdHJpbmcodGhpcy5zZWxlY3RvcikpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcywgJ2FsbCcpO1xuICAgICAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5zdHlsZUN1cnNvcikge1xuICAgICAgICAgICAgICAgIHRoaXMuX2VsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyByZW1vdmUgZGVsZWdhdGVkIGV2ZW50c1xuICAgICAgICAgICAgZm9yICh2YXIgdHlwZSBpbiBkZWxlZ2F0ZWRFdmVudHMpIHtcbiAgICAgICAgICAgICAgICB2YXIgZGVsZWdhdGVkID0gZGVsZWdhdGVkRXZlbnRzW3R5cGVdO1xuXG4gICAgICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2ldID09PSB0aGlzLnNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgICAgICAmJiBkZWxlZ2F0ZWQuY29udGV4dHNbaV0gPT09IHRoaXMuX2NvbnRleHQpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5zcGxpY2UoaSwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnMuc3BsaWNlKGksIDEpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgdGhlIGFycmF5cyBpZiB0aGV5IGFyZSBlbXB0eVxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZEV2ZW50c1t0eXBlXSA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIHR5cGUsIGRlbGVnYXRlTGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIHR5cGUsIGRlbGVnYXRlVXNlQ2FwdHVyZSwgdHJ1ZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5kcm9wem9uZShmYWxzZSk7XG5cbiAgICAgICAgaW50ZXJhY3RhYmxlcy5zcGxpY2UoaW5kZXhPZihpbnRlcmFjdGFibGVzLCB0aGlzKSwgMSk7XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cbn07XG5cbmZ1bmN0aW9uIHdhcm5PbmNlIChtZXRob2QsIG1lc3NhZ2UpIHtcbiAgICB2YXIgd2FybmVkID0gZmFsc2U7XG5cbiAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoIXdhcm5lZCkge1xuICAgICAgICAgICAgd2luZG93LmNvbnNvbGUud2FybihtZXNzYWdlKTtcbiAgICAgICAgICAgIHdhcm5lZCA9IHRydWU7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gbWV0aG9kLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgfTtcbn1cblxuSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5zbmFwID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5zbmFwLFxuICAgICdJbnRlcmFjdGFibGUjc25hcCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBzbmFwcGluZyBhdCBodHRwOi8vaW50ZXJhY3Rqcy5pby9kb2NzL3NuYXBwaW5nJyk7XG5JbnRlcmFjdGFibGUucHJvdG90eXBlLnJlc3RyaWN0ID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5yZXN0cmljdCxcbiAgICAnSW50ZXJhY3RhYmxlI3Jlc3RyaWN0IGlzIGRlcHJlY2F0ZWQuIFNlZSB0aGUgbmV3IGRvY3VtZW50YXRpb24gZm9yIHJlc3RpY3RpbmcgYXQgaHR0cDovL2ludGVyYWN0anMuaW8vZG9jcy9yZXN0cmljdGlvbicpO1xuSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5pbmVydGlhID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5pbmVydGlhLFxuICAgICdJbnRlcmFjdGFibGUjaW5lcnRpYSBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBpbmVydGlhIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvaW5lcnRpYScpO1xuSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5hdXRvU2Nyb2xsID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5hdXRvU2Nyb2xsLFxuICAgICdJbnRlcmFjdGFibGUjYXV0b1Njcm9sbCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBhdXRvU2Nyb2xsIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI2F1dG9zY3JvbGwnKTtcbkludGVyYWN0YWJsZS5wcm90b3R5cGUuc3F1YXJlUmVzaXplID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5zcXVhcmVSZXNpemUsXG4gICAgJ0ludGVyYWN0YWJsZSNzcXVhcmVSZXNpemUgaXMgZGVwcmVjYXRlZC4gU2VlIGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI3Jlc2l6ZS1zcXVhcmUnKTtcblxuLypcXFxuICogaW50ZXJhY3QuaXNTZXRcbiBbIG1ldGhvZCBdXG4gKlxuICogQ2hlY2sgaWYgYW4gZWxlbWVudCBoYXMgYmVlbiBzZXRcbiAtIGVsZW1lbnQgKEVsZW1lbnQpIFRoZSBFbGVtZW50IGJlaW5nIHNlYXJjaGVkIGZvclxuID0gKGJvb2xlYW4pIEluZGljYXRlcyBpZiB0aGUgZWxlbWVudCBvciBDU1Mgc2VsZWN0b3Igd2FzIHByZXZpb3VzbHkgcGFzc2VkIHRvIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3QuaXNTZXQgPSBmdW5jdGlvbihlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIGludGVyYWN0YWJsZXMuaW5kZXhPZkVsZW1lbnQoZWxlbWVudCwgb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQpICE9PSAtMTtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0Lm9uXG4gWyBtZXRob2QgXVxuICpcbiAqIEFkZHMgYSBnbG9iYWwgbGlzdGVuZXIgZm9yIGFuIEludGVyYWN0RXZlbnQgb3IgYWRkcyBhIERPTSBldmVudCB0b1xuICogYGRvY3VtZW50YFxuICpcbiAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRvIGxpc3RlbiBmb3JcbiAtIGxpc3RlbmVyICAgKGZ1bmN0aW9uKSBUaGUgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIG9uIHRoZSBnaXZlbiBldmVudChzKVxuIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgYWRkRXZlbnRMaXN0ZW5lclxuID0gKG9iamVjdCkgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5vbiA9IGZ1bmN0aW9uICh0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgIGlmIChpc1N0cmluZyh0eXBlKSAmJiB0eXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICB0eXBlID0gdHlwZS50cmltKCkuc3BsaXQoLyArLyk7XG4gICAgfVxuXG4gICAgaWYgKGlzQXJyYXkodHlwZSkpIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0eXBlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpbnRlcmFjdC5vbih0eXBlW2ldLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfVxuXG4gICAgaWYgKGlzT2JqZWN0KHR5cGUpKSB7XG4gICAgICAgIGZvciAodmFyIHByb3AgaW4gdHlwZSkge1xuICAgICAgICAgICAgaW50ZXJhY3Qub24ocHJvcCwgdHlwZVtwcm9wXSwgbGlzdGVuZXIpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cblxuICAgIC8vIGlmIGl0IGlzIGFuIEludGVyYWN0RXZlbnQgdHlwZSwgYWRkIGxpc3RlbmVyIHRvIGdsb2JhbEV2ZW50c1xuICAgIGlmIChjb250YWlucyhldmVudFR5cGVzLCB0eXBlKSkge1xuICAgICAgICAvLyBpZiB0aGlzIHR5cGUgb2YgZXZlbnQgd2FzIG5ldmVyIGJvdW5kXG4gICAgICAgIGlmICghZ2xvYmFsRXZlbnRzW3R5cGVdKSB7XG4gICAgICAgICAgICBnbG9iYWxFdmVudHNbdHlwZV0gPSBbbGlzdGVuZXJdO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgZ2xvYmFsRXZlbnRzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8vIElmIG5vbiBJbnRlcmFjdEV2ZW50IHR5cGUsIGFkZEV2ZW50TGlzdGVuZXIgdG8gZG9jdW1lbnRcbiAgICBlbHNlIHtcbiAgICAgICAgZXZlbnRzLmFkZChkb2N1bWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgIH1cblxuICAgIHJldHVybiBpbnRlcmFjdDtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0Lm9mZlxuIFsgbWV0aG9kIF1cbiAqXG4gKiBSZW1vdmVzIGEgZ2xvYmFsIEludGVyYWN0RXZlbnQgbGlzdGVuZXIgb3IgRE9NIGV2ZW50IGZyb20gYGRvY3VtZW50YFxuICpcbiAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAtIGxpc3RlbmVyICAgKGZ1bmN0aW9uKSBUaGUgbGlzdGVuZXIgZnVuY3Rpb24gdG8gYmUgcmVtb3ZlZFxuIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgcmVtb3ZlRXZlbnRMaXN0ZW5lclxuID0gKG9iamVjdCkgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5vZmYgPSBmdW5jdGlvbiAodHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICBpZiAoaXNTdHJpbmcodHlwZSkgJiYgdHlwZS5zZWFyY2goJyAnKSAhPT0gLTEpIHtcbiAgICAgICAgdHlwZSA9IHR5cGUudHJpbSgpLnNwbGl0KC8gKy8pO1xuICAgIH1cblxuICAgIGlmIChpc0FycmF5KHR5cGUpKSB7XG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdHlwZS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaW50ZXJhY3Qub2ZmKHR5cGVbaV0sIGxpc3RlbmVyLCB1c2VDYXB0dXJlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9XG5cbiAgICBpZiAoaXNPYmplY3QodHlwZSkpIHtcbiAgICAgICAgZm9yICh2YXIgcHJvcCBpbiB0eXBlKSB7XG4gICAgICAgICAgICBpbnRlcmFjdC5vZmYocHJvcCwgdHlwZVtwcm9wXSwgbGlzdGVuZXIpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cblxuICAgIGlmICghY29udGFpbnMoZXZlbnRUeXBlcywgdHlwZSkpIHtcbiAgICAgICAgZXZlbnRzLnJlbW92ZShkb2N1bWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdmFyIGluZGV4O1xuXG4gICAgICAgIGlmICh0eXBlIGluIGdsb2JhbEV2ZW50c1xuICAgICAgICAgICAgJiYgKGluZGV4ID0gaW5kZXhPZihnbG9iYWxFdmVudHNbdHlwZV0sIGxpc3RlbmVyKSkgIT09IC0xKSB7XG4gICAgICAgICAgICBnbG9iYWxFdmVudHNbdHlwZV0uc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBpbnRlcmFjdDtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0LmVuYWJsZURyYWdnaW5nXG4gWyBtZXRob2QgXVxuICpcbiAqIERlcHJlY2F0ZWQuXG4gKlxuICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZHJhZ2dpbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5lbmFibGVEcmFnZ2luZyA9IHdhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgIGlmIChuZXdWYWx1ZSAhPT0gbnVsbCAmJiBuZXdWYWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGFjdGlvbklzRW5hYmxlZC5kcmFnID0gbmV3VmFsdWU7XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cbiAgICByZXR1cm4gYWN0aW9uSXNFbmFibGVkLmRyYWc7XG59LCAnaW50ZXJhY3QuZW5hYmxlRHJhZ2dpbmcgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBzb29uIGJlIHJlbW92ZWQuJyk7XG5cbi8qXFxcbiAqIGludGVyYWN0LmVuYWJsZVJlc2l6aW5nXG4gWyBtZXRob2QgXVxuICpcbiAqIERlcHJlY2F0ZWQuXG4gKlxuICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgcmVzaXppbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5lbmFibGVSZXNpemluZyA9IHdhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgIGlmIChuZXdWYWx1ZSAhPT0gbnVsbCAmJiBuZXdWYWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGFjdGlvbklzRW5hYmxlZC5yZXNpemUgPSBuZXdWYWx1ZTtcblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfVxuICAgIHJldHVybiBhY3Rpb25Jc0VuYWJsZWQucmVzaXplO1xufSwgJ2ludGVyYWN0LmVuYWJsZVJlc2l6aW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4vKlxcXG4gKiBpbnRlcmFjdC5lbmFibGVHZXN0dXJpbmdcbiBbIG1ldGhvZCBdXG4gKlxuICogRGVwcmVjYXRlZC5cbiAqXG4gKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciBnZXN0dXJpbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5lbmFibGVHZXN0dXJpbmcgPSB3YXJuT25jZShmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICBpZiAobmV3VmFsdWUgIT09IG51bGwgJiYgbmV3VmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBhY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9XG4gICAgcmV0dXJuIGFjdGlvbklzRW5hYmxlZC5nZXN0dXJlO1xufSwgJ2ludGVyYWN0LmVuYWJsZUdlc3R1cmluZyBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIHNvb24gYmUgcmVtb3ZlZC4nKTtcblxuaW50ZXJhY3QuZXZlbnRUeXBlcyA9IGV2ZW50VHlwZXM7XG5cbi8qXFxcbiAqIGludGVyYWN0LmRlYnVnXG4gWyBtZXRob2QgXVxuICpcbiAqIFJldHVybnMgZGVidWdnaW5nIGRhdGFcbiA9IChvYmplY3QpIEFuIG9iamVjdCB3aXRoIHByb3BlcnRpZXMgdGhhdCBvdXRsaW5lIHRoZSBjdXJyZW50IHN0YXRlIGFuZCBleHBvc2UgaW50ZXJuYWwgZnVuY3Rpb25zIGFuZCB2YXJpYWJsZXNcbiBcXCovXG5pbnRlcmFjdC5kZWJ1ZyA9IGZ1bmN0aW9uICgpIHtcbiAgICB2YXIgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbnNbMF0gfHwgbmV3IEludGVyYWN0aW9uKCk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICBpbnRlcmFjdGlvbnMgICAgICAgICAgOiBpbnRlcmFjdGlvbnMsXG4gICAgICAgIHRhcmdldCAgICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnRhcmdldCxcbiAgICAgICAgZHJhZ2dpbmcgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uZHJhZ2dpbmcsXG4gICAgICAgIHJlc2l6aW5nICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnJlc2l6aW5nLFxuICAgICAgICBnZXN0dXJpbmcgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5nZXN0dXJpbmcsXG4gICAgICAgIHByZXBhcmVkICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXBhcmVkLFxuICAgICAgICBtYXRjaGVzICAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5tYXRjaGVzLFxuICAgICAgICBtYXRjaEVsZW1lbnRzICAgICAgICAgOiBpbnRlcmFjdGlvbi5tYXRjaEVsZW1lbnRzLFxuXG4gICAgICAgIHByZXZDb29yZHMgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXZDb29yZHMsXG4gICAgICAgIHN0YXJ0Q29vcmRzICAgICAgICAgICA6IGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLFxuXG4gICAgICAgIHBvaW50ZXJJZHMgICAgICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJJZHMsXG4gICAgICAgIHBvaW50ZXJzICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICBhZGRQb2ludGVyICAgICAgICAgICAgOiBsaXN0ZW5lcnMuYWRkUG9pbnRlcixcbiAgICAgICAgcmVtb3ZlUG9pbnRlciAgICAgICAgIDogbGlzdGVuZXJzLnJlbW92ZVBvaW50ZXIsXG4gICAgICAgIHJlY29yZFBvaW50ZXIgICAgICAgIDogbGlzdGVuZXJzLnJlY29yZFBvaW50ZXIsXG5cbiAgICAgICAgc25hcCAgICAgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uc25hcFN0YXR1cyxcbiAgICAgICAgcmVzdHJpY3QgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ucmVzdHJpY3RTdGF0dXMsXG4gICAgICAgIGluZXJ0aWEgICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMsXG5cbiAgICAgICAgZG93blRpbWUgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uZG93blRpbWVzWzBdLFxuICAgICAgICBkb3duRXZlbnQgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duRXZlbnQsXG4gICAgICAgIGRvd25Qb2ludGVyICAgICAgICAgICA6IGludGVyYWN0aW9uLmRvd25Qb2ludGVyLFxuICAgICAgICBwcmV2RXZlbnQgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQsXG5cbiAgICAgICAgSW50ZXJhY3RhYmxlICAgICAgICAgIDogSW50ZXJhY3RhYmxlLFxuICAgICAgICBpbnRlcmFjdGFibGVzICAgICAgICAgOiBpbnRlcmFjdGFibGVzLFxuICAgICAgICBwb2ludGVySXNEb3duICAgICAgICAgOiBpbnRlcmFjdGlvbi5wb2ludGVySXNEb3duLFxuICAgICAgICBkZWZhdWx0T3B0aW9ucyAgICAgICAgOiBkZWZhdWx0T3B0aW9ucyxcbiAgICAgICAgZGVmYXVsdEFjdGlvbkNoZWNrZXIgIDogZGVmYXVsdEFjdGlvbkNoZWNrZXIsXG5cbiAgICAgICAgYWN0aW9uQ3Vyc29ycyAgICAgICAgIDogYWN0aW9uQ3Vyc29ycyxcbiAgICAgICAgZHJhZ01vdmUgICAgICAgICAgICAgIDogbGlzdGVuZXJzLmRyYWdNb3ZlLFxuICAgICAgICByZXNpemVNb3ZlICAgICAgICAgICAgOiBsaXN0ZW5lcnMucmVzaXplTW92ZSxcbiAgICAgICAgZ2VzdHVyZU1vdmUgICAgICAgICAgIDogbGlzdGVuZXJzLmdlc3R1cmVNb3ZlLFxuICAgICAgICBwb2ludGVyVXAgICAgICAgICAgICAgOiBsaXN0ZW5lcnMucG9pbnRlclVwLFxuICAgICAgICBwb2ludGVyRG93biAgICAgICAgICAgOiBsaXN0ZW5lcnMucG9pbnRlckRvd24sXG4gICAgICAgIHBvaW50ZXJNb3ZlICAgICAgICAgICA6IGxpc3RlbmVycy5wb2ludGVyTW92ZSxcbiAgICAgICAgcG9pbnRlckhvdmVyICAgICAgICAgIDogbGlzdGVuZXJzLnBvaW50ZXJIb3ZlcixcblxuICAgICAgICBldmVudFR5cGVzICAgICAgICAgICAgOiBldmVudFR5cGVzLFxuXG4gICAgICAgIGV2ZW50cyAgICAgICAgICAgICAgICA6IGV2ZW50cyxcbiAgICAgICAgZ2xvYmFsRXZlbnRzICAgICAgICAgIDogZ2xvYmFsRXZlbnRzLFxuICAgICAgICBkZWxlZ2F0ZWRFdmVudHMgICAgICAgOiBkZWxlZ2F0ZWRFdmVudHNcbiAgICB9O1xufTtcblxuLy8gZXhwb3NlIHRoZSBmdW5jdGlvbnMgdXNlZCB0byBjYWxjdWxhdGUgbXVsdGktdG91Y2ggcHJvcGVydGllc1xuaW50ZXJhY3QuZ2V0VG91Y2hBdmVyYWdlICA9IHRvdWNoQXZlcmFnZTtcbmludGVyYWN0LmdldFRvdWNoQkJveCAgICAgPSB0b3VjaEJCb3g7XG5pbnRlcmFjdC5nZXRUb3VjaERpc3RhbmNlID0gdG91Y2hEaXN0YW5jZTtcbmludGVyYWN0LmdldFRvdWNoQW5nbGUgICAgPSB0b3VjaEFuZ2xlO1xuXG5pbnRlcmFjdC5nZXRFbGVtZW50UmVjdCAgID0gZ2V0RWxlbWVudFJlY3Q7XG5pbnRlcmFjdC5tYXRjaGVzU2VsZWN0b3IgID0gbWF0Y2hlc1NlbGVjdG9yO1xuaW50ZXJhY3QuY2xvc2VzdCAgICAgICAgICA9IGNsb3Nlc3Q7XG5cbi8qXFxcbiAqIGludGVyYWN0Lm1hcmdpblxuIFsgbWV0aG9kIF1cbiAqXG4gKiBSZXR1cm5zIG9yIHNldHMgdGhlIG1hcmdpbiBmb3IgYXV0b2NoZWNrIHJlc2l6aW5nIHVzZWQgaW5cbiAqIEBJbnRlcmFjdGFibGUuZ2V0QWN0aW9uLiBUaGF0IGlzIHRoZSBkaXN0YW5jZSBmcm9tIHRoZSBib3R0b20gYW5kIHJpZ2h0XG4gKiBlZGdlcyBvZiBhbiBlbGVtZW50IGNsaWNraW5nIGluIHdoaWNoIHdpbGwgc3RhcnQgcmVzaXppbmdcbiAqXG4gLSBuZXdWYWx1ZSAobnVtYmVyKSAjb3B0aW9uYWxcbiA9IChudW1iZXIgfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgbWFyZ2luIHZhbHVlIG9yIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3QubWFyZ2luID0gZnVuY3Rpb24gKG5ld3ZhbHVlKSB7XG4gICAgaWYgKGlzTnVtYmVyKG5ld3ZhbHVlKSkge1xuICAgICAgICBtYXJnaW4gPSBuZXd2YWx1ZTtcblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfVxuICAgIHJldHVybiBtYXJnaW47XG59O1xuXG4vKlxcXG4gKiBpbnRlcmFjdC5zdXBwb3J0c1RvdWNoXG4gWyBtZXRob2QgXVxuICpcbiA9IChib29sZWFuKSBXaGV0aGVyIG9yIG5vdCB0aGUgYnJvd3NlciBzdXBwb3J0cyB0b3VjaCBpbnB1dFxuIFxcKi9cbmludGVyYWN0LnN1cHBvcnRzVG91Y2ggPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHN1cHBvcnRzVG91Y2g7XG59O1xuXG4vKlxcXG4gKiBpbnRlcmFjdC5zdXBwb3J0c1BvaW50ZXJFdmVudFxuIFsgbWV0aG9kIF1cbiAqXG4gPSAoYm9vbGVhbikgV2hldGhlciBvciBub3QgdGhlIGJyb3dzZXIgc3VwcG9ydHMgUG9pbnRlckV2ZW50c1xuIFxcKi9cbmludGVyYWN0LnN1cHBvcnRzUG9pbnRlckV2ZW50ID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiBzdXBwb3J0c1BvaW50ZXJFdmVudDtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0LnN0b3BcbiBbIG1ldGhvZCBdXG4gKlxuICogQ2FuY2VscyBhbGwgaW50ZXJhY3Rpb25zIChlbmQgZXZlbnRzIGFyZSBub3QgZmlyZWQpXG4gKlxuIC0gZXZlbnQgKEV2ZW50KSBBbiBldmVudCBvbiB3aGljaCB0byBjYWxsIHByZXZlbnREZWZhdWx0KClcbiA9IChvYmplY3QpIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3Quc3RvcCA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgIGZvciAodmFyIGkgPSBpbnRlcmFjdGlvbnMubGVuZ3RoIC0gMTsgaSA+IDA7IGktLSkge1xuICAgICAgICBpbnRlcmFjdGlvbnNbaV0uc3RvcChldmVudCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGludGVyYWN0O1xufTtcblxuLypcXFxuICogaW50ZXJhY3QuZHluYW1pY0Ryb3BcbiBbIG1ldGhvZCBdXG4gKlxuICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgdGhlIGRpbWVuc2lvbnMgb2YgZHJvcHpvbmUgZWxlbWVudHMgYXJlXG4gKiBjYWxjdWxhdGVkIG9uIGV2ZXJ5IGRyYWdtb3ZlIG9yIG9ubHkgb24gZHJhZ3N0YXJ0IGZvciB0aGUgZGVmYXVsdFxuICogZHJvcENoZWNrZXJcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIFRydWUgdG8gY2hlY2sgb24gZWFjaCBtb3ZlLiBGYWxzZSB0byBjaGVjayBvbmx5IGJlZm9yZSBzdGFydFxuID0gKGJvb2xlYW4gfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuIFxcKi9cbmludGVyYWN0LmR5bmFtaWNEcm9wID0gZnVuY3Rpb24gKG5ld1ZhbHVlKSB7XG4gICAgaWYgKGlzQm9vbChuZXdWYWx1ZSkpIHtcbiAgICAgICAgLy9pZiAoZHJhZ2dpbmcgJiYgZHluYW1pY0Ryb3AgIT09IG5ld1ZhbHVlICYmICFuZXdWYWx1ZSkge1xuICAgICAgICAvL2NhbGNSZWN0cyhkcm9wem9uZXMpO1xuICAgICAgICAvL31cblxuICAgICAgICBkeW5hbWljRHJvcCA9IG5ld1ZhbHVlO1xuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9XG4gICAgcmV0dXJuIGR5bmFtaWNEcm9wO1xufTtcblxuLypcXFxuICogaW50ZXJhY3QucG9pbnRlck1vdmVUb2xlcmFuY2VcbiBbIG1ldGhvZCBdXG4gKiBSZXR1cm5zIG9yIHNldHMgdGhlIGRpc3RhbmNlIHRoZSBwb2ludGVyIG11c3QgYmUgbW92ZWQgYmVmb3JlIGFuIGFjdGlvblxuICogc2VxdWVuY2Ugb2NjdXJzLiBUaGlzIGFsc28gYWZmZWN0cyB0b2xlcmFuY2UgZm9yIHRhcCBldmVudHMuXG4gKlxuIC0gbmV3VmFsdWUgKG51bWJlcikgI29wdGlvbmFsIFRoZSBtb3ZlbWVudCBmcm9tIHRoZSBzdGFydCBwb3NpdGlvbiBtdXN0IGJlIGdyZWF0ZXIgdGhhbiB0aGlzIHZhbHVlXG4gPSAobnVtYmVyIHwgSW50ZXJhY3RhYmxlKSBUaGUgY3VycmVudCBzZXR0aW5nIG9yIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3QucG9pbnRlck1vdmVUb2xlcmFuY2UgPSBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICBpZiAoaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgIHBvaW50ZXJNb3ZlVG9sZXJhbmNlID0gbmV3VmFsdWU7XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuXG4gICAgcmV0dXJuIHBvaW50ZXJNb3ZlVG9sZXJhbmNlO1xufTtcblxuLypcXFxuICogaW50ZXJhY3QubWF4SW50ZXJhY3Rpb25zXG4gWyBtZXRob2QgXVxuICoqXG4gKiBSZXR1cm5zIG9yIHNldHMgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgaW50ZXJhY3Rpb25zIGFsbG93ZWQuXG4gKiBCeSBkZWZhdWx0IG9ubHkgMSBpbnRlcmFjdGlvbiBpcyBhbGxvd2VkIGF0IGEgdGltZSAoZm9yIGJhY2t3YXJkc1xuICogY29tcGF0aWJpbGl0eSkuIFRvIGFsbG93IG11bHRpcGxlIGludGVyYWN0aW9ucyBvbiB0aGUgc2FtZSBJbnRlcmFjdGFibGVzXG4gKiBhbmQgZWxlbWVudHMsIHlvdSBuZWVkIHRvIGVuYWJsZSBpdCBpbiB0aGUgZHJhZ2dhYmxlLCByZXNpemFibGUgYW5kXG4gKiBnZXN0dXJhYmxlIGAnbWF4J2AgYW5kIGAnbWF4UGVyRWxlbWVudCdgIG9wdGlvbnMuXG4gKipcbiAtIG5ld1ZhbHVlIChudW1iZXIpICNvcHRpb25hbCBBbnkgbnVtYmVyLiBuZXdWYWx1ZSA8PSAwIG1lYW5zIG5vIGludGVyYWN0aW9ucy5cbiBcXCovXG5pbnRlcmFjdC5tYXhJbnRlcmFjdGlvbnMgPSBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICBpZiAoaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgIG1heEludGVyYWN0aW9ucyA9IG5ld1ZhbHVlO1xuXG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cblxuICAgIHJldHVybiBtYXhJbnRlcmFjdGlvbnM7XG59O1xuXG5pbnRlcmFjdC5jcmVhdGVTbmFwR3JpZCA9IGZ1bmN0aW9uIChncmlkKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICh4LCB5KSB7XG4gICAgICAgIHZhciBvZmZzZXRYID0gMCxcbiAgICAgICAgICAgIG9mZnNldFkgPSAwO1xuXG4gICAgICAgIGlmIChpc09iamVjdChncmlkLm9mZnNldCkpIHtcbiAgICAgICAgICAgIG9mZnNldFggPSBncmlkLm9mZnNldC54O1xuICAgICAgICAgICAgb2Zmc2V0WSA9IGdyaWQub2Zmc2V0Lnk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZ3JpZHggPSBNYXRoLnJvdW5kKCh4IC0gb2Zmc2V0WCkgLyBncmlkLngpLFxuICAgICAgICAgICAgZ3JpZHkgPSBNYXRoLnJvdW5kKCh5IC0gb2Zmc2V0WSkgLyBncmlkLnkpLFxuXG4gICAgICAgICAgICBuZXdYID0gZ3JpZHggKiBncmlkLnggKyBvZmZzZXRYLFxuICAgICAgICAgICAgbmV3WSA9IGdyaWR5ICogZ3JpZC55ICsgb2Zmc2V0WTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgeDogbmV3WCxcbiAgICAgICAgICAgIHk6IG5ld1ksXG4gICAgICAgICAgICByYW5nZTogZ3JpZC5yYW5nZVxuICAgICAgICB9O1xuICAgIH07XG59O1xuXG5mdW5jdGlvbiBlbmRBbGxJbnRlcmFjdGlvbnMgKGV2ZW50KSB7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBpbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJFbmQoZXZlbnQsIGV2ZW50KTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGxpc3RlblRvRG9jdW1lbnQgKGRvYykge1xuICAgIGlmIChjb250YWlucyhkb2N1bWVudHMsIGRvYykpIHsgcmV0dXJuOyB9XG5cbiAgICB2YXIgd2luID0gZG9jLmRlZmF1bHRWaWV3IHx8IGRvYy5wYXJlbnRXaW5kb3c7XG5cbiAgICAvLyBhZGQgZGVsZWdhdGUgZXZlbnQgbGlzdGVuZXJcbiAgICBmb3IgKHZhciBldmVudFR5cGUgaW4gZGVsZWdhdGVkRXZlbnRzKSB7XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBldmVudFR5cGUsIGRlbGVnYXRlTGlzdGVuZXIpO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgZXZlbnRUeXBlLCBkZWxlZ2F0ZVVzZUNhcHR1cmUsIHRydWUpO1xuICAgIH1cblxuICAgIGlmIChQb2ludGVyRXZlbnQpIHtcbiAgICAgICAgaWYgKFBvaW50ZXJFdmVudCA9PT0gd2luLk1TUG9pbnRlckV2ZW50KSB7XG4gICAgICAgICAgICBwRXZlbnRUeXBlcyA9IHtcbiAgICAgICAgICAgICAgICB1cDogJ01TUG9pbnRlclVwJywgZG93bjogJ01TUG9pbnRlckRvd24nLCBvdmVyOiAnbW91c2VvdmVyJyxcbiAgICAgICAgICAgICAgICBvdXQ6ICdtb3VzZW91dCcsIG1vdmU6ICdNU1BvaW50ZXJNb3ZlJywgY2FuY2VsOiAnTVNQb2ludGVyQ2FuY2VsJyB9O1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgcEV2ZW50VHlwZXMgPSB7XG4gICAgICAgICAgICAgICAgdXA6ICdwb2ludGVydXAnLCBkb3duOiAncG9pbnRlcmRvd24nLCBvdmVyOiAncG9pbnRlcm92ZXInLFxuICAgICAgICAgICAgICAgIG91dDogJ3BvaW50ZXJvdXQnLCBtb3ZlOiAncG9pbnRlcm1vdmUnLCBjYW5jZWw6ICdwb2ludGVyY2FuY2VsJyB9O1xuICAgICAgICB9XG5cbiAgICAgICAgZXZlbnRzLmFkZChkb2MsIHBFdmVudFR5cGVzLmRvd24gICwgbGlzdGVuZXJzLnNlbGVjdG9yRG93biApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgcEV2ZW50VHlwZXMubW92ZSAgLCBsaXN0ZW5lcnMucG9pbnRlck1vdmUgICk7XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBwRXZlbnRUeXBlcy5vdmVyICAsIGxpc3RlbmVycy5wb2ludGVyT3ZlciAgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsIHBFdmVudFR5cGVzLm91dCAgICwgbGlzdGVuZXJzLnBvaW50ZXJPdXQgICApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgcEV2ZW50VHlwZXMudXAgICAgLCBsaXN0ZW5lcnMucG9pbnRlclVwICAgICk7XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBwRXZlbnRUeXBlcy5jYW5jZWwsIGxpc3RlbmVycy5wb2ludGVyQ2FuY2VsKTtcblxuICAgICAgICAvLyBhdXRvc2Nyb2xsXG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBwRXZlbnRUeXBlcy5tb3ZlLCBsaXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZWRvd24nLCBsaXN0ZW5lcnMuc2VsZWN0b3JEb3duKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBsaXN0ZW5lcnMucG9pbnRlck1vdmUgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZXVwJyAgLCBsaXN0ZW5lcnMucG9pbnRlclVwICAgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW92ZXInLCBsaXN0ZW5lcnMucG9pbnRlck92ZXIgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW91dCcgLCBsaXN0ZW5lcnMucG9pbnRlck91dCAgKTtcblxuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoc3RhcnQnICwgbGlzdGVuZXJzLnNlbGVjdG9yRG93biApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNobW92ZScgICwgbGlzdGVuZXJzLnBvaW50ZXJNb3ZlICApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoZW5kJyAgICwgbGlzdGVuZXJzLnBvaW50ZXJVcCAgICApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoY2FuY2VsJywgbGlzdGVuZXJzLnBvaW50ZXJDYW5jZWwpO1xuXG4gICAgICAgIC8vIGF1dG9zY3JvbGxcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBsaXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNobW92ZScsIGxpc3RlbmVycy5hdXRvU2Nyb2xsTW92ZSk7XG4gICAgfVxuXG4gICAgZXZlbnRzLmFkZCh3aW4sICdibHVyJywgZW5kQWxsSW50ZXJhY3Rpb25zKTtcblxuICAgIHRyeSB7XG4gICAgICAgIGlmICh3aW4uZnJhbWVFbGVtZW50KSB7XG4gICAgICAgICAgICB2YXIgcGFyZW50RG9jID0gd2luLmZyYW1lRWxlbWVudC5vd25lckRvY3VtZW50LFxuICAgICAgICAgICAgICAgIHBhcmVudFdpbmRvdyA9IHBhcmVudERvYy5kZWZhdWx0VmlldztcblxuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdtb3VzZXVwJyAgICAgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICd0b3VjaGVuZCcgICAgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICd0b3VjaGNhbmNlbCcgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdwb2ludGVydXAnICAgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdNU1BvaW50ZXJVcCcgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnRXaW5kb3csICdibHVyJyAgICAgICAgICwgZW5kQWxsSW50ZXJhY3Rpb25zICk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGludGVyYWN0LndpbmRvd1BhcmVudEVycm9yID0gZXJyb3I7XG4gICAgfVxuXG4gICAgaWYgKGV2ZW50cy51c2VBdHRhY2hFdmVudCkge1xuICAgICAgICAvLyBGb3IgSUUncyBsYWNrIG9mIEV2ZW50I3ByZXZlbnREZWZhdWx0XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnc2VsZWN0c3RhcnQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgIHZhciBpbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uc1swXTtcblxuICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLmN1cnJlbnRBY3Rpb24oKSkge1xuICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBGb3IgSUUncyBiYWQgZGJsY2xpY2sgZXZlbnQgc2VxdWVuY2VcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdkYmxjbGljaycsIGRvT25JbnRlcmFjdGlvbnMoJ2llOERibGNsaWNrJykpO1xuICAgIH1cblxuICAgIGRvY3VtZW50cy5wdXNoKGRvYyk7XG59XG5cbmxpc3RlblRvRG9jdW1lbnQoZG9jdW1lbnQpO1xuXG5mdW5jdGlvbiBpbmRleE9mIChhcnJheSwgdGFyZ2V0KSB7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGFycmF5Lmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIGlmIChhcnJheVtpXSA9PT0gdGFyZ2V0KSB7XG4gICAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiAtMTtcbn1cblxuZnVuY3Rpb24gY29udGFpbnMgKGFycmF5LCB0YXJnZXQpIHtcbiAgICByZXR1cm4gaW5kZXhPZihhcnJheSwgdGFyZ2V0KSAhPT0gLTE7XG59XG5cbmZ1bmN0aW9uIG1hdGNoZXNTZWxlY3RvciAoZWxlbWVudCwgc2VsZWN0b3IsIG5vZGVMaXN0KSB7XG4gICAgaWYgKGllOE1hdGNoZXNTZWxlY3Rvcikge1xuICAgICAgICByZXR1cm4gaWU4TWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIHNlbGVjdG9yLCBub2RlTGlzdCk7XG4gICAgfVxuXG4gICAgLy8gcmVtb3ZlIC9kZWVwLyBmcm9tIHNlbGVjdG9ycyBpZiBzaGFkb3dET00gcG9seWZpbGwgaXMgdXNlZFxuICAgIGlmICh3aW5kb3cgIT09IHJlYWxXaW5kb3cpIHtcbiAgICAgICAgc2VsZWN0b3IgPSBzZWxlY3Rvci5yZXBsYWNlKC9cXC9kZWVwXFwvL2csICcgJyk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGVsZW1lbnRbcHJlZml4ZWRNYXRjaGVzU2VsZWN0b3JdKHNlbGVjdG9yKTtcbn1cblxuZnVuY3Rpb24gbWF0Y2hlc1VwVG8gKGVsZW1lbnQsIHNlbGVjdG9yLCBsaW1pdCkge1xuICAgIHdoaWxlIChpc0VsZW1lbnQoZWxlbWVudCkpIHtcbiAgICAgICAgaWYgKG1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBzZWxlY3RvcikpIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKGVsZW1lbnQgPT09IGxpbWl0KSB7XG4gICAgICAgICAgICByZXR1cm4gbWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIHNlbGVjdG9yKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBmYWxzZTtcbn1cblxuLy8gRm9yIElFOCdzIGxhY2sgb2YgYW4gRWxlbWVudCNtYXRjaGVzU2VsZWN0b3Jcbi8vIHRha2VuIGZyb20gaHR0cDovL3RhbmFsaW4uY29tL2VuL2Jsb2cvMjAxMi8xMi9tYXRjaGVzLXNlbGVjdG9yLWllOC8gYW5kIG1vZGlmaWVkXG5pZiAoIShwcmVmaXhlZE1hdGNoZXNTZWxlY3RvciBpbiBFbGVtZW50LnByb3RvdHlwZSkgfHwgIWlzRnVuY3Rpb24oRWxlbWVudC5wcm90b3R5cGVbcHJlZml4ZWRNYXRjaGVzU2VsZWN0b3JdKSkge1xuICAgIGllOE1hdGNoZXNTZWxlY3RvciA9IGZ1bmN0aW9uIChlbGVtZW50LCBzZWxlY3RvciwgZWxlbXMpIHtcbiAgICAgICAgZWxlbXMgPSBlbGVtcyB8fCBlbGVtZW50LnBhcmVudE5vZGUucXVlcnlTZWxlY3RvckFsbChzZWxlY3Rvcik7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGVsZW1zLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoZWxlbXNbaV0gPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9O1xufVxuXG4vLyByZXF1ZXN0QW5pbWF0aW9uRnJhbWUgcG9seWZpbGxcbihmdW5jdGlvbigpIHtcbiAgICB2YXIgbGFzdFRpbWUgPSAwLFxuICAgICAgICB2ZW5kb3JzID0gWydtcycsICdtb3onLCAnd2Via2l0JywgJ28nXTtcblxuICAgIGZvcih2YXIgeCA9IDA7IHggPCB2ZW5kb3JzLmxlbmd0aCAmJiAhcmVhbFdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU7ICsreCkge1xuICAgICAgICByZXFGcmFtZSA9IHJlYWxXaW5kb3dbdmVuZG9yc1t4XSsnUmVxdWVzdEFuaW1hdGlvbkZyYW1lJ107XG4gICAgICAgIGNhbmNlbEZyYW1lID0gcmVhbFdpbmRvd1t2ZW5kb3JzW3hdKydDYW5jZWxBbmltYXRpb25GcmFtZSddIHx8IHJlYWxXaW5kb3dbdmVuZG9yc1t4XSsnQ2FuY2VsUmVxdWVzdEFuaW1hdGlvbkZyYW1lJ107XG4gICAgfVxuXG4gICAgaWYgKCFyZXFGcmFtZSkge1xuICAgICAgICByZXFGcmFtZSA9IGZ1bmN0aW9uKGNhbGxiYWNrKSB7XG4gICAgICAgICAgICB2YXIgY3VyclRpbWUgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgICAgICB0aW1lVG9DYWxsID0gTWF0aC5tYXgoMCwgMTYgLSAoY3VyclRpbWUgLSBsYXN0VGltZSkpLFxuICAgICAgICAgICAgICAgIGlkID0gc2V0VGltZW91dChmdW5jdGlvbigpIHsgY2FsbGJhY2soY3VyclRpbWUgKyB0aW1lVG9DYWxsKTsgfSxcbiAgICAgICAgICAgICAgICAgICAgdGltZVRvQ2FsbCk7XG4gICAgICAgICAgICBsYXN0VGltZSA9IGN1cnJUaW1lICsgdGltZVRvQ2FsbDtcbiAgICAgICAgICAgIHJldHVybiBpZDtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBpZiAoIWNhbmNlbEZyYW1lKSB7XG4gICAgICAgIGNhbmNlbEZyYW1lID0gZnVuY3Rpb24oaWQpIHtcbiAgICAgICAgICAgIGNsZWFyVGltZW91dChpZCk7XG4gICAgICAgIH07XG4gICAgfVxufSgpKTtcblxuLy8gQ29tbW9uSlNcbmlmICh0eXBlb2YgZXhwb3J0cyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBpZiAodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbW9kdWxlLmV4cG9ydHMpIHtcbiAgICAgICAgZXhwb3J0cyA9IG1vZHVsZS5leHBvcnRzID0gaW50ZXJhY3Q7XG4gICAgfVxuICAgIGV4cG9ydHNbJ2ludGVyYWN0J10gPSBpbnRlcmFjdDtcbn1cbi8vIEFNRFxuZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgZGVmaW5lKCdpbnRlcmFjdCcsIGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfSk7XG59O1xuXG4vLyBBbHdheXMgZXhwb3J0IG9uIHRoZSBnbG9iYWwgc2NvcGVcbndpbmRvd1snaW50ZXJhY3QnXSA9IGludGVyYWN0OyIsInZhciBpbnRlcmFjdFdpbmRvdyA9IHR5cGVvZiB3aW5kb3cgPT09ICd1bmRlZmluZWQnID8gdW5kZWZpbmVkIDogd2luZG93O1xuXG5tb2R1bGUuZXhwb3J0cyA9IGludGVyYWN0V2luZG93OyJdfQ== diff --git a/gulp/config.js b/gulp/config.js index 0a6245c8a..60fb03861 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -24,41 +24,20 @@ module.exports = { src: src + "/htdocs/**", dest: dest }, - iconFonts: { - name: 'Gulp Starter Icons', - src: src + '/icons/*.svg', - dest: dest + '/fonts', - sassDest: src + '/sass', - template: './gulp/tasks/iconFont/template.sass.swig', - sassOutputName: '_icons.sass', - fontPath: 'fonts', - className: 'icon', - options: { - fontName: 'Post-Creator-Icons', - appendCodepoints: true, - normalize: false - } - }, browserify: { // A separate bundle will be generated for each // bundle config in the list below - bundleConfigs: [{ - entries: src + '/javascript/global.coffee', - dest: dest, - outputName: 'global.js', - // Additional file extentions to make optional - extensions: ['.coffee', '.hbs'], - // list of modules to make require-able externally - require: ['jquery', 'backbone/node_modules/underscore'] - // See https://github.com/greypants/gulp-starter/issues/87 for note about - // why this is 'backbone/node_modules/underscore' and not 'underscore' - }, { - entries: src + '/javascript/page.js', - dest: dest, - outputName: 'page.js', - // list of externally available modules to exclude from the bundle - external: ['jquery', 'underscore'] - }] + bundleConfigs: [ + { + entries: src + '/interact.js', + dest: dest, + outputName: 'interact.js', + // Additional file extentions to make optional + extensions: [], + // list of modules to make require-able externally + require: ['interact'] + } + ] }, production: { cssSrc: dest + '/*.css', diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js index e4c055911..8ab2d30d7 100644 --- a/gulp/tasks/default.js +++ b/gulp/tasks/default.js @@ -1,3 +1,5 @@ var gulp = require('gulp'); -gulp.task('default', ['sass', 'images', 'markup', 'watch']); +//gulp.task('default', ['sass', 'images', 'markup', 'watch']); + +gulp.task('default', ['watch']); diff --git a/gulp/tasks/iconFont/generateIconSass.js b/gulp/tasks/iconFont/generateIconSass.js deleted file mode 100644 index 6b3341b70..000000000 --- a/gulp/tasks/iconFont/generateIconSass.js +++ /dev/null @@ -1,25 +0,0 @@ -var gulp = require('gulp'); -var config = require('../../config').iconFonts; -var swig = require('gulp-swig'); -var rename = require('gulp-rename'); - -module.exports = function(codepoints, options) { - gulp.src(config.template) - .pipe(swig({ - data: { - icons: codepoints.map(function(icon) { - return { - name: icon.name, - code: icon.codepoint.toString(16) - } - }), - - fontName: config.options.fontName, - fontPath: config.fontPath, - className: config.className, - comment: 'DO NOT EDIT DIRECTLY!\n Generated by gulp/tasks/iconFont.js\n from ' + config.template - } - })) - .pipe(rename(config.sassOutputName)) - .pipe(gulp.dest(config.sassDest)); -}; diff --git a/gulp/tasks/iconFont/index.js b/gulp/tasks/iconFont/index.js deleted file mode 100644 index 6be8b2eb0..000000000 --- a/gulp/tasks/iconFont/index.js +++ /dev/null @@ -1,11 +0,0 @@ -var gulp = require('gulp'); -var iconfont = require('gulp-iconfont'); -var config = require('../../config').iconFonts; -var generateIconSass = require('./generateIconSass'); - -gulp.task('iconFont', function() { - return gulp.src(config.src) - .pipe(iconfont(config.options)) - .on('codepoints', generateIconSass) - .pipe(gulp.dest(config.dest)); -}); diff --git a/gulp/tasks/iconFont/template.sass.swig b/gulp/tasks/iconFont/template.sass.swig deleted file mode 100644 index 17cb6cb1d..000000000 --- a/gulp/tasks/iconFont/template.sass.swig +++ /dev/null @@ -1,33 +0,0 @@ -// {{comment}} - -@font-face - font-family: {{fontName}} - src: url("{{fontPath}}/{{fontName}}.eot") - src: url("{{fontPath}}/{{fontName}}.eot?#iefix") format('embedded-opentype'), url("{{fontPath}}/{{fontName}}.woff") format('woff'), url("{{fontPath}}/{{fontName}}.ttf") format('truetype'), url("{{fontPath}}/{{fontName}}.svg#{{fontName}}") format('svg') - font-weight: normal - font-style: normal - -=icon($content) - &:before - -moz-osx-font-smoothing: grayscale - -webkit-font-smoothing: antialiased - content: $content - font-family: '{{fontName}}' - font-style: normal - font-variant: normal - font-weight: normal - line-height: 1 - speak: none - text-transform: none - @content - -{% for icon in icons -%} -=icon--{{icon.name}} - +icon("\{{icon.code}}") - @content - -.icon - &.-{{icon.name}} - +icon--{{icon.name}} - -{% endfor %} diff --git a/gulp/tasks/karma.js b/gulp/tasks/karma.js index 37e2c759d..505bc5859 100644 --- a/gulp/tasks/karma.js +++ b/gulp/tasks/karma.js @@ -8,6 +8,16 @@ var karmaTask = function(done) { }, done); }; +var karmaContinuosTask = function(done) { + karma.server.start({ + configFile: process.cwd() + '/karma.conf.js', + action: 'watch' + }, done); +}; + + gulp.task('karma', karmaTask); +gulp.task('test', karmaContinuosTask); + module.exports = karmaTask; diff --git a/gulp/tasks/production.js b/gulp/tasks/production.js index 7cab1cff6..aee8e5dc7 100644 --- a/gulp/tasks/production.js +++ b/gulp/tasks/production.js @@ -3,5 +3,5 @@ var gulp = require('gulp'); // Run this to compress all the things! gulp.task('production', ['karma'], function(){ // This runs only if the karma tests pass - gulp.start(['markup', 'images', 'iconFont', 'minifyCss', 'uglifyJs']) + gulp.start(['markup', 'images', 'minifyCss', 'uglifyJs']) }); diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js index 1a72d81bc..922d0e11b 100644 --- a/gulp/tasks/watch.js +++ b/gulp/tasks/watch.js @@ -6,9 +6,9 @@ var gulp = require('gulp'); var config = require('../config'); -gulp.task('watch', ['watchify','browserSync'], function() { - gulp.watch(config.sass.src, ['sass']); - gulp.watch(config.images.src, ['images']); - gulp.watch(config.markup.src, ['markup']); +gulp.task('watch', ['watchify', 'karma'], function() { + //gulp.watch(config.sass.src, ['sass']); + //gulp.watch(config.images.src, ['images']); + //gulp.watch(config.markup.src, ['markup']); // Watchify will watch and recompile our JS, so no need to gulp.watch it }); diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 000000000..fd99801f5 --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,64 @@ +// Karma configuration + +module.exports = function (config) { + config.set({ + + // base path that will be used to resolve all patterns (eg. files, exclude) + basePath: '', + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha', 'chai', 'fixture', 'browserify'], + + // list of files / patterns to load in the browser + files: [ + 'test/*.js', + 'test/fixtures/*.html' + ], + + // list of files to exclude + exclude: [], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'test/*': ['browserify'], + 'test/fixtures/*.html': ['html2js'] + }, + + browserify: { + debug: true, + extensions: ['.js'] + }, + + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['nyan'], + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + logLevel: config.LOG_INFO, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Chrome'], + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + singleRun: false, + + // Helps to address an issue on TravisCI where activity can time out + browserNoActivityTimeout: 30000 + + }); +}; diff --git a/package.json b/package.json index 9d3e03062..5a7832840 100644 --- a/package.json +++ b/package.json @@ -1,43 +1,77 @@ { - "name": "interact.js", - "version": "1.2.4", - "repository": { - "type": "git", - "url": "https://github.com/taye/interact.js.git" - }, - "main": "interact.js", - "description": "Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)", - "homepage": "http://interactjs.io", - "authors": [{ - "name" : "Taye Adeyemi", - "email": "dev@taye.me", - "url" : "http://taye.me" - }], - "keywords": [ - "interact.js", - "draggable", - "droppable", - "drag", - "drop", - "drag and drop", - "resize", - "touch", - "multi-touch", - "gesture", - "snap", - "inertia", - "grid", - "autoscroll", - "SVG" - ], - "license" : "MIT", - "spm": { - "main": "interact.js", - "ignore": [ - "test", - "demo", - "img", - "docs" - ] + "name": "interact.js", + "version": "1.2.4", + "repository": { + "type": "git", + "url": "https://github.com/taye/interact.js.git" + }, + "main": "interact.js", + "description": "Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)", + "homepage": "http://interactjs.io", + "authors": [ + { + "name": "Taye Adeyemi", + "email": "dev@taye.me", + "url": "http://taye.me" } + ], + "keywords": [ + "interact.js", + "draggable", + "droppable", + "drag", + "drop", + "drag and drop", + "resize", + "touch", + "multi-touch", + "gesture", + "snap", + "inertia", + "grid", + "autoscroll", + "SVG" + ], + "license": "MIT", + "spm": { + "main": "interact.js", + "ignore": [ + "test", + "demo", + "img", + "docs" + ] + }, + "devDependencies": { + "browser-sync": "^2.7.4", + "browserify": "^10.2.1", + "chai": "^2.3.0", + "gulp": "^3.8.11", + "gulp-autoprefixer": "^2.3.0", + "gulp-changed": "^1.2.1", + "gulp-filesize": "0.0.6", + "gulp-imagemin": "^2.2.1", + "gulp-minify-css": "^1.1.1", + "gulp-notify": "^2.2.0", + "gulp-rename": "^1.2.2", + "gulp-sass": "^2.0.1", + "gulp-sourcemaps": "^1.5.2", + "gulp-uglify": "^1.2.0", + "gulp-util": "^3.0.4", + "karma": "^0.12.32", + "karma-browserify": "^4.2.1", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^0.1.12", + "karma-fixture": "^0.2.4", + "karma-html2js-preprocessor": "^0.1.0", + "karma-mocha": "^0.1.10", + "karma-nyan-reporter": "0.0.60", + "lodash": "^3.9.2", + "merge-stream": "^0.1.7", + "mocha": "^2.2.5", + "pretty-hrtime": "^1.0.0", + "require-dir": "^0.3.0", + "vinyl-source-stream": "^1.1.0", + "watchify": "^3.2.1" + } } diff --git a/interact.js b/src/interact.js similarity index 100% rename from interact.js rename to src/interact.js diff --git a/src/utils/window.js b/src/utils/window.js new file mode 100644 index 000000000..4861bbaae --- /dev/null +++ b/src/utils/window.js @@ -0,0 +1,3 @@ +var interactWindow = typeof window === 'undefined' ? undefined : window; + +module.exports = interactWindow; \ No newline at end of file diff --git a/test/fixtures/baseFixture.html b/test/fixtures/baseFixture.html new file mode 100644 index 000000000..281c6866c --- /dev/null +++ b/test/fixtures/baseFixture.html @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/test/data.js b/test/fixtures/data.js similarity index 100% rename from test/data.js rename to test/fixtures/data.js diff --git a/test/test.js b/test/test.js index 50d86ffa0..bb9080210 100644 --- a/test/test.js +++ b/test/test.js @@ -1,11 +1,16 @@ +var interact = require('../src/interact'); + +require('./fixtures/data'); + var expect = chai.expect, should = chai.should(), debug = interact.debug(), PointerEvent = window.PointerEvent || window.MSPointerEvent; -function blank () {} +function blank() { +} -function mockEvent (options, target, currentTarget) { +function mockEvent(options, target, currentTarget) { 'use strict'; options.target = options.target || target; @@ -17,8 +22,8 @@ function mockEvent (options, target, currentTarget) { type: options.type, pageX: options.x, pageY: options.y, - clientX: options.x - (options.scrollX|0), - clientY: options.y - (options.scrollY|0), + clientX: options.x - (options.scrollX | 0), + clientY: options.y - (options.scrollY | 0), touches: options.touches && options.touches.map(mockEvent), changedTouches: options.changed && options.changed.map(mockEvent), pointerId: options.pointerId || 0, @@ -33,15 +38,24 @@ function mockEvent (options, target, currentTarget) { describe('interact', function () { 'use strict'; + before(function () { + fixture.setBase('test/fixtures'); + fixture.load('baseFixture.html'); + }); + + afterEach(function () { + fixture.cleanup(); + }); + describe('when called as a function', function () { var validSelector = 'svg .draggable, body button'; it('should return an Interactable when given an Element', function () { - var bod = interact(document.body); + var el = interact(fixture.el); - expect(bod).to.be.an.instanceof(debug.Interactable); + expect(el).to.be.an.instanceof(debug.Interactable); - bod.element().should.equal(document.body); + el.element().should.equal(fixture.el); }); it('should return an Interactable when given a valid CSS selector string', function () { @@ -63,7 +77,8 @@ describe('interact', function () { error.should.be.instanceof(DOMException); }); - it('should return the same value from a given parameter unless returned Interactable is unset', function () { var iBody = interact(document.body), + it('should return the same value from a given parameter unless returned Interactable is unset', function () { + var iBody = interact(document.body), iSelector = interact(validSelector); interact(document.body).should.equal(iBody); @@ -88,14 +103,14 @@ describe('Interactable', function () { var defaults = debug.defaultOptions, iable = interact(document.createElement('div')), simpleOptions = { - draggable : 'draggable', - dropzone : 'dropzone', - resizable : 'resizable', - squareResize : 'squareResize', - gesturable : 'gesturable', - styleCursor : 'styleCursor', - origin : 'origin', - deltaSource : 'deltaSource' + draggable: 'draggable', + dropzone: 'dropzone', + resizable: 'resizable', + squareResize: 'squareResize', + gesturable: 'gesturable', + styleCursor: 'styleCursor', + origin: 'origin', + deltaSource: 'deltaSource' }, enableOptions = [ 'snap', @@ -143,7 +158,9 @@ describe('Interactable', function () { i, action, actions = ['drag', 'resizexy', 'resizex', 'resizey', 'gesture'], - returnActionI = function () { return actions[i]; }; + returnActionI = function () { + return actions[i]; + }; it('should set set the function used to determine actions on pointer down events', function () { iDiv.actionChecker(returnActionI); @@ -186,9 +203,9 @@ describe('Events', function () { describe('drag sequence', function () { draggable.draggable({ - onstart: pushEvent, - onmove: pushEvent, - onend: pushEvent + onstart: pushEvent, + onmove: pushEvent, + onend: pushEvent }).actionChecker(function () { return 'drag'; }); @@ -196,11 +213,11 @@ describe('Events', function () { debug.pointerDown(mockEvents[0]); debug.pointerMove(mockEvents[1]); debug.pointerMove(mockEvents[2]); - debug.pointerUp (mockEvents[3]); + debug.pointerUp(mockEvents[3]); it('should be triggered by mousedown -> mousemove -> mouseup sequence', function () { events.length.should.equal(4); - + events[0].type.should.equal('dragstart'); events[1].type.should.equal('dragmove'); events[2].type.should.equal('dragmove'); @@ -278,12 +295,15 @@ describe('Events', function () { onstart: pushEvent, onmove: pushEvent, onend: pushEvent - }).actionChecker(function () { return 'gesture'; }), + }).actionChecker(function () { + return 'gesture'; + }), mockEvents = data.touch2Move2End2.map(function (e) { return mockEvent(e, element); }), gestureEvents = [], - eventMap = [1, 2, 3, 4]; + eventMap = [1, 2, 3, 4], + debugRecord; // The pointers must be recorded here since event listeners // don't call the related functions. The recorded pointermove events @@ -292,10 +312,12 @@ describe('Events', function () { debug.pointerDown(mockEvents[0]); debug.pointerDown(mockEvents[1]); - debug[PointerEvent? 'recordPointers': 'recordTouches'](mockEvents[2]); + debugRecord = PointerEvent && debug.recordPointer || debug.recordTouches || debug.recordPointer; + + debugRecord(mockEvents[2]); debug.pointerMove(mockEvents[2]); - debug[PointerEvent? 'recordPointers': 'recordTouches'](mockEvents[3]); + debugRecord(mockEvents[3]); debug.pointerMove(mockEvents[3]); debug.pointerUp(mockEvents[4]); @@ -337,12 +359,12 @@ describe('Events', function () { mEvent = mockEvents[eventMap[i]], gEvent = gestureEvents[i], i < gestureEvents.length; i++) { - var average = PointerEvent? mEvent: interact.getTouchAverage(mEvent), + var average = PointerEvent ? mEvent : interact.getTouchAverage(mEvent), coords = ['pageX', 'pageY', 'clientX', 'clientY']; - - coords.forEach(function (coord) { - gEvent[coord].should.equal(average[coord]); - }); + + coords.forEach(function (coord) { + gEvent[coord].should.equal(average[coord]); + }); } }); }); From 988f92a261d72a7481394db1fbfbed43afb14f0e Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 15:20:46 +0100 Subject: [PATCH 003/131] Move "global" variables into `scope` object --- src/interact.js | 1682 ++++++++++++++++++++++++----------------------- 1 file changed, 843 insertions(+), 839 deletions(-) diff --git a/src/interact.js b/src/interact.js index 70d4d5865..3915f53c5 100644 --- a/src/interact.js +++ b/src/interact.js @@ -11,338 +11,344 @@ // return early if there's no window to work with (eg. Node.js) if (!realWindow) { return; } - var // get wrapped window if using Shadow DOM polyfill - window = (function () { - // create a TextNode - var el = realWindow.document.createTextNode(''); - - // check if it's wrapped by a polyfill - if (el.ownerDocument !== realWindow.document - && typeof realWindow.wrap === 'function' - && realWindow.wrap(el) === el) { - // return wrapped window - return realWindow.wrap(realWindow); - } - - // no Shadow DOM polyfil or native implementation - return realWindow; - }()), - - document = window.document, - DocumentFragment = window.DocumentFragment || blank, - SVGElement = window.SVGElement || blank, - SVGSVGElement = window.SVGSVGElement || blank, - SVGElementInstance = window.SVGElementInstance || blank, - HTMLElement = window.HTMLElement || window.Element, - - PointerEvent = (window.PointerEvent || window.MSPointerEvent), - pEventTypes, - - hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); }, - - tmpXY = {}, // reduce object creation in getXY() - - documents = [], // all documents being listened to - - interactables = [], // all set interactables - interactions = [], // all interactions - - dynamicDrop = false, - - // { - // type: { - // selectors: ['selector', ...], - // contexts : [document, ...], - // listeners: [[listener, useCapture], ...] - // } - // } - delegatedEvents = {}, - - defaultOptions = { - base: { - accept : null, - actionChecker : null, - styleCursor : true, - preventDefault: 'auto', - origin : { x: 0, y: 0 }, - deltaSource : 'page', - allowFrom : null, - ignoreFrom : null, - _context : document, - dropChecker : null - }, + var scope = {}; - drag: { - enabled: false, - manualStart: true, - max: Infinity, - maxPerElement: 1, + scope.realWindow = realWindow; - snap: null, - restrict: null, - inertia: null, - autoScroll: null, + // get wrapped window if using Shadow DOM polyfill + scope.window = (function () { + // create a TextNode + var el = realWindow.document.createTextNode(''); - axis: 'xy', - }, + // check if it's wrapped by a polyfill + if (el.ownerDocument !== realWindow.document + && typeof realWindow.wrap === 'function' + && realWindow.wrap(el) === el) { + // return wrapped window + return realWindow.wrap(realWindow); + } - drop: { - enabled: false, - accept: null, - overlap: 'pointer' - }, + // no Shadow DOM polyfil or native implementation + return realWindow; + }()); - resize: { - enabled: false, - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - square: false, - axis: 'xy', - - // use default margin - margin: NaN, - - // object with props left, right, top, bottom which are - // true/false values to resize when the pointer is over that edge, - // CSS selectors to match the handles for each direction - // or the Elements for each handle - edges: null, - - // a value of 'none' will limit the resize rect to a minimum of 0x0 - // 'negate' will alow the rect to have negative width/height - // 'reposition' will keep the width/height positive by swapping - // the top and bottom edges and/or swapping the left and right edges - invert: 'none' - }, + scope.blank = function () {}; + + scope.document = scope.window.document; + scope.DocumentFragment = scope.window.DocumentFragment || scope.blank; + scope.SVGElement = scope.window.SVGElement || scope.blank; + scope.SVGSVGElement = scope.window.SVGSVGElement || scope.blank; + scope.SVGElementInstance = scope.window.SVGElementInstance || scope.blank; + scope.HTMLElement = scope.window.HTMLElement || scope.window.Element; + + scope.PointerEvent = (scope.window.PointerEvent || scope.window.MSPointerEvent); + scope.pEventTypes = null; + + scope.hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); }; + + scope.tmpXY = {}; // reduce object creation in getXY() + + scope.documents = []; // all documents being listened to + + scope.interactables = []; // all set interactables + scope.interactions = []; // all interactions + + scope.dynamicDrop = false; + + // { + // type: { + // selectors: ['selector', ...], + // contexts : [document, ...], + // listeners: [[listener, useCapture], ...] + // } + // } + scope.delegatedEvents = {}; + + scope.defaultOptions = { + base: { + accept : null, + actionChecker : null, + styleCursor : true, + preventDefault: 'auto', + origin : { x: 0, y: 0 }, + deltaSource : 'page', + allowFrom : null, + ignoreFrom : null, + _context : scope.document, + dropChecker : null + }, - gesture: { - manualStart: false, - enabled: false, - max: Infinity, - maxPerElement: 1, + drag: { + enabled: false, + manualStart: true, + max: Infinity, + maxPerElement: 1, - restrict: null - }, + snap: null, + restrict: null, + inertia: null, + autoScroll: null, - perAction: { - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: { - enabled : false, - endOnly : false, - range : Infinity, - targets : null, - offsets : null, - - relativePoints: null - }, - - restrict: { - enabled: false, - endOnly: false - }, - - autoScroll: { - enabled : false, - container : null, // the item that is scrolled (Window or HTMLElement) - margin : 60, - speed : 300 // the scroll speed in pixels per second - }, - - inertia: { - enabled : false, - resistance : 10, // the lambda in exponential decay - minSpeed : 100, // target speed must be above this for inertia to start - endSpeed : 10, // the speed at which inertia is slow enough to stop - allowResume : true, // allow resuming an action in inertia phase - zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 - smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia - } - }, + axis: 'xy', + }, - _holdDuration: 600 + drop: { + enabled: false, + accept: null, + overlap: 'pointer' }, - // Things related to autoScroll - autoScroll = { - interaction: null, - i: null, // the handle returned by window.setInterval - x: 0, y: 0, // Direction each pulse is to scroll in + resize: { + enabled: false, + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + square: false, + axis: 'xy', + + // use default margin + margin: NaN, + + // object with props left, right, top, bottom which are + // true/false values to resize when the pointer is over that edge, + // CSS selectors to match the handles for each direction + // or the Elements for each handle + edges: null, + + // a value of 'none' will limit the resize rect to a minimum of 0x0 + // 'negate' will alow the rect to have negative width/height + // 'reposition' will keep the width/height positive by swapping + // the top and bottom edges and/or swapping the left and right edges + invert: 'none' + }, - // scroll the window by the values in scroll.x/y - scroll: function () { - var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll, - container = options.container || getWindow(autoScroll.interaction.element), - now = new Date().getTime(), - // change in time in seconds - dt = (now - autoScroll.prevTime) / 1000, - // displacement - s = options.speed * dt; - - if (s >= 1) { - if (isWindow(container)) { - container.scrollBy(autoScroll.x * s, autoScroll.y * s); - } - else if (container) { - container.scrollLeft += autoScroll.x * s; - container.scrollTop += autoScroll.y * s; - } + gesture: { + manualStart: false, + enabled: false, + max: Infinity, + maxPerElement: 1, - autoScroll.prevTime = now; - } + restrict: null + }, - if (autoScroll.isScrolling) { - cancelFrame(autoScroll.i); - autoScroll.i = reqFrame(autoScroll.scroll); - } - }, + perAction: { + manualStart: false, + max: Infinity, + maxPerElement: 1, - isScrolling: false, - prevTime: 0, + snap: { + enabled : false, + endOnly : false, + range : Infinity, + targets : null, + offsets : null, - start: function (interaction) { - autoScroll.isScrolling = true; - cancelFrame(autoScroll.i); + relativePoints: null + }, + + restrict: { + enabled: false, + endOnly: false + }, - autoScroll.interaction = interaction; - autoScroll.prevTime = new Date().getTime(); - autoScroll.i = reqFrame(autoScroll.scroll); + autoScroll: { + enabled : false, + container : null, // the item that is scrolled (Window or HTMLElement) + margin : 60, + speed : 300 // the scroll speed in pixels per second }, - stop: function () { - autoScroll.isScrolling = false; - cancelFrame(autoScroll.i); + inertia: { + enabled : false, + resistance : 10, // the lambda in exponential decay + minSpeed : 100, // target speed must be above this for inertia to start + endSpeed : 10, // the speed at which inertia is slow enough to stop + allowResume : true, // allow resuming an action in inertia phase + zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 + smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia } }, - // Does the browser support touch input? - supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch), - - // Does the browser support PointerEvents - supportsPointerEvent = !!PointerEvent, - - // Less Precision with touch input - margin = supportsTouch || supportsPointerEvent? 20: 10, - - pointerMoveTolerance = 1, - - // for ignoring browser's simulated mouse events - prevTouchTime = 0, - - // Allow this many interactions to happen simultaneously - maxInteractions = Infinity, - - // Check if is IE9 or older - actionCursors = (document.all && !window.atob) ? { - drag : 'move', - resizex : 'e-resize', - resizey : 's-resize', - resizexy: 'se-resize', - - resizetop : 'n-resize', - resizeleft : 'w-resize', - resizebottom : 's-resize', - resizeright : 'e-resize', - resizetopleft : 'se-resize', - resizebottomright: 'se-resize', - resizetopright : 'ne-resize', - resizebottomleft : 'ne-resize', - - gesture : '' - } : { - drag : 'move', - resizex : 'ew-resize', - resizey : 'ns-resize', - resizexy: 'nwse-resize', - - resizetop : 'ns-resize', - resizeleft : 'ew-resize', - resizebottom : 'ns-resize', - resizeright : 'ew-resize', - resizetopleft : 'nwse-resize', - resizebottomright: 'nwse-resize', - resizetopright : 'nesw-resize', - resizebottomleft : 'nesw-resize', - - gesture : '' + _holdDuration: 600 + }; + + // Things related to autoScroll + scope.autoScroll = { + interaction: null, + i: null, // the handle returned by window.setInterval + x: 0, y: 0, // Direction each pulse is to scroll in + + // scroll the window by the values in scroll.x/y + scroll: function () { + var options = scope.autoScroll.interaction.target.options[scope.autoScroll.interaction.prepared.name].autoScroll, + container = options.container || scope.getWindow(scope.autoScroll.interaction.element), + now = new Date().getTime(), + // change in time in seconds + dt = (now - scope.autoScroll.prevTime) / 1000, + // displacement + s = options.speed * dt; + + if (s >= 1) { + if (scope.isWindow(container)) { + container.scrollBy(scope.autoScroll.x * s, scope.autoScroll.y * s); + } + else if (container) { + container.scrollLeft += scope.autoScroll.x * s; + container.scrollTop += scope.autoScroll.y * s; + } + + scope.autoScroll.prevTime = now; + } + + if (scope.autoScroll.isScrolling) { + cancelFrame(scope.autoScroll.i); + scope.autoScroll.i = reqFrame(scope.autoScroll.scroll); + } }, - actionIsEnabled = { - drag : true, - resize : true, - gesture: true + isScrolling: false, + prevTime: 0, + + start: function (interaction) { + scope.autoScroll.isScrolling = true; + cancelFrame(scope.autoScroll.i); + + scope.autoScroll.interaction = interaction; + scope.autoScroll.prevTime = new Date().getTime(); + scope.autoScroll.i = reqFrame(scope.autoScroll.scroll); }, - // because Webkit and Opera still use 'mousewheel' event type - wheelEvent = 'onmousewheel' in document? 'mousewheel': 'wheel', - - eventTypes = [ - 'dragstart', - 'dragmove', - 'draginertiastart', - 'dragend', - 'dragenter', - 'dragleave', - 'dropactivate', - 'dropdeactivate', - 'dropmove', - 'drop', - 'resizestart', - 'resizemove', - 'resizeinertiastart', - 'resizeend', - 'gesturestart', - 'gesturemove', - 'gestureinertiastart', - 'gestureend', - - 'down', - 'move', - 'up', - 'cancel', - 'tap', - 'doubletap', - 'hold' - ], - - globalEvents = {}, - - // Opera Mobile must be handled differently - isOperaMobile = navigator.appName == 'Opera' && - supportsTouch && - navigator.userAgent.match('Presto'), - - // scrolling doesn't change the result of - // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 - isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) - && /OS [1-7][^\d]/.test(navigator.appVersion)), - - // prefix matchesSelector - prefixedMatchesSelector = 'matches' in Element.prototype? - 'matches': 'webkitMatchesSelector' in Element.prototype? - 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? - 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? - 'oMatchesSelector': 'msMatchesSelector', - - // will be polyfill function if browser is IE8 - ie8MatchesSelector, + stop: function () { + scope.autoScroll.isScrolling = false; + cancelFrame(scope.autoScroll.i); + } + }; + + // Does the browser support touch input? + scope.supportsTouch = (('ontouchstart' in scope.window) || scope.window.DocumentTouch && scope.document instanceof scope.window.DocumentTouch); + + // Does the browser support PointerEvents + scope.supportsPointerEvent = !!scope.PointerEvent; + + // Less Precision with touch input + scope.margin = scope.supportsTouch || scope.supportsPointerEvent? 20: 10; + + scope.pointerMoveTolerance = 1; + + // for ignoring browser's simulated mouse events + scope.prevTouchTime = 0; + + // Allow this many interactions to happen simultaneously + scope.maxInteractions = Infinity; + + // Check if is IE9 or older + scope.actionCursors = (scope.document.all && !scope.window.atob) ? { + drag : 'move', + resizex : 'e-resize', + resizey : 's-resize', + resizexy: 'se-resize', + + resizetop : 'n-resize', + resizeleft : 'w-resize', + resizebottom : 's-resize', + resizeright : 'e-resize', + resizetopleft : 'se-resize', + resizebottomright: 'se-resize', + resizetopright : 'ne-resize', + resizebottomleft : 'ne-resize', + + gesture : '' + } : { + drag : 'move', + resizex : 'ew-resize', + resizey : 'ns-resize', + resizexy: 'nwse-resize', + + resizetop : 'ns-resize', + resizeleft : 'ew-resize', + resizebottom : 'ns-resize', + resizeright : 'ew-resize', + resizetopleft : 'nwse-resize', + resizebottomright: 'nwse-resize', + resizetopright : 'nesw-resize', + resizebottomleft : 'nesw-resize', + + gesture : '' + }; + + scope.actionIsEnabled = { + drag : true, + resize : true, + gesture: true + }; + + // because Webkit and Opera still use 'mousewheel' event type + scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel'; + + scope.eventTypes = [ + 'dragstart', + 'dragmove', + 'draginertiastart', + 'dragend', + 'dragenter', + 'dragleave', + 'dropactivate', + 'dropdeactivate', + 'dropmove', + 'drop', + 'resizestart', + 'resizemove', + 'resizeinertiastart', + 'resizeend', + 'gesturestart', + 'gesturemove', + 'gestureinertiastart', + 'gestureend', + + 'down', + 'move', + 'up', + 'cancel', + 'tap', + 'doubletap', + 'hold' + ]; + + scope.globalEvents = {}; + + // Opera Mobile must be handled differently + scope.isOperaMobile = navigator.appName == 'Opera' && + scope.supportsTouch && + navigator.userAgent.match('Presto'); + + // scrolling doesn't change the result of + // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 + scope.isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) + && /OS [1-7][^\d]/.test(navigator.appVersion)); + + // prefix matchesSelector + scope.prefixedMatchesSelector = 'matches' in Element.prototype? + 'matches': 'webkitMatchesSelector' in Element.prototype? + 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? + 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? + 'oMatchesSelector': 'msMatchesSelector'; + + // will be polyfill function if browser is IE8 + scope.ie8MatchesSelector = null; // native requestAnimationFrame or polyfill - reqFrame = realWindow.requestAnimationFrame, + var reqFrame = realWindow.requestAnimationFrame, cancelFrame = realWindow.cancelAnimationFrame, // Events wrapper events = (function () { - var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window), + var useAttachEvent = ('attachEvent' in scope.window) && !('addEventListener' in scope.window), addEvent = useAttachEvent? 'attachEvent': 'addEventListener', removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', on = useAttachEvent? 'on': '', @@ -393,8 +399,8 @@ event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; if (/mouse|click/.test(event.type)) { - event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; - event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; + event.pageX = event.clientX + scope.getWindow(element).document.documentElement.scrollLeft; + event.pageY = event.clientY + scope.getWindow(element).document.documentElement.scrollTop; } listener(event); @@ -513,46 +519,44 @@ }; }()); - function blank () {} - - function isElement (o) { + scope.isElement = function (o) { if (!o || (typeof o !== 'object')) { return false; } - var _window = getWindow(o) || window; + var _window = scope.getWindow(o) || scope.window; return (/object|function/.test(typeof _window.Element) ? o instanceof _window.Element //DOM2 : o.nodeType === 1 && typeof o.nodeName === "string"); - } - function isWindow (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); } - function isDocFrag (thing) { return !!thing && thing instanceof DocumentFragment; } - function isArray (thing) { - return isObject(thing) + }; + scope.isWindow = function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }; + scope.isDocFrag = function (thing) { return !!thing && thing instanceof scope.DocumentFragment; }; + scope.isArray = function (thing) { + return scope.isObject(thing) && (typeof thing.length !== undefined) - && isFunction(thing.splice); - } - function isObject (thing) { return !!thing && (typeof thing === 'object'); } - function isFunction (thing) { return typeof thing === 'function'; } - function isNumber (thing) { return typeof thing === 'number' ; } - function isBool (thing) { return typeof thing === 'boolean' ; } - function isString (thing) { return typeof thing === 'string' ; } + && scope.isFunction(thing.splice); + }; + scope.isObject = function (thing) { return !!thing && (typeof thing === 'object'); }; + scope.isFunction = function (thing) { return typeof thing === 'function'; }; + scope.isNumber = function (thing) { return typeof thing === 'number' ; }; + scope.isBool = function (thing) { return typeof thing === 'boolean' ; }; + scope.isString = function (thing) { return typeof thing === 'string' ; }; - function trySelector (value) { - if (!isString(value)) { return false; } + scope.trySelector = function (value) { + if (!scope.isString(value)) { return false; } // an exception will be raised if it is invalid - document.querySelector(value); + scope.document.querySelector(value); return true; - } + }; - function extend (dest, source) { + scope.extend = function (dest, source) { for (var prop in source) { dest[prop] = source[prop]; } return dest; - } + }; - function copyCoords (dest, src) { + scope.copyCoords = function (dest, src) { dest.page = dest.page || {}; dest.page.x = src.page.x; dest.page.y = src.page.y; @@ -562,30 +566,30 @@ dest.client.y = src.client.y; dest.timeStamp = src.timeStamp; - } + }; - function setEventXY (targetObj, pointer, interaction) { + scope.setEventXY = function (targetObj, pointer, interaction) { if (!pointer) { if (interaction.pointerIds.length > 1) { - pointer = touchAverage(interaction.pointers); + pointer = scope.touchAverage(interaction.pointers); } else { pointer = interaction.pointers[0]; } } - getPageXY(pointer, tmpXY, interaction); - targetObj.page.x = tmpXY.x; - targetObj.page.y = tmpXY.y; + scope.getPageXY(pointer, scope.tmpXY, interaction); + targetObj.page.x = scope.tmpXY.x; + targetObj.page.y = scope.tmpXY.y; - getClientXY(pointer, tmpXY, interaction); - targetObj.client.x = tmpXY.x; - targetObj.client.y = tmpXY.y; + scope.getClientXY(pointer, scope.tmpXY, interaction); + targetObj.client.x = scope.tmpXY.x; + targetObj.client.y = scope.tmpXY.y; targetObj.timeStamp = new Date().getTime(); - } + }; - function setEventDeltas (targetObj, prev, cur) { + scope.setEventDeltas = function (targetObj, prev, cur) { targetObj.page.x = cur.page.x - prev.page.x; targetObj.page.y = cur.page.y - prev.page.y; targetObj.client.x = cur.client.x - prev.client.x; @@ -594,17 +598,17 @@ // set pointer velocity var dt = Math.max(targetObj.timeStamp / 1000, 0.001); - targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; + targetObj.page.speed = scope.hypot(targetObj.page.x, targetObj.page.y) / dt; targetObj.page.vx = targetObj.page.x / dt; targetObj.page.vy = targetObj.page.y / dt; - targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; + targetObj.client.speed = scope.hypot(targetObj.client.x, targetObj.page.y) / dt; targetObj.client.vx = targetObj.client.x / dt; targetObj.client.vy = targetObj.client.y / dt; - } + }; // Get specified X/Y coords for mouse or event.touches[0] - function getXY (type, pointer, xy) { + scope.getXY = function (type, pointer, xy) { xy = xy || {}; type = type || 'page'; @@ -612,16 +616,16 @@ xy.y = pointer[type + 'Y']; return xy; - } + }; - function getPageXY (pointer, page, interaction) { + scope.getPageXY = function (pointer, page, interaction) { page = page || {}; if (pointer instanceof InteractEvent) { if (/inertiastart/.test(pointer.type)) { interaction = interaction || pointer.interaction; - extend(page, interaction.inertiaStatus.upCoords.page); + scope.extend(page, interaction.inertiaStatus.upCoords.page); page.x += interaction.inertiaStatus.sx; page.y += interaction.inertiaStatus.sy; @@ -632,25 +636,25 @@ } } // Opera Mobile handles the viewport and scrolling oddly - else if (isOperaMobile) { - getXY('screen', pointer, page); + else if (scope.isOperaMobile) { + scope.getXY('screen', pointer, page); - page.x += window.scrollX; - page.y += window.scrollY; + page.x += scope.window.scrollX; + page.y += scope.window.scrollY; } else { - getXY('page', pointer, page); + scope.getXY('page', pointer, page); } return page; - } + }; - function getClientXY (pointer, client, interaction) { + scope.getClientXY = function (pointer, client, interaction) { client = client || {}; if (pointer instanceof InteractEvent) { if (/inertiastart/.test(pointer.type)) { - extend(client, interaction.inertiaStatus.upCoords.client); + scope.extend(client, interaction.inertiaStatus.upCoords.client); client.x += interaction.inertiaStatus.sx; client.y += interaction.inertiaStatus.sy; @@ -662,45 +666,45 @@ } else { // Opera Mobile handles the viewport and scrolling oddly - getXY(isOperaMobile? 'screen': 'client', pointer, client); + scope.getXY(scope.isOperaMobile? 'screen': 'client', pointer, client); } return client; - } + }; - function getScrollXY (win) { - win = win || window; + scope.getScrollXY = function (win) { + win = win || scope.window; return { x: win.scrollX || win.document.documentElement.scrollLeft, y: win.scrollY || win.document.documentElement.scrollTop }; - } + }; - function getPointerId (pointer) { - return isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; - } + scope.getPointerId = function (pointer) { + return scope.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; + }; - function getActualElement (element) { - return (element instanceof SVGElementInstance + scope.getActualElement = function (element) { + return (element instanceof scope.SVGElementInstance ? element.correspondingUseElement : element); - } + }; - function getWindow (node) { - if (isWindow(node)) { + scope.getWindow = function (node) { + if (scope.isWindow(node)) { return node; } var rootNode = (node.ownerDocument || node); - return rootNode.defaultView || rootNode.parentWindow || window; - } + return rootNode.defaultView || rootNode.parentWindow || scope.window; + }; - function getElementRect (element) { - var scroll = isIOS7orLower + scope.getElementRect = function (element) { + var scroll = scope.isIOS7orLower ? { x: 0, y: 0 } - : getScrollXY(getWindow(element)), - clientRect = (element instanceof SVGElement)? + : scope.getScrollXY(scope.getWindow(element)), + clientRect = (element instanceof scope.SVGElement)? element.getBoundingClientRect(): element.getClientRects()[0]; @@ -712,13 +716,13 @@ width : clientRect.width || clientRect.right - clientRect.left, height: clientRect.heigh || clientRect.bottom - clientRect.top }; - } + }; - function getTouchPair (event) { + scope.getTouchPair = function (event) { var touches = []; // array of touches is supplied - if (isArray(event)) { + if (scope.isArray(event)) { touches[0] = event[0]; touches[1] = event[1]; } @@ -741,10 +745,10 @@ } return touches; - } + }; - function touchAverage (event) { - var touches = getTouchPair(event); + scope.touchAverage = function (event) { + var touches = scope.getTouchPair(event); return { pageX: (touches[0].pageX + touches[1].pageX) / 2, @@ -752,14 +756,14 @@ clientX: (touches[0].clientX + touches[1].clientX) / 2, clientY: (touches[0].clientY + touches[1].clientY) / 2 }; - } + }; - function touchBBox (event) { + scope.touchBBox = function (event) { if (!event.length && !(event.touches && event.touches.length > 1)) { return; } - var touches = getTouchPair(event), + var touches = scope.getTouchPair(event), minX = Math.min(touches[0].pageX, touches[1].pageX), minY = Math.min(touches[0].pageY, touches[1].pageY), maxX = Math.max(touches[0].pageX, touches[1].pageX), @@ -773,33 +777,33 @@ width: maxX - minX, height: maxY - minY }; - } + }; - function touchDistance (event, deltaSource) { - deltaSource = deltaSource || defaultOptions.deltaSource; + scope.touchDistance = function (event, deltaSource) { + deltaSource = deltaSource || scope.defaultOptions.deltaSource; var sourceX = deltaSource + 'X', sourceY = deltaSource + 'Y', - touches = getTouchPair(event); + touches = scope.getTouchPair(event); var dx = touches[0][sourceX] - touches[1][sourceX], dy = touches[0][sourceY] - touches[1][sourceY]; - return hypot(dx, dy); - } + return scope.hypot(dx, dy); + }; - function touchAngle (event, prevAngle, deltaSource) { - deltaSource = deltaSource || defaultOptions.deltaSource; + scope.touchAngle = function (event, prevAngle, deltaSource) { + deltaSource = deltaSource || scope.defaultOptions.deltaSource; var sourceX = deltaSource + 'X', sourceY = deltaSource + 'Y', - touches = getTouchPair(event), + touches = scope.getTouchPair(event), dx = touches[0][sourceX] - touches[1][sourceX], dy = touches[0][sourceY] - touches[1][sourceY], angle = 180 * Math.atan(dy / dx) / Math.PI; - if (isNumber(prevAngle)) { + if (scope.isNumber(prevAngle)) { var dr = angle - prevAngle, drClamped = dr % 360; @@ -818,57 +822,57 @@ } return angle; - } + }; - function getOriginXY (interactable, element) { + scope.getOriginXY = function (interactable, element) { var origin = interactable ? interactable.options.origin - : defaultOptions.origin; + : scope.defaultOptions.origin; if (origin === 'parent') { - origin = parentElement(element); + origin = scope.parentElement(element); } else if (origin === 'self') { origin = interactable.getRect(element); } - else if (trySelector(origin)) { - origin = closest(element, origin) || { x: 0, y: 0 }; + else if (scope.trySelector(origin)) { + origin = scope.closest(element, origin) || { x: 0, y: 0 }; } - if (isFunction(origin)) { + if (scope.isFunction(origin)) { origin = origin(interactable && element); } - if (isElement(origin)) { - origin = getElementRect(origin); + if (scope.isElement(origin)) { + origin = scope.getElementRect(origin); } origin.x = ('x' in origin)? origin.x : origin.left; origin.y = ('y' in origin)? origin.y : origin.top; return origin; - } + }; // http://stackoverflow.com/a/5634528/2280888 - function _getQBezierValue(t, p1, p2, p3) { + scope._getQBezierValue = function (t, p1, p2, p3) { var iT = 1 - t; return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; - } + }; - function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) { + scope.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) { return { - x: _getQBezierValue(position, startX, cpX, endX), - y: _getQBezierValue(position, startY, cpY, endY) + x: scope._getQBezierValue(position, startX, cpX, endX), + y: scope._getQBezierValue(position, startY, cpY, endY) }; - } + }; // http://gizma.com/easing/ - function easeOutQuad (t, b, c, d) { + scope.easeOutQuad = function (t, b, c, d) { t /= d; return -c * t*(t-2) + b; - } + }; - function nodeContains (parent, child) { + scope.nodeContains = function (parent, child) { while (child) { if (child === parent) { return true; @@ -878,79 +882,79 @@ } return false; - } + }; - function closest (child, selector) { - var parent = parentElement(child); + scope.closest = function (child, selector) { + var parent = scope.parentElement(child); - while (isElement(parent)) { + while (scope.isElement(parent)) { if (matchesSelector(parent, selector)) { return parent; } - parent = parentElement(parent); + parent = scope.parentElement(parent); } return null; - } + }; - function parentElement (node) { + scope.parentElement = function (node) { var parent = node.parentNode; - if (isDocFrag(parent)) { + if (scope.isDocFrag(parent)) { // skip past #shado-root fragments - while ((parent = parent.host) && isDocFrag(parent)) {} + while ((parent = parent.host) && scope.isDocFrag(parent)) {} return parent; } return parent; - } + }; - function inContext (interactable, element) { + scope.inContext = function (interactable, element) { return interactable._context === element.ownerDocument - || nodeContains(interactable._context, element); - } + || scope.nodeContains(interactable._context, element); + }; - function testIgnore (interactable, interactableElement, element) { + scope.testIgnore = function (interactable, interactableElement, element) { var ignoreFrom = interactable.options.ignoreFrom; - if (!ignoreFrom || !isElement(element)) { return false; } + if (!ignoreFrom || !scope.isElement(element)) { return false; } - if (isString(ignoreFrom)) { + if (scope.isString(ignoreFrom)) { return matchesUpTo(element, ignoreFrom, interactableElement); } - else if (isElement(ignoreFrom)) { - return nodeContains(ignoreFrom, element); + else if (scope.isElement(ignoreFrom)) { + return scope.nodeContains(ignoreFrom, element); } return false; - } + }; - function testAllow (interactable, interactableElement, element) { + scope.testAllow = function (interactable, interactableElement, element) { var allowFrom = interactable.options.allowFrom; if (!allowFrom) { return true; } - if (!isElement(element)) { return false; } + if (!scope.isElement(element)) { return false; } - if (isString(allowFrom)) { + if (scope.isString(allowFrom)) { return matchesUpTo(element, allowFrom, interactableElement); } - else if (isElement(allowFrom)) { - return nodeContains(allowFrom, element); + else if (scope.isElement(allowFrom)) { + return scope.nodeContains(allowFrom, element); } return false; - } + }; - function checkAxis (axis, interactable) { + scope.checkAxis = function (axis, interactable) { if (!interactable) { return false; } var thisAxis = interactable.options.drag.axis; return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); - } + }; - function checkSnap (interactable, action) { + scope.checkSnap = function (interactable, action) { var options = interactable.options; if (/^resize/.test(action)) { @@ -958,9 +962,9 @@ } return options[action].snap && options[action].snap.enabled; - } + }; - function checkRestrict (interactable, action) { + scope.checkRestrict = function (interactable, action) { var options = interactable.options; if (/^resize/.test(action)) { @@ -968,9 +972,9 @@ } return options[action].restrict && options[action].restrict.enabled; - } + }; - function checkAutoScroll (interactable, action) { + scope.checkAutoScroll = function (interactable, action) { var options = interactable.options; if (/^resize/.test(action)) { @@ -978,9 +982,9 @@ } return options[action].autoScroll && options[action].autoScroll.enabled; - } + }; - function withinInteractionLimit (interactable, element, action) { + scope.withinInteractionLimit = function (interactable, element, action) { var options = interactable.options, maxActions = options[action.name].max, maxPerElement = options[action.name].maxPerElement, @@ -988,8 +992,8 @@ targetCount = 0, targetElementCount = 0; - for (var i = 0, len = interactions.length; i < len; i++) { - var interaction = interactions[i], + for (var i = 0, len = scope.interactions.length; i < len; i++) { + var interaction = scope.interactions[i], otherAction = interaction.prepared.name, active = interaction.interacting(); @@ -997,7 +1001,7 @@ activeInteractions++; - if (activeInteractions >= maxInteractions) { + if (activeInteractions >= scope.maxInteractions) { return false; } @@ -1018,11 +1022,11 @@ } } - return maxInteractions > 0; - } + return scope.maxInteractions > 0; + }; // Test for the element that's "above" all other qualifiers - function indexOfDeepestElement (elements) { + scope.indexOfDeepestElement = function (elements) { var dropzone, deepestZone = elements[0], index = deepestZone? 0: -1, @@ -1069,9 +1073,9 @@ // if this element is an svg element and the current deepest is // an HTMLElement - if (deepestZone instanceof HTMLElement - && dropzone instanceof SVGElement - && !(dropzone instanceof SVGSVGElement)) { + if (deepestZone instanceof scope.HTMLElement + && dropzone instanceof scope.SVGElement + && !(dropzone instanceof scope.SVGSVGElement)) { if (dropzone === deepestZone.parentNode) { continue; @@ -1122,7 +1126,7 @@ } return index; - } + }; function Interaction () { this.target = null; // current interactable being interacted with @@ -1163,7 +1167,7 @@ i : null }; - if (isFunction(Function.prototype.bind)) { + if (scope.isFunction(Function.prototype.bind)) { this.boundInertiaFrame = this.inertiaFrame.bind(this); this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); } @@ -1270,13 +1274,13 @@ this.mouse = false; - interactions.push(this); + scope.interactions.push(this); } Interaction.prototype = { - getPageXY : function (pointer, xy) { return getPageXY(pointer, xy, this); }, - getClientXY: function (pointer, xy) { return getClientXY(pointer, xy, this); }, - setEventXY : function (target, ptr) { return setEventXY(target, ptr, this); }, + getPageXY : function (pointer, xy) { return scope.getPageXY(pointer, xy, this); }, + getClientXY: function (pointer, xy) { return scope.getClientXY(pointer, xy, this); }, + setEventXY : function (target, ptr) { return scope.setEventXY(target, ptr, this); }, pointerOver: function (pointer, event, eventTarget) { if (this.prepared.name || !this.mouse) { return; } @@ -1288,8 +1292,8 @@ this.addPointer(pointer); if (this.target - && (testIgnore(this.target, this.element, eventTarget) - || !testAllow(this.target, this.element, eventTarget))) { + && (scope.testIgnore(this.target, this.element, eventTarget) + || !scope.testAllow(this.target, this.element, eventTarget))) { // if the eventTarget should be ignored or shouldn't be allowed // clear the previous target this.target = null; @@ -1298,23 +1302,23 @@ this.matchElements = []; } - var elementInteractable = interactables.get(eventTarget), + var elementInteractable = scope.interactables.get(eventTarget), elementAction = (elementInteractable - && !testIgnore(elementInteractable, eventTarget, eventTarget) - && testAllow(elementInteractable, eventTarget, eventTarget) + && !scope.testIgnore(elementInteractable, eventTarget, eventTarget) + && scope.testAllow(elementInteractable, eventTarget, eventTarget) && validateAction( elementInteractable.getAction(pointer, event, this, eventTarget), elementInteractable)); - if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { + if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { elementAction = null; } function pushCurMatches (interactable, selector) { if (interactable - && inContext(interactable, eventTarget) - && !testIgnore(interactable, eventTarget, eventTarget) - && testAllow(interactable, eventTarget, eventTarget) + && scope.inContext(interactable, eventTarget) + && !scope.testIgnore(interactable, eventTarget, eventTarget) + && scope.testAllow(interactable, eventTarget, eventTarget) && matchesSelector(eventTarget, selector)) { curMatches.push(interactable); @@ -1329,7 +1333,7 @@ this.matchElements = []; } else { - interactables.forEachSelector(pushCurMatches); + scope.interactables.forEachSelector(pushCurMatches); if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { this.matches = curMatches; @@ -1337,14 +1341,14 @@ this.pointerHover(pointer, event, this.matches, this.matchElements); events.add(eventTarget, - PointerEvent? pEventTypes.move : 'mousemove', + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', listeners.pointerHover); } else if (this.target) { - if (nodeContains(prevTargetElement, eventTarget)) { + if (scope.nodeContains(prevTargetElement, eventTarget)) { this.pointerHover(pointer, event, this.matches, this.matchElements); events.add(this.element, - PointerEvent? pEventTypes.move : 'mousemove', + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', listeners.pointerHover); } else { @@ -1394,9 +1398,9 @@ if (this.prepared.name) { return; } // Remove temporary event listeners for selector Interactables - if (!interactables.get(eventTarget)) { + if (!scope.interactables.get(eventTarget)) { events.remove(eventTarget, - PointerEvent? pEventTypes.move : 'mousemove', + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', listeners.pointerHover); } @@ -1408,21 +1412,21 @@ selectorDown: function (pointer, event, eventTarget, curEventTarget) { var that = this, // copy event to be used in timeout for IE8 - eventCopy = events.useAttachEvent? extend({}, event) : event, + eventCopy = events.useAttachEvent? scope.extend({}, event) : event, element = eventTarget, pointerIndex = this.addPointer(pointer), action; this.holdTimers[pointerIndex] = setTimeout(function () { that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); - }, defaultOptions._holdDuration); + }, scope.defaultOptions._holdDuration); this.pointerIsDown = true; // Check if the down event hits the current inertia target if (this.inertiaStatus.active && this.target.selector) { // climb up the DOM tree from the event target - while (isElement(element)) { + while (scope.isElement(element)) { // if this element is the current inertia target element if (element === this.element @@ -1436,7 +1440,7 @@ this.collectEventTargets(pointer, event, eventTarget, 'down'); return; } - element = parentElement(element); + element = scope.parentElement(element); } } @@ -1447,13 +1451,13 @@ } function pushMatches (interactable, selector, context) { - var elements = ie8MatchesSelector + var elements = scope.ie8MatchesSelector ? context.querySelectorAll(selector) : undefined; - if (inContext(interactable, element) - && !testIgnore(interactable, element, eventTarget) - && testAllow(interactable, element, eventTarget) + if (scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) && matchesSelector(element, selector, elements)) { that.matches.push(interactable); @@ -1465,14 +1469,14 @@ this.setEventXY(this.curCoords, pointer); this.downEvent = event; - while (isElement(element) && !action) { + while (scope.isElement(element) && !action) { this.matches = []; this.matchElements = []; - interactables.forEachSelector(pushMatches); + scope.interactables.forEachSelector(pushMatches); action = this.validateSelector(pointer, event, this.matches, this.matchElements); - element = parentElement(element); + element = scope.parentElement(element); } if (action) { @@ -1488,9 +1492,9 @@ // do these now since pointerDown isn't being called from here this.downTimes[pointerIndex] = new Date().getTime(); this.downTargets[pointerIndex] = eventTarget; - extend(this.downPointer, pointer); + scope.extend(this.downPointer, pointer); - copyCoords(this.prevCoords, this.curCoords); + scope.copyCoords(this.prevCoords, this.curCoords); this.pointerWasMoved = false; } @@ -1517,13 +1521,13 @@ // Otherwise, set the target if there is no action prepared if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { - var interactable = interactables.get(curEventTarget); + var interactable = scope.interactables.get(curEventTarget); if (interactable - && !testIgnore(interactable, curEventTarget, eventTarget) - && testAllow(interactable, curEventTarget, eventTarget) + && !scope.testIgnore(interactable, curEventTarget, eventTarget) + && scope.testAllow(interactable, curEventTarget, eventTarget) && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) - && withinInteractionLimit(interactable, curEventTarget, action)) { + && scope.withinInteractionLimit(interactable, curEventTarget, action)) { this.target = interactable; this.element = curEventTarget; } @@ -1558,7 +1562,7 @@ this.downTimes[pointerIndex] = new Date().getTime(); this.downTargets[pointerIndex] = eventTarget; - extend(this.downPointer, pointer); + scope.extend(this.downPointer, pointer); this.setEventXY(this.prevCoords); this.pointerWasMoved = false; @@ -1580,8 +1584,8 @@ setModifications: function (coords, preEnd) { var target = this.target, shouldMove = true, - shouldSnap = checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), - shouldRestrict = checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); + shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), + shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } @@ -1598,7 +1602,7 @@ setStartOffsets: function (action, interactable, element) { var rect = interactable.getRect(element), - origin = getOriginXY(interactable, element), + origin = scope.getOriginXY(interactable, element), snap = interactable.options[this.prepared.name].snap, restrict = interactable.options[this.prepared.name].restrict, width, height; @@ -1693,8 +1697,8 @@ // if this interaction had been removed after stopping // add it back - if (indexOf(interactions, this) === -1) { - interactions.push(this); + if (indexOf(scope.interactions, this) === -1) { + scope.interactions.push(this); } this.prepared.name = action.name; @@ -1723,14 +1727,14 @@ && this.curCoords.client.y === this.prevCoords.client.y); var dx, dy, - pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); // register movement greater than pointerMoveTolerance if (this.pointerIsDown && !this.pointerWasMoved) { dx = this.curCoords.client.x - this.startCoords.client.x; dy = this.curCoords.client.y - this.startCoords.client.y; - this.pointerWasMoved = hypot(dx, dy) > pointerMoveTolerance; + this.pointerWasMoved = scope.hypot(dx, dy) > scope.pointerMoveTolerance; } if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { @@ -1749,7 +1753,7 @@ } // set pointer coordinate, time changes and speeds - setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + scope.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); if (!this.prepared.name) { return; } @@ -1759,7 +1763,7 @@ // if just starting an action, calculate the pointer speed now if (!this.interacting()) { - setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + scope.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); // check if a drag is in the correct axis if (this.prepared.name === 'drag') { @@ -1778,14 +1782,14 @@ var element = eventTarget; // check element interactables - while (isElement(element)) { - var elementInteractable = interactables.get(element); + while (scope.isElement(element)) { + var elementInteractable = scope.interactables.get(element); if (elementInteractable && elementInteractable !== this.target && !elementInteractable.options.drag.manualStart && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' - && checkAxis(axis, elementInteractable)) { + && scope.checkAxis(axis, elementInteractable)) { this.prepared.name = 'drag'; this.target = elementInteractable; @@ -1793,7 +1797,7 @@ break; } - element = parentElement(element); + element = scope.parentElement(element); } // if there's no drag from element interactables, @@ -1802,20 +1806,20 @@ var thisInteraction = this; var getDraggable = function (interactable, selector, context) { - var elements = ie8MatchesSelector + var elements = scope.ie8MatchesSelector ? context.querySelectorAll(selector) : undefined; if (interactable === thisInteraction.target) { return; } - if (inContext(interactable, eventTarget) + if (scope.inContext(interactable, eventTarget) && !interactable.options.drag.manualStart - && !testIgnore(interactable, element, eventTarget) - && testAllow(interactable, element, eventTarget) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) && matchesSelector(element, selector, elements) && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' - && checkAxis(axis, interactable) - && withinInteractionLimit(interactable, element, 'drag')) { + && scope.checkAxis(axis, interactable) + && scope.withinInteractionLimit(interactable, element, 'drag')) { return interactable; } @@ -1823,8 +1827,8 @@ element = eventTarget; - while (isElement(element)) { - var selectorInteractable = interactables.forEachSelector(getDraggable); + while (scope.isElement(element)) { + var selectorInteractable = scope.interactables.forEachSelector(getDraggable); if (selectorInteractable) { this.prepared.name = 'drag'; @@ -1833,7 +1837,7 @@ break; } - element = parentElement(element); + element = scope.parentElement(element); } } } @@ -1844,7 +1848,7 @@ if (starting && (this.target.options[this.prepared.name].manualStart - || !withinInteractionLimit(this.target, this.element, this.prepared))) { + || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { this.stop(); return; } @@ -1865,7 +1869,7 @@ } } - copyCoords(this.prevCoords, this.curCoords); + scope.copyCoords(this.prevCoords, this.curCoords); if (this.dragging || this.resizing) { this.autoScrollMove(pointer); @@ -1926,7 +1930,7 @@ var startRect = this.target.getRect(this.element); if (this.target.options.resize.square) { - var squareEdges = extend({}, this.prepared.edges); + var squareEdges = scope.extend({}, this.prepared.edges); squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); @@ -1941,9 +1945,9 @@ this.resizeRects = { start : startRect, - current : extend({}, startRect), - restricted: extend({}, startRect), - previous : extend({}, startRect), + current : scope.extend({}, startRect), + restricted: scope.extend({}, startRect), + previous : scope.extend({}, startRect), delta : { left: 0, right : 0, width : 0, top : 0, bottom: 0, height: 0 @@ -1976,7 +1980,7 @@ current = this.resizeRects.current, restricted = this.resizeRects.restricted, delta = this.resizeRects.delta, - previous = extend(this.resizeRects.previous, restricted); + previous = scope.extend(this.resizeRects.previous, restricted); if (this.target.options.resize.square) { var originalEdges = edges; @@ -1999,7 +2003,7 @@ if (invertible) { // if invertible, copy the current rect - extend(restricted, current); + scope.extend(restricted, current); if (invert === 'reposition') { // swap edge values if necessary to keep width/height positive @@ -2091,7 +2095,7 @@ }, pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -2104,7 +2108,7 @@ }, pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -2148,8 +2152,8 @@ inertiaPossible = false, inertia = false, smoothEnd = false, - endSnap = checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, - endRestrict = checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, + endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, + endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, dx = 0, dy = 0, startEvent; @@ -2201,7 +2205,7 @@ } if (inertia || smoothEnd) { - copyCoords(inertiaStatus.upCoords, this.curCoords); + scope.copyCoords(inertiaStatus.upCoords, this.curCoords); this.pointers[0] = inertiaStatus.startEvent = startEvent = new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); @@ -2217,8 +2221,8 @@ this.calcInertia(inertiaStatus); - var page = extend({}, this.curCoords.page), - origin = getOriginXY(target, this.element), + var page = scope.extend({}, this.curCoords.page), + origin = scope.getOriginXY(target, this.element), statusObject; page.x = page.x + inertiaStatus.xe - origin.x; @@ -2320,15 +2324,15 @@ element = element || this.element; // collect all dropzones and their elements which qualify for a drop - for (i = 0; i < interactables.length; i++) { - if (!interactables[i].options.drop.enabled) { continue; } + for (i = 0; i < scope.interactables.length; i++) { + if (!scope.interactables[i].options.drop.enabled) { continue; } - var current = interactables[i], + var current = scope.interactables[i], accept = current.options.drop.accept; // test the draggable element against the dropzone's accept setting - if ((isElement(accept) && accept !== element) - || (isString(accept) + if ((scope.isElement(accept) && accept !== element) + || (scope.isString(accept) && !matchesSelector(element, accept))) { continue; @@ -2395,7 +2399,7 @@ getDrop: function (event, dragElement) { var validDrops = []; - if (dynamicDrop) { + if (scope.dynamicDrop) { this.setActiveDrops(dragElement); } @@ -2411,7 +2415,7 @@ } // get the most appropriate dropzone based on DOM depth and order - var dropIndex = indexOfDeepestElement(validDrops), + var dropIndex = scope.indexOfDeepestElement(validDrops), dropzone = this.activeDrops.dropzones[dropIndex] || null, element = this.activeDrops.elements [dropIndex] || null; @@ -2538,7 +2542,7 @@ stop: function (event) { if (this.interacting()) { - autoScroll.stop(); + scope.autoScroll.stop(); this.matches = []; this.matchElements = []; @@ -2549,7 +2553,7 @@ } // prevent Default only if were previously interacting - if (event && isFunction(event.preventDefault)) { + if (event && scope.isFunction(event.preventDefault)) { this.checkAndPreventDefault(event, target, this.element); } @@ -2566,15 +2570,15 @@ // remove pointers if their ID isn't in this.pointerIds for (var i = 0; i < this.pointers.length; i++) { - if (indexOf(this.pointerIds, getPointerId(this.pointers[i])) === -1) { + if (indexOf(this.pointerIds, scope.getPointerId(this.pointers[i])) === -1) { this.pointers.splice(i, 1); } } - for (i = 0; i < interactions.length; i++) { + for (i = 0; i < scope.interactions.length; i++) { // remove this interaction if it's not the only one of it's type - if (interactions[i] !== this && interactions[i].mouse === this.mouse) { - interactions.splice(indexOf(interactions, this), 1); + if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { + scope.interactions.splice(indexOf(scope.interactions, this), 1); } } }, @@ -2594,7 +2598,7 @@ inertiaStatus.sy = inertiaStatus.ye * progress; } else { - var quadPoint = getQuadraticCurvePoint( + var quadPoint = scope.getQuadraticCurvePoint( 0, 0, inertiaStatus.xe, inertiaStatus.ye, inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, @@ -2625,8 +2629,8 @@ duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; if (t < duration) { - inertiaStatus.sx = easeOutQuad(t, 0, inertiaStatus.xe, duration); - inertiaStatus.sy = easeOutQuad(t, 0, inertiaStatus.ye, duration); + inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration); + inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration); this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); @@ -2646,7 +2650,7 @@ }, addPointer: function (pointer) { - var id = getPointerId(pointer), + var id = scope.getPointerId(pointer), index = this.mouse? 0 : indexOf(this.pointerIds, id); if (index === -1) { @@ -2660,7 +2664,7 @@ }, removePointer: function (pointer) { - var id = getPointerId(pointer), + var id = scope.getPointerId(pointer), index = this.mouse? 0 : indexOf(this.pointerIds, id); if (index === -1) { return; } @@ -2680,7 +2684,7 @@ // The inertia start event should be this.pointers[0] if (this.inertiaStatus.active) { return; } - var index = this.mouse? 0: indexOf(this.pointerIds, getPointerId(pointer)); + var index = this.mouse? 0: indexOf(this.pointerIds, scope.getPointerId(pointer)); if (index === -1) { return; } @@ -2688,7 +2692,7 @@ }, collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); // do not fire a tap event if the pointer was moved before being lifted if (eventType === 'tap' && (this.pointerWasMoved @@ -2702,15 +2706,15 @@ element = eventTarget; function collectSelectors (interactable, selector, context) { - var els = ie8MatchesSelector + var els = scope.ie8MatchesSelector ? context.querySelectorAll(selector) : undefined; if (interactable._iEvents[eventType] - && isElement(element) - && inContext(interactable, element) - && !testIgnore(interactable, element, eventTarget) - && testAllow(interactable, element, eventTarget) + && scope.isElement(element) + && scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) && matchesSelector(element, selector, els)) { targets.push(interactable); @@ -2724,9 +2728,9 @@ elements.push(element); } - interactables.forEachSelector(collectSelectors); + scope.interactables.forEachSelector(collectSelectors); - element = parentElement(element); + element = scope.parentElement(element); } // create the tap event even if there are no listeners so that @@ -2737,7 +2741,7 @@ }, firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : indexOf(getPointerId(pointer)), + var pointerIndex = this.mouse? 0 : indexOf(scope.getPointerId(pointer)), pointerEvent = {}, i, // for tap events @@ -2749,9 +2753,9 @@ pointerEvent = pointer; } else { - extend(pointerEvent, event); + scope.extend(pointerEvent, event); if (event !== pointer) { - extend(pointerEvent, pointer); + scope.extend(pointerEvent, pointer); } pointerEvent.preventDefault = preventOriginalDefault; @@ -2762,9 +2766,9 @@ pointerEvent.timeStamp = new Date().getTime(); pointerEvent.originalEvent = event; pointerEvent.type = eventType; - pointerEvent.pointerId = getPointerId(pointer); - pointerEvent.pointerType = this.mouse? 'mouse' : !supportsPointerEvent? 'touch' - : isString(pointer.pointerType) + pointerEvent.pointerId = scope.getPointerId(pointer); + pointerEvent.pointerType = this.mouse? 'mouse' : !scope.supportsPointerEvent? 'touch' + : scope.isString(pointer.pointerType) ? pointer.pointerType : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; } @@ -2796,7 +2800,7 @@ if (createNewDoubleTap) { var doubleTap = {}; - extend(doubleTap, pointerEvent); + scope.extend(doubleTap, pointerEvent); doubleTap.dt = interval; doubleTap.type = 'doubletap'; @@ -2816,7 +2820,7 @@ matchElement = matchElements[i], action = validateAction(match.getAction(pointer, event, this, matchElement), match); - if (action && withinInteractionLimit(match, matchElement, action)) { + if (action && scope.withinInteractionLimit(match, matchElement, action)) { this.target = match; this.element = matchElement; @@ -2838,9 +2842,9 @@ page = { x: status.x, y: status.y }; } else { - var origin = getOriginXY(this.target, this.element); + var origin = scope.getOriginXY(this.target, this.element); - page = extend({}, pageCoords); + page = scope.extend({}, pageCoords); page.x -= origin.x; page.y -= origin.y; @@ -2861,7 +2865,7 @@ }; for (i = 0; i < len; i++) { - if (isFunction(snap.targets[i])) { + if (scope.isFunction(snap.targets[i])) { target = snap.targets[i](relative.x, relative.y, this); } else { @@ -2871,10 +2875,10 @@ if (!target) { continue; } targets.push({ - x: isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, - y: isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, + x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, + y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, - range: isNumber(target.range)? target.range: snap.range + range: scope.isNumber(target.range)? target.range: snap.range }); } } @@ -2894,7 +2898,7 @@ var range = target.range, dx = target.x - page.x, dy = target.y - page.y, - distance = hypot(dx, dy), + distance = scope.hypot(dx, dy), inRange = distance <= range; // Infinite targets count as being out of range @@ -2968,7 +2972,7 @@ page = status.useStatusXY ? page = { x: status.x, y: status.y } - : page = extend({}, pageCoords); + : page = scope.extend({}, pageCoords); if (status.snap && status.snap.locked) { page.x += status.snap.dx || 0; @@ -2984,26 +2988,26 @@ var rect, restrictedX, restrictedY; - if (isString(restriction)) { + if (scope.isString(restriction)) { if (restriction === 'parent') { - restriction = parentElement(this.element); + restriction = scope.parentElement(this.element); } else if (restriction === 'self') { restriction = target.getRect(this.element); } else { - restriction = closest(this.element, restriction); + restriction = scope.closest(this.element, restriction); } if (!restriction) { return status; } } - if (isFunction(restriction)) { + if (scope.isFunction(restriction)) { restriction = restriction(page.x, page.y, this.element); } - if (isElement(restriction)) { - restriction = getElementRect(restriction); + if (scope.isElement(restriction)) { + restriction = scope.getElementRect(restriction); } rect = restriction; @@ -3088,12 +3092,12 @@ autoScrollMove: function (pointer) { if (!(this.interacting() - && checkAutoScroll(this.target, this.prepared.name))) { + && scope.checkAutoScroll(this.target, this.prepared.name))) { return; } if (this.inertiaStatus.active) { - autoScroll.x = autoScroll.y = 0; + scope.autoScroll.x = scope.autoScroll.y = 0; return; } @@ -3102,32 +3106,32 @@ bottom, left, options = this.target.options[this.prepared.name].autoScroll, - container = options.container || getWindow(this.element); + container = options.container || scope.getWindow(this.element); - if (isWindow(container)) { - left = pointer.clientX < autoScroll.margin; - top = pointer.clientY < autoScroll.margin; - right = pointer.clientX > container.innerWidth - autoScroll.margin; - bottom = pointer.clientY > container.innerHeight - autoScroll.margin; + if (scope.isWindow(container)) { + left = pointer.clientX < scope.autoScroll.margin; + top = pointer.clientY < scope.autoScroll.margin; + right = pointer.clientX > container.innerWidth - scope.autoScroll.margin; + bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin; } else { - var rect = getElementRect(container); + var rect = scope.getElementRect(container); - left = pointer.clientX < rect.left + autoScroll.margin; - top = pointer.clientY < rect.top + autoScroll.margin; - right = pointer.clientX > rect.right - autoScroll.margin; - bottom = pointer.clientY > rect.bottom - autoScroll.margin; + left = pointer.clientX < rect.left + scope.autoScroll.margin; + top = pointer.clientY < rect.top + scope.autoScroll.margin; + right = pointer.clientX > rect.right - scope.autoScroll.margin; + bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin; } - autoScroll.x = (right ? 1: left? -1: 0); - autoScroll.y = (bottom? 1: top? -1: 0); + scope.autoScroll.x = (right ? 1: left? -1: 0); + scope.autoScroll.y = (bottom? 1: top? -1: 0); - if (!autoScroll.isScrolling) { + if (!scope.autoScroll.isScrolling) { // set the autoScroll properties to those of the target - autoScroll.margin = options.margin; - autoScroll.speed = options.speed; + scope.autoScroll.margin = options.margin; + scope.autoScroll.speed = options.speed; - autoScroll.start(this); + scope.autoScroll.start(this); } }, @@ -3139,18 +3143,18 @@ }; function getInteractionFromPointer (pointer, eventType, eventTarget) { - var i = 0, len = interactions.length, + var i = 0, len = scope.interactions.length, mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) // MSPointerEvent.MSPOINTER_TYPE_MOUSE || pointer.pointerType === 4), interaction; - var id = getPointerId(pointer); + var id = scope.getPointerId(pointer); // try to resume inertia with a new pointer if (/down|start/i.test(eventType)) { for (i = 0; i < len; i++) { - interaction = interactions[i]; + interaction = scope.interactions[i]; var element = eventTarget; @@ -3167,19 +3171,19 @@ return interaction; } - element = parentElement(element); + element = scope.parentElement(element); } } } } // if it's a mouse interaction - if (mouseEvent || !(supportsTouch || supportsPointerEvent)) { + if (mouseEvent || !(scope.supportsTouch || scope.supportsPointerEvent)) { // find a mouse interaction that's not in inertia phase for (i = 0; i < len; i++) { - if (interactions[i].mouse && !interactions[i].inertiaStatus.active) { - return interactions[i]; + if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { + return scope.interactions[i]; } } @@ -3187,7 +3191,7 @@ // if the eventType is a mousedown, and inertia is active // ignore the interaction for (i = 0; i < len; i++) { - if (interactions[i].mouse && !(/down/.test(eventType) && interactions[i].inertiaStatus.active)) { + if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { return interaction; } } @@ -3201,8 +3205,8 @@ // get interaction that has this pointer for (i = 0; i < len; i++) { - if (contains(interactions[i].pointerIds, id)) { - return interactions[i]; + if (contains(scope.interactions[i].pointerIds, id)) { + return scope.interactions[i]; } } @@ -3213,7 +3217,7 @@ // get first idle interaction for (i = 0; i < len; i++) { - interaction = interactions[i]; + interaction = scope.interactions[i]; if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) && !interaction.interacting() @@ -3231,14 +3235,14 @@ function doOnInteractions (method) { return (function (event) { var interaction, - eventTarget = getActualElement(event.path + eventTarget = scope.getActualElement(event.path ? event.path[0] : event.target), - curEventTarget = getActualElement(event.currentTarget), + curEventTarget = scope.getActualElement(event.currentTarget), i; - if (supportsTouch && /touch/.test(event.type)) { - prevTouchTime = new Date().getTime(); + if (scope.supportsTouch && /touch/.test(event.type)) { + scope.prevTouchTime = new Date().getTime(); for (i = 0; i < event.changedTouches.length; i++) { var pointer = event.changedTouches[i]; @@ -3253,17 +3257,17 @@ } } else { - if (!supportsPointerEvent && /mouse/.test(event.type)) { + if (!scope.supportsPointerEvent && /mouse/.test(event.type)) { // ignore mouse events while touch interactions are active - for (i = 0; i < interactions.length; i++) { - if (!interactions[i].mouse && interactions[i].pointerIsDown) { + for (i = 0; i < scope.interactions.length; i++) { + if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { return; } } // try to ignore mouse events that are simulated by the browser // after a touch event - if (new Date().getTime() - prevTouchTime < 500) { + if (new Date().getTime() - scope.prevTouchTime < 500) { return; } } @@ -3286,19 +3290,19 @@ snapStatus = interaction.snapStatus, restrictStatus = interaction.restrictStatus, pointers = interaction.pointers, - deltaSource = (target && target.options || defaultOptions).deltaSource, + deltaSource = (target && target.options || scope.defaultOptions).deltaSource, sourceX = deltaSource + 'X', sourceY = deltaSource + 'Y', - options = target? target.options: defaultOptions, - origin = getOriginXY(target, element), + options = target? target.options: scope.defaultOptions, + origin = scope.getOriginXY(target, element), starting = phase === 'start', ending = phase === 'end', coords = starting? interaction.startCoords : interaction.curCoords; element = element || interaction.element; - page = extend({}, coords.page); - client = extend({}, coords.client); + page = scope.extend({}, coords.page); + client = scope.extend({}, coords.client); page.x -= origin.x; page.y -= origin.y; @@ -3308,7 +3312,7 @@ var relativePoints = options[action].snap && options[action].snap.relativePoints ; - if (checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { + if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { this.snap = { range : snapStatus.range, locked : snapStatus.locked, @@ -3328,7 +3332,7 @@ } } - if (checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { + if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { page.x += restrictStatus.dx; page.y += restrictStatus.dy; client.x += restrictStatus.dx; @@ -3436,11 +3440,11 @@ this.touches = [pointers[0], pointers[1]]; if (starting) { - this.distance = touchDistance(pointers, deltaSource); - this.box = touchBBox(pointers); + this.distance = scope.touchDistance(pointers, deltaSource); + this.box = scope.touchBBox(pointers); this.scale = 1; this.ds = 0; - this.angle = touchAngle(pointers, undefined, deltaSource); + this.angle = scope.touchAngle(pointers, undefined, deltaSource); this.da = 0; } else if (ending || event instanceof InteractEvent) { @@ -3452,10 +3456,10 @@ this.da = this.angle - interaction.gesture.startAngle; } else { - this.distance = touchDistance(pointers, deltaSource); - this.box = touchBBox(pointers); + this.distance = scope.touchDistance(pointers, deltaSource); + this.box = scope.touchBBox(pointers); this.scale = this.distance / interaction.gesture.startDistance; - this.angle = touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); + this.angle = scope.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); this.ds = this.scale - interaction.gesture.prevScale; this.da = this.angle - interaction.gesture.prevAngle; @@ -3488,7 +3492,7 @@ dy = this[sourceY] - interaction.prevEvent[sourceY], dt = this.dt / 1000; - this.speed = hypot(dx, dy) / dt; + this.speed = scope.hypot(dx, dy) / dt; this.velocityX = dx / dt; this.velocityY = dy / dt; } @@ -3533,7 +3537,7 @@ } InteractEvent.prototype = { - preventDefault: blank, + preventDefault: scope.blank, stopImmediatePropagation: function () { this.immediatePropagationStopped = this.propagationStopped = true; }, @@ -3550,11 +3554,11 @@ var cursor = ''; if (action.name === 'drag') { - cursor = actionCursors.drag; + cursor = scope.actionCursors.drag; } if (action.name === 'resize') { if (action.axis) { - cursor = actionCursors[action.name + action.axis]; + cursor = scope.actionCursors[action.name + action.axis]; } else if (action.edges) { var cursorKey = 'resize', @@ -3566,7 +3570,7 @@ } } - cursor = actionCursors[cursorKey]; + cursor = scope.actionCursors[cursorKey]; } } @@ -3580,8 +3584,8 @@ // true value, use pointer coords and element rect if (value === true) { // if dimensions are negative, "switch" edges - var width = isNumber(rect.width)? rect.width : rect.right - rect.left, - height = isNumber(rect.height)? rect.height : rect.bottom - rect.top; + var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left, + height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top; if (width < 0) { if (name === 'left' ) { name = 'right'; } @@ -3600,9 +3604,9 @@ } // the remaining checks require an element - if (!isElement(element)) { return false; } + if (!scope.isElement(element)) { return false; } - return isElement(value) + return scope.isElement(value) // the value is an element to use as a resize handle ? value === element // otherwise check if element matches value as selector @@ -3615,12 +3619,12 @@ action = null, resizeAxes = null, resizeEdges, - page = extend({}, interaction.curCoords.page), + page = scope.extend({}, interaction.curCoords.page), options = this.options; if (!rect) { return null; } - if (actionIsEnabled.resize && options.resize.enabled) { + if (scope.actionIsEnabled.resize && options.resize.enabled) { var resizeOptions = options.resize; resizeEdges = { @@ -3628,7 +3632,7 @@ }; // if using resize.edges - if (isObject(resizeOptions.edges)) { + if (scope.isObject(resizeOptions.edges)) { for (var edge in resizeEdges) { resizeEdges[edge] = checkResizeEdge(edge, resizeOptions.edges[edge], @@ -3636,7 +3640,7 @@ interaction._eventTarget, element, rect, - resizeOptions.margin || margin); + resizeOptions.margin || scope.margin); } resizeEdges.left = resizeEdges.left && !resizeEdges.right; @@ -3645,8 +3649,8 @@ shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; } else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - margin); + var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), + bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); shouldResize = right || bottom; resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); @@ -3655,11 +3659,11 @@ action = shouldResize ? 'resize' - : actionIsEnabled.drag && options.drag.enabled + : scope.actionIsEnabled.drag && options.drag.enabled ? 'drag' : null; - if (actionIsEnabled.gesture + if (scope.actionIsEnabled.gesture && interaction.pointerIds.length >=2 && !(interaction.dragging || interaction.resizing)) { action = 'gesture'; @@ -3679,7 +3683,7 @@ // Check if action is enabled globally and the current target supports it // If so, return the validated action. Otherwise, return null function validateAction (action, interactable) { - if (!isObject(action)) { return null; } + if (!scope.isObject(action)) { return null; } var actionName = action.name, options = interactable.options; @@ -3687,7 +3691,7 @@ if (( (actionName === 'resize' && options.resize.enabled ) || (actionName === 'drag' && options.drag.enabled ) || (actionName === 'gesture' && options.gesture.enabled)) - && actionIsEnabled[actionName]) { + && scope.actionIsEnabled[actionName]) { if (actionName === 'resize' || actionName === 'resizeyx') { actionName = 'resizexy'; @@ -3716,8 +3720,8 @@ // listener is added to a selector interactable function delegateListener (event, useCapture) { var fakeEvent = {}, - delegated = delegatedEvents[event.type], - eventTarget = getActualElement(event.path + delegated = scope.delegatedEvents[event.type], + eventTarget = scope.getActualElement(event.path ? event.path[0] : event.target), element = eventTarget; @@ -3733,14 +3737,14 @@ fakeEvent.preventDefault = preventOriginalDefault; // climb up document tree looking for selector matches - while (isElement(element)) { + while (scope.isElement(element)) { for (var i = 0; i < delegated.selectors.length; i++) { var selector = delegated.selectors[i], context = delegated.contexts[i]; if (matchesSelector(element, selector) - && nodeContains(context, eventTarget) - && nodeContains(context, element)) { + && scope.nodeContains(context, eventTarget) + && scope.nodeContains(context, element)) { var listeners = delegated.listeners[i]; @@ -3754,7 +3758,7 @@ } } - element = parentElement(element); + element = scope.parentElement(element); } } @@ -3762,8 +3766,8 @@ return delegateListener.call(this, event, true); } - interactables.indexOfElement = function indexOfElement (element, context) { - context = context || document; + scope.interactables.indexOfElement = function indexOfElement (element, context) { + context = context || scope.document; for (var i = 0; i < this.length; i++) { var interactable = this[i]; @@ -3778,11 +3782,11 @@ return -1; }; - interactables.get = function interactableGet (element, options) { + scope.interactables.get = function interactableGet (element, options) { return this[this.indexOfElement(element, options && options.context)]; }; - interactables.forEachSelector = function (callback) { + scope.interactables.forEachSelector = function (callback) { for (var i = 0; i < this.length; i++) { var interactable = this[i]; @@ -3824,7 +3828,7 @@ | .autoScroll(true); \*/ function interact (element, options) { - return interactables.get(element, options) || new Interactable(element, options); + return scope.interactables.get(element, options) || new Interactable(element, options); } /*\ @@ -3839,28 +3843,28 @@ var _window; - if (trySelector(element)) { + if (scope.trySelector(element)) { this.selector = element; var context = options && options.context; - _window = context? getWindow(context) : window; + _window = context? scope.getWindow(context) : scope.window; if (context && (_window.Node ? context instanceof _window.Node - : (isElement(context) || context === _window.document))) { + : (scope.isElement(context) || context === _window.document))) { this._context = context; } } else { - _window = getWindow(element); + _window = scope.getWindow(element); - if (isElement(element, _window)) { + if (scope.isElement(element, _window)) { - if (PointerEvent) { - events.add(this._element, pEventTypes.down, listeners.pointerDown ); - events.add(this._element, pEventTypes.move, listeners.pointerHover); + if (scope.PointerEvent) { + events.add(this._element, scope.pEventTypes.down, listeners.pointerDown ); + events.add(this._element, scope.pEventTypes.move, listeners.pointerHover); } else { events.add(this._element, 'mousedown' , listeners.pointerDown ); @@ -3873,11 +3877,11 @@ this._doc = _window.document; - if (!contains(documents, this._doc)) { + if (!contains(scope.documents, this._doc)) { listenToDocument(this._doc); } - interactables.push(this); + scope.interactables.push(this); this.set(options); } @@ -3885,20 +3889,20 @@ Interactable.prototype = { setOnEvents: function (action, phases) { if (action === 'drop') { - if (isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } - if (isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } - if (isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } - if (isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } - if (isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } - if (isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } + if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } + if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } + if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } + if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } + if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } + if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } } else { action = 'on' + action; - if (isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } - if (isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } - if (isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } - if (isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } + if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } + if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } + if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } + if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } } return this; @@ -3936,7 +3940,7 @@ | }); \*/ draggable: function (options) { - if (isObject(options)) { + if (scope.isObject(options)) { this.options.drag.enabled = options.enabled === false? false: true; this.setPerAction('drag', options); this.setOnEvents('drag', options); @@ -3951,7 +3955,7 @@ return this; } - if (isBool(options)) { + if (scope.isBool(options)) { this.options.drag.enabled = options; return this; @@ -3964,17 +3968,17 @@ // for all the default per-action options for (var option in options) { // if this option exists for this action - if (option in defaultOptions[action]) { + if (option in scope.defaultOptions[action]) { // if the option in the options arg is an object value - if (isObject(options[option])) { + if (scope.isObject(options[option])) { // duplicate the object - this.options[action][option] = extend(this.options[action][option] || {}, options[option]); + this.options[action][option] = scope.extend(this.options[action][option] || {}, options[option]); - if (isObject(defaultOptions.perAction[option]) && 'enabled' in defaultOptions.perAction[option]) { + if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { this.options[action][option].enabled = options[option].enabled === false? false : true; } } - else if (isBool(options[option]) && isObject(defaultOptions.perAction[option])) { + else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) { this.options[action][option].enabled = options[option]; } else if (options[option] !== undefined) { @@ -4015,7 +4019,7 @@ = (boolean | object) The current setting or this Interactable \*/ dropzone: function (options) { - if (isObject(options)) { + if (scope.isObject(options)) { this.options.drop.enabled = options.enabled === false? false: true; this.setOnEvents('drop', options); this.accept(options.accept); @@ -4023,14 +4027,14 @@ if (/^(pointer|center)$/.test(options.overlap)) { this.options.drop.overlap = options.overlap; } - else if (isNumber(options.overlap)) { + else if (scope.isNumber(options.overlap)) { this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); } return this; } - if (isBool(options)) { + if (scope.isBool(options)) { this.options.drop.enabled = options; return this; @@ -4053,8 +4057,8 @@ var dropOverlap = this.options.drop.overlap; if (dropOverlap === 'pointer') { - var page = getPageXY(pointer), - origin = getOriginXY(draggable, draggableElement), + var page = scope.getPageXY(pointer), + origin = scope.getOriginXY(draggable, draggableElement), horizontal, vertical; @@ -4076,7 +4080,7 @@ dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; } - if (isNumber(dropOverlap)) { + if (scope.isNumber(dropOverlap)) { var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), overlapRatio = overlapArea / (dragRect.width * dragRect.height); @@ -4125,7 +4129,7 @@ | } \*/ dropChecker: function (checker) { - if (isFunction(checker)) { + if (scope.isFunction(checker)) { this.options.dropChecker = checker; return this; @@ -4157,14 +4161,14 @@ = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable \*/ accept: function (newValue) { - if (isElement(newValue)) { + if (scope.isElement(newValue)) { this.options.drop.accept = newValue; return this; } // test if it is a valid CSS selector - if (trySelector(newValue)) { + if (scope.trySelector(newValue)) { this.options.drop.accept = newValue; return this; @@ -4216,7 +4220,7 @@ | }); \*/ resizable: function (options) { - if (isObject(options)) { + if (scope.isObject(options)) { this.options.resize.enabled = options.enabled === false? false: true; this.setPerAction('resize', options); this.setOnEvents('resize', options); @@ -4225,16 +4229,16 @@ this.options.resize.axis = options.axis; } else if (options.axis === null) { - this.options.resize.axis = defaultOptions.resize.axis; + this.options.resize.axis = scope.defaultOptions.resize.axis; } - if (isBool(options.square)) { + if (scope.isBool(options.square)) { this.options.resize.square = options.square; } return this; } - if (isBool(options)) { + if (scope.isBool(options)) { this.options.resize.enabled = options; return this; @@ -4258,7 +4262,7 @@ = (object) this Interactable \*/ squareResize: function (newValue) { - if (isBool(newValue)) { + if (scope.isBool(newValue)) { this.options.resize.square = newValue; return this; @@ -4297,7 +4301,7 @@ | }); \*/ gesturable: function (options) { - if (isObject(options)) { + if (scope.isObject(options)) { this.options.gesture.enabled = options.enabled === false? false: true; this.setPerAction('gesture', options); this.setOnEvents('gesture', options); @@ -4305,7 +4309,7 @@ return this; } - if (isBool(options)) { + if (scope.isBool(options)) { this.options.gesture.enabled = options; return this; @@ -4335,10 +4339,10 @@ = (Interactable) this Interactable \*/ autoScroll: function (options) { - if (isObject(options)) { - options = extend({ actions: ['drag', 'resize']}, options); + if (scope.isObject(options)) { + options = scope.extend({ actions: ['drag', 'resize']}, options); } - else if (isBool(options)) { + else if (scope.isBool(options)) { options = { actions: ['drag', 'resize'], enabled: options }; } @@ -4413,28 +4417,28 @@ }, setOptions: function (option, options) { - var actions = options && isArray(options.actions) + var actions = options && scope.isArray(options.actions) ? options.actions : ['drag']; var i; - if (isObject(options) || isBool(options)) { + if (scope.isObject(options) || scope.isBool(options)) { for (i = 0; i < actions.length; i++) { var action = /resize/.test(actions[i])? 'resize' : actions[i]; - if (!isObject(this.options[action])) { continue; } + if (!scope.isObject(this.options[action])) { continue; } var thisOption = this.options[action][option]; - if (isObject(options)) { - extend(thisOption, options); + if (scope.isObject(options)) { + scope.extend(thisOption, options); thisOption.enabled = options.enabled === false? false: true; if (option === 'snap') { if (thisOption.mode === 'grid') { thisOption.targets = [ - interact.createSnapGrid(extend({ + interact.createSnapGrid(scope.extend({ offset: thisOption.gridOffset || { x: 0, y: 0 } }, thisOption.grid || {})) ]; @@ -4451,7 +4455,7 @@ } } } - else if (isBool(options)) { + else if (scope.isBool(options)) { thisOption.enabled = options; } } @@ -4463,7 +4467,7 @@ allActions = ['drag', 'resize', 'gesture']; for (i = 0; i < allActions.length; i++) { - if (option in defaultOptions[allActions[i]]) { + if (option in scope.defaultOptions[allActions[i]]) { ret[allActions[i]] = this.options[allActions[i]][option]; } } @@ -4570,7 +4574,7 @@ | }); \*/ actionChecker: function (checker) { - if (isFunction(checker)) { + if (scope.isFunction(checker)) { this.options.actionChecker = checker; return this; @@ -4606,11 +4610,11 @@ getRect: function rectCheck (element) { element = element || this._element; - if (this.selector && !(isElement(element))) { + if (this.selector && !(scope.isElement(element))) { element = this._context.querySelector(this.selector); } - return getElementRect(element); + return scope.getElementRect(element); }, /*\ @@ -4624,7 +4628,7 @@ = (function | object) The checker function or this Interactable \*/ rectChecker: function (checker) { - if (isFunction(checker)) { + if (scope.isFunction(checker)) { this.getRect = checker; return this; @@ -4651,7 +4655,7 @@ = (boolean | Interactable) The current setting or this Interactable \*/ styleCursor: function (newValue) { - if (isBool(newValue)) { + if (scope.isBool(newValue)) { this.options.styleCursor = newValue; return this; @@ -4685,7 +4689,7 @@ return this; } - if (isBool(newValue)) { + if (scope.isBool(newValue)) { this.options.preventDefault = newValue? 'always' : 'never'; return this; } @@ -4707,11 +4711,11 @@ = (object) The current origin or this Interactable \*/ origin: function (newValue) { - if (trySelector(newValue)) { + if (scope.trySelector(newValue)) { this.options.origin = newValue; return this; } - else if (isObject(newValue)) { + else if (scope.isObject(newValue)) { this.options.origin = newValue; return this; } @@ -4777,7 +4781,7 @@ | }); \*/ restrict: function (options) { - if (!isObject(options)) { + if (!scope.isObject(options)) { return this.setOptions('restrict', options); } @@ -4788,7 +4792,7 @@ var action = actions[i]; if (action in options) { - var perAction = extend({ + var perAction = scope.extend({ actions: [action], restriction: options[action] }, options); @@ -4813,7 +4817,7 @@ return this._context; }, - _context: document, + _context: scope.document, /*\ * Interactable.ignoreFrom @@ -4831,12 +4835,12 @@ | interact(element).ignoreFrom('input, textarea, a'); \*/ ignoreFrom: function (newValue) { - if (trySelector(newValue)) { // CSS selector to match event.target + if (scope.trySelector(newValue)) { // CSS selector to match event.target this.options.ignoreFrom = newValue; return this; } - if (isElement(newValue)) { // specific element + if (scope.isElement(newValue)) { // specific element this.options.ignoreFrom = newValue; return this; } @@ -4860,12 +4864,12 @@ | interact(element).allowFrom('.handle'); \*/ allowFrom: function (newValue) { - if (trySelector(newValue)) { // CSS selector to match event.target + if (scope.trySelector(newValue)) { // CSS selector to match event.target this.options.allowFrom = newValue; return this; } - if (isElement(newValue)) { // specific element + if (scope.isElement(newValue)) { // specific element this.options.allowFrom = newValue; return this; } @@ -4897,7 +4901,7 @@ = (Interactable) this Interactable \*/ fire: function (iEvent) { - if (!(iEvent && iEvent.type) || !contains(eventTypes, iEvent.type)) { + if (!(iEvent && iEvent.type) || !contains(scope.eventTypes, iEvent.type)) { return this; } @@ -4918,13 +4922,13 @@ } // interactable.onevent listener - if (isFunction(this[onEvent])) { + if (scope.isFunction(this[onEvent])) { funcName = this[onEvent].name; this[onEvent](iEvent); } // interact.on() listeners - if (iEvent.type in globalEvents && (listeners = globalEvents[iEvent.type])) { + if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { funcName = listeners[i].name; @@ -4949,11 +4953,11 @@ on: function (eventType, listener, useCapture) { var i; - if (isString(eventType) && eventType.search(' ') !== -1) { + if (scope.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } - if (isArray(eventType)) { + if (scope.isArray(eventType)) { for (i = 0; i < eventType.length; i++) { this.on(eventType[i], listener, useCapture); } @@ -4961,7 +4965,7 @@ return this; } - if (isObject(eventType)) { + if (scope.isObject(eventType)) { for (var prop in eventType) { this.on(prop, eventType[prop], listener); } @@ -4970,13 +4974,13 @@ } if (eventType === 'wheel') { - eventType = wheelEvent; + eventType = scope.wheelEvent; } // convert to boolean useCapture = useCapture? true: false; - if (contains(eventTypes, eventType)) { + if (contains(scope.eventTypes, eventType)) { // if this type of event was never bound to this Interactable if (!(eventType in this._iEvents)) { this._iEvents[eventType] = [listener]; @@ -4987,21 +4991,21 @@ } // delegated event for selector else if (this.selector) { - if (!delegatedEvents[eventType]) { - delegatedEvents[eventType] = { + if (!scope.delegatedEvents[eventType]) { + scope.delegatedEvents[eventType] = { selectors: [], contexts : [], listeners: [] }; // add delegate listener functions - for (i = 0; i < documents.length; i++) { - events.add(documents[i], eventType, delegateListener); - events.add(documents[i], eventType, delegateUseCapture, true); + for (i = 0; i < scope.documents.length; i++) { + events.add(scope.documents[i], eventType, delegateListener); + events.add(scope.documents[i], eventType, delegateUseCapture, true); } } - var delegated = delegatedEvents[eventType], + var delegated = scope.delegatedEvents[eventType], index; for (index = delegated.selectors.length - 1; index >= 0; index--) { @@ -5043,11 +5047,11 @@ off: function (eventType, listener, useCapture) { var i; - if (isString(eventType) && eventType.search(' ') !== -1) { + if (scope.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } - if (isArray(eventType)) { + if (scope.isArray(eventType)) { for (i = 0; i < eventType.length; i++) { this.off(eventType[i], listener, useCapture); } @@ -5055,7 +5059,7 @@ return this; } - if (isObject(eventType)) { + if (scope.isObject(eventType)) { for (var prop in eventType) { this.off(prop, eventType[prop], listener); } @@ -5070,11 +5074,11 @@ useCapture = useCapture? true: false; if (eventType === 'wheel') { - eventType = wheelEvent; + eventType = scope.wheelEvent; } // if it is an action event type - if (contains(eventTypes, eventType)) { + if (contains(scope.eventTypes, eventType)) { eventList = this._iEvents[eventType]; if (eventList && (index = indexOf(eventList, listener)) !== -1) { @@ -5083,7 +5087,7 @@ } // delegated event else if (this.selector) { - var delegated = delegatedEvents[eventType], + var delegated = scope.delegatedEvents[eventType], matchFound = false; if (!delegated) { return this; } @@ -5119,7 +5123,7 @@ // remove the arrays if they are empty if (!delegated.selectors.length) { - delegatedEvents[eventType] = null; + scope.delegatedEvents[eventType] = null; } } @@ -5150,21 +5154,21 @@ = (object) This Interactablw \*/ set: function (options) { - if (!isObject(options)) { + if (!scope.isObject(options)) { options = {}; } - this.options = extend({}, defaultOptions.base); + this.options = scope.extend({}, scope.defaultOptions.base); var i, actions = ['drag', 'drop', 'resize', 'gesture'], methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], - perActions = extend(extend({}, defaultOptions.perAction), options[action] || {}); + perActions = scope.extend(scope.extend({}, scope.defaultOptions.perAction), options[action] || {}); for (i = 0; i < actions.length; i++) { var action = actions[i]; - this.options[action] = extend({}, defaultOptions[action]); + this.options[action] = scope.extend({}, scope.defaultOptions[action]); this.setPerAction(action, perActions); @@ -5180,7 +5184,7 @@ for (i = 0, len = settings.length; i < len; i++) { var setting = settings[i]; - this.options[setting] = defaultOptions.base[setting]; + this.options[setting] = scope.defaultOptions.base[setting]; if (setting in options) { this[setting](options[setting]); @@ -5202,7 +5206,7 @@ unset: function () { events.remove(this._element, 'all'); - if (!isString(this.selector)) { + if (!scope.isString(this.selector)) { events.remove(this, 'all'); if (this.options.styleCursor) { this._element.style.cursor = ''; @@ -5210,8 +5214,8 @@ } else { // remove delegated events - for (var type in delegatedEvents) { - var delegated = delegatedEvents[type]; + for (var type in scope.delegatedEvents) { + var delegated = scope.delegatedEvents[type]; for (var i = 0; i < delegated.selectors.length; i++) { if (delegated.selectors[i] === this.selector @@ -5223,7 +5227,7 @@ // remove the arrays if they are empty if (!delegated.selectors.length) { - delegatedEvents[type] = null; + scope.delegatedEvents[type] = null; } } @@ -5237,7 +5241,7 @@ this.dropzone(false); - interactables.splice(indexOf(interactables, this), 1); + scope.interactables.splice(indexOf(scope.interactables, this), 1); return interact; } @@ -5248,7 +5252,7 @@ return function () { if (!warned) { - window.console.warn(message); + scope.window.console.warn(message); warned = true; } @@ -5276,7 +5280,7 @@ = (boolean) Indicates if the element or CSS selector was previously passed to interact \*/ interact.isSet = function(element, options) { - return interactables.indexOfElement(element, options && options.context) !== -1; + return scope.interactables.indexOfElement(element, options && options.context) !== -1; }; /*\ @@ -5292,11 +5296,11 @@ = (object) interact \*/ interact.on = function (type, listener, useCapture) { - if (isString(type) && type.search(' ') !== -1) { + if (scope.isString(type) && type.search(' ') !== -1) { type = type.trim().split(/ +/); } - if (isArray(type)) { + if (scope.isArray(type)) { for (var i = 0; i < type.length; i++) { interact.on(type[i], listener, useCapture); } @@ -5304,7 +5308,7 @@ return interact; } - if (isObject(type)) { + if (scope.isObject(type)) { for (var prop in type) { interact.on(prop, type[prop], listener); } @@ -5313,18 +5317,18 @@ } // if it is an InteractEvent type, add listener to globalEvents - if (contains(eventTypes, type)) { + if (contains(scope.eventTypes, type)) { // if this type of event was never bound - if (!globalEvents[type]) { - globalEvents[type] = [listener]; + if (!scope.globalEvents[type]) { + scope.globalEvents[type] = [listener]; } else { - globalEvents[type].push(listener); + scope.globalEvents[type].push(listener); } } // If non InteractEvent type, addEventListener to document else { - events.add(document, type, listener, useCapture); + events.add(scope.document, type, listener, useCapture); } return interact; @@ -5342,11 +5346,11 @@ = (object) interact \*/ interact.off = function (type, listener, useCapture) { - if (isString(type) && type.search(' ') !== -1) { + if (scope.isString(type) && type.search(' ') !== -1) { type = type.trim().split(/ +/); } - if (isArray(type)) { + if (scope.isArray(type)) { for (var i = 0; i < type.length; i++) { interact.off(type[i], listener, useCapture); } @@ -5354,7 +5358,7 @@ return interact; } - if (isObject(type)) { + if (scope.isObject(type)) { for (var prop in type) { interact.off(prop, type[prop], listener); } @@ -5362,15 +5366,15 @@ return interact; } - if (!contains(eventTypes, type)) { - events.remove(document, type, listener, useCapture); + if (!contains(scope.eventTypes, type)) { + events.remove(scope.document, type, listener, useCapture); } else { var index; - if (type in globalEvents - && (index = indexOf(globalEvents[type], listener)) !== -1) { - globalEvents[type].splice(index, 1); + if (type in scope.globalEvents + && (index = indexOf(scope.globalEvents[type], listener)) !== -1) { + scope.globalEvents[type].splice(index, 1); } } @@ -5390,11 +5394,11 @@ \*/ interact.enableDragging = warnOnce(function (newValue) { if (newValue !== null && newValue !== undefined) { - actionIsEnabled.drag = newValue; + scope.actionIsEnabled.drag = newValue; return interact; } - return actionIsEnabled.drag; + return scope.actionIsEnabled.drag; }, 'interact.enableDragging is deprecated and will soon be removed.'); /*\ @@ -5410,11 +5414,11 @@ \*/ interact.enableResizing = warnOnce(function (newValue) { if (newValue !== null && newValue !== undefined) { - actionIsEnabled.resize = newValue; + scope.actionIsEnabled.resize = newValue; return interact; } - return actionIsEnabled.resize; + return scope.actionIsEnabled.resize; }, 'interact.enableResizing is deprecated and will soon be removed.'); /*\ @@ -5430,14 +5434,14 @@ \*/ interact.enableGesturing = warnOnce(function (newValue) { if (newValue !== null && newValue !== undefined) { - actionIsEnabled.gesture = newValue; + scope.actionIsEnabled.gesture = newValue; return interact; } - return actionIsEnabled.gesture; + return scope.actionIsEnabled.gesture; }, 'interact.enableGesturing is deprecated and will soon be removed.'); - interact.eventTypes = eventTypes; + interact.eventTypes = scope.eventTypes; /*\ * interact.debug @@ -5447,10 +5451,10 @@ = (object) An object with properties that outline the current state and expose internal functions and variables \*/ interact.debug = function () { - var interaction = interactions[0] || new Interaction(); + var interaction = scope.interactions[0] || new Interaction(); return { - interactions : interactions, + interactions : scope.interactions, target : interaction.target, dragging : interaction.dragging, resizing : interaction.resizing, @@ -5478,12 +5482,12 @@ prevEvent : interaction.prevEvent, Interactable : Interactable, - interactables : interactables, + interactables : scope.interactables, pointerIsDown : interaction.pointerIsDown, - defaultOptions : defaultOptions, + defaultOptions : scope.defaultOptions, defaultActionChecker : defaultActionChecker, - actionCursors : actionCursors, + actionCursors : scope.actionCursors, dragMove : listeners.dragMove, resizeMove : listeners.resizeMove, gestureMove : listeners.gestureMove, @@ -5492,23 +5496,23 @@ pointerMove : listeners.pointerMove, pointerHover : listeners.pointerHover, - eventTypes : eventTypes, + eventTypes : scope.eventTypes, events : events, - globalEvents : globalEvents, - delegatedEvents : delegatedEvents + globalEvents : scope.globalEvents, + delegatedEvents : scope.delegatedEvents }; }; // expose the functions used to calculate multi-touch properties - interact.getTouchAverage = touchAverage; - interact.getTouchBBox = touchBBox; - interact.getTouchDistance = touchDistance; - interact.getTouchAngle = touchAngle; + interact.getTouchAverage = scope.touchAverage; + interact.getTouchBBox = scope.touchBBox; + interact.getTouchDistance = scope.touchDistance; + interact.getTouchAngle = scope.touchAngle; - interact.getElementRect = getElementRect; + interact.getElementRect = scope.getElementRect; interact.matchesSelector = matchesSelector; - interact.closest = closest; + interact.closest = scope.closest; /*\ * interact.margin @@ -5522,12 +5526,12 @@ = (number | interact) The current margin value or interact \*/ interact.margin = function (newvalue) { - if (isNumber(newvalue)) { - margin = newvalue; + if (scope.isNumber(newvalue)) { + scope.margin = newvalue; return interact; } - return margin; + return scope.margin; }; /*\ @@ -5537,7 +5541,7 @@ = (boolean) Whether or not the browser supports touch input \*/ interact.supportsTouch = function () { - return supportsTouch; + return scope.supportsTouch; }; /*\ @@ -5547,7 +5551,7 @@ = (boolean) Whether or not the browser supports PointerEvents \*/ interact.supportsPointerEvent = function () { - return supportsPointerEvent; + return scope.supportsPointerEvent; }; /*\ @@ -5560,8 +5564,8 @@ = (object) interact \*/ interact.stop = function (event) { - for (var i = interactions.length - 1; i > 0; i--) { - interactions[i].stop(event); + for (var i = scope.interactions.length - 1; i > 0; i--) { + scope.interactions[i].stop(event); } return interact; @@ -5579,16 +5583,16 @@ = (boolean | interact) The current setting or interact \*/ interact.dynamicDrop = function (newValue) { - if (isBool(newValue)) { + if (scope.isBool(newValue)) { //if (dragging && dynamicDrop !== newValue && !newValue) { //calcRects(dropzones); //} - dynamicDrop = newValue; + scope.dynamicDrop = newValue; return interact; } - return dynamicDrop; + return scope.dynamicDrop; }; /*\ @@ -5601,13 +5605,13 @@ = (number | Interactable) The current setting or interact \*/ interact.pointerMoveTolerance = function (newValue) { - if (isNumber(newValue)) { - pointerMoveTolerance = newValue; + if (scope.isNumber(newValue)) { + scope.pointerMoveTolerance = newValue; return this; } - return pointerMoveTolerance; + return scope.pointerMoveTolerance; }; /*\ @@ -5623,13 +5627,13 @@ - newValue (number) #optional Any number. newValue <= 0 means no interactions. \*/ interact.maxInteractions = function (newValue) { - if (isNumber(newValue)) { - maxInteractions = newValue; + if (scope.isNumber(newValue)) { + scope.maxInteractions = newValue; return this; } - return maxInteractions; + return scope.maxInteractions; }; interact.createSnapGrid = function (grid) { @@ -5637,7 +5641,7 @@ var offsetX = 0, offsetY = 0; - if (isObject(grid.offset)) { + if (scope.isObject(grid.offset)) { offsetX = grid.offset.x; offsetY = grid.offset.y; } @@ -5657,43 +5661,43 @@ }; function endAllInteractions (event) { - for (var i = 0; i < interactions.length; i++) { - interactions[i].pointerEnd(event, event); + for (var i = 0; i < scope.interactions.length; i++) { + scope.interactions[i].pointerEnd(event, event); } } function listenToDocument (doc) { - if (contains(documents, doc)) { return; } + if (contains(scope.documents, doc)) { return; } var win = doc.defaultView || doc.parentWindow; // add delegate event listener - for (var eventType in delegatedEvents) { + for (var eventType in scope.delegatedEvents) { events.add(doc, eventType, delegateListener); events.add(doc, eventType, delegateUseCapture, true); } - if (PointerEvent) { - if (PointerEvent === win.MSPointerEvent) { - pEventTypes = { + if (scope.PointerEvent) { + if (scope.PointerEvent === win.MSPointerEvent) { + scope.pEventTypes = { up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' }; } else { - pEventTypes = { + scope.pEventTypes = { up: 'pointerup', down: 'pointerdown', over: 'pointerover', out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }; } - events.add(doc, pEventTypes.down , listeners.selectorDown ); - events.add(doc, pEventTypes.move , listeners.pointerMove ); - events.add(doc, pEventTypes.over , listeners.pointerOver ); - events.add(doc, pEventTypes.out , listeners.pointerOut ); - events.add(doc, pEventTypes.up , listeners.pointerUp ); - events.add(doc, pEventTypes.cancel, listeners.pointerCancel); + events.add(doc, scope.pEventTypes.down , listeners.selectorDown ); + events.add(doc, scope.pEventTypes.move , listeners.pointerMove ); + events.add(doc, scope.pEventTypes.over , listeners.pointerOver ); + events.add(doc, scope.pEventTypes.out , listeners.pointerOut ); + events.add(doc, scope.pEventTypes.up , listeners.pointerUp ); + events.add(doc, scope.pEventTypes.cancel, listeners.pointerCancel); // autoscroll - events.add(doc, pEventTypes.move, listeners.autoScrollMove); + events.add(doc, scope.pEventTypes.move, listeners.autoScrollMove); } else { events.add(doc, 'mousedown', listeners.selectorDown); @@ -5734,7 +5738,7 @@ if (events.useAttachEvent) { // For IE's lack of Event#preventDefault events.add(doc, 'selectstart', function (event) { - var interaction = interactions[0]; + var interaction = scope.interactions[0]; if (interaction.currentAction()) { interaction.checkAndPreventDefault(event); @@ -5745,10 +5749,10 @@ events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick')); } - documents.push(doc); + scope.documents.push(doc); } - listenToDocument(document); + listenToDocument(scope.document); function indexOf (array, target) { for (var i = 0, len = array.length; i < len; i++) { @@ -5765,25 +5769,25 @@ } function matchesSelector (element, selector, nodeList) { - if (ie8MatchesSelector) { - return ie8MatchesSelector(element, selector, nodeList); + if (scope.ie8MatchesSelector) { + return scope.ie8MatchesSelector(element, selector, nodeList); } // remove /deep/ from selectors if shadowDOM polyfill is used - if (window !== realWindow) { + if (scope.window !== realWindow) { selector = selector.replace(/\/deep\//g, ' '); } - return element[prefixedMatchesSelector](selector); + return element[scope.prefixedMatchesSelector](selector); } function matchesUpTo (element, selector, limit) { - while (isElement(element)) { + while (scope.isElement(element)) { if (matchesSelector(element, selector)) { return true; } - element = parentElement(element); + element = scope.parentElement(element); if (element === limit) { return matchesSelector(element, selector); @@ -5795,8 +5799,8 @@ // For IE8's lack of an Element#matchesSelector // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - if (!(prefixedMatchesSelector in Element.prototype) || !isFunction(Element.prototype[prefixedMatchesSelector])) { - ie8MatchesSelector = function (element, selector, elems) { + if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { + scope.ie8MatchesSelector = function (element, selector, elems) { elems = elems || element.parentNode.querySelectorAll(selector); for (var i = 0, len = elems.length; i < len; i++) { From fb80db1428a1acc3da294c95e9787ee0557b45a0 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 16:35:52 +0100 Subject: [PATCH 004/131] Add more functions to `scope` --- src/interact.js | 194 ++++++++++++++++++++++++------------------------ 1 file changed, 97 insertions(+), 97 deletions(-) diff --git a/src/interact.js b/src/interact.js index 3915f53c5..b6227fa1b 100644 --- a/src/interact.js +++ b/src/interact.js @@ -358,7 +358,7 @@ attachedListeners = []; function add (element, type, listener, useCapture) { - var elementIndex = indexOf(elements, element), + var elementIndex = scope.indexOf(elements, element), target = targets[elementIndex]; if (!target) { @@ -382,12 +382,12 @@ target.typeCount++; } - if (!contains(target.events[type], listener)) { + if (!scope.contains(target.events[type], listener)) { var ret; if (useAttachEvent) { var listeners = attachedListeners[elementIndex], - listenerIndex = indexOf(listeners.supplied, listener); + listenerIndex = scope.indexOf(listeners.supplied, listener); var wrapped = listeners.wrapped[listenerIndex] || function (event) { if (!event.immediatePropagationStopped) { @@ -429,7 +429,7 @@ function remove (element, type, listener, useCapture) { var i, - elementIndex = indexOf(elements, element), + elementIndex = scope.indexOf(elements, element), target = targets[elementIndex], listeners, listenerIndex, @@ -441,7 +441,7 @@ if (useAttachEvent) { listeners = attachedListeners[elementIndex]; - listenerIndex = indexOf(listeners.supplied, listener); + listenerIndex = scope.indexOf(listeners.supplied, listener); wrapped = listeners.wrapped[listenerIndex]; } @@ -888,7 +888,7 @@ var parent = scope.parentElement(child); while (scope.isElement(parent)) { - if (matchesSelector(parent, selector)) { return parent; } + if (scope.matchesSelector(parent, selector)) { return parent; } parent = scope.parentElement(parent); } @@ -920,7 +920,7 @@ if (!ignoreFrom || !scope.isElement(element)) { return false; } if (scope.isString(ignoreFrom)) { - return matchesUpTo(element, ignoreFrom, interactableElement); + return scope.matchesUpTo(element, ignoreFrom, interactableElement); } else if (scope.isElement(ignoreFrom)) { return scope.nodeContains(ignoreFrom, element); @@ -937,7 +937,7 @@ if (!scope.isElement(element)) { return false; } if (scope.isString(allowFrom)) { - return matchesUpTo(element, allowFrom, interactableElement); + return scope.matchesUpTo(element, allowFrom, interactableElement); } else if (scope.isElement(allowFrom)) { return scope.nodeContains(allowFrom, element); @@ -1128,6 +1128,65 @@ return index; }; + scope.indexOf = function (array, target) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === target) { + return i; + } + } + + return -1; + }; + + scope.contains = function (array, target) { + return scope.indexOf(array, target) !== -1; + }; + + scope.matchesSelector = function (element, selector, nodeList) { + if (scope.ie8MatchesSelector) { + return scope.ie8MatchesSelector(element, selector, nodeList); + } + + // remove /deep/ from selectors if shadowDOM polyfill is used + if (scope.window !== realWindow) { + selector = selector.replace(/\/deep\//g, ' '); + } + + return element[scope.prefixedMatchesSelector](selector); + }; + + scope.matchesUpTo = function (element, selector, limit) { + while (scope.isElement(element)) { + if (scope.matchesSelector(element, selector)) { + return true; + } + + element = scope.parentElement(element); + + if (element === limit) { + return scope.matchesSelector(element, selector); + } + } + + return false; + }; + + // For IE8's lack of an Element#matchesSelector + // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified + if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { + scope.ie8MatchesSelector = function (element, selector, elems) { + elems = elems || element.parentNode.querySelectorAll(selector); + + for (var i = 0, len = elems.length; i < len; i++) { + if (elems[i] === element) { + return true; + } + } + + return false; + }; + } + function Interaction () { this.target = null; // current interactable being interacted with this.element = null; // the target element of the interactable @@ -1319,7 +1378,7 @@ && scope.inContext(interactable, eventTarget) && !scope.testIgnore(interactable, eventTarget, eventTarget) && scope.testAllow(interactable, eventTarget, eventTarget) - && matchesSelector(eventTarget, selector)) { + && scope.matchesSelector(eventTarget, selector)) { curMatches.push(interactable); curMatchElements.push(eventTarget); @@ -1458,7 +1517,7 @@ if (scope.inContext(interactable, element) && !scope.testIgnore(interactable, element, eventTarget) && scope.testAllow(interactable, element, eventTarget) - && matchesSelector(element, selector, elements)) { + && scope.matchesSelector(element, selector, elements)) { that.matches.push(interactable); that.matchElements.push(element); @@ -1697,7 +1756,7 @@ // if this interaction had been removed after stopping // add it back - if (indexOf(scope.interactions, this) === -1) { + if (scope.indexOf(scope.interactions, this) === -1) { scope.interactions.push(this); } @@ -1727,7 +1786,7 @@ && this.curCoords.client.y === this.prevCoords.client.y); var dx, dy, - pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); + pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); // register movement greater than pointerMoveTolerance if (this.pointerIsDown && !this.pointerWasMoved) { @@ -1816,7 +1875,7 @@ && !interactable.options.drag.manualStart && !scope.testIgnore(interactable, element, eventTarget) && scope.testAllow(interactable, element, eventTarget) - && matchesSelector(element, selector, elements) + && scope.matchesSelector(element, selector, elements) && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' && scope.checkAxis(axis, interactable) && scope.withinInteractionLimit(interactable, element, 'drag')) { @@ -2095,7 +2154,7 @@ }, pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -2108,7 +2167,7 @@ }, pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -2333,7 +2392,7 @@ // test the draggable element against the dropzone's accept setting if ((scope.isElement(accept) && accept !== element) || (scope.isString(accept) - && !matchesSelector(element, accept))) { + && !scope.matchesSelector(element, accept))) { continue; } @@ -2570,7 +2629,7 @@ // remove pointers if their ID isn't in this.pointerIds for (var i = 0; i < this.pointers.length; i++) { - if (indexOf(this.pointerIds, scope.getPointerId(this.pointers[i])) === -1) { + if (scope.indexOf(this.pointerIds, scope.getPointerId(this.pointers[i])) === -1) { this.pointers.splice(i, 1); } } @@ -2578,7 +2637,7 @@ for (i = 0; i < scope.interactions.length; i++) { // remove this interaction if it's not the only one of it's type if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { - scope.interactions.splice(indexOf(scope.interactions, this), 1); + scope.interactions.splice(scope.indexOf(scope.interactions, this), 1); } } }, @@ -2651,7 +2710,7 @@ addPointer: function (pointer) { var id = scope.getPointerId(pointer), - index = this.mouse? 0 : indexOf(this.pointerIds, id); + index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); if (index === -1) { index = this.pointerIds.length; @@ -2665,7 +2724,7 @@ removePointer: function (pointer) { var id = scope.getPointerId(pointer), - index = this.mouse? 0 : indexOf(this.pointerIds, id); + index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); if (index === -1) { return; } @@ -2684,7 +2743,7 @@ // The inertia start event should be this.pointers[0] if (this.inertiaStatus.active) { return; } - var index = this.mouse? 0: indexOf(this.pointerIds, scope.getPointerId(pointer)); + var index = this.mouse? 0: scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); if (index === -1) { return; } @@ -2692,7 +2751,7 @@ }, collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, scope.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); // do not fire a tap event if the pointer was moved before being lifted if (eventType === 'tap' && (this.pointerWasMoved @@ -2715,7 +2774,7 @@ && scope.inContext(interactable, element) && !scope.testIgnore(interactable, element, eventTarget) && scope.testAllow(interactable, element, eventTarget) - && matchesSelector(element, selector, els)) { + && scope.matchesSelector(element, selector, els)) { targets.push(interactable); elements.push(element); @@ -2741,7 +2800,7 @@ }, firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : indexOf(scope.getPointerId(pointer)), + var pointerIndex = this.mouse? 0 : scope.indexOf(scope.getPointerId(pointer)), pointerEvent = {}, i, // for tap events @@ -3205,7 +3264,7 @@ // get interaction that has this pointer for (i = 0; i < len; i++) { - if (contains(scope.interactions[i].pointerIds, id)) { + if (scope.contains(scope.interactions[i].pointerIds, id)) { return scope.interactions[i]; } } @@ -3610,7 +3669,7 @@ // the value is an element to use as a resize handle ? value === element // otherwise check if element matches value as selector - : matchesUpTo(element, value, interactableElement); + : scope.matchesUpTo(element, value, interactableElement); } function defaultActionChecker (pointer, interaction, element) { @@ -3742,7 +3801,7 @@ var selector = delegated.selectors[i], context = delegated.contexts[i]; - if (matchesSelector(element, selector) + if (scope.matchesSelector(element, selector) && scope.nodeContains(context, eventTarget) && scope.nodeContains(context, element)) { @@ -3877,7 +3936,7 @@ this._doc = _window.document; - if (!contains(scope.documents, this._doc)) { + if (!scope.contains(scope.documents, this._doc)) { listenToDocument(this._doc); } @@ -4901,7 +4960,7 @@ = (Interactable) this Interactable \*/ fire: function (iEvent) { - if (!(iEvent && iEvent.type) || !contains(scope.eventTypes, iEvent.type)) { + if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) { return this; } @@ -4980,7 +5039,7 @@ // convert to boolean useCapture = useCapture? true: false; - if (contains(scope.eventTypes, eventType)) { + if (scope.contains(scope.eventTypes, eventType)) { // if this type of event was never bound to this Interactable if (!(eventType in this._iEvents)) { this._iEvents[eventType] = [listener]; @@ -5078,10 +5137,10 @@ } // if it is an action event type - if (contains(scope.eventTypes, eventType)) { + if (scope.contains(scope.eventTypes, eventType)) { eventList = this._iEvents[eventType]; - if (eventList && (index = indexOf(eventList, listener)) !== -1) { + if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) { this._iEvents[eventType].splice(index, 1); } } @@ -5241,7 +5300,7 @@ this.dropzone(false); - scope.interactables.splice(indexOf(scope.interactables, this), 1); + scope.interactables.splice(scope.indexOf(scope.interactables, this), 1); return interact; } @@ -5317,7 +5376,7 @@ } // if it is an InteractEvent type, add listener to globalEvents - if (contains(scope.eventTypes, type)) { + if (scope.contains(scope.eventTypes, type)) { // if this type of event was never bound if (!scope.globalEvents[type]) { scope.globalEvents[type] = [listener]; @@ -5366,14 +5425,14 @@ return interact; } - if (!contains(scope.eventTypes, type)) { + if (!scope.contains(scope.eventTypes, type)) { events.remove(scope.document, type, listener, useCapture); } else { var index; if (type in scope.globalEvents - && (index = indexOf(scope.globalEvents[type], listener)) !== -1) { + && (index = scope.indexOf(scope.globalEvents[type], listener)) !== -1) { scope.globalEvents[type].splice(index, 1); } } @@ -5511,7 +5570,7 @@ interact.getTouchAngle = scope.touchAngle; interact.getElementRect = scope.getElementRect; - interact.matchesSelector = matchesSelector; + interact.matchesSelector = scope.matchesSelector; interact.closest = scope.closest; /*\ @@ -5667,7 +5726,7 @@ } function listenToDocument (doc) { - if (contains(scope.documents, doc)) { return; } + if (scope.contains(scope.documents, doc)) { return; } var win = doc.defaultView || doc.parentWindow; @@ -5754,65 +5813,6 @@ listenToDocument(scope.document); - function indexOf (array, target) { - for (var i = 0, len = array.length; i < len; i++) { - if (array[i] === target) { - return i; - } - } - - return -1; - } - - function contains (array, target) { - return indexOf(array, target) !== -1; - } - - function matchesSelector (element, selector, nodeList) { - if (scope.ie8MatchesSelector) { - return scope.ie8MatchesSelector(element, selector, nodeList); - } - - // remove /deep/ from selectors if shadowDOM polyfill is used - if (scope.window !== realWindow) { - selector = selector.replace(/\/deep\//g, ' '); - } - - return element[scope.prefixedMatchesSelector](selector); - } - - function matchesUpTo (element, selector, limit) { - while (scope.isElement(element)) { - if (matchesSelector(element, selector)) { - return true; - } - - element = scope.parentElement(element); - - if (element === limit) { - return matchesSelector(element, selector); - } - } - - return false; - } - - // For IE8's lack of an Element#matchesSelector - // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { - scope.ie8MatchesSelector = function (element, selector, elems) { - elems = elems || element.parentNode.querySelectorAll(selector); - - for (var i = 0, len = elems.length; i < len; i++) { - if (elems[i] === element) { - return true; - } - } - - return false; - }; - } - // requestAnimationFrame polyfill (function() { var lastTime = 0, From 4e2366d66abe768a1b775b767477821e33ae7be8 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 16:41:31 +0100 Subject: [PATCH 005/131] Add scope module --- src/interact.js | 2 +- src/scope.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 src/scope.js diff --git a/src/interact.js b/src/interact.js index b6227fa1b..b6bdbc02b 100644 --- a/src/interact.js +++ b/src/interact.js @@ -11,7 +11,7 @@ // return early if there's no window to work with (eg. Node.js) if (!realWindow) { return; } - var scope = {}; + var scope = require('./scope'); scope.realWindow = realWindow; diff --git a/src/scope.js b/src/scope.js new file mode 100644 index 000000000..a316c7bac --- /dev/null +++ b/src/scope.js @@ -0,0 +1,5 @@ +'use strict'; + +var scope = {}; + +module.exports = scope; From 34dd30d3829d48c31dcbd266c01be00d802c554b Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 16:54:09 +0100 Subject: [PATCH 006/131] Get window and realWindow in ./utils/window.js --- src/interact.js | 40 +++++++++++----------------------------- src/scope.js | 5 +++++ src/utils/window.js | 24 ++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/interact.js b/src/interact.js index b6bdbc02b..087c660b5 100644 --- a/src/interact.js +++ b/src/interact.js @@ -5,33 +5,15 @@ * Open source under the MIT License. * https://raw.github.com/taye/interact.js/master/LICENSE */ -(function (realWindow) { +(function () { 'use strict'; - // return early if there's no window to work with (eg. Node.js) - if (!realWindow) { return; } - var scope = require('./scope'); - scope.realWindow = realWindow; + // return early if there's no window to work with (eg. Node.js) + if (!scope.realWindow) { return; } // get wrapped window if using Shadow DOM polyfill - scope.window = (function () { - // create a TextNode - var el = realWindow.document.createTextNode(''); - - // check if it's wrapped by a polyfill - if (el.ownerDocument !== realWindow.document - && typeof realWindow.wrap === 'function' - && realWindow.wrap(el) === el) { - // return wrapped window - return realWindow.wrap(realWindow); - } - - // no Shadow DOM polyfil or native implementation - return realWindow; - }()); - scope.blank = function () {}; scope.document = scope.window.document; @@ -343,8 +325,8 @@ scope.ie8MatchesSelector = null; // native requestAnimationFrame or polyfill - var reqFrame = realWindow.requestAnimationFrame, - cancelFrame = realWindow.cancelAnimationFrame, + var reqFrame = scope.realWindow.requestAnimationFrame, + cancelFrame = scope.realWindow.cancelAnimationFrame, // Events wrapper events = (function () { @@ -1148,7 +1130,7 @@ } // remove /deep/ from selectors if shadowDOM polyfill is used - if (scope.window !== realWindow) { + if (scope.window !== scope.realWindow) { selector = selector.replace(/\/deep\//g, ' '); } @@ -5818,9 +5800,9 @@ var lastTime = 0, vendors = ['ms', 'moz', 'webkit', 'o']; - for(var x = 0; x < vendors.length && !realWindow.requestAnimationFrame; ++x) { - reqFrame = realWindow[vendors[x]+'RequestAnimationFrame']; - cancelFrame = realWindow[vendors[x]+'CancelAnimationFrame'] || realWindow[vendors[x]+'CancelRequestAnimationFrame']; + for(var x = 0; x < vendors.length && !scope.realWindow.requestAnimationFrame; ++x) { + reqFrame = scope.realWindow[vendors[x]+'RequestAnimationFrame']; + cancelFrame = scope.realWindow[vendors[x]+'CancelAnimationFrame'] || scope.realWindow[vendors[x]+'CancelRequestAnimationFrame']; } if (!reqFrame) { @@ -5857,7 +5839,7 @@ }); } else { - realWindow.interact = interact; + scope.realWindow.interact = interact; } -} (typeof window === 'undefined'? undefined : window)); +} ()); diff --git a/src/scope.js b/src/scope.js index a316c7bac..f047e2884 100644 --- a/src/scope.js +++ b/src/scope.js @@ -2,4 +2,9 @@ var scope = {}; +var win = require('./utils/window'); + +scope.window = win.window; +scope.realWindow = win.realWindow; + module.exports = scope; diff --git a/src/utils/window.js b/src/utils/window.js index 4861bbaae..eec229c2e 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -1,3 +1,23 @@ -var interactWindow = typeof window === 'undefined' ? undefined : window; +'use strict'; -module.exports = interactWindow; \ No newline at end of file +if (typeof window === 'undefined') { + module.exports.window = undefined; + module.exports.realWindow = undefined; +} +else { + module.exports.realWindow = window; + + // create a TextNode + var el = window.document.createTextNode(''); + + // check if it's wrapped by a polyfill + if (el.ownerDocument !== window.document + && typeof window.wrap === 'function' + && window.wrap(el) === el) { + // return wrapped window + module.exports.window = window.wrap(window); + } + + // no Shadow DOM polyfil or native implementation + module.exports.window = window; +} From fe98b2a22cd1f3f00bf38b5e07313847cf9409f3 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 17:41:56 +0100 Subject: [PATCH 007/131] Separate more code into modules events extend arr - indexOf, contains domObjects - document, Element, etc. isType - isArray, isString, isWindow, etc. --- src/interact.js | 232 ++-------------------------------------- src/scope.js | 6 +- src/utils/arr.js | 20 ++++ src/utils/domObjects.js | 16 +++ src/utils/events.js | 176 ++++++++++++++++++++++++++++++ src/utils/extend.js | 8 ++ src/utils/isType.js | 17 +++ src/utils/window.js | 14 +++ 8 files changed, 261 insertions(+), 228 deletions(-) create mode 100644 src/utils/arr.js create mode 100644 src/utils/domObjects.js create mode 100644 src/utils/events.js create mode 100644 src/utils/extend.js create mode 100644 src/utils/isType.js diff --git a/src/interact.js b/src/interact.js index 087c660b5..dae81a7e0 100644 --- a/src/interact.js +++ b/src/interact.js @@ -8,22 +8,13 @@ (function () { 'use strict'; - var scope = require('./scope'); - // return early if there's no window to work with (eg. Node.js) - if (!scope.realWindow) { return; } + if (!require('./utils/window').window) { return; } - // get wrapped window if using Shadow DOM polyfill - scope.blank = function () {}; + var scope = require('./scope'); - scope.document = scope.window.document; - scope.DocumentFragment = scope.window.DocumentFragment || scope.blank; - scope.SVGElement = scope.window.SVGElement || scope.blank; - scope.SVGSVGElement = scope.window.SVGSVGElement || scope.blank; - scope.SVGElementInstance = scope.window.SVGElementInstance || scope.blank; - scope.HTMLElement = scope.window.HTMLElement || scope.window.Element; + scope.blank = function () {}; - scope.PointerEvent = (scope.window.PointerEvent || scope.window.MSPointerEvent); scope.pEventTypes = null; scope.hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); }; @@ -329,177 +320,7 @@ cancelFrame = scope.realWindow.cancelAnimationFrame, // Events wrapper - events = (function () { - var useAttachEvent = ('attachEvent' in scope.window) && !('addEventListener' in scope.window), - addEvent = useAttachEvent? 'attachEvent': 'addEventListener', - removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', - on = useAttachEvent? 'on': '', - - elements = [], - targets = [], - attachedListeners = []; - - function add (element, type, listener, useCapture) { - var elementIndex = scope.indexOf(elements, element), - target = targets[elementIndex]; - - if (!target) { - target = { - events: {}, - typeCount: 0 - }; - - elementIndex = elements.push(element) - 1; - targets.push(target); - - attachedListeners.push((useAttachEvent ? { - supplied: [], - wrapped : [], - useCount: [] - } : null)); - } - - if (!target.events[type]) { - target.events[type] = []; - target.typeCount++; - } - - if (!scope.contains(target.events[type], listener)) { - var ret; - - if (useAttachEvent) { - var listeners = attachedListeners[elementIndex], - listenerIndex = scope.indexOf(listeners.supplied, listener); - - var wrapped = listeners.wrapped[listenerIndex] || function (event) { - if (!event.immediatePropagationStopped) { - event.target = event.srcElement; - event.currentTarget = element; - - event.preventDefault = event.preventDefault || preventDef; - event.stopPropagation = event.stopPropagation || stopProp; - event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; - - if (/mouse|click/.test(event.type)) { - event.pageX = event.clientX + scope.getWindow(element).document.documentElement.scrollLeft; - event.pageY = event.clientY + scope.getWindow(element).document.documentElement.scrollTop; - } - - listener(event); - } - }; - - ret = element[addEvent](on + type, wrapped, Boolean(useCapture)); - - if (listenerIndex === -1) { - listeners.supplied.push(listener); - listeners.wrapped.push(wrapped); - listeners.useCount.push(1); - } - else { - listeners.useCount[listenerIndex]++; - } - } - else { - ret = element[addEvent](type, listener, useCapture || false); - } - target.events[type].push(listener); - - return ret; - } - } - - function remove (element, type, listener, useCapture) { - var i, - elementIndex = scope.indexOf(elements, element), - target = targets[elementIndex], - listeners, - listenerIndex, - wrapped = listener; - - if (!target || !target.events) { - return; - } - - if (useAttachEvent) { - listeners = attachedListeners[elementIndex]; - listenerIndex = scope.indexOf(listeners.supplied, listener); - wrapped = listeners.wrapped[listenerIndex]; - } - - if (type === 'all') { - for (type in target.events) { - if (target.events.hasOwnProperty(type)) { - remove(element, type, 'all'); - } - } - return; - } - - if (target.events[type]) { - var len = target.events[type].length; - - if (listener === 'all') { - for (i = 0; i < len; i++) { - remove(element, type, target.events[type][i], Boolean(useCapture)); - } - return; - } else { - for (i = 0; i < len; i++) { - if (target.events[type][i] === listener) { - element[removeEvent](on + type, wrapped, useCapture || false); - target.events[type].splice(i, 1); - - if (useAttachEvent && listeners) { - listeners.useCount[listenerIndex]--; - if (listeners.useCount[listenerIndex] === 0) { - listeners.supplied.splice(listenerIndex, 1); - listeners.wrapped.splice(listenerIndex, 1); - listeners.useCount.splice(listenerIndex, 1); - } - } - - break; - } - } - } - - if (target.events[type] && target.events[type].length === 0) { - target.events[type] = null; - target.typeCount--; - } - } - - if (!target.typeCount) { - targets.splice(elementIndex, 1); - elements.splice(elementIndex, 1); - attachedListeners.splice(elementIndex, 1); - } - } - - function preventDef () { - this.returnValue = false; - } - - function stopProp () { - this.cancelBubble = true; - } - - function stopImmProp () { - this.cancelBubble = true; - this.immediatePropagationStopped = true; - } - - return { - add: add, - remove: remove, - useAttachEvent: useAttachEvent, - - _elements: elements, - _targets: targets, - _attachedListeners: attachedListeners - }; - }()); + events = require('./utils/events'); scope.isElement = function (o) { if (!o || (typeof o !== 'object')) { return false; } @@ -510,18 +331,8 @@ ? o instanceof _window.Element //DOM2 : o.nodeType === 1 && typeof o.nodeName === "string"); }; - scope.isWindow = function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }; - scope.isDocFrag = function (thing) { return !!thing && thing instanceof scope.DocumentFragment; }; - scope.isArray = function (thing) { - return scope.isObject(thing) - && (typeof thing.length !== undefined) - && scope.isFunction(thing.splice); - }; - scope.isObject = function (thing) { return !!thing && (typeof thing === 'object'); }; - scope.isFunction = function (thing) { return typeof thing === 'function'; }; - scope.isNumber = function (thing) { return typeof thing === 'number' ; }; - scope.isBool = function (thing) { return typeof thing === 'boolean' ; }; - scope.isString = function (thing) { return typeof thing === 'string' ; }; + + scope.extend(scope, require('./utils/isType')); scope.trySelector = function (value) { if (!scope.isString(value)) { return false; } @@ -531,13 +342,6 @@ return true; }; - scope.extend = function (dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - return dest; - }; - scope.copyCoords = function (dest, src) { dest.page = dest.page || {}; dest.page.x = src.page.x; @@ -672,16 +476,6 @@ : element); }; - scope.getWindow = function (node) { - if (scope.isWindow(node)) { - return node; - } - - var rootNode = (node.ownerDocument || node); - - return rootNode.defaultView || rootNode.parentWindow || scope.window; - }; - scope.getElementRect = function (element) { var scroll = scope.isIOS7orLower ? { x: 0, y: 0 } @@ -1110,19 +904,7 @@ return index; }; - scope.indexOf = function (array, target) { - for (var i = 0, len = array.length; i < len; i++) { - if (array[i] === target) { - return i; - } - } - - return -1; - }; - - scope.contains = function (array, target) { - return scope.indexOf(array, target) !== -1; - }; + scope.extend(scope, require('./utils/arr.js')); scope.matchesSelector = function (element, selector, nodeList) { if (scope.ie8MatchesSelector) { diff --git a/src/scope.js b/src/scope.js index f047e2884..4eeb15f7c 100644 --- a/src/scope.js +++ b/src/scope.js @@ -2,9 +2,9 @@ var scope = {}; -var win = require('./utils/window'); +scope.extend = require('./utils/extend'); -scope.window = win.window; -scope.realWindow = win.realWindow; +scope.extend(scope, require('./utils/window')); +scope.extend(scope, require('./utils/domObjects')); module.exports = scope; diff --git a/src/utils/arr.js b/src/utils/arr.js new file mode 100644 index 000000000..29a076fe6 --- /dev/null +++ b/src/utils/arr.js @@ -0,0 +1,20 @@ +'use strict'; + +function indexOf (array, target) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === target) { + return i; + } + } + + return -1; +} + +function contains (array, target) { + return indexOf(array, target) !== -1; +} + +module.exports = { + indexOf: indexOf, + contains: contains +}; diff --git a/src/utils/domObjects.js b/src/utils/domObjects.js new file mode 100644 index 000000000..563982522 --- /dev/null +++ b/src/utils/domObjects.js @@ -0,0 +1,16 @@ +'use strict'; + +var domObjects = {}, + win = require('./window').window, + blank = function () {}; + +domObjects.document = win.document; +domObjects.DocumentFragment = win.DocumentFragment || blank; +domObjects.SVGElement = win.SVGElement || blank; +domObjects.SVGSVGElement = win.SVGSVGElement || blank; +domObjects.SVGElementInstance = win.SVGElementInstance || blank; +domObjects.HTMLElement = win.HTMLElement || win.Element; + +domObjects.PointerEvent = (win.PointerEvent || win.MSPointerEvent); + +module.exports = domObjects; diff --git a/src/utils/events.js b/src/utils/events.js new file mode 100644 index 000000000..d2a06e0c0 --- /dev/null +++ b/src/utils/events.js @@ -0,0 +1,176 @@ +'use strict'; + +var arr = require('./arr'), + indexOf = arr.indexOf, + contains = arr.contains, + getWindow = require('./window').getWindow, + + useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window), + addEvent = useAttachEvent? 'attachEvent': 'addEventListener', + removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', + on = useAttachEvent? 'on': '', + + elements = [], + targets = [], + attachedListeners = []; + +function add (element, type, listener, useCapture) { + var elementIndex = indexOf(elements, element), + target = targets[elementIndex]; + + if (!target) { + target = { + events: {}, + typeCount: 0 + }; + + elementIndex = elements.push(element) - 1; + targets.push(target); + + attachedListeners.push((useAttachEvent ? { + supplied: [], + wrapped : [], + useCount: [] + } : null)); + } + + if (!target.events[type]) { + target.events[type] = []; + target.typeCount++; + } + + if (!contains(target.events[type], listener)) { + var ret; + + if (useAttachEvent) { + var listeners = attachedListeners[elementIndex], + listenerIndex = indexOf(listeners.supplied, listener); + + var wrapped = listeners.wrapped[listenerIndex] || function (event) { + if (!event.immediatePropagationStopped) { + event.target = event.srcElement; + event.currentTarget = element; + + event.preventDefault = event.preventDefault || preventDef; + event.stopPropagation = event.stopPropagation || stopProp; + event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; + + if (/mouse|click/.test(event.type)) { + event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; + event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; + } + + listener(event); + } + }; + + ret = element[addEvent](on + type, wrapped, Boolean(useCapture)); + + if (listenerIndex === -1) { + listeners.supplied.push(listener); + listeners.wrapped.push(wrapped); + listeners.useCount.push(1); + } + else { + listeners.useCount[listenerIndex]++; + } + } + else { + ret = element[addEvent](type, listener, useCapture || false); + } + target.events[type].push(listener); + + return ret; + } +} + +function remove (element, type, listener, useCapture) { + var i, + elementIndex = indexOf(elements, element), + target = targets[elementIndex], + listeners, + listenerIndex, + wrapped = listener; + + if (!target || !target.events) { + return; + } + + if (useAttachEvent) { + listeners = attachedListeners[elementIndex]; + listenerIndex = indexOf(listeners.supplied, listener); + wrapped = listeners.wrapped[listenerIndex]; + } + + if (type === 'all') { + for (type in target.events) { + if (target.events.hasOwnProperty(type)) { + remove(element, type, 'all'); + } + } + return; + } + + if (target.events[type]) { + var len = target.events[type].length; + + if (listener === 'all') { + for (i = 0; i < len; i++) { + remove(element, type, target.events[type][i], Boolean(useCapture)); + } + return; + } else { + for (i = 0; i < len; i++) { + if (target.events[type][i] === listener) { + element[removeEvent](on + type, wrapped, useCapture || false); + target.events[type].splice(i, 1); + + if (useAttachEvent && listeners) { + listeners.useCount[listenerIndex]--; + if (listeners.useCount[listenerIndex] === 0) { + listeners.supplied.splice(listenerIndex, 1); + listeners.wrapped.splice(listenerIndex, 1); + listeners.useCount.splice(listenerIndex, 1); + } + } + + break; + } + } + } + + if (target.events[type] && target.events[type].length === 0) { + target.events[type] = null; + target.typeCount--; + } + } + + if (!target.typeCount) { + targets.splice(elementIndex, 1); + elements.splice(elementIndex, 1); + attachedListeners.splice(elementIndex, 1); + } +} + +function preventDef () { + this.returnValue = false; +} + +function stopProp () { + this.cancelBubble = true; +} + +function stopImmProp () { + this.cancelBubble = true; + this.immediatePropagationStopped = true; +} + +module.exports = { + add: add, + remove: remove, + useAttachEvent: useAttachEvent, + + _elements: elements, + _targets: targets, + _attachedListeners: attachedListeners +}; diff --git a/src/utils/extend.js b/src/utils/extend.js new file mode 100644 index 000000000..876471dc9 --- /dev/null +++ b/src/utils/extend.js @@ -0,0 +1,8 @@ +'use strict'; + +module.exports = function extend (dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; + } + return dest; +}; diff --git a/src/utils/isType.js b/src/utils/isType.js new file mode 100644 index 000000000..da3c13faf --- /dev/null +++ b/src/utils/isType.js @@ -0,0 +1,17 @@ +'use strict'; + +var domObjects = require('./domObjects'); + +module.exports.isWindow = function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }; +module.exports.isDocFrag = function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }; +module.exports.isArray = function (thing) { + return module.exports.isObject(thing) + && (typeof thing.length !== undefined) + && module.exports.isFunction(thing.splice); +}; +module.exports.isObject = function (thing) { return !!thing && (typeof thing === 'object'); }; +module.exports.isFunction = function (thing) { return typeof thing === 'function'; }; +module.exports.isNumber = function (thing) { return typeof thing === 'number' ; }; +module.exports.isBool = function (thing) { return typeof thing === 'boolean' ; }; +module.exports.isString = function (thing) { return typeof thing === 'string' ; }; + diff --git a/src/utils/window.js b/src/utils/window.js index eec229c2e..0cc8f55f0 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -5,6 +5,8 @@ if (typeof window === 'undefined') { module.exports.realWindow = undefined; } else { + // get wrapped window if using Shadow DOM polyfill + module.exports.realWindow = window; // create a TextNode @@ -21,3 +23,15 @@ else { // no Shadow DOM polyfil or native implementation module.exports.window = window; } + +var isWindow = require('./isType').isWindow; + +module.exports.getWindow = function getWindow (node) { + if (isWindow(node)) { + return node; + } + + var rootNode = (node.ownerDocument || node); + + return rootNode.defaultView || rootNode.parentWindow || module.exports.window; +}; From d3e4e661455d1f1b1e0eaa32435d9a447e512d8b Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 18:28:28 +0100 Subject: [PATCH 008/131] Put extend and blank in `utils` object --- src/interact.js | 67 +++++++++++++++++++++++----------------------- src/scope.js | 9 +++---- src/utils/index.js | 13 +++++++++ 3 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 src/utils/index.js diff --git a/src/interact.js b/src/interact.js index dae81a7e0..2870475df 100644 --- a/src/interact.js +++ b/src/interact.js @@ -11,9 +11,8 @@ // return early if there's no window to work with (eg. Node.js) if (!require('./utils/window').window) { return; } - var scope = require('./scope'); - - scope.blank = function () {}; + var scope = require('./scope'), + utils = require('./utils'); scope.pEventTypes = null; @@ -332,7 +331,7 @@ : o.nodeType === 1 && typeof o.nodeName === "string"); }; - scope.extend(scope, require('./utils/isType')); + utils.extend(scope, require('./utils/isType')); scope.trySelector = function (value) { if (!scope.isString(value)) { return false; } @@ -411,7 +410,7 @@ if (/inertiastart/.test(pointer.type)) { interaction = interaction || pointer.interaction; - scope.extend(page, interaction.inertiaStatus.upCoords.page); + utils.extend(page, interaction.inertiaStatus.upCoords.page); page.x += interaction.inertiaStatus.sx; page.y += interaction.inertiaStatus.sy; @@ -440,7 +439,7 @@ if (pointer instanceof InteractEvent) { if (/inertiastart/.test(pointer.type)) { - scope.extend(client, interaction.inertiaStatus.upCoords.client); + utils.extend(client, interaction.inertiaStatus.upCoords.client); client.x += interaction.inertiaStatus.sx; client.y += interaction.inertiaStatus.sy; @@ -904,7 +903,7 @@ return index; }; - scope.extend(scope, require('./utils/arr.js')); + utils.extend(scope, require('./utils/arr.js')); scope.matchesSelector = function (element, selector, nodeList) { if (scope.ie8MatchesSelector) { @@ -1235,7 +1234,7 @@ selectorDown: function (pointer, event, eventTarget, curEventTarget) { var that = this, // copy event to be used in timeout for IE8 - eventCopy = events.useAttachEvent? scope.extend({}, event) : event, + eventCopy = events.useAttachEvent? utils.extend({}, event) : event, element = eventTarget, pointerIndex = this.addPointer(pointer), action; @@ -1315,7 +1314,7 @@ // do these now since pointerDown isn't being called from here this.downTimes[pointerIndex] = new Date().getTime(); this.downTargets[pointerIndex] = eventTarget; - scope.extend(this.downPointer, pointer); + utils.extend(this.downPointer, pointer); scope.copyCoords(this.prevCoords, this.curCoords); this.pointerWasMoved = false; @@ -1385,7 +1384,7 @@ this.downTimes[pointerIndex] = new Date().getTime(); this.downTargets[pointerIndex] = eventTarget; - scope.extend(this.downPointer, pointer); + utils.extend(this.downPointer, pointer); this.setEventXY(this.prevCoords); this.pointerWasMoved = false; @@ -1753,7 +1752,7 @@ var startRect = this.target.getRect(this.element); if (this.target.options.resize.square) { - var squareEdges = scope.extend({}, this.prepared.edges); + var squareEdges = utils.extend({}, this.prepared.edges); squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); @@ -1768,9 +1767,9 @@ this.resizeRects = { start : startRect, - current : scope.extend({}, startRect), - restricted: scope.extend({}, startRect), - previous : scope.extend({}, startRect), + current : utils.extend({}, startRect), + restricted: utils.extend({}, startRect), + previous : utils.extend({}, startRect), delta : { left: 0, right : 0, width : 0, top : 0, bottom: 0, height: 0 @@ -1803,7 +1802,7 @@ current = this.resizeRects.current, restricted = this.resizeRects.restricted, delta = this.resizeRects.delta, - previous = scope.extend(this.resizeRects.previous, restricted); + previous = utils.extend(this.resizeRects.previous, restricted); if (this.target.options.resize.square) { var originalEdges = edges; @@ -1826,7 +1825,7 @@ if (invertible) { // if invertible, copy the current rect - scope.extend(restricted, current); + utils.extend(restricted, current); if (invert === 'reposition') { // swap edge values if necessary to keep width/height positive @@ -2044,7 +2043,7 @@ this.calcInertia(inertiaStatus); - var page = scope.extend({}, this.curCoords.page), + var page = utils.extend({}, this.curCoords.page), origin = scope.getOriginXY(target, this.element), statusObject; @@ -2576,9 +2575,9 @@ pointerEvent = pointer; } else { - scope.extend(pointerEvent, event); + utils.extend(pointerEvent, event); if (event !== pointer) { - scope.extend(pointerEvent, pointer); + utils.extend(pointerEvent, pointer); } pointerEvent.preventDefault = preventOriginalDefault; @@ -2623,7 +2622,7 @@ if (createNewDoubleTap) { var doubleTap = {}; - scope.extend(doubleTap, pointerEvent); + utils.extend(doubleTap, pointerEvent); doubleTap.dt = interval; doubleTap.type = 'doubletap'; @@ -2667,7 +2666,7 @@ else { var origin = scope.getOriginXY(this.target, this.element); - page = scope.extend({}, pageCoords); + page = utils.extend({}, pageCoords); page.x -= origin.x; page.y -= origin.y; @@ -2795,7 +2794,7 @@ page = status.useStatusXY ? page = { x: status.x, y: status.y } - : page = scope.extend({}, pageCoords); + : page = utils.extend({}, pageCoords); if (status.snap && status.snap.locked) { page.x += status.snap.dx || 0; @@ -3124,8 +3123,8 @@ element = element || interaction.element; - page = scope.extend({}, coords.page); - client = scope.extend({}, coords.client); + page = utils.extend({}, coords.page); + client = utils.extend({}, coords.client); page.x -= origin.x; page.y -= origin.y; @@ -3360,7 +3359,7 @@ } InteractEvent.prototype = { - preventDefault: scope.blank, + preventDefault: utils.blank, stopImmediatePropagation: function () { this.immediatePropagationStopped = this.propagationStopped = true; }, @@ -3442,7 +3441,7 @@ action = null, resizeAxes = null, resizeEdges, - page = scope.extend({}, interaction.curCoords.page), + page = utils.extend({}, interaction.curCoords.page), options = this.options; if (!rect) { return null; } @@ -3795,7 +3794,7 @@ // if the option in the options arg is an object value if (scope.isObject(options[option])) { // duplicate the object - this.options[action][option] = scope.extend(this.options[action][option] || {}, options[option]); + this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { this.options[action][option].enabled = options[option].enabled === false? false : true; @@ -4163,7 +4162,7 @@ \*/ autoScroll: function (options) { if (scope.isObject(options)) { - options = scope.extend({ actions: ['drag', 'resize']}, options); + options = utils.extend({ actions: ['drag', 'resize']}, options); } else if (scope.isBool(options)) { options = { actions: ['drag', 'resize'], enabled: options }; @@ -4255,13 +4254,13 @@ var thisOption = this.options[action][option]; if (scope.isObject(options)) { - scope.extend(thisOption, options); + utils.extend(thisOption, options); thisOption.enabled = options.enabled === false? false: true; if (option === 'snap') { if (thisOption.mode === 'grid') { thisOption.targets = [ - interact.createSnapGrid(scope.extend({ + interact.createSnapGrid(utils.extend({ offset: thisOption.gridOffset || { x: 0, y: 0 } }, thisOption.grid || {})) ]; @@ -4615,7 +4614,7 @@ var action = actions[i]; if (action in options) { - var perAction = scope.extend({ + var perAction = utils.extend({ actions: [action], restriction: options[action] }, options); @@ -4981,17 +4980,17 @@ options = {}; } - this.options = scope.extend({}, scope.defaultOptions.base); + this.options = utils.extend({}, scope.defaultOptions.base); var i, actions = ['drag', 'drop', 'resize', 'gesture'], methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], - perActions = scope.extend(scope.extend({}, scope.defaultOptions.perAction), options[action] || {}); + perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {}); for (i = 0; i < actions.length; i++) { var action = actions[i]; - this.options[action] = scope.extend({}, scope.defaultOptions[action]); + this.options[action] = utils.extend({}, scope.defaultOptions[action]); this.setPerAction(action, perActions); diff --git a/src/scope.js b/src/scope.js index 4eeb15f7c..eecabd9a7 100644 --- a/src/scope.js +++ b/src/scope.js @@ -1,10 +1,9 @@ 'use strict'; -var scope = {}; +var scope = {}, + extend = require('./utils/extend'); -scope.extend = require('./utils/extend'); - -scope.extend(scope, require('./utils/window')); -scope.extend(scope, require('./utils/domObjects')); +extend(scope, require('./utils/window')); +extend(scope, require('./utils/domObjects')); module.exports = scope; diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 000000000..f0bb79171 --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1,13 @@ +'use strict'; + +var utils = {}, + extend = require('./extend'); + +utils.extend = extend; +utils.blank = function () {}; +utils.raf = require('./raf'); + +extend(utils, require('./arr')); +extend(utils, require('./isType')); + +module.exports = utils; From 5c5990d29e511bacf412201d80502a3008b613dd Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 18:46:26 +0100 Subject: [PATCH 009/131] Move defaultOptions and autoScroll into modules --- src/autoScroll.js | 59 +++++++++++++++ src/defaultOptions.js | 115 +++++++++++++++++++++++++++++ src/interact.js | 165 +----------------------------------------- 3 files changed, 176 insertions(+), 163 deletions(-) create mode 100644 src/autoScroll.js create mode 100644 src/defaultOptions.js diff --git a/src/autoScroll.js b/src/autoScroll.js new file mode 100644 index 000000000..44ae42e0d --- /dev/null +++ b/src/autoScroll.js @@ -0,0 +1,59 @@ +'use strict'; + +var raf = require('./utils/raf'), + getWindow = require('./utils/window').getWindow, + isWindow = require('./utils/isType').isWindow; + +var autoScroll = { + + interaction: null, + i: null, // the handle returned by window.setInterval + x: 0, y: 0, // Direction each pulse is to scroll in + + isScrolling: false, + prevTime: 0, + + start: function (interaction) { + autoScroll.isScrolling = true; + raf.cancel(autoScroll.i); + + autoScroll.interaction = interaction; + autoScroll.prevTime = new Date().getTime(); + autoScroll.i = raf.request(autoScroll.scroll); + }, + + stop: function () { + autoScroll.isScrolling = false; + raf.cancel(autoScroll.i); + }, + + // scroll the window by the values in scroll.x/y + scroll: function () { + var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll, + container = options.container || getWindow(autoScroll.interaction.element), + now = new Date().getTime(), + // change in time in seconds + dt = (now - autoScroll.prevTime) / 1000, + // displacement + s = options.speed * dt; + + if (s >= 1) { + if (isWindow(container)) { + container.scrollBy(autoScroll.x * s, autoScroll.y * s); + } + else if (container) { + container.scrollLeft += autoScroll.x * s; + container.scrollTop += autoScroll.y * s; + } + + autoScroll.prevTime = now; + } + + if (autoScroll.isScrolling) { + raf.cancel(autoScroll.i); + autoScroll.i = raf.request(autoScroll.scroll); + } + } +}; + +module.exports = autoScroll; diff --git a/src/defaultOptions.js b/src/defaultOptions.js new file mode 100644 index 000000000..8ffcde372 --- /dev/null +++ b/src/defaultOptions.js @@ -0,0 +1,115 @@ +'use strict'; + +module.exports = { + base: { + accept : null, + actionChecker : null, + styleCursor : true, + preventDefault: 'auto', + origin : { x: 0, y: 0 }, + deltaSource : 'page', + allowFrom : null, + ignoreFrom : null, + _context : require('./utils/domObjects').document, + dropChecker : null + }, + + drag: { + enabled: false, + manualStart: true, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + axis: 'xy', + }, + + drop: { + enabled: false, + accept: null, + overlap: 'pointer' + }, + + resize: { + enabled: false, + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + square: false, + axis: 'xy', + + // use default margin + margin: NaN, + + // object with props left, right, top, bottom which are + // true/false values to resize when the pointer is over that edge, + // CSS selectors to match the handles for each direction + // or the Elements for each handle + edges: null, + + // a value of 'none' will limit the resize rect to a minimum of 0x0 + // 'negate' will alow the rect to have negative width/height + // 'reposition' will keep the width/height positive by swapping + // the top and bottom edges and/or swapping the left and right edges + invert: 'none' + }, + + gesture: { + manualStart: false, + enabled: false, + max: Infinity, + maxPerElement: 1, + + restrict: null + }, + + perAction: { + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: { + enabled : false, + endOnly : false, + range : Infinity, + targets : null, + offsets : null, + + relativePoints: null + }, + + restrict: { + enabled: false, + endOnly: false + }, + + autoScroll: { + enabled : false, + container : null, // the item that is scrolled (Window or HTMLElement) + margin : 60, + speed : 300 // the scroll speed in pixels per second + }, + + inertia: { + enabled : false, + resistance : 10, // the lambda in exponential decay + minSpeed : 100, // target speed must be above this for inertia to start + endSpeed : 10, // the speed at which inertia is slow enough to stop + allowResume : true, // allow resuming an action in inertia phase + zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 + smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia + } + }, + + _holdDuration: 600 +}; diff --git a/src/interact.js b/src/interact.js index 2870475df..7983188ef 100644 --- a/src/interact.js +++ b/src/interact.js @@ -36,171 +36,10 @@ // } scope.delegatedEvents = {}; - scope.defaultOptions = { - base: { - accept : null, - actionChecker : null, - styleCursor : true, - preventDefault: 'auto', - origin : { x: 0, y: 0 }, - deltaSource : 'page', - allowFrom : null, - ignoreFrom : null, - _context : scope.document, - dropChecker : null - }, - - drag: { - enabled: false, - manualStart: true, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - axis: 'xy', - }, - - drop: { - enabled: false, - accept: null, - overlap: 'pointer' - }, - - resize: { - enabled: false, - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - square: false, - axis: 'xy', - - // use default margin - margin: NaN, - - // object with props left, right, top, bottom which are - // true/false values to resize when the pointer is over that edge, - // CSS selectors to match the handles for each direction - // or the Elements for each handle - edges: null, - - // a value of 'none' will limit the resize rect to a minimum of 0x0 - // 'negate' will alow the rect to have negative width/height - // 'reposition' will keep the width/height positive by swapping - // the top and bottom edges and/or swapping the left and right edges - invert: 'none' - }, - - gesture: { - manualStart: false, - enabled: false, - max: Infinity, - maxPerElement: 1, - - restrict: null - }, - - perAction: { - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: { - enabled : false, - endOnly : false, - range : Infinity, - targets : null, - offsets : null, - - relativePoints: null - }, - - restrict: { - enabled: false, - endOnly: false - }, - - autoScroll: { - enabled : false, - container : null, // the item that is scrolled (Window or HTMLElement) - margin : 60, - speed : 300 // the scroll speed in pixels per second - }, - - inertia: { - enabled : false, - resistance : 10, // the lambda in exponential decay - minSpeed : 100, // target speed must be above this for inertia to start - endSpeed : 10, // the speed at which inertia is slow enough to stop - allowResume : true, // allow resuming an action in inertia phase - zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 - smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia - } - }, - - _holdDuration: 600 - }; + scope.defaultOptions = require('./defaultOptions'); // Things related to autoScroll - scope.autoScroll = { - interaction: null, - i: null, // the handle returned by window.setInterval - x: 0, y: 0, // Direction each pulse is to scroll in - - // scroll the window by the values in scroll.x/y - scroll: function () { - var options = scope.autoScroll.interaction.target.options[scope.autoScroll.interaction.prepared.name].autoScroll, - container = options.container || scope.getWindow(scope.autoScroll.interaction.element), - now = new Date().getTime(), - // change in time in seconds - dt = (now - scope.autoScroll.prevTime) / 1000, - // displacement - s = options.speed * dt; - - if (s >= 1) { - if (scope.isWindow(container)) { - container.scrollBy(scope.autoScroll.x * s, scope.autoScroll.y * s); - } - else if (container) { - container.scrollLeft += scope.autoScroll.x * s; - container.scrollTop += scope.autoScroll.y * s; - } - - scope.autoScroll.prevTime = now; - } - - if (scope.autoScroll.isScrolling) { - cancelFrame(scope.autoScroll.i); - scope.autoScroll.i = reqFrame(scope.autoScroll.scroll); - } - }, - - isScrolling: false, - prevTime: 0, - - start: function (interaction) { - scope.autoScroll.isScrolling = true; - cancelFrame(scope.autoScroll.i); - - scope.autoScroll.interaction = interaction; - scope.autoScroll.prevTime = new Date().getTime(); - scope.autoScroll.i = reqFrame(scope.autoScroll.scroll); - }, - - stop: function () { - scope.autoScroll.isScrolling = false; - cancelFrame(scope.autoScroll.i); - } - }; + scope.autoScroll = require('./autoScroll'); // Does the browser support touch input? scope.supportsTouch = (('ontouchstart' in scope.window) || scope.window.DocumentTouch && scope.document instanceof scope.window.DocumentTouch); From a2b79378282723d7b91fe6c676d1883a490c1744 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 19:07:48 +0100 Subject: [PATCH 010/131] .jshintrc: add node and elision --- .jshintrc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.jshintrc b/.jshintrc index 30e75c10e..b4ae95036 100644 --- a/.jshintrc +++ b/.jshintrc @@ -2,6 +2,7 @@ "strict" : true, "validthis": true, "browser" : true, + "node" : true, "jquery" : true, "curly" : true, "laxbreak" : true, @@ -10,5 +11,6 @@ "undef" : true, "unused" : true, "strict" : true, + "elision" : true, "trailing" : true } From aa0524f8fde35898be3867d85edaaca23f130883 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 19:10:20 +0100 Subject: [PATCH 011/131] Move warnOnce and hypot into utils --- src/interact.js | 43 ++++++++++++++----------------------------- src/utils/index.js | 21 +++++++++++++++++++-- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/interact.js b/src/interact.js index 7983188ef..5021e74a3 100644 --- a/src/interact.js +++ b/src/interact.js @@ -16,8 +16,6 @@ scope.pEventTypes = null; - scope.hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); }; - scope.tmpXY = {}; // reduce object creation in getXY() scope.documents = []; // all documents being listened to @@ -222,11 +220,11 @@ // set pointer velocity var dt = Math.max(targetObj.timeStamp / 1000, 0.001); - targetObj.page.speed = scope.hypot(targetObj.page.x, targetObj.page.y) / dt; + targetObj.page.speed = utils.hypot(targetObj.page.x, targetObj.page.y) / dt; targetObj.page.vx = targetObj.page.x / dt; targetObj.page.vy = targetObj.page.y / dt; - targetObj.client.speed = scope.hypot(targetObj.client.x, targetObj.page.y) / dt; + targetObj.client.speed = utils.hypot(targetObj.client.x, targetObj.page.y) / dt; targetObj.client.vx = targetObj.client.x / dt; targetObj.client.vy = targetObj.client.y / dt; }; @@ -404,7 +402,7 @@ var dx = touches[0][sourceX] - touches[1][sourceX], dy = touches[0][sourceY] - touches[1][sourceY]; - return scope.hypot(dx, dy); + return utils.hypot(dx, dy); }; scope.touchAngle = function (event, prevAngle, deltaSource) { @@ -1395,7 +1393,7 @@ dx = this.curCoords.client.x - this.startCoords.client.x; dy = this.curCoords.client.y - this.startCoords.client.y; - this.pointerWasMoved = scope.hypot(dx, dy) > scope.pointerMoveTolerance; + this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; } if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { @@ -2559,7 +2557,7 @@ var range = target.range, dx = target.x - page.x, dy = target.y - page.y, - distance = scope.hypot(dx, dy), + distance = utils.hypot(dx, dy), inRange = distance <= range; // Infinite targets count as being out of range @@ -3153,7 +3151,7 @@ dy = this[sourceY] - interaction.prevEvent[sourceY], dt = this.dt / 1000; - this.speed = scope.hypot(dx, dy) / dt; + this.speed = utils.hypot(dx, dy) / dt; this.velocityX = dx / dt; this.velocityY = dy / dt; } @@ -4908,28 +4906,15 @@ } }; - function warnOnce (method, message) { - var warned = false; - - return function () { - if (!warned) { - scope.window.console.warn(message); - warned = true; - } - - return method.apply(this, arguments); - }; - } - - Interactable.prototype.snap = warnOnce(Interactable.prototype.snap, + Interactable.prototype.snap = utils.warnOnce(Interactable.prototype.snap, 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping'); - Interactable.prototype.restrict = warnOnce(Interactable.prototype.restrict, + Interactable.prototype.restrict = utils.warnOnce(Interactable.prototype.restrict, 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction'); - Interactable.prototype.inertia = warnOnce(Interactable.prototype.inertia, + Interactable.prototype.inertia = utils.warnOnce(Interactable.prototype.inertia, 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia'); - Interactable.prototype.autoScroll = warnOnce(Interactable.prototype.autoScroll, + Interactable.prototype.autoScroll = utils.warnOnce(Interactable.prototype.autoScroll, 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll'); - Interactable.prototype.squareResize = warnOnce(Interactable.prototype.squareResize, + Interactable.prototype.squareResize = utils.warnOnce(Interactable.prototype.squareResize, 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square'); /*\ @@ -5053,7 +5038,7 @@ - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables = (boolean | object) The current setting or interact \*/ - interact.enableDragging = warnOnce(function (newValue) { + interact.enableDragging = utils.warnOnce(function (newValue) { if (newValue !== null && newValue !== undefined) { scope.actionIsEnabled.drag = newValue; @@ -5073,7 +5058,7 @@ - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables = (boolean | object) The current setting or interact \*/ - interact.enableResizing = warnOnce(function (newValue) { + interact.enableResizing = utils.warnOnce(function (newValue) { if (newValue !== null && newValue !== undefined) { scope.actionIsEnabled.resize = newValue; @@ -5093,7 +5078,7 @@ - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables = (boolean | object) The current setting or interact \*/ - interact.enableGesturing = warnOnce(function (newValue) { + interact.enableGesturing = utils.warnOnce(function (newValue) { if (newValue !== null && newValue !== undefined) { scope.actionIsEnabled.gesture = newValue; diff --git a/src/utils/index.js b/src/utils/index.js index f0bb79171..475cdfabb 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,10 +1,27 @@ 'use strict'; var utils = {}, - extend = require('./extend'); + extend = require('./extend'), + win = require('./window'); -utils.extend = extend; utils.blank = function () {}; + +utils.hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); }; + +utils.warnOnce = function (method, message) { + var warned = false; + + return function () { + if (!warned) { + win.console.warn(message); + warned = true; + } + + return method.apply(this, arguments); + }; +}; + +utils.extend = extend; utils.raf = require('./raf'); extend(utils, require('./arr')); From 7e73e48a233d5e224ac92c5568b8ce8057f411e6 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 21:03:34 +0100 Subject: [PATCH 012/131] Remove old requestAnimationFrame polyfil --- src/interact.js | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/src/interact.js b/src/interact.js index 5021e74a3..eb1b410c7 100644 --- a/src/interact.js +++ b/src/interact.js @@ -5400,34 +5400,6 @@ listenToDocument(scope.document); - // requestAnimationFrame polyfill - (function() { - var lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o']; - - for(var x = 0; x < vendors.length && !scope.realWindow.requestAnimationFrame; ++x) { - reqFrame = scope.realWindow[vendors[x]+'RequestAnimationFrame']; - cancelFrame = scope.realWindow[vendors[x]+'CancelAnimationFrame'] || scope.realWindow[vendors[x]+'CancelRequestAnimationFrame']; - } - - if (!reqFrame) { - reqFrame = function(callback) { - var currTime = new Date().getTime(), - timeToCall = Math.max(0, 16 - (currTime - lastTime)), - id = setTimeout(function() { callback(currTime + timeToCall); }, - timeToCall); - lastTime = currTime + timeToCall; - return id; - }; - } - - if (!cancelFrame) { - cancelFrame = function(id) { - clearTimeout(id); - }; - } - }()); - /* global exports: true, module, define */ // http://documentcloud.github.io/underscore/docs/underscore.html#section-11 From c67d797f90b22db915f86ca5ccd015745b0c9b63 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 21:12:32 +0100 Subject: [PATCH 013/131] Add new modules: browser, pointerUtils, raf, hypot --- src/interact.js | 242 +++++++++----------------------------- src/scope.js | 2 + src/utils/browser.js | 23 ++++ src/utils/hypot.js | 3 + src/utils/index.js | 11 +- src/utils/pointerUtils.js | 127 ++++++++++++++++++++ src/utils/raf.js | 33 ++++++ 7 files changed, 249 insertions(+), 192 deletions(-) create mode 100644 src/utils/browser.js create mode 100644 src/utils/hypot.js create mode 100644 src/utils/pointerUtils.js create mode 100644 src/utils/raf.js diff --git a/src/interact.js b/src/interact.js index eb1b410c7..1265e81b3 100644 --- a/src/interact.js +++ b/src/interact.js @@ -12,12 +12,11 @@ if (!require('./utils/window').window) { return; } var scope = require('./scope'), - utils = require('./utils'); + utils = require('./utils'), + browser = utils.browser; scope.pEventTypes = null; - scope.tmpXY = {}; // reduce object creation in getXY() - scope.documents = []; // all documents being listened to scope.interactables = []; // all set interactables @@ -39,14 +38,8 @@ // Things related to autoScroll scope.autoScroll = require('./autoScroll'); - // Does the browser support touch input? - scope.supportsTouch = (('ontouchstart' in scope.window) || scope.window.DocumentTouch && scope.document instanceof scope.window.DocumentTouch); - - // Does the browser support PointerEvents - scope.supportsPointerEvent = !!scope.PointerEvent; - // Less Precision with touch input - scope.margin = scope.supportsTouch || scope.supportsPointerEvent? 20: 10; + scope.margin = browser.supportsTouch || browser.supportsPointerEvent? 20: 10; scope.pointerMoveTolerance = 1; @@ -131,16 +124,6 @@ scope.globalEvents = {}; - // Opera Mobile must be handled differently - scope.isOperaMobile = navigator.appName == 'Opera' && - scope.supportsTouch && - navigator.userAgent.match('Presto'); - - // scrolling doesn't change the result of - // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 - scope.isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) - && /OS [1-7][^\d]/.test(navigator.appVersion)); - // prefix matchesSelector scope.prefixedMatchesSelector = 'matches' in Element.prototype? 'matches': 'webkitMatchesSelector' in Element.prototype? @@ -168,8 +151,6 @@ : o.nodeType === 1 && typeof o.nodeName === "string"); }; - utils.extend(scope, require('./utils/isType')); - scope.trySelector = function (value) { if (!scope.isString(value)) { return false; } @@ -178,122 +159,6 @@ return true; }; - scope.copyCoords = function (dest, src) { - dest.page = dest.page || {}; - dest.page.x = src.page.x; - dest.page.y = src.page.y; - - dest.client = dest.client || {}; - dest.client.x = src.client.x; - dest.client.y = src.client.y; - - dest.timeStamp = src.timeStamp; - }; - - scope.setEventXY = function (targetObj, pointer, interaction) { - if (!pointer) { - if (interaction.pointerIds.length > 1) { - pointer = scope.touchAverage(interaction.pointers); - } - else { - pointer = interaction.pointers[0]; - } - } - - scope.getPageXY(pointer, scope.tmpXY, interaction); - targetObj.page.x = scope.tmpXY.x; - targetObj.page.y = scope.tmpXY.y; - - scope.getClientXY(pointer, scope.tmpXY, interaction); - targetObj.client.x = scope.tmpXY.x; - targetObj.client.y = scope.tmpXY.y; - - targetObj.timeStamp = new Date().getTime(); - }; - - scope.setEventDeltas = function (targetObj, prev, cur) { - targetObj.page.x = cur.page.x - prev.page.x; - targetObj.page.y = cur.page.y - prev.page.y; - targetObj.client.x = cur.client.x - prev.client.x; - targetObj.client.y = cur.client.y - prev.client.y; - targetObj.timeStamp = new Date().getTime() - prev.timeStamp; - - // set pointer velocity - var dt = Math.max(targetObj.timeStamp / 1000, 0.001); - targetObj.page.speed = utils.hypot(targetObj.page.x, targetObj.page.y) / dt; - targetObj.page.vx = targetObj.page.x / dt; - targetObj.page.vy = targetObj.page.y / dt; - - targetObj.client.speed = utils.hypot(targetObj.client.x, targetObj.page.y) / dt; - targetObj.client.vx = targetObj.client.x / dt; - targetObj.client.vy = targetObj.client.y / dt; - }; - - // Get specified X/Y coords for mouse or event.touches[0] - scope.getXY = function (type, pointer, xy) { - xy = xy || {}; - type = type || 'page'; - - xy.x = pointer[type + 'X']; - xy.y = pointer[type + 'Y']; - - return xy; - }; - - scope.getPageXY = function (pointer, page, interaction) { - page = page || {}; - - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - interaction = interaction || pointer.interaction; - - utils.extend(page, interaction.inertiaStatus.upCoords.page); - - page.x += interaction.inertiaStatus.sx; - page.y += interaction.inertiaStatus.sy; - } - else { - page.x = pointer.pageX; - page.y = pointer.pageY; - } - } - // Opera Mobile handles the viewport and scrolling oddly - else if (scope.isOperaMobile) { - scope.getXY('screen', pointer, page); - - page.x += scope.window.scrollX; - page.y += scope.window.scrollY; - } - else { - scope.getXY('page', pointer, page); - } - - return page; - }; - - scope.getClientXY = function (pointer, client, interaction) { - client = client || {}; - - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - utils.extend(client, interaction.inertiaStatus.upCoords.client); - - client.x += interaction.inertiaStatus.sx; - client.y += interaction.inertiaStatus.sy; - } - else { - client.x = pointer.clientX; - client.y = pointer.clientY; - } - } - else { - // Opera Mobile handles the viewport and scrolling oddly - scope.getXY(scope.isOperaMobile? 'screen': 'client', pointer, client); - } - - return client; - }; - scope.getScrollXY = function (win) { win = win || scope.window; return { @@ -302,7 +167,7 @@ }; }; - scope.getPointerId = function (pointer) { + utils.getPointerId = function (pointer) { return scope.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; }; @@ -313,7 +178,7 @@ }; scope.getElementRect = function (element) { - var scroll = scope.isIOS7orLower + var scroll = browser.isIOS7orLower ? { x: 0, y: 0 } : scope.getScrollXY(scope.getWindow(element)), clientRect = (element instanceof scope.SVGElement)? @@ -330,7 +195,7 @@ }; }; - scope.getTouchPair = function (event) { + utils.getTouchPair = function (event) { var touches = []; // array of touches is supplied @@ -359,8 +224,8 @@ return touches; }; - scope.touchAverage = function (event) { - var touches = scope.getTouchPair(event); + utils.touchAverage = function (event) { + var touches = utils.getTouchPair(event); return { pageX: (touches[0].pageX + touches[1].pageX) / 2, @@ -370,12 +235,12 @@ }; }; - scope.touchBBox = function (event) { + utils.touchBBox = function (event) { if (!event.length && !(event.touches && event.touches.length > 1)) { return; } - var touches = scope.getTouchPair(event), + var touches = utils.getTouchPair(event), minX = Math.min(touches[0].pageX, touches[1].pageX), minY = Math.min(touches[0].pageY, touches[1].pageY), maxX = Math.max(touches[0].pageX, touches[1].pageX), @@ -391,12 +256,12 @@ }; }; - scope.touchDistance = function (event, deltaSource) { + utils.touchDistance = function (event, deltaSource) { deltaSource = deltaSource || scope.defaultOptions.deltaSource; var sourceX = deltaSource + 'X', sourceY = deltaSource + 'Y', - touches = scope.getTouchPair(event); + touches = utils.getTouchPair(event); var dx = touches[0][sourceX] - touches[1][sourceX], @@ -405,12 +270,12 @@ return utils.hypot(dx, dy); }; - scope.touchAngle = function (event, prevAngle, deltaSource) { + utils.touchAngle = function (event, prevAngle, deltaSource) { deltaSource = deltaSource || scope.defaultOptions.deltaSource; var sourceX = deltaSource + 'X', sourceY = deltaSource + 'Y', - touches = scope.getTouchPair(event), + touches = utils.getTouchPair(event), dx = touches[0][sourceX] - touches[1][sourceX], dy = touches[0][sourceY] - touches[1][sourceY], angle = 180 * Math.atan(dy / dx) / Math.PI; @@ -740,8 +605,6 @@ return index; }; - utils.extend(scope, require('./utils/arr.js')); - scope.matchesSelector = function (element, selector, nodeList) { if (scope.ie8MatchesSelector) { return scope.ie8MatchesSelector(element, selector, nodeList); @@ -937,9 +800,9 @@ } Interaction.prototype = { - getPageXY : function (pointer, xy) { return scope.getPageXY(pointer, xy, this); }, - getClientXY: function (pointer, xy) { return scope.getClientXY(pointer, xy, this); }, - setEventXY : function (target, ptr) { return scope.setEventXY(target, ptr, this); }, + getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); }, + getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); }, + setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); }, pointerOver: function (pointer, event, eventTarget) { if (this.prepared.name || !this.mouse) { return; } @@ -1153,7 +1016,7 @@ this.downTargets[pointerIndex] = eventTarget; utils.extend(this.downPointer, pointer); - scope.copyCoords(this.prevCoords, this.curCoords); + utils.copyCoords(this.prevCoords, this.curCoords); this.pointerWasMoved = false; } @@ -1386,7 +1249,7 @@ && this.curCoords.client.y === this.prevCoords.client.y); var dx, dy, - pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); + pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); // register movement greater than pointerMoveTolerance if (this.pointerIsDown && !this.pointerWasMoved) { @@ -1412,7 +1275,7 @@ } // set pointer coordinate, time changes and speeds - scope.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); if (!this.prepared.name) { return; } @@ -1422,7 +1285,7 @@ // if just starting an action, calculate the pointer speed now if (!this.interacting()) { - scope.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); // check if a drag is in the correct axis if (this.prepared.name === 'drag') { @@ -1528,7 +1391,7 @@ } } - scope.copyCoords(this.prevCoords, this.curCoords); + utils.copyCoords(this.prevCoords, this.curCoords); if (this.dragging || this.resizing) { this.autoScrollMove(pointer); @@ -1754,7 +1617,7 @@ }, pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -1767,7 +1630,7 @@ }, pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -1864,7 +1727,7 @@ } if (inertia || smoothEnd) { - scope.copyCoords(inertiaStatus.upCoords, this.curCoords); + utils.copyCoords(inertiaStatus.upCoords, this.curCoords); this.pointers[0] = inertiaStatus.startEvent = startEvent = new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); @@ -2229,7 +2092,7 @@ // remove pointers if their ID isn't in this.pointerIds for (var i = 0; i < this.pointers.length; i++) { - if (scope.indexOf(this.pointerIds, scope.getPointerId(this.pointers[i])) === -1) { + if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { this.pointers.splice(i, 1); } } @@ -2309,7 +2172,7 @@ }, addPointer: function (pointer) { - var id = scope.getPointerId(pointer), + var id = utils.getPointerId(pointer), index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); if (index === -1) { @@ -2323,7 +2186,7 @@ }, removePointer: function (pointer) { - var id = scope.getPointerId(pointer), + var id = utils.getPointerId(pointer), index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); if (index === -1) { return; } @@ -2343,7 +2206,7 @@ // The inertia start event should be this.pointers[0] if (this.inertiaStatus.active) { return; } - var index = this.mouse? 0: scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); + var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); if (index === -1) { return; } @@ -2351,7 +2214,7 @@ }, collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, scope.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); // do not fire a tap event if the pointer was moved before being lifted if (eventType === 'tap' && (this.pointerWasMoved @@ -2400,7 +2263,7 @@ }, firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(scope.getPointerId(pointer)), + var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)), pointerEvent = {}, i, // for tap events @@ -2425,8 +2288,8 @@ pointerEvent.timeStamp = new Date().getTime(); pointerEvent.originalEvent = event; pointerEvent.type = eventType; - pointerEvent.pointerId = scope.getPointerId(pointer); - pointerEvent.pointerType = this.mouse? 'mouse' : !scope.supportsPointerEvent? 'touch' + pointerEvent.pointerId = utils.getPointerId(pointer); + pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' : scope.isString(pointer.pointerType) ? pointer.pointerType : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; @@ -2808,7 +2671,7 @@ || pointer.pointerType === 4), interaction; - var id = scope.getPointerId(pointer); + var id = utils.getPointerId(pointer); // try to resume inertia with a new pointer if (/down|start/i.test(eventType)) { @@ -2837,7 +2700,7 @@ } // if it's a mouse interaction - if (mouseEvent || !(scope.supportsTouch || scope.supportsPointerEvent)) { + if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { // find a mouse interaction that's not in inertia phase for (i = 0; i < len; i++) { @@ -2900,7 +2763,7 @@ curEventTarget = scope.getActualElement(event.currentTarget), i; - if (scope.supportsTouch && /touch/.test(event.type)) { + if (browser.supportsTouch && /touch/.test(event.type)) { scope.prevTouchTime = new Date().getTime(); for (i = 0; i < event.changedTouches.length; i++) { @@ -2916,7 +2779,7 @@ } } else { - if (!scope.supportsPointerEvent && /mouse/.test(event.type)) { + if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { // ignore mouse events while touch interactions are active for (i = 0; i < scope.interactions.length; i++) { if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { @@ -3099,11 +2962,11 @@ this.touches = [pointers[0], pointers[1]]; if (starting) { - this.distance = scope.touchDistance(pointers, deltaSource); - this.box = scope.touchBBox(pointers); + this.distance = utils.touchDistance(pointers, deltaSource); + this.box = utils.touchBBox(pointers); this.scale = 1; this.ds = 0; - this.angle = scope.touchAngle(pointers, undefined, deltaSource); + this.angle = utils.touchAngle(pointers, undefined, deltaSource); this.da = 0; } else if (ending || event instanceof InteractEvent) { @@ -3115,10 +2978,10 @@ this.da = this.angle - interaction.gesture.startAngle; } else { - this.distance = scope.touchDistance(pointers, deltaSource); - this.box = scope.touchBBox(pointers); + this.distance = utils.touchDistance(pointers, deltaSource); + this.box = utils.touchBBox(pointers); this.scale = this.distance / interaction.gesture.startDistance; - this.angle = scope.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); + this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); this.ds = this.scale - interaction.gesture.prevScale; this.da = this.angle - interaction.gesture.prevAngle; @@ -3716,7 +3579,7 @@ var dropOverlap = this.options.drop.overlap; if (dropOverlap === 'pointer') { - var page = scope.getPageXY(pointer), + var page = utils.getPageXY(pointer), origin = scope.getOriginXY(draggable, draggableElement), horizontal, vertical; @@ -5151,10 +5014,10 @@ }; // expose the functions used to calculate multi-touch properties - interact.getTouchAverage = scope.touchAverage; - interact.getTouchBBox = scope.touchBBox; - interact.getTouchDistance = scope.touchDistance; - interact.getTouchAngle = scope.touchAngle; + interact.getTouchAverage = utils.touchAverage; + interact.getTouchBBox = utils.touchBBox; + interact.getTouchDistance = utils.touchDistance; + interact.getTouchAngle = utils.touchAngle; interact.getElementRect = scope.getElementRect; interact.matchesSelector = scope.matchesSelector; @@ -5187,7 +5050,7 @@ = (boolean) Whether or not the browser supports touch input \*/ interact.supportsTouch = function () { - return scope.supportsTouch; + return browser.supportsTouch; }; /*\ @@ -5197,7 +5060,7 @@ = (boolean) Whether or not the browser supports PointerEvents \*/ interact.supportsPointerEvent = function () { - return scope.supportsPointerEvent; + return browser.supportsPointerEvent; }; /*\ @@ -5400,6 +5263,11 @@ listenToDocument(scope.document); + scope.interact = interact; + scope.Interactable = Interactable; + scope.Interaction = Interaction; + scope.InteractEvent = InteractEvent; + /* global exports: true, module, define */ // http://documentcloud.github.io/underscore/docs/underscore.html#section-11 diff --git a/src/scope.js b/src/scope.js index eecabd9a7..7c6265da5 100644 --- a/src/scope.js +++ b/src/scope.js @@ -5,5 +5,7 @@ var scope = {}, extend(scope, require('./utils/window')); extend(scope, require('./utils/domObjects')); +extend(scope, require('./utils/arr.js')); +extend(scope, require('./utils/isType')); module.exports = scope; diff --git a/src/utils/browser.js b/src/utils/browser.js new file mode 100644 index 000000000..7d006117c --- /dev/null +++ b/src/utils/browser.js @@ -0,0 +1,23 @@ +'use strict'; + +var browser = {}, + win = require('./window'), + domObjects = require('./domObjects'); + +// Does the browser support touch input? +browser.supportsTouch = !!(('ontouchstart' in win) || win.window.DocumentTouch && domObjects.document instanceof win.DocumentTouch); + +// Does the browser support PointerEvents +browser.supportsPointerEvent = !!domObjects.PointerEvent; + +// Opera Mobile must be handled differently +browser.isOperaMobile = (navigator.appName == 'Opera' + && browser.supportsTouch + && navigator.userAgent.match('Presto')); + +// scrolling doesn't change the result of +// getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 +browser.isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) + && /OS [1-7][^\d]/.test(navigator.appVersion)); + +module.exports = browser; diff --git a/src/utils/hypot.js b/src/utils/hypot.js new file mode 100644 index 000000000..82f8f07e7 --- /dev/null +++ b/src/utils/hypot.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = function hypot (x, y) { return Math.sqrt(x * x + y * y); }; diff --git a/src/utils/index.js b/src/utils/index.js index 475cdfabb..a909729e7 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -6,14 +6,12 @@ var utils = {}, utils.blank = function () {}; -utils.hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); }; - utils.warnOnce = function (method, message) { var warned = false; return function () { if (!warned) { - win.console.warn(message); + win.window.console.warn(message); warned = true; } @@ -21,10 +19,13 @@ utils.warnOnce = function (method, message) { }; }; -utils.extend = extend; -utils.raf = require('./raf'); +utils.extend = extend; +utils.hypot = require('./hypot'); +utils.raf = require('./raf'); +utils.browser = require('./browser'); extend(utils, require('./arr')); extend(utils, require('./isType')); +extend(utils, require('./pointerUtils')); module.exports = utils; diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js new file mode 100644 index 000000000..63f0aebc3 --- /dev/null +++ b/src/utils/pointerUtils.js @@ -0,0 +1,127 @@ +'use strict'; + +var pointerUtils = {}, + // reduce object creation in getXY() + tmpXY = {}, + win = require('./window'), + hypot = require('./hypot'), + extend = require('./extend'), + scope = require('../scope'); + +pointerUtils.copyCoords = function (dest, src) { + dest.page = dest.page || {}; + dest.page.x = src.page.x; + dest.page.y = src.page.y; + + dest.client = dest.client || {}; + dest.client.x = src.client.x; + dest.client.y = src.client.y; + + dest.timeStamp = src.timeStamp; +}; + +pointerUtils.setEventXY = function (targetObj, pointer, interaction) { + if (!pointer) { + if (interaction.pointerIds.length > 1) { + pointer = pointerUtils.touchAverage(interaction.pointers); + } + else { + pointer = interaction.pointers[0]; + } + } + + pointerUtils.getPageXY(pointer, tmpXY, interaction); + targetObj.page.x = tmpXY.x; + targetObj.page.y = tmpXY.y; + + pointerUtils.getClientXY(pointer, tmpXY, interaction); + targetObj.client.x = tmpXY.x; + targetObj.client.y = tmpXY.y; + + targetObj.timeStamp = new Date().getTime(); +}; + +pointerUtils.setEventDeltas = function (targetObj, prev, cur) { + targetObj.page.x = cur.page.x - prev.page.x; + targetObj.page.y = cur.page.y - prev.page.y; + targetObj.client.x = cur.client.x - prev.client.x; + targetObj.client.y = cur.client.y - prev.client.y; + targetObj.timeStamp = new Date().getTime() - prev.timeStamp; + + // set pointer velocity + var dt = Math.max(targetObj.timeStamp / 1000, 0.001); + targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; + targetObj.page.vx = targetObj.page.x / dt; + targetObj.page.vy = targetObj.page.y / dt; + + targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; + targetObj.client.vx = targetObj.client.x / dt; + targetObj.client.vy = targetObj.client.y / dt; +}; + +// Get specified X/Y coords for mouse or event.touches[0] +pointerUtils.getXY = function (type, pointer, xy) { + xy = xy || {}; + type = type || 'page'; + + xy.x = pointer[type + 'X']; + xy.y = pointer[type + 'Y']; + + return xy; +}; + +pointerUtils.getPageXY = function (pointer, page, interaction) { + page = page || {}; + + if (pointer instanceof scope.InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + interaction = interaction || pointer.interaction; + + extend(page, interaction.inertiaStatus.upCoords.page); + + page.x += interaction.inertiaStatus.sx; + page.y += interaction.inertiaStatus.sy; + } + else { + page.x = pointer.pageX; + page.y = pointer.pageY; + } + } + // Opera Mobile handles the viewport and scrolling oddly + else if (scope.isOperaMobile) { + pointerUtils.getXY('screen', pointer, page); + + page.x += win.window.scrollX; + page.y += win.window.scrollY; + } + else { + pointerUtils.getXY('page', pointer, page); + } + + return page; +}; + +pointerUtils.getClientXY = function (pointer, client, interaction) { + client = client || {}; + + if (pointer instanceof scope.InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + extend(client, interaction.inertiaStatus.upCoords.client); + + client.x += interaction.inertiaStatus.sx; + client.y += interaction.inertiaStatus.sy; + } + else { + client.x = pointer.clientX; + client.y = pointer.clientY; + } + } + else { + // Opera Mobile handles the viewport and scrolling oddly + pointerUtils.getXY(scope.isOperaMobile? 'screen': 'client', pointer, client); + } + + return client; +}; + +module.exports = pointerUtils; diff --git a/src/utils/raf.js b/src/utils/raf.js new file mode 100644 index 000000000..2fea2d842 --- /dev/null +++ b/src/utils/raf.js @@ -0,0 +1,33 @@ +'use strict'; + +var lastTime = 0, + vendors = ['ms', 'moz', 'webkit', 'o'], + reqFrame, + cancelFrame; + +for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + reqFrame = window[vendors[x]+'RequestAnimationFrame']; + cancelFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; +} + +if (!reqFrame) { + reqFrame = function(callback) { + var currTime = new Date().getTime(), + timeToCall = Math.max(0, 16 - (currTime - lastTime)), + id = setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; +} + +if (!cancelFrame) { + cancelFrame = function(id) { + clearTimeout(id); + }; +} + +module.exports = { + request: reqFrame, + cancel: cancelFrame +}; From 6009217dc9338f87b2094c31b28d9e99e403cd0a Mon Sep 17 00:00:00 2001 From: stephen-james Date: Sun, 31 May 2015 21:34:23 +0100 Subject: [PATCH 014/131] add isIe9OrOlder to `browser` module, use to clarify detection of `actionCursors` --- src/interact.js | 3 +-- src/utils/browser.js | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/interact.js b/src/interact.js index 1265e81b3..9c4208628 100644 --- a/src/interact.js +++ b/src/interact.js @@ -49,8 +49,7 @@ // Allow this many interactions to happen simultaneously scope.maxInteractions = Infinity; - // Check if is IE9 or older - scope.actionCursors = (scope.document.all && !scope.window.atob) ? { + scope.actionCursors = browser.isIe9OrOlder ? { drag : 'move', resizex : 'e-resize', resizey : 's-resize', diff --git a/src/utils/browser.js b/src/utils/browser.js index 7d006117c..e3537aeb9 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -20,4 +20,6 @@ browser.isOperaMobile = (navigator.appName == 'Opera' browser.isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)); +browser.isIe9OrOlder = domObjects.document.all && !win.window.atob; + module.exports = browser; From 09a410c2724e75ab12299ce34546611c815e0ff2 Mon Sep 17 00:00:00 2001 From: stephen-james Date: Sun, 31 May 2015 21:46:26 +0100 Subject: [PATCH 015/131] reformatting browser module --- src/utils/browser.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/src/utils/browser.js b/src/utils/browser.js index e3537aeb9..945bccae6 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -1,25 +1,26 @@ 'use strict'; -var browser = {}, - win = require('./window'), +var win = require('./window'), domObjects = require('./domObjects'); -// Does the browser support touch input? -browser.supportsTouch = !!(('ontouchstart' in win) || win.window.DocumentTouch && domObjects.document instanceof win.DocumentTouch); +var browser = { + // Does the browser support touch input? + supportsTouch : !!(('ontouchstart' in win) || win.window.DocumentTouch + && domObjects.document instanceof win.DocumentTouch), -// Does the browser support PointerEvents -browser.supportsPointerEvent = !!domObjects.PointerEvent; + // Does the browser support PointerEvents + supportsPointerEvent : !!domObjects.PointerEvent, -// Opera Mobile must be handled differently -browser.isOperaMobile = (navigator.appName == 'Opera' - && browser.supportsTouch - && navigator.userAgent.match('Presto')); + // Opera Mobile must be handled differently + isOperaMobile : (navigator.appName === 'Opera' + && browser.supportsTouch + && navigator.userAgent.match('Presto')), -// scrolling doesn't change the result of -// getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 -browser.isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) - && /OS [1-7][^\d]/.test(navigator.appVersion)); + // scrolling doesn't change the result of + // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 + isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)), -browser.isIe9OrOlder = domObjects.document.all && !win.window.atob; + isIe9OrOlder : domObjects.document.all && !win.window.atob +}; module.exports = browser; From 74994732d0890ea8e47983871c500146eccf5b22 Mon Sep 17 00:00:00 2001 From: stephen-james Date: Sun, 31 May 2015 22:26:58 +0100 Subject: [PATCH 016/131] add jshint gulp task --- gulp/config.js | 4 ++++ gulp/tasks/jshint.js | 17 +++++++++++++++++ package.json | 2 ++ 3 files changed, 23 insertions(+) create mode 100644 gulp/tasks/jshint.js diff --git a/gulp/config.js b/gulp/config.js index 60fb03861..edc83d552 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -39,6 +39,10 @@ module.exports = { } ] }, + jshint: { + src: src + "/**/*.js", + settings: '.jshintrc' + }, production: { cssSrc: dest + '/*.css', jsSrc: dest + '/*.js', diff --git a/gulp/tasks/jshint.js b/gulp/tasks/jshint.js new file mode 100644 index 000000000..b28dabaf0 --- /dev/null +++ b/gulp/tasks/jshint.js @@ -0,0 +1,17 @@ +var gulp = require('gulp'); +var jshint = require('gulp-jshint'); +var stylish = require('jshint-stylish'); +var config = require('../config').jshint; + +var jshintTask = function() { + + gulp.src(config.src) + .pipe(jshint(config.settings)) + .pipe(jshint.reporter(stylish)) + .pipe(jshint.reporter('fail')); + +}; + +gulp.task('jshint', jshintTask); + +module.exports = jshintTask; diff --git a/package.json b/package.json index 5a7832840..2c9b3ad9e 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "gulp-changed": "^1.2.1", "gulp-filesize": "0.0.6", "gulp-imagemin": "^2.2.1", + "gulp-jshint": "^1.11.0", "gulp-minify-css": "^1.1.1", "gulp-notify": "^2.2.0", "gulp-rename": "^1.2.2", @@ -58,6 +59,7 @@ "gulp-sourcemaps": "^1.5.2", "gulp-uglify": "^1.2.0", "gulp-util": "^3.0.4", + "jshint-stylish": "^2.0.0", "karma": "^0.12.32", "karma-browserify": "^4.2.1", "karma-chai": "^0.1.0", From b9d380fee838deedf3e97c56a0bbd3a523817d4e Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 21:21:06 +0100 Subject: [PATCH 017/131] Move isElement into isTypes module --- src/interact.js | 56 +++++++++++++++++++-------------------------- src/utils/isType.js | 13 ++++++++++- 2 files changed, 35 insertions(+), 34 deletions(-) diff --git a/src/interact.js b/src/interact.js index 9c4208628..a5d9e4b47 100644 --- a/src/interact.js +++ b/src/interact.js @@ -140,16 +140,6 @@ // Events wrapper events = require('./utils/events'); - scope.isElement = function (o) { - if (!o || (typeof o !== 'object')) { return false; } - - var _window = scope.getWindow(o) || scope.window; - - return (/object|function/.test(typeof _window.Element) - ? o instanceof _window.Element //DOM2 - : o.nodeType === 1 && typeof o.nodeName === "string"); - }; - scope.trySelector = function (value) { if (!scope.isString(value)) { return false; } @@ -319,7 +309,7 @@ origin = origin(interactable && element); } - if (scope.isElement(origin)) { + if (utils.isElement(origin)) { origin = scope.getElementRect(origin); } @@ -363,7 +353,7 @@ scope.closest = function (child, selector) { var parent = scope.parentElement(child); - while (scope.isElement(parent)) { + while (utils.isElement(parent)) { if (scope.matchesSelector(parent, selector)) { return parent; } parent = scope.parentElement(parent); @@ -393,12 +383,12 @@ scope.testIgnore = function (interactable, interactableElement, element) { var ignoreFrom = interactable.options.ignoreFrom; - if (!ignoreFrom || !scope.isElement(element)) { return false; } + if (!ignoreFrom || !utils.isElement(element)) { return false; } if (scope.isString(ignoreFrom)) { return scope.matchesUpTo(element, ignoreFrom, interactableElement); } - else if (scope.isElement(ignoreFrom)) { + else if (utils.isElement(ignoreFrom)) { return scope.nodeContains(ignoreFrom, element); } @@ -410,12 +400,12 @@ if (!allowFrom) { return true; } - if (!scope.isElement(element)) { return false; } + if (!utils.isElement(element)) { return false; } if (scope.isString(allowFrom)) { return scope.matchesUpTo(element, allowFrom, interactableElement); } - else if (scope.isElement(allowFrom)) { + else if (utils.isElement(allowFrom)) { return scope.nodeContains(allowFrom, element); } @@ -618,7 +608,7 @@ }; scope.matchesUpTo = function (element, selector, limit) { - while (scope.isElement(element)) { + while (utils.isElement(element)) { if (scope.matchesSelector(element, selector)) { return true; } @@ -947,7 +937,7 @@ // Check if the down event hits the current inertia target if (this.inertiaStatus.active && this.target.selector) { // climb up the DOM tree from the event target - while (scope.isElement(element)) { + while (utils.isElement(element)) { // if this element is the current inertia target element if (element === this.element @@ -990,7 +980,7 @@ this.setEventXY(this.curCoords, pointer); this.downEvent = event; - while (scope.isElement(element) && !action) { + while (utils.isElement(element) && !action) { this.matches = []; this.matchElements = []; @@ -1303,7 +1293,7 @@ var element = eventTarget; // check element interactables - while (scope.isElement(element)) { + while (utils.isElement(element)) { var elementInteractable = scope.interactables.get(element); if (elementInteractable @@ -1348,7 +1338,7 @@ element = eventTarget; - while (scope.isElement(element)) { + while (utils.isElement(element)) { var selectorInteractable = scope.interactables.forEachSelector(getDraggable); if (selectorInteractable) { @@ -1852,7 +1842,7 @@ accept = current.options.drop.accept; // test the draggable element against the dropzone's accept setting - if ((scope.isElement(accept) && accept !== element) + if ((utils.isElement(accept) && accept !== element) || (scope.isString(accept) && !scope.matchesSelector(element, accept))) { @@ -2232,7 +2222,7 @@ : undefined; if (interactable._iEvents[eventType] - && scope.isElement(element) + && utils.isElement(element) && scope.inContext(interactable, element) && !scope.testIgnore(interactable, element, eventTarget) && scope.testAllow(interactable, element, eventTarget) @@ -2527,7 +2517,7 @@ restriction = restriction(page.x, page.y, this.element); } - if (scope.isElement(restriction)) { + if (utils.isElement(restriction)) { restriction = scope.getElementRect(restriction); } @@ -3125,9 +3115,9 @@ } // the remaining checks require an element - if (!scope.isElement(element)) { return false; } + if (!utils.isElement(element)) { return false; } - return scope.isElement(value) + return utils.isElement(value) // the value is an element to use as a resize handle ? value === element // otherwise check if element matches value as selector @@ -3258,7 +3248,7 @@ fakeEvent.preventDefault = preventOriginalDefault; // climb up document tree looking for selector matches - while (scope.isElement(element)) { + while (utils.isElement(element)) { for (var i = 0; i < delegated.selectors.length; i++) { var selector = delegated.selectors[i], context = delegated.contexts[i]; @@ -3373,7 +3363,7 @@ if (context && (_window.Node ? context instanceof _window.Node - : (scope.isElement(context) || context === _window.document))) { + : (utils.isElement(context) || context === _window.document))) { this._context = context; } @@ -3381,7 +3371,7 @@ else { _window = scope.getWindow(element); - if (scope.isElement(element, _window)) { + if (utils.isElement(element, _window)) { if (scope.PointerEvent) { events.add(this._element, scope.pEventTypes.down, listeners.pointerDown ); @@ -3682,7 +3672,7 @@ = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable \*/ accept: function (newValue) { - if (scope.isElement(newValue)) { + if (utils.isElement(newValue)) { this.options.drop.accept = newValue; return this; @@ -4131,7 +4121,7 @@ getRect: function rectCheck (element) { element = element || this._element; - if (this.selector && !(scope.isElement(element))) { + if (this.selector && !(utils.isElement(element))) { element = this._context.querySelector(this.selector); } @@ -4361,7 +4351,7 @@ return this; } - if (scope.isElement(newValue)) { // specific element + if (utils.isElement(newValue)) { // specific element this.options.ignoreFrom = newValue; return this; } @@ -4390,7 +4380,7 @@ return this; } - if (scope.isElement(newValue)) { // specific element + if (utils.isElement(newValue)) { // specific element this.options.allowFrom = newValue; return this; } diff --git a/src/utils/isType.js b/src/utils/isType.js index da3c13faf..cce37821f 100644 --- a/src/utils/isType.js +++ b/src/utils/isType.js @@ -1,6 +1,17 @@ 'use strict'; -var domObjects = require('./domObjects'); +var win = require('./window'), + domObjects = require('./domObjects'); + +module.exports.isElement = function (o) { + if (!o || (typeof o !== 'object')) { return false; } + + var _window = win.getWindow(o) || win.window; + + return (/object|function/.test(typeof _window.Element) + ? o instanceof _window.Element //DOM2 + : o.nodeType === 1 && typeof o.nodeName === "string"); +}; module.exports.isWindow = function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }; module.exports.isDocFrag = function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }; From f6825387da0bb71224e03b33776a7ab92fbc05f1 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 31 May 2015 22:09:03 +0100 Subject: [PATCH 018/131] Define getPointerId in pointerUtils --- src/interact.js | 4 ---- src/utils/pointerUtils.js | 6 ++++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/interact.js b/src/interact.js index a5d9e4b47..8513924ce 100644 --- a/src/interact.js +++ b/src/interact.js @@ -156,10 +156,6 @@ }; }; - utils.getPointerId = function (pointer) { - return scope.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; - }; - scope.getActualElement = function (element) { return (element instanceof scope.SVGElementInstance ? element.correspondingUseElement diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 63f0aebc3..bc6d9a3c2 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -6,6 +6,8 @@ var pointerUtils = {}, win = require('./window'), hypot = require('./hypot'), extend = require('./extend'), + + // scope shouldn't be necessary in this module scope = require('../scope'); pointerUtils.copyCoords = function (dest, src) { @@ -124,4 +126,8 @@ pointerUtils.getClientXY = function (pointer, client, interaction) { return client; }; +pointerUtils.getPointerId = function (pointer) { + return scope.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; +}; + module.exports = pointerUtils; From 76835f7f3a86c53856ef57bb53a5745a62af8bbc Mon Sep 17 00:00:00 2001 From: stephen-james Date: Sun, 31 May 2015 22:39:08 +0100 Subject: [PATCH 019/131] removes IIFE, Browserify now wraps this and protects scope --- src/interact.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/interact.js b/src/interact.js index 8513924ce..c5ca942dc 100644 --- a/src/interact.js +++ b/src/interact.js @@ -5,7 +5,7 @@ * Open source under the MIT License. * https://raw.github.com/taye/interact.js/master/LICENSE */ -(function () { + 'use strict'; // return early if there's no window to work with (eg. Node.js) @@ -5270,6 +5270,4 @@ } else { scope.realWindow.interact = interact; - } - -} ()); + } \ No newline at end of file From 673674ee6040f540442a6489a76d40a43631cb5d Mon Sep 17 00:00:00 2001 From: stephen-james Date: Sun, 31 May 2015 23:22:05 +0100 Subject: [PATCH 020/131] point references to `interact.js` in demo htdocs to `build` folder --- demo/dropzones.html | 2 +- demo/events.html | 2 +- demo/gallery.html | 2 +- demo/html_svg.html | 2 +- demo/iframes-middle.html | 2 +- demo/snap.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/demo/dropzones.html b/demo/dropzones.html index 6633de794..f7e1594d7 100644 --- a/demo/dropzones.html +++ b/demo/dropzones.html @@ -3,7 +3,7 @@ Highlight dropzones with interact.js - + diff --git a/demo/events.html b/demo/events.html index 1d58f9adb..d94ed625c 100644 --- a/demo/events.html +++ b/demo/events.html @@ -9,7 +9,7 @@ - + diff --git a/demo/gallery.html b/demo/gallery.html index 1a176762d..a37e2bd81 100644 --- a/demo/gallery.html +++ b/demo/gallery.html @@ -88,7 +88,7 @@ } - + diff --git a/demo/html_svg.html b/demo/html_svg.html index 48661b60a..118cb004f 100644 --- a/demo/html_svg.html +++ b/demo/html_svg.html @@ -5,7 +5,7 @@ interact.js demo - + diff --git a/demo/iframes-middle.html b/demo/iframes-middle.html index b58f0f6e1..96e84ea6a 100644 --- a/demo/iframes-middle.html +++ b/demo/iframes-middle.html @@ -1,7 +1,7 @@ - + diff --git a/demo/snap.html b/demo/snap.html index 232322335..f0da5b464 100644 --- a/demo/snap.html +++ b/demo/snap.html @@ -5,7 +5,7 @@ interact.js drag snapping - + From f167c227d59afa5cc397530c3d03e5b2ee315c64 Mon Sep 17 00:00:00 2001 From: stephen-james Date: Sun, 31 May 2015 23:22:05 +0100 Subject: [PATCH 021/131] point references to `interact.js` in demo htdocs to `build` folder --- demo/dropzones.html | 2 +- demo/events.html | 2 +- demo/gallery.html | 2 +- demo/html_svg.html | 2 +- demo/iframes-middle.html | 2 +- demo/snap.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/demo/dropzones.html b/demo/dropzones.html index 6633de794..f7e1594d7 100644 --- a/demo/dropzones.html +++ b/demo/dropzones.html @@ -3,7 +3,7 @@ Highlight dropzones with interact.js - + diff --git a/demo/events.html b/demo/events.html index 1d58f9adb..d94ed625c 100644 --- a/demo/events.html +++ b/demo/events.html @@ -9,7 +9,7 @@ - + diff --git a/demo/gallery.html b/demo/gallery.html index 1a176762d..a37e2bd81 100644 --- a/demo/gallery.html +++ b/demo/gallery.html @@ -88,7 +88,7 @@ } - + diff --git a/demo/html_svg.html b/demo/html_svg.html index 48661b60a..118cb004f 100644 --- a/demo/html_svg.html +++ b/demo/html_svg.html @@ -5,7 +5,7 @@ interact.js demo - + diff --git a/demo/iframes-middle.html b/demo/iframes-middle.html index b58f0f6e1..96e84ea6a 100644 --- a/demo/iframes-middle.html +++ b/demo/iframes-middle.html @@ -1,7 +1,7 @@ - + diff --git a/demo/snap.html b/demo/snap.html index 232322335..f0da5b464 100644 --- a/demo/snap.html +++ b/demo/snap.html @@ -5,7 +5,7 @@ interact.js drag snapping - + From 8a6f8f5124208f33e82cd365c309605e748f31fa Mon Sep 17 00:00:00 2001 From: stephen-james Date: Mon, 1 Jun 2015 00:26:53 +0100 Subject: [PATCH 022/131] extract `InteractEvent` and `Interaction` to modules --- build/interact.js | 9478 +++++++++++++++++++++--------------------- src/InteractEvent.js | 269 ++ src/Interaction.js | 2080 +++++++++ src/interact.js | 2713 +----------- 4 files changed, 7364 insertions(+), 7176 deletions(-) create mode 100644 src/InteractEvent.js create mode 100644 src/Interaction.js diff --git a/build/interact.js b/build/interact.js index 8ae176cd7..31817f3bb 100644 --- a/build/interact.js +++ b/build/interact.js @@ -1,6 +1,4 @@ (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 1) { - if (isWindow(container)) { - container.scrollBy(autoScroll.x * s, autoScroll.y * s); - } - else if (container) { - container.scrollLeft += autoScroll.x * s; - container.scrollTop += autoScroll.y * s; - } + // return early if there's no window to work with (eg. Node.js) + if (!require('./utils/window').window) { return; } - autoScroll.prevTime = now; - } + var scope = require('./scope'), + utils = require('./utils'), + browser = utils.browser; - if (autoScroll.isScrolling) { - cancelFrame(autoScroll.i); - autoScroll.i = reqFrame(autoScroll.scroll); - } - }, + scope.pEventTypes = null; - isScrolling: false, - prevTime: 0, + scope.documents = []; // all documents being listened to - start: function (interaction) { - autoScroll.isScrolling = true; - cancelFrame(autoScroll.i); + scope.interactables = []; // all set interactables + scope.interactions = []; // all interactions - autoScroll.interaction = interaction; - autoScroll.prevTime = new Date().getTime(); - autoScroll.i = reqFrame(autoScroll.scroll); - }, + scope.dynamicDrop = false; - stop: function () { - autoScroll.isScrolling = false; - cancelFrame(autoScroll.i); - } - }, + // { + // type: { + // selectors: ['selector', ...], + // contexts : [document, ...], + // listeners: [[listener, useCapture], ...] + // } + // } + scope.delegatedEvents = {}; -// Does the browser support touch input? - supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch), + scope.defaultOptions = require('./defaultOptions'); -// Does the browser support PointerEvents - supportsPointerEvent = !!PointerEvent, + // Things related to autoScroll + scope.autoScroll = require('./autoScroll'); -// Less Precision with touch input - margin = supportsTouch || supportsPointerEvent? 20: 10, + // Less Precision with touch input + scope.margin = browser.supportsTouch || browser.supportsPointerEvent? 20: 10; - pointerMoveTolerance = 1, + scope.pointerMoveTolerance = 1; -// for ignoring browser's simulated mouse events - prevTouchTime = 0, + // for ignoring browser's simulated mouse events + scope.prevTouchTime = 0; -// Allow this many interactions to happen simultaneously - maxInteractions = Infinity, + // Allow this many interactions to happen simultaneously + scope.maxInteractions = Infinity; -// Check if is IE9 or older - actionCursors = (document.all && !window.atob) ? { + scope.actionCursors = browser.isIe9OrOlder ? { drag : 'move', resizex : 'e-resize', resizey : 's-resize', @@ -277,18 +82,18 @@ var // get wrapped window if using Shadow DOM polyfill resizebottomleft : 'nesw-resize', gesture : '' - }, + }; - actionIsEnabled = { + scope.actionIsEnabled = { drag : true, resize : true, gesture: true - }, + }; -// because Webkit and Opera still use 'mousewheel' event type - wheelEvent = 'onmousewheel' in document? 'mousewheel': 'wheel', + // because Webkit and Opera still use 'mousewheel' event type + scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel'; - eventTypes = [ + scope.eventTypes = [ 'dragstart', 'dragmove', 'draginertiastart', @@ -315,5550 +120,5909 @@ var // get wrapped window if using Shadow DOM polyfill 'tap', 'doubletap', 'hold' - ], - - globalEvents = {}, - -// Opera Mobile must be handled differently - isOperaMobile = navigator.appName == 'Opera' && - supportsTouch && - navigator.userAgent.match('Presto'), - -// scrolling doesn't change the result of -// getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 - isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform) - && /OS [1-7][^\d]/.test(navigator.appVersion)), - -// prefix matchesSelector - prefixedMatchesSelector = 'matches' in Element.prototype? - 'matches': 'webkitMatchesSelector' in Element.prototype? - 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? - 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? - 'oMatchesSelector': 'msMatchesSelector', - -// will be polyfill function if browser is IE8 - ie8MatchesSelector, - -// native requestAnimationFrame or polyfill - reqFrame = realWindow.requestAnimationFrame, - cancelFrame = realWindow.cancelAnimationFrame, - -// Events wrapper - events = (function () { - var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window), - addEvent = useAttachEvent? 'attachEvent': 'addEventListener', - removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', - on = useAttachEvent? 'on': '', - - elements = [], - targets = [], - attachedListeners = []; - - function add (element, type, listener, useCapture) { - var elementIndex = indexOf(elements, element), - target = targets[elementIndex]; - - if (!target) { - target = { - events: {}, - typeCount: 0 - }; + ]; - elementIndex = elements.push(element) - 1; - targets.push(target); + scope.globalEvents = {}; - attachedListeners.push((useAttachEvent ? { - supplied: [], - wrapped : [], - useCount: [] - } : null)); - } + // prefix matchesSelector + scope.prefixedMatchesSelector = 'matches' in Element.prototype? + 'matches': 'webkitMatchesSelector' in Element.prototype? + 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? + 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? + 'oMatchesSelector': 'msMatchesSelector'; - if (!target.events[type]) { - target.events[type] = []; - target.typeCount++; - } + // will be polyfill function if browser is IE8 + scope.ie8MatchesSelector = null; - if (!contains(target.events[type], listener)) { - var ret; + // Events wrapper + var events = require('./utils/events'); - if (useAttachEvent) { - var listeners = attachedListeners[elementIndex], - listenerIndex = indexOf(listeners.supplied, listener); + scope.listeners = {}; + + var interactionListeners = [ + 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove', + 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', + 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', + 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' + ]; - var wrapped = listeners.wrapped[listenerIndex] || function (event) { - if (!event.immediatePropagationStopped) { - event.target = event.srcElement; - event.currentTarget = element; + scope.trySelector = function (value) { + if (!scope.isString(value)) { return false; } - event.preventDefault = event.preventDefault || preventDef; - event.stopPropagation = event.stopPropagation || stopProp; - event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; + // an exception will be raised if it is invalid + scope.document.querySelector(value); + return true; + }; - if (/mouse|click/.test(event.type)) { - event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; - event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; - } + scope.getScrollXY = function (win) { + win = win || scope.window; + return { + x: win.scrollX || win.document.documentElement.scrollLeft, + y: win.scrollY || win.document.documentElement.scrollTop + }; + }; - listener(event); - } - }; + scope.getActualElement = function (element) { + return (element instanceof scope.SVGElementInstance + ? element.correspondingUseElement + : element); + }; - ret = element[addEvent](on + type, wrapped, Boolean(useCapture)); + scope.getElementRect = function (element) { + var scroll = browser.isIOS7orLower + ? { x: 0, y: 0 } + : scope.getScrollXY(scope.getWindow(element)), + clientRect = (element instanceof scope.SVGElement)? + element.getBoundingClientRect(): + element.getClientRects()[0]; - if (listenerIndex === -1) { - listeners.supplied.push(listener); - listeners.wrapped.push(wrapped); - listeners.useCount.push(1); - } - else { - listeners.useCount[listenerIndex]++; - } + return clientRect && { + left : clientRect.left + scroll.x, + right : clientRect.right + scroll.x, + top : clientRect.top + scroll.y, + bottom: clientRect.bottom + scroll.y, + width : clientRect.width || clientRect.right - clientRect.left, + height: clientRect.heigh || clientRect.bottom - clientRect.top + }; + }; + + utils.getTouchPair = function (event) { + var touches = []; + + // array of touches is supplied + if (scope.isArray(event)) { + touches[0] = event[0]; + touches[1] = event[1]; + } + // an event + else { + if (event.type === 'touchend') { + if (event.touches.length === 1) { + touches[0] = event.touches[0]; + touches[1] = event.changedTouches[0]; } - else { - ret = element[addEvent](type, listener, useCapture || false); + else if (event.touches.length === 0) { + touches[0] = event.changedTouches[0]; + touches[1] = event.changedTouches[1]; } - target.events[type].push(listener); - - return ret; + } + else { + touches[0] = event.touches[0]; + touches[1] = event.touches[1]; } } - function remove (element, type, listener, useCapture) { - var i, - elementIndex = indexOf(elements, element), - target = targets[elementIndex], - listeners, - listenerIndex, - wrapped = listener; + return touches; + }; - if (!target || !target.events) { - return; - } + utils.touchAverage = function (event) { + var touches = utils.getTouchPair(event); - if (useAttachEvent) { - listeners = attachedListeners[elementIndex]; - listenerIndex = indexOf(listeners.supplied, listener); - wrapped = listeners.wrapped[listenerIndex]; - } + return { + pageX: (touches[0].pageX + touches[1].pageX) / 2, + pageY: (touches[0].pageY + touches[1].pageY) / 2, + clientX: (touches[0].clientX + touches[1].clientX) / 2, + clientY: (touches[0].clientY + touches[1].clientY) / 2 + }; + }; - if (type === 'all') { - for (type in target.events) { - if (target.events.hasOwnProperty(type)) { - remove(element, type, 'all'); - } - } - return; - } + utils.touchBBox = function (event) { + if (!event.length && !(event.touches && event.touches.length > 1)) { + return; + } - if (target.events[type]) { - var len = target.events[type].length; + var touches = utils.getTouchPair(event), + minX = Math.min(touches[0].pageX, touches[1].pageX), + minY = Math.min(touches[0].pageY, touches[1].pageY), + maxX = Math.max(touches[0].pageX, touches[1].pageX), + maxY = Math.max(touches[0].pageY, touches[1].pageY); - if (listener === 'all') { - for (i = 0; i < len; i++) { - remove(element, type, target.events[type][i], Boolean(useCapture)); - } - return; - } else { - for (i = 0; i < len; i++) { - if (target.events[type][i] === listener) { - element[removeEvent](on + type, wrapped, useCapture || false); - target.events[type].splice(i, 1); - - if (useAttachEvent && listeners) { - listeners.useCount[listenerIndex]--; - if (listeners.useCount[listenerIndex] === 0) { - listeners.supplied.splice(listenerIndex, 1); - listeners.wrapped.splice(listenerIndex, 1); - listeners.useCount.splice(listenerIndex, 1); - } - } + return { + x: minX, + y: minY, + left: minX, + top: minY, + width: maxX - minX, + height: maxY - minY + }; + }; - break; - } - } - } + utils.touchDistance = function (event, deltaSource) { + deltaSource = deltaSource || scope.defaultOptions.deltaSource; - if (target.events[type] && target.events[type].length === 0) { - target.events[type] = null; - target.typeCount--; - } - } + var sourceX = deltaSource + 'X', + sourceY = deltaSource + 'Y', + touches = utils.getTouchPair(event); - if (!target.typeCount) { - targets.splice(elementIndex, 1); - elements.splice(elementIndex, 1); - attachedListeners.splice(elementIndex, 1); + + var dx = touches[0][sourceX] - touches[1][sourceX], + dy = touches[0][sourceY] - touches[1][sourceY]; + + return utils.hypot(dx, dy); + }; + + utils.touchAngle = function (event, prevAngle, deltaSource) { + deltaSource = deltaSource || scope.defaultOptions.deltaSource; + + var sourceX = deltaSource + 'X', + sourceY = deltaSource + 'Y', + touches = utils.getTouchPair(event), + dx = touches[0][sourceX] - touches[1][sourceX], + dy = touches[0][sourceY] - touches[1][sourceY], + angle = 180 * Math.atan(dy / dx) / Math.PI; + + if (scope.isNumber(prevAngle)) { + var dr = angle - prevAngle, + drClamped = dr % 360; + + if (drClamped > 315) { + angle -= 360 + (angle / 360)|0 * 360; + } + else if (drClamped > 135) { + angle -= 180 + (angle / 360)|0 * 360; + } + else if (drClamped < -315) { + angle += 360 + (angle / 360)|0 * 360; + } + else if (drClamped < -135) { + angle += 180 + (angle / 360)|0 * 360; } } - function preventDef () { - this.returnValue = false; + return angle; + }; + + scope.getOriginXY = function (interactable, element) { + var origin = interactable + ? interactable.options.origin + : scope.defaultOptions.origin; + + if (origin === 'parent') { + origin = scope.parentElement(element); + } + else if (origin === 'self') { + origin = interactable.getRect(element); + } + else if (scope.trySelector(origin)) { + origin = scope.closest(element, origin) || { x: 0, y: 0 }; } - function stopProp () { - this.cancelBubble = true; + if (scope.isFunction(origin)) { + origin = origin(interactable && element); } - function stopImmProp () { - this.cancelBubble = true; - this.immediatePropagationStopped = true; + if (utils.isElement(origin)) { + origin = scope.getElementRect(origin); } - return { - add: add, - remove: remove, - useAttachEvent: useAttachEvent, + origin.x = ('x' in origin)? origin.x : origin.left; + origin.y = ('y' in origin)? origin.y : origin.top; - _elements: elements, - _targets: targets, - _attachedListeners: attachedListeners - }; - }()); + return origin; + }; -function blank () {} + // http://stackoverflow.com/a/5634528/2280888 + scope._getQBezierValue = function (t, p1, p2, p3) { + var iT = 1 - t; + return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; + }; -function isElement (o) { - if (!o || (typeof o !== 'object')) { return false; } + scope.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) { + return { + x: scope._getQBezierValue(position, startX, cpX, endX), + y: scope._getQBezierValue(position, startY, cpY, endY) + }; + }; - var _window = getWindow(o) || window; + // http://gizma.com/easing/ + scope.easeOutQuad = function (t, b, c, d) { + t /= d; + return -c * t*(t-2) + b; + }; - return (/object|function/.test(typeof _window.Element) - ? o instanceof _window.Element //DOM2 - : o.nodeType === 1 && typeof o.nodeName === "string"); -} -function isWindow (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); } -function isDocFrag (thing) { return !!thing && thing instanceof DocumentFragment; } -function isArray (thing) { - return isObject(thing) - && (typeof thing.length !== undefined) - && isFunction(thing.splice); -} -function isObject (thing) { return !!thing && (typeof thing === 'object'); } -function isFunction (thing) { return typeof thing === 'function'; } -function isNumber (thing) { return typeof thing === 'number' ; } -function isBool (thing) { return typeof thing === 'boolean' ; } -function isString (thing) { return typeof thing === 'string' ; } - -function trySelector (value) { - if (!isString(value)) { return false; } - - // an exception will be raised if it is invalid - document.querySelector(value); - return true; -} + scope.nodeContains = function (parent, child) { + while (child) { + if (child === parent) { + return true; + } -function extend (dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - return dest; -} + child = child.parentNode; + } -function copyCoords (dest, src) { - dest.page = dest.page || {}; - dest.page.x = src.page.x; - dest.page.y = src.page.y; + return false; + }; - dest.client = dest.client || {}; - dest.client.x = src.client.x; - dest.client.y = src.client.y; + scope.closest = function (child, selector) { + var parent = scope.parentElement(child); - dest.timeStamp = src.timeStamp; -} + while (utils.isElement(parent)) { + if (scope.matchesSelector(parent, selector)) { return parent; } -function setEventXY (targetObj, pointer, interaction) { - if (!pointer) { - if (interaction.pointerIds.length > 1) { - pointer = touchAverage(interaction.pointers); - } - else { - pointer = interaction.pointers[0]; + parent = scope.parentElement(parent); } - } - getPageXY(pointer, tmpXY, interaction); - targetObj.page.x = tmpXY.x; - targetObj.page.y = tmpXY.y; + return null; + }; - getClientXY(pointer, tmpXY, interaction); - targetObj.client.x = tmpXY.x; - targetObj.client.y = tmpXY.y; + scope.parentElement = function (node) { + var parent = node.parentNode; - targetObj.timeStamp = new Date().getTime(); -} + if (scope.isDocFrag(parent)) { + // skip past #shado-root fragments + while ((parent = parent.host) && scope.isDocFrag(parent)) {} -function setEventDeltas (targetObj, prev, cur) { - targetObj.page.x = cur.page.x - prev.page.x; - targetObj.page.y = cur.page.y - prev.page.y; - targetObj.client.x = cur.client.x - prev.client.x; - targetObj.client.y = cur.client.y - prev.client.y; - targetObj.timeStamp = new Date().getTime() - prev.timeStamp; + return parent; + } - // set pointer velocity - var dt = Math.max(targetObj.timeStamp / 1000, 0.001); - targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; - targetObj.page.vx = targetObj.page.x / dt; - targetObj.page.vy = targetObj.page.y / dt; + return parent; + }; - targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; - targetObj.client.vx = targetObj.client.x / dt; - targetObj.client.vy = targetObj.client.y / dt; -} + scope.inContext = function (interactable, element) { + return interactable._context === element.ownerDocument + || scope.nodeContains(interactable._context, element); + }; -// Get specified X/Y coords for mouse or event.touches[0] -function getXY (type, pointer, xy) { - xy = xy || {}; - type = type || 'page'; + scope.testIgnore = function (interactable, interactableElement, element) { + var ignoreFrom = interactable.options.ignoreFrom; - xy.x = pointer[type + 'X']; - xy.y = pointer[type + 'Y']; + if (!ignoreFrom || !utils.isElement(element)) { return false; } - return xy; -} + if (scope.isString(ignoreFrom)) { + return scope.matchesUpTo(element, ignoreFrom, interactableElement); + } + else if (utils.isElement(ignoreFrom)) { + return scope.nodeContains(ignoreFrom, element); + } -function getPageXY (pointer, page, interaction) { - page = page || {}; + return false; + }; - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - interaction = interaction || pointer.interaction; + scope.testAllow = function (interactable, interactableElement, element) { + var allowFrom = interactable.options.allowFrom; - extend(page, interaction.inertiaStatus.upCoords.page); + if (!allowFrom) { return true; } - page.x += interaction.inertiaStatus.sx; - page.y += interaction.inertiaStatus.sy; + if (!utils.isElement(element)) { return false; } + + if (scope.isString(allowFrom)) { + return scope.matchesUpTo(element, allowFrom, interactableElement); } - else { - page.x = pointer.pageX; - page.y = pointer.pageY; + else if (utils.isElement(allowFrom)) { + return scope.nodeContains(allowFrom, element); } - } - // Opera Mobile handles the viewport and scrolling oddly - else if (isOperaMobile) { - getXY('screen', pointer, page); - page.x += window.scrollX; - page.y += window.scrollY; - } - else { - getXY('page', pointer, page); - } + return false; + }; - return page; -} + scope.checkAxis = function (axis, interactable) { + if (!interactable) { return false; } -function getClientXY (pointer, client, interaction) { - client = client || {}; + var thisAxis = interactable.options.drag.axis; - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - extend(client, interaction.inertiaStatus.upCoords.client); + return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); + }; - client.x += interaction.inertiaStatus.sx; - client.y += interaction.inertiaStatus.sy; - } - else { - client.x = pointer.clientX; - client.y = pointer.clientY; - } - } - else { - // Opera Mobile handles the viewport and scrolling oddly - getXY(isOperaMobile? 'screen': 'client', pointer, client); - } + scope.checkSnap = function (interactable, action) { + var options = interactable.options; - return client; -} + if (/^resize/.test(action)) { + action = 'resize'; + } -function getScrollXY (win) { - win = win || window; - return { - x: win.scrollX || win.document.documentElement.scrollLeft, - y: win.scrollY || win.document.documentElement.scrollTop + return options[action].snap && options[action].snap.enabled; }; -} -function getPointerId (pointer) { - return isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; -} + scope.checkRestrict = function (interactable, action) { + var options = interactable.options; -function getActualElement (element) { - return (element instanceof SVGElementInstance - ? element.correspondingUseElement - : element); -} + if (/^resize/.test(action)) { + action = 'resize'; + } -function getWindow (node) { - if (isWindow(node)) { - return node; - } + return options[action].restrict && options[action].restrict.enabled; + }; - var rootNode = (node.ownerDocument || node); + scope.checkAutoScroll = function (interactable, action) { + var options = interactable.options; - return rootNode.defaultView || rootNode.parentWindow || window; -} + if (/^resize/.test(action)) { + action = 'resize'; + } -function getElementRect (element) { - var scroll = isIOS7orLower - ? { x: 0, y: 0 } - : getScrollXY(getWindow(element)), - clientRect = (element instanceof SVGElement)? - element.getBoundingClientRect(): - element.getClientRects()[0]; + return options[action].autoScroll && options[action].autoScroll.enabled; + }; - return clientRect && { - left : clientRect.left + scroll.x, - right : clientRect.right + scroll.x, - top : clientRect.top + scroll.y, - bottom: clientRect.bottom + scroll.y, - width : clientRect.width || clientRect.right - clientRect.left, - height: clientRect.heigh || clientRect.bottom - clientRect.top - }; -} + scope.withinInteractionLimit = function (interactable, element, action) { + var options = interactable.options, + maxActions = options[action.name].max, + maxPerElement = options[action.name].maxPerElement, + activeInteractions = 0, + targetCount = 0, + targetElementCount = 0; -function getTouchPair (event) { - var touches = []; + for (var i = 0, len = scope.interactions.length; i < len; i++) { + var interaction = scope.interactions[i], + otherAction = interaction.prepared.name, + active = interaction.interacting(); - // array of touches is supplied - if (isArray(event)) { - touches[0] = event[0]; - touches[1] = event[1]; - } - // an event - else { - if (event.type === 'touchend') { - if (event.touches.length === 1) { - touches[0] = event.touches[0]; - touches[1] = event.changedTouches[0]; + if (!active) { continue; } + + activeInteractions++; + + if (activeInteractions >= scope.maxInteractions) { + return false; } - else if (event.touches.length === 0) { - touches[0] = event.changedTouches[0]; - touches[1] = event.changedTouches[1]; + + if (interaction.target !== interactable) { continue; } + + targetCount += (otherAction === action.name)|0; + + if (targetCount >= maxActions) { + return false; } - } - else { - touches[0] = event.touches[0]; - touches[1] = event.touches[1]; - } - } - return touches; -} + if (interaction.element === element) { + targetElementCount++; -function touchAverage (event) { - var touches = getTouchPair(event); + if (otherAction !== action.name || targetElementCount >= maxPerElement) { + return false; + } + } + } - return { - pageX: (touches[0].pageX + touches[1].pageX) / 2, - pageY: (touches[0].pageY + touches[1].pageY) / 2, - clientX: (touches[0].clientX + touches[1].clientX) / 2, - clientY: (touches[0].clientY + touches[1].clientY) / 2 + return scope.maxInteractions > 0; }; -} -function touchBBox (event) { - if (!event.length && !(event.touches && event.touches.length > 1)) { - return; - } + // Test for the element that's "above" all other qualifiers + scope.indexOfDeepestElement = function (elements) { + var dropzone, + deepestZone = elements[0], + index = deepestZone? 0: -1, + parent, + deepestZoneParents = [], + dropzoneParents = [], + child, + i, + n; - var touches = getTouchPair(event), - minX = Math.min(touches[0].pageX, touches[1].pageX), - minY = Math.min(touches[0].pageY, touches[1].pageY), - maxX = Math.max(touches[0].pageX, touches[1].pageX), - maxY = Math.max(touches[0].pageY, touches[1].pageY); - - return { - x: minX, - y: minY, - left: minX, - top: minY, - width: maxX - minX, - height: maxY - minY - }; -} + for (i = 1; i < elements.length; i++) { + dropzone = elements[i]; + + // an element might belong to multiple selector dropzones + if (!dropzone || dropzone === deepestZone) { + continue; + } + + if (!deepestZone) { + deepestZone = dropzone; + index = i; + continue; + } + + // check if the deepest or current are document.documentElement or document.rootElement + // - if the current dropzone is, do nothing and continue + if (dropzone.parentNode === dropzone.ownerDocument) { + continue; + } + // - if deepest is, update with the current dropzone and continue to next + else if (deepestZone.parentNode === dropzone.ownerDocument) { + deepestZone = dropzone; + index = i; + continue; + } + + if (!deepestZoneParents.length) { + parent = deepestZone; + while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { + deepestZoneParents.unshift(parent); + parent = parent.parentNode; + } + } -function touchDistance (event, deltaSource) { - deltaSource = deltaSource || defaultOptions.deltaSource; + // if this element is an svg element and the current deepest is + // an HTMLElement + if (deepestZone instanceof scope.HTMLElement + && dropzone instanceof scope.SVGElement + && !(dropzone instanceof scope.SVGSVGElement)) { - var sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - touches = getTouchPair(event); + if (dropzone === deepestZone.parentNode) { + continue; + } + parent = dropzone.ownerSVGElement; + } + else { + parent = dropzone; + } - var dx = touches[0][sourceX] - touches[1][sourceX], - dy = touches[0][sourceY] - touches[1][sourceY]; + dropzoneParents = []; - return hypot(dx, dy); -} + while (parent.parentNode !== parent.ownerDocument) { + dropzoneParents.unshift(parent); + parent = parent.parentNode; + } -function touchAngle (event, prevAngle, deltaSource) { - deltaSource = deltaSource || defaultOptions.deltaSource; + n = 0; - var sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - touches = getTouchPair(event), - dx = touches[0][sourceX] - touches[1][sourceX], - dy = touches[0][sourceY] - touches[1][sourceY], - angle = 180 * Math.atan(dy / dx) / Math.PI; + // get (position of last common ancestor) + 1 + while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { + n++; + } + + var parents = [ + dropzoneParents[n - 1], + dropzoneParents[n], + deepestZoneParents[n] + ]; + + child = parents[0].lastChild; + + while (child) { + if (child === parents[1]) { + deepestZone = dropzone; + index = i; + deepestZoneParents = []; - if (isNumber(prevAngle)) { - var dr = angle - prevAngle, - drClamped = dr % 360; + break; + } + else if (child === parents[2]) { + break; + } - if (drClamped > 315) { - angle -= 360 + (angle / 360)|0 * 360; + child = child.previousSibling; + } } - else if (drClamped > 135) { - angle -= 180 + (angle / 360)|0 * 360; + + return index; + }; + + scope.matchesSelector = function (element, selector, nodeList) { + if (scope.ie8MatchesSelector) { + return scope.ie8MatchesSelector(element, selector, nodeList); } - else if (drClamped < -315) { - angle += 360 + (angle / 360)|0 * 360; + + // remove /deep/ from selectors if shadowDOM polyfill is used + if (scope.window !== scope.realWindow) { + selector = selector.replace(/\/deep\//g, ' '); } - else if (drClamped < -135) { - angle += 180 + (angle / 360)|0 * 360; + + return element[scope.prefixedMatchesSelector](selector); + }; + + scope.matchesUpTo = function (element, selector, limit) { + while (utils.isElement(element)) { + if (scope.matchesSelector(element, selector)) { + return true; + } + + element = scope.parentElement(element); + + if (element === limit) { + return scope.matchesSelector(element, selector); + } } - } - return angle; -} + return false; + }; -function getOriginXY (interactable, element) { - var origin = interactable - ? interactable.options.origin - : defaultOptions.origin; + // For IE8's lack of an Element#matchesSelector + // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified + if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { + scope.ie8MatchesSelector = function (element, selector, elems) { + elems = elems || element.parentNode.querySelectorAll(selector); - if (origin === 'parent') { - origin = parentElement(element); - } - else if (origin === 'self') { - origin = interactable.getRect(element); - } - else if (trySelector(origin)) { - origin = closest(element, origin) || { x: 0, y: 0 }; - } + for (var i = 0, len = elems.length; i < len; i++) { + if (elems[i] === element) { + return true; + } + } - if (isFunction(origin)) { - origin = origin(interactable && element); + return false; + }; } - if (isElement(origin)) { - origin = getElementRect(origin); - } + var Interaction = require('./Interaction'); - origin.x = ('x' in origin)? origin.x : origin.left; - origin.y = ('y' in origin)? origin.y : origin.top; + function getInteractionFromPointer (pointer, eventType, eventTarget) { + var i = 0, len = scope.interactions.length, + mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) + // MSPointerEvent.MSPOINTER_TYPE_MOUSE + || pointer.pointerType === 4), + interaction; - return origin; -} + var id = utils.getPointerId(pointer); -// http://stackoverflow.com/a/5634528/2280888 -function _getQBezierValue(t, p1, p2, p3) { - var iT = 1 - t; - return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; -} + // try to resume inertia with a new pointer + if (/down|start/i.test(eventType)) { + for (i = 0; i < len; i++) { + interaction = scope.interactions[i]; + + var element = eventTarget; + + if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume + && (interaction.mouse === mouseEvent)) { + while (element) { + // if the element is the interaction element + if (element === interaction.element) { + // update the interaction's pointer + if (interaction.pointers[0]) { + interaction.removePointer(interaction.pointers[0]); + } + interaction.addPointer(pointer); -function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) { - return { - x: _getQBezierValue(position, startX, cpX, endX), - y: _getQBezierValue(position, startY, cpY, endY) - }; -} + return interaction; + } + element = scope.parentElement(element); + } + } + } + } -// http://gizma.com/easing/ -function easeOutQuad (t, b, c, d) { - t /= d; - return -c * t*(t-2) + b; -} + // if it's a mouse interaction + if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { + + // find a mouse interaction that's not in inertia phase + for (i = 0; i < len; i++) { + if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { + return scope.interactions[i]; + } + } + + // find any interaction specifically for mouse. + // if the eventType is a mousedown, and inertia is active + // ignore the interaction + for (i = 0; i < len; i++) { + if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { + return interaction; + } + } + + // create a new interaction for mouse + interaction = new Interaction(); + interaction.mouse = true; + + return interaction; + } -function nodeContains (parent, child) { - while (child) { - if (child === parent) { - return true; + // get interaction that has this pointer + for (i = 0; i < len; i++) { + if (scope.contains(scope.interactions[i].pointerIds, id)) { + return scope.interactions[i]; + } } - child = child.parentNode; - } + // at this stage, a pointerUp should not return an interaction + if (/up|end|out/i.test(eventType)) { + return null; + } - return false; -} + // get first idle interaction + for (i = 0; i < len; i++) { + interaction = scope.interactions[i]; -function closest (child, selector) { - var parent = parentElement(child); + if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) + && !interaction.interacting() + && !(!mouseEvent && interaction.mouse)) { - while (isElement(parent)) { - if (matchesSelector(parent, selector)) { return parent; } + interaction.addPointer(pointer); - parent = parentElement(parent); + return interaction; + } + } + + return new Interaction(); } - return null; -} + function doOnInteractions (method) { + return (function (event) { + var interaction, + eventTarget = scope.getActualElement(event.path + ? event.path[0] + : event.target), + curEventTarget = scope.getActualElement(event.currentTarget), + i; -function parentElement (node) { - var parent = node.parentNode; + if (browser.supportsTouch && /touch/.test(event.type)) { + scope.prevTouchTime = new Date().getTime(); - if (isDocFrag(parent)) { - // skip past #shado-root fragments - while ((parent = parent.host) && isDocFrag(parent)) {} + for (i = 0; i < event.changedTouches.length; i++) { + var pointer = event.changedTouches[i]; - return parent; - } + interaction = getInteractionFromPointer(pointer, event.type, eventTarget); - return parent; -} + if (!interaction) { continue; } -function inContext (interactable, element) { - return interactable._context === element.ownerDocument - || nodeContains(interactable._context, element); -} + interaction._updateEventTargets(eventTarget, curEventTarget); + + interaction[method](pointer, event, eventTarget, curEventTarget); + } + } + else { + if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { + // ignore mouse events while touch interactions are active + for (i = 0; i < scope.interactions.length; i++) { + if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { + return; + } + } -function testIgnore (interactable, interactableElement, element) { - var ignoreFrom = interactable.options.ignoreFrom; + // try to ignore mouse events that are simulated by the browser + // after a touch event + if (new Date().getTime() - scope.prevTouchTime < 500) { + return; + } + } + + interaction = getInteractionFromPointer(event, event.type, eventTarget); - if (!ignoreFrom || !isElement(element)) { return false; } + if (!interaction) { return; } - if (isString(ignoreFrom)) { - return matchesUpTo(element, ignoreFrom, interactableElement); + interaction._updateEventTargets(eventTarget, curEventTarget); + + interaction[method](event, event, eventTarget, curEventTarget); + } + }); } - else if (isElement(ignoreFrom)) { - return nodeContains(ignoreFrom, element); + + function preventOriginalDefault () { + this.originalEvent.preventDefault(); } - return false; -} + function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { + // false, '', undefined, null + if (!value) { return false; } + + // true value, use pointer coords and element rect + if (value === true) { + // if dimensions are negative, "switch" edges + var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left, + height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top; + + if (width < 0) { + if (name === 'left' ) { name = 'right'; } + else if (name === 'right') { name = 'left' ; } + } + if (height < 0) { + if (name === 'top' ) { name = 'bottom'; } + else if (name === 'bottom') { name = 'top' ; } + } -function testAllow (interactable, interactableElement, element) { - var allowFrom = interactable.options.allowFrom; + if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } + if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } - if (!allowFrom) { return true; } + if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } + if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } + } - if (!isElement(element)) { return false; } + // the remaining checks require an element + if (!utils.isElement(element)) { return false; } - if (isString(allowFrom)) { - return matchesUpTo(element, allowFrom, interactableElement); - } - else if (isElement(allowFrom)) { - return nodeContains(allowFrom, element); + return utils.isElement(value) + // the value is an element to use as a resize handle + ? value === element + // otherwise check if element matches value as selector + : scope.matchesUpTo(element, value, interactableElement); } - return false; -} + function defaultActionChecker (pointer, interaction, element) { + var rect = this.getRect(element), + shouldResize = false, + action = null, + resizeAxes = null, + resizeEdges, + page = utils.extend({}, interaction.curCoords.page), + options = this.options; -function checkAxis (axis, interactable) { - if (!interactable) { return false; } + if (!rect) { return null; } - var thisAxis = interactable.options.drag.axis; + if (scope.actionIsEnabled.resize && options.resize.enabled) { + var resizeOptions = options.resize; - return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); -} + resizeEdges = { + left: false, right: false, top: false, bottom: false + }; -function checkSnap (interactable, action) { - var options = interactable.options; + // if using resize.edges + if (scope.isObject(resizeOptions.edges)) { + for (var edge in resizeEdges) { + resizeEdges[edge] = checkResizeEdge(edge, + resizeOptions.edges[edge], + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || scope.margin); + } - if (/^resize/.test(action)) { - action = 'resize'; - } + resizeEdges.left = resizeEdges.left && !resizeEdges.right; + resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; - return options[action].snap && options[action].snap.enabled; -} + shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; + } + else { + var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), + bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); -function checkRestrict (interactable, action) { - var options = interactable.options; + shouldResize = right || bottom; + resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); + } + } - if (/^resize/.test(action)) { - action = 'resize'; - } + action = shouldResize + ? 'resize' + : scope.actionIsEnabled.drag && options.drag.enabled + ? 'drag' + : null; - return options[action].restrict && options[action].restrict.enabled; -} + if (scope.actionIsEnabled.gesture + && interaction.pointerIds.length >=2 + && !(interaction.dragging || interaction.resizing)) { + action = 'gesture'; + } -function checkAutoScroll (interactable, action) { - var options = interactable.options; + if (action) { + return { + name: action, + axis: resizeAxes, + edges: resizeEdges + }; + } - if (/^resize/.test(action)) { - action = 'resize'; + return null; } - return options[action].autoScroll && options[action].autoScroll.enabled; -} + var InteractEvent = require('./InteractEvent'); -function withinInteractionLimit (interactable, element, action) { - var options = interactable.options, - maxActions = options[action.name].max, - maxPerElement = options[action.name].maxPerElement, - activeInteractions = 0, - targetCount = 0, - targetElementCount = 0; + for (var i = 0, len = interactionListeners.length; i < len; i++) { + var listenerName = interactionListeners[i]; - for (var i = 0, len = interactions.length; i < len; i++) { - var interaction = interactions[i], - otherAction = interaction.prepared.name, - active = interaction.interacting(); + scope.listeners[listenerName] = doOnInteractions(listenerName); + } - if (!active) { continue; } + // bound to the interactable context when a DOM event + // listener is added to a selector interactable + function delegateListener (event, useCapture) { + var fakeEvent = {}, + delegated = scope.delegatedEvents[event.type], + eventTarget = scope.getActualElement(event.path + ? event.path[0] + : event.target), + element = eventTarget; - activeInteractions++; + useCapture = useCapture? true: false; - if (activeInteractions >= maxInteractions) { - return false; + // duplicate the event so that currentTarget can be changed + for (var prop in event) { + fakeEvent[prop] = event[prop]; } - if (interaction.target !== interactable) { continue; } + fakeEvent.originalEvent = event; + fakeEvent.preventDefault = preventOriginalDefault; - targetCount += (otherAction === action.name)|0; + // climb up document tree looking for selector matches + while (utils.isElement(element)) { + for (var i = 0; i < delegated.selectors.length; i++) { + var selector = delegated.selectors[i], + context = delegated.contexts[i]; - if (targetCount >= maxActions) { - return false; - } + if (scope.matchesSelector(element, selector) + && scope.nodeContains(context, eventTarget) + && scope.nodeContains(context, element)) { - if (interaction.element === element) { - targetElementCount++; + var listeners = delegated.listeners[i]; - if (otherAction !== action.name || targetElementCount >= maxPerElement) { - return false; + fakeEvent.currentTarget = element; + + for (var j = 0; j < listeners.length; j++) { + if (listeners[j][1] === useCapture) { + listeners[j][0](fakeEvent); + } + } + } } + + element = scope.parentElement(element); } } - return maxInteractions > 0; -} - -// Test for the element that's "above" all other qualifiers -function indexOfDeepestElement (elements) { - var dropzone, - deepestZone = elements[0], - index = deepestZone? 0: -1, - parent, - deepestZoneParents = [], - dropzoneParents = [], - child, - i, - n; - - for (i = 1; i < elements.length; i++) { - dropzone = elements[i]; + function delegateUseCapture (event) { + return delegateListener.call(this, event, true); + } - // an element might belong to multiple selector dropzones - if (!dropzone || dropzone === deepestZone) { - continue; - } + scope.interactables.indexOfElement = function indexOfElement (element, context) { + context = context || scope.document; - if (!deepestZone) { - deepestZone = dropzone; - index = i; - continue; - } + for (var i = 0; i < this.length; i++) { + var interactable = this[i]; - // check if the deepest or current are document.documentElement or document.rootElement - // - if the current dropzone is, do nothing and continue - if (dropzone.parentNode === dropzone.ownerDocument) { - continue; - } - // - if deepest is, update with the current dropzone and continue to next - else if (deepestZone.parentNode === dropzone.ownerDocument) { - deepestZone = dropzone; - index = i; - continue; - } + if ((interactable.selector === element + && (interactable._context === context)) + || (!interactable.selector && interactable._element === element)) { - if (!deepestZoneParents.length) { - parent = deepestZone; - while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { - deepestZoneParents.unshift(parent); - parent = parent.parentNode; + return i; } } + return -1; + }; - // if this element is an svg element and the current deepest is - // an HTMLElement - if (deepestZone instanceof HTMLElement - && dropzone instanceof SVGElement - && !(dropzone instanceof SVGSVGElement)) { + scope.interactables.get = function interactableGet (element, options) { + return this[this.indexOfElement(element, options && options.context)]; + }; + + scope.interactables.forEachSelector = function (callback) { + for (var i = 0; i < this.length; i++) { + var interactable = this[i]; - if (dropzone === deepestZone.parentNode) { + if (!interactable.selector) { continue; } - parent = dropzone.ownerSVGElement; - } - else { - parent = dropzone; + var ret = callback(interactable, interactable.selector, interactable._context, i, this); + + if (ret !== undefined) { + return ret; + } } + }; - dropzoneParents = []; + /*\ + * interact + [ method ] + * + * The methods of this variable can be used to set elements as + * interactables and also to change various default settings. + * + * Calling it as a function and passing an element or a valid CSS selector + * string returns an Interactable object which has various methods to + * configure it. + * + - element (Element | string) The HTML or SVG Element to interact with or CSS selector + = (object) An @Interactable + * + > Usage + | interact(document.getElementById('draggable')).draggable(true); + | + | var rectables = interact('rect'); + | rectables + | .gesturable(true) + | .on('gesturemove', function (event) { + | // something cool... + | }) + | .autoScroll(true); + \*/ + function interact (element, options) { + return scope.interactables.get(element, options) || new Interactable(element, options); + } - while (parent.parentNode !== parent.ownerDocument) { - dropzoneParents.unshift(parent); - parent = parent.parentNode; - } + /*\ + * Interactable + [ property ] + ** + * Object type returned by @interact + \*/ + function Interactable (element, options) { + this._element = element; + this._iEvents = this._iEvents || {}; - n = 0; + var _window; - // get (position of last common ancestor) + 1 - while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { - n++; - } + if (scope.trySelector(element)) { + this.selector = element; - var parents = [ - dropzoneParents[n - 1], - dropzoneParents[n], - deepestZoneParents[n] - ]; + var context = options && options.context; - child = parents[0].lastChild; + _window = context? scope.getWindow(context) : scope.window; - while (child) { - if (child === parents[1]) { - deepestZone = dropzone; - index = i; - deepestZoneParents = []; + if (context && (_window.Node + ? context instanceof _window.Node + : (utils.isElement(context) || context === _window.document))) { - break; + this._context = context; } - else if (child === parents[2]) { - break; + } + else { + _window = scope.getWindow(element); + + if (utils.isElement(element, _window)) { + + if (scope.PointerEvent) { + events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown ); + events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover); + } + else { + events.add(this._element, 'mousedown' , scope.listeners.pointerDown ); + events.add(this._element, 'mousemove' , scope.listeners.pointerHover); + events.add(this._element, 'touchstart', scope.listeners.pointerDown ); + events.add(this._element, 'touchmove' , scope.listeners.pointerHover); + } } + } + + this._doc = _window.document; - child = child.previousSibling; + if (!scope.contains(scope.documents, this._doc)) { + listenToDocument(this._doc); } - } - return index; -} + scope.interactables.push(this); -function Interaction () { - this.target = null; // current interactable being interacted with - this.element = null; // the target element of the interactable - this.dropTarget = null; // the dropzone a drag target might be dropped into - this.dropElement = null; // the element at the time of checking - this.prevDropTarget = null; // the dropzone that was recently dragged away from - this.prevDropElement = null; // the element at the time of checking + this.set(options); + } - this.prepared = { // action that's ready to be fired on next move event - name : null, - axis : null, - edges: null - }; + Interactable.prototype = { + setOnEvents: function (action, phases) { + if (action === 'drop') { + if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } + if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } + if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } + if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } + if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } + if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } + } + else { + action = 'on' + action; - this.matches = []; // all selectors that are matched by target element - this.matchElements = []; // corresponding elements + if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } + if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } + if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } + if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } + } - this.inertiaStatus = { - active : false, - smoothEnd : false, + return this; + }, - startEvent: null, - upCoords: {}, + /*\ + * Interactable.draggable + [ method ] + * + * Gets or sets whether drag actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of drag events + | var isDraggable = interact('ul li').draggable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) + = (object) This Interactable + | interact(element).draggable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // the axis in which the first movement must be + | // for the drag sequence to start + | // 'xy' by default - any direction + | axis: 'x' || 'y' || 'xy', + | + | // max number of drags that can happen concurrently + | // with elements of this Interactable. Infinity by default + | max: Infinity, + | + | // max number of drags that can target the same element+Interactable + | // 1 by default + | maxPerElement: 2 + | }); + \*/ + draggable: function (options) { + if (scope.isObject(options)) { + this.options.drag.enabled = options.enabled === false? false: true; + this.setPerAction('drag', options); + this.setOnEvents('drag', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.drag.axis = options.axis; + } + else if (options.axis === null) { + delete this.options.drag.axis; + } - xe: 0, ye: 0, - sx: 0, sy: 0, + return this; + } - t0: 0, - vx0: 0, vys: 0, - duration: 0, + if (scope.isBool(options)) { + this.options.drag.enabled = options; - resumeDx: 0, - resumeDy: 0, - - lambda_v0: 0, - one_ve_v0: 0, - i : null - }; - - if (isFunction(Function.prototype.bind)) { - this.boundInertiaFrame = this.inertiaFrame.bind(this); - this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); - } - else { - var that = this; + return this; + } - this.boundInertiaFrame = function () { return that.inertiaFrame(); }; - this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); }; - } + return this.options.drag; + }, - this.activeDrops = { - dropzones: [], // the dropzones that are mentioned below - elements : [], // elements of dropzones that accept the target draggable - rects : [] // the rects of the elements mentioned above - }; + setPerAction: function (action, options) { + // for all the default per-action options + for (var option in options) { + // if this option exists for this action + if (option in scope.defaultOptions[action]) { + // if the option in the options arg is an object value + if (scope.isObject(options[option])) { + // duplicate the object + this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); + + if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { + this.options[action][option].enabled = options[option].enabled === false? false : true; + } + } + else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) { + this.options[action][option].enabled = options[option]; + } + else if (options[option] !== undefined) { + // or if it's not undefined, do a plain assignment + this.options[action][option] = options[option]; + } + } + } + }, - // keep track of added pointers - this.pointers = []; - this.pointerIds = []; - this.downTargets = []; - this.downTimes = []; - this.holdTimers = []; + /*\ + * Interactable.dropzone + [ method ] + * + * Returns or sets whether elements can be dropped onto this + * Interactable to trigger drop events + * + * Dropzones can receive the following events: + * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends + * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone + * - `dragmove` when a draggable that has entered the dropzone is moved + * - `drop` when a draggable is dropped into this dropzone + * + * Use the `accept` option to allow only elements that match the given CSS selector or element. + * + * Use the `overlap` option to set how drops are checked for. The allowed values are: + * - `'pointer'`, the pointer must be over the dropzone (default) + * - `'center'`, the draggable element's center must be over the dropzone + * - a number from 0-1 which is the `(intersection area) / (draggable area)`. + * e.g. `0.5` for drop to happen when half of the area of the + * draggable is over the dropzone + * + - options (boolean | object | null) #optional The new value to be set. + | interact('.drop').dropzone({ + | accept: '.can-drop' || document.getElementById('single-drop'), + | overlap: 'pointer' || 'center' || zeroToOne + | } + = (boolean | object) The current setting or this Interactable + \*/ + dropzone: function (options) { + if (scope.isObject(options)) { + this.options.drop.enabled = options.enabled === false? false: true; + this.setOnEvents('drop', options); + this.accept(options.accept); + + if (/^(pointer|center)$/.test(options.overlap)) { + this.options.drop.overlap = options.overlap; + } + else if (scope.isNumber(options.overlap)) { + this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); + } - // Previous native pointer move event coordinates - this.prevCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - // current native pointer move event coordinates - this.curCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; + return this; + } - // Starting InteractEvent pointer coordinates - this.startCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; + if (scope.isBool(options)) { + this.options.drop.enabled = options; - // Change in coordinates and time of the pointer - this.pointerDelta = { - page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - timeStamp: 0 - }; + return this; + } - this.downEvent = null; // pointerdown/mousedown/touchstart event - this.downPointer = {}; + return this.options.drop; + }, - this._eventTarget = null; - this._curEventTarget = null; + dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) { + var dropped = false; - this.prevEvent = null; // previous action event - this.tapTime = 0; // time of the most recent tap event - this.prevTap = null; + // if the dropzone has no rect (eg. display: none) + // call the custom dropChecker or just return false + if (!(rect = rect || this.getRect(dropElement))) { + return (this.options.dropChecker + ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) + : false); + } - this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.snapOffsets = []; + var dropOverlap = this.options.drop.overlap; - this.gesture = { - start: { x: 0, y: 0 }, + if (dropOverlap === 'pointer') { + var page = utils.getPageXY(pointer), + origin = scope.getOriginXY(draggable, draggableElement), + horizontal, + vertical; - startDistance: 0, // distance between two touches of touchStart - prevDistance : 0, - distance : 0, + page.x += origin.x; + page.y += origin.y; - scale: 1, // gesture.distance / gesture.startDistance + horizontal = (page.x > rect.left) && (page.x < rect.right); + vertical = (page.y > rect.top ) && (page.y < rect.bottom); - startAngle: 0, // angle of line joining two touches - prevAngle : 0 // angle of the previous gesture event - }; + dropped = horizontal && vertical; + } - this.snapStatus = { - x : 0, y : 0, - dx : 0, dy : 0, - realX : 0, realY : 0, - snappedX: 0, snappedY: 0, - targets : [], - locked : false, - changed : false - }; + var dragRect = draggable.getRect(draggableElement); - this.restrictStatus = { - dx : 0, dy : 0, - restrictedX: 0, restrictedY: 0, - snap : null, - restricted : false, - changed : false - }; + if (dropOverlap === 'center') { + var cx = dragRect.left + dragRect.width / 2, + cy = dragRect.top + dragRect.height / 2; - this.restrictStatus.snap = this.snapStatus; + dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; + } - this.pointerIsDown = false; - this.pointerWasMoved = false; - this.gesturing = false; - this.dragging = false; - this.resizing = false; - this.resizeAxes = 'xy'; + if (scope.isNumber(dropOverlap)) { + var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) + * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), + overlapRatio = overlapArea / (dragRect.width * dragRect.height); - this.mouse = false; + dropped = overlapRatio >= dropOverlap; + } - interactions.push(this); -} + if (this.options.dropChecker) { + dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); + } -Interaction.prototype = { - getPageXY : function (pointer, xy) { return getPageXY(pointer, xy, this); }, - getClientXY: function (pointer, xy) { return getClientXY(pointer, xy, this); }, - setEventXY : function (target, ptr) { return setEventXY(target, ptr, this); }, + return dropped; + }, - pointerOver: function (pointer, event, eventTarget) { - if (this.prepared.name || !this.mouse) { return; } + /*\ + * Interactable.dropChecker + [ method ] + * + * Gets or sets the function used to check if a dragged element is + * over this Interactable. + * + - checker (function) #optional The function that will be called when checking for a drop + = (Function | Interactable) The checker function or this Interactable + * + * The checker function takes the following arguments: + * + - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag + - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer + - dropped (boolean) The value from the default drop check + - dropzone (Interactable) The dropzone interactable + - dropElement (Element) The dropzone element + - draggable (Interactable) The Interactable being dragged + - draggableElement (Element) The actual element that's being dragged + * + > Usage: + | interact(target) + | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent + | event, // TouchEvent/PointerEvent/MouseEvent + | dropped, // result of the default checker + | dropzone, // dropzone Interactable + | dropElement, // dropzone elemnt + | draggable, // draggable Interactable + | draggableElement) {// draggable element + | + | return dropped && event.target.hasAttribute('allow-drop'); + | } + \*/ + dropChecker: function (checker) { + if (scope.isFunction(checker)) { + this.options.dropChecker = checker; + + return this; + } + if (checker === null) { + delete this.options.getRect; - var curMatches = [], - curMatchElements = [], - prevTargetElement = this.element; + return this; + } - this.addPointer(pointer); + return this.options.dropChecker; + }, - if (this.target - && (testIgnore(this.target, this.element, eventTarget) - || !testAllow(this.target, this.element, eventTarget))) { - // if the eventTarget should be ignored or shouldn't be allowed - // clear the previous target - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; - } + /*\ + * Interactable.accept + [ method ] + * + * Deprecated. add an `accept` property to the options object passed to + * @Interactable.dropzone instead. + * + * Gets or sets the Element or CSS selector match that this + * Interactable accepts if it is a dropzone. + * + - newValue (Element | string | null) #optional + * If it is an Element, then only that element can be dropped into this dropzone. + * If it is a string, the element being dragged must match it as a selector. + * If it is null, the accept options is cleared - it accepts any element. + * + = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable + \*/ + accept: function (newValue) { + if (utils.isElement(newValue)) { + this.options.drop.accept = newValue; + + return this; + } - var elementInteractable = interactables.get(eventTarget), - elementAction = (elementInteractable - && !testIgnore(elementInteractable, eventTarget, eventTarget) - && testAllow(elementInteractable, eventTarget, eventTarget) - && validateAction( - elementInteractable.getAction(pointer, event, this, eventTarget), - elementInteractable)); + // test if it is a valid CSS selector + if (scope.trySelector(newValue)) { + this.options.drop.accept = newValue; - if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { - elementAction = null; - } + return this; + } - function pushCurMatches (interactable, selector) { - if (interactable - && inContext(interactable, eventTarget) - && !testIgnore(interactable, eventTarget, eventTarget) - && testAllow(interactable, eventTarget, eventTarget) - && matchesSelector(eventTarget, selector)) { + if (newValue === null) { + delete this.options.drop.accept; - curMatches.push(interactable); - curMatchElements.push(eventTarget); + return this; } - } - - if (elementAction) { - this.target = elementInteractable; - this.element = eventTarget; - this.matches = []; - this.matchElements = []; - } - else { - interactables.forEachSelector(pushCurMatches); - if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { - this.matches = curMatches; - this.matchElements = curMatchElements; + return this.options.drop.accept; + }, - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(eventTarget, - PointerEvent? pEventTypes.move : 'mousemove', - listeners.pointerHover); - } - else if (this.target) { - if (nodeContains(prevTargetElement, eventTarget)) { - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(this.element, - PointerEvent? pEventTypes.move : 'mousemove', - listeners.pointerHover); + /*\ + * Interactable.resizable + [ method ] + * + * Gets or sets whether resize actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of resize elements + | var isResizeable = interact('input[type=text]').resizable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) + = (object) This Interactable + | interact(element).resizable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | edges: { + | top : true, // Use pointer coords to check for resize. + | left : false, // Disable resizing from left edge. + | bottom: '.resize-s',// Resize if pointer target matches selector + | right : handleEl // Resize if pointer target is the given Element + | }, + | + | // a value of 'none' will limit the resize rect to a minimum of 0x0 + | // 'negate' will allow the rect to have negative width/height + | // 'reposition' will keep the width/height positive by swapping + | // the top and bottom edges and/or swapping the left and right edges + | invert: 'none' || 'negate' || 'reposition' + | + | // limit multiple resizes. + | // See the explanation in the @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); + \*/ + resizable: function (options) { + if (scope.isObject(options)) { + this.options.resize.enabled = options.enabled === false? false: true; + this.setPerAction('resize', options); + this.setOnEvents('resize', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.resize.axis = options.axis; } - else { - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; + else if (options.axis === null) { + this.options.resize.axis = scope.defaultOptions.resize.axis; } + + if (scope.isBool(options.square)) { + this.options.resize.square = options.square; + } + + return this; } - } - }, + if (scope.isBool(options)) { + this.options.resize.enabled = options; - // Check what action would be performed on pointerMove target if a mouse - // button were pressed and change the cursor accordingly - pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { - var target = this.target; + return this; + } + return this.options.resize; + }, - if (!this.prepared.name && this.mouse) { + /*\ + * Interactable.squareResize + [ method ] + * + * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead + * + * Gets or sets whether resizing is forced 1:1 aspect + * + = (boolean) Current setting + * + * or + * + - newValue (boolean) #optional + = (object) this Interactable + \*/ + squareResize: function (newValue) { + if (scope.isBool(newValue)) { + this.options.resize.square = newValue; + + return this; + } - var action; + if (newValue === null) { + delete this.options.resize.square; - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); + return this; + } - if (matches) { - action = this.validateSelector(pointer, event, matches, matchElements); + return this.options.resize.square; + }, + + /*\ + * Interactable.gesturable + [ method ] + * + * Gets or sets whether multitouch gestures can be performed on the + * Interactable's element + * + = (boolean) Indicates if this can be the target of gesture events + | var isGestureable = interact(element).gesturable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) + = (object) this Interactable + | interact(element).gesturable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // limit multiple gestures. + | // See the explanation in @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); + \*/ + gesturable: function (options) { + if (scope.isObject(options)) { + this.options.gesture.enabled = options.enabled === false? false: true; + this.setPerAction('gesture', options); + this.setOnEvents('gesture', options); + + return this; } - else if (target) { - action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); + + if (scope.isBool(options)) { + this.options.gesture.enabled = options; + + return this; } - if (target && target.options.styleCursor) { - if (action) { - target._doc.documentElement.style.cursor = getActionCursor(action); - } - else { - target._doc.documentElement.style.cursor = ''; - } + return this.options.gesture; + }, + + /*\ + * Interactable.autoScroll + [ method ] + ** + * Deprecated. Add an `autoscroll` property to the options object + * passed to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets whether dragging and resizing near the edges of the + * window/container trigger autoScroll for this Interactable + * + = (object) Object with autoScroll properties + * + * or + * + - options (object | boolean) #optional + * options can be: + * - an object with margin, distance and interval properties, + * - true or false to enable or disable autoScroll or + = (Interactable) this Interactable + \*/ + autoScroll: function (options) { + if (scope.isObject(options)) { + options = utils.extend({ actions: ['drag', 'resize']}, options); + } + else if (scope.isBool(options)) { + options = { actions: ['drag', 'resize'], enabled: options }; } - } - else if (this.prepared.name) { - this.checkAndPreventDefault(event, target, this.element); - } - }, - pointerOut: function (pointer, event, eventTarget) { - if (this.prepared.name) { return; } + return this.setOptions('autoScroll', options); + }, - // Remove temporary event listeners for selector Interactables - if (!interactables.get(eventTarget)) { - events.remove(eventTarget, - PointerEvent? pEventTypes.move : 'mousemove', - listeners.pointerHover); - } + /*\ + * Interactable.snap + [ method ] + ** + * Deprecated. Add a `snap` property to the options object passed + * to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets if and how action coordinates are snapped. By + * default, snapping is relative to the pointer coordinates. You can + * change this by setting the + * [`elementOrigin`](https://github.com/taye/interact.js/pull/72). + ** + = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled + ** + * or + ** + - options (object | boolean | null) #optional + = (Interactable) this Interactable + > Usage + | interact(document.querySelector('#thing')).snap({ + | targets: [ + | // snap to this specific point + | { + | x: 100, + | y: 100, + | range: 25 + | }, + | // give this function the x and y page coords and snap to the object returned + | function (x, y) { + | return { + | x: x, + | y: (75 + 50 * Math.sin(x * 0.04)), + | range: 40 + | }; + | }, + | // create a function that snaps to a grid + | interact.createSnapGrid({ + | x: 50, + | y: 50, + | range: 10, // optional + | offset: { x: 5, y: 10 } // optional + | }) + | ], + | // do not snap during normal movement. + | // Instead, trigger only one snapped move event + | // immediately before the end event. + | endOnly: true, + | + | relativePoints: [ + | { x: 0, y: 0 }, // snap relative to the top left of the element + | { x: 1, y: 1 }, // and also to the bottom right + | ], + | + | // offset the snap target coordinates + | // can be an object with x/y or 'startCoords' + | offset: { x: 50, y: 50 } + | } + | }); + \*/ + snap: function (options) { + var ret = this.setOptions('snap', options); + + if (ret === this) { return this; } + + return ret.drag; + }, - if (this.target && this.target.options.styleCursor && !this.interacting()) { - this.target._doc.documentElement.style.cursor = ''; - } - }, + setOptions: function (option, options) { + var actions = options && scope.isArray(options.actions) + ? options.actions + : ['drag']; - selectorDown: function (pointer, event, eventTarget, curEventTarget) { - var that = this, - // copy event to be used in timeout for IE8 - eventCopy = events.useAttachEvent? extend({}, event) : event, - element = eventTarget, - pointerIndex = this.addPointer(pointer), - action; + var i; - this.holdTimers[pointerIndex] = setTimeout(function () { - that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); - }, defaultOptions._holdDuration); + if (scope.isObject(options) || scope.isBool(options)) { + for (i = 0; i < actions.length; i++) { + var action = /resize/.test(actions[i])? 'resize' : actions[i]; - this.pointerIsDown = true; + if (!scope.isObject(this.options[action])) { continue; } - // Check if the down event hits the current inertia target - if (this.inertiaStatus.active && this.target.selector) { - // climb up the DOM tree from the event target - while (isElement(element)) { + var thisOption = this.options[action][option]; - // if this element is the current inertia target element - if (element === this.element - // and the prospective action is the same as the ongoing one - && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { + if (scope.isObject(options)) { + utils.extend(thisOption, options); + thisOption.enabled = options.enabled === false? false: true; - // stop inertia so that the next move will be a normal one - cancelFrame(this.inertiaStatus.i); - this.inertiaStatus.active = false; + if (option === 'snap') { + if (thisOption.mode === 'grid') { + thisOption.targets = [ + interact.createSnapGrid(utils.extend({ + offset: thisOption.gridOffset || { x: 0, y: 0 } + }, thisOption.grid || {})) + ]; + } + else if (thisOption.mode === 'anchor') { + thisOption.targets = thisOption.anchors; + } + else if (thisOption.mode === 'path') { + thisOption.targets = thisOption.paths; + } - this.collectEventTargets(pointer, event, eventTarget, 'down'); - return; + if ('elementOrigin' in options) { + thisOption.relativePoints = [options.elementOrigin]; + } + } + } + else if (scope.isBool(options)) { + thisOption.enabled = options; + } } - element = parentElement(element); + + return this; } - } - // do nothing if interacting - if (this.interacting()) { - this.collectEventTargets(pointer, event, eventTarget, 'down'); - return; - } + var ret = {}, + allActions = ['drag', 'resize', 'gesture']; - function pushMatches (interactable, selector, context) { - var elements = ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; - - if (inContext(interactable, element) - && !testIgnore(interactable, element, eventTarget) - && testAllow(interactable, element, eventTarget) - && matchesSelector(element, selector, elements)) { - - that.matches.push(interactable); - that.matchElements.push(element); + for (i = 0; i < allActions.length; i++) { + if (option in scope.defaultOptions[allActions[i]]) { + ret[allActions[i]] = this.options[allActions[i]][option]; + } } - } - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); - this.downEvent = event; + return ret; + }, - while (isElement(element) && !action) { - this.matches = []; - this.matchElements = []; - interactables.forEachSelector(pushMatches); + /*\ + * Interactable.inertia + [ method ] + ** + * Deprecated. Add an `inertia` property to the options object passed + * to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets if and how events continue to run after the pointer is released + ** + = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled + ** + * or + ** + - options (object | boolean | null) #optional + = (Interactable) this Interactable + > Usage + | // enable and use default settings + | interact(element).inertia(true); + | + | // enable and use custom settings + | interact(element).inertia({ + | // value greater than 0 + | // high values slow the object down more quickly + | resistance : 16, + | + | // the minimum launch speed (pixels per second) that results in inertia start + | minSpeed : 200, + | + | // inertia will stop when the object slows down to this speed + | endSpeed : 20, + | + | // boolean; should actions be resumed when the pointer goes down during inertia + | allowResume : true, + | + | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy + | zeroResumeDelta: false, + | + | // if snap/restrict are set to be endOnly and inertia is enabled, releasing + | // the pointer without triggering inertia will animate from the release + | // point to the snaped/restricted point in the given amount of time (ms) + | smoothEndDuration: 300, + | + | // an array of action types that can have inertia (no gesture) + | actions : ['drag', 'resize'] + | }); + | + | // reset custom settings and use all defaults + | interact(element).inertia(null); + \*/ + inertia: function (options) { + var ret = this.setOptions('inertia', options); + + if (ret === this) { return this; } + + return ret.drag; + }, - action = this.validateSelector(pointer, event, this.matches, this.matchElements); - element = parentElement(element); - } + getAction: function (pointer, event, interaction, element) { + var action = this.defaultActionChecker(pointer, interaction, element); - if (action) { - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; + if (this.options.actionChecker) { + return this.options.actionChecker(pointer, event, action, this, element, interaction); + } - this.collectEventTargets(pointer, event, eventTarget, 'down'); + return action; + }, - return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); - } - else { - // do these now since pointerDown isn't being called from here - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - extend(this.downPointer, pointer); + defaultActionChecker: defaultActionChecker, + + /*\ + * Interactable.actionChecker + [ method ] + * + * Gets or sets the function used to check action to be performed on + * pointerDown + * + - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. + = (Function | Interactable) The checker function or this Interactable + * + | interact('.resize-drag') + | .resizable(true) + | .draggable(true) + | .actionChecker(function (pointer, event, action, interactable, element, interaction) { + | + | if (interact.matchesSelector(event.target, '.drag-handle') { + | // force drag with handle target + | action.name = drag; + | } + | else { + | // resize from the top and right edges + | action.name = 'resize'; + | action.edges = { top: true, right: true }; + | } + | + | return action; + | }); + \*/ + actionChecker: function (checker) { + if (scope.isFunction(checker)) { + this.options.actionChecker = checker; + + return this; + } - copyCoords(this.prevCoords, this.curCoords); - this.pointerWasMoved = false; - } + if (checker === null) { + delete this.options.actionChecker; - this.collectEventTargets(pointer, event, eventTarget, 'down'); - }, + return this; + } - // Determine action to be performed on next pointerMove and add appropriate - // style and event Listeners - pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { - if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { - this.checkAndPreventDefault(event, this.target, this.element); + return this.options.actionChecker; + }, - return; - } + /*\ + * Interactable.getRect + [ method ] + * + * The default function to get an Interactables bounding rect. Can be + * overridden using @Interactable.rectChecker. + * + - element (Element) #optional The element to measure. + = (object) The object's bounding rectangle. + o { + o top : 0, + o left : 0, + o bottom: 0, + o right : 0, + o width : 0, + o height: 0 + o } + \*/ + getRect: function rectCheck (element) { + element = element || this._element; + + if (this.selector && !(utils.isElement(element))) { + element = this._context.querySelector(this.selector); + } - this.pointerIsDown = true; - this.downEvent = event; + return scope.getElementRect(element); + }, - var pointerIndex = this.addPointer(pointer), - action; + /*\ + * Interactable.rectChecker + [ method ] + * + * Returns or sets the function used to calculate the interactable's + * element's rectangle + * + - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect + = (function | object) The checker function or this Interactable + \*/ + rectChecker: function (checker) { + if (scope.isFunction(checker)) { + this.getRect = checker; + + return this; + } - // If it is the second touch of a multi-touch gesture, keep the target - // the same if a target was set by the first touch - // Otherwise, set the target if there is no action prepared - if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { + if (checker === null) { + delete this.options.getRect; - var interactable = interactables.get(curEventTarget); + return this; + } - if (interactable - && !testIgnore(interactable, curEventTarget, eventTarget) - && testAllow(interactable, curEventTarget, eventTarget) - && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) - && withinInteractionLimit(interactable, curEventTarget, action)) { - this.target = interactable; - this.element = curEventTarget; + return this.getRect; + }, + + /*\ + * Interactable.styleCursor + [ method ] + * + * Returns or sets whether the action that would be performed when the + * mouse on the element are checked on `mousemove` so that the cursor + * may be styled appropriately + * + - newValue (boolean) #optional + = (boolean | Interactable) The current setting or this Interactable + \*/ + styleCursor: function (newValue) { + if (scope.isBool(newValue)) { + this.options.styleCursor = newValue; + + return this; } - } - var target = this.target, - options = target && target.options; + if (newValue === null) { + delete this.options.styleCursor; - if (target && (forceAction || !this.prepared.name)) { - action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); + return this; + } - this.setEventXY(this.startCoords); + return this.options.styleCursor; + }, - if (!action) { return; } + /*\ + * Interactable.preventDefault + [ method ] + * + * Returns or sets whether to prevent the browser's default behaviour + * in response to pointer events. Can be set to: + * - `'always'` to always prevent + * - `'never'` to never prevent + * - `'auto'` to let interact.js try to determine what would be best + * + - newValue (string) #optional `true`, `false` or `'auto'` + = (string | Interactable) The current setting or this Interactable + \*/ + preventDefault: function (newValue) { + if (/^(always|never|auto)$/.test(newValue)) { + this.options.preventDefault = newValue; + return this; + } - if (options.styleCursor) { - target._doc.documentElement.style.cursor = getActionCursor(action); + if (scope.isBool(newValue)) { + this.options.preventDefault = newValue? 'always' : 'never'; + return this; } - this.resizeAxes = action.name === 'resize'? action.axis : null; + return this.options.preventDefault; + }, - if (action === 'gesture' && this.pointerIds.length < 2) { - action = null; + /*\ + * Interactable.origin + [ method ] + * + * Gets or sets the origin of the Interactable's element. The x and y + * of the origin will be subtracted from action event coordinates. + * + - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector + * OR + - origin (Element) #optional An HTML or SVG Element whose rect will be used + ** + = (object) The current origin or this Interactable + \*/ + origin: function (newValue) { + if (scope.trySelector(newValue)) { + this.options.origin = newValue; + return this; + } + else if (scope.isObject(newValue)) { + this.options.origin = newValue; + return this; } - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; + return this.options.origin; + }, - this.snapStatus.snappedX = this.snapStatus.snappedY = - this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN; + /*\ + * Interactable.deltaSource + [ method ] + * + * Returns or sets the mouse coordinate types used to calculate the + * movement of the pointer. + * + - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work + = (string | object) The current deltaSource or this Interactable + \*/ + deltaSource: function (newValue) { + if (newValue === 'page' || newValue === 'client') { + this.options.deltaSource = newValue; + + return this; + } - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - extend(this.downPointer, pointer); + return this.options.deltaSource; + }, - this.setEventXY(this.prevCoords); - this.pointerWasMoved = false; + /*\ + * Interactable.restrict + [ method ] + ** + * Deprecated. Add a `restrict` property to the options object passed to + * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead. + * + * Returns or sets the rectangles within which actions on this + * interactable (after snap calculations) are restricted. By default, + * restricting is relative to the pointer coordinates. You can change + * this by setting the + * [`elementRect`](https://github.com/taye/interact.js/pull/72). + ** + - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self' + = (object) The current restrictions object or this Interactable + ** + | interact(element).restrict({ + | // the rect will be `interact.getElementRect(element.parentNode)` + | drag: element.parentNode, + | + | // x and y are relative to the the interactable's origin + | resize: { x: 100, y: 100, width: 200, height: 200 } + | }) + | + | interact('.draggable').restrict({ + | // the rect will be the selected element's parent + | drag: 'parent', + | + | // do not restrict during normal movement. + | // Instead, trigger only one restricted move event + | // immediately before the end event. + | endOnly: true, + | + | // https://github.com/taye/interact.js/pull/72#issue-41813493 + | elementRect: { top: 0, left: 0, bottom: 1, right: 1 } + | }); + \*/ + restrict: function (options) { + if (!scope.isObject(options)) { + return this.setOptions('restrict', options); + } - this.checkAndPreventDefault(event, target, this.element); - } - // if inertia is active try to resume action - else if (this.inertiaStatus.active - && curEventTarget === this.element - && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { + var actions = ['drag', 'resize', 'gesture'], + ret; - cancelFrame(this.inertiaStatus.i); - this.inertiaStatus.active = false; + for (var i = 0; i < actions.length; i++) { + var action = actions[i]; - this.checkAndPreventDefault(event, target, this.element); - } - }, + if (action in options) { + var perAction = utils.extend({ + actions: [action], + restriction: options[action] + }, options); - setModifications: function (coords, preEnd) { - var target = this.target, - shouldMove = true, - shouldSnap = checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), - shouldRestrict = checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); + ret = this.setOptions('restrict', perAction); + } + } - if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } - if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } + return ret; + }, - if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { - shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; - } - else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) { - shouldMove = false; - } + /*\ + * Interactable.context + [ method ] + * + * Gets the selector context Node of the Interactable. The default is `window.document`. + * + = (Node) The context Node of this Interactable + ** + \*/ + context: function () { + return this._context; + }, - return shouldMove; - }, + _context: scope.document, + + /*\ + * Interactable.ignoreFrom + [ method ] + * + * If the target of the `mousedown`, `pointerdown` or `touchstart` + * event or any of it's parents match the given CSS selector or + * Element, no drag/resize/gesture is started. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements + = (string | Element | object) The current ignoreFrom value or this Interactable + ** + | interact(element, { ignoreFrom: document.getElementById('no-action') }); + | // or + | interact(element).ignoreFrom('input, textarea, a'); + \*/ + ignoreFrom: function (newValue) { + if (scope.trySelector(newValue)) { // CSS selector to match event.target + this.options.ignoreFrom = newValue; + return this; + } - setStartOffsets: function (action, interactable, element) { - var rect = interactable.getRect(element), - origin = getOriginXY(interactable, element), - snap = interactable.options[this.prepared.name].snap, - restrict = interactable.options[this.prepared.name].restrict, - width, height; + if (utils.isElement(newValue)) { // specific element + this.options.ignoreFrom = newValue; + return this; + } - if (rect) { - this.startOffset.left = this.startCoords.page.x - rect.left; - this.startOffset.top = this.startCoords.page.y - rect.top; + return this.options.ignoreFrom; + }, - this.startOffset.right = rect.right - this.startCoords.page.x; - this.startOffset.bottom = rect.bottom - this.startCoords.page.y; + /*\ + * Interactable.allowFrom + [ method ] + * + * A drag/resize/gesture is started only If the target of the + * `mousedown`, `pointerdown` or `touchstart` event or any of it's + * parents match the given CSS selector or Element. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element + = (string | Element | object) The current allowFrom value or this Interactable + ** + | interact(element, { allowFrom: document.getElementById('drag-handle') }); + | // or + | interact(element).allowFrom('.handle'); + \*/ + allowFrom: function (newValue) { + if (scope.trySelector(newValue)) { // CSS selector to match event.target + this.options.allowFrom = newValue; + return this; + } - if ('width' in rect) { width = rect.width; } - else { width = rect.right - rect.left; } - if ('height' in rect) { height = rect.height; } - else { height = rect.bottom - rect.top; } - } - else { - this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; - } + if (utils.isElement(newValue)) { // specific element + this.options.allowFrom = newValue; + return this; + } - this.snapOffsets.splice(0); + return this.options.allowFrom; + }, - var snapOffset = snap && snap.offset === 'startCoords' - ? { - x: this.startCoords.page.x - origin.x, - y: this.startCoords.page.y - origin.y - } - : snap && snap.offset || { x: 0, y: 0 }; + /*\ + * Interactable.element + [ method ] + * + * If this is not a selector Interactable, it returns the element this + * interactable represents + * + = (Element) HTML / SVG Element + \*/ + element: function () { + return this._element; + }, - if (rect && snap && snap.relativePoints && snap.relativePoints.length) { - for (var i = 0; i < snap.relativePoints.length; i++) { - this.snapOffsets.push({ - x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x, - y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y - }); + /*\ + * Interactable.fire + [ method ] + * + * Calls listeners for the given InteractEvent type bound globally + * and directly to this Interactable + * + - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable + = (Interactable) this Interactable + \*/ + fire: function (iEvent) { + if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) { + return this; } - } - else { - this.snapOffsets.push(snapOffset); - } - - if (rect && restrict.elementRect) { - this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left); - this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top); - this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right)); - this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom)); - } - else { - this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0; - } - }, + var listeners, + i, + len, + onEvent = 'on' + iEvent.type, + funcName = ''; - /*\ - * Interaction.start - [ method ] - * - * Start an action with the given Interactable and Element as tartgets. The - * action must be enabled for the target Interactable and an appropriate number - * of pointers must be held down – 1 for drag/resize, 2 for gesture. - * - * Use it with `interactable.able({ manualStart: false })` to always - * [start actions manually](https://github.com/taye/interact.js/issues/114) - * - - action (object) The action to be performed - drag, resize, etc. - - interactable (Interactable) The Interactable to target - - element (Element) The DOM Element to target - = (object) interact - ** - | interact(target) - | .draggable({ - | // disable the default drag start by down->move - | manualStart: true - | }) - | // start dragging after the user holds the pointer down - | .on('hold', function (event) { - | var interaction = event.interaction; - | - | if (!interaction.interacting()) { - | interaction.start({ name: 'drag' }, - | event.interactable, - | event.currentTarget); - | } - | }); - \*/ - start: function (action, interactable, element) { - if (this.interacting() - || !this.pointerIsDown - || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { - return; - } + // Interactable#on() listeners + if (iEvent.type in this._iEvents) { + listeners = this._iEvents[iEvent.type]; - // if this interaction had been removed after stopping - // add it back - if (indexOf(interactions, this) === -1) { - interactions.push(this); - } + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + funcName = listeners[i].name; + listeners[i](iEvent); + } + } - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - this.target = interactable; - this.element = element; + // interactable.onevent listener + if (scope.isFunction(this[onEvent])) { + funcName = this[onEvent].name; + this[onEvent](iEvent); + } - this.setEventXY(this.startCoords); - this.setStartOffsets(action.name, interactable, element); - this.setModifications(this.startCoords.page); + // interact.on() listeners + if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { - this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); - }, + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + funcName = listeners[i].name; + listeners[i](iEvent); + } + } - pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { - this.recordPointer(pointer); + return this; + }, - this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) - ? this.inertiaStatus.startEvent - : undefined); + /*\ + * Interactable.on + [ method ] + * + * Binds a listener for an InteractEvent or DOM event. + * + - eventType (string | array | object) The types of events to listen for + - listener (function) The function to be called on the given event(s) + - useCapture (boolean) #optional useCapture flag for addEventListener + = (object) This Interactable + \*/ + on: function (eventType, listener, useCapture) { + var i; + + if (scope.isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } - var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x - && this.curCoords.page.y === this.prevCoords.page.y - && this.curCoords.client.x === this.prevCoords.client.x - && this.curCoords.client.y === this.prevCoords.client.y); + if (scope.isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.on(eventType[i], listener, useCapture); + } - var dx, dy, - pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + return this; + } - // register movement greater than pointerMoveTolerance - if (this.pointerIsDown && !this.pointerWasMoved) { - dx = this.curCoords.client.x - this.startCoords.client.x; - dy = this.curCoords.client.y - this.startCoords.client.y; + if (scope.isObject(eventType)) { + for (var prop in eventType) { + this.on(prop, eventType[prop], listener); + } - this.pointerWasMoved = hypot(dx, dy) > pointerMoveTolerance; - } + return this; + } - if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { - if (this.pointerIsDown) { - clearTimeout(this.holdTimers[pointerIndex]); + if (eventType === 'wheel') { + eventType = scope.wheelEvent; } - this.collectEventTargets(pointer, event, eventTarget, 'move'); - } + // convert to boolean + useCapture = useCapture? true: false; - if (!this.pointerIsDown) { return; } - - if (duplicateMove && this.pointerWasMoved && !preEnd) { - this.checkAndPreventDefault(event, this.target, this.element); - return; - } - - // set pointer coordinate, time changes and speeds - setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - - if (!this.prepared.name) { return; } - - if (this.pointerWasMoved - // ignore movement while inertia is active - && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { + if (scope.contains(scope.eventTypes, eventType)) { + // if this type of event was never bound to this Interactable + if (!(eventType in this._iEvents)) { + this._iEvents[eventType] = [listener]; + } + else { + this._iEvents[eventType].push(listener); + } + } + // delegated event for selector + else if (this.selector) { + if (!scope.delegatedEvents[eventType]) { + scope.delegatedEvents[eventType] = { + selectors: [], + contexts : [], + listeners: [] + }; - // if just starting an action, calculate the pointer speed now - if (!this.interacting()) { - setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + // add delegate listener functions + for (i = 0; i < scope.documents.length; i++) { + events.add(scope.documents[i], eventType, delegateListener); + events.add(scope.documents[i], eventType, delegateUseCapture, true); + } + } - // check if a drag is in the correct axis - if (this.prepared.name === 'drag') { - var absX = Math.abs(dx), - absY = Math.abs(dy), - targetAxis = this.target.options.drag.axis, - axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); + var delegated = scope.delegatedEvents[eventType], + index; - // if the movement isn't in the axis of the interactable - if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { - // cancel the prepared action - this.prepared.name = null; + for (index = delegated.selectors.length - 1; index >= 0; index--) { + if (delegated.selectors[index] === this.selector + && delegated.contexts[index] === this._context) { + break; + } + } - // then try to get a drag from another ineractable + if (index === -1) { + index = delegated.selectors.length; - var element = eventTarget; + delegated.selectors.push(this.selector); + delegated.contexts .push(this._context); + delegated.listeners.push([]); + } - // check element interactables - while (isElement(element)) { - var elementInteractable = interactables.get(element); + // keep listener and useCapture flag + delegated.listeners[index].push([listener, useCapture]); + } + else { + events.add(this._element, eventType, listener, useCapture); + } - if (elementInteractable - && elementInteractable !== this.target - && !elementInteractable.options.drag.manualStart - && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' - && checkAxis(axis, elementInteractable)) { + return this; + }, - this.prepared.name = 'drag'; - this.target = elementInteractable; - this.element = element; - break; - } + /*\ + * Interactable.off + [ method ] + * + * Removes an InteractEvent or DOM event listener + * + - eventType (string | array | object) The types of events that were listened for + - listener (function) The listener function to be removed + - useCapture (boolean) #optional useCapture flag for removeEventListener + = (object) This Interactable + \*/ + off: function (eventType, listener, useCapture) { + var i; + + if (scope.isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } - element = parentElement(element); - } + if (scope.isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.off(eventType[i], listener, useCapture); + } - // if there's no drag from element interactables, - // check the selector interactables - if (!this.prepared.name) { - var thisInteraction = this; + return this; + } - var getDraggable = function (interactable, selector, context) { - var elements = ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; + if (scope.isObject(eventType)) { + for (var prop in eventType) { + this.off(prop, eventType[prop], listener); + } - if (interactable === thisInteraction.target) { return; } + return this; + } - if (inContext(interactable, eventTarget) - && !interactable.options.drag.manualStart - && !testIgnore(interactable, element, eventTarget) - && testAllow(interactable, element, eventTarget) - && matchesSelector(element, selector, elements) - && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' - && checkAxis(axis, interactable) - && withinInteractionLimit(interactable, element, 'drag')) { + var eventList, + index = -1; - return interactable; - } - }; + // convert to boolean + useCapture = useCapture? true: false; - element = eventTarget; + if (eventType === 'wheel') { + eventType = scope.wheelEvent; + } - while (isElement(element)) { - var selectorInteractable = interactables.forEachSelector(getDraggable); + // if it is an action event type + if (scope.contains(scope.eventTypes, eventType)) { + eventList = this._iEvents[eventType]; - if (selectorInteractable) { - this.prepared.name = 'drag'; - this.target = selectorInteractable; - this.element = element; - break; + if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) { + this._iEvents[eventType].splice(index, 1); + } + } + // delegated event + else if (this.selector) { + var delegated = scope.delegatedEvents[eventType], + matchFound = false; + + if (!delegated) { return this; } + + // count from last index of delegated to 0 + for (index = delegated.selectors.length - 1; index >= 0; index--) { + // look for matching selector and context Node + if (delegated.selectors[index] === this.selector + && delegated.contexts[index] === this._context) { + + var listeners = delegated.listeners[index]; + + // each item of the listeners array is an array: [function, useCaptureFlag] + for (i = listeners.length - 1; i >= 0; i--) { + var fn = listeners[i][0], + useCap = listeners[i][1]; + + // check if the listener functions and useCapture flags match + if (fn === listener && useCap === useCapture) { + // remove the listener from the array of listeners + listeners.splice(i, 1); + + // if all listeners for this interactable have been removed + // remove the interactable from the delegated arrays + if (!listeners.length) { + delegated.selectors.splice(index, 1); + delegated.contexts .splice(index, 1); + delegated.listeners.splice(index, 1); + + // remove delegate function from context + events.remove(this._context, eventType, delegateListener); + events.remove(this._context, eventType, delegateUseCapture, true); + + // remove the arrays if they are empty + if (!delegated.selectors.length) { + scope.delegatedEvents[eventType] = null; + } } - element = parentElement(element); + // only remove one listener + matchFound = true; + break; } } + + if (matchFound) { break; } } } } + // remove listener from this Interatable's element + else { + events.remove(this._element, eventType, listener, useCapture); + } - var starting = !!this.prepared.name && !this.interacting(); + return this; + }, - if (starting - && (this.target.options[this.prepared.name].manualStart - || !withinInteractionLimit(this.target, this.element, this.prepared))) { - this.stop(); - return; + /*\ + * Interactable.set + [ method ] + * + * Reset the options of this Interactable + - options (object) The new settings to apply + = (object) This Interactablw + \*/ + set: function (options) { + if (!scope.isObject(options)) { + options = {}; } - if (this.prepared.name && this.target) { - if (starting) { - this.start(this.prepared, this.target, this.element); - } + this.options = utils.extend({}, scope.defaultOptions.base); - var shouldMove = this.setModifications(this.curCoords.page, preEnd); + var i, + actions = ['drag', 'drop', 'resize', 'gesture'], + methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], + perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {}); - // move if snapping or restriction doesn't prevent it - if (shouldMove || starting) { - this.prevEvent = this[this.prepared.name + 'Move'](event); - } + for (i = 0; i < actions.length; i++) { + var action = actions[i]; - this.checkAndPreventDefault(event, this.target, this.element); + this.options[action] = utils.extend({}, scope.defaultOptions[action]); + + this.setPerAction(action, perActions); + + this[methods[i]](options[action]); } - } - copyCoords(this.prevCoords, this.curCoords); + var settings = [ + 'accept', 'actionChecker', 'allowFrom', 'deltaSource', + 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', + 'rectChecker' + ]; - if (this.dragging || this.resizing) { - this.autoScrollMove(pointer); - } - }, + for (i = 0, len = settings.length; i < len; i++) { + var setting = settings[i]; - dragStart: function (event) { - var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); + this.options[setting] = scope.defaultOptions.base[setting]; - this.dragging = true; - this.target.fire(dragEvent); + if (setting in options) { + this[setting](options[setting]); + } + } - // reset active dropzones - this.activeDrops.dropzones = []; - this.activeDrops.elements = []; - this.activeDrops.rects = []; + return this; + }, - if (!this.dynamicDrop) { - this.setActiveDrops(this.element); - } + /*\ + * Interactable.unset + [ method ] + * + * Remove this interactable from the list of interactables and remove + * it's drag, drop, resize and gesture capabilities + * + = (object) @interact + \*/ + unset: function () { + events.remove(this._element, 'all'); + + if (!scope.isString(this.selector)) { + events.remove(this, 'all'); + if (this.options.styleCursor) { + this._element.style.cursor = ''; + } + } + else { + // remove delegated events + for (var type in scope.delegatedEvents) { + var delegated = scope.delegatedEvents[type]; - var dropEvents = this.getDropEvents(event, dragEvent); + for (var i = 0; i < delegated.selectors.length; i++) { + if (delegated.selectors[i] === this.selector + && delegated.contexts[i] === this._context) { - if (dropEvents.activate) { - this.fireActiveDrops(dropEvents.activate); - } + delegated.selectors.splice(i, 1); + delegated.contexts .splice(i, 1); + delegated.listeners.splice(i, 1); - return dragEvent; - }, + // remove the arrays if they are empty + if (!delegated.selectors.length) { + scope.delegatedEvents[type] = null; + } + } - dragMove: function (event) { - var target = this.target, - dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), - draggableElement = this.element, - drop = this.getDrop(event, draggableElement); + events.remove(this._context, type, delegateListener); + events.remove(this._context, type, delegateUseCapture, true); - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; + break; + } + } + } - var dropEvents = this.getDropEvents(event, dragEvent); + this.dropzone(false); - target.fire(dragEvent); + scope.interactables.splice(scope.indexOf(scope.interactables, this), 1); - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } + return interact; + } + }; - this.prevDropTarget = this.dropTarget; - this.prevDropElement = this.dropElement; + Interactable.prototype.snap = utils.warnOnce(Interactable.prototype.snap, + 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping'); + Interactable.prototype.restrict = utils.warnOnce(Interactable.prototype.restrict, + 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction'); + Interactable.prototype.inertia = utils.warnOnce(Interactable.prototype.inertia, + 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia'); + Interactable.prototype.autoScroll = utils.warnOnce(Interactable.prototype.autoScroll, + 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll'); + Interactable.prototype.squareResize = utils.warnOnce(Interactable.prototype.squareResize, + 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square'); - return dragEvent; - }, + /*\ + * interact.isSet + [ method ] + * + * Check if an element has been set + - element (Element) The Element being searched for + = (boolean) Indicates if the element or CSS selector was previously passed to interact + \*/ + interact.isSet = function(element, options) { + return scope.interactables.indexOfElement(element, options && options.context) !== -1; + }; - resizeStart: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); + /*\ + * interact.on + [ method ] + * + * Adds a global listener for an InteractEvent or adds a DOM event to + * `document` + * + - type (string | array | object) The types of events to listen for + - listener (function) The function to be called on the given event(s) + - useCapture (boolean) #optional useCapture flag for addEventListener + = (object) interact + \*/ + interact.on = function (type, listener, useCapture) { + if (scope.isString(type) && type.search(' ') !== -1) { + type = type.trim().split(/ +/); + } - if (this.prepared.edges) { - var startRect = this.target.getRect(this.element); + if (scope.isArray(type)) { + for (var i = 0; i < type.length; i++) { + interact.on(type[i], listener, useCapture); + } - if (this.target.options.resize.square) { - var squareEdges = extend({}, this.prepared.edges); + return interact; + } - squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); - squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); - squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); - squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); + if (scope.isObject(type)) { + for (var prop in type) { + interact.on(prop, type[prop], listener); + } - this.prepared._squareEdges = squareEdges; + return interact; + } + + // if it is an InteractEvent type, add listener to globalEvents + if (scope.contains(scope.eventTypes, type)) { + // if this type of event was never bound + if (!scope.globalEvents[type]) { + scope.globalEvents[type] = [listener]; } else { - this.prepared._squareEdges = null; + scope.globalEvents[type].push(listener); } + } + // If non InteractEvent type, addEventListener to document + else { + events.add(scope.document, type, listener, useCapture); + } - this.resizeRects = { - start : startRect, - current : extend({}, startRect), - restricted: extend({}, startRect), - previous : extend({}, startRect), - delta : { - left: 0, right : 0, width : 0, - top : 0, bottom: 0, height: 0 - } - }; + return interact; + }; - resizeEvent.rect = this.resizeRects.restricted; - resizeEvent.deltaRect = this.resizeRects.delta; + /*\ + * interact.off + [ method ] + * + * Removes a global InteractEvent listener or DOM event from `document` + * + - type (string | array | object) The types of events that were listened for + - listener (function) The listener function to be removed + - useCapture (boolean) #optional useCapture flag for removeEventListener + = (object) interact + \*/ + interact.off = function (type, listener, useCapture) { + if (scope.isString(type) && type.search(' ') !== -1) { + type = type.trim().split(/ +/); } - this.target.fire(resizeEvent); + if (scope.isArray(type)) { + for (var i = 0; i < type.length; i++) { + interact.off(type[i], listener, useCapture); + } - this.resizing = true; + return interact; + } - return resizeEvent; - }, + if (scope.isObject(type)) { + for (var prop in type) { + interact.off(prop, type[prop], listener); + } - resizeMove: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); + return interact; + } - var edges = this.prepared.edges, - invert = this.target.options.resize.invert, - invertible = invert === 'reposition' || invert === 'negate'; + if (!scope.contains(scope.eventTypes, type)) { + events.remove(scope.document, type, listener, useCapture); + } + else { + var index; - if (edges) { - var dx = resizeEvent.dx, - dy = resizeEvent.dy, + if (type in scope.globalEvents + && (index = scope.indexOf(scope.globalEvents[type], listener)) !== -1) { + scope.globalEvents[type].splice(index, 1); + } + } - start = this.resizeRects.start, - current = this.resizeRects.current, - restricted = this.resizeRects.restricted, - delta = this.resizeRects.delta, - previous = extend(this.resizeRects.previous, restricted); + return interact; + }; - if (this.target.options.resize.square) { - var originalEdges = edges; + /*\ + * interact.enableDragging + [ method ] + * + * Deprecated. + * + * Returns or sets whether dragging is enabled for any Interactables + * + - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables + = (boolean | object) The current setting or interact + \*/ + interact.enableDragging = utils.warnOnce(function (newValue) { + if (newValue !== null && newValue !== undefined) { + scope.actionIsEnabled.drag = newValue; - edges = this.prepared._squareEdges; + return interact; + } + return scope.actionIsEnabled.drag; + }, 'interact.enableDragging is deprecated and will soon be removed.'); - if ((originalEdges.left && originalEdges.bottom) - || (originalEdges.right && originalEdges.top)) { - dy = -dx; - } - else if (originalEdges.left || originalEdges.right) { dy = dx; } - else if (originalEdges.top || originalEdges.bottom) { dx = dy; } - } + /*\ + * interact.enableResizing + [ method ] + * + * Deprecated. + * + * Returns or sets whether resizing is enabled for any Interactables + * + - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables + = (boolean | object) The current setting or interact + \*/ + interact.enableResizing = utils.warnOnce(function (newValue) { + if (newValue !== null && newValue !== undefined) { + scope.actionIsEnabled.resize = newValue; - // update the 'current' rect without modifications - if (edges.top ) { current.top += dy; } - if (edges.bottom) { current.bottom += dy; } - if (edges.left ) { current.left += dx; } - if (edges.right ) { current.right += dx; } - - if (invertible) { - // if invertible, copy the current rect - extend(restricted, current); - - if (invert === 'reposition') { - // swap edge values if necessary to keep width/height positive - var swap; - - if (restricted.top > restricted.bottom) { - swap = restricted.top; - - restricted.top = restricted.bottom; - restricted.bottom = swap; - } - if (restricted.left > restricted.right) { - swap = restricted.left; - - restricted.left = restricted.right; - restricted.right = swap; - } - } - } - else { - // if not invertible, restrict to minimum of 0x0 rect - restricted.top = Math.min(current.top, start.bottom); - restricted.bottom = Math.max(current.bottom, start.top); - restricted.left = Math.min(current.left, start.right); - restricted.right = Math.max(current.right, start.left); - } - - restricted.width = restricted.right - restricted.left; - restricted.height = restricted.bottom - restricted.top ; - - for (var edge in restricted) { - delta[edge] = restricted[edge] - previous[edge]; - } - - resizeEvent.edges = this.prepared.edges; - resizeEvent.rect = restricted; - resizeEvent.deltaRect = delta; + return interact; } + return scope.actionIsEnabled.resize; + }, 'interact.enableResizing is deprecated and will soon be removed.'); - this.target.fire(resizeEvent); + /*\ + * interact.enableGesturing + [ method ] + * + * Deprecated. + * + * Returns or sets whether gesturing is enabled for any Interactables + * + - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables + = (boolean | object) The current setting or interact + \*/ + interact.enableGesturing = utils.warnOnce(function (newValue) { + if (newValue !== null && newValue !== undefined) { + scope.actionIsEnabled.gesture = newValue; - return resizeEvent; - }, + return interact; + } + return scope.actionIsEnabled.gesture; + }, 'interact.enableGesturing is deprecated and will soon be removed.'); - gestureStart: function (event) { - var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); + interact.eventTypes = scope.eventTypes; - gestureEvent.ds = 0; + /*\ + * interact.debug + [ method ] + * + * Returns debugging data + = (object) An object with properties that outline the current state and expose internal functions and variables + \*/ + interact.debug = function () { + var interaction = scope.interactions[0] || new Interaction(); - this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; - this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; - this.gesture.scale = 1; + return { + interactions : scope.interactions, + target : interaction.target, + dragging : interaction.dragging, + resizing : interaction.resizing, + gesturing : interaction.gesturing, + prepared : interaction.prepared, + matches : interaction.matches, + matchElements : interaction.matchElements, + + prevCoords : interaction.prevCoords, + startCoords : interaction.startCoords, + + pointerIds : interaction.pointerIds, + pointers : interaction.pointers, + addPointer : scope.listeners.addPointer, + removePointer : scope.listeners.removePointer, + recordPointer : scope.listeners.recordPointer, + + snap : interaction.snapStatus, + restrict : interaction.restrictStatus, + inertia : interaction.inertiaStatus, + + downTime : interaction.downTimes[0], + downEvent : interaction.downEvent, + downPointer : interaction.downPointer, + prevEvent : interaction.prevEvent, + + Interactable : Interactable, + interactables : scope.interactables, + pointerIsDown : interaction.pointerIsDown, + defaultOptions : scope.defaultOptions, + defaultActionChecker : defaultActionChecker, + + actionCursors : scope.actionCursors, + dragMove : scope.listeners.dragMove, + resizeMove : scope.listeners.resizeMove, + gestureMove : scope.listeners.gestureMove, + pointerUp : scope.listeners.pointerUp, + pointerDown : scope.listeners.pointerDown, + pointerMove : scope.listeners.pointerMove, + pointerHover : scope.listeners.pointerHover, + + eventTypes : scope.eventTypes, + + events : events, + globalEvents : scope.globalEvents, + delegatedEvents : scope.delegatedEvents + }; + }; - this.gesturing = true; + // expose the functions used to calculate multi-touch properties + interact.getTouchAverage = utils.touchAverage; + interact.getTouchBBox = utils.touchBBox; + interact.getTouchDistance = utils.touchDistance; + interact.getTouchAngle = utils.touchAngle; - this.target.fire(gestureEvent); + interact.getElementRect = scope.getElementRect; + interact.matchesSelector = scope.matchesSelector; + interact.closest = scope.closest; - return gestureEvent; - }, + /*\ + * interact.margin + [ method ] + * + * Returns or sets the margin for autocheck resizing used in + * @Interactable.getAction. That is the distance from the bottom and right + * edges of an element clicking in which will start resizing + * + - newValue (number) #optional + = (number | interact) The current margin value or interact + \*/ + interact.margin = function (newvalue) { + if (scope.isNumber(newvalue)) { + scope.margin = newvalue; - gestureMove: function (event) { - if (!this.pointerIds.length) { - return this.prevEvent; + return interact; } + return scope.margin; + }; - var gestureEvent; + /*\ + * interact.supportsTouch + [ method ] + * + = (boolean) Whether or not the browser supports touch input + \*/ + interact.supportsTouch = function () { + return browser.supportsTouch; + }; - gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); - gestureEvent.ds = gestureEvent.scale - this.gesture.scale; + /*\ + * interact.supportsPointerEvent + [ method ] + * + = (boolean) Whether or not the browser supports PointerEvents + \*/ + interact.supportsPointerEvent = function () { + return browser.supportsPointerEvent; + }; - this.target.fire(gestureEvent); + /*\ + * interact.stop + [ method ] + * + * Cancels all interactions (end events are not fired) + * + - event (Event) An event on which to call preventDefault() + = (object) interact + \*/ + interact.stop = function (event) { + for (var i = scope.interactions.length - 1; i > 0; i--) { + scope.interactions[i].stop(event); + } - this.gesture.prevAngle = gestureEvent.angle; - this.gesture.prevDistance = gestureEvent.distance; + return interact; + }; - if (gestureEvent.scale !== Infinity && - gestureEvent.scale !== null && - gestureEvent.scale !== undefined && - !isNaN(gestureEvent.scale)) { + /*\ + * interact.dynamicDrop + [ method ] + * + * Returns or sets whether the dimensions of dropzone elements are + * calculated on every dragmove or only on dragstart for the default + * dropChecker + * + - newValue (boolean) #optional True to check on each move. False to check only before start + = (boolean | interact) The current setting or interact + \*/ + interact.dynamicDrop = function (newValue) { + if (scope.isBool(newValue)) { + //if (dragging && dynamicDrop !== newValue && !newValue) { + //calcRects(dropzones); + //} - this.gesture.scale = gestureEvent.scale; - } + scope.dynamicDrop = newValue; - return gestureEvent; - }, + return interact; + } + return scope.dynamicDrop; + }; - pointerHold: function (pointer, event, eventTarget) { - this.collectEventTargets(pointer, event, eventTarget, 'hold'); - }, + /*\ + * interact.pointerMoveTolerance + [ method ] + * Returns or sets the distance the pointer must be moved before an action + * sequence occurs. This also affects tolerance for tap events. + * + - newValue (number) #optional The movement from the start position must be greater than this value + = (number | Interactable) The current setting or interact + \*/ + interact.pointerMoveTolerance = function (newValue) { + if (scope.isNumber(newValue)) { + scope.pointerMoveTolerance = newValue; - pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + return this; + } - clearTimeout(this.holdTimers[pointerIndex]); + return scope.pointerMoveTolerance; + }; - this.collectEventTargets(pointer, event, eventTarget, 'up' ); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); + /*\ + * interact.maxInteractions + [ method ] + ** + * Returns or sets the maximum number of concurrent interactions allowed. + * By default only 1 interaction is allowed at a time (for backwards + * compatibility). To allow multiple interactions on the same Interactables + * and elements, you need to enable it in the draggable, resizable and + * gesturable `'max'` and `'maxPerElement'` options. + ** + - newValue (number) #optional Any number. newValue <= 0 means no interactions. + \*/ + interact.maxInteractions = function (newValue) { + if (scope.isNumber(newValue)) { + scope.maxInteractions = newValue; - this.pointerEnd(pointer, event, eventTarget, curEventTarget); + return this; + } - this.removePointer(pointer); - }, + return scope.maxInteractions; + }; - pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + interact.createSnapGrid = function (grid) { + return function (x, y) { + var offsetX = 0, + offsetY = 0; - clearTimeout(this.holdTimers[pointerIndex]); + if (scope.isObject(grid.offset)) { + offsetX = grid.offset.x; + offsetY = grid.offset.y; + } - this.collectEventTargets(pointer, event, eventTarget, 'cancel'); - this.pointerEnd(pointer, event, eventTarget, curEventTarget); + var gridx = Math.round((x - offsetX) / grid.x), + gridy = Math.round((y - offsetY) / grid.y), - this.removePointer(pointer); - }, + newX = gridx * grid.x + offsetX, + newY = gridy * grid.y + offsetY; - // http://www.quirksmode.org/dom/events/click.html - // >Events leading to dblclick - // - // IE8 doesn't fire down event before dblclick. - // This workaround tries to fire a tap and doubletap after dblclick - ie8Dblclick: function (pointer, event, eventTarget) { - if (this.prevTap - && event.clientX === this.prevTap.clientX - && event.clientY === this.prevTap.clientY - && eventTarget === this.prevTap.target) { + return { + x: newX, + y: newY, + range: grid.range + }; + }; + }; - this.downTargets[0] = eventTarget; - this.downTimes[0] = new Date().getTime(); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); + function endAllInteractions (event) { + for (var i = 0; i < scope.interactions.length; i++) { + scope.interactions[i].pointerEnd(event, event); } - }, - - // End interact move events and stop auto-scroll unless inertia is enabled - pointerEnd: function (pointer, event, eventTarget, curEventTarget) { - var endEvent, - target = this.target, - options = target && target.options, - inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, - inertiaStatus = this.inertiaStatus; + } - if (this.interacting()) { + function listenToDocument (doc) { + if (scope.contains(scope.documents, doc)) { return; } - if (inertiaStatus.active) { return; } + var win = doc.defaultView || doc.parentWindow; - var pointerSpeed, - now = new Date().getTime(), - inertiaPossible = false, - inertia = false, - smoothEnd = false, - endSnap = checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, - endRestrict = checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, - dx = 0, - dy = 0, - startEvent; + // add delegate event listener + for (var eventType in scope.delegatedEvents) { + events.add(doc, eventType, delegateListener); + events.add(doc, eventType, delegateUseCapture, true); + } - if (this.dragging) { - if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } - else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } - else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } + if (scope.PointerEvent) { + if (scope.PointerEvent === win.MSPointerEvent) { + scope.pEventTypes = { + up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', + out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' }; } else { - pointerSpeed = this.pointerDelta.client.speed; + scope.pEventTypes = { + up: 'pointerup', down: 'pointerdown', over: 'pointerover', + out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }; } - // check if inertia should be started - inertiaPossible = (inertiaOptions && inertiaOptions.enabled - && this.prepared.name !== 'gesture' - && event !== inertiaStatus.startEvent); + events.add(doc, scope.pEventTypes.down , scope.listeners.selectorDown ); + events.add(doc, scope.pEventTypes.move , scope.listeners.pointerMove ); + events.add(doc, scope.pEventTypes.over , scope.listeners.pointerOver ); + events.add(doc, scope.pEventTypes.out , scope.listeners.pointerOut ); + events.add(doc, scope.pEventTypes.up , scope.listeners.pointerUp ); + events.add(doc, scope.pEventTypes.cancel, scope.listeners.pointerCancel); - inertia = (inertiaPossible - && (now - this.curCoords.timeStamp) < 50 - && pointerSpeed > inertiaOptions.minSpeed - && pointerSpeed > inertiaOptions.endSpeed); + // autoscroll + events.add(doc, scope.pEventTypes.move, scope.listeners.autoScrollMove); + } + else { + events.add(doc, 'mousedown', scope.listeners.selectorDown); + events.add(doc, 'mousemove', scope.listeners.pointerMove ); + events.add(doc, 'mouseup' , scope.listeners.pointerUp ); + events.add(doc, 'mouseover', scope.listeners.pointerOver ); + events.add(doc, 'mouseout' , scope.listeners.pointerOut ); + + events.add(doc, 'touchstart' , scope.listeners.selectorDown ); + events.add(doc, 'touchmove' , scope.listeners.pointerMove ); + events.add(doc, 'touchend' , scope.listeners.pointerUp ); + events.add(doc, 'touchcancel', scope.listeners.pointerCancel); + + // autoscroll + events.add(doc, 'mousemove', scope.listeners.autoScrollMove); + events.add(doc, 'touchmove', scope.listeners.autoScrollMove); + } + + events.add(win, 'blur', endAllInteractions); + + try { + if (win.frameElement) { + var parentDoc = win.frameElement.ownerDocument, + parentWindow = parentDoc.defaultView; + + events.add(parentDoc , 'mouseup' , scope.listeners.pointerEnd); + events.add(parentDoc , 'touchend' , scope.listeners.pointerEnd); + events.add(parentDoc , 'touchcancel' , scope.listeners.pointerEnd); + events.add(parentDoc , 'pointerup' , scope.listeners.pointerEnd); + events.add(parentDoc , 'MSPointerUp' , scope.listeners.pointerEnd); + events.add(parentWindow, 'blur' , endAllInteractions ); + } + } + catch (error) { + interact.windowParentError = error; + } - if (inertiaPossible && !inertia && (endSnap || endRestrict)) { + if (events.useAttachEvent) { + // For IE's lack of Event#preventDefault + events.add(doc, 'selectstart', function (event) { + var interaction = scope.interactions[0]; - var snapRestrict = {}; + if (interaction.currentAction()) { + interaction.checkAndPreventDefault(event); + } + }); - snapRestrict.snap = snapRestrict.restrict = snapRestrict; + // For IE's bad dblclick event sequence + events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick')); + } - if (endSnap) { - this.setSnapping(this.curCoords.page, snapRestrict); - if (snapRestrict.locked) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } + scope.documents.push(doc); + } - if (endRestrict) { - this.setRestriction(this.curCoords.page, snapRestrict); - if (snapRestrict.restricted) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } + listenToDocument(scope.document); - if (dx || dy) { - smoothEnd = true; - } - } + scope.interact = interact; + scope.Interactable = Interactable; + scope.Interaction = Interaction; + scope.InteractEvent = InteractEvent; - if (inertia || smoothEnd) { - copyCoords(inertiaStatus.upCoords, this.curCoords); + /* global exports: true, module, define */ - this.pointers[0] = inertiaStatus.startEvent = startEvent = - new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); + // http://documentcloud.github.io/underscore/docs/underscore.html#section-11 + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = interact; + } + exports.interact = interact; + } + // AMD + else if (typeof define === 'function' && define.amd) { + define('interact', function() { + return interact; + }); + } + else { + scope.realWindow.interact = interact; + } - inertiaStatus.t0 = now; +},{"./InteractEvent":2,"./Interaction":3,"./autoScroll":4,"./defaultOptions":5,"./scope":6,"./utils":13,"./utils/events":10,"./utils/window":17}],2:[function(require,module,exports){ +'use strict'; - target.fire(inertiaStatus.startEvent); +var scope = require('./scope'); +var utils = require('./utils'); - if (inertia) { - inertiaStatus.vx0 = this.pointerDelta.client.vx; - inertiaStatus.vy0 = this.pointerDelta.client.vy; - inertiaStatus.v0 = pointerSpeed; +function InteractEvent (interaction, event, action, phase, element, related) { + var client, + page, + target = interaction.target, + snapStatus = interaction.snapStatus, + restrictStatus = interaction.restrictStatus, + pointers = interaction.pointers, + deltaSource = (target && target.options || scope.defaultOptions).deltaSource, + sourceX = deltaSource + 'X', + sourceY = deltaSource + 'Y', + options = target? target.options: scope.defaultOptions, + origin = scope.getOriginXY(target, element), + starting = phase === 'start', + ending = phase === 'end', + coords = starting? interaction.startCoords : interaction.curCoords; - this.calcInertia(inertiaStatus); + element = element || interaction.element; - var page = extend({}, this.curCoords.page), - origin = getOriginXY(target, this.element), - statusObject; + page = utils.extend({}, coords.page); + client = utils.extend({}, coords.client); - page.x = page.x + inertiaStatus.xe - origin.x; - page.y = page.y + inertiaStatus.ye - origin.y; + page.x -= origin.x; + page.y -= origin.y; - statusObject = { - useStatusXY: true, - x: page.x, - y: page.y, - dx: 0, - dy: 0, - snap: null - }; + client.x -= origin.x; + client.y -= origin.y; - statusObject.snap = statusObject; + var relativePoints = options[action].snap && options[action].snap.relativePoints ; - dx = dy = 0; + if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { + this.snap = { + range : snapStatus.range, + locked : snapStatus.locked, + x : snapStatus.snappedX, + y : snapStatus.snappedY, + realX : snapStatus.realX, + realY : snapStatus.realY, + dx : snapStatus.dx, + dy : snapStatus.dy + }; - if (endSnap) { - var snap = this.setSnapping(this.curCoords.page, statusObject); + if (snapStatus.locked) { + page.x += snapStatus.dx; + page.y += snapStatus.dy; + client.x += snapStatus.dx; + client.y += snapStatus.dy; + } + } - if (snap.locked) { - dx += snap.dx; - dy += snap.dy; - } - } + if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { + page.x += restrictStatus.dx; + page.y += restrictStatus.dy; + client.x += restrictStatus.dx; + client.y += restrictStatus.dy; - if (endRestrict) { - var restrict = this.setRestriction(this.curCoords.page, statusObject); + this.restrict = { + dx: restrictStatus.dx, + dy: restrictStatus.dy + }; + } - if (restrict.restricted) { - dx += restrict.dx; - dy += restrict.dy; - } - } + this.pageX = page.x; + this.pageY = page.y; + this.clientX = client.x; + this.clientY = client.y; - inertiaStatus.modifiedXe += dx; - inertiaStatus.modifiedYe += dy; + this.x0 = interaction.startCoords.page.x - origin.x; + this.y0 = interaction.startCoords.page.y - origin.y; + this.clientX0 = interaction.startCoords.client.x - origin.x; + this.clientY0 = interaction.startCoords.client.y - origin.y; + this.ctrlKey = event.ctrlKey; + this.altKey = event.altKey; + this.shiftKey = event.shiftKey; + this.metaKey = event.metaKey; + this.button = event.button; + this.target = element; + this.t0 = interaction.downTimes[0]; + this.type = action + (phase || ''); - inertiaStatus.i = reqFrame(this.boundInertiaFrame); - } - else { - inertiaStatus.smoothEnd = true; - inertiaStatus.xe = dx; - inertiaStatus.ye = dy; + this.interaction = interaction; + this.interactable = target; - inertiaStatus.sx = inertiaStatus.sy = 0; + var inertiaStatus = interaction.inertiaStatus; - inertiaStatus.i = reqFrame(this.boundSmoothEndFrame); - } + if (inertiaStatus.active) { + this.detail = 'inertia'; + } - inertiaStatus.active = true; - return; - } + if (related) { + this.relatedTarget = related; + } - if (endSnap || endRestrict) { - // fire a move event at the snapped coordinates - this.pointerMove(pointer, event, eventTarget, curEventTarget, true); - } + // end event dx, dy is difference between start and end points + if (ending) { + if (deltaSource === 'client') { + this.dx = client.x - interaction.startCoords.client.x; + this.dy = client.y - interaction.startCoords.client.y; } + else { + this.dx = page.x - interaction.startCoords.page.x; + this.dy = page.y - interaction.startCoords.page.y; + } + } + else if (starting) { + this.dx = 0; + this.dy = 0; + } + // copy properties from previousmove if starting inertia + else if (phase === 'inertiastart') { + this.dx = interaction.prevEvent.dx; + this.dy = interaction.prevEvent.dy; + } + else { + if (deltaSource === 'client') { + this.dx = client.x - interaction.prevEvent.clientX; + this.dy = client.y - interaction.prevEvent.clientY; + } + else { + this.dx = page.x - interaction.prevEvent.pageX; + this.dy = page.y - interaction.prevEvent.pageY; + } + } + if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' + && !inertiaStatus.active + && options[action].inertia && options[action].inertia.zeroResumeDelta) { - if (this.dragging) { - endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); - - var draggableElement = this.element, - drop = this.getDrop(event, draggableElement); - - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; + inertiaStatus.resumeDx += this.dx; + inertiaStatus.resumeDy += this.dy; - var dropEvents = this.getDropEvents(event, endEvent); + this.dx = this.dy = 0; + } - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } - if (dropEvents.deactivate) { - this.fireActiveDrops(dropEvents.deactivate); + if (action === 'resize' && interaction.resizeAxes) { + if (options.resize.square) { + if (interaction.resizeAxes === 'y') { + this.dx = this.dy; } + else { + this.dy = this.dx; + } + this.axes = 'xy'; + } + else { + this.axes = interaction.resizeAxes; - target.fire(endEvent); + if (interaction.resizeAxes === 'x') { + this.dy = 0; + } + else if (interaction.resizeAxes === 'y') { + this.dx = 0; + } } - else if (this.resizing) { - endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); - target.fire(endEvent); + } + else if (action === 'gesture') { + this.touches = [pointers[0], pointers[1]]; + + if (starting) { + this.distance = utils.touchDistance(pointers, deltaSource); + this.box = utils.touchBBox(pointers); + this.scale = 1; + this.ds = 0; + this.angle = utils.touchAngle(pointers, undefined, deltaSource); + this.da = 0; } - else if (this.gesturing) { - endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); - target.fire(endEvent); + else if (ending || event instanceof InteractEvent) { + this.distance = interaction.prevEvent.distance; + this.box = interaction.prevEvent.box; + this.scale = interaction.prevEvent.scale; + this.ds = this.scale - 1; + this.angle = interaction.prevEvent.angle; + this.da = this.angle - interaction.gesture.startAngle; } + else { + this.distance = utils.touchDistance(pointers, deltaSource); + this.box = utils.touchBBox(pointers); + this.scale = this.distance / interaction.gesture.startDistance; + this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); - this.stop(event); - }, - - collectDrops: function (element) { - var drops = [], - elements = [], - i; + this.ds = this.scale - interaction.gesture.prevScale; + this.da = this.angle - interaction.gesture.prevAngle; + } + } - element = element || this.element; + if (starting) { + this.timeStamp = interaction.downTimes[0]; + this.dt = 0; + this.duration = 0; + this.speed = 0; + this.velocityX = 0; + this.velocityY = 0; + } + else if (phase === 'inertiastart') { + this.timeStamp = interaction.prevEvent.timeStamp; + this.dt = interaction.prevEvent.dt; + this.duration = interaction.prevEvent.duration; + this.speed = interaction.prevEvent.speed; + this.velocityX = interaction.prevEvent.velocityX; + this.velocityY = interaction.prevEvent.velocityY; + } + else { + this.timeStamp = new Date().getTime(); + this.dt = this.timeStamp - interaction.prevEvent.timeStamp; + this.duration = this.timeStamp - interaction.downTimes[0]; - // collect all dropzones and their elements which qualify for a drop - for (i = 0; i < interactables.length; i++) { - if (!interactables[i].options.drop.enabled) { continue; } + if (event instanceof InteractEvent) { + var dx = this[sourceX] - interaction.prevEvent[sourceX], + dy = this[sourceY] - interaction.prevEvent[sourceY], + dt = this.dt / 1000; - var current = interactables[i], - accept = current.options.drop.accept; + this.speed = utils.hypot(dx, dy) / dt; + this.velocityX = dx / dt; + this.velocityY = dy / dt; + } + // if normal move or end event, use previous user event coords + else { + // speed and velocity in pixels per second + this.speed = interaction.pointerDelta[deltaSource].speed; + this.velocityX = interaction.pointerDelta[deltaSource].vx; + this.velocityY = interaction.pointerDelta[deltaSource].vy; + } + } - // test the draggable element against the dropzone's accept setting - if ((isElement(accept) && accept !== element) - || (isString(accept) - && !matchesSelector(element, accept))) { + if ((ending || phase === 'inertiastart') + && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) { - continue; - } + var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI, + overlap = 22.5; - // query for new elements if necessary - var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; + if (angle < 0) { + angle += 360; + } - for (var j = 0, len = dropElements.length; j < len; j++) { - var currentElement = dropElements[j]; + var left = 135 - overlap <= angle && angle < 225 + overlap, + up = 225 - overlap <= angle && angle < 315 + overlap, - if (currentElement === element) { - continue; - } + right = !left && (315 - overlap <= angle || angle < 45 + overlap), + down = !up && 45 - overlap <= angle && angle < 135 + overlap; - drops.push(current); - elements.push(currentElement); + this.swipe = { + up : up, + down : down, + left : left, + right: right, + angle: angle, + speed: interaction.prevEvent.speed, + velocity: { + x: interaction.prevEvent.velocityX, + y: interaction.prevEvent.velocityY } - } - - return { - dropzones: drops, - elements: elements }; + } +} + +InteractEvent.prototype = { + preventDefault: utils.blank, + stopImmediatePropagation: function () { + this.immediatePropagationStopped = this.propagationStopped = true; }, + stopPropagation: function () { + this.propagationStopped = true; + } +}; - fireActiveDrops: function (event) { - var i, - current, - currentElement, - prevElement; +module.exports = InteractEvent; - // loop through all active dropzones and trigger event - for (i = 0; i < this.activeDrops.dropzones.length; i++) { - current = this.activeDrops.dropzones[i]; - currentElement = this.activeDrops.elements [i]; +},{"./scope":6,"./utils":13}],3:[function(require,module,exports){ +'use strict'; - // prevent trigger of duplicate events on same element - if (currentElement !== prevElement) { - // set current element as event target - event.target = currentElement; - current.fire(event); - } - prevElement = currentElement; - } - }, +var scope = require('./scope'); +var utils = require('./utils'); +var animationFrame = utils.raf; +var InteractEvent = require('./InteractEvent'); +var events = require('./utils/events'); +var browser = require('./utils/browser'); - // Collect a new set of possible drops and save them in activeDrops. - // setActiveDrops should always be called when a drag has just started or a - // drag event happens while dynamicDrop is true - setActiveDrops: function (dragElement) { - // get dropzones and their elements that could receive the draggable - var possibleDrops = this.collectDrops(dragElement, true); +function Interaction () { + this.target = null; // current interactable being interacted with + this.element = null; // the target element of the interactable + this.dropTarget = null; // the dropzone a drag target might be dropped into + this.dropElement = null; // the element at the time of checking + this.prevDropTarget = null; // the dropzone that was recently dragged away from + this.prevDropElement = null; // the element at the time of checking - this.activeDrops.dropzones = possibleDrops.dropzones; - this.activeDrops.elements = possibleDrops.elements; - this.activeDrops.rects = []; + this.prepared = { // action that's ready to be fired on next move event + name : null, + axis : null, + edges: null + }; - for (var i = 0; i < this.activeDrops.dropzones.length; i++) { - this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); + this.matches = []; // all selectors that are matched by target element + this.matchElements = []; // corresponding elements + + this.inertiaStatus = { + active : false, + smoothEnd : false, + + startEvent: null, + upCoords: {}, + + xe: 0, ye: 0, + sx: 0, sy: 0, + + t0: 0, + vx0: 0, vys: 0, + duration: 0, + + resumeDx: 0, + resumeDy: 0, + + lambda_v0: 0, + one_ve_v0: 0, + i : null + }; + + if (scope.isFunction(Function.prototype.bind)) { + this.boundInertiaFrame = this.inertiaFrame.bind(this); + this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); + } + else { + var that = this; + + this.boundInertiaFrame = function () { return that.inertiaFrame(); }; + this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); }; + } + + this.activeDrops = { + dropzones: [], // the dropzones that are mentioned below + elements : [], // elements of dropzones that accept the target draggable + rects : [] // the rects of the elements mentioned above + }; + + // keep track of added pointers + this.pointers = []; + this.pointerIds = []; + this.downTargets = []; + this.downTimes = []; + this.holdTimers = []; + + // Previous native pointer move event coordinates + this.prevCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + // current native pointer move event coordinates + this.curCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + + // Starting InteractEvent pointer coordinates + this.startCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + + // Change in coordinates and time of the pointer + this.pointerDelta = { + page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + timeStamp: 0 + }; + + this.downEvent = null; // pointerdown/mousedown/touchstart event + this.downPointer = {}; + + this._eventTarget = null; + this._curEventTarget = null; + + this.prevEvent = null; // previous action event + this.tapTime = 0; // time of the most recent tap event + this.prevTap = null; + + this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.snapOffsets = []; + + this.gesture = { + start: { x: 0, y: 0 }, + + startDistance: 0, // distance between two touches of touchStart + prevDistance : 0, + distance : 0, + + scale: 1, // gesture.distance / gesture.startDistance + + startAngle: 0, // angle of line joining two touches + prevAngle : 0 // angle of the previous gesture event + }; + + this.snapStatus = { + x : 0, y : 0, + dx : 0, dy : 0, + realX : 0, realY : 0, + snappedX: 0, snappedY: 0, + targets : [], + locked : false, + changed : false + }; + + this.restrictStatus = { + dx : 0, dy : 0, + restrictedX: 0, restrictedY: 0, + snap : null, + restricted : false, + changed : false + }; + + this.restrictStatus.snap = this.snapStatus; + + this.pointerIsDown = false; + this.pointerWasMoved = false; + this.gesturing = false; + this.dragging = false; + this.resizing = false; + this.resizeAxes = 'xy'; + + this.mouse = false; + + scope.interactions.push(this); +} + +// Check if action is enabled globally and the current target supports it +// If so, return the validated action. Otherwise, return null +function validateAction (action, interactable) { + if (!scope.isObject(action)) { return null; } + + var actionName = action.name, + options = interactable.options; + + if (( (actionName === 'resize' && options.resize.enabled ) + || (actionName === 'drag' && options.drag.enabled ) + || (actionName === 'gesture' && options.gesture.enabled)) + && scope.actionIsEnabled[actionName]) { + + if (actionName === 'resize' || actionName === 'resizeyx') { + actionName = 'resizexy'; } - }, - getDrop: function (event, dragElement) { - var validDrops = []; + return action; + } + return null; +} - if (dynamicDrop) { - this.setActiveDrops(dragElement); +function getActionCursor (action) { + var cursor = ''; + + if (action.name === 'drag') { + cursor = scope.actionCursors.drag; + } + if (action.name === 'resize') { + if (action.axis) { + cursor = scope.actionCursors[action.name + action.axis]; } + else if (action.edges) { + var cursorKey = 'resize', + edgeNames = ['top', 'bottom', 'left', 'right']; - // collect all dropzones and their elements which qualify for a drop - for (var j = 0; j < this.activeDrops.dropzones.length; j++) { - var current = this.activeDrops.dropzones[j], - currentElement = this.activeDrops.elements [j], - rect = this.activeDrops.rects [j]; + for (var i = 0; i < 4; i++) { + if (action.edges[edgeNames[i]]) { + cursorKey += edgeNames[i]; + } + } - validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) - ? currentElement - : null); + cursor = scope.actionCursors[cursorKey]; } + } - // get the most appropriate dropzone based on DOM depth and order - var dropIndex = indexOfDeepestElement(validDrops), - dropzone = this.activeDrops.dropzones[dropIndex] || null, - element = this.activeDrops.elements [dropIndex] || null; + return cursor; +} - return { - dropzone: dropzone, - element: element - }; - }, +function preventOriginalDefault () { + this.originalEvent.preventDefault(); +} - getDropEvents: function (pointerEvent, dragEvent) { - var dropEvents = { - enter : null, - leave : null, - activate : null, - deactivate: null, - move : null, - drop : null - }; +Interaction.prototype = { + getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); }, + getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); }, + setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); }, - if (this.dropElement !== this.prevDropElement) { - // if there was a prevDropTarget, create a dragleave event - if (this.prevDropTarget) { - dropEvents.leave = { - target : this.prevDropElement, - dropzone : this.prevDropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragleave' - }; + pointerOver: function (pointer, event, eventTarget) { + if (this.prepared.name || !this.mouse) { return; } - dragEvent.dragLeave = this.prevDropElement; - dragEvent.prevDropzone = this.prevDropTarget; - } - // if the dropTarget is not null, create a dragenter event - if (this.dropTarget) { - dropEvents.enter = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragenter' - }; + var curMatches = [], + curMatchElements = [], + prevTargetElement = this.element; - dragEvent.dragEnter = this.dropElement; - dragEvent.dropzone = this.dropTarget; - } + this.addPointer(pointer); + + if (this.target + && (scope.testIgnore(this.target, this.element, eventTarget) + || !scope.testAllow(this.target, this.element, eventTarget))) { + // if the eventTarget should be ignored or shouldn't be allowed + // clear the previous target + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; } - if (dragEvent.type === 'dragend' && this.dropTarget) { - dropEvents.drop = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'drop' - }; + var elementInteractable = scope.interactables.get(eventTarget), + elementAction = (elementInteractable + && !scope.testIgnore(elementInteractable, eventTarget, eventTarget) + && scope.testAllow(elementInteractable, eventTarget, eventTarget) + && validateAction( + elementInteractable.getAction(pointer, event, this, eventTarget), + elementInteractable)); - dragEvent.dropzone = this.dropTarget; + if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { + elementAction = null; } - if (dragEvent.type === 'dragstart') { - dropEvents.activate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropactivate' - }; + + function pushCurMatches (interactable, selector) { + if (interactable + && scope.inContext(interactable, eventTarget) + && !scope.testIgnore(interactable, eventTarget, eventTarget) + && scope.testAllow(interactable, eventTarget, eventTarget) + && scope.matchesSelector(eventTarget, selector)) { + + curMatches.push(interactable); + curMatchElements.push(eventTarget); + } } - if (dragEvent.type === 'dragend') { - dropEvents.deactivate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropdeactivate' - }; + + if (elementAction) { + this.target = elementInteractable; + this.element = eventTarget; + this.matches = []; + this.matchElements = []; } - if (dragEvent.type === 'dragmove' && this.dropTarget) { - dropEvents.move = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - dragmove : dragEvent, - timeStamp : dragEvent.timeStamp, - type : 'dropmove' - }; - dragEvent.dropzone = this.dropTarget; + else { + scope.interactables.forEachSelector(pushCurMatches); + + if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { + this.matches = curMatches; + this.matchElements = curMatchElements; + + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(eventTarget, + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.listeners.pointerHover); + } + else if (this.target) { + if (scope.nodeContains(prevTargetElement, eventTarget)) { + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(this.element, + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.listeners.pointerHover); + } + else { + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; + } + } + } + }, + + // Check what action would be performed on pointerMove target if a mouse + // button were pressed and change the cursor accordingly + pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { + var target = this.target; + + if (!this.prepared.name && this.mouse) { + + var action; + + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); + + if (matches) { + action = this.validateSelector(pointer, event, matches, matchElements); + } + else if (target) { + action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); + } + + if (target && target.options.styleCursor) { + if (action) { + target._doc.documentElement.style.cursor = getActionCursor(action); + } + else { + target._doc.documentElement.style.cursor = ''; + } + } + } + else if (this.prepared.name) { + this.checkAndPreventDefault(event, target, this.element); } - - return dropEvents; }, - currentAction: function () { - return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; - }, + pointerOut: function (pointer, event, eventTarget) { + if (this.prepared.name) { return; } - interacting: function () { - return this.dragging || this.resizing || this.gesturing; + // Remove temporary event listeners for selector Interactables + if (!scope.interactables.get(eventTarget)) { + events.remove(eventTarget, + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.listeners.pointerHover); + } + + if (this.target && this.target.options.styleCursor && !this.interacting()) { + this.target._doc.documentElement.style.cursor = ''; + } }, - clearTargets: function () { - this.target = this.element = null; + selectorDown: function (pointer, event, eventTarget, curEventTarget) { + var that = this, + // copy event to be used in timeout for IE8 + eventCopy = events.useAttachEvent? utils.extend({}, event) : event, + element = eventTarget, + pointerIndex = this.addPointer(pointer), + action; - this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null; - }, + this.holdTimers[pointerIndex] = setTimeout(function () { + that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); + }, scope.defaultOptions._holdDuration); - stop: function (event) { - if (this.interacting()) { - autoScroll.stop(); - this.matches = []; - this.matchElements = []; + this.pointerIsDown = true; - var target = this.target; + // Check if the down event hits the current inertia target + if (this.inertiaStatus.active && this.target.selector) { + // climb up the DOM tree from the event target + while (utils.isElement(element)) { - if (target.options.styleCursor) { - target._doc.documentElement.style.cursor = ''; - } + // if this element is the current inertia target element + if (element === this.element + // and the prospective action is the same as the ongoing one + && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { - // prevent Default only if were previously interacting - if (event && isFunction(event.preventDefault)) { - this.checkAndPreventDefault(event, target, this.element); - } + // stop inertia so that the next move will be a normal one + animationFrame.cancel(this.inertiaStatus.i); + this.inertiaStatus.active = false; - if (this.dragging) { - this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; + this.collectEventTargets(pointer, event, eventTarget, 'down'); + return; + } + element = scope.parentElement(element); } + } - this.clearTargets(); + // do nothing if interacting + if (this.interacting()) { + this.collectEventTargets(pointer, event, eventTarget, 'down'); + return; } - this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; - this.prepared.name = this.prevEvent = null; - this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; + function pushMatches (interactable, selector, context) { + var elements = scope.ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; - // remove pointers if their ID isn't in this.pointerIds - for (var i = 0; i < this.pointers.length; i++) { - if (indexOf(this.pointerIds, getPointerId(this.pointers[i])) === -1) { - this.pointers.splice(i, 1); - } - } + if (scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && scope.matchesSelector(element, selector, elements)) { - for (i = 0; i < interactions.length; i++) { - // remove this interaction if it's not the only one of it's type - if (interactions[i] !== this && interactions[i].mouse === this.mouse) { - interactions.splice(indexOf(interactions, this), 1); + that.matches.push(interactable); + that.matchElements.push(element); } } - }, - inertiaFrame: function () { - var inertiaStatus = this.inertiaStatus, - options = this.target.options[this.prepared.name].inertia, - lambda = options.resistance, - t = new Date().getTime() / 1000 - inertiaStatus.t0; + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); + this.downEvent = event; - if (t < inertiaStatus.te) { + while (utils.isElement(element) && !action) { + this.matches = []; + this.matchElements = []; - var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; + scope.interactables.forEachSelector(pushMatches); - if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { - inertiaStatus.sx = inertiaStatus.xe * progress; - inertiaStatus.sy = inertiaStatus.ye * progress; - } - else { - var quadPoint = getQuadraticCurvePoint( - 0, 0, - inertiaStatus.xe, inertiaStatus.ye, - inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, - progress); + action = this.validateSelector(pointer, event, this.matches, this.matchElements); + element = scope.parentElement(element); + } - inertiaStatus.sx = quadPoint.x; - inertiaStatus.sy = quadPoint.y; - } + if (action) { + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + this.collectEventTargets(pointer, event, eventTarget, 'down'); - inertiaStatus.i = reqFrame(this.boundInertiaFrame); + return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); } else { - inertiaStatus.sx = inertiaStatus.modifiedXe; - inertiaStatus.sy = inertiaStatus.modifiedYe; - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + // do these now since pointerDown isn't being called from here + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + utils.extend(this.downPointer, pointer); - inertiaStatus.active = false; - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + utils.copyCoords(this.prevCoords, this.curCoords); + this.pointerWasMoved = false; } + + this.collectEventTargets(pointer, event, eventTarget, 'down'); }, - smoothEndFrame: function () { - var inertiaStatus = this.inertiaStatus, - t = new Date().getTime() - inertiaStatus.t0, - duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; + // Determine action to be performed on next pointerMove and add appropriate + // style and event Listeners + pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { + if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { + this.checkAndPreventDefault(event, this.target, this.element); - if (t < duration) { - inertiaStatus.sx = easeOutQuad(t, 0, inertiaStatus.xe, duration); - inertiaStatus.sy = easeOutQuad(t, 0, inertiaStatus.ye, duration); + return; + } - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + this.pointerIsDown = true; + this.downEvent = event; - inertiaStatus.i = reqFrame(this.boundSmoothEndFrame); - } - else { - inertiaStatus.sx = inertiaStatus.xe; - inertiaStatus.sy = inertiaStatus.ye; + var pointerIndex = this.addPointer(pointer), + action; - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + // If it is the second touch of a multi-touch gesture, keep the target + // the same if a target was set by the first touch + // Otherwise, set the target if there is no action prepared + if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { - inertiaStatus.active = false; - inertiaStatus.smoothEnd = false; + var interactable = scope.interactables.get(curEventTarget); - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + if (interactable + && !scope.testIgnore(interactable, curEventTarget, eventTarget) + && scope.testAllow(interactable, curEventTarget, eventTarget) + && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) + && scope.withinInteractionLimit(interactable, curEventTarget, action)) { + this.target = interactable; + this.element = curEventTarget; + } } - }, - addPointer: function (pointer) { - var id = getPointerId(pointer), - index = this.mouse? 0 : indexOf(this.pointerIds, id); + var target = this.target, + options = target && target.options; - if (index === -1) { - index = this.pointerIds.length; - } + if (target && (forceAction || !this.prepared.name)) { + action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); - this.pointerIds[index] = id; - this.pointers[index] = pointer; + this.setEventXY(this.startCoords); - return index; - }, + if (!action) { return; } - removePointer: function (pointer) { - var id = getPointerId(pointer), - index = this.mouse? 0 : indexOf(this.pointerIds, id); + if (options.styleCursor) { + target._doc.documentElement.style.cursor = getActionCursor(action); + } - if (index === -1) { return; } + this.resizeAxes = action.name === 'resize'? action.axis : null; - if (!this.interacting()) { - this.pointers.splice(index, 1); - } + if (action === 'gesture' && this.pointerIds.length < 2) { + action = null; + } - this.pointerIds .splice(index, 1); - this.downTargets.splice(index, 1); - this.downTimes .splice(index, 1); - this.holdTimers .splice(index, 1); - }, + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; - recordPointer: function (pointer) { - // Do not update pointers while inertia is active. - // The inertia start event should be this.pointers[0] - if (this.inertiaStatus.active) { return; } + this.snapStatus.snappedX = this.snapStatus.snappedY = + this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN; - var index = this.mouse? 0: indexOf(this.pointerIds, getPointerId(pointer)); + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + utils.extend(this.downPointer, pointer); - if (index === -1) { return; } + this.setEventXY(this.prevCoords); + this.pointerWasMoved = false; - this.pointers[index] = pointer; + this.checkAndPreventDefault(event, target, this.element); + } + // if inertia is active try to resume action + else if (this.inertiaStatus.active + && curEventTarget === this.element + && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { + + animationFrame.cancel(this.inertiaStatus.i); + this.inertiaStatus.active = false; + + this.checkAndPreventDefault(event, target, this.element); + } }, - collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)); + setModifications: function (coords, preEnd) { + var target = this.target, + shouldMove = true, + shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), + shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); - // do not fire a tap event if the pointer was moved before being lifted - if (eventType === 'tap' && (this.pointerWasMoved - // or if the pointerup target is different to the pointerdown target - || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) { - return; + if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } + if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } + + if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { + shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; + } + else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) { + shouldMove = false; } - var targets = [], - elements = [], - element = eventTarget; + return shouldMove; + }, - function collectSelectors (interactable, selector, context) { - var els = ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; + setStartOffsets: function (action, interactable, element) { + var rect = interactable.getRect(element), + origin = scope.getOriginXY(interactable, element), + snap = interactable.options[this.prepared.name].snap, + restrict = interactable.options[this.prepared.name].restrict, + width, height; - if (interactable._iEvents[eventType] - && isElement(element) - && inContext(interactable, element) - && !testIgnore(interactable, element, eventTarget) - && testAllow(interactable, element, eventTarget) - && matchesSelector(element, selector, els)) { + if (rect) { + this.startOffset.left = this.startCoords.page.x - rect.left; + this.startOffset.top = this.startCoords.page.y - rect.top; + + this.startOffset.right = rect.right - this.startCoords.page.x; + this.startOffset.bottom = rect.bottom - this.startCoords.page.y; - targets.push(interactable); - elements.push(element); - } + if ('width' in rect) { width = rect.width; } + else { width = rect.right - rect.left; } + if ('height' in rect) { height = rect.height; } + else { height = rect.bottom - rect.top; } + } + else { + this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; } - while (element) { - if (interact.isSet(element) && interact(element)._iEvents[eventType]) { - targets.push(interact(element)); - elements.push(element); - } - - interactables.forEachSelector(collectSelectors); + this.snapOffsets.splice(0); - element = parentElement(element); + var snapOffset = snap && snap.offset === 'startCoords' + ? { + x: this.startCoords.page.x - origin.x, + y: this.startCoords.page.y - origin.y } + : snap && snap.offset || { x: 0, y: 0 }; - // create the tap event even if there are no listeners so that - // doubletap can still be created and fired - if (targets.length || eventType === 'tap') { - this.firePointers(pointer, event, eventTarget, targets, elements, eventType); + if (rect && snap && snap.relativePoints && snap.relativePoints.length) { + for (var i = 0; i < snap.relativePoints.length; i++) { + this.snapOffsets.push({ + x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x, + y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y + }); + } + } + else { + this.snapOffsets.push(snapOffset); } - }, - firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : indexOf(getPointerId(pointer)), - pointerEvent = {}, - i, - // for tap events - interval, createNewDoubleTap; + if (rect && restrict.elementRect) { + this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left); + this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top); - // if it's a doubletap then the event properties would have been - // copied from the tap event and provided as the pointer argument - if (eventType === 'doubletap') { - pointerEvent = pointer; + this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right)); + this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom)); } else { - extend(pointerEvent, event); - if (event !== pointer) { - extend(pointerEvent, pointer); - } + this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0; + } + }, - pointerEvent.preventDefault = preventOriginalDefault; - pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; - pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; - pointerEvent.interaction = this; + /*\ + * Interaction.start + [ method ] + * + * Start an action with the given Interactable and Element as tartgets. The + * action must be enabled for the target Interactable and an appropriate number + * of pointers must be held down – 1 for drag/resize, 2 for gesture. + * + * Use it with `interactable.able({ manualStart: false })` to always + * [start actions manually](https://github.com/taye/interact.js/issues/114) + * + - action (object) The action to be performed - drag, resize, etc. + - interactable (Interactable) The Interactable to target + - element (Element) The DOM Element to target + = (object) interact + ** + | interact(target) + | .draggable({ + | // disable the default drag start by down->move + | manualStart: true + | }) + | // start dragging after the user holds the pointer down + | .on('hold', function (event) { + | var interaction = event.interaction; + | + | if (!interaction.interacting()) { + | interaction.start({ name: 'drag' }, + | event.interactable, + | event.currentTarget); + | } + | }); + \*/ + start: function (action, interactable, element) { + if (this.interacting() + || !this.pointerIsDown + || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { + return; + } - pointerEvent.timeStamp = new Date().getTime(); - pointerEvent.originalEvent = event; - pointerEvent.type = eventType; - pointerEvent.pointerId = getPointerId(pointer); - pointerEvent.pointerType = this.mouse? 'mouse' : !supportsPointerEvent? 'touch' - : isString(pointer.pointerType) - ? pointer.pointerType - : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; + // if this interaction had been removed after stopping + // add it back + if (scope.indexOf(scope.interactions, this) === -1) { + scope.interactions.push(this); } - if (eventType === 'tap') { - pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex]; + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + this.target = interactable; + this.element = element; - interval = pointerEvent.timeStamp - this.tapTime; - createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap' - && this.prevTap.target === pointerEvent.target - && interval < 500); + this.setEventXY(this.startCoords); + this.setStartOffsets(action.name, interactable, element); + this.setModifications(this.startCoords.page); - pointerEvent.double = createNewDoubleTap; + this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); + }, - this.tapTime = pointerEvent.timeStamp; - } + pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { + this.recordPointer(pointer); - for (i = 0; i < targets.length; i++) { - pointerEvent.currentTarget = elements[i]; - pointerEvent.interactable = targets[i]; - targets[i].fire(pointerEvent); + this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) + ? this.inertiaStatus.startEvent + : undefined); - if (pointerEvent.immediatePropagationStopped - ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { - break; - } - } + var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x + && this.curCoords.page.y === this.prevCoords.page.y + && this.curCoords.client.x === this.prevCoords.client.x + && this.curCoords.client.y === this.prevCoords.client.y); - if (createNewDoubleTap) { - var doubleTap = {}; + var dx, dy, + pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - extend(doubleTap, pointerEvent); + // register movement greater than pointerMoveTolerance + if (this.pointerIsDown && !this.pointerWasMoved) { + dx = this.curCoords.client.x - this.startCoords.client.x; + dy = this.curCoords.client.y - this.startCoords.client.y; - doubleTap.dt = interval; - doubleTap.type = 'doubletap'; + this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; + } - this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap'); + if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { + if (this.pointerIsDown) { + clearTimeout(this.holdTimers[pointerIndex]); + } - this.prevTap = doubleTap; - } - else if (eventType === 'tap') { - this.prevTap = pointerEvent; + this.collectEventTargets(pointer, event, eventTarget, 'move'); } - }, - - validateSelector: function (pointer, event, matches, matchElements) { - for (var i = 0, len = matches.length; i < len; i++) { - var match = matches[i], - matchElement = matchElements[i], - action = validateAction(match.getAction(pointer, event, this, matchElement), match); - if (action && withinInteractionLimit(match, matchElement, action)) { - this.target = match; - this.element = matchElement; + if (!this.pointerIsDown) { return; } - return action; - } + if (duplicateMove && this.pointerWasMoved && !preEnd) { + this.checkAndPreventDefault(event, this.target, this.element); + return; } - }, - setSnapping: function (pageCoords, status) { - var snap = this.target.options[this.prepared.name].snap, - targets = [], - target, - page, - i; + // set pointer coordinate, time changes and speeds + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - status = status || this.snapStatus; + if (!this.prepared.name) { return; } - if (status.useStatusXY) { - page = { x: status.x, y: status.y }; - } - else { - var origin = getOriginXY(this.target, this.element); + if (this.pointerWasMoved + // ignore movement while inertia is active + && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { - page = extend({}, pageCoords); + // if just starting an action, calculate the pointer speed now + if (!this.interacting()) { + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - page.x -= origin.x; - page.y -= origin.y; - } + // check if a drag is in the correct axis + if (this.prepared.name === 'drag') { + var absX = Math.abs(dx), + absY = Math.abs(dy), + targetAxis = this.target.options.drag.axis, + axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); - status.realX = page.x; - status.realY = page.y; + // if the movement isn't in the axis of the interactable + if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { + // cancel the prepared action + this.prepared.name = null; - page.x = page.x - this.inertiaStatus.resumeDx; - page.y = page.y - this.inertiaStatus.resumeDy; + // then try to get a drag from another ineractable - var len = snap.targets? snap.targets.length : 0; + var element = eventTarget; - for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) { - var relative = { - x: page.x - this.snapOffsets[relIndex].x, - y: page.y - this.snapOffsets[relIndex].y - }; + // check element interactables + while (utils.isElement(element)) { + var elementInteractable = scope.interactables.get(element); - for (i = 0; i < len; i++) { - if (isFunction(snap.targets[i])) { - target = snap.targets[i](relative.x, relative.y, this); - } - else { - target = snap.targets[i]; - } + if (elementInteractable + && elementInteractable !== this.target + && !elementInteractable.options.drag.manualStart + && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' + && scope.checkAxis(axis, elementInteractable)) { - if (!target) { continue; } + this.prepared.name = 'drag'; + this.target = elementInteractable; + this.element = element; + break; + } - targets.push({ - x: isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, - y: isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, + element = scope.parentElement(element); + } - range: isNumber(target.range)? target.range: snap.range - }); - } - } + // if there's no drag from element interactables, + // check the selector interactables + if (!this.prepared.name) { + var thisInteraction = this; - var closest = { - target: null, - inRange: false, - distance: 0, - range: 0, - dx: 0, - dy: 0 - }; + var getDraggable = function (interactable, selector, context) { + var elements = scope.ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; - for (i = 0, len = targets.length; i < len; i++) { - target = targets[i]; + if (interactable === thisInteraction.target) { return; } - var range = target.range, - dx = target.x - page.x, - dy = target.y - page.y, - distance = hypot(dx, dy), - inRange = distance <= range; + if (scope.inContext(interactable, eventTarget) + && !interactable.options.drag.manualStart + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && scope.matchesSelector(element, selector, elements) + && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' + && scope.checkAxis(axis, interactable) + && scope.withinInteractionLimit(interactable, element, 'drag')) { - // Infinite targets count as being out of range - // compared to non infinite ones that are in range - if (range === Infinity && closest.inRange && closest.range !== Infinity) { - inRange = false; - } + return interactable; + } + }; - if (!closest.target || (inRange - // is the closest target in range? - ? (closest.inRange && range !== Infinity - // the pointer is relatively deeper in this target - ? distance / range < closest.distance / closest.range - // this target has Infinite range and the closest doesn't - : (range === Infinity && closest.range !== Infinity) - // OR this target is closer that the previous closest - || distance < closest.distance) - // The other is not in range and the pointer is closer to this target - : (!closest.inRange && distance < closest.distance))) { + element = eventTarget; - if (range === Infinity) { - inRange = true; + while (utils.isElement(element)) { + var selectorInteractable = scope.interactables.forEachSelector(getDraggable); + + if (selectorInteractable) { + this.prepared.name = 'drag'; + this.target = selectorInteractable; + this.element = element; + break; + } + + element = scope.parentElement(element); + } + } + } } + } - closest.target = target; - closest.distance = distance; - closest.range = range; - closest.inRange = inRange; - closest.dx = dx; - closest.dy = dy; + var starting = !!this.prepared.name && !this.interacting(); - status.range = range; + if (starting + && (this.target.options[this.prepared.name].manualStart + || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { + this.stop(); + return; } - } - var snapChanged; + if (this.prepared.name && this.target) { + if (starting) { + this.start(this.prepared, this.target, this.element); + } - if (closest.target) { - snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); + var shouldMove = this.setModifications(this.curCoords.page, preEnd); - status.snappedX = closest.target.x; - status.snappedY = closest.target.y; - } - else { - snapChanged = true; + // move if snapping or restriction doesn't prevent it + if (shouldMove || starting) { + this.prevEvent = this[this.prepared.name + 'Move'](event); + } - status.snappedX = NaN; - status.snappedY = NaN; + this.checkAndPreventDefault(event, this.target, this.element); + } } - status.dx = closest.dx; - status.dy = closest.dy; - - status.changed = (snapChanged || (closest.inRange && !status.locked)); - status.locked = closest.inRange; + utils.copyCoords(this.prevCoords, this.curCoords); - return status; + if (this.dragging || this.resizing) { + this.autoScrollMove(pointer); + } }, - setRestriction: function (pageCoords, status) { - var target = this.target, - restrict = target && target.options[this.prepared.name].restrict, - restriction = restrict && restrict.restriction, - page; - - if (!restriction) { - return status; - } + dragStart: function (event) { + var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); - status = status || this.restrictStatus; + this.dragging = true; + this.target.fire(dragEvent); - page = status.useStatusXY - ? page = { x: status.x, y: status.y } - : page = extend({}, pageCoords); + // reset active dropzones + this.activeDrops.dropzones = []; + this.activeDrops.elements = []; + this.activeDrops.rects = []; - if (status.snap && status.snap.locked) { - page.x += status.snap.dx || 0; - page.y += status.snap.dy || 0; + if (!this.dynamicDrop) { + this.setActiveDrops(this.element); } - page.x -= this.inertiaStatus.resumeDx; - page.y -= this.inertiaStatus.resumeDy; - - status.dx = 0; - status.dy = 0; - status.restricted = false; - - var rect, restrictedX, restrictedY; - - if (isString(restriction)) { - if (restriction === 'parent') { - restriction = parentElement(this.element); - } - else if (restriction === 'self') { - restriction = target.getRect(this.element); - } - else { - restriction = closest(this.element, restriction); - } + var dropEvents = this.getDropEvents(event, dragEvent); - if (!restriction) { return status; } + if (dropEvents.activate) { + this.fireActiveDrops(dropEvents.activate); } - if (isFunction(restriction)) { - restriction = restriction(page.x, page.y, this.element); - } + return dragEvent; + }, - if (isElement(restriction)) { - restriction = getElementRect(restriction); - } + dragMove: function (event) { + var target = this.target, + dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), + draggableElement = this.element, + drop = this.getDrop(event, draggableElement); - rect = restriction; + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; - if (!restriction) { - restrictedX = page.x; - restrictedY = page.y; - } - // object is assumed to have - // x, y, width, height or - // left, top, right, bottom - else if ('x' in restriction && 'y' in restriction) { - restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top ); - } - else { - restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top ); - } + var dropEvents = this.getDropEvents(event, dragEvent); - status.dx = restrictedX - page.x; - status.dy = restrictedY - page.y; + target.fire(dragEvent); - status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; - status.restricted = !!(status.dx || status.dy); + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } - status.restrictedX = restrictedX; - status.restrictedY = restrictedY; + this.prevDropTarget = this.dropTarget; + this.prevDropElement = this.dropElement; - return status; + return dragEvent; }, - checkAndPreventDefault: function (event, interactable, element) { - if (!(interactable = interactable || this.target)) { return; } + resizeStart: function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); - var options = interactable.options, - prevent = options.preventDefault; + if (this.prepared.edges) { + var startRect = this.target.getRect(this.element); - if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { - // do not preventDefault on pointerdown if the prepared action is a drag - // and dragging can only start from a certain direction - this allows - // a touch to pan the viewport if a drag isn't in the right direction - if (/down|start/i.test(event.type) - && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { + if (this.target.options.resize.square) { + var squareEdges = utils.extend({}, this.prepared.edges); - return; - } + squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); + squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); + squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); + squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); - // with manualStart, only preventDefault while interacting - if (options[this.prepared.name] && options[this.prepared.name].manualStart - && !this.interacting()) { - return; + this.prepared._squareEdges = squareEdges; + } + else { + this.prepared._squareEdges = null; } - event.preventDefault(); - return; - } + this.resizeRects = { + start : startRect, + current : utils.extend({}, startRect), + restricted: utils.extend({}, startRect), + previous : utils.extend({}, startRect), + delta : { + left: 0, right : 0, width : 0, + top : 0, bottom: 0, height: 0 + } + }; - if (prevent === 'always') { - event.preventDefault(); - return; + resizeEvent.rect = this.resizeRects.restricted; + resizeEvent.deltaRect = this.resizeRects.delta; } - }, - - calcInertia: function (status) { - var inertiaOptions = this.target.options[this.prepared.name].inertia, - lambda = inertiaOptions.resistance, - inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; - status.x0 = this.prevEvent.pageX; - status.y0 = this.prevEvent.pageY; - status.t0 = status.startEvent.timeStamp / 1000; - status.sx = status.sy = 0; + this.target.fire(resizeEvent); - status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; - status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; - status.te = inertiaDur; + this.resizing = true; - status.lambda_v0 = lambda / status.v0; - status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; + return resizeEvent; }, - autoScrollMove: function (pointer) { - if (!(this.interacting() - && checkAutoScroll(this.target, this.prepared.name))) { - return; - } - - if (this.inertiaStatus.active) { - autoScroll.x = autoScroll.y = 0; - return; - } + resizeMove: function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); - var top, - right, - bottom, - left, - options = this.target.options[this.prepared.name].autoScroll, - container = options.container || getWindow(this.element); + var edges = this.prepared.edges, + invert = this.target.options.resize.invert, + invertible = invert === 'reposition' || invert === 'negate'; - if (isWindow(container)) { - left = pointer.clientX < autoScroll.margin; - top = pointer.clientY < autoScroll.margin; - right = pointer.clientX > container.innerWidth - autoScroll.margin; - bottom = pointer.clientY > container.innerHeight - autoScroll.margin; - } - else { - var rect = getElementRect(container); + if (edges) { + var dx = resizeEvent.dx, + dy = resizeEvent.dy, - left = pointer.clientX < rect.left + autoScroll.margin; - top = pointer.clientY < rect.top + autoScroll.margin; - right = pointer.clientX > rect.right - autoScroll.margin; - bottom = pointer.clientY > rect.bottom - autoScroll.margin; - } + start = this.resizeRects.start, + current = this.resizeRects.current, + restricted = this.resizeRects.restricted, + delta = this.resizeRects.delta, + previous = utils.extend(this.resizeRects.previous, restricted); - autoScroll.x = (right ? 1: left? -1: 0); - autoScroll.y = (bottom? 1: top? -1: 0); + if (this.target.options.resize.square) { + var originalEdges = edges; - if (!autoScroll.isScrolling) { - // set the autoScroll properties to those of the target - autoScroll.margin = options.margin; - autoScroll.speed = options.speed; + edges = this.prepared._squareEdges; - autoScroll.start(this); - } - }, + if ((originalEdges.left && originalEdges.bottom) + || (originalEdges.right && originalEdges.top)) { + dy = -dx; + } + else if (originalEdges.left || originalEdges.right) { dy = dx; } + else if (originalEdges.top || originalEdges.bottom) { dx = dy; } + } - _updateEventTargets: function (target, currentTarget) { - this._eventTarget = target; - this._curEventTarget = currentTarget; - } + // update the 'current' rect without modifications + if (edges.top ) { current.top += dy; } + if (edges.bottom) { current.bottom += dy; } + if (edges.left ) { current.left += dx; } + if (edges.right ) { current.right += dx; } -}; + if (invertible) { + // if invertible, copy the current rect + utils.extend(restricted, current); -function getInteractionFromPointer (pointer, eventType, eventTarget) { - var i = 0, len = interactions.length, - mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) - // MSPointerEvent.MSPOINTER_TYPE_MOUSE - || pointer.pointerType === 4), - interaction; + if (invert === 'reposition') { + // swap edge values if necessary to keep width/height positive + var swap; - var id = getPointerId(pointer); + if (restricted.top > restricted.bottom) { + swap = restricted.top; - // try to resume inertia with a new pointer - if (/down|start/i.test(eventType)) { - for (i = 0; i < len; i++) { - interaction = interactions[i]; - - var element = eventTarget; - - if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume - && (interaction.mouse === mouseEvent)) { - while (element) { - // if the element is the interaction element - if (element === interaction.element) { - // update the interaction's pointer - if (interaction.pointers[0]) { - interaction.removePointer(interaction.pointers[0]); - } - interaction.addPointer(pointer); + restricted.top = restricted.bottom; + restricted.bottom = swap; + } + if (restricted.left > restricted.right) { + swap = restricted.left; - return interaction; + restricted.left = restricted.right; + restricted.right = swap; } - element = parentElement(element); } } - } - } + else { + // if not invertible, restrict to minimum of 0x0 rect + restricted.top = Math.min(current.top, start.bottom); + restricted.bottom = Math.max(current.bottom, start.top); + restricted.left = Math.min(current.left, start.right); + restricted.right = Math.max(current.right, start.left); + } - // if it's a mouse interaction - if (mouseEvent || !(supportsTouch || supportsPointerEvent)) { + restricted.width = restricted.right - restricted.left; + restricted.height = restricted.bottom - restricted.top ; - // find a mouse interaction that's not in inertia phase - for (i = 0; i < len; i++) { - if (interactions[i].mouse && !interactions[i].inertiaStatus.active) { - return interactions[i]; + for (var edge in restricted) { + delta[edge] = restricted[edge] - previous[edge]; } - } - // find any interaction specifically for mouse. - // if the eventType is a mousedown, and inertia is active - // ignore the interaction - for (i = 0; i < len; i++) { - if (interactions[i].mouse && !(/down/.test(eventType) && interactions[i].inertiaStatus.active)) { - return interaction; - } + resizeEvent.edges = this.prepared.edges; + resizeEvent.rect = restricted; + resizeEvent.deltaRect = delta; } - // create a new interaction for mouse - interaction = new Interaction(); - interaction.mouse = true; + this.target.fire(resizeEvent); - return interaction; - } + return resizeEvent; + }, + + gestureStart: function (event) { + var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); + + gestureEvent.ds = 0; + + this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; + this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; + this.gesture.scale = 1; + + this.gesturing = true; + + this.target.fire(gestureEvent); + + return gestureEvent; + }, - // get interaction that has this pointer - for (i = 0; i < len; i++) { - if (contains(interactions[i].pointerIds, id)) { - return interactions[i]; + gestureMove: function (event) { + if (!this.pointerIds.length) { + return this.prevEvent; } - } - // at this stage, a pointerUp should not return an interaction - if (/up|end|out/i.test(eventType)) { - return null; - } + var gestureEvent; - // get first idle interaction - for (i = 0; i < len; i++) { - interaction = interactions[i]; + gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); + gestureEvent.ds = gestureEvent.scale - this.gesture.scale; - if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) - && !interaction.interacting() - && !(!mouseEvent && interaction.mouse)) { + this.target.fire(gestureEvent); - interaction.addPointer(pointer); + this.gesture.prevAngle = gestureEvent.angle; + this.gesture.prevDistance = gestureEvent.distance; - return interaction; + if (gestureEvent.scale !== Infinity && + gestureEvent.scale !== null && + gestureEvent.scale !== undefined && + !isNaN(gestureEvent.scale)) { + + this.gesture.scale = gestureEvent.scale; } - } - return new Interaction(); -} + return gestureEvent; + }, -function doOnInteractions (method) { - return (function (event) { - var interaction, - eventTarget = getActualElement(event.path - ? event.path[0] - : event.target), - curEventTarget = getActualElement(event.currentTarget), - i; + pointerHold: function (pointer, event, eventTarget) { + this.collectEventTargets(pointer, event, eventTarget, 'hold'); + }, - if (supportsTouch && /touch/.test(event.type)) { - prevTouchTime = new Date().getTime(); + pointerUp: function (pointer, event, eventTarget, curEventTarget) { + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - for (i = 0; i < event.changedTouches.length; i++) { - var pointer = event.changedTouches[i]; + clearTimeout(this.holdTimers[pointerIndex]); - interaction = getInteractionFromPointer(pointer, event.type, eventTarget); + this.collectEventTargets(pointer, event, eventTarget, 'up' ); + this.collectEventTargets(pointer, event, eventTarget, 'tap'); - if (!interaction) { continue; } + this.pointerEnd(pointer, event, eventTarget, curEventTarget); - interaction._updateEventTargets(eventTarget, curEventTarget); + this.removePointer(pointer); + }, - interaction[method](pointer, event, eventTarget, curEventTarget); - } - } - else { - if (!supportsPointerEvent && /mouse/.test(event.type)) { - // ignore mouse events while touch interactions are active - for (i = 0; i < interactions.length; i++) { - if (!interactions[i].mouse && interactions[i].pointerIsDown) { - return; - } - } + pointerCancel: function (pointer, event, eventTarget, curEventTarget) { + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - // try to ignore mouse events that are simulated by the browser - // after a touch event - if (new Date().getTime() - prevTouchTime < 500) { - return; - } - } + clearTimeout(this.holdTimers[pointerIndex]); - interaction = getInteractionFromPointer(event, event.type, eventTarget); + this.collectEventTargets(pointer, event, eventTarget, 'cancel'); + this.pointerEnd(pointer, event, eventTarget, curEventTarget); - if (!interaction) { return; } + this.removePointer(pointer); + }, - interaction._updateEventTargets(eventTarget, curEventTarget); + // http://www.quirksmode.org/dom/events/click.html + // >Events leading to dblclick + // + // IE8 doesn't fire down event before dblclick. + // This workaround tries to fire a tap and doubletap after dblclick + ie8Dblclick: function (pointer, event, eventTarget) { + if (this.prevTap + && event.clientX === this.prevTap.clientX + && event.clientY === this.prevTap.clientY + && eventTarget === this.prevTap.target) { - interaction[method](event, event, eventTarget, curEventTarget); + this.downTargets[0] = eventTarget; + this.downTimes[0] = new Date().getTime(); + this.collectEventTargets(pointer, event, eventTarget, 'tap'); } - }); -} + }, -function InteractEvent (interaction, event, action, phase, element, related) { - var client, - page, - target = interaction.target, - snapStatus = interaction.snapStatus, - restrictStatus = interaction.restrictStatus, - pointers = interaction.pointers, - deltaSource = (target && target.options || defaultOptions).deltaSource, - sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - options = target? target.options: defaultOptions, - origin = getOriginXY(target, element), - starting = phase === 'start', - ending = phase === 'end', - coords = starting? interaction.startCoords : interaction.curCoords; + // End interact move events and stop auto-scroll unless inertia is enabled + pointerEnd: function (pointer, event, eventTarget, curEventTarget) { + var endEvent, + target = this.target, + options = target && target.options, + inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, + inertiaStatus = this.inertiaStatus; - element = element || interaction.element; + if (this.interacting()) { - page = extend({}, coords.page); - client = extend({}, coords.client); + if (inertiaStatus.active) { return; } - page.x -= origin.x; - page.y -= origin.y; + var pointerSpeed, + now = new Date().getTime(), + inertiaPossible = false, + inertia = false, + smoothEnd = false, + endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, + endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, + dx = 0, + dy = 0, + startEvent; - client.x -= origin.x; - client.y -= origin.y; + if (this.dragging) { + if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } + else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } + else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } + } + else { + pointerSpeed = this.pointerDelta.client.speed; + } - var relativePoints = options[action].snap && options[action].snap.relativePoints ; + // check if inertia should be started + inertiaPossible = (inertiaOptions && inertiaOptions.enabled + && this.prepared.name !== 'gesture' + && event !== inertiaStatus.startEvent); - if (checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { - this.snap = { - range : snapStatus.range, - locked : snapStatus.locked, - x : snapStatus.snappedX, - y : snapStatus.snappedY, - realX : snapStatus.realX, - realY : snapStatus.realY, - dx : snapStatus.dx, - dy : snapStatus.dy - }; + inertia = (inertiaPossible + && (now - this.curCoords.timeStamp) < 50 + && pointerSpeed > inertiaOptions.minSpeed + && pointerSpeed > inertiaOptions.endSpeed); - if (snapStatus.locked) { - page.x += snapStatus.dx; - page.y += snapStatus.dy; - client.x += snapStatus.dx; - client.y += snapStatus.dy; - } - } + if (inertiaPossible && !inertia && (endSnap || endRestrict)) { - if (checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { - page.x += restrictStatus.dx; - page.y += restrictStatus.dy; - client.x += restrictStatus.dx; - client.y += restrictStatus.dy; + var snapRestrict = {}; - this.restrict = { - dx: restrictStatus.dx, - dy: restrictStatus.dy - }; - } + snapRestrict.snap = snapRestrict.restrict = snapRestrict; - this.pageX = page.x; - this.pageY = page.y; - this.clientX = client.x; - this.clientY = client.y; + if (endSnap) { + this.setSnapping(this.curCoords.page, snapRestrict); + if (snapRestrict.locked) { + dx += snapRestrict.dx; + dy += snapRestrict.dy; + } + } + + if (endRestrict) { + this.setRestriction(this.curCoords.page, snapRestrict); + if (snapRestrict.restricted) { + dx += snapRestrict.dx; + dy += snapRestrict.dy; + } + } + + if (dx || dy) { + smoothEnd = true; + } + } - this.x0 = interaction.startCoords.page.x - origin.x; - this.y0 = interaction.startCoords.page.y - origin.y; - this.clientX0 = interaction.startCoords.client.x - origin.x; - this.clientY0 = interaction.startCoords.client.y - origin.y; - this.ctrlKey = event.ctrlKey; - this.altKey = event.altKey; - this.shiftKey = event.shiftKey; - this.metaKey = event.metaKey; - this.button = event.button; - this.target = element; - this.t0 = interaction.downTimes[0]; - this.type = action + (phase || ''); + if (inertia || smoothEnd) { + utils.copyCoords(inertiaStatus.upCoords, this.curCoords); - this.interaction = interaction; - this.interactable = target; + this.pointers[0] = inertiaStatus.startEvent = startEvent = + new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); - var inertiaStatus = interaction.inertiaStatus; + inertiaStatus.t0 = now; - if (inertiaStatus.active) { - this.detail = 'inertia'; - } + target.fire(inertiaStatus.startEvent); - if (related) { - this.relatedTarget = related; - } + if (inertia) { + inertiaStatus.vx0 = this.pointerDelta.client.vx; + inertiaStatus.vy0 = this.pointerDelta.client.vy; + inertiaStatus.v0 = pointerSpeed; - // end event dx, dy is difference between start and end points - if (ending) { - if (deltaSource === 'client') { - this.dx = client.x - interaction.startCoords.client.x; - this.dy = client.y - interaction.startCoords.client.y; - } - else { - this.dx = page.x - interaction.startCoords.page.x; - this.dy = page.y - interaction.startCoords.page.y; - } - } - else if (starting) { - this.dx = 0; - this.dy = 0; - } - // copy properties from previousmove if starting inertia - else if (phase === 'inertiastart') { - this.dx = interaction.prevEvent.dx; - this.dy = interaction.prevEvent.dy; - } - else { - if (deltaSource === 'client') { - this.dx = client.x - interaction.prevEvent.clientX; - this.dy = client.y - interaction.prevEvent.clientY; - } - else { - this.dx = page.x - interaction.prevEvent.pageX; - this.dy = page.y - interaction.prevEvent.pageY; - } - } - if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' - && !inertiaStatus.active - && options[action].inertia && options[action].inertia.zeroResumeDelta) { + this.calcInertia(inertiaStatus); - inertiaStatus.resumeDx += this.dx; - inertiaStatus.resumeDy += this.dy; + var page = utils.extend({}, this.curCoords.page), + origin = scope.getOriginXY(target, this.element), + statusObject; - this.dx = this.dy = 0; - } + page.x = page.x + inertiaStatus.xe - origin.x; + page.y = page.y + inertiaStatus.ye - origin.y; - if (action === 'resize' && interaction.resizeAxes) { - if (options.resize.square) { - if (interaction.resizeAxes === 'y') { - this.dx = this.dy; - } - else { - this.dy = this.dx; - } - this.axes = 'xy'; - } - else { - this.axes = interaction.resizeAxes; + statusObject = { + useStatusXY: true, + x: page.x, + y: page.y, + dx: 0, + dy: 0, + snap: null + }; - if (interaction.resizeAxes === 'x') { - this.dy = 0; - } - else if (interaction.resizeAxes === 'y') { - this.dx = 0; - } - } - } - else if (action === 'gesture') { - this.touches = [pointers[0], pointers[1]]; + statusObject.snap = statusObject; - if (starting) { - this.distance = touchDistance(pointers, deltaSource); - this.box = touchBBox(pointers); - this.scale = 1; - this.ds = 0; - this.angle = touchAngle(pointers, undefined, deltaSource); - this.da = 0; - } - else if (ending || event instanceof InteractEvent) { - this.distance = interaction.prevEvent.distance; - this.box = interaction.prevEvent.box; - this.scale = interaction.prevEvent.scale; - this.ds = this.scale - 1; - this.angle = interaction.prevEvent.angle; - this.da = this.angle - interaction.gesture.startAngle; - } - else { - this.distance = touchDistance(pointers, deltaSource); - this.box = touchBBox(pointers); - this.scale = this.distance / interaction.gesture.startDistance; - this.angle = touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); + dx = dy = 0; - this.ds = this.scale - interaction.gesture.prevScale; - this.da = this.angle - interaction.gesture.prevAngle; - } - } + if (endSnap) { + var snap = this.setSnapping(this.curCoords.page, statusObject); - if (starting) { - this.timeStamp = interaction.downTimes[0]; - this.dt = 0; - this.duration = 0; - this.speed = 0; - this.velocityX = 0; - this.velocityY = 0; - } - else if (phase === 'inertiastart') { - this.timeStamp = interaction.prevEvent.timeStamp; - this.dt = interaction.prevEvent.dt; - this.duration = interaction.prevEvent.duration; - this.speed = interaction.prevEvent.speed; - this.velocityX = interaction.prevEvent.velocityX; - this.velocityY = interaction.prevEvent.velocityY; - } - else { - this.timeStamp = new Date().getTime(); - this.dt = this.timeStamp - interaction.prevEvent.timeStamp; - this.duration = this.timeStamp - interaction.downTimes[0]; + if (snap.locked) { + dx += snap.dx; + dy += snap.dy; + } + } - if (event instanceof InteractEvent) { - var dx = this[sourceX] - interaction.prevEvent[sourceX], - dy = this[sourceY] - interaction.prevEvent[sourceY], - dt = this.dt / 1000; + if (endRestrict) { + var restrict = this.setRestriction(this.curCoords.page, statusObject); - this.speed = hypot(dx, dy) / dt; - this.velocityX = dx / dt; - this.velocityY = dy / dt; - } - // if normal move or end event, use previous user event coords - else { - // speed and velocity in pixels per second - this.speed = interaction.pointerDelta[deltaSource].speed; - this.velocityX = interaction.pointerDelta[deltaSource].vx; - this.velocityY = interaction.pointerDelta[deltaSource].vy; - } - } + if (restrict.restricted) { + dx += restrict.dx; + dy += restrict.dy; + } + } - if ((ending || phase === 'inertiastart') - && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) { + inertiaStatus.modifiedXe += dx; + inertiaStatus.modifiedYe += dy; - var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI, - overlap = 22.5; + inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); + } + else { + inertiaStatus.smoothEnd = true; + inertiaStatus.xe = dx; + inertiaStatus.ye = dy; - if (angle < 0) { - angle += 360; - } + inertiaStatus.sx = inertiaStatus.sy = 0; - var left = 135 - overlap <= angle && angle < 225 + overlap, - up = 225 - overlap <= angle && angle < 315 + overlap, + inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); + } - right = !left && (315 - overlap <= angle || angle < 45 + overlap), - down = !up && 45 - overlap <= angle && angle < 135 + overlap; + inertiaStatus.active = true; + return; + } - this.swipe = { - up : up, - down : down, - left : left, - right: right, - angle: angle, - speed: interaction.prevEvent.speed, - velocity: { - x: interaction.prevEvent.velocityX, - y: interaction.prevEvent.velocityY + if (endSnap || endRestrict) { + // fire a move event at the snapped coordinates + this.pointerMove(pointer, event, eventTarget, curEventTarget, true); } - }; - } -} + } -InteractEvent.prototype = { - preventDefault: blank, - stopImmediatePropagation: function () { - this.immediatePropagationStopped = this.propagationStopped = true; - }, - stopPropagation: function () { - this.propagationStopped = true; - } -}; + if (this.dragging) { + endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); -function preventOriginalDefault () { - this.originalEvent.preventDefault(); -} + var draggableElement = this.element, + drop = this.getDrop(event, draggableElement); -function getActionCursor (action) { - var cursor = ''; + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; - if (action.name === 'drag') { - cursor = actionCursors.drag; - } - if (action.name === 'resize') { - if (action.axis) { - cursor = actionCursors[action.name + action.axis]; - } - else if (action.edges) { - var cursorKey = 'resize', - edgeNames = ['top', 'bottom', 'left', 'right']; + var dropEvents = this.getDropEvents(event, endEvent); - for (var i = 0; i < 4; i++) { - if (action.edges[edgeNames[i]]) { - cursorKey += edgeNames[i]; - } + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } + if (dropEvents.deactivate) { + this.fireActiveDrops(dropEvents.deactivate); } - cursor = actionCursors[cursorKey]; + target.fire(endEvent); + } + else if (this.resizing) { + endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); + target.fire(endEvent); + } + else if (this.gesturing) { + endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); + target.fire(endEvent); } - } - return cursor; -} + this.stop(event); + }, -function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { - // false, '', undefined, null - if (!value) { return false; } + collectDrops: function (element) { + var drops = [], + elements = [], + i; - // true value, use pointer coords and element rect - if (value === true) { - // if dimensions are negative, "switch" edges - var width = isNumber(rect.width)? rect.width : rect.right - rect.left, - height = isNumber(rect.height)? rect.height : rect.bottom - rect.top; + element = element || this.element; - if (width < 0) { - if (name === 'left' ) { name = 'right'; } - else if (name === 'right') { name = 'left' ; } - } - if (height < 0) { - if (name === 'top' ) { name = 'bottom'; } - else if (name === 'bottom') { name = 'top' ; } - } + // collect all dropzones and their elements which qualify for a drop + for (i = 0; i < scope.interactables.length; i++) { + if (!scope.interactables[i].options.drop.enabled) { continue; } - if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } - if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } + var current = scope.interactables[i], + accept = current.options.drop.accept; - if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } - if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } - } + // test the draggable element against the dropzone's accept setting + if ((utils.isElement(accept) && accept !== element) + || (scope.isString(accept) + && !scope.matchesSelector(element, accept))) { - // the remaining checks require an element - if (!isElement(element)) { return false; } + continue; + } - return isElement(value) - // the value is an element to use as a resize handle - ? value === element - // otherwise check if element matches value as selector - : matchesUpTo(element, value, interactableElement); -} + // query for new elements if necessary + var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; -function defaultActionChecker (pointer, interaction, element) { - var rect = this.getRect(element), - shouldResize = false, - action = null, - resizeAxes = null, - resizeEdges, - page = extend({}, interaction.curCoords.page), - options = this.options; + for (var j = 0, len = dropElements.length; j < len; j++) { + var currentElement = dropElements[j]; - if (!rect) { return null; } + if (currentElement === element) { + continue; + } - if (actionIsEnabled.resize && options.resize.enabled) { - var resizeOptions = options.resize; + drops.push(current); + elements.push(currentElement); + } + } - resizeEdges = { - left: false, right: false, top: false, bottom: false + return { + dropzones: drops, + elements: elements }; + }, + + fireActiveDrops: function (event) { + var i, + current, + currentElement, + prevElement; + + // loop through all active dropzones and trigger event + for (i = 0; i < this.activeDrops.dropzones.length; i++) { + current = this.activeDrops.dropzones[i]; + currentElement = this.activeDrops.elements [i]; - // if using resize.edges - if (isObject(resizeOptions.edges)) { - for (var edge in resizeEdges) { - resizeEdges[edge] = checkResizeEdge(edge, - resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || margin); + // prevent trigger of duplicate events on same element + if (currentElement !== prevElement) { + // set current element as event target + event.target = currentElement; + current.fire(event); } + prevElement = currentElement; + } + }, + + // Collect a new set of possible drops and save them in activeDrops. + // setActiveDrops should always be called when a drag has just started or a + // drag event happens while dynamicDrop is true + setActiveDrops: function (dragElement) { + // get dropzones and their elements that could receive the draggable + var possibleDrops = this.collectDrops(dragElement, true); - resizeEdges.left = resizeEdges.left && !resizeEdges.right; - resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; + this.activeDrops.dropzones = possibleDrops.dropzones; + this.activeDrops.elements = possibleDrops.elements; + this.activeDrops.rects = []; - shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; + for (var i = 0; i < this.activeDrops.dropzones.length; i++) { + this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); } - else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - margin); + }, + + getDrop: function (event, dragElement) { + var validDrops = []; - shouldResize = right || bottom; - resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); + if (scope.dynamicDrop) { + this.setActiveDrops(dragElement); } - } - action = shouldResize - ? 'resize' - : actionIsEnabled.drag && options.drag.enabled - ? 'drag' - : null; + // collect all dropzones and their elements which qualify for a drop + for (var j = 0; j < this.activeDrops.dropzones.length; j++) { + var current = this.activeDrops.dropzones[j], + currentElement = this.activeDrops.elements [j], + rect = this.activeDrops.rects [j]; + + validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) + ? currentElement + : null); + } - if (actionIsEnabled.gesture - && interaction.pointerIds.length >=2 - && !(interaction.dragging || interaction.resizing)) { - action = 'gesture'; - } + // get the most appropriate dropzone based on DOM depth and order + var dropIndex = scope.indexOfDeepestElement(validDrops), + dropzone = this.activeDrops.dropzones[dropIndex] || null, + element = this.activeDrops.elements [dropIndex] || null; - if (action) { return { - name: action, - axis: resizeAxes, - edges: resizeEdges + dropzone: dropzone, + element: element }; - } - - return null; -} + }, -// Check if action is enabled globally and the current target supports it -// If so, return the validated action. Otherwise, return null -function validateAction (action, interactable) { - if (!isObject(action)) { return null; } + getDropEvents: function (pointerEvent, dragEvent) { + var dropEvents = { + enter : null, + leave : null, + activate : null, + deactivate: null, + move : null, + drop : null + }; - var actionName = action.name, - options = interactable.options; + if (this.dropElement !== this.prevDropElement) { + // if there was a prevDropTarget, create a dragleave event + if (this.prevDropTarget) { + dropEvents.leave = { + target : this.prevDropElement, + dropzone : this.prevDropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragleave' + }; - if (( (actionName === 'resize' && options.resize.enabled ) - || (actionName === 'drag' && options.drag.enabled ) - || (actionName === 'gesture' && options.gesture.enabled)) - && actionIsEnabled[actionName]) { + dragEvent.dragLeave = this.prevDropElement; + dragEvent.prevDropzone = this.prevDropTarget; + } + // if the dropTarget is not null, create a dragenter event + if (this.dropTarget) { + dropEvents.enter = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragenter' + }; - if (actionName === 'resize' || actionName === 'resizeyx') { - actionName = 'resizexy'; + dragEvent.dragEnter = this.dropElement; + dragEvent.dropzone = this.dropTarget; + } } - return action; - } - return null; -} - -var listeners = {}, - interactionListeners = [ - 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove', - 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', - 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', - 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' - ]; + if (dragEvent.type === 'dragend' && this.dropTarget) { + dropEvents.drop = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'drop' + }; -for (var i = 0, len = interactionListeners.length; i < len; i++) { - var name = interactionListeners[i]; + dragEvent.dropzone = this.dropTarget; + } + if (dragEvent.type === 'dragstart') { + dropEvents.activate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropactivate' + }; + } + if (dragEvent.type === 'dragend') { + dropEvents.deactivate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropdeactivate' + }; + } + if (dragEvent.type === 'dragmove' && this.dropTarget) { + dropEvents.move = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + dragmove : dragEvent, + timeStamp : dragEvent.timeStamp, + type : 'dropmove' + }; + dragEvent.dropzone = this.dropTarget; + } - listeners[name] = doOnInteractions(name); -} + return dropEvents; + }, -// bound to the interactable context when a DOM event -// listener is added to a selector interactable -function delegateListener (event, useCapture) { - var fakeEvent = {}, - delegated = delegatedEvents[event.type], - eventTarget = getActualElement(event.path - ? event.path[0] - : event.target), - element = eventTarget; - - useCapture = useCapture? true: false; - - // duplicate the event so that currentTarget can be changed - for (var prop in event) { - fakeEvent[prop] = event[prop]; - } + currentAction: function () { + return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; + }, - fakeEvent.originalEvent = event; - fakeEvent.preventDefault = preventOriginalDefault; + interacting: function () { + return this.dragging || this.resizing || this.gesturing; + }, - // climb up document tree looking for selector matches - while (isElement(element)) { - for (var i = 0; i < delegated.selectors.length; i++) { - var selector = delegated.selectors[i], - context = delegated.contexts[i]; + clearTargets: function () { + this.target = this.element = null; - if (matchesSelector(element, selector) - && nodeContains(context, eventTarget) - && nodeContains(context, element)) { + this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null; + }, - var listeners = delegated.listeners[i]; + stop: function (event) { + if (this.interacting()) { + scope.autoScroll.stop(); + this.matches = []; + this.matchElements = []; - fakeEvent.currentTarget = element; + var target = this.target; - for (var j = 0; j < listeners.length; j++) { - if (listeners[j][1] === useCapture) { - listeners[j][0](fakeEvent); - } - } + if (target.options.styleCursor) { + target._doc.documentElement.style.cursor = ''; } - } - element = parentElement(element); - } -} - -function delegateUseCapture (event) { - return delegateListener.call(this, event, true); -} - -interactables.indexOfElement = function indexOfElement (element, context) { - context = context || document; - - for (var i = 0; i < this.length; i++) { - var interactable = this[i]; + // prevent Default only if were previously interacting + if (event && scope.isFunction(event.preventDefault)) { + this.checkAndPreventDefault(event, target, this.element); + } - if ((interactable.selector === element - && (interactable._context === context)) - || (!interactable.selector && interactable._element === element)) { + if (this.dragging) { + this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; + } - return i; + this.clearTargets(); } - } - return -1; -}; - -interactables.get = function interactableGet (element, options) { - return this[this.indexOfElement(element, options && options.context)]; -}; -interactables.forEachSelector = function (callback) { - for (var i = 0; i < this.length; i++) { - var interactable = this[i]; + this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; + this.prepared.name = this.prevEvent = null; + this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; - if (!interactable.selector) { - continue; + // remove pointers if their ID isn't in this.pointerIds + for (var i = 0; i < this.pointers.length; i++) { + if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { + this.pointers.splice(i, 1); + } } - var ret = callback(interactable, interactable.selector, interactable._context, i, this); - - if (ret !== undefined) { - return ret; + for (i = 0; i < scope.interactions.length; i++) { + // remove this interaction if it's not the only one of it's type + if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { + scope.interactions.splice(scope.indexOf(scope.interactions, this), 1); + } } - } -}; - -/*\ - * interact - [ method ] - * - * The methods of this variable can be used to set elements as - * interactables and also to change various default settings. - * - * Calling it as a function and passing an element or a valid CSS selector - * string returns an Interactable object which has various methods to - * configure it. - * - - element (Element | string) The HTML or SVG Element to interact with or CSS selector - = (object) An @Interactable - * - > Usage - | interact(document.getElementById('draggable')).draggable(true); - | - | var rectables = interact('rect'); - | rectables - | .gesturable(true) - | .on('gesturemove', function (event) { - | // something cool... - | }) - | .autoScroll(true); - \*/ -function interact (element, options) { - return interactables.get(element, options) || new Interactable(element, options); -} + }, -/*\ - * Interactable - [ property ] - ** - * Object type returned by @interact - \*/ -function Interactable (element, options) { - this._element = element; - this._iEvents = this._iEvents || {}; + inertiaFrame: function () { + var inertiaStatus = this.inertiaStatus, + options = this.target.options[this.prepared.name].inertia, + lambda = options.resistance, + t = new Date().getTime() / 1000 - inertiaStatus.t0; - var _window; + if (t < inertiaStatus.te) { - if (trySelector(element)) { - this.selector = element; + var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; - var context = options && options.context; + if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { + inertiaStatus.sx = inertiaStatus.xe * progress; + inertiaStatus.sy = inertiaStatus.ye * progress; + } + else { + var quadPoint = scope.getQuadraticCurvePoint( + 0, 0, + inertiaStatus.xe, inertiaStatus.ye, + inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, + progress); - _window = context? getWindow(context) : window; + inertiaStatus.sx = quadPoint.x; + inertiaStatus.sy = quadPoint.y; + } - if (context && (_window.Node - ? context instanceof _window.Node - : (isElement(context) || context === _window.document))) { + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - this._context = context; + inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); } - } - else { - _window = getWindow(element); + else { + inertiaStatus.sx = inertiaStatus.modifiedXe; + inertiaStatus.sy = inertiaStatus.modifiedYe; - if (isElement(element, _window)) { + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - if (PointerEvent) { - events.add(this._element, pEventTypes.down, listeners.pointerDown ); - events.add(this._element, pEventTypes.move, listeners.pointerHover); - } - else { - events.add(this._element, 'mousedown' , listeners.pointerDown ); - events.add(this._element, 'mousemove' , listeners.pointerHover); - events.add(this._element, 'touchstart', listeners.pointerDown ); - events.add(this._element, 'touchmove' , listeners.pointerHover); - } + inertiaStatus.active = false; + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); } - } - - this._doc = _window.document; + }, - if (!contains(documents, this._doc)) { - listenToDocument(this._doc); - } + smoothEndFrame: function () { + var inertiaStatus = this.inertiaStatus, + t = new Date().getTime() - inertiaStatus.t0, + duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; - interactables.push(this); + if (t < duration) { + inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration); + inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration); - this.set(options); -} + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); -Interactable.prototype = { - setOnEvents: function (action, phases) { - if (action === 'drop') { - if (isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } - if (isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } - if (isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } - if (isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } - if (isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } - if (isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } + inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); } else { - action = 'on' + action; - - if (isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } - if (isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } - if (isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } - if (isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } - } - - return this; - }, + inertiaStatus.sx = inertiaStatus.xe; + inertiaStatus.sy = inertiaStatus.ye; - /*\ - * Interactable.draggable - [ method ] - * - * Gets or sets whether drag actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of drag events - | var isDraggable = interact('ul li').draggable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) - = (object) This Interactable - | interact(element).draggable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // the axis in which the first movement must be - | // for the drag sequence to start - | // 'xy' by default - any direction - | axis: 'x' || 'y' || 'xy', - | - | // max number of drags that can happen concurrently - | // with elements of this Interactable. Infinity by default - | max: Infinity, - | - | // max number of drags that can target the same element+Interactable - | // 1 by default - | maxPerElement: 2 - | }); - \*/ - draggable: function (options) { - if (isObject(options)) { - this.options.drag.enabled = options.enabled === false? false: true; - this.setPerAction('drag', options); - this.setOnEvents('drag', options); + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.drag.axis = options.axis; - } - else if (options.axis === null) { - delete this.options.drag.axis; - } + inertiaStatus.active = false; + inertiaStatus.smoothEnd = false; - return this; + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); } + }, - if (isBool(options)) { - this.options.drag.enabled = options; + addPointer: function (pointer) { + var id = utils.getPointerId(pointer), + index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); - return this; + if (index === -1) { + index = this.pointerIds.length; } - return this.options.drag; - }, + this.pointerIds[index] = id; + this.pointers[index] = pointer; - setPerAction: function (action, options) { - // for all the default per-action options - for (var option in options) { - // if this option exists for this action - if (option in defaultOptions[action]) { - // if the option in the options arg is an object value - if (isObject(options[option])) { - // duplicate the object - this.options[action][option] = extend(this.options[action][option] || {}, options[option]); - - if (isObject(defaultOptions.perAction[option]) && 'enabled' in defaultOptions.perAction[option]) { - this.options[action][option].enabled = options[option].enabled === false? false : true; - } - } - else if (isBool(options[option]) && isObject(defaultOptions.perAction[option])) { - this.options[action][option].enabled = options[option]; - } - else if (options[option] !== undefined) { - // or if it's not undefined, do a plain assignment - this.options[action][option] = options[option]; - } - } - } + return index; }, - /*\ - * Interactable.dropzone - [ method ] - * - * Returns or sets whether elements can be dropped onto this - * Interactable to trigger drop events - * - * Dropzones can receive the following events: - * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends - * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone - * - `dragmove` when a draggable that has entered the dropzone is moved - * - `drop` when a draggable is dropped into this dropzone - * - * Use the `accept` option to allow only elements that match the given CSS selector or element. - * - * Use the `overlap` option to set how drops are checked for. The allowed values are: - * - `'pointer'`, the pointer must be over the dropzone (default) - * - `'center'`, the draggable element's center must be over the dropzone - * - a number from 0-1 which is the `(intersection area) / (draggable area)`. - * e.g. `0.5` for drop to happen when half of the area of the - * draggable is over the dropzone - * - - options (boolean | object | null) #optional The new value to be set. - | interact('.drop').dropzone({ - | accept: '.can-drop' || document.getElementById('single-drop'), - | overlap: 'pointer' || 'center' || zeroToOne - | } - = (boolean | object) The current setting or this Interactable - \*/ - dropzone: function (options) { - if (isObject(options)) { - this.options.drop.enabled = options.enabled === false? false: true; - this.setOnEvents('drop', options); - this.accept(options.accept); + removePointer: function (pointer) { + var id = utils.getPointerId(pointer), + index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); - if (/^(pointer|center)$/.test(options.overlap)) { - this.options.drop.overlap = options.overlap; - } - else if (isNumber(options.overlap)) { - this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); - } + if (index === -1) { return; } - return this; + if (!this.interacting()) { + this.pointers.splice(index, 1); } - if (isBool(options)) { - this.options.drop.enabled = options; + this.pointerIds .splice(index, 1); + this.downTargets.splice(index, 1); + this.downTimes .splice(index, 1); + this.holdTimers .splice(index, 1); + }, + + recordPointer: function (pointer) { + // Do not update pointers while inertia is active. + // The inertia start event should be this.pointers[0] + if (this.inertiaStatus.active) { return; } + + var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - return this; - } + if (index === -1) { return; } - return this.options.drop; + this.pointers[index] = pointer; }, - dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) { - var dropped = false; + collectEventTargets: function (pointer, event, eventTarget, eventType) { + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - // if the dropzone has no rect (eg. display: none) - // call the custom dropChecker or just return false - if (!(rect = rect || this.getRect(dropElement))) { - return (this.options.dropChecker - ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) - : false); + // do not fire a tap event if the pointer was moved before being lifted + if (eventType === 'tap' && (this.pointerWasMoved + // or if the pointerup target is different to the pointerdown target + || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) { + return; } - var dropOverlap = this.options.drop.overlap; - - if (dropOverlap === 'pointer') { - var page = getPageXY(pointer), - origin = getOriginXY(draggable, draggableElement), - horizontal, - vertical; + var targets = [], + elements = [], + element = eventTarget; - page.x += origin.x; - page.y += origin.y; + function collectSelectors (interactable, selector, context) { + var els = scope.ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; - horizontal = (page.x > rect.left) && (page.x < rect.right); - vertical = (page.y > rect.top ) && (page.y < rect.bottom); + if (interactable._iEvents[eventType] + && utils.isElement(element) + && scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && scope.matchesSelector(element, selector, els)) { - dropped = horizontal && vertical; + targets.push(interactable); + elements.push(element); + } } - var dragRect = draggable.getRect(draggableElement); - if (dropOverlap === 'center') { - var cx = dragRect.left + dragRect.width / 2, - cy = dragRect.top + dragRect.height / 2; + var interact = scope.interact; - dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; - } + while (element) { + if (interact.isSet(element) && interact(element)._iEvents[eventType]) { + targets.push(interact(element)); + elements.push(element); + } - if (isNumber(dropOverlap)) { - var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) - * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), - overlapRatio = overlapArea / (dragRect.width * dragRect.height); + scope.interactables.forEachSelector(collectSelectors); - dropped = overlapRatio >= dropOverlap; + element = scope.parentElement(element); } - if (this.options.dropChecker) { - dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); + // create the tap event even if there are no listeners so that + // doubletap can still be created and fired + if (targets.length || eventType === 'tap') { + this.firePointers(pointer, event, eventTarget, targets, elements, eventType); } - - return dropped; }, - /*\ - * Interactable.dropChecker - [ method ] - * - * Gets or sets the function used to check if a dragged element is - * over this Interactable. - * - - checker (function) #optional The function that will be called when checking for a drop - = (Function | Interactable) The checker function or this Interactable - * - * The checker function takes the following arguments: - * - - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag - - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer - - dropped (boolean) The value from the default drop check - - dropzone (Interactable) The dropzone interactable - - dropElement (Element) The dropzone element - - draggable (Interactable) The Interactable being dragged - - draggableElement (Element) The actual element that's being dragged - * - > Usage: - | interact(target) - | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent - | event, // TouchEvent/PointerEvent/MouseEvent - | dropped, // result of the default checker - | dropzone, // dropzone Interactable - | dropElement, // dropzone elemnt - | draggable, // draggable Interactable - | draggableElement) {// draggable element - | - | return dropped && event.target.hasAttribute('allow-drop'); - | } - \*/ - dropChecker: function (checker) { - if (isFunction(checker)) { - this.options.dropChecker = checker; + firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { + var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)), + pointerEvent = {}, + i, + // for tap events + interval, createNewDoubleTap; - return this; + // if it's a doubletap then the event properties would have been + // copied from the tap event and provided as the pointer argument + if (eventType === 'doubletap') { + pointerEvent = pointer; } - if (checker === null) { - delete this.options.getRect; + else { + utils.extend(pointerEvent, event); + if (event !== pointer) { + utils.extend(pointerEvent, pointer); + } - return this; + pointerEvent.preventDefault = preventOriginalDefault; + pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; + pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; + pointerEvent.interaction = this; + + pointerEvent.timeStamp = new Date().getTime(); + pointerEvent.originalEvent = event; + pointerEvent.type = eventType; + pointerEvent.pointerId = utils.getPointerId(pointer); + pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' + : scope.isString(pointer.pointerType) + ? pointer.pointerType + : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; } - return this.options.dropChecker; - }, + if (eventType === 'tap') { + pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex]; - /*\ - * Interactable.accept - [ method ] - * - * Deprecated. add an `accept` property to the options object passed to - * @Interactable.dropzone instead. - * - * Gets or sets the Element or CSS selector match that this - * Interactable accepts if it is a dropzone. - * - - newValue (Element | string | null) #optional - * If it is an Element, then only that element can be dropped into this dropzone. - * If it is a string, the element being dragged must match it as a selector. - * If it is null, the accept options is cleared - it accepts any element. - * - = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable - \*/ - accept: function (newValue) { - if (isElement(newValue)) { - this.options.drop.accept = newValue; + interval = pointerEvent.timeStamp - this.tapTime; + createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap' + && this.prevTap.target === pointerEvent.target + && interval < 500); - return this; + pointerEvent.double = createNewDoubleTap; + + this.tapTime = pointerEvent.timeStamp; } - // test if it is a valid CSS selector - if (trySelector(newValue)) { - this.options.drop.accept = newValue; + for (i = 0; i < targets.length; i++) { + pointerEvent.currentTarget = elements[i]; + pointerEvent.interactable = targets[i]; + targets[i].fire(pointerEvent); - return this; + if (pointerEvent.immediatePropagationStopped + ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { + break; + } } - if (newValue === null) { - delete this.options.drop.accept; + if (createNewDoubleTap) { + var doubleTap = {}; + + utils.extend(doubleTap, pointerEvent); - return this; - } + doubleTap.dt = interval; + doubleTap.type = 'doubletap'; + + this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap'); - return this.options.drop.accept; + this.prevTap = doubleTap; + } + else if (eventType === 'tap') { + this.prevTap = pointerEvent; + } }, - /*\ - * Interactable.resizable - [ method ] - * - * Gets or sets whether resize actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of resize elements - | var isResizeable = interact('input[type=text]').resizable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) - = (object) This Interactable - | interact(element).resizable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | edges: { - | top : true, // Use pointer coords to check for resize. - | left : false, // Disable resizing from left edge. - | bottom: '.resize-s',// Resize if pointer target matches selector - | right : handleEl // Resize if pointer target is the given Element - | }, - | - | // a value of 'none' will limit the resize rect to a minimum of 0x0 - | // 'negate' will allow the rect to have negative width/height - | // 'reposition' will keep the width/height positive by swapping - | // the top and bottom edges and/or swapping the left and right edges - | invert: 'none' || 'negate' || 'reposition' - | - | // limit multiple resizes. - | // See the explanation in the @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - resizable: function (options) { - if (isObject(options)) { - this.options.resize.enabled = options.enabled === false? false: true; - this.setPerAction('resize', options); - this.setOnEvents('resize', options); + validateSelector: function (pointer, event, matches, matchElements) { + for (var i = 0, len = matches.length; i < len; i++) { + var match = matches[i], + matchElement = matchElements[i], + action = validateAction(match.getAction(pointer, event, this, matchElement), match); - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.resize.axis = options.axis; - } - else if (options.axis === null) { - this.options.resize.axis = defaultOptions.resize.axis; - } + if (action && scope.withinInteractionLimit(match, matchElement, action)) { + this.target = match; + this.element = matchElement; - if (isBool(options.square)) { - this.options.resize.square = options.square; + return action; } + } + }, + + setSnapping: function (pageCoords, status) { + var snap = this.target.options[this.prepared.name].snap, + targets = [], + target, + page, + i; - return this; - } - if (isBool(options)) { - this.options.resize.enabled = options; + status = status || this.snapStatus; - return this; + if (status.useStatusXY) { + page = { x: status.x, y: status.y }; } - return this.options.resize; - }, + else { + var origin = scope.getOriginXY(this.target, this.element); - /*\ - * Interactable.squareResize - [ method ] - * - * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead - * - * Gets or sets whether resizing is forced 1:1 aspect - * - = (boolean) Current setting - * - * or - * - - newValue (boolean) #optional - = (object) this Interactable - \*/ - squareResize: function (newValue) { - if (isBool(newValue)) { - this.options.resize.square = newValue; + page = utils.extend({}, pageCoords); - return this; + page.x -= origin.x; + page.y -= origin.y; } - if (newValue === null) { - delete this.options.resize.square; + status.realX = page.x; + status.realY = page.y; - return this; - } + page.x = page.x - this.inertiaStatus.resumeDx; + page.y = page.y - this.inertiaStatus.resumeDy; - return this.options.resize.square; - }, + var len = snap.targets? snap.targets.length : 0; - /*\ - * Interactable.gesturable - [ method ] - * - * Gets or sets whether multitouch gestures can be performed on the - * Interactable's element - * - = (boolean) Indicates if this can be the target of gesture events - | var isGestureable = interact(element).gesturable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) - = (object) this Interactable - | interact(element).gesturable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // limit multiple gestures. - | // See the explanation in @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - gesturable: function (options) { - if (isObject(options)) { - this.options.gesture.enabled = options.enabled === false? false: true; - this.setPerAction('gesture', options); - this.setOnEvents('gesture', options); + for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) { + var relative = { + x: page.x - this.snapOffsets[relIndex].x, + y: page.y - this.snapOffsets[relIndex].y + }; - return this; - } + for (i = 0; i < len; i++) { + if (scope.isFunction(snap.targets[i])) { + target = snap.targets[i](relative.x, relative.y, this); + } + else { + target = snap.targets[i]; + } - if (isBool(options)) { - this.options.gesture.enabled = options; + if (!target) { continue; } - return this; + targets.push({ + x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, + y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, + + range: scope.isNumber(target.range)? target.range: snap.range + }); + } } - return this.options.gesture; - }, + var closest = { + target: null, + inRange: false, + distance: 0, + range: 0, + dx: 0, + dy: 0 + }; - /*\ - * Interactable.autoScroll - [ method ] - ** - * Deprecated. Add an `autoscroll` property to the options object - * passed to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets whether dragging and resizing near the edges of the - * window/container trigger autoScroll for this Interactable - * - = (object) Object with autoScroll properties - * - * or - * - - options (object | boolean) #optional - * options can be: - * - an object with margin, distance and interval properties, - * - true or false to enable or disable autoScroll or - = (Interactable) this Interactable - \*/ - autoScroll: function (options) { - if (isObject(options)) { - options = extend({ actions: ['drag', 'resize']}, options); - } - else if (isBool(options)) { - options = { actions: ['drag', 'resize'], enabled: options }; - } + for (i = 0, len = targets.length; i < len; i++) { + target = targets[i]; - return this.setOptions('autoScroll', options); - }, + var range = target.range, + dx = target.x - page.x, + dy = target.y - page.y, + distance = utils.hypot(dx, dy), + inRange = distance <= range; - /*\ - * Interactable.snap - [ method ] - ** - * Deprecated. Add a `snap` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how action coordinates are snapped. By - * default, snapping is relative to the pointer coordinates. You can - * change this by setting the - * [`elementOrigin`](https://github.com/taye/interact.js/pull/72). - ** - = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | interact(document.querySelector('#thing')).snap({ - | targets: [ - | // snap to this specific point - | { - | x: 100, - | y: 100, - | range: 25 - | }, - | // give this function the x and y page coords and snap to the object returned - | function (x, y) { - | return { - | x: x, - | y: (75 + 50 * Math.sin(x * 0.04)), - | range: 40 - | }; - | }, - | // create a function that snaps to a grid - | interact.createSnapGrid({ - | x: 50, - | y: 50, - | range: 10, // optional - | offset: { x: 5, y: 10 } // optional - | }) - | ], - | // do not snap during normal movement. - | // Instead, trigger only one snapped move event - | // immediately before the end event. - | endOnly: true, - | - | relativePoints: [ - | { x: 0, y: 0 }, // snap relative to the top left of the element - | { x: 1, y: 1 }, // and also to the bottom right - | ], - | - | // offset the snap target coordinates - | // can be an object with x/y or 'startCoords' - | offset: { x: 50, y: 50 } - | } - | }); - \*/ - snap: function (options) { - var ret = this.setOptions('snap', options); + // Infinite targets count as being out of range + // compared to non infinite ones that are in range + if (range === Infinity && closest.inRange && closest.range !== Infinity) { + inRange = false; + } - if (ret === this) { return this; } + if (!closest.target || (inRange + // is the closest target in range? + ? (closest.inRange && range !== Infinity + // the pointer is relatively deeper in this target + ? distance / range < closest.distance / closest.range + // this target has Infinite range and the closest doesn't + : (range === Infinity && closest.range !== Infinity) + // OR this target is closer that the previous closest + || distance < closest.distance) + // The other is not in range and the pointer is closer to this target + : (!closest.inRange && distance < closest.distance))) { - return ret.drag; - }, + if (range === Infinity) { + inRange = true; + } - setOptions: function (option, options) { - var actions = options && isArray(options.actions) - ? options.actions - : ['drag']; + closest.target = target; + closest.distance = distance; + closest.range = range; + closest.inRange = inRange; + closest.dx = dx; + closest.dy = dy; - var i; + status.range = range; + } + } - if (isObject(options) || isBool(options)) { - for (i = 0; i < actions.length; i++) { - var action = /resize/.test(actions[i])? 'resize' : actions[i]; + var snapChanged; - if (!isObject(this.options[action])) { continue; } + if (closest.target) { + snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); - var thisOption = this.options[action][option]; + status.snappedX = closest.target.x; + status.snappedY = closest.target.y; + } + else { + snapChanged = true; - if (isObject(options)) { - extend(thisOption, options); - thisOption.enabled = options.enabled === false? false: true; + status.snappedX = NaN; + status.snappedY = NaN; + } - if (option === 'snap') { - if (thisOption.mode === 'grid') { - thisOption.targets = [ - interact.createSnapGrid(extend({ - offset: thisOption.gridOffset || { x: 0, y: 0 } - }, thisOption.grid || {})) - ]; - } - else if (thisOption.mode === 'anchor') { - thisOption.targets = thisOption.anchors; - } - else if (thisOption.mode === 'path') { - thisOption.targets = thisOption.paths; - } + status.dx = closest.dx; + status.dy = closest.dy; - if ('elementOrigin' in options) { - thisOption.relativePoints = [options.elementOrigin]; - } - } - } - else if (isBool(options)) { - thisOption.enabled = options; - } - } + status.changed = (snapChanged || (closest.inRange && !status.locked)); + status.locked = closest.inRange; - return this; - } + return status; + }, - var ret = {}, - allActions = ['drag', 'resize', 'gesture']; + setRestriction: function (pageCoords, status) { + var target = this.target, + restrict = target && target.options[this.prepared.name].restrict, + restriction = restrict && restrict.restriction, + page; - for (i = 0; i < allActions.length; i++) { - if (option in defaultOptions[allActions[i]]) { - ret[allActions[i]] = this.options[allActions[i]][option]; - } + if (!restriction) { + return status; } - return ret; - }, + status = status || this.restrictStatus; + page = status.useStatusXY + ? page = { x: status.x, y: status.y } + : page = utils.extend({}, pageCoords); - /*\ - * Interactable.inertia - [ method ] - ** - * Deprecated. Add an `inertia` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how events continue to run after the pointer is released - ** - = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | // enable and use default settings - | interact(element).inertia(true); - | - | // enable and use custom settings - | interact(element).inertia({ - | // value greater than 0 - | // high values slow the object down more quickly - | resistance : 16, - | - | // the minimum launch speed (pixels per second) that results in inertia start - | minSpeed : 200, - | - | // inertia will stop when the object slows down to this speed - | endSpeed : 20, - | - | // boolean; should actions be resumed when the pointer goes down during inertia - | allowResume : true, - | - | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy - | zeroResumeDelta: false, - | - | // if snap/restrict are set to be endOnly and inertia is enabled, releasing - | // the pointer without triggering inertia will animate from the release - | // point to the snaped/restricted point in the given amount of time (ms) - | smoothEndDuration: 300, - | - | // an array of action types that can have inertia (no gesture) - | actions : ['drag', 'resize'] - | }); - | - | // reset custom settings and use all defaults - | interact(element).inertia(null); - \*/ - inertia: function (options) { - var ret = this.setOptions('inertia', options); + if (status.snap && status.snap.locked) { + page.x += status.snap.dx || 0; + page.y += status.snap.dy || 0; + } - if (ret === this) { return this; } + page.x -= this.inertiaStatus.resumeDx; + page.y -= this.inertiaStatus.resumeDy; - return ret.drag; - }, + status.dx = 0; + status.dy = 0; + status.restricted = false; + + var rect, restrictedX, restrictedY; - getAction: function (pointer, event, interaction, element) { - var action = this.defaultActionChecker(pointer, interaction, element); + if (scope.isString(restriction)) { + if (restriction === 'parent') { + restriction = scope.parentElement(this.element); + } + else if (restriction === 'self') { + restriction = target.getRect(this.element); + } + else { + restriction = scope.closest(this.element, restriction); + } - if (this.options.actionChecker) { - return this.options.actionChecker(pointer, event, action, this, element, interaction); + if (!restriction) { return status; } } - return action; - }, + if (scope.isFunction(restriction)) { + restriction = restriction(page.x, page.y, this.element); + } - defaultActionChecker: defaultActionChecker, + if (utils.isElement(restriction)) { + restriction = scope.getElementRect(restriction); + } - /*\ - * Interactable.actionChecker - [ method ] - * - * Gets or sets the function used to check action to be performed on - * pointerDown - * - - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. - = (Function | Interactable) The checker function or this Interactable - * - | interact('.resize-drag') - | .resizable(true) - | .draggable(true) - | .actionChecker(function (pointer, event, action, interactable, element, interaction) { - | - | if (interact.matchesSelector(event.target, '.drag-handle') { - | // force drag with handle target - | action.name = drag; - | } - | else { - | // resize from the top and right edges - | action.name = 'resize'; - | action.edges = { top: true, right: true }; - | } - | - | return action; - | }); - \*/ - actionChecker: function (checker) { - if (isFunction(checker)) { - this.options.actionChecker = checker; + rect = restriction; - return this; + if (!restriction) { + restrictedX = page.x; + restrictedY = page.y; + } + // object is assumed to have + // x, y, width, height or + // left, top, right, bottom + else if ('x' in restriction && 'y' in restriction) { + restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left); + restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top ); + } + else { + restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left); + restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top ); } - if (checker === null) { - delete this.options.actionChecker; + status.dx = restrictedX - page.x; + status.dy = restrictedY - page.y; + + status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; + status.restricted = !!(status.dx || status.dy); - return this; - } + status.restrictedX = restrictedX; + status.restrictedY = restrictedY; - return this.options.actionChecker; + return status; }, - /*\ - * Interactable.getRect - [ method ] - * - * The default function to get an Interactables bounding rect. Can be - * overridden using @Interactable.rectChecker. - * - - element (Element) #optional The element to measure. - = (object) The object's bounding rectangle. - o { - o top : 0, - o left : 0, - o bottom: 0, - o right : 0, - o width : 0, - o height: 0 - o } - \*/ - getRect: function rectCheck (element) { - element = element || this._element; + checkAndPreventDefault: function (event, interactable, element) { + if (!(interactable = interactable || this.target)) { return; } - if (this.selector && !(isElement(element))) { - element = this._context.querySelector(this.selector); - } + var options = interactable.options, + prevent = options.preventDefault; + + if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { + // do not preventDefault on pointerdown if the prepared action is a drag + // and dragging can only start from a certain direction - this allows + // a touch to pan the viewport if a drag isn't in the right direction + if (/down|start/i.test(event.type) + && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { - return getElementRect(element); - }, + return; + } - /*\ - * Interactable.rectChecker - [ method ] - * - * Returns or sets the function used to calculate the interactable's - * element's rectangle - * - - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect - = (function | object) The checker function or this Interactable - \*/ - rectChecker: function (checker) { - if (isFunction(checker)) { - this.getRect = checker; + // with manualStart, only preventDefault while interacting + if (options[this.prepared.name] && options[this.prepared.name].manualStart + && !this.interacting()) { + return; + } - return this; + event.preventDefault(); + return; } - if (checker === null) { - delete this.options.getRect; - - return this; + if (prevent === 'always') { + event.preventDefault(); + return; } - - return this.getRect; }, - /*\ - * Interactable.styleCursor - [ method ] - * - * Returns or sets whether the action that would be performed when the - * mouse on the element are checked on `mousemove` so that the cursor - * may be styled appropriately - * - - newValue (boolean) #optional - = (boolean | Interactable) The current setting or this Interactable - \*/ - styleCursor: function (newValue) { - if (isBool(newValue)) { - this.options.styleCursor = newValue; - - return this; - } + calcInertia: function (status) { + var inertiaOptions = this.target.options[this.prepared.name].inertia, + lambda = inertiaOptions.resistance, + inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; - if (newValue === null) { - delete this.options.styleCursor; + status.x0 = this.prevEvent.pageX; + status.y0 = this.prevEvent.pageY; + status.t0 = status.startEvent.timeStamp / 1000; + status.sx = status.sy = 0; - return this; - } + status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; + status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; + status.te = inertiaDur; - return this.options.styleCursor; + status.lambda_v0 = lambda / status.v0; + status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; }, - /*\ - * Interactable.preventDefault - [ method ] - * - * Returns or sets whether to prevent the browser's default behaviour - * in response to pointer events. Can be set to: - * - `'always'` to always prevent - * - `'never'` to never prevent - * - `'auto'` to let interact.js try to determine what would be best - * - - newValue (string) #optional `true`, `false` or `'auto'` - = (string | Interactable) The current setting or this Interactable - \*/ - preventDefault: function (newValue) { - if (/^(always|never|auto)$/.test(newValue)) { - this.options.preventDefault = newValue; - return this; + autoScrollMove: function (pointer) { + if (!(this.interacting() + && scope.checkAutoScroll(this.target, this.prepared.name))) { + return; } - if (isBool(newValue)) { - this.options.preventDefault = newValue? 'always' : 'never'; - return this; + if (this.inertiaStatus.active) { + scope.autoScroll.x = scope.autoScroll.y = 0; + return; } - return this.options.preventDefault; - }, + var top, + right, + bottom, + left, + options = this.target.options[this.prepared.name].autoScroll, + container = options.container || scope.getWindow(this.element); - /*\ - * Interactable.origin - [ method ] - * - * Gets or sets the origin of the Interactable's element. The x and y - * of the origin will be subtracted from action event coordinates. - * - - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector - * OR - - origin (Element) #optional An HTML or SVG Element whose rect will be used - ** - = (object) The current origin or this Interactable - \*/ - origin: function (newValue) { - if (trySelector(newValue)) { - this.options.origin = newValue; - return this; + if (scope.isWindow(container)) { + left = pointer.clientX < scope.autoScroll.margin; + top = pointer.clientY < scope.autoScroll.margin; + right = pointer.clientX > container.innerWidth - scope.autoScroll.margin; + bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin; } - else if (isObject(newValue)) { - this.options.origin = newValue; - return this; + else { + var rect = scope.getElementRect(container); + + left = pointer.clientX < rect.left + scope.autoScroll.margin; + top = pointer.clientY < rect.top + scope.autoScroll.margin; + right = pointer.clientX > rect.right - scope.autoScroll.margin; + bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin; } - return this.options.origin; - }, + scope.autoScroll.x = (right ? 1: left? -1: 0); + scope.autoScroll.y = (bottom? 1: top? -1: 0); - /*\ - * Interactable.deltaSource - [ method ] - * - * Returns or sets the mouse coordinate types used to calculate the - * movement of the pointer. - * - - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work - = (string | object) The current deltaSource or this Interactable - \*/ - deltaSource: function (newValue) { - if (newValue === 'page' || newValue === 'client') { - this.options.deltaSource = newValue; + if (!scope.autoScroll.isScrolling) { + // set the autoScroll properties to those of the target + scope.autoScroll.margin = options.margin; + scope.autoScroll.speed = options.speed; - return this; + scope.autoScroll.start(this); } - - return this.options.deltaSource; }, - /*\ - * Interactable.restrict - [ method ] - ** - * Deprecated. Add a `restrict` property to the options object passed to - * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead. - * - * Returns or sets the rectangles within which actions on this - * interactable (after snap calculations) are restricted. By default, - * restricting is relative to the pointer coordinates. You can change - * this by setting the - * [`elementRect`](https://github.com/taye/interact.js/pull/72). - ** - - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self' - = (object) The current restrictions object or this Interactable - ** - | interact(element).restrict({ - | // the rect will be `interact.getElementRect(element.parentNode)` - | drag: element.parentNode, - | - | // x and y are relative to the the interactable's origin - | resize: { x: 100, y: 100, width: 200, height: 200 } - | }) - | - | interact('.draggable').restrict({ - | // the rect will be the selected element's parent - | drag: 'parent', - | - | // do not restrict during normal movement. - | // Instead, trigger only one restricted move event - | // immediately before the end event. - | endOnly: true, - | - | // https://github.com/taye/interact.js/pull/72#issue-41813493 - | elementRect: { top: 0, left: 0, bottom: 1, right: 1 } - | }); - \*/ - restrict: function (options) { - if (!isObject(options)) { - return this.setOptions('restrict', options); - } + _updateEventTargets: function (target, currentTarget) { + this._eventTarget = target; + this._curEventTarget = currentTarget; + } - var actions = ['drag', 'resize', 'gesture'], - ret; +}; - for (var i = 0; i < actions.length; i++) { - var action = actions[i]; +module.exports = Interaction; +},{"./InteractEvent":2,"./scope":6,"./utils":13,"./utils/browser":8,"./utils/events":10}],4:[function(require,module,exports){ +'use strict'; - if (action in options) { - var perAction = extend({ - actions: [action], - restriction: options[action] - }, options); +var raf = require('./utils/raf'), + getWindow = require('./utils/window').getWindow, + isWindow = require('./utils/isType').isWindow; - ret = this.setOptions('restrict', perAction); - } - } +var autoScroll = { - return ret; + interaction: null, + i: null, // the handle returned by window.setInterval + x: 0, y: 0, // Direction each pulse is to scroll in + + isScrolling: false, + prevTime: 0, + + start: function (interaction) { + autoScroll.isScrolling = true; + raf.cancel(autoScroll.i); + + autoScroll.interaction = interaction; + autoScroll.prevTime = new Date().getTime(); + autoScroll.i = raf.request(autoScroll.scroll); }, - /*\ - * Interactable.context - [ method ] - * - * Gets the selector context Node of the Interactable. The default is `window.document`. - * - = (Node) The context Node of this Interactable - ** - \*/ - context: function () { - return this._context; + stop: function () { + autoScroll.isScrolling = false; + raf.cancel(autoScroll.i); }, - _context: document, + // scroll the window by the values in scroll.x/y + scroll: function () { + var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll, + container = options.container || getWindow(autoScroll.interaction.element), + now = new Date().getTime(), + // change in time in seconds + dt = (now - autoScroll.prevTime) / 1000, + // displacement + s = options.speed * dt; - /*\ - * Interactable.ignoreFrom - [ method ] - * - * If the target of the `mousedown`, `pointerdown` or `touchstart` - * event or any of it's parents match the given CSS selector or - * Element, no drag/resize/gesture is started. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements - = (string | Element | object) The current ignoreFrom value or this Interactable - ** - | interact(element, { ignoreFrom: document.getElementById('no-action') }); - | // or - | interact(element).ignoreFrom('input, textarea, a'); - \*/ - ignoreFrom: function (newValue) { - if (trySelector(newValue)) { // CSS selector to match event.target - this.options.ignoreFrom = newValue; - return this; - } + if (s >= 1) { + if (isWindow(container)) { + container.scrollBy(autoScroll.x * s, autoScroll.y * s); + } + else if (container) { + container.scrollLeft += autoScroll.x * s; + container.scrollTop += autoScroll.y * s; + } - if (isElement(newValue)) { // specific element - this.options.ignoreFrom = newValue; - return this; + autoScroll.prevTime = now; } - return this.options.ignoreFrom; - }, - - /*\ - * Interactable.allowFrom - [ method ] - * - * A drag/resize/gesture is started only If the target of the - * `mousedown`, `pointerdown` or `touchstart` event or any of it's - * parents match the given CSS selector or Element. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element - = (string | Element | object) The current allowFrom value or this Interactable - ** - | interact(element, { allowFrom: document.getElementById('drag-handle') }); - | // or - | interact(element).allowFrom('.handle'); - \*/ - allowFrom: function (newValue) { - if (trySelector(newValue)) { // CSS selector to match event.target - this.options.allowFrom = newValue; - return this; + if (autoScroll.isScrolling) { + raf.cancel(autoScroll.i); + autoScroll.i = raf.request(autoScroll.scroll); } + } +}; - if (isElement(newValue)) { // specific element - this.options.allowFrom = newValue; - return this; - } +module.exports = autoScroll; - return this.options.allowFrom; - }, +},{"./utils/isType":14,"./utils/raf":16,"./utils/window":17}],5:[function(require,module,exports){ +'use strict'; - /*\ - * Interactable.element - [ method ] - * - * If this is not a selector Interactable, it returns the element this - * interactable represents - * - = (Element) HTML / SVG Element - \*/ - element: function () { - return this._element; +module.exports = { + base: { + accept : null, + actionChecker : null, + styleCursor : true, + preventDefault: 'auto', + origin : { x: 0, y: 0 }, + deltaSource : 'page', + allowFrom : null, + ignoreFrom : null, + _context : require('./utils/domObjects').document, + dropChecker : null }, - /*\ - * Interactable.fire - [ method ] - * - * Calls listeners for the given InteractEvent type bound globally - * and directly to this Interactable - * - - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable - = (Interactable) this Interactable - \*/ - fire: function (iEvent) { - if (!(iEvent && iEvent.type) || !contains(eventTypes, iEvent.type)) { - return this; - } - - var listeners, - i, - len, - onEvent = 'on' + iEvent.type, - funcName = ''; + drag: { + enabled: false, + manualStart: true, + max: Infinity, + maxPerElement: 1, - // Interactable#on() listeners - if (iEvent.type in this._iEvents) { - listeners = this._iEvents[iEvent.type]; + snap: null, + restrict: null, + inertia: null, + autoScroll: null, - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } + axis: 'xy', + }, - // interactable.onevent listener - if (isFunction(this[onEvent])) { - funcName = this[onEvent].name; - this[onEvent](iEvent); - } + drop: { + enabled: false, + accept: null, + overlap: 'pointer' + }, - // interact.on() listeners - if (iEvent.type in globalEvents && (listeners = globalEvents[iEvent.type])) { + resize: { + enabled: false, + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + square: false, + axis: 'xy', + + // use default margin + margin: NaN, + + // object with props left, right, top, bottom which are + // true/false values to resize when the pointer is over that edge, + // CSS selectors to match the handles for each direction + // or the Elements for each handle + edges: null, + + // a value of 'none' will limit the resize rect to a minimum of 0x0 + // 'negate' will alow the rect to have negative width/height + // 'reposition' will keep the width/height positive by swapping + // the top and bottom edges and/or swapping the left and right edges + invert: 'none' + }, - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } + gesture: { + manualStart: false, + enabled: false, + max: Infinity, + maxPerElement: 1, - return this; + restrict: null }, - /*\ - * Interactable.on - [ method ] - * - * Binds a listener for an InteractEvent or DOM event. - * - - eventType (string | array | object) The types of events to listen for - - listener (function) The function to be called on the given event(s) - - useCapture (boolean) #optional useCapture flag for addEventListener - = (object) This Interactable - \*/ - on: function (eventType, listener, useCapture) { - var i; - - if (isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } + perAction: { + manualStart: false, + max: Infinity, + maxPerElement: 1, - if (isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.on(eventType[i], listener, useCapture); - } + snap: { + enabled : false, + endOnly : false, + range : Infinity, + targets : null, + offsets : null, - return this; - } + relativePoints: null + }, - if (isObject(eventType)) { - for (var prop in eventType) { - this.on(prop, eventType[prop], listener); - } + restrict: { + enabled: false, + endOnly: false + }, - return this; - } + autoScroll: { + enabled : false, + container : null, // the item that is scrolled (Window or HTMLElement) + margin : 60, + speed : 300 // the scroll speed in pixels per second + }, - if (eventType === 'wheel') { - eventType = wheelEvent; + inertia: { + enabled : false, + resistance : 10, // the lambda in exponential decay + minSpeed : 100, // target speed must be above this for inertia to start + endSpeed : 10, // the speed at which inertia is slow enough to stop + allowResume : true, // allow resuming an action in inertia phase + zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 + smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia } + }, - // convert to boolean - useCapture = useCapture? true: false; - - if (contains(eventTypes, eventType)) { - // if this type of event was never bound to this Interactable - if (!(eventType in this._iEvents)) { - this._iEvents[eventType] = [listener]; - } - else { - this._iEvents[eventType].push(listener); - } - } - // delegated event for selector - else if (this.selector) { - if (!delegatedEvents[eventType]) { - delegatedEvents[eventType] = { - selectors: [], - contexts : [], - listeners: [] - }; + _holdDuration: 600 +}; - // add delegate listener functions - for (i = 0; i < documents.length; i++) { - events.add(documents[i], eventType, delegateListener); - events.add(documents[i], eventType, delegateUseCapture, true); - } - } +},{"./utils/domObjects":9}],6:[function(require,module,exports){ +'use strict'; - var delegated = delegatedEvents[eventType], - index; +var scope = {}, + extend = require('./utils/extend'); - for (index = delegated.selectors.length - 1; index >= 0; index--) { - if (delegated.selectors[index] === this.selector - && delegated.contexts[index] === this._context) { - break; - } - } +extend(scope, require('./utils/window')); +extend(scope, require('./utils/domObjects')); +extend(scope, require('./utils/arr.js')); +extend(scope, require('./utils/isType')); - if (index === -1) { - index = delegated.selectors.length; +module.exports = scope; - delegated.selectors.push(this.selector); - delegated.contexts .push(this._context); - delegated.listeners.push([]); - } +},{"./utils/arr.js":7,"./utils/domObjects":9,"./utils/extend":11,"./utils/isType":14,"./utils/window":17}],7:[function(require,module,exports){ +'use strict'; - // keep listener and useCapture flag - delegated.listeners[index].push([listener, useCapture]); - } - else { - events.add(this._element, eventType, listener, useCapture); +function indexOf (array, target) { + for (var i = 0, len = array.length; i < len; i++) { + if (array[i] === target) { + return i; } + } - return this; - }, - - /*\ - * Interactable.off - [ method ] - * - * Removes an InteractEvent or DOM event listener - * - - eventType (string | array | object) The types of events that were listened for - - listener (function) The listener function to be removed - - useCapture (boolean) #optional useCapture flag for removeEventListener - = (object) This Interactable - \*/ - off: function (eventType, listener, useCapture) { - var i; + return -1; +} - if (isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } +function contains (array, target) { + return indexOf(array, target) !== -1; +} - if (isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.off(eventType[i], listener, useCapture); - } +module.exports = { + indexOf: indexOf, + contains: contains +}; - return this; - } +},{}],8:[function(require,module,exports){ +'use strict'; - if (isObject(eventType)) { - for (var prop in eventType) { - this.off(prop, eventType[prop], listener); - } +var win = require('./window'), + domObjects = require('./domObjects'); - return this; - } +var browser = { + // Does the browser support touch input? + supportsTouch : !!(('ontouchstart' in win) || win.window.DocumentTouch + && domObjects.document instanceof win.DocumentTouch), - var eventList, - index = -1; + // Does the browser support PointerEvents + supportsPointerEvent : !!domObjects.PointerEvent, - // convert to boolean - useCapture = useCapture? true: false; + // Opera Mobile must be handled differently + isOperaMobile : (navigator.appName === 'Opera' + && browser.supportsTouch + && navigator.userAgent.match('Presto')), - if (eventType === 'wheel') { - eventType = wheelEvent; - } + // scrolling doesn't change the result of + // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 + isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)), - // if it is an action event type - if (contains(eventTypes, eventType)) { - eventList = this._iEvents[eventType]; + isIe9OrOlder : domObjects.document.all && !win.window.atob +}; - if (eventList && (index = indexOf(eventList, listener)) !== -1) { - this._iEvents[eventType].splice(index, 1); - } - } - // delegated event - else if (this.selector) { - var delegated = delegatedEvents[eventType], - matchFound = false; +module.exports = browser; - if (!delegated) { return this; } +},{"./domObjects":9,"./window":17}],9:[function(require,module,exports){ +'use strict'; - // count from last index of delegated to 0 - for (index = delegated.selectors.length - 1; index >= 0; index--) { - // look for matching selector and context Node - if (delegated.selectors[index] === this.selector - && delegated.contexts[index] === this._context) { +var domObjects = {}, + win = require('./window').window, + blank = function () {}; - var listeners = delegated.listeners[index]; +domObjects.document = win.document; +domObjects.DocumentFragment = win.DocumentFragment || blank; +domObjects.SVGElement = win.SVGElement || blank; +domObjects.SVGSVGElement = win.SVGSVGElement || blank; +domObjects.SVGElementInstance = win.SVGElementInstance || blank; +domObjects.HTMLElement = win.HTMLElement || win.Element; - // each item of the listeners array is an array: [function, useCaptureFlag] - for (i = listeners.length - 1; i >= 0; i--) { - var fn = listeners[i][0], - useCap = listeners[i][1]; +domObjects.PointerEvent = (win.PointerEvent || win.MSPointerEvent); - // check if the listener functions and useCapture flags match - if (fn === listener && useCap === useCapture) { - // remove the listener from the array of listeners - listeners.splice(i, 1); +module.exports = domObjects; - // if all listeners for this interactable have been removed - // remove the interactable from the delegated arrays - if (!listeners.length) { - delegated.selectors.splice(index, 1); - delegated.contexts .splice(index, 1); - delegated.listeners.splice(index, 1); +},{"./window":17}],10:[function(require,module,exports){ +'use strict'; - // remove delegate function from context - events.remove(this._context, eventType, delegateListener); - events.remove(this._context, eventType, delegateUseCapture, true); +var arr = require('./arr'), + indexOf = arr.indexOf, + contains = arr.contains, + getWindow = require('./window').getWindow, - // remove the arrays if they are empty - if (!delegated.selectors.length) { - delegatedEvents[eventType] = null; - } - } + useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window), + addEvent = useAttachEvent? 'attachEvent': 'addEventListener', + removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', + on = useAttachEvent? 'on': '', - // only remove one listener - matchFound = true; - break; - } - } + elements = [], + targets = [], + attachedListeners = []; - if (matchFound) { break; } - } - } - } - // remove listener from this Interatable's element - else { - events.remove(this._element, eventType, listener, useCapture); - } +function add (element, type, listener, useCapture) { + var elementIndex = indexOf(elements, element), + target = targets[elementIndex]; - return this; - }, + if (!target) { + target = { + events: {}, + typeCount: 0 + }; - /*\ - * Interactable.set - [ method ] - * - * Reset the options of this Interactable - - options (object) The new settings to apply - = (object) This Interactablw - \*/ - set: function (options) { - if (!isObject(options)) { - options = {}; - } + elementIndex = elements.push(element) - 1; + targets.push(target); - this.options = extend({}, defaultOptions.base); + attachedListeners.push((useAttachEvent ? { + supplied: [], + wrapped : [], + useCount: [] + } : null)); + } - var i, - actions = ['drag', 'drop', 'resize', 'gesture'], - methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], - perActions = extend(extend({}, defaultOptions.perAction), options[action] || {}); + if (!target.events[type]) { + target.events[type] = []; + target.typeCount++; + } - for (i = 0; i < actions.length; i++) { - var action = actions[i]; + if (!contains(target.events[type], listener)) { + var ret; - this.options[action] = extend({}, defaultOptions[action]); + if (useAttachEvent) { + var listeners = attachedListeners[elementIndex], + listenerIndex = indexOf(listeners.supplied, listener); - this.setPerAction(action, perActions); + var wrapped = listeners.wrapped[listenerIndex] || function (event) { + if (!event.immediatePropagationStopped) { + event.target = event.srcElement; + event.currentTarget = element; - this[methods[i]](options[action]); - } + event.preventDefault = event.preventDefault || preventDef; + event.stopPropagation = event.stopPropagation || stopProp; + event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; - var settings = [ - 'accept', 'actionChecker', 'allowFrom', 'deltaSource', - 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', - 'rectChecker' - ]; + if (/mouse|click/.test(event.type)) { + event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; + event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; + } - for (i = 0, len = settings.length; i < len; i++) { - var setting = settings[i]; + listener(event); + } + }; - this.options[setting] = defaultOptions.base[setting]; + ret = element[addEvent](on + type, wrapped, Boolean(useCapture)); - if (setting in options) { - this[setting](options[setting]); + if (listenerIndex === -1) { + listeners.supplied.push(listener); + listeners.wrapped.push(wrapped); + listeners.useCount.push(1); + } + else { + listeners.useCount[listenerIndex]++; } } + else { + ret = element[addEvent](type, listener, useCapture || false); + } + target.events[type].push(listener); - return this; - }, + return ret; + } +} - /*\ - * Interactable.unset - [ method ] - * - * Remove this interactable from the list of interactables and remove - * it's drag, drop, resize and gesture capabilities - * - = (object) @interact - \*/ - unset: function () { - events.remove(this._element, 'all'); +function remove (element, type, listener, useCapture) { + var i, + elementIndex = indexOf(elements, element), + target = targets[elementIndex], + listeners, + listenerIndex, + wrapped = listener; + + if (!target || !target.events) { + return; + } + + if (useAttachEvent) { + listeners = attachedListeners[elementIndex]; + listenerIndex = indexOf(listeners.supplied, listener); + wrapped = listeners.wrapped[listenerIndex]; + } - if (!isString(this.selector)) { - events.remove(this, 'all'); - if (this.options.styleCursor) { - this._element.style.cursor = ''; + if (type === 'all') { + for (type in target.events) { + if (target.events.hasOwnProperty(type)) { + remove(element, type, 'all'); } } - else { - // remove delegated events - for (var type in delegatedEvents) { - var delegated = delegatedEvents[type]; - - for (var i = 0; i < delegated.selectors.length; i++) { - if (delegated.selectors[i] === this.selector - && delegated.contexts[i] === this._context) { + return; + } - delegated.selectors.splice(i, 1); - delegated.contexts .splice(i, 1); - delegated.listeners.splice(i, 1); + if (target.events[type]) { + var len = target.events[type].length; - // remove the arrays if they are empty - if (!delegated.selectors.length) { - delegatedEvents[type] = null; + if (listener === 'all') { + for (i = 0; i < len; i++) { + remove(element, type, target.events[type][i], Boolean(useCapture)); + } + return; + } else { + for (i = 0; i < len; i++) { + if (target.events[type][i] === listener) { + element[removeEvent](on + type, wrapped, useCapture || false); + target.events[type].splice(i, 1); + + if (useAttachEvent && listeners) { + listeners.useCount[listenerIndex]--; + if (listeners.useCount[listenerIndex] === 0) { + listeners.supplied.splice(listenerIndex, 1); + listeners.wrapped.splice(listenerIndex, 1); + listeners.useCount.splice(listenerIndex, 1); } } - events.remove(this._context, type, delegateListener); - events.remove(this._context, type, delegateUseCapture, true); - break; } } } - this.dropzone(false); - - interactables.splice(indexOf(interactables, this), 1); - - return interact; - } -}; - -function warnOnce (method, message) { - var warned = false; - - return function () { - if (!warned) { - window.console.warn(message); - warned = true; + if (target.events[type] && target.events[type].length === 0) { + target.events[type] = null; + target.typeCount--; } + } - return method.apply(this, arguments); - }; + if (!target.typeCount) { + targets.splice(elementIndex, 1); + elements.splice(elementIndex, 1); + attachedListeners.splice(elementIndex, 1); + } } -Interactable.prototype.snap = warnOnce(Interactable.prototype.snap, - 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping'); -Interactable.prototype.restrict = warnOnce(Interactable.prototype.restrict, - 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction'); -Interactable.prototype.inertia = warnOnce(Interactable.prototype.inertia, - 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia'); -Interactable.prototype.autoScroll = warnOnce(Interactable.prototype.autoScroll, - 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll'); -Interactable.prototype.squareResize = warnOnce(Interactable.prototype.squareResize, - 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square'); - -/*\ - * interact.isSet - [ method ] - * - * Check if an element has been set - - element (Element) The Element being searched for - = (boolean) Indicates if the element or CSS selector was previously passed to interact - \*/ -interact.isSet = function(element, options) { - return interactables.indexOfElement(element, options && options.context) !== -1; -}; +function preventDef () { + this.returnValue = false; +} -/*\ - * interact.on - [ method ] - * - * Adds a global listener for an InteractEvent or adds a DOM event to - * `document` - * - - type (string | array | object) The types of events to listen for - - listener (function) The function to be called on the given event(s) - - useCapture (boolean) #optional useCapture flag for addEventListener - = (object) interact - \*/ -interact.on = function (type, listener, useCapture) { - if (isString(type) && type.search(' ') !== -1) { - type = type.trim().split(/ +/); - } +function stopProp () { + this.cancelBubble = true; +} - if (isArray(type)) { - for (var i = 0; i < type.length; i++) { - interact.on(type[i], listener, useCapture); - } +function stopImmProp () { + this.cancelBubble = true; + this.immediatePropagationStopped = true; +} - return interact; - } +module.exports = { + add: add, + remove: remove, + useAttachEvent: useAttachEvent, - if (isObject(type)) { - for (var prop in type) { - interact.on(prop, type[prop], listener); - } + _elements: elements, + _targets: targets, + _attachedListeners: attachedListeners +}; - return interact; - } +},{"./arr":7,"./window":17}],11:[function(require,module,exports){ +'use strict'; - // if it is an InteractEvent type, add listener to globalEvents - if (contains(eventTypes, type)) { - // if this type of event was never bound - if (!globalEvents[type]) { - globalEvents[type] = [listener]; - } - else { - globalEvents[type].push(listener); - } - } - // If non InteractEvent type, addEventListener to document - else { - events.add(document, type, listener, useCapture); +module.exports = function extend (dest, source) { + for (var prop in source) { + dest[prop] = source[prop]; } - - return interact; + return dest; }; -/*\ - * interact.off - [ method ] - * - * Removes a global InteractEvent listener or DOM event from `document` - * - - type (string | array | object) The types of events that were listened for - - listener (function) The listener function to be removed - - useCapture (boolean) #optional useCapture flag for removeEventListener - = (object) interact - \*/ -interact.off = function (type, listener, useCapture) { - if (isString(type) && type.search(' ') !== -1) { - type = type.trim().split(/ +/); - } +},{}],12:[function(require,module,exports){ +'use strict'; - if (isArray(type)) { - for (var i = 0; i < type.length; i++) { - interact.off(type[i], listener, useCapture); - } +module.exports = function hypot (x, y) { return Math.sqrt(x * x + y * y); }; - return interact; - } +},{}],13:[function(require,module,exports){ +'use strict'; - if (isObject(type)) { - for (var prop in type) { - interact.off(prop, type[prop], listener); - } +var utils = {}, + extend = require('./extend'), + win = require('./window'); - return interact; - } +utils.blank = function () {}; - if (!contains(eventTypes, type)) { - events.remove(document, type, listener, useCapture); - } - else { - var index; +utils.warnOnce = function (method, message) { + var warned = false; - if (type in globalEvents - && (index = indexOf(globalEvents[type], listener)) !== -1) { - globalEvents[type].splice(index, 1); + return function () { + if (!warned) { + win.window.console.warn(message); + warned = true; } - } - return interact; + return method.apply(this, arguments); + }; }; -/*\ - * interact.enableDragging - [ method ] - * - * Deprecated. - * - * Returns or sets whether dragging is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ -interact.enableDragging = warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - actionIsEnabled.drag = newValue; +utils.extend = extend; +utils.hypot = require('./hypot'); +utils.raf = require('./raf'); +utils.browser = require('./browser'); - return interact; - } - return actionIsEnabled.drag; -}, 'interact.enableDragging is deprecated and will soon be removed.'); +extend(utils, require('./arr')); +extend(utils, require('./isType')); +extend(utils, require('./pointerUtils')); -/*\ - * interact.enableResizing - [ method ] - * - * Deprecated. - * - * Returns or sets whether resizing is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ -interact.enableResizing = warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - actionIsEnabled.resize = newValue; +module.exports = utils; - return interact; - } - return actionIsEnabled.resize; -}, 'interact.enableResizing is deprecated and will soon be removed.'); +},{"./arr":7,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./pointerUtils":15,"./raf":16,"./window":17}],14:[function(require,module,exports){ +'use strict'; -/*\ - * interact.enableGesturing - [ method ] - * - * Deprecated. - * - * Returns or sets whether gesturing is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ -interact.enableGesturing = warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - actionIsEnabled.gesture = newValue; +var win = require('./window'), + domObjects = require('./domObjects'); - return interact; - } - return actionIsEnabled.gesture; -}, 'interact.enableGesturing is deprecated and will soon be removed.'); +module.exports.isElement = function (o) { + if (!o || (typeof o !== 'object')) { return false; } -interact.eventTypes = eventTypes; + var _window = win.getWindow(o) || win.window; -/*\ - * interact.debug - [ method ] - * - * Returns debugging data - = (object) An object with properties that outline the current state and expose internal functions and variables - \*/ -interact.debug = function () { - var interaction = interactions[0] || new Interaction(); - - return { - interactions : interactions, - target : interaction.target, - dragging : interaction.dragging, - resizing : interaction.resizing, - gesturing : interaction.gesturing, - prepared : interaction.prepared, - matches : interaction.matches, - matchElements : interaction.matchElements, - - prevCoords : interaction.prevCoords, - startCoords : interaction.startCoords, - - pointerIds : interaction.pointerIds, - pointers : interaction.pointers, - addPointer : listeners.addPointer, - removePointer : listeners.removePointer, - recordPointer : listeners.recordPointer, - - snap : interaction.snapStatus, - restrict : interaction.restrictStatus, - inertia : interaction.inertiaStatus, - - downTime : interaction.downTimes[0], - downEvent : interaction.downEvent, - downPointer : interaction.downPointer, - prevEvent : interaction.prevEvent, - - Interactable : Interactable, - interactables : interactables, - pointerIsDown : interaction.pointerIsDown, - defaultOptions : defaultOptions, - defaultActionChecker : defaultActionChecker, - - actionCursors : actionCursors, - dragMove : listeners.dragMove, - resizeMove : listeners.resizeMove, - gestureMove : listeners.gestureMove, - pointerUp : listeners.pointerUp, - pointerDown : listeners.pointerDown, - pointerMove : listeners.pointerMove, - pointerHover : listeners.pointerHover, - - eventTypes : eventTypes, - - events : events, - globalEvents : globalEvents, - delegatedEvents : delegatedEvents - }; + return (/object|function/.test(typeof _window.Element) + ? o instanceof _window.Element //DOM2 + : o.nodeType === 1 && typeof o.nodeName === "string"); }; -// expose the functions used to calculate multi-touch properties -interact.getTouchAverage = touchAverage; -interact.getTouchBBox = touchBBox; -interact.getTouchDistance = touchDistance; -interact.getTouchAngle = touchAngle; - -interact.getElementRect = getElementRect; -interact.matchesSelector = matchesSelector; -interact.closest = closest; - -/*\ - * interact.margin - [ method ] - * - * Returns or sets the margin for autocheck resizing used in - * @Interactable.getAction. That is the distance from the bottom and right - * edges of an element clicking in which will start resizing - * - - newValue (number) #optional - = (number | interact) The current margin value or interact - \*/ -interact.margin = function (newvalue) { - if (isNumber(newvalue)) { - margin = newvalue; - - return interact; - } - return margin; +module.exports.isWindow = function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }; +module.exports.isDocFrag = function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }; +module.exports.isArray = function (thing) { + return module.exports.isObject(thing) + && (typeof thing.length !== undefined) + && module.exports.isFunction(thing.splice); }; +module.exports.isObject = function (thing) { return !!thing && (typeof thing === 'object'); }; +module.exports.isFunction = function (thing) { return typeof thing === 'function'; }; +module.exports.isNumber = function (thing) { return typeof thing === 'number' ; }; +module.exports.isBool = function (thing) { return typeof thing === 'boolean' ; }; +module.exports.isString = function (thing) { return typeof thing === 'string' ; }; -/*\ - * interact.supportsTouch - [ method ] - * - = (boolean) Whether or not the browser supports touch input - \*/ -interact.supportsTouch = function () { - return supportsTouch; -}; -/*\ - * interact.supportsPointerEvent - [ method ] - * - = (boolean) Whether or not the browser supports PointerEvents - \*/ -interact.supportsPointerEvent = function () { - return supportsPointerEvent; -}; +},{"./domObjects":9,"./window":17}],15:[function(require,module,exports){ +'use strict'; -/*\ - * interact.stop - [ method ] - * - * Cancels all interactions (end events are not fired) - * - - event (Event) An event on which to call preventDefault() - = (object) interact - \*/ -interact.stop = function (event) { - for (var i = interactions.length - 1; i > 0; i--) { - interactions[i].stop(event); - } +var pointerUtils = {}, + // reduce object creation in getXY() + tmpXY = {}, + win = require('./window'), + hypot = require('./hypot'), + extend = require('./extend'), - return interact; -}; + // scope shouldn't be necessary in this module + scope = require('../scope'); -/*\ - * interact.dynamicDrop - [ method ] - * - * Returns or sets whether the dimensions of dropzone elements are - * calculated on every dragmove or only on dragstart for the default - * dropChecker - * - - newValue (boolean) #optional True to check on each move. False to check only before start - = (boolean | interact) The current setting or interact - \*/ -interact.dynamicDrop = function (newValue) { - if (isBool(newValue)) { - //if (dragging && dynamicDrop !== newValue && !newValue) { - //calcRects(dropzones); - //} +pointerUtils.copyCoords = function (dest, src) { + dest.page = dest.page || {}; + dest.page.x = src.page.x; + dest.page.y = src.page.y; - dynamicDrop = newValue; + dest.client = dest.client || {}; + dest.client.x = src.client.x; + dest.client.y = src.client.y; - return interact; - } - return dynamicDrop; + dest.timeStamp = src.timeStamp; }; -/*\ - * interact.pointerMoveTolerance - [ method ] - * Returns or sets the distance the pointer must be moved before an action - * sequence occurs. This also affects tolerance for tap events. - * - - newValue (number) #optional The movement from the start position must be greater than this value - = (number | Interactable) The current setting or interact - \*/ -interact.pointerMoveTolerance = function (newValue) { - if (isNumber(newValue)) { - pointerMoveTolerance = newValue; - - return this; +pointerUtils.setEventXY = function (targetObj, pointer, interaction) { + if (!pointer) { + if (interaction.pointerIds.length > 1) { + pointer = pointerUtils.touchAverage(interaction.pointers); + } + else { + pointer = interaction.pointers[0]; + } } - return pointerMoveTolerance; -}; + pointerUtils.getPageXY(pointer, tmpXY, interaction); + targetObj.page.x = tmpXY.x; + targetObj.page.y = tmpXY.y; -/*\ - * interact.maxInteractions - [ method ] - ** - * Returns or sets the maximum number of concurrent interactions allowed. - * By default only 1 interaction is allowed at a time (for backwards - * compatibility). To allow multiple interactions on the same Interactables - * and elements, you need to enable it in the draggable, resizable and - * gesturable `'max'` and `'maxPerElement'` options. - ** - - newValue (number) #optional Any number. newValue <= 0 means no interactions. - \*/ -interact.maxInteractions = function (newValue) { - if (isNumber(newValue)) { - maxInteractions = newValue; - - return this; - } + pointerUtils.getClientXY(pointer, tmpXY, interaction); + targetObj.client.x = tmpXY.x; + targetObj.client.y = tmpXY.y; - return maxInteractions; + targetObj.timeStamp = new Date().getTime(); }; -interact.createSnapGrid = function (grid) { - return function (x, y) { - var offsetX = 0, - offsetY = 0; +pointerUtils.setEventDeltas = function (targetObj, prev, cur) { + targetObj.page.x = cur.page.x - prev.page.x; + targetObj.page.y = cur.page.y - prev.page.y; + targetObj.client.x = cur.client.x - prev.client.x; + targetObj.client.y = cur.client.y - prev.client.y; + targetObj.timeStamp = new Date().getTime() - prev.timeStamp; + + // set pointer velocity + var dt = Math.max(targetObj.timeStamp / 1000, 0.001); + targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; + targetObj.page.vx = targetObj.page.x / dt; + targetObj.page.vy = targetObj.page.y / dt; - if (isObject(grid.offset)) { - offsetX = grid.offset.x; - offsetY = grid.offset.y; - } + targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; + targetObj.client.vx = targetObj.client.x / dt; + targetObj.client.vy = targetObj.client.y / dt; +}; - var gridx = Math.round((x - offsetX) / grid.x), - gridy = Math.round((y - offsetY) / grid.y), +// Get specified X/Y coords for mouse or event.touches[0] +pointerUtils.getXY = function (type, pointer, xy) { + xy = xy || {}; + type = type || 'page'; - newX = gridx * grid.x + offsetX, - newY = gridy * grid.y + offsetY; + xy.x = pointer[type + 'X']; + xy.y = pointer[type + 'Y']; - return { - x: newX, - y: newY, - range: grid.range - }; - }; + return xy; }; -function endAllInteractions (event) { - for (var i = 0; i < interactions.length; i++) { - interactions[i].pointerEnd(event, event); - } -} - -function listenToDocument (doc) { - if (contains(documents, doc)) { return; } +pointerUtils.getPageXY = function (pointer, page, interaction) { + page = page || {}; - var win = doc.defaultView || doc.parentWindow; + if (pointer instanceof scope.InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + interaction = interaction || pointer.interaction; - // add delegate event listener - for (var eventType in delegatedEvents) { - events.add(doc, eventType, delegateListener); - events.add(doc, eventType, delegateUseCapture, true); - } + extend(page, interaction.inertiaStatus.upCoords.page); - if (PointerEvent) { - if (PointerEvent === win.MSPointerEvent) { - pEventTypes = { - up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', - out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' }; + page.x += interaction.inertiaStatus.sx; + page.y += interaction.inertiaStatus.sy; } else { - pEventTypes = { - up: 'pointerup', down: 'pointerdown', over: 'pointerover', - out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }; + page.x = pointer.pageX; + page.y = pointer.pageY; } + } + // Opera Mobile handles the viewport and scrolling oddly + else if (scope.isOperaMobile) { + pointerUtils.getXY('screen', pointer, page); - events.add(doc, pEventTypes.down , listeners.selectorDown ); - events.add(doc, pEventTypes.move , listeners.pointerMove ); - events.add(doc, pEventTypes.over , listeners.pointerOver ); - events.add(doc, pEventTypes.out , listeners.pointerOut ); - events.add(doc, pEventTypes.up , listeners.pointerUp ); - events.add(doc, pEventTypes.cancel, listeners.pointerCancel); - - // autoscroll - events.add(doc, pEventTypes.move, listeners.autoScrollMove); + page.x += win.window.scrollX; + page.y += win.window.scrollY; } else { - events.add(doc, 'mousedown', listeners.selectorDown); - events.add(doc, 'mousemove', listeners.pointerMove ); - events.add(doc, 'mouseup' , listeners.pointerUp ); - events.add(doc, 'mouseover', listeners.pointerOver ); - events.add(doc, 'mouseout' , listeners.pointerOut ); - - events.add(doc, 'touchstart' , listeners.selectorDown ); - events.add(doc, 'touchmove' , listeners.pointerMove ); - events.add(doc, 'touchend' , listeners.pointerUp ); - events.add(doc, 'touchcancel', listeners.pointerCancel); - - // autoscroll - events.add(doc, 'mousemove', listeners.autoScrollMove); - events.add(doc, 'touchmove', listeners.autoScrollMove); + pointerUtils.getXY('page', pointer, page); } - events.add(win, 'blur', endAllInteractions); + return page; +}; + +pointerUtils.getClientXY = function (pointer, client, interaction) { + client = client || {}; - try { - if (win.frameElement) { - var parentDoc = win.frameElement.ownerDocument, - parentWindow = parentDoc.defaultView; + if (pointer instanceof scope.InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + extend(client, interaction.inertiaStatus.upCoords.client); - events.add(parentDoc , 'mouseup' , listeners.pointerEnd); - events.add(parentDoc , 'touchend' , listeners.pointerEnd); - events.add(parentDoc , 'touchcancel' , listeners.pointerEnd); - events.add(parentDoc , 'pointerup' , listeners.pointerEnd); - events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd); - events.add(parentWindow, 'blur' , endAllInteractions ); + client.x += interaction.inertiaStatus.sx; + client.y += interaction.inertiaStatus.sy; + } + else { + client.x = pointer.clientX; + client.y = pointer.clientY; } } - catch (error) { - interact.windowParentError = error; + else { + // Opera Mobile handles the viewport and scrolling oddly + pointerUtils.getXY(scope.isOperaMobile? 'screen': 'client', pointer, client); } - if (events.useAttachEvent) { - // For IE's lack of Event#preventDefault - events.add(doc, 'selectstart', function (event) { - var interaction = interactions[0]; + return client; +}; - if (interaction.currentAction()) { - interaction.checkAndPreventDefault(event); - } - }); +pointerUtils.getPointerId = function (pointer) { + return scope.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; +}; - // For IE's bad dblclick event sequence - events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick')); - } +module.exports = pointerUtils; - documents.push(doc); -} +},{"../scope":6,"./extend":11,"./hypot":12,"./window":17}],16:[function(require,module,exports){ +'use strict'; -listenToDocument(document); +var lastTime = 0, + vendors = ['ms', 'moz', 'webkit', 'o'], + reqFrame, + cancelFrame; -function indexOf (array, target) { - for (var i = 0, len = array.length; i < len; i++) { - if (array[i] === target) { - return i; - } - } +for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + reqFrame = window[vendors[x]+'RequestAnimationFrame']; + cancelFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; +} - return -1; +if (!reqFrame) { + reqFrame = function(callback) { + var currTime = new Date().getTime(), + timeToCall = Math.max(0, 16 - (currTime - lastTime)), + id = setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; } -function contains (array, target) { - return indexOf(array, target) !== -1; +if (!cancelFrame) { + cancelFrame = function(id) { + clearTimeout(id); + }; } -function matchesSelector (element, selector, nodeList) { - if (ie8MatchesSelector) { - return ie8MatchesSelector(element, selector, nodeList); - } +module.exports = { + request: reqFrame, + cancel: cancelFrame +}; - // remove /deep/ from selectors if shadowDOM polyfill is used - if (window !== realWindow) { - selector = selector.replace(/\/deep\//g, ' '); - } +},{}],17:[function(require,module,exports){ +'use strict'; - return element[prefixedMatchesSelector](selector); +if (typeof window === 'undefined') { + module.exports.window = undefined; + module.exports.realWindow = undefined; } +else { + // get wrapped window if using Shadow DOM polyfill -function matchesUpTo (element, selector, limit) { - while (isElement(element)) { - if (matchesSelector(element, selector)) { - return true; - } + module.exports.realWindow = window; - element = parentElement(element); + // create a TextNode + var el = window.document.createTextNode(''); - if (element === limit) { - return matchesSelector(element, selector); - } + // check if it's wrapped by a polyfill + if (el.ownerDocument !== window.document + && typeof window.wrap === 'function' + && window.wrap(el) === el) { + // return wrapped window + module.exports.window = window.wrap(window); } - return false; -} - -// For IE8's lack of an Element#matchesSelector -// taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified -if (!(prefixedMatchesSelector in Element.prototype) || !isFunction(Element.prototype[prefixedMatchesSelector])) { - ie8MatchesSelector = function (element, selector, elems) { - elems = elems || element.parentNode.querySelectorAll(selector); - - for (var i = 0, len = elems.length; i < len; i++) { - if (elems[i] === element) { - return true; - } - } - - return false; - }; + // no Shadow DOM polyfil or native implementation + module.exports.window = window; } -// requestAnimationFrame polyfill -(function() { - var lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o']; +var isWindow = require('./isType').isWindow; - for(var x = 0; x < vendors.length && !realWindow.requestAnimationFrame; ++x) { - reqFrame = realWindow[vendors[x]+'RequestAnimationFrame']; - cancelFrame = realWindow[vendors[x]+'CancelAnimationFrame'] || realWindow[vendors[x]+'CancelRequestAnimationFrame']; - } - - if (!reqFrame) { - reqFrame = function(callback) { - var currTime = new Date().getTime(), - timeToCall = Math.max(0, 16 - (currTime - lastTime)), - id = setTimeout(function() { callback(currTime + timeToCall); }, - timeToCall); - lastTime = currTime + timeToCall; - return id; - }; +module.exports.getWindow = function getWindow (node) { + if (isWindow(node)) { + return node; } - if (!cancelFrame) { - cancelFrame = function(id) { - clearTimeout(id); - }; - } -}()); + var rootNode = (node.ownerDocument || node); -// CommonJS -if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = interact; - } - exports['interact'] = interact; -} -// AMD -else if (typeof define === 'function' && define.amd) { - define('interact', function() { - return interact; - }); + return rootNode.defaultView || rootNode.parentWindow || module.exports.window; }; -// Always export on the global scope -window['interact'] = interact; -},{"./utils/window":2}],2:[function(require,module,exports){ -var interactWindow = typeof window === 'undefined' ? undefined : window; - -module.exports = interactWindow; -},{}]},{},[1]) -//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvaW50ZXJhY3QuanMiLCJzcmMvdXRpbHMvd2luZG93LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaHVMQTtBQUNBO0FBQ0EiLCJmaWxlIjoiZ2VuZXJhdGVkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXNDb250ZW50IjpbIihmdW5jdGlvbiBlKHQsbixyKXtmdW5jdGlvbiBzKG8sdSl7aWYoIW5bb10pe2lmKCF0W29dKXt2YXIgYT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2lmKCF1JiZhKXJldHVybiBhKG8sITApO2lmKGkpcmV0dXJuIGkobywhMCk7dmFyIGY9bmV3IEVycm9yKFwiQ2Fubm90IGZpbmQgbW9kdWxlICdcIitvK1wiJ1wiKTt0aHJvdyBmLmNvZGU9XCJNT0RVTEVfTk9UX0ZPVU5EXCIsZn12YXIgbD1uW29dPXtleHBvcnRzOnt9fTt0W29dWzBdLmNhbGwobC5leHBvcnRzLGZ1bmN0aW9uKGUpe3ZhciBuPXRbb11bMV1bZV07cmV0dXJuIHMobj9uOmUpfSxsLGwuZXhwb3J0cyxlLHQsbixyKX1yZXR1cm4gbltvXS5leHBvcnRzfXZhciBpPXR5cGVvZiByZXF1aXJlPT1cImZ1bmN0aW9uXCImJnJlcXVpcmU7Zm9yKHZhciBvPTA7bzxyLmxlbmd0aDtvKyspcyhyW29dKTtyZXR1cm4gc30pIiwiJ3VzZSBzdHJpY3QnO1xuXG4vKipcbiAqIGludGVyYWN0LmpzIHYxLjIuNFxuICpcbiAqIENvcHlyaWdodCAoYykgMjAxMi0yMDE1IFRheWUgQWRleWVtaSA8ZGV2QHRheWUubWU+XG4gKiBPcGVuIHNvdXJjZSB1bmRlciB0aGUgTUlUIExpY2Vuc2UuXG4gKiBodHRwczovL3Jhdy5naXRodWIuY29tL3RheWUvaW50ZXJhY3QuanMvbWFzdGVyL0xJQ0VOU0VcbiAqL1xuXG52YXIgcmVhbFdpbmRvdyA9IHJlcXVpcmUoJy4vdXRpbHMvd2luZG93Jyk7XG5cbi8vIHJldHVybiBlYXJseSBpZiB0aGVyZSdzIG5vIHdpbmRvdyB0byB3b3JrIHdpdGggKGVnLiBOb2RlLmpzKVxuaWYgKCFyZWFsV2luZG93KSB7IHJldHVybjsgfVxuXG52YXIgLy8gZ2V0IHdyYXBwZWQgd2luZG93IGlmIHVzaW5nIFNoYWRvdyBET00gcG9seWZpbGxcbiAgICB3aW5kb3cgPSAoZnVuY3Rpb24gKCkge1xuICAgICAgICAvLyBjcmVhdGUgYSBUZXh0Tm9kZVxuICAgICAgICB2YXIgZWwgPSByZWFsV2luZG93LmRvY3VtZW50LmNyZWF0ZVRleHROb2RlKCcnKTtcblxuICAgICAgICAvLyBjaGVjayBpZiBpdCdzIHdyYXBwZWQgYnkgYSBwb2x5ZmlsbFxuICAgICAgICBpZiAoZWwub3duZXJEb2N1bWVudCAhPT0gcmVhbFdpbmRvdy5kb2N1bWVudFxuICAgICAgICAgICAgJiYgdHlwZW9mIHJlYWxXaW5kb3cud3JhcCA9PT0gJ2Z1bmN0aW9uJ1xuICAgICAgICAgICAgJiYgcmVhbFdpbmRvdy53cmFwKGVsKSA9PT0gZWwpIHtcbiAgICAgICAgICAgIC8vIHJldHVybiB3cmFwcGVkIHdpbmRvd1xuICAgICAgICAgICAgcmV0dXJuIHJlYWxXaW5kb3cud3JhcChyZWFsV2luZG93KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIG5vIFNoYWRvdyBET00gcG9seWZpbCBvciBuYXRpdmUgaW1wbGVtZW50YXRpb25cbiAgICAgICAgcmV0dXJuIHJlYWxXaW5kb3c7XG4gICAgfSgpKSxcblxuICAgIGRvY3VtZW50ICAgICAgICAgICA9IHdpbmRvdy5kb2N1bWVudCxcbiAgICBEb2N1bWVudEZyYWdtZW50ICAgPSB3aW5kb3cuRG9jdW1lbnRGcmFnbWVudCAgIHx8IGJsYW5rLFxuICAgIFNWR0VsZW1lbnQgICAgICAgICA9IHdpbmRvdy5TVkdFbGVtZW50ICAgICAgICAgfHwgYmxhbmssXG4gICAgU1ZHU1ZHRWxlbWVudCAgICAgID0gd2luZG93LlNWR1NWR0VsZW1lbnQgICAgICB8fCBibGFuayxcbiAgICBTVkdFbGVtZW50SW5zdGFuY2UgPSB3aW5kb3cuU1ZHRWxlbWVudEluc3RhbmNlIHx8IGJsYW5rLFxuICAgIEhUTUxFbGVtZW50ICAgICAgICA9IHdpbmRvdy5IVE1MRWxlbWVudCAgICAgICAgfHwgd2luZG93LkVsZW1lbnQsXG5cbiAgICBQb2ludGVyRXZlbnQgPSAod2luZG93LlBvaW50ZXJFdmVudCB8fCB3aW5kb3cuTVNQb2ludGVyRXZlbnQpLFxuICAgIHBFdmVudFR5cGVzLFxuXG4gICAgaHlwb3QgPSBNYXRoLmh5cG90IHx8IGZ1bmN0aW9uICh4LCB5KSB7IHJldHVybiBNYXRoLnNxcnQoeCAqIHggKyB5ICogeSk7IH0sXG5cbiAgICB0bXBYWSA9IHt9LCAgICAgLy8gcmVkdWNlIG9iamVjdCBjcmVhdGlvbiBpbiBnZXRYWSgpXG5cbiAgICBkb2N1bWVudHMgICAgICAgPSBbXSwgICAvLyBhbGwgZG9jdW1lbnRzIGJlaW5nIGxpc3RlbmVkIHRvXG5cbiAgICBpbnRlcmFjdGFibGVzICAgPSBbXSwgICAvLyBhbGwgc2V0IGludGVyYWN0YWJsZXNcbiAgICBpbnRlcmFjdGlvbnMgICAgPSBbXSwgICAvLyBhbGwgaW50ZXJhY3Rpb25zXG5cbiAgICBkeW5hbWljRHJvcCAgICAgPSBmYWxzZSxcblxuLy8ge1xuLy8gICAgICB0eXBlOiB7XG4vLyAgICAgICAgICBzZWxlY3RvcnM6IFsnc2VsZWN0b3InLCAuLi5dLFxuLy8gICAgICAgICAgY29udGV4dHMgOiBbZG9jdW1lbnQsIC4uLl0sXG4vLyAgICAgICAgICBsaXN0ZW5lcnM6IFtbbGlzdGVuZXIsIHVzZUNhcHR1cmVdLCAuLi5dXG4vLyAgICAgIH1cbi8vICB9XG4gICAgZGVsZWdhdGVkRXZlbnRzID0ge30sXG5cbiAgICBkZWZhdWx0T3B0aW9ucyA9IHtcbiAgICAgICAgYmFzZToge1xuICAgICAgICAgICAgYWNjZXB0ICAgICAgICA6IG51bGwsXG4gICAgICAgICAgICBhY3Rpb25DaGVja2VyIDogbnVsbCxcbiAgICAgICAgICAgIHN0eWxlQ3Vyc29yICAgOiB0cnVlLFxuICAgICAgICAgICAgcHJldmVudERlZmF1bHQ6ICdhdXRvJyxcbiAgICAgICAgICAgIG9yaWdpbiAgICAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgICAgIGRlbHRhU291cmNlICAgOiAncGFnZScsXG4gICAgICAgICAgICBhbGxvd0Zyb20gICAgIDogbnVsbCxcbiAgICAgICAgICAgIGlnbm9yZUZyb20gICAgOiBudWxsLFxuICAgICAgICAgICAgX2NvbnRleHQgICAgICA6IGRvY3VtZW50LFxuICAgICAgICAgICAgZHJvcENoZWNrZXIgICA6IG51bGxcbiAgICAgICAgfSxcblxuICAgICAgICBkcmFnOiB7XG4gICAgICAgICAgICBlbmFibGVkOiBmYWxzZSxcbiAgICAgICAgICAgIG1hbnVhbFN0YXJ0OiB0cnVlLFxuICAgICAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgICAgIG1heFBlckVsZW1lbnQ6IDEsXG5cbiAgICAgICAgICAgIHNuYXA6IG51bGwsXG4gICAgICAgICAgICByZXN0cmljdDogbnVsbCxcbiAgICAgICAgICAgIGluZXJ0aWE6IG51bGwsXG4gICAgICAgICAgICBhdXRvU2Nyb2xsOiBudWxsLFxuXG4gICAgICAgICAgICBheGlzOiAneHknLFxuICAgICAgICB9LFxuXG4gICAgICAgIGRyb3A6IHtcbiAgICAgICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICAgICAgYWNjZXB0OiBudWxsLFxuICAgICAgICAgICAgb3ZlcmxhcDogJ3BvaW50ZXInXG4gICAgICAgIH0sXG5cbiAgICAgICAgcmVzaXplOiB7XG4gICAgICAgICAgICBlbmFibGVkOiBmYWxzZSxcbiAgICAgICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICAgICBtYXhQZXJFbGVtZW50OiAxLFxuXG4gICAgICAgICAgICBzbmFwOiBudWxsLFxuICAgICAgICAgICAgcmVzdHJpY3Q6IG51bGwsXG4gICAgICAgICAgICBpbmVydGlhOiBudWxsLFxuICAgICAgICAgICAgYXV0b1Njcm9sbDogbnVsbCxcblxuICAgICAgICAgICAgc3F1YXJlOiBmYWxzZSxcbiAgICAgICAgICAgIGF4aXM6ICd4eScsXG5cbiAgICAgICAgICAgIC8vIHVzZSBkZWZhdWx0IG1hcmdpblxuICAgICAgICAgICAgbWFyZ2luOiBOYU4sXG5cbiAgICAgICAgICAgIC8vIG9iamVjdCB3aXRoIHByb3BzIGxlZnQsIHJpZ2h0LCB0b3AsIGJvdHRvbSB3aGljaCBhcmVcbiAgICAgICAgICAgIC8vIHRydWUvZmFsc2UgdmFsdWVzIHRvIHJlc2l6ZSB3aGVuIHRoZSBwb2ludGVyIGlzIG92ZXIgdGhhdCBlZGdlLFxuICAgICAgICAgICAgLy8gQ1NTIHNlbGVjdG9ycyB0byBtYXRjaCB0aGUgaGFuZGxlcyBmb3IgZWFjaCBkaXJlY3Rpb25cbiAgICAgICAgICAgIC8vIG9yIHRoZSBFbGVtZW50cyBmb3IgZWFjaCBoYW5kbGVcbiAgICAgICAgICAgIGVkZ2VzOiBudWxsLFxuXG4gICAgICAgICAgICAvLyBhIHZhbHVlIG9mICdub25lJyB3aWxsIGxpbWl0IHRoZSByZXNpemUgcmVjdCB0byBhIG1pbmltdW0gb2YgMHgwXG4gICAgICAgICAgICAvLyAnbmVnYXRlJyB3aWxsIGFsb3cgdGhlIHJlY3QgdG8gaGF2ZSBuZWdhdGl2ZSB3aWR0aC9oZWlnaHRcbiAgICAgICAgICAgIC8vICdyZXBvc2l0aW9uJyB3aWxsIGtlZXAgdGhlIHdpZHRoL2hlaWdodCBwb3NpdGl2ZSBieSBzd2FwcGluZ1xuICAgICAgICAgICAgLy8gdGhlIHRvcCBhbmQgYm90dG9tIGVkZ2VzIGFuZC9vciBzd2FwcGluZyB0aGUgbGVmdCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgICAgICAgIGludmVydDogJ25vbmUnXG4gICAgICAgIH0sXG5cbiAgICAgICAgZ2VzdHVyZToge1xuICAgICAgICAgICAgbWFudWFsU3RhcnQ6IGZhbHNlLFxuICAgICAgICAgICAgZW5hYmxlZDogZmFsc2UsXG4gICAgICAgICAgICBtYXg6IEluZmluaXR5LFxuICAgICAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICAgICAgcmVzdHJpY3Q6IG51bGxcbiAgICAgICAgfSxcblxuICAgICAgICBwZXJBY3Rpb246IHtcbiAgICAgICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICAgICBtYXhQZXJFbGVtZW50OiAxLFxuXG4gICAgICAgICAgICBzbmFwOiB7XG4gICAgICAgICAgICAgICAgZW5hYmxlZCAgICAgOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBlbmRPbmx5ICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHJhbmdlICAgICAgIDogSW5maW5pdHksXG4gICAgICAgICAgICAgICAgdGFyZ2V0cyAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIG9mZnNldHMgICAgIDogbnVsbCxcblxuICAgICAgICAgICAgICAgIHJlbGF0aXZlUG9pbnRzOiBudWxsXG4gICAgICAgICAgICB9LFxuXG4gICAgICAgICAgICByZXN0cmljdDoge1xuICAgICAgICAgICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICAgICAgICAgIGVuZE9ubHk6IGZhbHNlXG4gICAgICAgICAgICB9LFxuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsOiB7XG4gICAgICAgICAgICAgICAgZW5hYmxlZCAgICAgOiBmYWxzZSxcbiAgICAgICAgICAgICAgICBjb250YWluZXIgICA6IG51bGwsICAgICAvLyB0aGUgaXRlbSB0aGF0IGlzIHNjcm9sbGVkIChXaW5kb3cgb3IgSFRNTEVsZW1lbnQpXG4gICAgICAgICAgICAgICAgbWFyZ2luICAgICAgOiA2MCxcbiAgICAgICAgICAgICAgICBzcGVlZCAgICAgICA6IDMwMCAgICAgICAvLyB0aGUgc2Nyb2xsIHNwZWVkIGluIHBpeGVscyBwZXIgc2Vjb25kXG4gICAgICAgICAgICB9LFxuXG4gICAgICAgICAgICBpbmVydGlhOiB7XG4gICAgICAgICAgICAgICAgZW5hYmxlZCAgICAgICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgICAgIHJlc2lzdGFuY2UgICAgICAgOiAxMCwgICAgLy8gdGhlIGxhbWJkYSBpbiBleHBvbmVudGlhbCBkZWNheVxuICAgICAgICAgICAgICAgIG1pblNwZWVkICAgICAgICAgOiAxMDAsICAgLy8gdGFyZ2V0IHNwZWVkIG11c3QgYmUgYWJvdmUgdGhpcyBmb3IgaW5lcnRpYSB0byBzdGFydFxuICAgICAgICAgICAgICAgIGVuZFNwZWVkICAgICAgICAgOiAxMCwgICAgLy8gdGhlIHNwZWVkIGF0IHdoaWNoIGluZXJ0aWEgaXMgc2xvdyBlbm91Z2ggdG8gc3RvcFxuICAgICAgICAgICAgICAgIGFsbG93UmVzdW1lICAgICAgOiB0cnVlLCAgLy8gYWxsb3cgcmVzdW1pbmcgYW4gYWN0aW9uIGluIGluZXJ0aWEgcGhhc2VcbiAgICAgICAgICAgICAgICB6ZXJvUmVzdW1lRGVsdGEgIDogdHJ1ZSwgIC8vIGlmIGFuIGFjdGlvbiBpcyByZXN1bWVkIGFmdGVyIGxhdW5jaCwgc2V0IGR4L2R5IHRvIDBcbiAgICAgICAgICAgICAgICBzbW9vdGhFbmREdXJhdGlvbjogMzAwICAgIC8vIGFuaW1hdGUgdG8gc25hcC9yZXN0cmljdCBlbmRPbmx5IGlmIHRoZXJlJ3Mgbm8gaW5lcnRpYVxuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuXG4gICAgICAgIF9ob2xkRHVyYXRpb246IDYwMFxuICAgIH0sXG5cbi8vIFRoaW5ncyByZWxhdGVkIHRvIGF1dG9TY3JvbGxcbiAgICBhdXRvU2Nyb2xsID0ge1xuICAgICAgICBpbnRlcmFjdGlvbjogbnVsbCxcbiAgICAgICAgaTogbnVsbCwgICAgLy8gdGhlIGhhbmRsZSByZXR1cm5lZCBieSB3aW5kb3cuc2V0SW50ZXJ2YWxcbiAgICAgICAgeDogMCwgeTogMCwgLy8gRGlyZWN0aW9uIGVhY2ggcHVsc2UgaXMgdG8gc2Nyb2xsIGluXG5cbiAgICAgICAgLy8gc2Nyb2xsIHRoZSB3aW5kb3cgYnkgdGhlIHZhbHVlcyBpbiBzY3JvbGwueC95XG4gICAgICAgIHNjcm9sbDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgdmFyIG9wdGlvbnMgPSBhdXRvU2Nyb2xsLmludGVyYWN0aW9uLnRhcmdldC5vcHRpb25zW2F1dG9TY3JvbGwuaW50ZXJhY3Rpb24ucHJlcGFyZWQubmFtZV0uYXV0b1Njcm9sbCxcbiAgICAgICAgICAgICAgICBjb250YWluZXIgPSBvcHRpb25zLmNvbnRhaW5lciB8fCBnZXRXaW5kb3coYXV0b1Njcm9sbC5pbnRlcmFjdGlvbi5lbGVtZW50KSxcbiAgICAgICAgICAgICAgICBub3cgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgIC8vIGNoYW5nZSBpbiB0aW1lIGluIHNlY29uZHNcbiAgICAgICAgICAgICAgICBkdCA9IChub3cgLSBhdXRvU2Nyb2xsLnByZXZUaW1lKSAvIDEwMDAsXG4gICAgICAgICAgICAvLyBkaXNwbGFjZW1lbnRcbiAgICAgICAgICAgICAgICBzID0gb3B0aW9ucy5zcGVlZCAqIGR0O1xuXG4gICAgICAgICAgICBpZiAocyA+PSAxKSB7XG4gICAgICAgICAgICAgICAgaWYgKGlzV2luZG93KGNvbnRhaW5lcikpIHtcbiAgICAgICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbEJ5KGF1dG9TY3JvbGwueCAqIHMsIGF1dG9TY3JvbGwueSAqIHMpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChjb250YWluZXIpIHtcbiAgICAgICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbExlZnQgKz0gYXV0b1Njcm9sbC54ICogcztcbiAgICAgICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbFRvcCAgKz0gYXV0b1Njcm9sbC55ICogcztcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbm93O1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoYXV0b1Njcm9sbC5pc1Njcm9sbGluZykge1xuICAgICAgICAgICAgICAgIGNhbmNlbEZyYW1lKGF1dG9TY3JvbGwuaSk7XG4gICAgICAgICAgICAgICAgYXV0b1Njcm9sbC5pID0gcmVxRnJhbWUoYXV0b1Njcm9sbC5zY3JvbGwpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9LFxuXG4gICAgICAgIGlzU2Nyb2xsaW5nOiBmYWxzZSxcbiAgICAgICAgcHJldlRpbWU6IDAsXG5cbiAgICAgICAgc3RhcnQ6IGZ1bmN0aW9uIChpbnRlcmFjdGlvbikge1xuICAgICAgICAgICAgYXV0b1Njcm9sbC5pc1Njcm9sbGluZyA9IHRydWU7XG4gICAgICAgICAgICBjYW5jZWxGcmFtZShhdXRvU2Nyb2xsLmkpO1xuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsLmludGVyYWN0aW9uID0gaW50ZXJhY3Rpb247XG4gICAgICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICBhdXRvU2Nyb2xsLmkgPSByZXFGcmFtZShhdXRvU2Nyb2xsLnNjcm9sbCk7XG4gICAgICAgIH0sXG5cbiAgICAgICAgc3RvcDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgYXV0b1Njcm9sbC5pc1Njcm9sbGluZyA9IGZhbHNlO1xuICAgICAgICAgICAgY2FuY2VsRnJhbWUoYXV0b1Njcm9sbC5pKTtcbiAgICAgICAgfVxuICAgIH0sXG5cbi8vIERvZXMgdGhlIGJyb3dzZXIgc3VwcG9ydCB0b3VjaCBpbnB1dD9cbiAgICBzdXBwb3J0c1RvdWNoID0gKCgnb250b3VjaHN0YXJ0JyBpbiB3aW5kb3cpIHx8IHdpbmRvdy5Eb2N1bWVudFRvdWNoICYmIGRvY3VtZW50IGluc3RhbmNlb2Ygd2luZG93LkRvY3VtZW50VG91Y2gpLFxuXG4vLyBEb2VzIHRoZSBicm93c2VyIHN1cHBvcnQgUG9pbnRlckV2ZW50c1xuICAgIHN1cHBvcnRzUG9pbnRlckV2ZW50ID0gISFQb2ludGVyRXZlbnQsXG5cbi8vIExlc3MgUHJlY2lzaW9uIHdpdGggdG91Y2ggaW5wdXRcbiAgICBtYXJnaW4gPSBzdXBwb3J0c1RvdWNoIHx8IHN1cHBvcnRzUG9pbnRlckV2ZW50PyAyMDogMTAsXG5cbiAgICBwb2ludGVyTW92ZVRvbGVyYW5jZSA9IDEsXG5cbi8vIGZvciBpZ25vcmluZyBicm93c2VyJ3Mgc2ltdWxhdGVkIG1vdXNlIGV2ZW50c1xuICAgIHByZXZUb3VjaFRpbWUgPSAwLFxuXG4vLyBBbGxvdyB0aGlzIG1hbnkgaW50ZXJhY3Rpb25zIHRvIGhhcHBlbiBzaW11bHRhbmVvdXNseVxuICAgIG1heEludGVyYWN0aW9ucyA9IEluZmluaXR5LFxuXG4vLyBDaGVjayBpZiBpcyBJRTkgb3Igb2xkZXJcbiAgICBhY3Rpb25DdXJzb3JzID0gKGRvY3VtZW50LmFsbCAmJiAhd2luZG93LmF0b2IpID8ge1xuICAgICAgICBkcmFnICAgIDogJ21vdmUnLFxuICAgICAgICByZXNpemV4IDogJ2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpleSA6ICdzLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXh5OiAnc2UtcmVzaXplJyxcblxuICAgICAgICByZXNpemV0b3AgICAgICAgIDogJ24tcmVzaXplJyxcbiAgICAgICAgcmVzaXplbGVmdCAgICAgICA6ICd3LXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbSAgICAgOiAncy1yZXNpemUnLFxuICAgICAgICByZXNpemVyaWdodCAgICAgIDogJ2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wbGVmdCAgICA6ICdzZS1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b21yaWdodDogJ3NlLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXRvcHJpZ2h0ICAgOiAnbmUtcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tbGVmdCA6ICduZS1yZXNpemUnLFxuXG4gICAgICAgIGdlc3R1cmUgOiAnJ1xuICAgIH0gOiB7XG4gICAgICAgIGRyYWcgICAgOiAnbW92ZScsXG4gICAgICAgIHJlc2l6ZXggOiAnZXctcmVzaXplJyxcbiAgICAgICAgcmVzaXpleSA6ICducy1yZXNpemUnLFxuICAgICAgICByZXNpemV4eTogJ253c2UtcmVzaXplJyxcblxuICAgICAgICByZXNpemV0b3AgICAgICAgIDogJ25zLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWxlZnQgICAgICAgOiAnZXctcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tICAgICA6ICducy1yZXNpemUnLFxuICAgICAgICByZXNpemVyaWdodCAgICAgIDogJ2V3LXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXRvcGxlZnQgICAgOiAnbndzZS1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b21yaWdodDogJ253c2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wcmlnaHQgICA6ICduZXN3LXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbWxlZnQgOiAnbmVzdy1yZXNpemUnLFxuXG4gICAgICAgIGdlc3R1cmUgOiAnJ1xuICAgIH0sXG5cbiAgICBhY3Rpb25Jc0VuYWJsZWQgPSB7XG4gICAgICAgIGRyYWcgICA6IHRydWUsXG4gICAgICAgIHJlc2l6ZSA6IHRydWUsXG4gICAgICAgIGdlc3R1cmU6IHRydWVcbiAgICB9LFxuXG4vLyBiZWNhdXNlIFdlYmtpdCBhbmQgT3BlcmEgc3RpbGwgdXNlICdtb3VzZXdoZWVsJyBldmVudCB0eXBlXG4gICAgd2hlZWxFdmVudCA9ICdvbm1vdXNld2hlZWwnIGluIGRvY3VtZW50PyAnbW91c2V3aGVlbCc6ICd3aGVlbCcsXG5cbiAgICBldmVudFR5cGVzID0gW1xuICAgICAgICAnZHJhZ3N0YXJ0JyxcbiAgICAgICAgJ2RyYWdtb3ZlJyxcbiAgICAgICAgJ2RyYWdpbmVydGlhc3RhcnQnLFxuICAgICAgICAnZHJhZ2VuZCcsXG4gICAgICAgICdkcmFnZW50ZXInLFxuICAgICAgICAnZHJhZ2xlYXZlJyxcbiAgICAgICAgJ2Ryb3BhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wZGVhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wbW92ZScsXG4gICAgICAgICdkcm9wJyxcbiAgICAgICAgJ3Jlc2l6ZXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZW1vdmUnLFxuICAgICAgICAncmVzaXplaW5lcnRpYXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZWVuZCcsXG4gICAgICAgICdnZXN0dXJlc3RhcnQnLFxuICAgICAgICAnZ2VzdHVyZW1vdmUnLFxuICAgICAgICAnZ2VzdHVyZWluZXJ0aWFzdGFydCcsXG4gICAgICAgICdnZXN0dXJlZW5kJyxcblxuICAgICAgICAnZG93bicsXG4gICAgICAgICdtb3ZlJyxcbiAgICAgICAgJ3VwJyxcbiAgICAgICAgJ2NhbmNlbCcsXG4gICAgICAgICd0YXAnLFxuICAgICAgICAnZG91YmxldGFwJyxcbiAgICAgICAgJ2hvbGQnXG4gICAgXSxcblxuICAgIGdsb2JhbEV2ZW50cyA9IHt9LFxuXG4vLyBPcGVyYSBNb2JpbGUgbXVzdCBiZSBoYW5kbGVkIGRpZmZlcmVudGx5XG4gICAgaXNPcGVyYU1vYmlsZSA9IG5hdmlnYXRvci5hcHBOYW1lID09ICdPcGVyYScgJiZcbiAgICAgICAgc3VwcG9ydHNUb3VjaCAmJlxuICAgICAgICBuYXZpZ2F0b3IudXNlckFnZW50Lm1hdGNoKCdQcmVzdG8nKSxcblxuLy8gc2Nyb2xsaW5nIGRvZXNuJ3QgY2hhbmdlIHRoZSByZXN1bHQgb2Zcbi8vIGdldEJvdW5kaW5nQ2xpZW50UmVjdC9nZXRDbGllbnRSZWN0cyBvbiBpT1MgPD03IGJ1dCBpdCBkb2VzIG9uIGlPUyA4XG4gICAgaXNJT1M3b3JMb3dlciA9ICgvaVAoaG9uZXxvZHxhZCkvLnRlc3QobmF2aWdhdG9yLnBsYXRmb3JtKVxuICAgICYmIC9PUyBbMS03XVteXFxkXS8udGVzdChuYXZpZ2F0b3IuYXBwVmVyc2lvbikpLFxuXG4vLyBwcmVmaXggbWF0Y2hlc1NlbGVjdG9yXG4gICAgcHJlZml4ZWRNYXRjaGVzU2VsZWN0b3IgPSAnbWF0Y2hlcycgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICdtYXRjaGVzJzogJ3dlYmtpdE1hdGNoZXNTZWxlY3RvcicgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICd3ZWJraXRNYXRjaGVzU2VsZWN0b3InOiAnbW96TWF0Y2hlc1NlbGVjdG9yJyBpbiBFbGVtZW50LnByb3RvdHlwZT9cbiAgICAgICAgJ21vek1hdGNoZXNTZWxlY3Rvcic6ICdvTWF0Y2hlc1NlbGVjdG9yJyBpbiBFbGVtZW50LnByb3RvdHlwZT9cbiAgICAgICAgJ29NYXRjaGVzU2VsZWN0b3InOiAnbXNNYXRjaGVzU2VsZWN0b3InLFxuXG4vLyB3aWxsIGJlIHBvbHlmaWxsIGZ1bmN0aW9uIGlmIGJyb3dzZXIgaXMgSUU4XG4gICAgaWU4TWF0Y2hlc1NlbGVjdG9yLFxuXG4vLyBuYXRpdmUgcmVxdWVzdEFuaW1hdGlvbkZyYW1lIG9yIHBvbHlmaWxsXG4gICAgcmVxRnJhbWUgPSByZWFsV2luZG93LnJlcXVlc3RBbmltYXRpb25GcmFtZSxcbiAgICBjYW5jZWxGcmFtZSA9IHJlYWxXaW5kb3cuY2FuY2VsQW5pbWF0aW9uRnJhbWUsXG5cbi8vIEV2ZW50cyB3cmFwcGVyXG4gICAgZXZlbnRzID0gKGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIHVzZUF0dGFjaEV2ZW50ID0gKCdhdHRhY2hFdmVudCcgaW4gd2luZG93KSAmJiAhKCdhZGRFdmVudExpc3RlbmVyJyBpbiB3aW5kb3cpLFxuICAgICAgICAgICAgYWRkRXZlbnQgICAgICAgPSB1c2VBdHRhY2hFdmVudD8gICdhdHRhY2hFdmVudCc6ICdhZGRFdmVudExpc3RlbmVyJyxcbiAgICAgICAgICAgIHJlbW92ZUV2ZW50ICAgID0gdXNlQXR0YWNoRXZlbnQ/ICAnZGV0YWNoRXZlbnQnOiAncmVtb3ZlRXZlbnRMaXN0ZW5lcicsXG4gICAgICAgICAgICBvbiAgICAgICAgICAgICA9IHVzZUF0dGFjaEV2ZW50PyAnb24nOiAnJyxcblxuICAgICAgICAgICAgZWxlbWVudHMgICAgICAgICAgPSBbXSxcbiAgICAgICAgICAgIHRhcmdldHMgICAgICAgICAgID0gW10sXG4gICAgICAgICAgICBhdHRhY2hlZExpc3RlbmVycyA9IFtdO1xuXG4gICAgICAgIGZ1bmN0aW9uIGFkZCAoZWxlbWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgIHZhciBlbGVtZW50SW5kZXggPSBpbmRleE9mKGVsZW1lbnRzLCBlbGVtZW50KSxcbiAgICAgICAgICAgICAgICB0YXJnZXQgPSB0YXJnZXRzW2VsZW1lbnRJbmRleF07XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ID0ge1xuICAgICAgICAgICAgICAgICAgICBldmVudHM6IHt9LFxuICAgICAgICAgICAgICAgICAgICB0eXBlQ291bnQ6IDBcbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgZWxlbWVudEluZGV4ID0gZWxlbWVudHMucHVzaChlbGVtZW50KSAtIDE7XG4gICAgICAgICAgICAgICAgdGFyZ2V0cy5wdXNoKHRhcmdldCk7XG5cbiAgICAgICAgICAgICAgICBhdHRhY2hlZExpc3RlbmVycy5wdXNoKCh1c2VBdHRhY2hFdmVudCA/IHtcbiAgICAgICAgICAgICAgICAgICAgc3VwcGxpZWQ6IFtdLFxuICAgICAgICAgICAgICAgICAgICB3cmFwcGVkIDogW10sXG4gICAgICAgICAgICAgICAgICAgIHVzZUNvdW50OiBbXVxuICAgICAgICAgICAgICAgIH0gOiBudWxsKSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0LmV2ZW50c1t0eXBlXSkge1xuICAgICAgICAgICAgICAgIHRhcmdldC5ldmVudHNbdHlwZV0gPSBbXTtcbiAgICAgICAgICAgICAgICB0YXJnZXQudHlwZUNvdW50Kys7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghY29udGFpbnModGFyZ2V0LmV2ZW50c1t0eXBlXSwgbGlzdGVuZXIpKSB7XG4gICAgICAgICAgICAgICAgdmFyIHJldDtcblxuICAgICAgICAgICAgICAgIGlmICh1c2VBdHRhY2hFdmVudCkge1xuICAgICAgICAgICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gYXR0YWNoZWRMaXN0ZW5lcnNbZWxlbWVudEluZGV4XSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVySW5kZXggPSBpbmRleE9mKGxpc3RlbmVycy5zdXBwbGllZCwgbGlzdGVuZXIpO1xuXG4gICAgICAgICAgICAgICAgICAgIHZhciB3cmFwcGVkID0gbGlzdGVuZXJzLndyYXBwZWRbbGlzdGVuZXJJbmRleF0gfHwgZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFldmVudC5pbW1lZGlhdGVQcm9wYWdhdGlvblN0b3BwZWQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQudGFyZ2V0ID0gZXZlbnQuc3JjRWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQuY3VycmVudFRhcmdldCA9IGVsZW1lbnQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQgPSBldmVudC5wcmV2ZW50RGVmYXVsdCB8fCBwcmV2ZW50RGVmO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5zdG9wUHJvcGFnYXRpb24gPSBldmVudC5zdG9wUHJvcGFnYXRpb24gfHwgc3RvcFByb3A7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiA9IGV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiB8fCBzdG9wSW1tUHJvcDtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoL21vdXNlfGNsaWNrLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5wYWdlWCA9IGV2ZW50LmNsaWVudFggKyBnZXRXaW5kb3coZWxlbWVudCkuZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbExlZnQ7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5wYWdlWSA9IGV2ZW50LmNsaWVudFkgKyBnZXRXaW5kb3coZWxlbWVudCkuZG9jdW1lbnQuZG9jdW1lbnRFbGVtZW50LnNjcm9sbFRvcDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyKGV2ZW50KTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgICAgIHJldCA9IGVsZW1lbnRbYWRkRXZlbnRdKG9uICsgdHlwZSwgd3JhcHBlZCwgQm9vbGVhbih1c2VDYXB0dXJlKSk7XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKGxpc3RlbmVySW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMuc3VwcGxpZWQucHVzaChsaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMud3JhcHBlZC5wdXNoKHdyYXBwZWQpO1xuICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnVzZUNvdW50LnB1c2goMSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0rKztcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0ID0gZWxlbWVudFthZGRFdmVudF0odHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUgfHwgZmFsc2UpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHJldDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIHJlbW92ZSAoZWxlbWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgIHZhciBpLFxuICAgICAgICAgICAgICAgIGVsZW1lbnRJbmRleCA9IGluZGV4T2YoZWxlbWVudHMsIGVsZW1lbnQpLFxuICAgICAgICAgICAgICAgIHRhcmdldCA9IHRhcmdldHNbZWxlbWVudEluZGV4XSxcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnMsXG4gICAgICAgICAgICAgICAgbGlzdGVuZXJJbmRleCxcbiAgICAgICAgICAgICAgICB3cmFwcGVkID0gbGlzdGVuZXI7XG5cbiAgICAgICAgICAgIGlmICghdGFyZ2V0IHx8ICF0YXJnZXQuZXZlbnRzKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodXNlQXR0YWNoRXZlbnQpIHtcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnMgPSBhdHRhY2hlZExpc3RlbmVyc1tlbGVtZW50SW5kZXhdO1xuICAgICAgICAgICAgICAgIGxpc3RlbmVySW5kZXggPSBpbmRleE9mKGxpc3RlbmVycy5zdXBwbGllZCwgbGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgIHdyYXBwZWQgPSBsaXN0ZW5lcnMud3JhcHBlZFtsaXN0ZW5lckluZGV4XTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHR5cGUgPT09ICdhbGwnKSB7XG4gICAgICAgICAgICAgICAgZm9yICh0eXBlIGluIHRhcmdldC5ldmVudHMpIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHRhcmdldC5ldmVudHMuaGFzT3duUHJvcGVydHkodHlwZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlbW92ZShlbGVtZW50LCB0eXBlLCAnYWxsJyk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGFyZ2V0LmV2ZW50c1t0eXBlXSkge1xuICAgICAgICAgICAgICAgIHZhciBsZW4gPSB0YXJnZXQuZXZlbnRzW3R5cGVdLmxlbmd0aDtcblxuICAgICAgICAgICAgICAgIGlmIChsaXN0ZW5lciA9PT0gJ2FsbCcpIHtcbiAgICAgICAgICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICByZW1vdmUoZWxlbWVudCwgdHlwZSwgdGFyZ2V0LmV2ZW50c1t0eXBlXVtpXSwgQm9vbGVhbih1c2VDYXB0dXJlKSk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRhcmdldC5ldmVudHNbdHlwZV1baV0gPT09IGxpc3RlbmVyKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudFtyZW1vdmVFdmVudF0ob24gKyB0eXBlLCB3cmFwcGVkLCB1c2VDYXB0dXJlIHx8IGZhbHNlKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICh1c2VBdHRhY2hFdmVudCAmJiBsaXN0ZW5lcnMpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnVzZUNvdW50W2xpc3RlbmVySW5kZXhdLS07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0gPT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zdXBwbGllZC5zcGxpY2UobGlzdGVuZXJJbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMud3JhcHBlZC5zcGxpY2UobGlzdGVuZXJJbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnQuc3BsaWNlKGxpc3RlbmVySW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAodGFyZ2V0LmV2ZW50c1t0eXBlXSAmJiB0YXJnZXQuZXZlbnRzW3R5cGVdLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdID0gbnVsbDtcbiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0LnR5cGVDb3VudC0tO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCF0YXJnZXQudHlwZUNvdW50KSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0cy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICAgICAgICAgICAgICBhdHRhY2hlZExpc3RlbmVycy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIHByZXZlbnREZWYgKCkge1xuICAgICAgICAgICAgdGhpcy5yZXR1cm5WYWx1ZSA9IGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gc3RvcFByb3AgKCkge1xuICAgICAgICAgICAgdGhpcy5jYW5jZWxCdWJibGUgPSB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gc3RvcEltbVByb3AgKCkge1xuICAgICAgICAgICAgdGhpcy5jYW5jZWxCdWJibGUgPSB0cnVlO1xuICAgICAgICAgICAgdGhpcy5pbW1lZGlhdGVQcm9wYWdhdGlvblN0b3BwZWQgPSB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGFkZDogYWRkLFxuICAgICAgICAgICAgcmVtb3ZlOiByZW1vdmUsXG4gICAgICAgICAgICB1c2VBdHRhY2hFdmVudDogdXNlQXR0YWNoRXZlbnQsXG5cbiAgICAgICAgICAgIF9lbGVtZW50czogZWxlbWVudHMsXG4gICAgICAgICAgICBfdGFyZ2V0czogdGFyZ2V0cyxcbiAgICAgICAgICAgIF9hdHRhY2hlZExpc3RlbmVyczogYXR0YWNoZWRMaXN0ZW5lcnNcbiAgICAgICAgfTtcbiAgICB9KCkpO1xuXG5mdW5jdGlvbiBibGFuayAoKSB7fVxuXG5mdW5jdGlvbiBpc0VsZW1lbnQgKG8pIHtcbiAgICBpZiAoIW8gfHwgKHR5cGVvZiBvICE9PSAnb2JqZWN0JykpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICB2YXIgX3dpbmRvdyA9IGdldFdpbmRvdyhvKSB8fCB3aW5kb3c7XG5cbiAgICByZXR1cm4gKC9vYmplY3R8ZnVuY3Rpb24vLnRlc3QodHlwZW9mIF93aW5kb3cuRWxlbWVudClcbiAgICAgICAgPyBvIGluc3RhbmNlb2YgX3dpbmRvdy5FbGVtZW50IC8vRE9NMlxuICAgICAgICA6IG8ubm9kZVR5cGUgPT09IDEgJiYgdHlwZW9mIG8ubm9kZU5hbWUgPT09IFwic3RyaW5nXCIpO1xufVxuZnVuY3Rpb24gaXNXaW5kb3cgKHRoaW5nKSB7IHJldHVybiAhISh0aGluZyAmJiB0aGluZy5XaW5kb3cpICYmICh0aGluZyBpbnN0YW5jZW9mIHRoaW5nLldpbmRvdyk7IH1cbmZ1bmN0aW9uIGlzRG9jRnJhZyAodGhpbmcpIHsgcmV0dXJuICEhdGhpbmcgJiYgdGhpbmcgaW5zdGFuY2VvZiBEb2N1bWVudEZyYWdtZW50OyB9XG5mdW5jdGlvbiBpc0FycmF5ICh0aGluZykge1xuICAgIHJldHVybiBpc09iamVjdCh0aGluZylcbiAgICAgICAgJiYgKHR5cGVvZiB0aGluZy5sZW5ndGggIT09IHVuZGVmaW5lZClcbiAgICAgICAgJiYgaXNGdW5jdGlvbih0aGluZy5zcGxpY2UpO1xufVxuZnVuY3Rpb24gaXNPYmplY3QgICAodGhpbmcpIHsgcmV0dXJuICEhdGhpbmcgJiYgKHR5cGVvZiB0aGluZyA9PT0gJ29iamVjdCcpOyB9XG5mdW5jdGlvbiBpc0Z1bmN0aW9uICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnZnVuY3Rpb24nOyB9XG5mdW5jdGlvbiBpc051bWJlciAgICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnbnVtYmVyJyAgOyB9XG5mdW5jdGlvbiBpc0Jvb2wgICAgICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnYm9vbGVhbicgOyB9XG5mdW5jdGlvbiBpc1N0cmluZyAgICh0aGluZykgeyByZXR1cm4gdHlwZW9mIHRoaW5nID09PSAnc3RyaW5nJyAgOyB9XG5cbmZ1bmN0aW9uIHRyeVNlbGVjdG9yICh2YWx1ZSkge1xuICAgIGlmICghaXNTdHJpbmcodmFsdWUpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgLy8gYW4gZXhjZXB0aW9uIHdpbGwgYmUgcmFpc2VkIGlmIGl0IGlzIGludmFsaWRcbiAgICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yKHZhbHVlKTtcbiAgICByZXR1cm4gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gZXh0ZW5kIChkZXN0LCBzb3VyY2UpIHtcbiAgICBmb3IgKHZhciBwcm9wIGluIHNvdXJjZSkge1xuICAgICAgICBkZXN0W3Byb3BdID0gc291cmNlW3Byb3BdO1xuICAgIH1cbiAgICByZXR1cm4gZGVzdDtcbn1cblxuZnVuY3Rpb24gY29weUNvb3JkcyAoZGVzdCwgc3JjKSB7XG4gICAgZGVzdC5wYWdlID0gZGVzdC5wYWdlIHx8IHt9O1xuICAgIGRlc3QucGFnZS54ID0gc3JjLnBhZ2UueDtcbiAgICBkZXN0LnBhZ2UueSA9IHNyYy5wYWdlLnk7XG5cbiAgICBkZXN0LmNsaWVudCA9IGRlc3QuY2xpZW50IHx8IHt9O1xuICAgIGRlc3QuY2xpZW50LnggPSBzcmMuY2xpZW50Lng7XG4gICAgZGVzdC5jbGllbnQueSA9IHNyYy5jbGllbnQueTtcblxuICAgIGRlc3QudGltZVN0YW1wID0gc3JjLnRpbWVTdGFtcDtcbn1cblxuZnVuY3Rpb24gc2V0RXZlbnRYWSAodGFyZ2V0T2JqLCBwb2ludGVyLCBpbnRlcmFjdGlvbikge1xuICAgIGlmICghcG9pbnRlcikge1xuICAgICAgICBpZiAoaW50ZXJhY3Rpb24ucG9pbnRlcklkcy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICBwb2ludGVyID0gdG91Y2hBdmVyYWdlKGludGVyYWN0aW9uLnBvaW50ZXJzKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHBvaW50ZXIgPSBpbnRlcmFjdGlvbi5wb2ludGVyc1swXTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGdldFBhZ2VYWShwb2ludGVyLCB0bXBYWSwgaW50ZXJhY3Rpb24pO1xuICAgIHRhcmdldE9iai5wYWdlLnggPSB0bXBYWS54O1xuICAgIHRhcmdldE9iai5wYWdlLnkgPSB0bXBYWS55O1xuXG4gICAgZ2V0Q2xpZW50WFkocG9pbnRlciwgdG1wWFksIGludGVyYWN0aW9uKTtcbiAgICB0YXJnZXRPYmouY2xpZW50LnggPSB0bXBYWS54O1xuICAgIHRhcmdldE9iai5jbGllbnQueSA9IHRtcFhZLnk7XG5cbiAgICB0YXJnZXRPYmoudGltZVN0YW1wID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG59XG5cbmZ1bmN0aW9uIHNldEV2ZW50RGVsdGFzICh0YXJnZXRPYmosIHByZXYsIGN1cikge1xuICAgIHRhcmdldE9iai5wYWdlLnggICAgID0gY3VyLnBhZ2UueCAgICAgIC0gcHJldi5wYWdlLng7XG4gICAgdGFyZ2V0T2JqLnBhZ2UueSAgICAgPSBjdXIucGFnZS55ICAgICAgLSBwcmV2LnBhZ2UueTtcbiAgICB0YXJnZXRPYmouY2xpZW50LnggICA9IGN1ci5jbGllbnQueCAgICAtIHByZXYuY2xpZW50Lng7XG4gICAgdGFyZ2V0T2JqLmNsaWVudC55ICAgPSBjdXIuY2xpZW50LnkgICAgLSBwcmV2LmNsaWVudC55O1xuICAgIHRhcmdldE9iai50aW1lU3RhbXAgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSAtIHByZXYudGltZVN0YW1wO1xuXG4gICAgLy8gc2V0IHBvaW50ZXIgdmVsb2NpdHlcbiAgICB2YXIgZHQgPSBNYXRoLm1heCh0YXJnZXRPYmoudGltZVN0YW1wIC8gMTAwMCwgMC4wMDEpO1xuICAgIHRhcmdldE9iai5wYWdlLnNwZWVkICAgPSBoeXBvdCh0YXJnZXRPYmoucGFnZS54LCB0YXJnZXRPYmoucGFnZS55KSAvIGR0O1xuICAgIHRhcmdldE9iai5wYWdlLnZ4ICAgICAgPSB0YXJnZXRPYmoucGFnZS54IC8gZHQ7XG4gICAgdGFyZ2V0T2JqLnBhZ2UudnkgICAgICA9IHRhcmdldE9iai5wYWdlLnkgLyBkdDtcblxuICAgIHRhcmdldE9iai5jbGllbnQuc3BlZWQgPSBoeXBvdCh0YXJnZXRPYmouY2xpZW50LngsIHRhcmdldE9iai5wYWdlLnkpIC8gZHQ7XG4gICAgdGFyZ2V0T2JqLmNsaWVudC52eCAgICA9IHRhcmdldE9iai5jbGllbnQueCAvIGR0O1xuICAgIHRhcmdldE9iai5jbGllbnQudnkgICAgPSB0YXJnZXRPYmouY2xpZW50LnkgLyBkdDtcbn1cblxuLy8gR2V0IHNwZWNpZmllZCBYL1kgY29vcmRzIGZvciBtb3VzZSBvciBldmVudC50b3VjaGVzWzBdXG5mdW5jdGlvbiBnZXRYWSAodHlwZSwgcG9pbnRlciwgeHkpIHtcbiAgICB4eSA9IHh5IHx8IHt9O1xuICAgIHR5cGUgPSB0eXBlIHx8ICdwYWdlJztcblxuICAgIHh5LnggPSBwb2ludGVyW3R5cGUgKyAnWCddO1xuICAgIHh5LnkgPSBwb2ludGVyW3R5cGUgKyAnWSddO1xuXG4gICAgcmV0dXJuIHh5O1xufVxuXG5mdW5jdGlvbiBnZXRQYWdlWFkgKHBvaW50ZXIsIHBhZ2UsIGludGVyYWN0aW9uKSB7XG4gICAgcGFnZSA9IHBhZ2UgfHwge307XG5cbiAgICBpZiAocG9pbnRlciBpbnN0YW5jZW9mIEludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgaWYgKC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbiB8fCBwb2ludGVyLmludGVyYWN0aW9uO1xuXG4gICAgICAgICAgICBleHRlbmQocGFnZSwgaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy51cENvb3Jkcy5wYWdlKTtcblxuICAgICAgICAgICAgcGFnZS54ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3g7XG4gICAgICAgICAgICBwYWdlLnkgKz0gaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy5zeTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHBhZ2UueCA9IHBvaW50ZXIucGFnZVg7XG4gICAgICAgICAgICBwYWdlLnkgPSBwb2ludGVyLnBhZ2VZO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8vIE9wZXJhIE1vYmlsZSBoYW5kbGVzIHRoZSB2aWV3cG9ydCBhbmQgc2Nyb2xsaW5nIG9kZGx5XG4gICAgZWxzZSBpZiAoaXNPcGVyYU1vYmlsZSkge1xuICAgICAgICBnZXRYWSgnc2NyZWVuJywgcG9pbnRlciwgcGFnZSk7XG5cbiAgICAgICAgcGFnZS54ICs9IHdpbmRvdy5zY3JvbGxYO1xuICAgICAgICBwYWdlLnkgKz0gd2luZG93LnNjcm9sbFk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBnZXRYWSgncGFnZScsIHBvaW50ZXIsIHBhZ2UpO1xuICAgIH1cblxuICAgIHJldHVybiBwYWdlO1xufVxuXG5mdW5jdGlvbiBnZXRDbGllbnRYWSAocG9pbnRlciwgY2xpZW50LCBpbnRlcmFjdGlvbikge1xuICAgIGNsaWVudCA9IGNsaWVudCB8fCB7fTtcblxuICAgIGlmIChwb2ludGVyIGluc3RhbmNlb2YgSW50ZXJhY3RFdmVudCkge1xuICAgICAgICBpZiAoL2luZXJ0aWFzdGFydC8udGVzdChwb2ludGVyLnR5cGUpKSB7XG4gICAgICAgICAgICBleHRlbmQoY2xpZW50LCBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLnVwQ29vcmRzLmNsaWVudCk7XG5cbiAgICAgICAgICAgIGNsaWVudC54ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3g7XG4gICAgICAgICAgICBjbGllbnQueSArPSBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLnN5O1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgY2xpZW50LnggPSBwb2ludGVyLmNsaWVudFg7XG4gICAgICAgICAgICBjbGllbnQueSA9IHBvaW50ZXIuY2xpZW50WTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgLy8gT3BlcmEgTW9iaWxlIGhhbmRsZXMgdGhlIHZpZXdwb3J0IGFuZCBzY3JvbGxpbmcgb2RkbHlcbiAgICAgICAgZ2V0WFkoaXNPcGVyYU1vYmlsZT8gJ3NjcmVlbic6ICdjbGllbnQnLCBwb2ludGVyLCBjbGllbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBjbGllbnQ7XG59XG5cbmZ1bmN0aW9uIGdldFNjcm9sbFhZICh3aW4pIHtcbiAgICB3aW4gPSB3aW4gfHwgd2luZG93O1xuICAgIHJldHVybiB7XG4gICAgICAgIHg6IHdpbi5zY3JvbGxYIHx8IHdpbi5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsTGVmdCxcbiAgICAgICAgeTogd2luLnNjcm9sbFkgfHwgd2luLmRvY3VtZW50LmRvY3VtZW50RWxlbWVudC5zY3JvbGxUb3BcbiAgICB9O1xufVxuXG5mdW5jdGlvbiBnZXRQb2ludGVySWQgKHBvaW50ZXIpIHtcbiAgICByZXR1cm4gaXNOdW1iZXIocG9pbnRlci5wb2ludGVySWQpPyBwb2ludGVyLnBvaW50ZXJJZCA6IHBvaW50ZXIuaWRlbnRpZmllcjtcbn1cblxuZnVuY3Rpb24gZ2V0QWN0dWFsRWxlbWVudCAoZWxlbWVudCkge1xuICAgIHJldHVybiAoZWxlbWVudCBpbnN0YW5jZW9mIFNWR0VsZW1lbnRJbnN0YW5jZVxuICAgICAgICA/IGVsZW1lbnQuY29ycmVzcG9uZGluZ1VzZUVsZW1lbnRcbiAgICAgICAgOiBlbGVtZW50KTtcbn1cblxuZnVuY3Rpb24gZ2V0V2luZG93IChub2RlKSB7XG4gICAgaWYgKGlzV2luZG93KG5vZGUpKSB7XG4gICAgICAgIHJldHVybiBub2RlO1xuICAgIH1cblxuICAgIHZhciByb290Tm9kZSA9IChub2RlLm93bmVyRG9jdW1lbnQgfHwgbm9kZSk7XG5cbiAgICByZXR1cm4gcm9vdE5vZGUuZGVmYXVsdFZpZXcgfHwgcm9vdE5vZGUucGFyZW50V2luZG93IHx8IHdpbmRvdztcbn1cblxuZnVuY3Rpb24gZ2V0RWxlbWVudFJlY3QgKGVsZW1lbnQpIHtcbiAgICB2YXIgc2Nyb2xsID0gaXNJT1M3b3JMb3dlclxuICAgICAgICAgICAgPyB7IHg6IDAsIHk6IDAgfVxuICAgICAgICAgICAgOiBnZXRTY3JvbGxYWShnZXRXaW5kb3coZWxlbWVudCkpLFxuICAgICAgICBjbGllbnRSZWN0ID0gKGVsZW1lbnQgaW5zdGFuY2VvZiBTVkdFbGVtZW50KT9cbiAgICAgICAgICAgIGVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk6XG4gICAgICAgICAgICBlbGVtZW50LmdldENsaWVudFJlY3RzKClbMF07XG5cbiAgICByZXR1cm4gY2xpZW50UmVjdCAmJiB7XG4gICAgICAgICAgICBsZWZ0ICA6IGNsaWVudFJlY3QubGVmdCAgICsgc2Nyb2xsLngsXG4gICAgICAgICAgICByaWdodCA6IGNsaWVudFJlY3QucmlnaHQgICsgc2Nyb2xsLngsXG4gICAgICAgICAgICB0b3AgICA6IGNsaWVudFJlY3QudG9wICAgICsgc2Nyb2xsLnksXG4gICAgICAgICAgICBib3R0b206IGNsaWVudFJlY3QuYm90dG9tICsgc2Nyb2xsLnksXG4gICAgICAgICAgICB3aWR0aCA6IGNsaWVudFJlY3Qud2lkdGggfHwgY2xpZW50UmVjdC5yaWdodCAtIGNsaWVudFJlY3QubGVmdCxcbiAgICAgICAgICAgIGhlaWdodDogY2xpZW50UmVjdC5oZWlnaCB8fCBjbGllbnRSZWN0LmJvdHRvbSAtIGNsaWVudFJlY3QudG9wXG4gICAgICAgIH07XG59XG5cbmZ1bmN0aW9uIGdldFRvdWNoUGFpciAoZXZlbnQpIHtcbiAgICB2YXIgdG91Y2hlcyA9IFtdO1xuXG4gICAgLy8gYXJyYXkgb2YgdG91Y2hlcyBpcyBzdXBwbGllZFxuICAgIGlmIChpc0FycmF5KGV2ZW50KSkge1xuICAgICAgICB0b3VjaGVzWzBdID0gZXZlbnRbMF07XG4gICAgICAgIHRvdWNoZXNbMV0gPSBldmVudFsxXTtcbiAgICB9XG4gICAgLy8gYW4gZXZlbnRcbiAgICBlbHNlIHtcbiAgICAgICAgaWYgKGV2ZW50LnR5cGUgPT09ICd0b3VjaGVuZCcpIHtcbiAgICAgICAgICAgIGlmIChldmVudC50b3VjaGVzLmxlbmd0aCA9PT0gMSkge1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudC5jaGFuZ2VkVG91Y2hlc1swXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGV2ZW50LnRvdWNoZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgdG91Y2hlc1swXSA9IGV2ZW50LmNoYW5nZWRUb3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudC5jaGFuZ2VkVG91Y2hlc1sxXTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgdG91Y2hlc1sxXSA9IGV2ZW50LnRvdWNoZXNbMV07XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gdG91Y2hlcztcbn1cblxuZnVuY3Rpb24gdG91Y2hBdmVyYWdlIChldmVudCkge1xuICAgIHZhciB0b3VjaGVzID0gZ2V0VG91Y2hQYWlyKGV2ZW50KTtcblxuICAgIHJldHVybiB7XG4gICAgICAgIHBhZ2VYOiAodG91Y2hlc1swXS5wYWdlWCArIHRvdWNoZXNbMV0ucGFnZVgpIC8gMixcbiAgICAgICAgcGFnZVk6ICh0b3VjaGVzWzBdLnBhZ2VZICsgdG91Y2hlc1sxXS5wYWdlWSkgLyAyLFxuICAgICAgICBjbGllbnRYOiAodG91Y2hlc1swXS5jbGllbnRYICsgdG91Y2hlc1sxXS5jbGllbnRYKSAvIDIsXG4gICAgICAgIGNsaWVudFk6ICh0b3VjaGVzWzBdLmNsaWVudFkgKyB0b3VjaGVzWzFdLmNsaWVudFkpIC8gMlxuICAgIH07XG59XG5cbmZ1bmN0aW9uIHRvdWNoQkJveCAoZXZlbnQpIHtcbiAgICBpZiAoIWV2ZW50Lmxlbmd0aCAmJiAhKGV2ZW50LnRvdWNoZXMgJiYgZXZlbnQudG91Y2hlcy5sZW5ndGggPiAxKSkge1xuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgdmFyIHRvdWNoZXMgPSBnZXRUb3VjaFBhaXIoZXZlbnQpLFxuICAgICAgICBtaW5YID0gTWF0aC5taW4odG91Y2hlc1swXS5wYWdlWCwgdG91Y2hlc1sxXS5wYWdlWCksXG4gICAgICAgIG1pblkgPSBNYXRoLm1pbih0b3VjaGVzWzBdLnBhZ2VZLCB0b3VjaGVzWzFdLnBhZ2VZKSxcbiAgICAgICAgbWF4WCA9IE1hdGgubWF4KHRvdWNoZXNbMF0ucGFnZVgsIHRvdWNoZXNbMV0ucGFnZVgpLFxuICAgICAgICBtYXhZID0gTWF0aC5tYXgodG91Y2hlc1swXS5wYWdlWSwgdG91Y2hlc1sxXS5wYWdlWSk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICB4OiBtaW5YLFxuICAgICAgICB5OiBtaW5ZLFxuICAgICAgICBsZWZ0OiBtaW5YLFxuICAgICAgICB0b3A6IG1pblksXG4gICAgICAgIHdpZHRoOiBtYXhYIC0gbWluWCxcbiAgICAgICAgaGVpZ2h0OiBtYXhZIC0gbWluWVxuICAgIH07XG59XG5cbmZ1bmN0aW9uIHRvdWNoRGlzdGFuY2UgKGV2ZW50LCBkZWx0YVNvdXJjZSkge1xuICAgIGRlbHRhU291cmNlID0gZGVsdGFTb3VyY2UgfHwgZGVmYXVsdE9wdGlvbnMuZGVsdGFTb3VyY2U7XG5cbiAgICB2YXIgc291cmNlWCA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgIHRvdWNoZXMgPSBnZXRUb3VjaFBhaXIoZXZlbnQpO1xuXG5cbiAgICB2YXIgZHggPSB0b3VjaGVzWzBdW3NvdXJjZVhdIC0gdG91Y2hlc1sxXVtzb3VyY2VYXSxcbiAgICAgICAgZHkgPSB0b3VjaGVzWzBdW3NvdXJjZVldIC0gdG91Y2hlc1sxXVtzb3VyY2VZXTtcblxuICAgIHJldHVybiBoeXBvdChkeCwgZHkpO1xufVxuXG5mdW5jdGlvbiB0b3VjaEFuZ2xlIChldmVudCwgcHJldkFuZ2xlLCBkZWx0YVNvdXJjZSkge1xuICAgIGRlbHRhU291cmNlID0gZGVsdGFTb3VyY2UgfHwgZGVmYXVsdE9wdGlvbnMuZGVsdGFTb3VyY2U7XG5cbiAgICB2YXIgc291cmNlWCA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgIHRvdWNoZXMgPSBnZXRUb3VjaFBhaXIoZXZlbnQpLFxuICAgICAgICBkeCA9IHRvdWNoZXNbMF1bc291cmNlWF0gLSB0b3VjaGVzWzFdW3NvdXJjZVhdLFxuICAgICAgICBkeSA9IHRvdWNoZXNbMF1bc291cmNlWV0gLSB0b3VjaGVzWzFdW3NvdXJjZVldLFxuICAgICAgICBhbmdsZSA9IDE4MCAqIE1hdGguYXRhbihkeSAvIGR4KSAvIE1hdGguUEk7XG5cbiAgICBpZiAoaXNOdW1iZXIocHJldkFuZ2xlKSkge1xuICAgICAgICB2YXIgZHIgPSBhbmdsZSAtIHByZXZBbmdsZSxcbiAgICAgICAgICAgIGRyQ2xhbXBlZCA9IGRyICUgMzYwO1xuXG4gICAgICAgIGlmIChkckNsYW1wZWQgPiAzMTUpIHtcbiAgICAgICAgICAgIGFuZ2xlIC09IDM2MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChkckNsYW1wZWQgPiAxMzUpIHtcbiAgICAgICAgICAgIGFuZ2xlIC09IDE4MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChkckNsYW1wZWQgPCAtMzE1KSB7XG4gICAgICAgICAgICBhbmdsZSArPSAzNjAgKyAoYW5nbGUgLyAzNjApfDAgKiAzNjA7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZHJDbGFtcGVkIDwgLTEzNSkge1xuICAgICAgICAgICAgYW5nbGUgKz0gMTgwICsgKGFuZ2xlIC8gMzYwKXwwICogMzYwO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuICBhbmdsZTtcbn1cblxuZnVuY3Rpb24gZ2V0T3JpZ2luWFkgKGludGVyYWN0YWJsZSwgZWxlbWVudCkge1xuICAgIHZhciBvcmlnaW4gPSBpbnRlcmFjdGFibGVcbiAgICAgICAgPyBpbnRlcmFjdGFibGUub3B0aW9ucy5vcmlnaW5cbiAgICAgICAgOiBkZWZhdWx0T3B0aW9ucy5vcmlnaW47XG5cbiAgICBpZiAob3JpZ2luID09PSAncGFyZW50Jykge1xuICAgICAgICBvcmlnaW4gPSBwYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgIH1cbiAgICBlbHNlIGlmIChvcmlnaW4gPT09ICdzZWxmJykge1xuICAgICAgICBvcmlnaW4gPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAodHJ5U2VsZWN0b3Iob3JpZ2luKSkge1xuICAgICAgICBvcmlnaW4gPSBjbG9zZXN0KGVsZW1lbnQsIG9yaWdpbikgfHwgeyB4OiAwLCB5OiAwIH07XG4gICAgfVxuXG4gICAgaWYgKGlzRnVuY3Rpb24ob3JpZ2luKSkge1xuICAgICAgICBvcmlnaW4gPSBvcmlnaW4oaW50ZXJhY3RhYmxlICYmIGVsZW1lbnQpO1xuICAgIH1cblxuICAgIGlmIChpc0VsZW1lbnQob3JpZ2luKSkgIHtcbiAgICAgICAgb3JpZ2luID0gZ2V0RWxlbWVudFJlY3Qob3JpZ2luKTtcbiAgICB9XG5cbiAgICBvcmlnaW4ueCA9ICgneCcgaW4gb3JpZ2luKT8gb3JpZ2luLnggOiBvcmlnaW4ubGVmdDtcbiAgICBvcmlnaW4ueSA9ICgneScgaW4gb3JpZ2luKT8gb3JpZ2luLnkgOiBvcmlnaW4udG9wO1xuXG4gICAgcmV0dXJuIG9yaWdpbjtcbn1cblxuLy8gaHR0cDovL3N0YWNrb3ZlcmZsb3cuY29tL2EvNTYzNDUyOC8yMjgwODg4XG5mdW5jdGlvbiBfZ2V0UUJlemllclZhbHVlKHQsIHAxLCBwMiwgcDMpIHtcbiAgICB2YXIgaVQgPSAxIC0gdDtcbiAgICByZXR1cm4gaVQgKiBpVCAqIHAxICsgMiAqIGlUICogdCAqIHAyICsgdCAqIHQgKiBwMztcbn1cblxuZnVuY3Rpb24gZ2V0UXVhZHJhdGljQ3VydmVQb2ludChzdGFydFgsIHN0YXJ0WSwgY3BYLCBjcFksIGVuZFgsIGVuZFksIHBvc2l0aW9uKSB7XG4gICAgcmV0dXJuIHtcbiAgICAgICAgeDogIF9nZXRRQmV6aWVyVmFsdWUocG9zaXRpb24sIHN0YXJ0WCwgY3BYLCBlbmRYKSxcbiAgICAgICAgeTogIF9nZXRRQmV6aWVyVmFsdWUocG9zaXRpb24sIHN0YXJ0WSwgY3BZLCBlbmRZKVxuICAgIH07XG59XG5cbi8vIGh0dHA6Ly9naXptYS5jb20vZWFzaW5nL1xuZnVuY3Rpb24gZWFzZU91dFF1YWQgKHQsIGIsIGMsIGQpIHtcbiAgICB0IC89IGQ7XG4gICAgcmV0dXJuIC1jICogdCoodC0yKSArIGI7XG59XG5cbmZ1bmN0aW9uIG5vZGVDb250YWlucyAocGFyZW50LCBjaGlsZCkge1xuICAgIHdoaWxlIChjaGlsZCkge1xuICAgICAgICBpZiAoY2hpbGQgPT09IHBhcmVudCkge1xuICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgIH1cblxuICAgICAgICBjaGlsZCA9IGNoaWxkLnBhcmVudE5vZGU7XG4gICAgfVxuXG4gICAgcmV0dXJuIGZhbHNlO1xufVxuXG5mdW5jdGlvbiBjbG9zZXN0IChjaGlsZCwgc2VsZWN0b3IpIHtcbiAgICB2YXIgcGFyZW50ID0gcGFyZW50RWxlbWVudChjaGlsZCk7XG5cbiAgICB3aGlsZSAoaXNFbGVtZW50KHBhcmVudCkpIHtcbiAgICAgICAgaWYgKG1hdGNoZXNTZWxlY3RvcihwYXJlbnQsIHNlbGVjdG9yKSkgeyByZXR1cm4gcGFyZW50OyB9XG5cbiAgICAgICAgcGFyZW50ID0gcGFyZW50RWxlbWVudChwYXJlbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBudWxsO1xufVxuXG5mdW5jdGlvbiBwYXJlbnRFbGVtZW50IChub2RlKSB7XG4gICAgdmFyIHBhcmVudCA9IG5vZGUucGFyZW50Tm9kZTtcblxuICAgIGlmIChpc0RvY0ZyYWcocGFyZW50KSkge1xuICAgICAgICAvLyBza2lwIHBhc3QgI3NoYWRvLXJvb3QgZnJhZ21lbnRzXG4gICAgICAgIHdoaWxlICgocGFyZW50ID0gcGFyZW50Lmhvc3QpICYmIGlzRG9jRnJhZyhwYXJlbnQpKSB7fVxuXG4gICAgICAgIHJldHVybiBwYXJlbnQ7XG4gICAgfVxuXG4gICAgcmV0dXJuIHBhcmVudDtcbn1cblxuZnVuY3Rpb24gaW5Db250ZXh0IChpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICByZXR1cm4gaW50ZXJhY3RhYmxlLl9jb250ZXh0ID09PSBlbGVtZW50Lm93bmVyRG9jdW1lbnRcbiAgICAgICAgfHwgbm9kZUNvbnRhaW5zKGludGVyYWN0YWJsZS5fY29udGV4dCwgZWxlbWVudCk7XG59XG5cbmZ1bmN0aW9uIHRlc3RJZ25vcmUgKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlRWxlbWVudCwgZWxlbWVudCkge1xuICAgIHZhciBpZ25vcmVGcm9tID0gaW50ZXJhY3RhYmxlLm9wdGlvbnMuaWdub3JlRnJvbTtcblxuICAgIGlmICghaWdub3JlRnJvbSB8fCAhaXNFbGVtZW50KGVsZW1lbnQpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgaWYgKGlzU3RyaW5nKGlnbm9yZUZyb20pKSB7XG4gICAgICAgIHJldHVybiBtYXRjaGVzVXBUbyhlbGVtZW50LCBpZ25vcmVGcm9tLCBpbnRlcmFjdGFibGVFbGVtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAoaXNFbGVtZW50KGlnbm9yZUZyb20pKSB7XG4gICAgICAgIHJldHVybiBub2RlQ29udGFpbnMoaWdub3JlRnJvbSwgZWxlbWVudCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGZhbHNlO1xufVxuXG5mdW5jdGlvbiB0ZXN0QWxsb3cgKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlRWxlbWVudCwgZWxlbWVudCkge1xuICAgIHZhciBhbGxvd0Zyb20gPSBpbnRlcmFjdGFibGUub3B0aW9ucy5hbGxvd0Zyb207XG5cbiAgICBpZiAoIWFsbG93RnJvbSkgeyByZXR1cm4gdHJ1ZTsgfVxuXG4gICAgaWYgKCFpc0VsZW1lbnQoZWxlbWVudCkpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICBpZiAoaXNTdHJpbmcoYWxsb3dGcm9tKSkge1xuICAgICAgICByZXR1cm4gbWF0Y2hlc1VwVG8oZWxlbWVudCwgYWxsb3dGcm9tLCBpbnRlcmFjdGFibGVFbGVtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAoaXNFbGVtZW50KGFsbG93RnJvbSkpIHtcbiAgICAgICAgcmV0dXJuIG5vZGVDb250YWlucyhhbGxvd0Zyb20sIGVsZW1lbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBmYWxzZTtcbn1cblxuZnVuY3Rpb24gY2hlY2tBeGlzIChheGlzLCBpbnRlcmFjdGFibGUpIHtcbiAgICBpZiAoIWludGVyYWN0YWJsZSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgIHZhciB0aGlzQXhpcyA9IGludGVyYWN0YWJsZS5vcHRpb25zLmRyYWcuYXhpcztcblxuICAgIHJldHVybiAoYXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gYXhpcyk7XG59XG5cbmZ1bmN0aW9uIGNoZWNrU25hcCAoaW50ZXJhY3RhYmxlLCBhY3Rpb24pIHtcbiAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zO1xuXG4gICAgaWYgKC9ecmVzaXplLy50ZXN0KGFjdGlvbikpIHtcbiAgICAgICAgYWN0aW9uID0gJ3Jlc2l6ZSc7XG4gICAgfVxuXG4gICAgcmV0dXJuIG9wdGlvbnNbYWN0aW9uXS5zbmFwICYmIG9wdGlvbnNbYWN0aW9uXS5zbmFwLmVuYWJsZWQ7XG59XG5cbmZ1bmN0aW9uIGNoZWNrUmVzdHJpY3QgKGludGVyYWN0YWJsZSwgYWN0aW9uKSB7XG4gICAgdmFyIG9wdGlvbnMgPSBpbnRlcmFjdGFibGUub3B0aW9ucztcblxuICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgIGFjdGlvbiA9ICdyZXNpemUnO1xuICAgIH1cblxuICAgIHJldHVybiAgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0ICYmIG9wdGlvbnNbYWN0aW9uXS5yZXN0cmljdC5lbmFibGVkO1xufVxuXG5mdW5jdGlvbiBjaGVja0F1dG9TY3JvbGwgKGludGVyYWN0YWJsZSwgYWN0aW9uKSB7XG4gICAgdmFyIG9wdGlvbnMgPSBpbnRlcmFjdGFibGUub3B0aW9ucztcblxuICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgIGFjdGlvbiA9ICdyZXNpemUnO1xuICAgIH1cblxuICAgIHJldHVybiAgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwgJiYgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwuZW5hYmxlZDtcbn1cblxuZnVuY3Rpb24gd2l0aGluSW50ZXJhY3Rpb25MaW1pdCAoaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBhY3Rpb24pIHtcbiAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICBtYXhBY3Rpb25zID0gb3B0aW9uc1thY3Rpb24ubmFtZV0ubWF4LFxuICAgICAgICBtYXhQZXJFbGVtZW50ID0gb3B0aW9uc1thY3Rpb24ubmFtZV0ubWF4UGVyRWxlbWVudCxcbiAgICAgICAgYWN0aXZlSW50ZXJhY3Rpb25zID0gMCxcbiAgICAgICAgdGFyZ2V0Q291bnQgPSAwLFxuICAgICAgICB0YXJnZXRFbGVtZW50Q291bnQgPSAwO1xuXG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGludGVyYWN0aW9ucy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICB2YXIgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbnNbaV0sXG4gICAgICAgICAgICBvdGhlckFjdGlvbiA9IGludGVyYWN0aW9uLnByZXBhcmVkLm5hbWUsXG4gICAgICAgICAgICBhY3RpdmUgPSBpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgIGlmICghYWN0aXZlKSB7IGNvbnRpbnVlOyB9XG5cbiAgICAgICAgYWN0aXZlSW50ZXJhY3Rpb25zKys7XG5cbiAgICAgICAgaWYgKGFjdGl2ZUludGVyYWN0aW9ucyA+PSBtYXhJbnRlcmFjdGlvbnMpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpbnRlcmFjdGlvbi50YXJnZXQgIT09IGludGVyYWN0YWJsZSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgIHRhcmdldENvdW50ICs9IChvdGhlckFjdGlvbiA9PT0gYWN0aW9uLm5hbWUpfDA7XG5cbiAgICAgICAgaWYgKHRhcmdldENvdW50ID49IG1heEFjdGlvbnMpIHtcbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpbnRlcmFjdGlvbi5lbGVtZW50ID09PSBlbGVtZW50KSB7XG4gICAgICAgICAgICB0YXJnZXRFbGVtZW50Q291bnQrKztcblxuICAgICAgICAgICAgaWYgKG90aGVyQWN0aW9uICE9PSBhY3Rpb24ubmFtZSB8fCB0YXJnZXRFbGVtZW50Q291bnQgPj0gbWF4UGVyRWxlbWVudCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBtYXhJbnRlcmFjdGlvbnMgPiAwO1xufVxuXG4vLyBUZXN0IGZvciB0aGUgZWxlbWVudCB0aGF0J3MgXCJhYm92ZVwiIGFsbCBvdGhlciBxdWFsaWZpZXJzXG5mdW5jdGlvbiBpbmRleE9mRGVlcGVzdEVsZW1lbnQgKGVsZW1lbnRzKSB7XG4gICAgdmFyIGRyb3B6b25lLFxuICAgICAgICBkZWVwZXN0Wm9uZSA9IGVsZW1lbnRzWzBdLFxuICAgICAgICBpbmRleCA9IGRlZXBlc3Rab25lPyAwOiAtMSxcbiAgICAgICAgcGFyZW50LFxuICAgICAgICBkZWVwZXN0Wm9uZVBhcmVudHMgPSBbXSxcbiAgICAgICAgZHJvcHpvbmVQYXJlbnRzID0gW10sXG4gICAgICAgIGNoaWxkLFxuICAgICAgICBpLFxuICAgICAgICBuO1xuXG4gICAgZm9yIChpID0gMTsgaSA8IGVsZW1lbnRzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGRyb3B6b25lID0gZWxlbWVudHNbaV07XG5cbiAgICAgICAgLy8gYW4gZWxlbWVudCBtaWdodCBiZWxvbmcgdG8gbXVsdGlwbGUgc2VsZWN0b3IgZHJvcHpvbmVzXG4gICAgICAgIGlmICghZHJvcHpvbmUgfHwgZHJvcHpvbmUgPT09IGRlZXBlc3Rab25lKSB7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghZGVlcGVzdFpvbmUpIHtcbiAgICAgICAgICAgIGRlZXBlc3Rab25lID0gZHJvcHpvbmU7XG4gICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGNoZWNrIGlmIHRoZSBkZWVwZXN0IG9yIGN1cnJlbnQgYXJlIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCBvciBkb2N1bWVudC5yb290RWxlbWVudFxuICAgICAgICAvLyAtIGlmIHRoZSBjdXJyZW50IGRyb3B6b25lIGlzLCBkbyBub3RoaW5nIGFuZCBjb250aW51ZVxuICAgICAgICBpZiAoZHJvcHpvbmUucGFyZW50Tm9kZSA9PT0gZHJvcHpvbmUub3duZXJEb2N1bWVudCkge1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgLy8gLSBpZiBkZWVwZXN0IGlzLCB1cGRhdGUgd2l0aCB0aGUgY3VycmVudCBkcm9wem9uZSBhbmQgY29udGludWUgdG8gbmV4dFxuICAgICAgICBlbHNlIGlmIChkZWVwZXN0Wm9uZS5wYXJlbnROb2RlID09PSBkcm9wem9uZS5vd25lckRvY3VtZW50KSB7XG4gICAgICAgICAgICBkZWVwZXN0Wm9uZSA9IGRyb3B6b25lO1xuICAgICAgICAgICAgaW5kZXggPSBpO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWRlZXBlc3Rab25lUGFyZW50cy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHBhcmVudCA9IGRlZXBlc3Rab25lO1xuICAgICAgICAgICAgd2hpbGUgKHBhcmVudC5wYXJlbnROb2RlICYmIHBhcmVudC5wYXJlbnROb2RlICE9PSBwYXJlbnQub3duZXJEb2N1bWVudCkge1xuICAgICAgICAgICAgICAgIGRlZXBlc3Rab25lUGFyZW50cy51bnNoaWZ0KHBhcmVudCk7XG4gICAgICAgICAgICAgICAgcGFyZW50ID0gcGFyZW50LnBhcmVudE5vZGU7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBpZiB0aGlzIGVsZW1lbnQgaXMgYW4gc3ZnIGVsZW1lbnQgYW5kIHRoZSBjdXJyZW50IGRlZXBlc3QgaXNcbiAgICAgICAgLy8gYW4gSFRNTEVsZW1lbnRcbiAgICAgICAgaWYgKGRlZXBlc3Rab25lIGluc3RhbmNlb2YgSFRNTEVsZW1lbnRcbiAgICAgICAgICAgICYmIGRyb3B6b25lIGluc3RhbmNlb2YgU1ZHRWxlbWVudFxuICAgICAgICAgICAgJiYgIShkcm9wem9uZSBpbnN0YW5jZW9mIFNWR1NWR0VsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgIGlmIChkcm9wem9uZSA9PT0gZGVlcGVzdFpvbmUucGFyZW50Tm9kZSkge1xuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBwYXJlbnQgPSBkcm9wem9uZS5vd25lclNWR0VsZW1lbnQ7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBwYXJlbnQgPSBkcm9wem9uZTtcbiAgICAgICAgfVxuXG4gICAgICAgIGRyb3B6b25lUGFyZW50cyA9IFtdO1xuXG4gICAgICAgIHdoaWxlIChwYXJlbnQucGFyZW50Tm9kZSAhPT0gcGFyZW50Lm93bmVyRG9jdW1lbnQpIHtcbiAgICAgICAgICAgIGRyb3B6b25lUGFyZW50cy51bnNoaWZ0KHBhcmVudCk7XG4gICAgICAgICAgICBwYXJlbnQgPSBwYXJlbnQucGFyZW50Tm9kZTtcbiAgICAgICAgfVxuXG4gICAgICAgIG4gPSAwO1xuXG4gICAgICAgIC8vIGdldCAocG9zaXRpb24gb2YgbGFzdCBjb21tb24gYW5jZXN0b3IpICsgMVxuICAgICAgICB3aGlsZSAoZHJvcHpvbmVQYXJlbnRzW25dICYmIGRyb3B6b25lUGFyZW50c1tuXSA9PT0gZGVlcGVzdFpvbmVQYXJlbnRzW25dKSB7XG4gICAgICAgICAgICBuKys7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgcGFyZW50cyA9IFtcbiAgICAgICAgICAgIGRyb3B6b25lUGFyZW50c1tuIC0gMV0sXG4gICAgICAgICAgICBkcm9wem9uZVBhcmVudHNbbl0sXG4gICAgICAgICAgICBkZWVwZXN0Wm9uZVBhcmVudHNbbl1cbiAgICAgICAgXTtcblxuICAgICAgICBjaGlsZCA9IHBhcmVudHNbMF0ubGFzdENoaWxkO1xuXG4gICAgICAgIHdoaWxlIChjaGlsZCkge1xuICAgICAgICAgICAgaWYgKGNoaWxkID09PSBwYXJlbnRzWzFdKSB7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmUgPSBkcm9wem9uZTtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmVQYXJlbnRzID0gW107XG5cbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGNoaWxkID09PSBwYXJlbnRzWzJdKSB7XG4gICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNoaWxkID0gY2hpbGQucHJldmlvdXNTaWJsaW5nO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGluZGV4O1xufVxuXG5mdW5jdGlvbiBJbnRlcmFjdGlvbiAoKSB7XG4gICAgdGhpcy50YXJnZXQgICAgICAgICAgPSBudWxsOyAvLyBjdXJyZW50IGludGVyYWN0YWJsZSBiZWluZyBpbnRlcmFjdGVkIHdpdGhcbiAgICB0aGlzLmVsZW1lbnQgICAgICAgICA9IG51bGw7IC8vIHRoZSB0YXJnZXQgZWxlbWVudCBvZiB0aGUgaW50ZXJhY3RhYmxlXG4gICAgdGhpcy5kcm9wVGFyZ2V0ICAgICAgPSBudWxsOyAvLyB0aGUgZHJvcHpvbmUgYSBkcmFnIHRhcmdldCBtaWdodCBiZSBkcm9wcGVkIGludG9cbiAgICB0aGlzLmRyb3BFbGVtZW50ICAgICA9IG51bGw7IC8vIHRoZSBlbGVtZW50IGF0IHRoZSB0aW1lIG9mIGNoZWNraW5nXG4gICAgdGhpcy5wcmV2RHJvcFRhcmdldCAgPSBudWxsOyAvLyB0aGUgZHJvcHpvbmUgdGhhdCB3YXMgcmVjZW50bHkgZHJhZ2dlZCBhd2F5IGZyb21cbiAgICB0aGlzLnByZXZEcm9wRWxlbWVudCA9IG51bGw7IC8vIHRoZSBlbGVtZW50IGF0IHRoZSB0aW1lIG9mIGNoZWNraW5nXG5cbiAgICB0aGlzLnByZXBhcmVkICAgICAgICA9IHsgICAgIC8vIGFjdGlvbiB0aGF0J3MgcmVhZHkgdG8gYmUgZmlyZWQgb24gbmV4dCBtb3ZlIGV2ZW50XG4gICAgICAgIG5hbWUgOiBudWxsLFxuICAgICAgICBheGlzIDogbnVsbCxcbiAgICAgICAgZWRnZXM6IG51bGxcbiAgICB9O1xuXG4gICAgdGhpcy5tYXRjaGVzICAgICAgICAgPSBbXTsgICAvLyBhbGwgc2VsZWN0b3JzIHRoYXQgYXJlIG1hdGNoZWQgYnkgdGFyZ2V0IGVsZW1lbnRcbiAgICB0aGlzLm1hdGNoRWxlbWVudHMgICA9IFtdOyAgIC8vIGNvcnJlc3BvbmRpbmcgZWxlbWVudHNcblxuICAgIHRoaXMuaW5lcnRpYVN0YXR1cyA9IHtcbiAgICAgICAgYWN0aXZlICAgICAgIDogZmFsc2UsXG4gICAgICAgIHNtb290aEVuZCAgICA6IGZhbHNlLFxuXG4gICAgICAgIHN0YXJ0RXZlbnQ6IG51bGwsXG4gICAgICAgIHVwQ29vcmRzOiB7fSxcblxuICAgICAgICB4ZTogMCwgeWU6IDAsXG4gICAgICAgIHN4OiAwLCBzeTogMCxcblxuICAgICAgICB0MDogMCxcbiAgICAgICAgdngwOiAwLCB2eXM6IDAsXG4gICAgICAgIGR1cmF0aW9uOiAwLFxuXG4gICAgICAgIHJlc3VtZUR4OiAwLFxuICAgICAgICByZXN1bWVEeTogMCxcblxuICAgICAgICBsYW1iZGFfdjA6IDAsXG4gICAgICAgIG9uZV92ZV92MDogMCxcbiAgICAgICAgaSAgOiBudWxsXG4gICAgfTtcblxuICAgIGlmIChpc0Z1bmN0aW9uKEZ1bmN0aW9uLnByb3RvdHlwZS5iaW5kKSkge1xuICAgICAgICB0aGlzLmJvdW5kSW5lcnRpYUZyYW1lID0gdGhpcy5pbmVydGlhRnJhbWUuYmluZCh0aGlzKTtcbiAgICAgICAgdGhpcy5ib3VuZFNtb290aEVuZEZyYW1lID0gdGhpcy5zbW9vdGhFbmRGcmFtZS5iaW5kKHRoaXMpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdmFyIHRoYXQgPSB0aGlzO1xuXG4gICAgICAgIHRoaXMuYm91bmRJbmVydGlhRnJhbWUgPSBmdW5jdGlvbiAoKSB7IHJldHVybiB0aGF0LmluZXJ0aWFGcmFtZSgpOyB9O1xuICAgICAgICB0aGlzLmJvdW5kU21vb3RoRW5kRnJhbWUgPSBmdW5jdGlvbiAoKSB7IHJldHVybiB0aGF0LnNtb290aEVuZEZyYW1lKCk7IH07XG4gICAgfVxuXG4gICAgdGhpcy5hY3RpdmVEcm9wcyA9IHtcbiAgICAgICAgZHJvcHpvbmVzOiBbXSwgICAgICAvLyB0aGUgZHJvcHpvbmVzIHRoYXQgYXJlIG1lbnRpb25lZCBiZWxvd1xuICAgICAgICBlbGVtZW50cyA6IFtdLCAgICAgIC8vIGVsZW1lbnRzIG9mIGRyb3B6b25lcyB0aGF0IGFjY2VwdCB0aGUgdGFyZ2V0IGRyYWdnYWJsZVxuICAgICAgICByZWN0cyAgICA6IFtdICAgICAgIC8vIHRoZSByZWN0cyBvZiB0aGUgZWxlbWVudHMgbWVudGlvbmVkIGFib3ZlXG4gICAgfTtcblxuICAgIC8vIGtlZXAgdHJhY2sgb2YgYWRkZWQgcG9pbnRlcnNcbiAgICB0aGlzLnBvaW50ZXJzICAgID0gW107XG4gICAgdGhpcy5wb2ludGVySWRzICA9IFtdO1xuICAgIHRoaXMuZG93blRhcmdldHMgPSBbXTtcbiAgICB0aGlzLmRvd25UaW1lcyAgID0gW107XG4gICAgdGhpcy5ob2xkVGltZXJzICA9IFtdO1xuXG4gICAgLy8gUHJldmlvdXMgbmF0aXZlIHBvaW50ZXIgbW92ZSBldmVudCBjb29yZGluYXRlc1xuICAgIHRoaXMucHJldkNvb3JkcyA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcbiAgICAvLyBjdXJyZW50IG5hdGl2ZSBwb2ludGVyIG1vdmUgZXZlbnQgY29vcmRpbmF0ZXNcbiAgICB0aGlzLmN1ckNvb3JkcyA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcblxuICAgIC8vIFN0YXJ0aW5nIEludGVyYWN0RXZlbnQgcG9pbnRlciBjb29yZGluYXRlc1xuICAgIHRoaXMuc3RhcnRDb29yZHMgPSB7XG4gICAgICAgIHBhZ2UgICAgIDogeyB4OiAwLCB5OiAwIH0sXG4gICAgICAgIGNsaWVudCAgIDogeyB4OiAwLCB5OiAwIH0sXG4gICAgICAgIHRpbWVTdGFtcDogMFxuICAgIH07XG5cbiAgICAvLyBDaGFuZ2UgaW4gY29vcmRpbmF0ZXMgYW5kIHRpbWUgb2YgdGhlIHBvaW50ZXJcbiAgICB0aGlzLnBvaW50ZXJEZWx0YSA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAsIHZ4OiAwLCB2eTogMCwgc3BlZWQ6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAsIHZ4OiAwLCB2eTogMCwgc3BlZWQ6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcblxuICAgIHRoaXMuZG93bkV2ZW50ICAgPSBudWxsOyAgICAvLyBwb2ludGVyZG93bi9tb3VzZWRvd24vdG91Y2hzdGFydCBldmVudFxuICAgIHRoaXMuZG93blBvaW50ZXIgPSB7fTtcblxuICAgIHRoaXMuX2V2ZW50VGFyZ2V0ICAgID0gbnVsbDtcbiAgICB0aGlzLl9jdXJFdmVudFRhcmdldCA9IG51bGw7XG5cbiAgICB0aGlzLnByZXZFdmVudCA9IG51bGw7ICAgICAgLy8gcHJldmlvdXMgYWN0aW9uIGV2ZW50XG4gICAgdGhpcy50YXBUaW1lICAgPSAwOyAgICAgICAgIC8vIHRpbWUgb2YgdGhlIG1vc3QgcmVjZW50IHRhcCBldmVudFxuICAgIHRoaXMucHJldlRhcCAgID0gbnVsbDtcblxuICAgIHRoaXMuc3RhcnRPZmZzZXQgICAgPSB7IGxlZnQ6IDAsIHJpZ2h0OiAwLCB0b3A6IDAsIGJvdHRvbTogMCB9O1xuICAgIHRoaXMucmVzdHJpY3RPZmZzZXQgPSB7IGxlZnQ6IDAsIHJpZ2h0OiAwLCB0b3A6IDAsIGJvdHRvbTogMCB9O1xuICAgIHRoaXMuc25hcE9mZnNldHMgICAgPSBbXTtcblxuICAgIHRoaXMuZ2VzdHVyZSA9IHtcbiAgICAgICAgc3RhcnQ6IHsgeDogMCwgeTogMCB9LFxuXG4gICAgICAgIHN0YXJ0RGlzdGFuY2U6IDAsICAgLy8gZGlzdGFuY2UgYmV0d2VlbiB0d28gdG91Y2hlcyBvZiB0b3VjaFN0YXJ0XG4gICAgICAgIHByZXZEaXN0YW5jZSA6IDAsXG4gICAgICAgIGRpc3RhbmNlICAgICA6IDAsXG5cbiAgICAgICAgc2NhbGU6IDEsICAgICAgICAgICAvLyBnZXN0dXJlLmRpc3RhbmNlIC8gZ2VzdHVyZS5zdGFydERpc3RhbmNlXG5cbiAgICAgICAgc3RhcnRBbmdsZTogMCwgICAgICAvLyBhbmdsZSBvZiBsaW5lIGpvaW5pbmcgdHdvIHRvdWNoZXNcbiAgICAgICAgcHJldkFuZ2xlIDogMCAgICAgICAvLyBhbmdsZSBvZiB0aGUgcHJldmlvdXMgZ2VzdHVyZSBldmVudFxuICAgIH07XG5cbiAgICB0aGlzLnNuYXBTdGF0dXMgPSB7XG4gICAgICAgIHggICAgICAgOiAwLCB5ICAgICAgIDogMCxcbiAgICAgICAgZHggICAgICA6IDAsIGR5ICAgICAgOiAwLFxuICAgICAgICByZWFsWCAgIDogMCwgcmVhbFkgICA6IDAsXG4gICAgICAgIHNuYXBwZWRYOiAwLCBzbmFwcGVkWTogMCxcbiAgICAgICAgdGFyZ2V0cyA6IFtdLFxuICAgICAgICBsb2NrZWQgIDogZmFsc2UsXG4gICAgICAgIGNoYW5nZWQgOiBmYWxzZVxuICAgIH07XG5cbiAgICB0aGlzLnJlc3RyaWN0U3RhdHVzID0ge1xuICAgICAgICBkeCAgICAgICAgIDogMCwgZHkgICAgICAgICA6IDAsXG4gICAgICAgIHJlc3RyaWN0ZWRYOiAwLCByZXN0cmljdGVkWTogMCxcbiAgICAgICAgc25hcCAgICAgICA6IG51bGwsXG4gICAgICAgIHJlc3RyaWN0ZWQgOiBmYWxzZSxcbiAgICAgICAgY2hhbmdlZCAgICA6IGZhbHNlXG4gICAgfTtcblxuICAgIHRoaXMucmVzdHJpY3RTdGF0dXMuc25hcCA9IHRoaXMuc25hcFN0YXR1cztcblxuICAgIHRoaXMucG9pbnRlcklzRG93biAgID0gZmFsc2U7XG4gICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBmYWxzZTtcbiAgICB0aGlzLmdlc3R1cmluZyAgICAgICA9IGZhbHNlO1xuICAgIHRoaXMuZHJhZ2dpbmcgICAgICAgID0gZmFsc2U7XG4gICAgdGhpcy5yZXNpemluZyAgICAgICAgPSBmYWxzZTtcbiAgICB0aGlzLnJlc2l6ZUF4ZXMgICAgICA9ICd4eSc7XG5cbiAgICB0aGlzLm1vdXNlID0gZmFsc2U7XG5cbiAgICBpbnRlcmFjdGlvbnMucHVzaCh0aGlzKTtcbn1cblxuSW50ZXJhY3Rpb24ucHJvdG90eXBlID0ge1xuICAgIGdldFBhZ2VYWSAgOiBmdW5jdGlvbiAocG9pbnRlciwgeHkpIHsgcmV0dXJuICAgZ2V0UGFnZVhZKHBvaW50ZXIsIHh5LCB0aGlzKTsgfSxcbiAgICBnZXRDbGllbnRYWTogZnVuY3Rpb24gKHBvaW50ZXIsIHh5KSB7IHJldHVybiBnZXRDbGllbnRYWShwb2ludGVyLCB4eSwgdGhpcyk7IH0sXG4gICAgc2V0RXZlbnRYWSA6IGZ1bmN0aW9uICh0YXJnZXQsIHB0cikgeyByZXR1cm4gIHNldEV2ZW50WFkodGFyZ2V0LCBwdHIsIHRoaXMpOyB9LFxuXG4gICAgcG9pbnRlck92ZXI6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQpIHtcbiAgICAgICAgaWYgKHRoaXMucHJlcGFyZWQubmFtZSB8fCAhdGhpcy5tb3VzZSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgY3VyTWF0Y2hlcyA9IFtdLFxuICAgICAgICAgICAgY3VyTWF0Y2hFbGVtZW50cyA9IFtdLFxuICAgICAgICAgICAgcHJldlRhcmdldEVsZW1lbnQgPSB0aGlzLmVsZW1lbnQ7XG5cbiAgICAgICAgdGhpcy5hZGRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgIGlmICh0aGlzLnRhcmdldFxuICAgICAgICAgICAgJiYgKHRlc3RJZ25vcmUodGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICB8fCAhdGVzdEFsbG93KHRoaXMudGFyZ2V0LCB0aGlzLmVsZW1lbnQsIGV2ZW50VGFyZ2V0KSkpIHtcbiAgICAgICAgICAgIC8vIGlmIHRoZSBldmVudFRhcmdldCBzaG91bGQgYmUgaWdub3JlZCBvciBzaG91bGRuJ3QgYmUgYWxsb3dlZFxuICAgICAgICAgICAgLy8gY2xlYXIgdGhlIHByZXZpb3VzIHRhcmdldFxuICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gbnVsbDtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZWxlbWVudEludGVyYWN0YWJsZSA9IGludGVyYWN0YWJsZXMuZ2V0KGV2ZW50VGFyZ2V0KSxcbiAgICAgICAgICAgIGVsZW1lbnRBY3Rpb24gPSAoZWxlbWVudEludGVyYWN0YWJsZVxuICAgICAgICAgICAgJiYgIXRlc3RJZ25vcmUoZWxlbWVudEludGVyYWN0YWJsZSwgZXZlbnRUYXJnZXQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgJiYgdGVzdEFsbG93KGVsZW1lbnRJbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICYmIHZhbGlkYXRlQWN0aW9uKFxuICAgICAgICAgICAgICAgIGVsZW1lbnRJbnRlcmFjdGFibGUuZ2V0QWN0aW9uKHBvaW50ZXIsIGV2ZW50LCB0aGlzLCBldmVudFRhcmdldCksXG4gICAgICAgICAgICAgICAgZWxlbWVudEludGVyYWN0YWJsZSkpO1xuXG4gICAgICAgIGlmIChlbGVtZW50QWN0aW9uICYmICF3aXRoaW5JbnRlcmFjdGlvbkxpbWl0KGVsZW1lbnRJbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBlbGVtZW50QWN0aW9uKSkge1xuICAgICAgICAgICAgZWxlbWVudEFjdGlvbiA9IG51bGw7XG4gICAgICAgIH1cblxuICAgICAgICBmdW5jdGlvbiBwdXNoQ3VyTWF0Y2hlcyAoaW50ZXJhY3RhYmxlLCBzZWxlY3Rvcikge1xuICAgICAgICAgICAgaWYgKGludGVyYWN0YWJsZVxuICAgICAgICAgICAgICAgICYmIGluQ29udGV4dChpbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmICF0ZXN0SWdub3JlKGludGVyYWN0YWJsZSwgZXZlbnRUYXJnZXQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIHRlc3RBbGxvdyhpbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBtYXRjaGVzU2VsZWN0b3IoZXZlbnRUYXJnZXQsIHNlbGVjdG9yKSkge1xuXG4gICAgICAgICAgICAgICAgY3VyTWF0Y2hlcy5wdXNoKGludGVyYWN0YWJsZSk7XG4gICAgICAgICAgICAgICAgY3VyTWF0Y2hFbGVtZW50cy5wdXNoKGV2ZW50VGFyZ2V0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChlbGVtZW50QWN0aW9uKSB7XG4gICAgICAgICAgICB0aGlzLnRhcmdldCA9IGVsZW1lbnRJbnRlcmFjdGFibGU7XG4gICAgICAgICAgICB0aGlzLmVsZW1lbnQgPSBldmVudFRhcmdldDtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBpbnRlcmFjdGFibGVzLmZvckVhY2hTZWxlY3RvcihwdXNoQ3VyTWF0Y2hlcyk7XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnZhbGlkYXRlU2VsZWN0b3IocG9pbnRlciwgZXZlbnQsIGN1ck1hdGNoZXMsIGN1ck1hdGNoRWxlbWVudHMpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5tYXRjaGVzID0gY3VyTWF0Y2hlcztcbiAgICAgICAgICAgICAgICB0aGlzLm1hdGNoRWxlbWVudHMgPSBjdXJNYXRjaEVsZW1lbnRzO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVySG92ZXIocG9pbnRlciwgZXZlbnQsIHRoaXMubWF0Y2hlcywgdGhpcy5tYXRjaEVsZW1lbnRzKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKGV2ZW50VGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICBQb2ludGVyRXZlbnQ/IHBFdmVudFR5cGVzLm1vdmUgOiAnbW91c2Vtb3ZlJyxcbiAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmICh0aGlzLnRhcmdldCkge1xuICAgICAgICAgICAgICAgIGlmIChub2RlQ29udGFpbnMocHJldlRhcmdldEVsZW1lbnQsIGV2ZW50VGFyZ2V0KSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnBvaW50ZXJIb3Zlcihwb2ludGVyLCBldmVudCwgdGhpcy5tYXRjaGVzLCB0aGlzLm1hdGNoRWxlbWVudHMpO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuZWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgICAgIFBvaW50ZXJFdmVudD8gcEV2ZW50VHlwZXMubW92ZSA6ICdtb3VzZW1vdmUnLFxuICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLnRhcmdldCA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuZWxlbWVudCA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm1hdGNoRWxlbWVudHMgPSBbXTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gQ2hlY2sgd2hhdCBhY3Rpb24gd291bGQgYmUgcGVyZm9ybWVkIG9uIHBvaW50ZXJNb3ZlIHRhcmdldCBpZiBhIG1vdXNlXG4gICAgLy8gYnV0dG9uIHdlcmUgcHJlc3NlZCBhbmQgY2hhbmdlIHRoZSBjdXJzb3IgYWNjb3JkaW5nbHlcbiAgICBwb2ludGVySG92ZXI6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBtYXRjaGVzLCBtYXRjaEVsZW1lbnRzKSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldDtcblxuICAgICAgICBpZiAoIXRoaXMucHJlcGFyZWQubmFtZSAmJiB0aGlzLm1vdXNlKSB7XG5cbiAgICAgICAgICAgIHZhciBhY3Rpb247XG5cbiAgICAgICAgICAgIC8vIHVwZGF0ZSBwb2ludGVyIGNvb3JkcyBmb3IgZGVmYXVsdEFjdGlvbkNoZWNrZXIgdG8gdXNlXG4gICAgICAgICAgICB0aGlzLnNldEV2ZW50WFkodGhpcy5jdXJDb29yZHMsIHBvaW50ZXIpO1xuXG4gICAgICAgICAgICBpZiAobWF0Y2hlcykge1xuICAgICAgICAgICAgICAgIGFjdGlvbiA9IHRoaXMudmFsaWRhdGVTZWxlY3Rvcihwb2ludGVyLCBldmVudCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cyk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmICh0YXJnZXQpIHtcbiAgICAgICAgICAgICAgICBhY3Rpb24gPSB2YWxpZGF0ZUFjdGlvbih0YXJnZXQuZ2V0QWN0aW9uKHRoaXMucG9pbnRlcnNbMF0sIGV2ZW50LCB0aGlzLCB0aGlzLmVsZW1lbnQpLCB0aGlzLnRhcmdldCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMuc3R5bGVDdXJzb3IpIHtcbiAgICAgICAgICAgICAgICBpZiAoYWN0aW9uKSB7XG4gICAgICAgICAgICAgICAgICAgIHRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBnZXRBY3Rpb25DdXJzb3IoYWN0aW9uKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIHRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSAnJztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodGhpcy5wcmVwYXJlZC5uYW1lKSB7XG4gICAgICAgICAgICB0aGlzLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQsIHRhcmdldCwgdGhpcy5lbGVtZW50KTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBwb2ludGVyT3V0OiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIGlmICh0aGlzLnByZXBhcmVkLm5hbWUpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgLy8gUmVtb3ZlIHRlbXBvcmFyeSBldmVudCBsaXN0ZW5lcnMgZm9yIHNlbGVjdG9yIEludGVyYWN0YWJsZXNcbiAgICAgICAgaWYgKCFpbnRlcmFjdGFibGVzLmdldChldmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUoZXZlbnRUYXJnZXQsXG4gICAgICAgICAgICAgICAgUG9pbnRlckV2ZW50PyBwRXZlbnRUeXBlcy5tb3ZlIDogJ21vdXNlbW92ZScsXG4gICAgICAgICAgICAgICAgbGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodGhpcy50YXJnZXQgJiYgdGhpcy50YXJnZXQub3B0aW9ucy5zdHlsZUN1cnNvciAmJiAhdGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICB0aGlzLnRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSAnJztcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzZWxlY3RvckRvd246IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciB0aGF0ID0gdGhpcyxcbiAgICAgICAgLy8gY29weSBldmVudCB0byBiZSB1c2VkIGluIHRpbWVvdXQgZm9yIElFOFxuICAgICAgICAgICAgZXZlbnRDb3B5ID0gZXZlbnRzLnVzZUF0dGFjaEV2ZW50PyBleHRlbmQoe30sIGV2ZW50KSA6IGV2ZW50LFxuICAgICAgICAgICAgZWxlbWVudCA9IGV2ZW50VGFyZ2V0LFxuICAgICAgICAgICAgcG9pbnRlckluZGV4ID0gdGhpcy5hZGRQb2ludGVyKHBvaW50ZXIpLFxuICAgICAgICAgICAgYWN0aW9uO1xuXG4gICAgICAgIHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdID0gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAgICAgICB0aGF0LnBvaW50ZXJIb2xkKGV2ZW50cy51c2VBdHRhY2hFdmVudD8gZXZlbnRDb3B5IDogcG9pbnRlciwgZXZlbnRDb3B5LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuICAgICAgICB9LCBkZWZhdWx0T3B0aW9ucy5faG9sZER1cmF0aW9uKTtcblxuICAgICAgICB0aGlzLnBvaW50ZXJJc0Rvd24gPSB0cnVlO1xuXG4gICAgICAgIC8vIENoZWNrIGlmIHRoZSBkb3duIGV2ZW50IGhpdHMgdGhlIGN1cnJlbnQgaW5lcnRpYSB0YXJnZXRcbiAgICAgICAgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgJiYgdGhpcy50YXJnZXQuc2VsZWN0b3IpIHtcbiAgICAgICAgICAgIC8vIGNsaW1iIHVwIHRoZSBET00gdHJlZSBmcm9tIHRoZSBldmVudCB0YXJnZXRcbiAgICAgICAgICAgIHdoaWxlIChpc0VsZW1lbnQoZWxlbWVudCkpIHtcblxuICAgICAgICAgICAgICAgIC8vIGlmIHRoaXMgZWxlbWVudCBpcyB0aGUgY3VycmVudCBpbmVydGlhIHRhcmdldCBlbGVtZW50XG4gICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQgPT09IHRoaXMuZWxlbWVudFxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gYW5kIHRoZSBwcm9zcGVjdGl2ZSBhY3Rpb24gaXMgdGhlIHNhbWUgYXMgdGhlIG9uZ29pbmcgb25lXG4gICAgICAgICAgICAgICAgICAgICYmIHZhbGlkYXRlQWN0aW9uKHRoaXMudGFyZ2V0LmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgdGhpcy5lbGVtZW50KSwgdGhpcy50YXJnZXQpLm5hbWUgPT09IHRoaXMucHJlcGFyZWQubmFtZSkge1xuXG4gICAgICAgICAgICAgICAgICAgIC8vIHN0b3AgaW5lcnRpYSBzbyB0aGF0IHRoZSBuZXh0IG1vdmUgd2lsbCBiZSBhIG5vcm1hbCBvbmVcbiAgICAgICAgICAgICAgICAgICAgY2FuY2VsRnJhbWUodGhpcy5pbmVydGlhU3RhdHVzLmkpO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmluZXJ0aWFTdGF0dXMuYWN0aXZlID0gZmFsc2U7XG5cbiAgICAgICAgICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2Rvd24nKTtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbGVtZW50ID0gcGFyZW50RWxlbWVudChlbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGRvIG5vdGhpbmcgaWYgaW50ZXJhY3RpbmdcbiAgICAgICAgaWYgKHRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2Rvd24nKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGZ1bmN0aW9uIHB1c2hNYXRjaGVzIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICB2YXIgZWxlbWVudHMgPSBpZThNYXRjaGVzU2VsZWN0b3JcbiAgICAgICAgICAgICAgICA/IGNvbnRleHQucXVlcnlTZWxlY3RvckFsbChzZWxlY3RvcilcbiAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZDtcblxuICAgICAgICAgICAgaWYgKGluQ29udGV4dChpbnRlcmFjdGFibGUsIGVsZW1lbnQpXG4gICAgICAgICAgICAgICAgJiYgIXRlc3RJZ25vcmUoaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiB0ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBtYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVsZW1lbnRzKSkge1xuXG4gICAgICAgICAgICAgICAgdGhhdC5tYXRjaGVzLnB1c2goaW50ZXJhY3RhYmxlKTtcbiAgICAgICAgICAgICAgICB0aGF0Lm1hdGNoRWxlbWVudHMucHVzaChlbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHVwZGF0ZSBwb2ludGVyIGNvb3JkcyBmb3IgZGVmYXVsdEFjdGlvbkNoZWNrZXIgdG8gdXNlXG4gICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLmN1ckNvb3JkcywgcG9pbnRlcik7XG4gICAgICAgIHRoaXMuZG93bkV2ZW50ID0gZXZlbnQ7XG5cbiAgICAgICAgd2hpbGUgKGlzRWxlbWVudChlbGVtZW50KSAmJiAhYWN0aW9uKSB7XG4gICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hFbGVtZW50cyA9IFtdO1xuXG4gICAgICAgICAgICBpbnRlcmFjdGFibGVzLmZvckVhY2hTZWxlY3RvcihwdXNoTWF0Y2hlcyk7XG5cbiAgICAgICAgICAgIGFjdGlvbiA9IHRoaXMudmFsaWRhdGVTZWxlY3Rvcihwb2ludGVyLCBldmVudCwgdGhpcy5tYXRjaGVzLCB0aGlzLm1hdGNoRWxlbWVudHMpO1xuICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoYWN0aW9uKSB7XG4gICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgID0gYWN0aW9uLm5hbWU7XG4gICAgICAgICAgICB0aGlzLnByZXBhcmVkLmF4aXMgID0gYWN0aW9uLmF4aXM7XG4gICAgICAgICAgICB0aGlzLnByZXBhcmVkLmVkZ2VzID0gYWN0aW9uLmVkZ2VzO1xuXG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnZG93bicpO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5wb2ludGVyRG93bihwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBhY3Rpb24pO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgLy8gZG8gdGhlc2Ugbm93IHNpbmNlIHBvaW50ZXJEb3duIGlzbid0IGJlaW5nIGNhbGxlZCBmcm9tIGhlcmVcbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzW3BvaW50ZXJJbmRleF0gPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbcG9pbnRlckluZGV4XSA9IGV2ZW50VGFyZ2V0O1xuICAgICAgICAgICAgZXh0ZW5kKHRoaXMuZG93blBvaW50ZXIsIHBvaW50ZXIpO1xuXG4gICAgICAgICAgICBjb3B5Q29vcmRzKHRoaXMucHJldkNvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBmYWxzZTtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICdkb3duJyk7XG4gICAgfSxcblxuICAgIC8vIERldGVybWluZSBhY3Rpb24gdG8gYmUgcGVyZm9ybWVkIG9uIG5leHQgcG9pbnRlck1vdmUgYW5kIGFkZCBhcHByb3ByaWF0ZVxuICAgIC8vIHN0eWxlIGFuZCBldmVudCBMaXN0ZW5lcnNcbiAgICBwb2ludGVyRG93bjogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQsIGZvcmNlQWN0aW9uKSB7XG4gICAgICAgIGlmICghZm9yY2VBY3Rpb24gJiYgIXRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgJiYgdGhpcy5wb2ludGVyV2FzTW92ZWQgJiYgdGhpcy5wcmVwYXJlZC5uYW1lKSB7XG4gICAgICAgICAgICB0aGlzLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQsIHRoaXMudGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuXG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJc0Rvd24gPSB0cnVlO1xuICAgICAgICB0aGlzLmRvd25FdmVudCA9IGV2ZW50O1xuXG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLmFkZFBvaW50ZXIocG9pbnRlciksXG4gICAgICAgICAgICBhY3Rpb247XG5cbiAgICAgICAgLy8gSWYgaXQgaXMgdGhlIHNlY29uZCB0b3VjaCBvZiBhIG11bHRpLXRvdWNoIGdlc3R1cmUsIGtlZXAgdGhlIHRhcmdldFxuICAgICAgICAvLyB0aGUgc2FtZSBpZiBhIHRhcmdldCB3YXMgc2V0IGJ5IHRoZSBmaXJzdCB0b3VjaFxuICAgICAgICAvLyBPdGhlcndpc2UsIHNldCB0aGUgdGFyZ2V0IGlmIHRoZXJlIGlzIG5vIGFjdGlvbiBwcmVwYXJlZFxuICAgICAgICBpZiAoKHRoaXMucG9pbnRlcklkcy5sZW5ndGggPCAyICYmICF0aGlzLnRhcmdldCkgfHwgIXRoaXMucHJlcGFyZWQubmFtZSkge1xuXG4gICAgICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlcy5nZXQoY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgJiYgIXRlc3RJZ25vcmUoaW50ZXJhY3RhYmxlLCBjdXJFdmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgdGVzdEFsbG93KGludGVyYWN0YWJsZSwgY3VyRXZlbnRUYXJnZXQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIChhY3Rpb24gPSB2YWxpZGF0ZUFjdGlvbihmb3JjZUFjdGlvbiB8fCBpbnRlcmFjdGFibGUuZ2V0QWN0aW9uKHBvaW50ZXIsIGV2ZW50LCB0aGlzLCBjdXJFdmVudFRhcmdldCksIGludGVyYWN0YWJsZSwgZXZlbnRUYXJnZXQpKVxuICAgICAgICAgICAgICAgICYmIHdpdGhpbkludGVyYWN0aW9uTGltaXQoaW50ZXJhY3RhYmxlLCBjdXJFdmVudFRhcmdldCwgYWN0aW9uKSkge1xuICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gaW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgICAgIHRoaXMuZWxlbWVudCA9IGN1ckV2ZW50VGFyZ2V0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHRhcmdldCA9IHRoaXMudGFyZ2V0LFxuICAgICAgICAgICAgb3B0aW9ucyA9IHRhcmdldCAmJiB0YXJnZXQub3B0aW9ucztcblxuICAgICAgICBpZiAodGFyZ2V0ICYmIChmb3JjZUFjdGlvbiB8fCAhdGhpcy5wcmVwYXJlZC5uYW1lKSkge1xuICAgICAgICAgICAgYWN0aW9uID0gYWN0aW9uIHx8IHZhbGlkYXRlQWN0aW9uKGZvcmNlQWN0aW9uIHx8IHRhcmdldC5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIGN1ckV2ZW50VGFyZ2V0KSwgdGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuXG4gICAgICAgICAgICB0aGlzLnNldEV2ZW50WFkodGhpcy5zdGFydENvb3Jkcyk7XG5cbiAgICAgICAgICAgIGlmICghYWN0aW9uKSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICBpZiAob3B0aW9ucy5zdHlsZUN1cnNvcikge1xuICAgICAgICAgICAgICAgIHRhcmdldC5fZG9jLmRvY3VtZW50RWxlbWVudC5zdHlsZS5jdXJzb3IgPSBnZXRBY3Rpb25DdXJzb3IoYWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5yZXNpemVBeGVzID0gYWN0aW9uLm5hbWUgPT09ICdyZXNpemUnPyBhY3Rpb24uYXhpcyA6IG51bGw7XG5cbiAgICAgICAgICAgIGlmIChhY3Rpb24gPT09ICdnZXN0dXJlJyAmJiB0aGlzLnBvaW50ZXJJZHMubGVuZ3RoIDwgMikge1xuICAgICAgICAgICAgICAgIGFjdGlvbiA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSAgPSBhY3Rpb24ubmFtZTtcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuZWRnZXMgPSBhY3Rpb24uZWRnZXM7XG5cbiAgICAgICAgICAgIHRoaXMuc25hcFN0YXR1cy5zbmFwcGVkWCA9IHRoaXMuc25hcFN0YXR1cy5zbmFwcGVkWSA9XG4gICAgICAgICAgICAgICAgdGhpcy5yZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkWCA9IHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZFkgPSBOYU47XG5cbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzW3BvaW50ZXJJbmRleF0gPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbcG9pbnRlckluZGV4XSA9IGV2ZW50VGFyZ2V0O1xuICAgICAgICAgICAgZXh0ZW5kKHRoaXMuZG93blBvaW50ZXIsIHBvaW50ZXIpO1xuXG4gICAgICAgICAgICB0aGlzLnNldEV2ZW50WFkodGhpcy5wcmV2Q29vcmRzKTtcbiAgICAgICAgICAgIHRoaXMucG9pbnRlcldhc01vdmVkID0gZmFsc2U7XG5cbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIC8vIGlmIGluZXJ0aWEgaXMgYWN0aXZlIHRyeSB0byByZXN1bWUgYWN0aW9uXG4gICAgICAgIGVsc2UgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmVcbiAgICAgICAgICAgICYmIGN1ckV2ZW50VGFyZ2V0ID09PSB0aGlzLmVsZW1lbnRcbiAgICAgICAgICAgICYmIHZhbGlkYXRlQWN0aW9uKHRhcmdldC5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIHRoaXMuZWxlbWVudCksIHRhcmdldCkubmFtZSA9PT0gdGhpcy5wcmVwYXJlZC5uYW1lKSB7XG5cbiAgICAgICAgICAgIGNhbmNlbEZyYW1lKHRoaXMuaW5lcnRpYVN0YXR1cy5pKTtcbiAgICAgICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc2V0TW9kaWZpY2F0aW9uczogZnVuY3Rpb24gKGNvb3JkcywgcHJlRW5kKSB7XG4gICAgICAgIHZhciB0YXJnZXQgICAgICAgICA9IHRoaXMudGFyZ2V0LFxuICAgICAgICAgICAgc2hvdWxkTW92ZSAgICAgPSB0cnVlLFxuICAgICAgICAgICAgc2hvdWxkU25hcCAgICAgPSBjaGVja1NuYXAodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICAgICAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uc25hcC5lbmRPbmx5ICAgICB8fCBwcmVFbmQpLFxuICAgICAgICAgICAgc2hvdWxkUmVzdHJpY3QgPSBjaGVja1Jlc3RyaWN0KHRhcmdldCwgdGhpcy5wcmVwYXJlZC5uYW1lKSAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QuZW5kT25seSB8fCBwcmVFbmQpO1xuXG4gICAgICAgIGlmIChzaG91bGRTbmFwICAgICkgeyB0aGlzLnNldFNuYXBwaW5nICAgKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMuc25hcFN0YXR1cyAgICAubG9ja2VkICAgICA9IGZhbHNlOyB9XG4gICAgICAgIGlmIChzaG91bGRSZXN0cmljdCkgeyB0aGlzLnNldFJlc3RyaWN0aW9uKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlOyB9XG5cbiAgICAgICAgaWYgKHNob3VsZFNuYXAgJiYgdGhpcy5zbmFwU3RhdHVzLmxvY2tlZCAmJiAhdGhpcy5zbmFwU3RhdHVzLmNoYW5nZWQpIHtcbiAgICAgICAgICAgIHNob3VsZE1vdmUgPSBzaG91bGRSZXN0cmljdCAmJiB0aGlzLnJlc3RyaWN0U3RhdHVzLnJlc3RyaWN0ZWQgJiYgdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHNob3VsZFJlc3RyaWN0ICYmIHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCAmJiAhdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkKSB7XG4gICAgICAgICAgICBzaG91bGRNb3ZlID0gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gc2hvdWxkTW92ZTtcbiAgICB9LFxuXG4gICAgc2V0U3RhcnRPZmZzZXRzOiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIHJlY3QgPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgICAgIG9yaWdpbiA9IGdldE9yaWdpblhZKGludGVyYWN0YWJsZSwgZWxlbWVudCksXG4gICAgICAgICAgICBzbmFwID0gaW50ZXJhY3RhYmxlLm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgcmVzdHJpY3QgPSBpbnRlcmFjdGFibGUub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnJlc3RyaWN0LFxuICAgICAgICAgICAgd2lkdGgsIGhlaWdodDtcblxuICAgICAgICBpZiAocmVjdCkge1xuICAgICAgICAgICAgdGhpcy5zdGFydE9mZnNldC5sZWZ0ID0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnggLSByZWN0LmxlZnQ7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueSAtIHJlY3QudG9wO1xuXG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICA9IHJlY3QucmlnaHQgIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLng7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LmJvdHRvbSA9IHJlY3QuYm90dG9tIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnk7XG5cbiAgICAgICAgICAgIGlmICgnd2lkdGgnIGluIHJlY3QpIHsgd2lkdGggPSByZWN0LndpZHRoOyB9XG4gICAgICAgICAgICBlbHNlIHsgd2lkdGggPSByZWN0LnJpZ2h0IC0gcmVjdC5sZWZ0OyB9XG4gICAgICAgICAgICBpZiAoJ2hlaWdodCcgaW4gcmVjdCkgeyBoZWlnaHQgPSByZWN0LmhlaWdodDsgfVxuICAgICAgICAgICAgZWxzZSB7IGhlaWdodCA9IHJlY3QuYm90dG9tIC0gcmVjdC50b3A7IH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc3RhcnRPZmZzZXQubGVmdCA9IHRoaXMuc3RhcnRPZmZzZXQudG9wID0gdGhpcy5zdGFydE9mZnNldC5yaWdodCA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tID0gMDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc25hcE9mZnNldHMuc3BsaWNlKDApO1xuXG4gICAgICAgIHZhciBzbmFwT2Zmc2V0ID0gc25hcCAmJiBzbmFwLm9mZnNldCA9PT0gJ3N0YXJ0Q29vcmRzJ1xuICAgICAgICAgICAgPyB7XG4gICAgICAgICAgICB4OiB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueCAtIG9yaWdpbi54LFxuICAgICAgICAgICAgeTogdGhpcy5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueVxuICAgICAgICB9XG4gICAgICAgICAgICA6IHNuYXAgJiYgc25hcC5vZmZzZXQgfHwgeyB4OiAwLCB5OiAwIH07XG5cbiAgICAgICAgaWYgKHJlY3QgJiYgc25hcCAmJiBzbmFwLnJlbGF0aXZlUG9pbnRzICYmIHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLnNuYXBPZmZzZXRzLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICB4OiB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogc25hcC5yZWxhdGl2ZVBvaW50c1tpXS54KSArIHNuYXBPZmZzZXQueCxcbiAgICAgICAgICAgICAgICAgICAgeTogdGhpcy5zdGFydE9mZnNldC50b3AgIC0gKGhlaWdodCAqIHNuYXAucmVsYXRpdmVQb2ludHNbaV0ueSkgKyBzbmFwT2Zmc2V0LnlcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc25hcE9mZnNldHMucHVzaChzbmFwT2Zmc2V0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChyZWN0ICYmIHJlc3RyaWN0LmVsZW1lbnRSZWN0KSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogcmVzdHJpY3QuZWxlbWVudFJlY3QubGVmdCk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgLSAoaGVpZ2h0ICogcmVzdHJpY3QuZWxlbWVudFJlY3QudG9wKTtcblxuICAgICAgICAgICAgdGhpcy5yZXN0cmljdE9mZnNldC5yaWdodCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICAtICh3aWR0aCAgKiAoMSAtIHJlc3RyaWN0LmVsZW1lbnRSZWN0LnJpZ2h0KSk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tIC0gKGhlaWdodCAqICgxIC0gcmVzdHJpY3QuZWxlbWVudFJlY3QuYm90dG9tKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCA9IHRoaXMucmVzdHJpY3RPZmZzZXQucmlnaHQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IDA7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0aW9uLnN0YXJ0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFN0YXJ0IGFuIGFjdGlvbiB3aXRoIHRoZSBnaXZlbiBJbnRlcmFjdGFibGUgYW5kIEVsZW1lbnQgYXMgdGFydGdldHMuIFRoZVxuICAgICAqIGFjdGlvbiBtdXN0IGJlIGVuYWJsZWQgZm9yIHRoZSB0YXJnZXQgSW50ZXJhY3RhYmxlIGFuZCBhbiBhcHByb3ByaWF0ZSBudW1iZXJcbiAgICAgKiBvZiBwb2ludGVycyBtdXN0IGJlIGhlbGQgZG93biDigJMgMSBmb3IgZHJhZy9yZXNpemUsIDIgZm9yIGdlc3R1cmUuXG4gICAgICpcbiAgICAgKiBVc2UgaXQgd2l0aCBgaW50ZXJhY3RhYmxlLjxhY3Rpb24+YWJsZSh7IG1hbnVhbFN0YXJ0OiBmYWxzZSB9KWAgdG8gYWx3YXlzXG4gICAgICogW3N0YXJ0IGFjdGlvbnMgbWFudWFsbHldKGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL2lzc3Vlcy8xMTQpXG4gICAgICpcbiAgICAgLSBhY3Rpb24gICAgICAgKG9iamVjdCkgIFRoZSBhY3Rpb24gdG8gYmUgcGVyZm9ybWVkIC0gZHJhZywgcmVzaXplLCBldGMuXG4gICAgIC0gaW50ZXJhY3RhYmxlIChJbnRlcmFjdGFibGUpIFRoZSBJbnRlcmFjdGFibGUgdG8gdGFyZ2V0XG4gICAgIC0gZWxlbWVudCAgICAgIChFbGVtZW50KSBUaGUgRE9NIEVsZW1lbnQgdG8gdGFyZ2V0XG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICAgKipcbiAgICAgfCBpbnRlcmFjdCh0YXJnZXQpXG4gICAgIHwgICAuZHJhZ2dhYmxlKHtcbiAgICAgfCAgICAgLy8gZGlzYWJsZSB0aGUgZGVmYXVsdCBkcmFnIHN0YXJ0IGJ5IGRvd24tPm1vdmVcbiAgICAgfCAgICAgbWFudWFsU3RhcnQ6IHRydWVcbiAgICAgfCAgIH0pXG4gICAgIHwgICAvLyBzdGFydCBkcmFnZ2luZyBhZnRlciB0aGUgdXNlciBob2xkcyB0aGUgcG9pbnRlciBkb3duXG4gICAgIHwgICAub24oJ2hvbGQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgfCAgICAgdmFyIGludGVyYWN0aW9uID0gZXZlbnQuaW50ZXJhY3Rpb247XG4gICAgIHxcbiAgICAgfCAgICAgaWYgKCFpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpKSB7XG4gICAgIHwgICAgICAgaW50ZXJhY3Rpb24uc3RhcnQoeyBuYW1lOiAnZHJhZycgfSxcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5pbnRlcmFjdGFibGUsXG4gICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQuY3VycmVudFRhcmdldCk7XG4gICAgIHwgICAgIH1cbiAgICAgfCB9KTtcbiAgICAgXFwqL1xuICAgIHN0YXJ0OiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgfHwgIXRoaXMucG9pbnRlcklzRG93blxuICAgICAgICAgICAgfHwgdGhpcy5wb2ludGVySWRzLmxlbmd0aCA8IChhY3Rpb24ubmFtZSA9PT0gJ2dlc3R1cmUnPyAyIDogMSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGlmIHRoaXMgaW50ZXJhY3Rpb24gaGFkIGJlZW4gcmVtb3ZlZCBhZnRlciBzdG9wcGluZ1xuICAgICAgICAvLyBhZGQgaXQgYmFja1xuICAgICAgICBpZiAoaW5kZXhPZihpbnRlcmFjdGlvbnMsIHRoaXMpID09PSAtMSkge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb25zLnB1c2godGhpcyk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgID0gYWN0aW9uLm5hbWU7XG4gICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgdGhpcy5wcmVwYXJlZC5lZGdlcyA9IGFjdGlvbi5lZGdlcztcbiAgICAgICAgdGhpcy50YXJnZXQgICAgICAgICA9IGludGVyYWN0YWJsZTtcbiAgICAgICAgdGhpcy5lbGVtZW50ICAgICAgICA9IGVsZW1lbnQ7XG5cbiAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuc3RhcnRDb29yZHMpO1xuICAgICAgICB0aGlzLnNldFN0YXJ0T2Zmc2V0cyhhY3Rpb24ubmFtZSwgaW50ZXJhY3RhYmxlLCBlbGVtZW50KTtcbiAgICAgICAgdGhpcy5zZXRNb2RpZmljYXRpb25zKHRoaXMuc3RhcnRDb29yZHMucGFnZSk7XG5cbiAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdTdGFydCddKHRoaXMuZG93bkV2ZW50KTtcbiAgICB9LFxuXG4gICAgcG9pbnRlck1vdmU6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBwcmVFbmQpIHtcbiAgICAgICAgdGhpcy5yZWNvcmRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLmN1ckNvb3JkcywgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50KVxuICAgICAgICAgICAgPyB0aGlzLmluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudFxuICAgICAgICAgICAgOiB1bmRlZmluZWQpO1xuXG4gICAgICAgIHZhciBkdXBsaWNhdGVNb3ZlID0gKHRoaXMuY3VyQ29vcmRzLnBhZ2UueCA9PT0gdGhpcy5wcmV2Q29vcmRzLnBhZ2UueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5wYWdlLnkgPT09IHRoaXMucHJldkNvb3Jkcy5wYWdlLnlcbiAgICAgICAgJiYgdGhpcy5jdXJDb29yZHMuY2xpZW50LnggPT09IHRoaXMucHJldkNvb3Jkcy5jbGllbnQueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueSA9PT0gdGhpcy5wcmV2Q29vcmRzLmNsaWVudC55KTtcblxuICAgICAgICB2YXIgZHgsIGR5LFxuICAgICAgICAgICAgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YodGhpcy5wb2ludGVySWRzLCBnZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIC8vIHJlZ2lzdGVyIG1vdmVtZW50IGdyZWF0ZXIgdGhhbiBwb2ludGVyTW92ZVRvbGVyYW5jZVxuICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duICYmICF0aGlzLnBvaW50ZXJXYXNNb3ZlZCkge1xuICAgICAgICAgICAgZHggPSB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueCAtIHRoaXMuc3RhcnRDb29yZHMuY2xpZW50Lng7XG4gICAgICAgICAgICBkeSA9IHRoaXMuY3VyQ29vcmRzLmNsaWVudC55IC0gdGhpcy5zdGFydENvb3Jkcy5jbGllbnQueTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBoeXBvdChkeCwgZHkpID4gcG9pbnRlck1vdmVUb2xlcmFuY2U7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWR1cGxpY2F0ZU1vdmUgJiYgKCF0aGlzLnBvaW50ZXJJc0Rvd24gfHwgdGhpcy5wb2ludGVyV2FzTW92ZWQpKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duKSB7XG4gICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ21vdmUnKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghdGhpcy5wb2ludGVySXNEb3duKSB7IHJldHVybjsgfVxuXG4gICAgICAgIGlmIChkdXBsaWNhdGVNb3ZlICYmIHRoaXMucG9pbnRlcldhc01vdmVkICYmICFwcmVFbmQpIHtcbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICAvLyBzZXQgcG9pbnRlciBjb29yZGluYXRlLCB0aW1lIGNoYW5nZXMgYW5kIHNwZWVkc1xuICAgICAgICBzZXRFdmVudERlbHRhcyh0aGlzLnBvaW50ZXJEZWx0YSwgdGhpcy5wcmV2Q29vcmRzLCB0aGlzLmN1ckNvb3Jkcyk7XG5cbiAgICAgICAgaWYgKCF0aGlzLnByZXBhcmVkLm5hbWUpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgaWYgKHRoaXMucG9pbnRlcldhc01vdmVkXG4gICAgICAgICAgICAgICAgLy8gaWdub3JlIG1vdmVtZW50IHdoaWxlIGluZXJ0aWEgaXMgYWN0aXZlXG4gICAgICAgICAgICAmJiAoIXRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgfHwgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50ICYmIC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkpKSB7XG5cbiAgICAgICAgICAgIC8vIGlmIGp1c3Qgc3RhcnRpbmcgYW4gYWN0aW9uLCBjYWxjdWxhdGUgdGhlIHBvaW50ZXIgc3BlZWQgbm93XG4gICAgICAgICAgICBpZiAoIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHNldEV2ZW50RGVsdGFzKHRoaXMucG9pbnRlckRlbHRhLCB0aGlzLnByZXZDb29yZHMsIHRoaXMuY3VyQ29vcmRzKTtcblxuICAgICAgICAgICAgICAgIC8vIGNoZWNrIGlmIGEgZHJhZyBpcyBpbiB0aGUgY29ycmVjdCBheGlzXG4gICAgICAgICAgICAgICAgaWYgKHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBhYnNYID0gTWF0aC5hYnMoZHgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgYWJzWSA9IE1hdGguYWJzKGR5KSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldEF4aXMgPSB0aGlzLnRhcmdldC5vcHRpb25zLmRyYWcuYXhpcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMgPSAoYWJzWCA+IGFic1kgPyAneCcgOiBhYnNYIDwgYWJzWSA/ICd5JyA6ICd4eScpO1xuXG4gICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBtb3ZlbWVudCBpc24ndCBpbiB0aGUgYXhpcyBvZiB0aGUgaW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgICAgIGlmIChheGlzICE9PSAneHknICYmIHRhcmdldEF4aXMgIT09ICd4eScgJiYgdGFyZ2V0QXhpcyAhPT0gYXhpcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gY2FuY2VsIHRoZSBwcmVwYXJlZCBhY3Rpb25cbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSA9IG51bGw7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHRoZW4gdHJ5IHRvIGdldCBhIGRyYWcgZnJvbSBhbm90aGVyIGluZXJhY3RhYmxlXG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIGVsZW1lbnQgaW50ZXJhY3RhYmxlc1xuICAgICAgICAgICAgICAgICAgICAgICAgd2hpbGUgKGlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50SW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlcy5nZXQoZWxlbWVudCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudEludGVyYWN0YWJsZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBlbGVtZW50SW50ZXJhY3RhYmxlICE9PSB0aGlzLnRhcmdldFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhZWxlbWVudEludGVyYWN0YWJsZS5vcHRpb25zLmRyYWcubWFudWFsU3RhcnRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgZWxlbWVudEludGVyYWN0YWJsZS5nZXRBY3Rpb24odGhpcy5kb3duUG9pbnRlciwgdGhpcy5kb3duRXZlbnQsIHRoaXMsIGVsZW1lbnQpLm5hbWUgPT09ICdkcmFnJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBjaGVja0F4aXMoYXhpcywgZWxlbWVudEludGVyYWN0YWJsZSkpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gZWxlbWVudEludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZXJlJ3Mgbm8gZHJhZyBmcm9tIGVsZW1lbnQgaW50ZXJhY3RhYmxlcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIHRoZSBzZWxlY3RvciBpbnRlcmFjdGFibGVzXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoIXRoaXMucHJlcGFyZWQubmFtZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciB0aGlzSW50ZXJhY3Rpb24gPSB0aGlzO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGdldERyYWdnYWJsZSA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50cyA9IGllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPyBjb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZDtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlID09PSB0aGlzSW50ZXJhY3Rpb24udGFyZ2V0KSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChpbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYmICFpbnRlcmFjdGFibGUub3B0aW9ucy5kcmFnLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhdGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgdGVzdEFsbG93KGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBtYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVsZW1lbnRzKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgaW50ZXJhY3RhYmxlLmdldEFjdGlvbih0aGlzSW50ZXJhY3Rpb24uZG93blBvaW50ZXIsIHRoaXNJbnRlcmFjdGlvbi5kb3duRXZlbnQsIHRoaXNJbnRlcmFjdGlvbiwgZWxlbWVudCkubmFtZSA9PT0gJ2RyYWcnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBjaGVja0F4aXMoYXhpcywgaW50ZXJhY3RhYmxlKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgd2l0aGluSW50ZXJhY3Rpb25MaW1pdChpbnRlcmFjdGFibGUsIGVsZW1lbnQsICdkcmFnJykpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGlsZSAoaXNFbGVtZW50KGVsZW1lbnQpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBzZWxlY3RvckludGVyYWN0YWJsZSA9IGludGVyYWN0YWJsZXMuZm9yRWFjaFNlbGVjdG9yKGdldERyYWdnYWJsZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHNlbGVjdG9ySW50ZXJhY3RhYmxlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnRhcmdldCA9IHNlbGVjdG9ySW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc3RhcnRpbmcgPSAhIXRoaXMucHJlcGFyZWQubmFtZSAmJiAhdGhpcy5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgICAgICBpZiAoc3RhcnRpbmdcbiAgICAgICAgICAgICAgICAmJiAodGhpcy50YXJnZXQub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgfHwgIXdpdGhpbkludGVyYWN0aW9uTGltaXQodGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCwgdGhpcy5wcmVwYXJlZCkpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5zdG9wKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5uYW1lICYmIHRoaXMudGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgaWYgKHN0YXJ0aW5nKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc3RhcnQodGhpcy5wcmVwYXJlZCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgdmFyIHNob3VsZE1vdmUgPSB0aGlzLnNldE1vZGlmaWNhdGlvbnModGhpcy5jdXJDb29yZHMucGFnZSwgcHJlRW5kKTtcblxuICAgICAgICAgICAgICAgIC8vIG1vdmUgaWYgc25hcHBpbmcgb3IgcmVzdHJpY3Rpb24gZG9lc24ndCBwcmV2ZW50IGl0XG4gICAgICAgICAgICAgICAgaWYgKHNob3VsZE1vdmUgfHwgc3RhcnRpbmcpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdNb3ZlJ10oZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBjb3B5Q29vcmRzKHRoaXMucHJldkNvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgIGlmICh0aGlzLmRyYWdnaW5nIHx8IHRoaXMucmVzaXppbmcpIHtcbiAgICAgICAgICAgIHRoaXMuYXV0b1Njcm9sbE1vdmUocG9pbnRlcik7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgZHJhZ1N0YXJ0OiBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgdmFyIGRyYWdFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZHJhZycsICdzdGFydCcsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcmFnZ2luZyA9IHRydWU7XG4gICAgICAgIHRoaXMudGFyZ2V0LmZpcmUoZHJhZ0V2ZW50KTtcblxuICAgICAgICAvLyByZXNldCBhY3RpdmUgZHJvcHpvbmVzXG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHMgID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgID0gW107XG5cbiAgICAgICAgaWYgKCF0aGlzLmR5bmFtaWNEcm9wKSB7XG4gICAgICAgICAgICB0aGlzLnNldEFjdGl2ZURyb3BzKHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5hY3RpdmF0ZSkge1xuICAgICAgICAgICAgdGhpcy5maXJlQWN0aXZlRHJvcHMoZHJvcEV2ZW50cy5hY3RpdmF0ZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICBkcmFnTW92ZTogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIGRyYWdFdmVudCAgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ2RyYWcnLCAnbW92ZScsIHRoaXMuZWxlbWVudCksXG4gICAgICAgICAgICBkcmFnZ2FibGVFbGVtZW50ID0gdGhpcy5lbGVtZW50LFxuICAgICAgICAgICAgZHJvcCA9IHRoaXMuZ2V0RHJvcChldmVudCwgZHJhZ2dhYmxlRWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gZHJvcC5kcm9wem9uZTtcbiAgICAgICAgdGhpcy5kcm9wRWxlbWVudCA9IGRyb3AuZWxlbWVudDtcblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICB0YXJnZXQuZmlyZShkcmFnRXZlbnQpO1xuXG4gICAgICAgIGlmIChkcm9wRXZlbnRzLmxlYXZlKSB7IHRoaXMucHJldkRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmxlYXZlKTsgfVxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5lbnRlcikgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5lbnRlcik7IH1cbiAgICAgICAgaWYgKGRyb3BFdmVudHMubW92ZSApIHsgICAgIHRoaXMuZHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMubW92ZSApOyB9XG5cbiAgICAgICAgdGhpcy5wcmV2RHJvcFRhcmdldCAgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIHRoaXMucHJldkRyb3BFbGVtZW50ID0gdGhpcy5kcm9wRWxlbWVudDtcblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICByZXNpemVTdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciByZXNpemVFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAncmVzaXplJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5lZGdlcykge1xuICAgICAgICAgICAgdmFyIHN0YXJ0UmVjdCA9IHRoaXMudGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHRoaXMudGFyZ2V0Lm9wdGlvbnMucmVzaXplLnNxdWFyZSkge1xuICAgICAgICAgICAgICAgIHZhciBzcXVhcmVFZGdlcyA9IGV4dGVuZCh7fSwgdGhpcy5wcmVwYXJlZC5lZGdlcyk7XG5cbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy50b3AgICAgPSBzcXVhcmVFZGdlcy50b3AgICAgfHwgKHNxdWFyZUVkZ2VzLmxlZnQgICAmJiAhc3F1YXJlRWRnZXMuYm90dG9tKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5sZWZ0ICAgPSBzcXVhcmVFZGdlcy5sZWZ0ICAgfHwgKHNxdWFyZUVkZ2VzLnRvcCAgICAmJiAhc3F1YXJlRWRnZXMucmlnaHQgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5ib3R0b20gPSBzcXVhcmVFZGdlcy5ib3R0b20gfHwgKHNxdWFyZUVkZ2VzLnJpZ2h0ICAmJiAhc3F1YXJlRWRnZXMudG9wICAgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5yaWdodCAgPSBzcXVhcmVFZGdlcy5yaWdodCAgfHwgKHNxdWFyZUVkZ2VzLmJvdHRvbSAmJiAhc3F1YXJlRWRnZXMubGVmdCAgKTtcblxuICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuX3NxdWFyZUVkZ2VzID0gc3F1YXJlRWRnZXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcyA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucmVzaXplUmVjdHMgPSB7XG4gICAgICAgICAgICAgICAgc3RhcnQgICAgIDogc3RhcnRSZWN0LFxuICAgICAgICAgICAgICAgIGN1cnJlbnQgICA6IGV4dGVuZCh7fSwgc3RhcnRSZWN0KSxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkOiBleHRlbmQoe30sIHN0YXJ0UmVjdCksXG4gICAgICAgICAgICAgICAgcHJldmlvdXMgIDogZXh0ZW5kKHt9LCBzdGFydFJlY3QpLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICA6IHtcbiAgICAgICAgICAgICAgICAgICAgbGVmdDogMCwgcmlnaHQgOiAwLCB3aWR0aCA6IDAsXG4gICAgICAgICAgICAgICAgICAgIHRvcCA6IDAsIGJvdHRvbTogMCwgaGVpZ2h0OiAwXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmVzaXplRXZlbnQucmVjdCA9IHRoaXMucmVzaXplUmVjdHMucmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IHRoaXMucmVzaXplUmVjdHMuZGVsdGE7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKHJlc2l6ZUV2ZW50KTtcblxuICAgICAgICB0aGlzLnJlc2l6aW5nID0gdHJ1ZTtcblxuICAgICAgICByZXR1cm4gcmVzaXplRXZlbnQ7XG4gICAgfSxcblxuICAgIHJlc2l6ZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgcmVzaXplRXZlbnQgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ3Jlc2l6ZScsICdtb3ZlJywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICB2YXIgZWRnZXMgPSB0aGlzLnByZXBhcmVkLmVkZ2VzLFxuICAgICAgICAgICAgaW52ZXJ0ID0gdGhpcy50YXJnZXQub3B0aW9ucy5yZXNpemUuaW52ZXJ0LFxuICAgICAgICAgICAgaW52ZXJ0aWJsZSA9IGludmVydCA9PT0gJ3JlcG9zaXRpb24nIHx8IGludmVydCA9PT0gJ25lZ2F0ZSc7XG5cbiAgICAgICAgaWYgKGVkZ2VzKSB7XG4gICAgICAgICAgICB2YXIgZHggPSByZXNpemVFdmVudC5keCxcbiAgICAgICAgICAgICAgICBkeSA9IHJlc2l6ZUV2ZW50LmR5LFxuXG4gICAgICAgICAgICAgICAgc3RhcnQgICAgICA9IHRoaXMucmVzaXplUmVjdHMuc3RhcnQsXG4gICAgICAgICAgICAgICAgY3VycmVudCAgICA9IHRoaXMucmVzaXplUmVjdHMuY3VycmVudCxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkID0gdGhpcy5yZXNpemVSZWN0cy5yZXN0cmljdGVkLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICAgPSB0aGlzLnJlc2l6ZVJlY3RzLmRlbHRhLFxuICAgICAgICAgICAgICAgIHByZXZpb3VzICAgPSBleHRlbmQodGhpcy5yZXNpemVSZWN0cy5wcmV2aW91cywgcmVzdHJpY3RlZCk7XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnRhcmdldC5vcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgICAgICB2YXIgb3JpZ2luYWxFZGdlcyA9IGVkZ2VzO1xuXG4gICAgICAgICAgICAgICAgZWRnZXMgPSB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcztcblxuICAgICAgICAgICAgICAgIGlmICgob3JpZ2luYWxFZGdlcy5sZWZ0ICYmIG9yaWdpbmFsRWRnZXMuYm90dG9tKVxuICAgICAgICAgICAgICAgICAgICB8fCAob3JpZ2luYWxFZGdlcy5yaWdodCAmJiBvcmlnaW5hbEVkZ2VzLnRvcCkpIHtcbiAgICAgICAgICAgICAgICAgICAgZHkgPSAtZHg7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9yaWdpbmFsRWRnZXMubGVmdCB8fCBvcmlnaW5hbEVkZ2VzLnJpZ2h0KSB7IGR5ID0gZHg7IH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChvcmlnaW5hbEVkZ2VzLnRvcCB8fCBvcmlnaW5hbEVkZ2VzLmJvdHRvbSkgeyBkeCA9IGR5OyB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgJ2N1cnJlbnQnIHJlY3Qgd2l0aG91dCBtb2RpZmljYXRpb25zXG4gICAgICAgICAgICBpZiAoZWRnZXMudG9wICAgKSB7IGN1cnJlbnQudG9wICAgICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMuYm90dG9tKSB7IGN1cnJlbnQuYm90dG9tICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMubGVmdCAgKSB7IGN1cnJlbnQubGVmdCAgICs9IGR4OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMucmlnaHQgKSB7IGN1cnJlbnQucmlnaHQgICs9IGR4OyB9XG5cbiAgICAgICAgICAgIGlmIChpbnZlcnRpYmxlKSB7XG4gICAgICAgICAgICAgICAgLy8gaWYgaW52ZXJ0aWJsZSwgY29weSB0aGUgY3VycmVudCByZWN0XG4gICAgICAgICAgICAgICAgZXh0ZW5kKHJlc3RyaWN0ZWQsIGN1cnJlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGludmVydCA9PT0gJ3JlcG9zaXRpb24nKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHN3YXAgZWRnZSB2YWx1ZXMgaWYgbmVjZXNzYXJ5IHRvIGtlZXAgd2lkdGgvaGVpZ2h0IHBvc2l0aXZlXG4gICAgICAgICAgICAgICAgICAgIHZhciBzd2FwO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN0cmljdGVkLnRvcCA+IHJlc3RyaWN0ZWQuYm90dG9tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzd2FwID0gcmVzdHJpY3RlZC50b3A7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQudG9wID0gcmVzdHJpY3RlZC5ib3R0b207XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IHN3YXA7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3RyaWN0ZWQubGVmdCA+IHJlc3RyaWN0ZWQucmlnaHQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHN3YXAgPSByZXN0cmljdGVkLmxlZnQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCA9IHJlc3RyaWN0ZWQucmlnaHQ7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLnJpZ2h0ID0gc3dhcDtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIGlmIG5vdCBpbnZlcnRpYmxlLCByZXN0cmljdCB0byBtaW5pbXVtIG9mIDB4MCByZWN0XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC50b3AgICAgPSBNYXRoLm1pbihjdXJyZW50LnRvcCwgc3RhcnQuYm90dG9tKTtcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IE1hdGgubWF4KGN1cnJlbnQuYm90dG9tLCBzdGFydC50b3ApO1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCAgID0gTWF0aC5taW4oY3VycmVudC5sZWZ0LCBzdGFydC5yaWdodCk7XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC5yaWdodCAgPSBNYXRoLm1heChjdXJyZW50LnJpZ2h0LCBzdGFydC5sZWZ0KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzdHJpY3RlZC53aWR0aCAgPSByZXN0cmljdGVkLnJpZ2h0ICAtIHJlc3RyaWN0ZWQubGVmdDtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWQuaGVpZ2h0ID0gcmVzdHJpY3RlZC5ib3R0b20gLSByZXN0cmljdGVkLnRvcCA7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGVkZ2UgaW4gcmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgIGRlbHRhW2VkZ2VdID0gcmVzdHJpY3RlZFtlZGdlXSAtIHByZXZpb3VzW2VkZ2VdO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXNpemVFdmVudC5lZGdlcyA9IHRoaXMucHJlcGFyZWQuZWRnZXM7XG4gICAgICAgICAgICByZXNpemVFdmVudC5yZWN0ID0gcmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IGRlbHRhO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShyZXNpemVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIHJlc2l6ZUV2ZW50O1xuICAgIH0sXG5cbiAgICBnZXN0dXJlU3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSAwO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5zdGFydERpc3RhbmNlID0gdGhpcy5nZXN0dXJlLnByZXZEaXN0YW5jZSA9IGdlc3R1cmVFdmVudC5kaXN0YW5jZTtcbiAgICAgICAgdGhpcy5nZXN0dXJlLnN0YXJ0QW5nbGUgPSB0aGlzLmdlc3R1cmUucHJldkFuZ2xlID0gZ2VzdHVyZUV2ZW50LmFuZ2xlO1xuICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSAxO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyaW5nID0gdHJ1ZTtcblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKGdlc3R1cmVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIGdlc3R1cmVFdmVudDtcbiAgICB9LFxuXG4gICAgZ2VzdHVyZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBpZiAoIXRoaXMucG9pbnRlcklkcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnByZXZFdmVudDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBnZXN0dXJlRXZlbnQ7XG5cbiAgICAgICAgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ21vdmUnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSBnZXN0dXJlRXZlbnQuc2NhbGUgLSB0aGlzLmdlc3R1cmUuc2NhbGU7XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShnZXN0dXJlRXZlbnQpO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2QW5nbGUgPSBnZXN0dXJlRXZlbnQuYW5nbGU7XG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2RGlzdGFuY2UgPSBnZXN0dXJlRXZlbnQuZGlzdGFuY2U7XG5cbiAgICAgICAgaWYgKGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gSW5maW5pdHkgJiZcbiAgICAgICAgICAgIGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gbnVsbCAmJlxuICAgICAgICAgICAgZ2VzdHVyZUV2ZW50LnNjYWxlICE9PSB1bmRlZmluZWQgICYmXG4gICAgICAgICAgICAhaXNOYU4oZ2VzdHVyZUV2ZW50LnNjYWxlKSkge1xuXG4gICAgICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSBnZXN0dXJlRXZlbnQuc2NhbGU7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZ2VzdHVyZUV2ZW50O1xuICAgIH0sXG5cbiAgICBwb2ludGVySG9sZDogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCkge1xuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnaG9sZCcpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyVXA6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLm1vdXNlPyAwIDogaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGdldFBvaW50ZXJJZChwb2ludGVyKSk7XG5cbiAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcblxuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndXAnICk7XG4gICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICd0YXAnKTtcblxuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyQ2FuY2VsOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YodGhpcy5wb2ludGVySWRzLCBnZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIGNsZWFyVGltZW91dCh0aGlzLmhvbGRUaW1lcnNbcG9pbnRlckluZGV4XSk7XG5cbiAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2NhbmNlbCcpO1xuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICAvLyBodHRwOi8vd3d3LnF1aXJrc21vZGUub3JnL2RvbS9ldmVudHMvY2xpY2suaHRtbFxuICAgIC8vID5FdmVudHMgbGVhZGluZyB0byBkYmxjbGlja1xuICAgIC8vXG4gICAgLy8gSUU4IGRvZXNuJ3QgZmlyZSBkb3duIGV2ZW50IGJlZm9yZSBkYmxjbGljay5cbiAgICAvLyBUaGlzIHdvcmthcm91bmQgdHJpZXMgdG8gZmlyZSBhIHRhcCBhbmQgZG91YmxldGFwIGFmdGVyIGRibGNsaWNrXG4gICAgaWU4RGJsY2xpY2s6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQpIHtcbiAgICAgICAgaWYgKHRoaXMucHJldlRhcFxuICAgICAgICAgICAgJiYgZXZlbnQuY2xpZW50WCA9PT0gdGhpcy5wcmV2VGFwLmNsaWVudFhcbiAgICAgICAgICAgICYmIGV2ZW50LmNsaWVudFkgPT09IHRoaXMucHJldlRhcC5jbGllbnRZXG4gICAgICAgICAgICAmJiBldmVudFRhcmdldCAgID09PSB0aGlzLnByZXZUYXAudGFyZ2V0KSB7XG5cbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbMF0gPSBldmVudFRhcmdldDtcbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzWzBdID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndGFwJyk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gRW5kIGludGVyYWN0IG1vdmUgZXZlbnRzIGFuZCBzdG9wIGF1dG8tc2Nyb2xsIHVubGVzcyBpbmVydGlhIGlzIGVuYWJsZWRcbiAgICBwb2ludGVyRW5kOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgZW5kRXZlbnQsXG4gICAgICAgICAgICB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIG9wdGlvbnMgPSB0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMsXG4gICAgICAgICAgICBpbmVydGlhT3B0aW9ucyA9IG9wdGlvbnMgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5pbmVydGlhLFxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cyA9IHRoaXMuaW5lcnRpYVN0YXR1cztcblxuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhU3RhdHVzLmFjdGl2ZSkgeyByZXR1cm47IH1cblxuICAgICAgICAgICAgdmFyIHBvaW50ZXJTcGVlZCxcbiAgICAgICAgICAgICAgICBub3cgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSBmYWxzZSxcbiAgICAgICAgICAgICAgICBpbmVydGlhID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgc21vb3RoRW5kID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgZW5kU25hcCA9IGNoZWNrU25hcCh0YXJnZXQsIHRoaXMucHJlcGFyZWQubmFtZSkgJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnNuYXAuZW5kT25seSxcbiAgICAgICAgICAgICAgICBlbmRSZXN0cmljdCA9IGNoZWNrUmVzdHJpY3QodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5yZXN0cmljdC5lbmRPbmx5LFxuICAgICAgICAgICAgICAgIGR4ID0gMCxcbiAgICAgICAgICAgICAgICBkeSA9IDAsXG4gICAgICAgICAgICAgICAgc3RhcnRFdmVudDtcblxuICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ2dpbmcpIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChvcHRpb25zLmRyYWcuYXhpcyA9PT0gJ3gnICkgeyBwb2ludGVyU3BlZWQgPSBNYXRoLmFicyh0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudngpOyB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAob3B0aW9ucy5kcmFnLmF4aXMgPT09ICd5JyApIHsgcG9pbnRlclNwZWVkID0gTWF0aC5hYnModGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5KTsgfVxuICAgICAgICAgICAgICAgIGVsc2UgICAvKm9wdGlvbnMuZHJhZy5heGlzID09PSAneHknKi97IHBvaW50ZXJTcGVlZCA9IHRoaXMucG9pbnRlckRlbHRhLmNsaWVudC5zcGVlZDsgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgcG9pbnRlclNwZWVkID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnNwZWVkO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBjaGVjayBpZiBpbmVydGlhIHNob3VsZCBiZSBzdGFydGVkXG4gICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSAoaW5lcnRpYU9wdGlvbnMgJiYgaW5lcnRpYU9wdGlvbnMuZW5hYmxlZFxuICAgICAgICAgICAgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICE9PSAnZ2VzdHVyZSdcbiAgICAgICAgICAgICYmIGV2ZW50ICE9PSBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhID0gKGluZXJ0aWFQb3NzaWJsZVxuICAgICAgICAgICAgJiYgKG5vdyAtIHRoaXMuY3VyQ29vcmRzLnRpbWVTdGFtcCkgPCA1MFxuICAgICAgICAgICAgJiYgcG9pbnRlclNwZWVkID4gaW5lcnRpYU9wdGlvbnMubWluU3BlZWRcbiAgICAgICAgICAgICYmIHBvaW50ZXJTcGVlZCA+IGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkKTtcblxuICAgICAgICAgICAgaWYgKGluZXJ0aWFQb3NzaWJsZSAmJiAhaW5lcnRpYSAmJiAoZW5kU25hcCB8fCBlbmRSZXN0cmljdCkpIHtcblxuICAgICAgICAgICAgICAgIHZhciBzbmFwUmVzdHJpY3QgPSB7fTtcblxuICAgICAgICAgICAgICAgIHNuYXBSZXN0cmljdC5zbmFwID0gc25hcFJlc3RyaWN0LnJlc3RyaWN0ID0gc25hcFJlc3RyaWN0O1xuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFNuYXApIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5zZXRTbmFwcGluZyh0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzbmFwUmVzdHJpY3QpO1xuICAgICAgICAgICAgICAgICAgICBpZiAoc25hcFJlc3RyaWN0LmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZHggKz0gc25hcFJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcFJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFJlc3RyaWN0KSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc2V0UmVzdHJpY3Rpb24odGhpcy5jdXJDb29yZHMucGFnZSwgc25hcFJlc3RyaWN0KTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHNuYXBSZXN0cmljdC5yZXN0cmljdGVkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeCArPSBzbmFwUmVzdHJpY3QuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeSArPSBzbmFwUmVzdHJpY3QuZHk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAoZHggfHwgZHkpIHtcbiAgICAgICAgICAgICAgICAgICAgc21vb3RoRW5kID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhIHx8IHNtb290aEVuZCkge1xuICAgICAgICAgICAgICAgIGNvcHlDb29yZHMoaW5lcnRpYVN0YXR1cy51cENvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVyc1swXSA9IGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCA9IHN0YXJ0RXZlbnQgPVxuICAgICAgICAgICAgICAgICAgICBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgdGhpcy5wcmVwYXJlZC5uYW1lLCAnaW5lcnRpYXN0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudDAgPSBub3c7XG5cbiAgICAgICAgICAgICAgICB0YXJnZXQuZmlyZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGluZXJ0aWEpIHtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy52eDAgPSB0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudng7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudnkwID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnYwID0gcG9pbnRlclNwZWVkO1xuXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuY2FsY0luZXJ0aWEoaW5lcnRpYVN0YXR1cyk7XG5cbiAgICAgICAgICAgICAgICAgICAgdmFyIHBhZ2UgPSBleHRlbmQoe30sIHRoaXMuY3VyQ29vcmRzLnBhZ2UpLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JpZ2luID0gZ2V0T3JpZ2luWFkodGFyZ2V0LCB0aGlzLmVsZW1lbnQpLFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzT2JqZWN0O1xuXG4gICAgICAgICAgICAgICAgICAgIHBhZ2UueCA9IHBhZ2UueCArIGluZXJ0aWFTdGF0dXMueGUgLSBvcmlnaW4ueDtcbiAgICAgICAgICAgICAgICAgICAgcGFnZS55ID0gcGFnZS55ICsgaW5lcnRpYVN0YXR1cy55ZSAtIG9yaWdpbi55O1xuXG4gICAgICAgICAgICAgICAgICAgIHN0YXR1c09iamVjdCA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHVzZVN0YXR1c1hZOiB0cnVlLFxuICAgICAgICAgICAgICAgICAgICAgICAgeDogcGFnZS54LFxuICAgICAgICAgICAgICAgICAgICAgICAgeTogcGFnZS55LFxuICAgICAgICAgICAgICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICAgICAgICAgICAgICBkeTogMCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNuYXA6IG51bGxcbiAgICAgICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgICAgICBzdGF0dXNPYmplY3Quc25hcCA9IHN0YXR1c09iamVjdDtcblxuICAgICAgICAgICAgICAgICAgICBkeCA9IGR5ID0gMDtcblxuICAgICAgICAgICAgICAgICAgICBpZiAoZW5kU25hcCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHNuYXAgPSB0aGlzLnNldFNuYXBwaW5nKHRoaXMuY3VyQ29vcmRzLnBhZ2UsIHN0YXR1c09iamVjdCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzbmFwLmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHNuYXAuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcC5keTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIGlmIChlbmRSZXN0cmljdCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHJlc3RyaWN0ID0gdGhpcy5zZXRSZXN0cmljdGlvbih0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzdGF0dXNPYmplY3QpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAocmVzdHJpY3QucmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR5ICs9IHJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFhlICs9IGR4O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUgKz0gZHk7XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5pID0gcmVxRnJhbWUodGhpcy5ib3VuZEluZXJ0aWFGcmFtZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnNtb290aEVuZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMueGUgPSBkeDtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy55ZSA9IGR5O1xuXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLnN5ID0gMDtcblxuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLmkgPSByZXFGcmFtZSh0aGlzLmJvdW5kU21vb3RoRW5kRnJhbWUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuYWN0aXZlID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChlbmRTbmFwIHx8IGVuZFJlc3RyaWN0KSB7XG4gICAgICAgICAgICAgICAgLy8gZmlyZSBhIG1vdmUgZXZlbnQgYXQgdGhlIHNuYXBwZWQgY29vcmRpbmF0ZXNcbiAgICAgICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQsIHRydWUpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMuZHJhZ2dpbmcpIHtcbiAgICAgICAgICAgIGVuZEV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdkcmFnJywgJ2VuZCcsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgICAgIHZhciBkcmFnZ2FibGVFbGVtZW50ID0gdGhpcy5lbGVtZW50LFxuICAgICAgICAgICAgICAgIGRyb3AgPSB0aGlzLmdldERyb3AoZXZlbnQsIGRyYWdnYWJsZUVsZW1lbnQpO1xuXG4gICAgICAgICAgICB0aGlzLmRyb3BUYXJnZXQgPSBkcm9wLmRyb3B6b25lO1xuICAgICAgICAgICAgdGhpcy5kcm9wRWxlbWVudCA9IGRyb3AuZWxlbWVudDtcblxuICAgICAgICAgICAgdmFyIGRyb3BFdmVudHMgPSB0aGlzLmdldERyb3BFdmVudHMoZXZlbnQsIGVuZEV2ZW50KTtcblxuICAgICAgICAgICAgaWYgKGRyb3BFdmVudHMubGVhdmUpIHsgdGhpcy5wcmV2RHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMubGVhdmUpOyB9XG4gICAgICAgICAgICBpZiAoZHJvcEV2ZW50cy5lbnRlcikgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5lbnRlcik7IH1cbiAgICAgICAgICAgIGlmIChkcm9wRXZlbnRzLmRyb3AgKSB7ICAgICB0aGlzLmRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmRyb3AgKTsgfVxuICAgICAgICAgICAgaWYgKGRyb3BFdmVudHMuZGVhY3RpdmF0ZSkge1xuICAgICAgICAgICAgICAgIHRoaXMuZmlyZUFjdGl2ZURyb3BzKGRyb3BFdmVudHMuZGVhY3RpdmF0ZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRhcmdldC5maXJlKGVuZEV2ZW50KTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmICh0aGlzLnJlc2l6aW5nKSB7XG4gICAgICAgICAgICBlbmRFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAncmVzaXplJywgJ2VuZCcsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB0YXJnZXQuZmlyZShlbmRFdmVudCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodGhpcy5nZXN0dXJpbmcpIHtcbiAgICAgICAgICAgIGVuZEV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ2VuZCcsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB0YXJnZXQuZmlyZShlbmRFdmVudCk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnN0b3AoZXZlbnQpO1xuICAgIH0sXG5cbiAgICBjb2xsZWN0RHJvcHM6IGZ1bmN0aW9uIChlbGVtZW50KSB7XG4gICAgICAgIHZhciBkcm9wcyA9IFtdLFxuICAgICAgICAgICAgZWxlbWVudHMgPSBbXSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgZWxlbWVudCA9IGVsZW1lbnQgfHwgdGhpcy5lbGVtZW50O1xuXG4gICAgICAgIC8vIGNvbGxlY3QgYWxsIGRyb3B6b25lcyBhbmQgdGhlaXIgZWxlbWVudHMgd2hpY2ggcXVhbGlmeSBmb3IgYSBkcm9wXG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBpbnRlcmFjdGFibGVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoIWludGVyYWN0YWJsZXNbaV0ub3B0aW9ucy5kcm9wLmVuYWJsZWQpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgdmFyIGN1cnJlbnQgPSBpbnRlcmFjdGFibGVzW2ldLFxuICAgICAgICAgICAgICAgIGFjY2VwdCA9IGN1cnJlbnQub3B0aW9ucy5kcm9wLmFjY2VwdDtcblxuICAgICAgICAgICAgLy8gdGVzdCB0aGUgZHJhZ2dhYmxlIGVsZW1lbnQgYWdhaW5zdCB0aGUgZHJvcHpvbmUncyBhY2NlcHQgc2V0dGluZ1xuICAgICAgICAgICAgaWYgKChpc0VsZW1lbnQoYWNjZXB0KSAmJiBhY2NlcHQgIT09IGVsZW1lbnQpXG4gICAgICAgICAgICAgICAgfHwgKGlzU3RyaW5nKGFjY2VwdClcbiAgICAgICAgICAgICAgICAmJiAhbWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIGFjY2VwdCkpKSB7XG5cbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gcXVlcnkgZm9yIG5ldyBlbGVtZW50cyBpZiBuZWNlc3NhcnlcbiAgICAgICAgICAgIHZhciBkcm9wRWxlbWVudHMgPSBjdXJyZW50LnNlbGVjdG9yPyBjdXJyZW50Ll9jb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoY3VycmVudC5zZWxlY3RvcikgOiBbY3VycmVudC5fZWxlbWVudF07XG5cbiAgICAgICAgICAgIGZvciAodmFyIGogPSAwLCBsZW4gPSBkcm9wRWxlbWVudHMubGVuZ3RoOyBqIDwgbGVuOyBqKyspIHtcbiAgICAgICAgICAgICAgICB2YXIgY3VycmVudEVsZW1lbnQgPSBkcm9wRWxlbWVudHNbal07XG5cbiAgICAgICAgICAgICAgICBpZiAoY3VycmVudEVsZW1lbnQgPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgZHJvcHMucHVzaChjdXJyZW50KTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKGN1cnJlbnRFbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBkcm9wem9uZXM6IGRyb3BzLFxuICAgICAgICAgICAgZWxlbWVudHM6IGVsZW1lbnRzXG4gICAgICAgIH07XG4gICAgfSxcblxuICAgIGZpcmVBY3RpdmVEcm9wczogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciBpLFxuICAgICAgICAgICAgY3VycmVudCxcbiAgICAgICAgICAgIGN1cnJlbnRFbGVtZW50LFxuICAgICAgICAgICAgcHJldkVsZW1lbnQ7XG5cbiAgICAgICAgLy8gbG9vcCB0aHJvdWdoIGFsbCBhY3RpdmUgZHJvcHpvbmVzIGFuZCB0cmlnZ2VyIGV2ZW50XG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgY3VycmVudCA9IHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzW2ldO1xuICAgICAgICAgICAgY3VycmVudEVsZW1lbnQgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzIFtpXTtcblxuICAgICAgICAgICAgLy8gcHJldmVudCB0cmlnZ2VyIG9mIGR1cGxpY2F0ZSBldmVudHMgb24gc2FtZSBlbGVtZW50XG4gICAgICAgICAgICBpZiAoY3VycmVudEVsZW1lbnQgIT09IHByZXZFbGVtZW50KSB7XG4gICAgICAgICAgICAgICAgLy8gc2V0IGN1cnJlbnQgZWxlbWVudCBhcyBldmVudCB0YXJnZXRcbiAgICAgICAgICAgICAgICBldmVudC50YXJnZXQgPSBjdXJyZW50RWxlbWVudDtcbiAgICAgICAgICAgICAgICBjdXJyZW50LmZpcmUoZXZlbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcHJldkVsZW1lbnQgPSBjdXJyZW50RWxlbWVudDtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICAvLyBDb2xsZWN0IGEgbmV3IHNldCBvZiBwb3NzaWJsZSBkcm9wcyBhbmQgc2F2ZSB0aGVtIGluIGFjdGl2ZURyb3BzLlxuICAgIC8vIHNldEFjdGl2ZURyb3BzIHNob3VsZCBhbHdheXMgYmUgY2FsbGVkIHdoZW4gYSBkcmFnIGhhcyBqdXN0IHN0YXJ0ZWQgb3IgYVxuICAgIC8vIGRyYWcgZXZlbnQgaGFwcGVucyB3aGlsZSBkeW5hbWljRHJvcCBpcyB0cnVlXG4gICAgc2V0QWN0aXZlRHJvcHM6IGZ1bmN0aW9uIChkcmFnRWxlbWVudCkge1xuICAgICAgICAvLyBnZXQgZHJvcHpvbmVzIGFuZCB0aGVpciBlbGVtZW50cyB0aGF0IGNvdWxkIHJlY2VpdmUgdGhlIGRyYWdnYWJsZVxuICAgICAgICB2YXIgcG9zc2libGVEcm9wcyA9IHRoaXMuY29sbGVjdERyb3BzKGRyYWdFbGVtZW50LCB0cnVlKTtcblxuICAgICAgICB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcyA9IHBvc3NpYmxlRHJvcHMuZHJvcHpvbmVzO1xuICAgICAgICB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzICA9IHBvc3NpYmxlRHJvcHMuZWxlbWVudHM7XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgID0gW107XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5yZWN0c1tpXSA9IHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzW2ldLmdldFJlY3QodGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50c1tpXSk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgZ2V0RHJvcDogZnVuY3Rpb24gKGV2ZW50LCBkcmFnRWxlbWVudCkge1xuICAgICAgICB2YXIgdmFsaWREcm9wcyA9IFtdO1xuXG4gICAgICAgIGlmIChkeW5hbWljRHJvcCkge1xuICAgICAgICAgICAgdGhpcy5zZXRBY3RpdmVEcm9wcyhkcmFnRWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBjb2xsZWN0IGFsbCBkcm9wem9uZXMgYW5kIHRoZWlyIGVsZW1lbnRzIHdoaWNoIHF1YWxpZnkgZm9yIGEgZHJvcFxuICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICB2YXIgY3VycmVudCAgICAgICAgPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tqXSxcbiAgICAgICAgICAgICAgICBjdXJyZW50RWxlbWVudCA9IHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHMgW2pdLFxuICAgICAgICAgICAgICAgIHJlY3QgICAgICAgICAgID0gdGhpcy5hY3RpdmVEcm9wcy5yZWN0cyAgICBbal07XG5cbiAgICAgICAgICAgIHZhbGlkRHJvcHMucHVzaChjdXJyZW50LmRyb3BDaGVjayh0aGlzLnBvaW50ZXJzWzBdLCBldmVudCwgdGhpcy50YXJnZXQsIGRyYWdFbGVtZW50LCBjdXJyZW50RWxlbWVudCwgcmVjdClcbiAgICAgICAgICAgICAgICA/IGN1cnJlbnRFbGVtZW50XG4gICAgICAgICAgICAgICAgOiBudWxsKTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGdldCB0aGUgbW9zdCBhcHByb3ByaWF0ZSBkcm9wem9uZSBiYXNlZCBvbiBET00gZGVwdGggYW5kIG9yZGVyXG4gICAgICAgIHZhciBkcm9wSW5kZXggPSBpbmRleE9mRGVlcGVzdEVsZW1lbnQodmFsaWREcm9wcyksXG4gICAgICAgICAgICBkcm9wem9uZSAgPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tkcm9wSW5kZXhdIHx8IG51bGwsXG4gICAgICAgICAgICBlbGVtZW50ICAgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzIFtkcm9wSW5kZXhdIHx8IG51bGw7XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGRyb3B6b25lOiBkcm9wem9uZSxcbiAgICAgICAgICAgIGVsZW1lbnQ6IGVsZW1lbnRcbiAgICAgICAgfTtcbiAgICB9LFxuXG4gICAgZ2V0RHJvcEV2ZW50czogZnVuY3Rpb24gKHBvaW50ZXJFdmVudCwgZHJhZ0V2ZW50KSB7XG4gICAgICAgIHZhciBkcm9wRXZlbnRzID0ge1xuICAgICAgICAgICAgZW50ZXIgICAgIDogbnVsbCxcbiAgICAgICAgICAgIGxlYXZlICAgICA6IG51bGwsXG4gICAgICAgICAgICBhY3RpdmF0ZSAgOiBudWxsLFxuICAgICAgICAgICAgZGVhY3RpdmF0ZTogbnVsbCxcbiAgICAgICAgICAgIG1vdmUgICAgICA6IG51bGwsXG4gICAgICAgICAgICBkcm9wICAgICAgOiBudWxsXG4gICAgICAgIH07XG5cbiAgICAgICAgaWYgKHRoaXMuZHJvcEVsZW1lbnQgIT09IHRoaXMucHJldkRyb3BFbGVtZW50KSB7XG4gICAgICAgICAgICAvLyBpZiB0aGVyZSB3YXMgYSBwcmV2RHJvcFRhcmdldCwgY3JlYXRlIGEgZHJhZ2xlYXZlIGV2ZW50XG4gICAgICAgICAgICBpZiAodGhpcy5wcmV2RHJvcFRhcmdldCkge1xuICAgICAgICAgICAgICAgIGRyb3BFdmVudHMubGVhdmUgPSB7XG4gICAgICAgICAgICAgICAgICAgIHRhcmdldCAgICAgICA6IHRoaXMucHJldkRyb3BFbGVtZW50LFxuICAgICAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLnByZXZEcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICByZWxhdGVkVGFyZ2V0OiBkcmFnRXZlbnQudGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uICA6IHRoaXMsXG4gICAgICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcmFnbGVhdmUnXG4gICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgIGRyYWdFdmVudC5kcmFnTGVhdmUgPSB0aGlzLnByZXZEcm9wRWxlbWVudDtcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQucHJldkRyb3B6b25lID0gdGhpcy5wcmV2RHJvcFRhcmdldDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIGlmIHRoZSBkcm9wVGFyZ2V0IGlzIG5vdCBudWxsLCBjcmVhdGUgYSBkcmFnZW50ZXIgZXZlbnRcbiAgICAgICAgICAgIGlmICh0aGlzLmRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgICAgICBkcm9wRXZlbnRzLmVudGVyID0ge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiB0aGlzLmRyb3BFbGVtZW50LFxuICAgICAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLmRyb3BUYXJnZXQsXG4gICAgICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgICAgIGRyYWdnYWJsZSAgICA6IGRyYWdFdmVudC5pbnRlcmFjdGFibGUsXG4gICAgICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICAgICAgdGltZVN0YW1wICAgIDogZHJhZ0V2ZW50LnRpbWVTdGFtcCxcbiAgICAgICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2RyYWdlbnRlcidcbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgZHJhZ0V2ZW50LmRyYWdFbnRlciA9IHRoaXMuZHJvcEVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgZHJhZ0V2ZW50LmRyb3B6b25lID0gdGhpcy5kcm9wVGFyZ2V0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ2VuZCcgJiYgdGhpcy5kcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICBkcm9wRXZlbnRzLmRyb3AgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogdGhpcy5kcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLmRyb3BUYXJnZXQsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2Ryb3AnXG4gICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICBkcmFnRXZlbnQuZHJvcHpvbmUgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ3N0YXJ0Jykge1xuICAgICAgICAgICAgZHJvcEV2ZW50cy5hY3RpdmF0ZSA9IHtcbiAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIGRyb3B6b25lICAgICA6IG51bGwsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2Ryb3BhY3RpdmF0ZSdcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ2VuZCcpIHtcbiAgICAgICAgICAgIGRyb3BFdmVudHMuZGVhY3RpdmF0ZSA9IHtcbiAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIGRyb3B6b25lICAgICA6IG51bGwsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgdHlwZSAgICAgICAgIDogJ2Ryb3BkZWFjdGl2YXRlJ1xuICAgICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgICAgICBpZiAoZHJhZ0V2ZW50LnR5cGUgPT09ICdkcmFnbW92ZScgJiYgdGhpcy5kcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICBkcm9wRXZlbnRzLm1vdmUgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogdGhpcy5kcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiB0aGlzLmRyb3BUYXJnZXQsXG4gICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgIGRyYWdFdmVudCAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgIGRyYWdtb3ZlICAgICA6IGRyYWdFdmVudCxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wbW92ZSdcbiAgICAgICAgICAgIH07XG4gICAgICAgICAgICBkcmFnRXZlbnQuZHJvcHpvbmUgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZHJvcEV2ZW50cztcbiAgICB9LFxuXG4gICAgY3VycmVudEFjdGlvbjogZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gKHRoaXMuZHJhZ2dpbmcgJiYgJ2RyYWcnKSB8fCAodGhpcy5yZXNpemluZyAmJiAncmVzaXplJykgfHwgKHRoaXMuZ2VzdHVyaW5nICYmICdnZXN0dXJlJykgfHwgbnVsbDtcbiAgICB9LFxuXG4gICAgaW50ZXJhY3Rpbmc6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuZHJhZ2dpbmcgfHwgdGhpcy5yZXNpemluZyB8fCB0aGlzLmdlc3R1cmluZztcbiAgICB9LFxuXG4gICAgY2xlYXJUYXJnZXRzOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMudGFyZ2V0ID0gdGhpcy5lbGVtZW50ID0gbnVsbDtcblxuICAgICAgICB0aGlzLmRyb3BUYXJnZXQgPSB0aGlzLmRyb3BFbGVtZW50ID0gdGhpcy5wcmV2RHJvcFRhcmdldCA9IHRoaXMucHJldkRyb3BFbGVtZW50ID0gbnVsbDtcbiAgICB9LFxuXG4gICAgc3RvcDogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIGlmICh0aGlzLmludGVyYWN0aW5nKCkpIHtcbiAgICAgICAgICAgIGF1dG9TY3JvbGwuc3RvcCgpO1xuICAgICAgICAgICAgdGhpcy5tYXRjaGVzID0gW107XG4gICAgICAgICAgICB0aGlzLm1hdGNoRWxlbWVudHMgPSBbXTtcblxuICAgICAgICAgICAgdmFyIHRhcmdldCA9IHRoaXMudGFyZ2V0O1xuXG4gICAgICAgICAgICBpZiAodGFyZ2V0Lm9wdGlvbnMuc3R5bGVDdXJzb3IpIHtcbiAgICAgICAgICAgICAgICB0YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHByZXZlbnQgRGVmYXVsdCBvbmx5IGlmIHdlcmUgcHJldmlvdXNseSBpbnRlcmFjdGluZ1xuICAgICAgICAgICAgaWYgKGV2ZW50ICYmIGlzRnVuY3Rpb24oZXZlbnQucHJldmVudERlZmF1bHQpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLmRyYWdnaW5nKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzID0gdGhpcy5hY3RpdmVEcm9wcy5yZWN0cyA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMuY2xlYXJUYXJnZXRzKCk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJc0Rvd24gPSB0aGlzLnNuYXBTdGF0dXMubG9ja2VkID0gdGhpcy5kcmFnZ2luZyA9IHRoaXMucmVzaXppbmcgPSB0aGlzLmdlc3R1cmluZyA9IGZhbHNlO1xuICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSB0aGlzLnByZXZFdmVudCA9IG51bGw7XG4gICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeCA9IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeSA9IDA7XG5cbiAgICAgICAgLy8gcmVtb3ZlIHBvaW50ZXJzIGlmIHRoZWlyIElEIGlzbid0IGluIHRoaXMucG9pbnRlcklkc1xuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMucG9pbnRlcnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpbmRleE9mKHRoaXMucG9pbnRlcklkcywgZ2V0UG9pbnRlcklkKHRoaXMucG9pbnRlcnNbaV0pKSA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICB0aGlzLnBvaW50ZXJzLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBpbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIC8vIHJlbW92ZSB0aGlzIGludGVyYWN0aW9uIGlmIGl0J3Mgbm90IHRoZSBvbmx5IG9uZSBvZiBpdCdzIHR5cGVcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbnNbaV0gIT09IHRoaXMgJiYgaW50ZXJhY3Rpb25zW2ldLm1vdXNlID09PSB0aGlzLm1vdXNlKSB7XG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb25zLnNwbGljZShpbmRleE9mKGludGVyYWN0aW9ucywgdGhpcyksIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSxcblxuICAgIGluZXJ0aWFGcmFtZTogZnVuY3Rpb24gKCkge1xuICAgICAgICB2YXIgaW5lcnRpYVN0YXR1cyA9IHRoaXMuaW5lcnRpYVN0YXR1cyxcbiAgICAgICAgICAgIG9wdGlvbnMgPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYSxcbiAgICAgICAgICAgIGxhbWJkYSA9IG9wdGlvbnMucmVzaXN0YW5jZSxcbiAgICAgICAgICAgIHQgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSAvIDEwMDAgLSBpbmVydGlhU3RhdHVzLnQwO1xuXG4gICAgICAgIGlmICh0IDwgaW5lcnRpYVN0YXR1cy50ZSkge1xuXG4gICAgICAgICAgICB2YXIgcHJvZ3Jlc3MgPSAgMSAtIChNYXRoLmV4cCgtbGFtYmRhICogdCkgLSBpbmVydGlhU3RhdHVzLmxhbWJkYV92MCkgLyBpbmVydGlhU3RhdHVzLm9uZV92ZV92MDtcblxuICAgICAgICAgICAgaWYgKGluZXJ0aWFTdGF0dXMubW9kaWZpZWRYZSA9PT0gaW5lcnRpYVN0YXR1cy54ZSAmJiBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUgPT09IGluZXJ0aWFTdGF0dXMueWUpIHtcbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gaW5lcnRpYVN0YXR1cy54ZSAqIHByb2dyZXNzO1xuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3kgPSBpbmVydGlhU3RhdHVzLnllICogcHJvZ3Jlc3M7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB2YXIgcXVhZFBvaW50ID0gZ2V0UXVhZHJhdGljQ3VydmVQb2ludChcbiAgICAgICAgICAgICAgICAgICAgMCwgMCxcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy54ZSwgaW5lcnRpYVN0YXR1cy55ZSxcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFhlLCBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUsXG4gICAgICAgICAgICAgICAgICAgIHByb2dyZXNzKTtcblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBxdWFkUG9pbnQueDtcbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gcXVhZFBvaW50Lnk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucG9pbnRlck1vdmUoaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50LCBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLmkgPSByZXFGcmFtZSh0aGlzLmJvdW5kSW5lcnRpYUZyYW1lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWGU7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllO1xuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMucG9pbnRlckVuZChpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc21vb3RoRW5kRnJhbWU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIGluZXJ0aWFTdGF0dXMgPSB0aGlzLmluZXJ0aWFTdGF0dXMsXG4gICAgICAgICAgICB0ID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLSBpbmVydGlhU3RhdHVzLnQwLFxuICAgICAgICAgICAgZHVyYXRpb24gPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYS5zbW9vdGhFbmREdXJhdGlvbjtcblxuICAgICAgICBpZiAodCA8IGR1cmF0aW9uKSB7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gZWFzZU91dFF1YWQodCwgMCwgaW5lcnRpYVN0YXR1cy54ZSwgZHVyYXRpb24pO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IGVhc2VPdXRRdWFkKHQsIDAsIGluZXJ0aWFTdGF0dXMueWUsIGR1cmF0aW9uKTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyTW92ZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG5cbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuaSA9IHJlcUZyYW1lKHRoaXMuYm91bmRTbW9vdGhFbmRGcmFtZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gaW5lcnRpYVN0YXR1cy54ZTtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3kgPSBpbmVydGlhU3RhdHVzLnllO1xuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc21vb3RoRW5kID0gZmFsc2U7XG5cbiAgICAgICAgICAgIHRoaXMucG9pbnRlckVuZChpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgYWRkUG9pbnRlcjogZnVuY3Rpb24gKHBvaW50ZXIpIHtcbiAgICAgICAgdmFyIGlkID0gZ2V0UG9pbnRlcklkKHBvaW50ZXIpLFxuICAgICAgICAgICAgaW5kZXggPSB0aGlzLm1vdXNlPyAwIDogaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGlkKTtcblxuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICBpbmRleCA9IHRoaXMucG9pbnRlcklkcy5sZW5ndGg7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJZHNbaW5kZXhdID0gaWQ7XG4gICAgICAgIHRoaXMucG9pbnRlcnNbaW5kZXhdID0gcG9pbnRlcjtcblxuICAgICAgICByZXR1cm4gaW5kZXg7XG4gICAgfSxcblxuICAgIHJlbW92ZVBvaW50ZXI6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIHZhciBpZCA9IGdldFBvaW50ZXJJZChwb2ludGVyKSxcbiAgICAgICAgICAgIGluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YodGhpcy5wb2ludGVySWRzLCBpZCk7XG5cbiAgICAgICAgaWYgKGluZGV4ID09PSAtMSkgeyByZXR1cm47IH1cblxuICAgICAgICBpZiAoIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgdGhpcy5wb2ludGVycy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5wb2ludGVySWRzIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB0aGlzLmRvd25UYXJnZXRzLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgIHRoaXMuZG93blRpbWVzICAuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgdGhpcy5ob2xkVGltZXJzIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgIH0sXG5cbiAgICByZWNvcmRQb2ludGVyOiBmdW5jdGlvbiAocG9pbnRlcikge1xuICAgICAgICAvLyBEbyBub3QgdXBkYXRlIHBvaW50ZXJzIHdoaWxlIGluZXJ0aWEgaXMgYWN0aXZlLlxuICAgICAgICAvLyBUaGUgaW5lcnRpYSBzdGFydCBldmVudCBzaG91bGQgYmUgdGhpcy5wb2ludGVyc1swXVxuICAgICAgICBpZiAodGhpcy5pbmVydGlhU3RhdHVzLmFjdGl2ZSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgaW5kZXggPSB0aGlzLm1vdXNlPyAwOiBpbmRleE9mKHRoaXMucG9pbnRlcklkcywgZ2V0UG9pbnRlcklkKHBvaW50ZXIpKTtcblxuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7IHJldHVybjsgfVxuXG4gICAgICAgIHRoaXMucG9pbnRlcnNbaW5kZXhdID0gcG9pbnRlcjtcbiAgICB9LFxuXG4gICAgY29sbGVjdEV2ZW50VGFyZ2V0czogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgZXZlbnRUeXBlKSB7XG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLm1vdXNlPyAwIDogaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGdldFBvaW50ZXJJZChwb2ludGVyKSk7XG5cbiAgICAgICAgLy8gZG8gbm90IGZpcmUgYSB0YXAgZXZlbnQgaWYgdGhlIHBvaW50ZXIgd2FzIG1vdmVkIGJlZm9yZSBiZWluZyBsaWZ0ZWRcbiAgICAgICAgaWYgKGV2ZW50VHlwZSA9PT0gJ3RhcCcgJiYgKHRoaXMucG9pbnRlcldhc01vdmVkXG4gICAgICAgICAgICAgICAgLy8gb3IgaWYgdGhlIHBvaW50ZXJ1cCB0YXJnZXQgaXMgZGlmZmVyZW50IHRvIHRoZSBwb2ludGVyZG93biB0YXJnZXRcbiAgICAgICAgICAgIHx8ICEodGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdICYmIHRoaXMuZG93blRhcmdldHNbcG9pbnRlckluZGV4XSA9PT0gZXZlbnRUYXJnZXQpKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHRhcmdldHMgPSBbXSxcbiAgICAgICAgICAgIGVsZW1lbnRzID0gW10sXG4gICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgZnVuY3Rpb24gY29sbGVjdFNlbGVjdG9ycyAoaW50ZXJhY3RhYmxlLCBzZWxlY3RvciwgY29udGV4dCkge1xuICAgICAgICAgICAgdmFyIGVscyA9IGllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgID8gY29udGV4dC5xdWVyeVNlbGVjdG9yQWxsKHNlbGVjdG9yKVxuICAgICAgICAgICAgICAgIDogdW5kZWZpbmVkO1xuXG4gICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlLl9pRXZlbnRzW2V2ZW50VHlwZV1cbiAgICAgICAgICAgICAgICAmJiBpc0VsZW1lbnQoZWxlbWVudClcbiAgICAgICAgICAgICAgICAmJiBpbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBlbGVtZW50KVxuICAgICAgICAgICAgICAgICYmICF0ZXN0SWdub3JlKGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgdGVzdEFsbG93KGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgbWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIHNlbGVjdG9yLCBlbHMpKSB7XG5cbiAgICAgICAgICAgICAgICB0YXJnZXRzLnB1c2goaW50ZXJhY3RhYmxlKTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgd2hpbGUgKGVsZW1lbnQpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdC5pc1NldChlbGVtZW50KSAmJiBpbnRlcmFjdChlbGVtZW50KS5faUV2ZW50c1tldmVudFR5cGVdKSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0cy5wdXNoKGludGVyYWN0KGVsZW1lbnQpKTtcbiAgICAgICAgICAgICAgICBlbGVtZW50cy5wdXNoKGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpbnRlcmFjdGFibGVzLmZvckVhY2hTZWxlY3Rvcihjb2xsZWN0U2VsZWN0b3JzKTtcblxuICAgICAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBjcmVhdGUgdGhlIHRhcCBldmVudCBldmVuIGlmIHRoZXJlIGFyZSBubyBsaXN0ZW5lcnMgc28gdGhhdFxuICAgICAgICAvLyBkb3VibGV0YXAgY2FuIHN0aWxsIGJlIGNyZWF0ZWQgYW5kIGZpcmVkXG4gICAgICAgIGlmICh0YXJnZXRzLmxlbmd0aCB8fCBldmVudFR5cGUgPT09ICd0YXAnKSB7XG4gICAgICAgICAgICB0aGlzLmZpcmVQb2ludGVycyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIHRhcmdldHMsIGVsZW1lbnRzLCBldmVudFR5cGUpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIGZpcmVQb2ludGVyczogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgdGFyZ2V0cywgZWxlbWVudHMsIGV2ZW50VHlwZSkge1xuICAgICAgICB2YXIgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IGluZGV4T2YoZ2V0UG9pbnRlcklkKHBvaW50ZXIpKSxcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudCA9IHt9LFxuICAgICAgICAgICAgaSxcbiAgICAgICAgLy8gZm9yIHRhcCBldmVudHNcbiAgICAgICAgICAgIGludGVydmFsLCBjcmVhdGVOZXdEb3VibGVUYXA7XG5cbiAgICAgICAgLy8gaWYgaXQncyBhIGRvdWJsZXRhcCB0aGVuIHRoZSBldmVudCBwcm9wZXJ0aWVzIHdvdWxkIGhhdmUgYmVlblxuICAgICAgICAvLyBjb3BpZWQgZnJvbSB0aGUgdGFwIGV2ZW50IGFuZCBwcm92aWRlZCBhcyB0aGUgcG9pbnRlciBhcmd1bWVudFxuICAgICAgICBpZiAoZXZlbnRUeXBlID09PSAnZG91YmxldGFwJykge1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50ID0gcG9pbnRlcjtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGV4dGVuZChwb2ludGVyRXZlbnQsIGV2ZW50KTtcbiAgICAgICAgICAgIGlmIChldmVudCAhPT0gcG9pbnRlcikge1xuICAgICAgICAgICAgICAgIGV4dGVuZChwb2ludGVyRXZlbnQsIHBvaW50ZXIpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQucHJldmVudERlZmF1bHQgICAgICAgICAgID0gcHJldmVudE9yaWdpbmFsRGVmYXVsdDtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5zdG9wUHJvcGFnYXRpb24gICAgICAgICAgPSBJbnRlcmFjdEV2ZW50LnByb3RvdHlwZS5zdG9wUHJvcGFnYXRpb247XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uID0gSW50ZXJhY3RFdmVudC5wcm90b3R5cGUuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LmludGVyYWN0aW9uICAgICAgICAgICAgICA9IHRoaXM7XG5cbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC50aW1lU3RhbXAgICAgID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQub3JpZ2luYWxFdmVudCA9IGV2ZW50O1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnR5cGUgICAgICAgICAgPSBldmVudFR5cGU7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQucG9pbnRlcklkICAgICA9IGdldFBvaW50ZXJJZChwb2ludGVyKTtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5wb2ludGVyVHlwZSAgID0gdGhpcy5tb3VzZT8gJ21vdXNlJyA6ICFzdXBwb3J0c1BvaW50ZXJFdmVudD8gJ3RvdWNoJ1xuICAgICAgICAgICAgICAgIDogaXNTdHJpbmcocG9pbnRlci5wb2ludGVyVHlwZSlcbiAgICAgICAgICAgICAgICA/IHBvaW50ZXIucG9pbnRlclR5cGVcbiAgICAgICAgICAgICAgICA6IFssLCd0b3VjaCcsICdwZW4nLCAnbW91c2UnXVtwb2ludGVyLnBvaW50ZXJUeXBlXTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChldmVudFR5cGUgPT09ICd0YXAnKSB7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZHQgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XTtcblxuICAgICAgICAgICAgaW50ZXJ2YWwgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy50YXBUaW1lO1xuICAgICAgICAgICAgY3JlYXRlTmV3RG91YmxlVGFwID0gISEodGhpcy5wcmV2VGFwICYmIHRoaXMucHJldlRhcC50eXBlICE9PSAnZG91YmxldGFwJ1xuICAgICAgICAgICAgJiYgdGhpcy5wcmV2VGFwLnRhcmdldCA9PT0gcG9pbnRlckV2ZW50LnRhcmdldFxuICAgICAgICAgICAgJiYgaW50ZXJ2YWwgPCA1MDApO1xuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZG91YmxlID0gY3JlYXRlTmV3RG91YmxlVGFwO1xuXG4gICAgICAgICAgICB0aGlzLnRhcFRpbWUgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wO1xuICAgICAgICB9XG5cbiAgICAgICAgZm9yIChpID0gMDsgaSA8IHRhcmdldHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5jdXJyZW50VGFyZ2V0ID0gZWxlbWVudHNbaV07XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuaW50ZXJhY3RhYmxlID0gdGFyZ2V0c1tpXTtcbiAgICAgICAgICAgIHRhcmdldHNbaV0uZmlyZShwb2ludGVyRXZlbnQpO1xuXG4gICAgICAgICAgICBpZiAocG9pbnRlckV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZFxuICAgICAgICAgICAgICAgIHx8KHBvaW50ZXJFdmVudC5wcm9wYWdhdGlvblN0b3BwZWQgJiYgZWxlbWVudHNbaSArIDFdICE9PSBwb2ludGVyRXZlbnQuY3VycmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjcmVhdGVOZXdEb3VibGVUYXApIHtcbiAgICAgICAgICAgIHZhciBkb3VibGVUYXAgPSB7fTtcblxuICAgICAgICAgICAgZXh0ZW5kKGRvdWJsZVRhcCwgcG9pbnRlckV2ZW50KTtcblxuICAgICAgICAgICAgZG91YmxlVGFwLmR0ICAgPSBpbnRlcnZhbDtcbiAgICAgICAgICAgIGRvdWJsZVRhcC50eXBlID0gJ2RvdWJsZXRhcCc7XG5cbiAgICAgICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhkb3VibGVUYXAsIGV2ZW50LCBldmVudFRhcmdldCwgJ2RvdWJsZXRhcCcpO1xuXG4gICAgICAgICAgICB0aGlzLnByZXZUYXAgPSBkb3VibGVUYXA7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZXZlbnRUeXBlID09PSAndGFwJykge1xuICAgICAgICAgICAgdGhpcy5wcmV2VGFwID0gcG9pbnRlckV2ZW50O1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIHZhbGlkYXRlU2VsZWN0b3I6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cykge1xuICAgICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gbWF0Y2hlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgdmFyIG1hdGNoID0gbWF0Y2hlc1tpXSxcbiAgICAgICAgICAgICAgICBtYXRjaEVsZW1lbnQgPSBtYXRjaEVsZW1lbnRzW2ldLFxuICAgICAgICAgICAgICAgIGFjdGlvbiA9IHZhbGlkYXRlQWN0aW9uKG1hdGNoLmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgbWF0Y2hFbGVtZW50KSwgbWF0Y2gpO1xuXG4gICAgICAgICAgICBpZiAoYWN0aW9uICYmIHdpdGhpbkludGVyYWN0aW9uTGltaXQobWF0Y2gsIG1hdGNoRWxlbWVudCwgYWN0aW9uKSkge1xuICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gbWF0Y2g7XG4gICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gbWF0Y2hFbGVtZW50O1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIGFjdGlvbjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzZXRTbmFwcGluZzogZnVuY3Rpb24gKHBhZ2VDb29yZHMsIHN0YXR1cykge1xuICAgICAgICB2YXIgc25hcCA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgdGFyZ2V0cyA9IFtdLFxuICAgICAgICAgICAgdGFyZ2V0LFxuICAgICAgICAgICAgcGFnZSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMuc25hcFN0YXR1cztcblxuICAgICAgICBpZiAoc3RhdHVzLnVzZVN0YXR1c1hZKSB7XG4gICAgICAgICAgICBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZhciBvcmlnaW4gPSBnZXRPcmlnaW5YWSh0aGlzLnRhcmdldCwgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgcGFnZSA9IGV4dGVuZCh7fSwgcGFnZUNvb3Jkcyk7XG5cbiAgICAgICAgICAgIHBhZ2UueCAtPSBvcmlnaW4ueDtcbiAgICAgICAgICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5yZWFsWCA9IHBhZ2UueDtcbiAgICAgICAgc3RhdHVzLnJlYWxZID0gcGFnZS55O1xuXG4gICAgICAgIHBhZ2UueCA9IHBhZ2UueCAtIHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55ID0gcGFnZS55IC0gdGhpcy5pbmVydGlhU3RhdHVzLnJlc3VtZUR5O1xuXG4gICAgICAgIHZhciBsZW4gPSBzbmFwLnRhcmdldHM/IHNuYXAudGFyZ2V0cy5sZW5ndGggOiAwO1xuXG4gICAgICAgIGZvciAodmFyIHJlbEluZGV4ID0gMDsgcmVsSW5kZXggPCB0aGlzLnNuYXBPZmZzZXRzLmxlbmd0aDsgcmVsSW5kZXgrKykge1xuICAgICAgICAgICAgdmFyIHJlbGF0aXZlID0ge1xuICAgICAgICAgICAgICAgIHg6IHBhZ2UueCAtIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngsXG4gICAgICAgICAgICAgICAgeTogcGFnZS55IC0gdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24oc25hcC50YXJnZXRzW2ldKSkge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV0ocmVsYXRpdmUueCwgcmVsYXRpdmUueSwgdGhpcyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV07XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKCF0YXJnZXQpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaCh7XG4gICAgICAgICAgICAgICAgICAgIHg6IGlzTnVtYmVyKHRhcmdldC54KSA/ICh0YXJnZXQueCArIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngpIDogcmVsYXRpdmUueCxcbiAgICAgICAgICAgICAgICAgICAgeTogaXNOdW1iZXIodGFyZ2V0LnkpID8gKHRhcmdldC55ICsgdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueSkgOiByZWxhdGl2ZS55LFxuXG4gICAgICAgICAgICAgICAgICAgIHJhbmdlOiBpc051bWJlcih0YXJnZXQucmFuZ2UpPyB0YXJnZXQucmFuZ2U6IHNuYXAucmFuZ2VcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBjbG9zZXN0ID0ge1xuICAgICAgICAgICAgdGFyZ2V0OiBudWxsLFxuICAgICAgICAgICAgaW5SYW5nZTogZmFsc2UsXG4gICAgICAgICAgICBkaXN0YW5jZTogMCxcbiAgICAgICAgICAgIHJhbmdlOiAwLFxuICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICBkeTogMFxuICAgICAgICB9O1xuXG4gICAgICAgIGZvciAoaSA9IDAsIGxlbiA9IHRhcmdldHMubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIHRhcmdldCA9IHRhcmdldHNbaV07XG5cbiAgICAgICAgICAgIHZhciByYW5nZSA9IHRhcmdldC5yYW5nZSxcbiAgICAgICAgICAgICAgICBkeCA9IHRhcmdldC54IC0gcGFnZS54LFxuICAgICAgICAgICAgICAgIGR5ID0gdGFyZ2V0LnkgLSBwYWdlLnksXG4gICAgICAgICAgICAgICAgZGlzdGFuY2UgPSBoeXBvdChkeCwgZHkpLFxuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBkaXN0YW5jZSA8PSByYW5nZTtcblxuICAgICAgICAgICAgLy8gSW5maW5pdGUgdGFyZ2V0cyBjb3VudCBhcyBiZWluZyBvdXQgb2YgcmFuZ2VcbiAgICAgICAgICAgIC8vIGNvbXBhcmVkIHRvIG5vbiBpbmZpbml0ZSBvbmVzIHRoYXQgYXJlIGluIHJhbmdlXG4gICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5ICYmIGNsb3Nlc3QuaW5SYW5nZSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSkge1xuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBmYWxzZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFjbG9zZXN0LnRhcmdldCB8fCAoaW5SYW5nZVxuICAgICAgICAgICAgICAgICAgICAvLyBpcyB0aGUgY2xvc2VzdCB0YXJnZXQgaW4gcmFuZ2U/XG4gICAgICAgICAgICAgICAgICAgID8gKGNsb3Nlc3QuaW5SYW5nZSAmJiByYW5nZSAhPT0gSW5maW5pdHlcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhlIHBvaW50ZXIgaXMgcmVsYXRpdmVseSBkZWVwZXIgaW4gdGhpcyB0YXJnZXRcbiAgICAgICAgICAgICAgICAgICAgPyBkaXN0YW5jZSAvIHJhbmdlIDwgY2xvc2VzdC5kaXN0YW5jZSAvIGNsb3Nlc3QucmFuZ2VcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhpcyB0YXJnZXQgaGFzIEluZmluaXRlIHJhbmdlIGFuZCB0aGUgY2xvc2VzdCBkb2Vzbid0XG4gICAgICAgICAgICAgICAgICAgIDogKHJhbmdlID09PSBJbmZpbml0eSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSlcbiAgICAgICAgICAgICAgICAgICAgLy8gT1IgdGhpcyB0YXJnZXQgaXMgY2xvc2VyIHRoYXQgdGhlIHByZXZpb3VzIGNsb3Nlc3RcbiAgICAgICAgICAgICAgICB8fCBkaXN0YW5jZSA8IGNsb3Nlc3QuZGlzdGFuY2UpXG4gICAgICAgICAgICAgICAgICAgIC8vIFRoZSBvdGhlciBpcyBub3QgaW4gcmFuZ2UgYW5kIHRoZSBwb2ludGVyIGlzIGNsb3NlciB0byB0aGlzIHRhcmdldFxuICAgICAgICAgICAgICAgICAgICA6ICghY2xvc2VzdC5pblJhbmdlICYmIGRpc3RhbmNlIDwgY2xvc2VzdC5kaXN0YW5jZSkpKSB7XG5cbiAgICAgICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5KSB7XG4gICAgICAgICAgICAgICAgICAgIGluUmFuZ2UgPSB0cnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNsb3Nlc3QudGFyZ2V0ID0gdGFyZ2V0O1xuICAgICAgICAgICAgICAgIGNsb3Nlc3QuZGlzdGFuY2UgPSBkaXN0YW5jZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5pblJhbmdlID0gaW5SYW5nZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LmR4ID0gZHg7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5keSA9IGR5O1xuXG4gICAgICAgICAgICAgICAgc3RhdHVzLnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc25hcENoYW5nZWQ7XG5cbiAgICAgICAgaWYgKGNsb3Nlc3QudGFyZ2V0KSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IChzdGF0dXMuc25hcHBlZFggIT09IGNsb3Nlc3QudGFyZ2V0LnggfHwgc3RhdHVzLnNuYXBwZWRZICE9PSBjbG9zZXN0LnRhcmdldC55KTtcblxuICAgICAgICAgICAgc3RhdHVzLnNuYXBwZWRYID0gY2xvc2VzdC50YXJnZXQueDtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IGNsb3Nlc3QudGFyZ2V0Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IHRydWU7XG5cbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWCA9IE5hTjtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IE5hTjtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IGNsb3Nlc3QuZHg7XG4gICAgICAgIHN0YXR1cy5keSA9IGNsb3Nlc3QuZHk7XG5cbiAgICAgICAgc3RhdHVzLmNoYW5nZWQgPSAoc25hcENoYW5nZWQgfHwgKGNsb3Nlc3QuaW5SYW5nZSAmJiAhc3RhdHVzLmxvY2tlZCkpO1xuICAgICAgICBzdGF0dXMubG9ja2VkID0gY2xvc2VzdC5pblJhbmdlO1xuXG4gICAgICAgIHJldHVybiBzdGF0dXM7XG4gICAgfSxcblxuICAgIHNldFJlc3RyaWN0aW9uOiBmdW5jdGlvbiAocGFnZUNvb3Jkcywgc3RhdHVzKSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIHJlc3RyaWN0ID0gdGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QsXG4gICAgICAgICAgICByZXN0cmljdGlvbiA9IHJlc3RyaWN0ICYmIHJlc3RyaWN0LnJlc3RyaWN0aW9uLFxuICAgICAgICAgICAgcGFnZTtcblxuICAgICAgICBpZiAoIXJlc3RyaWN0aW9uKSB7XG4gICAgICAgICAgICByZXR1cm4gc3RhdHVzO1xuICAgICAgICB9XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMucmVzdHJpY3RTdGF0dXM7XG5cbiAgICAgICAgcGFnZSA9IHN0YXR1cy51c2VTdGF0dXNYWVxuICAgICAgICAgICAgPyBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfVxuICAgICAgICAgICAgOiBwYWdlID0gZXh0ZW5kKHt9LCBwYWdlQ29vcmRzKTtcblxuICAgICAgICBpZiAoc3RhdHVzLnNuYXAgJiYgc3RhdHVzLnNuYXAubG9ja2VkKSB7XG4gICAgICAgICAgICBwYWdlLnggKz0gc3RhdHVzLnNuYXAuZHggfHwgMDtcbiAgICAgICAgICAgIHBhZ2UueSArPSBzdGF0dXMuc25hcC5keSB8fCAwO1xuICAgICAgICB9XG5cbiAgICAgICAgcGFnZS54IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeTtcblxuICAgICAgICBzdGF0dXMuZHggPSAwO1xuICAgICAgICBzdGF0dXMuZHkgPSAwO1xuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlO1xuXG4gICAgICAgIHZhciByZWN0LCByZXN0cmljdGVkWCwgcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgaWYgKGlzU3RyaW5nKHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgaWYgKHJlc3RyaWN0aW9uID09PSAncGFyZW50Jykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gcGFyZW50RWxlbWVudCh0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAocmVzdHJpY3Rpb24gPT09ICdzZWxmJykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gdGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gY2xvc2VzdCh0aGlzLmVsZW1lbnQsIHJlc3RyaWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFyZXN0cmljdGlvbikgeyByZXR1cm4gc3RhdHVzOyB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNGdW5jdGlvbihyZXN0cmljdGlvbikpIHtcbiAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gcmVzdHJpY3Rpb24ocGFnZS54LCBwYWdlLnksIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNFbGVtZW50KHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgcmVzdHJpY3Rpb24gPSBnZXRFbGVtZW50UmVjdChyZXN0cmljdGlvbik7XG4gICAgICAgIH1cblxuICAgICAgICByZWN0ID0gcmVzdHJpY3Rpb247XG5cbiAgICAgICAgaWYgKCFyZXN0cmljdGlvbikge1xuICAgICAgICAgICAgcmVzdHJpY3RlZFggPSBwYWdlLng7XG4gICAgICAgICAgICByZXN0cmljdGVkWSA9IHBhZ2UueTtcbiAgICAgICAgfVxuICAgICAgICAvLyBvYmplY3QgaXMgYXNzdW1lZCB0byBoYXZlXG4gICAgICAgIC8vIHgsIHksIHdpZHRoLCBoZWlnaHQgb3JcbiAgICAgICAgLy8gbGVmdCwgdG9wLCByaWdodCwgYm90dG9tXG4gICAgICAgIGVsc2UgaWYgKCd4JyBpbiByZXN0cmljdGlvbiAmJiAneScgaW4gcmVzdHJpY3Rpb24pIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC54ICsgcmVjdC53aWR0aCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC54ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC55ICsgcmVjdC5oZWlnaHQgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC55ICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5yaWdodCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC5sZWZ0ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5ib3R0b20gLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC50b3AgICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IHJlc3RyaWN0ZWRYIC0gcGFnZS54O1xuICAgICAgICBzdGF0dXMuZHkgPSByZXN0cmljdGVkWSAtIHBhZ2UueTtcblxuICAgICAgICBzdGF0dXMuY2hhbmdlZCA9IHN0YXR1cy5yZXN0cmljdGVkWCAhPT0gcmVzdHJpY3RlZFggfHwgc3RhdHVzLnJlc3RyaWN0ZWRZICE9PSByZXN0cmljdGVkWTtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWQgPSAhIShzdGF0dXMuZHggfHwgc3RhdHVzLmR5KTtcblxuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZFggPSByZXN0cmljdGVkWDtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWRZID0gcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgcmV0dXJuIHN0YXR1cztcbiAgICB9LFxuXG4gICAgY2hlY2tBbmRQcmV2ZW50RGVmYXVsdDogZnVuY3Rpb24gKGV2ZW50LCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKCEoaW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlIHx8IHRoaXMudGFyZ2V0KSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICAgICAgcHJldmVudCA9IG9wdGlvbnMucHJldmVudERlZmF1bHQ7XG5cbiAgICAgICAgaWYgKHByZXZlbnQgPT09ICdhdXRvJyAmJiBlbGVtZW50ICYmICEvXihpbnB1dHxzZWxlY3R8dGV4dGFyZWEpJC9pLnRlc3QoZXZlbnQudGFyZ2V0Lm5vZGVOYW1lKSkge1xuICAgICAgICAgICAgLy8gZG8gbm90IHByZXZlbnREZWZhdWx0IG9uIHBvaW50ZXJkb3duIGlmIHRoZSBwcmVwYXJlZCBhY3Rpb24gaXMgYSBkcmFnXG4gICAgICAgICAgICAvLyBhbmQgZHJhZ2dpbmcgY2FuIG9ubHkgc3RhcnQgZnJvbSBhIGNlcnRhaW4gZGlyZWN0aW9uIC0gdGhpcyBhbGxvd3NcbiAgICAgICAgICAgIC8vIGEgdG91Y2ggdG8gcGFuIHRoZSB2aWV3cG9ydCBpZiBhIGRyYWcgaXNuJ3QgaW4gdGhlIHJpZ2h0IGRpcmVjdGlvblxuICAgICAgICAgICAgaWYgKC9kb3dufHN0YXJ0L2kudGVzdChldmVudC50eXBlKVxuICAgICAgICAgICAgICAgICYmIHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnICYmIG9wdGlvbnMuZHJhZy5heGlzICE9PSAneHknKSB7XG5cbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHdpdGggbWFudWFsU3RhcnQsIG9ubHkgcHJldmVudERlZmF1bHQgd2hpbGUgaW50ZXJhY3RpbmdcbiAgICAgICAgICAgIGlmIChvcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0gJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgJiYgIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChwcmV2ZW50ID09PSAnYWx3YXlzJykge1xuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBjYWxjSW5lcnRpYTogZnVuY3Rpb24gKHN0YXR1cykge1xuICAgICAgICB2YXIgaW5lcnRpYU9wdGlvbnMgPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYSxcbiAgICAgICAgICAgIGxhbWJkYSA9IGluZXJ0aWFPcHRpb25zLnJlc2lzdGFuY2UsXG4gICAgICAgICAgICBpbmVydGlhRHVyID0gLU1hdGgubG9nKGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkIC8gc3RhdHVzLnYwKSAvIGxhbWJkYTtcblxuICAgICAgICBzdGF0dXMueDAgPSB0aGlzLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgc3RhdHVzLnkwID0gdGhpcy5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIHN0YXR1cy50MCA9IHN0YXR1cy5zdGFydEV2ZW50LnRpbWVTdGFtcCAvIDEwMDA7XG4gICAgICAgIHN0YXR1cy5zeCA9IHN0YXR1cy5zeSA9IDA7XG5cbiAgICAgICAgc3RhdHVzLm1vZGlmaWVkWGUgPSBzdGF0dXMueGUgPSAoc3RhdHVzLnZ4MCAtIGluZXJ0aWFEdXIpIC8gbGFtYmRhO1xuICAgICAgICBzdGF0dXMubW9kaWZpZWRZZSA9IHN0YXR1cy55ZSA9IChzdGF0dXMudnkwIC0gaW5lcnRpYUR1cikgLyBsYW1iZGE7XG4gICAgICAgIHN0YXR1cy50ZSA9IGluZXJ0aWFEdXI7XG5cbiAgICAgICAgc3RhdHVzLmxhbWJkYV92MCA9IGxhbWJkYSAvIHN0YXR1cy52MDtcbiAgICAgICAgc3RhdHVzLm9uZV92ZV92MCA9IDEgLSBpbmVydGlhT3B0aW9ucy5lbmRTcGVlZCAvIHN0YXR1cy52MDtcbiAgICB9LFxuXG4gICAgYXV0b1Njcm9sbE1vdmU6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIGlmICghKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgJiYgY2hlY2tBdXRvU2Nyb2xsKHRoaXMudGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUpIHtcbiAgICAgICAgICAgIGF1dG9TY3JvbGwueCA9IGF1dG9TY3JvbGwueSA9IDA7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdG9wLFxuICAgICAgICAgICAgcmlnaHQsXG4gICAgICAgICAgICBib3R0b20sXG4gICAgICAgICAgICBsZWZ0LFxuICAgICAgICAgICAgb3B0aW9ucyA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5hdXRvU2Nyb2xsLFxuICAgICAgICAgICAgY29udGFpbmVyID0gb3B0aW9ucy5jb250YWluZXIgfHwgZ2V0V2luZG93KHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKGlzV2luZG93KGNvbnRhaW5lcikpIHtcbiAgICAgICAgICAgIGxlZnQgICA9IHBvaW50ZXIuY2xpZW50WCA8IGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICByaWdodCAgPSBwb2ludGVyLmNsaWVudFggPiBjb250YWluZXIuaW5uZXJXaWR0aCAgLSBhdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIGJvdHRvbSA9IHBvaW50ZXIuY2xpZW50WSA+IGNvbnRhaW5lci5pbm5lckhlaWdodCAtIGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdmFyIHJlY3QgPSBnZXRFbGVtZW50UmVjdChjb250YWluZXIpO1xuXG4gICAgICAgICAgICBsZWZ0ICAgPSBwb2ludGVyLmNsaWVudFggPCByZWN0LmxlZnQgICArIGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgcmVjdC50b3AgICAgKyBhdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIHJpZ2h0ICA9IHBvaW50ZXIuY2xpZW50WCA+IHJlY3QucmlnaHQgIC0gYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICBib3R0b20gPSBwb2ludGVyLmNsaWVudFkgPiByZWN0LmJvdHRvbSAtIGF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG5cbiAgICAgICAgYXV0b1Njcm9sbC54ID0gKHJpZ2h0ID8gMTogbGVmdD8gLTE6IDApO1xuICAgICAgICBhdXRvU2Nyb2xsLnkgPSAoYm90dG9tPyAxOiAgdG9wPyAtMTogMCk7XG5cbiAgICAgICAgaWYgKCFhdXRvU2Nyb2xsLmlzU2Nyb2xsaW5nKSB7XG4gICAgICAgICAgICAvLyBzZXQgdGhlIGF1dG9TY3JvbGwgcHJvcGVydGllcyB0byB0aG9zZSBvZiB0aGUgdGFyZ2V0XG4gICAgICAgICAgICBhdXRvU2Nyb2xsLm1hcmdpbiA9IG9wdGlvbnMubWFyZ2luO1xuICAgICAgICAgICAgYXV0b1Njcm9sbC5zcGVlZCAgPSBvcHRpb25zLnNwZWVkO1xuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsLnN0YXJ0KHRoaXMpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIF91cGRhdGVFdmVudFRhcmdldHM6IGZ1bmN0aW9uICh0YXJnZXQsIGN1cnJlbnRUYXJnZXQpIHtcbiAgICAgICAgdGhpcy5fZXZlbnRUYXJnZXQgICAgPSB0YXJnZXQ7XG4gICAgICAgIHRoaXMuX2N1ckV2ZW50VGFyZ2V0ID0gY3VycmVudFRhcmdldDtcbiAgICB9XG5cbn07XG5cbmZ1bmN0aW9uIGdldEludGVyYWN0aW9uRnJvbVBvaW50ZXIgKHBvaW50ZXIsIGV2ZW50VHlwZSwgZXZlbnRUYXJnZXQpIHtcbiAgICB2YXIgaSA9IDAsIGxlbiA9IGludGVyYWN0aW9ucy5sZW5ndGgsXG4gICAgICAgIG1vdXNlRXZlbnQgPSAoL21vdXNlL2kudGVzdChwb2ludGVyLnBvaW50ZXJUeXBlIHx8IGV2ZW50VHlwZSlcbiAgICAgICAgICAgIC8vIE1TUG9pbnRlckV2ZW50Lk1TUE9JTlRFUl9UWVBFX01PVVNFXG4gICAgICAgIHx8IHBvaW50ZXIucG9pbnRlclR5cGUgPT09IDQpLFxuICAgICAgICBpbnRlcmFjdGlvbjtcblxuICAgIHZhciBpZCA9IGdldFBvaW50ZXJJZChwb2ludGVyKTtcblxuICAgIC8vIHRyeSB0byByZXN1bWUgaW5lcnRpYSB3aXRoIGEgbmV3IHBvaW50ZXJcbiAgICBpZiAoL2Rvd258c3RhcnQvaS50ZXN0KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uc1tpXTtcblxuICAgICAgICAgICAgdmFyIGVsZW1lbnQgPSBldmVudFRhcmdldDtcblxuICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuYWN0aXZlICYmIGludGVyYWN0aW9uLnRhcmdldC5vcHRpb25zW2ludGVyYWN0aW9uLnByZXBhcmVkLm5hbWVdLmluZXJ0aWEuYWxsb3dSZXN1bWVcbiAgICAgICAgICAgICAgICAmJiAoaW50ZXJhY3Rpb24ubW91c2UgPT09IG1vdXNlRXZlbnQpKSB7XG4gICAgICAgICAgICAgICAgd2hpbGUgKGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gaWYgdGhlIGVsZW1lbnQgaXMgdGhlIGludGVyYWN0aW9uIGVsZW1lbnRcbiAgICAgICAgICAgICAgICAgICAgaWYgKGVsZW1lbnQgPT09IGludGVyYWN0aW9uLmVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgaW50ZXJhY3Rpb24ncyBwb2ludGVyXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3Rpb24ucG9pbnRlcnNbMF0pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5yZW1vdmVQb2ludGVyKGludGVyYWN0aW9uLnBvaW50ZXJzWzBdKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmFkZFBvaW50ZXIocG9pbnRlcik7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbjtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICBlbGVtZW50ID0gcGFyZW50RWxlbWVudChlbGVtZW50KTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICAvLyBpZiBpdCdzIGEgbW91c2UgaW50ZXJhY3Rpb25cbiAgICBpZiAobW91c2VFdmVudCB8fCAhKHN1cHBvcnRzVG91Y2ggfHwgc3VwcG9ydHNQb2ludGVyRXZlbnQpKSB7XG5cbiAgICAgICAgLy8gZmluZCBhIG1vdXNlIGludGVyYWN0aW9uIHRoYXQncyBub3QgaW4gaW5lcnRpYSBwaGFzZVxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbnNbaV0ubW91c2UgJiYgIWludGVyYWN0aW9uc1tpXS5pbmVydGlhU3RhdHVzLmFjdGl2ZSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbnNbaV07XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBmaW5kIGFueSBpbnRlcmFjdGlvbiBzcGVjaWZpY2FsbHkgZm9yIG1vdXNlLlxuICAgICAgICAvLyBpZiB0aGUgZXZlbnRUeXBlIGlzIGEgbW91c2Vkb3duLCBhbmQgaW5lcnRpYSBpcyBhY3RpdmVcbiAgICAgICAgLy8gaWdub3JlIHRoZSBpbnRlcmFjdGlvblxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbnNbaV0ubW91c2UgJiYgISgvZG93bi8udGVzdChldmVudFR5cGUpICYmIGludGVyYWN0aW9uc1tpXS5pbmVydGlhU3RhdHVzLmFjdGl2ZSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Rpb247XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBjcmVhdGUgYSBuZXcgaW50ZXJhY3Rpb24gZm9yIG1vdXNlXG4gICAgICAgIGludGVyYWN0aW9uID0gbmV3IEludGVyYWN0aW9uKCk7XG4gICAgICAgIGludGVyYWN0aW9uLm1vdXNlID0gdHJ1ZTtcblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Rpb247XG4gICAgfVxuXG4gICAgLy8gZ2V0IGludGVyYWN0aW9uIHRoYXQgaGFzIHRoaXMgcG9pbnRlclxuICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBpZiAoY29udGFpbnMoaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJJZHMsIGlkKSkge1xuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uc1tpXTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIC8vIGF0IHRoaXMgc3RhZ2UsIGEgcG9pbnRlclVwIHNob3VsZCBub3QgcmV0dXJuIGFuIGludGVyYWN0aW9uXG4gICAgaWYgKC91cHxlbmR8b3V0L2kudGVzdChldmVudFR5cGUpKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIC8vIGdldCBmaXJzdCBpZGxlIGludGVyYWN0aW9uXG4gICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIGludGVyYWN0aW9uID0gaW50ZXJhY3Rpb25zW2ldO1xuXG4gICAgICAgIGlmICgoIWludGVyYWN0aW9uLnByZXBhcmVkLm5hbWUgfHwgKGludGVyYWN0aW9uLnRhcmdldC5vcHRpb25zLmdlc3R1cmUuZW5hYmxlZCkpXG4gICAgICAgICAgICAmJiAhaW50ZXJhY3Rpb24uaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgJiYgISghbW91c2VFdmVudCAmJiBpbnRlcmFjdGlvbi5tb3VzZSkpIHtcblxuICAgICAgICAgICAgaW50ZXJhY3Rpb24uYWRkUG9pbnRlcihwb2ludGVyKTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIG5ldyBJbnRlcmFjdGlvbigpO1xufVxuXG5mdW5jdGlvbiBkb09uSW50ZXJhY3Rpb25zIChtZXRob2QpIHtcbiAgICByZXR1cm4gKGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgaW50ZXJhY3Rpb24sXG4gICAgICAgICAgICBldmVudFRhcmdldCA9IGdldEFjdHVhbEVsZW1lbnQoZXZlbnQucGF0aFxuICAgICAgICAgICAgICAgID8gZXZlbnQucGF0aFswXVxuICAgICAgICAgICAgICAgIDogZXZlbnQudGFyZ2V0KSxcbiAgICAgICAgICAgIGN1ckV2ZW50VGFyZ2V0ID0gZ2V0QWN0dWFsRWxlbWVudChldmVudC5jdXJyZW50VGFyZ2V0KSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgaWYgKHN1cHBvcnRzVG91Y2ggJiYgL3RvdWNoLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICBwcmV2VG91Y2hUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG5cbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBldmVudC5jaGFuZ2VkVG91Y2hlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHZhciBwb2ludGVyID0gZXZlbnQuY2hhbmdlZFRvdWNoZXNbaV07XG5cbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiA9IGdldEludGVyYWN0aW9uRnJvbVBvaW50ZXIocG9pbnRlciwgZXZlbnQudHlwZSwgZXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKCFpbnRlcmFjdGlvbikgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uX3VwZGF0ZUV2ZW50VGFyZ2V0cyhldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb25bbWV0aG9kXShwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGlmICghc3VwcG9ydHNQb2ludGVyRXZlbnQgJiYgL21vdXNlLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICAgICAgLy8gaWdub3JlIG1vdXNlIGV2ZW50cyB3aGlsZSB0b3VjaCBpbnRlcmFjdGlvbnMgYXJlIGFjdGl2ZVxuICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBpbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgaWYgKCFpbnRlcmFjdGlvbnNbaV0ubW91c2UgJiYgaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJJc0Rvd24pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIC8vIHRyeSB0byBpZ25vcmUgbW91c2UgZXZlbnRzIHRoYXQgYXJlIHNpbXVsYXRlZCBieSB0aGUgYnJvd3NlclxuICAgICAgICAgICAgICAgIC8vIGFmdGVyIGEgdG91Y2ggZXZlbnRcbiAgICAgICAgICAgICAgICBpZiAobmV3IERhdGUoKS5nZXRUaW1lKCkgLSBwcmV2VG91Y2hUaW1lIDwgNTAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGludGVyYWN0aW9uID0gZ2V0SW50ZXJhY3Rpb25Gcm9tUG9pbnRlcihldmVudCwgZXZlbnQudHlwZSwgZXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICBpZiAoIWludGVyYWN0aW9uKSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICBpbnRlcmFjdGlvbi5fdXBkYXRlRXZlbnRUYXJnZXRzKGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgICAgIGludGVyYWN0aW9uW21ldGhvZF0oZXZlbnQsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuICAgICAgICB9XG4gICAgfSk7XG59XG5cbmZ1bmN0aW9uIEludGVyYWN0RXZlbnQgKGludGVyYWN0aW9uLCBldmVudCwgYWN0aW9uLCBwaGFzZSwgZWxlbWVudCwgcmVsYXRlZCkge1xuICAgIHZhciBjbGllbnQsXG4gICAgICAgIHBhZ2UsXG4gICAgICAgIHRhcmdldCAgICAgID0gaW50ZXJhY3Rpb24udGFyZ2V0LFxuICAgICAgICBzbmFwU3RhdHVzICA9IGludGVyYWN0aW9uLnNuYXBTdGF0dXMsXG4gICAgICAgIHJlc3RyaWN0U3RhdHVzICA9IGludGVyYWN0aW9uLnJlc3RyaWN0U3RhdHVzLFxuICAgICAgICBwb2ludGVycyAgICA9IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICBkZWx0YVNvdXJjZSA9ICh0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMgfHwgZGVmYXVsdE9wdGlvbnMpLmRlbHRhU291cmNlLFxuICAgICAgICBzb3VyY2VYICAgICA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZICAgICA9IGRlbHRhU291cmNlICsgJ1knLFxuICAgICAgICBvcHRpb25zICAgICA9IHRhcmdldD8gdGFyZ2V0Lm9wdGlvbnM6IGRlZmF1bHRPcHRpb25zLFxuICAgICAgICBvcmlnaW4gICAgICA9IGdldE9yaWdpblhZKHRhcmdldCwgZWxlbWVudCksXG4gICAgICAgIHN0YXJ0aW5nICAgID0gcGhhc2UgPT09ICdzdGFydCcsXG4gICAgICAgIGVuZGluZyAgICAgID0gcGhhc2UgPT09ICdlbmQnLFxuICAgICAgICBjb29yZHMgICAgICA9IHN0YXJ0aW5nPyBpbnRlcmFjdGlvbi5zdGFydENvb3JkcyA6IGludGVyYWN0aW9uLmN1ckNvb3JkcztcblxuICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IGludGVyYWN0aW9uLmVsZW1lbnQ7XG5cbiAgICBwYWdlICAgPSBleHRlbmQoe30sIGNvb3Jkcy5wYWdlKTtcbiAgICBjbGllbnQgPSBleHRlbmQoe30sIGNvb3Jkcy5jbGllbnQpO1xuXG4gICAgcGFnZS54IC09IG9yaWdpbi54O1xuICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcblxuICAgIGNsaWVudC54IC09IG9yaWdpbi54O1xuICAgIGNsaWVudC55IC09IG9yaWdpbi55O1xuXG4gICAgdmFyIHJlbGF0aXZlUG9pbnRzID0gb3B0aW9uc1thY3Rpb25dLnNuYXAgJiYgb3B0aW9uc1thY3Rpb25dLnNuYXAucmVsYXRpdmVQb2ludHMgO1xuXG4gICAgaWYgKGNoZWNrU25hcCh0YXJnZXQsIGFjdGlvbikgJiYgIShzdGFydGluZyAmJiByZWxhdGl2ZVBvaW50cyAmJiByZWxhdGl2ZVBvaW50cy5sZW5ndGgpKSB7XG4gICAgICAgIHRoaXMuc25hcCA9IHtcbiAgICAgICAgICAgIHJhbmdlICA6IHNuYXBTdGF0dXMucmFuZ2UsXG4gICAgICAgICAgICBsb2NrZWQgOiBzbmFwU3RhdHVzLmxvY2tlZCxcbiAgICAgICAgICAgIHggICAgICA6IHNuYXBTdGF0dXMuc25hcHBlZFgsXG4gICAgICAgICAgICB5ICAgICAgOiBzbmFwU3RhdHVzLnNuYXBwZWRZLFxuICAgICAgICAgICAgcmVhbFggIDogc25hcFN0YXR1cy5yZWFsWCxcbiAgICAgICAgICAgIHJlYWxZICA6IHNuYXBTdGF0dXMucmVhbFksXG4gICAgICAgICAgICBkeCAgICAgOiBzbmFwU3RhdHVzLmR4LFxuICAgICAgICAgICAgZHkgICAgIDogc25hcFN0YXR1cy5keVxuICAgICAgICB9O1xuXG4gICAgICAgIGlmIChzbmFwU3RhdHVzLmxvY2tlZCkge1xuICAgICAgICAgICAgcGFnZS54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBwYWdlLnkgKz0gc25hcFN0YXR1cy5keTtcbiAgICAgICAgICAgIGNsaWVudC54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBjbGllbnQueSArPSBzbmFwU3RhdHVzLmR5O1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKGNoZWNrUmVzdHJpY3QodGFyZ2V0LCBhY3Rpb24pICYmICEoc3RhcnRpbmcgJiYgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0LmVsZW1lbnRSZWN0KSAmJiByZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkKSB7XG4gICAgICAgIHBhZ2UueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgcGFnZS55ICs9IHJlc3RyaWN0U3RhdHVzLmR5O1xuICAgICAgICBjbGllbnQueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgY2xpZW50LnkgKz0gcmVzdHJpY3RTdGF0dXMuZHk7XG5cbiAgICAgICAgdGhpcy5yZXN0cmljdCA9IHtcbiAgICAgICAgICAgIGR4OiByZXN0cmljdFN0YXR1cy5keCxcbiAgICAgICAgICAgIGR5OiByZXN0cmljdFN0YXR1cy5keVxuICAgICAgICB9O1xuICAgIH1cblxuICAgIHRoaXMucGFnZVggICAgID0gcGFnZS54O1xuICAgIHRoaXMucGFnZVkgICAgID0gcGFnZS55O1xuICAgIHRoaXMuY2xpZW50WCAgID0gY2xpZW50Lng7XG4gICAgdGhpcy5jbGllbnRZICAgPSBjbGllbnQueTtcblxuICAgIHRoaXMueDAgICAgICAgID0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54IC0gb3JpZ2luLng7XG4gICAgdGhpcy55MCAgICAgICAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueTtcbiAgICB0aGlzLmNsaWVudFgwICA9IGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54IC0gb3JpZ2luLng7XG4gICAgdGhpcy5jbGllbnRZMCAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5jbGllbnQueSAtIG9yaWdpbi55O1xuICAgIHRoaXMuY3RybEtleSAgID0gZXZlbnQuY3RybEtleTtcbiAgICB0aGlzLmFsdEtleSAgICA9IGV2ZW50LmFsdEtleTtcbiAgICB0aGlzLnNoaWZ0S2V5ICA9IGV2ZW50LnNoaWZ0S2V5O1xuICAgIHRoaXMubWV0YUtleSAgID0gZXZlbnQubWV0YUtleTtcbiAgICB0aGlzLmJ1dHRvbiAgICA9IGV2ZW50LmJ1dHRvbjtcbiAgICB0aGlzLnRhcmdldCAgICA9IGVsZW1lbnQ7XG4gICAgdGhpcy50MCAgICAgICAgPSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG4gICAgdGhpcy50eXBlICAgICAgPSBhY3Rpb24gKyAocGhhc2UgfHwgJycpO1xuXG4gICAgdGhpcy5pbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uO1xuICAgIHRoaXMuaW50ZXJhY3RhYmxlID0gdGFyZ2V0O1xuXG4gICAgdmFyIGluZXJ0aWFTdGF0dXMgPSBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzO1xuXG4gICAgaWYgKGluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7XG4gICAgICAgIHRoaXMuZGV0YWlsID0gJ2luZXJ0aWEnO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGVkKSB7XG4gICAgICAgIHRoaXMucmVsYXRlZFRhcmdldCA9IHJlbGF0ZWQ7XG4gICAgfVxuXG4gICAgLy8gZW5kIGV2ZW50IGR4LCBkeSBpcyBkaWZmZXJlbmNlIGJldHdlZW4gc3RhcnQgYW5kIGVuZCBwb2ludHNcbiAgICBpZiAoZW5kaW5nKSB7XG4gICAgICAgIGlmIChkZWx0YVNvdXJjZSA9PT0gJ2NsaWVudCcpIHtcbiAgICAgICAgICAgIHRoaXMuZHggPSBjbGllbnQueCAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54O1xuICAgICAgICAgICAgdGhpcy5keSA9IGNsaWVudC55IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMuY2xpZW50Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gcGFnZS54IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54O1xuICAgICAgICAgICAgdGhpcy5keSA9IHBhZ2UueSAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLnBhZ2UueTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLmR4ID0gMDtcbiAgICAgICAgdGhpcy5keSA9IDA7XG4gICAgfVxuICAgIC8vIGNvcHkgcHJvcGVydGllcyBmcm9tIHByZXZpb3VzbW92ZSBpZiBzdGFydGluZyBpbmVydGlhXG4gICAgZWxzZSBpZiAocGhhc2UgPT09ICdpbmVydGlhc3RhcnQnKSB7XG4gICAgICAgIHRoaXMuZHggPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHg7XG4gICAgICAgIHRoaXMuZHkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBpZiAoZGVsdGFTb3VyY2UgPT09ICdjbGllbnQnKSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gY2xpZW50LnggLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuY2xpZW50WDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBjbGllbnQueSAtIGludGVyYWN0aW9uLnByZXZFdmVudC5jbGllbnRZO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5keCA9IHBhZ2UueCAtIGludGVyYWN0aW9uLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBwYWdlLnkgLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKGludGVyYWN0aW9uLnByZXZFdmVudCAmJiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGV0YWlsID09PSAnaW5lcnRpYSdcbiAgICAgICAgJiYgIWluZXJ0aWFTdGF0dXMuYWN0aXZlXG4gICAgICAgICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhLnplcm9SZXN1bWVEZWx0YSkge1xuXG4gICAgICAgIGluZXJ0aWFTdGF0dXMucmVzdW1lRHggKz0gdGhpcy5keDtcbiAgICAgICAgaW5lcnRpYVN0YXR1cy5yZXN1bWVEeSArPSB0aGlzLmR5O1xuXG4gICAgICAgIHRoaXMuZHggPSB0aGlzLmR5ID0gMDtcbiAgICB9XG5cbiAgICBpZiAoYWN0aW9uID09PSAncmVzaXplJyAmJiBpbnRlcmFjdGlvbi5yZXNpemVBeGVzKSB7XG4gICAgICAgIGlmIChvcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneScpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR4ID0gdGhpcy5keTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuZHkgPSB0aGlzLmR4O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5heGVzID0gJ3h5JztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuYXhlcyA9IGludGVyYWN0aW9uLnJlc2l6ZUF4ZXM7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneCcpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR5ID0gMDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGludGVyYWN0aW9uLnJlc2l6ZUF4ZXMgPT09ICd5Jykge1xuICAgICAgICAgICAgICAgIHRoaXMuZHggPSAwO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2dlc3R1cmUnKSB7XG4gICAgICAgIHRoaXMudG91Y2hlcyA9IFtwb2ludGVyc1swXSwgcG9pbnRlcnNbMV1dO1xuXG4gICAgICAgIGlmIChzdGFydGluZykge1xuICAgICAgICAgICAgdGhpcy5kaXN0YW5jZSA9IHRvdWNoRGlzdGFuY2UocG9pbnRlcnMsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuYm94ICAgICAgPSB0b3VjaEJCb3gocG9pbnRlcnMpO1xuICAgICAgICAgICAgdGhpcy5zY2FsZSAgICA9IDE7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gMDtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB0b3VjaEFuZ2xlKHBvaW50ZXJzLCB1bmRlZmluZWQsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuZGEgICAgICAgPSAwO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGVuZGluZyB8fCBldmVudCBpbnN0YW5jZW9mIEludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgICAgIHRoaXMuZGlzdGFuY2UgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGlzdGFuY2U7XG4gICAgICAgICAgICB0aGlzLmJveCAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmJveDtcbiAgICAgICAgICAgIHRoaXMuc2NhbGUgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc2NhbGU7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gdGhpcy5zY2FsZSAtIDE7XG4gICAgICAgICAgICB0aGlzLmFuZ2xlICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmFuZ2xlO1xuICAgICAgICAgICAgdGhpcy5kYSAgICAgICA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnN0YXJ0QW5nbGU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmRpc3RhbmNlID0gdG91Y2hEaXN0YW5jZShwb2ludGVycywgZGVsdGFTb3VyY2UpO1xuICAgICAgICAgICAgdGhpcy5ib3ggICAgICA9IHRvdWNoQkJveChwb2ludGVycyk7XG4gICAgICAgICAgICB0aGlzLnNjYWxlICAgID0gdGhpcy5kaXN0YW5jZSAvIGludGVyYWN0aW9uLmdlc3R1cmUuc3RhcnREaXN0YW5jZTtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB0b3VjaEFuZ2xlKHBvaW50ZXJzLCBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZSwgZGVsdGFTb3VyY2UpO1xuXG4gICAgICAgICAgICB0aGlzLmRzID0gdGhpcy5zY2FsZSAtIGludGVyYWN0aW9uLmdlc3R1cmUucHJldlNjYWxlO1xuICAgICAgICAgICAgdGhpcy5kYSA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLmRvd25UaW1lc1swXTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSAwO1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IDA7XG4gICAgICAgIHRoaXMuc3BlZWQgICAgID0gMDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVggPSAwO1xuICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IDA7XG4gICAgfVxuICAgIGVsc2UgaWYgKHBoYXNlID09PSAnaW5lcnRpYXN0YXJ0Jykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHQgICAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmR0O1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IGludGVyYWN0aW9uLnByZXZFdmVudC5kdXJhdGlvbjtcbiAgICAgICAgdGhpcy5zcGVlZCAgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc3BlZWQ7XG4gICAgICAgIHRoaXMudmVsb2NpdHlYID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdGhpcy50aW1lU3RhbXAgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHVyYXRpb24gID0gdGhpcy50aW1lU3RhbXAgLSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG5cbiAgICAgICAgaWYgKGV2ZW50IGluc3RhbmNlb2YgSW50ZXJhY3RFdmVudCkge1xuICAgICAgICAgICAgdmFyIGR4ID0gdGhpc1tzb3VyY2VYXSAtIGludGVyYWN0aW9uLnByZXZFdmVudFtzb3VyY2VYXSxcbiAgICAgICAgICAgICAgICBkeSA9IHRoaXNbc291cmNlWV0gLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnRbc291cmNlWV0sXG4gICAgICAgICAgICAgICAgZHQgPSB0aGlzLmR0IC8gMTAwMDtcblxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IGh5cG90KGR4LCBkeSkgLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlYID0gZHggLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlZID0gZHkgLyBkdDtcbiAgICAgICAgfVxuICAgICAgICAvLyBpZiBub3JtYWwgbW92ZSBvciBlbmQgZXZlbnQsIHVzZSBwcmV2aW91cyB1c2VyIGV2ZW50IGNvb3Jkc1xuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIHNwZWVkIGFuZCB2ZWxvY2l0eSBpbiBwaXhlbHMgcGVyIHNlY29uZFxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0uc3BlZWQ7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udng7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udnk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoKGVuZGluZyB8fCBwaGFzZSA9PT0gJ2luZXJ0aWFzdGFydCcpXG4gICAgICAgICYmIGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCA+IDYwMCAmJiB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXAgPCAxNTApIHtcblxuICAgICAgICB2YXIgYW5nbGUgPSAxODAgKiBNYXRoLmF0YW4yKGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVksIGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVgpIC8gTWF0aC5QSSxcbiAgICAgICAgICAgIG92ZXJsYXAgPSAyMi41O1xuXG4gICAgICAgIGlmIChhbmdsZSA8IDApIHtcbiAgICAgICAgICAgIGFuZ2xlICs9IDM2MDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBsZWZ0ID0gMTM1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDIyNSArIG92ZXJsYXAsXG4gICAgICAgICAgICB1cCAgID0gMjI1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDMxNSArIG92ZXJsYXAsXG5cbiAgICAgICAgICAgIHJpZ2h0ID0gIWxlZnQgJiYgKDMxNSAtIG92ZXJsYXAgPD0gYW5nbGUgfHwgYW5nbGUgPCAgNDUgKyBvdmVybGFwKSxcbiAgICAgICAgICAgIGRvd24gID0gIXVwICAgJiYgICA0NSAtIG92ZXJsYXAgPD0gYW5nbGUgJiYgYW5nbGUgPCAxMzUgKyBvdmVybGFwO1xuXG4gICAgICAgIHRoaXMuc3dpcGUgPSB7XG4gICAgICAgICAgICB1cCAgIDogdXAsXG4gICAgICAgICAgICBkb3duIDogZG93bixcbiAgICAgICAgICAgIGxlZnQgOiBsZWZ0LFxuICAgICAgICAgICAgcmlnaHQ6IHJpZ2h0LFxuICAgICAgICAgICAgYW5nbGU6IGFuZ2xlLFxuICAgICAgICAgICAgc3BlZWQ6IGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCxcbiAgICAgICAgICAgIHZlbG9jaXR5OiB7XG4gICAgICAgICAgICAgICAgeDogaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WCxcbiAgICAgICAgICAgICAgICB5OiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZXG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgfVxufVxuXG5JbnRlcmFjdEV2ZW50LnByb3RvdHlwZSA9IHtcbiAgICBwcmV2ZW50RGVmYXVsdDogYmxhbmssXG4gICAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkID0gdGhpcy5wcm9wYWdhdGlvblN0b3BwZWQgPSB0cnVlO1xuICAgIH0sXG4gICAgc3RvcFByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMucHJvcGFnYXRpb25TdG9wcGVkID0gdHJ1ZTtcbiAgICB9XG59O1xuXG5mdW5jdGlvbiBwcmV2ZW50T3JpZ2luYWxEZWZhdWx0ICgpIHtcbiAgICB0aGlzLm9yaWdpbmFsRXZlbnQucHJldmVudERlZmF1bHQoKTtcbn1cblxuZnVuY3Rpb24gZ2V0QWN0aW9uQ3Vyc29yIChhY3Rpb24pIHtcbiAgICB2YXIgY3Vyc29yID0gJyc7XG5cbiAgICBpZiAoYWN0aW9uLm5hbWUgPT09ICdkcmFnJykge1xuICAgICAgICBjdXJzb3IgPSAgYWN0aW9uQ3Vyc29ycy5kcmFnO1xuICAgIH1cbiAgICBpZiAoYWN0aW9uLm5hbWUgPT09ICdyZXNpemUnKSB7XG4gICAgICAgIGlmIChhY3Rpb24uYXhpcykge1xuICAgICAgICAgICAgY3Vyc29yID0gIGFjdGlvbkN1cnNvcnNbYWN0aW9uLm5hbWUgKyBhY3Rpb24uYXhpc107XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoYWN0aW9uLmVkZ2VzKSB7XG4gICAgICAgICAgICB2YXIgY3Vyc29yS2V5ID0gJ3Jlc2l6ZScsXG4gICAgICAgICAgICAgICAgZWRnZU5hbWVzID0gWyd0b3AnLCAnYm90dG9tJywgJ2xlZnQnLCAncmlnaHQnXTtcblxuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCA0OyBpKyspIHtcbiAgICAgICAgICAgICAgICBpZiAoYWN0aW9uLmVkZ2VzW2VkZ2VOYW1lc1tpXV0pIHtcbiAgICAgICAgICAgICAgICAgICAgY3Vyc29yS2V5ICs9IGVkZ2VOYW1lc1tpXTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGN1cnNvciA9IGFjdGlvbkN1cnNvcnNbY3Vyc29yS2V5XTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBjdXJzb3I7XG59XG5cbmZ1bmN0aW9uIGNoZWNrUmVzaXplRWRnZSAobmFtZSwgdmFsdWUsIHBhZ2UsIGVsZW1lbnQsIGludGVyYWN0YWJsZUVsZW1lbnQsIHJlY3QsIG1hcmdpbikge1xuICAgIC8vIGZhbHNlLCAnJywgdW5kZWZpbmVkLCBudWxsXG4gICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgIC8vIHRydWUgdmFsdWUsIHVzZSBwb2ludGVyIGNvb3JkcyBhbmQgZWxlbWVudCByZWN0XG4gICAgaWYgKHZhbHVlID09PSB0cnVlKSB7XG4gICAgICAgIC8vIGlmIGRpbWVuc2lvbnMgYXJlIG5lZ2F0aXZlLCBcInN3aXRjaFwiIGVkZ2VzXG4gICAgICAgIHZhciB3aWR0aCA9IGlzTnVtYmVyKHJlY3Qud2lkdGgpPyByZWN0LndpZHRoIDogcmVjdC5yaWdodCAtIHJlY3QubGVmdCxcbiAgICAgICAgICAgIGhlaWdodCA9IGlzTnVtYmVyKHJlY3QuaGVpZ2h0KT8gcmVjdC5oZWlnaHQgOiByZWN0LmJvdHRvbSAtIHJlY3QudG9wO1xuXG4gICAgICAgIGlmICh3aWR0aCA8IDApIHtcbiAgICAgICAgICAgIGlmICAgICAgKG5hbWUgPT09ICdsZWZ0JyApIHsgbmFtZSA9ICdyaWdodCc7IH1cbiAgICAgICAgICAgIGVsc2UgaWYgKG5hbWUgPT09ICdyaWdodCcpIHsgbmFtZSA9ICdsZWZ0JyA7IH1cbiAgICAgICAgfVxuICAgICAgICBpZiAoaGVpZ2h0IDwgMCkge1xuICAgICAgICAgICAgaWYgICAgICAobmFtZSA9PT0gJ3RvcCcgICApIHsgbmFtZSA9ICdib3R0b20nOyB9XG4gICAgICAgICAgICBlbHNlIGlmIChuYW1lID09PSAnYm90dG9tJykgeyBuYW1lID0gJ3RvcCcgICA7IH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChuYW1lID09PSAnbGVmdCcgICkgeyByZXR1cm4gcGFnZS54IDwgKCh3aWR0aCAgPj0gMD8gcmVjdC5sZWZ0OiByZWN0LnJpZ2h0ICkgKyBtYXJnaW4pOyB9XG4gICAgICAgIGlmIChuYW1lID09PSAndG9wJyAgICkgeyByZXR1cm4gcGFnZS55IDwgKChoZWlnaHQgPj0gMD8gcmVjdC50b3AgOiByZWN0LmJvdHRvbSkgKyBtYXJnaW4pOyB9XG5cbiAgICAgICAgaWYgKG5hbWUgPT09ICdyaWdodCcgKSB7IHJldHVybiBwYWdlLnggPiAoKHdpZHRoICA+PSAwPyByZWN0LnJpZ2h0IDogcmVjdC5sZWZ0KSAtIG1hcmdpbik7IH1cbiAgICAgICAgaWYgKG5hbWUgPT09ICdib3R0b20nKSB7IHJldHVybiBwYWdlLnkgPiAoKGhlaWdodCA+PSAwPyByZWN0LmJvdHRvbTogcmVjdC50b3AgKSAtIG1hcmdpbik7IH1cbiAgICB9XG5cbiAgICAvLyB0aGUgcmVtYWluaW5nIGNoZWNrcyByZXF1aXJlIGFuIGVsZW1lbnRcbiAgICBpZiAoIWlzRWxlbWVudChlbGVtZW50KSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgIHJldHVybiBpc0VsZW1lbnQodmFsdWUpXG4gICAgICAgIC8vIHRoZSB2YWx1ZSBpcyBhbiBlbGVtZW50IHRvIHVzZSBhcyBhIHJlc2l6ZSBoYW5kbGVcbiAgICAgICAgPyB2YWx1ZSA9PT0gZWxlbWVudFxuICAgICAgICAvLyBvdGhlcndpc2UgY2hlY2sgaWYgZWxlbWVudCBtYXRjaGVzIHZhbHVlIGFzIHNlbGVjdG9yXG4gICAgICAgIDogbWF0Y2hlc1VwVG8oZWxlbWVudCwgdmFsdWUsIGludGVyYWN0YWJsZUVsZW1lbnQpO1xufVxuXG5mdW5jdGlvbiBkZWZhdWx0QWN0aW9uQ2hlY2tlciAocG9pbnRlciwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpIHtcbiAgICB2YXIgcmVjdCA9IHRoaXMuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgc2hvdWxkUmVzaXplID0gZmFsc2UsXG4gICAgICAgIGFjdGlvbiA9IG51bGwsXG4gICAgICAgIHJlc2l6ZUF4ZXMgPSBudWxsLFxuICAgICAgICByZXNpemVFZGdlcyxcbiAgICAgICAgcGFnZSA9IGV4dGVuZCh7fSwgaW50ZXJhY3Rpb24uY3VyQ29vcmRzLnBhZ2UpLFxuICAgICAgICBvcHRpb25zID0gdGhpcy5vcHRpb25zO1xuXG4gICAgaWYgKCFyZWN0KSB7IHJldHVybiBudWxsOyB9XG5cbiAgICBpZiAoYWN0aW9uSXNFbmFibGVkLnJlc2l6ZSAmJiBvcHRpb25zLnJlc2l6ZS5lbmFibGVkKSB7XG4gICAgICAgIHZhciByZXNpemVPcHRpb25zID0gb3B0aW9ucy5yZXNpemU7XG5cbiAgICAgICAgcmVzaXplRWRnZXMgPSB7XG4gICAgICAgICAgICBsZWZ0OiBmYWxzZSwgcmlnaHQ6IGZhbHNlLCB0b3A6IGZhbHNlLCBib3R0b206IGZhbHNlXG4gICAgICAgIH07XG5cbiAgICAgICAgLy8gaWYgdXNpbmcgcmVzaXplLmVkZ2VzXG4gICAgICAgIGlmIChpc09iamVjdChyZXNpemVPcHRpb25zLmVkZ2VzKSkge1xuICAgICAgICAgICAgZm9yICh2YXIgZWRnZSBpbiByZXNpemVFZGdlcykge1xuICAgICAgICAgICAgICAgIHJlc2l6ZUVkZ2VzW2VkZ2VdID0gY2hlY2tSZXNpemVFZGdlKGVkZ2UsXG4gICAgICAgICAgICAgICAgICAgIHJlc2l6ZU9wdGlvbnMuZWRnZXNbZWRnZV0sXG4gICAgICAgICAgICAgICAgICAgIHBhZ2UsXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLl9ldmVudFRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgZWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgcmVjdCxcbiAgICAgICAgICAgICAgICAgICAgcmVzaXplT3B0aW9ucy5tYXJnaW4gfHwgbWFyZ2luKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzaXplRWRnZXMubGVmdCA9IHJlc2l6ZUVkZ2VzLmxlZnQgJiYgIXJlc2l6ZUVkZ2VzLnJpZ2h0O1xuICAgICAgICAgICAgcmVzaXplRWRnZXMudG9wICA9IHJlc2l6ZUVkZ2VzLnRvcCAgJiYgIXJlc2l6ZUVkZ2VzLmJvdHRvbTtcblxuICAgICAgICAgICAgc2hvdWxkUmVzaXplID0gcmVzaXplRWRnZXMubGVmdCB8fCByZXNpemVFZGdlcy5yaWdodCB8fCByZXNpemVFZGdlcy50b3AgfHwgcmVzaXplRWRnZXMuYm90dG9tO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdmFyIHJpZ2h0ICA9IG9wdGlvbnMucmVzaXplLmF4aXMgIT09ICd5JyAmJiBwYWdlLnggPiAocmVjdC5yaWdodCAgLSBtYXJnaW4pLFxuICAgICAgICAgICAgICAgIGJvdHRvbSA9IG9wdGlvbnMucmVzaXplLmF4aXMgIT09ICd4JyAmJiBwYWdlLnkgPiAocmVjdC5ib3R0b20gLSBtYXJnaW4pO1xuXG4gICAgICAgICAgICBzaG91bGRSZXNpemUgPSByaWdodCB8fCBib3R0b207XG4gICAgICAgICAgICByZXNpemVBeGVzID0gKHJpZ2h0PyAneCcgOiAnJykgKyAoYm90dG9tPyAneScgOiAnJyk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBhY3Rpb24gPSBzaG91bGRSZXNpemVcbiAgICAgICAgPyAncmVzaXplJ1xuICAgICAgICA6IGFjdGlvbklzRW5hYmxlZC5kcmFnICYmIG9wdGlvbnMuZHJhZy5lbmFibGVkXG4gICAgICAgID8gJ2RyYWcnXG4gICAgICAgIDogbnVsbDtcblxuICAgIGlmIChhY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZVxuICAgICAgICAmJiBpbnRlcmFjdGlvbi5wb2ludGVySWRzLmxlbmd0aCA+PTJcbiAgICAgICAgJiYgIShpbnRlcmFjdGlvbi5kcmFnZ2luZyB8fCBpbnRlcmFjdGlvbi5yZXNpemluZykpIHtcbiAgICAgICAgYWN0aW9uID0gJ2dlc3R1cmUnO1xuICAgIH1cblxuICAgIGlmIChhY3Rpb24pIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIG5hbWU6IGFjdGlvbixcbiAgICAgICAgICAgIGF4aXM6IHJlc2l6ZUF4ZXMsXG4gICAgICAgICAgICBlZGdlczogcmVzaXplRWRnZXNcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICByZXR1cm4gbnVsbDtcbn1cblxuLy8gQ2hlY2sgaWYgYWN0aW9uIGlzIGVuYWJsZWQgZ2xvYmFsbHkgYW5kIHRoZSBjdXJyZW50IHRhcmdldCBzdXBwb3J0cyBpdFxuLy8gSWYgc28sIHJldHVybiB0aGUgdmFsaWRhdGVkIGFjdGlvbi4gT3RoZXJ3aXNlLCByZXR1cm4gbnVsbFxuZnVuY3Rpb24gdmFsaWRhdGVBY3Rpb24gKGFjdGlvbiwgaW50ZXJhY3RhYmxlKSB7XG4gICAgaWYgKCFpc09iamVjdChhY3Rpb24pKSB7IHJldHVybiBudWxsOyB9XG5cbiAgICB2YXIgYWN0aW9uTmFtZSA9IGFjdGlvbi5uYW1lLFxuICAgICAgICBvcHRpb25zID0gaW50ZXJhY3RhYmxlLm9wdGlvbnM7XG5cbiAgICBpZiAoKCAgKGFjdGlvbk5hbWUgID09PSAncmVzaXplJyAgICYmIG9wdGlvbnMucmVzaXplLmVuYWJsZWQgKVxuICAgICAgICB8fCAoYWN0aW9uTmFtZSAgICAgID09PSAnZHJhZycgICAgICYmIG9wdGlvbnMuZHJhZy5lbmFibGVkICApXG4gICAgICAgIHx8IChhY3Rpb25OYW1lICAgICAgPT09ICdnZXN0dXJlJyAgJiYgb3B0aW9ucy5nZXN0dXJlLmVuYWJsZWQpKVxuICAgICAgICAmJiBhY3Rpb25Jc0VuYWJsZWRbYWN0aW9uTmFtZV0pIHtcblxuICAgICAgICBpZiAoYWN0aW9uTmFtZSA9PT0gJ3Jlc2l6ZScgfHwgYWN0aW9uTmFtZSA9PT0gJ3Jlc2l6ZXl4Jykge1xuICAgICAgICAgICAgYWN0aW9uTmFtZSA9ICdyZXNpemV4eSc7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gYWN0aW9uO1xuICAgIH1cbiAgICByZXR1cm4gbnVsbDtcbn1cblxudmFyIGxpc3RlbmVycyA9IHt9LFxuICAgIGludGVyYWN0aW9uTGlzdGVuZXJzID0gW1xuICAgICAgICAnZHJhZ1N0YXJ0JywgJ2RyYWdNb3ZlJywgJ3Jlc2l6ZVN0YXJ0JywgJ3Jlc2l6ZU1vdmUnLCAnZ2VzdHVyZVN0YXJ0JywgJ2dlc3R1cmVNb3ZlJyxcbiAgICAgICAgJ3BvaW50ZXJPdmVyJywgJ3BvaW50ZXJPdXQnLCAncG9pbnRlckhvdmVyJywgJ3NlbGVjdG9yRG93bicsXG4gICAgICAgICdwb2ludGVyRG93bicsICdwb2ludGVyTW92ZScsICdwb2ludGVyVXAnLCAncG9pbnRlckNhbmNlbCcsICdwb2ludGVyRW5kJyxcbiAgICAgICAgJ2FkZFBvaW50ZXInLCAncmVtb3ZlUG9pbnRlcicsICdyZWNvcmRQb2ludGVyJywgJ2F1dG9TY3JvbGxNb3ZlJ1xuICAgIF07XG5cbmZvciAodmFyIGkgPSAwLCBsZW4gPSBpbnRlcmFjdGlvbkxpc3RlbmVycy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgIHZhciBuYW1lID0gaW50ZXJhY3Rpb25MaXN0ZW5lcnNbaV07XG5cbiAgICBsaXN0ZW5lcnNbbmFtZV0gPSBkb09uSW50ZXJhY3Rpb25zKG5hbWUpO1xufVxuXG4vLyBib3VuZCB0byB0aGUgaW50ZXJhY3RhYmxlIGNvbnRleHQgd2hlbiBhIERPTSBldmVudFxuLy8gbGlzdGVuZXIgaXMgYWRkZWQgdG8gYSBzZWxlY3RvciBpbnRlcmFjdGFibGVcbmZ1bmN0aW9uIGRlbGVnYXRlTGlzdGVuZXIgKGV2ZW50LCB1c2VDYXB0dXJlKSB7XG4gICAgdmFyIGZha2VFdmVudCA9IHt9LFxuICAgICAgICBkZWxlZ2F0ZWQgPSBkZWxlZ2F0ZWRFdmVudHNbZXZlbnQudHlwZV0sXG4gICAgICAgIGV2ZW50VGFyZ2V0ID0gZ2V0QWN0dWFsRWxlbWVudChldmVudC5wYXRoXG4gICAgICAgICAgICA/IGV2ZW50LnBhdGhbMF1cbiAgICAgICAgICAgIDogZXZlbnQudGFyZ2V0KSxcbiAgICAgICAgZWxlbWVudCA9IGV2ZW50VGFyZ2V0O1xuXG4gICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgLy8gZHVwbGljYXRlIHRoZSBldmVudCBzbyB0aGF0IGN1cnJlbnRUYXJnZXQgY2FuIGJlIGNoYW5nZWRcbiAgICBmb3IgKHZhciBwcm9wIGluIGV2ZW50KSB7XG4gICAgICAgIGZha2VFdmVudFtwcm9wXSA9IGV2ZW50W3Byb3BdO1xuICAgIH1cblxuICAgIGZha2VFdmVudC5vcmlnaW5hbEV2ZW50ID0gZXZlbnQ7XG4gICAgZmFrZUV2ZW50LnByZXZlbnREZWZhdWx0ID0gcHJldmVudE9yaWdpbmFsRGVmYXVsdDtcblxuICAgIC8vIGNsaW1iIHVwIGRvY3VtZW50IHRyZWUgbG9va2luZyBmb3Igc2VsZWN0b3IgbWF0Y2hlc1xuICAgIHdoaWxlIChpc0VsZW1lbnQoZWxlbWVudCkpIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgc2VsZWN0b3IgPSBkZWxlZ2F0ZWQuc2VsZWN0b3JzW2ldLFxuICAgICAgICAgICAgICAgIGNvbnRleHQgPSBkZWxlZ2F0ZWQuY29udGV4dHNbaV07XG5cbiAgICAgICAgICAgIGlmIChtYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgJiYgbm9kZUNvbnRhaW5zKGNvbnRleHQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIG5vZGVDb250YWlucyhjb250ZXh0LCBlbGVtZW50KSkge1xuXG4gICAgICAgICAgICAgICAgdmFyIGxpc3RlbmVycyA9IGRlbGVnYXRlZC5saXN0ZW5lcnNbaV07XG5cbiAgICAgICAgICAgICAgICBmYWtlRXZlbnQuY3VycmVudFRhcmdldCA9IGVsZW1lbnQ7XG5cbiAgICAgICAgICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IGxpc3RlbmVycy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgICAgICAgICBpZiAobGlzdGVuZXJzW2pdWzFdID09PSB1c2VDYXB0dXJlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnNbal1bMF0oZmFrZUV2ZW50KTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGVsZW1lbnQgPSBwYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgIH1cbn1cblxuZnVuY3Rpb24gZGVsZWdhdGVVc2VDYXB0dXJlIChldmVudCkge1xuICAgIHJldHVybiBkZWxlZ2F0ZUxpc3RlbmVyLmNhbGwodGhpcywgZXZlbnQsIHRydWUpO1xufVxuXG5pbnRlcmFjdGFibGVzLmluZGV4T2ZFbGVtZW50ID0gZnVuY3Rpb24gaW5kZXhPZkVsZW1lbnQgKGVsZW1lbnQsIGNvbnRleHQpIHtcbiAgICBjb250ZXh0ID0gY29udGV4dCB8fCBkb2N1bWVudDtcblxuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gdGhpc1tpXTtcblxuICAgICAgICBpZiAoKGludGVyYWN0YWJsZS5zZWxlY3RvciA9PT0gZWxlbWVudFxuICAgICAgICAgICAgJiYgKGludGVyYWN0YWJsZS5fY29udGV4dCA9PT0gY29udGV4dCkpXG4gICAgICAgICAgICB8fCAoIWludGVyYWN0YWJsZS5zZWxlY3RvciAmJiBpbnRlcmFjdGFibGUuX2VsZW1lbnQgPT09IGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgIHJldHVybiBpO1xuICAgICAgICB9XG4gICAgfVxuICAgIHJldHVybiAtMTtcbn07XG5cbmludGVyYWN0YWJsZXMuZ2V0ID0gZnVuY3Rpb24gaW50ZXJhY3RhYmxlR2V0IChlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIHRoaXNbdGhpcy5pbmRleE9mRWxlbWVudChlbGVtZW50LCBvcHRpb25zICYmIG9wdGlvbnMuY29udGV4dCldO1xufTtcblxuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IgPSBmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIGludGVyYWN0YWJsZSA9IHRoaXNbaV07XG5cbiAgICAgICAgaWYgKCFpbnRlcmFjdGFibGUuc2VsZWN0b3IpIHtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHJldCA9IGNhbGxiYWNrKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlLnNlbGVjdG9yLCBpbnRlcmFjdGFibGUuX2NvbnRleHQsIGksIHRoaXMpO1xuXG4gICAgICAgIGlmIChyZXQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgcmV0dXJuIHJldDtcbiAgICAgICAgfVxuICAgIH1cbn07XG5cbi8qXFxcbiAqIGludGVyYWN0XG4gWyBtZXRob2QgXVxuICpcbiAqIFRoZSBtZXRob2RzIG9mIHRoaXMgdmFyaWFibGUgY2FuIGJlIHVzZWQgdG8gc2V0IGVsZW1lbnRzIGFzXG4gKiBpbnRlcmFjdGFibGVzIGFuZCBhbHNvIHRvIGNoYW5nZSB2YXJpb3VzIGRlZmF1bHQgc2V0dGluZ3MuXG4gKlxuICogQ2FsbGluZyBpdCBhcyBhIGZ1bmN0aW9uIGFuZCBwYXNzaW5nIGFuIGVsZW1lbnQgb3IgYSB2YWxpZCBDU1Mgc2VsZWN0b3JcbiAqIHN0cmluZyByZXR1cm5zIGFuIEludGVyYWN0YWJsZSBvYmplY3Qgd2hpY2ggaGFzIHZhcmlvdXMgbWV0aG9kcyB0b1xuICogY29uZmlndXJlIGl0LlxuICpcbiAtIGVsZW1lbnQgKEVsZW1lbnQgfCBzdHJpbmcpIFRoZSBIVE1MIG9yIFNWRyBFbGVtZW50IHRvIGludGVyYWN0IHdpdGggb3IgQ1NTIHNlbGVjdG9yXG4gPSAob2JqZWN0KSBBbiBASW50ZXJhY3RhYmxlXG4gKlxuID4gVXNhZ2VcbiB8IGludGVyYWN0KGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkcmFnZ2FibGUnKSkuZHJhZ2dhYmxlKHRydWUpO1xuIHxcbiB8IHZhciByZWN0YWJsZXMgPSBpbnRlcmFjdCgncmVjdCcpO1xuIHwgcmVjdGFibGVzXG4gfCAgICAgLmdlc3R1cmFibGUodHJ1ZSlcbiB8ICAgICAub24oJ2dlc3R1cmVtb3ZlJywgZnVuY3Rpb24gKGV2ZW50KSB7XG4gfCAgICAgICAgIC8vIHNvbWV0aGluZyBjb29sLi4uXG4gfCAgICAgfSlcbiB8ICAgICAuYXV0b1Njcm9sbCh0cnVlKTtcbiBcXCovXG5mdW5jdGlvbiBpbnRlcmFjdCAoZWxlbWVudCwgb3B0aW9ucykge1xuICAgIHJldHVybiBpbnRlcmFjdGFibGVzLmdldChlbGVtZW50LCBvcHRpb25zKSB8fCBuZXcgSW50ZXJhY3RhYmxlKGVsZW1lbnQsIG9wdGlvbnMpO1xufVxuXG4vKlxcXG4gKiBJbnRlcmFjdGFibGVcbiBbIHByb3BlcnR5IF1cbiAqKlxuICogT2JqZWN0IHR5cGUgcmV0dXJuZWQgYnkgQGludGVyYWN0XG4gXFwqL1xuZnVuY3Rpb24gSW50ZXJhY3RhYmxlIChlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgdGhpcy5fZWxlbWVudCA9IGVsZW1lbnQ7XG4gICAgdGhpcy5faUV2ZW50cyA9IHRoaXMuX2lFdmVudHMgfHwge307XG5cbiAgICB2YXIgX3dpbmRvdztcblxuICAgIGlmICh0cnlTZWxlY3RvcihlbGVtZW50KSkge1xuICAgICAgICB0aGlzLnNlbGVjdG9yID0gZWxlbWVudDtcblxuICAgICAgICB2YXIgY29udGV4dCA9IG9wdGlvbnMgJiYgb3B0aW9ucy5jb250ZXh0O1xuXG4gICAgICAgIF93aW5kb3cgPSBjb250ZXh0PyBnZXRXaW5kb3coY29udGV4dCkgOiB3aW5kb3c7XG5cbiAgICAgICAgaWYgKGNvbnRleHQgJiYgKF93aW5kb3cuTm9kZVxuICAgICAgICAgICAgICAgID8gY29udGV4dCBpbnN0YW5jZW9mIF93aW5kb3cuTm9kZVxuICAgICAgICAgICAgICAgIDogKGlzRWxlbWVudChjb250ZXh0KSB8fCBjb250ZXh0ID09PSBfd2luZG93LmRvY3VtZW50KSkpIHtcblxuICAgICAgICAgICAgdGhpcy5fY29udGV4dCA9IGNvbnRleHQ7XG4gICAgICAgIH1cbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAgIF93aW5kb3cgPSBnZXRXaW5kb3coZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKGlzRWxlbWVudChlbGVtZW50LCBfd2luZG93KSkge1xuXG4gICAgICAgICAgICBpZiAoUG9pbnRlckV2ZW50KSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCBwRXZlbnRUeXBlcy5kb3duLCBsaXN0ZW5lcnMucG9pbnRlckRvd24gKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIHBFdmVudFR5cGVzLm1vdmUsIGxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAnbW91c2Vkb3duJyAsIGxpc3RlbmVycy5wb2ludGVyRG93biApO1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQodGhpcy5fZWxlbWVudCwgJ21vdXNlbW92ZScgLCBsaXN0ZW5lcnMucG9pbnRlckhvdmVyKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsICd0b3VjaHN0YXJ0JywgbGlzdGVuZXJzLnBvaW50ZXJEb3duICk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAndG91Y2htb3ZlJyAsIGxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuXG4gICAgdGhpcy5fZG9jID0gX3dpbmRvdy5kb2N1bWVudDtcblxuICAgIGlmICghY29udGFpbnMoZG9jdW1lbnRzLCB0aGlzLl9kb2MpKSB7XG4gICAgICAgIGxpc3RlblRvRG9jdW1lbnQodGhpcy5fZG9jKTtcbiAgICB9XG5cbiAgICBpbnRlcmFjdGFibGVzLnB1c2godGhpcyk7XG5cbiAgICB0aGlzLnNldChvcHRpb25zKTtcbn1cblxuSW50ZXJhY3RhYmxlLnByb3RvdHlwZSA9IHtcbiAgICBzZXRPbkV2ZW50czogZnVuY3Rpb24gKGFjdGlvbiwgcGhhc2VzKSB7XG4gICAgICAgIGlmIChhY3Rpb24gPT09ICdkcm9wJykge1xuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uZHJvcCkgICAgICAgICAgKSB7IHRoaXMub25kcm9wICAgICAgICAgICA9IHBoYXNlcy5vbmRyb3AgICAgICAgICAgOyB9XG4gICAgICAgICAgICBpZiAoaXNGdW5jdGlvbihwaGFzZXMub25kcm9wYWN0aXZhdGUpICApIHsgdGhpcy5vbmRyb3BhY3RpdmF0ZSAgID0gcGhhc2VzLm9uZHJvcGFjdGl2YXRlICA7IH1cbiAgICAgICAgICAgIGlmIChpc0Z1bmN0aW9uKHBoYXNlcy5vbmRyb3BkZWFjdGl2YXRlKSkgeyB0aGlzLm9uZHJvcGRlYWN0aXZhdGUgPSBwaGFzZXMub25kcm9wZGVhY3RpdmF0ZTsgfVxuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uZHJhZ2VudGVyKSAgICAgKSB7IHRoaXMub25kcmFnZW50ZXIgICAgICA9IHBoYXNlcy5vbmRyYWdlbnRlciAgICAgOyB9XG4gICAgICAgICAgICBpZiAoaXNGdW5jdGlvbihwaGFzZXMub25kcmFnbGVhdmUpICAgICApIHsgdGhpcy5vbmRyYWdsZWF2ZSAgICAgID0gcGhhc2VzLm9uZHJhZ2xlYXZlICAgICA7IH1cbiAgICAgICAgICAgIGlmIChpc0Z1bmN0aW9uKHBoYXNlcy5vbmRyb3Btb3ZlKSAgICAgICkgeyB0aGlzLm9uZHJvcG1vdmUgICAgICAgPSBwaGFzZXMub25kcm9wbW92ZSAgICAgIDsgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgYWN0aW9uID0gJ29uJyArIGFjdGlvbjtcblxuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uc3RhcnQpICAgICAgICkgeyB0aGlzW2FjdGlvbiArICdzdGFydCcgICAgICAgICBdID0gcGhhc2VzLm9uc3RhcnQgICAgICAgICA7IH1cbiAgICAgICAgICAgIGlmIChpc0Z1bmN0aW9uKHBoYXNlcy5vbm1vdmUpICAgICAgICApIHsgdGhpc1thY3Rpb24gKyAnbW92ZScgICAgICAgICAgXSA9IHBoYXNlcy5vbm1vdmUgICAgICAgICAgOyB9XG4gICAgICAgICAgICBpZiAoaXNGdW5jdGlvbihwaGFzZXMub25lbmQpICAgICAgICAgKSB7IHRoaXNbYWN0aW9uICsgJ2VuZCcgICAgICAgICAgIF0gPSBwaGFzZXMub25lbmQgICAgICAgICAgIDsgfVxuICAgICAgICAgICAgaWYgKGlzRnVuY3Rpb24ocGhhc2VzLm9uaW5lcnRpYXN0YXJ0KSkgeyB0aGlzW2FjdGlvbiArICdpbmVydGlhc3RhcnQnICBdID0gcGhhc2VzLm9uaW5lcnRpYXN0YXJ0ICA7IH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmRyYWdnYWJsZVxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBHZXRzIG9yIHNldHMgd2hldGhlciBkcmFnIGFjdGlvbnMgY2FuIGJlIHBlcmZvcm1lZCBvbiB0aGVcbiAgICAgKiBJbnRlcmFjdGFibGVcbiAgICAgKlxuICAgICA9IChib29sZWFuKSBJbmRpY2F0ZXMgaWYgdGhpcyBjYW4gYmUgdGhlIHRhcmdldCBvZiBkcmFnIGV2ZW50c1xuICAgICB8IHZhciBpc0RyYWdnYWJsZSA9IGludGVyYWN0KCd1bCBsaScpLmRyYWdnYWJsZSgpO1xuICAgICAqIG9yXG4gICAgIC0gb3B0aW9ucyAoYm9vbGVhbiB8IG9iamVjdCkgI29wdGlvbmFsIHRydWUvZmFsc2Ugb3IgQW4gb2JqZWN0IHdpdGggZXZlbnQgbGlzdGVuZXJzIHRvIGJlIGZpcmVkIG9uIGRyYWcgZXZlbnRzIChvYmplY3QgbWFrZXMgdGhlIEludGVyYWN0YWJsZSBkcmFnZ2FibGUpXG4gICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5kcmFnZ2FibGUoe1xuICAgICB8ICAgICBvbnN0YXJ0OiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICB8ICAgICBvbm1vdmUgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICB8ICAgICBvbmVuZCAgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICB8XG4gICAgIHwgICAgIC8vIHRoZSBheGlzIGluIHdoaWNoIHRoZSBmaXJzdCBtb3ZlbWVudCBtdXN0IGJlXG4gICAgIHwgICAgIC8vIGZvciB0aGUgZHJhZyBzZXF1ZW5jZSB0byBzdGFydFxuICAgICB8ICAgICAvLyAneHknIGJ5IGRlZmF1bHQgLSBhbnkgZGlyZWN0aW9uXG4gICAgIHwgICAgIGF4aXM6ICd4JyB8fCAneScgfHwgJ3h5JyxcbiAgICAgfFxuICAgICB8ICAgICAvLyBtYXggbnVtYmVyIG9mIGRyYWdzIHRoYXQgY2FuIGhhcHBlbiBjb25jdXJyZW50bHlcbiAgICAgfCAgICAgLy8gd2l0aCBlbGVtZW50cyBvZiB0aGlzIEludGVyYWN0YWJsZS4gSW5maW5pdHkgYnkgZGVmYXVsdFxuICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICB8XG4gICAgIHwgICAgIC8vIG1heCBudW1iZXIgb2YgZHJhZ3MgdGhhdCBjYW4gdGFyZ2V0IHRoZSBzYW1lIGVsZW1lbnQrSW50ZXJhY3RhYmxlXG4gICAgIHwgICAgIC8vIDEgYnkgZGVmYXVsdFxuICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAyXG4gICAgIHwgfSk7XG4gICAgIFxcKi9cbiAgICBkcmFnZ2FibGU6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgIGlmIChpc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyYWcuZW5hYmxlZCA9IG9wdGlvbnMuZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlOiB0cnVlO1xuICAgICAgICAgICAgdGhpcy5zZXRQZXJBY3Rpb24oJ2RyYWcnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIHRoaXMuc2V0T25FdmVudHMoJ2RyYWcnLCBvcHRpb25zKTtcblxuICAgICAgICAgICAgaWYgKC9eeCR8XnkkfF54eSQvLnRlc3Qob3B0aW9ucy5heGlzKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcmFnLmF4aXMgPSBvcHRpb25zLmF4aXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChvcHRpb25zLmF4aXMgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmRyYWcuYXhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJhZy5lbmFibGVkID0gb3B0aW9ucztcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmRyYWc7XG4gICAgfSxcblxuICAgIHNldFBlckFjdGlvbjogZnVuY3Rpb24gKGFjdGlvbiwgb3B0aW9ucykge1xuICAgICAgICAvLyBmb3IgYWxsIHRoZSBkZWZhdWx0IHBlci1hY3Rpb24gb3B0aW9uc1xuICAgICAgICBmb3IgKHZhciBvcHRpb24gaW4gb3B0aW9ucykge1xuICAgICAgICAgICAgLy8gaWYgdGhpcyBvcHRpb24gZXhpc3RzIGZvciB0aGlzIGFjdGlvblxuICAgICAgICAgICAgaWYgKG9wdGlvbiBpbiBkZWZhdWx0T3B0aW9uc1thY3Rpb25dKSB7XG4gICAgICAgICAgICAgICAgLy8gaWYgdGhlIG9wdGlvbiBpbiB0aGUgb3B0aW9ucyBhcmcgaXMgYW4gb2JqZWN0IHZhbHVlXG4gICAgICAgICAgICAgICAgaWYgKGlzT2JqZWN0KG9wdGlvbnNbb3B0aW9uXSkpIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gZHVwbGljYXRlIHRoZSBvYmplY3RcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXSA9IGV4dGVuZCh0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dIHx8IHt9LCBvcHRpb25zW29wdGlvbl0pO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChpc09iamVjdChkZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb25bb3B0aW9uXSkgJiYgJ2VuYWJsZWQnIGluIGRlZmF1bHRPcHRpb25zLnBlckFjdGlvbltvcHRpb25dKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dLmVuYWJsZWQgPSBvcHRpb25zW29wdGlvbl0uZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlIDogdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChpc0Jvb2wob3B0aW9uc1tvcHRpb25dKSAmJiBpc09iamVjdChkZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb25bb3B0aW9uXSkpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXS5lbmFibGVkID0gb3B0aW9uc1tvcHRpb25dO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChvcHRpb25zW29wdGlvbl0gIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgICAgICAgICAvLyBvciBpZiBpdCdzIG5vdCB1bmRlZmluZWQsIGRvIGEgcGxhaW4gYXNzaWdubWVudFxuICAgICAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dID0gb3B0aW9uc1tvcHRpb25dO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmRyb3B6b25lXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIGVsZW1lbnRzIGNhbiBiZSBkcm9wcGVkIG9udG8gdGhpc1xuICAgICAqIEludGVyYWN0YWJsZSB0byB0cmlnZ2VyIGRyb3AgZXZlbnRzXG4gICAgICpcbiAgICAgKiBEcm9wem9uZXMgY2FuIHJlY2VpdmUgdGhlIGZvbGxvd2luZyBldmVudHM6XG4gICAgICogIC0gYGRyb3BhY3RpdmF0ZWAgYW5kIGBkcm9wZGVhY3RpdmF0ZWAgd2hlbiBhbiBhY2NlcHRhYmxlIGRyYWcgc3RhcnRzIGFuZCBlbmRzXG4gICAgICogIC0gYGRyYWdlbnRlcmAgYW5kIGBkcmFnbGVhdmVgIHdoZW4gYSBkcmFnZ2FibGUgZW50ZXJzIGFuZCBsZWF2ZXMgdGhlIGRyb3B6b25lXG4gICAgICogIC0gYGRyYWdtb3ZlYCB3aGVuIGEgZHJhZ2dhYmxlIHRoYXQgaGFzIGVudGVyZWQgdGhlIGRyb3B6b25lIGlzIG1vdmVkXG4gICAgICogIC0gYGRyb3BgIHdoZW4gYSBkcmFnZ2FibGUgaXMgZHJvcHBlZCBpbnRvIHRoaXMgZHJvcHpvbmVcbiAgICAgKlxuICAgICAqICBVc2UgdGhlIGBhY2NlcHRgIG9wdGlvbiB0byBhbGxvdyBvbmx5IGVsZW1lbnRzIHRoYXQgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvciBlbGVtZW50LlxuICAgICAqXG4gICAgICogIFVzZSB0aGUgYG92ZXJsYXBgIG9wdGlvbiB0byBzZXQgaG93IGRyb3BzIGFyZSBjaGVja2VkIGZvci4gVGhlIGFsbG93ZWQgdmFsdWVzIGFyZTpcbiAgICAgKiAgIC0gYCdwb2ludGVyJ2AsIHRoZSBwb2ludGVyIG11c3QgYmUgb3ZlciB0aGUgZHJvcHpvbmUgKGRlZmF1bHQpXG4gICAgICogICAtIGAnY2VudGVyJ2AsIHRoZSBkcmFnZ2FibGUgZWxlbWVudCdzIGNlbnRlciBtdXN0IGJlIG92ZXIgdGhlIGRyb3B6b25lXG4gICAgICogICAtIGEgbnVtYmVyIGZyb20gMC0xIHdoaWNoIGlzIHRoZSBgKGludGVyc2VjdGlvbiBhcmVhKSAvIChkcmFnZ2FibGUgYXJlYSlgLlxuICAgICAqICAgICAgIGUuZy4gYDAuNWAgZm9yIGRyb3AgdG8gaGFwcGVuIHdoZW4gaGFsZiBvZiB0aGUgYXJlYSBvZiB0aGVcbiAgICAgKiAgICAgICBkcmFnZ2FibGUgaXMgb3ZlciB0aGUgZHJvcHpvbmVcbiAgICAgKlxuICAgICAtIG9wdGlvbnMgKGJvb2xlYW4gfCBvYmplY3QgfCBudWxsKSAjb3B0aW9uYWwgVGhlIG5ldyB2YWx1ZSB0byBiZSBzZXQuXG4gICAgIHwgaW50ZXJhY3QoJy5kcm9wJykuZHJvcHpvbmUoe1xuICAgICB8ICAgYWNjZXB0OiAnLmNhbi1kcm9wJyB8fCBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc2luZ2xlLWRyb3AnKSxcbiAgICAgfCAgIG92ZXJsYXA6ICdwb2ludGVyJyB8fCAnY2VudGVyJyB8fCB6ZXJvVG9PbmVcbiAgICAgfCB9XG4gICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIGRyb3B6b25lOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcbiAgICAgICAgICAgIHRoaXMuc2V0T25FdmVudHMoJ2Ryb3AnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIHRoaXMuYWNjZXB0KG9wdGlvbnMuYWNjZXB0KTtcblxuICAgICAgICAgICAgaWYgKC9eKHBvaW50ZXJ8Y2VudGVyKSQvLnRlc3Qob3B0aW9ucy5vdmVybGFwKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLm92ZXJsYXAgPSBvcHRpb25zLm92ZXJsYXA7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChpc051bWJlcihvcHRpb25zLm92ZXJsYXApKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3Aub3ZlcmxhcCA9IE1hdGgubWF4KE1hdGgubWluKDEsIG9wdGlvbnMub3ZlcmxhcCksIDApO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0Jvb2wob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcDtcbiAgICB9LFxuXG4gICAgZHJvcENoZWNrOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGRyYWdnYWJsZSwgZHJhZ2dhYmxlRWxlbWVudCwgZHJvcEVsZW1lbnQsIHJlY3QpIHtcbiAgICAgICAgdmFyIGRyb3BwZWQgPSBmYWxzZTtcblxuICAgICAgICAvLyBpZiB0aGUgZHJvcHpvbmUgaGFzIG5vIHJlY3QgKGVnLiBkaXNwbGF5OiBub25lKVxuICAgICAgICAvLyBjYWxsIHRoZSBjdXN0b20gZHJvcENoZWNrZXIgb3IganVzdCByZXR1cm4gZmFsc2VcbiAgICAgICAgaWYgKCEocmVjdCA9IHJlY3QgfHwgdGhpcy5nZXRSZWN0KGRyb3BFbGVtZW50KSkpIHtcbiAgICAgICAgICAgIHJldHVybiAodGhpcy5vcHRpb25zLmRyb3BDaGVja2VyXG4gICAgICAgICAgICAgICAgPyB0aGlzLm9wdGlvbnMuZHJvcENoZWNrZXIocG9pbnRlciwgZXZlbnQsIGRyb3BwZWQsIHRoaXMsIGRyb3BFbGVtZW50LCBkcmFnZ2FibGUsIGRyYWdnYWJsZUVsZW1lbnQpXG4gICAgICAgICAgICAgICAgOiBmYWxzZSk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZHJvcE92ZXJsYXAgPSB0aGlzLm9wdGlvbnMuZHJvcC5vdmVybGFwO1xuXG4gICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ3BvaW50ZXInKSB7XG4gICAgICAgICAgICB2YXIgcGFnZSA9IGdldFBhZ2VYWShwb2ludGVyKSxcbiAgICAgICAgICAgICAgICBvcmlnaW4gPSBnZXRPcmlnaW5YWShkcmFnZ2FibGUsIGRyYWdnYWJsZUVsZW1lbnQpLFxuICAgICAgICAgICAgICAgIGhvcml6b250YWwsXG4gICAgICAgICAgICAgICAgdmVydGljYWw7XG5cbiAgICAgICAgICAgIHBhZ2UueCArPSBvcmlnaW4ueDtcbiAgICAgICAgICAgIHBhZ2UueSArPSBvcmlnaW4ueTtcblxuICAgICAgICAgICAgaG9yaXpvbnRhbCA9IChwYWdlLnggPiByZWN0LmxlZnQpICYmIChwYWdlLnggPCByZWN0LnJpZ2h0KTtcbiAgICAgICAgICAgIHZlcnRpY2FsICAgPSAocGFnZS55ID4gcmVjdC50b3AgKSAmJiAocGFnZS55IDwgcmVjdC5ib3R0b20pO1xuXG4gICAgICAgICAgICBkcm9wcGVkID0gaG9yaXpvbnRhbCAmJiB2ZXJ0aWNhbDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBkcmFnUmVjdCA9IGRyYWdnYWJsZS5nZXRSZWN0KGRyYWdnYWJsZUVsZW1lbnQpO1xuXG4gICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ2NlbnRlcicpIHtcbiAgICAgICAgICAgIHZhciBjeCA9IGRyYWdSZWN0LmxlZnQgKyBkcmFnUmVjdC53aWR0aCAgLyAyLFxuICAgICAgICAgICAgICAgIGN5ID0gZHJhZ1JlY3QudG9wICArIGRyYWdSZWN0LmhlaWdodCAvIDI7XG5cbiAgICAgICAgICAgIGRyb3BwZWQgPSBjeCA+PSByZWN0LmxlZnQgJiYgY3ggPD0gcmVjdC5yaWdodCAmJiBjeSA+PSByZWN0LnRvcCAmJiBjeSA8PSByZWN0LmJvdHRvbTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc051bWJlcihkcm9wT3ZlcmxhcCkpIHtcbiAgICAgICAgICAgIHZhciBvdmVybGFwQXJlYSAgPSAoTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5yaWdodCAsIGRyYWdSZWN0LnJpZ2h0ICkgLSBNYXRoLm1heChyZWN0LmxlZnQsIGRyYWdSZWN0LmxlZnQpKVxuICAgICAgICAgICAgICAgICogTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5ib3R0b20sIGRyYWdSZWN0LmJvdHRvbSkgLSBNYXRoLm1heChyZWN0LnRvcCAsIGRyYWdSZWN0LnRvcCApKSksXG4gICAgICAgICAgICAgICAgb3ZlcmxhcFJhdGlvID0gb3ZlcmxhcEFyZWEgLyAoZHJhZ1JlY3Qud2lkdGggKiBkcmFnUmVjdC5oZWlnaHQpO1xuXG4gICAgICAgICAgICBkcm9wcGVkID0gb3ZlcmxhcFJhdGlvID49IGRyb3BPdmVybGFwO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlcikge1xuICAgICAgICAgICAgZHJvcHBlZCA9IHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlcihwb2ludGVyLCBkcm9wcGVkLCB0aGlzLCBkcm9wRWxlbWVudCwgZHJhZ2dhYmxlLCBkcmFnZ2FibGVFbGVtZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBkcm9wcGVkO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmRyb3BDaGVja2VyXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB0aGUgZnVuY3Rpb24gdXNlZCB0byBjaGVjayBpZiBhIGRyYWdnZWQgZWxlbWVudCBpc1xuICAgICAqIG92ZXIgdGhpcyBJbnRlcmFjdGFibGUuXG4gICAgICpcbiAgICAgLSBjaGVja2VyIChmdW5jdGlvbikgI29wdGlvbmFsIFRoZSBmdW5jdGlvbiB0aGF0IHdpbGwgYmUgY2FsbGVkIHdoZW4gY2hlY2tpbmcgZm9yIGEgZHJvcFxuICAgICA9IChGdW5jdGlvbiB8IEludGVyYWN0YWJsZSkgVGhlIGNoZWNrZXIgZnVuY3Rpb24gb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgKlxuICAgICAqIFRoZSBjaGVja2VyIGZ1bmN0aW9uIHRha2VzIHRoZSBmb2xsb3dpbmcgYXJndW1lbnRzOlxuICAgICAqXG4gICAgIC0gcG9pbnRlciAoVG91Y2ggfCBQb2ludGVyRXZlbnQgfCBNb3VzZUV2ZW50KSBUaGUgcG9pbnRlci9ldmVudCB0aGF0IGVuZHMgYSBkcmFnXG4gICAgIC0gZXZlbnQgKFRvdWNoRXZlbnQgfCBQb2ludGVyRXZlbnQgfCBNb3VzZUV2ZW50KSBUaGUgZXZlbnQgcmVsYXRlZCB0byB0aGUgcG9pbnRlclxuICAgICAtIGRyb3BwZWQgKGJvb2xlYW4pIFRoZSB2YWx1ZSBmcm9tIHRoZSBkZWZhdWx0IGRyb3AgY2hlY2tcbiAgICAgLSBkcm9wem9uZSAoSW50ZXJhY3RhYmxlKSBUaGUgZHJvcHpvbmUgaW50ZXJhY3RhYmxlXG4gICAgIC0gZHJvcEVsZW1lbnQgKEVsZW1lbnQpIFRoZSBkcm9wem9uZSBlbGVtZW50XG4gICAgIC0gZHJhZ2dhYmxlIChJbnRlcmFjdGFibGUpIFRoZSBJbnRlcmFjdGFibGUgYmVpbmcgZHJhZ2dlZFxuICAgICAtIGRyYWdnYWJsZUVsZW1lbnQgKEVsZW1lbnQpIFRoZSBhY3R1YWwgZWxlbWVudCB0aGF0J3MgYmVpbmcgZHJhZ2dlZFxuICAgICAqXG4gICAgID4gVXNhZ2U6XG4gICAgIHwgaW50ZXJhY3QodGFyZ2V0KVxuICAgICB8IC5kcm9wQ2hlY2tlcihmdW5jdGlvbihwb2ludGVyLCAgICAgICAgICAgLy8gVG91Y2gvUG9pbnRlckV2ZW50L01vdXNlRXZlbnRcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQsICAgICAgICAgICAgIC8vIFRvdWNoRXZlbnQvUG9pbnRlckV2ZW50L01vdXNlRXZlbnRcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJvcHBlZCwgICAgICAgICAgIC8vIHJlc3VsdCBvZiB0aGUgZGVmYXVsdCBjaGVja2VyXG4gICAgIHwgICAgICAgICAgICAgICAgICAgICAgIGRyb3B6b25lLCAgICAgICAgICAvLyBkcm9wem9uZSBJbnRlcmFjdGFibGVcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJvcEVsZW1lbnQsICAgICAgIC8vIGRyb3B6b25lIGVsZW1udFxuICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUsICAgICAgICAgLy8gZHJhZ2dhYmxlIEludGVyYWN0YWJsZVxuICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGVFbGVtZW50KSB7Ly8gZHJhZ2dhYmxlIGVsZW1lbnRcbiAgICAgfFxuICAgICB8ICAgcmV0dXJuIGRyb3BwZWQgJiYgZXZlbnQudGFyZ2V0Lmhhc0F0dHJpYnV0ZSgnYWxsb3ctZHJvcCcpO1xuICAgICB8IH1cbiAgICAgXFwqL1xuICAgIGRyb3BDaGVja2VyOiBmdW5jdGlvbiAoY2hlY2tlcikge1xuICAgICAgICBpZiAoaXNGdW5jdGlvbihjaGVja2VyKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3BDaGVja2VyID0gY2hlY2tlcjtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgIGRlbGV0ZSB0aGlzLm9wdGlvbnMuZ2V0UmVjdDtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmRyb3BDaGVja2VyO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmFjY2VwdFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBEZXByZWNhdGVkLiBhZGQgYW4gYGFjY2VwdGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZCB0b1xuICAgICAqIEBJbnRlcmFjdGFibGUuZHJvcHpvbmUgaW5zdGVhZC5cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB0aGUgRWxlbWVudCBvciBDU1Mgc2VsZWN0b3IgbWF0Y2ggdGhhdCB0aGlzXG4gICAgICogSW50ZXJhY3RhYmxlIGFjY2VwdHMgaWYgaXQgaXMgYSBkcm9wem9uZS5cbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChFbGVtZW50IHwgc3RyaW5nIHwgbnVsbCkgI29wdGlvbmFsXG4gICAgICogSWYgaXQgaXMgYW4gRWxlbWVudCwgdGhlbiBvbmx5IHRoYXQgZWxlbWVudCBjYW4gYmUgZHJvcHBlZCBpbnRvIHRoaXMgZHJvcHpvbmUuXG4gICAgICogSWYgaXQgaXMgYSBzdHJpbmcsIHRoZSBlbGVtZW50IGJlaW5nIGRyYWdnZWQgbXVzdCBtYXRjaCBpdCBhcyBhIHNlbGVjdG9yLlxuICAgICAqIElmIGl0IGlzIG51bGwsIHRoZSBhY2NlcHQgb3B0aW9ucyBpcyBjbGVhcmVkIC0gaXQgYWNjZXB0cyBhbnkgZWxlbWVudC5cbiAgICAgKlxuICAgICA9IChzdHJpbmcgfCBFbGVtZW50IHwgbnVsbCB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgYWNjZXB0IG9wdGlvbiBpZiBnaXZlbiBgdW5kZWZpbmVkYCBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgYWNjZXB0OiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgaWYgKGlzRWxlbWVudChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmFjY2VwdCA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHRlc3QgaWYgaXQgaXMgYSB2YWxpZCBDU1Mgc2VsZWN0b3JcbiAgICAgICAgaWYgKHRyeVNlbGVjdG9yKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0ID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0O1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcC5hY2NlcHQ7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUucmVzaXphYmxlXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB3aGV0aGVyIHJlc2l6ZSBhY3Rpb25zIGNhbiBiZSBwZXJmb3JtZWQgb24gdGhlXG4gICAgICogSW50ZXJhY3RhYmxlXG4gICAgICpcbiAgICAgPSAoYm9vbGVhbikgSW5kaWNhdGVzIGlmIHRoaXMgY2FuIGJlIHRoZSB0YXJnZXQgb2YgcmVzaXplIGVsZW1lbnRzXG4gICAgIHwgdmFyIGlzUmVzaXplYWJsZSA9IGludGVyYWN0KCdpbnB1dFt0eXBlPXRleHRdJykucmVzaXphYmxlKCk7XG4gICAgICogb3JcbiAgICAgLSBvcHRpb25zIChib29sZWFuIHwgb2JqZWN0KSAjb3B0aW9uYWwgdHJ1ZS9mYWxzZSBvciBBbiBvYmplY3Qgd2l0aCBldmVudCBsaXN0ZW5lcnMgdG8gYmUgZmlyZWQgb24gcmVzaXplIGV2ZW50cyAob2JqZWN0IG1ha2VzIHRoZSBJbnRlcmFjdGFibGUgcmVzaXphYmxlKVxuICAgICA9IChvYmplY3QpIFRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkucmVzaXphYmxlKHtcbiAgICAgfCAgICAgb25zdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgfCAgICAgb25tb3ZlIDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgfCAgICAgb25lbmQgIDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgfFxuICAgICB8ICAgICBlZGdlczoge1xuICAgICB8ICAgICAgIHRvcCAgIDogdHJ1ZSwgICAgICAgLy8gVXNlIHBvaW50ZXIgY29vcmRzIHRvIGNoZWNrIGZvciByZXNpemUuXG4gICAgIHwgICAgICAgbGVmdCAgOiBmYWxzZSwgICAgICAvLyBEaXNhYmxlIHJlc2l6aW5nIGZyb20gbGVmdCBlZGdlLlxuICAgICB8ICAgICAgIGJvdHRvbTogJy5yZXNpemUtcycsLy8gUmVzaXplIGlmIHBvaW50ZXIgdGFyZ2V0IG1hdGNoZXMgc2VsZWN0b3JcbiAgICAgfCAgICAgICByaWdodCA6IGhhbmRsZUVsICAgIC8vIFJlc2l6ZSBpZiBwb2ludGVyIHRhcmdldCBpcyB0aGUgZ2l2ZW4gRWxlbWVudFxuICAgICB8ICAgICB9LFxuICAgICB8XG4gICAgIHwgICAgIC8vIGEgdmFsdWUgb2YgJ25vbmUnIHdpbGwgbGltaXQgdGhlIHJlc2l6ZSByZWN0IHRvIGEgbWluaW11bSBvZiAweDBcbiAgICAgfCAgICAgLy8gJ25lZ2F0ZScgd2lsbCBhbGxvdyB0aGUgcmVjdCB0byBoYXZlIG5lZ2F0aXZlIHdpZHRoL2hlaWdodFxuICAgICB8ICAgICAvLyAncmVwb3NpdGlvbicgd2lsbCBrZWVwIHRoZSB3aWR0aC9oZWlnaHQgcG9zaXRpdmUgYnkgc3dhcHBpbmdcbiAgICAgfCAgICAgLy8gdGhlIHRvcCBhbmQgYm90dG9tIGVkZ2VzIGFuZC9vciBzd2FwcGluZyB0aGUgbGVmdCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgfCAgICAgaW52ZXJ0OiAnbm9uZScgfHwgJ25lZ2F0ZScgfHwgJ3JlcG9zaXRpb24nXG4gICAgIHxcbiAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgcmVzaXplcy5cbiAgICAgfCAgICAgLy8gU2VlIHRoZSBleHBsYW5hdGlvbiBpbiB0aGUgQEludGVyYWN0YWJsZS5kcmFnZ2FibGUgZXhhbXBsZVxuICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICB8IH0pO1xuICAgICBcXCovXG4gICAgcmVzaXphYmxlOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuZW5hYmxlZCA9IG9wdGlvbnMuZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlOiB0cnVlO1xuICAgICAgICAgICAgdGhpcy5zZXRQZXJBY3Rpb24oJ3Jlc2l6ZScsIG9wdGlvbnMpO1xuICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygncmVzaXplJywgb3B0aW9ucyk7XG5cbiAgICAgICAgICAgIGlmICgvXngkfF55JHxeeHkkLy50ZXN0KG9wdGlvbnMuYXhpcykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmF4aXMgPSBvcHRpb25zLmF4aXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChvcHRpb25zLmF4aXMgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmF4aXMgPSBkZWZhdWx0T3B0aW9ucy5yZXNpemUuYXhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGlzQm9vbChvcHRpb25zLnNxdWFyZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLnNxdWFyZSA9IG9wdGlvbnMuc3F1YXJlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICBpZiAoaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLnJlc2l6ZTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5zcXVhcmVSZXNpemVcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogRGVwcmVjYXRlZC4gQWRkIGEgYHNxdWFyZTogdHJ1ZSB8fCBmYWxzZWAgcHJvcGVydHkgdG8gQEludGVyYWN0YWJsZS5yZXNpemFibGUgaW5zdGVhZFxuICAgICAqXG4gICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgcmVzaXppbmcgaXMgZm9yY2VkIDE6MSBhc3BlY3RcbiAgICAgKlxuICAgICA9IChib29sZWFuKSBDdXJyZW50IHNldHRpbmdcbiAgICAgKlxuICAgICAqIG9yXG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsXG4gICAgID0gKG9iamVjdCkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIHNxdWFyZVJlc2l6ZTogZnVuY3Rpb24gKG5ld1ZhbHVlKSB7XG4gICAgICAgIGlmIChpc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLnNxdWFyZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChuZXdWYWx1ZSA9PT0gbnVsbCkge1xuICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5yZXNpemUuc3F1YXJlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMucmVzaXplLnNxdWFyZTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5nZXN0dXJhYmxlXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgb3Igc2V0cyB3aGV0aGVyIG11bHRpdG91Y2ggZ2VzdHVyZXMgY2FuIGJlIHBlcmZvcm1lZCBvbiB0aGVcbiAgICAgKiBJbnRlcmFjdGFibGUncyBlbGVtZW50XG4gICAgICpcbiAgICAgPSAoYm9vbGVhbikgSW5kaWNhdGVzIGlmIHRoaXMgY2FuIGJlIHRoZSB0YXJnZXQgb2YgZ2VzdHVyZSBldmVudHNcbiAgICAgfCB2YXIgaXNHZXN0dXJlYWJsZSA9IGludGVyYWN0KGVsZW1lbnQpLmdlc3R1cmFibGUoKTtcbiAgICAgKiBvclxuICAgICAtIG9wdGlvbnMgKGJvb2xlYW4gfCBvYmplY3QpICNvcHRpb25hbCB0cnVlL2ZhbHNlIG9yIEFuIG9iamVjdCB3aXRoIGV2ZW50IGxpc3RlbmVycyB0byBiZSBmaXJlZCBvbiBnZXN0dXJlIGV2ZW50cyAobWFrZXMgdGhlIEludGVyYWN0YWJsZSBnZXN0dXJhYmxlKVxuICAgICA9IChvYmplY3QpIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuZ2VzdHVyYWJsZSh7XG4gICAgIHwgICAgIG9uc3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgIHwgICAgIG9ubW92ZSA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgIHwgICAgIG9uZW5kICA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgIHxcbiAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgZ2VzdHVyZXMuXG4gICAgIHwgICAgIC8vIFNlZSB0aGUgZXhwbGFuYXRpb24gaW4gQEludGVyYWN0YWJsZS5kcmFnZ2FibGUgZXhhbXBsZVxuICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICB8IH0pO1xuICAgICBcXCovXG4gICAgZ2VzdHVyYWJsZTogZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgICAgICAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICB0aGlzLm9wdGlvbnMuZ2VzdHVyZS5lbmFibGVkID0gb3B0aW9ucy5lbmFibGVkID09PSBmYWxzZT8gZmFsc2U6IHRydWU7XG4gICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbignZ2VzdHVyZScsIG9wdGlvbnMpO1xuICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygnZ2VzdHVyZScsIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0Jvb2wob3B0aW9ucykpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5nZXN0dXJlLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZ2VzdHVyZTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5hdXRvU2Nyb2xsXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKipcbiAgICAgKiBEZXByZWNhdGVkLiBBZGQgYW4gYGF1dG9zY3JvbGxgIHByb3BlcnR5IHRvIHRoZSBvcHRpb25zIG9iamVjdFxuICAgICAqIHBhc3NlZCB0byBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSBvciBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBpbnN0ZWFkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZHJhZ2dpbmcgYW5kIHJlc2l6aW5nIG5lYXIgdGhlIGVkZ2VzIG9mIHRoZVxuICAgICAqIHdpbmRvdy9jb250YWluZXIgdHJpZ2dlciBhdXRvU2Nyb2xsIGZvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAqXG4gICAgID0gKG9iamVjdCkgT2JqZWN0IHdpdGggYXV0b1Njcm9sbCBwcm9wZXJ0aWVzXG4gICAgICpcbiAgICAgKiBvclxuICAgICAqXG4gICAgIC0gb3B0aW9ucyAob2JqZWN0IHwgYm9vbGVhbikgI29wdGlvbmFsXG4gICAgICogb3B0aW9ucyBjYW4gYmU6XG4gICAgICogLSBhbiBvYmplY3Qgd2l0aCBtYXJnaW4sIGRpc3RhbmNlIGFuZCBpbnRlcnZhbCBwcm9wZXJ0aWVzLFxuICAgICAqIC0gdHJ1ZSBvciBmYWxzZSB0byBlbmFibGUgb3IgZGlzYWJsZSBhdXRvU2Nyb2xsIG9yXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIGF1dG9TY3JvbGw6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgIGlmIChpc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgb3B0aW9ucyA9IGV4dGVuZCh7IGFjdGlvbnM6IFsnZHJhZycsICdyZXNpemUnXX0sIG9wdGlvbnMpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgb3B0aW9ucyA9IHsgYWN0aW9uczogWydkcmFnJywgJ3Jlc2l6ZSddLCBlbmFibGVkOiBvcHRpb25zIH07XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5zZXRPcHRpb25zKCdhdXRvU2Nyb2xsJywgb3B0aW9ucyk7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuc25hcFxuICAgICBbIG1ldGhvZCBdXG4gICAgICoqXG4gICAgICogRGVwcmVjYXRlZC4gQWRkIGEgYHNuYXBgIHByb3BlcnR5IHRvIHRoZSBvcHRpb25zIG9iamVjdCBwYXNzZWRcbiAgICAgKiB0byBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSBvciBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBpbnN0ZWFkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIGlmIGFuZCBob3cgYWN0aW9uIGNvb3JkaW5hdGVzIGFyZSBzbmFwcGVkLiBCeVxuICAgICAqIGRlZmF1bHQsIHNuYXBwaW5nIGlzIHJlbGF0aXZlIHRvIHRoZSBwb2ludGVyIGNvb3JkaW5hdGVzLiBZb3UgY2FuXG4gICAgICogY2hhbmdlIHRoaXMgYnkgc2V0dGluZyB0aGVcbiAgICAgKiBbYGVsZW1lbnRPcmlnaW5gXShodHRwczovL2dpdGh1Yi5jb20vdGF5ZS9pbnRlcmFjdC5qcy9wdWxsLzcyKS5cbiAgICAgKipcbiAgICAgPSAoYm9vbGVhbiB8IG9iamVjdCkgYGZhbHNlYCBpZiBzbmFwIGlzIGRpc2FibGVkOyBvYmplY3Qgd2l0aCBzbmFwIHByb3BlcnRpZXMgaWYgc25hcCBpcyBlbmFibGVkXG4gICAgICoqXG4gICAgICogb3JcbiAgICAgKipcbiAgICAgLSBvcHRpb25zIChvYmplY3QgfCBib29sZWFuIHwgbnVsbCkgI29wdGlvbmFsXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgPiBVc2FnZVxuICAgICB8IGludGVyYWN0KGRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoJyN0aGluZycpKS5zbmFwKHtcbiAgICAgfCAgICAgdGFyZ2V0czogW1xuICAgICB8ICAgICAgICAgLy8gc25hcCB0byB0aGlzIHNwZWNpZmljIHBvaW50XG4gICAgIHwgICAgICAgICB7XG4gICAgIHwgICAgICAgICAgICAgeDogMTAwLFxuICAgICB8ICAgICAgICAgICAgIHk6IDEwMCxcbiAgICAgfCAgICAgICAgICAgICByYW5nZTogMjVcbiAgICAgfCAgICAgICAgIH0sXG4gICAgIHwgICAgICAgICAvLyBnaXZlIHRoaXMgZnVuY3Rpb24gdGhlIHggYW5kIHkgcGFnZSBjb29yZHMgYW5kIHNuYXAgdG8gdGhlIG9iamVjdCByZXR1cm5lZFxuICAgICB8ICAgICAgICAgZnVuY3Rpb24gKHgsIHkpIHtcbiAgICAgfCAgICAgICAgICAgICByZXR1cm4ge1xuICAgICB8ICAgICAgICAgICAgICAgICB4OiB4LFxuICAgICB8ICAgICAgICAgICAgICAgICB5OiAoNzUgKyA1MCAqIE1hdGguc2luKHggKiAwLjA0KSksXG4gICAgIHwgICAgICAgICAgICAgICAgIHJhbmdlOiA0MFxuICAgICB8ICAgICAgICAgICAgIH07XG4gICAgIHwgICAgICAgICB9LFxuICAgICB8ICAgICAgICAgLy8gY3JlYXRlIGEgZnVuY3Rpb24gdGhhdCBzbmFwcyB0byBhIGdyaWRcbiAgICAgfCAgICAgICAgIGludGVyYWN0LmNyZWF0ZVNuYXBHcmlkKHtcbiAgICAgfCAgICAgICAgICAgICB4OiA1MCxcbiAgICAgfCAgICAgICAgICAgICB5OiA1MCxcbiAgICAgfCAgICAgICAgICAgICByYW5nZTogMTAsICAgICAgICAgICAgICAvLyBvcHRpb25hbFxuICAgICB8ICAgICAgICAgICAgIG9mZnNldDogeyB4OiA1LCB5OiAxMCB9IC8vIG9wdGlvbmFsXG4gICAgIHwgICAgICAgICB9KVxuICAgICB8ICAgICBdLFxuICAgICB8ICAgICAvLyBkbyBub3Qgc25hcCBkdXJpbmcgbm9ybWFsIG1vdmVtZW50LlxuICAgICB8ICAgICAvLyBJbnN0ZWFkLCB0cmlnZ2VyIG9ubHkgb25lIHNuYXBwZWQgbW92ZSBldmVudFxuICAgICB8ICAgICAvLyBpbW1lZGlhdGVseSBiZWZvcmUgdGhlIGVuZCBldmVudC5cbiAgICAgfCAgICAgZW5kT25seTogdHJ1ZSxcbiAgICAgfFxuICAgICB8ICAgICByZWxhdGl2ZVBvaW50czogW1xuICAgICB8ICAgICAgICAgeyB4OiAwLCB5OiAwIH0sICAvLyBzbmFwIHJlbGF0aXZlIHRvIHRoZSB0b3AgbGVmdCBvZiB0aGUgZWxlbWVudFxuICAgICB8ICAgICAgICAgeyB4OiAxLCB5OiAxIH0sICAvLyBhbmQgYWxzbyB0byB0aGUgYm90dG9tIHJpZ2h0XG4gICAgIHwgICAgIF0sXG4gICAgIHxcbiAgICAgfCAgICAgLy8gb2Zmc2V0IHRoZSBzbmFwIHRhcmdldCBjb29yZGluYXRlc1xuICAgICB8ICAgICAvLyBjYW4gYmUgYW4gb2JqZWN0IHdpdGggeC95IG9yICdzdGFydENvb3JkcydcbiAgICAgfCAgICAgb2Zmc2V0OiB7IHg6IDUwLCB5OiA1MCB9XG4gICAgIHwgICB9XG4gICAgIHwgfSk7XG4gICAgIFxcKi9cbiAgICBzbmFwOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICB2YXIgcmV0ID0gdGhpcy5zZXRPcHRpb25zKCdzbmFwJywgb3B0aW9ucyk7XG5cbiAgICAgICAgaWYgKHJldCA9PT0gdGhpcykgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgIHJldHVybiByZXQuZHJhZztcbiAgICB9LFxuXG4gICAgc2V0T3B0aW9uczogZnVuY3Rpb24gKG9wdGlvbiwgb3B0aW9ucykge1xuICAgICAgICB2YXIgYWN0aW9ucyA9IG9wdGlvbnMgJiYgaXNBcnJheShvcHRpb25zLmFjdGlvbnMpXG4gICAgICAgICAgICA/IG9wdGlvbnMuYWN0aW9uc1xuICAgICAgICAgICAgOiBbJ2RyYWcnXTtcblxuICAgICAgICB2YXIgaTtcblxuICAgICAgICBpZiAoaXNPYmplY3Qob3B0aW9ucykgfHwgaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgYWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHZhciBhY3Rpb24gPSAvcmVzaXplLy50ZXN0KGFjdGlvbnNbaV0pPyAncmVzaXplJyA6IGFjdGlvbnNbaV07XG5cbiAgICAgICAgICAgICAgICBpZiAoIWlzT2JqZWN0KHRoaXMub3B0aW9uc1thY3Rpb25dKSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICAgICAgdmFyIHRoaXNPcHRpb24gPSB0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dO1xuXG4gICAgICAgICAgICAgICAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgICAgIGV4dGVuZCh0aGlzT3B0aW9uLCBvcHRpb25zKTtcbiAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi5lbmFibGVkID0gb3B0aW9ucy5lbmFibGVkID09PSBmYWxzZT8gZmFsc2U6IHRydWU7XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKG9wdGlvbiA9PT0gJ3NuYXAnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAodGhpc09wdGlvbi5tb2RlID09PSAnZ3JpZCcpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnRhcmdldHMgPSBbXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0LmNyZWF0ZVNuYXBHcmlkKGV4dGVuZCh7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvZmZzZXQ6IHRoaXNPcHRpb24uZ3JpZE9mZnNldCB8fCB7IHg6IDAsIHk6IDAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LCB0aGlzT3B0aW9uLmdyaWQgfHwge30pKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIF07XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICBlbHNlIGlmICh0aGlzT3B0aW9uLm1vZGUgPT09ICdhbmNob3InKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi50YXJnZXRzID0gdGhpc09wdGlvbi5hbmNob3JzO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSBpZiAodGhpc09wdGlvbi5tb2RlID09PSAncGF0aCcpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnRhcmdldHMgPSB0aGlzT3B0aW9uLnBhdGhzO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoJ2VsZW1lbnRPcmlnaW4nIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnJlbGF0aXZlUG9pbnRzID0gW29wdGlvbnMuZWxlbWVudE9yaWdpbl07XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAoaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXNPcHRpb24uZW5hYmxlZCA9IG9wdGlvbnM7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciByZXQgPSB7fSxcbiAgICAgICAgICAgIGFsbEFjdGlvbnMgPSBbJ2RyYWcnLCAncmVzaXplJywgJ2dlc3R1cmUnXTtcblxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgYWxsQWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaWYgKG9wdGlvbiBpbiBkZWZhdWx0T3B0aW9uc1thbGxBY3Rpb25zW2ldXSkge1xuICAgICAgICAgICAgICAgIHJldFthbGxBY3Rpb25zW2ldXSA9IHRoaXMub3B0aW9uc1thbGxBY3Rpb25zW2ldXVtvcHRpb25dO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHJldDtcbiAgICB9LFxuXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmluZXJ0aWFcbiAgICAgWyBtZXRob2QgXVxuICAgICAqKlxuICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhbiBgaW5lcnRpYWAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZFxuICAgICAqIHRvIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIG9yIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWQuXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgaWYgYW5kIGhvdyBldmVudHMgY29udGludWUgdG8gcnVuIGFmdGVyIHRoZSBwb2ludGVyIGlzIHJlbGVhc2VkXG4gICAgICoqXG4gICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIGBmYWxzZWAgaWYgaW5lcnRpYSBpcyBkaXNhYmxlZDsgYG9iamVjdGAgd2l0aCBpbmVydGlhIHByb3BlcnRpZXMgaWYgaW5lcnRpYSBpcyBlbmFibGVkXG4gICAgICoqXG4gICAgICogb3JcbiAgICAgKipcbiAgICAgLSBvcHRpb25zIChvYmplY3QgfCBib29sZWFuIHwgbnVsbCkgI29wdGlvbmFsXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgPiBVc2FnZVxuICAgICB8IC8vIGVuYWJsZSBhbmQgdXNlIGRlZmF1bHQgc2V0dGluZ3NcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pbmVydGlhKHRydWUpO1xuICAgICB8XG4gICAgIHwgLy8gZW5hYmxlIGFuZCB1c2UgY3VzdG9tIHNldHRpbmdzXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYSh7XG4gICAgIHwgICAgIC8vIHZhbHVlIGdyZWF0ZXIgdGhhbiAwXG4gICAgIHwgICAgIC8vIGhpZ2ggdmFsdWVzIHNsb3cgdGhlIG9iamVjdCBkb3duIG1vcmUgcXVpY2tseVxuICAgICB8ICAgICByZXNpc3RhbmNlICAgICA6IDE2LFxuICAgICB8XG4gICAgIHwgICAgIC8vIHRoZSBtaW5pbXVtIGxhdW5jaCBzcGVlZCAocGl4ZWxzIHBlciBzZWNvbmQpIHRoYXQgcmVzdWx0cyBpbiBpbmVydGlhIHN0YXJ0XG4gICAgIHwgICAgIG1pblNwZWVkICAgICAgIDogMjAwLFxuICAgICB8XG4gICAgIHwgICAgIC8vIGluZXJ0aWEgd2lsbCBzdG9wIHdoZW4gdGhlIG9iamVjdCBzbG93cyBkb3duIHRvIHRoaXMgc3BlZWRcbiAgICAgfCAgICAgZW5kU3BlZWQgICAgICAgOiAyMCxcbiAgICAgfFxuICAgICB8ICAgICAvLyBib29sZWFuOyBzaG91bGQgYWN0aW9ucyBiZSByZXN1bWVkIHdoZW4gdGhlIHBvaW50ZXIgZ29lcyBkb3duIGR1cmluZyBpbmVydGlhXG4gICAgIHwgICAgIGFsbG93UmVzdW1lICAgIDogdHJ1ZSxcbiAgICAgfFxuICAgICB8ICAgICAvLyBib29sZWFuOyBzaG91bGQgdGhlIGp1bXAgd2hlbiByZXN1bWluZyBmcm9tIGluZXJ0aWEgYmUgaWdub3JlZCBpbiBldmVudC5keC9keVxuICAgICB8ICAgICB6ZXJvUmVzdW1lRGVsdGE6IGZhbHNlLFxuICAgICB8XG4gICAgIHwgICAgIC8vIGlmIHNuYXAvcmVzdHJpY3QgYXJlIHNldCB0byBiZSBlbmRPbmx5IGFuZCBpbmVydGlhIGlzIGVuYWJsZWQsIHJlbGVhc2luZ1xuICAgICB8ICAgICAvLyB0aGUgcG9pbnRlciB3aXRob3V0IHRyaWdnZXJpbmcgaW5lcnRpYSB3aWxsIGFuaW1hdGUgZnJvbSB0aGUgcmVsZWFzZVxuICAgICB8ICAgICAvLyBwb2ludCB0byB0aGUgc25hcGVkL3Jlc3RyaWN0ZWQgcG9pbnQgaW4gdGhlIGdpdmVuIGFtb3VudCBvZiB0aW1lIChtcylcbiAgICAgfCAgICAgc21vb3RoRW5kRHVyYXRpb246IDMwMCxcbiAgICAgfFxuICAgICB8ICAgICAvLyBhbiBhcnJheSBvZiBhY3Rpb24gdHlwZXMgdGhhdCBjYW4gaGF2ZSBpbmVydGlhIChubyBnZXN0dXJlKVxuICAgICB8ICAgICBhY3Rpb25zICAgICAgICA6IFsnZHJhZycsICdyZXNpemUnXVxuICAgICB8IH0pO1xuICAgICB8XG4gICAgIHwgLy8gcmVzZXQgY3VzdG9tIHNldHRpbmdzIGFuZCB1c2UgYWxsIGRlZmF1bHRzXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYShudWxsKTtcbiAgICAgXFwqL1xuICAgIGluZXJ0aWE6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgIHZhciByZXQgPSB0aGlzLnNldE9wdGlvbnMoJ2luZXJ0aWEnLCBvcHRpb25zKTtcblxuICAgICAgICBpZiAocmV0ID09PSB0aGlzKSB7IHJldHVybiB0aGlzOyB9XG5cbiAgICAgICAgcmV0dXJuIHJldC5kcmFnO1xuICAgIH0sXG5cbiAgICBnZXRBY3Rpb246IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIGFjdGlvbiA9IHRoaXMuZGVmYXVsdEFjdGlvbkNoZWNrZXIocG9pbnRlciwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpO1xuXG4gICAgICAgIGlmICh0aGlzLm9wdGlvbnMuYWN0aW9uQ2hlY2tlcikge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyKHBvaW50ZXIsIGV2ZW50LCBhY3Rpb24sIHRoaXMsIGVsZW1lbnQsIGludGVyYWN0aW9uKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBhY3Rpb247XG4gICAgfSxcblxuICAgIGRlZmF1bHRBY3Rpb25DaGVja2VyOiBkZWZhdWx0QWN0aW9uQ2hlY2tlcixcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuYWN0aW9uQ2hlY2tlclxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBHZXRzIG9yIHNldHMgdGhlIGZ1bmN0aW9uIHVzZWQgdG8gY2hlY2sgYWN0aW9uIHRvIGJlIHBlcmZvcm1lZCBvblxuICAgICAqIHBvaW50ZXJEb3duXG4gICAgICpcbiAgICAgLSBjaGVja2VyIChmdW5jdGlvbiB8IG51bGwpICNvcHRpb25hbCBBIGZ1bmN0aW9uIHdoaWNoIHRha2VzIGEgcG9pbnRlciBldmVudCwgZGVmYXVsdEFjdGlvbiBzdHJpbmcsIGludGVyYWN0YWJsZSwgZWxlbWVudCBhbmQgaW50ZXJhY3Rpb24gYXMgcGFyYW1ldGVycyBhbmQgcmV0dXJucyBhbiBvYmplY3Qgd2l0aCBuYW1lIHByb3BlcnR5ICdkcmFnJyAncmVzaXplJyBvciAnZ2VzdHVyZScgYW5kIG9wdGlvbmFsbHkgYW4gYGVkZ2VzYCBvYmplY3Qgd2l0aCBib29sZWFuICd0b3AnLCAnbGVmdCcsICdib3R0b20nIGFuZCByaWdodCBwcm9wcy5cbiAgICAgPSAoRnVuY3Rpb24gfCBJbnRlcmFjdGFibGUpIFRoZSBjaGVja2VyIGZ1bmN0aW9uIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICpcbiAgICAgfCBpbnRlcmFjdCgnLnJlc2l6ZS1kcmFnJylcbiAgICAgfCAgIC5yZXNpemFibGUodHJ1ZSlcbiAgICAgfCAgIC5kcmFnZ2FibGUodHJ1ZSlcbiAgICAgfCAgIC5hY3Rpb25DaGVja2VyKGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGludGVyYWN0aW9uKSB7XG4gICAgIHxcbiAgICAgfCAgIGlmIChpbnRlcmFjdC5tYXRjaGVzU2VsZWN0b3IoZXZlbnQudGFyZ2V0LCAnLmRyYWctaGFuZGxlJykge1xuICAgICB8ICAgICAvLyBmb3JjZSBkcmFnIHdpdGggaGFuZGxlIHRhcmdldFxuICAgICB8ICAgICBhY3Rpb24ubmFtZSA9IGRyYWc7XG4gICAgIHwgICB9XG4gICAgIHwgICBlbHNlIHtcbiAgICAgfCAgICAgLy8gcmVzaXplIGZyb20gdGhlIHRvcCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgfCAgICAgYWN0aW9uLm5hbWUgID0gJ3Jlc2l6ZSc7XG4gICAgIHwgICAgIGFjdGlvbi5lZGdlcyA9IHsgdG9wOiB0cnVlLCByaWdodDogdHJ1ZSB9O1xuICAgICB8ICAgfVxuICAgICB8XG4gICAgIHwgICByZXR1cm4gYWN0aW9uO1xuICAgICB8IH0pO1xuICAgICBcXCovXG4gICAgYWN0aW9uQ2hlY2tlcjogZnVuY3Rpb24gKGNoZWNrZXIpIHtcbiAgICAgICAgaWYgKGlzRnVuY3Rpb24oY2hlY2tlcikpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyID0gY2hlY2tlcjtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoY2hlY2tlciA9PT0gbnVsbCkge1xuICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuYWN0aW9uQ2hlY2tlcjtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5nZXRSZWN0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFRoZSBkZWZhdWx0IGZ1bmN0aW9uIHRvIGdldCBhbiBJbnRlcmFjdGFibGVzIGJvdW5kaW5nIHJlY3QuIENhbiBiZVxuICAgICAqIG92ZXJyaWRkZW4gdXNpbmcgQEludGVyYWN0YWJsZS5yZWN0Q2hlY2tlci5cbiAgICAgKlxuICAgICAtIGVsZW1lbnQgKEVsZW1lbnQpICNvcHRpb25hbCBUaGUgZWxlbWVudCB0byBtZWFzdXJlLlxuICAgICA9IChvYmplY3QpIFRoZSBvYmplY3QncyBib3VuZGluZyByZWN0YW5nbGUuXG4gICAgIG8ge1xuICAgICBvICAgICB0b3AgICA6IDAsXG4gICAgIG8gICAgIGxlZnQgIDogMCxcbiAgICAgbyAgICAgYm90dG9tOiAwLFxuICAgICBvICAgICByaWdodCA6IDAsXG4gICAgIG8gICAgIHdpZHRoIDogMCxcbiAgICAgbyAgICAgaGVpZ2h0OiAwXG4gICAgIG8gfVxuICAgICBcXCovXG4gICAgZ2V0UmVjdDogZnVuY3Rpb24gcmVjdENoZWNrIChlbGVtZW50KSB7XG4gICAgICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IHRoaXMuX2VsZW1lbnQ7XG5cbiAgICAgICAgaWYgKHRoaXMuc2VsZWN0b3IgJiYgIShpc0VsZW1lbnQoZWxlbWVudCkpKSB7XG4gICAgICAgICAgICBlbGVtZW50ID0gdGhpcy5fY29udGV4dC5xdWVyeVNlbGVjdG9yKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGdldEVsZW1lbnRSZWN0KGVsZW1lbnQpO1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLnJlY3RDaGVja2VyXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgZnVuY3Rpb24gdXNlZCB0byBjYWxjdWxhdGUgdGhlIGludGVyYWN0YWJsZSdzXG4gICAgICogZWxlbWVudCdzIHJlY3RhbmdsZVxuICAgICAqXG4gICAgIC0gY2hlY2tlciAoZnVuY3Rpb24pICNvcHRpb25hbCBBIGZ1bmN0aW9uIHdoaWNoIHJldHVybnMgdGhpcyBJbnRlcmFjdGFibGUncyBib3VuZGluZyByZWN0YW5nbGUuIFNlZSBASW50ZXJhY3RhYmxlLmdldFJlY3RcbiAgICAgPSAoZnVuY3Rpb24gfCBvYmplY3QpIFRoZSBjaGVja2VyIGZ1bmN0aW9uIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIFxcKi9cbiAgICByZWN0Q2hlY2tlcjogZnVuY3Rpb24gKGNoZWNrZXIpIHtcbiAgICAgICAgaWYgKGlzRnVuY3Rpb24oY2hlY2tlcikpIHtcbiAgICAgICAgICAgIHRoaXMuZ2V0UmVjdCA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgIGRlbGV0ZSB0aGlzLm9wdGlvbnMuZ2V0UmVjdDtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5nZXRSZWN0O1xuICAgIH0sXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLnN0eWxlQ3Vyc29yXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIHRoZSBhY3Rpb24gdGhhdCB3b3VsZCBiZSBwZXJmb3JtZWQgd2hlbiB0aGVcbiAgICAgKiBtb3VzZSBvbiB0aGUgZWxlbWVudCBhcmUgY2hlY2tlZCBvbiBgbW91c2Vtb3ZlYCBzbyB0aGF0IHRoZSBjdXJzb3JcbiAgICAgKiBtYXkgYmUgc3R5bGVkIGFwcHJvcHJpYXRlbHlcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChib29sZWFuKSAjb3B0aW9uYWxcbiAgICAgPSAoYm9vbGVhbiB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgc3R5bGVDdXJzb3I6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoaXNCb29sKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuc3R5bGVDdXJzb3I7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUucHJldmVudERlZmF1bHRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgdG8gcHJldmVudCB0aGUgYnJvd3NlcidzIGRlZmF1bHQgYmVoYXZpb3VyXG4gICAgICogaW4gcmVzcG9uc2UgdG8gcG9pbnRlciBldmVudHMuIENhbiBiZSBzZXQgdG86XG4gICAgICogIC0gYCdhbHdheXMnYCB0byBhbHdheXMgcHJldmVudFxuICAgICAqICAtIGAnbmV2ZXInYCB0byBuZXZlciBwcmV2ZW50XG4gICAgICogIC0gYCdhdXRvJ2AgdG8gbGV0IGludGVyYWN0LmpzIHRyeSB0byBkZXRlcm1pbmUgd2hhdCB3b3VsZCBiZSBiZXN0XG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoc3RyaW5nKSAjb3B0aW9uYWwgYHRydWVgLCBgZmFsc2VgIG9yIGAnYXV0bydgXG4gICAgID0gKHN0cmluZyB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgcHJldmVudERlZmF1bHQ6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoL14oYWx3YXlzfG5ldmVyfGF1dG8pJC8udGVzdChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5wcmV2ZW50RGVmYXVsdCA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNCb29sKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLnByZXZlbnREZWZhdWx0ID0gbmV3VmFsdWU/ICdhbHdheXMnIDogJ25ldmVyJztcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5wcmV2ZW50RGVmYXVsdDtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5vcmlnaW5cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogR2V0cyBvciBzZXRzIHRoZSBvcmlnaW4gb2YgdGhlIEludGVyYWN0YWJsZSdzIGVsZW1lbnQuICBUaGUgeCBhbmQgeVxuICAgICAqIG9mIHRoZSBvcmlnaW4gd2lsbCBiZSBzdWJ0cmFjdGVkIGZyb20gYWN0aW9uIGV2ZW50IGNvb3JkaW5hdGVzLlxuICAgICAqXG4gICAgIC0gb3JpZ2luIChvYmplY3QgfCBzdHJpbmcpICNvcHRpb25hbCBBbiBvYmplY3QgZWcuIHsgeDogMCwgeTogMCB9IG9yIHN0cmluZyAncGFyZW50JywgJ3NlbGYnIG9yIGFueSBDU1Mgc2VsZWN0b3JcbiAgICAgKiBPUlxuICAgICAtIG9yaWdpbiAoRWxlbWVudCkgI29wdGlvbmFsIEFuIEhUTUwgb3IgU1ZHIEVsZW1lbnQgd2hvc2UgcmVjdCB3aWxsIGJlIHVzZWRcbiAgICAgKipcbiAgICAgPSAob2JqZWN0KSBUaGUgY3VycmVudCBvcmlnaW4gb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIG9yaWdpbjogZnVuY3Rpb24gKG5ld1ZhbHVlKSB7XG4gICAgICAgIGlmICh0cnlTZWxlY3RvcihuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5vcmlnaW4gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGlzT2JqZWN0KG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgdGhpcy5vcHRpb25zLm9yaWdpbiA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLm9yaWdpbjtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5kZWx0YVNvdXJjZVxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgdGhlIG1vdXNlIGNvb3JkaW5hdGUgdHlwZXMgdXNlZCB0byBjYWxjdWxhdGUgdGhlXG4gICAgICogbW92ZW1lbnQgb2YgdGhlIHBvaW50ZXIuXG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoc3RyaW5nKSAjb3B0aW9uYWwgVXNlICdjbGllbnQnIGlmIHlvdSB3aWxsIGJlIHNjcm9sbGluZyB3aGlsZSBpbnRlcmFjdGluZzsgVXNlICdwYWdlJyBpZiB5b3Ugd2FudCBhdXRvU2Nyb2xsIHRvIHdvcmtcbiAgICAgPSAoc3RyaW5nIHwgb2JqZWN0KSBUaGUgY3VycmVudCBkZWx0YVNvdXJjZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICBcXCovXG4gICAgZGVsdGFTb3VyY2U6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAobmV3VmFsdWUgPT09ICdwYWdlJyB8fCBuZXdWYWx1ZSA9PT0gJ2NsaWVudCcpIHtcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kZWx0YVNvdXJjZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZGVsdGFTb3VyY2U7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUucmVzdHJpY3RcbiAgICAgWyBtZXRob2QgXVxuICAgICAqKlxuICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhIGByZXN0cmljdGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZCB0b1xuICAgICAqIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlLCBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBvciBASW50ZXJhY3RhYmxlLmdlc3R1cmFibGUgaW5zdGVhZC5cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgcmVjdGFuZ2xlcyB3aXRoaW4gd2hpY2ggYWN0aW9ucyBvbiB0aGlzXG4gICAgICogaW50ZXJhY3RhYmxlIChhZnRlciBzbmFwIGNhbGN1bGF0aW9ucykgYXJlIHJlc3RyaWN0ZWQuIEJ5IGRlZmF1bHQsXG4gICAgICogcmVzdHJpY3RpbmcgaXMgcmVsYXRpdmUgdG8gdGhlIHBvaW50ZXIgY29vcmRpbmF0ZXMuIFlvdSBjYW4gY2hhbmdlXG4gICAgICogdGhpcyBieSBzZXR0aW5nIHRoZVxuICAgICAqIFtgZWxlbWVudFJlY3RgXShodHRwczovL2dpdGh1Yi5jb20vdGF5ZS9pbnRlcmFjdC5qcy9wdWxsLzcyKS5cbiAgICAgKipcbiAgICAgLSBvcHRpb25zIChvYmplY3QpICNvcHRpb25hbCBhbiBvYmplY3Qgd2l0aCBrZXlzIGRyYWcsIHJlc2l6ZSwgYW5kL29yIGdlc3R1cmUgd2hvc2UgdmFsdWVzIGFyZSByZWN0cywgRWxlbWVudHMsIENTUyBzZWxlY3RvcnMsIG9yICdwYXJlbnQnIG9yICdzZWxmJ1xuICAgICA9IChvYmplY3QpIFRoZSBjdXJyZW50IHJlc3RyaWN0aW9ucyBvYmplY3Qgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgKipcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5yZXN0cmljdCh7XG4gICAgIHwgICAgIC8vIHRoZSByZWN0IHdpbGwgYmUgYGludGVyYWN0LmdldEVsZW1lbnRSZWN0KGVsZW1lbnQucGFyZW50Tm9kZSlgXG4gICAgIHwgICAgIGRyYWc6IGVsZW1lbnQucGFyZW50Tm9kZSxcbiAgICAgfFxuICAgICB8ICAgICAvLyB4IGFuZCB5IGFyZSByZWxhdGl2ZSB0byB0aGUgdGhlIGludGVyYWN0YWJsZSdzIG9yaWdpblxuICAgICB8ICAgICByZXNpemU6IHsgeDogMTAwLCB5OiAxMDAsIHdpZHRoOiAyMDAsIGhlaWdodDogMjAwIH1cbiAgICAgfCB9KVxuICAgICB8XG4gICAgIHwgaW50ZXJhY3QoJy5kcmFnZ2FibGUnKS5yZXN0cmljdCh7XG4gICAgIHwgICAgIC8vIHRoZSByZWN0IHdpbGwgYmUgdGhlIHNlbGVjdGVkIGVsZW1lbnQncyBwYXJlbnRcbiAgICAgfCAgICAgZHJhZzogJ3BhcmVudCcsXG4gICAgIHxcbiAgICAgfCAgICAgLy8gZG8gbm90IHJlc3RyaWN0IGR1cmluZyBub3JtYWwgbW92ZW1lbnQuXG4gICAgIHwgICAgIC8vIEluc3RlYWQsIHRyaWdnZXIgb25seSBvbmUgcmVzdHJpY3RlZCBtb3ZlIGV2ZW50XG4gICAgIHwgICAgIC8vIGltbWVkaWF0ZWx5IGJlZm9yZSB0aGUgZW5kIGV2ZW50LlxuICAgICB8ICAgICBlbmRPbmx5OiB0cnVlLFxuICAgICB8XG4gICAgIHwgICAgIC8vIGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL3B1bGwvNzIjaXNzdWUtNDE4MTM0OTNcbiAgICAgfCAgICAgZWxlbWVudFJlY3Q6IHsgdG9wOiAwLCBsZWZ0OiAwLCBib3R0b206IDEsIHJpZ2h0OiAxIH1cbiAgICAgfCB9KTtcbiAgICAgXFwqL1xuICAgIHJlc3RyaWN0OiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoIWlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcy5zZXRPcHRpb25zKCdyZXN0cmljdCcsIG9wdGlvbnMpO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIGFjdGlvbnMgPSBbJ2RyYWcnLCAncmVzaXplJywgJ2dlc3R1cmUnXSxcbiAgICAgICAgICAgIHJldDtcblxuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHZhciBhY3Rpb24gPSBhY3Rpb25zW2ldO1xuXG4gICAgICAgICAgICBpZiAoYWN0aW9uIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICB2YXIgcGVyQWN0aW9uID0gZXh0ZW5kKHtcbiAgICAgICAgICAgICAgICAgICAgYWN0aW9uczogW2FjdGlvbl0sXG4gICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uOiBvcHRpb25zW2FjdGlvbl1cbiAgICAgICAgICAgICAgICB9LCBvcHRpb25zKTtcblxuICAgICAgICAgICAgICAgIHJldCA9IHRoaXMuc2V0T3B0aW9ucygncmVzdHJpY3QnLCBwZXJBY3Rpb24pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHJldDtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5jb250ZXh0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEdldHMgdGhlIHNlbGVjdG9yIGNvbnRleHQgTm9kZSBvZiB0aGUgSW50ZXJhY3RhYmxlLiBUaGUgZGVmYXVsdCBpcyBgd2luZG93LmRvY3VtZW50YC5cbiAgICAgKlxuICAgICA9IChOb2RlKSBUaGUgY29udGV4dCBOb2RlIG9mIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICoqXG4gICAgIFxcKi9cbiAgICBjb250ZXh0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLl9jb250ZXh0O1xuICAgIH0sXG5cbiAgICBfY29udGV4dDogZG9jdW1lbnQsXG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlLmlnbm9yZUZyb21cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogSWYgdGhlIHRhcmdldCBvZiB0aGUgYG1vdXNlZG93bmAsIGBwb2ludGVyZG93bmAgb3IgYHRvdWNoc3RhcnRgXG4gICAgICogZXZlbnQgb3IgYW55IG9mIGl0J3MgcGFyZW50cyBtYXRjaCB0aGUgZ2l2ZW4gQ1NTIHNlbGVjdG9yIG9yXG4gICAgICogRWxlbWVudCwgbm8gZHJhZy9yZXNpemUvZ2VzdHVyZSBpcyBzdGFydGVkLlxuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKHN0cmluZyB8IEVsZW1lbnQgfCBudWxsKSAjb3B0aW9uYWwgYSBDU1Mgc2VsZWN0b3Igc3RyaW5nLCBhbiBFbGVtZW50IG9yIGBudWxsYCB0byBub3QgaWdub3JlIGFueSBlbGVtZW50c1xuICAgICA9IChzdHJpbmcgfCBFbGVtZW50IHwgb2JqZWN0KSBUaGUgY3VycmVudCBpZ25vcmVGcm9tIHZhbHVlIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICoqXG4gICAgIHwgaW50ZXJhY3QoZWxlbWVudCwgeyBpZ25vcmVGcm9tOiBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbm8tYWN0aW9uJykgfSk7XG4gICAgIHwgLy8gb3JcbiAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pZ25vcmVGcm9tKCdpbnB1dCwgdGV4dGFyZWEsIGEnKTtcbiAgICAgXFwqL1xuICAgIGlnbm9yZUZyb206IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAodHJ5U2VsZWN0b3IobmV3VmFsdWUpKSB7ICAgICAgICAgICAgLy8gQ1NTIHNlbGVjdG9yIHRvIG1hdGNoIGV2ZW50LnRhcmdldFxuICAgICAgICAgICAgdGhpcy5vcHRpb25zLmlnbm9yZUZyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGlzRWxlbWVudChuZXdWYWx1ZSkpIHsgICAgICAgICAgICAgIC8vIHNwZWNpZmljIGVsZW1lbnRcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5pZ25vcmVGcm9tID0gbmV3VmFsdWU7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuaWdub3JlRnJvbTtcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5hbGxvd0Zyb21cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogQSBkcmFnL3Jlc2l6ZS9nZXN0dXJlIGlzIHN0YXJ0ZWQgb25seSBJZiB0aGUgdGFyZ2V0IG9mIHRoZVxuICAgICAqIGBtb3VzZWRvd25gLCBgcG9pbnRlcmRvd25gIG9yIGB0b3VjaHN0YXJ0YCBldmVudCBvciBhbnkgb2YgaXQnc1xuICAgICAqIHBhcmVudHMgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvciBFbGVtZW50LlxuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKHN0cmluZyB8IEVsZW1lbnQgfCBudWxsKSAjb3B0aW9uYWwgYSBDU1Mgc2VsZWN0b3Igc3RyaW5nLCBhbiBFbGVtZW50IG9yIGBudWxsYCB0byBhbGxvdyBmcm9tIGFueSBlbGVtZW50XG4gICAgID0gKHN0cmluZyB8IEVsZW1lbnQgfCBvYmplY3QpIFRoZSBjdXJyZW50IGFsbG93RnJvbSB2YWx1ZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAqKlxuICAgICB8IGludGVyYWN0KGVsZW1lbnQsIHsgYWxsb3dGcm9tOiBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZHJhZy1oYW5kbGUnKSB9KTtcbiAgICAgfCAvLyBvclxuICAgICB8IGludGVyYWN0KGVsZW1lbnQpLmFsbG93RnJvbSgnLmhhbmRsZScpO1xuICAgICBcXCovXG4gICAgYWxsb3dGcm9tOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgaWYgKHRyeVNlbGVjdG9yKG5ld1ZhbHVlKSkgeyAgICAgICAgICAgIC8vIENTUyBzZWxlY3RvciB0byBtYXRjaCBldmVudC50YXJnZXRcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5hbGxvd0Zyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGlzRWxlbWVudChuZXdWYWx1ZSkpIHsgICAgICAgICAgICAgIC8vIHNwZWNpZmljIGVsZW1lbnRcbiAgICAgICAgICAgIHRoaXMub3B0aW9ucy5hbGxvd0Zyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hbGxvd0Zyb207XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuZWxlbWVudFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBJZiB0aGlzIGlzIG5vdCBhIHNlbGVjdG9yIEludGVyYWN0YWJsZSwgaXQgcmV0dXJucyB0aGUgZWxlbWVudCB0aGlzXG4gICAgICogaW50ZXJhY3RhYmxlIHJlcHJlc2VudHNcbiAgICAgKlxuICAgICA9IChFbGVtZW50KSBIVE1MIC8gU1ZHIEVsZW1lbnRcbiAgICAgXFwqL1xuICAgIGVsZW1lbnQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuX2VsZW1lbnQ7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUuZmlyZVxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBDYWxscyBsaXN0ZW5lcnMgZm9yIHRoZSBnaXZlbiBJbnRlcmFjdEV2ZW50IHR5cGUgYm91bmQgZ2xvYmFsbHlcbiAgICAgKiBhbmQgZGlyZWN0bHkgdG8gdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgKlxuICAgICAtIGlFdmVudCAoSW50ZXJhY3RFdmVudCkgVGhlIEludGVyYWN0RXZlbnQgb2JqZWN0IHRvIGJlIGZpcmVkIG9uIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIGZpcmU6IGZ1bmN0aW9uIChpRXZlbnQpIHtcbiAgICAgICAgaWYgKCEoaUV2ZW50ICYmIGlFdmVudC50eXBlKSB8fCAhY29udGFpbnMoZXZlbnRUeXBlcywgaUV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBsaXN0ZW5lcnMsXG4gICAgICAgICAgICBpLFxuICAgICAgICAgICAgbGVuLFxuICAgICAgICAgICAgb25FdmVudCA9ICdvbicgKyBpRXZlbnQudHlwZSxcbiAgICAgICAgICAgIGZ1bmNOYW1lID0gJyc7XG5cbiAgICAgICAgLy8gSW50ZXJhY3RhYmxlI29uKCkgbGlzdGVuZXJzXG4gICAgICAgIGlmIChpRXZlbnQudHlwZSBpbiB0aGlzLl9pRXZlbnRzKSB7XG4gICAgICAgICAgICBsaXN0ZW5lcnMgPSB0aGlzLl9pRXZlbnRzW2lFdmVudC50eXBlXTtcblxuICAgICAgICAgICAgZm9yIChpID0gMCwgbGVuID0gbGlzdGVuZXJzLmxlbmd0aDsgaSA8IGxlbiAmJiAhaUV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgZnVuY05hbWUgPSBsaXN0ZW5lcnNbaV0ubmFtZTtcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnNbaV0oaUV2ZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGludGVyYWN0YWJsZS5vbmV2ZW50IGxpc3RlbmVyXG4gICAgICAgIGlmIChpc0Z1bmN0aW9uKHRoaXNbb25FdmVudF0pKSB7XG4gICAgICAgICAgICBmdW5jTmFtZSA9IHRoaXNbb25FdmVudF0ubmFtZTtcbiAgICAgICAgICAgIHRoaXNbb25FdmVudF0oaUV2ZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGludGVyYWN0Lm9uKCkgbGlzdGVuZXJzXG4gICAgICAgIGlmIChpRXZlbnQudHlwZSBpbiBnbG9iYWxFdmVudHMgJiYgKGxpc3RlbmVycyA9IGdsb2JhbEV2ZW50c1tpRXZlbnQudHlwZV0pKSAge1xuXG4gICAgICAgICAgICBmb3IgKGkgPSAwLCBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoOyBpIDwgbGVuICYmICFpRXZlbnQuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkOyBpKyspIHtcbiAgICAgICAgICAgICAgICBmdW5jTmFtZSA9IGxpc3RlbmVyc1tpXS5uYW1lO1xuICAgICAgICAgICAgICAgIGxpc3RlbmVyc1tpXShpRXZlbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUub25cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogQmluZHMgYSBsaXN0ZW5lciBmb3IgYW4gSW50ZXJhY3RFdmVudCBvciBET00gZXZlbnQuXG4gICAgICpcbiAgICAgLSBldmVudFR5cGUgIChzdHJpbmcgfCBhcnJheSB8IG9iamVjdCkgVGhlIHR5cGVzIG9mIGV2ZW50cyB0byBsaXN0ZW4gZm9yXG4gICAgIC0gbGlzdGVuZXIgICAoZnVuY3Rpb24pIFRoZSBmdW5jdGlvbiB0byBiZSBjYWxsZWQgb24gdGhlIGdpdmVuIGV2ZW50KHMpXG4gICAgIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgYWRkRXZlbnRMaXN0ZW5lclxuICAgICA9IChvYmplY3QpIFRoaXMgSW50ZXJhY3RhYmxlXG4gICAgIFxcKi9cbiAgICBvbjogZnVuY3Rpb24gKGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgdmFyIGk7XG5cbiAgICAgICAgaWYgKGlzU3RyaW5nKGV2ZW50VHlwZSkgJiYgZXZlbnRUeXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gZXZlbnRUeXBlLnRyaW0oKS5zcGxpdCgvICsvKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0FycmF5KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBldmVudFR5cGUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9uKGV2ZW50VHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc09iamVjdChldmVudFR5cGUpKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBwcm9wIGluIGV2ZW50VHlwZSkge1xuICAgICAgICAgICAgICAgIHRoaXMub24ocHJvcCwgZXZlbnRUeXBlW3Byb3BdLCBsaXN0ZW5lcik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGV2ZW50VHlwZSA9PT0gJ3doZWVsJykge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gd2hlZWxFdmVudDtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGNvbnZlcnQgdG8gYm9vbGVhblxuICAgICAgICB1c2VDYXB0dXJlID0gdXNlQ2FwdHVyZT8gdHJ1ZTogZmFsc2U7XG5cbiAgICAgICAgaWYgKGNvbnRhaW5zKGV2ZW50VHlwZXMsIGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIC8vIGlmIHRoaXMgdHlwZSBvZiBldmVudCB3YXMgbmV2ZXIgYm91bmQgdG8gdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgICAgIGlmICghKGV2ZW50VHlwZSBpbiB0aGlzLl9pRXZlbnRzKSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX2lFdmVudHNbZXZlbnRUeXBlXSA9IFtsaXN0ZW5lcl07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLl9pRXZlbnRzW2V2ZW50VHlwZV0ucHVzaChsaXN0ZW5lcik7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gZGVsZWdhdGVkIGV2ZW50IGZvciBzZWxlY3RvclxuICAgICAgICBlbHNlIGlmICh0aGlzLnNlbGVjdG9yKSB7XG4gICAgICAgICAgICBpZiAoIWRlbGVnYXRlZEV2ZW50c1tldmVudFR5cGVdKSB7XG4gICAgICAgICAgICAgICAgZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0gPSB7XG4gICAgICAgICAgICAgICAgICAgIHNlbGVjdG9yczogW10sXG4gICAgICAgICAgICAgICAgICAgIGNvbnRleHRzIDogW10sXG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyczogW11cbiAgICAgICAgICAgICAgICB9O1xuXG4gICAgICAgICAgICAgICAgLy8gYWRkIGRlbGVnYXRlIGxpc3RlbmVyIGZ1bmN0aW9uc1xuICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBkb2N1bWVudHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZChkb2N1bWVudHNbaV0sIGV2ZW50VHlwZSwgZGVsZWdhdGVMaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jdW1lbnRzW2ldLCBldmVudFR5cGUsIGRlbGVnYXRlVXNlQ2FwdHVyZSwgdHJ1ZSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgZGVsZWdhdGVkID0gZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0sXG4gICAgICAgICAgICAgICAgaW5kZXg7XG5cbiAgICAgICAgICAgIGZvciAoaW5kZXggPSBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCAtIDE7IGluZGV4ID49IDA7IGluZGV4LS0pIHtcbiAgICAgICAgICAgICAgICBpZiAoZGVsZWdhdGVkLnNlbGVjdG9yc1tpbmRleF0gPT09IHRoaXMuc2VsZWN0b3JcbiAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2luZGV4XSA9PT0gdGhpcy5fY29udGV4dCkge1xuICAgICAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbmRleCA9PT0gLTEpIHtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGRlbGVnYXRlZC5zZWxlY3RvcnMubGVuZ3RoO1xuXG4gICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5wdXNoKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5jb250ZXh0cyAucHVzaCh0aGlzLl9jb250ZXh0KTtcbiAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQubGlzdGVuZXJzLnB1c2goW10pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBrZWVwIGxpc3RlbmVyIGFuZCB1c2VDYXB0dXJlIGZsYWdcbiAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnNbaW5kZXhdLnB1c2goW2xpc3RlbmVyLCB1c2VDYXB0dXJlXSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUub2ZmXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJlbW92ZXMgYW4gSW50ZXJhY3RFdmVudCBvciBET00gZXZlbnQgbGlzdGVuZXJcbiAgICAgKlxuICAgICAtIGV2ZW50VHlwZSAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGxpc3RlbmVyIGZ1bmN0aW9uIHRvIGJlIHJlbW92ZWRcbiAgICAgLSB1c2VDYXB0dXJlIChib29sZWFuKSAjb3B0aW9uYWwgdXNlQ2FwdHVyZSBmbGFnIGZvciByZW1vdmVFdmVudExpc3RlbmVyXG4gICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgXFwqL1xuICAgIG9mZjogZnVuY3Rpb24gKGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgdmFyIGk7XG5cbiAgICAgICAgaWYgKGlzU3RyaW5nKGV2ZW50VHlwZSkgJiYgZXZlbnRUeXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICAgICAgZXZlbnRUeXBlID0gZXZlbnRUeXBlLnRyaW0oKS5zcGxpdCgvICsvKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChpc0FycmF5KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBldmVudFR5cGUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9mZihldmVudFR5cGVbaV0sIGxpc3RlbmVyLCB1c2VDYXB0dXJlKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoaXNPYmplY3QoZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgZm9yICh2YXIgcHJvcCBpbiBldmVudFR5cGUpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9mZihwcm9wLCBldmVudFR5cGVbcHJvcF0sIGxpc3RlbmVyKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZXZlbnRMaXN0LFxuICAgICAgICAgICAgaW5kZXggPSAtMTtcblxuICAgICAgICAvLyBjb252ZXJ0IHRvIGJvb2xlYW5cbiAgICAgICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgICAgIGlmIChldmVudFR5cGUgPT09ICd3aGVlbCcpIHtcbiAgICAgICAgICAgIGV2ZW50VHlwZSA9IHdoZWVsRXZlbnQ7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBpZiBpdCBpcyBhbiBhY3Rpb24gZXZlbnQgdHlwZVxuICAgICAgICBpZiAoY29udGFpbnMoZXZlbnRUeXBlcywgZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgZXZlbnRMaXN0ID0gdGhpcy5faUV2ZW50c1tldmVudFR5cGVdO1xuXG4gICAgICAgICAgICBpZiAoZXZlbnRMaXN0ICYmIChpbmRleCA9IGluZGV4T2YoZXZlbnRMaXN0LCBsaXN0ZW5lcikpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgIHRoaXMuX2lFdmVudHNbZXZlbnRUeXBlXS5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIGRlbGVnYXRlZCBldmVudFxuICAgICAgICBlbHNlIGlmICh0aGlzLnNlbGVjdG9yKSB7XG4gICAgICAgICAgICB2YXIgZGVsZWdhdGVkID0gZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0sXG4gICAgICAgICAgICAgICAgbWF0Y2hGb3VuZCA9IGZhbHNlO1xuXG4gICAgICAgICAgICBpZiAoIWRlbGVnYXRlZCkgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgICAgICAvLyBjb3VudCBmcm9tIGxhc3QgaW5kZXggb2YgZGVsZWdhdGVkIHRvIDBcbiAgICAgICAgICAgIGZvciAoaW5kZXggPSBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCAtIDE7IGluZGV4ID49IDA7IGluZGV4LS0pIHtcbiAgICAgICAgICAgICAgICAvLyBsb29rIGZvciBtYXRjaGluZyBzZWxlY3RvciBhbmQgY29udGV4dCBOb2RlXG4gICAgICAgICAgICAgICAgaWYgKGRlbGVnYXRlZC5zZWxlY3RvcnNbaW5kZXhdID09PSB0aGlzLnNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgICYmIGRlbGVnYXRlZC5jb250ZXh0c1tpbmRleF0gPT09IHRoaXMuX2NvbnRleHQpIHtcblxuICAgICAgICAgICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gZGVsZWdhdGVkLmxpc3RlbmVyc1tpbmRleF07XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gZWFjaCBpdGVtIG9mIHRoZSBsaXN0ZW5lcnMgYXJyYXkgaXMgYW4gYXJyYXk6IFtmdW5jdGlvbiwgdXNlQ2FwdHVyZUZsYWddXG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IGxpc3RlbmVycy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGZuID0gbGlzdGVuZXJzW2ldWzBdLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHVzZUNhcCA9IGxpc3RlbmVyc1tpXVsxXTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgLy8gY2hlY2sgaWYgdGhlIGxpc3RlbmVyIGZ1bmN0aW9ucyBhbmQgdXNlQ2FwdHVyZSBmbGFncyBtYXRjaFxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGZuID09PSBsaXN0ZW5lciAmJiB1c2VDYXAgPT09IHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgdGhlIGxpc3RlbmVyIGZyb20gdGhlIGFycmF5IG9mIGxpc3RlbmVyc1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zcGxpY2UoaSwgMSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBpZiBhbGwgbGlzdGVuZXJzIGZvciB0aGlzIGludGVyYWN0YWJsZSBoYXZlIGJlZW4gcmVtb3ZlZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHJlbW92ZSB0aGUgaW50ZXJhY3RhYmxlIGZyb20gdGhlIGRlbGVnYXRlZCBhcnJheXNcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoIWxpc3RlbmVycy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnMuc3BsaWNlKGluZGV4LCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgZGVsZWdhdGUgZnVuY3Rpb24gZnJvbSBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fY29udGV4dCwgZXZlbnRUeXBlLCBkZWxlZ2F0ZUxpc3RlbmVyKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRzLnJlbW92ZSh0aGlzLl9jb250ZXh0LCBldmVudFR5cGUsIGRlbGVnYXRlVXNlQ2FwdHVyZSwgdHJ1ZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gcmVtb3ZlIHRoZSBhcnJheXMgaWYgdGhleSBhcmUgZW1wdHlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0gPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gb25seSByZW1vdmUgb25lIGxpc3RlbmVyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0Y2hGb3VuZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBpZiAobWF0Y2hGb3VuZCkgeyBicmVhazsgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICAvLyByZW1vdmUgbGlzdGVuZXIgZnJvbSB0aGlzIEludGVyYXRhYmxlJ3MgZWxlbWVudFxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fZWxlbWVudCwgZXZlbnRUeXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gdGhpcztcbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0YWJsZS5zZXRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmVzZXQgdGhlIG9wdGlvbnMgb2YgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgLSBvcHRpb25zIChvYmplY3QpIFRoZSBuZXcgc2V0dGluZ3MgdG8gYXBwbHlcbiAgICAgPSAob2JqZWN0KSBUaGlzIEludGVyYWN0YWJsd1xuICAgICBcXCovXG4gICAgc2V0OiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICBpZiAoIWlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICBvcHRpb25zID0ge307XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLm9wdGlvbnMgPSBleHRlbmQoe30sIGRlZmF1bHRPcHRpb25zLmJhc2UpO1xuXG4gICAgICAgIHZhciBpLFxuICAgICAgICAgICAgYWN0aW9ucyA9IFsnZHJhZycsICdkcm9wJywgJ3Jlc2l6ZScsICdnZXN0dXJlJ10sXG4gICAgICAgICAgICBtZXRob2RzID0gWydkcmFnZ2FibGUnLCAnZHJvcHpvbmUnLCAncmVzaXphYmxlJywgJ2dlc3R1cmFibGUnXSxcbiAgICAgICAgICAgIHBlckFjdGlvbnMgPSBleHRlbmQoZXh0ZW5kKHt9LCBkZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb24pLCBvcHRpb25zW2FjdGlvbl0gfHwge30pO1xuXG4gICAgICAgIGZvciAoaSA9IDA7IGkgPCBhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgYWN0aW9uID0gYWN0aW9uc1tpXTtcblxuICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl0gPSBleHRlbmQoe30sIGRlZmF1bHRPcHRpb25zW2FjdGlvbl0pO1xuXG4gICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbihhY3Rpb24sIHBlckFjdGlvbnMpO1xuXG4gICAgICAgICAgICB0aGlzW21ldGhvZHNbaV1dKG9wdGlvbnNbYWN0aW9uXSk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc2V0dGluZ3MgPSBbXG4gICAgICAgICAgICAnYWNjZXB0JywgJ2FjdGlvbkNoZWNrZXInLCAnYWxsb3dGcm9tJywgJ2RlbHRhU291cmNlJyxcbiAgICAgICAgICAgICdkcm9wQ2hlY2tlcicsICdpZ25vcmVGcm9tJywgJ29yaWdpbicsICdwcmV2ZW50RGVmYXVsdCcsXG4gICAgICAgICAgICAncmVjdENoZWNrZXInXG4gICAgICAgIF07XG5cbiAgICAgICAgZm9yIChpID0gMCwgbGVuID0gc2V0dGluZ3MubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIHZhciBzZXR0aW5nID0gc2V0dGluZ3NbaV07XG5cbiAgICAgICAgICAgIHRoaXMub3B0aW9uc1tzZXR0aW5nXSA9IGRlZmF1bHRPcHRpb25zLmJhc2Vbc2V0dGluZ107XG5cbiAgICAgICAgICAgIGlmIChzZXR0aW5nIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICB0aGlzW3NldHRpbmddKG9wdGlvbnNbc2V0dGluZ10pO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfSxcblxuICAgIC8qXFxcbiAgICAgKiBJbnRlcmFjdGFibGUudW5zZXRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmVtb3ZlIHRoaXMgaW50ZXJhY3RhYmxlIGZyb20gdGhlIGxpc3Qgb2YgaW50ZXJhY3RhYmxlcyBhbmQgcmVtb3ZlXG4gICAgICogaXQncyBkcmFnLCBkcm9wLCByZXNpemUgYW5kIGdlc3R1cmUgY2FwYWJpbGl0aWVzXG4gICAgICpcbiAgICAgPSAob2JqZWN0KSBAaW50ZXJhY3RcbiAgICAgXFwqL1xuICAgIHVuc2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fZWxlbWVudCwgJ2FsbCcpO1xuXG4gICAgICAgIGlmICghaXNTdHJpbmcodGhpcy5zZWxlY3RvcikpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcywgJ2FsbCcpO1xuICAgICAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5zdHlsZUN1cnNvcikge1xuICAgICAgICAgICAgICAgIHRoaXMuX2VsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyByZW1vdmUgZGVsZWdhdGVkIGV2ZW50c1xuICAgICAgICAgICAgZm9yICh2YXIgdHlwZSBpbiBkZWxlZ2F0ZWRFdmVudHMpIHtcbiAgICAgICAgICAgICAgICB2YXIgZGVsZWdhdGVkID0gZGVsZWdhdGVkRXZlbnRzW3R5cGVdO1xuXG4gICAgICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2ldID09PSB0aGlzLnNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgICAgICAmJiBkZWxlZ2F0ZWQuY29udGV4dHNbaV0gPT09IHRoaXMuX2NvbnRleHQpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5zcGxpY2UoaSwgMSk7XG4gICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnMuc3BsaWNlKGksIDEpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgdGhlIGFycmF5cyBpZiB0aGV5IGFyZSBlbXB0eVxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZEV2ZW50c1t0eXBlXSA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIHR5cGUsIGRlbGVnYXRlTGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIHR5cGUsIGRlbGVnYXRlVXNlQ2FwdHVyZSwgdHJ1ZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5kcm9wem9uZShmYWxzZSk7XG5cbiAgICAgICAgaW50ZXJhY3RhYmxlcy5zcGxpY2UoaW5kZXhPZihpbnRlcmFjdGFibGVzLCB0aGlzKSwgMSk7XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cbn07XG5cbmZ1bmN0aW9uIHdhcm5PbmNlIChtZXRob2QsIG1lc3NhZ2UpIHtcbiAgICB2YXIgd2FybmVkID0gZmFsc2U7XG5cbiAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoIXdhcm5lZCkge1xuICAgICAgICAgICAgd2luZG93LmNvbnNvbGUud2FybihtZXNzYWdlKTtcbiAgICAgICAgICAgIHdhcm5lZCA9IHRydWU7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gbWV0aG9kLmFwcGx5KHRoaXMsIGFyZ3VtZW50cyk7XG4gICAgfTtcbn1cblxuSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5zbmFwID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5zbmFwLFxuICAgICdJbnRlcmFjdGFibGUjc25hcCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBzbmFwcGluZyBhdCBodHRwOi8vaW50ZXJhY3Rqcy5pby9kb2NzL3NuYXBwaW5nJyk7XG5JbnRlcmFjdGFibGUucHJvdG90eXBlLnJlc3RyaWN0ID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5yZXN0cmljdCxcbiAgICAnSW50ZXJhY3RhYmxlI3Jlc3RyaWN0IGlzIGRlcHJlY2F0ZWQuIFNlZSB0aGUgbmV3IGRvY3VtZW50YXRpb24gZm9yIHJlc3RpY3RpbmcgYXQgaHR0cDovL2ludGVyYWN0anMuaW8vZG9jcy9yZXN0cmljdGlvbicpO1xuSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5pbmVydGlhID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5pbmVydGlhLFxuICAgICdJbnRlcmFjdGFibGUjaW5lcnRpYSBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBpbmVydGlhIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvaW5lcnRpYScpO1xuSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5hdXRvU2Nyb2xsID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5hdXRvU2Nyb2xsLFxuICAgICdJbnRlcmFjdGFibGUjYXV0b1Njcm9sbCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBhdXRvU2Nyb2xsIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI2F1dG9zY3JvbGwnKTtcbkludGVyYWN0YWJsZS5wcm90b3R5cGUuc3F1YXJlUmVzaXplID0gd2Fybk9uY2UoSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5zcXVhcmVSZXNpemUsXG4gICAgJ0ludGVyYWN0YWJsZSNzcXVhcmVSZXNpemUgaXMgZGVwcmVjYXRlZC4gU2VlIGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI3Jlc2l6ZS1zcXVhcmUnKTtcblxuLypcXFxuICogaW50ZXJhY3QuaXNTZXRcbiBbIG1ldGhvZCBdXG4gKlxuICogQ2hlY2sgaWYgYW4gZWxlbWVudCBoYXMgYmVlbiBzZXRcbiAtIGVsZW1lbnQgKEVsZW1lbnQpIFRoZSBFbGVtZW50IGJlaW5nIHNlYXJjaGVkIGZvclxuID0gKGJvb2xlYW4pIEluZGljYXRlcyBpZiB0aGUgZWxlbWVudCBvciBDU1Mgc2VsZWN0b3Igd2FzIHByZXZpb3VzbHkgcGFzc2VkIHRvIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3QuaXNTZXQgPSBmdW5jdGlvbihlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgcmV0dXJuIGludGVyYWN0YWJsZXMuaW5kZXhPZkVsZW1lbnQoZWxlbWVudCwgb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQpICE9PSAtMTtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0Lm9uXG4gWyBtZXRob2QgXVxuICpcbiAqIEFkZHMgYSBnbG9iYWwgbGlzdGVuZXIgZm9yIGFuIEludGVyYWN0RXZlbnQgb3IgYWRkcyBhIERPTSBldmVudCB0b1xuICogYGRvY3VtZW50YFxuICpcbiAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRvIGxpc3RlbiBmb3JcbiAtIGxpc3RlbmVyICAgKGZ1bmN0aW9uKSBUaGUgZnVuY3Rpb24gdG8gYmUgY2FsbGVkIG9uIHRoZSBnaXZlbiBldmVudChzKVxuIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgYWRkRXZlbnRMaXN0ZW5lclxuID0gKG9iamVjdCkgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5vbiA9IGZ1bmN0aW9uICh0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgIGlmIChpc1N0cmluZyh0eXBlKSAmJiB0eXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICB0eXBlID0gdHlwZS50cmltKCkuc3BsaXQoLyArLyk7XG4gICAgfVxuXG4gICAgaWYgKGlzQXJyYXkodHlwZSkpIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0eXBlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpbnRlcmFjdC5vbih0eXBlW2ldLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfVxuXG4gICAgaWYgKGlzT2JqZWN0KHR5cGUpKSB7XG4gICAgICAgIGZvciAodmFyIHByb3AgaW4gdHlwZSkge1xuICAgICAgICAgICAgaW50ZXJhY3Qub24ocHJvcCwgdHlwZVtwcm9wXSwgbGlzdGVuZXIpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cblxuICAgIC8vIGlmIGl0IGlzIGFuIEludGVyYWN0RXZlbnQgdHlwZSwgYWRkIGxpc3RlbmVyIHRvIGdsb2JhbEV2ZW50c1xuICAgIGlmIChjb250YWlucyhldmVudFR5cGVzLCB0eXBlKSkge1xuICAgICAgICAvLyBpZiB0aGlzIHR5cGUgb2YgZXZlbnQgd2FzIG5ldmVyIGJvdW5kXG4gICAgICAgIGlmICghZ2xvYmFsRXZlbnRzW3R5cGVdKSB7XG4gICAgICAgICAgICBnbG9iYWxFdmVudHNbdHlwZV0gPSBbbGlzdGVuZXJdO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgZ2xvYmFsRXZlbnRzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8vIElmIG5vbiBJbnRlcmFjdEV2ZW50IHR5cGUsIGFkZEV2ZW50TGlzdGVuZXIgdG8gZG9jdW1lbnRcbiAgICBlbHNlIHtcbiAgICAgICAgZXZlbnRzLmFkZChkb2N1bWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgIH1cblxuICAgIHJldHVybiBpbnRlcmFjdDtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0Lm9mZlxuIFsgbWV0aG9kIF1cbiAqXG4gKiBSZW1vdmVzIGEgZ2xvYmFsIEludGVyYWN0RXZlbnQgbGlzdGVuZXIgb3IgRE9NIGV2ZW50IGZyb20gYGRvY3VtZW50YFxuICpcbiAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAtIGxpc3RlbmVyICAgKGZ1bmN0aW9uKSBUaGUgbGlzdGVuZXIgZnVuY3Rpb24gdG8gYmUgcmVtb3ZlZFxuIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgcmVtb3ZlRXZlbnRMaXN0ZW5lclxuID0gKG9iamVjdCkgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5vZmYgPSBmdW5jdGlvbiAodHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICBpZiAoaXNTdHJpbmcodHlwZSkgJiYgdHlwZS5zZWFyY2goJyAnKSAhPT0gLTEpIHtcbiAgICAgICAgdHlwZSA9IHR5cGUudHJpbSgpLnNwbGl0KC8gKy8pO1xuICAgIH1cblxuICAgIGlmIChpc0FycmF5KHR5cGUpKSB7XG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdHlwZS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaW50ZXJhY3Qub2ZmKHR5cGVbaV0sIGxpc3RlbmVyLCB1c2VDYXB0dXJlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9XG5cbiAgICBpZiAoaXNPYmplY3QodHlwZSkpIHtcbiAgICAgICAgZm9yICh2YXIgcHJvcCBpbiB0eXBlKSB7XG4gICAgICAgICAgICBpbnRlcmFjdC5vZmYocHJvcCwgdHlwZVtwcm9wXSwgbGlzdGVuZXIpO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cblxuICAgIGlmICghY29udGFpbnMoZXZlbnRUeXBlcywgdHlwZSkpIHtcbiAgICAgICAgZXZlbnRzLnJlbW92ZShkb2N1bWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdmFyIGluZGV4O1xuXG4gICAgICAgIGlmICh0eXBlIGluIGdsb2JhbEV2ZW50c1xuICAgICAgICAgICAgJiYgKGluZGV4ID0gaW5kZXhPZihnbG9iYWxFdmVudHNbdHlwZV0sIGxpc3RlbmVyKSkgIT09IC0xKSB7XG4gICAgICAgICAgICBnbG9iYWxFdmVudHNbdHlwZV0uc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBpbnRlcmFjdDtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0LmVuYWJsZURyYWdnaW5nXG4gWyBtZXRob2QgXVxuICpcbiAqIERlcHJlY2F0ZWQuXG4gKlxuICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZHJhZ2dpbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5lbmFibGVEcmFnZ2luZyA9IHdhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgIGlmIChuZXdWYWx1ZSAhPT0gbnVsbCAmJiBuZXdWYWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGFjdGlvbklzRW5hYmxlZC5kcmFnID0gbmV3VmFsdWU7XG5cbiAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgIH1cbiAgICByZXR1cm4gYWN0aW9uSXNFbmFibGVkLmRyYWc7XG59LCAnaW50ZXJhY3QuZW5hYmxlRHJhZ2dpbmcgaXMgZGVwcmVjYXRlZCBhbmQgd2lsbCBzb29uIGJlIHJlbW92ZWQuJyk7XG5cbi8qXFxcbiAqIGludGVyYWN0LmVuYWJsZVJlc2l6aW5nXG4gWyBtZXRob2QgXVxuICpcbiAqIERlcHJlY2F0ZWQuXG4gKlxuICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgcmVzaXppbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5lbmFibGVSZXNpemluZyA9IHdhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgIGlmIChuZXdWYWx1ZSAhPT0gbnVsbCAmJiBuZXdWYWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIGFjdGlvbklzRW5hYmxlZC5yZXNpemUgPSBuZXdWYWx1ZTtcblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfVxuICAgIHJldHVybiBhY3Rpb25Jc0VuYWJsZWQucmVzaXplO1xufSwgJ2ludGVyYWN0LmVuYWJsZVJlc2l6aW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4vKlxcXG4gKiBpbnRlcmFjdC5lbmFibGVHZXN0dXJpbmdcbiBbIG1ldGhvZCBdXG4gKlxuICogRGVwcmVjYXRlZC5cbiAqXG4gKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciBnZXN0dXJpbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiBcXCovXG5pbnRlcmFjdC5lbmFibGVHZXN0dXJpbmcgPSB3YXJuT25jZShmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICBpZiAobmV3VmFsdWUgIT09IG51bGwgJiYgbmV3VmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICBhY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9XG4gICAgcmV0dXJuIGFjdGlvbklzRW5hYmxlZC5nZXN0dXJlO1xufSwgJ2ludGVyYWN0LmVuYWJsZUdlc3R1cmluZyBpcyBkZXByZWNhdGVkIGFuZCB3aWxsIHNvb24gYmUgcmVtb3ZlZC4nKTtcblxuaW50ZXJhY3QuZXZlbnRUeXBlcyA9IGV2ZW50VHlwZXM7XG5cbi8qXFxcbiAqIGludGVyYWN0LmRlYnVnXG4gWyBtZXRob2QgXVxuICpcbiAqIFJldHVybnMgZGVidWdnaW5nIGRhdGFcbiA9IChvYmplY3QpIEFuIG9iamVjdCB3aXRoIHByb3BlcnRpZXMgdGhhdCBvdXRsaW5lIHRoZSBjdXJyZW50IHN0YXRlIGFuZCBleHBvc2UgaW50ZXJuYWwgZnVuY3Rpb25zIGFuZCB2YXJpYWJsZXNcbiBcXCovXG5pbnRlcmFjdC5kZWJ1ZyA9IGZ1bmN0aW9uICgpIHtcbiAgICB2YXIgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbnNbMF0gfHwgbmV3IEludGVyYWN0aW9uKCk7XG5cbiAgICByZXR1cm4ge1xuICAgICAgICBpbnRlcmFjdGlvbnMgICAgICAgICAgOiBpbnRlcmFjdGlvbnMsXG4gICAgICAgIHRhcmdldCAgICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnRhcmdldCxcbiAgICAgICAgZHJhZ2dpbmcgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uZHJhZ2dpbmcsXG4gICAgICAgIHJlc2l6aW5nICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnJlc2l6aW5nLFxuICAgICAgICBnZXN0dXJpbmcgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5nZXN0dXJpbmcsXG4gICAgICAgIHByZXBhcmVkICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXBhcmVkLFxuICAgICAgICBtYXRjaGVzICAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5tYXRjaGVzLFxuICAgICAgICBtYXRjaEVsZW1lbnRzICAgICAgICAgOiBpbnRlcmFjdGlvbi5tYXRjaEVsZW1lbnRzLFxuXG4gICAgICAgIHByZXZDb29yZHMgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXZDb29yZHMsXG4gICAgICAgIHN0YXJ0Q29vcmRzICAgICAgICAgICA6IGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLFxuXG4gICAgICAgIHBvaW50ZXJJZHMgICAgICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJJZHMsXG4gICAgICAgIHBvaW50ZXJzICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICBhZGRQb2ludGVyICAgICAgICAgICAgOiBsaXN0ZW5lcnMuYWRkUG9pbnRlcixcbiAgICAgICAgcmVtb3ZlUG9pbnRlciAgICAgICAgIDogbGlzdGVuZXJzLnJlbW92ZVBvaW50ZXIsXG4gICAgICAgIHJlY29yZFBvaW50ZXIgICAgICAgIDogbGlzdGVuZXJzLnJlY29yZFBvaW50ZXIsXG5cbiAgICAgICAgc25hcCAgICAgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uc25hcFN0YXR1cyxcbiAgICAgICAgcmVzdHJpY3QgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ucmVzdHJpY3RTdGF0dXMsXG4gICAgICAgIGluZXJ0aWEgICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMsXG5cbiAgICAgICAgZG93blRpbWUgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uZG93blRpbWVzWzBdLFxuICAgICAgICBkb3duRXZlbnQgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duRXZlbnQsXG4gICAgICAgIGRvd25Qb2ludGVyICAgICAgICAgICA6IGludGVyYWN0aW9uLmRvd25Qb2ludGVyLFxuICAgICAgICBwcmV2RXZlbnQgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQsXG5cbiAgICAgICAgSW50ZXJhY3RhYmxlICAgICAgICAgIDogSW50ZXJhY3RhYmxlLFxuICAgICAgICBpbnRlcmFjdGFibGVzICAgICAgICAgOiBpbnRlcmFjdGFibGVzLFxuICAgICAgICBwb2ludGVySXNEb3duICAgICAgICAgOiBpbnRlcmFjdGlvbi5wb2ludGVySXNEb3duLFxuICAgICAgICBkZWZhdWx0T3B0aW9ucyAgICAgICAgOiBkZWZhdWx0T3B0aW9ucyxcbiAgICAgICAgZGVmYXVsdEFjdGlvbkNoZWNrZXIgIDogZGVmYXVsdEFjdGlvbkNoZWNrZXIsXG5cbiAgICAgICAgYWN0aW9uQ3Vyc29ycyAgICAgICAgIDogYWN0aW9uQ3Vyc29ycyxcbiAgICAgICAgZHJhZ01vdmUgICAgICAgICAgICAgIDogbGlzdGVuZXJzLmRyYWdNb3ZlLFxuICAgICAgICByZXNpemVNb3ZlICAgICAgICAgICAgOiBsaXN0ZW5lcnMucmVzaXplTW92ZSxcbiAgICAgICAgZ2VzdHVyZU1vdmUgICAgICAgICAgIDogbGlzdGVuZXJzLmdlc3R1cmVNb3ZlLFxuICAgICAgICBwb2ludGVyVXAgICAgICAgICAgICAgOiBsaXN0ZW5lcnMucG9pbnRlclVwLFxuICAgICAgICBwb2ludGVyRG93biAgICAgICAgICAgOiBsaXN0ZW5lcnMucG9pbnRlckRvd24sXG4gICAgICAgIHBvaW50ZXJNb3ZlICAgICAgICAgICA6IGxpc3RlbmVycy5wb2ludGVyTW92ZSxcbiAgICAgICAgcG9pbnRlckhvdmVyICAgICAgICAgIDogbGlzdGVuZXJzLnBvaW50ZXJIb3ZlcixcblxuICAgICAgICBldmVudFR5cGVzICAgICAgICAgICAgOiBldmVudFR5cGVzLFxuXG4gICAgICAgIGV2ZW50cyAgICAgICAgICAgICAgICA6IGV2ZW50cyxcbiAgICAgICAgZ2xvYmFsRXZlbnRzICAgICAgICAgIDogZ2xvYmFsRXZlbnRzLFxuICAgICAgICBkZWxlZ2F0ZWRFdmVudHMgICAgICAgOiBkZWxlZ2F0ZWRFdmVudHNcbiAgICB9O1xufTtcblxuLy8gZXhwb3NlIHRoZSBmdW5jdGlvbnMgdXNlZCB0byBjYWxjdWxhdGUgbXVsdGktdG91Y2ggcHJvcGVydGllc1xuaW50ZXJhY3QuZ2V0VG91Y2hBdmVyYWdlICA9IHRvdWNoQXZlcmFnZTtcbmludGVyYWN0LmdldFRvdWNoQkJveCAgICAgPSB0b3VjaEJCb3g7XG5pbnRlcmFjdC5nZXRUb3VjaERpc3RhbmNlID0gdG91Y2hEaXN0YW5jZTtcbmludGVyYWN0LmdldFRvdWNoQW5nbGUgICAgPSB0b3VjaEFuZ2xlO1xuXG5pbnRlcmFjdC5nZXRFbGVtZW50UmVjdCAgID0gZ2V0RWxlbWVudFJlY3Q7XG5pbnRlcmFjdC5tYXRjaGVzU2VsZWN0b3IgID0gbWF0Y2hlc1NlbGVjdG9yO1xuaW50ZXJhY3QuY2xvc2VzdCAgICAgICAgICA9IGNsb3Nlc3Q7XG5cbi8qXFxcbiAqIGludGVyYWN0Lm1hcmdpblxuIFsgbWV0aG9kIF1cbiAqXG4gKiBSZXR1cm5zIG9yIHNldHMgdGhlIG1hcmdpbiBmb3IgYXV0b2NoZWNrIHJlc2l6aW5nIHVzZWQgaW5cbiAqIEBJbnRlcmFjdGFibGUuZ2V0QWN0aW9uLiBUaGF0IGlzIHRoZSBkaXN0YW5jZSBmcm9tIHRoZSBib3R0b20gYW5kIHJpZ2h0XG4gKiBlZGdlcyBvZiBhbiBlbGVtZW50IGNsaWNraW5nIGluIHdoaWNoIHdpbGwgc3RhcnQgcmVzaXppbmdcbiAqXG4gLSBuZXdWYWx1ZSAobnVtYmVyKSAjb3B0aW9uYWxcbiA9IChudW1iZXIgfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgbWFyZ2luIHZhbHVlIG9yIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3QubWFyZ2luID0gZnVuY3Rpb24gKG5ld3ZhbHVlKSB7XG4gICAgaWYgKGlzTnVtYmVyKG5ld3ZhbHVlKSkge1xuICAgICAgICBtYXJnaW4gPSBuZXd2YWx1ZTtcblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfVxuICAgIHJldHVybiBtYXJnaW47XG59O1xuXG4vKlxcXG4gKiBpbnRlcmFjdC5zdXBwb3J0c1RvdWNoXG4gWyBtZXRob2QgXVxuICpcbiA9IChib29sZWFuKSBXaGV0aGVyIG9yIG5vdCB0aGUgYnJvd3NlciBzdXBwb3J0cyB0b3VjaCBpbnB1dFxuIFxcKi9cbmludGVyYWN0LnN1cHBvcnRzVG91Y2ggPSBmdW5jdGlvbiAoKSB7XG4gICAgcmV0dXJuIHN1cHBvcnRzVG91Y2g7XG59O1xuXG4vKlxcXG4gKiBpbnRlcmFjdC5zdXBwb3J0c1BvaW50ZXJFdmVudFxuIFsgbWV0aG9kIF1cbiAqXG4gPSAoYm9vbGVhbikgV2hldGhlciBvciBub3QgdGhlIGJyb3dzZXIgc3VwcG9ydHMgUG9pbnRlckV2ZW50c1xuIFxcKi9cbmludGVyYWN0LnN1cHBvcnRzUG9pbnRlckV2ZW50ID0gZnVuY3Rpb24gKCkge1xuICAgIHJldHVybiBzdXBwb3J0c1BvaW50ZXJFdmVudDtcbn07XG5cbi8qXFxcbiAqIGludGVyYWN0LnN0b3BcbiBbIG1ldGhvZCBdXG4gKlxuICogQ2FuY2VscyBhbGwgaW50ZXJhY3Rpb25zIChlbmQgZXZlbnRzIGFyZSBub3QgZmlyZWQpXG4gKlxuIC0gZXZlbnQgKEV2ZW50KSBBbiBldmVudCBvbiB3aGljaCB0byBjYWxsIHByZXZlbnREZWZhdWx0KClcbiA9IChvYmplY3QpIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3Quc3RvcCA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgIGZvciAodmFyIGkgPSBpbnRlcmFjdGlvbnMubGVuZ3RoIC0gMTsgaSA+IDA7IGktLSkge1xuICAgICAgICBpbnRlcmFjdGlvbnNbaV0uc3RvcChldmVudCk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGludGVyYWN0O1xufTtcblxuLypcXFxuICogaW50ZXJhY3QuZHluYW1pY0Ryb3BcbiBbIG1ldGhvZCBdXG4gKlxuICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgdGhlIGRpbWVuc2lvbnMgb2YgZHJvcHpvbmUgZWxlbWVudHMgYXJlXG4gKiBjYWxjdWxhdGVkIG9uIGV2ZXJ5IGRyYWdtb3ZlIG9yIG9ubHkgb24gZHJhZ3N0YXJ0IGZvciB0aGUgZGVmYXVsdFxuICogZHJvcENoZWNrZXJcbiAqXG4gLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIFRydWUgdG8gY2hlY2sgb24gZWFjaCBtb3ZlLiBGYWxzZSB0byBjaGVjayBvbmx5IGJlZm9yZSBzdGFydFxuID0gKGJvb2xlYW4gfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuIFxcKi9cbmludGVyYWN0LmR5bmFtaWNEcm9wID0gZnVuY3Rpb24gKG5ld1ZhbHVlKSB7XG4gICAgaWYgKGlzQm9vbChuZXdWYWx1ZSkpIHtcbiAgICAgICAgLy9pZiAoZHJhZ2dpbmcgJiYgZHluYW1pY0Ryb3AgIT09IG5ld1ZhbHVlICYmICFuZXdWYWx1ZSkge1xuICAgICAgICAvL2NhbGNSZWN0cyhkcm9wem9uZXMpO1xuICAgICAgICAvL31cblxuICAgICAgICBkeW5hbWljRHJvcCA9IG5ld1ZhbHVlO1xuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9XG4gICAgcmV0dXJuIGR5bmFtaWNEcm9wO1xufTtcblxuLypcXFxuICogaW50ZXJhY3QucG9pbnRlck1vdmVUb2xlcmFuY2VcbiBbIG1ldGhvZCBdXG4gKiBSZXR1cm5zIG9yIHNldHMgdGhlIGRpc3RhbmNlIHRoZSBwb2ludGVyIG11c3QgYmUgbW92ZWQgYmVmb3JlIGFuIGFjdGlvblxuICogc2VxdWVuY2Ugb2NjdXJzLiBUaGlzIGFsc28gYWZmZWN0cyB0b2xlcmFuY2UgZm9yIHRhcCBldmVudHMuXG4gKlxuIC0gbmV3VmFsdWUgKG51bWJlcikgI29wdGlvbmFsIFRoZSBtb3ZlbWVudCBmcm9tIHRoZSBzdGFydCBwb3NpdGlvbiBtdXN0IGJlIGdyZWF0ZXIgdGhhbiB0aGlzIHZhbHVlXG4gPSAobnVtYmVyIHwgSW50ZXJhY3RhYmxlKSBUaGUgY3VycmVudCBzZXR0aW5nIG9yIGludGVyYWN0XG4gXFwqL1xuaW50ZXJhY3QucG9pbnRlck1vdmVUb2xlcmFuY2UgPSBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICBpZiAoaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgIHBvaW50ZXJNb3ZlVG9sZXJhbmNlID0gbmV3VmFsdWU7XG5cbiAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgfVxuXG4gICAgcmV0dXJuIHBvaW50ZXJNb3ZlVG9sZXJhbmNlO1xufTtcblxuLypcXFxuICogaW50ZXJhY3QubWF4SW50ZXJhY3Rpb25zXG4gWyBtZXRob2QgXVxuICoqXG4gKiBSZXR1cm5zIG9yIHNldHMgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvbmN1cnJlbnQgaW50ZXJhY3Rpb25zIGFsbG93ZWQuXG4gKiBCeSBkZWZhdWx0IG9ubHkgMSBpbnRlcmFjdGlvbiBpcyBhbGxvd2VkIGF0IGEgdGltZSAoZm9yIGJhY2t3YXJkc1xuICogY29tcGF0aWJpbGl0eSkuIFRvIGFsbG93IG11bHRpcGxlIGludGVyYWN0aW9ucyBvbiB0aGUgc2FtZSBJbnRlcmFjdGFibGVzXG4gKiBhbmQgZWxlbWVudHMsIHlvdSBuZWVkIHRvIGVuYWJsZSBpdCBpbiB0aGUgZHJhZ2dhYmxlLCByZXNpemFibGUgYW5kXG4gKiBnZXN0dXJhYmxlIGAnbWF4J2AgYW5kIGAnbWF4UGVyRWxlbWVudCdgIG9wdGlvbnMuXG4gKipcbiAtIG5ld1ZhbHVlIChudW1iZXIpICNvcHRpb25hbCBBbnkgbnVtYmVyLiBuZXdWYWx1ZSA8PSAwIG1lYW5zIG5vIGludGVyYWN0aW9ucy5cbiBcXCovXG5pbnRlcmFjdC5tYXhJbnRlcmFjdGlvbnMgPSBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICBpZiAoaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgIG1heEludGVyYWN0aW9ucyA9IG5ld1ZhbHVlO1xuXG4gICAgICAgIHJldHVybiB0aGlzO1xuICAgIH1cblxuICAgIHJldHVybiBtYXhJbnRlcmFjdGlvbnM7XG59O1xuXG5pbnRlcmFjdC5jcmVhdGVTbmFwR3JpZCA9IGZ1bmN0aW9uIChncmlkKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uICh4LCB5KSB7XG4gICAgICAgIHZhciBvZmZzZXRYID0gMCxcbiAgICAgICAgICAgIG9mZnNldFkgPSAwO1xuXG4gICAgICAgIGlmIChpc09iamVjdChncmlkLm9mZnNldCkpIHtcbiAgICAgICAgICAgIG9mZnNldFggPSBncmlkLm9mZnNldC54O1xuICAgICAgICAgICAgb2Zmc2V0WSA9IGdyaWQub2Zmc2V0Lnk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZ3JpZHggPSBNYXRoLnJvdW5kKCh4IC0gb2Zmc2V0WCkgLyBncmlkLngpLFxuICAgICAgICAgICAgZ3JpZHkgPSBNYXRoLnJvdW5kKCh5IC0gb2Zmc2V0WSkgLyBncmlkLnkpLFxuXG4gICAgICAgICAgICBuZXdYID0gZ3JpZHggKiBncmlkLnggKyBvZmZzZXRYLFxuICAgICAgICAgICAgbmV3WSA9IGdyaWR5ICogZ3JpZC55ICsgb2Zmc2V0WTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgeDogbmV3WCxcbiAgICAgICAgICAgIHk6IG5ld1ksXG4gICAgICAgICAgICByYW5nZTogZ3JpZC5yYW5nZVxuICAgICAgICB9O1xuICAgIH07XG59O1xuXG5mdW5jdGlvbiBlbmRBbGxJbnRlcmFjdGlvbnMgKGV2ZW50KSB7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBpbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJFbmQoZXZlbnQsIGV2ZW50KTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIGxpc3RlblRvRG9jdW1lbnQgKGRvYykge1xuICAgIGlmIChjb250YWlucyhkb2N1bWVudHMsIGRvYykpIHsgcmV0dXJuOyB9XG5cbiAgICB2YXIgd2luID0gZG9jLmRlZmF1bHRWaWV3IHx8IGRvYy5wYXJlbnRXaW5kb3c7XG5cbiAgICAvLyBhZGQgZGVsZWdhdGUgZXZlbnQgbGlzdGVuZXJcbiAgICBmb3IgKHZhciBldmVudFR5cGUgaW4gZGVsZWdhdGVkRXZlbnRzKSB7XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBldmVudFR5cGUsIGRlbGVnYXRlTGlzdGVuZXIpO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgZXZlbnRUeXBlLCBkZWxlZ2F0ZVVzZUNhcHR1cmUsIHRydWUpO1xuICAgIH1cblxuICAgIGlmIChQb2ludGVyRXZlbnQpIHtcbiAgICAgICAgaWYgKFBvaW50ZXJFdmVudCA9PT0gd2luLk1TUG9pbnRlckV2ZW50KSB7XG4gICAgICAgICAgICBwRXZlbnRUeXBlcyA9IHtcbiAgICAgICAgICAgICAgICB1cDogJ01TUG9pbnRlclVwJywgZG93bjogJ01TUG9pbnRlckRvd24nLCBvdmVyOiAnbW91c2VvdmVyJyxcbiAgICAgICAgICAgICAgICBvdXQ6ICdtb3VzZW91dCcsIG1vdmU6ICdNU1BvaW50ZXJNb3ZlJywgY2FuY2VsOiAnTVNQb2ludGVyQ2FuY2VsJyB9O1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgcEV2ZW50VHlwZXMgPSB7XG4gICAgICAgICAgICAgICAgdXA6ICdwb2ludGVydXAnLCBkb3duOiAncG9pbnRlcmRvd24nLCBvdmVyOiAncG9pbnRlcm92ZXInLFxuICAgICAgICAgICAgICAgIG91dDogJ3BvaW50ZXJvdXQnLCBtb3ZlOiAncG9pbnRlcm1vdmUnLCBjYW5jZWw6ICdwb2ludGVyY2FuY2VsJyB9O1xuICAgICAgICB9XG5cbiAgICAgICAgZXZlbnRzLmFkZChkb2MsIHBFdmVudFR5cGVzLmRvd24gICwgbGlzdGVuZXJzLnNlbGVjdG9yRG93biApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgcEV2ZW50VHlwZXMubW92ZSAgLCBsaXN0ZW5lcnMucG9pbnRlck1vdmUgICk7XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBwRXZlbnRUeXBlcy5vdmVyICAsIGxpc3RlbmVycy5wb2ludGVyT3ZlciAgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsIHBFdmVudFR5cGVzLm91dCAgICwgbGlzdGVuZXJzLnBvaW50ZXJPdXQgICApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgcEV2ZW50VHlwZXMudXAgICAgLCBsaXN0ZW5lcnMucG9pbnRlclVwICAgICk7XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBwRXZlbnRUeXBlcy5jYW5jZWwsIGxpc3RlbmVycy5wb2ludGVyQ2FuY2VsKTtcblxuICAgICAgICAvLyBhdXRvc2Nyb2xsXG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCBwRXZlbnRUeXBlcy5tb3ZlLCBsaXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZWRvd24nLCBsaXN0ZW5lcnMuc2VsZWN0b3JEb3duKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBsaXN0ZW5lcnMucG9pbnRlck1vdmUgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZXVwJyAgLCBsaXN0ZW5lcnMucG9pbnRlclVwICAgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW92ZXInLCBsaXN0ZW5lcnMucG9pbnRlck92ZXIgKTtcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW91dCcgLCBsaXN0ZW5lcnMucG9pbnRlck91dCAgKTtcblxuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoc3RhcnQnICwgbGlzdGVuZXJzLnNlbGVjdG9yRG93biApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNobW92ZScgICwgbGlzdGVuZXJzLnBvaW50ZXJNb3ZlICApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoZW5kJyAgICwgbGlzdGVuZXJzLnBvaW50ZXJVcCAgICApO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoY2FuY2VsJywgbGlzdGVuZXJzLnBvaW50ZXJDYW5jZWwpO1xuXG4gICAgICAgIC8vIGF1dG9zY3JvbGxcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBsaXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNobW92ZScsIGxpc3RlbmVycy5hdXRvU2Nyb2xsTW92ZSk7XG4gICAgfVxuXG4gICAgZXZlbnRzLmFkZCh3aW4sICdibHVyJywgZW5kQWxsSW50ZXJhY3Rpb25zKTtcblxuICAgIHRyeSB7XG4gICAgICAgIGlmICh3aW4uZnJhbWVFbGVtZW50KSB7XG4gICAgICAgICAgICB2YXIgcGFyZW50RG9jID0gd2luLmZyYW1lRWxlbWVudC5vd25lckRvY3VtZW50LFxuICAgICAgICAgICAgICAgIHBhcmVudFdpbmRvdyA9IHBhcmVudERvYy5kZWZhdWx0VmlldztcblxuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdtb3VzZXVwJyAgICAgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICd0b3VjaGVuZCcgICAgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICd0b3VjaGNhbmNlbCcgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdwb2ludGVydXAnICAgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdNU1BvaW50ZXJVcCcgICwgbGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnRXaW5kb3csICdibHVyJyAgICAgICAgICwgZW5kQWxsSW50ZXJhY3Rpb25zICk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgY2F0Y2ggKGVycm9yKSB7XG4gICAgICAgIGludGVyYWN0LndpbmRvd1BhcmVudEVycm9yID0gZXJyb3I7XG4gICAgfVxuXG4gICAgaWYgKGV2ZW50cy51c2VBdHRhY2hFdmVudCkge1xuICAgICAgICAvLyBGb3IgSUUncyBsYWNrIG9mIEV2ZW50I3ByZXZlbnREZWZhdWx0XG4gICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnc2VsZWN0c3RhcnQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgIHZhciBpbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uc1swXTtcblxuICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLmN1cnJlbnRBY3Rpb24oKSkge1xuICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9KTtcblxuICAgICAgICAvLyBGb3IgSUUncyBiYWQgZGJsY2xpY2sgZXZlbnQgc2VxdWVuY2VcbiAgICAgICAgZXZlbnRzLmFkZChkb2MsICdkYmxjbGljaycsIGRvT25JbnRlcmFjdGlvbnMoJ2llOERibGNsaWNrJykpO1xuICAgIH1cblxuICAgIGRvY3VtZW50cy5wdXNoKGRvYyk7XG59XG5cbmxpc3RlblRvRG9jdW1lbnQoZG9jdW1lbnQpO1xuXG5mdW5jdGlvbiBpbmRleE9mIChhcnJheSwgdGFyZ2V0KSB7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGFycmF5Lmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIGlmIChhcnJheVtpXSA9PT0gdGFyZ2V0KSB7XG4gICAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiAtMTtcbn1cblxuZnVuY3Rpb24gY29udGFpbnMgKGFycmF5LCB0YXJnZXQpIHtcbiAgICByZXR1cm4gaW5kZXhPZihhcnJheSwgdGFyZ2V0KSAhPT0gLTE7XG59XG5cbmZ1bmN0aW9uIG1hdGNoZXNTZWxlY3RvciAoZWxlbWVudCwgc2VsZWN0b3IsIG5vZGVMaXN0KSB7XG4gICAgaWYgKGllOE1hdGNoZXNTZWxlY3Rvcikge1xuICAgICAgICByZXR1cm4gaWU4TWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIHNlbGVjdG9yLCBub2RlTGlzdCk7XG4gICAgfVxuXG4gICAgLy8gcmVtb3ZlIC9kZWVwLyBmcm9tIHNlbGVjdG9ycyBpZiBzaGFkb3dET00gcG9seWZpbGwgaXMgdXNlZFxuICAgIGlmICh3aW5kb3cgIT09IHJlYWxXaW5kb3cpIHtcbiAgICAgICAgc2VsZWN0b3IgPSBzZWxlY3Rvci5yZXBsYWNlKC9cXC9kZWVwXFwvL2csICcgJyk7XG4gICAgfVxuXG4gICAgcmV0dXJuIGVsZW1lbnRbcHJlZml4ZWRNYXRjaGVzU2VsZWN0b3JdKHNlbGVjdG9yKTtcbn1cblxuZnVuY3Rpb24gbWF0Y2hlc1VwVG8gKGVsZW1lbnQsIHNlbGVjdG9yLCBsaW1pdCkge1xuICAgIHdoaWxlIChpc0VsZW1lbnQoZWxlbWVudCkpIHtcbiAgICAgICAgaWYgKG1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBzZWxlY3RvcikpIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgZWxlbWVudCA9IHBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKGVsZW1lbnQgPT09IGxpbWl0KSB7XG4gICAgICAgICAgICByZXR1cm4gbWF0Y2hlc1NlbGVjdG9yKGVsZW1lbnQsIHNlbGVjdG9yKTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIHJldHVybiBmYWxzZTtcbn1cblxuLy8gRm9yIElFOCdzIGxhY2sgb2YgYW4gRWxlbWVudCNtYXRjaGVzU2VsZWN0b3Jcbi8vIHRha2VuIGZyb20gaHR0cDovL3RhbmFsaW4uY29tL2VuL2Jsb2cvMjAxMi8xMi9tYXRjaGVzLXNlbGVjdG9yLWllOC8gYW5kIG1vZGlmaWVkXG5pZiAoIShwcmVmaXhlZE1hdGNoZXNTZWxlY3RvciBpbiBFbGVtZW50LnByb3RvdHlwZSkgfHwgIWlzRnVuY3Rpb24oRWxlbWVudC5wcm90b3R5cGVbcHJlZml4ZWRNYXRjaGVzU2VsZWN0b3JdKSkge1xuICAgIGllOE1hdGNoZXNTZWxlY3RvciA9IGZ1bmN0aW9uIChlbGVtZW50LCBzZWxlY3RvciwgZWxlbXMpIHtcbiAgICAgICAgZWxlbXMgPSBlbGVtcyB8fCBlbGVtZW50LnBhcmVudE5vZGUucXVlcnlTZWxlY3RvckFsbChzZWxlY3Rvcik7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGVsZW1zLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoZWxlbXNbaV0gPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9O1xufVxuXG4vLyByZXF1ZXN0QW5pbWF0aW9uRnJhbWUgcG9seWZpbGxcbihmdW5jdGlvbigpIHtcbiAgICB2YXIgbGFzdFRpbWUgPSAwLFxuICAgICAgICB2ZW5kb3JzID0gWydtcycsICdtb3onLCAnd2Via2l0JywgJ28nXTtcblxuICAgIGZvcih2YXIgeCA9IDA7IHggPCB2ZW5kb3JzLmxlbmd0aCAmJiAhcmVhbFdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU7ICsreCkge1xuICAgICAgICByZXFGcmFtZSA9IHJlYWxXaW5kb3dbdmVuZG9yc1t4XSsnUmVxdWVzdEFuaW1hdGlvbkZyYW1lJ107XG4gICAgICAgIGNhbmNlbEZyYW1lID0gcmVhbFdpbmRvd1t2ZW5kb3JzW3hdKydDYW5jZWxBbmltYXRpb25GcmFtZSddIHx8IHJlYWxXaW5kb3dbdmVuZG9yc1t4XSsnQ2FuY2VsUmVxdWVzdEFuaW1hdGlvbkZyYW1lJ107XG4gICAgfVxuXG4gICAgaWYgKCFyZXFGcmFtZSkge1xuICAgICAgICByZXFGcmFtZSA9IGZ1bmN0aW9uKGNhbGxiYWNrKSB7XG4gICAgICAgICAgICB2YXIgY3VyclRpbWUgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgICAgICB0aW1lVG9DYWxsID0gTWF0aC5tYXgoMCwgMTYgLSAoY3VyclRpbWUgLSBsYXN0VGltZSkpLFxuICAgICAgICAgICAgICAgIGlkID0gc2V0VGltZW91dChmdW5jdGlvbigpIHsgY2FsbGJhY2soY3VyclRpbWUgKyB0aW1lVG9DYWxsKTsgfSxcbiAgICAgICAgICAgICAgICAgICAgdGltZVRvQ2FsbCk7XG4gICAgICAgICAgICBsYXN0VGltZSA9IGN1cnJUaW1lICsgdGltZVRvQ2FsbDtcbiAgICAgICAgICAgIHJldHVybiBpZDtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICBpZiAoIWNhbmNlbEZyYW1lKSB7XG4gICAgICAgIGNhbmNlbEZyYW1lID0gZnVuY3Rpb24oaWQpIHtcbiAgICAgICAgICAgIGNsZWFyVGltZW91dChpZCk7XG4gICAgICAgIH07XG4gICAgfVxufSgpKTtcblxuLy8gQ29tbW9uSlNcbmlmICh0eXBlb2YgZXhwb3J0cyAhPT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBpZiAodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbW9kdWxlLmV4cG9ydHMpIHtcbiAgICAgICAgZXhwb3J0cyA9IG1vZHVsZS5leHBvcnRzID0gaW50ZXJhY3Q7XG4gICAgfVxuICAgIGV4cG9ydHNbJ2ludGVyYWN0J10gPSBpbnRlcmFjdDtcbn1cbi8vIEFNRFxuZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgZGVmaW5lKCdpbnRlcmFjdCcsIGZ1bmN0aW9uKCkge1xuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfSk7XG59O1xuXG4vLyBBbHdheXMgZXhwb3J0IG9uIHRoZSBnbG9iYWwgc2NvcGVcbndpbmRvd1snaW50ZXJhY3QnXSA9IGludGVyYWN0OyIsInZhciBpbnRlcmFjdFdpbmRvdyA9IHR5cGVvZiB3aW5kb3cgPT09ICd1bmRlZmluZWQnID8gdW5kZWZpbmVkIDogd2luZG93O1xuXG5tb2R1bGUuZXhwb3J0cyA9IGludGVyYWN0V2luZG93OyJdfQ== +},{"./isType":14}]},{},[1]) +//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvaW50ZXJhY3QuanMiLCJzcmMvSW50ZXJhY3RFdmVudC5qcyIsInNyYy9JbnRlcmFjdGlvbi5qcyIsInNyYy9hdXRvU2Nyb2xsLmpzIiwic3JjL2RlZmF1bHRPcHRpb25zLmpzIiwic3JjL3Njb3BlLmpzIiwic3JjL3V0aWxzL2Fyci5qcyIsInNyYy91dGlscy9icm93c2VyLmpzIiwic3JjL3V0aWxzL2RvbU9iamVjdHMuanMiLCJzcmMvdXRpbHMvZXZlbnRzLmpzIiwic3JjL3V0aWxzL2V4dGVuZC5qcyIsInNyYy91dGlscy9oeXBvdC5qcyIsInNyYy91dGlscy9pbmRleC5qcyIsInNyYy91dGlscy9pc1R5cGUuanMiLCJzcmMvdXRpbHMvcG9pbnRlclV0aWxzLmpzIiwic3JjL3V0aWxzL3JhZi5qcyIsInNyYy91dGlscy93aW5kb3cuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcDRGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN1FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9oRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25IQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDWEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3BCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNoTEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1JBO0FBQ0E7QUFDQTtBQUNBOztBQ0hBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcklBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIi8qKlxuICogaW50ZXJhY3QuanMgdjEuMi40XG4gKlxuICogQ29weXJpZ2h0IChjKSAyMDEyLTIwMTUgVGF5ZSBBZGV5ZW1pIDxkZXZAdGF5ZS5tZT5cbiAqIE9wZW4gc291cmNlIHVuZGVyIHRoZSBNSVQgTGljZW5zZS5cbiAqIGh0dHBzOi8vcmF3LmdpdGh1Yi5jb20vdGF5ZS9pbnRlcmFjdC5qcy9tYXN0ZXIvTElDRU5TRVxuICovXG5cbiAgICAndXNlIHN0cmljdCc7XG5cbiAgICAvLyByZXR1cm4gZWFybHkgaWYgdGhlcmUncyBubyB3aW5kb3cgdG8gd29yayB3aXRoIChlZy4gTm9kZS5qcylcbiAgICBpZiAoIXJlcXVpcmUoJy4vdXRpbHMvd2luZG93Jykud2luZG93KSB7IHJldHVybjsgfVxuXG4gICAgdmFyIHNjb3BlID0gcmVxdWlyZSgnLi9zY29wZScpLFxuICAgICAgICB1dGlscyA9IHJlcXVpcmUoJy4vdXRpbHMnKSxcbiAgICAgICAgYnJvd3NlciA9IHV0aWxzLmJyb3dzZXI7XG5cbiAgICBzY29wZS5wRXZlbnRUeXBlcyA9IG51bGw7XG5cbiAgICBzY29wZS5kb2N1bWVudHMgICAgICAgPSBbXTsgICAvLyBhbGwgZG9jdW1lbnRzIGJlaW5nIGxpc3RlbmVkIHRvXG5cbiAgICBzY29wZS5pbnRlcmFjdGFibGVzICAgPSBbXTsgICAvLyBhbGwgc2V0IGludGVyYWN0YWJsZXNcbiAgICBzY29wZS5pbnRlcmFjdGlvbnMgICAgPSBbXTsgICAvLyBhbGwgaW50ZXJhY3Rpb25zXG5cbiAgICBzY29wZS5keW5hbWljRHJvcCAgICAgPSBmYWxzZTtcblxuICAgIC8vIHtcbiAgICAvLyAgICAgIHR5cGU6IHtcbiAgICAvLyAgICAgICAgICBzZWxlY3RvcnM6IFsnc2VsZWN0b3InLCAuLi5dLFxuICAgIC8vICAgICAgICAgIGNvbnRleHRzIDogW2RvY3VtZW50LCAuLi5dLFxuICAgIC8vICAgICAgICAgIGxpc3RlbmVyczogW1tsaXN0ZW5lciwgdXNlQ2FwdHVyZV0sIC4uLl1cbiAgICAvLyAgICAgIH1cbiAgICAvLyAgfVxuICAgIHNjb3BlLmRlbGVnYXRlZEV2ZW50cyA9IHt9O1xuXG4gICAgc2NvcGUuZGVmYXVsdE9wdGlvbnMgPSByZXF1aXJlKCcuL2RlZmF1bHRPcHRpb25zJyk7XG5cbiAgICAvLyBUaGluZ3MgcmVsYXRlZCB0byBhdXRvU2Nyb2xsXG4gICAgc2NvcGUuYXV0b1Njcm9sbCA9IHJlcXVpcmUoJy4vYXV0b1Njcm9sbCcpO1xuXG4gICAgLy8gTGVzcyBQcmVjaXNpb24gd2l0aCB0b3VjaCBpbnB1dFxuICAgIHNjb3BlLm1hcmdpbiA9IGJyb3dzZXIuc3VwcG9ydHNUb3VjaCB8fCBicm93c2VyLnN1cHBvcnRzUG9pbnRlckV2ZW50PyAyMDogMTA7XG5cbiAgICBzY29wZS5wb2ludGVyTW92ZVRvbGVyYW5jZSA9IDE7XG5cbiAgICAvLyBmb3IgaWdub3JpbmcgYnJvd3NlcidzIHNpbXVsYXRlZCBtb3VzZSBldmVudHNcbiAgICBzY29wZS5wcmV2VG91Y2hUaW1lID0gMDtcblxuICAgIC8vIEFsbG93IHRoaXMgbWFueSBpbnRlcmFjdGlvbnMgdG8gaGFwcGVuIHNpbXVsdGFuZW91c2x5XG4gICAgc2NvcGUubWF4SW50ZXJhY3Rpb25zID0gSW5maW5pdHk7XG5cbiAgICBzY29wZS5hY3Rpb25DdXJzb3JzID0gYnJvd3Nlci5pc0llOU9yT2xkZXIgPyB7XG4gICAgICAgIGRyYWcgICAgOiAnbW92ZScsXG4gICAgICAgIHJlc2l6ZXggOiAnZS1yZXNpemUnLFxuICAgICAgICByZXNpemV5IDogJ3MtcmVzaXplJyxcbiAgICAgICAgcmVzaXpleHk6ICdzZS1yZXNpemUnLFxuXG4gICAgICAgIHJlc2l6ZXRvcCAgICAgICAgOiAnbi1yZXNpemUnLFxuICAgICAgICByZXNpemVsZWZ0ICAgICAgIDogJ3ctcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tICAgICA6ICdzLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXJpZ2h0ICAgICAgOiAnZS1yZXNpemUnLFxuICAgICAgICByZXNpemV0b3BsZWZ0ICAgIDogJ3NlLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbXJpZ2h0OiAnc2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wcmlnaHQgICA6ICduZS1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b21sZWZ0IDogJ25lLXJlc2l6ZScsXG5cbiAgICAgICAgZ2VzdHVyZSA6ICcnXG4gICAgfSA6IHtcbiAgICAgICAgZHJhZyAgICA6ICdtb3ZlJyxcbiAgICAgICAgcmVzaXpleCA6ICdldy1yZXNpemUnLFxuICAgICAgICByZXNpemV5IDogJ25zLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXh5OiAnbndzZS1yZXNpemUnLFxuXG4gICAgICAgIHJlc2l6ZXRvcCAgICAgICAgOiAnbnMtcmVzaXplJyxcbiAgICAgICAgcmVzaXplbGVmdCAgICAgICA6ICdldy1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b20gICAgIDogJ25zLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXJpZ2h0ICAgICAgOiAnZXctcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wbGVmdCAgICA6ICdud3NlLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbXJpZ2h0OiAnbndzZS1yZXNpemUnLFxuICAgICAgICByZXNpemV0b3ByaWdodCAgIDogJ25lc3ctcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tbGVmdCA6ICduZXN3LXJlc2l6ZScsXG5cbiAgICAgICAgZ2VzdHVyZSA6ICcnXG4gICAgfTtcblxuICAgIHNjb3BlLmFjdGlvbklzRW5hYmxlZCA9IHtcbiAgICAgICAgZHJhZyAgIDogdHJ1ZSxcbiAgICAgICAgcmVzaXplIDogdHJ1ZSxcbiAgICAgICAgZ2VzdHVyZTogdHJ1ZVxuICAgIH07XG5cbiAgICAvLyBiZWNhdXNlIFdlYmtpdCBhbmQgT3BlcmEgc3RpbGwgdXNlICdtb3VzZXdoZWVsJyBldmVudCB0eXBlXG4gICAgc2NvcGUud2hlZWxFdmVudCA9ICdvbm1vdXNld2hlZWwnIGluIHNjb3BlLmRvY3VtZW50PyAnbW91c2V3aGVlbCc6ICd3aGVlbCc7XG5cbiAgICBzY29wZS5ldmVudFR5cGVzID0gW1xuICAgICAgICAnZHJhZ3N0YXJ0JyxcbiAgICAgICAgJ2RyYWdtb3ZlJyxcbiAgICAgICAgJ2RyYWdpbmVydGlhc3RhcnQnLFxuICAgICAgICAnZHJhZ2VuZCcsXG4gICAgICAgICdkcmFnZW50ZXInLFxuICAgICAgICAnZHJhZ2xlYXZlJyxcbiAgICAgICAgJ2Ryb3BhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wZGVhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wbW92ZScsXG4gICAgICAgICdkcm9wJyxcbiAgICAgICAgJ3Jlc2l6ZXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZW1vdmUnLFxuICAgICAgICAncmVzaXplaW5lcnRpYXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZWVuZCcsXG4gICAgICAgICdnZXN0dXJlc3RhcnQnLFxuICAgICAgICAnZ2VzdHVyZW1vdmUnLFxuICAgICAgICAnZ2VzdHVyZWluZXJ0aWFzdGFydCcsXG4gICAgICAgICdnZXN0dXJlZW5kJyxcblxuICAgICAgICAnZG93bicsXG4gICAgICAgICdtb3ZlJyxcbiAgICAgICAgJ3VwJyxcbiAgICAgICAgJ2NhbmNlbCcsXG4gICAgICAgICd0YXAnLFxuICAgICAgICAnZG91YmxldGFwJyxcbiAgICAgICAgJ2hvbGQnXG4gICAgXTtcblxuICAgIHNjb3BlLmdsb2JhbEV2ZW50cyA9IHt9O1xuXG4gICAgLy8gcHJlZml4IG1hdGNoZXNTZWxlY3RvclxuICAgIHNjb3BlLnByZWZpeGVkTWF0Y2hlc1NlbGVjdG9yID0gJ21hdGNoZXMnIGluIEVsZW1lbnQucHJvdG90eXBlP1xuICAgICAgICAgICAgJ21hdGNoZXMnOiAnd2Via2l0TWF0Y2hlc1NlbGVjdG9yJyBpbiBFbGVtZW50LnByb3RvdHlwZT9cbiAgICAgICAgICAgICAgICAnd2Via2l0TWF0Y2hlc1NlbGVjdG9yJzogJ21vek1hdGNoZXNTZWxlY3RvcicgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICAgICAgICAgICAgICdtb3pNYXRjaGVzU2VsZWN0b3InOiAnb01hdGNoZXNTZWxlY3RvcicgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICAgICAgICAgICAgICAgICAnb01hdGNoZXNTZWxlY3Rvcic6ICdtc01hdGNoZXNTZWxlY3Rvcic7XG5cbiAgICAvLyB3aWxsIGJlIHBvbHlmaWxsIGZ1bmN0aW9uIGlmIGJyb3dzZXIgaXMgSUU4XG4gICAgc2NvcGUuaWU4TWF0Y2hlc1NlbGVjdG9yID0gbnVsbDtcblxuICAgIC8vIEV2ZW50cyB3cmFwcGVyXG4gICAgdmFyIGV2ZW50cyA9IHJlcXVpcmUoJy4vdXRpbHMvZXZlbnRzJyk7XG5cbiAgICBzY29wZS5saXN0ZW5lcnMgPSB7fTtcblxuICAgIHZhciBpbnRlcmFjdGlvbkxpc3RlbmVycyA9IFtcbiAgICAgICAgJ2RyYWdTdGFydCcsICdkcmFnTW92ZScsICdyZXNpemVTdGFydCcsICdyZXNpemVNb3ZlJywgJ2dlc3R1cmVTdGFydCcsICdnZXN0dXJlTW92ZScsXG4gICAgICAgICdwb2ludGVyT3ZlcicsICdwb2ludGVyT3V0JywgJ3BvaW50ZXJIb3ZlcicsICdzZWxlY3RvckRvd24nLFxuICAgICAgICAncG9pbnRlckRvd24nLCAncG9pbnRlck1vdmUnLCAncG9pbnRlclVwJywgJ3BvaW50ZXJDYW5jZWwnLCAncG9pbnRlckVuZCcsXG4gICAgICAgICdhZGRQb2ludGVyJywgJ3JlbW92ZVBvaW50ZXInLCAncmVjb3JkUG9pbnRlcicsICdhdXRvU2Nyb2xsTW92ZSdcbiAgICBdO1xuXG4gICAgc2NvcGUudHJ5U2VsZWN0b3IgPSBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgaWYgKCFzY29wZS5pc1N0cmluZyh2YWx1ZSkpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICAgICAgLy8gYW4gZXhjZXB0aW9uIHdpbGwgYmUgcmFpc2VkIGlmIGl0IGlzIGludmFsaWRcbiAgICAgICAgc2NvcGUuZG9jdW1lbnQucXVlcnlTZWxlY3Rvcih2YWx1ZSk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH07XG5cbiAgICBzY29wZS5nZXRTY3JvbGxYWSA9IGZ1bmN0aW9uICh3aW4pIHtcbiAgICAgICAgd2luID0gd2luIHx8IHNjb3BlLndpbmRvdztcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHg6IHdpbi5zY3JvbGxYIHx8IHdpbi5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsTGVmdCxcbiAgICAgICAgICAgIHk6IHdpbi5zY3JvbGxZIHx8IHdpbi5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsVG9wXG4gICAgICAgIH07XG4gICAgfTtcblxuICAgIHNjb3BlLmdldEFjdHVhbEVsZW1lbnQgPSBmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICByZXR1cm4gKGVsZW1lbnQgaW5zdGFuY2VvZiBzY29wZS5TVkdFbGVtZW50SW5zdGFuY2VcbiAgICAgICAgICAgID8gZWxlbWVudC5jb3JyZXNwb25kaW5nVXNlRWxlbWVudFxuICAgICAgICAgICAgOiBlbGVtZW50KTtcbiAgICB9O1xuXG4gICAgc2NvcGUuZ2V0RWxlbWVudFJlY3QgPSBmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICB2YXIgc2Nyb2xsID0gYnJvd3Nlci5pc0lPUzdvckxvd2VyXG4gICAgICAgICAgICAgICAgPyB7IHg6IDAsIHk6IDAgfVxuICAgICAgICAgICAgICAgIDogc2NvcGUuZ2V0U2Nyb2xsWFkoc2NvcGUuZ2V0V2luZG93KGVsZW1lbnQpKSxcbiAgICAgICAgICAgIGNsaWVudFJlY3QgPSAoZWxlbWVudCBpbnN0YW5jZW9mIHNjb3BlLlNWR0VsZW1lbnQpP1xuICAgICAgICAgICAgICAgIGVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk6XG4gICAgICAgICAgICAgICAgZWxlbWVudC5nZXRDbGllbnRSZWN0cygpWzBdO1xuXG4gICAgICAgIHJldHVybiBjbGllbnRSZWN0ICYmIHtcbiAgICAgICAgICAgIGxlZnQgIDogY2xpZW50UmVjdC5sZWZ0ICAgKyBzY3JvbGwueCxcbiAgICAgICAgICAgIHJpZ2h0IDogY2xpZW50UmVjdC5yaWdodCAgKyBzY3JvbGwueCxcbiAgICAgICAgICAgIHRvcCAgIDogY2xpZW50UmVjdC50b3AgICAgKyBzY3JvbGwueSxcbiAgICAgICAgICAgIGJvdHRvbTogY2xpZW50UmVjdC5ib3R0b20gKyBzY3JvbGwueSxcbiAgICAgICAgICAgIHdpZHRoIDogY2xpZW50UmVjdC53aWR0aCB8fCBjbGllbnRSZWN0LnJpZ2h0IC0gY2xpZW50UmVjdC5sZWZ0LFxuICAgICAgICAgICAgaGVpZ2h0OiBjbGllbnRSZWN0LmhlaWdoIHx8IGNsaWVudFJlY3QuYm90dG9tIC0gY2xpZW50UmVjdC50b3BcbiAgICAgICAgfTtcbiAgICB9O1xuXG4gICAgdXRpbHMuZ2V0VG91Y2hQYWlyID0gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciB0b3VjaGVzID0gW107XG5cbiAgICAgICAgLy8gYXJyYXkgb2YgdG91Y2hlcyBpcyBzdXBwbGllZFxuICAgICAgICBpZiAoc2NvcGUuaXNBcnJheShldmVudCkpIHtcbiAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudFswXTtcbiAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudFsxXTtcbiAgICAgICAgfVxuICAgICAgICAvLyBhbiBldmVudFxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGlmIChldmVudC50eXBlID09PSAndG91Y2hlbmQnKSB7XG4gICAgICAgICAgICAgICAgaWYgKGV2ZW50LnRvdWNoZXMubGVuZ3RoID09PSAxKSB7XG4gICAgICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgICAgICB0b3VjaGVzWzFdID0gZXZlbnQuY2hhbmdlZFRvdWNoZXNbMF07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKGV2ZW50LnRvdWNoZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC5jaGFuZ2VkVG91Y2hlc1swXTtcbiAgICAgICAgICAgICAgICAgICAgdG91Y2hlc1sxXSA9IGV2ZW50LmNoYW5nZWRUb3VjaGVzWzFdO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudC50b3VjaGVzWzFdO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRvdWNoZXM7XG4gICAgfTtcblxuICAgIHV0aWxzLnRvdWNoQXZlcmFnZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgdG91Y2hlcyA9IHV0aWxzLmdldFRvdWNoUGFpcihldmVudCk7XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHBhZ2VYOiAodG91Y2hlc1swXS5wYWdlWCArIHRvdWNoZXNbMV0ucGFnZVgpIC8gMixcbiAgICAgICAgICAgIHBhZ2VZOiAodG91Y2hlc1swXS5wYWdlWSArIHRvdWNoZXNbMV0ucGFnZVkpIC8gMixcbiAgICAgICAgICAgIGNsaWVudFg6ICh0b3VjaGVzWzBdLmNsaWVudFggKyB0b3VjaGVzWzFdLmNsaWVudFgpIC8gMixcbiAgICAgICAgICAgIGNsaWVudFk6ICh0b3VjaGVzWzBdLmNsaWVudFkgKyB0b3VjaGVzWzFdLmNsaWVudFkpIC8gMlxuICAgICAgICB9O1xuICAgIH07XG5cbiAgICB1dGlscy50b3VjaEJCb3ggPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgaWYgKCFldmVudC5sZW5ndGggJiYgIShldmVudC50b3VjaGVzICYmIGV2ZW50LnRvdWNoZXMubGVuZ3RoID4gMSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciB0b3VjaGVzID0gdXRpbHMuZ2V0VG91Y2hQYWlyKGV2ZW50KSxcbiAgICAgICAgICAgIG1pblggPSBNYXRoLm1pbih0b3VjaGVzWzBdLnBhZ2VYLCB0b3VjaGVzWzFdLnBhZ2VYKSxcbiAgICAgICAgICAgIG1pblkgPSBNYXRoLm1pbih0b3VjaGVzWzBdLnBhZ2VZLCB0b3VjaGVzWzFdLnBhZ2VZKSxcbiAgICAgICAgICAgIG1heFggPSBNYXRoLm1heCh0b3VjaGVzWzBdLnBhZ2VYLCB0b3VjaGVzWzFdLnBhZ2VYKSxcbiAgICAgICAgICAgIG1heFkgPSBNYXRoLm1heCh0b3VjaGVzWzBdLnBhZ2VZLCB0b3VjaGVzWzFdLnBhZ2VZKTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgeDogbWluWCxcbiAgICAgICAgICAgIHk6IG1pblksXG4gICAgICAgICAgICBsZWZ0OiBtaW5YLFxuICAgICAgICAgICAgdG9wOiBtaW5ZLFxuICAgICAgICAgICAgd2lkdGg6IG1heFggLSBtaW5YLFxuICAgICAgICAgICAgaGVpZ2h0OiBtYXhZIC0gbWluWVxuICAgICAgICB9O1xuICAgIH07XG5cbiAgICB1dGlscy50b3VjaERpc3RhbmNlID0gZnVuY3Rpb24gKGV2ZW50LCBkZWx0YVNvdXJjZSkge1xuICAgICAgICBkZWx0YVNvdXJjZSA9IGRlbHRhU291cmNlIHx8IHNjb3BlLmRlZmF1bHRPcHRpb25zLmRlbHRhU291cmNlO1xuXG4gICAgICAgIHZhciBzb3VyY2VYID0gZGVsdGFTb3VyY2UgKyAnWCcsXG4gICAgICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgICAgICB0b3VjaGVzID0gdXRpbHMuZ2V0VG91Y2hQYWlyKGV2ZW50KTtcblxuXG4gICAgICAgIHZhciBkeCA9IHRvdWNoZXNbMF1bc291cmNlWF0gLSB0b3VjaGVzWzFdW3NvdXJjZVhdLFxuICAgICAgICAgICAgZHkgPSB0b3VjaGVzWzBdW3NvdXJjZVldIC0gdG91Y2hlc1sxXVtzb3VyY2VZXTtcblxuICAgICAgICByZXR1cm4gdXRpbHMuaHlwb3QoZHgsIGR5KTtcbiAgICB9O1xuXG4gICAgdXRpbHMudG91Y2hBbmdsZSA9IGZ1bmN0aW9uIChldmVudCwgcHJldkFuZ2xlLCBkZWx0YVNvdXJjZSkge1xuICAgICAgICBkZWx0YVNvdXJjZSA9IGRlbHRhU291cmNlIHx8IHNjb3BlLmRlZmF1bHRPcHRpb25zLmRlbHRhU291cmNlO1xuXG4gICAgICAgIHZhciBzb3VyY2VYID0gZGVsdGFTb3VyY2UgKyAnWCcsXG4gICAgICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgICAgICB0b3VjaGVzID0gdXRpbHMuZ2V0VG91Y2hQYWlyKGV2ZW50KSxcbiAgICAgICAgICAgIGR4ID0gdG91Y2hlc1swXVtzb3VyY2VYXSAtIHRvdWNoZXNbMV1bc291cmNlWF0sXG4gICAgICAgICAgICBkeSA9IHRvdWNoZXNbMF1bc291cmNlWV0gLSB0b3VjaGVzWzFdW3NvdXJjZVldLFxuICAgICAgICAgICAgYW5nbGUgPSAxODAgKiBNYXRoLmF0YW4oZHkgLyBkeCkgLyBNYXRoLlBJO1xuXG4gICAgICAgIGlmIChzY29wZS5pc051bWJlcihwcmV2QW5nbGUpKSB7XG4gICAgICAgICAgICB2YXIgZHIgPSBhbmdsZSAtIHByZXZBbmdsZSxcbiAgICAgICAgICAgICAgICBkckNsYW1wZWQgPSBkciAlIDM2MDtcblxuICAgICAgICAgICAgaWYgKGRyQ2xhbXBlZCA+IDMxNSkge1xuICAgICAgICAgICAgICAgIGFuZ2xlIC09IDM2MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGRyQ2xhbXBlZCA+IDEzNSkge1xuICAgICAgICAgICAgICAgIGFuZ2xlIC09IDE4MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGRyQ2xhbXBlZCA8IC0zMTUpIHtcbiAgICAgICAgICAgICAgICBhbmdsZSArPSAzNjAgKyAoYW5nbGUgLyAzNjApfDAgKiAzNjA7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChkckNsYW1wZWQgPCAtMTM1KSB7XG4gICAgICAgICAgICAgICAgYW5nbGUgKz0gMTgwICsgKGFuZ2xlIC8gMzYwKXwwICogMzYwO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuICBhbmdsZTtcbiAgICB9O1xuXG4gICAgc2NvcGUuZ2V0T3JpZ2luWFkgPSBmdW5jdGlvbiAoaW50ZXJhY3RhYmxlLCBlbGVtZW50KSB7XG4gICAgICAgIHZhciBvcmlnaW4gPSBpbnRlcmFjdGFibGVcbiAgICAgICAgICAgICAgICA/IGludGVyYWN0YWJsZS5vcHRpb25zLm9yaWdpblxuICAgICAgICAgICAgICAgIDogc2NvcGUuZGVmYXVsdE9wdGlvbnMub3JpZ2luO1xuXG4gICAgICAgIGlmIChvcmlnaW4gPT09ICdwYXJlbnQnKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKG9yaWdpbiA9PT0gJ3NlbGYnKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChzY29wZS50cnlTZWxlY3RvcihvcmlnaW4pKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBzY29wZS5jbG9zZXN0KGVsZW1lbnQsIG9yaWdpbikgfHwgeyB4OiAwLCB5OiAwIH07XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihvcmlnaW4pKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBvcmlnaW4oaW50ZXJhY3RhYmxlICYmIGVsZW1lbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChvcmlnaW4pKSAge1xuICAgICAgICAgICAgb3JpZ2luID0gc2NvcGUuZ2V0RWxlbWVudFJlY3Qob3JpZ2luKTtcbiAgICAgICAgfVxuXG4gICAgICAgIG9yaWdpbi54ID0gKCd4JyBpbiBvcmlnaW4pPyBvcmlnaW4ueCA6IG9yaWdpbi5sZWZ0O1xuICAgICAgICBvcmlnaW4ueSA9ICgneScgaW4gb3JpZ2luKT8gb3JpZ2luLnkgOiBvcmlnaW4udG9wO1xuXG4gICAgICAgIHJldHVybiBvcmlnaW47XG4gICAgfTtcblxuICAgIC8vIGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9hLzU2MzQ1MjgvMjI4MDg4OFxuICAgIHNjb3BlLl9nZXRRQmV6aWVyVmFsdWUgPSBmdW5jdGlvbiAodCwgcDEsIHAyLCBwMykge1xuICAgICAgICB2YXIgaVQgPSAxIC0gdDtcbiAgICAgICAgcmV0dXJuIGlUICogaVQgKiBwMSArIDIgKiBpVCAqIHQgKiBwMiArIHQgKiB0ICogcDM7XG4gICAgfTtcblxuICAgIHNjb3BlLmdldFF1YWRyYXRpY0N1cnZlUG9pbnQgPSBmdW5jdGlvbiAoc3RhcnRYLCBzdGFydFksIGNwWCwgY3BZLCBlbmRYLCBlbmRZLCBwb3NpdGlvbikge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgeDogIHNjb3BlLl9nZXRRQmV6aWVyVmFsdWUocG9zaXRpb24sIHN0YXJ0WCwgY3BYLCBlbmRYKSxcbiAgICAgICAgICAgIHk6ICBzY29wZS5fZ2V0UUJlemllclZhbHVlKHBvc2l0aW9uLCBzdGFydFksIGNwWSwgZW5kWSlcbiAgICAgICAgfTtcbiAgICB9O1xuXG4gICAgLy8gaHR0cDovL2dpem1hLmNvbS9lYXNpbmcvXG4gICAgc2NvcGUuZWFzZU91dFF1YWQgPSBmdW5jdGlvbiAodCwgYiwgYywgZCkge1xuICAgICAgICB0IC89IGQ7XG4gICAgICAgIHJldHVybiAtYyAqIHQqKHQtMikgKyBiO1xuICAgIH07XG5cbiAgICBzY29wZS5ub2RlQ29udGFpbnMgPSBmdW5jdGlvbiAocGFyZW50LCBjaGlsZCkge1xuICAgICAgICB3aGlsZSAoY2hpbGQpIHtcbiAgICAgICAgICAgIGlmIChjaGlsZCA9PT0gcGFyZW50KSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNoaWxkID0gY2hpbGQucGFyZW50Tm9kZTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9O1xuXG4gICAgc2NvcGUuY2xvc2VzdCA9IGZ1bmN0aW9uIChjaGlsZCwgc2VsZWN0b3IpIHtcbiAgICAgICAgdmFyIHBhcmVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoY2hpbGQpO1xuXG4gICAgICAgIHdoaWxlICh1dGlscy5pc0VsZW1lbnQocGFyZW50KSkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLm1hdGNoZXNTZWxlY3RvcihwYXJlbnQsIHNlbGVjdG9yKSkgeyByZXR1cm4gcGFyZW50OyB9XG5cbiAgICAgICAgICAgIHBhcmVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQocGFyZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBudWxsO1xuICAgIH07XG5cbiAgICBzY29wZS5wYXJlbnRFbGVtZW50ID0gZnVuY3Rpb24gKG5vZGUpIHtcbiAgICAgICAgdmFyIHBhcmVudCA9IG5vZGUucGFyZW50Tm9kZTtcblxuICAgICAgICBpZiAoc2NvcGUuaXNEb2NGcmFnKHBhcmVudCkpIHtcbiAgICAgICAgICAgIC8vIHNraXAgcGFzdCAjc2hhZG8tcm9vdCBmcmFnbWVudHNcbiAgICAgICAgICAgIHdoaWxlICgocGFyZW50ID0gcGFyZW50Lmhvc3QpICYmIHNjb3BlLmlzRG9jRnJhZyhwYXJlbnQpKSB7fVxuXG4gICAgICAgICAgICByZXR1cm4gcGFyZW50O1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHBhcmVudDtcbiAgICB9O1xuXG4gICAgc2NvcGUuaW5Db250ZXh0ID0gZnVuY3Rpb24gKGludGVyYWN0YWJsZSwgZWxlbWVudCkge1xuICAgICAgICByZXR1cm4gaW50ZXJhY3RhYmxlLl9jb250ZXh0ID09PSBlbGVtZW50Lm93bmVyRG9jdW1lbnRcbiAgICAgICAgICAgICAgICB8fCBzY29wZS5ub2RlQ29udGFpbnMoaW50ZXJhY3RhYmxlLl9jb250ZXh0LCBlbGVtZW50KTtcbiAgICB9O1xuXG4gICAgc2NvcGUudGVzdElnbm9yZSA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGludGVyYWN0YWJsZUVsZW1lbnQsIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIGlnbm9yZUZyb20gPSBpbnRlcmFjdGFibGUub3B0aW9ucy5pZ25vcmVGcm9tO1xuXG4gICAgICAgIGlmICghaWdub3JlRnJvbSB8fCAhdXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgICAgIGlmIChzY29wZS5pc1N0cmluZyhpZ25vcmVGcm9tKSkge1xuICAgICAgICAgICAgcmV0dXJuIHNjb3BlLm1hdGNoZXNVcFRvKGVsZW1lbnQsIGlnbm9yZUZyb20sIGludGVyYWN0YWJsZUVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHV0aWxzLmlzRWxlbWVudChpZ25vcmVGcm9tKSkge1xuICAgICAgICAgICAgcmV0dXJuIHNjb3BlLm5vZGVDb250YWlucyhpZ25vcmVGcm9tLCBlbGVtZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9O1xuXG4gICAgc2NvcGUudGVzdEFsbG93ID0gZnVuY3Rpb24gKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlRWxlbWVudCwgZWxlbWVudCkge1xuICAgICAgICB2YXIgYWxsb3dGcm9tID0gaW50ZXJhY3RhYmxlLm9wdGlvbnMuYWxsb3dGcm9tO1xuXG4gICAgICAgIGlmICghYWxsb3dGcm9tKSB7IHJldHVybiB0cnVlOyB9XG5cbiAgICAgICAgaWYgKCF1dGlscy5pc0VsZW1lbnQoZWxlbWVudCkpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKGFsbG93RnJvbSkpIHtcbiAgICAgICAgICAgIHJldHVybiBzY29wZS5tYXRjaGVzVXBUbyhlbGVtZW50LCBhbGxvd0Zyb20sIGludGVyYWN0YWJsZUVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHV0aWxzLmlzRWxlbWVudChhbGxvd0Zyb20pKSB7XG4gICAgICAgICAgICByZXR1cm4gc2NvcGUubm9kZUNvbnRhaW5zKGFsbG93RnJvbSwgZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfTtcblxuICAgIHNjb3BlLmNoZWNrQXhpcyA9IGZ1bmN0aW9uIChheGlzLCBpbnRlcmFjdGFibGUpIHtcbiAgICAgICAgaWYgKCFpbnRlcmFjdGFibGUpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICAgICAgdmFyIHRoaXNBeGlzID0gaW50ZXJhY3RhYmxlLm9wdGlvbnMuZHJhZy5heGlzO1xuXG4gICAgICAgIHJldHVybiAoYXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gYXhpcyk7XG4gICAgfTtcblxuICAgIHNjb3BlLmNoZWNrU25hcCA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGFjdGlvbikge1xuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zO1xuXG4gICAgICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgICAgICBhY3Rpb24gPSAncmVzaXplJztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBvcHRpb25zW2FjdGlvbl0uc25hcCAmJiBvcHRpb25zW2FjdGlvbl0uc25hcC5lbmFibGVkO1xuICAgIH07XG5cbiAgICBzY29wZS5jaGVja1Jlc3RyaWN0ID0gZnVuY3Rpb24gKGludGVyYWN0YWJsZSwgYWN0aW9uKSB7XG4gICAgICAgIHZhciBvcHRpb25zID0gaW50ZXJhY3RhYmxlLm9wdGlvbnM7XG5cbiAgICAgICAgaWYgKC9ecmVzaXplLy50ZXN0KGFjdGlvbikpIHtcbiAgICAgICAgICAgIGFjdGlvbiA9ICdyZXNpemUnO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuICBvcHRpb25zW2FjdGlvbl0ucmVzdHJpY3QgJiYgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0LmVuYWJsZWQ7XG4gICAgfTtcblxuICAgIHNjb3BlLmNoZWNrQXV0b1Njcm9sbCA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGFjdGlvbikge1xuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zO1xuXG4gICAgICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgICAgICBhY3Rpb24gPSAncmVzaXplJztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiAgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwgJiYgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwuZW5hYmxlZDtcbiAgICB9O1xuXG4gICAgc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdCA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGFjdGlvbikge1xuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICAgICAgbWF4QWN0aW9ucyA9IG9wdGlvbnNbYWN0aW9uLm5hbWVdLm1heCxcbiAgICAgICAgICAgIG1heFBlckVsZW1lbnQgPSBvcHRpb25zW2FjdGlvbi5uYW1lXS5tYXhQZXJFbGVtZW50LFxuICAgICAgICAgICAgYWN0aXZlSW50ZXJhY3Rpb25zID0gMCxcbiAgICAgICAgICAgIHRhcmdldENvdW50ID0gMCxcbiAgICAgICAgICAgIHRhcmdldEVsZW1lbnRDb3VudCA9IDA7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IHNjb3BlLmludGVyYWN0aW9ucy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgdmFyIGludGVyYWN0aW9uID0gc2NvcGUuaW50ZXJhY3Rpb25zW2ldLFxuICAgICAgICAgICAgICAgIG90aGVyQWN0aW9uID0gaW50ZXJhY3Rpb24ucHJlcGFyZWQubmFtZSxcbiAgICAgICAgICAgICAgICBhY3RpdmUgPSBpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgICAgICBpZiAoIWFjdGl2ZSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICBhY3RpdmVJbnRlcmFjdGlvbnMrKztcblxuICAgICAgICAgICAgaWYgKGFjdGl2ZUludGVyYWN0aW9ucyA+PSBzY29wZS5tYXhJbnRlcmFjdGlvbnMpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi50YXJnZXQgIT09IGludGVyYWN0YWJsZSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICB0YXJnZXRDb3VudCArPSAob3RoZXJBY3Rpb24gPT09IGFjdGlvbi5uYW1lKXwwO1xuXG4gICAgICAgICAgICBpZiAodGFyZ2V0Q291bnQgPj0gbWF4QWN0aW9ucykge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLmVsZW1lbnQgPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICB0YXJnZXRFbGVtZW50Q291bnQrKztcblxuICAgICAgICAgICAgICAgIGlmIChvdGhlckFjdGlvbiAhPT0gYWN0aW9uLm5hbWUgfHwgdGFyZ2V0RWxlbWVudENvdW50ID49IG1heFBlckVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBzY29wZS5tYXhJbnRlcmFjdGlvbnMgPiAwO1xuICAgIH07XG5cbiAgICAvLyBUZXN0IGZvciB0aGUgZWxlbWVudCB0aGF0J3MgXCJhYm92ZVwiIGFsbCBvdGhlciBxdWFsaWZpZXJzXG4gICAgc2NvcGUuaW5kZXhPZkRlZXBlc3RFbGVtZW50ID0gZnVuY3Rpb24gKGVsZW1lbnRzKSB7XG4gICAgICAgIHZhciBkcm9wem9uZSxcbiAgICAgICAgICAgIGRlZXBlc3Rab25lID0gZWxlbWVudHNbMF0sXG4gICAgICAgICAgICBpbmRleCA9IGRlZXBlc3Rab25lPyAwOiAtMSxcbiAgICAgICAgICAgIHBhcmVudCxcbiAgICAgICAgICAgIGRlZXBlc3Rab25lUGFyZW50cyA9IFtdLFxuICAgICAgICAgICAgZHJvcHpvbmVQYXJlbnRzID0gW10sXG4gICAgICAgICAgICBjaGlsZCxcbiAgICAgICAgICAgIGksXG4gICAgICAgICAgICBuO1xuXG4gICAgICAgIGZvciAoaSA9IDE7IGkgPCBlbGVtZW50cy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgZHJvcHpvbmUgPSBlbGVtZW50c1tpXTtcblxuICAgICAgICAgICAgLy8gYW4gZWxlbWVudCBtaWdodCBiZWxvbmcgdG8gbXVsdGlwbGUgc2VsZWN0b3IgZHJvcHpvbmVzXG4gICAgICAgICAgICBpZiAoIWRyb3B6b25lIHx8IGRyb3B6b25lID09PSBkZWVwZXN0Wm9uZSkge1xuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoIWRlZXBlc3Rab25lKSB7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmUgPSBkcm9wem9uZTtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIGNoZWNrIGlmIHRoZSBkZWVwZXN0IG9yIGN1cnJlbnQgYXJlIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCBvciBkb2N1bWVudC5yb290RWxlbWVudFxuICAgICAgICAgICAgLy8gLSBpZiB0aGUgY3VycmVudCBkcm9wem9uZSBpcywgZG8gbm90aGluZyBhbmQgY29udGludWVcbiAgICAgICAgICAgIGlmIChkcm9wem9uZS5wYXJlbnROb2RlID09PSBkcm9wem9uZS5vd25lckRvY3VtZW50KSB7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyAtIGlmIGRlZXBlc3QgaXMsIHVwZGF0ZSB3aXRoIHRoZSBjdXJyZW50IGRyb3B6b25lIGFuZCBjb250aW51ZSB0byBuZXh0XG4gICAgICAgICAgICBlbHNlIGlmIChkZWVwZXN0Wm9uZS5wYXJlbnROb2RlID09PSBkcm9wem9uZS5vd25lckRvY3VtZW50KSB7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmUgPSBkcm9wem9uZTtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghZGVlcGVzdFpvbmVQYXJlbnRzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHBhcmVudCA9IGRlZXBlc3Rab25lO1xuICAgICAgICAgICAgICAgIHdoaWxlIChwYXJlbnQucGFyZW50Tm9kZSAmJiBwYXJlbnQucGFyZW50Tm9kZSAhPT0gcGFyZW50Lm93bmVyRG9jdW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgZGVlcGVzdFpvbmVQYXJlbnRzLnVuc2hpZnQocGFyZW50KTtcbiAgICAgICAgICAgICAgICAgICAgcGFyZW50ID0gcGFyZW50LnBhcmVudE5vZGU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBpZiB0aGlzIGVsZW1lbnQgaXMgYW4gc3ZnIGVsZW1lbnQgYW5kIHRoZSBjdXJyZW50IGRlZXBlc3QgaXNcbiAgICAgICAgICAgIC8vIGFuIEhUTUxFbGVtZW50XG4gICAgICAgICAgICBpZiAoZGVlcGVzdFpvbmUgaW5zdGFuY2VvZiBzY29wZS5IVE1MRWxlbWVudFxuICAgICAgICAgICAgICAgICYmIGRyb3B6b25lIGluc3RhbmNlb2Ygc2NvcGUuU1ZHRWxlbWVudFxuICAgICAgICAgICAgICAgICYmICEoZHJvcHpvbmUgaW5zdGFuY2VvZiBzY29wZS5TVkdTVkdFbGVtZW50KSkge1xuXG4gICAgICAgICAgICAgICAgaWYgKGRyb3B6b25lID09PSBkZWVwZXN0Wm9uZS5wYXJlbnROb2RlKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHBhcmVudCA9IGRyb3B6b25lLm93bmVyU1ZHRWxlbWVudDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHBhcmVudCA9IGRyb3B6b25lO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBkcm9wem9uZVBhcmVudHMgPSBbXTtcblxuICAgICAgICAgICAgd2hpbGUgKHBhcmVudC5wYXJlbnROb2RlICE9PSBwYXJlbnQub3duZXJEb2N1bWVudCkge1xuICAgICAgICAgICAgICAgIGRyb3B6b25lUGFyZW50cy51bnNoaWZ0KHBhcmVudCk7XG4gICAgICAgICAgICAgICAgcGFyZW50ID0gcGFyZW50LnBhcmVudE5vZGU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIG4gPSAwO1xuXG4gICAgICAgICAgICAvLyBnZXQgKHBvc2l0aW9uIG9mIGxhc3QgY29tbW9uIGFuY2VzdG9yKSArIDFcbiAgICAgICAgICAgIHdoaWxlIChkcm9wem9uZVBhcmVudHNbbl0gJiYgZHJvcHpvbmVQYXJlbnRzW25dID09PSBkZWVwZXN0Wm9uZVBhcmVudHNbbl0pIHtcbiAgICAgICAgICAgICAgICBuKys7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBwYXJlbnRzID0gW1xuICAgICAgICAgICAgICAgIGRyb3B6b25lUGFyZW50c1tuIC0gMV0sXG4gICAgICAgICAgICAgICAgZHJvcHpvbmVQYXJlbnRzW25dLFxuICAgICAgICAgICAgICAgIGRlZXBlc3Rab25lUGFyZW50c1tuXVxuICAgICAgICAgICAgXTtcblxuICAgICAgICAgICAgY2hpbGQgPSBwYXJlbnRzWzBdLmxhc3RDaGlsZDtcblxuICAgICAgICAgICAgd2hpbGUgKGNoaWxkKSB7XG4gICAgICAgICAgICAgICAgaWYgKGNoaWxkID09PSBwYXJlbnRzWzFdKSB7XG4gICAgICAgICAgICAgICAgICAgIGRlZXBlc3Rab25lID0gZHJvcHpvbmU7XG4gICAgICAgICAgICAgICAgICAgIGluZGV4ID0gaTtcbiAgICAgICAgICAgICAgICAgICAgZGVlcGVzdFpvbmVQYXJlbnRzID0gW107XG5cbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKGNoaWxkID09PSBwYXJlbnRzWzJdKSB7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNoaWxkID0gY2hpbGQucHJldmlvdXNTaWJsaW5nO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGluZGV4O1xuICAgIH07XG5cbiAgICBzY29wZS5tYXRjaGVzU2VsZWN0b3IgPSBmdW5jdGlvbiAoZWxlbWVudCwgc2VsZWN0b3IsIG5vZGVMaXN0KSB7XG4gICAgICAgIGlmIChzY29wZS5pZThNYXRjaGVzU2VsZWN0b3IpIHtcbiAgICAgICAgICAgIHJldHVybiBzY29wZS5pZThNYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIG5vZGVMaXN0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHJlbW92ZSAvZGVlcC8gZnJvbSBzZWxlY3RvcnMgaWYgc2hhZG93RE9NIHBvbHlmaWxsIGlzIHVzZWRcbiAgICAgICAgaWYgKHNjb3BlLndpbmRvdyAhPT0gc2NvcGUucmVhbFdpbmRvdykge1xuICAgICAgICAgICAgc2VsZWN0b3IgPSBzZWxlY3Rvci5yZXBsYWNlKC9cXC9kZWVwXFwvL2csICcgJyk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZWxlbWVudFtzY29wZS5wcmVmaXhlZE1hdGNoZXNTZWxlY3Rvcl0oc2VsZWN0b3IpO1xuICAgIH07XG5cbiAgICBzY29wZS5tYXRjaGVzVXBUbyA9IGZ1bmN0aW9uIChlbGVtZW50LCBzZWxlY3RvciwgbGltaXQpIHtcbiAgICAgICAgd2hpbGUgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLm1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBzZWxlY3RvcikpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG5cbiAgICAgICAgICAgIGlmIChlbGVtZW50ID09PSBsaW1pdCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH07XG5cbiAgICAvLyBGb3IgSUU4J3MgbGFjayBvZiBhbiBFbGVtZW50I21hdGNoZXNTZWxlY3RvclxuICAgIC8vIHRha2VuIGZyb20gaHR0cDovL3RhbmFsaW4uY29tL2VuL2Jsb2cvMjAxMi8xMi9tYXRjaGVzLXNlbGVjdG9yLWllOC8gYW5kIG1vZGlmaWVkXG4gICAgaWYgKCEoc2NvcGUucHJlZml4ZWRNYXRjaGVzU2VsZWN0b3IgaW4gRWxlbWVudC5wcm90b3R5cGUpIHx8ICFzY29wZS5pc0Z1bmN0aW9uKEVsZW1lbnQucHJvdG90eXBlW3Njb3BlLnByZWZpeGVkTWF0Y2hlc1NlbGVjdG9yXSkpIHtcbiAgICAgICAgc2NvcGUuaWU4TWF0Y2hlc1NlbGVjdG9yID0gZnVuY3Rpb24gKGVsZW1lbnQsIHNlbGVjdG9yLCBlbGVtcykge1xuICAgICAgICAgICAgZWxlbXMgPSBlbGVtcyB8fCBlbGVtZW50LnBhcmVudE5vZGUucXVlcnlTZWxlY3RvckFsbChzZWxlY3Rvcik7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBlbGVtcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIGlmIChlbGVtc1tpXSA9PT0gZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICB2YXIgSW50ZXJhY3Rpb24gPSByZXF1aXJlKCcuL0ludGVyYWN0aW9uJyk7XG5cbiAgICBmdW5jdGlvbiBnZXRJbnRlcmFjdGlvbkZyb21Qb2ludGVyIChwb2ludGVyLCBldmVudFR5cGUsIGV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciBpID0gMCwgbGVuID0gc2NvcGUuaW50ZXJhY3Rpb25zLmxlbmd0aCxcbiAgICAgICAgICAgIG1vdXNlRXZlbnQgPSAoL21vdXNlL2kudGVzdChwb2ludGVyLnBvaW50ZXJUeXBlIHx8IGV2ZW50VHlwZSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gTVNQb2ludGVyRXZlbnQuTVNQT0lOVEVSX1RZUEVfTU9VU0VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgfHwgcG9pbnRlci5wb2ludGVyVHlwZSA9PT0gNCksXG4gICAgICAgICAgICBpbnRlcmFjdGlvbjtcblxuICAgICAgICB2YXIgaWQgPSB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcik7XG5cbiAgICAgICAgLy8gdHJ5IHRvIHJlc3VtZSBpbmVydGlhIHdpdGggYSBuZXcgcG9pbnRlclxuICAgICAgICBpZiAoL2Rvd258c3RhcnQvaS50ZXN0KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIGludGVyYWN0aW9uID0gc2NvcGUuaW50ZXJhY3Rpb25zW2ldO1xuXG4gICAgICAgICAgICAgICAgdmFyIGVsZW1lbnQgPSBldmVudFRhcmdldDtcblxuICAgICAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLmFjdGl2ZSAmJiBpbnRlcmFjdGlvbi50YXJnZXQub3B0aW9uc1tpbnRlcmFjdGlvbi5wcmVwYXJlZC5uYW1lXS5pbmVydGlhLmFsbG93UmVzdW1lXG4gICAgICAgICAgICAgICAgICAgICYmIChpbnRlcmFjdGlvbi5tb3VzZSA9PT0gbW91c2VFdmVudCkpIHtcbiAgICAgICAgICAgICAgICAgICAgd2hpbGUgKGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBlbGVtZW50IGlzIHRoZSBpbnRlcmFjdGlvbiBlbGVtZW50XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCA9PT0gaW50ZXJhY3Rpb24uZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgaW50ZXJhY3Rpb24ncyBwb2ludGVyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLnBvaW50ZXJzWzBdKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLnJlbW92ZVBvaW50ZXIoaW50ZXJhY3Rpb24ucG9pbnRlcnNbMF0pO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5hZGRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBpZiBpdCdzIGEgbW91c2UgaW50ZXJhY3Rpb25cbiAgICAgICAgaWYgKG1vdXNlRXZlbnQgfHwgIShicm93c2VyLnN1cHBvcnRzVG91Y2ggfHwgYnJvd3Nlci5zdXBwb3J0c1BvaW50ZXJFdmVudCkpIHtcblxuICAgICAgICAgICAgLy8gZmluZCBhIG1vdXNlIGludGVyYWN0aW9uIHRoYXQncyBub3QgaW4gaW5lcnRpYSBwaGFzZVxuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLmludGVyYWN0aW9uc1tpXS5tb3VzZSAmJiAhc2NvcGUuaW50ZXJhY3Rpb25zW2ldLmluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBzY29wZS5pbnRlcmFjdGlvbnNbaV07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBmaW5kIGFueSBpbnRlcmFjdGlvbiBzcGVjaWZpY2FsbHkgZm9yIG1vdXNlLlxuICAgICAgICAgICAgLy8gaWYgdGhlIGV2ZW50VHlwZSBpcyBhIG1vdXNlZG93biwgYW5kIGluZXJ0aWEgaXMgYWN0aXZlXG4gICAgICAgICAgICAvLyBpZ25vcmUgdGhlIGludGVyYWN0aW9uXG4gICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaW50ZXJhY3Rpb25zW2ldLm1vdXNlICYmICEoL2Rvd24vLnRlc3QoZXZlbnRUeXBlKSAmJiBzY29wZS5pbnRlcmFjdGlvbnNbaV0uaW5lcnRpYVN0YXR1cy5hY3RpdmUpKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIGNyZWF0ZSBhIG5ldyBpbnRlcmFjdGlvbiBmb3IgbW91c2VcbiAgICAgICAgICAgIGludGVyYWN0aW9uID0gbmV3IEludGVyYWN0aW9uKCk7XG4gICAgICAgICAgICBpbnRlcmFjdGlvbi5tb3VzZSA9IHRydWU7XG5cbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGdldCBpbnRlcmFjdGlvbiB0aGF0IGhhcyB0aGlzIHBvaW50ZXJcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuY29udGFpbnMoc2NvcGUuaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJJZHMsIGlkKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBzY29wZS5pbnRlcmFjdGlvbnNbaV07XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBhdCB0aGlzIHN0YWdlLCBhIHBvaW50ZXJVcCBzaG91bGQgbm90IHJldHVybiBhbiBpbnRlcmFjdGlvblxuICAgICAgICBpZiAoL3VwfGVuZHxvdXQvaS50ZXN0KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gZ2V0IGZpcnN0IGlkbGUgaW50ZXJhY3Rpb25cbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpbnRlcmFjdGlvbiA9IHNjb3BlLmludGVyYWN0aW9uc1tpXTtcblxuICAgICAgICAgICAgaWYgKCghaW50ZXJhY3Rpb24ucHJlcGFyZWQubmFtZSB8fCAoaW50ZXJhY3Rpb24udGFyZ2V0Lm9wdGlvbnMuZ2VzdHVyZS5lbmFibGVkKSlcbiAgICAgICAgICAgICAgICAmJiAhaW50ZXJhY3Rpb24uaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgICAgICYmICEoIW1vdXNlRXZlbnQgJiYgaW50ZXJhY3Rpb24ubW91c2UpKSB7XG5cbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5hZGRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG5ldyBJbnRlcmFjdGlvbigpO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIGRvT25JbnRlcmFjdGlvbnMgKG1ldGhvZCkge1xuICAgICAgICByZXR1cm4gKGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICAgICAgdmFyIGludGVyYWN0aW9uLFxuICAgICAgICAgICAgICAgIGV2ZW50VGFyZ2V0ID0gc2NvcGUuZ2V0QWN0dWFsRWxlbWVudChldmVudC5wYXRoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID8gZXZlbnQucGF0aFswXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IGV2ZW50LnRhcmdldCksXG4gICAgICAgICAgICAgICAgY3VyRXZlbnRUYXJnZXQgPSBzY29wZS5nZXRBY3R1YWxFbGVtZW50KGV2ZW50LmN1cnJlbnRUYXJnZXQpLFxuICAgICAgICAgICAgICAgIGk7XG5cbiAgICAgICAgICAgIGlmIChicm93c2VyLnN1cHBvcnRzVG91Y2ggJiYgL3RvdWNoLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICAgICAgc2NvcGUucHJldlRvdWNoVGltZSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuXG4gICAgICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGV2ZW50LmNoYW5nZWRUb3VjaGVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBwb2ludGVyID0gZXZlbnQuY2hhbmdlZFRvdWNoZXNbaV07XG5cbiAgICAgICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBnZXRJbnRlcmFjdGlvbkZyb21Qb2ludGVyKHBvaW50ZXIsIGV2ZW50LnR5cGUsIGV2ZW50VGFyZ2V0KTtcblxuICAgICAgICAgICAgICAgICAgICBpZiAoIWludGVyYWN0aW9uKSB7IGNvbnRpbnVlOyB9XG5cbiAgICAgICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uX3VwZGF0ZUV2ZW50VGFyZ2V0cyhldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uW21ldGhvZF0ocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgaWYgKCFicm93c2VyLnN1cHBvcnRzUG9pbnRlckV2ZW50ICYmIC9tb3VzZS8udGVzdChldmVudC50eXBlKSkge1xuICAgICAgICAgICAgICAgICAgICAvLyBpZ25vcmUgbW91c2UgZXZlbnRzIHdoaWxlIHRvdWNoIGludGVyYWN0aW9ucyBhcmUgYWN0aXZlXG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBzY29wZS5pbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmICghc2NvcGUuaW50ZXJhY3Rpb25zW2ldLm1vdXNlICYmIHNjb3BlLmludGVyYWN0aW9uc1tpXS5wb2ludGVySXNEb3duKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gdHJ5IHRvIGlnbm9yZSBtb3VzZSBldmVudHMgdGhhdCBhcmUgc2ltdWxhdGVkIGJ5IHRoZSBicm93c2VyXG4gICAgICAgICAgICAgICAgICAgIC8vIGFmdGVyIGEgdG91Y2ggZXZlbnRcbiAgICAgICAgICAgICAgICAgICAgaWYgKG5ldyBEYXRlKCkuZ2V0VGltZSgpIC0gc2NvcGUucHJldlRvdWNoVGltZSA8IDUwMCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBnZXRJbnRlcmFjdGlvbkZyb21Qb2ludGVyKGV2ZW50LCBldmVudC50eXBlLCBldmVudFRhcmdldCk7XG5cbiAgICAgICAgICAgICAgICBpZiAoIWludGVyYWN0aW9uKSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uX3VwZGF0ZUV2ZW50VGFyZ2V0cyhldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb25bbWV0aG9kXShldmVudCwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIHByZXZlbnRPcmlnaW5hbERlZmF1bHQgKCkge1xuICAgICAgICB0aGlzLm9yaWdpbmFsRXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICB9XG5cbiAgICBmdW5jdGlvbiBjaGVja1Jlc2l6ZUVkZ2UgKG5hbWUsIHZhbHVlLCBwYWdlLCBlbGVtZW50LCBpbnRlcmFjdGFibGVFbGVtZW50LCByZWN0LCBtYXJnaW4pIHtcbiAgICAgICAgLy8gZmFsc2UsICcnLCB1bmRlZmluZWQsIG51bGxcbiAgICAgICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgICAgICAvLyB0cnVlIHZhbHVlLCB1c2UgcG9pbnRlciBjb29yZHMgYW5kIGVsZW1lbnQgcmVjdFxuICAgICAgICBpZiAodmFsdWUgPT09IHRydWUpIHtcbiAgICAgICAgICAgIC8vIGlmIGRpbWVuc2lvbnMgYXJlIG5lZ2F0aXZlLCBcInN3aXRjaFwiIGVkZ2VzXG4gICAgICAgICAgICB2YXIgd2lkdGggPSBzY29wZS5pc051bWJlcihyZWN0LndpZHRoKT8gcmVjdC53aWR0aCA6IHJlY3QucmlnaHQgLSByZWN0LmxlZnQsXG4gICAgICAgICAgICAgICAgaGVpZ2h0ID0gc2NvcGUuaXNOdW1iZXIocmVjdC5oZWlnaHQpPyByZWN0LmhlaWdodCA6IHJlY3QuYm90dG9tIC0gcmVjdC50b3A7XG5cbiAgICAgICAgICAgIGlmICh3aWR0aCA8IDApIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChuYW1lID09PSAnbGVmdCcgKSB7IG5hbWUgPSAncmlnaHQnOyB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAobmFtZSA9PT0gJ3JpZ2h0JykgeyBuYW1lID0gJ2xlZnQnIDsgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGhlaWdodCA8IDApIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChuYW1lID09PSAndG9wJyAgICkgeyBuYW1lID0gJ2JvdHRvbSc7IH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChuYW1lID09PSAnYm90dG9tJykgeyBuYW1lID0gJ3RvcCcgICA7IH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKG5hbWUgPT09ICdsZWZ0JyAgKSB7IHJldHVybiBwYWdlLnggPCAoKHdpZHRoICA+PSAwPyByZWN0LmxlZnQ6IHJlY3QucmlnaHQgKSArIG1hcmdpbik7IH1cbiAgICAgICAgICAgIGlmIChuYW1lID09PSAndG9wJyAgICkgeyByZXR1cm4gcGFnZS55IDwgKChoZWlnaHQgPj0gMD8gcmVjdC50b3AgOiByZWN0LmJvdHRvbSkgKyBtYXJnaW4pOyB9XG5cbiAgICAgICAgICAgIGlmIChuYW1lID09PSAncmlnaHQnICkgeyByZXR1cm4gcGFnZS54ID4gKCh3aWR0aCAgPj0gMD8gcmVjdC5yaWdodCA6IHJlY3QubGVmdCkgLSBtYXJnaW4pOyB9XG4gICAgICAgICAgICBpZiAobmFtZSA9PT0gJ2JvdHRvbScpIHsgcmV0dXJuIHBhZ2UueSA+ICgoaGVpZ2h0ID49IDA/IHJlY3QuYm90dG9tOiByZWN0LnRvcCApIC0gbWFyZ2luKTsgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gdGhlIHJlbWFpbmluZyBjaGVja3MgcmVxdWlyZSBhbiBlbGVtZW50XG4gICAgICAgIGlmICghdXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgICAgIHJldHVybiB1dGlscy5pc0VsZW1lbnQodmFsdWUpXG4gICAgICAgICAgICAgICAgICAgIC8vIHRoZSB2YWx1ZSBpcyBhbiBlbGVtZW50IHRvIHVzZSBhcyBhIHJlc2l6ZSBoYW5kbGVcbiAgICAgICAgICAgICAgICAgICAgPyB2YWx1ZSA9PT0gZWxlbWVudFxuICAgICAgICAgICAgICAgICAgICAvLyBvdGhlcndpc2UgY2hlY2sgaWYgZWxlbWVudCBtYXRjaGVzIHZhbHVlIGFzIHNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgIDogc2NvcGUubWF0Y2hlc1VwVG8oZWxlbWVudCwgdmFsdWUsIGludGVyYWN0YWJsZUVsZW1lbnQpO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIGRlZmF1bHRBY3Rpb25DaGVja2VyIChwb2ludGVyLCBpbnRlcmFjdGlvbiwgZWxlbWVudCkge1xuICAgICAgICB2YXIgcmVjdCA9IHRoaXMuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgICAgIHNob3VsZFJlc2l6ZSA9IGZhbHNlLFxuICAgICAgICAgICAgYWN0aW9uID0gbnVsbCxcbiAgICAgICAgICAgIHJlc2l6ZUF4ZXMgPSBudWxsLFxuICAgICAgICAgICAgcmVzaXplRWRnZXMsXG4gICAgICAgICAgICBwYWdlID0gdXRpbHMuZXh0ZW5kKHt9LCBpbnRlcmFjdGlvbi5jdXJDb29yZHMucGFnZSksXG4gICAgICAgICAgICBvcHRpb25zID0gdGhpcy5vcHRpb25zO1xuXG4gICAgICAgIGlmICghcmVjdCkgeyByZXR1cm4gbnVsbDsgfVxuXG4gICAgICAgIGlmIChzY29wZS5hY3Rpb25Jc0VuYWJsZWQucmVzaXplICYmIG9wdGlvbnMucmVzaXplLmVuYWJsZWQpIHtcbiAgICAgICAgICAgIHZhciByZXNpemVPcHRpb25zID0gb3B0aW9ucy5yZXNpemU7XG5cbiAgICAgICAgICAgIHJlc2l6ZUVkZ2VzID0ge1xuICAgICAgICAgICAgICAgIGxlZnQ6IGZhbHNlLCByaWdodDogZmFsc2UsIHRvcDogZmFsc2UsIGJvdHRvbTogZmFsc2VcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIC8vIGlmIHVzaW5nIHJlc2l6ZS5lZGdlc1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KHJlc2l6ZU9wdGlvbnMuZWRnZXMpKSB7XG4gICAgICAgICAgICAgICAgZm9yICh2YXIgZWRnZSBpbiByZXNpemVFZGdlcykge1xuICAgICAgICAgICAgICAgICAgICByZXNpemVFZGdlc1tlZGdlXSA9IGNoZWNrUmVzaXplRWRnZShlZGdlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNpemVPcHRpb25zLmVkZ2VzW2VkZ2VdLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWdlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5fZXZlbnRUYXJnZXQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZW1lbnQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY3QsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc2l6ZU9wdGlvbnMubWFyZ2luIHx8IHNjb3BlLm1hcmdpbik7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmVzaXplRWRnZXMubGVmdCA9IHJlc2l6ZUVkZ2VzLmxlZnQgJiYgIXJlc2l6ZUVkZ2VzLnJpZ2h0O1xuICAgICAgICAgICAgICAgIHJlc2l6ZUVkZ2VzLnRvcCAgPSByZXNpemVFZGdlcy50b3AgICYmICFyZXNpemVFZGdlcy5ib3R0b207XG5cbiAgICAgICAgICAgICAgICBzaG91bGRSZXNpemUgPSByZXNpemVFZGdlcy5sZWZ0IHx8IHJlc2l6ZUVkZ2VzLnJpZ2h0IHx8IHJlc2l6ZUVkZ2VzLnRvcCB8fCByZXNpemVFZGdlcy5ib3R0b207XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB2YXIgcmlnaHQgID0gb3B0aW9ucy5yZXNpemUuYXhpcyAhPT0gJ3knICYmIHBhZ2UueCA+IChyZWN0LnJpZ2h0ICAtIHNjb3BlLm1hcmdpbiksXG4gICAgICAgICAgICAgICAgICAgIGJvdHRvbSA9IG9wdGlvbnMucmVzaXplLmF4aXMgIT09ICd4JyAmJiBwYWdlLnkgPiAocmVjdC5ib3R0b20gLSBzY29wZS5tYXJnaW4pO1xuXG4gICAgICAgICAgICAgICAgc2hvdWxkUmVzaXplID0gcmlnaHQgfHwgYm90dG9tO1xuICAgICAgICAgICAgICAgIHJlc2l6ZUF4ZXMgPSAocmlnaHQ/ICd4JyA6ICcnKSArIChib3R0b20/ICd5JyA6ICcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGFjdGlvbiA9IHNob3VsZFJlc2l6ZVxuICAgICAgICAgICAgPyAncmVzaXplJ1xuICAgICAgICAgICAgOiBzY29wZS5hY3Rpb25Jc0VuYWJsZWQuZHJhZyAmJiBvcHRpb25zLmRyYWcuZW5hYmxlZFxuICAgICAgICAgICAgICAgID8gJ2RyYWcnXG4gICAgICAgICAgICAgICAgOiBudWxsO1xuXG4gICAgICAgIGlmIChzY29wZS5hY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZVxuICAgICAgICAgICAgJiYgaW50ZXJhY3Rpb24ucG9pbnRlcklkcy5sZW5ndGggPj0yXG4gICAgICAgICAgICAmJiAhKGludGVyYWN0aW9uLmRyYWdnaW5nIHx8IGludGVyYWN0aW9uLnJlc2l6aW5nKSkge1xuICAgICAgICAgICAgYWN0aW9uID0gJ2dlc3R1cmUnO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGFjdGlvbikge1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICBuYW1lOiBhY3Rpb24sXG4gICAgICAgICAgICAgICAgYXhpczogcmVzaXplQXhlcyxcbiAgICAgICAgICAgICAgICBlZGdlczogcmVzaXplRWRnZXNcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICB2YXIgSW50ZXJhY3RFdmVudCA9IHJlcXVpcmUoJy4vSW50ZXJhY3RFdmVudCcpO1xuXG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGludGVyYWN0aW9uTGlzdGVuZXJzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIHZhciBsaXN0ZW5lck5hbWUgPSBpbnRlcmFjdGlvbkxpc3RlbmVyc1tpXTtcblxuICAgICAgICBzY29wZS5saXN0ZW5lcnNbbGlzdGVuZXJOYW1lXSA9IGRvT25JbnRlcmFjdGlvbnMobGlzdGVuZXJOYW1lKTtcbiAgICB9XG5cbiAgICAvLyBib3VuZCB0byB0aGUgaW50ZXJhY3RhYmxlIGNvbnRleHQgd2hlbiBhIERPTSBldmVudFxuICAgIC8vIGxpc3RlbmVyIGlzIGFkZGVkIHRvIGEgc2VsZWN0b3IgaW50ZXJhY3RhYmxlXG4gICAgZnVuY3Rpb24gZGVsZWdhdGVMaXN0ZW5lciAoZXZlbnQsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgdmFyIGZha2VFdmVudCA9IHt9LFxuICAgICAgICAgICAgZGVsZWdhdGVkID0gc2NvcGUuZGVsZWdhdGVkRXZlbnRzW2V2ZW50LnR5cGVdLFxuICAgICAgICAgICAgZXZlbnRUYXJnZXQgPSBzY29wZS5nZXRBY3R1YWxFbGVtZW50KGV2ZW50LnBhdGhcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA/IGV2ZW50LnBhdGhbMF1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IGV2ZW50LnRhcmdldCksXG4gICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgICAgIC8vIGR1cGxpY2F0ZSB0aGUgZXZlbnQgc28gdGhhdCBjdXJyZW50VGFyZ2V0IGNhbiBiZSBjaGFuZ2VkXG4gICAgICAgIGZvciAodmFyIHByb3AgaW4gZXZlbnQpIHtcbiAgICAgICAgICAgIGZha2VFdmVudFtwcm9wXSA9IGV2ZW50W3Byb3BdO1xuICAgICAgICB9XG5cbiAgICAgICAgZmFrZUV2ZW50Lm9yaWdpbmFsRXZlbnQgPSBldmVudDtcbiAgICAgICAgZmFrZUV2ZW50LnByZXZlbnREZWZhdWx0ID0gcHJldmVudE9yaWdpbmFsRGVmYXVsdDtcblxuICAgICAgICAvLyBjbGltYiB1cCBkb2N1bWVudCB0cmVlIGxvb2tpbmcgZm9yIHNlbGVjdG9yIG1hdGNoZXNcbiAgICAgICAgd2hpbGUgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgdmFyIHNlbGVjdG9yID0gZGVsZWdhdGVkLnNlbGVjdG9yc1tpXSxcbiAgICAgICAgICAgICAgICAgICAgY29udGV4dCA9IGRlbGVnYXRlZC5jb250ZXh0c1tpXTtcblxuICAgICAgICAgICAgICAgIGlmIChzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgICAgICYmIHNjb3BlLm5vZGVDb250YWlucyhjb250ZXh0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAgICAgJiYgc2NvcGUubm9kZUNvbnRhaW5zKGNvbnRleHQsIGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgICAgICAgICAgdmFyIGxpc3RlbmVycyA9IGRlbGVnYXRlZC5saXN0ZW5lcnNbaV07XG5cbiAgICAgICAgICAgICAgICAgICAgZmFrZUV2ZW50LmN1cnJlbnRUYXJnZXQgPSBlbGVtZW50O1xuXG4gICAgICAgICAgICAgICAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgbGlzdGVuZXJzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAobGlzdGVuZXJzW2pdWzFdID09PSB1c2VDYXB0dXJlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzW2pdWzBdKGZha2VFdmVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGVsZW1lbnQgPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gZGVsZWdhdGVVc2VDYXB0dXJlIChldmVudCkge1xuICAgICAgICByZXR1cm4gZGVsZWdhdGVMaXN0ZW5lci5jYWxsKHRoaXMsIGV2ZW50LCB0cnVlKTtcbiAgICB9XG5cbiAgICBzY29wZS5pbnRlcmFjdGFibGVzLmluZGV4T2ZFbGVtZW50ID0gZnVuY3Rpb24gaW5kZXhPZkVsZW1lbnQgKGVsZW1lbnQsIGNvbnRleHQpIHtcbiAgICAgICAgY29udGV4dCA9IGNvbnRleHQgfHwgc2NvcGUuZG9jdW1lbnQ7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gdGhpc1tpXTtcblxuICAgICAgICAgICAgaWYgKChpbnRlcmFjdGFibGUuc2VsZWN0b3IgPT09IGVsZW1lbnRcbiAgICAgICAgICAgICAgICAmJiAoaW50ZXJhY3RhYmxlLl9jb250ZXh0ID09PSBjb250ZXh0KSlcbiAgICAgICAgICAgICAgICB8fCAoIWludGVyYWN0YWJsZS5zZWxlY3RvciAmJiBpbnRlcmFjdGFibGUuX2VsZW1lbnQgPT09IGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gLTE7XG4gICAgfTtcblxuICAgIHNjb3BlLmludGVyYWN0YWJsZXMuZ2V0ID0gZnVuY3Rpb24gaW50ZXJhY3RhYmxlR2V0IChlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgICAgIHJldHVybiB0aGlzW3RoaXMuaW5kZXhPZkVsZW1lbnQoZWxlbWVudCwgb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQpXTtcbiAgICB9O1xuXG4gICAgc2NvcGUuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IgPSBmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gdGhpc1tpXTtcblxuICAgICAgICAgICAgaWYgKCFpbnRlcmFjdGFibGUuc2VsZWN0b3IpIHtcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIHJldCA9IGNhbGxiYWNrKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlLnNlbGVjdG9yLCBpbnRlcmFjdGFibGUuX2NvbnRleHQsIGksIHRoaXMpO1xuXG4gICAgICAgICAgICBpZiAocmV0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gcmV0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBUaGUgbWV0aG9kcyBvZiB0aGlzIHZhcmlhYmxlIGNhbiBiZSB1c2VkIHRvIHNldCBlbGVtZW50cyBhc1xuICAgICAqIGludGVyYWN0YWJsZXMgYW5kIGFsc28gdG8gY2hhbmdlIHZhcmlvdXMgZGVmYXVsdCBzZXR0aW5ncy5cbiAgICAgKlxuICAgICAqIENhbGxpbmcgaXQgYXMgYSBmdW5jdGlvbiBhbmQgcGFzc2luZyBhbiBlbGVtZW50IG9yIGEgdmFsaWQgQ1NTIHNlbGVjdG9yXG4gICAgICogc3RyaW5nIHJldHVybnMgYW4gSW50ZXJhY3RhYmxlIG9iamVjdCB3aGljaCBoYXMgdmFyaW91cyBtZXRob2RzIHRvXG4gICAgICogY29uZmlndXJlIGl0LlxuICAgICAqXG4gICAgIC0gZWxlbWVudCAoRWxlbWVudCB8IHN0cmluZykgVGhlIEhUTUwgb3IgU1ZHIEVsZW1lbnQgdG8gaW50ZXJhY3Qgd2l0aCBvciBDU1Mgc2VsZWN0b3JcbiAgICAgPSAob2JqZWN0KSBBbiBASW50ZXJhY3RhYmxlXG4gICAgICpcbiAgICAgPiBVc2FnZVxuICAgICB8IGludGVyYWN0KGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkcmFnZ2FibGUnKSkuZHJhZ2dhYmxlKHRydWUpO1xuICAgICB8XG4gICAgIHwgdmFyIHJlY3RhYmxlcyA9IGludGVyYWN0KCdyZWN0Jyk7XG4gICAgIHwgcmVjdGFibGVzXG4gICAgIHwgICAgIC5nZXN0dXJhYmxlKHRydWUpXG4gICAgIHwgICAgIC5vbignZ2VzdHVyZW1vdmUnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgfCAgICAgICAgIC8vIHNvbWV0aGluZyBjb29sLi4uXG4gICAgIHwgICAgIH0pXG4gICAgIHwgICAgIC5hdXRvU2Nyb2xsKHRydWUpO1xuICAgIFxcKi9cbiAgICBmdW5jdGlvbiBpbnRlcmFjdCAoZWxlbWVudCwgb3B0aW9ucykge1xuICAgICAgICByZXR1cm4gc2NvcGUuaW50ZXJhY3RhYmxlcy5nZXQoZWxlbWVudCwgb3B0aW9ucykgfHwgbmV3IEludGVyYWN0YWJsZShlbGVtZW50LCBvcHRpb25zKTtcbiAgICB9XG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlXG4gICAgIFsgcHJvcGVydHkgXVxuICAgICAqKlxuICAgICAqIE9iamVjdCB0eXBlIHJldHVybmVkIGJ5IEBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBmdW5jdGlvbiBJbnRlcmFjdGFibGUgKGVsZW1lbnQsIG9wdGlvbnMpIHtcbiAgICAgICAgdGhpcy5fZWxlbWVudCA9IGVsZW1lbnQ7XG4gICAgICAgIHRoaXMuX2lFdmVudHMgPSB0aGlzLl9pRXZlbnRzIHx8IHt9O1xuXG4gICAgICAgIHZhciBfd2luZG93O1xuXG4gICAgICAgIGlmIChzY29wZS50cnlTZWxlY3RvcihlbGVtZW50KSkge1xuICAgICAgICAgICAgdGhpcy5zZWxlY3RvciA9IGVsZW1lbnQ7XG5cbiAgICAgICAgICAgIHZhciBjb250ZXh0ID0gb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQ7XG5cbiAgICAgICAgICAgIF93aW5kb3cgPSBjb250ZXh0PyBzY29wZS5nZXRXaW5kb3coY29udGV4dCkgOiBzY29wZS53aW5kb3c7XG5cbiAgICAgICAgICAgIGlmIChjb250ZXh0ICYmIChfd2luZG93Lk5vZGVcbiAgICAgICAgICAgICAgICAgICAgPyBjb250ZXh0IGluc3RhbmNlb2YgX3dpbmRvdy5Ob2RlXG4gICAgICAgICAgICAgICAgICAgIDogKHV0aWxzLmlzRWxlbWVudChjb250ZXh0KSB8fCBjb250ZXh0ID09PSBfd2luZG93LmRvY3VtZW50KSkpIHtcblxuICAgICAgICAgICAgICAgIHRoaXMuX2NvbnRleHQgPSBjb250ZXh0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgX3dpbmRvdyA9IHNjb3BlLmdldFdpbmRvdyhlbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50LCBfd2luZG93KSkge1xuXG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLlBvaW50ZXJFdmVudCkge1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIHNjb3BlLnBFdmVudFR5cGVzLmRvd24sIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyRG93biApO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIHNjb3BlLnBFdmVudFR5cGVzLm1vdmUsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAnbW91c2Vkb3duJyAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyRG93biApO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsICdtb3VzZW1vdmUnICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQodGhpcy5fZWxlbWVudCwgJ3RvdWNoc3RhcnQnLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlckRvd24gKTtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAndG91Y2htb3ZlJyAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuX2RvYyA9IF93aW5kb3cuZG9jdW1lbnQ7XG5cbiAgICAgICAgaWYgKCFzY29wZS5jb250YWlucyhzY29wZS5kb2N1bWVudHMsIHRoaXMuX2RvYykpIHtcbiAgICAgICAgICAgIGxpc3RlblRvRG9jdW1lbnQodGhpcy5fZG9jKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHNjb3BlLmludGVyYWN0YWJsZXMucHVzaCh0aGlzKTtcblxuICAgICAgICB0aGlzLnNldChvcHRpb25zKTtcbiAgICB9XG5cbiAgICBJbnRlcmFjdGFibGUucHJvdG90eXBlID0ge1xuICAgICAgICBzZXRPbkV2ZW50czogZnVuY3Rpb24gKGFjdGlvbiwgcGhhc2VzKSB7XG4gICAgICAgICAgICBpZiAoYWN0aW9uID09PSAnZHJvcCcpIHtcbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wKSAgICAgICAgICApIHsgdGhpcy5vbmRyb3AgICAgICAgICAgID0gcGhhc2VzLm9uZHJvcCAgICAgICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wYWN0aXZhdGUpICApIHsgdGhpcy5vbmRyb3BhY3RpdmF0ZSAgID0gcGhhc2VzLm9uZHJvcGFjdGl2YXRlICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wZGVhY3RpdmF0ZSkpIHsgdGhpcy5vbmRyb3BkZWFjdGl2YXRlID0gcGhhc2VzLm9uZHJvcGRlYWN0aXZhdGU7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcmFnZW50ZXIpICAgICApIHsgdGhpcy5vbmRyYWdlbnRlciAgICAgID0gcGhhc2VzLm9uZHJhZ2VudGVyICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcmFnbGVhdmUpICAgICApIHsgdGhpcy5vbmRyYWdsZWF2ZSAgICAgID0gcGhhc2VzLm9uZHJhZ2xlYXZlICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wbW92ZSkgICAgICApIHsgdGhpcy5vbmRyb3Btb3ZlICAgICAgID0gcGhhc2VzLm9uZHJvcG1vdmUgICAgICA7IH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGFjdGlvbiA9ICdvbicgKyBhY3Rpb247XG5cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25zdGFydCkgICAgICAgKSB7IHRoaXNbYWN0aW9uICsgJ3N0YXJ0JyAgICAgICAgIF0gPSBwaGFzZXMub25zdGFydCAgICAgICAgIDsgfVxuICAgICAgICAgICAgICAgIGlmIChzY29wZS5pc0Z1bmN0aW9uKHBoYXNlcy5vbm1vdmUpICAgICAgICApIHsgdGhpc1thY3Rpb24gKyAnbW92ZScgICAgICAgICAgXSA9IHBoYXNlcy5vbm1vdmUgICAgICAgICAgOyB9XG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24ocGhhc2VzLm9uZW5kKSAgICAgICAgICkgeyB0aGlzW2FjdGlvbiArICdlbmQnICAgICAgICAgICBdID0gcGhhc2VzLm9uZW5kICAgICAgICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25pbmVydGlhc3RhcnQpKSB7IHRoaXNbYWN0aW9uICsgJ2luZXJ0aWFzdGFydCcgIF0gPSBwaGFzZXMub25pbmVydGlhc3RhcnQgIDsgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5kcmFnZ2FibGVcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgZHJhZyBhY3Rpb25zIGNhbiBiZSBwZXJmb3JtZWQgb24gdGhlXG4gICAgICAgICAqIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgPSAoYm9vbGVhbikgSW5kaWNhdGVzIGlmIHRoaXMgY2FuIGJlIHRoZSB0YXJnZXQgb2YgZHJhZyBldmVudHNcbiAgICAgICAgIHwgdmFyIGlzRHJhZ2dhYmxlID0gaW50ZXJhY3QoJ3VsIGxpJykuZHJhZ2dhYmxlKCk7XG4gICAgICAgICAqIG9yXG4gICAgICAgICAtIG9wdGlvbnMgKGJvb2xlYW4gfCBvYmplY3QpICNvcHRpb25hbCB0cnVlL2ZhbHNlIG9yIEFuIG9iamVjdCB3aXRoIGV2ZW50IGxpc3RlbmVycyB0byBiZSBmaXJlZCBvbiBkcmFnIGV2ZW50cyAob2JqZWN0IG1ha2VzIHRoZSBJbnRlcmFjdGFibGUgZHJhZ2dhYmxlKVxuICAgICAgICAgPSAob2JqZWN0KSBUaGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5kcmFnZ2FibGUoe1xuICAgICAgICAgfCAgICAgb25zdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgICAgIHwgICAgIG9ubW92ZSA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgICAgICB8ICAgICBvbmVuZCAgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gdGhlIGF4aXMgaW4gd2hpY2ggdGhlIGZpcnN0IG1vdmVtZW50IG11c3QgYmVcbiAgICAgICAgIHwgICAgIC8vIGZvciB0aGUgZHJhZyBzZXF1ZW5jZSB0byBzdGFydFxuICAgICAgICAgfCAgICAgLy8gJ3h5JyBieSBkZWZhdWx0IC0gYW55IGRpcmVjdGlvblxuICAgICAgICAgfCAgICAgYXhpczogJ3gnIHx8ICd5JyB8fCAneHknLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbWF4IG51bWJlciBvZiBkcmFncyB0aGF0IGNhbiBoYXBwZW4gY29uY3VycmVudGx5XG4gICAgICAgICB8ICAgICAvLyB3aXRoIGVsZW1lbnRzIG9mIHRoaXMgSW50ZXJhY3RhYmxlLiBJbmZpbml0eSBieSBkZWZhdWx0XG4gICAgICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbWF4IG51bWJlciBvZiBkcmFncyB0aGF0IGNhbiB0YXJnZXQgdGhlIHNhbWUgZWxlbWVudCtJbnRlcmFjdGFibGVcbiAgICAgICAgIHwgICAgIC8vIDEgYnkgZGVmYXVsdFxuICAgICAgICAgfCAgICAgbWF4UGVyRWxlbWVudDogMlxuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICBkcmFnZ2FibGU6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJhZy5lbmFibGVkID0gb3B0aW9ucy5lbmFibGVkID09PSBmYWxzZT8gZmFsc2U6IHRydWU7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRQZXJBY3Rpb24oJ2RyYWcnLCBvcHRpb25zKTtcbiAgICAgICAgICAgICAgICB0aGlzLnNldE9uRXZlbnRzKCdkcmFnJywgb3B0aW9ucyk7XG5cbiAgICAgICAgICAgICAgICBpZiAoL154JHxeeSR8Xnh5JC8udGVzdChvcHRpb25zLmF4aXMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcmFnLmF4aXMgPSBvcHRpb25zLmF4aXM7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9wdGlvbnMuYXhpcyA9PT0gbnVsbCkge1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmRyYWcuYXhpcztcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcmFnLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJhZztcbiAgICAgICAgfSxcblxuICAgICAgICBzZXRQZXJBY3Rpb246IGZ1bmN0aW9uIChhY3Rpb24sIG9wdGlvbnMpIHtcbiAgICAgICAgICAgIC8vIGZvciBhbGwgdGhlIGRlZmF1bHQgcGVyLWFjdGlvbiBvcHRpb25zXG4gICAgICAgICAgICBmb3IgKHZhciBvcHRpb24gaW4gb3B0aW9ucykge1xuICAgICAgICAgICAgICAgIC8vIGlmIHRoaXMgb3B0aW9uIGV4aXN0cyBmb3IgdGhpcyBhY3Rpb25cbiAgICAgICAgICAgICAgICBpZiAob3B0aW9uIGluIHNjb3BlLmRlZmF1bHRPcHRpb25zW2FjdGlvbl0pIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gaWYgdGhlIG9wdGlvbiBpbiB0aGUgb3B0aW9ucyBhcmcgaXMgYW4gb2JqZWN0IHZhbHVlXG4gICAgICAgICAgICAgICAgICAgIGlmIChzY29wZS5pc09iamVjdChvcHRpb25zW29wdGlvbl0pKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBkdXBsaWNhdGUgdGhlIG9iamVjdFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXSA9IHV0aWxzLmV4dGVuZCh0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dIHx8IHt9LCBvcHRpb25zW29wdGlvbl0pO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3Qoc2NvcGUuZGVmYXVsdE9wdGlvbnMucGVyQWN0aW9uW29wdGlvbl0pICYmICdlbmFibGVkJyBpbiBzY29wZS5kZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb25bb3B0aW9uXSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9uc1thY3Rpb25dW29wdGlvbl0uZW5hYmxlZCA9IG9wdGlvbnNbb3B0aW9uXS5lbmFibGVkID09PSBmYWxzZT8gZmFsc2UgOiB0cnVlO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zW29wdGlvbl0pICYmIHNjb3BlLmlzT2JqZWN0KHNjb3BlLmRlZmF1bHRPcHRpb25zLnBlckFjdGlvbltvcHRpb25dKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXS5lbmFibGVkID0gb3B0aW9uc1tvcHRpb25dO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9wdGlvbnNbb3B0aW9uXSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBvciBpZiBpdCdzIG5vdCB1bmRlZmluZWQsIGRvIGEgcGxhaW4gYXNzaWdubWVudFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXSA9IG9wdGlvbnNbb3B0aW9uXTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5kcm9wem9uZVxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciBlbGVtZW50cyBjYW4gYmUgZHJvcHBlZCBvbnRvIHRoaXNcbiAgICAgICAgICogSW50ZXJhY3RhYmxlIHRvIHRyaWdnZXIgZHJvcCBldmVudHNcbiAgICAgICAgICpcbiAgICAgICAgICogRHJvcHpvbmVzIGNhbiByZWNlaXZlIHRoZSBmb2xsb3dpbmcgZXZlbnRzOlxuICAgICAgICAgKiAgLSBgZHJvcGFjdGl2YXRlYCBhbmQgYGRyb3BkZWFjdGl2YXRlYCB3aGVuIGFuIGFjY2VwdGFibGUgZHJhZyBzdGFydHMgYW5kIGVuZHNcbiAgICAgICAgICogIC0gYGRyYWdlbnRlcmAgYW5kIGBkcmFnbGVhdmVgIHdoZW4gYSBkcmFnZ2FibGUgZW50ZXJzIGFuZCBsZWF2ZXMgdGhlIGRyb3B6b25lXG4gICAgICAgICAqICAtIGBkcmFnbW92ZWAgd2hlbiBhIGRyYWdnYWJsZSB0aGF0IGhhcyBlbnRlcmVkIHRoZSBkcm9wem9uZSBpcyBtb3ZlZFxuICAgICAgICAgKiAgLSBgZHJvcGAgd2hlbiBhIGRyYWdnYWJsZSBpcyBkcm9wcGVkIGludG8gdGhpcyBkcm9wem9uZVxuICAgICAgICAgKlxuICAgICAgICAgKiAgVXNlIHRoZSBgYWNjZXB0YCBvcHRpb24gdG8gYWxsb3cgb25seSBlbGVtZW50cyB0aGF0IG1hdGNoIHRoZSBnaXZlbiBDU1Mgc2VsZWN0b3Igb3IgZWxlbWVudC5cbiAgICAgICAgICpcbiAgICAgICAgICogIFVzZSB0aGUgYG92ZXJsYXBgIG9wdGlvbiB0byBzZXQgaG93IGRyb3BzIGFyZSBjaGVja2VkIGZvci4gVGhlIGFsbG93ZWQgdmFsdWVzIGFyZTpcbiAgICAgICAgICogICAtIGAncG9pbnRlcidgLCB0aGUgcG9pbnRlciBtdXN0IGJlIG92ZXIgdGhlIGRyb3B6b25lIChkZWZhdWx0KVxuICAgICAgICAgKiAgIC0gYCdjZW50ZXInYCwgdGhlIGRyYWdnYWJsZSBlbGVtZW50J3MgY2VudGVyIG11c3QgYmUgb3ZlciB0aGUgZHJvcHpvbmVcbiAgICAgICAgICogICAtIGEgbnVtYmVyIGZyb20gMC0xIHdoaWNoIGlzIHRoZSBgKGludGVyc2VjdGlvbiBhcmVhKSAvIChkcmFnZ2FibGUgYXJlYSlgLlxuICAgICAgICAgKiAgICAgICBlLmcuIGAwLjVgIGZvciBkcm9wIHRvIGhhcHBlbiB3aGVuIGhhbGYgb2YgdGhlIGFyZWEgb2YgdGhlXG4gICAgICAgICAqICAgICAgIGRyYWdnYWJsZSBpcyBvdmVyIHRoZSBkcm9wem9uZVxuICAgICAgICAgKlxuICAgICAgICAgLSBvcHRpb25zIChib29sZWFuIHwgb2JqZWN0IHwgbnVsbCkgI29wdGlvbmFsIFRoZSBuZXcgdmFsdWUgdG8gYmUgc2V0LlxuICAgICAgICAgfCBpbnRlcmFjdCgnLmRyb3AnKS5kcm9wem9uZSh7XG4gICAgICAgICB8ICAgYWNjZXB0OiAnLmNhbi1kcm9wJyB8fCBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc2luZ2xlLWRyb3AnKSxcbiAgICAgICAgIHwgICBvdmVybGFwOiAncG9pbnRlcicgfHwgJ2NlbnRlcicgfHwgemVyb1RvT25lXG4gICAgICAgICB8IH1cbiAgICAgICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBkcm9wem9uZTogZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS5pc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcbiAgICAgICAgICAgICAgICB0aGlzLnNldE9uRXZlbnRzKCdkcm9wJywgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgdGhpcy5hY2NlcHQob3B0aW9ucy5hY2NlcHQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKC9eKHBvaW50ZXJ8Y2VudGVyKSQvLnRlc3Qob3B0aW9ucy5vdmVybGFwKSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJvcC5vdmVybGFwID0gb3B0aW9ucy5vdmVybGFwO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChzY29wZS5pc051bWJlcihvcHRpb25zLm92ZXJsYXApKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLm92ZXJsYXAgPSBNYXRoLm1heChNYXRoLm1pbigxLCBvcHRpb25zLm92ZXJsYXApLCAwKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcDtcbiAgICAgICAgfSxcblxuICAgICAgICBkcm9wQ2hlY2s6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZHJhZ2dhYmxlLCBkcmFnZ2FibGVFbGVtZW50LCBkcm9wRWxlbWVudCwgcmVjdCkge1xuICAgICAgICAgICAgdmFyIGRyb3BwZWQgPSBmYWxzZTtcblxuICAgICAgICAgICAgLy8gaWYgdGhlIGRyb3B6b25lIGhhcyBubyByZWN0IChlZy4gZGlzcGxheTogbm9uZSlcbiAgICAgICAgICAgIC8vIGNhbGwgdGhlIGN1c3RvbSBkcm9wQ2hlY2tlciBvciBqdXN0IHJldHVybiBmYWxzZVxuICAgICAgICAgICAgaWYgKCEocmVjdCA9IHJlY3QgfHwgdGhpcy5nZXRSZWN0KGRyb3BFbGVtZW50KSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gKHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlclxuICAgICAgICAgICAgICAgICAgICA/IHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlcihwb2ludGVyLCBldmVudCwgZHJvcHBlZCwgdGhpcywgZHJvcEVsZW1lbnQsIGRyYWdnYWJsZSwgZHJhZ2dhYmxlRWxlbWVudClcbiAgICAgICAgICAgICAgICAgICAgOiBmYWxzZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBkcm9wT3ZlcmxhcCA9IHRoaXMub3B0aW9ucy5kcm9wLm92ZXJsYXA7XG5cbiAgICAgICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ3BvaW50ZXInKSB7XG4gICAgICAgICAgICAgICAgdmFyIHBhZ2UgPSB1dGlscy5nZXRQYWdlWFkocG9pbnRlciksXG4gICAgICAgICAgICAgICAgICAgIG9yaWdpbiA9IHNjb3BlLmdldE9yaWdpblhZKGRyYWdnYWJsZSwgZHJhZ2dhYmxlRWxlbWVudCksXG4gICAgICAgICAgICAgICAgICAgIGhvcml6b250YWwsXG4gICAgICAgICAgICAgICAgICAgIHZlcnRpY2FsO1xuXG4gICAgICAgICAgICAgICAgcGFnZS54ICs9IG9yaWdpbi54O1xuICAgICAgICAgICAgICAgIHBhZ2UueSArPSBvcmlnaW4ueTtcblxuICAgICAgICAgICAgICAgIGhvcml6b250YWwgPSAocGFnZS54ID4gcmVjdC5sZWZ0KSAmJiAocGFnZS54IDwgcmVjdC5yaWdodCk7XG4gICAgICAgICAgICAgICAgdmVydGljYWwgICA9IChwYWdlLnkgPiByZWN0LnRvcCApICYmIChwYWdlLnkgPCByZWN0LmJvdHRvbSk7XG5cbiAgICAgICAgICAgICAgICBkcm9wcGVkID0gaG9yaXpvbnRhbCAmJiB2ZXJ0aWNhbDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIGRyYWdSZWN0ID0gZHJhZ2dhYmxlLmdldFJlY3QoZHJhZ2dhYmxlRWxlbWVudCk7XG5cbiAgICAgICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ2NlbnRlcicpIHtcbiAgICAgICAgICAgICAgICB2YXIgY3ggPSBkcmFnUmVjdC5sZWZ0ICsgZHJhZ1JlY3Qud2lkdGggIC8gMixcbiAgICAgICAgICAgICAgICAgICAgY3kgPSBkcmFnUmVjdC50b3AgICsgZHJhZ1JlY3QuaGVpZ2h0IC8gMjtcblxuICAgICAgICAgICAgICAgIGRyb3BwZWQgPSBjeCA+PSByZWN0LmxlZnQgJiYgY3ggPD0gcmVjdC5yaWdodCAmJiBjeSA+PSByZWN0LnRvcCAmJiBjeSA8PSByZWN0LmJvdHRvbTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzTnVtYmVyKGRyb3BPdmVybGFwKSkge1xuICAgICAgICAgICAgICAgIHZhciBvdmVybGFwQXJlYSAgPSAoTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5yaWdodCAsIGRyYWdSZWN0LnJpZ2h0ICkgLSBNYXRoLm1heChyZWN0LmxlZnQsIGRyYWdSZWN0LmxlZnQpKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICogTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5ib3R0b20sIGRyYWdSZWN0LmJvdHRvbSkgLSBNYXRoLm1heChyZWN0LnRvcCAsIGRyYWdSZWN0LnRvcCApKSksXG4gICAgICAgICAgICAgICAgICAgIG92ZXJsYXBSYXRpbyA9IG92ZXJsYXBBcmVhIC8gKGRyYWdSZWN0LndpZHRoICogZHJhZ1JlY3QuaGVpZ2h0KTtcblxuICAgICAgICAgICAgICAgIGRyb3BwZWQgPSBvdmVybGFwUmF0aW8gPj0gZHJvcE92ZXJsYXA7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLm9wdGlvbnMuZHJvcENoZWNrZXIpIHtcbiAgICAgICAgICAgICAgICBkcm9wcGVkID0gdGhpcy5vcHRpb25zLmRyb3BDaGVja2VyKHBvaW50ZXIsIGRyb3BwZWQsIHRoaXMsIGRyb3BFbGVtZW50LCBkcmFnZ2FibGUsIGRyYWdnYWJsZUVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gZHJvcHBlZDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5kcm9wQ2hlY2tlclxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBHZXRzIG9yIHNldHMgdGhlIGZ1bmN0aW9uIHVzZWQgdG8gY2hlY2sgaWYgYSBkcmFnZ2VkIGVsZW1lbnQgaXNcbiAgICAgICAgICogb3ZlciB0aGlzIEludGVyYWN0YWJsZS5cbiAgICAgICAgICpcbiAgICAgICAgIC0gY2hlY2tlciAoZnVuY3Rpb24pICNvcHRpb25hbCBUaGUgZnVuY3Rpb24gdGhhdCB3aWxsIGJlIGNhbGxlZCB3aGVuIGNoZWNraW5nIGZvciBhIGRyb3BcbiAgICAgICAgID0gKEZ1bmN0aW9uIHwgSW50ZXJhY3RhYmxlKSBUaGUgY2hlY2tlciBmdW5jdGlvbiBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgKiBUaGUgY2hlY2tlciBmdW5jdGlvbiB0YWtlcyB0aGUgZm9sbG93aW5nIGFyZ3VtZW50czpcbiAgICAgICAgICpcbiAgICAgICAgIC0gcG9pbnRlciAoVG91Y2ggfCBQb2ludGVyRXZlbnQgfCBNb3VzZUV2ZW50KSBUaGUgcG9pbnRlci9ldmVudCB0aGF0IGVuZHMgYSBkcmFnXG4gICAgICAgICAtIGV2ZW50IChUb3VjaEV2ZW50IHwgUG9pbnRlckV2ZW50IHwgTW91c2VFdmVudCkgVGhlIGV2ZW50IHJlbGF0ZWQgdG8gdGhlIHBvaW50ZXJcbiAgICAgICAgIC0gZHJvcHBlZCAoYm9vbGVhbikgVGhlIHZhbHVlIGZyb20gdGhlIGRlZmF1bHQgZHJvcCBjaGVja1xuICAgICAgICAgLSBkcm9wem9uZSAoSW50ZXJhY3RhYmxlKSBUaGUgZHJvcHpvbmUgaW50ZXJhY3RhYmxlXG4gICAgICAgICAtIGRyb3BFbGVtZW50IChFbGVtZW50KSBUaGUgZHJvcHpvbmUgZWxlbWVudFxuICAgICAgICAgLSBkcmFnZ2FibGUgKEludGVyYWN0YWJsZSkgVGhlIEludGVyYWN0YWJsZSBiZWluZyBkcmFnZ2VkXG4gICAgICAgICAtIGRyYWdnYWJsZUVsZW1lbnQgKEVsZW1lbnQpIFRoZSBhY3R1YWwgZWxlbWVudCB0aGF0J3MgYmVpbmcgZHJhZ2dlZFxuICAgICAgICAgKlxuICAgICAgICAgPiBVc2FnZTpcbiAgICAgICAgIHwgaW50ZXJhY3QodGFyZ2V0KVxuICAgICAgICAgfCAuZHJvcENoZWNrZXIoZnVuY3Rpb24ocG9pbnRlciwgICAgICAgICAgIC8vIFRvdWNoL1BvaW50ZXJFdmVudC9Nb3VzZUV2ZW50XG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBldmVudCwgICAgICAgICAgICAgLy8gVG91Y2hFdmVudC9Qb2ludGVyRXZlbnQvTW91c2VFdmVudFxuICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJvcHBlZCwgICAgICAgICAgIC8vIHJlc3VsdCBvZiB0aGUgZGVmYXVsdCBjaGVja2VyXG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcm9wem9uZSwgICAgICAgICAgLy8gZHJvcHpvbmUgSW50ZXJhY3RhYmxlXG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcm9wRWxlbWVudCwgICAgICAgLy8gZHJvcHpvbmUgZWxlbW50XG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUsICAgICAgICAgLy8gZHJhZ2dhYmxlIEludGVyYWN0YWJsZVxuICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJhZ2dhYmxlRWxlbWVudCkgey8vIGRyYWdnYWJsZSBlbGVtZW50XG4gICAgICAgICB8XG4gICAgICAgICB8ICAgcmV0dXJuIGRyb3BwZWQgJiYgZXZlbnQudGFyZ2V0Lmhhc0F0dHJpYnV0ZSgnYWxsb3ctZHJvcCcpO1xuICAgICAgICAgfCB9XG4gICAgICAgIFxcKi9cbiAgICAgICAgZHJvcENoZWNrZXI6IGZ1bmN0aW9uIChjaGVja2VyKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihjaGVja2VyKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlciA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChjaGVja2VyID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5nZXRSZWN0O1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcENoZWNrZXI7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYWNjZXB0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIERlcHJlY2F0ZWQuIGFkZCBhbiBgYWNjZXB0YCBwcm9wZXJ0eSB0byB0aGUgb3B0aW9ucyBvYmplY3QgcGFzc2VkIHRvXG4gICAgICAgICAqIEBJbnRlcmFjdGFibGUuZHJvcHpvbmUgaW5zdGVhZC5cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHRoZSBFbGVtZW50IG9yIENTUyBzZWxlY3RvciBtYXRjaCB0aGF0IHRoaXNcbiAgICAgICAgICogSW50ZXJhY3RhYmxlIGFjY2VwdHMgaWYgaXQgaXMgYSBkcm9wem9uZS5cbiAgICAgICAgICpcbiAgICAgICAgIC0gbmV3VmFsdWUgKEVsZW1lbnQgfCBzdHJpbmcgfCBudWxsKSAjb3B0aW9uYWxcbiAgICAgICAgICogSWYgaXQgaXMgYW4gRWxlbWVudCwgdGhlbiBvbmx5IHRoYXQgZWxlbWVudCBjYW4gYmUgZHJvcHBlZCBpbnRvIHRoaXMgZHJvcHpvbmUuXG4gICAgICAgICAqIElmIGl0IGlzIGEgc3RyaW5nLCB0aGUgZWxlbWVudCBiZWluZyBkcmFnZ2VkIG11c3QgbWF0Y2ggaXQgYXMgYSBzZWxlY3Rvci5cbiAgICAgICAgICogSWYgaXQgaXMgbnVsbCwgdGhlIGFjY2VwdCBvcHRpb25zIGlzIGNsZWFyZWQgLSBpdCBhY2NlcHRzIGFueSBlbGVtZW50LlxuICAgICAgICAgKlxuICAgICAgICAgPSAoc3RyaW5nIHwgRWxlbWVudCB8IG51bGwgfCBJbnRlcmFjdGFibGUpIFRoZSBjdXJyZW50IGFjY2VwdCBvcHRpb24gaWYgZ2l2ZW4gYHVuZGVmaW5lZGAgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBhY2NlcHQ6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJvcC5hY2NlcHQgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyB0ZXN0IGlmIGl0IGlzIGEgdmFsaWQgQ1NTIHNlbGVjdG9yXG4gICAgICAgICAgICBpZiAoc2NvcGUudHJ5U2VsZWN0b3IobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0ID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5kcm9wLmFjY2VwdDtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0O1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLnJlc2l6YWJsZVxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBHZXRzIG9yIHNldHMgd2hldGhlciByZXNpemUgYWN0aW9ucyBjYW4gYmUgcGVyZm9ybWVkIG9uIHRoZVxuICAgICAgICAgKiBJbnRlcmFjdGFibGVcbiAgICAgICAgICpcbiAgICAgICAgID0gKGJvb2xlYW4pIEluZGljYXRlcyBpZiB0aGlzIGNhbiBiZSB0aGUgdGFyZ2V0IG9mIHJlc2l6ZSBlbGVtZW50c1xuICAgICAgICAgfCB2YXIgaXNSZXNpemVhYmxlID0gaW50ZXJhY3QoJ2lucHV0W3R5cGU9dGV4dF0nKS5yZXNpemFibGUoKTtcbiAgICAgICAgICogb3JcbiAgICAgICAgIC0gb3B0aW9ucyAoYm9vbGVhbiB8IG9iamVjdCkgI29wdGlvbmFsIHRydWUvZmFsc2Ugb3IgQW4gb2JqZWN0IHdpdGggZXZlbnQgbGlzdGVuZXJzIHRvIGJlIGZpcmVkIG9uIHJlc2l6ZSBldmVudHMgKG9iamVjdCBtYWtlcyB0aGUgSW50ZXJhY3RhYmxlIHJlc2l6YWJsZSlcbiAgICAgICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCkucmVzaXphYmxlKHtcbiAgICAgICAgIHwgICAgIG9uc3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgICAgICB8ICAgICBvbm1vdmUgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICAgICAgfCAgICAgb25lbmQgIDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIGVkZ2VzOiB7XG4gICAgICAgICB8ICAgICAgIHRvcCAgIDogdHJ1ZSwgICAgICAgLy8gVXNlIHBvaW50ZXIgY29vcmRzIHRvIGNoZWNrIGZvciByZXNpemUuXG4gICAgICAgICB8ICAgICAgIGxlZnQgIDogZmFsc2UsICAgICAgLy8gRGlzYWJsZSByZXNpemluZyBmcm9tIGxlZnQgZWRnZS5cbiAgICAgICAgIHwgICAgICAgYm90dG9tOiAnLnJlc2l6ZS1zJywvLyBSZXNpemUgaWYgcG9pbnRlciB0YXJnZXQgbWF0Y2hlcyBzZWxlY3RvclxuICAgICAgICAgfCAgICAgICByaWdodCA6IGhhbmRsZUVsICAgIC8vIFJlc2l6ZSBpZiBwb2ludGVyIHRhcmdldCBpcyB0aGUgZ2l2ZW4gRWxlbWVudFxuICAgICAgICAgfCAgICAgfSxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIC8vIGEgdmFsdWUgb2YgJ25vbmUnIHdpbGwgbGltaXQgdGhlIHJlc2l6ZSByZWN0IHRvIGEgbWluaW11bSBvZiAweDBcbiAgICAgICAgIHwgICAgIC8vICduZWdhdGUnIHdpbGwgYWxsb3cgdGhlIHJlY3QgdG8gaGF2ZSBuZWdhdGl2ZSB3aWR0aC9oZWlnaHRcbiAgICAgICAgIHwgICAgIC8vICdyZXBvc2l0aW9uJyB3aWxsIGtlZXAgdGhlIHdpZHRoL2hlaWdodCBwb3NpdGl2ZSBieSBzd2FwcGluZ1xuICAgICAgICAgfCAgICAgLy8gdGhlIHRvcCBhbmQgYm90dG9tIGVkZ2VzIGFuZC9vciBzd2FwcGluZyB0aGUgbGVmdCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgICAgIHwgICAgIGludmVydDogJ25vbmUnIHx8ICduZWdhdGUnIHx8ICdyZXBvc2l0aW9uJ1xuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgcmVzaXplcy5cbiAgICAgICAgIHwgICAgIC8vIFNlZSB0aGUgZXhwbGFuYXRpb24gaW4gdGhlIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIGV4YW1wbGVcbiAgICAgICAgIHwgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICByZXNpemFibGU6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcbiAgICAgICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbigncmVzaXplJywgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygncmVzaXplJywgb3B0aW9ucyk7XG5cbiAgICAgICAgICAgICAgICBpZiAoL154JHxeeSR8Xnh5JC8udGVzdChvcHRpb25zLmF4aXMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuYXhpcyA9IG9wdGlvbnMuYXhpcztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAob3B0aW9ucy5heGlzID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuYXhpcyA9IHNjb3BlLmRlZmF1bHRPcHRpb25zLnJlc2l6ZS5heGlzO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wob3B0aW9ucy5zcXVhcmUpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuc3F1YXJlID0gb3B0aW9ucy5zcXVhcmU7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnJlc2l6ZS5lbmFibGVkID0gb3B0aW9ucztcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5yZXNpemU7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuc3F1YXJlUmVzaXplXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhIGBzcXVhcmU6IHRydWUgfHwgZmFsc2VgIHByb3BlcnR5IHRvIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWRcbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgcmVzaXppbmcgaXMgZm9yY2VkIDE6MSBhc3BlY3RcbiAgICAgICAgICpcbiAgICAgICAgID0gKGJvb2xlYW4pIEN1cnJlbnQgc2V0dGluZ1xuICAgICAgICAgKlxuICAgICAgICAgKiBvclxuICAgICAgICAgKlxuICAgICAgICAgLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsXG4gICAgICAgICA9IChvYmplY3QpIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgc3F1YXJlUmVzaXplOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnJlc2l6ZS5zcXVhcmUgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAobmV3VmFsdWUgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLnJlc2l6ZS5zcXVhcmU7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5yZXNpemUuc3F1YXJlO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmdlc3R1cmFibGVcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgbXVsdGl0b3VjaCBnZXN0dXJlcyBjYW4gYmUgcGVyZm9ybWVkIG9uIHRoZVxuICAgICAgICAgKiBJbnRlcmFjdGFibGUncyBlbGVtZW50XG4gICAgICAgICAqXG4gICAgICAgICA9IChib29sZWFuKSBJbmRpY2F0ZXMgaWYgdGhpcyBjYW4gYmUgdGhlIHRhcmdldCBvZiBnZXN0dXJlIGV2ZW50c1xuICAgICAgICAgfCB2YXIgaXNHZXN0dXJlYWJsZSA9IGludGVyYWN0KGVsZW1lbnQpLmdlc3R1cmFibGUoKTtcbiAgICAgICAgICogb3JcbiAgICAgICAgIC0gb3B0aW9ucyAoYm9vbGVhbiB8IG9iamVjdCkgI29wdGlvbmFsIHRydWUvZmFsc2Ugb3IgQW4gb2JqZWN0IHdpdGggZXZlbnQgbGlzdGVuZXJzIHRvIGJlIGZpcmVkIG9uIGdlc3R1cmUgZXZlbnRzIChtYWtlcyB0aGUgSW50ZXJhY3RhYmxlIGdlc3R1cmFibGUpXG4gICAgICAgICA9IChvYmplY3QpIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICB8IGludGVyYWN0KGVsZW1lbnQpLmdlc3R1cmFibGUoe1xuICAgICAgICAgfCAgICAgb25zdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgICAgIHwgICAgIG9ubW92ZSA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgICAgICB8ICAgICBvbmVuZCAgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgZ2VzdHVyZXMuXG4gICAgICAgICB8ICAgICAvLyBTZWUgdGhlIGV4cGxhbmF0aW9uIGluIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIGV4YW1wbGVcbiAgICAgICAgIHwgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICBnZXN0dXJhYmxlOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmdlc3R1cmUuZW5hYmxlZCA9IG9wdGlvbnMuZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlOiB0cnVlO1xuICAgICAgICAgICAgICAgIHRoaXMuc2V0UGVyQWN0aW9uKCdnZXN0dXJlJywgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygnZ2VzdHVyZScsIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wob3B0aW9ucykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZ2VzdHVyZS5lbmFibGVkID0gb3B0aW9ucztcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmdlc3R1cmU7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYXV0b1Njcm9sbFxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKipcbiAgICAgICAgICogRGVwcmVjYXRlZC4gQWRkIGFuIGBhdXRvc2Nyb2xsYCBwcm9wZXJ0eSB0byB0aGUgb3B0aW9ucyBvYmplY3RcbiAgICAgICAgICogcGFzc2VkIHRvIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIG9yIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWQuXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIGRyYWdnaW5nIGFuZCByZXNpemluZyBuZWFyIHRoZSBlZGdlcyBvZiB0aGVcbiAgICAgICAgICogd2luZG93L2NvbnRhaW5lciB0cmlnZ2VyIGF1dG9TY3JvbGwgZm9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICAqXG4gICAgICAgICA9IChvYmplY3QpIE9iamVjdCB3aXRoIGF1dG9TY3JvbGwgcHJvcGVydGllc1xuICAgICAgICAgKlxuICAgICAgICAgKiBvclxuICAgICAgICAgKlxuICAgICAgICAgLSBvcHRpb25zIChvYmplY3QgfCBib29sZWFuKSAjb3B0aW9uYWxcbiAgICAgICAgICogb3B0aW9ucyBjYW4gYmU6XG4gICAgICAgICAqIC0gYW4gb2JqZWN0IHdpdGggbWFyZ2luLCBkaXN0YW5jZSBhbmQgaW50ZXJ2YWwgcHJvcGVydGllcyxcbiAgICAgICAgICogLSB0cnVlIG9yIGZhbHNlIHRvIGVuYWJsZSBvciBkaXNhYmxlIGF1dG9TY3JvbGwgb3JcbiAgICAgICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBhdXRvU2Nyb2xsOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgb3B0aW9ucyA9IHV0aWxzLmV4dGVuZCh7IGFjdGlvbnM6IFsnZHJhZycsICdyZXNpemUnXX0sIG9wdGlvbnMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoc2NvcGUuaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgb3B0aW9ucyA9IHsgYWN0aW9uczogWydkcmFnJywgJ3Jlc2l6ZSddLCBlbmFibGVkOiBvcHRpb25zIH07XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLnNldE9wdGlvbnMoJ2F1dG9TY3JvbGwnLCBvcHRpb25zKTtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5zbmFwXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqKlxuICAgICAgICAgKiBEZXByZWNhdGVkLiBBZGQgYSBgc25hcGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZFxuICAgICAgICAgKiB0byBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSBvciBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBpbnN0ZWFkLlxuICAgICAgICAgKlxuICAgICAgICAgKiBSZXR1cm5zIG9yIHNldHMgaWYgYW5kIGhvdyBhY3Rpb24gY29vcmRpbmF0ZXMgYXJlIHNuYXBwZWQuIEJ5XG4gICAgICAgICAqIGRlZmF1bHQsIHNuYXBwaW5nIGlzIHJlbGF0aXZlIHRvIHRoZSBwb2ludGVyIGNvb3JkaW5hdGVzLiBZb3UgY2FuXG4gICAgICAgICAqIGNoYW5nZSB0aGlzIGJ5IHNldHRpbmcgdGhlXG4gICAgICAgICAqIFtgZWxlbWVudE9yaWdpbmBdKGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL3B1bGwvNzIpLlxuICAgICAgICAgKipcbiAgICAgICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIGBmYWxzZWAgaWYgc25hcCBpcyBkaXNhYmxlZDsgb2JqZWN0IHdpdGggc25hcCBwcm9wZXJ0aWVzIGlmIHNuYXAgaXMgZW5hYmxlZFxuICAgICAgICAgKipcbiAgICAgICAgICogb3JcbiAgICAgICAgICoqXG4gICAgICAgICAtIG9wdGlvbnMgKG9iamVjdCB8IGJvb2xlYW4gfCBudWxsKSAjb3B0aW9uYWxcbiAgICAgICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgID4gVXNhZ2VcbiAgICAgICAgIHwgaW50ZXJhY3QoZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI3RoaW5nJykpLnNuYXAoe1xuICAgICAgICAgfCAgICAgdGFyZ2V0czogW1xuICAgICAgICAgfCAgICAgICAgIC8vIHNuYXAgdG8gdGhpcyBzcGVjaWZpYyBwb2ludFxuICAgICAgICAgfCAgICAgICAgIHtcbiAgICAgICAgIHwgICAgICAgICAgICAgeDogMTAwLFxuICAgICAgICAgfCAgICAgICAgICAgICB5OiAxMDAsXG4gICAgICAgICB8ICAgICAgICAgICAgIHJhbmdlOiAyNVxuICAgICAgICAgfCAgICAgICAgIH0sXG4gICAgICAgICB8ICAgICAgICAgLy8gZ2l2ZSB0aGlzIGZ1bmN0aW9uIHRoZSB4IGFuZCB5IHBhZ2UgY29vcmRzIGFuZCBzbmFwIHRvIHRoZSBvYmplY3QgcmV0dXJuZWRcbiAgICAgICAgIHwgICAgICAgICBmdW5jdGlvbiAoeCwgeSkge1xuICAgICAgICAgfCAgICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgfCAgICAgICAgICAgICAgICAgeDogeCxcbiAgICAgICAgIHwgICAgICAgICAgICAgICAgIHk6ICg3NSArIDUwICogTWF0aC5zaW4oeCAqIDAuMDQpKSxcbiAgICAgICAgIHwgICAgICAgICAgICAgICAgIHJhbmdlOiA0MFxuICAgICAgICAgfCAgICAgICAgICAgICB9O1xuICAgICAgICAgfCAgICAgICAgIH0sXG4gICAgICAgICB8ICAgICAgICAgLy8gY3JlYXRlIGEgZnVuY3Rpb24gdGhhdCBzbmFwcyB0byBhIGdyaWRcbiAgICAgICAgIHwgICAgICAgICBpbnRlcmFjdC5jcmVhdGVTbmFwR3JpZCh7XG4gICAgICAgICB8ICAgICAgICAgICAgIHg6IDUwLFxuICAgICAgICAgfCAgICAgICAgICAgICB5OiA1MCxcbiAgICAgICAgIHwgICAgICAgICAgICAgcmFuZ2U6IDEwLCAgICAgICAgICAgICAgLy8gb3B0aW9uYWxcbiAgICAgICAgIHwgICAgICAgICAgICAgb2Zmc2V0OiB7IHg6IDUsIHk6IDEwIH0gLy8gb3B0aW9uYWxcbiAgICAgICAgIHwgICAgICAgICB9KVxuICAgICAgICAgfCAgICAgXSxcbiAgICAgICAgIHwgICAgIC8vIGRvIG5vdCBzbmFwIGR1cmluZyBub3JtYWwgbW92ZW1lbnQuXG4gICAgICAgICB8ICAgICAvLyBJbnN0ZWFkLCB0cmlnZ2VyIG9ubHkgb25lIHNuYXBwZWQgbW92ZSBldmVudFxuICAgICAgICAgfCAgICAgLy8gaW1tZWRpYXRlbHkgYmVmb3JlIHRoZSBlbmQgZXZlbnQuXG4gICAgICAgICB8ICAgICBlbmRPbmx5OiB0cnVlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgcmVsYXRpdmVQb2ludHM6IFtcbiAgICAgICAgIHwgICAgICAgICB7IHg6IDAsIHk6IDAgfSwgIC8vIHNuYXAgcmVsYXRpdmUgdG8gdGhlIHRvcCBsZWZ0IG9mIHRoZSBlbGVtZW50XG4gICAgICAgICB8ICAgICAgICAgeyB4OiAxLCB5OiAxIH0sICAvLyBhbmQgYWxzbyB0byB0aGUgYm90dG9tIHJpZ2h0XG4gICAgICAgICB8ICAgICBdLCAgXG4gICAgICAgICB8XG4gICAgICAgICB8ICAgICAvLyBvZmZzZXQgdGhlIHNuYXAgdGFyZ2V0IGNvb3JkaW5hdGVzXG4gICAgICAgICB8ICAgICAvLyBjYW4gYmUgYW4gb2JqZWN0IHdpdGggeC95IG9yICdzdGFydENvb3JkcydcbiAgICAgICAgIHwgICAgIG9mZnNldDogeyB4OiA1MCwgeTogNTAgfVxuICAgICAgICAgfCAgIH1cbiAgICAgICAgIHwgfSk7XG4gICAgICAgIFxcKi9cbiAgICAgICAgc25hcDogZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgICAgICAgICAgIHZhciByZXQgPSB0aGlzLnNldE9wdGlvbnMoJ3NuYXAnLCBvcHRpb25zKTtcblxuICAgICAgICAgICAgaWYgKHJldCA9PT0gdGhpcykgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgICAgICByZXR1cm4gcmV0LmRyYWc7XG4gICAgICAgIH0sXG5cbiAgICAgICAgc2V0T3B0aW9uczogZnVuY3Rpb24gKG9wdGlvbiwgb3B0aW9ucykge1xuICAgICAgICAgICAgdmFyIGFjdGlvbnMgPSBvcHRpb25zICYmIHNjb3BlLmlzQXJyYXkob3B0aW9ucy5hY3Rpb25zKVxuICAgICAgICAgICAgICAgICAgICA/IG9wdGlvbnMuYWN0aW9uc1xuICAgICAgICAgICAgICAgICAgICA6IFsnZHJhZyddO1xuXG4gICAgICAgICAgICB2YXIgaTtcblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpIHx8IHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBhY3Rpb24gPSAvcmVzaXplLy50ZXN0KGFjdGlvbnNbaV0pPyAncmVzaXplJyA6IGFjdGlvbnNbaV07XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKCFzY29wZS5pc09iamVjdCh0aGlzLm9wdGlvbnNbYWN0aW9uXSkpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgICAgICAgICB2YXIgdGhpc09wdGlvbiA9IHRoaXMub3B0aW9uc1thY3Rpb25dW29wdGlvbl07XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB1dGlscy5leHRlbmQodGhpc09wdGlvbiwgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG9wdGlvbiA9PT0gJ3NuYXAnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXNPcHRpb24ubW9kZSA9PT0gJ2dyaWQnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXNPcHRpb24udGFyZ2V0cyA9IFtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0LmNyZWF0ZVNuYXBHcmlkKHV0aWxzLmV4dGVuZCh7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2Zmc2V0OiB0aGlzT3B0aW9uLmdyaWRPZmZzZXQgfHwgeyB4OiAwLCB5OiAwIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sIHRoaXNPcHRpb24uZ3JpZCB8fCB7fSkpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKHRoaXNPcHRpb24ubW9kZSA9PT0gJ2FuY2hvcicpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi50YXJnZXRzID0gdGhpc09wdGlvbi5hbmNob3JzO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlIGlmICh0aGlzT3B0aW9uLm1vZGUgPT09ICdwYXRoJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnRhcmdldHMgPSB0aGlzT3B0aW9uLnBhdGhzO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICgnZWxlbWVudE9yaWdpbicgaW4gb3B0aW9ucykge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnJlbGF0aXZlUG9pbnRzID0gW29wdGlvbnMuZWxlbWVudE9yaWdpbl07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi5lbmFibGVkID0gb3B0aW9ucztcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgcmV0ID0ge30sXG4gICAgICAgICAgICAgICAgYWxsQWN0aW9ucyA9IFsnZHJhZycsICdyZXNpemUnLCAnZ2VzdHVyZSddO1xuXG4gICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgYWxsQWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIGlmIChvcHRpb24gaW4gc2NvcGUuZGVmYXVsdE9wdGlvbnNbYWxsQWN0aW9uc1tpXV0pIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0W2FsbEFjdGlvbnNbaV1dID0gdGhpcy5vcHRpb25zW2FsbEFjdGlvbnNbaV1dW29wdGlvbl07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gcmV0O1xuICAgICAgICB9LFxuXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuaW5lcnRpYVxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKipcbiAgICAgICAgICogRGVwcmVjYXRlZC4gQWRkIGFuIGBpbmVydGlhYCBwcm9wZXJ0eSB0byB0aGUgb3B0aW9ucyBvYmplY3QgcGFzc2VkXG4gICAgICAgICAqIHRvIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIG9yIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWQuXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyBpZiBhbmQgaG93IGV2ZW50cyBjb250aW51ZSB0byBydW4gYWZ0ZXIgdGhlIHBvaW50ZXIgaXMgcmVsZWFzZWRcbiAgICAgICAgICoqXG4gICAgICAgICA9IChib29sZWFuIHwgb2JqZWN0KSBgZmFsc2VgIGlmIGluZXJ0aWEgaXMgZGlzYWJsZWQ7IGBvYmplY3RgIHdpdGggaW5lcnRpYSBwcm9wZXJ0aWVzIGlmIGluZXJ0aWEgaXMgZW5hYmxlZFxuICAgICAgICAgKipcbiAgICAgICAgICogb3JcbiAgICAgICAgICoqXG4gICAgICAgICAtIG9wdGlvbnMgKG9iamVjdCB8IGJvb2xlYW4gfCBudWxsKSAjb3B0aW9uYWxcbiAgICAgICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgID4gVXNhZ2VcbiAgICAgICAgIHwgLy8gZW5hYmxlIGFuZCB1c2UgZGVmYXVsdCBzZXR0aW5nc1xuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pbmVydGlhKHRydWUpO1xuICAgICAgICAgfFxuICAgICAgICAgfCAvLyBlbmFibGUgYW5kIHVzZSBjdXN0b20gc2V0dGluZ3NcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYSh7XG4gICAgICAgICB8ICAgICAvLyB2YWx1ZSBncmVhdGVyIHRoYW4gMFxuICAgICAgICAgfCAgICAgLy8gaGlnaCB2YWx1ZXMgc2xvdyB0aGUgb2JqZWN0IGRvd24gbW9yZSBxdWlja2x5XG4gICAgICAgICB8ICAgICByZXNpc3RhbmNlICAgICA6IDE2LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gdGhlIG1pbmltdW0gbGF1bmNoIHNwZWVkIChwaXhlbHMgcGVyIHNlY29uZCkgdGhhdCByZXN1bHRzIGluIGluZXJ0aWEgc3RhcnRcbiAgICAgICAgIHwgICAgIG1pblNwZWVkICAgICAgIDogMjAwLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gaW5lcnRpYSB3aWxsIHN0b3Agd2hlbiB0aGUgb2JqZWN0IHNsb3dzIGRvd24gdG8gdGhpcyBzcGVlZFxuICAgICAgICAgfCAgICAgZW5kU3BlZWQgICAgICAgOiAyMCxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIC8vIGJvb2xlYW47IHNob3VsZCBhY3Rpb25zIGJlIHJlc3VtZWQgd2hlbiB0aGUgcG9pbnRlciBnb2VzIGRvd24gZHVyaW5nIGluZXJ0aWFcbiAgICAgICAgIHwgICAgIGFsbG93UmVzdW1lICAgIDogdHJ1ZSxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIC8vIGJvb2xlYW47IHNob3VsZCB0aGUganVtcCB3aGVuIHJlc3VtaW5nIGZyb20gaW5lcnRpYSBiZSBpZ25vcmVkIGluIGV2ZW50LmR4L2R5XG4gICAgICAgICB8ICAgICB6ZXJvUmVzdW1lRGVsdGE6IGZhbHNlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gaWYgc25hcC9yZXN0cmljdCBhcmUgc2V0IHRvIGJlIGVuZE9ubHkgYW5kIGluZXJ0aWEgaXMgZW5hYmxlZCwgcmVsZWFzaW5nXG4gICAgICAgICB8ICAgICAvLyB0aGUgcG9pbnRlciB3aXRob3V0IHRyaWdnZXJpbmcgaW5lcnRpYSB3aWxsIGFuaW1hdGUgZnJvbSB0aGUgcmVsZWFzZVxuICAgICAgICAgfCAgICAgLy8gcG9pbnQgdG8gdGhlIHNuYXBlZC9yZXN0cmljdGVkIHBvaW50IGluIHRoZSBnaXZlbiBhbW91bnQgb2YgdGltZSAobXMpXG4gICAgICAgICB8ICAgICBzbW9vdGhFbmREdXJhdGlvbjogMzAwLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gYW4gYXJyYXkgb2YgYWN0aW9uIHR5cGVzIHRoYXQgY2FuIGhhdmUgaW5lcnRpYSAobm8gZ2VzdHVyZSlcbiAgICAgICAgIHwgICAgIGFjdGlvbnMgICAgICAgIDogWydkcmFnJywgJ3Jlc2l6ZSddXG4gICAgICAgICB8IH0pO1xuICAgICAgICAgfFxuICAgICAgICAgfCAvLyByZXNldCBjdXN0b20gc2V0dGluZ3MgYW5kIHVzZSBhbGwgZGVmYXVsdHNcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYShudWxsKTtcbiAgICAgICAgXFwqL1xuICAgICAgICBpbmVydGlhOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgdmFyIHJldCA9IHRoaXMuc2V0T3B0aW9ucygnaW5lcnRpYScsIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICBpZiAocmV0ID09PSB0aGlzKSB7IHJldHVybiB0aGlzOyB9XG5cbiAgICAgICAgICAgIHJldHVybiByZXQuZHJhZztcbiAgICAgICAgfSxcblxuICAgICAgICBnZXRBY3Rpb246IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpIHtcbiAgICAgICAgICAgIHZhciBhY3Rpb24gPSB0aGlzLmRlZmF1bHRBY3Rpb25DaGVja2VyKHBvaW50ZXIsIGludGVyYWN0aW9uLCBlbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyKHBvaW50ZXIsIGV2ZW50LCBhY3Rpb24sIHRoaXMsIGVsZW1lbnQsIGludGVyYWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIGFjdGlvbjtcbiAgICAgICAgfSxcblxuICAgICAgICBkZWZhdWx0QWN0aW9uQ2hlY2tlcjogZGVmYXVsdEFjdGlvbkNoZWNrZXIsXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYWN0aW9uQ2hlY2tlclxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBHZXRzIG9yIHNldHMgdGhlIGZ1bmN0aW9uIHVzZWQgdG8gY2hlY2sgYWN0aW9uIHRvIGJlIHBlcmZvcm1lZCBvblxuICAgICAgICAgKiBwb2ludGVyRG93blxuICAgICAgICAgKlxuICAgICAgICAgLSBjaGVja2VyIChmdW5jdGlvbiB8IG51bGwpICNvcHRpb25hbCBBIGZ1bmN0aW9uIHdoaWNoIHRha2VzIGEgcG9pbnRlciBldmVudCwgZGVmYXVsdEFjdGlvbiBzdHJpbmcsIGludGVyYWN0YWJsZSwgZWxlbWVudCBhbmQgaW50ZXJhY3Rpb24gYXMgcGFyYW1ldGVycyBhbmQgcmV0dXJucyBhbiBvYmplY3Qgd2l0aCBuYW1lIHByb3BlcnR5ICdkcmFnJyAncmVzaXplJyBvciAnZ2VzdHVyZScgYW5kIG9wdGlvbmFsbHkgYW4gYGVkZ2VzYCBvYmplY3Qgd2l0aCBib29sZWFuICd0b3AnLCAnbGVmdCcsICdib3R0b20nIGFuZCByaWdodCBwcm9wcy5cbiAgICAgICAgID0gKEZ1bmN0aW9uIHwgSW50ZXJhY3RhYmxlKSBUaGUgY2hlY2tlciBmdW5jdGlvbiBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgfCBpbnRlcmFjdCgnLnJlc2l6ZS1kcmFnJylcbiAgICAgICAgIHwgICAucmVzaXphYmxlKHRydWUpXG4gICAgICAgICB8ICAgLmRyYWdnYWJsZSh0cnVlKVxuICAgICAgICAgfCAgIC5hY3Rpb25DaGVja2VyKGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGludGVyYWN0aW9uKSB7XG4gICAgICAgICB8XG4gICAgICAgICB8ICAgaWYgKGludGVyYWN0Lm1hdGNoZXNTZWxlY3RvcihldmVudC50YXJnZXQsICcuZHJhZy1oYW5kbGUnKSB7XG4gICAgICAgICB8ICAgICAvLyBmb3JjZSBkcmFnIHdpdGggaGFuZGxlIHRhcmdldFxuICAgICAgICAgfCAgICAgYWN0aW9uLm5hbWUgPSBkcmFnO1xuICAgICAgICAgfCAgIH1cbiAgICAgICAgIHwgICBlbHNlIHtcbiAgICAgICAgIHwgICAgIC8vIHJlc2l6ZSBmcm9tIHRoZSB0b3AgYW5kIHJpZ2h0IGVkZ2VzXG4gICAgICAgICB8ICAgICBhY3Rpb24ubmFtZSAgPSAncmVzaXplJztcbiAgICAgICAgIHwgICAgIGFjdGlvbi5lZGdlcyA9IHsgdG9wOiB0cnVlLCByaWdodDogdHJ1ZSB9O1xuICAgICAgICAgfCAgIH1cbiAgICAgICAgIHxcbiAgICAgICAgIHwgICByZXR1cm4gYWN0aW9uO1xuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICBhY3Rpb25DaGVja2VyOiBmdW5jdGlvbiAoY2hlY2tlcikge1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24oY2hlY2tlcikpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuYWN0aW9uQ2hlY2tlciA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmFjdGlvbkNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmdldFJlY3RcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogVGhlIGRlZmF1bHQgZnVuY3Rpb24gdG8gZ2V0IGFuIEludGVyYWN0YWJsZXMgYm91bmRpbmcgcmVjdC4gQ2FuIGJlXG4gICAgICAgICAqIG92ZXJyaWRkZW4gdXNpbmcgQEludGVyYWN0YWJsZS5yZWN0Q2hlY2tlci5cbiAgICAgICAgICpcbiAgICAgICAgIC0gZWxlbWVudCAoRWxlbWVudCkgI29wdGlvbmFsIFRoZSBlbGVtZW50IHRvIG1lYXN1cmUuXG4gICAgICAgICA9IChvYmplY3QpIFRoZSBvYmplY3QncyBib3VuZGluZyByZWN0YW5nbGUuXG4gICAgICAgICBvIHtcbiAgICAgICAgIG8gICAgIHRvcCAgIDogMCxcbiAgICAgICAgIG8gICAgIGxlZnQgIDogMCxcbiAgICAgICAgIG8gICAgIGJvdHRvbTogMCxcbiAgICAgICAgIG8gICAgIHJpZ2h0IDogMCxcbiAgICAgICAgIG8gICAgIHdpZHRoIDogMCxcbiAgICAgICAgIG8gICAgIGhlaWdodDogMFxuICAgICAgICAgbyB9XG4gICAgICAgIFxcKi9cbiAgICAgICAgZ2V0UmVjdDogZnVuY3Rpb24gcmVjdENoZWNrIChlbGVtZW50KSB7XG4gICAgICAgICAgICBlbGVtZW50ID0gZWxlbWVudCB8fCB0aGlzLl9lbGVtZW50O1xuXG4gICAgICAgICAgICBpZiAodGhpcy5zZWxlY3RvciAmJiAhKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkpIHtcbiAgICAgICAgICAgICAgICBlbGVtZW50ID0gdGhpcy5fY29udGV4dC5xdWVyeVNlbGVjdG9yKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gc2NvcGUuZ2V0RWxlbWVudFJlY3QoZWxlbWVudCk7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUucmVjdENoZWNrZXJcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogUmV0dXJucyBvciBzZXRzIHRoZSBmdW5jdGlvbiB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgaW50ZXJhY3RhYmxlJ3NcbiAgICAgICAgICogZWxlbWVudCdzIHJlY3RhbmdsZVxuICAgICAgICAgKlxuICAgICAgICAgLSBjaGVja2VyIChmdW5jdGlvbikgI29wdGlvbmFsIEEgZnVuY3Rpb24gd2hpY2ggcmV0dXJucyB0aGlzIEludGVyYWN0YWJsZSdzIGJvdW5kaW5nIHJlY3RhbmdsZS4gU2VlIEBJbnRlcmFjdGFibGUuZ2V0UmVjdFxuICAgICAgICAgPSAoZnVuY3Rpb24gfCBvYmplY3QpIFRoZSBjaGVja2VyIGZ1bmN0aW9uIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgcmVjdENoZWNrZXI6IGZ1bmN0aW9uIChjaGVja2VyKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihjaGVja2VyKSkge1xuICAgICAgICAgICAgICAgIHRoaXMuZ2V0UmVjdCA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmdldFJlY3Q7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMuZ2V0UmVjdDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5zdHlsZUN1cnNvclxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciB0aGUgYWN0aW9uIHRoYXQgd291bGQgYmUgcGVyZm9ybWVkIHdoZW4gdGhlXG4gICAgICAgICAqIG1vdXNlIG9uIHRoZSBlbGVtZW50IGFyZSBjaGVja2VkIG9uIGBtb3VzZW1vdmVgIHNvIHRoYXQgdGhlIGN1cnNvclxuICAgICAgICAgKiBtYXkgYmUgc3R5bGVkIGFwcHJvcHJpYXRlbHlcbiAgICAgICAgICpcbiAgICAgICAgIC0gbmV3VmFsdWUgKGJvb2xlYW4pICNvcHRpb25hbFxuICAgICAgICAgPSAoYm9vbGVhbiB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIHN0eWxlQ3Vyc29yOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5zdHlsZUN1cnNvcjtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLnByZXZlbnREZWZhdWx0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIHRvIHByZXZlbnQgdGhlIGJyb3dzZXIncyBkZWZhdWx0IGJlaGF2aW91clxuICAgICAgICAgKiBpbiByZXNwb25zZSB0byBwb2ludGVyIGV2ZW50cy4gQ2FuIGJlIHNldCB0bzpcbiAgICAgICAgICogIC0gYCdhbHdheXMnYCB0byBhbHdheXMgcHJldmVudFxuICAgICAgICAgKiAgLSBgJ25ldmVyJ2AgdG8gbmV2ZXIgcHJldmVudFxuICAgICAgICAgKiAgLSBgJ2F1dG8nYCB0byBsZXQgaW50ZXJhY3QuanMgdHJ5IHRvIGRldGVybWluZSB3aGF0IHdvdWxkIGJlIGJlc3RcbiAgICAgICAgICpcbiAgICAgICAgIC0gbmV3VmFsdWUgKHN0cmluZykgI29wdGlvbmFsIGB0cnVlYCwgYGZhbHNlYCBvciBgJ2F1dG8nYFxuICAgICAgICAgPSAoc3RyaW5nIHwgSW50ZXJhY3RhYmxlKSBUaGUgY3VycmVudCBzZXR0aW5nIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgcHJldmVudERlZmF1bHQ6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICAgICAgaWYgKC9eKGFsd2F5c3xuZXZlcnxhdXRvKSQvLnRlc3QobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnByZXZlbnREZWZhdWx0ID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnByZXZlbnREZWZhdWx0ID0gbmV3VmFsdWU/ICdhbHdheXMnIDogJ25ldmVyJztcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5wcmV2ZW50RGVmYXVsdDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5vcmlnaW5cbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHRoZSBvcmlnaW4gb2YgdGhlIEludGVyYWN0YWJsZSdzIGVsZW1lbnQuICBUaGUgeCBhbmQgeVxuICAgICAgICAgKiBvZiB0aGUgb3JpZ2luIHdpbGwgYmUgc3VidHJhY3RlZCBmcm9tIGFjdGlvbiBldmVudCBjb29yZGluYXRlcy5cbiAgICAgICAgICpcbiAgICAgICAgIC0gb3JpZ2luIChvYmplY3QgfCBzdHJpbmcpICNvcHRpb25hbCBBbiBvYmplY3QgZWcuIHsgeDogMCwgeTogMCB9IG9yIHN0cmluZyAncGFyZW50JywgJ3NlbGYnIG9yIGFueSBDU1Mgc2VsZWN0b3JcbiAgICAgICAgICogT1JcbiAgICAgICAgIC0gb3JpZ2luIChFbGVtZW50KSAjb3B0aW9uYWwgQW4gSFRNTCBvciBTVkcgRWxlbWVudCB3aG9zZSByZWN0IHdpbGwgYmUgdXNlZFxuICAgICAgICAgKipcbiAgICAgICAgID0gKG9iamVjdCkgVGhlIGN1cnJlbnQgb3JpZ2luIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgb3JpZ2luOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS50cnlTZWxlY3RvcihuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMub3JpZ2luID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChzY29wZS5pc09iamVjdChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMub3JpZ2luID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMub3JpZ2luO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmRlbHRhU291cmNlXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgbW91c2UgY29vcmRpbmF0ZSB0eXBlcyB1c2VkIHRvIGNhbGN1bGF0ZSB0aGVcbiAgICAgICAgICogbW92ZW1lbnQgb2YgdGhlIHBvaW50ZXIuXG4gICAgICAgICAqXG4gICAgICAgICAtIG5ld1ZhbHVlIChzdHJpbmcpICNvcHRpb25hbCBVc2UgJ2NsaWVudCcgaWYgeW91IHdpbGwgYmUgc2Nyb2xsaW5nIHdoaWxlIGludGVyYWN0aW5nOyBVc2UgJ3BhZ2UnIGlmIHlvdSB3YW50IGF1dG9TY3JvbGwgdG8gd29ya1xuICAgICAgICAgPSAoc3RyaW5nIHwgb2JqZWN0KSBUaGUgY3VycmVudCBkZWx0YVNvdXJjZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIGRlbHRhU291cmNlOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChuZXdWYWx1ZSA9PT0gJ3BhZ2UnIHx8IG5ld1ZhbHVlID09PSAnY2xpZW50Jykge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kZWx0YVNvdXJjZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZGVsdGFTb3VyY2U7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUucmVzdHJpY3RcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICoqXG4gICAgICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhIGByZXN0cmljdGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZCB0b1xuICAgICAgICAgKiBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSwgQEludGVyYWN0YWJsZS5yZXNpemFibGUgb3IgQEludGVyYWN0YWJsZS5nZXN0dXJhYmxlIGluc3RlYWQuXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgcmVjdGFuZ2xlcyB3aXRoaW4gd2hpY2ggYWN0aW9ucyBvbiB0aGlzXG4gICAgICAgICAqIGludGVyYWN0YWJsZSAoYWZ0ZXIgc25hcCBjYWxjdWxhdGlvbnMpIGFyZSByZXN0cmljdGVkLiBCeSBkZWZhdWx0LFxuICAgICAgICAgKiByZXN0cmljdGluZyBpcyByZWxhdGl2ZSB0byB0aGUgcG9pbnRlciBjb29yZGluYXRlcy4gWW91IGNhbiBjaGFuZ2VcbiAgICAgICAgICogdGhpcyBieSBzZXR0aW5nIHRoZVxuICAgICAgICAgKiBbYGVsZW1lbnRSZWN0YF0oaHR0cHM6Ly9naXRodWIuY29tL3RheWUvaW50ZXJhY3QuanMvcHVsbC83MikuXG4gICAgICAgICAqKlxuICAgICAgICAgLSBvcHRpb25zIChvYmplY3QpICNvcHRpb25hbCBhbiBvYmplY3Qgd2l0aCBrZXlzIGRyYWcsIHJlc2l6ZSwgYW5kL29yIGdlc3R1cmUgd2hvc2UgdmFsdWVzIGFyZSByZWN0cywgRWxlbWVudHMsIENTUyBzZWxlY3RvcnMsIG9yICdwYXJlbnQnIG9yICdzZWxmJ1xuICAgICAgICAgPSAob2JqZWN0KSBUaGUgY3VycmVudCByZXN0cmljdGlvbnMgb2JqZWN0IG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICAqKlxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5yZXN0cmljdCh7XG4gICAgICAgICB8ICAgICAvLyB0aGUgcmVjdCB3aWxsIGJlIGBpbnRlcmFjdC5nZXRFbGVtZW50UmVjdChlbGVtZW50LnBhcmVudE5vZGUpYFxuICAgICAgICAgfCAgICAgZHJhZzogZWxlbWVudC5wYXJlbnROb2RlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8geCBhbmQgeSBhcmUgcmVsYXRpdmUgdG8gdGhlIHRoZSBpbnRlcmFjdGFibGUncyBvcmlnaW5cbiAgICAgICAgIHwgICAgIHJlc2l6ZTogeyB4OiAxMDAsIHk6IDEwMCwgd2lkdGg6IDIwMCwgaGVpZ2h0OiAyMDAgfVxuICAgICAgICAgfCB9KVxuICAgICAgICAgfFxuICAgICAgICAgfCBpbnRlcmFjdCgnLmRyYWdnYWJsZScpLnJlc3RyaWN0KHtcbiAgICAgICAgIHwgICAgIC8vIHRoZSByZWN0IHdpbGwgYmUgdGhlIHNlbGVjdGVkIGVsZW1lbnQncyBwYXJlbnRcbiAgICAgICAgIHwgICAgIGRyYWc6ICdwYXJlbnQnLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gZG8gbm90IHJlc3RyaWN0IGR1cmluZyBub3JtYWwgbW92ZW1lbnQuXG4gICAgICAgICB8ICAgICAvLyBJbnN0ZWFkLCB0cmlnZ2VyIG9ubHkgb25lIHJlc3RyaWN0ZWQgbW92ZSBldmVudFxuICAgICAgICAgfCAgICAgLy8gaW1tZWRpYXRlbHkgYmVmb3JlIHRoZSBlbmQgZXZlbnQuXG4gICAgICAgICB8ICAgICBlbmRPbmx5OiB0cnVlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3RheWUvaW50ZXJhY3QuanMvcHVsbC83MiNpc3N1ZS00MTgxMzQ5M1xuICAgICAgICAgfCAgICAgZWxlbWVudFJlY3Q6IHsgdG9wOiAwLCBsZWZ0OiAwLCBib3R0b206IDEsIHJpZ2h0OiAxIH1cbiAgICAgICAgIHwgfSk7XG4gICAgICAgIFxcKi9cbiAgICAgICAgcmVzdHJpY3Q6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgICAgICBpZiAoIXNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMuc2V0T3B0aW9ucygncmVzdHJpY3QnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIGFjdGlvbnMgPSBbJ2RyYWcnLCAncmVzaXplJywgJ2dlc3R1cmUnXSxcbiAgICAgICAgICAgICAgICByZXQ7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHZhciBhY3Rpb24gPSBhY3Rpb25zW2ldO1xuXG4gICAgICAgICAgICAgICAgaWYgKGFjdGlvbiBpbiBvcHRpb25zKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBwZXJBY3Rpb24gPSB1dGlscy5leHRlbmQoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGlvbnM6IFthY3Rpb25dLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uOiBvcHRpb25zW2FjdGlvbl1cbiAgICAgICAgICAgICAgICAgICAgICAgIH0sIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICAgICAgICAgIHJldCA9IHRoaXMuc2V0T3B0aW9ucygncmVzdHJpY3QnLCBwZXJBY3Rpb24pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHJldDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5jb250ZXh0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIEdldHMgdGhlIHNlbGVjdG9yIGNvbnRleHQgTm9kZSBvZiB0aGUgSW50ZXJhY3RhYmxlLiBUaGUgZGVmYXVsdCBpcyBgd2luZG93LmRvY3VtZW50YC5cbiAgICAgICAgICpcbiAgICAgICAgID0gKE5vZGUpIFRoZSBjb250ZXh0IE5vZGUgb2YgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgICoqXG4gICAgICAgIFxcKi9cbiAgICAgICAgY29udGV4dDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuX2NvbnRleHQ7XG4gICAgICAgIH0sXG5cbiAgICAgICAgX2NvbnRleHQ6IHNjb3BlLmRvY3VtZW50LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmlnbm9yZUZyb21cbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogSWYgdGhlIHRhcmdldCBvZiB0aGUgYG1vdXNlZG93bmAsIGBwb2ludGVyZG93bmAgb3IgYHRvdWNoc3RhcnRgXG4gICAgICAgICAqIGV2ZW50IG9yIGFueSBvZiBpdCdzIHBhcmVudHMgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvclxuICAgICAgICAgKiBFbGVtZW50LCBubyBkcmFnL3Jlc2l6ZS9nZXN0dXJlIGlzIHN0YXJ0ZWQuXG4gICAgICAgICAqXG4gICAgICAgICAtIG5ld1ZhbHVlIChzdHJpbmcgfCBFbGVtZW50IHwgbnVsbCkgI29wdGlvbmFsIGEgQ1NTIHNlbGVjdG9yIHN0cmluZywgYW4gRWxlbWVudCBvciBgbnVsbGAgdG8gbm90IGlnbm9yZSBhbnkgZWxlbWVudHNcbiAgICAgICAgID0gKHN0cmluZyB8IEVsZW1lbnQgfCBvYmplY3QpIFRoZSBjdXJyZW50IGlnbm9yZUZyb20gdmFsdWUgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgICoqXG4gICAgICAgICB8IGludGVyYWN0KGVsZW1lbnQsIHsgaWdub3JlRnJvbTogZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ25vLWFjdGlvbicpIH0pO1xuICAgICAgICAgfCAvLyBvclxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pZ25vcmVGcm9tKCdpbnB1dCwgdGV4dGFyZWEsIGEnKTtcbiAgICAgICAgXFwqL1xuICAgICAgICBpZ25vcmVGcm9tOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS50cnlTZWxlY3RvcihuZXdWYWx1ZSkpIHsgICAgICAgICAgICAvLyBDU1Mgc2VsZWN0b3IgdG8gbWF0Y2ggZXZlbnQudGFyZ2V0XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmlnbm9yZUZyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChuZXdWYWx1ZSkpIHsgICAgICAgICAgICAgIC8vIHNwZWNpZmljIGVsZW1lbnRcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuaWdub3JlRnJvbSA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmlnbm9yZUZyb207XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYWxsb3dGcm9tXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIEEgZHJhZy9yZXNpemUvZ2VzdHVyZSBpcyBzdGFydGVkIG9ubHkgSWYgdGhlIHRhcmdldCBvZiB0aGVcbiAgICAgICAgICogYG1vdXNlZG93bmAsIGBwb2ludGVyZG93bmAgb3IgYHRvdWNoc3RhcnRgIGV2ZW50IG9yIGFueSBvZiBpdCdzXG4gICAgICAgICAqIHBhcmVudHMgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvciBFbGVtZW50LlxuICAgICAgICAgKlxuICAgICAgICAgLSBuZXdWYWx1ZSAoc3RyaW5nIHwgRWxlbWVudCB8IG51bGwpICNvcHRpb25hbCBhIENTUyBzZWxlY3RvciBzdHJpbmcsIGFuIEVsZW1lbnQgb3IgYG51bGxgIHRvIGFsbG93IGZyb20gYW55IGVsZW1lbnRcbiAgICAgICAgID0gKHN0cmluZyB8IEVsZW1lbnQgfCBvYmplY3QpIFRoZSBjdXJyZW50IGFsbG93RnJvbSB2YWx1ZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKipcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCwgeyBhbGxvd0Zyb206IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkcmFnLWhhbmRsZScpIH0pO1xuICAgICAgICAgfCAvLyBvclxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5hbGxvd0Zyb20oJy5oYW5kbGUnKTtcbiAgICAgICAgXFwqL1xuICAgICAgICBhbGxvd0Zyb206IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLnRyeVNlbGVjdG9yKG5ld1ZhbHVlKSkgeyAgICAgICAgICAgIC8vIENTUyBzZWxlY3RvciB0byBtYXRjaCBldmVudC50YXJnZXRcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuYWxsb3dGcm9tID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh1dGlscy5pc0VsZW1lbnQobmV3VmFsdWUpKSB7ICAgICAgICAgICAgICAvLyBzcGVjaWZpYyBlbGVtZW50XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmFsbG93RnJvbSA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmFsbG93RnJvbTtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5lbGVtZW50XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIElmIHRoaXMgaXMgbm90IGEgc2VsZWN0b3IgSW50ZXJhY3RhYmxlLCBpdCByZXR1cm5zIHRoZSBlbGVtZW50IHRoaXNcbiAgICAgICAgICogaW50ZXJhY3RhYmxlIHJlcHJlc2VudHNcbiAgICAgICAgICpcbiAgICAgICAgID0gKEVsZW1lbnQpIEhUTUwgLyBTVkcgRWxlbWVudFxuICAgICAgICBcXCovXG4gICAgICAgIGVsZW1lbnQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLl9lbGVtZW50O1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmZpcmVcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogQ2FsbHMgbGlzdGVuZXJzIGZvciB0aGUgZ2l2ZW4gSW50ZXJhY3RFdmVudCB0eXBlIGJvdW5kIGdsb2JhbGx5XG4gICAgICAgICAqIGFuZCBkaXJlY3RseSB0byB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgLSBpRXZlbnQgKEludGVyYWN0RXZlbnQpIFRoZSBJbnRlcmFjdEV2ZW50IG9iamVjdCB0byBiZSBmaXJlZCBvbiB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgPSAoSW50ZXJhY3RhYmxlKSB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIGZpcmU6IGZ1bmN0aW9uIChpRXZlbnQpIHtcbiAgICAgICAgICAgIGlmICghKGlFdmVudCAmJiBpRXZlbnQudHlwZSkgfHwgIXNjb3BlLmNvbnRhaW5zKHNjb3BlLmV2ZW50VHlwZXMsIGlFdmVudC50eXBlKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgbGlzdGVuZXJzLFxuICAgICAgICAgICAgICAgIGksXG4gICAgICAgICAgICAgICAgbGVuLFxuICAgICAgICAgICAgICAgIG9uRXZlbnQgPSAnb24nICsgaUV2ZW50LnR5cGUsXG4gICAgICAgICAgICAgICAgZnVuY05hbWUgPSAnJztcblxuICAgICAgICAgICAgLy8gSW50ZXJhY3RhYmxlI29uKCkgbGlzdGVuZXJzXG4gICAgICAgICAgICBpZiAoaUV2ZW50LnR5cGUgaW4gdGhpcy5faUV2ZW50cykge1xuICAgICAgICAgICAgICAgIGxpc3RlbmVycyA9IHRoaXMuX2lFdmVudHNbaUV2ZW50LnR5cGVdO1xuXG4gICAgICAgICAgICAgICAgZm9yIChpID0gMCwgbGVuID0gbGlzdGVuZXJzLmxlbmd0aDsgaSA8IGxlbiAmJiAhaUV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIGZ1bmNOYW1lID0gbGlzdGVuZXJzW2ldLm5hbWU7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyc1tpXShpRXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gaW50ZXJhY3RhYmxlLm9uZXZlbnQgbGlzdGVuZXJcbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Z1bmN0aW9uKHRoaXNbb25FdmVudF0pKSB7XG4gICAgICAgICAgICAgICAgZnVuY05hbWUgPSB0aGlzW29uRXZlbnRdLm5hbWU7XG4gICAgICAgICAgICAgICAgdGhpc1tvbkV2ZW50XShpRXZlbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBpbnRlcmFjdC5vbigpIGxpc3RlbmVyc1xuICAgICAgICAgICAgaWYgKGlFdmVudC50eXBlIGluIHNjb3BlLmdsb2JhbEV2ZW50cyAmJiAobGlzdGVuZXJzID0gc2NvcGUuZ2xvYmFsRXZlbnRzW2lFdmVudC50eXBlXSkpICB7XG5cbiAgICAgICAgICAgICAgICBmb3IgKGkgPSAwLCBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoOyBpIDwgbGVuICYmICFpRXZlbnQuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgZnVuY05hbWUgPSBsaXN0ZW5lcnNbaV0ubmFtZTtcbiAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzW2ldKGlFdmVudCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5vblxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBCaW5kcyBhIGxpc3RlbmVyIGZvciBhbiBJbnRlcmFjdEV2ZW50IG9yIERPTSBldmVudC5cbiAgICAgICAgICpcbiAgICAgICAgIC0gZXZlbnRUeXBlICAoc3RyaW5nIHwgYXJyYXkgfCBvYmplY3QpIFRoZSB0eXBlcyBvZiBldmVudHMgdG8gbGlzdGVuIGZvclxuICAgICAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCBvbiB0aGUgZ2l2ZW4gZXZlbnQocylcbiAgICAgICAgIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgYWRkRXZlbnRMaXN0ZW5lclxuICAgICAgICAgPSAob2JqZWN0KSBUaGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIG9uOiBmdW5jdGlvbiAoZXZlbnRUeXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgICAgICAgICAgdmFyIGk7XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc1N0cmluZyhldmVudFR5cGUpICYmIGV2ZW50VHlwZS5zZWFyY2goJyAnKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICBldmVudFR5cGUgPSBldmVudFR5cGUudHJpbSgpLnNwbGl0KC8gKy8pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNBcnJheShldmVudFR5cGUpKSB7XG4gICAgICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGV2ZW50VHlwZS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm9uKGV2ZW50VHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QoZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gZXZlbnRUeXBlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub24ocHJvcCwgZXZlbnRUeXBlW3Byb3BdLCBsaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChldmVudFR5cGUgPT09ICd3aGVlbCcpIHtcbiAgICAgICAgICAgICAgICBldmVudFR5cGUgPSBzY29wZS53aGVlbEV2ZW50O1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBjb252ZXJ0IHRvIGJvb2xlYW5cbiAgICAgICAgICAgIHVzZUNhcHR1cmUgPSB1c2VDYXB0dXJlPyB0cnVlOiBmYWxzZTtcblxuICAgICAgICAgICAgaWYgKHNjb3BlLmNvbnRhaW5zKHNjb3BlLmV2ZW50VHlwZXMsIGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgICAgICAvLyBpZiB0aGlzIHR5cGUgb2YgZXZlbnQgd2FzIG5ldmVyIGJvdW5kIHRvIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgaWYgKCEoZXZlbnRUeXBlIGluIHRoaXMuX2lFdmVudHMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX2lFdmVudHNbZXZlbnRUeXBlXSA9IFtsaXN0ZW5lcl07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLl9pRXZlbnRzW2V2ZW50VHlwZV0ucHVzaChsaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gZGVsZWdhdGVkIGV2ZW50IGZvciBzZWxlY3RvclxuICAgICAgICAgICAgZWxzZSBpZiAodGhpcy5zZWxlY3Rvcikge1xuICAgICAgICAgICAgICAgIGlmICghc2NvcGUuZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0pIHtcbiAgICAgICAgICAgICAgICAgICAgc2NvcGUuZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0gPSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RvcnM6IFtdLFxuICAgICAgICAgICAgICAgICAgICAgICAgY29udGV4dHMgOiBbXSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyczogW11cbiAgICAgICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgICAgICAvLyBhZGQgZGVsZWdhdGUgbGlzdGVuZXIgZnVuY3Rpb25zXG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBzY29wZS5kb2N1bWVudHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQoc2NvcGUuZG9jdW1lbnRzW2ldLCBldmVudFR5cGUsIGRlbGVnYXRlTGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZChzY29wZS5kb2N1bWVudHNbaV0sIGV2ZW50VHlwZSwgZGVsZWdhdGVVc2VDYXB0dXJlLCB0cnVlKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHZhciBkZWxlZ2F0ZWQgPSBzY29wZS5kZWxlZ2F0ZWRFdmVudHNbZXZlbnRUeXBlXSxcbiAgICAgICAgICAgICAgICAgICAgaW5kZXg7XG5cbiAgICAgICAgICAgICAgICBmb3IgKGluZGV4ID0gZGVsZWdhdGVkLnNlbGVjdG9ycy5sZW5ndGggLSAxOyBpbmRleCA+PSAwOyBpbmRleC0tKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2luZGV4XSA9PT0gdGhpcy5zZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2luZGV4XSA9PT0gdGhpcy5fY29udGV4dCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgICAgIGluZGV4ID0gZGVsZWdhdGVkLnNlbGVjdG9ycy5sZW5ndGg7XG5cbiAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5wdXNoKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnB1c2godGhpcy5fY29udGV4dCk7XG4gICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnMucHVzaChbXSk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgLy8ga2VlcCBsaXN0ZW5lciBhbmQgdXNlQ2FwdHVyZSBmbGFnXG4gICAgICAgICAgICAgICAgZGVsZWdhdGVkLmxpc3RlbmVyc1tpbmRleF0ucHVzaChbbGlzdGVuZXIsIHVzZUNhcHR1cmVdKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQodGhpcy5fZWxlbWVudCwgZXZlbnRUeXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLm9mZlxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBSZW1vdmVzIGFuIEludGVyYWN0RXZlbnQgb3IgRE9NIGV2ZW50IGxpc3RlbmVyXG4gICAgICAgICAqXG4gICAgICAgICAtIGV2ZW50VHlwZSAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAgICAgICAgIC0gbGlzdGVuZXIgICAoZnVuY3Rpb24pIFRoZSBsaXN0ZW5lciBmdW5jdGlvbiB0byBiZSByZW1vdmVkXG4gICAgICAgICAtIHVzZUNhcHR1cmUgKGJvb2xlYW4pICNvcHRpb25hbCB1c2VDYXB0dXJlIGZsYWcgZm9yIHJlbW92ZUV2ZW50TGlzdGVuZXJcbiAgICAgICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBvZmY6IGZ1bmN0aW9uIChldmVudFR5cGUsIGxpc3RlbmVyLCB1c2VDYXB0dXJlKSB7XG4gICAgICAgICAgICB2YXIgaTtcblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKGV2ZW50VHlwZSkgJiYgZXZlbnRUeXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgIGV2ZW50VHlwZSA9IGV2ZW50VHlwZS50cmltKCkuc3BsaXQoLyArLyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc0FycmF5KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgZXZlbnRUeXBlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub2ZmKGV2ZW50VHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QoZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gZXZlbnRUeXBlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub2ZmKHByb3AsIGV2ZW50VHlwZVtwcm9wXSwgbGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgZXZlbnRMaXN0LFxuICAgICAgICAgICAgICAgIGluZGV4ID0gLTE7XG5cbiAgICAgICAgICAgIC8vIGNvbnZlcnQgdG8gYm9vbGVhblxuICAgICAgICAgICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgICAgICAgICBpZiAoZXZlbnRUeXBlID09PSAnd2hlZWwnKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRUeXBlID0gc2NvcGUud2hlZWxFdmVudDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gaWYgaXQgaXMgYW4gYWN0aW9uIGV2ZW50IHR5cGVcbiAgICAgICAgICAgIGlmIChzY29wZS5jb250YWlucyhzY29wZS5ldmVudFR5cGVzLCBldmVudFR5cGUpKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRMaXN0ID0gdGhpcy5faUV2ZW50c1tldmVudFR5cGVdO1xuXG4gICAgICAgICAgICAgICAgaWYgKGV2ZW50TGlzdCAmJiAoaW5kZXggPSBzY29wZS5pbmRleE9mKGV2ZW50TGlzdCwgbGlzdGVuZXIpKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5faUV2ZW50c1tldmVudFR5cGVdLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gZGVsZWdhdGVkIGV2ZW50XG4gICAgICAgICAgICBlbHNlIGlmICh0aGlzLnNlbGVjdG9yKSB7XG4gICAgICAgICAgICAgICAgdmFyIGRlbGVnYXRlZCA9IHNjb3BlLmRlbGVnYXRlZEV2ZW50c1tldmVudFR5cGVdLFxuICAgICAgICAgICAgICAgICAgICBtYXRjaEZvdW5kID0gZmFsc2U7XG5cbiAgICAgICAgICAgICAgICBpZiAoIWRlbGVnYXRlZCkgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgICAgICAgICAgLy8gY291bnQgZnJvbSBsYXN0IGluZGV4IG9mIGRlbGVnYXRlZCB0byAwXG4gICAgICAgICAgICAgICAgZm9yIChpbmRleCA9IGRlbGVnYXRlZC5zZWxlY3RvcnMubGVuZ3RoIC0gMTsgaW5kZXggPj0gMDsgaW5kZXgtLSkge1xuICAgICAgICAgICAgICAgICAgICAvLyBsb29rIGZvciBtYXRjaGluZyBzZWxlY3RvciBhbmQgY29udGV4dCBOb2RlXG4gICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2luZGV4XSA9PT0gdGhpcy5zZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2luZGV4XSA9PT0gdGhpcy5fY29udGV4dCkge1xuXG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gZGVsZWdhdGVkLmxpc3RlbmVyc1tpbmRleF07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGVhY2ggaXRlbSBvZiB0aGUgbGlzdGVuZXJzIGFycmF5IGlzIGFuIGFycmF5OiBbZnVuY3Rpb24sIHVzZUNhcHR1cmVGbGFnXVxuICAgICAgICAgICAgICAgICAgICAgICAgZm9yIChpID0gbGlzdGVuZXJzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGZuID0gbGlzdGVuZXJzW2ldWzBdLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2VDYXAgPSBsaXN0ZW5lcnNbaV1bMV07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBjaGVjayBpZiB0aGUgbGlzdGVuZXIgZnVuY3Rpb25zIGFuZCB1c2VDYXB0dXJlIGZsYWdzIG1hdGNoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGZuID09PSBsaXN0ZW5lciAmJiB1c2VDYXAgPT09IHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gcmVtb3ZlIHRoZSBsaXN0ZW5lciBmcm9tIHRoZSBhcnJheSBvZiBsaXN0ZW5lcnNcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBpZiBhbGwgbGlzdGVuZXJzIGZvciB0aGlzIGludGVyYWN0YWJsZSBoYXZlIGJlZW4gcmVtb3ZlZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgdGhlIGludGVyYWN0YWJsZSBmcm9tIHRoZSBkZWxlZ2F0ZWQgYXJyYXlzXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghbGlzdGVuZXJzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLmNvbnRleHRzIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLmxpc3RlbmVycy5zcGxpY2UoaW5kZXgsIDEpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgZGVsZWdhdGUgZnVuY3Rpb24gZnJvbSBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIGV2ZW50VHlwZSwgZGVsZWdhdGVMaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIGV2ZW50VHlwZSwgZGVsZWdhdGVVc2VDYXB0dXJlLCB0cnVlKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gcmVtb3ZlIHRoZSBhcnJheXMgaWYgdGhleSBhcmUgZW1wdHlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghZGVsZWdhdGVkLnNlbGVjdG9ycy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY29wZS5kZWxlZ2F0ZWRFdmVudHNbZXZlbnRUeXBlXSA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBvbmx5IHJlbW92ZSBvbmUgbGlzdGVuZXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0Y2hGb3VuZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1hdGNoRm91bmQpIHsgYnJlYWs7IH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIHJlbW92ZSBsaXN0ZW5lciBmcm9tIHRoaXMgSW50ZXJhdGFibGUncyBlbGVtZW50XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2VsZW1lbnQsIGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5zZXRcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogUmVzZXQgdGhlIG9wdGlvbnMgb2YgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgIC0gb3B0aW9ucyAob2JqZWN0KSBUaGUgbmV3IHNldHRpbmdzIHRvIGFwcGx5XG4gICAgICAgICA9IChvYmplY3QpIFRoaXMgSW50ZXJhY3RhYmx3XG4gICAgICAgIFxcKi9cbiAgICAgICAgc2V0OiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgaWYgKCFzY29wZS5pc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIG9wdGlvbnMgPSB7fTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5vcHRpb25zID0gdXRpbHMuZXh0ZW5kKHt9LCBzY29wZS5kZWZhdWx0T3B0aW9ucy5iYXNlKTtcblxuICAgICAgICAgICAgdmFyIGksXG4gICAgICAgICAgICAgICAgYWN0aW9ucyA9IFsnZHJhZycsICdkcm9wJywgJ3Jlc2l6ZScsICdnZXN0dXJlJ10sXG4gICAgICAgICAgICAgICAgbWV0aG9kcyA9IFsnZHJhZ2dhYmxlJywgJ2Ryb3B6b25lJywgJ3Jlc2l6YWJsZScsICdnZXN0dXJhYmxlJ10sXG4gICAgICAgICAgICAgICAgcGVyQWN0aW9ucyA9IHV0aWxzLmV4dGVuZCh1dGlscy5leHRlbmQoe30sIHNjb3BlLmRlZmF1bHRPcHRpb25zLnBlckFjdGlvbiksIG9wdGlvbnNbYWN0aW9uXSB8fCB7fSk7XG5cbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgdmFyIGFjdGlvbiA9IGFjdGlvbnNbaV07XG5cbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnNbYWN0aW9uXSA9IHV0aWxzLmV4dGVuZCh7fSwgc2NvcGUuZGVmYXVsdE9wdGlvbnNbYWN0aW9uXSk7XG5cbiAgICAgICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbihhY3Rpb24sIHBlckFjdGlvbnMpO1xuXG4gICAgICAgICAgICAgICAgdGhpc1ttZXRob2RzW2ldXShvcHRpb25zW2FjdGlvbl0pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc2V0dGluZ3MgPSBbXG4gICAgICAgICAgICAgICAgICAgICdhY2NlcHQnLCAnYWN0aW9uQ2hlY2tlcicsICdhbGxvd0Zyb20nLCAnZGVsdGFTb3VyY2UnLFxuICAgICAgICAgICAgICAgICAgICAnZHJvcENoZWNrZXInLCAnaWdub3JlRnJvbScsICdvcmlnaW4nLCAncHJldmVudERlZmF1bHQnLFxuICAgICAgICAgICAgICAgICAgICAncmVjdENoZWNrZXInXG4gICAgICAgICAgICAgICAgXTtcblxuICAgICAgICAgICAgZm9yIChpID0gMCwgbGVuID0gc2V0dGluZ3MubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgICAgICB2YXIgc2V0dGluZyA9IHNldHRpbmdzW2ldO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW3NldHRpbmddID0gc2NvcGUuZGVmYXVsdE9wdGlvbnMuYmFzZVtzZXR0aW5nXTtcblxuICAgICAgICAgICAgICAgIGlmIChzZXR0aW5nIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpc1tzZXR0aW5nXShvcHRpb25zW3NldHRpbmddKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLnVuc2V0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIFJlbW92ZSB0aGlzIGludGVyYWN0YWJsZSBmcm9tIHRoZSBsaXN0IG9mIGludGVyYWN0YWJsZXMgYW5kIHJlbW92ZVxuICAgICAgICAgKiBpdCdzIGRyYWcsIGRyb3AsIHJlc2l6ZSBhbmQgZ2VzdHVyZSBjYXBhYmlsaXRpZXNcbiAgICAgICAgICpcbiAgICAgICAgID0gKG9iamVjdCkgQGludGVyYWN0XG4gICAgICAgIFxcKi9cbiAgICAgICAgdW5zZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fZWxlbWVudCwgJ2FsbCcpO1xuXG4gICAgICAgICAgICBpZiAoIXNjb3BlLmlzU3RyaW5nKHRoaXMuc2VsZWN0b3IpKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzLnJlbW92ZSh0aGlzLCAnYWxsJyk7XG4gICAgICAgICAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5zdHlsZUN1cnNvcikge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLl9lbGVtZW50LnN0eWxlLmN1cnNvciA9ICcnO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIHJlbW92ZSBkZWxlZ2F0ZWQgZXZlbnRzXG4gICAgICAgICAgICAgICAgZm9yICh2YXIgdHlwZSBpbiBzY29wZS5kZWxlZ2F0ZWRFdmVudHMpIHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIGRlbGVnYXRlZCA9IHNjb3BlLmRlbGVnYXRlZEV2ZW50c1t0eXBlXTtcblxuICAgICAgICAgICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGRlbGVnYXRlZC5zZWxlY3RvcnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2ldID09PSB0aGlzLnNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2ldID09PSB0aGlzLl9jb250ZXh0KSB7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuc2VsZWN0b3JzLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQubGlzdGVuZXJzLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHJlbW92ZSB0aGUgYXJyYXlzIGlmIHRoZXkgYXJlIGVtcHR5XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY29wZS5kZWxlZ2F0ZWRFdmVudHNbdHlwZV0gPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRzLnJlbW92ZSh0aGlzLl9jb250ZXh0LCB0eXBlLCBkZWxlZ2F0ZUxpc3RlbmVyKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fY29udGV4dCwgdHlwZSwgZGVsZWdhdGVVc2VDYXB0dXJlLCB0cnVlKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMuZHJvcHpvbmUoZmFsc2UpO1xuXG4gICAgICAgICAgICBzY29wZS5pbnRlcmFjdGFibGVzLnNwbGljZShzY29wZS5pbmRleE9mKHNjb3BlLmludGVyYWN0YWJsZXMsIHRoaXMpLCAxKTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIEludGVyYWN0YWJsZS5wcm90b3R5cGUuc25hcCA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuc25hcCxcbiAgICAgICAgICdJbnRlcmFjdGFibGUjc25hcCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBzbmFwcGluZyBhdCBodHRwOi8vaW50ZXJhY3Rqcy5pby9kb2NzL3NuYXBwaW5nJyk7XG4gICAgSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5yZXN0cmljdCA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUucmVzdHJpY3QsXG4gICAgICAgICAnSW50ZXJhY3RhYmxlI3Jlc3RyaWN0IGlzIGRlcHJlY2F0ZWQuIFNlZSB0aGUgbmV3IGRvY3VtZW50YXRpb24gZm9yIHJlc3RpY3RpbmcgYXQgaHR0cDovL2ludGVyYWN0anMuaW8vZG9jcy9yZXN0cmljdGlvbicpO1xuICAgIEludGVyYWN0YWJsZS5wcm90b3R5cGUuaW5lcnRpYSA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuaW5lcnRpYSxcbiAgICAgICAgICdJbnRlcmFjdGFibGUjaW5lcnRpYSBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBpbmVydGlhIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvaW5lcnRpYScpO1xuICAgIEludGVyYWN0YWJsZS5wcm90b3R5cGUuYXV0b1Njcm9sbCA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuYXV0b1Njcm9sbCxcbiAgICAgICAgICdJbnRlcmFjdGFibGUjYXV0b1Njcm9sbCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBhdXRvU2Nyb2xsIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI2F1dG9zY3JvbGwnKTtcbiAgICBJbnRlcmFjdGFibGUucHJvdG90eXBlLnNxdWFyZVJlc2l6ZSA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuc3F1YXJlUmVzaXplLFxuICAgICAgICAgJ0ludGVyYWN0YWJsZSNzcXVhcmVSZXNpemUgaXMgZGVwcmVjYXRlZC4gU2VlIGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI3Jlc2l6ZS1zcXVhcmUnKTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5pc1NldFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBDaGVjayBpZiBhbiBlbGVtZW50IGhhcyBiZWVuIHNldFxuICAgICAtIGVsZW1lbnQgKEVsZW1lbnQpIFRoZSBFbGVtZW50IGJlaW5nIHNlYXJjaGVkIGZvclxuICAgICA9IChib29sZWFuKSBJbmRpY2F0ZXMgaWYgdGhlIGVsZW1lbnQgb3IgQ1NTIHNlbGVjdG9yIHdhcyBwcmV2aW91c2x5IHBhc3NlZCB0byBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5pc1NldCA9IGZ1bmN0aW9uKGVsZW1lbnQsIG9wdGlvbnMpIHtcbiAgICAgICAgcmV0dXJuIHNjb3BlLmludGVyYWN0YWJsZXMuaW5kZXhPZkVsZW1lbnQoZWxlbWVudCwgb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQpICE9PSAtMTtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0Lm9uXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEFkZHMgYSBnbG9iYWwgbGlzdGVuZXIgZm9yIGFuIEludGVyYWN0RXZlbnQgb3IgYWRkcyBhIERPTSBldmVudCB0b1xuICAgICAqIGBkb2N1bWVudGBcbiAgICAgKlxuICAgICAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRvIGxpc3RlbiBmb3JcbiAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCBvbiB0aGUgZ2l2ZW4gZXZlbnQocylcbiAgICAgLSB1c2VDYXB0dXJlIChib29sZWFuKSAjb3B0aW9uYWwgdXNlQ2FwdHVyZSBmbGFnIGZvciBhZGRFdmVudExpc3RlbmVyXG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICBcXCovXG4gICAgaW50ZXJhY3Qub24gPSBmdW5jdGlvbiAodHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKHR5cGUpICYmIHR5cGUuc2VhcmNoKCcgJykgIT09IC0xKSB7XG4gICAgICAgICAgICB0eXBlID0gdHlwZS50cmltKCkuc3BsaXQoLyArLyk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNBcnJheSh0eXBlKSkge1xuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0eXBlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaW50ZXJhY3Qub24odHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QodHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gdHlwZSkge1xuICAgICAgICAgICAgICAgIGludGVyYWN0Lm9uKHByb3AsIHR5cGVbcHJvcF0sIGxpc3RlbmVyKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gaWYgaXQgaXMgYW4gSW50ZXJhY3RFdmVudCB0eXBlLCBhZGQgbGlzdGVuZXIgdG8gZ2xvYmFsRXZlbnRzXG4gICAgICAgIGlmIChzY29wZS5jb250YWlucyhzY29wZS5ldmVudFR5cGVzLCB0eXBlKSkge1xuICAgICAgICAgICAgLy8gaWYgdGhpcyB0eXBlIG9mIGV2ZW50IHdhcyBuZXZlciBib3VuZFxuICAgICAgICAgICAgaWYgKCFzY29wZS5nbG9iYWxFdmVudHNbdHlwZV0pIHtcbiAgICAgICAgICAgICAgICBzY29wZS5nbG9iYWxFdmVudHNbdHlwZV0gPSBbbGlzdGVuZXJdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgc2NvcGUuZ2xvYmFsRXZlbnRzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIElmIG5vbiBJbnRlcmFjdEV2ZW50IHR5cGUsIGFkZEV2ZW50TGlzdGVuZXIgdG8gZG9jdW1lbnRcbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBldmVudHMuYWRkKHNjb3BlLmRvY3VtZW50LCB0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5vZmZcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmVtb3ZlcyBhIGdsb2JhbCBJbnRlcmFjdEV2ZW50IGxpc3RlbmVyIG9yIERPTSBldmVudCBmcm9tIGBkb2N1bWVudGBcbiAgICAgKlxuICAgICAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGxpc3RlbmVyIGZ1bmN0aW9uIHRvIGJlIHJlbW92ZWRcbiAgICAgLSB1c2VDYXB0dXJlIChib29sZWFuKSAjb3B0aW9uYWwgdXNlQ2FwdHVyZSBmbGFnIGZvciByZW1vdmVFdmVudExpc3RlbmVyXG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICAgXFwqL1xuICAgIGludGVyYWN0Lm9mZiA9IGZ1bmN0aW9uICh0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNTdHJpbmcodHlwZSkgJiYgdHlwZS5zZWFyY2goJyAnKSAhPT0gLTEpIHtcbiAgICAgICAgICAgIHR5cGUgPSB0eXBlLnRyaW0oKS5zcGxpdCgvICsvKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChzY29wZS5pc0FycmF5KHR5cGUpKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHR5cGUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICBpbnRlcmFjdC5vZmYodHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QodHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gdHlwZSkge1xuICAgICAgICAgICAgICAgIGludGVyYWN0Lm9mZihwcm9wLCB0eXBlW3Byb3BdLCBsaXN0ZW5lcik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghc2NvcGUuY29udGFpbnMoc2NvcGUuZXZlbnRUeXBlcywgdHlwZSkpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUoc2NvcGUuZG9jdW1lbnQsIHR5cGUsIGxpc3RlbmVyLCB1c2VDYXB0dXJlKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZhciBpbmRleDtcblxuICAgICAgICAgICAgaWYgKHR5cGUgaW4gc2NvcGUuZ2xvYmFsRXZlbnRzXG4gICAgICAgICAgICAgICAgJiYgKGluZGV4ID0gc2NvcGUuaW5kZXhPZihzY29wZS5nbG9iYWxFdmVudHNbdHlwZV0sIGxpc3RlbmVyKSkgIT09IC0xKSB7XG4gICAgICAgICAgICAgICAgc2NvcGUuZ2xvYmFsRXZlbnRzW3R5cGVdLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5lbmFibGVEcmFnZ2luZ1xuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBEZXByZWNhdGVkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZHJhZ2dpbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChib29sZWFuKSAjb3B0aW9uYWwgYHRydWVgIHRvIGFsbG93IHRoZSBhY3Rpb247IGBmYWxzZWAgdG8gZGlzYWJsZSBhY3Rpb24gZm9yIGFsbCBJbnRlcmFjdGFibGVzXG4gICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiAgICBcXCovXG4gICAgaW50ZXJhY3QuZW5hYmxlRHJhZ2dpbmcgPSB1dGlscy53YXJuT25jZShmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgaWYgKG5ld1ZhbHVlICE9PSBudWxsICYmIG5ld1ZhbHVlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHNjb3BlLmFjdGlvbklzRW5hYmxlZC5kcmFnID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc2NvcGUuYWN0aW9uSXNFbmFibGVkLmRyYWc7XG4gICAgfSwgJ2ludGVyYWN0LmVuYWJsZURyYWdnaW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LmVuYWJsZVJlc2l6aW5nXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIERlcHJlY2F0ZWQuXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciByZXNpemluZyBpcyBlbmFibGVkIGZvciBhbnkgSW50ZXJhY3RhYmxlc1xuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKGJvb2xlYW4pICNvcHRpb25hbCBgdHJ1ZWAgdG8gYWxsb3cgdGhlIGFjdGlvbjsgYGZhbHNlYCB0byBkaXNhYmxlIGFjdGlvbiBmb3IgYWxsIEludGVyYWN0YWJsZXNcbiAgICAgPSAoYm9vbGVhbiB8IG9iamVjdCkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5lbmFibGVSZXNpemluZyA9IHV0aWxzLndhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAobmV3VmFsdWUgIT09IG51bGwgJiYgbmV3VmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgc2NvcGUuYWN0aW9uSXNFbmFibGVkLnJlc2l6ZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHNjb3BlLmFjdGlvbklzRW5hYmxlZC5yZXNpemU7XG4gICAgfSwgJ2ludGVyYWN0LmVuYWJsZVJlc2l6aW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LmVuYWJsZUdlc3R1cmluZ1xuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBEZXByZWNhdGVkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZ2VzdHVyaW5nIGlzIGVuYWJsZWQgZm9yIGFueSBJbnRlcmFjdGFibGVzXG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuICAgICA9IChib29sZWFuIHwgb2JqZWN0KSBUaGUgY3VycmVudCBzZXR0aW5nIG9yIGludGVyYWN0XG4gICAgXFwqL1xuICAgIGludGVyYWN0LmVuYWJsZUdlc3R1cmluZyA9IHV0aWxzLndhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAobmV3VmFsdWUgIT09IG51bGwgJiYgbmV3VmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgc2NvcGUuYWN0aW9uSXNFbmFibGVkLmdlc3R1cmUgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzY29wZS5hY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZTtcbiAgICB9LCAnaW50ZXJhY3QuZW5hYmxlR2VzdHVyaW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4gICAgaW50ZXJhY3QuZXZlbnRUeXBlcyA9IHNjb3BlLmV2ZW50VHlwZXM7XG5cbiAgICAvKlxcXG4gICAgICogaW50ZXJhY3QuZGVidWdcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmV0dXJucyBkZWJ1Z2dpbmcgZGF0YVxuICAgICA9IChvYmplY3QpIEFuIG9iamVjdCB3aXRoIHByb3BlcnRpZXMgdGhhdCBvdXRsaW5lIHRoZSBjdXJyZW50IHN0YXRlIGFuZCBleHBvc2UgaW50ZXJuYWwgZnVuY3Rpb25zIGFuZCB2YXJpYWJsZXNcbiAgICBcXCovXG4gICAgaW50ZXJhY3QuZGVidWcgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBpbnRlcmFjdGlvbiA9IHNjb3BlLmludGVyYWN0aW9uc1swXSB8fCBuZXcgSW50ZXJhY3Rpb24oKTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb25zICAgICAgICAgIDogc2NvcGUuaW50ZXJhY3Rpb25zLFxuICAgICAgICAgICAgdGFyZ2V0ICAgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24udGFyZ2V0LFxuICAgICAgICAgICAgZHJhZ2dpbmcgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uZHJhZ2dpbmcsXG4gICAgICAgICAgICByZXNpemluZyAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5yZXNpemluZyxcbiAgICAgICAgICAgIGdlc3R1cmluZyAgICAgICAgICAgICA6IGludGVyYWN0aW9uLmdlc3R1cmluZyxcbiAgICAgICAgICAgIHByZXBhcmVkICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXBhcmVkLFxuICAgICAgICAgICAgbWF0Y2hlcyAgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ubWF0Y2hlcyxcbiAgICAgICAgICAgIG1hdGNoRWxlbWVudHMgICAgICAgICA6IGludGVyYWN0aW9uLm1hdGNoRWxlbWVudHMsXG5cbiAgICAgICAgICAgIHByZXZDb29yZHMgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXZDb29yZHMsXG4gICAgICAgICAgICBzdGFydENvb3JkcyAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5zdGFydENvb3JkcyxcblxuICAgICAgICAgICAgcG9pbnRlcklkcyAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ucG9pbnRlcklkcyxcbiAgICAgICAgICAgIHBvaW50ZXJzICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICAgICAgYWRkUG9pbnRlciAgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLmFkZFBvaW50ZXIsXG4gICAgICAgICAgICByZW1vdmVQb2ludGVyICAgICAgICAgOiBzY29wZS5saXN0ZW5lcnMucmVtb3ZlUG9pbnRlcixcbiAgICAgICAgICAgIHJlY29yZFBvaW50ZXIgICAgICAgICA6IHNjb3BlLmxpc3RlbmVycy5yZWNvcmRQb2ludGVyLFxuXG4gICAgICAgICAgICBzbmFwICAgICAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5zbmFwU3RhdHVzLFxuICAgICAgICAgICAgcmVzdHJpY3QgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ucmVzdHJpY3RTdGF0dXMsXG4gICAgICAgICAgICBpbmVydGlhICAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLFxuXG4gICAgICAgICAgICBkb3duVGltZSAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF0sXG4gICAgICAgICAgICBkb3duRXZlbnQgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duRXZlbnQsXG4gICAgICAgICAgICBkb3duUG9pbnRlciAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duUG9pbnRlcixcbiAgICAgICAgICAgIHByZXZFdmVudCAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXZFdmVudCxcblxuICAgICAgICAgICAgSW50ZXJhY3RhYmxlICAgICAgICAgIDogSW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgaW50ZXJhY3RhYmxlcyAgICAgICAgIDogc2NvcGUuaW50ZXJhY3RhYmxlcyxcbiAgICAgICAgICAgIHBvaW50ZXJJc0Rvd24gICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJJc0Rvd24sXG4gICAgICAgICAgICBkZWZhdWx0T3B0aW9ucyAgICAgICAgOiBzY29wZS5kZWZhdWx0T3B0aW9ucyxcbiAgICAgICAgICAgIGRlZmF1bHRBY3Rpb25DaGVja2VyICA6IGRlZmF1bHRBY3Rpb25DaGVja2VyLFxuXG4gICAgICAgICAgICBhY3Rpb25DdXJzb3JzICAgICAgICAgOiBzY29wZS5hY3Rpb25DdXJzb3JzLFxuICAgICAgICAgICAgZHJhZ01vdmUgICAgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLmRyYWdNb3ZlLFxuICAgICAgICAgICAgcmVzaXplTW92ZSAgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnJlc2l6ZU1vdmUsXG4gICAgICAgICAgICBnZXN0dXJlTW92ZSAgICAgICAgICAgOiBzY29wZS5saXN0ZW5lcnMuZ2VzdHVyZU1vdmUsXG4gICAgICAgICAgICBwb2ludGVyVXAgICAgICAgICAgICAgOiBzY29wZS5saXN0ZW5lcnMucG9pbnRlclVwLFxuICAgICAgICAgICAgcG9pbnRlckRvd24gICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJEb3duLFxuICAgICAgICAgICAgcG9pbnRlck1vdmUgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJNb3ZlLFxuICAgICAgICAgICAgcG9pbnRlckhvdmVyICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJIb3ZlcixcblxuICAgICAgICAgICAgZXZlbnRUeXBlcyAgICAgICAgICAgIDogc2NvcGUuZXZlbnRUeXBlcyxcblxuICAgICAgICAgICAgZXZlbnRzICAgICAgICAgICAgICAgIDogZXZlbnRzLFxuICAgICAgICAgICAgZ2xvYmFsRXZlbnRzICAgICAgICAgIDogc2NvcGUuZ2xvYmFsRXZlbnRzLFxuICAgICAgICAgICAgZGVsZWdhdGVkRXZlbnRzICAgICAgIDogc2NvcGUuZGVsZWdhdGVkRXZlbnRzXG4gICAgICAgIH07XG4gICAgfTtcblxuICAgIC8vIGV4cG9zZSB0aGUgZnVuY3Rpb25zIHVzZWQgdG8gY2FsY3VsYXRlIG11bHRpLXRvdWNoIHByb3BlcnRpZXNcbiAgICBpbnRlcmFjdC5nZXRUb3VjaEF2ZXJhZ2UgID0gdXRpbHMudG91Y2hBdmVyYWdlO1xuICAgIGludGVyYWN0LmdldFRvdWNoQkJveCAgICAgPSB1dGlscy50b3VjaEJCb3g7XG4gICAgaW50ZXJhY3QuZ2V0VG91Y2hEaXN0YW5jZSA9IHV0aWxzLnRvdWNoRGlzdGFuY2U7XG4gICAgaW50ZXJhY3QuZ2V0VG91Y2hBbmdsZSAgICA9IHV0aWxzLnRvdWNoQW5nbGU7XG5cbiAgICBpbnRlcmFjdC5nZXRFbGVtZW50UmVjdCAgID0gc2NvcGUuZ2V0RWxlbWVudFJlY3Q7XG4gICAgaW50ZXJhY3QubWF0Y2hlc1NlbGVjdG9yICA9IHNjb3BlLm1hdGNoZXNTZWxlY3RvcjtcbiAgICBpbnRlcmFjdC5jbG9zZXN0ICAgICAgICAgID0gc2NvcGUuY2xvc2VzdDtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5tYXJnaW5cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHRoZSBtYXJnaW4gZm9yIGF1dG9jaGVjayByZXNpemluZyB1c2VkIGluXG4gICAgICogQEludGVyYWN0YWJsZS5nZXRBY3Rpb24uIFRoYXQgaXMgdGhlIGRpc3RhbmNlIGZyb20gdGhlIGJvdHRvbSBhbmQgcmlnaHRcbiAgICAgKiBlZGdlcyBvZiBhbiBlbGVtZW50IGNsaWNraW5nIGluIHdoaWNoIHdpbGwgc3RhcnQgcmVzaXppbmdcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChudW1iZXIpICNvcHRpb25hbFxuICAgICA9IChudW1iZXIgfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgbWFyZ2luIHZhbHVlIG9yIGludGVyYWN0XG4gICAgXFwqL1xuICAgIGludGVyYWN0Lm1hcmdpbiA9IGZ1bmN0aW9uIChuZXd2YWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNOdW1iZXIobmV3dmFsdWUpKSB7XG4gICAgICAgICAgICBzY29wZS5tYXJnaW4gPSBuZXd2YWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzY29wZS5tYXJnaW47XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5zdXBwb3J0c1RvdWNoXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICA9IChib29sZWFuKSBXaGV0aGVyIG9yIG5vdCB0aGUgYnJvd3NlciBzdXBwb3J0cyB0b3VjaCBpbnB1dFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5zdXBwb3J0c1RvdWNoID0gZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gYnJvd3Nlci5zdXBwb3J0c1RvdWNoO1xuICAgIH07XG5cbiAgICAvKlxcXG4gICAgICogaW50ZXJhY3Quc3VwcG9ydHNQb2ludGVyRXZlbnRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgID0gKGJvb2xlYW4pIFdoZXRoZXIgb3Igbm90IHRoZSBicm93c2VyIHN1cHBvcnRzIFBvaW50ZXJFdmVudHNcbiAgICBcXCovXG4gICAgaW50ZXJhY3Quc3VwcG9ydHNQb2ludGVyRXZlbnQgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBicm93c2VyLnN1cHBvcnRzUG9pbnRlckV2ZW50O1xuICAgIH07XG5cbiAgICAvKlxcXG4gICAgICogaW50ZXJhY3Quc3RvcFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBDYW5jZWxzIGFsbCBpbnRlcmFjdGlvbnMgKGVuZCBldmVudHMgYXJlIG5vdCBmaXJlZClcbiAgICAgKlxuICAgICAtIGV2ZW50IChFdmVudCkgQW4gZXZlbnQgb24gd2hpY2ggdG8gY2FsbCBwcmV2ZW50RGVmYXVsdCgpXG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICBcXCovXG4gICAgaW50ZXJhY3Quc3RvcCA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBmb3IgKHZhciBpID0gc2NvcGUuaW50ZXJhY3Rpb25zLmxlbmd0aCAtIDE7IGkgPiAwOyBpLS0pIHtcbiAgICAgICAgICAgIHNjb3BlLmludGVyYWN0aW9uc1tpXS5zdG9wKGV2ZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LmR5bmFtaWNEcm9wXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIHRoZSBkaW1lbnNpb25zIG9mIGRyb3B6b25lIGVsZW1lbnRzIGFyZVxuICAgICAqIGNhbGN1bGF0ZWQgb24gZXZlcnkgZHJhZ21vdmUgb3Igb25seSBvbiBkcmFnc3RhcnQgZm9yIHRoZSBkZWZhdWx0XG4gICAgICogZHJvcENoZWNrZXJcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChib29sZWFuKSAjb3B0aW9uYWwgVHJ1ZSB0byBjaGVjayBvbiBlYWNoIG1vdmUuIEZhbHNlIHRvIGNoZWNrIG9ubHkgYmVmb3JlIHN0YXJ0XG4gICAgID0gKGJvb2xlYW4gfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5keW5hbWljRHJvcCA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNCb29sKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgLy9pZiAoZHJhZ2dpbmcgJiYgZHluYW1pY0Ryb3AgIT09IG5ld1ZhbHVlICYmICFuZXdWYWx1ZSkge1xuICAgICAgICAgICAgICAgIC8vY2FsY1JlY3RzKGRyb3B6b25lcyk7XG4gICAgICAgICAgICAvL31cblxuICAgICAgICAgICAgc2NvcGUuZHluYW1pY0Ryb3AgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzY29wZS5keW5hbWljRHJvcDtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LnBvaW50ZXJNb3ZlVG9sZXJhbmNlXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgdGhlIGRpc3RhbmNlIHRoZSBwb2ludGVyIG11c3QgYmUgbW92ZWQgYmVmb3JlIGFuIGFjdGlvblxuICAgICAqIHNlcXVlbmNlIG9jY3Vycy4gVGhpcyBhbHNvIGFmZmVjdHMgdG9sZXJhbmNlIGZvciB0YXAgZXZlbnRzLlxuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKG51bWJlcikgI29wdGlvbmFsIFRoZSBtb3ZlbWVudCBmcm9tIHRoZSBzdGFydCBwb3NpdGlvbiBtdXN0IGJlIGdyZWF0ZXIgdGhhbiB0aGlzIHZhbHVlXG4gICAgID0gKG51bWJlciB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5wb2ludGVyTW92ZVRvbGVyYW5jZSA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICBzY29wZS5wb2ludGVyTW92ZVRvbGVyYW5jZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBzY29wZS5wb2ludGVyTW92ZVRvbGVyYW5jZTtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0Lm1heEludGVyYWN0aW9uc1xuICAgICBbIG1ldGhvZCBdXG4gICAgICoqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHRoZSBtYXhpbXVtIG51bWJlciBvZiBjb25jdXJyZW50IGludGVyYWN0aW9ucyBhbGxvd2VkLlxuICAgICAqIEJ5IGRlZmF1bHQgb25seSAxIGludGVyYWN0aW9uIGlzIGFsbG93ZWQgYXQgYSB0aW1lIChmb3IgYmFja3dhcmRzXG4gICAgICogY29tcGF0aWJpbGl0eSkuIFRvIGFsbG93IG11bHRpcGxlIGludGVyYWN0aW9ucyBvbiB0aGUgc2FtZSBJbnRlcmFjdGFibGVzXG4gICAgICogYW5kIGVsZW1lbnRzLCB5b3UgbmVlZCB0byBlbmFibGUgaXQgaW4gdGhlIGRyYWdnYWJsZSwgcmVzaXphYmxlIGFuZFxuICAgICAqIGdlc3R1cmFibGUgYCdtYXgnYCBhbmQgYCdtYXhQZXJFbGVtZW50J2Agb3B0aW9ucy5cbiAgICAgKipcbiAgICAgLSBuZXdWYWx1ZSAobnVtYmVyKSAjb3B0aW9uYWwgQW55IG51bWJlci4gbmV3VmFsdWUgPD0gMCBtZWFucyBubyBpbnRlcmFjdGlvbnMuXG4gICAgXFwqL1xuICAgIGludGVyYWN0Lm1heEludGVyYWN0aW9ucyA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICBzY29wZS5tYXhJbnRlcmFjdGlvbnMgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gc2NvcGUubWF4SW50ZXJhY3Rpb25zO1xuICAgIH07XG5cbiAgICBpbnRlcmFjdC5jcmVhdGVTbmFwR3JpZCA9IGZ1bmN0aW9uIChncmlkKSB7XG4gICAgICAgIHJldHVybiBmdW5jdGlvbiAoeCwgeSkge1xuICAgICAgICAgICAgdmFyIG9mZnNldFggPSAwLFxuICAgICAgICAgICAgICAgIG9mZnNldFkgPSAwO1xuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QoZ3JpZC5vZmZzZXQpKSB7XG4gICAgICAgICAgICAgICAgb2Zmc2V0WCA9IGdyaWQub2Zmc2V0Lng7XG4gICAgICAgICAgICAgICAgb2Zmc2V0WSA9IGdyaWQub2Zmc2V0Lnk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBncmlkeCA9IE1hdGgucm91bmQoKHggLSBvZmZzZXRYKSAvIGdyaWQueCksXG4gICAgICAgICAgICAgICAgZ3JpZHkgPSBNYXRoLnJvdW5kKCh5IC0gb2Zmc2V0WSkgLyBncmlkLnkpLFxuXG4gICAgICAgICAgICAgICAgbmV3WCA9IGdyaWR4ICogZ3JpZC54ICsgb2Zmc2V0WCxcbiAgICAgICAgICAgICAgICBuZXdZID0gZ3JpZHkgKiBncmlkLnkgKyBvZmZzZXRZO1xuXG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHg6IG5ld1gsXG4gICAgICAgICAgICAgICAgeTogbmV3WSxcbiAgICAgICAgICAgICAgICByYW5nZTogZ3JpZC5yYW5nZVxuICAgICAgICAgICAgfTtcbiAgICAgICAgfTtcbiAgICB9O1xuXG4gICAgZnVuY3Rpb24gZW5kQWxsSW50ZXJhY3Rpb25zIChldmVudCkge1xuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNjb3BlLmludGVyYWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJFbmQoZXZlbnQsIGV2ZW50KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGZ1bmN0aW9uIGxpc3RlblRvRG9jdW1lbnQgKGRvYykge1xuICAgICAgICBpZiAoc2NvcGUuY29udGFpbnMoc2NvcGUuZG9jdW1lbnRzLCBkb2MpKSB7IHJldHVybjsgfVxuXG4gICAgICAgIHZhciB3aW4gPSBkb2MuZGVmYXVsdFZpZXcgfHwgZG9jLnBhcmVudFdpbmRvdztcblxuICAgICAgICAvLyBhZGQgZGVsZWdhdGUgZXZlbnQgbGlzdGVuZXJcbiAgICAgICAgZm9yICh2YXIgZXZlbnRUeXBlIGluIHNjb3BlLmRlbGVnYXRlZEV2ZW50cykge1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsIGV2ZW50VHlwZSwgZGVsZWdhdGVMaXN0ZW5lcik7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgZXZlbnRUeXBlLCBkZWxlZ2F0ZVVzZUNhcHR1cmUsIHRydWUpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHNjb3BlLlBvaW50ZXJFdmVudCkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLlBvaW50ZXJFdmVudCA9PT0gd2luLk1TUG9pbnRlckV2ZW50KSB7XG4gICAgICAgICAgICAgICAgc2NvcGUucEV2ZW50VHlwZXMgPSB7XG4gICAgICAgICAgICAgICAgICAgIHVwOiAnTVNQb2ludGVyVXAnLCBkb3duOiAnTVNQb2ludGVyRG93bicsIG92ZXI6ICdtb3VzZW92ZXInLFxuICAgICAgICAgICAgICAgICAgICBvdXQ6ICdtb3VzZW91dCcsIG1vdmU6ICdNU1BvaW50ZXJNb3ZlJywgY2FuY2VsOiAnTVNQb2ludGVyQ2FuY2VsJyB9O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgc2NvcGUucEV2ZW50VHlwZXMgPSB7XG4gICAgICAgICAgICAgICAgICAgIHVwOiAncG9pbnRlcnVwJywgZG93bjogJ3BvaW50ZXJkb3duJywgb3ZlcjogJ3BvaW50ZXJvdmVyJyxcbiAgICAgICAgICAgICAgICAgICAgb3V0OiAncG9pbnRlcm91dCcsIG1vdmU6ICdwb2ludGVybW92ZScsIGNhbmNlbDogJ3BvaW50ZXJjYW5jZWwnIH07XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5kb3duICAsIHNjb3BlLmxpc3RlbmVycy5zZWxlY3RvckRvd24gKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5tb3ZlICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyTW92ZSAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5vdmVyICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyT3ZlciAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5vdXQgICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyT3V0ICAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy51cCAgICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyVXAgICAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5jYW5jZWwsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyQ2FuY2VsKTtcblxuICAgICAgICAgICAgLy8gYXV0b3Njcm9sbFxuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsIHNjb3BlLnBFdmVudFR5cGVzLm1vdmUsIHNjb3BlLmxpc3RlbmVycy5hdXRvU2Nyb2xsTW92ZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgJ21vdXNlZG93bicsIHNjb3BlLmxpc3RlbmVycy5zZWxlY3RvckRvd24pO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlck1vdmUgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnbW91c2V1cCcgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJVcCAgICk7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgJ21vdXNlb3ZlcicsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyT3ZlciApO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW91dCcgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlck91dCAgKTtcblxuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICd0b3VjaHN0YXJ0JyAsIHNjb3BlLmxpc3RlbmVycy5zZWxlY3RvckRvd24gKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAndG91Y2htb3ZlJyAgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlck1vdmUgICk7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoZW5kJyAgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJVcCAgICApO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICd0b3VjaGNhbmNlbCcsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyQ2FuY2VsKTtcblxuICAgICAgICAgICAgLy8gYXV0b3Njcm9sbFxuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBzY29wZS5saXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICd0b3VjaG1vdmUnLCBzY29wZS5saXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgICAgICB9XG5cbiAgICAgICAgZXZlbnRzLmFkZCh3aW4sICdibHVyJywgZW5kQWxsSW50ZXJhY3Rpb25zKTtcblxuICAgICAgICB0cnkge1xuICAgICAgICAgICAgaWYgKHdpbi5mcmFtZUVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICB2YXIgcGFyZW50RG9jID0gd2luLmZyYW1lRWxlbWVudC5vd25lckRvY3VtZW50LFxuICAgICAgICAgICAgICAgICAgICBwYXJlbnRXaW5kb3cgPSBwYXJlbnREb2MuZGVmYXVsdFZpZXc7XG5cbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHBhcmVudERvYyAgICwgJ21vdXNldXAnICAgICAgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlckVuZCk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICd0b3VjaGVuZCcgICAgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQocGFyZW50RG9jICAgLCAndG91Y2hjYW5jZWwnICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyRW5kKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHBhcmVudERvYyAgICwgJ3BvaW50ZXJ1cCcgICAgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlckVuZCk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdNU1BvaW50ZXJVcCcgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQocGFyZW50V2luZG93LCAnYmx1cicgICAgICAgICAsIGVuZEFsbEludGVyYWN0aW9ucyApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgaW50ZXJhY3Qud2luZG93UGFyZW50RXJyb3IgPSBlcnJvcjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChldmVudHMudXNlQXR0YWNoRXZlbnQpIHtcbiAgICAgICAgICAgIC8vIEZvciBJRSdzIGxhY2sgb2YgRXZlbnQjcHJldmVudERlZmF1bHRcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnc2VsZWN0c3RhcnQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgICAgICB2YXIgaW50ZXJhY3Rpb24gPSBzY29wZS5pbnRlcmFjdGlvbnNbMF07XG5cbiAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3Rpb24uY3VycmVudEFjdGlvbigpKSB7XG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAvLyBGb3IgSUUncyBiYWQgZGJsY2xpY2sgZXZlbnQgc2VxdWVuY2VcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnZGJsY2xpY2snLCBkb09uSW50ZXJhY3Rpb25zKCdpZThEYmxjbGljaycpKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHNjb3BlLmRvY3VtZW50cy5wdXNoKGRvYyk7XG4gICAgfVxuXG4gICAgbGlzdGVuVG9Eb2N1bWVudChzY29wZS5kb2N1bWVudCk7XG5cbiAgICBzY29wZS5pbnRlcmFjdCA9IGludGVyYWN0O1xuICAgIHNjb3BlLkludGVyYWN0YWJsZSA9IEludGVyYWN0YWJsZTtcbiAgICBzY29wZS5JbnRlcmFjdGlvbiA9IEludGVyYWN0aW9uO1xuICAgIHNjb3BlLkludGVyYWN0RXZlbnQgPSBJbnRlcmFjdEV2ZW50O1xuXG4gICAgLyogZ2xvYmFsIGV4cG9ydHM6IHRydWUsIG1vZHVsZSwgZGVmaW5lICovXG5cbiAgICAvLyBodHRwOi8vZG9jdW1lbnRjbG91ZC5naXRodWIuaW8vdW5kZXJzY29yZS9kb2NzL3VuZGVyc2NvcmUuaHRtbCNzZWN0aW9uLTExXG4gICAgaWYgKHR5cGVvZiBleHBvcnRzICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICBpZiAodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbW9kdWxlLmV4cG9ydHMpIHtcbiAgICAgICAgICAgIGV4cG9ydHMgPSBtb2R1bGUuZXhwb3J0cyA9IGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIGV4cG9ydHMuaW50ZXJhY3QgPSBpbnRlcmFjdDtcbiAgICB9XG4gICAgLy8gQU1EXG4gICAgZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgICAgIGRlZmluZSgnaW50ZXJhY3QnLCBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBzY29wZS5yZWFsV2luZG93LmludGVyYWN0ID0gaW50ZXJhY3Q7XG4gICAgfVxuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgc2NvcGUgPSByZXF1aXJlKCcuL3Njb3BlJyk7XG52YXIgdXRpbHMgPSByZXF1aXJlKCcuL3V0aWxzJyk7XG5cbmZ1bmN0aW9uIEludGVyYWN0RXZlbnQgKGludGVyYWN0aW9uLCBldmVudCwgYWN0aW9uLCBwaGFzZSwgZWxlbWVudCwgcmVsYXRlZCkge1xuICAgIHZhciBjbGllbnQsXG4gICAgICAgIHBhZ2UsXG4gICAgICAgIHRhcmdldCAgICAgID0gaW50ZXJhY3Rpb24udGFyZ2V0LFxuICAgICAgICBzbmFwU3RhdHVzICA9IGludGVyYWN0aW9uLnNuYXBTdGF0dXMsXG4gICAgICAgIHJlc3RyaWN0U3RhdHVzICA9IGludGVyYWN0aW9uLnJlc3RyaWN0U3RhdHVzLFxuICAgICAgICBwb2ludGVycyAgICA9IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICBkZWx0YVNvdXJjZSA9ICh0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMgfHwgc2NvcGUuZGVmYXVsdE9wdGlvbnMpLmRlbHRhU291cmNlLFxuICAgICAgICBzb3VyY2VYICAgICA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZICAgICA9IGRlbHRhU291cmNlICsgJ1knLFxuICAgICAgICBvcHRpb25zICAgICA9IHRhcmdldD8gdGFyZ2V0Lm9wdGlvbnM6IHNjb3BlLmRlZmF1bHRPcHRpb25zLFxuICAgICAgICBvcmlnaW4gICAgICA9IHNjb3BlLmdldE9yaWdpblhZKHRhcmdldCwgZWxlbWVudCksXG4gICAgICAgIHN0YXJ0aW5nICAgID0gcGhhc2UgPT09ICdzdGFydCcsXG4gICAgICAgIGVuZGluZyAgICAgID0gcGhhc2UgPT09ICdlbmQnLFxuICAgICAgICBjb29yZHMgICAgICA9IHN0YXJ0aW5nPyBpbnRlcmFjdGlvbi5zdGFydENvb3JkcyA6IGludGVyYWN0aW9uLmN1ckNvb3JkcztcblxuICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IGludGVyYWN0aW9uLmVsZW1lbnQ7XG5cbiAgICBwYWdlICAgPSB1dGlscy5leHRlbmQoe30sIGNvb3Jkcy5wYWdlKTtcbiAgICBjbGllbnQgPSB1dGlscy5leHRlbmQoe30sIGNvb3Jkcy5jbGllbnQpO1xuXG4gICAgcGFnZS54IC09IG9yaWdpbi54O1xuICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcblxuICAgIGNsaWVudC54IC09IG9yaWdpbi54O1xuICAgIGNsaWVudC55IC09IG9yaWdpbi55O1xuXG4gICAgdmFyIHJlbGF0aXZlUG9pbnRzID0gb3B0aW9uc1thY3Rpb25dLnNuYXAgJiYgb3B0aW9uc1thY3Rpb25dLnNuYXAucmVsYXRpdmVQb2ludHMgO1xuXG4gICAgaWYgKHNjb3BlLmNoZWNrU25hcCh0YXJnZXQsIGFjdGlvbikgJiYgIShzdGFydGluZyAmJiByZWxhdGl2ZVBvaW50cyAmJiByZWxhdGl2ZVBvaW50cy5sZW5ndGgpKSB7XG4gICAgICAgIHRoaXMuc25hcCA9IHtcbiAgICAgICAgICAgIHJhbmdlICA6IHNuYXBTdGF0dXMucmFuZ2UsXG4gICAgICAgICAgICBsb2NrZWQgOiBzbmFwU3RhdHVzLmxvY2tlZCxcbiAgICAgICAgICAgIHggICAgICA6IHNuYXBTdGF0dXMuc25hcHBlZFgsXG4gICAgICAgICAgICB5ICAgICAgOiBzbmFwU3RhdHVzLnNuYXBwZWRZLFxuICAgICAgICAgICAgcmVhbFggIDogc25hcFN0YXR1cy5yZWFsWCxcbiAgICAgICAgICAgIHJlYWxZICA6IHNuYXBTdGF0dXMucmVhbFksXG4gICAgICAgICAgICBkeCAgICAgOiBzbmFwU3RhdHVzLmR4LFxuICAgICAgICAgICAgZHkgICAgIDogc25hcFN0YXR1cy5keVxuICAgICAgICB9O1xuXG4gICAgICAgIGlmIChzbmFwU3RhdHVzLmxvY2tlZCkge1xuICAgICAgICAgICAgcGFnZS54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBwYWdlLnkgKz0gc25hcFN0YXR1cy5keTtcbiAgICAgICAgICAgIGNsaWVudC54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBjbGllbnQueSArPSBzbmFwU3RhdHVzLmR5O1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHNjb3BlLmNoZWNrUmVzdHJpY3QodGFyZ2V0LCBhY3Rpb24pICYmICEoc3RhcnRpbmcgJiYgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0LmVsZW1lbnRSZWN0KSAmJiByZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkKSB7XG4gICAgICAgIHBhZ2UueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgcGFnZS55ICs9IHJlc3RyaWN0U3RhdHVzLmR5O1xuICAgICAgICBjbGllbnQueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgY2xpZW50LnkgKz0gcmVzdHJpY3RTdGF0dXMuZHk7XG5cbiAgICAgICAgdGhpcy5yZXN0cmljdCA9IHtcbiAgICAgICAgICAgIGR4OiByZXN0cmljdFN0YXR1cy5keCxcbiAgICAgICAgICAgIGR5OiByZXN0cmljdFN0YXR1cy5keVxuICAgICAgICB9O1xuICAgIH1cblxuICAgIHRoaXMucGFnZVggICAgID0gcGFnZS54O1xuICAgIHRoaXMucGFnZVkgICAgID0gcGFnZS55O1xuICAgIHRoaXMuY2xpZW50WCAgID0gY2xpZW50Lng7XG4gICAgdGhpcy5jbGllbnRZICAgPSBjbGllbnQueTtcblxuICAgIHRoaXMueDAgICAgICAgID0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54IC0gb3JpZ2luLng7XG4gICAgdGhpcy55MCAgICAgICAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueTtcbiAgICB0aGlzLmNsaWVudFgwICA9IGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54IC0gb3JpZ2luLng7XG4gICAgdGhpcy5jbGllbnRZMCAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5jbGllbnQueSAtIG9yaWdpbi55O1xuICAgIHRoaXMuY3RybEtleSAgID0gZXZlbnQuY3RybEtleTtcbiAgICB0aGlzLmFsdEtleSAgICA9IGV2ZW50LmFsdEtleTtcbiAgICB0aGlzLnNoaWZ0S2V5ICA9IGV2ZW50LnNoaWZ0S2V5O1xuICAgIHRoaXMubWV0YUtleSAgID0gZXZlbnQubWV0YUtleTtcbiAgICB0aGlzLmJ1dHRvbiAgICA9IGV2ZW50LmJ1dHRvbjtcbiAgICB0aGlzLnRhcmdldCAgICA9IGVsZW1lbnQ7XG4gICAgdGhpcy50MCAgICAgICAgPSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG4gICAgdGhpcy50eXBlICAgICAgPSBhY3Rpb24gKyAocGhhc2UgfHwgJycpO1xuXG4gICAgdGhpcy5pbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uO1xuICAgIHRoaXMuaW50ZXJhY3RhYmxlID0gdGFyZ2V0O1xuXG4gICAgdmFyIGluZXJ0aWFTdGF0dXMgPSBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzO1xuXG4gICAgaWYgKGluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7XG4gICAgICAgIHRoaXMuZGV0YWlsID0gJ2luZXJ0aWEnO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGVkKSB7XG4gICAgICAgIHRoaXMucmVsYXRlZFRhcmdldCA9IHJlbGF0ZWQ7XG4gICAgfVxuXG4gICAgLy8gZW5kIGV2ZW50IGR4LCBkeSBpcyBkaWZmZXJlbmNlIGJldHdlZW4gc3RhcnQgYW5kIGVuZCBwb2ludHNcbiAgICBpZiAoZW5kaW5nKSB7XG4gICAgICAgIGlmIChkZWx0YVNvdXJjZSA9PT0gJ2NsaWVudCcpIHtcbiAgICAgICAgICAgIHRoaXMuZHggPSBjbGllbnQueCAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54O1xuICAgICAgICAgICAgdGhpcy5keSA9IGNsaWVudC55IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMuY2xpZW50Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gcGFnZS54IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54O1xuICAgICAgICAgICAgdGhpcy5keSA9IHBhZ2UueSAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLnBhZ2UueTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLmR4ID0gMDtcbiAgICAgICAgdGhpcy5keSA9IDA7XG4gICAgfVxuICAgIC8vIGNvcHkgcHJvcGVydGllcyBmcm9tIHByZXZpb3VzbW92ZSBpZiBzdGFydGluZyBpbmVydGlhXG4gICAgZWxzZSBpZiAocGhhc2UgPT09ICdpbmVydGlhc3RhcnQnKSB7XG4gICAgICAgIHRoaXMuZHggPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHg7XG4gICAgICAgIHRoaXMuZHkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBpZiAoZGVsdGFTb3VyY2UgPT09ICdjbGllbnQnKSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gY2xpZW50LnggLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuY2xpZW50WDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBjbGllbnQueSAtIGludGVyYWN0aW9uLnByZXZFdmVudC5jbGllbnRZO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5keCA9IHBhZ2UueCAtIGludGVyYWN0aW9uLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBwYWdlLnkgLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKGludGVyYWN0aW9uLnByZXZFdmVudCAmJiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGV0YWlsID09PSAnaW5lcnRpYSdcbiAgICAgICAgJiYgIWluZXJ0aWFTdGF0dXMuYWN0aXZlXG4gICAgICAgICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhLnplcm9SZXN1bWVEZWx0YSkge1xuXG4gICAgICAgIGluZXJ0aWFTdGF0dXMucmVzdW1lRHggKz0gdGhpcy5keDtcbiAgICAgICAgaW5lcnRpYVN0YXR1cy5yZXN1bWVEeSArPSB0aGlzLmR5O1xuXG4gICAgICAgIHRoaXMuZHggPSB0aGlzLmR5ID0gMDtcbiAgICB9XG5cbiAgICBpZiAoYWN0aW9uID09PSAncmVzaXplJyAmJiBpbnRlcmFjdGlvbi5yZXNpemVBeGVzKSB7XG4gICAgICAgIGlmIChvcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneScpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR4ID0gdGhpcy5keTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuZHkgPSB0aGlzLmR4O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5heGVzID0gJ3h5JztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuYXhlcyA9IGludGVyYWN0aW9uLnJlc2l6ZUF4ZXM7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneCcpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR5ID0gMDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGludGVyYWN0aW9uLnJlc2l6ZUF4ZXMgPT09ICd5Jykge1xuICAgICAgICAgICAgICAgIHRoaXMuZHggPSAwO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2dlc3R1cmUnKSB7XG4gICAgICAgIHRoaXMudG91Y2hlcyA9IFtwb2ludGVyc1swXSwgcG9pbnRlcnNbMV1dO1xuXG4gICAgICAgIGlmIChzdGFydGluZykge1xuICAgICAgICAgICAgdGhpcy5kaXN0YW5jZSA9IHV0aWxzLnRvdWNoRGlzdGFuY2UocG9pbnRlcnMsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuYm94ICAgICAgPSB1dGlscy50b3VjaEJCb3gocG9pbnRlcnMpO1xuICAgICAgICAgICAgdGhpcy5zY2FsZSAgICA9IDE7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gMDtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB1dGlscy50b3VjaEFuZ2xlKHBvaW50ZXJzLCB1bmRlZmluZWQsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuZGEgICAgICAgPSAwO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGVuZGluZyB8fCBldmVudCBpbnN0YW5jZW9mIEludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgICAgIHRoaXMuZGlzdGFuY2UgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGlzdGFuY2U7XG4gICAgICAgICAgICB0aGlzLmJveCAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmJveDtcbiAgICAgICAgICAgIHRoaXMuc2NhbGUgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc2NhbGU7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gdGhpcy5zY2FsZSAtIDE7XG4gICAgICAgICAgICB0aGlzLmFuZ2xlICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmFuZ2xlO1xuICAgICAgICAgICAgdGhpcy5kYSAgICAgICA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnN0YXJ0QW5nbGU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmRpc3RhbmNlID0gdXRpbHMudG91Y2hEaXN0YW5jZShwb2ludGVycywgZGVsdGFTb3VyY2UpO1xuICAgICAgICAgICAgdGhpcy5ib3ggICAgICA9IHV0aWxzLnRvdWNoQkJveChwb2ludGVycyk7XG4gICAgICAgICAgICB0aGlzLnNjYWxlICAgID0gdGhpcy5kaXN0YW5jZSAvIGludGVyYWN0aW9uLmdlc3R1cmUuc3RhcnREaXN0YW5jZTtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB1dGlscy50b3VjaEFuZ2xlKHBvaW50ZXJzLCBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZSwgZGVsdGFTb3VyY2UpO1xuXG4gICAgICAgICAgICB0aGlzLmRzID0gdGhpcy5zY2FsZSAtIGludGVyYWN0aW9uLmdlc3R1cmUucHJldlNjYWxlO1xuICAgICAgICAgICAgdGhpcy5kYSA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLmRvd25UaW1lc1swXTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSAwO1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IDA7XG4gICAgICAgIHRoaXMuc3BlZWQgICAgID0gMDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVggPSAwO1xuICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IDA7XG4gICAgfVxuICAgIGVsc2UgaWYgKHBoYXNlID09PSAnaW5lcnRpYXN0YXJ0Jykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHQgICAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmR0O1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IGludGVyYWN0aW9uLnByZXZFdmVudC5kdXJhdGlvbjtcbiAgICAgICAgdGhpcy5zcGVlZCAgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc3BlZWQ7XG4gICAgICAgIHRoaXMudmVsb2NpdHlYID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdGhpcy50aW1lU3RhbXAgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHVyYXRpb24gID0gdGhpcy50aW1lU3RhbXAgLSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG5cbiAgICAgICAgaWYgKGV2ZW50IGluc3RhbmNlb2YgSW50ZXJhY3RFdmVudCkge1xuICAgICAgICAgICAgdmFyIGR4ID0gdGhpc1tzb3VyY2VYXSAtIGludGVyYWN0aW9uLnByZXZFdmVudFtzb3VyY2VYXSxcbiAgICAgICAgICAgICAgICBkeSA9IHRoaXNbc291cmNlWV0gLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnRbc291cmNlWV0sXG4gICAgICAgICAgICAgICAgZHQgPSB0aGlzLmR0IC8gMTAwMDtcblxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IHV0aWxzLmh5cG90KGR4LCBkeSkgLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlYID0gZHggLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlZID0gZHkgLyBkdDtcbiAgICAgICAgfVxuICAgICAgICAvLyBpZiBub3JtYWwgbW92ZSBvciBlbmQgZXZlbnQsIHVzZSBwcmV2aW91cyB1c2VyIGV2ZW50IGNvb3Jkc1xuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIHNwZWVkIGFuZCB2ZWxvY2l0eSBpbiBwaXhlbHMgcGVyIHNlY29uZFxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0uc3BlZWQ7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udng7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udnk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoKGVuZGluZyB8fCBwaGFzZSA9PT0gJ2luZXJ0aWFzdGFydCcpXG4gICAgICAgICYmIGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCA+IDYwMCAmJiB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXAgPCAxNTApIHtcblxuICAgICAgICB2YXIgYW5nbGUgPSAxODAgKiBNYXRoLmF0YW4yKGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVksIGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVgpIC8gTWF0aC5QSSxcbiAgICAgICAgICAgIG92ZXJsYXAgPSAyMi41O1xuXG4gICAgICAgIGlmIChhbmdsZSA8IDApIHtcbiAgICAgICAgICAgIGFuZ2xlICs9IDM2MDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBsZWZ0ID0gMTM1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDIyNSArIG92ZXJsYXAsXG4gICAgICAgICAgICB1cCAgID0gMjI1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDMxNSArIG92ZXJsYXAsXG5cbiAgICAgICAgICAgIHJpZ2h0ID0gIWxlZnQgJiYgKDMxNSAtIG92ZXJsYXAgPD0gYW5nbGUgfHwgYW5nbGUgPCAgNDUgKyBvdmVybGFwKSxcbiAgICAgICAgICAgIGRvd24gID0gIXVwICAgJiYgICA0NSAtIG92ZXJsYXAgPD0gYW5nbGUgJiYgYW5nbGUgPCAxMzUgKyBvdmVybGFwO1xuXG4gICAgICAgIHRoaXMuc3dpcGUgPSB7XG4gICAgICAgICAgICB1cCAgIDogdXAsXG4gICAgICAgICAgICBkb3duIDogZG93bixcbiAgICAgICAgICAgIGxlZnQgOiBsZWZ0LFxuICAgICAgICAgICAgcmlnaHQ6IHJpZ2h0LFxuICAgICAgICAgICAgYW5nbGU6IGFuZ2xlLFxuICAgICAgICAgICAgc3BlZWQ6IGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCxcbiAgICAgICAgICAgIHZlbG9jaXR5OiB7XG4gICAgICAgICAgICAgICAgeDogaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WCxcbiAgICAgICAgICAgICAgICB5OiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZXG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgfVxufVxuXG5JbnRlcmFjdEV2ZW50LnByb3RvdHlwZSA9IHtcbiAgICBwcmV2ZW50RGVmYXVsdDogdXRpbHMuYmxhbmssXG4gICAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkID0gdGhpcy5wcm9wYWdhdGlvblN0b3BwZWQgPSB0cnVlO1xuICAgIH0sXG4gICAgc3RvcFByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMucHJvcGFnYXRpb25TdG9wcGVkID0gdHJ1ZTtcbiAgICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEludGVyYWN0RXZlbnQ7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBzY29wZSA9IHJlcXVpcmUoJy4vc2NvcGUnKTtcbnZhciB1dGlscyA9IHJlcXVpcmUoJy4vdXRpbHMnKTtcbnZhciBhbmltYXRpb25GcmFtZSA9IHV0aWxzLnJhZjtcbnZhciBJbnRlcmFjdEV2ZW50ID0gcmVxdWlyZSgnLi9JbnRlcmFjdEV2ZW50Jyk7XG52YXIgZXZlbnRzID0gcmVxdWlyZSgnLi91dGlscy9ldmVudHMnKTtcbnZhciBicm93c2VyID0gcmVxdWlyZSgnLi91dGlscy9icm93c2VyJyk7XG5cbmZ1bmN0aW9uIEludGVyYWN0aW9uICgpIHtcbiAgICB0aGlzLnRhcmdldCAgICAgICAgICA9IG51bGw7IC8vIGN1cnJlbnQgaW50ZXJhY3RhYmxlIGJlaW5nIGludGVyYWN0ZWQgd2l0aFxuICAgIHRoaXMuZWxlbWVudCAgICAgICAgID0gbnVsbDsgLy8gdGhlIHRhcmdldCBlbGVtZW50IG9mIHRoZSBpbnRlcmFjdGFibGVcbiAgICB0aGlzLmRyb3BUYXJnZXQgICAgICA9IG51bGw7IC8vIHRoZSBkcm9wem9uZSBhIGRyYWcgdGFyZ2V0IG1pZ2h0IGJlIGRyb3BwZWQgaW50b1xuICAgIHRoaXMuZHJvcEVsZW1lbnQgICAgID0gbnVsbDsgLy8gdGhlIGVsZW1lbnQgYXQgdGhlIHRpbWUgb2YgY2hlY2tpbmdcbiAgICB0aGlzLnByZXZEcm9wVGFyZ2V0ICA9IG51bGw7IC8vIHRoZSBkcm9wem9uZSB0aGF0IHdhcyByZWNlbnRseSBkcmFnZ2VkIGF3YXkgZnJvbVxuICAgIHRoaXMucHJldkRyb3BFbGVtZW50ID0gbnVsbDsgLy8gdGhlIGVsZW1lbnQgYXQgdGhlIHRpbWUgb2YgY2hlY2tpbmdcblxuICAgIHRoaXMucHJlcGFyZWQgICAgICAgID0geyAgICAgLy8gYWN0aW9uIHRoYXQncyByZWFkeSB0byBiZSBmaXJlZCBvbiBuZXh0IG1vdmUgZXZlbnRcbiAgICAgICAgbmFtZSA6IG51bGwsXG4gICAgICAgIGF4aXMgOiBudWxsLFxuICAgICAgICBlZGdlczogbnVsbFxuICAgIH07XG5cbiAgICB0aGlzLm1hdGNoZXMgICAgICAgICA9IFtdOyAgIC8vIGFsbCBzZWxlY3RvcnMgdGhhdCBhcmUgbWF0Y2hlZCBieSB0YXJnZXQgZWxlbWVudFxuICAgIHRoaXMubWF0Y2hFbGVtZW50cyAgID0gW107ICAgLy8gY29ycmVzcG9uZGluZyBlbGVtZW50c1xuXG4gICAgdGhpcy5pbmVydGlhU3RhdHVzID0ge1xuICAgICAgICBhY3RpdmUgICAgICAgOiBmYWxzZSxcbiAgICAgICAgc21vb3RoRW5kICAgIDogZmFsc2UsXG5cbiAgICAgICAgc3RhcnRFdmVudDogbnVsbCxcbiAgICAgICAgdXBDb29yZHM6IHt9LFxuXG4gICAgICAgIHhlOiAwLCB5ZTogMCxcbiAgICAgICAgc3g6IDAsIHN5OiAwLFxuXG4gICAgICAgIHQwOiAwLFxuICAgICAgICB2eDA6IDAsIHZ5czogMCxcbiAgICAgICAgZHVyYXRpb246IDAsXG5cbiAgICAgICAgcmVzdW1lRHg6IDAsXG4gICAgICAgIHJlc3VtZUR5OiAwLFxuXG4gICAgICAgIGxhbWJkYV92MDogMCxcbiAgICAgICAgb25lX3ZlX3YwOiAwLFxuICAgICAgICBpICA6IG51bGxcbiAgICB9O1xuXG4gICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24oRnVuY3Rpb24ucHJvdG90eXBlLmJpbmQpKSB7XG4gICAgICAgIHRoaXMuYm91bmRJbmVydGlhRnJhbWUgPSB0aGlzLmluZXJ0aWFGcmFtZS5iaW5kKHRoaXMpO1xuICAgICAgICB0aGlzLmJvdW5kU21vb3RoRW5kRnJhbWUgPSB0aGlzLnNtb290aEVuZEZyYW1lLmJpbmQodGhpcyk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICB2YXIgdGhhdCA9IHRoaXM7XG5cbiAgICAgICAgdGhpcy5ib3VuZEluZXJ0aWFGcmFtZSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIHRoYXQuaW5lcnRpYUZyYW1lKCk7IH07XG4gICAgICAgIHRoaXMuYm91bmRTbW9vdGhFbmRGcmFtZSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIHRoYXQuc21vb3RoRW5kRnJhbWUoKTsgfTtcbiAgICB9XG5cbiAgICB0aGlzLmFjdGl2ZURyb3BzID0ge1xuICAgICAgICBkcm9wem9uZXM6IFtdLCAgICAgIC8vIHRoZSBkcm9wem9uZXMgdGhhdCBhcmUgbWVudGlvbmVkIGJlbG93XG4gICAgICAgIGVsZW1lbnRzIDogW10sICAgICAgLy8gZWxlbWVudHMgb2YgZHJvcHpvbmVzIHRoYXQgYWNjZXB0IHRoZSB0YXJnZXQgZHJhZ2dhYmxlXG4gICAgICAgIHJlY3RzICAgIDogW10gICAgICAgLy8gdGhlIHJlY3RzIG9mIHRoZSBlbGVtZW50cyBtZW50aW9uZWQgYWJvdmVcbiAgICB9O1xuXG4gICAgLy8ga2VlcCB0cmFjayBvZiBhZGRlZCBwb2ludGVyc1xuICAgIHRoaXMucG9pbnRlcnMgICAgPSBbXTtcbiAgICB0aGlzLnBvaW50ZXJJZHMgID0gW107XG4gICAgdGhpcy5kb3duVGFyZ2V0cyA9IFtdO1xuICAgIHRoaXMuZG93blRpbWVzICAgPSBbXTtcbiAgICB0aGlzLmhvbGRUaW1lcnMgID0gW107XG5cbiAgICAvLyBQcmV2aW91cyBuYXRpdmUgcG9pbnRlciBtb3ZlIGV2ZW50IGNvb3JkaW5hdGVzXG4gICAgdGhpcy5wcmV2Q29vcmRzID0ge1xuICAgICAgICBwYWdlICAgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICBjbGllbnQgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICB0aW1lU3RhbXA6IDBcbiAgICB9O1xuICAgIC8vIGN1cnJlbnQgbmF0aXZlIHBvaW50ZXIgbW92ZSBldmVudCBjb29yZGluYXRlc1xuICAgIHRoaXMuY3VyQ29vcmRzID0ge1xuICAgICAgICBwYWdlICAgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICBjbGllbnQgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICB0aW1lU3RhbXA6IDBcbiAgICB9O1xuXG4gICAgLy8gU3RhcnRpbmcgSW50ZXJhY3RFdmVudCBwb2ludGVyIGNvb3JkaW5hdGVzXG4gICAgdGhpcy5zdGFydENvb3JkcyA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcblxuICAgIC8vIENoYW5nZSBpbiBjb29yZGluYXRlcyBhbmQgdGltZSBvZiB0aGUgcG9pbnRlclxuICAgIHRoaXMucG9pbnRlckRlbHRhID0ge1xuICAgICAgICBwYWdlICAgICA6IHsgeDogMCwgeTogMCwgdng6IDAsIHZ5OiAwLCBzcGVlZDogMCB9LFxuICAgICAgICBjbGllbnQgICA6IHsgeDogMCwgeTogMCwgdng6IDAsIHZ5OiAwLCBzcGVlZDogMCB9LFxuICAgICAgICB0aW1lU3RhbXA6IDBcbiAgICB9O1xuXG4gICAgdGhpcy5kb3duRXZlbnQgICA9IG51bGw7ICAgIC8vIHBvaW50ZXJkb3duL21vdXNlZG93bi90b3VjaHN0YXJ0IGV2ZW50XG4gICAgdGhpcy5kb3duUG9pbnRlciA9IHt9O1xuXG4gICAgdGhpcy5fZXZlbnRUYXJnZXQgICAgPSBudWxsO1xuICAgIHRoaXMuX2N1ckV2ZW50VGFyZ2V0ID0gbnVsbDtcblxuICAgIHRoaXMucHJldkV2ZW50ID0gbnVsbDsgICAgICAvLyBwcmV2aW91cyBhY3Rpb24gZXZlbnRcbiAgICB0aGlzLnRhcFRpbWUgICA9IDA7ICAgICAgICAgLy8gdGltZSBvZiB0aGUgbW9zdCByZWNlbnQgdGFwIGV2ZW50XG4gICAgdGhpcy5wcmV2VGFwICAgPSBudWxsO1xuXG4gICAgdGhpcy5zdGFydE9mZnNldCAgICA9IHsgbGVmdDogMCwgcmlnaHQ6IDAsIHRvcDogMCwgYm90dG9tOiAwIH07XG4gICAgdGhpcy5yZXN0cmljdE9mZnNldCA9IHsgbGVmdDogMCwgcmlnaHQ6IDAsIHRvcDogMCwgYm90dG9tOiAwIH07XG4gICAgdGhpcy5zbmFwT2Zmc2V0cyAgICA9IFtdO1xuXG4gICAgdGhpcy5nZXN0dXJlID0ge1xuICAgICAgICBzdGFydDogeyB4OiAwLCB5OiAwIH0sXG5cbiAgICAgICAgc3RhcnREaXN0YW5jZTogMCwgICAvLyBkaXN0YW5jZSBiZXR3ZWVuIHR3byB0b3VjaGVzIG9mIHRvdWNoU3RhcnRcbiAgICAgICAgcHJldkRpc3RhbmNlIDogMCxcbiAgICAgICAgZGlzdGFuY2UgICAgIDogMCxcblxuICAgICAgICBzY2FsZTogMSwgICAgICAgICAgIC8vIGdlc3R1cmUuZGlzdGFuY2UgLyBnZXN0dXJlLnN0YXJ0RGlzdGFuY2VcblxuICAgICAgICBzdGFydEFuZ2xlOiAwLCAgICAgIC8vIGFuZ2xlIG9mIGxpbmUgam9pbmluZyB0d28gdG91Y2hlc1xuICAgICAgICBwcmV2QW5nbGUgOiAwICAgICAgIC8vIGFuZ2xlIG9mIHRoZSBwcmV2aW91cyBnZXN0dXJlIGV2ZW50XG4gICAgfTtcblxuICAgIHRoaXMuc25hcFN0YXR1cyA9IHtcbiAgICAgICAgeCAgICAgICA6IDAsIHkgICAgICAgOiAwLFxuICAgICAgICBkeCAgICAgIDogMCwgZHkgICAgICA6IDAsXG4gICAgICAgIHJlYWxYICAgOiAwLCByZWFsWSAgIDogMCxcbiAgICAgICAgc25hcHBlZFg6IDAsIHNuYXBwZWRZOiAwLFxuICAgICAgICB0YXJnZXRzIDogW10sXG4gICAgICAgIGxvY2tlZCAgOiBmYWxzZSxcbiAgICAgICAgY2hhbmdlZCA6IGZhbHNlXG4gICAgfTtcblxuICAgIHRoaXMucmVzdHJpY3RTdGF0dXMgPSB7XG4gICAgICAgIGR4ICAgICAgICAgOiAwLCBkeSAgICAgICAgIDogMCxcbiAgICAgICAgcmVzdHJpY3RlZFg6IDAsIHJlc3RyaWN0ZWRZOiAwLFxuICAgICAgICBzbmFwICAgICAgIDogbnVsbCxcbiAgICAgICAgcmVzdHJpY3RlZCA6IGZhbHNlLFxuICAgICAgICBjaGFuZ2VkICAgIDogZmFsc2VcbiAgICB9O1xuXG4gICAgdGhpcy5yZXN0cmljdFN0YXR1cy5zbmFwID0gdGhpcy5zbmFwU3RhdHVzO1xuXG4gICAgdGhpcy5wb2ludGVySXNEb3duICAgPSBmYWxzZTtcbiAgICB0aGlzLnBvaW50ZXJXYXNNb3ZlZCA9IGZhbHNlO1xuICAgIHRoaXMuZ2VzdHVyaW5nICAgICAgID0gZmFsc2U7XG4gICAgdGhpcy5kcmFnZ2luZyAgICAgICAgPSBmYWxzZTtcbiAgICB0aGlzLnJlc2l6aW5nICAgICAgICA9IGZhbHNlO1xuICAgIHRoaXMucmVzaXplQXhlcyAgICAgID0gJ3h5JztcblxuICAgIHRoaXMubW91c2UgPSBmYWxzZTtcblxuICAgIHNjb3BlLmludGVyYWN0aW9ucy5wdXNoKHRoaXMpO1xufVxuXG4vLyBDaGVjayBpZiBhY3Rpb24gaXMgZW5hYmxlZCBnbG9iYWxseSBhbmQgdGhlIGN1cnJlbnQgdGFyZ2V0IHN1cHBvcnRzIGl0XG4vLyBJZiBzbywgcmV0dXJuIHRoZSB2YWxpZGF0ZWQgYWN0aW9uLiBPdGhlcndpc2UsIHJldHVybiBudWxsXG5mdW5jdGlvbiB2YWxpZGF0ZUFjdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUpIHtcbiAgICBpZiAoIXNjb3BlLmlzT2JqZWN0KGFjdGlvbikpIHsgcmV0dXJuIG51bGw7IH1cblxuICAgIHZhciBhY3Rpb25OYW1lID0gYWN0aW9uLm5hbWUsXG4gICAgICAgIG9wdGlvbnMgPSBpbnRlcmFjdGFibGUub3B0aW9ucztcblxuICAgIGlmICgoICAoYWN0aW9uTmFtZSAgPT09ICdyZXNpemUnICAgJiYgb3B0aW9ucy5yZXNpemUuZW5hYmxlZCApXG4gICAgICAgIHx8IChhY3Rpb25OYW1lICAgICAgPT09ICdkcmFnJyAgICAgJiYgb3B0aW9ucy5kcmFnLmVuYWJsZWQgIClcbiAgICAgICAgfHwgKGFjdGlvbk5hbWUgICAgICA9PT0gJ2dlc3R1cmUnICAmJiBvcHRpb25zLmdlc3R1cmUuZW5hYmxlZCkpXG4gICAgICAgICYmIHNjb3BlLmFjdGlvbklzRW5hYmxlZFthY3Rpb25OYW1lXSkge1xuXG4gICAgICAgIGlmIChhY3Rpb25OYW1lID09PSAncmVzaXplJyB8fCBhY3Rpb25OYW1lID09PSAncmVzaXpleXgnKSB7XG4gICAgICAgICAgICBhY3Rpb25OYW1lID0gJ3Jlc2l6ZXh5JztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBhY3Rpb247XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xufVxuXG5mdW5jdGlvbiBnZXRBY3Rpb25DdXJzb3IgKGFjdGlvbikge1xuICAgIHZhciBjdXJzb3IgPSAnJztcblxuICAgIGlmIChhY3Rpb24ubmFtZSA9PT0gJ2RyYWcnKSB7XG4gICAgICAgIGN1cnNvciA9ICBzY29wZS5hY3Rpb25DdXJzb3JzLmRyYWc7XG4gICAgfVxuICAgIGlmIChhY3Rpb24ubmFtZSA9PT0gJ3Jlc2l6ZScpIHtcbiAgICAgICAgaWYgKGFjdGlvbi5heGlzKSB7XG4gICAgICAgICAgICBjdXJzb3IgPSAgc2NvcGUuYWN0aW9uQ3Vyc29yc1thY3Rpb24ubmFtZSArIGFjdGlvbi5heGlzXTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChhY3Rpb24uZWRnZXMpIHtcbiAgICAgICAgICAgIHZhciBjdXJzb3JLZXkgPSAncmVzaXplJyxcbiAgICAgICAgICAgICAgICBlZGdlTmFtZXMgPSBbJ3RvcCcsICdib3R0b20nLCAnbGVmdCcsICdyaWdodCddO1xuXG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IDQ7IGkrKykge1xuICAgICAgICAgICAgICAgIGlmIChhY3Rpb24uZWRnZXNbZWRnZU5hbWVzW2ldXSkge1xuICAgICAgICAgICAgICAgICAgICBjdXJzb3JLZXkgKz0gZWRnZU5hbWVzW2ldO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY3Vyc29yID0gc2NvcGUuYWN0aW9uQ3Vyc29yc1tjdXJzb3JLZXldO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGN1cnNvcjtcbn1cblxuZnVuY3Rpb24gcHJldmVudE9yaWdpbmFsRGVmYXVsdCAoKSB7XG4gICAgdGhpcy5vcmlnaW5hbEV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG59XG5cbkludGVyYWN0aW9uLnByb3RvdHlwZSA9IHtcbiAgICBnZXRQYWdlWFkgIDogZnVuY3Rpb24gKHBvaW50ZXIsIHh5KSB7IHJldHVybiAgIHV0aWxzLmdldFBhZ2VYWShwb2ludGVyLCB4eSwgdGhpcyk7IH0sXG4gICAgZ2V0Q2xpZW50WFk6IGZ1bmN0aW9uIChwb2ludGVyLCB4eSkgeyByZXR1cm4gdXRpbHMuZ2V0Q2xpZW50WFkocG9pbnRlciwgeHksIHRoaXMpOyB9LFxuICAgIHNldEV2ZW50WFkgOiBmdW5jdGlvbiAodGFyZ2V0LCBwdHIpIHsgcmV0dXJuICB1dGlscy5zZXRFdmVudFhZKHRhcmdldCwgcHRyLCB0aGlzKTsgfSxcblxuICAgIHBvaW50ZXJPdmVyOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIGlmICh0aGlzLnByZXBhcmVkLm5hbWUgfHwgIXRoaXMubW91c2UpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgdmFyIGN1ck1hdGNoZXMgPSBbXSxcbiAgICAgICAgICAgIGN1ck1hdGNoRWxlbWVudHMgPSBbXSxcbiAgICAgICAgICAgIHByZXZUYXJnZXRFbGVtZW50ID0gdGhpcy5lbGVtZW50O1xuXG4gICAgICAgIHRoaXMuYWRkUG9pbnRlcihwb2ludGVyKTtcblxuICAgICAgICBpZiAodGhpcy50YXJnZXRcbiAgICAgICAgICAgICYmIChzY29wZS50ZXN0SWdub3JlKHRoaXMudGFyZ2V0LCB0aGlzLmVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgfHwgIXNjb3BlLnRlc3RBbGxvdyh0aGlzLnRhcmdldCwgdGhpcy5lbGVtZW50LCBldmVudFRhcmdldCkpKSB7XG4gICAgICAgICAgICAvLyBpZiB0aGUgZXZlbnRUYXJnZXQgc2hvdWxkIGJlIGlnbm9yZWQgb3Igc2hvdWxkbid0IGJlIGFsbG93ZWRcbiAgICAgICAgICAgIC8vIGNsZWFyIHRoZSBwcmV2aW91cyB0YXJnZXRcbiAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gbnVsbDtcbiAgICAgICAgICAgIHRoaXMuZWxlbWVudCA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hFbGVtZW50cyA9IFtdO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIGVsZW1lbnRJbnRlcmFjdGFibGUgPSBzY29wZS5pbnRlcmFjdGFibGVzLmdldChldmVudFRhcmdldCksXG4gICAgICAgICAgICBlbGVtZW50QWN0aW9uID0gKGVsZW1lbnRJbnRlcmFjdGFibGVcbiAgICAgICAgICAgICYmICFzY29wZS50ZXN0SWdub3JlKGVsZW1lbnRJbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICYmIHNjb3BlLnRlc3RBbGxvdyhlbGVtZW50SW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAmJiB2YWxpZGF0ZUFjdGlvbihcbiAgICAgICAgICAgICAgICBlbGVtZW50SW50ZXJhY3RhYmxlLmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgZXZlbnRUYXJnZXQpLFxuICAgICAgICAgICAgICAgIGVsZW1lbnRJbnRlcmFjdGFibGUpKTtcblxuICAgICAgICBpZiAoZWxlbWVudEFjdGlvbiAmJiAhc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdChlbGVtZW50SW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCwgZWxlbWVudEFjdGlvbikpIHtcbiAgICAgICAgICAgIGVsZW1lbnRBY3Rpb24gPSBudWxsO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gcHVzaEN1ck1hdGNoZXMgKGludGVyYWN0YWJsZSwgc2VsZWN0b3IpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGFibGVcbiAgICAgICAgICAgICAgICAmJiBzY29wZS5pbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS50ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgc2NvcGUubWF0Y2hlc1NlbGVjdG9yKGV2ZW50VGFyZ2V0LCBzZWxlY3RvcikpIHtcblxuICAgICAgICAgICAgICAgIGN1ck1hdGNoZXMucHVzaChpbnRlcmFjdGFibGUpO1xuICAgICAgICAgICAgICAgIGN1ck1hdGNoRWxlbWVudHMucHVzaChldmVudFRhcmdldCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoZWxlbWVudEFjdGlvbikge1xuICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBlbGVtZW50SW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hFbGVtZW50cyA9IFtdO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IocHVzaEN1ck1hdGNoZXMpO1xuXG4gICAgICAgICAgICBpZiAodGhpcy52YWxpZGF0ZVNlbGVjdG9yKHBvaW50ZXIsIGV2ZW50LCBjdXJNYXRjaGVzLCBjdXJNYXRjaEVsZW1lbnRzKSkge1xuICAgICAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IGN1ck1hdGNoZXM7XG4gICAgICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gY3VyTWF0Y2hFbGVtZW50cztcblxuICAgICAgICAgICAgICAgIHRoaXMucG9pbnRlckhvdmVyKHBvaW50ZXIsIGV2ZW50LCB0aGlzLm1hdGNoZXMsIHRoaXMubWF0Y2hFbGVtZW50cyk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZChldmVudFRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgc2NvcGUuUG9pbnRlckV2ZW50PyBzY29wZS5wRXZlbnRUeXBlcy5tb3ZlIDogJ21vdXNlbW92ZScsXG4gICAgICAgICAgICAgICAgICAgIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAodGhpcy50YXJnZXQpIHtcbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUubm9kZUNvbnRhaW5zKHByZXZUYXJnZXRFbGVtZW50LCBldmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wb2ludGVySG92ZXIocG9pbnRlciwgZXZlbnQsIHRoaXMubWF0Y2hlcywgdGhpcy5tYXRjaEVsZW1lbnRzKTtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLmVsZW1lbnQsXG4gICAgICAgICAgICAgICAgICAgICAgICBzY29wZS5Qb2ludGVyRXZlbnQ/IHNjb3BlLnBFdmVudFR5cGVzLm1vdmUgOiAnbW91c2Vtb3ZlJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmVsZW1lbnQgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSxcblxuICAgIC8vIENoZWNrIHdoYXQgYWN0aW9uIHdvdWxkIGJlIHBlcmZvcm1lZCBvbiBwb2ludGVyTW92ZSB0YXJnZXQgaWYgYSBtb3VzZVxuICAgIC8vIGJ1dHRvbiB3ZXJlIHByZXNzZWQgYW5kIGNoYW5nZSB0aGUgY3Vyc29yIGFjY29yZGluZ2x5XG4gICAgcG9pbnRlckhvdmVyOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cykge1xuICAgICAgICB2YXIgdGFyZ2V0ID0gdGhpcy50YXJnZXQ7XG5cbiAgICAgICAgaWYgKCF0aGlzLnByZXBhcmVkLm5hbWUgJiYgdGhpcy5tb3VzZSkge1xuXG4gICAgICAgICAgICB2YXIgYWN0aW9uO1xuXG4gICAgICAgICAgICAvLyB1cGRhdGUgcG9pbnRlciBjb29yZHMgZm9yIGRlZmF1bHRBY3Rpb25DaGVja2VyIHRvIHVzZVxuICAgICAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuY3VyQ29vcmRzLCBwb2ludGVyKTtcblxuICAgICAgICAgICAgaWYgKG1hdGNoZXMpIHtcbiAgICAgICAgICAgICAgICBhY3Rpb24gPSB0aGlzLnZhbGlkYXRlU2VsZWN0b3IocG9pbnRlciwgZXZlbnQsIG1hdGNoZXMsIG1hdGNoRWxlbWVudHMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAodGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgYWN0aW9uID0gdmFsaWRhdGVBY3Rpb24odGFyZ2V0LmdldEFjdGlvbih0aGlzLnBvaW50ZXJzWzBdLCBldmVudCwgdGhpcywgdGhpcy5lbGVtZW50KSwgdGhpcy50YXJnZXQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zLnN0eWxlQ3Vyc29yKSB7XG4gICAgICAgICAgICAgICAgaWYgKGFjdGlvbikge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gZ2V0QWN0aW9uQ3Vyc29yKGFjdGlvbik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHRoaXMucHJlcGFyZWQubmFtZSkge1xuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgcG9pbnRlck91dDogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCkge1xuICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5uYW1lKSB7IHJldHVybjsgfVxuXG4gICAgICAgIC8vIFJlbW92ZSB0ZW1wb3JhcnkgZXZlbnQgbGlzdGVuZXJzIGZvciBzZWxlY3RvciBJbnRlcmFjdGFibGVzXG4gICAgICAgIGlmICghc2NvcGUuaW50ZXJhY3RhYmxlcy5nZXQoZXZlbnRUYXJnZXQpKSB7XG4gICAgICAgICAgICBldmVudHMucmVtb3ZlKGV2ZW50VGFyZ2V0LFxuICAgICAgICAgICAgICAgIHNjb3BlLlBvaW50ZXJFdmVudD8gc2NvcGUucEV2ZW50VHlwZXMubW92ZSA6ICdtb3VzZW1vdmUnLFxuICAgICAgICAgICAgICAgIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMudGFyZ2V0ICYmIHRoaXMudGFyZ2V0Lm9wdGlvbnMuc3R5bGVDdXJzb3IgJiYgIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgdGhpcy50YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc2VsZWN0b3JEb3duOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgdGhhdCA9IHRoaXMsXG4gICAgICAgIC8vIGNvcHkgZXZlbnQgdG8gYmUgdXNlZCBpbiB0aW1lb3V0IGZvciBJRThcbiAgICAgICAgICAgIGV2ZW50Q29weSA9IGV2ZW50cy51c2VBdHRhY2hFdmVudD8gdXRpbHMuZXh0ZW5kKHt9LCBldmVudCkgOiBldmVudCxcbiAgICAgICAgICAgIGVsZW1lbnQgPSBldmVudFRhcmdldCxcbiAgICAgICAgICAgIHBvaW50ZXJJbmRleCA9IHRoaXMuYWRkUG9pbnRlcihwb2ludGVyKSxcbiAgICAgICAgICAgIGFjdGlvbjtcblxuICAgICAgICB0aGlzLmhvbGRUaW1lcnNbcG9pbnRlckluZGV4XSA9IHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgdGhhdC5wb2ludGVySG9sZChldmVudHMudXNlQXR0YWNoRXZlbnQ/IGV2ZW50Q29weSA6IHBvaW50ZXIsIGV2ZW50Q29weSwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KTtcbiAgICAgICAgfSwgc2NvcGUuZGVmYXVsdE9wdGlvbnMuX2hvbGREdXJhdGlvbik7XG5cbiAgICAgICAgdGhpcy5wb2ludGVySXNEb3duID0gdHJ1ZTtcblxuICAgICAgICAvLyBDaGVjayBpZiB0aGUgZG93biBldmVudCBoaXRzIHRoZSBjdXJyZW50IGluZXJ0aWEgdGFyZ2V0XG4gICAgICAgIGlmICh0aGlzLmluZXJ0aWFTdGF0dXMuYWN0aXZlICYmIHRoaXMudGFyZ2V0LnNlbGVjdG9yKSB7XG4gICAgICAgICAgICAvLyBjbGltYiB1cCB0aGUgRE9NIHRyZWUgZnJvbSB0aGUgZXZlbnQgdGFyZ2V0XG4gICAgICAgICAgICB3aGlsZSAodXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgICAgICAvLyBpZiB0aGlzIGVsZW1lbnQgaXMgdGhlIGN1cnJlbnQgaW5lcnRpYSB0YXJnZXQgZWxlbWVudFxuICAgICAgICAgICAgICAgIGlmIChlbGVtZW50ID09PSB0aGlzLmVsZW1lbnRcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGFuZCB0aGUgcHJvc3BlY3RpdmUgYWN0aW9uIGlzIHRoZSBzYW1lIGFzIHRoZSBvbmdvaW5nIG9uZVxuICAgICAgICAgICAgICAgICAgICAmJiB2YWxpZGF0ZUFjdGlvbih0aGlzLnRhcmdldC5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIHRoaXMuZWxlbWVudCksIHRoaXMudGFyZ2V0KS5uYW1lID09PSB0aGlzLnByZXBhcmVkLm5hbWUpIHtcblxuICAgICAgICAgICAgICAgICAgICAvLyBzdG9wIGluZXJ0aWEgc28gdGhhdCB0aGUgbmV4dCBtb3ZlIHdpbGwgYmUgYSBub3JtYWwgb25lXG4gICAgICAgICAgICAgICAgICAgIGFuaW1hdGlvbkZyYW1lLmNhbmNlbCh0aGlzLmluZXJ0aWFTdGF0dXMuaSk7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcblxuICAgICAgICAgICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnZG93bicpO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsZW1lbnQgPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gZG8gbm90aGluZyBpZiBpbnRlcmFjdGluZ1xuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnZG93bicpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gcHVzaE1hdGNoZXMgKGludGVyYWN0YWJsZSwgc2VsZWN0b3IsIGNvbnRleHQpIHtcbiAgICAgICAgICAgIHZhciBlbGVtZW50cyA9IHNjb3BlLmllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgID8gY29udGV4dC5xdWVyeVNlbGVjdG9yQWxsKHNlbGVjdG9yKVxuICAgICAgICAgICAgICAgIDogdW5kZWZpbmVkO1xuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaW5Db250ZXh0KGludGVyYWN0YWJsZSwgZWxlbWVudClcbiAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIHNjb3BlLnRlc3RBbGxvdyhpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIHNjb3BlLm1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBzZWxlY3RvciwgZWxlbWVudHMpKSB7XG5cbiAgICAgICAgICAgICAgICB0aGF0Lm1hdGNoZXMucHVzaChpbnRlcmFjdGFibGUpO1xuICAgICAgICAgICAgICAgIHRoYXQubWF0Y2hFbGVtZW50cy5wdXNoKGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gdXBkYXRlIHBvaW50ZXIgY29vcmRzIGZvciBkZWZhdWx0QWN0aW9uQ2hlY2tlciB0byB1c2VcbiAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuY3VyQ29vcmRzLCBwb2ludGVyKTtcbiAgICAgICAgdGhpcy5kb3duRXZlbnQgPSBldmVudDtcblxuICAgICAgICB3aGlsZSAodXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpICYmICFhY3Rpb24pIHtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG5cbiAgICAgICAgICAgIHNjb3BlLmludGVyYWN0YWJsZXMuZm9yRWFjaFNlbGVjdG9yKHB1c2hNYXRjaGVzKTtcblxuICAgICAgICAgICAgYWN0aW9uID0gdGhpcy52YWxpZGF0ZVNlbGVjdG9yKHBvaW50ZXIsIGV2ZW50LCB0aGlzLm1hdGNoZXMsIHRoaXMubWF0Y2hFbGVtZW50cyk7XG4gICAgICAgICAgICBlbGVtZW50ID0gc2NvcGUucGFyZW50RWxlbWVudChlbGVtZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChhY3Rpb24pIHtcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSAgPSBhY3Rpb24ubmFtZTtcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuZWRnZXMgPSBhY3Rpb24uZWRnZXM7XG5cbiAgICAgICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICdkb3duJyk7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLnBvaW50ZXJEb3duKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQsIGFjdGlvbik7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBkbyB0aGVzZSBub3cgc2luY2UgcG9pbnRlckRvd24gaXNuJ3QgYmVpbmcgY2FsbGVkIGZyb20gaGVyZVxuICAgICAgICAgICAgdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgICAgICAgdGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdID0gZXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB1dGlscy5leHRlbmQodGhpcy5kb3duUG9pbnRlciwgcG9pbnRlcik7XG5cbiAgICAgICAgICAgIHV0aWxzLmNvcHlDb29yZHModGhpcy5wcmV2Q29vcmRzLCB0aGlzLmN1ckNvb3Jkcyk7XG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJXYXNNb3ZlZCA9IGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2Rvd24nKTtcbiAgICB9LFxuXG4gICAgLy8gRGV0ZXJtaW5lIGFjdGlvbiB0byBiZSBwZXJmb3JtZWQgb24gbmV4dCBwb2ludGVyTW92ZSBhbmQgYWRkIGFwcHJvcHJpYXRlXG4gICAgLy8gc3R5bGUgYW5kIGV2ZW50IExpc3RlbmVyc1xuICAgIHBvaW50ZXJEb3duOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCwgZm9yY2VBY3Rpb24pIHtcbiAgICAgICAgaWYgKCFmb3JjZUFjdGlvbiAmJiAhdGhpcy5pbmVydGlhU3RhdHVzLmFjdGl2ZSAmJiB0aGlzLnBvaW50ZXJXYXNNb3ZlZCAmJiB0aGlzLnByZXBhcmVkLm5hbWUpIHtcbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMucG9pbnRlcklzRG93biA9IHRydWU7XG4gICAgICAgIHRoaXMuZG93bkV2ZW50ID0gZXZlbnQ7XG5cbiAgICAgICAgdmFyIHBvaW50ZXJJbmRleCA9IHRoaXMuYWRkUG9pbnRlcihwb2ludGVyKSxcbiAgICAgICAgICAgIGFjdGlvbjtcblxuICAgICAgICAvLyBJZiBpdCBpcyB0aGUgc2Vjb25kIHRvdWNoIG9mIGEgbXVsdGktdG91Y2ggZ2VzdHVyZSwga2VlcCB0aGUgdGFyZ2V0XG4gICAgICAgIC8vIHRoZSBzYW1lIGlmIGEgdGFyZ2V0IHdhcyBzZXQgYnkgdGhlIGZpcnN0IHRvdWNoXG4gICAgICAgIC8vIE90aGVyd2lzZSwgc2V0IHRoZSB0YXJnZXQgaWYgdGhlcmUgaXMgbm8gYWN0aW9uIHByZXBhcmVkXG4gICAgICAgIGlmICgodGhpcy5wb2ludGVySWRzLmxlbmd0aCA8IDIgJiYgIXRoaXMudGFyZ2V0KSB8fCAhdGhpcy5wcmVwYXJlZC5uYW1lKSB7XG5cbiAgICAgICAgICAgIHZhciBpbnRlcmFjdGFibGUgPSBzY29wZS5pbnRlcmFjdGFibGVzLmdldChjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGFibGVcbiAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGN1ckV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS50ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBjdXJFdmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgKGFjdGlvbiA9IHZhbGlkYXRlQWN0aW9uKGZvcmNlQWN0aW9uIHx8IGludGVyYWN0YWJsZS5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIGN1ckV2ZW50VGFyZ2V0KSwgaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCkpXG4gICAgICAgICAgICAgICAgJiYgc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdChpbnRlcmFjdGFibGUsIGN1ckV2ZW50VGFyZ2V0LCBhY3Rpb24pKSB7XG4gICAgICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBpbnRlcmFjdGFibGU7XG4gICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gY3VyRXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdGFyZ2V0ID0gdGhpcy50YXJnZXQsXG4gICAgICAgICAgICBvcHRpb25zID0gdGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zO1xuXG4gICAgICAgIGlmICh0YXJnZXQgJiYgKGZvcmNlQWN0aW9uIHx8ICF0aGlzLnByZXBhcmVkLm5hbWUpKSB7XG4gICAgICAgICAgICBhY3Rpb24gPSBhY3Rpb24gfHwgdmFsaWRhdGVBY3Rpb24oZm9yY2VBY3Rpb24gfHwgdGFyZ2V0LmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgY3VyRXZlbnRUYXJnZXQpLCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLnN0YXJ0Q29vcmRzKTtcblxuICAgICAgICAgICAgaWYgKCFhY3Rpb24pIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgICAgIGlmIChvcHRpb25zLnN0eWxlQ3Vyc29yKSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0Ll9kb2MuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IGdldEFjdGlvbkN1cnNvcihhY3Rpb24pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0aGlzLnJlc2l6ZUF4ZXMgPSBhY3Rpb24ubmFtZSA9PT0gJ3Jlc2l6ZSc/IGFjdGlvbi5heGlzIDogbnVsbDtcblxuICAgICAgICAgICAgaWYgKGFjdGlvbiA9PT0gJ2dlc3R1cmUnICYmIHRoaXMucG9pbnRlcklkcy5sZW5ndGggPCAyKSB7XG4gICAgICAgICAgICAgICAgYWN0aW9uID0gbnVsbDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5wcmVwYXJlZC5uYW1lICA9IGFjdGlvbi5uYW1lO1xuICAgICAgICAgICAgdGhpcy5wcmVwYXJlZC5heGlzICA9IGFjdGlvbi5heGlzO1xuICAgICAgICAgICAgdGhpcy5wcmVwYXJlZC5lZGdlcyA9IGFjdGlvbi5lZGdlcztcblxuICAgICAgICAgICAgdGhpcy5zbmFwU3RhdHVzLnNuYXBwZWRYID0gdGhpcy5zbmFwU3RhdHVzLnNuYXBwZWRZID1cbiAgICAgICAgICAgICAgICB0aGlzLnJlc3RyaWN0U3RhdHVzLnJlc3RyaWN0ZWRYID0gdGhpcy5yZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkWSA9IE5hTjtcblxuICAgICAgICAgICAgdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgICAgICAgdGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdID0gZXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB1dGlscy5leHRlbmQodGhpcy5kb3duUG9pbnRlciwgcG9pbnRlcik7XG5cbiAgICAgICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLnByZXZDb29yZHMpO1xuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gaWYgaW5lcnRpYSBpcyBhY3RpdmUgdHJ5IHRvIHJlc3VtZSBhY3Rpb25cbiAgICAgICAgZWxzZSBpZiAodGhpcy5pbmVydGlhU3RhdHVzLmFjdGl2ZVxuICAgICAgICAgICAgJiYgY3VyRXZlbnRUYXJnZXQgPT09IHRoaXMuZWxlbWVudFxuICAgICAgICAgICAgJiYgdmFsaWRhdGVBY3Rpb24odGFyZ2V0LmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgdGhpcy5lbGVtZW50KSwgdGFyZ2V0KS5uYW1lID09PSB0aGlzLnByZXBhcmVkLm5hbWUpIHtcblxuICAgICAgICAgICAgYW5pbWF0aW9uRnJhbWUuY2FuY2VsKHRoaXMuaW5lcnRpYVN0YXR1cy5pKTtcbiAgICAgICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc2V0TW9kaWZpY2F0aW9uczogZnVuY3Rpb24gKGNvb3JkcywgcHJlRW5kKSB7XG4gICAgICAgIHZhciB0YXJnZXQgICAgICAgICA9IHRoaXMudGFyZ2V0LFxuICAgICAgICAgICAgc2hvdWxkTW92ZSAgICAgPSB0cnVlLFxuICAgICAgICAgICAgc2hvdWxkU25hcCAgICAgPSBzY29wZS5jaGVja1NuYXAodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICAgICAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uc25hcC5lbmRPbmx5ICAgICB8fCBwcmVFbmQpLFxuICAgICAgICAgICAgc2hvdWxkUmVzdHJpY3QgPSBzY29wZS5jaGVja1Jlc3RyaWN0KHRhcmdldCwgdGhpcy5wcmVwYXJlZC5uYW1lKSAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QuZW5kT25seSB8fCBwcmVFbmQpO1xuXG4gICAgICAgIGlmIChzaG91bGRTbmFwICAgICkgeyB0aGlzLnNldFNuYXBwaW5nICAgKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMuc25hcFN0YXR1cyAgICAubG9ja2VkICAgICA9IGZhbHNlOyB9XG4gICAgICAgIGlmIChzaG91bGRSZXN0cmljdCkgeyB0aGlzLnNldFJlc3RyaWN0aW9uKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlOyB9XG5cbiAgICAgICAgaWYgKHNob3VsZFNuYXAgJiYgdGhpcy5zbmFwU3RhdHVzLmxvY2tlZCAmJiAhdGhpcy5zbmFwU3RhdHVzLmNoYW5nZWQpIHtcbiAgICAgICAgICAgIHNob3VsZE1vdmUgPSBzaG91bGRSZXN0cmljdCAmJiB0aGlzLnJlc3RyaWN0U3RhdHVzLnJlc3RyaWN0ZWQgJiYgdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHNob3VsZFJlc3RyaWN0ICYmIHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCAmJiAhdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkKSB7XG4gICAgICAgICAgICBzaG91bGRNb3ZlID0gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gc2hvdWxkTW92ZTtcbiAgICB9LFxuXG4gICAgc2V0U3RhcnRPZmZzZXRzOiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIHJlY3QgPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgICAgIG9yaWdpbiA9IHNjb3BlLmdldE9yaWdpblhZKGludGVyYWN0YWJsZSwgZWxlbWVudCksXG4gICAgICAgICAgICBzbmFwID0gaW50ZXJhY3RhYmxlLm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgcmVzdHJpY3QgPSBpbnRlcmFjdGFibGUub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnJlc3RyaWN0LFxuICAgICAgICAgICAgd2lkdGgsIGhlaWdodDtcblxuICAgICAgICBpZiAocmVjdCkge1xuICAgICAgICAgICAgdGhpcy5zdGFydE9mZnNldC5sZWZ0ID0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnggLSByZWN0LmxlZnQ7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueSAtIHJlY3QudG9wO1xuXG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICA9IHJlY3QucmlnaHQgIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLng7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LmJvdHRvbSA9IHJlY3QuYm90dG9tIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnk7XG5cbiAgICAgICAgICAgIGlmICgnd2lkdGgnIGluIHJlY3QpIHsgd2lkdGggPSByZWN0LndpZHRoOyB9XG4gICAgICAgICAgICBlbHNlIHsgd2lkdGggPSByZWN0LnJpZ2h0IC0gcmVjdC5sZWZ0OyB9XG4gICAgICAgICAgICBpZiAoJ2hlaWdodCcgaW4gcmVjdCkgeyBoZWlnaHQgPSByZWN0LmhlaWdodDsgfVxuICAgICAgICAgICAgZWxzZSB7IGhlaWdodCA9IHJlY3QuYm90dG9tIC0gcmVjdC50b3A7IH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc3RhcnRPZmZzZXQubGVmdCA9IHRoaXMuc3RhcnRPZmZzZXQudG9wID0gdGhpcy5zdGFydE9mZnNldC5yaWdodCA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tID0gMDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc25hcE9mZnNldHMuc3BsaWNlKDApO1xuXG4gICAgICAgIHZhciBzbmFwT2Zmc2V0ID0gc25hcCAmJiBzbmFwLm9mZnNldCA9PT0gJ3N0YXJ0Q29vcmRzJ1xuICAgICAgICAgICAgPyB7XG4gICAgICAgICAgICB4OiB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueCAtIG9yaWdpbi54LFxuICAgICAgICAgICAgeTogdGhpcy5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueVxuICAgICAgICB9XG4gICAgICAgICAgICA6IHNuYXAgJiYgc25hcC5vZmZzZXQgfHwgeyB4OiAwLCB5OiAwIH07XG5cbiAgICAgICAgaWYgKHJlY3QgJiYgc25hcCAmJiBzbmFwLnJlbGF0aXZlUG9pbnRzICYmIHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLnNuYXBPZmZzZXRzLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICB4OiB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogc25hcC5yZWxhdGl2ZVBvaW50c1tpXS54KSArIHNuYXBPZmZzZXQueCxcbiAgICAgICAgICAgICAgICAgICAgeTogdGhpcy5zdGFydE9mZnNldC50b3AgIC0gKGhlaWdodCAqIHNuYXAucmVsYXRpdmVQb2ludHNbaV0ueSkgKyBzbmFwT2Zmc2V0LnlcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc25hcE9mZnNldHMucHVzaChzbmFwT2Zmc2V0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChyZWN0ICYmIHJlc3RyaWN0LmVsZW1lbnRSZWN0KSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogcmVzdHJpY3QuZWxlbWVudFJlY3QubGVmdCk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgLSAoaGVpZ2h0ICogcmVzdHJpY3QuZWxlbWVudFJlY3QudG9wKTtcblxuICAgICAgICAgICAgdGhpcy5yZXN0cmljdE9mZnNldC5yaWdodCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICAtICh3aWR0aCAgKiAoMSAtIHJlc3RyaWN0LmVsZW1lbnRSZWN0LnJpZ2h0KSk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tIC0gKGhlaWdodCAqICgxIC0gcmVzdHJpY3QuZWxlbWVudFJlY3QuYm90dG9tKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCA9IHRoaXMucmVzdHJpY3RPZmZzZXQucmlnaHQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IDA7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0aW9uLnN0YXJ0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFN0YXJ0IGFuIGFjdGlvbiB3aXRoIHRoZSBnaXZlbiBJbnRlcmFjdGFibGUgYW5kIEVsZW1lbnQgYXMgdGFydGdldHMuIFRoZVxuICAgICAqIGFjdGlvbiBtdXN0IGJlIGVuYWJsZWQgZm9yIHRoZSB0YXJnZXQgSW50ZXJhY3RhYmxlIGFuZCBhbiBhcHByb3ByaWF0ZSBudW1iZXJcbiAgICAgKiBvZiBwb2ludGVycyBtdXN0IGJlIGhlbGQgZG93biDigJMgMSBmb3IgZHJhZy9yZXNpemUsIDIgZm9yIGdlc3R1cmUuXG4gICAgICpcbiAgICAgKiBVc2UgaXQgd2l0aCBgaW50ZXJhY3RhYmxlLjxhY3Rpb24+YWJsZSh7IG1hbnVhbFN0YXJ0OiBmYWxzZSB9KWAgdG8gYWx3YXlzXG4gICAgICogW3N0YXJ0IGFjdGlvbnMgbWFudWFsbHldKGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL2lzc3Vlcy8xMTQpXG4gICAgICpcbiAgICAgLSBhY3Rpb24gICAgICAgKG9iamVjdCkgIFRoZSBhY3Rpb24gdG8gYmUgcGVyZm9ybWVkIC0gZHJhZywgcmVzaXplLCBldGMuXG4gICAgIC0gaW50ZXJhY3RhYmxlIChJbnRlcmFjdGFibGUpIFRoZSBJbnRlcmFjdGFibGUgdG8gdGFyZ2V0XG4gICAgIC0gZWxlbWVudCAgICAgIChFbGVtZW50KSBUaGUgRE9NIEVsZW1lbnQgdG8gdGFyZ2V0XG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICAgKipcbiAgICAgfCBpbnRlcmFjdCh0YXJnZXQpXG4gICAgIHwgICAuZHJhZ2dhYmxlKHtcbiAgICAgfCAgICAgLy8gZGlzYWJsZSB0aGUgZGVmYXVsdCBkcmFnIHN0YXJ0IGJ5IGRvd24tPm1vdmVcbiAgICAgfCAgICAgbWFudWFsU3RhcnQ6IHRydWVcbiAgICAgfCAgIH0pXG4gICAgIHwgICAvLyBzdGFydCBkcmFnZ2luZyBhZnRlciB0aGUgdXNlciBob2xkcyB0aGUgcG9pbnRlciBkb3duXG4gICAgIHwgICAub24oJ2hvbGQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgfCAgICAgdmFyIGludGVyYWN0aW9uID0gZXZlbnQuaW50ZXJhY3Rpb247XG4gICAgIHxcbiAgICAgfCAgICAgaWYgKCFpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpKSB7XG4gICAgIHwgICAgICAgaW50ZXJhY3Rpb24uc3RhcnQoeyBuYW1lOiAnZHJhZycgfSxcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5pbnRlcmFjdGFibGUsXG4gICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQuY3VycmVudFRhcmdldCk7XG4gICAgIHwgICAgIH1cbiAgICAgfCB9KTtcbiAgICAgXFwqL1xuICAgIHN0YXJ0OiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgfHwgIXRoaXMucG9pbnRlcklzRG93blxuICAgICAgICAgICAgfHwgdGhpcy5wb2ludGVySWRzLmxlbmd0aCA8IChhY3Rpb24ubmFtZSA9PT0gJ2dlc3R1cmUnPyAyIDogMSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGlmIHRoaXMgaW50ZXJhY3Rpb24gaGFkIGJlZW4gcmVtb3ZlZCBhZnRlciBzdG9wcGluZ1xuICAgICAgICAvLyBhZGQgaXQgYmFja1xuICAgICAgICBpZiAoc2NvcGUuaW5kZXhPZihzY29wZS5pbnRlcmFjdGlvbnMsIHRoaXMpID09PSAtMSkge1xuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3Rpb25zLnB1c2godGhpcyk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgID0gYWN0aW9uLm5hbWU7XG4gICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgdGhpcy5wcmVwYXJlZC5lZGdlcyA9IGFjdGlvbi5lZGdlcztcbiAgICAgICAgdGhpcy50YXJnZXQgICAgICAgICA9IGludGVyYWN0YWJsZTtcbiAgICAgICAgdGhpcy5lbGVtZW50ICAgICAgICA9IGVsZW1lbnQ7XG5cbiAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuc3RhcnRDb29yZHMpO1xuICAgICAgICB0aGlzLnNldFN0YXJ0T2Zmc2V0cyhhY3Rpb24ubmFtZSwgaW50ZXJhY3RhYmxlLCBlbGVtZW50KTtcbiAgICAgICAgdGhpcy5zZXRNb2RpZmljYXRpb25zKHRoaXMuc3RhcnRDb29yZHMucGFnZSk7XG5cbiAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdTdGFydCddKHRoaXMuZG93bkV2ZW50KTtcbiAgICB9LFxuXG4gICAgcG9pbnRlck1vdmU6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBwcmVFbmQpIHtcbiAgICAgICAgdGhpcy5yZWNvcmRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLmN1ckNvb3JkcywgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50KVxuICAgICAgICAgICAgPyB0aGlzLmluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudFxuICAgICAgICAgICAgOiB1bmRlZmluZWQpO1xuXG4gICAgICAgIHZhciBkdXBsaWNhdGVNb3ZlID0gKHRoaXMuY3VyQ29vcmRzLnBhZ2UueCA9PT0gdGhpcy5wcmV2Q29vcmRzLnBhZ2UueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5wYWdlLnkgPT09IHRoaXMucHJldkNvb3Jkcy5wYWdlLnlcbiAgICAgICAgJiYgdGhpcy5jdXJDb29yZHMuY2xpZW50LnggPT09IHRoaXMucHJldkNvb3Jkcy5jbGllbnQueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueSA9PT0gdGhpcy5wcmV2Q29vcmRzLmNsaWVudC55KTtcblxuICAgICAgICB2YXIgZHgsIGR5LFxuICAgICAgICAgICAgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IHNjb3BlLmluZGV4T2YodGhpcy5wb2ludGVySWRzLCB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIC8vIHJlZ2lzdGVyIG1vdmVtZW50IGdyZWF0ZXIgdGhhbiBwb2ludGVyTW92ZVRvbGVyYW5jZVxuICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duICYmICF0aGlzLnBvaW50ZXJXYXNNb3ZlZCkge1xuICAgICAgICAgICAgZHggPSB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueCAtIHRoaXMuc3RhcnRDb29yZHMuY2xpZW50Lng7XG4gICAgICAgICAgICBkeSA9IHRoaXMuY3VyQ29vcmRzLmNsaWVudC55IC0gdGhpcy5zdGFydENvb3Jkcy5jbGllbnQueTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSB1dGlscy5oeXBvdChkeCwgZHkpID4gc2NvcGUucG9pbnRlck1vdmVUb2xlcmFuY2U7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWR1cGxpY2F0ZU1vdmUgJiYgKCF0aGlzLnBvaW50ZXJJc0Rvd24gfHwgdGhpcy5wb2ludGVyV2FzTW92ZWQpKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duKSB7XG4gICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ21vdmUnKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghdGhpcy5wb2ludGVySXNEb3duKSB7IHJldHVybjsgfVxuXG4gICAgICAgIGlmIChkdXBsaWNhdGVNb3ZlICYmIHRoaXMucG9pbnRlcldhc01vdmVkICYmICFwcmVFbmQpIHtcbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICAvLyBzZXQgcG9pbnRlciBjb29yZGluYXRlLCB0aW1lIGNoYW5nZXMgYW5kIHNwZWVkc1xuICAgICAgICB1dGlscy5zZXRFdmVudERlbHRhcyh0aGlzLnBvaW50ZXJEZWx0YSwgdGhpcy5wcmV2Q29vcmRzLCB0aGlzLmN1ckNvb3Jkcyk7XG5cbiAgICAgICAgaWYgKCF0aGlzLnByZXBhcmVkLm5hbWUpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgaWYgKHRoaXMucG9pbnRlcldhc01vdmVkXG4gICAgICAgICAgICAgICAgLy8gaWdub3JlIG1vdmVtZW50IHdoaWxlIGluZXJ0aWEgaXMgYWN0aXZlXG4gICAgICAgICAgICAmJiAoIXRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgfHwgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50ICYmIC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkpKSB7XG5cbiAgICAgICAgICAgIC8vIGlmIGp1c3Qgc3RhcnRpbmcgYW4gYWN0aW9uLCBjYWxjdWxhdGUgdGhlIHBvaW50ZXIgc3BlZWQgbm93XG4gICAgICAgICAgICBpZiAoIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHV0aWxzLnNldEV2ZW50RGVsdGFzKHRoaXMucG9pbnRlckRlbHRhLCB0aGlzLnByZXZDb29yZHMsIHRoaXMuY3VyQ29vcmRzKTtcblxuICAgICAgICAgICAgICAgIC8vIGNoZWNrIGlmIGEgZHJhZyBpcyBpbiB0aGUgY29ycmVjdCBheGlzXG4gICAgICAgICAgICAgICAgaWYgKHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBhYnNYID0gTWF0aC5hYnMoZHgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgYWJzWSA9IE1hdGguYWJzKGR5KSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldEF4aXMgPSB0aGlzLnRhcmdldC5vcHRpb25zLmRyYWcuYXhpcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMgPSAoYWJzWCA+IGFic1kgPyAneCcgOiBhYnNYIDwgYWJzWSA/ICd5JyA6ICd4eScpO1xuXG4gICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBtb3ZlbWVudCBpc24ndCBpbiB0aGUgYXhpcyBvZiB0aGUgaW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgICAgIGlmIChheGlzICE9PSAneHknICYmIHRhcmdldEF4aXMgIT09ICd4eScgJiYgdGFyZ2V0QXhpcyAhPT0gYXhpcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gY2FuY2VsIHRoZSBwcmVwYXJlZCBhY3Rpb25cbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSA9IG51bGw7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHRoZW4gdHJ5IHRvIGdldCBhIGRyYWcgZnJvbSBhbm90aGVyIGluZXJhY3RhYmxlXG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIGVsZW1lbnQgaW50ZXJhY3RhYmxlc1xuICAgICAgICAgICAgICAgICAgICAgICAgd2hpbGUgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50SW50ZXJhY3RhYmxlID0gc2NvcGUuaW50ZXJhY3RhYmxlcy5nZXQoZWxlbWVudCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudEludGVyYWN0YWJsZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBlbGVtZW50SW50ZXJhY3RhYmxlICE9PSB0aGlzLnRhcmdldFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhZWxlbWVudEludGVyYWN0YWJsZS5vcHRpb25zLmRyYWcubWFudWFsU3RhcnRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgZWxlbWVudEludGVyYWN0YWJsZS5nZXRBY3Rpb24odGhpcy5kb3duUG9pbnRlciwgdGhpcy5kb3duRXZlbnQsIHRoaXMsIGVsZW1lbnQpLm5hbWUgPT09ICdkcmFnJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBzY29wZS5jaGVja0F4aXMoYXhpcywgZWxlbWVudEludGVyYWN0YWJsZSkpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gZWxlbWVudEludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZXJlJ3Mgbm8gZHJhZyBmcm9tIGVsZW1lbnQgaW50ZXJhY3RhYmxlcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIHRoZSBzZWxlY3RvciBpbnRlcmFjdGFibGVzXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoIXRoaXMucHJlcGFyZWQubmFtZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciB0aGlzSW50ZXJhY3Rpb24gPSB0aGlzO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGdldERyYWdnYWJsZSA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50cyA9IHNjb3BlLmllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPyBjb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZDtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlID09PSB0aGlzSW50ZXJhY3Rpb24udGFyZ2V0KSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzY29wZS5pbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYmICFpbnRlcmFjdGFibGUub3B0aW9ucy5kcmFnLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgc2NvcGUudGVzdEFsbG93KGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVsZW1lbnRzKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgaW50ZXJhY3RhYmxlLmdldEFjdGlvbih0aGlzSW50ZXJhY3Rpb24uZG93blBvaW50ZXIsIHRoaXNJbnRlcmFjdGlvbi5kb3duRXZlbnQsIHRoaXNJbnRlcmFjdGlvbiwgZWxlbWVudCkubmFtZSA9PT0gJ2RyYWcnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBzY29wZS5jaGVja0F4aXMoYXhpcywgaW50ZXJhY3RhYmxlKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdChpbnRlcmFjdGFibGUsIGVsZW1lbnQsICdkcmFnJykpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGlsZSAodXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBzZWxlY3RvckludGVyYWN0YWJsZSA9IHNjb3BlLmludGVyYWN0YWJsZXMuZm9yRWFjaFNlbGVjdG9yKGdldERyYWdnYWJsZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHNlbGVjdG9ySW50ZXJhY3RhYmxlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnRhcmdldCA9IHNlbGVjdG9ySW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc3RhcnRpbmcgPSAhIXRoaXMucHJlcGFyZWQubmFtZSAmJiAhdGhpcy5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgICAgICBpZiAoc3RhcnRpbmdcbiAgICAgICAgICAgICAgICAmJiAodGhpcy50YXJnZXQub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgfHwgIXNjb3BlLndpdGhpbkludGVyYWN0aW9uTGltaXQodGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCwgdGhpcy5wcmVwYXJlZCkpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5zdG9wKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5uYW1lICYmIHRoaXMudGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgaWYgKHN0YXJ0aW5nKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc3RhcnQodGhpcy5wcmVwYXJlZCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgdmFyIHNob3VsZE1vdmUgPSB0aGlzLnNldE1vZGlmaWNhdGlvbnModGhpcy5jdXJDb29yZHMucGFnZSwgcHJlRW5kKTtcblxuICAgICAgICAgICAgICAgIC8vIG1vdmUgaWYgc25hcHBpbmcgb3IgcmVzdHJpY3Rpb24gZG9lc24ndCBwcmV2ZW50IGl0XG4gICAgICAgICAgICAgICAgaWYgKHNob3VsZE1vdmUgfHwgc3RhcnRpbmcpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdNb3ZlJ10oZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB1dGlscy5jb3B5Q29vcmRzKHRoaXMucHJldkNvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgIGlmICh0aGlzLmRyYWdnaW5nIHx8IHRoaXMucmVzaXppbmcpIHtcbiAgICAgICAgICAgIHRoaXMuYXV0b1Njcm9sbE1vdmUocG9pbnRlcik7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgZHJhZ1N0YXJ0OiBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgdmFyIGRyYWdFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZHJhZycsICdzdGFydCcsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcmFnZ2luZyA9IHRydWU7XG4gICAgICAgIHRoaXMudGFyZ2V0LmZpcmUoZHJhZ0V2ZW50KTtcblxuICAgICAgICAvLyByZXNldCBhY3RpdmUgZHJvcHpvbmVzXG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHMgID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgID0gW107XG5cbiAgICAgICAgaWYgKCF0aGlzLmR5bmFtaWNEcm9wKSB7XG4gICAgICAgICAgICB0aGlzLnNldEFjdGl2ZURyb3BzKHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5hY3RpdmF0ZSkge1xuICAgICAgICAgICAgdGhpcy5maXJlQWN0aXZlRHJvcHMoZHJvcEV2ZW50cy5hY3RpdmF0ZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICBkcmFnTW92ZTogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIGRyYWdFdmVudCAgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ2RyYWcnLCAnbW92ZScsIHRoaXMuZWxlbWVudCksXG4gICAgICAgICAgICBkcmFnZ2FibGVFbGVtZW50ID0gdGhpcy5lbGVtZW50LFxuICAgICAgICAgICAgZHJvcCA9IHRoaXMuZ2V0RHJvcChldmVudCwgZHJhZ2dhYmxlRWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gZHJvcC5kcm9wem9uZTtcbiAgICAgICAgdGhpcy5kcm9wRWxlbWVudCA9IGRyb3AuZWxlbWVudDtcblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICB0YXJnZXQuZmlyZShkcmFnRXZlbnQpO1xuXG4gICAgICAgIGlmIChkcm9wRXZlbnRzLmxlYXZlKSB7IHRoaXMucHJldkRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmxlYXZlKTsgfVxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5lbnRlcikgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5lbnRlcik7IH1cbiAgICAgICAgaWYgKGRyb3BFdmVudHMubW92ZSApIHsgICAgIHRoaXMuZHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMubW92ZSApOyB9XG5cbiAgICAgICAgdGhpcy5wcmV2RHJvcFRhcmdldCAgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIHRoaXMucHJldkRyb3BFbGVtZW50ID0gdGhpcy5kcm9wRWxlbWVudDtcblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICByZXNpemVTdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciByZXNpemVFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAncmVzaXplJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5lZGdlcykge1xuICAgICAgICAgICAgdmFyIHN0YXJ0UmVjdCA9IHRoaXMudGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHRoaXMudGFyZ2V0Lm9wdGlvbnMucmVzaXplLnNxdWFyZSkge1xuICAgICAgICAgICAgICAgIHZhciBzcXVhcmVFZGdlcyA9IHV0aWxzLmV4dGVuZCh7fSwgdGhpcy5wcmVwYXJlZC5lZGdlcyk7XG5cbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy50b3AgICAgPSBzcXVhcmVFZGdlcy50b3AgICAgfHwgKHNxdWFyZUVkZ2VzLmxlZnQgICAmJiAhc3F1YXJlRWRnZXMuYm90dG9tKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5sZWZ0ICAgPSBzcXVhcmVFZGdlcy5sZWZ0ICAgfHwgKHNxdWFyZUVkZ2VzLnRvcCAgICAmJiAhc3F1YXJlRWRnZXMucmlnaHQgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5ib3R0b20gPSBzcXVhcmVFZGdlcy5ib3R0b20gfHwgKHNxdWFyZUVkZ2VzLnJpZ2h0ICAmJiAhc3F1YXJlRWRnZXMudG9wICAgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5yaWdodCAgPSBzcXVhcmVFZGdlcy5yaWdodCAgfHwgKHNxdWFyZUVkZ2VzLmJvdHRvbSAmJiAhc3F1YXJlRWRnZXMubGVmdCAgKTtcblxuICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuX3NxdWFyZUVkZ2VzID0gc3F1YXJlRWRnZXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcyA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucmVzaXplUmVjdHMgPSB7XG4gICAgICAgICAgICAgICAgc3RhcnQgICAgIDogc3RhcnRSZWN0LFxuICAgICAgICAgICAgICAgIGN1cnJlbnQgICA6IHV0aWxzLmV4dGVuZCh7fSwgc3RhcnRSZWN0KSxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkOiB1dGlscy5leHRlbmQoe30sIHN0YXJ0UmVjdCksXG4gICAgICAgICAgICAgICAgcHJldmlvdXMgIDogdXRpbHMuZXh0ZW5kKHt9LCBzdGFydFJlY3QpLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICA6IHtcbiAgICAgICAgICAgICAgICAgICAgbGVmdDogMCwgcmlnaHQgOiAwLCB3aWR0aCA6IDAsXG4gICAgICAgICAgICAgICAgICAgIHRvcCA6IDAsIGJvdHRvbTogMCwgaGVpZ2h0OiAwXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmVzaXplRXZlbnQucmVjdCA9IHRoaXMucmVzaXplUmVjdHMucmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IHRoaXMucmVzaXplUmVjdHMuZGVsdGE7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKHJlc2l6ZUV2ZW50KTtcblxuICAgICAgICB0aGlzLnJlc2l6aW5nID0gdHJ1ZTtcblxuICAgICAgICByZXR1cm4gcmVzaXplRXZlbnQ7XG4gICAgfSxcblxuICAgIHJlc2l6ZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgcmVzaXplRXZlbnQgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ3Jlc2l6ZScsICdtb3ZlJywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICB2YXIgZWRnZXMgPSB0aGlzLnByZXBhcmVkLmVkZ2VzLFxuICAgICAgICAgICAgaW52ZXJ0ID0gdGhpcy50YXJnZXQub3B0aW9ucy5yZXNpemUuaW52ZXJ0LFxuICAgICAgICAgICAgaW52ZXJ0aWJsZSA9IGludmVydCA9PT0gJ3JlcG9zaXRpb24nIHx8IGludmVydCA9PT0gJ25lZ2F0ZSc7XG5cbiAgICAgICAgaWYgKGVkZ2VzKSB7XG4gICAgICAgICAgICB2YXIgZHggPSByZXNpemVFdmVudC5keCxcbiAgICAgICAgICAgICAgICBkeSA9IHJlc2l6ZUV2ZW50LmR5LFxuXG4gICAgICAgICAgICAgICAgc3RhcnQgICAgICA9IHRoaXMucmVzaXplUmVjdHMuc3RhcnQsXG4gICAgICAgICAgICAgICAgY3VycmVudCAgICA9IHRoaXMucmVzaXplUmVjdHMuY3VycmVudCxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkID0gdGhpcy5yZXNpemVSZWN0cy5yZXN0cmljdGVkLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICAgPSB0aGlzLnJlc2l6ZVJlY3RzLmRlbHRhLFxuICAgICAgICAgICAgICAgIHByZXZpb3VzICAgPSB1dGlscy5leHRlbmQodGhpcy5yZXNpemVSZWN0cy5wcmV2aW91cywgcmVzdHJpY3RlZCk7XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnRhcmdldC5vcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgICAgICB2YXIgb3JpZ2luYWxFZGdlcyA9IGVkZ2VzO1xuXG4gICAgICAgICAgICAgICAgZWRnZXMgPSB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcztcblxuICAgICAgICAgICAgICAgIGlmICgob3JpZ2luYWxFZGdlcy5sZWZ0ICYmIG9yaWdpbmFsRWRnZXMuYm90dG9tKVxuICAgICAgICAgICAgICAgICAgICB8fCAob3JpZ2luYWxFZGdlcy5yaWdodCAmJiBvcmlnaW5hbEVkZ2VzLnRvcCkpIHtcbiAgICAgICAgICAgICAgICAgICAgZHkgPSAtZHg7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9yaWdpbmFsRWRnZXMubGVmdCB8fCBvcmlnaW5hbEVkZ2VzLnJpZ2h0KSB7IGR5ID0gZHg7IH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChvcmlnaW5hbEVkZ2VzLnRvcCB8fCBvcmlnaW5hbEVkZ2VzLmJvdHRvbSkgeyBkeCA9IGR5OyB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgJ2N1cnJlbnQnIHJlY3Qgd2l0aG91dCBtb2RpZmljYXRpb25zXG4gICAgICAgICAgICBpZiAoZWRnZXMudG9wICAgKSB7IGN1cnJlbnQudG9wICAgICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMuYm90dG9tKSB7IGN1cnJlbnQuYm90dG9tICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMubGVmdCAgKSB7IGN1cnJlbnQubGVmdCAgICs9IGR4OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMucmlnaHQgKSB7IGN1cnJlbnQucmlnaHQgICs9IGR4OyB9XG5cbiAgICAgICAgICAgIGlmIChpbnZlcnRpYmxlKSB7XG4gICAgICAgICAgICAgICAgLy8gaWYgaW52ZXJ0aWJsZSwgY29weSB0aGUgY3VycmVudCByZWN0XG4gICAgICAgICAgICAgICAgdXRpbHMuZXh0ZW5kKHJlc3RyaWN0ZWQsIGN1cnJlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGludmVydCA9PT0gJ3JlcG9zaXRpb24nKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHN3YXAgZWRnZSB2YWx1ZXMgaWYgbmVjZXNzYXJ5IHRvIGtlZXAgd2lkdGgvaGVpZ2h0IHBvc2l0aXZlXG4gICAgICAgICAgICAgICAgICAgIHZhciBzd2FwO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN0cmljdGVkLnRvcCA+IHJlc3RyaWN0ZWQuYm90dG9tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzd2FwID0gcmVzdHJpY3RlZC50b3A7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQudG9wID0gcmVzdHJpY3RlZC5ib3R0b207XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IHN3YXA7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3RyaWN0ZWQubGVmdCA+IHJlc3RyaWN0ZWQucmlnaHQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHN3YXAgPSByZXN0cmljdGVkLmxlZnQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCA9IHJlc3RyaWN0ZWQucmlnaHQ7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLnJpZ2h0ID0gc3dhcDtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIGlmIG5vdCBpbnZlcnRpYmxlLCByZXN0cmljdCB0byBtaW5pbXVtIG9mIDB4MCByZWN0XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC50b3AgICAgPSBNYXRoLm1pbihjdXJyZW50LnRvcCwgc3RhcnQuYm90dG9tKTtcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IE1hdGgubWF4KGN1cnJlbnQuYm90dG9tLCBzdGFydC50b3ApO1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCAgID0gTWF0aC5taW4oY3VycmVudC5sZWZ0LCBzdGFydC5yaWdodCk7XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC5yaWdodCAgPSBNYXRoLm1heChjdXJyZW50LnJpZ2h0LCBzdGFydC5sZWZ0KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzdHJpY3RlZC53aWR0aCAgPSByZXN0cmljdGVkLnJpZ2h0ICAtIHJlc3RyaWN0ZWQubGVmdDtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWQuaGVpZ2h0ID0gcmVzdHJpY3RlZC5ib3R0b20gLSByZXN0cmljdGVkLnRvcCA7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGVkZ2UgaW4gcmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgIGRlbHRhW2VkZ2VdID0gcmVzdHJpY3RlZFtlZGdlXSAtIHByZXZpb3VzW2VkZ2VdO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXNpemVFdmVudC5lZGdlcyA9IHRoaXMucHJlcGFyZWQuZWRnZXM7XG4gICAgICAgICAgICByZXNpemVFdmVudC5yZWN0ID0gcmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IGRlbHRhO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShyZXNpemVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIHJlc2l6ZUV2ZW50O1xuICAgIH0sXG5cbiAgICBnZXN0dXJlU3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSAwO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5zdGFydERpc3RhbmNlID0gdGhpcy5nZXN0dXJlLnByZXZEaXN0YW5jZSA9IGdlc3R1cmVFdmVudC5kaXN0YW5jZTtcbiAgICAgICAgdGhpcy5nZXN0dXJlLnN0YXJ0QW5nbGUgPSB0aGlzLmdlc3R1cmUucHJldkFuZ2xlID0gZ2VzdHVyZUV2ZW50LmFuZ2xlO1xuICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSAxO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyaW5nID0gdHJ1ZTtcblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKGdlc3R1cmVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIGdlc3R1cmVFdmVudDtcbiAgICB9LFxuXG4gICAgZ2VzdHVyZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBpZiAoIXRoaXMucG9pbnRlcklkcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnByZXZFdmVudDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBnZXN0dXJlRXZlbnQ7XG5cbiAgICAgICAgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ21vdmUnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSBnZXN0dXJlRXZlbnQuc2NhbGUgLSB0aGlzLmdlc3R1cmUuc2NhbGU7XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShnZXN0dXJlRXZlbnQpO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2QW5nbGUgPSBnZXN0dXJlRXZlbnQuYW5nbGU7XG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2RGlzdGFuY2UgPSBnZXN0dXJlRXZlbnQuZGlzdGFuY2U7XG5cbiAgICAgICAgaWYgKGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gSW5maW5pdHkgJiZcbiAgICAgICAgICAgIGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gbnVsbCAmJlxuICAgICAgICAgICAgZ2VzdHVyZUV2ZW50LnNjYWxlICE9PSB1bmRlZmluZWQgICYmXG4gICAgICAgICAgICAhaXNOYU4oZ2VzdHVyZUV2ZW50LnNjYWxlKSkge1xuXG4gICAgICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSBnZXN0dXJlRXZlbnQuc2NhbGU7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZ2VzdHVyZUV2ZW50O1xuICAgIH0sXG5cbiAgICBwb2ludGVySG9sZDogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCkge1xuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnaG9sZCcpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyVXA6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLm1vdXNlPyAwIDogc2NvcGUuaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIHV0aWxzLmdldFBvaW50ZXJJZChwb2ludGVyKSk7XG5cbiAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcblxuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndXAnICk7XG4gICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICd0YXAnKTtcblxuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyQ2FuY2VsOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IHNjb3BlLmluZGV4T2YodGhpcy5wb2ludGVySWRzLCB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIGNsZWFyVGltZW91dCh0aGlzLmhvbGRUaW1lcnNbcG9pbnRlckluZGV4XSk7XG5cbiAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2NhbmNlbCcpO1xuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICAvLyBodHRwOi8vd3d3LnF1aXJrc21vZGUub3JnL2RvbS9ldmVudHMvY2xpY2suaHRtbFxuICAgIC8vID5FdmVudHMgbGVhZGluZyB0byBkYmxjbGlja1xuICAgIC8vXG4gICAgLy8gSUU4IGRvZXNuJ3QgZmlyZSBkb3duIGV2ZW50IGJlZm9yZSBkYmxjbGljay5cbiAgICAvLyBUaGlzIHdvcmthcm91bmQgdHJpZXMgdG8gZmlyZSBhIHRhcCBhbmQgZG91YmxldGFwIGFmdGVyIGRibGNsaWNrXG4gICAgaWU4RGJsY2xpY2s6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQpIHtcbiAgICAgICAgaWYgKHRoaXMucHJldlRhcFxuICAgICAgICAgICAgJiYgZXZlbnQuY2xpZW50WCA9PT0gdGhpcy5wcmV2VGFwLmNsaWVudFhcbiAgICAgICAgICAgICYmIGV2ZW50LmNsaWVudFkgPT09IHRoaXMucHJldlRhcC5jbGllbnRZXG4gICAgICAgICAgICAmJiBldmVudFRhcmdldCAgID09PSB0aGlzLnByZXZUYXAudGFyZ2V0KSB7XG5cbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbMF0gPSBldmVudFRhcmdldDtcbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzWzBdID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndGFwJyk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gRW5kIGludGVyYWN0IG1vdmUgZXZlbnRzIGFuZCBzdG9wIGF1dG8tc2Nyb2xsIHVubGVzcyBpbmVydGlhIGlzIGVuYWJsZWRcbiAgICBwb2ludGVyRW5kOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgZW5kRXZlbnQsXG4gICAgICAgICAgICB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIG9wdGlvbnMgPSB0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMsXG4gICAgICAgICAgICBpbmVydGlhT3B0aW9ucyA9IG9wdGlvbnMgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5pbmVydGlhLFxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cyA9IHRoaXMuaW5lcnRpYVN0YXR1cztcblxuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhU3RhdHVzLmFjdGl2ZSkgeyByZXR1cm47IH1cblxuICAgICAgICAgICAgdmFyIHBvaW50ZXJTcGVlZCxcbiAgICAgICAgICAgICAgICBub3cgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSBmYWxzZSxcbiAgICAgICAgICAgICAgICBpbmVydGlhID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgc21vb3RoRW5kID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgZW5kU25hcCA9IHNjb3BlLmNoZWNrU25hcCh0YXJnZXQsIHRoaXMucHJlcGFyZWQubmFtZSkgJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnNuYXAuZW5kT25seSxcbiAgICAgICAgICAgICAgICBlbmRSZXN0cmljdCA9IHNjb3BlLmNoZWNrUmVzdHJpY3QodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5yZXN0cmljdC5lbmRPbmx5LFxuICAgICAgICAgICAgICAgIGR4ID0gMCxcbiAgICAgICAgICAgICAgICBkeSA9IDAsXG4gICAgICAgICAgICAgICAgc3RhcnRFdmVudDtcblxuICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ2dpbmcpIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChvcHRpb25zLmRyYWcuYXhpcyA9PT0gJ3gnICkgeyBwb2ludGVyU3BlZWQgPSBNYXRoLmFicyh0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudngpOyB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAob3B0aW9ucy5kcmFnLmF4aXMgPT09ICd5JyApIHsgcG9pbnRlclNwZWVkID0gTWF0aC5hYnModGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5KTsgfVxuICAgICAgICAgICAgICAgIGVsc2UgICAvKm9wdGlvbnMuZHJhZy5heGlzID09PSAneHknKi97IHBvaW50ZXJTcGVlZCA9IHRoaXMucG9pbnRlckRlbHRhLmNsaWVudC5zcGVlZDsgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgcG9pbnRlclNwZWVkID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnNwZWVkO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBjaGVjayBpZiBpbmVydGlhIHNob3VsZCBiZSBzdGFydGVkXG4gICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSAoaW5lcnRpYU9wdGlvbnMgJiYgaW5lcnRpYU9wdGlvbnMuZW5hYmxlZFxuICAgICAgICAgICAgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICE9PSAnZ2VzdHVyZSdcbiAgICAgICAgICAgICYmIGV2ZW50ICE9PSBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhID0gKGluZXJ0aWFQb3NzaWJsZVxuICAgICAgICAgICAgJiYgKG5vdyAtIHRoaXMuY3VyQ29vcmRzLnRpbWVTdGFtcCkgPCA1MFxuICAgICAgICAgICAgJiYgcG9pbnRlclNwZWVkID4gaW5lcnRpYU9wdGlvbnMubWluU3BlZWRcbiAgICAgICAgICAgICYmIHBvaW50ZXJTcGVlZCA+IGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkKTtcblxuICAgICAgICAgICAgaWYgKGluZXJ0aWFQb3NzaWJsZSAmJiAhaW5lcnRpYSAmJiAoZW5kU25hcCB8fCBlbmRSZXN0cmljdCkpIHtcblxuICAgICAgICAgICAgICAgIHZhciBzbmFwUmVzdHJpY3QgPSB7fTtcblxuICAgICAgICAgICAgICAgIHNuYXBSZXN0cmljdC5zbmFwID0gc25hcFJlc3RyaWN0LnJlc3RyaWN0ID0gc25hcFJlc3RyaWN0O1xuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFNuYXApIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5zZXRTbmFwcGluZyh0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzbmFwUmVzdHJpY3QpO1xuICAgICAgICAgICAgICAgICAgICBpZiAoc25hcFJlc3RyaWN0LmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZHggKz0gc25hcFJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcFJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFJlc3RyaWN0KSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc2V0UmVzdHJpY3Rpb24odGhpcy5jdXJDb29yZHMucGFnZSwgc25hcFJlc3RyaWN0KTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHNuYXBSZXN0cmljdC5yZXN0cmljdGVkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeCArPSBzbmFwUmVzdHJpY3QuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeSArPSBzbmFwUmVzdHJpY3QuZHk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAoZHggfHwgZHkpIHtcbiAgICAgICAgICAgICAgICAgICAgc21vb3RoRW5kID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhIHx8IHNtb290aEVuZCkge1xuICAgICAgICAgICAgICAgIHV0aWxzLmNvcHlDb29yZHMoaW5lcnRpYVN0YXR1cy51cENvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVyc1swXSA9IGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCA9IHN0YXJ0RXZlbnQgPVxuICAgICAgICAgICAgICAgICAgICBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgdGhpcy5wcmVwYXJlZC5uYW1lLCAnaW5lcnRpYXN0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudDAgPSBub3c7XG5cbiAgICAgICAgICAgICAgICB0YXJnZXQuZmlyZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGluZXJ0aWEpIHtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy52eDAgPSB0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudng7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudnkwID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnYwID0gcG9pbnRlclNwZWVkO1xuXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuY2FsY0luZXJ0aWEoaW5lcnRpYVN0YXR1cyk7XG5cbiAgICAgICAgICAgICAgICAgICAgdmFyIHBhZ2UgPSB1dGlscy5leHRlbmQoe30sIHRoaXMuY3VyQ29vcmRzLnBhZ2UpLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JpZ2luID0gc2NvcGUuZ2V0T3JpZ2luWFkodGFyZ2V0LCB0aGlzLmVsZW1lbnQpLFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzT2JqZWN0O1xuXG4gICAgICAgICAgICAgICAgICAgIHBhZ2UueCA9IHBhZ2UueCArIGluZXJ0aWFTdGF0dXMueGUgLSBvcmlnaW4ueDtcbiAgICAgICAgICAgICAgICAgICAgcGFnZS55ID0gcGFnZS55ICsgaW5lcnRpYVN0YXR1cy55ZSAtIG9yaWdpbi55O1xuXG4gICAgICAgICAgICAgICAgICAgIHN0YXR1c09iamVjdCA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHVzZVN0YXR1c1hZOiB0cnVlLFxuICAgICAgICAgICAgICAgICAgICAgICAgeDogcGFnZS54LFxuICAgICAgICAgICAgICAgICAgICAgICAgeTogcGFnZS55LFxuICAgICAgICAgICAgICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICAgICAgICAgICAgICBkeTogMCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNuYXA6IG51bGxcbiAgICAgICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgICAgICBzdGF0dXNPYmplY3Quc25hcCA9IHN0YXR1c09iamVjdDtcblxuICAgICAgICAgICAgICAgICAgICBkeCA9IGR5ID0gMDtcblxuICAgICAgICAgICAgICAgICAgICBpZiAoZW5kU25hcCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHNuYXAgPSB0aGlzLnNldFNuYXBwaW5nKHRoaXMuY3VyQ29vcmRzLnBhZ2UsIHN0YXR1c09iamVjdCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzbmFwLmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHNuYXAuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcC5keTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIGlmIChlbmRSZXN0cmljdCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHJlc3RyaWN0ID0gdGhpcy5zZXRSZXN0cmljdGlvbih0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzdGF0dXNPYmplY3QpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAocmVzdHJpY3QucmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR5ICs9IHJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFhlICs9IGR4O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUgKz0gZHk7XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5pID0gYW5pbWF0aW9uRnJhbWUucmVxdWVzdCh0aGlzLmJvdW5kSW5lcnRpYUZyYW1lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc21vb3RoRW5kID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy54ZSA9IGR4O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnllID0gZHk7XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeCA9IGluZXJ0aWFTdGF0dXMuc3kgPSAwO1xuXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuaSA9IGFuaW1hdGlvbkZyYW1lLnJlcXVlc3QodGhpcy5ib3VuZFNtb290aEVuZEZyYW1lKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLmFjdGl2ZSA9IHRydWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoZW5kU25hcCB8fCBlbmRSZXN0cmljdCkge1xuICAgICAgICAgICAgICAgIC8vIGZpcmUgYSBtb3ZlIGV2ZW50IGF0IHRoZSBzbmFwcGVkIGNvb3JkaW5hdGVzXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVyTW92ZShwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCB0cnVlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0aGlzLmRyYWdnaW5nKSB7XG4gICAgICAgICAgICBlbmRFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZHJhZycsICdlbmQnLCB0aGlzLmVsZW1lbnQpO1xuXG4gICAgICAgICAgICB2YXIgZHJhZ2dhYmxlRWxlbWVudCA9IHRoaXMuZWxlbWVudCxcbiAgICAgICAgICAgICAgICBkcm9wID0gdGhpcy5nZXREcm9wKGV2ZW50LCBkcmFnZ2FibGVFbGVtZW50KTtcblxuICAgICAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gZHJvcC5kcm9wem9uZTtcbiAgICAgICAgICAgIHRoaXMuZHJvcEVsZW1lbnQgPSBkcm9wLmVsZW1lbnQ7XG5cbiAgICAgICAgICAgIHZhciBkcm9wRXZlbnRzID0gdGhpcy5nZXREcm9wRXZlbnRzKGV2ZW50LCBlbmRFdmVudCk7XG5cbiAgICAgICAgICAgIGlmIChkcm9wRXZlbnRzLmxlYXZlKSB7IHRoaXMucHJldkRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmxlYXZlKTsgfVxuICAgICAgICAgICAgaWYgKGRyb3BFdmVudHMuZW50ZXIpIHsgICAgIHRoaXMuZHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMuZW50ZXIpOyB9XG4gICAgICAgICAgICBpZiAoZHJvcEV2ZW50cy5kcm9wICkgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5kcm9wICk7IH1cbiAgICAgICAgICAgIGlmIChkcm9wRXZlbnRzLmRlYWN0aXZhdGUpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmZpcmVBY3RpdmVEcm9wcyhkcm9wRXZlbnRzLmRlYWN0aXZhdGUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0YXJnZXQuZmlyZShlbmRFdmVudCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodGhpcy5yZXNpemluZykge1xuICAgICAgICAgICAgZW5kRXZlbnQgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ3Jlc2l6ZScsICdlbmQnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgdGFyZ2V0LmZpcmUoZW5kRXZlbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHRoaXMuZ2VzdHVyaW5nKSB7XG4gICAgICAgICAgICBlbmRFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZ2VzdHVyZScsICdlbmQnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgdGFyZ2V0LmZpcmUoZW5kRXZlbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5zdG9wKGV2ZW50KTtcbiAgICB9LFxuXG4gICAgY29sbGVjdERyb3BzOiBmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICB2YXIgZHJvcHMgPSBbXSxcbiAgICAgICAgICAgIGVsZW1lbnRzID0gW10sXG4gICAgICAgICAgICBpO1xuXG4gICAgICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IHRoaXMuZWxlbWVudDtcblxuICAgICAgICAvLyBjb2xsZWN0IGFsbCBkcm9wem9uZXMgYW5kIHRoZWlyIGVsZW1lbnRzIHdoaWNoIHF1YWxpZnkgZm9yIGEgZHJvcFxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgc2NvcGUuaW50ZXJhY3RhYmxlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaWYgKCFzY29wZS5pbnRlcmFjdGFibGVzW2ldLm9wdGlvbnMuZHJvcC5lbmFibGVkKSB7IGNvbnRpbnVlOyB9XG5cbiAgICAgICAgICAgIHZhciBjdXJyZW50ID0gc2NvcGUuaW50ZXJhY3RhYmxlc1tpXSxcbiAgICAgICAgICAgICAgICBhY2NlcHQgPSBjdXJyZW50Lm9wdGlvbnMuZHJvcC5hY2NlcHQ7XG5cbiAgICAgICAgICAgIC8vIHRlc3QgdGhlIGRyYWdnYWJsZSBlbGVtZW50IGFnYWluc3QgdGhlIGRyb3B6b25lJ3MgYWNjZXB0IHNldHRpbmdcbiAgICAgICAgICAgIGlmICgodXRpbHMuaXNFbGVtZW50KGFjY2VwdCkgJiYgYWNjZXB0ICE9PSBlbGVtZW50KVxuICAgICAgICAgICAgICAgIHx8IChzY29wZS5pc1N0cmluZyhhY2NlcHQpXG4gICAgICAgICAgICAgICAgJiYgIXNjb3BlLm1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBhY2NlcHQpKSkge1xuXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHF1ZXJ5IGZvciBuZXcgZWxlbWVudHMgaWYgbmVjZXNzYXJ5XG4gICAgICAgICAgICB2YXIgZHJvcEVsZW1lbnRzID0gY3VycmVudC5zZWxlY3Rvcj8gY3VycmVudC5fY29udGV4dC5xdWVyeVNlbGVjdG9yQWxsKGN1cnJlbnQuc2VsZWN0b3IpIDogW2N1cnJlbnQuX2VsZW1lbnRdO1xuXG4gICAgICAgICAgICBmb3IgKHZhciBqID0gMCwgbGVuID0gZHJvcEVsZW1lbnRzLmxlbmd0aDsgaiA8IGxlbjsgaisrKSB7XG4gICAgICAgICAgICAgICAgdmFyIGN1cnJlbnRFbGVtZW50ID0gZHJvcEVsZW1lbnRzW2pdO1xuXG4gICAgICAgICAgICAgICAgaWYgKGN1cnJlbnRFbGVtZW50ID09PSBlbGVtZW50KSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGRyb3BzLnB1c2goY3VycmVudCk7XG4gICAgICAgICAgICAgICAgZWxlbWVudHMucHVzaChjdXJyZW50RWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgZHJvcHpvbmVzOiBkcm9wcyxcbiAgICAgICAgICAgIGVsZW1lbnRzOiBlbGVtZW50c1xuICAgICAgICB9O1xuICAgIH0sXG5cbiAgICBmaXJlQWN0aXZlRHJvcHM6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgaSxcbiAgICAgICAgICAgIGN1cnJlbnQsXG4gICAgICAgICAgICBjdXJyZW50RWxlbWVudCxcbiAgICAgICAgICAgIHByZXZFbGVtZW50O1xuXG4gICAgICAgIC8vIGxvb3AgdGhyb3VnaCBhbGwgYWN0aXZlIGRyb3B6b25lcyBhbmQgdHJpZ2dlciBldmVudFxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGN1cnJlbnQgPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tpXTtcbiAgICAgICAgICAgIGN1cnJlbnRFbGVtZW50ID0gdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyBbaV07XG5cbiAgICAgICAgICAgIC8vIHByZXZlbnQgdHJpZ2dlciBvZiBkdXBsaWNhdGUgZXZlbnRzIG9uIHNhbWUgZWxlbWVudFxuICAgICAgICAgICAgaWYgKGN1cnJlbnRFbGVtZW50ICE9PSBwcmV2RWxlbWVudCkge1xuICAgICAgICAgICAgICAgIC8vIHNldCBjdXJyZW50IGVsZW1lbnQgYXMgZXZlbnQgdGFyZ2V0XG4gICAgICAgICAgICAgICAgZXZlbnQudGFyZ2V0ID0gY3VycmVudEVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgY3VycmVudC5maXJlKGV2ZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHByZXZFbGVtZW50ID0gY3VycmVudEVsZW1lbnQ7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gQ29sbGVjdCBhIG5ldyBzZXQgb2YgcG9zc2libGUgZHJvcHMgYW5kIHNhdmUgdGhlbSBpbiBhY3RpdmVEcm9wcy5cbiAgICAvLyBzZXRBY3RpdmVEcm9wcyBzaG91bGQgYWx3YXlzIGJlIGNhbGxlZCB3aGVuIGEgZHJhZyBoYXMganVzdCBzdGFydGVkIG9yIGFcbiAgICAvLyBkcmFnIGV2ZW50IGhhcHBlbnMgd2hpbGUgZHluYW1pY0Ryb3AgaXMgdHJ1ZVxuICAgIHNldEFjdGl2ZURyb3BzOiBmdW5jdGlvbiAoZHJhZ0VsZW1lbnQpIHtcbiAgICAgICAgLy8gZ2V0IGRyb3B6b25lcyBhbmQgdGhlaXIgZWxlbWVudHMgdGhhdCBjb3VsZCByZWNlaXZlIHRoZSBkcmFnZ2FibGVcbiAgICAgICAgdmFyIHBvc3NpYmxlRHJvcHMgPSB0aGlzLmNvbGxlY3REcm9wcyhkcmFnRWxlbWVudCwgdHJ1ZSk7XG5cbiAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMgPSBwb3NzaWJsZURyb3BzLmRyb3B6b25lcztcbiAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyAgPSBwb3NzaWJsZURyb3BzLmVsZW1lbnRzO1xuICAgICAgICB0aGlzLmFjdGl2ZURyb3BzLnJlY3RzICAgICA9IFtdO1xuXG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHNbaV0gPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tpXS5nZXRSZWN0KHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHNbaV0pO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIGdldERyb3A6IGZ1bmN0aW9uIChldmVudCwgZHJhZ0VsZW1lbnQpIHtcbiAgICAgICAgdmFyIHZhbGlkRHJvcHMgPSBbXTtcblxuICAgICAgICBpZiAoc2NvcGUuZHluYW1pY0Ryb3ApIHtcbiAgICAgICAgICAgIHRoaXMuc2V0QWN0aXZlRHJvcHMoZHJhZ0VsZW1lbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gY29sbGVjdCBhbGwgZHJvcHpvbmVzIGFuZCB0aGVpciBlbGVtZW50cyB3aGljaCBxdWFsaWZ5IGZvciBhIGRyb3BcbiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgdmFyIGN1cnJlbnQgICAgICAgID0gdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXNbal0sXG4gICAgICAgICAgICAgICAgY3VycmVudEVsZW1lbnQgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzIFtqXSxcbiAgICAgICAgICAgICAgICByZWN0ICAgICAgICAgICA9IHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgW2pdO1xuXG4gICAgICAgICAgICB2YWxpZERyb3BzLnB1c2goY3VycmVudC5kcm9wQ2hlY2sodGhpcy5wb2ludGVyc1swXSwgZXZlbnQsIHRoaXMudGFyZ2V0LCBkcmFnRWxlbWVudCwgY3VycmVudEVsZW1lbnQsIHJlY3QpXG4gICAgICAgICAgICAgICAgPyBjdXJyZW50RWxlbWVudFxuICAgICAgICAgICAgICAgIDogbnVsbCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBnZXQgdGhlIG1vc3QgYXBwcm9wcmlhdGUgZHJvcHpvbmUgYmFzZWQgb24gRE9NIGRlcHRoIGFuZCBvcmRlclxuICAgICAgICB2YXIgZHJvcEluZGV4ID0gc2NvcGUuaW5kZXhPZkRlZXBlc3RFbGVtZW50KHZhbGlkRHJvcHMpLFxuICAgICAgICAgICAgZHJvcHpvbmUgID0gdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXNbZHJvcEluZGV4XSB8fCBudWxsLFxuICAgICAgICAgICAgZWxlbWVudCAgID0gdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyBbZHJvcEluZGV4XSB8fCBudWxsO1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBkcm9wem9uZTogZHJvcHpvbmUsXG4gICAgICAgICAgICBlbGVtZW50OiBlbGVtZW50XG4gICAgICAgIH07XG4gICAgfSxcblxuICAgIGdldERyb3BFdmVudHM6IGZ1bmN0aW9uIChwb2ludGVyRXZlbnQsIGRyYWdFdmVudCkge1xuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHtcbiAgICAgICAgICAgIGVudGVyICAgICA6IG51bGwsXG4gICAgICAgICAgICBsZWF2ZSAgICAgOiBudWxsLFxuICAgICAgICAgICAgYWN0aXZhdGUgIDogbnVsbCxcbiAgICAgICAgICAgIGRlYWN0aXZhdGU6IG51bGwsXG4gICAgICAgICAgICBtb3ZlICAgICAgOiBudWxsLFxuICAgICAgICAgICAgZHJvcCAgICAgIDogbnVsbFxuICAgICAgICB9O1xuXG4gICAgICAgIGlmICh0aGlzLmRyb3BFbGVtZW50ICE9PSB0aGlzLnByZXZEcm9wRWxlbWVudCkge1xuICAgICAgICAgICAgLy8gaWYgdGhlcmUgd2FzIGEgcHJldkRyb3BUYXJnZXQsIGNyZWF0ZSBhIGRyYWdsZWF2ZSBldmVudFxuICAgICAgICAgICAgaWYgKHRoaXMucHJldkRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgICAgICBkcm9wRXZlbnRzLmxlYXZlID0ge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiB0aGlzLnByZXZEcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5wcmV2RHJvcFRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICAgICAgZHJhZ0V2ZW50ICAgIDogZHJhZ0V2ZW50LFxuICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgICAgICB0eXBlICAgICAgICAgOiAnZHJhZ2xlYXZlJ1xuICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQuZHJhZ0xlYXZlID0gdGhpcy5wcmV2RHJvcEVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgZHJhZ0V2ZW50LnByZXZEcm9wem9uZSA9IHRoaXMucHJldkRyb3BUYXJnZXQ7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBpZiB0aGUgZHJvcFRhcmdldCBpcyBub3QgbnVsbCwgY3JlYXRlIGEgZHJhZ2VudGVyIGV2ZW50XG4gICAgICAgICAgICBpZiAodGhpcy5kcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgZHJvcEV2ZW50cy5lbnRlciA9IHtcbiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogdGhpcy5kcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5kcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICByZWxhdGVkVGFyZ2V0OiBkcmFnRXZlbnQudGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uICA6IHRoaXMsXG4gICAgICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcmFnZW50ZXInXG4gICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgIGRyYWdFdmVudC5kcmFnRW50ZXIgPSB0aGlzLmRyb3BFbGVtZW50O1xuICAgICAgICAgICAgICAgIGRyYWdFdmVudC5kcm9wem9uZSA9IHRoaXMuZHJvcFRhcmdldDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChkcmFnRXZlbnQudHlwZSA9PT0gJ2RyYWdlbmQnICYmIHRoaXMuZHJvcFRhcmdldCkge1xuICAgICAgICAgICAgZHJvcEV2ZW50cy5kcm9wID0ge1xuICAgICAgICAgICAgICAgIHRhcmdldCAgICAgICA6IHRoaXMuZHJvcEVsZW1lbnQsXG4gICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5kcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wJ1xuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgZHJhZ0V2ZW50LmRyb3B6b25lID0gdGhpcy5kcm9wVGFyZ2V0O1xuICAgICAgICB9XG4gICAgICAgIGlmIChkcmFnRXZlbnQudHlwZSA9PT0gJ2RyYWdzdGFydCcpIHtcbiAgICAgICAgICAgIGRyb3BFdmVudHMuYWN0aXZhdGUgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogbnVsbCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wYWN0aXZhdGUnXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICAgIGlmIChkcmFnRXZlbnQudHlwZSA9PT0gJ2RyYWdlbmQnKSB7XG4gICAgICAgICAgICBkcm9wRXZlbnRzLmRlYWN0aXZhdGUgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogbnVsbCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wZGVhY3RpdmF0ZSdcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ21vdmUnICYmIHRoaXMuZHJvcFRhcmdldCkge1xuICAgICAgICAgICAgZHJvcEV2ZW50cy5tb3ZlID0ge1xuICAgICAgICAgICAgICAgIHRhcmdldCAgICAgICA6IHRoaXMuZHJvcEVsZW1lbnQsXG4gICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5kcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICBkcmFnbW92ZSAgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgdGltZVN0YW1wICAgIDogZHJhZ0V2ZW50LnRpbWVTdGFtcCxcbiAgICAgICAgICAgICAgICB0eXBlICAgICAgICAgOiAnZHJvcG1vdmUnXG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgZHJhZ0V2ZW50LmRyb3B6b25lID0gdGhpcy5kcm9wVGFyZ2V0O1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGRyb3BFdmVudHM7XG4gICAgfSxcblxuICAgIGN1cnJlbnRBY3Rpb246IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuICh0aGlzLmRyYWdnaW5nICYmICdkcmFnJykgfHwgKHRoaXMucmVzaXppbmcgJiYgJ3Jlc2l6ZScpIHx8ICh0aGlzLmdlc3R1cmluZyAmJiAnZ2VzdHVyZScpIHx8IG51bGw7XG4gICAgfSxcblxuICAgIGludGVyYWN0aW5nOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmRyYWdnaW5nIHx8IHRoaXMucmVzaXppbmcgfHwgdGhpcy5nZXN0dXJpbmc7XG4gICAgfSxcblxuICAgIGNsZWFyVGFyZ2V0czogZnVuY3Rpb24gKCkge1xuICAgICAgICB0aGlzLnRhcmdldCA9IHRoaXMuZWxlbWVudCA9IG51bGw7XG5cbiAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gdGhpcy5kcm9wRWxlbWVudCA9IHRoaXMucHJldkRyb3BUYXJnZXQgPSB0aGlzLnByZXZEcm9wRWxlbWVudCA9IG51bGw7XG4gICAgfSxcblxuICAgIHN0b3A6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLnN0b3AoKTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG5cbiAgICAgICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldDtcblxuICAgICAgICAgICAgaWYgKHRhcmdldC5vcHRpb25zLnN0eWxlQ3Vyc29yKSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0Ll9kb2MuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9ICcnO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBwcmV2ZW50IERlZmF1bHQgb25seSBpZiB3ZXJlIHByZXZpb3VzbHkgaW50ZXJhY3RpbmdcbiAgICAgICAgICAgIGlmIChldmVudCAmJiBzY29wZS5pc0Z1bmN0aW9uKGV2ZW50LnByZXZlbnREZWZhdWx0KSkge1xuICAgICAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5kcmFnZ2luZykge1xuICAgICAgICAgICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzID0gdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyA9IHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgPSBudWxsO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0aGlzLmNsZWFyVGFyZ2V0cygpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5wb2ludGVySXNEb3duID0gdGhpcy5zbmFwU3RhdHVzLmxvY2tlZCA9IHRoaXMuZHJhZ2dpbmcgPSB0aGlzLnJlc2l6aW5nID0gdGhpcy5nZXN0dXJpbmcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5wcmVwYXJlZC5uYW1lID0gdGhpcy5wcmV2RXZlbnQgPSBudWxsO1xuICAgICAgICB0aGlzLmluZXJ0aWFTdGF0dXMucmVzdW1lRHggPSB0aGlzLmluZXJ0aWFTdGF0dXMucmVzdW1lRHkgPSAwO1xuXG4gICAgICAgIC8vIHJlbW92ZSBwb2ludGVycyBpZiB0aGVpciBJRCBpc24ndCBpbiB0aGlzLnBvaW50ZXJJZHNcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLnBvaW50ZXJzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIHV0aWxzLmdldFBvaW50ZXJJZCh0aGlzLnBvaW50ZXJzW2ldKSkgPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVycy5zcGxpY2UoaSwgMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgc2NvcGUuaW50ZXJhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAvLyByZW1vdmUgdGhpcyBpbnRlcmFjdGlvbiBpZiBpdCdzIG5vdCB0aGUgb25seSBvbmUgb2YgaXQncyB0eXBlXG4gICAgICAgICAgICBpZiAoc2NvcGUuaW50ZXJhY3Rpb25zW2ldICE9PSB0aGlzICYmIHNjb3BlLmludGVyYWN0aW9uc1tpXS5tb3VzZSA9PT0gdGhpcy5tb3VzZSkge1xuICAgICAgICAgICAgICAgIHNjb3BlLmludGVyYWN0aW9ucy5zcGxpY2Uoc2NvcGUuaW5kZXhPZihzY29wZS5pbnRlcmFjdGlvbnMsIHRoaXMpLCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBpbmVydGlhRnJhbWU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIGluZXJ0aWFTdGF0dXMgPSB0aGlzLmluZXJ0aWFTdGF0dXMsXG4gICAgICAgICAgICBvcHRpb25zID0gdGhpcy50YXJnZXQub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLmluZXJ0aWEsXG4gICAgICAgICAgICBsYW1iZGEgPSBvcHRpb25zLnJlc2lzdGFuY2UsXG4gICAgICAgICAgICB0ID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLyAxMDAwIC0gaW5lcnRpYVN0YXR1cy50MDtcblxuICAgICAgICBpZiAodCA8IGluZXJ0aWFTdGF0dXMudGUpIHtcblxuICAgICAgICAgICAgdmFyIHByb2dyZXNzID0gIDEgLSAoTWF0aC5leHAoLWxhbWJkYSAqIHQpIC0gaW5lcnRpYVN0YXR1cy5sYW1iZGFfdjApIC8gaW5lcnRpYVN0YXR1cy5vbmVfdmVfdjA7XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhU3RhdHVzLm1vZGlmaWVkWGUgPT09IGluZXJ0aWFTdGF0dXMueGUgJiYgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllID09PSBpbmVydGlhU3RhdHVzLnllKSB7XG4gICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeCA9IGluZXJ0aWFTdGF0dXMueGUgKiBwcm9ncmVzcztcbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gaW5lcnRpYVN0YXR1cy55ZSAqIHByb2dyZXNzO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgdmFyIHF1YWRQb2ludCA9IHNjb3BlLmdldFF1YWRyYXRpY0N1cnZlUG9pbnQoXG4gICAgICAgICAgICAgICAgICAgIDAsIDAsXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMueGUsIGluZXJ0aWFTdGF0dXMueWUsXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMubW9kaWZpZWRYZSwgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllLFxuICAgICAgICAgICAgICAgICAgICBwcm9ncmVzcyk7XG5cbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gcXVhZFBvaW50Lng7XG4gICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IHF1YWRQb2ludC55O1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5pID0gYW5pbWF0aW9uRnJhbWUucmVxdWVzdCh0aGlzLmJvdW5kSW5lcnRpYUZyYW1lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWGU7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllO1xuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMucG9pbnRlckVuZChpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc21vb3RoRW5kRnJhbWU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIGluZXJ0aWFTdGF0dXMgPSB0aGlzLmluZXJ0aWFTdGF0dXMsXG4gICAgICAgICAgICB0ID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLSBpbmVydGlhU3RhdHVzLnQwLFxuICAgICAgICAgICAgZHVyYXRpb24gPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYS5zbW9vdGhFbmREdXJhdGlvbjtcblxuICAgICAgICBpZiAodCA8IGR1cmF0aW9uKSB7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gc2NvcGUuZWFzZU91dFF1YWQodCwgMCwgaW5lcnRpYVN0YXR1cy54ZSwgZHVyYXRpb24pO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IHNjb3BlLmVhc2VPdXRRdWFkKHQsIDAsIGluZXJ0aWFTdGF0dXMueWUsIGR1cmF0aW9uKTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyTW92ZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG5cbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuaSA9IGFuaW1hdGlvbkZyYW1lLnJlcXVlc3QodGhpcy5ib3VuZFNtb290aEVuZEZyYW1lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLnhlO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IGluZXJ0aWFTdGF0dXMueWU7XG5cbiAgICAgICAgICAgIHRoaXMucG9pbnRlck1vdmUoaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50LCBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLmFjdGl2ZSA9IGZhbHNlO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zbW9vdGhFbmQgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyRW5kKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBhZGRQb2ludGVyOiBmdW5jdGlvbiAocG9pbnRlcikge1xuICAgICAgICB2YXIgaWQgPSB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlciksXG4gICAgICAgICAgICBpbmRleCA9IHRoaXMubW91c2U/IDAgOiBzY29wZS5pbmRleE9mKHRoaXMucG9pbnRlcklkcywgaWQpO1xuXG4gICAgICAgIGlmIChpbmRleCA9PT0gLTEpIHtcbiAgICAgICAgICAgIGluZGV4ID0gdGhpcy5wb2ludGVySWRzLmxlbmd0aDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMucG9pbnRlcklkc1tpbmRleF0gPSBpZDtcbiAgICAgICAgdGhpcy5wb2ludGVyc1tpbmRleF0gPSBwb2ludGVyO1xuXG4gICAgICAgIHJldHVybiBpbmRleDtcbiAgICB9LFxuXG4gICAgcmVtb3ZlUG9pbnRlcjogZnVuY3Rpb24gKHBvaW50ZXIpIHtcbiAgICAgICAgdmFyIGlkID0gdXRpbHMuZ2V0UG9pbnRlcklkKHBvaW50ZXIpLFxuICAgICAgICAgICAgaW5kZXggPSB0aGlzLm1vdXNlPyAwIDogc2NvcGUuaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGlkKTtcblxuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7IHJldHVybjsgfVxuXG4gICAgICAgIGlmICghdGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJzLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJZHMgLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgIHRoaXMuZG93blRhcmdldHMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgdGhpcy5kb3duVGltZXMgIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB0aGlzLmhvbGRUaW1lcnMgLnNwbGljZShpbmRleCwgMSk7XG4gICAgfSxcblxuICAgIHJlY29yZFBvaW50ZXI6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIC8vIERvIG5vdCB1cGRhdGUgcG9pbnRlcnMgd2hpbGUgaW5lcnRpYSBpcyBhY3RpdmUuXG4gICAgICAgIC8vIFRoZSBpbmVydGlhIHN0YXJ0IGV2ZW50IHNob3VsZCBiZSB0aGlzLnBvaW50ZXJzWzBdXG4gICAgICAgIGlmICh0aGlzLmluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7IHJldHVybjsgfVxuXG4gICAgICAgIHZhciBpbmRleCA9IHRoaXMubW91c2U/IDA6IHNjb3BlLmluZGV4T2YodGhpcy5wb2ludGVySWRzLCB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIGlmIChpbmRleCA9PT0gLTEpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgdGhpcy5wb2ludGVyc1tpbmRleF0gPSBwb2ludGVyO1xuICAgIH0sXG5cbiAgICBjb2xsZWN0RXZlbnRUYXJnZXRzOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBldmVudFR5cGUpIHtcbiAgICAgICAgdmFyIHBvaW50ZXJJbmRleCA9IHRoaXMubW91c2U/IDAgOiBzY29wZS5pbmRleE9mKHRoaXMucG9pbnRlcklkcywgdXRpbHMuZ2V0UG9pbnRlcklkKHBvaW50ZXIpKTtcblxuICAgICAgICAvLyBkbyBub3QgZmlyZSBhIHRhcCBldmVudCBpZiB0aGUgcG9pbnRlciB3YXMgbW92ZWQgYmVmb3JlIGJlaW5nIGxpZnRlZFxuICAgICAgICBpZiAoZXZlbnRUeXBlID09PSAndGFwJyAmJiAodGhpcy5wb2ludGVyV2FzTW92ZWRcbiAgICAgICAgICAgICAgICAvLyBvciBpZiB0aGUgcG9pbnRlcnVwIHRhcmdldCBpcyBkaWZmZXJlbnQgdG8gdGhlIHBvaW50ZXJkb3duIHRhcmdldFxuICAgICAgICAgICAgfHwgISh0aGlzLmRvd25UYXJnZXRzW3BvaW50ZXJJbmRleF0gJiYgdGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdID09PSBldmVudFRhcmdldCkpKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdGFyZ2V0cyA9IFtdLFxuICAgICAgICAgICAgZWxlbWVudHMgPSBbXSxcbiAgICAgICAgICAgIGVsZW1lbnQgPSBldmVudFRhcmdldDtcblxuICAgICAgICBmdW5jdGlvbiBjb2xsZWN0U2VsZWN0b3JzIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICB2YXIgZWxzID0gc2NvcGUuaWU4TWF0Y2hlc1NlbGVjdG9yXG4gICAgICAgICAgICAgICAgPyBjb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgOiB1bmRlZmluZWQ7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGFibGUuX2lFdmVudHNbZXZlbnRUeXBlXVxuICAgICAgICAgICAgICAgICYmIHV0aWxzLmlzRWxlbWVudChlbGVtZW50KVxuICAgICAgICAgICAgICAgICYmIHNjb3BlLmluQ29udGV4dChpbnRlcmFjdGFibGUsIGVsZW1lbnQpXG4gICAgICAgICAgICAgICAgJiYgIXNjb3BlLnRlc3RJZ25vcmUoaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS50ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVscykpIHtcblxuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaChpbnRlcmFjdGFibGUpO1xuICAgICAgICAgICAgICAgIGVsZW1lbnRzLnB1c2goZWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuXG4gICAgICAgIHZhciBpbnRlcmFjdCA9IHNjb3BlLmludGVyYWN0O1xuXG4gICAgICAgIHdoaWxlIChlbGVtZW50KSB7XG4gICAgICAgICAgICBpZiAoaW50ZXJhY3QuaXNTZXQoZWxlbWVudCkgJiYgaW50ZXJhY3QoZWxlbWVudCkuX2lFdmVudHNbZXZlbnRUeXBlXSkge1xuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaChpbnRlcmFjdChlbGVtZW50KSk7XG4gICAgICAgICAgICAgICAgZWxlbWVudHMucHVzaChlbGVtZW50KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IoY29sbGVjdFNlbGVjdG9ycyk7XG5cbiAgICAgICAgICAgIGVsZW1lbnQgPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gY3JlYXRlIHRoZSB0YXAgZXZlbnQgZXZlbiBpZiB0aGVyZSBhcmUgbm8gbGlzdGVuZXJzIHNvIHRoYXRcbiAgICAgICAgLy8gZG91YmxldGFwIGNhbiBzdGlsbCBiZSBjcmVhdGVkIGFuZCBmaXJlZFxuICAgICAgICBpZiAodGFyZ2V0cy5sZW5ndGggfHwgZXZlbnRUeXBlID09PSAndGFwJykge1xuICAgICAgICAgICAgdGhpcy5maXJlUG9pbnRlcnMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCB0YXJnZXRzLCBlbGVtZW50cywgZXZlbnRUeXBlKTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBmaXJlUG9pbnRlcnM6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIHRhcmdldHMsIGVsZW1lbnRzLCBldmVudFR5cGUpIHtcbiAgICAgICAgdmFyIHBvaW50ZXJJbmRleCA9IHRoaXMubW91c2U/IDAgOiBzY29wZS5pbmRleE9mKHV0aWxzLmdldFBvaW50ZXJJZChwb2ludGVyKSksXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQgPSB7fSxcbiAgICAgICAgICAgIGksXG4gICAgICAgIC8vIGZvciB0YXAgZXZlbnRzXG4gICAgICAgICAgICBpbnRlcnZhbCwgY3JlYXRlTmV3RG91YmxlVGFwO1xuXG4gICAgICAgIC8vIGlmIGl0J3MgYSBkb3VibGV0YXAgdGhlbiB0aGUgZXZlbnQgcHJvcGVydGllcyB3b3VsZCBoYXZlIGJlZW5cbiAgICAgICAgLy8gY29waWVkIGZyb20gdGhlIHRhcCBldmVudCBhbmQgcHJvdmlkZWQgYXMgdGhlIHBvaW50ZXIgYXJndW1lbnRcbiAgICAgICAgaWYgKGV2ZW50VHlwZSA9PT0gJ2RvdWJsZXRhcCcpIHtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudCA9IHBvaW50ZXI7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB1dGlscy5leHRlbmQocG9pbnRlckV2ZW50LCBldmVudCk7XG4gICAgICAgICAgICBpZiAoZXZlbnQgIT09IHBvaW50ZXIpIHtcbiAgICAgICAgICAgICAgICB1dGlscy5leHRlbmQocG9pbnRlckV2ZW50LCBwb2ludGVyKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnByZXZlbnREZWZhdWx0ICAgICAgICAgICA9IHByZXZlbnRPcmlnaW5hbERlZmF1bHQ7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuc3RvcFByb3BhZ2F0aW9uICAgICAgICAgID0gSW50ZXJhY3RFdmVudC5wcm90b3R5cGUuc3RvcFByb3BhZ2F0aW9uO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiA9IEludGVyYWN0RXZlbnQucHJvdG90eXBlLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbjtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5pbnRlcmFjdGlvbiAgICAgICAgICAgICAgPSB0aGlzO1xuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQudGltZVN0YW1wICAgICA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50Lm9yaWdpbmFsRXZlbnQgPSBldmVudDtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC50eXBlICAgICAgICAgID0gZXZlbnRUeXBlO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnBvaW50ZXJJZCAgICAgPSB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcik7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQucG9pbnRlclR5cGUgICA9IHRoaXMubW91c2U/ICdtb3VzZScgOiAhYnJvd3Nlci5zdXBwb3J0c1BvaW50ZXJFdmVudD8gJ3RvdWNoJ1xuICAgICAgICAgICAgICAgIDogc2NvcGUuaXNTdHJpbmcocG9pbnRlci5wb2ludGVyVHlwZSlcbiAgICAgICAgICAgICAgICA/IHBvaW50ZXIucG9pbnRlclR5cGVcbiAgICAgICAgICAgICAgICA6IFssLCd0b3VjaCcsICdwZW4nLCAnbW91c2UnXVtwb2ludGVyLnBvaW50ZXJUeXBlXTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChldmVudFR5cGUgPT09ICd0YXAnKSB7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZHQgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XTtcblxuICAgICAgICAgICAgaW50ZXJ2YWwgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy50YXBUaW1lO1xuICAgICAgICAgICAgY3JlYXRlTmV3RG91YmxlVGFwID0gISEodGhpcy5wcmV2VGFwICYmIHRoaXMucHJldlRhcC50eXBlICE9PSAnZG91YmxldGFwJ1xuICAgICAgICAgICAgJiYgdGhpcy5wcmV2VGFwLnRhcmdldCA9PT0gcG9pbnRlckV2ZW50LnRhcmdldFxuICAgICAgICAgICAgJiYgaW50ZXJ2YWwgPCA1MDApO1xuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZG91YmxlID0gY3JlYXRlTmV3RG91YmxlVGFwO1xuXG4gICAgICAgICAgICB0aGlzLnRhcFRpbWUgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wO1xuICAgICAgICB9XG5cbiAgICAgICAgZm9yIChpID0gMDsgaSA8IHRhcmdldHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5jdXJyZW50VGFyZ2V0ID0gZWxlbWVudHNbaV07XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuaW50ZXJhY3RhYmxlID0gdGFyZ2V0c1tpXTtcbiAgICAgICAgICAgIHRhcmdldHNbaV0uZmlyZShwb2ludGVyRXZlbnQpO1xuXG4gICAgICAgICAgICBpZiAocG9pbnRlckV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZFxuICAgICAgICAgICAgICAgIHx8KHBvaW50ZXJFdmVudC5wcm9wYWdhdGlvblN0b3BwZWQgJiYgZWxlbWVudHNbaSArIDFdICE9PSBwb2ludGVyRXZlbnQuY3VycmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjcmVhdGVOZXdEb3VibGVUYXApIHtcbiAgICAgICAgICAgIHZhciBkb3VibGVUYXAgPSB7fTtcblxuICAgICAgICAgICAgdXRpbHMuZXh0ZW5kKGRvdWJsZVRhcCwgcG9pbnRlckV2ZW50KTtcblxuICAgICAgICAgICAgZG91YmxlVGFwLmR0ICAgPSBpbnRlcnZhbDtcbiAgICAgICAgICAgIGRvdWJsZVRhcC50eXBlID0gJ2RvdWJsZXRhcCc7XG5cbiAgICAgICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhkb3VibGVUYXAsIGV2ZW50LCBldmVudFRhcmdldCwgJ2RvdWJsZXRhcCcpO1xuXG4gICAgICAgICAgICB0aGlzLnByZXZUYXAgPSBkb3VibGVUYXA7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZXZlbnRUeXBlID09PSAndGFwJykge1xuICAgICAgICAgICAgdGhpcy5wcmV2VGFwID0gcG9pbnRlckV2ZW50O1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIHZhbGlkYXRlU2VsZWN0b3I6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cykge1xuICAgICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gbWF0Y2hlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgdmFyIG1hdGNoID0gbWF0Y2hlc1tpXSxcbiAgICAgICAgICAgICAgICBtYXRjaEVsZW1lbnQgPSBtYXRjaEVsZW1lbnRzW2ldLFxuICAgICAgICAgICAgICAgIGFjdGlvbiA9IHZhbGlkYXRlQWN0aW9uKG1hdGNoLmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgbWF0Y2hFbGVtZW50KSwgbWF0Y2gpO1xuXG4gICAgICAgICAgICBpZiAoYWN0aW9uICYmIHNjb3BlLndpdGhpbkludGVyYWN0aW9uTGltaXQobWF0Y2gsIG1hdGNoRWxlbWVudCwgYWN0aW9uKSkge1xuICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gbWF0Y2g7XG4gICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gbWF0Y2hFbGVtZW50O1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIGFjdGlvbjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzZXRTbmFwcGluZzogZnVuY3Rpb24gKHBhZ2VDb29yZHMsIHN0YXR1cykge1xuICAgICAgICB2YXIgc25hcCA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgdGFyZ2V0cyA9IFtdLFxuICAgICAgICAgICAgdGFyZ2V0LFxuICAgICAgICAgICAgcGFnZSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMuc25hcFN0YXR1cztcblxuICAgICAgICBpZiAoc3RhdHVzLnVzZVN0YXR1c1hZKSB7XG4gICAgICAgICAgICBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZhciBvcmlnaW4gPSBzY29wZS5nZXRPcmlnaW5YWSh0aGlzLnRhcmdldCwgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgcGFnZSA9IHV0aWxzLmV4dGVuZCh7fSwgcGFnZUNvb3Jkcyk7XG5cbiAgICAgICAgICAgIHBhZ2UueCAtPSBvcmlnaW4ueDtcbiAgICAgICAgICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5yZWFsWCA9IHBhZ2UueDtcbiAgICAgICAgc3RhdHVzLnJlYWxZID0gcGFnZS55O1xuXG4gICAgICAgIHBhZ2UueCA9IHBhZ2UueCAtIHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55ID0gcGFnZS55IC0gdGhpcy5pbmVydGlhU3RhdHVzLnJlc3VtZUR5O1xuXG4gICAgICAgIHZhciBsZW4gPSBzbmFwLnRhcmdldHM/IHNuYXAudGFyZ2V0cy5sZW5ndGggOiAwO1xuXG4gICAgICAgIGZvciAodmFyIHJlbEluZGV4ID0gMDsgcmVsSW5kZXggPCB0aGlzLnNuYXBPZmZzZXRzLmxlbmd0aDsgcmVsSW5kZXgrKykge1xuICAgICAgICAgICAgdmFyIHJlbGF0aXZlID0ge1xuICAgICAgICAgICAgICAgIHg6IHBhZ2UueCAtIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngsXG4gICAgICAgICAgICAgICAgeTogcGFnZS55IC0gdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24oc25hcC50YXJnZXRzW2ldKSkge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV0ocmVsYXRpdmUueCwgcmVsYXRpdmUueSwgdGhpcyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV07XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKCF0YXJnZXQpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaCh7XG4gICAgICAgICAgICAgICAgICAgIHg6IHNjb3BlLmlzTnVtYmVyKHRhcmdldC54KSA/ICh0YXJnZXQueCArIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngpIDogcmVsYXRpdmUueCxcbiAgICAgICAgICAgICAgICAgICAgeTogc2NvcGUuaXNOdW1iZXIodGFyZ2V0LnkpID8gKHRhcmdldC55ICsgdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueSkgOiByZWxhdGl2ZS55LFxuXG4gICAgICAgICAgICAgICAgICAgIHJhbmdlOiBzY29wZS5pc051bWJlcih0YXJnZXQucmFuZ2UpPyB0YXJnZXQucmFuZ2U6IHNuYXAucmFuZ2VcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBjbG9zZXN0ID0ge1xuICAgICAgICAgICAgdGFyZ2V0OiBudWxsLFxuICAgICAgICAgICAgaW5SYW5nZTogZmFsc2UsXG4gICAgICAgICAgICBkaXN0YW5jZTogMCxcbiAgICAgICAgICAgIHJhbmdlOiAwLFxuICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICBkeTogMFxuICAgICAgICB9O1xuXG4gICAgICAgIGZvciAoaSA9IDAsIGxlbiA9IHRhcmdldHMubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIHRhcmdldCA9IHRhcmdldHNbaV07XG5cbiAgICAgICAgICAgIHZhciByYW5nZSA9IHRhcmdldC5yYW5nZSxcbiAgICAgICAgICAgICAgICBkeCA9IHRhcmdldC54IC0gcGFnZS54LFxuICAgICAgICAgICAgICAgIGR5ID0gdGFyZ2V0LnkgLSBwYWdlLnksXG4gICAgICAgICAgICAgICAgZGlzdGFuY2UgPSB1dGlscy5oeXBvdChkeCwgZHkpLFxuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBkaXN0YW5jZSA8PSByYW5nZTtcblxuICAgICAgICAgICAgLy8gSW5maW5pdGUgdGFyZ2V0cyBjb3VudCBhcyBiZWluZyBvdXQgb2YgcmFuZ2VcbiAgICAgICAgICAgIC8vIGNvbXBhcmVkIHRvIG5vbiBpbmZpbml0ZSBvbmVzIHRoYXQgYXJlIGluIHJhbmdlXG4gICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5ICYmIGNsb3Nlc3QuaW5SYW5nZSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSkge1xuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBmYWxzZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFjbG9zZXN0LnRhcmdldCB8fCAoaW5SYW5nZVxuICAgICAgICAgICAgICAgICAgICAvLyBpcyB0aGUgY2xvc2VzdCB0YXJnZXQgaW4gcmFuZ2U/XG4gICAgICAgICAgICAgICAgICAgID8gKGNsb3Nlc3QuaW5SYW5nZSAmJiByYW5nZSAhPT0gSW5maW5pdHlcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhlIHBvaW50ZXIgaXMgcmVsYXRpdmVseSBkZWVwZXIgaW4gdGhpcyB0YXJnZXRcbiAgICAgICAgICAgICAgICAgICAgPyBkaXN0YW5jZSAvIHJhbmdlIDwgY2xvc2VzdC5kaXN0YW5jZSAvIGNsb3Nlc3QucmFuZ2VcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhpcyB0YXJnZXQgaGFzIEluZmluaXRlIHJhbmdlIGFuZCB0aGUgY2xvc2VzdCBkb2Vzbid0XG4gICAgICAgICAgICAgICAgICAgIDogKHJhbmdlID09PSBJbmZpbml0eSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSlcbiAgICAgICAgICAgICAgICAgICAgLy8gT1IgdGhpcyB0YXJnZXQgaXMgY2xvc2VyIHRoYXQgdGhlIHByZXZpb3VzIGNsb3Nlc3RcbiAgICAgICAgICAgICAgICB8fCBkaXN0YW5jZSA8IGNsb3Nlc3QuZGlzdGFuY2UpXG4gICAgICAgICAgICAgICAgICAgIC8vIFRoZSBvdGhlciBpcyBub3QgaW4gcmFuZ2UgYW5kIHRoZSBwb2ludGVyIGlzIGNsb3NlciB0byB0aGlzIHRhcmdldFxuICAgICAgICAgICAgICAgICAgICA6ICghY2xvc2VzdC5pblJhbmdlICYmIGRpc3RhbmNlIDwgY2xvc2VzdC5kaXN0YW5jZSkpKSB7XG5cbiAgICAgICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5KSB7XG4gICAgICAgICAgICAgICAgICAgIGluUmFuZ2UgPSB0cnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNsb3Nlc3QudGFyZ2V0ID0gdGFyZ2V0O1xuICAgICAgICAgICAgICAgIGNsb3Nlc3QuZGlzdGFuY2UgPSBkaXN0YW5jZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5pblJhbmdlID0gaW5SYW5nZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LmR4ID0gZHg7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5keSA9IGR5O1xuXG4gICAgICAgICAgICAgICAgc3RhdHVzLnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc25hcENoYW5nZWQ7XG5cbiAgICAgICAgaWYgKGNsb3Nlc3QudGFyZ2V0KSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IChzdGF0dXMuc25hcHBlZFggIT09IGNsb3Nlc3QudGFyZ2V0LnggfHwgc3RhdHVzLnNuYXBwZWRZICE9PSBjbG9zZXN0LnRhcmdldC55KTtcblxuICAgICAgICAgICAgc3RhdHVzLnNuYXBwZWRYID0gY2xvc2VzdC50YXJnZXQueDtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IGNsb3Nlc3QudGFyZ2V0Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IHRydWU7XG5cbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWCA9IE5hTjtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IE5hTjtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IGNsb3Nlc3QuZHg7XG4gICAgICAgIHN0YXR1cy5keSA9IGNsb3Nlc3QuZHk7XG5cbiAgICAgICAgc3RhdHVzLmNoYW5nZWQgPSAoc25hcENoYW5nZWQgfHwgKGNsb3Nlc3QuaW5SYW5nZSAmJiAhc3RhdHVzLmxvY2tlZCkpO1xuICAgICAgICBzdGF0dXMubG9ja2VkID0gY2xvc2VzdC5pblJhbmdlO1xuXG4gICAgICAgIHJldHVybiBzdGF0dXM7XG4gICAgfSxcblxuICAgIHNldFJlc3RyaWN0aW9uOiBmdW5jdGlvbiAocGFnZUNvb3Jkcywgc3RhdHVzKSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIHJlc3RyaWN0ID0gdGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QsXG4gICAgICAgICAgICByZXN0cmljdGlvbiA9IHJlc3RyaWN0ICYmIHJlc3RyaWN0LnJlc3RyaWN0aW9uLFxuICAgICAgICAgICAgcGFnZTtcblxuICAgICAgICBpZiAoIXJlc3RyaWN0aW9uKSB7XG4gICAgICAgICAgICByZXR1cm4gc3RhdHVzO1xuICAgICAgICB9XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMucmVzdHJpY3RTdGF0dXM7XG5cbiAgICAgICAgcGFnZSA9IHN0YXR1cy51c2VTdGF0dXNYWVxuICAgICAgICAgICAgPyBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfVxuICAgICAgICAgICAgOiBwYWdlID0gdXRpbHMuZXh0ZW5kKHt9LCBwYWdlQ29vcmRzKTtcblxuICAgICAgICBpZiAoc3RhdHVzLnNuYXAgJiYgc3RhdHVzLnNuYXAubG9ja2VkKSB7XG4gICAgICAgICAgICBwYWdlLnggKz0gc3RhdHVzLnNuYXAuZHggfHwgMDtcbiAgICAgICAgICAgIHBhZ2UueSArPSBzdGF0dXMuc25hcC5keSB8fCAwO1xuICAgICAgICB9XG5cbiAgICAgICAgcGFnZS54IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeTtcblxuICAgICAgICBzdGF0dXMuZHggPSAwO1xuICAgICAgICBzdGF0dXMuZHkgPSAwO1xuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlO1xuXG4gICAgICAgIHZhciByZWN0LCByZXN0cmljdGVkWCwgcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgaWYgKHJlc3RyaWN0aW9uID09PSAncGFyZW50Jykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gc2NvcGUucGFyZW50RWxlbWVudCh0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAocmVzdHJpY3Rpb24gPT09ICdzZWxmJykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gdGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gc2NvcGUuY2xvc2VzdCh0aGlzLmVsZW1lbnQsIHJlc3RyaWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFyZXN0cmljdGlvbikgeyByZXR1cm4gc3RhdHVzOyB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihyZXN0cmljdGlvbikpIHtcbiAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gcmVzdHJpY3Rpb24ocGFnZS54LCBwYWdlLnksIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodXRpbHMuaXNFbGVtZW50KHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgcmVzdHJpY3Rpb24gPSBzY29wZS5nZXRFbGVtZW50UmVjdChyZXN0cmljdGlvbik7XG4gICAgICAgIH1cblxuICAgICAgICByZWN0ID0gcmVzdHJpY3Rpb247XG5cbiAgICAgICAgaWYgKCFyZXN0cmljdGlvbikge1xuICAgICAgICAgICAgcmVzdHJpY3RlZFggPSBwYWdlLng7XG4gICAgICAgICAgICByZXN0cmljdGVkWSA9IHBhZ2UueTtcbiAgICAgICAgfVxuICAgICAgICAvLyBvYmplY3QgaXMgYXNzdW1lZCB0byBoYXZlXG4gICAgICAgIC8vIHgsIHksIHdpZHRoLCBoZWlnaHQgb3JcbiAgICAgICAgLy8gbGVmdCwgdG9wLCByaWdodCwgYm90dG9tXG4gICAgICAgIGVsc2UgaWYgKCd4JyBpbiByZXN0cmljdGlvbiAmJiAneScgaW4gcmVzdHJpY3Rpb24pIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC54ICsgcmVjdC53aWR0aCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC54ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC55ICsgcmVjdC5oZWlnaHQgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC55ICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5yaWdodCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC5sZWZ0ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5ib3R0b20gLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC50b3AgICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IHJlc3RyaWN0ZWRYIC0gcGFnZS54O1xuICAgICAgICBzdGF0dXMuZHkgPSByZXN0cmljdGVkWSAtIHBhZ2UueTtcblxuICAgICAgICBzdGF0dXMuY2hhbmdlZCA9IHN0YXR1cy5yZXN0cmljdGVkWCAhPT0gcmVzdHJpY3RlZFggfHwgc3RhdHVzLnJlc3RyaWN0ZWRZICE9PSByZXN0cmljdGVkWTtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWQgPSAhIShzdGF0dXMuZHggfHwgc3RhdHVzLmR5KTtcblxuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZFggPSByZXN0cmljdGVkWDtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWRZID0gcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgcmV0dXJuIHN0YXR1cztcbiAgICB9LFxuXG4gICAgY2hlY2tBbmRQcmV2ZW50RGVmYXVsdDogZnVuY3Rpb24gKGV2ZW50LCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKCEoaW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlIHx8IHRoaXMudGFyZ2V0KSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICAgICAgcHJldmVudCA9IG9wdGlvbnMucHJldmVudERlZmF1bHQ7XG5cbiAgICAgICAgaWYgKHByZXZlbnQgPT09ICdhdXRvJyAmJiBlbGVtZW50ICYmICEvXihpbnB1dHxzZWxlY3R8dGV4dGFyZWEpJC9pLnRlc3QoZXZlbnQudGFyZ2V0Lm5vZGVOYW1lKSkge1xuICAgICAgICAgICAgLy8gZG8gbm90IHByZXZlbnREZWZhdWx0IG9uIHBvaW50ZXJkb3duIGlmIHRoZSBwcmVwYXJlZCBhY3Rpb24gaXMgYSBkcmFnXG4gICAgICAgICAgICAvLyBhbmQgZHJhZ2dpbmcgY2FuIG9ubHkgc3RhcnQgZnJvbSBhIGNlcnRhaW4gZGlyZWN0aW9uIC0gdGhpcyBhbGxvd3NcbiAgICAgICAgICAgIC8vIGEgdG91Y2ggdG8gcGFuIHRoZSB2aWV3cG9ydCBpZiBhIGRyYWcgaXNuJ3QgaW4gdGhlIHJpZ2h0IGRpcmVjdGlvblxuICAgICAgICAgICAgaWYgKC9kb3dufHN0YXJ0L2kudGVzdChldmVudC50eXBlKVxuICAgICAgICAgICAgICAgICYmIHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnICYmIG9wdGlvbnMuZHJhZy5heGlzICE9PSAneHknKSB7XG5cbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHdpdGggbWFudWFsU3RhcnQsIG9ubHkgcHJldmVudERlZmF1bHQgd2hpbGUgaW50ZXJhY3RpbmdcbiAgICAgICAgICAgIGlmIChvcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0gJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgJiYgIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChwcmV2ZW50ID09PSAnYWx3YXlzJykge1xuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBjYWxjSW5lcnRpYTogZnVuY3Rpb24gKHN0YXR1cykge1xuICAgICAgICB2YXIgaW5lcnRpYU9wdGlvbnMgPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYSxcbiAgICAgICAgICAgIGxhbWJkYSA9IGluZXJ0aWFPcHRpb25zLnJlc2lzdGFuY2UsXG4gICAgICAgICAgICBpbmVydGlhRHVyID0gLU1hdGgubG9nKGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkIC8gc3RhdHVzLnYwKSAvIGxhbWJkYTtcblxuICAgICAgICBzdGF0dXMueDAgPSB0aGlzLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgc3RhdHVzLnkwID0gdGhpcy5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIHN0YXR1cy50MCA9IHN0YXR1cy5zdGFydEV2ZW50LnRpbWVTdGFtcCAvIDEwMDA7XG4gICAgICAgIHN0YXR1cy5zeCA9IHN0YXR1cy5zeSA9IDA7XG5cbiAgICAgICAgc3RhdHVzLm1vZGlmaWVkWGUgPSBzdGF0dXMueGUgPSAoc3RhdHVzLnZ4MCAtIGluZXJ0aWFEdXIpIC8gbGFtYmRhO1xuICAgICAgICBzdGF0dXMubW9kaWZpZWRZZSA9IHN0YXR1cy55ZSA9IChzdGF0dXMudnkwIC0gaW5lcnRpYUR1cikgLyBsYW1iZGE7XG4gICAgICAgIHN0YXR1cy50ZSA9IGluZXJ0aWFEdXI7XG5cbiAgICAgICAgc3RhdHVzLmxhbWJkYV92MCA9IGxhbWJkYSAvIHN0YXR1cy52MDtcbiAgICAgICAgc3RhdHVzLm9uZV92ZV92MCA9IDEgLSBpbmVydGlhT3B0aW9ucy5lbmRTcGVlZCAvIHN0YXR1cy52MDtcbiAgICB9LFxuXG4gICAgYXV0b1Njcm9sbE1vdmU6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIGlmICghKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgJiYgc2NvcGUuY2hlY2tBdXRvU2Nyb2xsKHRoaXMudGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUpIHtcbiAgICAgICAgICAgIHNjb3BlLmF1dG9TY3JvbGwueCA9IHNjb3BlLmF1dG9TY3JvbGwueSA9IDA7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdG9wLFxuICAgICAgICAgICAgcmlnaHQsXG4gICAgICAgICAgICBib3R0b20sXG4gICAgICAgICAgICBsZWZ0LFxuICAgICAgICAgICAgb3B0aW9ucyA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5hdXRvU2Nyb2xsLFxuICAgICAgICAgICAgY29udGFpbmVyID0gb3B0aW9ucy5jb250YWluZXIgfHwgc2NvcGUuZ2V0V2luZG93KHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKHNjb3BlLmlzV2luZG93KGNvbnRhaW5lcikpIHtcbiAgICAgICAgICAgIGxlZnQgICA9IHBvaW50ZXIuY2xpZW50WCA8IHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgc2NvcGUuYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICByaWdodCAgPSBwb2ludGVyLmNsaWVudFggPiBjb250YWluZXIuaW5uZXJXaWR0aCAgLSBzY29wZS5hdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIGJvdHRvbSA9IHBvaW50ZXIuY2xpZW50WSA+IGNvbnRhaW5lci5pbm5lckhlaWdodCAtIHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdmFyIHJlY3QgPSBzY29wZS5nZXRFbGVtZW50UmVjdChjb250YWluZXIpO1xuXG4gICAgICAgICAgICBsZWZ0ICAgPSBwb2ludGVyLmNsaWVudFggPCByZWN0LmxlZnQgICArIHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgcmVjdC50b3AgICAgKyBzY29wZS5hdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIHJpZ2h0ICA9IHBvaW50ZXIuY2xpZW50WCA+IHJlY3QucmlnaHQgIC0gc2NvcGUuYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICBib3R0b20gPSBwb2ludGVyLmNsaWVudFkgPiByZWN0LmJvdHRvbSAtIHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG5cbiAgICAgICAgc2NvcGUuYXV0b1Njcm9sbC54ID0gKHJpZ2h0ID8gMTogbGVmdD8gLTE6IDApO1xuICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLnkgPSAoYm90dG9tPyAxOiAgdG9wPyAtMTogMCk7XG5cbiAgICAgICAgaWYgKCFzY29wZS5hdXRvU2Nyb2xsLmlzU2Nyb2xsaW5nKSB7XG4gICAgICAgICAgICAvLyBzZXQgdGhlIGF1dG9TY3JvbGwgcHJvcGVydGllcyB0byB0aG9zZSBvZiB0aGUgdGFyZ2V0XG4gICAgICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLm1hcmdpbiA9IG9wdGlvbnMubWFyZ2luO1xuICAgICAgICAgICAgc2NvcGUuYXV0b1Njcm9sbC5zcGVlZCAgPSBvcHRpb25zLnNwZWVkO1xuXG4gICAgICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLnN0YXJ0KHRoaXMpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIF91cGRhdGVFdmVudFRhcmdldHM6IGZ1bmN0aW9uICh0YXJnZXQsIGN1cnJlbnRUYXJnZXQpIHtcbiAgICAgICAgdGhpcy5fZXZlbnRUYXJnZXQgICAgPSB0YXJnZXQ7XG4gICAgICAgIHRoaXMuX2N1ckV2ZW50VGFyZ2V0ID0gY3VycmVudFRhcmdldDtcbiAgICB9XG5cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gSW50ZXJhY3Rpb247IiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgcmFmICAgICAgID0gcmVxdWlyZSgnLi91dGlscy9yYWYnKSxcbiAgICBnZXRXaW5kb3cgPSByZXF1aXJlKCcuL3V0aWxzL3dpbmRvdycpLmdldFdpbmRvdyxcbiAgICBpc1dpbmRvdyAgPSByZXF1aXJlKCcuL3V0aWxzL2lzVHlwZScpLmlzV2luZG93O1xuXG52YXIgYXV0b1Njcm9sbCA9IHtcblxuICAgIGludGVyYWN0aW9uOiBudWxsLFxuICAgIGk6IG51bGwsICAgIC8vIHRoZSBoYW5kbGUgcmV0dXJuZWQgYnkgd2luZG93LnNldEludGVydmFsXG4gICAgeDogMCwgeTogMCwgLy8gRGlyZWN0aW9uIGVhY2ggcHVsc2UgaXMgdG8gc2Nyb2xsIGluXG5cbiAgICBpc1Njcm9sbGluZzogZmFsc2UsXG4gICAgcHJldlRpbWU6IDAsXG5cbiAgICBzdGFydDogZnVuY3Rpb24gKGludGVyYWN0aW9uKSB7XG4gICAgICAgIGF1dG9TY3JvbGwuaXNTY3JvbGxpbmcgPSB0cnVlO1xuICAgICAgICByYWYuY2FuY2VsKGF1dG9TY3JvbGwuaSk7XG5cbiAgICAgICAgYXV0b1Njcm9sbC5pbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uO1xuICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgIGF1dG9TY3JvbGwuaSA9IHJhZi5yZXF1ZXN0KGF1dG9TY3JvbGwuc2Nyb2xsKTtcbiAgICB9LFxuXG4gICAgc3RvcDogZnVuY3Rpb24gKCkge1xuICAgICAgICBhdXRvU2Nyb2xsLmlzU2Nyb2xsaW5nID0gZmFsc2U7XG4gICAgICAgIHJhZi5jYW5jZWwoYXV0b1Njcm9sbC5pKTtcbiAgICB9LFxuXG4gICAgLy8gc2Nyb2xsIHRoZSB3aW5kb3cgYnkgdGhlIHZhbHVlcyBpbiBzY3JvbGwueC95XG4gICAgc2Nyb2xsOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBvcHRpb25zID0gYXV0b1Njcm9sbC5pbnRlcmFjdGlvbi50YXJnZXQub3B0aW9uc1thdXRvU2Nyb2xsLmludGVyYWN0aW9uLnByZXBhcmVkLm5hbWVdLmF1dG9TY3JvbGwsXG4gICAgICAgICAgICBjb250YWluZXIgPSBvcHRpb25zLmNvbnRhaW5lciB8fCBnZXRXaW5kb3coYXV0b1Njcm9sbC5pbnRlcmFjdGlvbi5lbGVtZW50KSxcbiAgICAgICAgICAgIG5vdyA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpLFxuICAgICAgICAgICAgLy8gY2hhbmdlIGluIHRpbWUgaW4gc2Vjb25kc1xuICAgICAgICAgICAgZHQgPSAobm93IC0gYXV0b1Njcm9sbC5wcmV2VGltZSkgLyAxMDAwLFxuICAgICAgICAgICAgLy8gZGlzcGxhY2VtZW50XG4gICAgICAgICAgICBzID0gb3B0aW9ucy5zcGVlZCAqIGR0O1xuXG4gICAgICAgIGlmIChzID49IDEpIHtcbiAgICAgICAgICAgIGlmIChpc1dpbmRvdyhjb250YWluZXIpKSB7XG4gICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbEJ5KGF1dG9TY3JvbGwueCAqIHMsIGF1dG9TY3JvbGwueSAqIHMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoY29udGFpbmVyKSB7XG4gICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbExlZnQgKz0gYXV0b1Njcm9sbC54ICogcztcbiAgICAgICAgICAgICAgICBjb250YWluZXIuc2Nyb2xsVG9wICArPSBhdXRvU2Nyb2xsLnkgKiBzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbm93O1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGF1dG9TY3JvbGwuaXNTY3JvbGxpbmcpIHtcbiAgICAgICAgICAgIHJhZi5jYW5jZWwoYXV0b1Njcm9sbC5pKTtcbiAgICAgICAgICAgIGF1dG9TY3JvbGwuaSA9IHJhZi5yZXF1ZXN0KGF1dG9TY3JvbGwuc2Nyb2xsKTtcbiAgICAgICAgfVxuICAgIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gYXV0b1Njcm9sbDtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSB7XG4gICAgYmFzZToge1xuICAgICAgICBhY2NlcHQgICAgICAgIDogbnVsbCxcbiAgICAgICAgYWN0aW9uQ2hlY2tlciA6IG51bGwsXG4gICAgICAgIHN0eWxlQ3Vyc29yICAgOiB0cnVlLFxuICAgICAgICBwcmV2ZW50RGVmYXVsdDogJ2F1dG8nLFxuICAgICAgICBvcmlnaW4gICAgICAgIDogeyB4OiAwLCB5OiAwIH0sXG4gICAgICAgIGRlbHRhU291cmNlICAgOiAncGFnZScsXG4gICAgICAgIGFsbG93RnJvbSAgICAgOiBudWxsLFxuICAgICAgICBpZ25vcmVGcm9tICAgIDogbnVsbCxcbiAgICAgICAgX2NvbnRleHQgICAgICA6IHJlcXVpcmUoJy4vdXRpbHMvZG9tT2JqZWN0cycpLmRvY3VtZW50LFxuICAgICAgICBkcm9wQ2hlY2tlciAgIDogbnVsbFxuICAgIH0sXG5cbiAgICBkcmFnOiB7XG4gICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICBtYW51YWxTdGFydDogdHJ1ZSxcbiAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICBzbmFwOiBudWxsLFxuICAgICAgICByZXN0cmljdDogbnVsbCxcbiAgICAgICAgaW5lcnRpYTogbnVsbCxcbiAgICAgICAgYXV0b1Njcm9sbDogbnVsbCxcblxuICAgICAgICBheGlzOiAneHknLFxuICAgIH0sXG5cbiAgICBkcm9wOiB7XG4gICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICBhY2NlcHQ6IG51bGwsXG4gICAgICAgIG92ZXJsYXA6ICdwb2ludGVyJ1xuICAgIH0sXG5cbiAgICByZXNpemU6IHtcbiAgICAgICAgZW5hYmxlZDogZmFsc2UsXG4gICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICBzbmFwOiBudWxsLFxuICAgICAgICByZXN0cmljdDogbnVsbCxcbiAgICAgICAgaW5lcnRpYTogbnVsbCxcbiAgICAgICAgYXV0b1Njcm9sbDogbnVsbCxcblxuICAgICAgICBzcXVhcmU6IGZhbHNlLFxuICAgICAgICBheGlzOiAneHknLFxuXG4gICAgICAgIC8vIHVzZSBkZWZhdWx0IG1hcmdpblxuICAgICAgICBtYXJnaW46IE5hTixcblxuICAgICAgICAvLyBvYmplY3Qgd2l0aCBwcm9wcyBsZWZ0LCByaWdodCwgdG9wLCBib3R0b20gd2hpY2ggYXJlXG4gICAgICAgIC8vIHRydWUvZmFsc2UgdmFsdWVzIHRvIHJlc2l6ZSB3aGVuIHRoZSBwb2ludGVyIGlzIG92ZXIgdGhhdCBlZGdlLFxuICAgICAgICAvLyBDU1Mgc2VsZWN0b3JzIHRvIG1hdGNoIHRoZSBoYW5kbGVzIGZvciBlYWNoIGRpcmVjdGlvblxuICAgICAgICAvLyBvciB0aGUgRWxlbWVudHMgZm9yIGVhY2ggaGFuZGxlXG4gICAgICAgIGVkZ2VzOiBudWxsLFxuXG4gICAgICAgIC8vIGEgdmFsdWUgb2YgJ25vbmUnIHdpbGwgbGltaXQgdGhlIHJlc2l6ZSByZWN0IHRvIGEgbWluaW11bSBvZiAweDBcbiAgICAgICAgLy8gJ25lZ2F0ZScgd2lsbCBhbG93IHRoZSByZWN0IHRvIGhhdmUgbmVnYXRpdmUgd2lkdGgvaGVpZ2h0XG4gICAgICAgIC8vICdyZXBvc2l0aW9uJyB3aWxsIGtlZXAgdGhlIHdpZHRoL2hlaWdodCBwb3NpdGl2ZSBieSBzd2FwcGluZ1xuICAgICAgICAvLyB0aGUgdG9wIGFuZCBib3R0b20gZWRnZXMgYW5kL29yIHN3YXBwaW5nIHRoZSBsZWZ0IGFuZCByaWdodCBlZGdlc1xuICAgICAgICBpbnZlcnQ6ICdub25lJ1xuICAgIH0sXG5cbiAgICBnZXN0dXJlOiB7XG4gICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgZW5hYmxlZDogZmFsc2UsXG4gICAgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgIG1heFBlckVsZW1lbnQ6IDEsXG5cbiAgICAgICAgcmVzdHJpY3Q6IG51bGxcbiAgICB9LFxuXG4gICAgcGVyQWN0aW9uOiB7XG4gICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICBzbmFwOiB7XG4gICAgICAgICAgICBlbmFibGVkICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgZW5kT25seSAgICAgOiBmYWxzZSxcbiAgICAgICAgICAgIHJhbmdlICAgICAgIDogSW5maW5pdHksXG4gICAgICAgICAgICB0YXJnZXRzICAgICA6IG51bGwsXG4gICAgICAgICAgICBvZmZzZXRzICAgICA6IG51bGwsXG5cbiAgICAgICAgICAgIHJlbGF0aXZlUG9pbnRzOiBudWxsXG4gICAgICAgIH0sXG5cbiAgICAgICAgcmVzdHJpY3Q6IHtcbiAgICAgICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICAgICAgZW5kT25seTogZmFsc2VcbiAgICAgICAgfSxcblxuICAgICAgICBhdXRvU2Nyb2xsOiB7XG4gICAgICAgICAgICBlbmFibGVkICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgY29udGFpbmVyICAgOiBudWxsLCAgICAgLy8gdGhlIGl0ZW0gdGhhdCBpcyBzY3JvbGxlZCAoV2luZG93IG9yIEhUTUxFbGVtZW50KVxuICAgICAgICAgICAgbWFyZ2luICAgICAgOiA2MCxcbiAgICAgICAgICAgIHNwZWVkICAgICAgIDogMzAwICAgICAgIC8vIHRoZSBzY3JvbGwgc3BlZWQgaW4gcGl4ZWxzIHBlciBzZWNvbmRcbiAgICAgICAgfSxcblxuICAgICAgICBpbmVydGlhOiB7XG4gICAgICAgICAgICBlbmFibGVkICAgICAgICAgIDogZmFsc2UsXG4gICAgICAgICAgICByZXNpc3RhbmNlICAgICAgIDogMTAsICAgIC8vIHRoZSBsYW1iZGEgaW4gZXhwb25lbnRpYWwgZGVjYXlcbiAgICAgICAgICAgIG1pblNwZWVkICAgICAgICAgOiAxMDAsICAgLy8gdGFyZ2V0IHNwZWVkIG11c3QgYmUgYWJvdmUgdGhpcyBmb3IgaW5lcnRpYSB0byBzdGFydFxuICAgICAgICAgICAgZW5kU3BlZWQgICAgICAgICA6IDEwLCAgICAvLyB0aGUgc3BlZWQgYXQgd2hpY2ggaW5lcnRpYSBpcyBzbG93IGVub3VnaCB0byBzdG9wXG4gICAgICAgICAgICBhbGxvd1Jlc3VtZSAgICAgIDogdHJ1ZSwgIC8vIGFsbG93IHJlc3VtaW5nIGFuIGFjdGlvbiBpbiBpbmVydGlhIHBoYXNlXG4gICAgICAgICAgICB6ZXJvUmVzdW1lRGVsdGEgIDogdHJ1ZSwgIC8vIGlmIGFuIGFjdGlvbiBpcyByZXN1bWVkIGFmdGVyIGxhdW5jaCwgc2V0IGR4L2R5IHRvIDBcbiAgICAgICAgICAgIHNtb290aEVuZER1cmF0aW9uOiAzMDAgICAgLy8gYW5pbWF0ZSB0byBzbmFwL3Jlc3RyaWN0IGVuZE9ubHkgaWYgdGhlcmUncyBubyBpbmVydGlhXG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgX2hvbGREdXJhdGlvbjogNjAwXG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgc2NvcGUgPSB7fSxcbiAgICBleHRlbmQgPSByZXF1aXJlKCcuL3V0aWxzL2V4dGVuZCcpO1xuXG5leHRlbmQoc2NvcGUsIHJlcXVpcmUoJy4vdXRpbHMvd2luZG93JykpO1xuZXh0ZW5kKHNjb3BlLCByZXF1aXJlKCcuL3V0aWxzL2RvbU9iamVjdHMnKSk7XG5leHRlbmQoc2NvcGUsIHJlcXVpcmUoJy4vdXRpbHMvYXJyLmpzJykpO1xuZXh0ZW5kKHNjb3BlLCByZXF1aXJlKCcuL3V0aWxzL2lzVHlwZScpKTtcblxubW9kdWxlLmV4cG9ydHMgPSBzY29wZTtcbiIsIid1c2Ugc3RyaWN0JztcblxuZnVuY3Rpb24gaW5kZXhPZiAoYXJyYXksIHRhcmdldCkge1xuICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBhcnJheS5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBpZiAoYXJyYXlbaV0gPT09IHRhcmdldCkge1xuICAgICAgICAgICAgcmV0dXJuIGk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gLTE7XG59XG5cbmZ1bmN0aW9uIGNvbnRhaW5zIChhcnJheSwgdGFyZ2V0KSB7XG4gICAgcmV0dXJuIGluZGV4T2YoYXJyYXksIHRhcmdldCkgIT09IC0xO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHtcbiAgICBpbmRleE9mOiBpbmRleE9mLFxuICAgIGNvbnRhaW5zOiBjb250YWluc1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIHdpbiA9IHJlcXVpcmUoJy4vd2luZG93JyksXG4gICAgZG9tT2JqZWN0cyA9IHJlcXVpcmUoJy4vZG9tT2JqZWN0cycpO1xuXG52YXIgYnJvd3NlciA9IHtcbiAgICAvLyBEb2VzIHRoZSBicm93c2VyIHN1cHBvcnQgdG91Y2ggaW5wdXQ/XG4gICAgc3VwcG9ydHNUb3VjaCA6ICEhKCgnb250b3VjaHN0YXJ0JyBpbiB3aW4pIHx8IHdpbi53aW5kb3cuRG9jdW1lbnRUb3VjaFxuICAgICAgICAmJiBkb21PYmplY3RzLmRvY3VtZW50IGluc3RhbmNlb2Ygd2luLkRvY3VtZW50VG91Y2gpLFxuXG4gICAgLy8gRG9lcyB0aGUgYnJvd3NlciBzdXBwb3J0IFBvaW50ZXJFdmVudHNcbiAgICBzdXBwb3J0c1BvaW50ZXJFdmVudCA6ICEhZG9tT2JqZWN0cy5Qb2ludGVyRXZlbnQsXG5cbiAgICAvLyBPcGVyYSBNb2JpbGUgbXVzdCBiZSBoYW5kbGVkIGRpZmZlcmVudGx5XG4gICAgaXNPcGVyYU1vYmlsZSA6IChuYXZpZ2F0b3IuYXBwTmFtZSA9PT0gJ09wZXJhJ1xuICAgICAgICAmJiBicm93c2VyLnN1cHBvcnRzVG91Y2hcbiAgICAgICAgJiYgbmF2aWdhdG9yLnVzZXJBZ2VudC5tYXRjaCgnUHJlc3RvJykpLFxuXG4gICAgLy8gc2Nyb2xsaW5nIGRvZXNuJ3QgY2hhbmdlIHRoZSByZXN1bHQgb2ZcbiAgICAvLyBnZXRCb3VuZGluZ0NsaWVudFJlY3QvZ2V0Q2xpZW50UmVjdHMgb24gaU9TIDw9NyBidXQgaXQgZG9lcyBvbiBpT1MgOFxuICAgIGlzSU9TN29yTG93ZXIgOiAoL2lQKGhvbmV8b2R8YWQpLy50ZXN0KG5hdmlnYXRvci5wbGF0Zm9ybSkgJiYgL09TIFsxLTddW15cXGRdLy50ZXN0KG5hdmlnYXRvci5hcHBWZXJzaW9uKSksXG5cbiAgICBpc0llOU9yT2xkZXIgOiBkb21PYmplY3RzLmRvY3VtZW50LmFsbCAmJiAhd2luLndpbmRvdy5hdG9iXG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IGJyb3dzZXI7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBkb21PYmplY3RzID0ge30sXG4gICAgd2luID0gcmVxdWlyZSgnLi93aW5kb3cnKS53aW5kb3csXG4gICAgYmxhbmsgPSBmdW5jdGlvbiAoKSB7fTtcblxuZG9tT2JqZWN0cy5kb2N1bWVudCAgICAgICAgICAgPSB3aW4uZG9jdW1lbnQ7XG5kb21PYmplY3RzLkRvY3VtZW50RnJhZ21lbnQgICA9IHdpbi5Eb2N1bWVudEZyYWdtZW50ICAgfHwgYmxhbms7XG5kb21PYmplY3RzLlNWR0VsZW1lbnQgICAgICAgICA9IHdpbi5TVkdFbGVtZW50ICAgICAgICAgfHwgYmxhbms7XG5kb21PYmplY3RzLlNWR1NWR0VsZW1lbnQgICAgICA9IHdpbi5TVkdTVkdFbGVtZW50ICAgICAgfHwgYmxhbms7XG5kb21PYmplY3RzLlNWR0VsZW1lbnRJbnN0YW5jZSA9IHdpbi5TVkdFbGVtZW50SW5zdGFuY2UgfHwgYmxhbms7XG5kb21PYmplY3RzLkhUTUxFbGVtZW50ICAgICAgICA9IHdpbi5IVE1MRWxlbWVudCAgICAgICAgfHwgd2luLkVsZW1lbnQ7XG5cbmRvbU9iamVjdHMuUG9pbnRlckV2ZW50ID0gKHdpbi5Qb2ludGVyRXZlbnQgfHwgd2luLk1TUG9pbnRlckV2ZW50KTtcblxubW9kdWxlLmV4cG9ydHMgPSBkb21PYmplY3RzO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgYXJyID0gcmVxdWlyZSgnLi9hcnInKSxcbiAgICBpbmRleE9mICA9IGFyci5pbmRleE9mLFxuICAgIGNvbnRhaW5zID0gYXJyLmNvbnRhaW5zLFxuICAgIGdldFdpbmRvdyA9IHJlcXVpcmUoJy4vd2luZG93JykuZ2V0V2luZG93LFxuXG4gICAgdXNlQXR0YWNoRXZlbnQgPSAoJ2F0dGFjaEV2ZW50JyBpbiB3aW5kb3cpICYmICEoJ2FkZEV2ZW50TGlzdGVuZXInIGluIHdpbmRvdyksXG4gICAgYWRkRXZlbnQgICAgICAgPSB1c2VBdHRhY2hFdmVudD8gICdhdHRhY2hFdmVudCc6ICdhZGRFdmVudExpc3RlbmVyJyxcbiAgICByZW1vdmVFdmVudCAgICA9IHVzZUF0dGFjaEV2ZW50PyAgJ2RldGFjaEV2ZW50JzogJ3JlbW92ZUV2ZW50TGlzdGVuZXInLFxuICAgIG9uICAgICAgICAgICAgID0gdXNlQXR0YWNoRXZlbnQ/ICdvbic6ICcnLFxuXG4gICAgZWxlbWVudHMgICAgICAgICAgPSBbXSxcbiAgICB0YXJnZXRzICAgICAgICAgICA9IFtdLFxuICAgIGF0dGFjaGVkTGlzdGVuZXJzID0gW107XG5cbmZ1bmN0aW9uIGFkZCAoZWxlbWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICB2YXIgZWxlbWVudEluZGV4ID0gaW5kZXhPZihlbGVtZW50cywgZWxlbWVudCksXG4gICAgICAgIHRhcmdldCA9IHRhcmdldHNbZWxlbWVudEluZGV4XTtcblxuICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgIHRhcmdldCA9IHtcbiAgICAgICAgICAgIGV2ZW50czoge30sXG4gICAgICAgICAgICB0eXBlQ291bnQ6IDBcbiAgICAgICAgfTtcblxuICAgICAgICBlbGVtZW50SW5kZXggPSBlbGVtZW50cy5wdXNoKGVsZW1lbnQpIC0gMTtcbiAgICAgICAgdGFyZ2V0cy5wdXNoKHRhcmdldCk7XG5cbiAgICAgICAgYXR0YWNoZWRMaXN0ZW5lcnMucHVzaCgodXNlQXR0YWNoRXZlbnQgPyB7XG4gICAgICAgICAgICAgICAgc3VwcGxpZWQ6IFtdLFxuICAgICAgICAgICAgICAgIHdyYXBwZWQgOiBbXSxcbiAgICAgICAgICAgICAgICB1c2VDb3VudDogW11cbiAgICAgICAgICAgIH0gOiBudWxsKSk7XG4gICAgfVxuXG4gICAgaWYgKCF0YXJnZXQuZXZlbnRzW3R5cGVdKSB7XG4gICAgICAgIHRhcmdldC5ldmVudHNbdHlwZV0gPSBbXTtcbiAgICAgICAgdGFyZ2V0LnR5cGVDb3VudCsrO1xuICAgIH1cblxuICAgIGlmICghY29udGFpbnModGFyZ2V0LmV2ZW50c1t0eXBlXSwgbGlzdGVuZXIpKSB7XG4gICAgICAgIHZhciByZXQ7XG5cbiAgICAgICAgaWYgKHVzZUF0dGFjaEV2ZW50KSB7XG4gICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gYXR0YWNoZWRMaXN0ZW5lcnNbZWxlbWVudEluZGV4XSxcbiAgICAgICAgICAgICAgICBsaXN0ZW5lckluZGV4ID0gaW5kZXhPZihsaXN0ZW5lcnMuc3VwcGxpZWQsIGxpc3RlbmVyKTtcblxuICAgICAgICAgICAgdmFyIHdyYXBwZWQgPSBsaXN0ZW5lcnMud3JhcHBlZFtsaXN0ZW5lckluZGV4XSB8fCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgICAgICBpZiAoIWV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZCkge1xuICAgICAgICAgICAgICAgICAgICBldmVudC50YXJnZXQgPSBldmVudC5zcmNFbGVtZW50O1xuICAgICAgICAgICAgICAgICAgICBldmVudC5jdXJyZW50VGFyZ2V0ID0gZWxlbWVudDtcblxuICAgICAgICAgICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCA9IGV2ZW50LnByZXZlbnREZWZhdWx0IHx8IHByZXZlbnREZWY7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbiA9IGV2ZW50LnN0b3BQcm9wYWdhdGlvbiB8fCBzdG9wUHJvcDtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uID0gZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uIHx8IHN0b3BJbW1Qcm9wO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmICgvbW91c2V8Y2xpY2svLnRlc3QoZXZlbnQudHlwZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50LnBhZ2VYID0gZXZlbnQuY2xpZW50WCArIGdldFdpbmRvdyhlbGVtZW50KS5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsTGVmdDtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50LnBhZ2VZID0gZXZlbnQuY2xpZW50WSArIGdldFdpbmRvdyhlbGVtZW50KS5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsVG9wO1xuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXIoZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIHJldCA9IGVsZW1lbnRbYWRkRXZlbnRdKG9uICsgdHlwZSwgd3JhcHBlZCwgQm9vbGVhbih1c2VDYXB0dXJlKSk7XG5cbiAgICAgICAgICAgIGlmIChsaXN0ZW5lckluZGV4ID09PSAtMSkge1xuICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zdXBwbGllZC5wdXNoKGxpc3RlbmVyKTtcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnMud3JhcHBlZC5wdXNoKHdyYXBwZWQpO1xuICAgICAgICAgICAgICAgIGxpc3RlbmVycy51c2VDb3VudC5wdXNoKDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgbGlzdGVuZXJzLnVzZUNvdW50W2xpc3RlbmVySW5kZXhdKys7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICByZXQgPSBlbGVtZW50W2FkZEV2ZW50XSh0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSB8fCBmYWxzZSk7XG4gICAgICAgIH1cbiAgICAgICAgdGFyZ2V0LmV2ZW50c1t0eXBlXS5wdXNoKGxpc3RlbmVyKTtcblxuICAgICAgICByZXR1cm4gcmV0O1xuICAgIH1cbn1cblxuZnVuY3Rpb24gcmVtb3ZlIChlbGVtZW50LCB0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgIHZhciBpLFxuICAgICAgICBlbGVtZW50SW5kZXggPSBpbmRleE9mKGVsZW1lbnRzLCBlbGVtZW50KSxcbiAgICAgICAgdGFyZ2V0ID0gdGFyZ2V0c1tlbGVtZW50SW5kZXhdLFxuICAgICAgICBsaXN0ZW5lcnMsXG4gICAgICAgIGxpc3RlbmVySW5kZXgsXG4gICAgICAgIHdyYXBwZWQgPSBsaXN0ZW5lcjtcblxuICAgIGlmICghdGFyZ2V0IHx8ICF0YXJnZXQuZXZlbnRzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAodXNlQXR0YWNoRXZlbnQpIHtcbiAgICAgICAgbGlzdGVuZXJzID0gYXR0YWNoZWRMaXN0ZW5lcnNbZWxlbWVudEluZGV4XTtcbiAgICAgICAgbGlzdGVuZXJJbmRleCA9IGluZGV4T2YobGlzdGVuZXJzLnN1cHBsaWVkLCBsaXN0ZW5lcik7XG4gICAgICAgIHdyYXBwZWQgPSBsaXN0ZW5lcnMud3JhcHBlZFtsaXN0ZW5lckluZGV4XTtcbiAgICB9XG5cbiAgICBpZiAodHlwZSA9PT0gJ2FsbCcpIHtcbiAgICAgICAgZm9yICh0eXBlIGluIHRhcmdldC5ldmVudHMpIHtcbiAgICAgICAgICAgIGlmICh0YXJnZXQuZXZlbnRzLmhhc093blByb3BlcnR5KHR5cGUpKSB7XG4gICAgICAgICAgICAgICAgcmVtb3ZlKGVsZW1lbnQsIHR5cGUsICdhbGwnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKHRhcmdldC5ldmVudHNbdHlwZV0pIHtcbiAgICAgICAgdmFyIGxlbiA9IHRhcmdldC5ldmVudHNbdHlwZV0ubGVuZ3RoO1xuXG4gICAgICAgIGlmIChsaXN0ZW5lciA9PT0gJ2FsbCcpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIHJlbW92ZShlbGVtZW50LCB0eXBlLCB0YXJnZXQuZXZlbnRzW3R5cGVdW2ldLCBCb29sZWFuKHVzZUNhcHR1cmUpKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIGlmICh0YXJnZXQuZXZlbnRzW3R5cGVdW2ldID09PSBsaXN0ZW5lcikge1xuICAgICAgICAgICAgICAgICAgICBlbGVtZW50W3JlbW92ZUV2ZW50XShvbiArIHR5cGUsIHdyYXBwZWQsIHVzZUNhcHR1cmUgfHwgZmFsc2UpO1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICBpZiAodXNlQXR0YWNoRXZlbnQgJiYgbGlzdGVuZXJzKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0tLTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0gPT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMuc3VwcGxpZWQuc3BsaWNlKGxpc3RlbmVySW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy53cmFwcGVkLnNwbGljZShsaXN0ZW5lckluZGV4LCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnQuc3BsaWNlKGxpc3RlbmVySW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRhcmdldC5ldmVudHNbdHlwZV0gJiYgdGFyZ2V0LmV2ZW50c1t0eXBlXS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHRhcmdldC5ldmVudHNbdHlwZV0gPSBudWxsO1xuICAgICAgICAgICAgdGFyZ2V0LnR5cGVDb3VudC0tO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKCF0YXJnZXQudHlwZUNvdW50KSB7XG4gICAgICAgIHRhcmdldHMuc3BsaWNlKGVsZW1lbnRJbmRleCwgMSk7XG4gICAgICAgIGVsZW1lbnRzLnNwbGljZShlbGVtZW50SW5kZXgsIDEpO1xuICAgICAgICBhdHRhY2hlZExpc3RlbmVycy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIHByZXZlbnREZWYgKCkge1xuICAgIHRoaXMucmV0dXJuVmFsdWUgPSBmYWxzZTtcbn1cblxuZnVuY3Rpb24gc3RvcFByb3AgKCkge1xuICAgIHRoaXMuY2FuY2VsQnViYmxlID0gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gc3RvcEltbVByb3AgKCkge1xuICAgIHRoaXMuY2FuY2VsQnViYmxlID0gdHJ1ZTtcbiAgICB0aGlzLmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZCA9IHRydWU7XG59XG5cbm1vZHVsZS5leHBvcnRzID0ge1xuICAgIGFkZDogYWRkLFxuICAgIHJlbW92ZTogcmVtb3ZlLFxuICAgIHVzZUF0dGFjaEV2ZW50OiB1c2VBdHRhY2hFdmVudCxcblxuICAgIF9lbGVtZW50czogZWxlbWVudHMsXG4gICAgX3RhcmdldHM6IHRhcmdldHMsXG4gICAgX2F0dGFjaGVkTGlzdGVuZXJzOiBhdHRhY2hlZExpc3RlbmVyc1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBleHRlbmQgKGRlc3QsIHNvdXJjZSkge1xuICAgIGZvciAodmFyIHByb3AgaW4gc291cmNlKSB7XG4gICAgICAgIGRlc3RbcHJvcF0gPSBzb3VyY2VbcHJvcF07XG4gICAgfVxuICAgIHJldHVybiBkZXN0O1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBoeXBvdCAoeCwgeSkgeyByZXR1cm4gTWF0aC5zcXJ0KHggKiB4ICsgeSAqIHkpOyB9O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgdXRpbHMgPSB7fSxcbiAgICBleHRlbmQgPSByZXF1aXJlKCcuL2V4dGVuZCcpLFxuICAgIHdpbiA9IHJlcXVpcmUoJy4vd2luZG93Jyk7XG5cbnV0aWxzLmJsYW5rICA9IGZ1bmN0aW9uICgpIHt9O1xuXG51dGlscy53YXJuT25jZSA9IGZ1bmN0aW9uIChtZXRob2QsIG1lc3NhZ2UpIHtcbiAgICB2YXIgd2FybmVkID0gZmFsc2U7XG5cbiAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoIXdhcm5lZCkge1xuICAgICAgICAgICAgd2luLndpbmRvdy5jb25zb2xlLndhcm4obWVzc2FnZSk7XG4gICAgICAgICAgICB3YXJuZWQgPSB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG1ldGhvZC5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgIH07XG59O1xuXG51dGlscy5leHRlbmQgID0gZXh0ZW5kO1xudXRpbHMuaHlwb3QgICA9IHJlcXVpcmUoJy4vaHlwb3QnKTtcbnV0aWxzLnJhZiAgICAgPSByZXF1aXJlKCcuL3JhZicpO1xudXRpbHMuYnJvd3NlciA9IHJlcXVpcmUoJy4vYnJvd3NlcicpO1xuXG5leHRlbmQodXRpbHMsIHJlcXVpcmUoJy4vYXJyJykpO1xuZXh0ZW5kKHV0aWxzLCByZXF1aXJlKCcuL2lzVHlwZScpKTtcbmV4dGVuZCh1dGlscywgcmVxdWlyZSgnLi9wb2ludGVyVXRpbHMnKSk7XG5cbm1vZHVsZS5leHBvcnRzID0gdXRpbHM7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciB3aW4gPSByZXF1aXJlKCcuL3dpbmRvdycpLFxuICAgIGRvbU9iamVjdHMgPSByZXF1aXJlKCcuL2RvbU9iamVjdHMnKTtcblxubW9kdWxlLmV4cG9ydHMuaXNFbGVtZW50ID0gZnVuY3Rpb24gKG8pIHtcbiAgICBpZiAoIW8gfHwgKHR5cGVvZiBvICE9PSAnb2JqZWN0JykpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICB2YXIgX3dpbmRvdyA9IHdpbi5nZXRXaW5kb3cobykgfHwgd2luLndpbmRvdztcblxuICAgIHJldHVybiAoL29iamVjdHxmdW5jdGlvbi8udGVzdCh0eXBlb2YgX3dpbmRvdy5FbGVtZW50KVxuICAgICAgICA/IG8gaW5zdGFuY2VvZiBfd2luZG93LkVsZW1lbnQgLy9ET00yXG4gICAgICAgIDogby5ub2RlVHlwZSA9PT0gMSAmJiB0eXBlb2Ygby5ub2RlTmFtZSA9PT0gXCJzdHJpbmdcIik7XG59O1xuXG5tb2R1bGUuZXhwb3J0cy5pc1dpbmRvdyAgID0gZnVuY3Rpb24gKHRoaW5nKSB7IHJldHVybiAhISh0aGluZyAmJiB0aGluZy5XaW5kb3cpICYmICh0aGluZyBpbnN0YW5jZW9mIHRoaW5nLldpbmRvdyk7IH07XG5tb2R1bGUuZXhwb3J0cy5pc0RvY0ZyYWcgID0gZnVuY3Rpb24gKHRoaW5nKSB7IHJldHVybiAhIXRoaW5nICYmIHRoaW5nIGluc3RhbmNlb2YgZG9tT2JqZWN0cy5Eb2N1bWVudEZyYWdtZW50OyB9O1xubW9kdWxlLmV4cG9ydHMuaXNBcnJheSAgICA9IGZ1bmN0aW9uICh0aGluZykge1xuICAgIHJldHVybiBtb2R1bGUuZXhwb3J0cy5pc09iamVjdCh0aGluZylcbiAgICAmJiAodHlwZW9mIHRoaW5nLmxlbmd0aCAhPT0gdW5kZWZpbmVkKVxuICAgICYmIG1vZHVsZS5leHBvcnRzLmlzRnVuY3Rpb24odGhpbmcuc3BsaWNlKTtcbn07XG5tb2R1bGUuZXhwb3J0cy5pc09iamVjdCAgID0gZnVuY3Rpb24gKHRoaW5nKSB7IHJldHVybiAhIXRoaW5nICYmICh0eXBlb2YgdGhpbmcgPT09ICdvYmplY3QnKTsgfTtcbm1vZHVsZS5leHBvcnRzLmlzRnVuY3Rpb24gPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ2Z1bmN0aW9uJzsgfTtcbm1vZHVsZS5leHBvcnRzLmlzTnVtYmVyICAgPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ251bWJlcicgIDsgfTtcbm1vZHVsZS5leHBvcnRzLmlzQm9vbCAgICAgPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ2Jvb2xlYW4nIDsgfTtcbm1vZHVsZS5leHBvcnRzLmlzU3RyaW5nICAgPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ3N0cmluZycgIDsgfTtcblxuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgcG9pbnRlclV0aWxzID0ge30sXG4gICAgLy8gcmVkdWNlIG9iamVjdCBjcmVhdGlvbiBpbiBnZXRYWSgpXG4gICAgdG1wWFkgPSB7fSxcbiAgICB3aW4gPSByZXF1aXJlKCcuL3dpbmRvdycpLFxuICAgIGh5cG90ID0gcmVxdWlyZSgnLi9oeXBvdCcpLFxuICAgIGV4dGVuZCA9IHJlcXVpcmUoJy4vZXh0ZW5kJyksXG5cbiAgICAvLyBzY29wZSBzaG91bGRuJ3QgYmUgbmVjZXNzYXJ5IGluIHRoaXMgbW9kdWxlXG4gICAgc2NvcGUgPSByZXF1aXJlKCcuLi9zY29wZScpO1xuXG5wb2ludGVyVXRpbHMuY29weUNvb3JkcyA9IGZ1bmN0aW9uIChkZXN0LCBzcmMpIHtcbiAgICBkZXN0LnBhZ2UgPSBkZXN0LnBhZ2UgfHwge307XG4gICAgZGVzdC5wYWdlLnggPSBzcmMucGFnZS54O1xuICAgIGRlc3QucGFnZS55ID0gc3JjLnBhZ2UueTtcblxuICAgIGRlc3QuY2xpZW50ID0gZGVzdC5jbGllbnQgfHwge307XG4gICAgZGVzdC5jbGllbnQueCA9IHNyYy5jbGllbnQueDtcbiAgICBkZXN0LmNsaWVudC55ID0gc3JjLmNsaWVudC55O1xuXG4gICAgZGVzdC50aW1lU3RhbXAgPSBzcmMudGltZVN0YW1wO1xufTtcblxucG9pbnRlclV0aWxzLnNldEV2ZW50WFkgPSBmdW5jdGlvbiAodGFyZ2V0T2JqLCBwb2ludGVyLCBpbnRlcmFjdGlvbikge1xuICAgIGlmICghcG9pbnRlcikge1xuICAgICAgICBpZiAoaW50ZXJhY3Rpb24ucG9pbnRlcklkcy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICBwb2ludGVyID0gcG9pbnRlclV0aWxzLnRvdWNoQXZlcmFnZShpbnRlcmFjdGlvbi5wb2ludGVycyk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBwb2ludGVyID0gaW50ZXJhY3Rpb24ucG9pbnRlcnNbMF07XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwb2ludGVyVXRpbHMuZ2V0UGFnZVhZKHBvaW50ZXIsIHRtcFhZLCBpbnRlcmFjdGlvbik7XG4gICAgdGFyZ2V0T2JqLnBhZ2UueCA9IHRtcFhZLng7XG4gICAgdGFyZ2V0T2JqLnBhZ2UueSA9IHRtcFhZLnk7XG5cbiAgICBwb2ludGVyVXRpbHMuZ2V0Q2xpZW50WFkocG9pbnRlciwgdG1wWFksIGludGVyYWN0aW9uKTtcbiAgICB0YXJnZXRPYmouY2xpZW50LnggPSB0bXBYWS54O1xuICAgIHRhcmdldE9iai5jbGllbnQueSA9IHRtcFhZLnk7XG5cbiAgICB0YXJnZXRPYmoudGltZVN0YW1wID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG59O1xuXG5wb2ludGVyVXRpbHMuc2V0RXZlbnREZWx0YXMgPSBmdW5jdGlvbiAodGFyZ2V0T2JqLCBwcmV2LCBjdXIpIHtcbiAgICB0YXJnZXRPYmoucGFnZS54ICAgICA9IGN1ci5wYWdlLnggICAgICAtIHByZXYucGFnZS54O1xuICAgIHRhcmdldE9iai5wYWdlLnkgICAgID0gY3VyLnBhZ2UueSAgICAgIC0gcHJldi5wYWdlLnk7XG4gICAgdGFyZ2V0T2JqLmNsaWVudC54ICAgPSBjdXIuY2xpZW50LnggICAgLSBwcmV2LmNsaWVudC54O1xuICAgIHRhcmdldE9iai5jbGllbnQueSAgID0gY3VyLmNsaWVudC55ICAgIC0gcHJldi5jbGllbnQueTtcbiAgICB0YXJnZXRPYmoudGltZVN0YW1wID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLSBwcmV2LnRpbWVTdGFtcDtcblxuICAgIC8vIHNldCBwb2ludGVyIHZlbG9jaXR5XG4gICAgdmFyIGR0ID0gTWF0aC5tYXgodGFyZ2V0T2JqLnRpbWVTdGFtcCAvIDEwMDAsIDAuMDAxKTtcbiAgICB0YXJnZXRPYmoucGFnZS5zcGVlZCAgID0gaHlwb3QodGFyZ2V0T2JqLnBhZ2UueCwgdGFyZ2V0T2JqLnBhZ2UueSkgLyBkdDtcbiAgICB0YXJnZXRPYmoucGFnZS52eCAgICAgID0gdGFyZ2V0T2JqLnBhZ2UueCAvIGR0O1xuICAgIHRhcmdldE9iai5wYWdlLnZ5ICAgICAgPSB0YXJnZXRPYmoucGFnZS55IC8gZHQ7XG5cbiAgICB0YXJnZXRPYmouY2xpZW50LnNwZWVkID0gaHlwb3QodGFyZ2V0T2JqLmNsaWVudC54LCB0YXJnZXRPYmoucGFnZS55KSAvIGR0O1xuICAgIHRhcmdldE9iai5jbGllbnQudnggICAgPSB0YXJnZXRPYmouY2xpZW50LnggLyBkdDtcbiAgICB0YXJnZXRPYmouY2xpZW50LnZ5ICAgID0gdGFyZ2V0T2JqLmNsaWVudC55IC8gZHQ7XG59O1xuXG4vLyBHZXQgc3BlY2lmaWVkIFgvWSBjb29yZHMgZm9yIG1vdXNlIG9yIGV2ZW50LnRvdWNoZXNbMF1cbnBvaW50ZXJVdGlscy5nZXRYWSA9IGZ1bmN0aW9uICh0eXBlLCBwb2ludGVyLCB4eSkge1xuICAgIHh5ID0geHkgfHwge307XG4gICAgdHlwZSA9IHR5cGUgfHwgJ3BhZ2UnO1xuXG4gICAgeHkueCA9IHBvaW50ZXJbdHlwZSArICdYJ107XG4gICAgeHkueSA9IHBvaW50ZXJbdHlwZSArICdZJ107XG5cbiAgICByZXR1cm4geHk7XG59O1xuXG5wb2ludGVyVXRpbHMuZ2V0UGFnZVhZID0gZnVuY3Rpb24gKHBvaW50ZXIsIHBhZ2UsIGludGVyYWN0aW9uKSB7XG4gICAgcGFnZSA9IHBhZ2UgfHwge307XG5cbiAgICBpZiAocG9pbnRlciBpbnN0YW5jZW9mIHNjb3BlLkludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgaWYgKC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbiB8fCBwb2ludGVyLmludGVyYWN0aW9uO1xuXG4gICAgICAgICAgICBleHRlbmQocGFnZSwgaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy51cENvb3Jkcy5wYWdlKTtcblxuICAgICAgICAgICAgcGFnZS54ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3g7XG4gICAgICAgICAgICBwYWdlLnkgKz0gaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy5zeTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHBhZ2UueCA9IHBvaW50ZXIucGFnZVg7XG4gICAgICAgICAgICBwYWdlLnkgPSBwb2ludGVyLnBhZ2VZO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8vIE9wZXJhIE1vYmlsZSBoYW5kbGVzIHRoZSB2aWV3cG9ydCBhbmQgc2Nyb2xsaW5nIG9kZGx5XG4gICAgZWxzZSBpZiAoc2NvcGUuaXNPcGVyYU1vYmlsZSkge1xuICAgICAgICBwb2ludGVyVXRpbHMuZ2V0WFkoJ3NjcmVlbicsIHBvaW50ZXIsIHBhZ2UpO1xuXG4gICAgICAgIHBhZ2UueCArPSB3aW4ud2luZG93LnNjcm9sbFg7XG4gICAgICAgIHBhZ2UueSArPSB3aW4ud2luZG93LnNjcm9sbFk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBwb2ludGVyVXRpbHMuZ2V0WFkoJ3BhZ2UnLCBwb2ludGVyLCBwYWdlKTtcbiAgICB9XG5cbiAgICByZXR1cm4gcGFnZTtcbn07XG5cbnBvaW50ZXJVdGlscy5nZXRDbGllbnRYWSA9IGZ1bmN0aW9uIChwb2ludGVyLCBjbGllbnQsIGludGVyYWN0aW9uKSB7XG4gICAgY2xpZW50ID0gY2xpZW50IHx8IHt9O1xuXG4gICAgaWYgKHBvaW50ZXIgaW5zdGFuY2VvZiBzY29wZS5JbnRlcmFjdEV2ZW50KSB7XG4gICAgICAgIGlmICgvaW5lcnRpYXN0YXJ0Ly50ZXN0KHBvaW50ZXIudHlwZSkpIHtcbiAgICAgICAgICAgIGV4dGVuZChjbGllbnQsIGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMudXBDb29yZHMuY2xpZW50KTtcblxuICAgICAgICAgICAgY2xpZW50LnggKz0gaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy5zeDtcbiAgICAgICAgICAgIGNsaWVudC55ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3k7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBjbGllbnQueCA9IHBvaW50ZXIuY2xpZW50WDtcbiAgICAgICAgICAgIGNsaWVudC55ID0gcG9pbnRlci5jbGllbnRZO1xuICAgICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICAvLyBPcGVyYSBNb2JpbGUgaGFuZGxlcyB0aGUgdmlld3BvcnQgYW5kIHNjcm9sbGluZyBvZGRseVxuICAgICAgICBwb2ludGVyVXRpbHMuZ2V0WFkoc2NvcGUuaXNPcGVyYU1vYmlsZT8gJ3NjcmVlbic6ICdjbGllbnQnLCBwb2ludGVyLCBjbGllbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBjbGllbnQ7XG59O1xuXG5wb2ludGVyVXRpbHMuZ2V0UG9pbnRlcklkID0gZnVuY3Rpb24gKHBvaW50ZXIpIHtcbiAgICByZXR1cm4gc2NvcGUuaXNOdW1iZXIocG9pbnRlci5wb2ludGVySWQpPyBwb2ludGVyLnBvaW50ZXJJZCA6IHBvaW50ZXIuaWRlbnRpZmllcjtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gcG9pbnRlclV0aWxzO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgbGFzdFRpbWUgPSAwLFxuICAgIHZlbmRvcnMgPSBbJ21zJywgJ21veicsICd3ZWJraXQnLCAnbyddLFxuICAgIHJlcUZyYW1lLFxuICAgIGNhbmNlbEZyYW1lO1xuXG5mb3IodmFyIHggPSAwOyB4IDwgdmVuZG9ycy5sZW5ndGggJiYgIXdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU7ICsreCkge1xuICAgIHJlcUZyYW1lID0gd2luZG93W3ZlbmRvcnNbeF0rJ1JlcXVlc3RBbmltYXRpb25GcmFtZSddO1xuICAgIGNhbmNlbEZyYW1lID0gd2luZG93W3ZlbmRvcnNbeF0rJ0NhbmNlbEFuaW1hdGlvbkZyYW1lJ10gfHwgd2luZG93W3ZlbmRvcnNbeF0rJ0NhbmNlbFJlcXVlc3RBbmltYXRpb25GcmFtZSddO1xufVxuXG5pZiAoIXJlcUZyYW1lKSB7XG4gICAgcmVxRnJhbWUgPSBmdW5jdGlvbihjYWxsYmFjaykge1xuICAgICAgICB2YXIgY3VyclRpbWUgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgIHRpbWVUb0NhbGwgPSBNYXRoLm1heCgwLCAxNiAtIChjdXJyVGltZSAtIGxhc3RUaW1lKSksXG4gICAgICAgICAgICBpZCA9IHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7IGNhbGxiYWNrKGN1cnJUaW1lICsgdGltZVRvQ2FsbCk7IH0sXG4gICAgICAgICAgdGltZVRvQ2FsbCk7XG4gICAgICAgIGxhc3RUaW1lID0gY3VyclRpbWUgKyB0aW1lVG9DYWxsO1xuICAgICAgICByZXR1cm4gaWQ7XG4gICAgfTtcbn1cblxuaWYgKCFjYW5jZWxGcmFtZSkge1xuICAgIGNhbmNlbEZyYW1lID0gZnVuY3Rpb24oaWQpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KGlkKTtcbiAgICB9O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHtcbiAgICByZXF1ZXN0OiByZXFGcmFtZSxcbiAgICBjYW5jZWw6IGNhbmNlbEZyYW1lXG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5pZiAodHlwZW9mIHdpbmRvdyA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBtb2R1bGUuZXhwb3J0cy53aW5kb3cgICAgID0gdW5kZWZpbmVkO1xuICAgIG1vZHVsZS5leHBvcnRzLnJlYWxXaW5kb3cgPSB1bmRlZmluZWQ7XG59XG5lbHNlIHtcbiAgICAvLyBnZXQgd3JhcHBlZCB3aW5kb3cgaWYgdXNpbmcgU2hhZG93IERPTSBwb2x5ZmlsbFxuXG4gICAgbW9kdWxlLmV4cG9ydHMucmVhbFdpbmRvdyA9IHdpbmRvdztcblxuICAgIC8vIGNyZWF0ZSBhIFRleHROb2RlXG4gICAgdmFyIGVsID0gd2luZG93LmRvY3VtZW50LmNyZWF0ZVRleHROb2RlKCcnKTtcblxuICAgIC8vIGNoZWNrIGlmIGl0J3Mgd3JhcHBlZCBieSBhIHBvbHlmaWxsXG4gICAgaWYgKGVsLm93bmVyRG9jdW1lbnQgIT09IHdpbmRvdy5kb2N1bWVudFxuICAgICAgICAmJiB0eXBlb2Ygd2luZG93LndyYXAgPT09ICdmdW5jdGlvbidcbiAgICAgICAgJiYgd2luZG93LndyYXAoZWwpID09PSBlbCkge1xuICAgICAgICAvLyByZXR1cm4gd3JhcHBlZCB3aW5kb3dcbiAgICAgICAgbW9kdWxlLmV4cG9ydHMud2luZG93ID0gd2luZG93LndyYXAod2luZG93KTtcbiAgICB9XG5cbiAgICAvLyBubyBTaGFkb3cgRE9NIHBvbHlmaWwgb3IgbmF0aXZlIGltcGxlbWVudGF0aW9uXG4gICAgbW9kdWxlLmV4cG9ydHMud2luZG93ID0gd2luZG93O1xufVxuXG52YXIgaXNXaW5kb3cgPSByZXF1aXJlKCcuL2lzVHlwZScpLmlzV2luZG93O1xuXG5tb2R1bGUuZXhwb3J0cy5nZXRXaW5kb3cgPSBmdW5jdGlvbiBnZXRXaW5kb3cgKG5vZGUpIHtcbiAgICBpZiAoaXNXaW5kb3cobm9kZSkpIHtcbiAgICAgICAgcmV0dXJuIG5vZGU7XG4gICAgfVxuXG4gICAgdmFyIHJvb3ROb2RlID0gKG5vZGUub3duZXJEb2N1bWVudCB8fCBub2RlKTtcblxuICAgIHJldHVybiByb290Tm9kZS5kZWZhdWx0VmlldyB8fCByb290Tm9kZS5wYXJlbnRXaW5kb3cgfHwgbW9kdWxlLmV4cG9ydHMud2luZG93O1xufTtcbiJdfQ== diff --git a/src/InteractEvent.js b/src/InteractEvent.js new file mode 100644 index 000000000..682d58d1b --- /dev/null +++ b/src/InteractEvent.js @@ -0,0 +1,269 @@ +'use strict'; + +var scope = require('./scope'); +var utils = require('./utils'); + +function InteractEvent (interaction, event, action, phase, element, related) { + var client, + page, + target = interaction.target, + snapStatus = interaction.snapStatus, + restrictStatus = interaction.restrictStatus, + pointers = interaction.pointers, + deltaSource = (target && target.options || scope.defaultOptions).deltaSource, + sourceX = deltaSource + 'X', + sourceY = deltaSource + 'Y', + options = target? target.options: scope.defaultOptions, + origin = scope.getOriginXY(target, element), + starting = phase === 'start', + ending = phase === 'end', + coords = starting? interaction.startCoords : interaction.curCoords; + + element = element || interaction.element; + + page = utils.extend({}, coords.page); + client = utils.extend({}, coords.client); + + page.x -= origin.x; + page.y -= origin.y; + + client.x -= origin.x; + client.y -= origin.y; + + var relativePoints = options[action].snap && options[action].snap.relativePoints ; + + if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { + this.snap = { + range : snapStatus.range, + locked : snapStatus.locked, + x : snapStatus.snappedX, + y : snapStatus.snappedY, + realX : snapStatus.realX, + realY : snapStatus.realY, + dx : snapStatus.dx, + dy : snapStatus.dy + }; + + if (snapStatus.locked) { + page.x += snapStatus.dx; + page.y += snapStatus.dy; + client.x += snapStatus.dx; + client.y += snapStatus.dy; + } + } + + if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { + page.x += restrictStatus.dx; + page.y += restrictStatus.dy; + client.x += restrictStatus.dx; + client.y += restrictStatus.dy; + + this.restrict = { + dx: restrictStatus.dx, + dy: restrictStatus.dy + }; + } + + this.pageX = page.x; + this.pageY = page.y; + this.clientX = client.x; + this.clientY = client.y; + + this.x0 = interaction.startCoords.page.x - origin.x; + this.y0 = interaction.startCoords.page.y - origin.y; + this.clientX0 = interaction.startCoords.client.x - origin.x; + this.clientY0 = interaction.startCoords.client.y - origin.y; + this.ctrlKey = event.ctrlKey; + this.altKey = event.altKey; + this.shiftKey = event.shiftKey; + this.metaKey = event.metaKey; + this.button = event.button; + this.target = element; + this.t0 = interaction.downTimes[0]; + this.type = action + (phase || ''); + + this.interaction = interaction; + this.interactable = target; + + var inertiaStatus = interaction.inertiaStatus; + + if (inertiaStatus.active) { + this.detail = 'inertia'; + } + + if (related) { + this.relatedTarget = related; + } + + // end event dx, dy is difference between start and end points + if (ending) { + if (deltaSource === 'client') { + this.dx = client.x - interaction.startCoords.client.x; + this.dy = client.y - interaction.startCoords.client.y; + } + else { + this.dx = page.x - interaction.startCoords.page.x; + this.dy = page.y - interaction.startCoords.page.y; + } + } + else if (starting) { + this.dx = 0; + this.dy = 0; + } + // copy properties from previousmove if starting inertia + else if (phase === 'inertiastart') { + this.dx = interaction.prevEvent.dx; + this.dy = interaction.prevEvent.dy; + } + else { + if (deltaSource === 'client') { + this.dx = client.x - interaction.prevEvent.clientX; + this.dy = client.y - interaction.prevEvent.clientY; + } + else { + this.dx = page.x - interaction.prevEvent.pageX; + this.dy = page.y - interaction.prevEvent.pageY; + } + } + if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' + && !inertiaStatus.active + && options[action].inertia && options[action].inertia.zeroResumeDelta) { + + inertiaStatus.resumeDx += this.dx; + inertiaStatus.resumeDy += this.dy; + + this.dx = this.dy = 0; + } + + if (action === 'resize' && interaction.resizeAxes) { + if (options.resize.square) { + if (interaction.resizeAxes === 'y') { + this.dx = this.dy; + } + else { + this.dy = this.dx; + } + this.axes = 'xy'; + } + else { + this.axes = interaction.resizeAxes; + + if (interaction.resizeAxes === 'x') { + this.dy = 0; + } + else if (interaction.resizeAxes === 'y') { + this.dx = 0; + } + } + } + else if (action === 'gesture') { + this.touches = [pointers[0], pointers[1]]; + + if (starting) { + this.distance = utils.touchDistance(pointers, deltaSource); + this.box = utils.touchBBox(pointers); + this.scale = 1; + this.ds = 0; + this.angle = utils.touchAngle(pointers, undefined, deltaSource); + this.da = 0; + } + else if (ending || event instanceof InteractEvent) { + this.distance = interaction.prevEvent.distance; + this.box = interaction.prevEvent.box; + this.scale = interaction.prevEvent.scale; + this.ds = this.scale - 1; + this.angle = interaction.prevEvent.angle; + this.da = this.angle - interaction.gesture.startAngle; + } + else { + this.distance = utils.touchDistance(pointers, deltaSource); + this.box = utils.touchBBox(pointers); + this.scale = this.distance / interaction.gesture.startDistance; + this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); + + this.ds = this.scale - interaction.gesture.prevScale; + this.da = this.angle - interaction.gesture.prevAngle; + } + } + + if (starting) { + this.timeStamp = interaction.downTimes[0]; + this.dt = 0; + this.duration = 0; + this.speed = 0; + this.velocityX = 0; + this.velocityY = 0; + } + else if (phase === 'inertiastart') { + this.timeStamp = interaction.prevEvent.timeStamp; + this.dt = interaction.prevEvent.dt; + this.duration = interaction.prevEvent.duration; + this.speed = interaction.prevEvent.speed; + this.velocityX = interaction.prevEvent.velocityX; + this.velocityY = interaction.prevEvent.velocityY; + } + else { + this.timeStamp = new Date().getTime(); + this.dt = this.timeStamp - interaction.prevEvent.timeStamp; + this.duration = this.timeStamp - interaction.downTimes[0]; + + if (event instanceof InteractEvent) { + var dx = this[sourceX] - interaction.prevEvent[sourceX], + dy = this[sourceY] - interaction.prevEvent[sourceY], + dt = this.dt / 1000; + + this.speed = utils.hypot(dx, dy) / dt; + this.velocityX = dx / dt; + this.velocityY = dy / dt; + } + // if normal move or end event, use previous user event coords + else { + // speed and velocity in pixels per second + this.speed = interaction.pointerDelta[deltaSource].speed; + this.velocityX = interaction.pointerDelta[deltaSource].vx; + this.velocityY = interaction.pointerDelta[deltaSource].vy; + } + } + + if ((ending || phase === 'inertiastart') + && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) { + + var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI, + overlap = 22.5; + + if (angle < 0) { + angle += 360; + } + + var left = 135 - overlap <= angle && angle < 225 + overlap, + up = 225 - overlap <= angle && angle < 315 + overlap, + + right = !left && (315 - overlap <= angle || angle < 45 + overlap), + down = !up && 45 - overlap <= angle && angle < 135 + overlap; + + this.swipe = { + up : up, + down : down, + left : left, + right: right, + angle: angle, + speed: interaction.prevEvent.speed, + velocity: { + x: interaction.prevEvent.velocityX, + y: interaction.prevEvent.velocityY + } + }; + } +} + +InteractEvent.prototype = { + preventDefault: utils.blank, + stopImmediatePropagation: function () { + this.immediatePropagationStopped = this.propagationStopped = true; + }, + stopPropagation: function () { + this.propagationStopped = true; + } +}; + +module.exports = InteractEvent; diff --git a/src/Interaction.js b/src/Interaction.js new file mode 100644 index 000000000..33e5f10a5 --- /dev/null +++ b/src/Interaction.js @@ -0,0 +1,2080 @@ +'use strict'; + +var scope = require('./scope'); +var utils = require('./utils'); +var animationFrame = utils.raf; +var InteractEvent = require('./InteractEvent'); +var events = require('./utils/events'); +var browser = require('./utils/browser'); + +function Interaction () { + this.target = null; // current interactable being interacted with + this.element = null; // the target element of the interactable + this.dropTarget = null; // the dropzone a drag target might be dropped into + this.dropElement = null; // the element at the time of checking + this.prevDropTarget = null; // the dropzone that was recently dragged away from + this.prevDropElement = null; // the element at the time of checking + + this.prepared = { // action that's ready to be fired on next move event + name : null, + axis : null, + edges: null + }; + + this.matches = []; // all selectors that are matched by target element + this.matchElements = []; // corresponding elements + + this.inertiaStatus = { + active : false, + smoothEnd : false, + + startEvent: null, + upCoords: {}, + + xe: 0, ye: 0, + sx: 0, sy: 0, + + t0: 0, + vx0: 0, vys: 0, + duration: 0, + + resumeDx: 0, + resumeDy: 0, + + lambda_v0: 0, + one_ve_v0: 0, + i : null + }; + + if (scope.isFunction(Function.prototype.bind)) { + this.boundInertiaFrame = this.inertiaFrame.bind(this); + this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); + } + else { + var that = this; + + this.boundInertiaFrame = function () { return that.inertiaFrame(); }; + this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); }; + } + + this.activeDrops = { + dropzones: [], // the dropzones that are mentioned below + elements : [], // elements of dropzones that accept the target draggable + rects : [] // the rects of the elements mentioned above + }; + + // keep track of added pointers + this.pointers = []; + this.pointerIds = []; + this.downTargets = []; + this.downTimes = []; + this.holdTimers = []; + + // Previous native pointer move event coordinates + this.prevCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + // current native pointer move event coordinates + this.curCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + + // Starting InteractEvent pointer coordinates + this.startCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0 + }; + + // Change in coordinates and time of the pointer + this.pointerDelta = { + page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + timeStamp: 0 + }; + + this.downEvent = null; // pointerdown/mousedown/touchstart event + this.downPointer = {}; + + this._eventTarget = null; + this._curEventTarget = null; + + this.prevEvent = null; // previous action event + this.tapTime = 0; // time of the most recent tap event + this.prevTap = null; + + this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.snapOffsets = []; + + this.gesture = { + start: { x: 0, y: 0 }, + + startDistance: 0, // distance between two touches of touchStart + prevDistance : 0, + distance : 0, + + scale: 1, // gesture.distance / gesture.startDistance + + startAngle: 0, // angle of line joining two touches + prevAngle : 0 // angle of the previous gesture event + }; + + this.snapStatus = { + x : 0, y : 0, + dx : 0, dy : 0, + realX : 0, realY : 0, + snappedX: 0, snappedY: 0, + targets : [], + locked : false, + changed : false + }; + + this.restrictStatus = { + dx : 0, dy : 0, + restrictedX: 0, restrictedY: 0, + snap : null, + restricted : false, + changed : false + }; + + this.restrictStatus.snap = this.snapStatus; + + this.pointerIsDown = false; + this.pointerWasMoved = false; + this.gesturing = false; + this.dragging = false; + this.resizing = false; + this.resizeAxes = 'xy'; + + this.mouse = false; + + scope.interactions.push(this); +} + +// Check if action is enabled globally and the current target supports it +// If so, return the validated action. Otherwise, return null +function validateAction (action, interactable) { + if (!scope.isObject(action)) { return null; } + + var actionName = action.name, + options = interactable.options; + + if (( (actionName === 'resize' && options.resize.enabled ) + || (actionName === 'drag' && options.drag.enabled ) + || (actionName === 'gesture' && options.gesture.enabled)) + && scope.actionIsEnabled[actionName]) { + + if (actionName === 'resize' || actionName === 'resizeyx') { + actionName = 'resizexy'; + } + + return action; + } + return null; +} + +function getActionCursor (action) { + var cursor = ''; + + if (action.name === 'drag') { + cursor = scope.actionCursors.drag; + } + if (action.name === 'resize') { + if (action.axis) { + cursor = scope.actionCursors[action.name + action.axis]; + } + else if (action.edges) { + var cursorKey = 'resize', + edgeNames = ['top', 'bottom', 'left', 'right']; + + for (var i = 0; i < 4; i++) { + if (action.edges[edgeNames[i]]) { + cursorKey += edgeNames[i]; + } + } + + cursor = scope.actionCursors[cursorKey]; + } + } + + return cursor; +} + +function preventOriginalDefault () { + this.originalEvent.preventDefault(); +} + +Interaction.prototype = { + getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); }, + getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); }, + setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); }, + + pointerOver: function (pointer, event, eventTarget) { + if (this.prepared.name || !this.mouse) { return; } + + var curMatches = [], + curMatchElements = [], + prevTargetElement = this.element; + + this.addPointer(pointer); + + if (this.target + && (scope.testIgnore(this.target, this.element, eventTarget) + || !scope.testAllow(this.target, this.element, eventTarget))) { + // if the eventTarget should be ignored or shouldn't be allowed + // clear the previous target + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; + } + + var elementInteractable = scope.interactables.get(eventTarget), + elementAction = (elementInteractable + && !scope.testIgnore(elementInteractable, eventTarget, eventTarget) + && scope.testAllow(elementInteractable, eventTarget, eventTarget) + && validateAction( + elementInteractable.getAction(pointer, event, this, eventTarget), + elementInteractable)); + + if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { + elementAction = null; + } + + function pushCurMatches (interactable, selector) { + if (interactable + && scope.inContext(interactable, eventTarget) + && !scope.testIgnore(interactable, eventTarget, eventTarget) + && scope.testAllow(interactable, eventTarget, eventTarget) + && scope.matchesSelector(eventTarget, selector)) { + + curMatches.push(interactable); + curMatchElements.push(eventTarget); + } + } + + if (elementAction) { + this.target = elementInteractable; + this.element = eventTarget; + this.matches = []; + this.matchElements = []; + } + else { + scope.interactables.forEachSelector(pushCurMatches); + + if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { + this.matches = curMatches; + this.matchElements = curMatchElements; + + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(eventTarget, + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.listeners.pointerHover); + } + else if (this.target) { + if (scope.nodeContains(prevTargetElement, eventTarget)) { + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(this.element, + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.listeners.pointerHover); + } + else { + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; + } + } + } + }, + + // Check what action would be performed on pointerMove target if a mouse + // button were pressed and change the cursor accordingly + pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { + var target = this.target; + + if (!this.prepared.name && this.mouse) { + + var action; + + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); + + if (matches) { + action = this.validateSelector(pointer, event, matches, matchElements); + } + else if (target) { + action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); + } + + if (target && target.options.styleCursor) { + if (action) { + target._doc.documentElement.style.cursor = getActionCursor(action); + } + else { + target._doc.documentElement.style.cursor = ''; + } + } + } + else if (this.prepared.name) { + this.checkAndPreventDefault(event, target, this.element); + } + }, + + pointerOut: function (pointer, event, eventTarget) { + if (this.prepared.name) { return; } + + // Remove temporary event listeners for selector Interactables + if (!scope.interactables.get(eventTarget)) { + events.remove(eventTarget, + scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.listeners.pointerHover); + } + + if (this.target && this.target.options.styleCursor && !this.interacting()) { + this.target._doc.documentElement.style.cursor = ''; + } + }, + + selectorDown: function (pointer, event, eventTarget, curEventTarget) { + var that = this, + // copy event to be used in timeout for IE8 + eventCopy = events.useAttachEvent? utils.extend({}, event) : event, + element = eventTarget, + pointerIndex = this.addPointer(pointer), + action; + + this.holdTimers[pointerIndex] = setTimeout(function () { + that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); + }, scope.defaultOptions._holdDuration); + + this.pointerIsDown = true; + + // Check if the down event hits the current inertia target + if (this.inertiaStatus.active && this.target.selector) { + // climb up the DOM tree from the event target + while (utils.isElement(element)) { + + // if this element is the current inertia target element + if (element === this.element + // and the prospective action is the same as the ongoing one + && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { + + // stop inertia so that the next move will be a normal one + animationFrame.cancel(this.inertiaStatus.i); + this.inertiaStatus.active = false; + + this.collectEventTargets(pointer, event, eventTarget, 'down'); + return; + } + element = scope.parentElement(element); + } + } + + // do nothing if interacting + if (this.interacting()) { + this.collectEventTargets(pointer, event, eventTarget, 'down'); + return; + } + + function pushMatches (interactable, selector, context) { + var elements = scope.ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; + + if (scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && scope.matchesSelector(element, selector, elements)) { + + that.matches.push(interactable); + that.matchElements.push(element); + } + } + + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); + this.downEvent = event; + + while (utils.isElement(element) && !action) { + this.matches = []; + this.matchElements = []; + + scope.interactables.forEachSelector(pushMatches); + + action = this.validateSelector(pointer, event, this.matches, this.matchElements); + element = scope.parentElement(element); + } + + if (action) { + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + + this.collectEventTargets(pointer, event, eventTarget, 'down'); + + return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); + } + else { + // do these now since pointerDown isn't being called from here + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + utils.extend(this.downPointer, pointer); + + utils.copyCoords(this.prevCoords, this.curCoords); + this.pointerWasMoved = false; + } + + this.collectEventTargets(pointer, event, eventTarget, 'down'); + }, + + // Determine action to be performed on next pointerMove and add appropriate + // style and event Listeners + pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { + if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { + this.checkAndPreventDefault(event, this.target, this.element); + + return; + } + + this.pointerIsDown = true; + this.downEvent = event; + + var pointerIndex = this.addPointer(pointer), + action; + + // If it is the second touch of a multi-touch gesture, keep the target + // the same if a target was set by the first touch + // Otherwise, set the target if there is no action prepared + if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { + + var interactable = scope.interactables.get(curEventTarget); + + if (interactable + && !scope.testIgnore(interactable, curEventTarget, eventTarget) + && scope.testAllow(interactable, curEventTarget, eventTarget) + && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) + && scope.withinInteractionLimit(interactable, curEventTarget, action)) { + this.target = interactable; + this.element = curEventTarget; + } + } + + var target = this.target, + options = target && target.options; + + if (target && (forceAction || !this.prepared.name)) { + action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); + + this.setEventXY(this.startCoords); + + if (!action) { return; } + + if (options.styleCursor) { + target._doc.documentElement.style.cursor = getActionCursor(action); + } + + this.resizeAxes = action.name === 'resize'? action.axis : null; + + if (action === 'gesture' && this.pointerIds.length < 2) { + action = null; + } + + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + + this.snapStatus.snappedX = this.snapStatus.snappedY = + this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN; + + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + utils.extend(this.downPointer, pointer); + + this.setEventXY(this.prevCoords); + this.pointerWasMoved = false; + + this.checkAndPreventDefault(event, target, this.element); + } + // if inertia is active try to resume action + else if (this.inertiaStatus.active + && curEventTarget === this.element + && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { + + animationFrame.cancel(this.inertiaStatus.i); + this.inertiaStatus.active = false; + + this.checkAndPreventDefault(event, target, this.element); + } + }, + + setModifications: function (coords, preEnd) { + var target = this.target, + shouldMove = true, + shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), + shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); + + if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } + if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } + + if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { + shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; + } + else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) { + shouldMove = false; + } + + return shouldMove; + }, + + setStartOffsets: function (action, interactable, element) { + var rect = interactable.getRect(element), + origin = scope.getOriginXY(interactable, element), + snap = interactable.options[this.prepared.name].snap, + restrict = interactable.options[this.prepared.name].restrict, + width, height; + + if (rect) { + this.startOffset.left = this.startCoords.page.x - rect.left; + this.startOffset.top = this.startCoords.page.y - rect.top; + + this.startOffset.right = rect.right - this.startCoords.page.x; + this.startOffset.bottom = rect.bottom - this.startCoords.page.y; + + if ('width' in rect) { width = rect.width; } + else { width = rect.right - rect.left; } + if ('height' in rect) { height = rect.height; } + else { height = rect.bottom - rect.top; } + } + else { + this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; + } + + this.snapOffsets.splice(0); + + var snapOffset = snap && snap.offset === 'startCoords' + ? { + x: this.startCoords.page.x - origin.x, + y: this.startCoords.page.y - origin.y + } + : snap && snap.offset || { x: 0, y: 0 }; + + if (rect && snap && snap.relativePoints && snap.relativePoints.length) { + for (var i = 0; i < snap.relativePoints.length; i++) { + this.snapOffsets.push({ + x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x, + y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y + }); + } + } + else { + this.snapOffsets.push(snapOffset); + } + + if (rect && restrict.elementRect) { + this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left); + this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top); + + this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right)); + this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom)); + } + else { + this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0; + } + }, + + /*\ + * Interaction.start + [ method ] + * + * Start an action with the given Interactable and Element as tartgets. The + * action must be enabled for the target Interactable and an appropriate number + * of pointers must be held down – 1 for drag/resize, 2 for gesture. + * + * Use it with `interactable.able({ manualStart: false })` to always + * [start actions manually](https://github.com/taye/interact.js/issues/114) + * + - action (object) The action to be performed - drag, resize, etc. + - interactable (Interactable) The Interactable to target + - element (Element) The DOM Element to target + = (object) interact + ** + | interact(target) + | .draggable({ + | // disable the default drag start by down->move + | manualStart: true + | }) + | // start dragging after the user holds the pointer down + | .on('hold', function (event) { + | var interaction = event.interaction; + | + | if (!interaction.interacting()) { + | interaction.start({ name: 'drag' }, + | event.interactable, + | event.currentTarget); + | } + | }); + \*/ + start: function (action, interactable, element) { + if (this.interacting() + || !this.pointerIsDown + || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { + return; + } + + // if this interaction had been removed after stopping + // add it back + if (scope.indexOf(scope.interactions, this) === -1) { + scope.interactions.push(this); + } + + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + this.target = interactable; + this.element = element; + + this.setEventXY(this.startCoords); + this.setStartOffsets(action.name, interactable, element); + this.setModifications(this.startCoords.page); + + this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); + }, + + pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { + this.recordPointer(pointer); + + this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) + ? this.inertiaStatus.startEvent + : undefined); + + var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x + && this.curCoords.page.y === this.prevCoords.page.y + && this.curCoords.client.x === this.prevCoords.client.x + && this.curCoords.client.y === this.prevCoords.client.y); + + var dx, dy, + pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + + // register movement greater than pointerMoveTolerance + if (this.pointerIsDown && !this.pointerWasMoved) { + dx = this.curCoords.client.x - this.startCoords.client.x; + dy = this.curCoords.client.y - this.startCoords.client.y; + + this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; + } + + if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { + if (this.pointerIsDown) { + clearTimeout(this.holdTimers[pointerIndex]); + } + + this.collectEventTargets(pointer, event, eventTarget, 'move'); + } + + if (!this.pointerIsDown) { return; } + + if (duplicateMove && this.pointerWasMoved && !preEnd) { + this.checkAndPreventDefault(event, this.target, this.element); + return; + } + + // set pointer coordinate, time changes and speeds + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + + if (!this.prepared.name) { return; } + + if (this.pointerWasMoved + // ignore movement while inertia is active + && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { + + // if just starting an action, calculate the pointer speed now + if (!this.interacting()) { + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + + // check if a drag is in the correct axis + if (this.prepared.name === 'drag') { + var absX = Math.abs(dx), + absY = Math.abs(dy), + targetAxis = this.target.options.drag.axis, + axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); + + // if the movement isn't in the axis of the interactable + if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { + // cancel the prepared action + this.prepared.name = null; + + // then try to get a drag from another ineractable + + var element = eventTarget; + + // check element interactables + while (utils.isElement(element)) { + var elementInteractable = scope.interactables.get(element); + + if (elementInteractable + && elementInteractable !== this.target + && !elementInteractable.options.drag.manualStart + && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' + && scope.checkAxis(axis, elementInteractable)) { + + this.prepared.name = 'drag'; + this.target = elementInteractable; + this.element = element; + break; + } + + element = scope.parentElement(element); + } + + // if there's no drag from element interactables, + // check the selector interactables + if (!this.prepared.name) { + var thisInteraction = this; + + var getDraggable = function (interactable, selector, context) { + var elements = scope.ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; + + if (interactable === thisInteraction.target) { return; } + + if (scope.inContext(interactable, eventTarget) + && !interactable.options.drag.manualStart + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && scope.matchesSelector(element, selector, elements) + && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' + && scope.checkAxis(axis, interactable) + && scope.withinInteractionLimit(interactable, element, 'drag')) { + + return interactable; + } + }; + + element = eventTarget; + + while (utils.isElement(element)) { + var selectorInteractable = scope.interactables.forEachSelector(getDraggable); + + if (selectorInteractable) { + this.prepared.name = 'drag'; + this.target = selectorInteractable; + this.element = element; + break; + } + + element = scope.parentElement(element); + } + } + } + } + } + + var starting = !!this.prepared.name && !this.interacting(); + + if (starting + && (this.target.options[this.prepared.name].manualStart + || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { + this.stop(); + return; + } + + if (this.prepared.name && this.target) { + if (starting) { + this.start(this.prepared, this.target, this.element); + } + + var shouldMove = this.setModifications(this.curCoords.page, preEnd); + + // move if snapping or restriction doesn't prevent it + if (shouldMove || starting) { + this.prevEvent = this[this.prepared.name + 'Move'](event); + } + + this.checkAndPreventDefault(event, this.target, this.element); + } + } + + utils.copyCoords(this.prevCoords, this.curCoords); + + if (this.dragging || this.resizing) { + this.autoScrollMove(pointer); + } + }, + + dragStart: function (event) { + var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); + + this.dragging = true; + this.target.fire(dragEvent); + + // reset active dropzones + this.activeDrops.dropzones = []; + this.activeDrops.elements = []; + this.activeDrops.rects = []; + + if (!this.dynamicDrop) { + this.setActiveDrops(this.element); + } + + var dropEvents = this.getDropEvents(event, dragEvent); + + if (dropEvents.activate) { + this.fireActiveDrops(dropEvents.activate); + } + + return dragEvent; + }, + + dragMove: function (event) { + var target = this.target, + dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), + draggableElement = this.element, + drop = this.getDrop(event, draggableElement); + + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; + + var dropEvents = this.getDropEvents(event, dragEvent); + + target.fire(dragEvent); + + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } + + this.prevDropTarget = this.dropTarget; + this.prevDropElement = this.dropElement; + + return dragEvent; + }, + + resizeStart: function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); + + if (this.prepared.edges) { + var startRect = this.target.getRect(this.element); + + if (this.target.options.resize.square) { + var squareEdges = utils.extend({}, this.prepared.edges); + + squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); + squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); + squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); + squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); + + this.prepared._squareEdges = squareEdges; + } + else { + this.prepared._squareEdges = null; + } + + this.resizeRects = { + start : startRect, + current : utils.extend({}, startRect), + restricted: utils.extend({}, startRect), + previous : utils.extend({}, startRect), + delta : { + left: 0, right : 0, width : 0, + top : 0, bottom: 0, height: 0 + } + }; + + resizeEvent.rect = this.resizeRects.restricted; + resizeEvent.deltaRect = this.resizeRects.delta; + } + + this.target.fire(resizeEvent); + + this.resizing = true; + + return resizeEvent; + }, + + resizeMove: function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); + + var edges = this.prepared.edges, + invert = this.target.options.resize.invert, + invertible = invert === 'reposition' || invert === 'negate'; + + if (edges) { + var dx = resizeEvent.dx, + dy = resizeEvent.dy, + + start = this.resizeRects.start, + current = this.resizeRects.current, + restricted = this.resizeRects.restricted, + delta = this.resizeRects.delta, + previous = utils.extend(this.resizeRects.previous, restricted); + + if (this.target.options.resize.square) { + var originalEdges = edges; + + edges = this.prepared._squareEdges; + + if ((originalEdges.left && originalEdges.bottom) + || (originalEdges.right && originalEdges.top)) { + dy = -dx; + } + else if (originalEdges.left || originalEdges.right) { dy = dx; } + else if (originalEdges.top || originalEdges.bottom) { dx = dy; } + } + + // update the 'current' rect without modifications + if (edges.top ) { current.top += dy; } + if (edges.bottom) { current.bottom += dy; } + if (edges.left ) { current.left += dx; } + if (edges.right ) { current.right += dx; } + + if (invertible) { + // if invertible, copy the current rect + utils.extend(restricted, current); + + if (invert === 'reposition') { + // swap edge values if necessary to keep width/height positive + var swap; + + if (restricted.top > restricted.bottom) { + swap = restricted.top; + + restricted.top = restricted.bottom; + restricted.bottom = swap; + } + if (restricted.left > restricted.right) { + swap = restricted.left; + + restricted.left = restricted.right; + restricted.right = swap; + } + } + } + else { + // if not invertible, restrict to minimum of 0x0 rect + restricted.top = Math.min(current.top, start.bottom); + restricted.bottom = Math.max(current.bottom, start.top); + restricted.left = Math.min(current.left, start.right); + restricted.right = Math.max(current.right, start.left); + } + + restricted.width = restricted.right - restricted.left; + restricted.height = restricted.bottom - restricted.top ; + + for (var edge in restricted) { + delta[edge] = restricted[edge] - previous[edge]; + } + + resizeEvent.edges = this.prepared.edges; + resizeEvent.rect = restricted; + resizeEvent.deltaRect = delta; + } + + this.target.fire(resizeEvent); + + return resizeEvent; + }, + + gestureStart: function (event) { + var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); + + gestureEvent.ds = 0; + + this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; + this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; + this.gesture.scale = 1; + + this.gesturing = true; + + this.target.fire(gestureEvent); + + return gestureEvent; + }, + + gestureMove: function (event) { + if (!this.pointerIds.length) { + return this.prevEvent; + } + + var gestureEvent; + + gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); + gestureEvent.ds = gestureEvent.scale - this.gesture.scale; + + this.target.fire(gestureEvent); + + this.gesture.prevAngle = gestureEvent.angle; + this.gesture.prevDistance = gestureEvent.distance; + + if (gestureEvent.scale !== Infinity && + gestureEvent.scale !== null && + gestureEvent.scale !== undefined && + !isNaN(gestureEvent.scale)) { + + this.gesture.scale = gestureEvent.scale; + } + + return gestureEvent; + }, + + pointerHold: function (pointer, event, eventTarget) { + this.collectEventTargets(pointer, event, eventTarget, 'hold'); + }, + + pointerUp: function (pointer, event, eventTarget, curEventTarget) { + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + + clearTimeout(this.holdTimers[pointerIndex]); + + this.collectEventTargets(pointer, event, eventTarget, 'up' ); + this.collectEventTargets(pointer, event, eventTarget, 'tap'); + + this.pointerEnd(pointer, event, eventTarget, curEventTarget); + + this.removePointer(pointer); + }, + + pointerCancel: function (pointer, event, eventTarget, curEventTarget) { + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + + clearTimeout(this.holdTimers[pointerIndex]); + + this.collectEventTargets(pointer, event, eventTarget, 'cancel'); + this.pointerEnd(pointer, event, eventTarget, curEventTarget); + + this.removePointer(pointer); + }, + + // http://www.quirksmode.org/dom/events/click.html + // >Events leading to dblclick + // + // IE8 doesn't fire down event before dblclick. + // This workaround tries to fire a tap and doubletap after dblclick + ie8Dblclick: function (pointer, event, eventTarget) { + if (this.prevTap + && event.clientX === this.prevTap.clientX + && event.clientY === this.prevTap.clientY + && eventTarget === this.prevTap.target) { + + this.downTargets[0] = eventTarget; + this.downTimes[0] = new Date().getTime(); + this.collectEventTargets(pointer, event, eventTarget, 'tap'); + } + }, + + // End interact move events and stop auto-scroll unless inertia is enabled + pointerEnd: function (pointer, event, eventTarget, curEventTarget) { + var endEvent, + target = this.target, + options = target && target.options, + inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, + inertiaStatus = this.inertiaStatus; + + if (this.interacting()) { + + if (inertiaStatus.active) { return; } + + var pointerSpeed, + now = new Date().getTime(), + inertiaPossible = false, + inertia = false, + smoothEnd = false, + endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, + endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, + dx = 0, + dy = 0, + startEvent; + + if (this.dragging) { + if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } + else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } + else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } + } + else { + pointerSpeed = this.pointerDelta.client.speed; + } + + // check if inertia should be started + inertiaPossible = (inertiaOptions && inertiaOptions.enabled + && this.prepared.name !== 'gesture' + && event !== inertiaStatus.startEvent); + + inertia = (inertiaPossible + && (now - this.curCoords.timeStamp) < 50 + && pointerSpeed > inertiaOptions.minSpeed + && pointerSpeed > inertiaOptions.endSpeed); + + if (inertiaPossible && !inertia && (endSnap || endRestrict)) { + + var snapRestrict = {}; + + snapRestrict.snap = snapRestrict.restrict = snapRestrict; + + if (endSnap) { + this.setSnapping(this.curCoords.page, snapRestrict); + if (snapRestrict.locked) { + dx += snapRestrict.dx; + dy += snapRestrict.dy; + } + } + + if (endRestrict) { + this.setRestriction(this.curCoords.page, snapRestrict); + if (snapRestrict.restricted) { + dx += snapRestrict.dx; + dy += snapRestrict.dy; + } + } + + if (dx || dy) { + smoothEnd = true; + } + } + + if (inertia || smoothEnd) { + utils.copyCoords(inertiaStatus.upCoords, this.curCoords); + + this.pointers[0] = inertiaStatus.startEvent = startEvent = + new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); + + inertiaStatus.t0 = now; + + target.fire(inertiaStatus.startEvent); + + if (inertia) { + inertiaStatus.vx0 = this.pointerDelta.client.vx; + inertiaStatus.vy0 = this.pointerDelta.client.vy; + inertiaStatus.v0 = pointerSpeed; + + this.calcInertia(inertiaStatus); + + var page = utils.extend({}, this.curCoords.page), + origin = scope.getOriginXY(target, this.element), + statusObject; + + page.x = page.x + inertiaStatus.xe - origin.x; + page.y = page.y + inertiaStatus.ye - origin.y; + + statusObject = { + useStatusXY: true, + x: page.x, + y: page.y, + dx: 0, + dy: 0, + snap: null + }; + + statusObject.snap = statusObject; + + dx = dy = 0; + + if (endSnap) { + var snap = this.setSnapping(this.curCoords.page, statusObject); + + if (snap.locked) { + dx += snap.dx; + dy += snap.dy; + } + } + + if (endRestrict) { + var restrict = this.setRestriction(this.curCoords.page, statusObject); + + if (restrict.restricted) { + dx += restrict.dx; + dy += restrict.dy; + } + } + + inertiaStatus.modifiedXe += dx; + inertiaStatus.modifiedYe += dy; + + inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); + } + else { + inertiaStatus.smoothEnd = true; + inertiaStatus.xe = dx; + inertiaStatus.ye = dy; + + inertiaStatus.sx = inertiaStatus.sy = 0; + + inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); + } + + inertiaStatus.active = true; + return; + } + + if (endSnap || endRestrict) { + // fire a move event at the snapped coordinates + this.pointerMove(pointer, event, eventTarget, curEventTarget, true); + } + } + + if (this.dragging) { + endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); + + var draggableElement = this.element, + drop = this.getDrop(event, draggableElement); + + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; + + var dropEvents = this.getDropEvents(event, endEvent); + + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } + if (dropEvents.deactivate) { + this.fireActiveDrops(dropEvents.deactivate); + } + + target.fire(endEvent); + } + else if (this.resizing) { + endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); + target.fire(endEvent); + } + else if (this.gesturing) { + endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); + target.fire(endEvent); + } + + this.stop(event); + }, + + collectDrops: function (element) { + var drops = [], + elements = [], + i; + + element = element || this.element; + + // collect all dropzones and their elements which qualify for a drop + for (i = 0; i < scope.interactables.length; i++) { + if (!scope.interactables[i].options.drop.enabled) { continue; } + + var current = scope.interactables[i], + accept = current.options.drop.accept; + + // test the draggable element against the dropzone's accept setting + if ((utils.isElement(accept) && accept !== element) + || (scope.isString(accept) + && !scope.matchesSelector(element, accept))) { + + continue; + } + + // query for new elements if necessary + var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; + + for (var j = 0, len = dropElements.length; j < len; j++) { + var currentElement = dropElements[j]; + + if (currentElement === element) { + continue; + } + + drops.push(current); + elements.push(currentElement); + } + } + + return { + dropzones: drops, + elements: elements + }; + }, + + fireActiveDrops: function (event) { + var i, + current, + currentElement, + prevElement; + + // loop through all active dropzones and trigger event + for (i = 0; i < this.activeDrops.dropzones.length; i++) { + current = this.activeDrops.dropzones[i]; + currentElement = this.activeDrops.elements [i]; + + // prevent trigger of duplicate events on same element + if (currentElement !== prevElement) { + // set current element as event target + event.target = currentElement; + current.fire(event); + } + prevElement = currentElement; + } + }, + + // Collect a new set of possible drops and save them in activeDrops. + // setActiveDrops should always be called when a drag has just started or a + // drag event happens while dynamicDrop is true + setActiveDrops: function (dragElement) { + // get dropzones and their elements that could receive the draggable + var possibleDrops = this.collectDrops(dragElement, true); + + this.activeDrops.dropzones = possibleDrops.dropzones; + this.activeDrops.elements = possibleDrops.elements; + this.activeDrops.rects = []; + + for (var i = 0; i < this.activeDrops.dropzones.length; i++) { + this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); + } + }, + + getDrop: function (event, dragElement) { + var validDrops = []; + + if (scope.dynamicDrop) { + this.setActiveDrops(dragElement); + } + + // collect all dropzones and their elements which qualify for a drop + for (var j = 0; j < this.activeDrops.dropzones.length; j++) { + var current = this.activeDrops.dropzones[j], + currentElement = this.activeDrops.elements [j], + rect = this.activeDrops.rects [j]; + + validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) + ? currentElement + : null); + } + + // get the most appropriate dropzone based on DOM depth and order + var dropIndex = scope.indexOfDeepestElement(validDrops), + dropzone = this.activeDrops.dropzones[dropIndex] || null, + element = this.activeDrops.elements [dropIndex] || null; + + return { + dropzone: dropzone, + element: element + }; + }, + + getDropEvents: function (pointerEvent, dragEvent) { + var dropEvents = { + enter : null, + leave : null, + activate : null, + deactivate: null, + move : null, + drop : null + }; + + if (this.dropElement !== this.prevDropElement) { + // if there was a prevDropTarget, create a dragleave event + if (this.prevDropTarget) { + dropEvents.leave = { + target : this.prevDropElement, + dropzone : this.prevDropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragleave' + }; + + dragEvent.dragLeave = this.prevDropElement; + dragEvent.prevDropzone = this.prevDropTarget; + } + // if the dropTarget is not null, create a dragenter event + if (this.dropTarget) { + dropEvents.enter = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragenter' + }; + + dragEvent.dragEnter = this.dropElement; + dragEvent.dropzone = this.dropTarget; + } + } + + if (dragEvent.type === 'dragend' && this.dropTarget) { + dropEvents.drop = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'drop' + }; + + dragEvent.dropzone = this.dropTarget; + } + if (dragEvent.type === 'dragstart') { + dropEvents.activate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropactivate' + }; + } + if (dragEvent.type === 'dragend') { + dropEvents.deactivate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropdeactivate' + }; + } + if (dragEvent.type === 'dragmove' && this.dropTarget) { + dropEvents.move = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + dragmove : dragEvent, + timeStamp : dragEvent.timeStamp, + type : 'dropmove' + }; + dragEvent.dropzone = this.dropTarget; + } + + return dropEvents; + }, + + currentAction: function () { + return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; + }, + + interacting: function () { + return this.dragging || this.resizing || this.gesturing; + }, + + clearTargets: function () { + this.target = this.element = null; + + this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null; + }, + + stop: function (event) { + if (this.interacting()) { + scope.autoScroll.stop(); + this.matches = []; + this.matchElements = []; + + var target = this.target; + + if (target.options.styleCursor) { + target._doc.documentElement.style.cursor = ''; + } + + // prevent Default only if were previously interacting + if (event && scope.isFunction(event.preventDefault)) { + this.checkAndPreventDefault(event, target, this.element); + } + + if (this.dragging) { + this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; + } + + this.clearTargets(); + } + + this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; + this.prepared.name = this.prevEvent = null; + this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; + + // remove pointers if their ID isn't in this.pointerIds + for (var i = 0; i < this.pointers.length; i++) { + if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { + this.pointers.splice(i, 1); + } + } + + for (i = 0; i < scope.interactions.length; i++) { + // remove this interaction if it's not the only one of it's type + if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { + scope.interactions.splice(scope.indexOf(scope.interactions, this), 1); + } + } + }, + + inertiaFrame: function () { + var inertiaStatus = this.inertiaStatus, + options = this.target.options[this.prepared.name].inertia, + lambda = options.resistance, + t = new Date().getTime() / 1000 - inertiaStatus.t0; + + if (t < inertiaStatus.te) { + + var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; + + if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { + inertiaStatus.sx = inertiaStatus.xe * progress; + inertiaStatus.sy = inertiaStatus.ye * progress; + } + else { + var quadPoint = scope.getQuadraticCurvePoint( + 0, 0, + inertiaStatus.xe, inertiaStatus.ye, + inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, + progress); + + inertiaStatus.sx = quadPoint.x; + inertiaStatus.sy = quadPoint.y; + } + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); + } + else { + inertiaStatus.sx = inertiaStatus.modifiedXe; + inertiaStatus.sy = inertiaStatus.modifiedYe; + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.active = false; + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + } + }, + + smoothEndFrame: function () { + var inertiaStatus = this.inertiaStatus, + t = new Date().getTime() - inertiaStatus.t0, + duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; + + if (t < duration) { + inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration); + inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration); + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); + } + else { + inertiaStatus.sx = inertiaStatus.xe; + inertiaStatus.sy = inertiaStatus.ye; + + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.active = false; + inertiaStatus.smoothEnd = false; + + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + } + }, + + addPointer: function (pointer) { + var id = utils.getPointerId(pointer), + index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); + + if (index === -1) { + index = this.pointerIds.length; + } + + this.pointerIds[index] = id; + this.pointers[index] = pointer; + + return index; + }, + + removePointer: function (pointer) { + var id = utils.getPointerId(pointer), + index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); + + if (index === -1) { return; } + + if (!this.interacting()) { + this.pointers.splice(index, 1); + } + + this.pointerIds .splice(index, 1); + this.downTargets.splice(index, 1); + this.downTimes .splice(index, 1); + this.holdTimers .splice(index, 1); + }, + + recordPointer: function (pointer) { + // Do not update pointers while inertia is active. + // The inertia start event should be this.pointers[0] + if (this.inertiaStatus.active) { return; } + + var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + + if (index === -1) { return; } + + this.pointers[index] = pointer; + }, + + collectEventTargets: function (pointer, event, eventTarget, eventType) { + var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + + // do not fire a tap event if the pointer was moved before being lifted + if (eventType === 'tap' && (this.pointerWasMoved + // or if the pointerup target is different to the pointerdown target + || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) { + return; + } + + var targets = [], + elements = [], + element = eventTarget; + + function collectSelectors (interactable, selector, context) { + var els = scope.ie8MatchesSelector + ? context.querySelectorAll(selector) + : undefined; + + if (interactable._iEvents[eventType] + && utils.isElement(element) + && scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && scope.matchesSelector(element, selector, els)) { + + targets.push(interactable); + elements.push(element); + } + } + + + var interact = scope.interact; + + while (element) { + if (interact.isSet(element) && interact(element)._iEvents[eventType]) { + targets.push(interact(element)); + elements.push(element); + } + + scope.interactables.forEachSelector(collectSelectors); + + element = scope.parentElement(element); + } + + // create the tap event even if there are no listeners so that + // doubletap can still be created and fired + if (targets.length || eventType === 'tap') { + this.firePointers(pointer, event, eventTarget, targets, elements, eventType); + } + }, + + firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { + var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)), + pointerEvent = {}, + i, + // for tap events + interval, createNewDoubleTap; + + // if it's a doubletap then the event properties would have been + // copied from the tap event and provided as the pointer argument + if (eventType === 'doubletap') { + pointerEvent = pointer; + } + else { + utils.extend(pointerEvent, event); + if (event !== pointer) { + utils.extend(pointerEvent, pointer); + } + + pointerEvent.preventDefault = preventOriginalDefault; + pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; + pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; + pointerEvent.interaction = this; + + pointerEvent.timeStamp = new Date().getTime(); + pointerEvent.originalEvent = event; + pointerEvent.type = eventType; + pointerEvent.pointerId = utils.getPointerId(pointer); + pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' + : scope.isString(pointer.pointerType) + ? pointer.pointerType + : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; + } + + if (eventType === 'tap') { + pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex]; + + interval = pointerEvent.timeStamp - this.tapTime; + createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap' + && this.prevTap.target === pointerEvent.target + && interval < 500); + + pointerEvent.double = createNewDoubleTap; + + this.tapTime = pointerEvent.timeStamp; + } + + for (i = 0; i < targets.length; i++) { + pointerEvent.currentTarget = elements[i]; + pointerEvent.interactable = targets[i]; + targets[i].fire(pointerEvent); + + if (pointerEvent.immediatePropagationStopped + ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { + break; + } + } + + if (createNewDoubleTap) { + var doubleTap = {}; + + utils.extend(doubleTap, pointerEvent); + + doubleTap.dt = interval; + doubleTap.type = 'doubletap'; + + this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap'); + + this.prevTap = doubleTap; + } + else if (eventType === 'tap') { + this.prevTap = pointerEvent; + } + }, + + validateSelector: function (pointer, event, matches, matchElements) { + for (var i = 0, len = matches.length; i < len; i++) { + var match = matches[i], + matchElement = matchElements[i], + action = validateAction(match.getAction(pointer, event, this, matchElement), match); + + if (action && scope.withinInteractionLimit(match, matchElement, action)) { + this.target = match; + this.element = matchElement; + + return action; + } + } + }, + + setSnapping: function (pageCoords, status) { + var snap = this.target.options[this.prepared.name].snap, + targets = [], + target, + page, + i; + + status = status || this.snapStatus; + + if (status.useStatusXY) { + page = { x: status.x, y: status.y }; + } + else { + var origin = scope.getOriginXY(this.target, this.element); + + page = utils.extend({}, pageCoords); + + page.x -= origin.x; + page.y -= origin.y; + } + + status.realX = page.x; + status.realY = page.y; + + page.x = page.x - this.inertiaStatus.resumeDx; + page.y = page.y - this.inertiaStatus.resumeDy; + + var len = snap.targets? snap.targets.length : 0; + + for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) { + var relative = { + x: page.x - this.snapOffsets[relIndex].x, + y: page.y - this.snapOffsets[relIndex].y + }; + + for (i = 0; i < len; i++) { + if (scope.isFunction(snap.targets[i])) { + target = snap.targets[i](relative.x, relative.y, this); + } + else { + target = snap.targets[i]; + } + + if (!target) { continue; } + + targets.push({ + x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, + y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, + + range: scope.isNumber(target.range)? target.range: snap.range + }); + } + } + + var closest = { + target: null, + inRange: false, + distance: 0, + range: 0, + dx: 0, + dy: 0 + }; + + for (i = 0, len = targets.length; i < len; i++) { + target = targets[i]; + + var range = target.range, + dx = target.x - page.x, + dy = target.y - page.y, + distance = utils.hypot(dx, dy), + inRange = distance <= range; + + // Infinite targets count as being out of range + // compared to non infinite ones that are in range + if (range === Infinity && closest.inRange && closest.range !== Infinity) { + inRange = false; + } + + if (!closest.target || (inRange + // is the closest target in range? + ? (closest.inRange && range !== Infinity + // the pointer is relatively deeper in this target + ? distance / range < closest.distance / closest.range + // this target has Infinite range and the closest doesn't + : (range === Infinity && closest.range !== Infinity) + // OR this target is closer that the previous closest + || distance < closest.distance) + // The other is not in range and the pointer is closer to this target + : (!closest.inRange && distance < closest.distance))) { + + if (range === Infinity) { + inRange = true; + } + + closest.target = target; + closest.distance = distance; + closest.range = range; + closest.inRange = inRange; + closest.dx = dx; + closest.dy = dy; + + status.range = range; + } + } + + var snapChanged; + + if (closest.target) { + snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); + + status.snappedX = closest.target.x; + status.snappedY = closest.target.y; + } + else { + snapChanged = true; + + status.snappedX = NaN; + status.snappedY = NaN; + } + + status.dx = closest.dx; + status.dy = closest.dy; + + status.changed = (snapChanged || (closest.inRange && !status.locked)); + status.locked = closest.inRange; + + return status; + }, + + setRestriction: function (pageCoords, status) { + var target = this.target, + restrict = target && target.options[this.prepared.name].restrict, + restriction = restrict && restrict.restriction, + page; + + if (!restriction) { + return status; + } + + status = status || this.restrictStatus; + + page = status.useStatusXY + ? page = { x: status.x, y: status.y } + : page = utils.extend({}, pageCoords); + + if (status.snap && status.snap.locked) { + page.x += status.snap.dx || 0; + page.y += status.snap.dy || 0; + } + + page.x -= this.inertiaStatus.resumeDx; + page.y -= this.inertiaStatus.resumeDy; + + status.dx = 0; + status.dy = 0; + status.restricted = false; + + var rect, restrictedX, restrictedY; + + if (scope.isString(restriction)) { + if (restriction === 'parent') { + restriction = scope.parentElement(this.element); + } + else if (restriction === 'self') { + restriction = target.getRect(this.element); + } + else { + restriction = scope.closest(this.element, restriction); + } + + if (!restriction) { return status; } + } + + if (scope.isFunction(restriction)) { + restriction = restriction(page.x, page.y, this.element); + } + + if (utils.isElement(restriction)) { + restriction = scope.getElementRect(restriction); + } + + rect = restriction; + + if (!restriction) { + restrictedX = page.x; + restrictedY = page.y; + } + // object is assumed to have + // x, y, width, height or + // left, top, right, bottom + else if ('x' in restriction && 'y' in restriction) { + restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left); + restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top ); + } + else { + restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left); + restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top ); + } + + status.dx = restrictedX - page.x; + status.dy = restrictedY - page.y; + + status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; + status.restricted = !!(status.dx || status.dy); + + status.restrictedX = restrictedX; + status.restrictedY = restrictedY; + + return status; + }, + + checkAndPreventDefault: function (event, interactable, element) { + if (!(interactable = interactable || this.target)) { return; } + + var options = interactable.options, + prevent = options.preventDefault; + + if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { + // do not preventDefault on pointerdown if the prepared action is a drag + // and dragging can only start from a certain direction - this allows + // a touch to pan the viewport if a drag isn't in the right direction + if (/down|start/i.test(event.type) + && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { + + return; + } + + // with manualStart, only preventDefault while interacting + if (options[this.prepared.name] && options[this.prepared.name].manualStart + && !this.interacting()) { + return; + } + + event.preventDefault(); + return; + } + + if (prevent === 'always') { + event.preventDefault(); + return; + } + }, + + calcInertia: function (status) { + var inertiaOptions = this.target.options[this.prepared.name].inertia, + lambda = inertiaOptions.resistance, + inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; + + status.x0 = this.prevEvent.pageX; + status.y0 = this.prevEvent.pageY; + status.t0 = status.startEvent.timeStamp / 1000; + status.sx = status.sy = 0; + + status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; + status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; + status.te = inertiaDur; + + status.lambda_v0 = lambda / status.v0; + status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; + }, + + autoScrollMove: function (pointer) { + if (!(this.interacting() + && scope.checkAutoScroll(this.target, this.prepared.name))) { + return; + } + + if (this.inertiaStatus.active) { + scope.autoScroll.x = scope.autoScroll.y = 0; + return; + } + + var top, + right, + bottom, + left, + options = this.target.options[this.prepared.name].autoScroll, + container = options.container || scope.getWindow(this.element); + + if (scope.isWindow(container)) { + left = pointer.clientX < scope.autoScroll.margin; + top = pointer.clientY < scope.autoScroll.margin; + right = pointer.clientX > container.innerWidth - scope.autoScroll.margin; + bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin; + } + else { + var rect = scope.getElementRect(container); + + left = pointer.clientX < rect.left + scope.autoScroll.margin; + top = pointer.clientY < rect.top + scope.autoScroll.margin; + right = pointer.clientX > rect.right - scope.autoScroll.margin; + bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin; + } + + scope.autoScroll.x = (right ? 1: left? -1: 0); + scope.autoScroll.y = (bottom? 1: top? -1: 0); + + if (!scope.autoScroll.isScrolling) { + // set the autoScroll properties to those of the target + scope.autoScroll.margin = options.margin; + scope.autoScroll.speed = options.speed; + + scope.autoScroll.start(this); + } + }, + + _updateEventTargets: function (target, currentTarget) { + this._eventTarget = target; + this._curEventTarget = currentTarget; + } + +}; + +module.exports = Interaction; \ No newline at end of file diff --git a/src/interact.js b/src/interact.js index c5ca942dc..dd6f1c819 100644 --- a/src/interact.js +++ b/src/interact.js @@ -133,12 +133,17 @@ // will be polyfill function if browser is IE8 scope.ie8MatchesSelector = null; - // native requestAnimationFrame or polyfill - var reqFrame = scope.realWindow.requestAnimationFrame, - cancelFrame = scope.realWindow.cancelAnimationFrame, + // Events wrapper + var events = require('./utils/events'); - // Events wrapper - events = require('./utils/events'); + scope.listeners = {}; + + var interactionListeners = [ + 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove', + 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', + 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', + 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' + ]; scope.trySelector = function (value) { if (!scope.isString(value)) { return false; } @@ -439,2215 +444,203 @@ scope.checkAutoScroll = function (interactable, action) { var options = interactable.options; - if (/^resize/.test(action)) { - action = 'resize'; - } - - return options[action].autoScroll && options[action].autoScroll.enabled; - }; - - scope.withinInteractionLimit = function (interactable, element, action) { - var options = interactable.options, - maxActions = options[action.name].max, - maxPerElement = options[action.name].maxPerElement, - activeInteractions = 0, - targetCount = 0, - targetElementCount = 0; - - for (var i = 0, len = scope.interactions.length; i < len; i++) { - var interaction = scope.interactions[i], - otherAction = interaction.prepared.name, - active = interaction.interacting(); - - if (!active) { continue; } - - activeInteractions++; - - if (activeInteractions >= scope.maxInteractions) { - return false; - } - - if (interaction.target !== interactable) { continue; } - - targetCount += (otherAction === action.name)|0; - - if (targetCount >= maxActions) { - return false; - } - - if (interaction.element === element) { - targetElementCount++; - - if (otherAction !== action.name || targetElementCount >= maxPerElement) { - return false; - } - } - } - - return scope.maxInteractions > 0; - }; - - // Test for the element that's "above" all other qualifiers - scope.indexOfDeepestElement = function (elements) { - var dropzone, - deepestZone = elements[0], - index = deepestZone? 0: -1, - parent, - deepestZoneParents = [], - dropzoneParents = [], - child, - i, - n; - - for (i = 1; i < elements.length; i++) { - dropzone = elements[i]; - - // an element might belong to multiple selector dropzones - if (!dropzone || dropzone === deepestZone) { - continue; - } - - if (!deepestZone) { - deepestZone = dropzone; - index = i; - continue; - } - - // check if the deepest or current are document.documentElement or document.rootElement - // - if the current dropzone is, do nothing and continue - if (dropzone.parentNode === dropzone.ownerDocument) { - continue; - } - // - if deepest is, update with the current dropzone and continue to next - else if (deepestZone.parentNode === dropzone.ownerDocument) { - deepestZone = dropzone; - index = i; - continue; - } - - if (!deepestZoneParents.length) { - parent = deepestZone; - while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { - deepestZoneParents.unshift(parent); - parent = parent.parentNode; - } - } - - // if this element is an svg element and the current deepest is - // an HTMLElement - if (deepestZone instanceof scope.HTMLElement - && dropzone instanceof scope.SVGElement - && !(dropzone instanceof scope.SVGSVGElement)) { - - if (dropzone === deepestZone.parentNode) { - continue; - } - - parent = dropzone.ownerSVGElement; - } - else { - parent = dropzone; - } - - dropzoneParents = []; - - while (parent.parentNode !== parent.ownerDocument) { - dropzoneParents.unshift(parent); - parent = parent.parentNode; - } - - n = 0; - - // get (position of last common ancestor) + 1 - while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { - n++; - } - - var parents = [ - dropzoneParents[n - 1], - dropzoneParents[n], - deepestZoneParents[n] - ]; - - child = parents[0].lastChild; - - while (child) { - if (child === parents[1]) { - deepestZone = dropzone; - index = i; - deepestZoneParents = []; - - break; - } - else if (child === parents[2]) { - break; - } - - child = child.previousSibling; - } - } - - return index; - }; - - scope.matchesSelector = function (element, selector, nodeList) { - if (scope.ie8MatchesSelector) { - return scope.ie8MatchesSelector(element, selector, nodeList); - } - - // remove /deep/ from selectors if shadowDOM polyfill is used - if (scope.window !== scope.realWindow) { - selector = selector.replace(/\/deep\//g, ' '); - } - - return element[scope.prefixedMatchesSelector](selector); - }; - - scope.matchesUpTo = function (element, selector, limit) { - while (utils.isElement(element)) { - if (scope.matchesSelector(element, selector)) { - return true; - } - - element = scope.parentElement(element); - - if (element === limit) { - return scope.matchesSelector(element, selector); - } - } - - return false; - }; - - // For IE8's lack of an Element#matchesSelector - // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { - scope.ie8MatchesSelector = function (element, selector, elems) { - elems = elems || element.parentNode.querySelectorAll(selector); - - for (var i = 0, len = elems.length; i < len; i++) { - if (elems[i] === element) { - return true; - } - } - - return false; - }; - } - - function Interaction () { - this.target = null; // current interactable being interacted with - this.element = null; // the target element of the interactable - this.dropTarget = null; // the dropzone a drag target might be dropped into - this.dropElement = null; // the element at the time of checking - this.prevDropTarget = null; // the dropzone that was recently dragged away from - this.prevDropElement = null; // the element at the time of checking - - this.prepared = { // action that's ready to be fired on next move event - name : null, - axis : null, - edges: null - }; - - this.matches = []; // all selectors that are matched by target element - this.matchElements = []; // corresponding elements - - this.inertiaStatus = { - active : false, - smoothEnd : false, - - startEvent: null, - upCoords: {}, - - xe: 0, ye: 0, - sx: 0, sy: 0, - - t0: 0, - vx0: 0, vys: 0, - duration: 0, - - resumeDx: 0, - resumeDy: 0, - - lambda_v0: 0, - one_ve_v0: 0, - i : null - }; - - if (scope.isFunction(Function.prototype.bind)) { - this.boundInertiaFrame = this.inertiaFrame.bind(this); - this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); - } - else { - var that = this; - - this.boundInertiaFrame = function () { return that.inertiaFrame(); }; - this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); }; - } - - this.activeDrops = { - dropzones: [], // the dropzones that are mentioned below - elements : [], // elements of dropzones that accept the target draggable - rects : [] // the rects of the elements mentioned above - }; - - // keep track of added pointers - this.pointers = []; - this.pointerIds = []; - this.downTargets = []; - this.downTimes = []; - this.holdTimers = []; - - // Previous native pointer move event coordinates - this.prevCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - // current native pointer move event coordinates - this.curCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - - // Starting InteractEvent pointer coordinates - this.startCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - - // Change in coordinates and time of the pointer - this.pointerDelta = { - page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - timeStamp: 0 - }; - - this.downEvent = null; // pointerdown/mousedown/touchstart event - this.downPointer = {}; - - this._eventTarget = null; - this._curEventTarget = null; - - this.prevEvent = null; // previous action event - this.tapTime = 0; // time of the most recent tap event - this.prevTap = null; - - this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.snapOffsets = []; - - this.gesture = { - start: { x: 0, y: 0 }, - - startDistance: 0, // distance between two touches of touchStart - prevDistance : 0, - distance : 0, - - scale: 1, // gesture.distance / gesture.startDistance - - startAngle: 0, // angle of line joining two touches - prevAngle : 0 // angle of the previous gesture event - }; - - this.snapStatus = { - x : 0, y : 0, - dx : 0, dy : 0, - realX : 0, realY : 0, - snappedX: 0, snappedY: 0, - targets : [], - locked : false, - changed : false - }; - - this.restrictStatus = { - dx : 0, dy : 0, - restrictedX: 0, restrictedY: 0, - snap : null, - restricted : false, - changed : false - }; - - this.restrictStatus.snap = this.snapStatus; - - this.pointerIsDown = false; - this.pointerWasMoved = false; - this.gesturing = false; - this.dragging = false; - this.resizing = false; - this.resizeAxes = 'xy'; - - this.mouse = false; - - scope.interactions.push(this); - } - - Interaction.prototype = { - getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); }, - getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); }, - setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); }, - - pointerOver: function (pointer, event, eventTarget) { - if (this.prepared.name || !this.mouse) { return; } - - var curMatches = [], - curMatchElements = [], - prevTargetElement = this.element; - - this.addPointer(pointer); - - if (this.target - && (scope.testIgnore(this.target, this.element, eventTarget) - || !scope.testAllow(this.target, this.element, eventTarget))) { - // if the eventTarget should be ignored or shouldn't be allowed - // clear the previous target - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; - } - - var elementInteractable = scope.interactables.get(eventTarget), - elementAction = (elementInteractable - && !scope.testIgnore(elementInteractable, eventTarget, eventTarget) - && scope.testAllow(elementInteractable, eventTarget, eventTarget) - && validateAction( - elementInteractable.getAction(pointer, event, this, eventTarget), - elementInteractable)); - - if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { - elementAction = null; - } - - function pushCurMatches (interactable, selector) { - if (interactable - && scope.inContext(interactable, eventTarget) - && !scope.testIgnore(interactable, eventTarget, eventTarget) - && scope.testAllow(interactable, eventTarget, eventTarget) - && scope.matchesSelector(eventTarget, selector)) { - - curMatches.push(interactable); - curMatchElements.push(eventTarget); - } - } - - if (elementAction) { - this.target = elementInteractable; - this.element = eventTarget; - this.matches = []; - this.matchElements = []; - } - else { - scope.interactables.forEachSelector(pushCurMatches); - - if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { - this.matches = curMatches; - this.matchElements = curMatchElements; - - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(eventTarget, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', - listeners.pointerHover); - } - else if (this.target) { - if (scope.nodeContains(prevTargetElement, eventTarget)) { - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(this.element, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', - listeners.pointerHover); - } - else { - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; - } - } - } - }, - - // Check what action would be performed on pointerMove target if a mouse - // button were pressed and change the cursor accordingly - pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { - var target = this.target; - - if (!this.prepared.name && this.mouse) { - - var action; - - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); - - if (matches) { - action = this.validateSelector(pointer, event, matches, matchElements); - } - else if (target) { - action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); - } - - if (target && target.options.styleCursor) { - if (action) { - target._doc.documentElement.style.cursor = getActionCursor(action); - } - else { - target._doc.documentElement.style.cursor = ''; - } - } - } - else if (this.prepared.name) { - this.checkAndPreventDefault(event, target, this.element); - } - }, - - pointerOut: function (pointer, event, eventTarget) { - if (this.prepared.name) { return; } - - // Remove temporary event listeners for selector Interactables - if (!scope.interactables.get(eventTarget)) { - events.remove(eventTarget, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', - listeners.pointerHover); - } - - if (this.target && this.target.options.styleCursor && !this.interacting()) { - this.target._doc.documentElement.style.cursor = ''; - } - }, - - selectorDown: function (pointer, event, eventTarget, curEventTarget) { - var that = this, - // copy event to be used in timeout for IE8 - eventCopy = events.useAttachEvent? utils.extend({}, event) : event, - element = eventTarget, - pointerIndex = this.addPointer(pointer), - action; - - this.holdTimers[pointerIndex] = setTimeout(function () { - that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); - }, scope.defaultOptions._holdDuration); - - this.pointerIsDown = true; - - // Check if the down event hits the current inertia target - if (this.inertiaStatus.active && this.target.selector) { - // climb up the DOM tree from the event target - while (utils.isElement(element)) { - - // if this element is the current inertia target element - if (element === this.element - // and the prospective action is the same as the ongoing one - && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { - - // stop inertia so that the next move will be a normal one - cancelFrame(this.inertiaStatus.i); - this.inertiaStatus.active = false; - - this.collectEventTargets(pointer, event, eventTarget, 'down'); - return; - } - element = scope.parentElement(element); - } - } - - // do nothing if interacting - if (this.interacting()) { - this.collectEventTargets(pointer, event, eventTarget, 'down'); - return; - } - - function pushMatches (interactable, selector, context) { - var elements = scope.ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; - - if (scope.inContext(interactable, element) - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, elements)) { - - that.matches.push(interactable); - that.matchElements.push(element); - } - } - - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); - this.downEvent = event; - - while (utils.isElement(element) && !action) { - this.matches = []; - this.matchElements = []; - - scope.interactables.forEachSelector(pushMatches); - - action = this.validateSelector(pointer, event, this.matches, this.matchElements); - element = scope.parentElement(element); - } - - if (action) { - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - - this.collectEventTargets(pointer, event, eventTarget, 'down'); - - return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); - } - else { - // do these now since pointerDown isn't being called from here - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - utils.extend(this.downPointer, pointer); - - utils.copyCoords(this.prevCoords, this.curCoords); - this.pointerWasMoved = false; - } - - this.collectEventTargets(pointer, event, eventTarget, 'down'); - }, - - // Determine action to be performed on next pointerMove and add appropriate - // style and event Listeners - pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { - if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { - this.checkAndPreventDefault(event, this.target, this.element); - - return; - } - - this.pointerIsDown = true; - this.downEvent = event; - - var pointerIndex = this.addPointer(pointer), - action; - - // If it is the second touch of a multi-touch gesture, keep the target - // the same if a target was set by the first touch - // Otherwise, set the target if there is no action prepared - if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { - - var interactable = scope.interactables.get(curEventTarget); - - if (interactable - && !scope.testIgnore(interactable, curEventTarget, eventTarget) - && scope.testAllow(interactable, curEventTarget, eventTarget) - && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) - && scope.withinInteractionLimit(interactable, curEventTarget, action)) { - this.target = interactable; - this.element = curEventTarget; - } - } - - var target = this.target, - options = target && target.options; - - if (target && (forceAction || !this.prepared.name)) { - action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); - - this.setEventXY(this.startCoords); - - if (!action) { return; } - - if (options.styleCursor) { - target._doc.documentElement.style.cursor = getActionCursor(action); - } - - this.resizeAxes = action.name === 'resize'? action.axis : null; - - if (action === 'gesture' && this.pointerIds.length < 2) { - action = null; - } - - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - - this.snapStatus.snappedX = this.snapStatus.snappedY = - this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN; - - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - utils.extend(this.downPointer, pointer); - - this.setEventXY(this.prevCoords); - this.pointerWasMoved = false; - - this.checkAndPreventDefault(event, target, this.element); - } - // if inertia is active try to resume action - else if (this.inertiaStatus.active - && curEventTarget === this.element - && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { - - cancelFrame(this.inertiaStatus.i); - this.inertiaStatus.active = false; - - this.checkAndPreventDefault(event, target, this.element); - } - }, - - setModifications: function (coords, preEnd) { - var target = this.target, - shouldMove = true, - shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), - shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); - - if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } - if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } - - if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { - shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; - } - else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) { - shouldMove = false; - } - - return shouldMove; - }, - - setStartOffsets: function (action, interactable, element) { - var rect = interactable.getRect(element), - origin = scope.getOriginXY(interactable, element), - snap = interactable.options[this.prepared.name].snap, - restrict = interactable.options[this.prepared.name].restrict, - width, height; - - if (rect) { - this.startOffset.left = this.startCoords.page.x - rect.left; - this.startOffset.top = this.startCoords.page.y - rect.top; - - this.startOffset.right = rect.right - this.startCoords.page.x; - this.startOffset.bottom = rect.bottom - this.startCoords.page.y; - - if ('width' in rect) { width = rect.width; } - else { width = rect.right - rect.left; } - if ('height' in rect) { height = rect.height; } - else { height = rect.bottom - rect.top; } - } - else { - this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; - } - - this.snapOffsets.splice(0); - - var snapOffset = snap && snap.offset === 'startCoords' - ? { - x: this.startCoords.page.x - origin.x, - y: this.startCoords.page.y - origin.y - } - : snap && snap.offset || { x: 0, y: 0 }; - - if (rect && snap && snap.relativePoints && snap.relativePoints.length) { - for (var i = 0; i < snap.relativePoints.length; i++) { - this.snapOffsets.push({ - x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x, - y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y - }); - } - } - else { - this.snapOffsets.push(snapOffset); - } - - if (rect && restrict.elementRect) { - this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left); - this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top); - - this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right)); - this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom)); - } - else { - this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0; - } - }, - - /*\ - * Interaction.start - [ method ] - * - * Start an action with the given Interactable and Element as tartgets. The - * action must be enabled for the target Interactable and an appropriate number - * of pointers must be held down – 1 for drag/resize, 2 for gesture. - * - * Use it with `interactable.able({ manualStart: false })` to always - * [start actions manually](https://github.com/taye/interact.js/issues/114) - * - - action (object) The action to be performed - drag, resize, etc. - - interactable (Interactable) The Interactable to target - - element (Element) The DOM Element to target - = (object) interact - ** - | interact(target) - | .draggable({ - | // disable the default drag start by down->move - | manualStart: true - | }) - | // start dragging after the user holds the pointer down - | .on('hold', function (event) { - | var interaction = event.interaction; - | - | if (!interaction.interacting()) { - | interaction.start({ name: 'drag' }, - | event.interactable, - | event.currentTarget); - | } - | }); - \*/ - start: function (action, interactable, element) { - if (this.interacting() - || !this.pointerIsDown - || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { - return; - } - - // if this interaction had been removed after stopping - // add it back - if (scope.indexOf(scope.interactions, this) === -1) { - scope.interactions.push(this); - } - - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - this.target = interactable; - this.element = element; - - this.setEventXY(this.startCoords); - this.setStartOffsets(action.name, interactable, element); - this.setModifications(this.startCoords.page); - - this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); - }, - - pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { - this.recordPointer(pointer); - - this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) - ? this.inertiaStatus.startEvent - : undefined); - - var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x - && this.curCoords.page.y === this.prevCoords.page.y - && this.curCoords.client.x === this.prevCoords.client.x - && this.curCoords.client.y === this.prevCoords.client.y); - - var dx, dy, - pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - // register movement greater than pointerMoveTolerance - if (this.pointerIsDown && !this.pointerWasMoved) { - dx = this.curCoords.client.x - this.startCoords.client.x; - dy = this.curCoords.client.y - this.startCoords.client.y; - - this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; - } - - if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { - if (this.pointerIsDown) { - clearTimeout(this.holdTimers[pointerIndex]); - } - - this.collectEventTargets(pointer, event, eventTarget, 'move'); - } - - if (!this.pointerIsDown) { return; } - - if (duplicateMove && this.pointerWasMoved && !preEnd) { - this.checkAndPreventDefault(event, this.target, this.element); - return; - } - - // set pointer coordinate, time changes and speeds - utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - - if (!this.prepared.name) { return; } - - if (this.pointerWasMoved - // ignore movement while inertia is active - && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { - - // if just starting an action, calculate the pointer speed now - if (!this.interacting()) { - utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - - // check if a drag is in the correct axis - if (this.prepared.name === 'drag') { - var absX = Math.abs(dx), - absY = Math.abs(dy), - targetAxis = this.target.options.drag.axis, - axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); - - // if the movement isn't in the axis of the interactable - if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { - // cancel the prepared action - this.prepared.name = null; - - // then try to get a drag from another ineractable - - var element = eventTarget; - - // check element interactables - while (utils.isElement(element)) { - var elementInteractable = scope.interactables.get(element); - - if (elementInteractable - && elementInteractable !== this.target - && !elementInteractable.options.drag.manualStart - && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' - && scope.checkAxis(axis, elementInteractable)) { - - this.prepared.name = 'drag'; - this.target = elementInteractable; - this.element = element; - break; - } - - element = scope.parentElement(element); - } - - // if there's no drag from element interactables, - // check the selector interactables - if (!this.prepared.name) { - var thisInteraction = this; - - var getDraggable = function (interactable, selector, context) { - var elements = scope.ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; - - if (interactable === thisInteraction.target) { return; } - - if (scope.inContext(interactable, eventTarget) - && !interactable.options.drag.manualStart - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, elements) - && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' - && scope.checkAxis(axis, interactable) - && scope.withinInteractionLimit(interactable, element, 'drag')) { - - return interactable; - } - }; - - element = eventTarget; - - while (utils.isElement(element)) { - var selectorInteractable = scope.interactables.forEachSelector(getDraggable); - - if (selectorInteractable) { - this.prepared.name = 'drag'; - this.target = selectorInteractable; - this.element = element; - break; - } - - element = scope.parentElement(element); - } - } - } - } - } - - var starting = !!this.prepared.name && !this.interacting(); - - if (starting - && (this.target.options[this.prepared.name].manualStart - || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { - this.stop(); - return; - } - - if (this.prepared.name && this.target) { - if (starting) { - this.start(this.prepared, this.target, this.element); - } - - var shouldMove = this.setModifications(this.curCoords.page, preEnd); - - // move if snapping or restriction doesn't prevent it - if (shouldMove || starting) { - this.prevEvent = this[this.prepared.name + 'Move'](event); - } - - this.checkAndPreventDefault(event, this.target, this.element); - } - } - - utils.copyCoords(this.prevCoords, this.curCoords); - - if (this.dragging || this.resizing) { - this.autoScrollMove(pointer); - } - }, - - dragStart: function (event) { - var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); - - this.dragging = true; - this.target.fire(dragEvent); - - // reset active dropzones - this.activeDrops.dropzones = []; - this.activeDrops.elements = []; - this.activeDrops.rects = []; - - if (!this.dynamicDrop) { - this.setActiveDrops(this.element); - } - - var dropEvents = this.getDropEvents(event, dragEvent); - - if (dropEvents.activate) { - this.fireActiveDrops(dropEvents.activate); - } - - return dragEvent; - }, - - dragMove: function (event) { - var target = this.target, - dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), - draggableElement = this.element, - drop = this.getDrop(event, draggableElement); - - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; - - var dropEvents = this.getDropEvents(event, dragEvent); - - target.fire(dragEvent); - - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } - - this.prevDropTarget = this.dropTarget; - this.prevDropElement = this.dropElement; - - return dragEvent; - }, - - resizeStart: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); - - if (this.prepared.edges) { - var startRect = this.target.getRect(this.element); - - if (this.target.options.resize.square) { - var squareEdges = utils.extend({}, this.prepared.edges); - - squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); - squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); - squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); - squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); - - this.prepared._squareEdges = squareEdges; - } - else { - this.prepared._squareEdges = null; - } - - this.resizeRects = { - start : startRect, - current : utils.extend({}, startRect), - restricted: utils.extend({}, startRect), - previous : utils.extend({}, startRect), - delta : { - left: 0, right : 0, width : 0, - top : 0, bottom: 0, height: 0 - } - }; - - resizeEvent.rect = this.resizeRects.restricted; - resizeEvent.deltaRect = this.resizeRects.delta; - } - - this.target.fire(resizeEvent); - - this.resizing = true; - - return resizeEvent; - }, - - resizeMove: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); - - var edges = this.prepared.edges, - invert = this.target.options.resize.invert, - invertible = invert === 'reposition' || invert === 'negate'; - - if (edges) { - var dx = resizeEvent.dx, - dy = resizeEvent.dy, - - start = this.resizeRects.start, - current = this.resizeRects.current, - restricted = this.resizeRects.restricted, - delta = this.resizeRects.delta, - previous = utils.extend(this.resizeRects.previous, restricted); - - if (this.target.options.resize.square) { - var originalEdges = edges; - - edges = this.prepared._squareEdges; - - if ((originalEdges.left && originalEdges.bottom) - || (originalEdges.right && originalEdges.top)) { - dy = -dx; - } - else if (originalEdges.left || originalEdges.right) { dy = dx; } - else if (originalEdges.top || originalEdges.bottom) { dx = dy; } - } - - // update the 'current' rect without modifications - if (edges.top ) { current.top += dy; } - if (edges.bottom) { current.bottom += dy; } - if (edges.left ) { current.left += dx; } - if (edges.right ) { current.right += dx; } - - if (invertible) { - // if invertible, copy the current rect - utils.extend(restricted, current); - - if (invert === 'reposition') { - // swap edge values if necessary to keep width/height positive - var swap; - - if (restricted.top > restricted.bottom) { - swap = restricted.top; - - restricted.top = restricted.bottom; - restricted.bottom = swap; - } - if (restricted.left > restricted.right) { - swap = restricted.left; - - restricted.left = restricted.right; - restricted.right = swap; - } - } - } - else { - // if not invertible, restrict to minimum of 0x0 rect - restricted.top = Math.min(current.top, start.bottom); - restricted.bottom = Math.max(current.bottom, start.top); - restricted.left = Math.min(current.left, start.right); - restricted.right = Math.max(current.right, start.left); - } - - restricted.width = restricted.right - restricted.left; - restricted.height = restricted.bottom - restricted.top ; - - for (var edge in restricted) { - delta[edge] = restricted[edge] - previous[edge]; - } - - resizeEvent.edges = this.prepared.edges; - resizeEvent.rect = restricted; - resizeEvent.deltaRect = delta; - } - - this.target.fire(resizeEvent); - - return resizeEvent; - }, - - gestureStart: function (event) { - var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); - - gestureEvent.ds = 0; - - this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; - this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; - this.gesture.scale = 1; - - this.gesturing = true; - - this.target.fire(gestureEvent); - - return gestureEvent; - }, - - gestureMove: function (event) { - if (!this.pointerIds.length) { - return this.prevEvent; - } - - var gestureEvent; - - gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); - gestureEvent.ds = gestureEvent.scale - this.gesture.scale; - - this.target.fire(gestureEvent); - - this.gesture.prevAngle = gestureEvent.angle; - this.gesture.prevDistance = gestureEvent.distance; - - if (gestureEvent.scale !== Infinity && - gestureEvent.scale !== null && - gestureEvent.scale !== undefined && - !isNaN(gestureEvent.scale)) { - - this.gesture.scale = gestureEvent.scale; - } - - return gestureEvent; - }, - - pointerHold: function (pointer, event, eventTarget) { - this.collectEventTargets(pointer, event, eventTarget, 'hold'); - }, - - pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - clearTimeout(this.holdTimers[pointerIndex]); - - this.collectEventTargets(pointer, event, eventTarget, 'up' ); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); - - this.pointerEnd(pointer, event, eventTarget, curEventTarget); - - this.removePointer(pointer); - }, - - pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - clearTimeout(this.holdTimers[pointerIndex]); - - this.collectEventTargets(pointer, event, eventTarget, 'cancel'); - this.pointerEnd(pointer, event, eventTarget, curEventTarget); - - this.removePointer(pointer); - }, - - // http://www.quirksmode.org/dom/events/click.html - // >Events leading to dblclick - // - // IE8 doesn't fire down event before dblclick. - // This workaround tries to fire a tap and doubletap after dblclick - ie8Dblclick: function (pointer, event, eventTarget) { - if (this.prevTap - && event.clientX === this.prevTap.clientX - && event.clientY === this.prevTap.clientY - && eventTarget === this.prevTap.target) { - - this.downTargets[0] = eventTarget; - this.downTimes[0] = new Date().getTime(); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); - } - }, - - // End interact move events and stop auto-scroll unless inertia is enabled - pointerEnd: function (pointer, event, eventTarget, curEventTarget) { - var endEvent, - target = this.target, - options = target && target.options, - inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, - inertiaStatus = this.inertiaStatus; - - if (this.interacting()) { - - if (inertiaStatus.active) { return; } - - var pointerSpeed, - now = new Date().getTime(), - inertiaPossible = false, - inertia = false, - smoothEnd = false, - endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, - endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, - dx = 0, - dy = 0, - startEvent; - - if (this.dragging) { - if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } - else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } - else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } - } - else { - pointerSpeed = this.pointerDelta.client.speed; - } - - // check if inertia should be started - inertiaPossible = (inertiaOptions && inertiaOptions.enabled - && this.prepared.name !== 'gesture' - && event !== inertiaStatus.startEvent); - - inertia = (inertiaPossible - && (now - this.curCoords.timeStamp) < 50 - && pointerSpeed > inertiaOptions.minSpeed - && pointerSpeed > inertiaOptions.endSpeed); - - if (inertiaPossible && !inertia && (endSnap || endRestrict)) { - - var snapRestrict = {}; - - snapRestrict.snap = snapRestrict.restrict = snapRestrict; - - if (endSnap) { - this.setSnapping(this.curCoords.page, snapRestrict); - if (snapRestrict.locked) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } - - if (endRestrict) { - this.setRestriction(this.curCoords.page, snapRestrict); - if (snapRestrict.restricted) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } - - if (dx || dy) { - smoothEnd = true; - } - } - - if (inertia || smoothEnd) { - utils.copyCoords(inertiaStatus.upCoords, this.curCoords); - - this.pointers[0] = inertiaStatus.startEvent = startEvent = - new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); - - inertiaStatus.t0 = now; - - target.fire(inertiaStatus.startEvent); - - if (inertia) { - inertiaStatus.vx0 = this.pointerDelta.client.vx; - inertiaStatus.vy0 = this.pointerDelta.client.vy; - inertiaStatus.v0 = pointerSpeed; - - this.calcInertia(inertiaStatus); - - var page = utils.extend({}, this.curCoords.page), - origin = scope.getOriginXY(target, this.element), - statusObject; - - page.x = page.x + inertiaStatus.xe - origin.x; - page.y = page.y + inertiaStatus.ye - origin.y; - - statusObject = { - useStatusXY: true, - x: page.x, - y: page.y, - dx: 0, - dy: 0, - snap: null - }; - - statusObject.snap = statusObject; - - dx = dy = 0; - - if (endSnap) { - var snap = this.setSnapping(this.curCoords.page, statusObject); - - if (snap.locked) { - dx += snap.dx; - dy += snap.dy; - } - } - - if (endRestrict) { - var restrict = this.setRestriction(this.curCoords.page, statusObject); - - if (restrict.restricted) { - dx += restrict.dx; - dy += restrict.dy; - } - } - - inertiaStatus.modifiedXe += dx; - inertiaStatus.modifiedYe += dy; - - inertiaStatus.i = reqFrame(this.boundInertiaFrame); - } - else { - inertiaStatus.smoothEnd = true; - inertiaStatus.xe = dx; - inertiaStatus.ye = dy; - - inertiaStatus.sx = inertiaStatus.sy = 0; - - inertiaStatus.i = reqFrame(this.boundSmoothEndFrame); - } - - inertiaStatus.active = true; - return; - } - - if (endSnap || endRestrict) { - // fire a move event at the snapped coordinates - this.pointerMove(pointer, event, eventTarget, curEventTarget, true); - } - } - - if (this.dragging) { - endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); - - var draggableElement = this.element, - drop = this.getDrop(event, draggableElement); - - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; - - var dropEvents = this.getDropEvents(event, endEvent); - - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } - if (dropEvents.deactivate) { - this.fireActiveDrops(dropEvents.deactivate); - } - - target.fire(endEvent); - } - else if (this.resizing) { - endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); - target.fire(endEvent); - } - else if (this.gesturing) { - endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); - target.fire(endEvent); - } - - this.stop(event); - }, - - collectDrops: function (element) { - var drops = [], - elements = [], - i; - - element = element || this.element; - - // collect all dropzones and their elements which qualify for a drop - for (i = 0; i < scope.interactables.length; i++) { - if (!scope.interactables[i].options.drop.enabled) { continue; } - - var current = scope.interactables[i], - accept = current.options.drop.accept; - - // test the draggable element against the dropzone's accept setting - if ((utils.isElement(accept) && accept !== element) - || (scope.isString(accept) - && !scope.matchesSelector(element, accept))) { - - continue; - } - - // query for new elements if necessary - var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; - - for (var j = 0, len = dropElements.length; j < len; j++) { - var currentElement = dropElements[j]; - - if (currentElement === element) { - continue; - } - - drops.push(current); - elements.push(currentElement); - } - } - - return { - dropzones: drops, - elements: elements - }; - }, - - fireActiveDrops: function (event) { - var i, - current, - currentElement, - prevElement; - - // loop through all active dropzones and trigger event - for (i = 0; i < this.activeDrops.dropzones.length; i++) { - current = this.activeDrops.dropzones[i]; - currentElement = this.activeDrops.elements [i]; - - // prevent trigger of duplicate events on same element - if (currentElement !== prevElement) { - // set current element as event target - event.target = currentElement; - current.fire(event); - } - prevElement = currentElement; - } - }, - - // Collect a new set of possible drops and save them in activeDrops. - // setActiveDrops should always be called when a drag has just started or a - // drag event happens while dynamicDrop is true - setActiveDrops: function (dragElement) { - // get dropzones and their elements that could receive the draggable - var possibleDrops = this.collectDrops(dragElement, true); - - this.activeDrops.dropzones = possibleDrops.dropzones; - this.activeDrops.elements = possibleDrops.elements; - this.activeDrops.rects = []; - - for (var i = 0; i < this.activeDrops.dropzones.length; i++) { - this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); - } - }, - - getDrop: function (event, dragElement) { - var validDrops = []; - - if (scope.dynamicDrop) { - this.setActiveDrops(dragElement); - } - - // collect all dropzones and their elements which qualify for a drop - for (var j = 0; j < this.activeDrops.dropzones.length; j++) { - var current = this.activeDrops.dropzones[j], - currentElement = this.activeDrops.elements [j], - rect = this.activeDrops.rects [j]; - - validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) - ? currentElement - : null); - } - - // get the most appropriate dropzone based on DOM depth and order - var dropIndex = scope.indexOfDeepestElement(validDrops), - dropzone = this.activeDrops.dropzones[dropIndex] || null, - element = this.activeDrops.elements [dropIndex] || null; - - return { - dropzone: dropzone, - element: element - }; - }, - - getDropEvents: function (pointerEvent, dragEvent) { - var dropEvents = { - enter : null, - leave : null, - activate : null, - deactivate: null, - move : null, - drop : null - }; - - if (this.dropElement !== this.prevDropElement) { - // if there was a prevDropTarget, create a dragleave event - if (this.prevDropTarget) { - dropEvents.leave = { - target : this.prevDropElement, - dropzone : this.prevDropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragleave' - }; - - dragEvent.dragLeave = this.prevDropElement; - dragEvent.prevDropzone = this.prevDropTarget; - } - // if the dropTarget is not null, create a dragenter event - if (this.dropTarget) { - dropEvents.enter = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragenter' - }; - - dragEvent.dragEnter = this.dropElement; - dragEvent.dropzone = this.dropTarget; - } - } - - if (dragEvent.type === 'dragend' && this.dropTarget) { - dropEvents.drop = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'drop' - }; - - dragEvent.dropzone = this.dropTarget; - } - if (dragEvent.type === 'dragstart') { - dropEvents.activate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropactivate' - }; - } - if (dragEvent.type === 'dragend') { - dropEvents.deactivate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropdeactivate' - }; - } - if (dragEvent.type === 'dragmove' && this.dropTarget) { - dropEvents.move = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - dragmove : dragEvent, - timeStamp : dragEvent.timeStamp, - type : 'dropmove' - }; - dragEvent.dropzone = this.dropTarget; - } - - return dropEvents; - }, - - currentAction: function () { - return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; - }, - - interacting: function () { - return this.dragging || this.resizing || this.gesturing; - }, - - clearTargets: function () { - this.target = this.element = null; - - this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null; - }, - - stop: function (event) { - if (this.interacting()) { - scope.autoScroll.stop(); - this.matches = []; - this.matchElements = []; - - var target = this.target; - - if (target.options.styleCursor) { - target._doc.documentElement.style.cursor = ''; - } - - // prevent Default only if were previously interacting - if (event && scope.isFunction(event.preventDefault)) { - this.checkAndPreventDefault(event, target, this.element); - } - - if (this.dragging) { - this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; - } - - this.clearTargets(); - } - - this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; - this.prepared.name = this.prevEvent = null; - this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; - - // remove pointers if their ID isn't in this.pointerIds - for (var i = 0; i < this.pointers.length; i++) { - if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { - this.pointers.splice(i, 1); - } - } - - for (i = 0; i < scope.interactions.length; i++) { - // remove this interaction if it's not the only one of it's type - if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { - scope.interactions.splice(scope.indexOf(scope.interactions, this), 1); - } - } - }, - - inertiaFrame: function () { - var inertiaStatus = this.inertiaStatus, - options = this.target.options[this.prepared.name].inertia, - lambda = options.resistance, - t = new Date().getTime() / 1000 - inertiaStatus.t0; - - if (t < inertiaStatus.te) { - - var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; - - if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { - inertiaStatus.sx = inertiaStatus.xe * progress; - inertiaStatus.sy = inertiaStatus.ye * progress; - } - else { - var quadPoint = scope.getQuadraticCurvePoint( - 0, 0, - inertiaStatus.xe, inertiaStatus.ye, - inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, - progress); - - inertiaStatus.sx = quadPoint.x; - inertiaStatus.sy = quadPoint.y; - } - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.i = reqFrame(this.boundInertiaFrame); - } - else { - inertiaStatus.sx = inertiaStatus.modifiedXe; - inertiaStatus.sy = inertiaStatus.modifiedYe; - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.active = false; - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); - } - }, - - smoothEndFrame: function () { - var inertiaStatus = this.inertiaStatus, - t = new Date().getTime() - inertiaStatus.t0, - duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; - - if (t < duration) { - inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration); - inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration); - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.i = reqFrame(this.boundSmoothEndFrame); - } - else { - inertiaStatus.sx = inertiaStatus.xe; - inertiaStatus.sy = inertiaStatus.ye; - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.active = false; - inertiaStatus.smoothEnd = false; - - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); - } - }, - - addPointer: function (pointer) { - var id = utils.getPointerId(pointer), - index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); - - if (index === -1) { - index = this.pointerIds.length; - } - - this.pointerIds[index] = id; - this.pointers[index] = pointer; - - return index; - }, - - removePointer: function (pointer) { - var id = utils.getPointerId(pointer), - index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); - - if (index === -1) { return; } - - if (!this.interacting()) { - this.pointers.splice(index, 1); - } - - this.pointerIds .splice(index, 1); - this.downTargets.splice(index, 1); - this.downTimes .splice(index, 1); - this.holdTimers .splice(index, 1); - }, - - recordPointer: function (pointer) { - // Do not update pointers while inertia is active. - // The inertia start event should be this.pointers[0] - if (this.inertiaStatus.active) { return; } - - var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - if (index === -1) { return; } - - this.pointers[index] = pointer; - }, - - collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - // do not fire a tap event if the pointer was moved before being lifted - if (eventType === 'tap' && (this.pointerWasMoved - // or if the pointerup target is different to the pointerdown target - || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) { - return; - } - - var targets = [], - elements = [], - element = eventTarget; - - function collectSelectors (interactable, selector, context) { - var els = scope.ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; - - if (interactable._iEvents[eventType] - && utils.isElement(element) - && scope.inContext(interactable, element) - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, els)) { - - targets.push(interactable); - elements.push(element); - } - } - - while (element) { - if (interact.isSet(element) && interact(element)._iEvents[eventType]) { - targets.push(interact(element)); - elements.push(element); - } - - scope.interactables.forEachSelector(collectSelectors); - - element = scope.parentElement(element); - } - - // create the tap event even if there are no listeners so that - // doubletap can still be created and fired - if (targets.length || eventType === 'tap') { - this.firePointers(pointer, event, eventTarget, targets, elements, eventType); - } - }, - - firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)), - pointerEvent = {}, - i, - // for tap events - interval, createNewDoubleTap; - - // if it's a doubletap then the event properties would have been - // copied from the tap event and provided as the pointer argument - if (eventType === 'doubletap') { - pointerEvent = pointer; - } - else { - utils.extend(pointerEvent, event); - if (event !== pointer) { - utils.extend(pointerEvent, pointer); - } - - pointerEvent.preventDefault = preventOriginalDefault; - pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; - pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; - pointerEvent.interaction = this; - - pointerEvent.timeStamp = new Date().getTime(); - pointerEvent.originalEvent = event; - pointerEvent.type = eventType; - pointerEvent.pointerId = utils.getPointerId(pointer); - pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' - : scope.isString(pointer.pointerType) - ? pointer.pointerType - : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; - } - - if (eventType === 'tap') { - pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex]; - - interval = pointerEvent.timeStamp - this.tapTime; - createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap' - && this.prevTap.target === pointerEvent.target - && interval < 500); - - pointerEvent.double = createNewDoubleTap; - - this.tapTime = pointerEvent.timeStamp; - } - - for (i = 0; i < targets.length; i++) { - pointerEvent.currentTarget = elements[i]; - pointerEvent.interactable = targets[i]; - targets[i].fire(pointerEvent); - - if (pointerEvent.immediatePropagationStopped - ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { - break; - } - } - - if (createNewDoubleTap) { - var doubleTap = {}; - - utils.extend(doubleTap, pointerEvent); - - doubleTap.dt = interval; - doubleTap.type = 'doubletap'; - - this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap'); - - this.prevTap = doubleTap; - } - else if (eventType === 'tap') { - this.prevTap = pointerEvent; - } - }, - - validateSelector: function (pointer, event, matches, matchElements) { - for (var i = 0, len = matches.length; i < len; i++) { - var match = matches[i], - matchElement = matchElements[i], - action = validateAction(match.getAction(pointer, event, this, matchElement), match); - - if (action && scope.withinInteractionLimit(match, matchElement, action)) { - this.target = match; - this.element = matchElement; - - return action; - } - } - }, - - setSnapping: function (pageCoords, status) { - var snap = this.target.options[this.prepared.name].snap, - targets = [], - target, - page, - i; - - status = status || this.snapStatus; - - if (status.useStatusXY) { - page = { x: status.x, y: status.y }; - } - else { - var origin = scope.getOriginXY(this.target, this.element); - - page = utils.extend({}, pageCoords); - - page.x -= origin.x; - page.y -= origin.y; - } - - status.realX = page.x; - status.realY = page.y; - - page.x = page.x - this.inertiaStatus.resumeDx; - page.y = page.y - this.inertiaStatus.resumeDy; - - var len = snap.targets? snap.targets.length : 0; - - for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) { - var relative = { - x: page.x - this.snapOffsets[relIndex].x, - y: page.y - this.snapOffsets[relIndex].y - }; - - for (i = 0; i < len; i++) { - if (scope.isFunction(snap.targets[i])) { - target = snap.targets[i](relative.x, relative.y, this); - } - else { - target = snap.targets[i]; - } - - if (!target) { continue; } - - targets.push({ - x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, - y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, - - range: scope.isNumber(target.range)? target.range: snap.range - }); - } - } - - var closest = { - target: null, - inRange: false, - distance: 0, - range: 0, - dx: 0, - dy: 0 - }; - - for (i = 0, len = targets.length; i < len; i++) { - target = targets[i]; - - var range = target.range, - dx = target.x - page.x, - dy = target.y - page.y, - distance = utils.hypot(dx, dy), - inRange = distance <= range; - - // Infinite targets count as being out of range - // compared to non infinite ones that are in range - if (range === Infinity && closest.inRange && closest.range !== Infinity) { - inRange = false; - } - - if (!closest.target || (inRange - // is the closest target in range? - ? (closest.inRange && range !== Infinity - // the pointer is relatively deeper in this target - ? distance / range < closest.distance / closest.range - // this target has Infinite range and the closest doesn't - : (range === Infinity && closest.range !== Infinity) - // OR this target is closer that the previous closest - || distance < closest.distance) - // The other is not in range and the pointer is closer to this target - : (!closest.inRange && distance < closest.distance))) { - - if (range === Infinity) { - inRange = true; - } + if (/^resize/.test(action)) { + action = 'resize'; + } - closest.target = target; - closest.distance = distance; - closest.range = range; - closest.inRange = inRange; - closest.dx = dx; - closest.dy = dy; + return options[action].autoScroll && options[action].autoScroll.enabled; + }; - status.range = range; - } - } + scope.withinInteractionLimit = function (interactable, element, action) { + var options = interactable.options, + maxActions = options[action.name].max, + maxPerElement = options[action.name].maxPerElement, + activeInteractions = 0, + targetCount = 0, + targetElementCount = 0; - var snapChanged; + for (var i = 0, len = scope.interactions.length; i < len; i++) { + var interaction = scope.interactions[i], + otherAction = interaction.prepared.name, + active = interaction.interacting(); - if (closest.target) { - snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); + if (!active) { continue; } - status.snappedX = closest.target.x; - status.snappedY = closest.target.y; - } - else { - snapChanged = true; + activeInteractions++; - status.snappedX = NaN; - status.snappedY = NaN; + if (activeInteractions >= scope.maxInteractions) { + return false; } - status.dx = closest.dx; - status.dy = closest.dy; - - status.changed = (snapChanged || (closest.inRange && !status.locked)); - status.locked = closest.inRange; - - return status; - }, + if (interaction.target !== interactable) { continue; } - setRestriction: function (pageCoords, status) { - var target = this.target, - restrict = target && target.options[this.prepared.name].restrict, - restriction = restrict && restrict.restriction, - page; + targetCount += (otherAction === action.name)|0; - if (!restriction) { - return status; + if (targetCount >= maxActions) { + return false; } - status = status || this.restrictStatus; - - page = status.useStatusXY - ? page = { x: status.x, y: status.y } - : page = utils.extend({}, pageCoords); + if (interaction.element === element) { + targetElementCount++; - if (status.snap && status.snap.locked) { - page.x += status.snap.dx || 0; - page.y += status.snap.dy || 0; + if (otherAction !== action.name || targetElementCount >= maxPerElement) { + return false; + } } + } - page.x -= this.inertiaStatus.resumeDx; - page.y -= this.inertiaStatus.resumeDy; + return scope.maxInteractions > 0; + }; - status.dx = 0; - status.dy = 0; - status.restricted = false; + // Test for the element that's "above" all other qualifiers + scope.indexOfDeepestElement = function (elements) { + var dropzone, + deepestZone = elements[0], + index = deepestZone? 0: -1, + parent, + deepestZoneParents = [], + dropzoneParents = [], + child, + i, + n; - var rect, restrictedX, restrictedY; + for (i = 1; i < elements.length; i++) { + dropzone = elements[i]; - if (scope.isString(restriction)) { - if (restriction === 'parent') { - restriction = scope.parentElement(this.element); - } - else if (restriction === 'self') { - restriction = target.getRect(this.element); - } - else { - restriction = scope.closest(this.element, restriction); - } + // an element might belong to multiple selector dropzones + if (!dropzone || dropzone === deepestZone) { + continue; + } - if (!restriction) { return status; } + if (!deepestZone) { + deepestZone = dropzone; + index = i; + continue; } - if (scope.isFunction(restriction)) { - restriction = restriction(page.x, page.y, this.element); + // check if the deepest or current are document.documentElement or document.rootElement + // - if the current dropzone is, do nothing and continue + if (dropzone.parentNode === dropzone.ownerDocument) { + continue; + } + // - if deepest is, update with the current dropzone and continue to next + else if (deepestZone.parentNode === dropzone.ownerDocument) { + deepestZone = dropzone; + index = i; + continue; } - if (utils.isElement(restriction)) { - restriction = scope.getElementRect(restriction); + if (!deepestZoneParents.length) { + parent = deepestZone; + while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { + deepestZoneParents.unshift(parent); + parent = parent.parentNode; + } } - rect = restriction; + // if this element is an svg element and the current deepest is + // an HTMLElement + if (deepestZone instanceof scope.HTMLElement + && dropzone instanceof scope.SVGElement + && !(dropzone instanceof scope.SVGSVGElement)) { - if (!restriction) { - restrictedX = page.x; - restrictedY = page.y; - } - // object is assumed to have - // x, y, width, height or - // left, top, right, bottom - else if ('x' in restriction && 'y' in restriction) { - restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top ); + if (dropzone === deepestZone.parentNode) { + continue; + } + + parent = dropzone.ownerSVGElement; } else { - restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top ); + parent = dropzone; } - status.dx = restrictedX - page.x; - status.dy = restrictedY - page.y; + dropzoneParents = []; - status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; - status.restricted = !!(status.dx || status.dy); + while (parent.parentNode !== parent.ownerDocument) { + dropzoneParents.unshift(parent); + parent = parent.parentNode; + } - status.restrictedX = restrictedX; - status.restrictedY = restrictedY; + n = 0; - return status; - }, + // get (position of last common ancestor) + 1 + while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { + n++; + } - checkAndPreventDefault: function (event, interactable, element) { - if (!(interactable = interactable || this.target)) { return; } + var parents = [ + dropzoneParents[n - 1], + dropzoneParents[n], + deepestZoneParents[n] + ]; - var options = interactable.options, - prevent = options.preventDefault; + child = parents[0].lastChild; - if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { - // do not preventDefault on pointerdown if the prepared action is a drag - // and dragging can only start from a certain direction - this allows - // a touch to pan the viewport if a drag isn't in the right direction - if (/down|start/i.test(event.type) - && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { + while (child) { + if (child === parents[1]) { + deepestZone = dropzone; + index = i; + deepestZoneParents = []; - return; + break; } - - // with manualStart, only preventDefault while interacting - if (options[this.prepared.name] && options[this.prepared.name].manualStart - && !this.interacting()) { - return; + else if (child === parents[2]) { + break; } - event.preventDefault(); - return; - } - - if (prevent === 'always') { - event.preventDefault(); - return; + child = child.previousSibling; } - }, - - calcInertia: function (status) { - var inertiaOptions = this.target.options[this.prepared.name].inertia, - lambda = inertiaOptions.resistance, - inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; + } - status.x0 = this.prevEvent.pageX; - status.y0 = this.prevEvent.pageY; - status.t0 = status.startEvent.timeStamp / 1000; - status.sx = status.sy = 0; + return index; + }; - status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; - status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; - status.te = inertiaDur; + scope.matchesSelector = function (element, selector, nodeList) { + if (scope.ie8MatchesSelector) { + return scope.ie8MatchesSelector(element, selector, nodeList); + } - status.lambda_v0 = lambda / status.v0; - status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; - }, + // remove /deep/ from selectors if shadowDOM polyfill is used + if (scope.window !== scope.realWindow) { + selector = selector.replace(/\/deep\//g, ' '); + } - autoScrollMove: function (pointer) { - if (!(this.interacting() - && scope.checkAutoScroll(this.target, this.prepared.name))) { - return; - } + return element[scope.prefixedMatchesSelector](selector); + }; - if (this.inertiaStatus.active) { - scope.autoScroll.x = scope.autoScroll.y = 0; - return; + scope.matchesUpTo = function (element, selector, limit) { + while (utils.isElement(element)) { + if (scope.matchesSelector(element, selector)) { + return true; } - var top, - right, - bottom, - left, - options = this.target.options[this.prepared.name].autoScroll, - container = options.container || scope.getWindow(this.element); - - if (scope.isWindow(container)) { - left = pointer.clientX < scope.autoScroll.margin; - top = pointer.clientY < scope.autoScroll.margin; - right = pointer.clientX > container.innerWidth - scope.autoScroll.margin; - bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin; - } - else { - var rect = scope.getElementRect(container); + element = scope.parentElement(element); - left = pointer.clientX < rect.left + scope.autoScroll.margin; - top = pointer.clientY < rect.top + scope.autoScroll.margin; - right = pointer.clientX > rect.right - scope.autoScroll.margin; - bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin; + if (element === limit) { + return scope.matchesSelector(element, selector); } + } - scope.autoScroll.x = (right ? 1: left? -1: 0); - scope.autoScroll.y = (bottom? 1: top? -1: 0); + return false; + }; - if (!scope.autoScroll.isScrolling) { - // set the autoScroll properties to those of the target - scope.autoScroll.margin = options.margin; - scope.autoScroll.speed = options.speed; + // For IE8's lack of an Element#matchesSelector + // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified + if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { + scope.ie8MatchesSelector = function (element, selector, elems) { + elems = elems || element.parentNode.querySelectorAll(selector); - scope.autoScroll.start(this); + for (var i = 0, len = elems.length; i < len; i++) { + if (elems[i] === element) { + return true; + } } - }, - _updateEventTargets: function (target, currentTarget) { - this._eventTarget = target; - this._curEventTarget = currentTarget; - } + return false; + }; + } - }; + var Interaction = require('./Interaction'); function getInteractionFromPointer (pointer, eventType, eventTarget) { var i = 0, len = scope.interactions.length, @@ -2790,300 +783,10 @@ }); } - function InteractEvent (interaction, event, action, phase, element, related) { - var client, - page, - target = interaction.target, - snapStatus = interaction.snapStatus, - restrictStatus = interaction.restrictStatus, - pointers = interaction.pointers, - deltaSource = (target && target.options || scope.defaultOptions).deltaSource, - sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - options = target? target.options: scope.defaultOptions, - origin = scope.getOriginXY(target, element), - starting = phase === 'start', - ending = phase === 'end', - coords = starting? interaction.startCoords : interaction.curCoords; - - element = element || interaction.element; - - page = utils.extend({}, coords.page); - client = utils.extend({}, coords.client); - - page.x -= origin.x; - page.y -= origin.y; - - client.x -= origin.x; - client.y -= origin.y; - - var relativePoints = options[action].snap && options[action].snap.relativePoints ; - - if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { - this.snap = { - range : snapStatus.range, - locked : snapStatus.locked, - x : snapStatus.snappedX, - y : snapStatus.snappedY, - realX : snapStatus.realX, - realY : snapStatus.realY, - dx : snapStatus.dx, - dy : snapStatus.dy - }; - - if (snapStatus.locked) { - page.x += snapStatus.dx; - page.y += snapStatus.dy; - client.x += snapStatus.dx; - client.y += snapStatus.dy; - } - } - - if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { - page.x += restrictStatus.dx; - page.y += restrictStatus.dy; - client.x += restrictStatus.dx; - client.y += restrictStatus.dy; - - this.restrict = { - dx: restrictStatus.dx, - dy: restrictStatus.dy - }; - } - - this.pageX = page.x; - this.pageY = page.y; - this.clientX = client.x; - this.clientY = client.y; - - this.x0 = interaction.startCoords.page.x - origin.x; - this.y0 = interaction.startCoords.page.y - origin.y; - this.clientX0 = interaction.startCoords.client.x - origin.x; - this.clientY0 = interaction.startCoords.client.y - origin.y; - this.ctrlKey = event.ctrlKey; - this.altKey = event.altKey; - this.shiftKey = event.shiftKey; - this.metaKey = event.metaKey; - this.button = event.button; - this.target = element; - this.t0 = interaction.downTimes[0]; - this.type = action + (phase || ''); - - this.interaction = interaction; - this.interactable = target; - - var inertiaStatus = interaction.inertiaStatus; - - if (inertiaStatus.active) { - this.detail = 'inertia'; - } - - if (related) { - this.relatedTarget = related; - } - - // end event dx, dy is difference between start and end points - if (ending) { - if (deltaSource === 'client') { - this.dx = client.x - interaction.startCoords.client.x; - this.dy = client.y - interaction.startCoords.client.y; - } - else { - this.dx = page.x - interaction.startCoords.page.x; - this.dy = page.y - interaction.startCoords.page.y; - } - } - else if (starting) { - this.dx = 0; - this.dy = 0; - } - // copy properties from previousmove if starting inertia - else if (phase === 'inertiastart') { - this.dx = interaction.prevEvent.dx; - this.dy = interaction.prevEvent.dy; - } - else { - if (deltaSource === 'client') { - this.dx = client.x - interaction.prevEvent.clientX; - this.dy = client.y - interaction.prevEvent.clientY; - } - else { - this.dx = page.x - interaction.prevEvent.pageX; - this.dy = page.y - interaction.prevEvent.pageY; - } - } - if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' - && !inertiaStatus.active - && options[action].inertia && options[action].inertia.zeroResumeDelta) { - - inertiaStatus.resumeDx += this.dx; - inertiaStatus.resumeDy += this.dy; - - this.dx = this.dy = 0; - } - - if (action === 'resize' && interaction.resizeAxes) { - if (options.resize.square) { - if (interaction.resizeAxes === 'y') { - this.dx = this.dy; - } - else { - this.dy = this.dx; - } - this.axes = 'xy'; - } - else { - this.axes = interaction.resizeAxes; - - if (interaction.resizeAxes === 'x') { - this.dy = 0; - } - else if (interaction.resizeAxes === 'y') { - this.dx = 0; - } - } - } - else if (action === 'gesture') { - this.touches = [pointers[0], pointers[1]]; - - if (starting) { - this.distance = utils.touchDistance(pointers, deltaSource); - this.box = utils.touchBBox(pointers); - this.scale = 1; - this.ds = 0; - this.angle = utils.touchAngle(pointers, undefined, deltaSource); - this.da = 0; - } - else if (ending || event instanceof InteractEvent) { - this.distance = interaction.prevEvent.distance; - this.box = interaction.prevEvent.box; - this.scale = interaction.prevEvent.scale; - this.ds = this.scale - 1; - this.angle = interaction.prevEvent.angle; - this.da = this.angle - interaction.gesture.startAngle; - } - else { - this.distance = utils.touchDistance(pointers, deltaSource); - this.box = utils.touchBBox(pointers); - this.scale = this.distance / interaction.gesture.startDistance; - this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); - - this.ds = this.scale - interaction.gesture.prevScale; - this.da = this.angle - interaction.gesture.prevAngle; - } - } - - if (starting) { - this.timeStamp = interaction.downTimes[0]; - this.dt = 0; - this.duration = 0; - this.speed = 0; - this.velocityX = 0; - this.velocityY = 0; - } - else if (phase === 'inertiastart') { - this.timeStamp = interaction.prevEvent.timeStamp; - this.dt = interaction.prevEvent.dt; - this.duration = interaction.prevEvent.duration; - this.speed = interaction.prevEvent.speed; - this.velocityX = interaction.prevEvent.velocityX; - this.velocityY = interaction.prevEvent.velocityY; - } - else { - this.timeStamp = new Date().getTime(); - this.dt = this.timeStamp - interaction.prevEvent.timeStamp; - this.duration = this.timeStamp - interaction.downTimes[0]; - - if (event instanceof InteractEvent) { - var dx = this[sourceX] - interaction.prevEvent[sourceX], - dy = this[sourceY] - interaction.prevEvent[sourceY], - dt = this.dt / 1000; - - this.speed = utils.hypot(dx, dy) / dt; - this.velocityX = dx / dt; - this.velocityY = dy / dt; - } - // if normal move or end event, use previous user event coords - else { - // speed and velocity in pixels per second - this.speed = interaction.pointerDelta[deltaSource].speed; - this.velocityX = interaction.pointerDelta[deltaSource].vx; - this.velocityY = interaction.pointerDelta[deltaSource].vy; - } - } - - if ((ending || phase === 'inertiastart') - && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) { - - var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI, - overlap = 22.5; - - if (angle < 0) { - angle += 360; - } - - var left = 135 - overlap <= angle && angle < 225 + overlap, - up = 225 - overlap <= angle && angle < 315 + overlap, - - right = !left && (315 - overlap <= angle || angle < 45 + overlap), - down = !up && 45 - overlap <= angle && angle < 135 + overlap; - - this.swipe = { - up : up, - down : down, - left : left, - right: right, - angle: angle, - speed: interaction.prevEvent.speed, - velocity: { - x: interaction.prevEvent.velocityX, - y: interaction.prevEvent.velocityY - } - }; - } - } - - InteractEvent.prototype = { - preventDefault: utils.blank, - stopImmediatePropagation: function () { - this.immediatePropagationStopped = this.propagationStopped = true; - }, - stopPropagation: function () { - this.propagationStopped = true; - } - }; - function preventOriginalDefault () { this.originalEvent.preventDefault(); } - function getActionCursor (action) { - var cursor = ''; - - if (action.name === 'drag') { - cursor = scope.actionCursors.drag; - } - if (action.name === 'resize') { - if (action.axis) { - cursor = scope.actionCursors[action.name + action.axis]; - } - else if (action.edges) { - var cursorKey = 'resize', - edgeNames = ['top', 'bottom', 'left', 'right']; - - for (var i = 0; i < 4; i++) { - if (action.edges[edgeNames[i]]) { - cursorKey += edgeNames[i]; - } - } - - cursor = scope.actionCursors[cursorKey]; - } - } - - return cursor; - } - function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { // false, '', undefined, null if (!value) { return false; } @@ -3187,40 +890,12 @@ return null; } - // Check if action is enabled globally and the current target supports it - // If so, return the validated action. Otherwise, return null - function validateAction (action, interactable) { - if (!scope.isObject(action)) { return null; } - - var actionName = action.name, - options = interactable.options; - - if (( (actionName === 'resize' && options.resize.enabled ) - || (actionName === 'drag' && options.drag.enabled ) - || (actionName === 'gesture' && options.gesture.enabled)) - && scope.actionIsEnabled[actionName]) { - - if (actionName === 'resize' || actionName === 'resizeyx') { - actionName = 'resizexy'; - } - - return action; - } - return null; - } - - var listeners = {}, - interactionListeners = [ - 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove', - 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', - 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', - 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' - ]; + var InteractEvent = require('./InteractEvent'); for (var i = 0, len = interactionListeners.length; i < len; i++) { - var name = interactionListeners[i]; + var listenerName = interactionListeners[i]; - listeners[name] = doOnInteractions(name); + scope.listeners[listenerName] = doOnInteractions(listenerName); } // bound to the interactable context when a DOM event @@ -3370,14 +1045,14 @@ if (utils.isElement(element, _window)) { if (scope.PointerEvent) { - events.add(this._element, scope.pEventTypes.down, listeners.pointerDown ); - events.add(this._element, scope.pEventTypes.move, listeners.pointerHover); + events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown ); + events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover); } else { - events.add(this._element, 'mousedown' , listeners.pointerDown ); - events.add(this._element, 'mousemove' , listeners.pointerHover); - events.add(this._element, 'touchstart', listeners.pointerDown ); - events.add(this._element, 'touchmove' , listeners.pointerHover); + events.add(this._element, 'mousedown' , scope.listeners.pointerDown ); + events.add(this._element, 'mousemove' , scope.listeners.pointerHover); + events.add(this._element, 'touchstart', scope.listeners.pointerDown ); + events.add(this._element, 'touchmove' , scope.listeners.pointerHover); } } } @@ -4962,9 +2637,9 @@ pointerIds : interaction.pointerIds, pointers : interaction.pointers, - addPointer : listeners.addPointer, - removePointer : listeners.removePointer, - recordPointer : listeners.recordPointer, + addPointer : scope.listeners.addPointer, + removePointer : scope.listeners.removePointer, + recordPointer : scope.listeners.recordPointer, snap : interaction.snapStatus, restrict : interaction.restrictStatus, @@ -4982,13 +2657,13 @@ defaultActionChecker : defaultActionChecker, actionCursors : scope.actionCursors, - dragMove : listeners.dragMove, - resizeMove : listeners.resizeMove, - gestureMove : listeners.gestureMove, - pointerUp : listeners.pointerUp, - pointerDown : listeners.pointerDown, - pointerMove : listeners.pointerMove, - pointerHover : listeners.pointerHover, + dragMove : scope.listeners.dragMove, + resizeMove : scope.listeners.resizeMove, + gestureMove : scope.listeners.gestureMove, + pointerUp : scope.listeners.pointerUp, + pointerDown : scope.listeners.pointerDown, + pointerMove : scope.listeners.pointerMove, + pointerHover : scope.listeners.pointerHover, eventTypes : scope.eventTypes, @@ -5183,31 +2858,31 @@ out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }; } - events.add(doc, scope.pEventTypes.down , listeners.selectorDown ); - events.add(doc, scope.pEventTypes.move , listeners.pointerMove ); - events.add(doc, scope.pEventTypes.over , listeners.pointerOver ); - events.add(doc, scope.pEventTypes.out , listeners.pointerOut ); - events.add(doc, scope.pEventTypes.up , listeners.pointerUp ); - events.add(doc, scope.pEventTypes.cancel, listeners.pointerCancel); + events.add(doc, scope.pEventTypes.down , scope.listeners.selectorDown ); + events.add(doc, scope.pEventTypes.move , scope.listeners.pointerMove ); + events.add(doc, scope.pEventTypes.over , scope.listeners.pointerOver ); + events.add(doc, scope.pEventTypes.out , scope.listeners.pointerOut ); + events.add(doc, scope.pEventTypes.up , scope.listeners.pointerUp ); + events.add(doc, scope.pEventTypes.cancel, scope.listeners.pointerCancel); // autoscroll - events.add(doc, scope.pEventTypes.move, listeners.autoScrollMove); + events.add(doc, scope.pEventTypes.move, scope.listeners.autoScrollMove); } else { - events.add(doc, 'mousedown', listeners.selectorDown); - events.add(doc, 'mousemove', listeners.pointerMove ); - events.add(doc, 'mouseup' , listeners.pointerUp ); - events.add(doc, 'mouseover', listeners.pointerOver ); - events.add(doc, 'mouseout' , listeners.pointerOut ); + events.add(doc, 'mousedown', scope.listeners.selectorDown); + events.add(doc, 'mousemove', scope.listeners.pointerMove ); + events.add(doc, 'mouseup' , scope.listeners.pointerUp ); + events.add(doc, 'mouseover', scope.listeners.pointerOver ); + events.add(doc, 'mouseout' , scope.listeners.pointerOut ); - events.add(doc, 'touchstart' , listeners.selectorDown ); - events.add(doc, 'touchmove' , listeners.pointerMove ); - events.add(doc, 'touchend' , listeners.pointerUp ); - events.add(doc, 'touchcancel', listeners.pointerCancel); + events.add(doc, 'touchstart' , scope.listeners.selectorDown ); + events.add(doc, 'touchmove' , scope.listeners.pointerMove ); + events.add(doc, 'touchend' , scope.listeners.pointerUp ); + events.add(doc, 'touchcancel', scope.listeners.pointerCancel); // autoscroll - events.add(doc, 'mousemove', listeners.autoScrollMove); - events.add(doc, 'touchmove', listeners.autoScrollMove); + events.add(doc, 'mousemove', scope.listeners.autoScrollMove); + events.add(doc, 'touchmove', scope.listeners.autoScrollMove); } events.add(win, 'blur', endAllInteractions); @@ -5217,11 +2892,11 @@ var parentDoc = win.frameElement.ownerDocument, parentWindow = parentDoc.defaultView; - events.add(parentDoc , 'mouseup' , listeners.pointerEnd); - events.add(parentDoc , 'touchend' , listeners.pointerEnd); - events.add(parentDoc , 'touchcancel' , listeners.pointerEnd); - events.add(parentDoc , 'pointerup' , listeners.pointerEnd); - events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd); + events.add(parentDoc , 'mouseup' , scope.listeners.pointerEnd); + events.add(parentDoc , 'touchend' , scope.listeners.pointerEnd); + events.add(parentDoc , 'touchcancel' , scope.listeners.pointerEnd); + events.add(parentDoc , 'pointerup' , scope.listeners.pointerEnd); + events.add(parentDoc , 'MSPointerUp' , scope.listeners.pointerEnd); events.add(parentWindow, 'blur' , endAllInteractions ); } } @@ -5270,4 +2945,4 @@ } else { scope.realWindow.interact = interact; - } \ No newline at end of file + } From 8dba1e4b419b4427cebf4c89aab8b450ed4c2afd Mon Sep 17 00:00:00 2001 From: stephen-james Date: Tue, 2 Jun 2015 21:09:23 +0100 Subject: [PATCH 023/131] Minor refactor, avoiding use of `Boolean()` function in favour of `!!` which is more performant http://jsperf.com/bool-vs-doublenot --- src/utils/events.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/events.js b/src/utils/events.js index d2a06e0c0..4f7767e15 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -64,7 +64,7 @@ function add (element, type, listener, useCapture) { } }; - ret = element[addEvent](on + type, wrapped, Boolean(useCapture)); + ret = element[addEvent](on + type, wrapped, !!useCapture); if (listenerIndex === -1) { listeners.supplied.push(listener); @@ -76,7 +76,7 @@ function add (element, type, listener, useCapture) { } } else { - ret = element[addEvent](type, listener, useCapture || false); + ret = element[addEvent](type, listener, !!useCapture); } target.events[type].push(listener); @@ -116,13 +116,13 @@ function remove (element, type, listener, useCapture) { if (listener === 'all') { for (i = 0; i < len; i++) { - remove(element, type, target.events[type][i], Boolean(useCapture)); + remove(element, type, target.events[type][i], !!useCapture); } return; } else { for (i = 0; i < len; i++) { if (target.events[type][i] === listener) { - element[removeEvent](on + type, wrapped, useCapture || false); + element[removeEvent](on + type, wrapped, !!useCapture); target.events[type].splice(i, 1); if (useAttachEvent && listeners) { From 1e2ce5a7faf36d193720ef51f1134b4331ab5308 Mon Sep 17 00:00:00 2001 From: stephen-james Date: Tue, 2 Jun 2015 21:27:01 +0100 Subject: [PATCH 024/131] readability with object literal for module and single export A subjective change, but takes out the repetition of `module.exports.`, may make it easier to grok --- src/utils/isType.js | 47 ++++++++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/src/utils/isType.js b/src/utils/isType.js index cce37821f..1607d1cb5 100644 --- a/src/utils/isType.js +++ b/src/utils/isType.js @@ -3,26 +3,37 @@ var win = require('./window'), domObjects = require('./domObjects'); -module.exports.isElement = function (o) { - if (!o || (typeof o !== 'object')) { return false; } +var isType = { + isElement : function (o) { + if (!o || (typeof o !== 'object')) { return false; } + + var _window = win.getWindow(o) || win.window; + + return (/object|function/.test(typeof _window.Element) + ? o instanceof _window.Element //DOM2 + : o.nodeType === 1 && typeof o.nodeName === "string"); + }, + + isWindow : function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }, - var _window = win.getWindow(o) || win.window; + isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }, - return (/object|function/.test(typeof _window.Element) - ? o instanceof _window.Element //DOM2 - : o.nodeType === 1 && typeof o.nodeName === "string"); -}; + isArray : function (thing) { + return isObject(thing) + && (typeof thing.length !== undefined) + && isFunction(thing.splice); + }, + + isObject : function (thing) { return !!thing && (typeof thing === 'object'); }, + + isFunction : function (thing) { return typeof thing === 'function'; }, + + isNumber : function (thing) { return typeof thing === 'number' ; }, + + isBool : function (thing) { return typeof thing === 'boolean' ; }, -module.exports.isWindow = function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }; -module.exports.isDocFrag = function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }; -module.exports.isArray = function (thing) { - return module.exports.isObject(thing) - && (typeof thing.length !== undefined) - && module.exports.isFunction(thing.splice); + isString : function (thing) { return typeof thing === 'string' ; } + }; -module.exports.isObject = function (thing) { return !!thing && (typeof thing === 'object'); }; -module.exports.isFunction = function (thing) { return typeof thing === 'function'; }; -module.exports.isNumber = function (thing) { return typeof thing === 'number' ; }; -module.exports.isBool = function (thing) { return typeof thing === 'boolean' ; }; -module.exports.isString = function (thing) { return typeof thing === 'string' ; }; +module.exports = isType; \ No newline at end of file From 173562daf36fadcaf141fbe4df6180b9eaf7e953 Mon Sep 17 00:00:00 2001 From: stephen-james Date: Tue, 2 Jun 2015 21:31:16 +0100 Subject: [PATCH 025/131] correction of non checking `typeof` Since `typeof` always returns a string (ie. `typeof someUndefinedVar` renders `"undefined"`) we'll need to check against `"undefined"` not `undefined` --- src/utils/isType.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/isType.js b/src/utils/isType.js index 1607d1cb5..c2c2282bd 100644 --- a/src/utils/isType.js +++ b/src/utils/isType.js @@ -20,7 +20,7 @@ var isType = { isArray : function (thing) { return isObject(thing) - && (typeof thing.length !== undefined) + && (typeof thing.length !== 'undefined') && isFunction(thing.splice); }, From f16d680f61e44c8309ee46d0819ac4dc7291a3ff Mon Sep 17 00:00:00 2001 From: stephen-james Date: Tue, 2 Jun 2015 21:40:55 +0100 Subject: [PATCH 026/131] remove `scope` dependency from `pointerUtils` replaced with references to `browser`, `isType` and `InteractEvent` --- src/utils/pointerUtils.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index bc6d9a3c2..0b32204ee 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -6,9 +6,9 @@ var pointerUtils = {}, win = require('./window'), hypot = require('./hypot'), extend = require('./extend'), - - // scope shouldn't be necessary in this module - scope = require('../scope'); + browser = require('./browser'), + isType = require('./isType'), + InteractEvent = require('../InteractEvent'); pointerUtils.copyCoords = function (dest, src) { dest.page = dest.page || {}; @@ -75,7 +75,7 @@ pointerUtils.getXY = function (type, pointer, xy) { pointerUtils.getPageXY = function (pointer, page, interaction) { page = page || {}; - if (pointer instanceof scope.InteractEvent) { + if (pointer instanceof InteractEvent) { if (/inertiastart/.test(pointer.type)) { interaction = interaction || pointer.interaction; @@ -90,7 +90,7 @@ pointerUtils.getPageXY = function (pointer, page, interaction) { } } // Opera Mobile handles the viewport and scrolling oddly - else if (scope.isOperaMobile) { + else if (browser.isOperaMobile) { pointerUtils.getXY('screen', pointer, page); page.x += win.window.scrollX; @@ -106,7 +106,7 @@ pointerUtils.getPageXY = function (pointer, page, interaction) { pointerUtils.getClientXY = function (pointer, client, interaction) { client = client || {}; - if (pointer instanceof scope.InteractEvent) { + if (pointer instanceof InteractEvent) { if (/inertiastart/.test(pointer.type)) { extend(client, interaction.inertiaStatus.upCoords.client); @@ -120,14 +120,14 @@ pointerUtils.getClientXY = function (pointer, client, interaction) { } else { // Opera Mobile handles the viewport and scrolling oddly - pointerUtils.getXY(scope.isOperaMobile? 'screen': 'client', pointer, client); + pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client); } return client; }; pointerUtils.getPointerId = function (pointer) { - return scope.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; + return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; }; module.exports = pointerUtils; From de34cc3e55cb4580220a26e0157a61db9ae4f8c4 Mon Sep 17 00:00:00 2001 From: stephen-james Date: Tue, 2 Jun 2015 22:01:13 +0100 Subject: [PATCH 027/131] clean up module implementation by removing conditional exports Again a subjective change, but by having exports statements in conditional blocks it makes it unclear what values are being exported by the module, making it necessary to read all the code to get a grasp of what is happening. Effectively, exporting inside conditionals is adding cyclomatic complexity to the module definition, its much clearer to the reader of the module if they can look in a single literal and see what they can expect to get from the module. Cleaned up the Shadow DOM check to make it more self explanatory --- src/utils/window.js | 49 ++++++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/src/utils/window.js b/src/utils/window.js index 0cc8f55f0..cabb08016 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -1,37 +1,40 @@ 'use strict'; -if (typeof window === 'undefined') { - module.exports.window = undefined; - module.exports.realWindow = undefined; -} -else { - // get wrapped window if using Shadow DOM polyfill - - module.exports.realWindow = window; +var isWindow = require('./isType').isWindow; +var isShadowDom = function() { // create a TextNode var el = window.document.createTextNode(''); // check if it's wrapped by a polyfill - if (el.ownerDocument !== window.document + return el.ownerDocument !== window.document && typeof window.wrap === 'function' - && window.wrap(el) === el) { - // return wrapped window - module.exports.window = window.wrap(window); - } + && window.wrap(el) === el; +}; - // no Shadow DOM polyfil or native implementation - module.exports.window = window; -} +var win = { -var isWindow = require('./isType').isWindow; + window: undefined, -module.exports.getWindow = function getWindow (node) { - if (isWindow(node)) { - return node; - } + realWindow: window, + + getWindow: function getWindow (node) { + if (isWindow(node)) { + return node; + } - var rootNode = (node.ownerDocument || node); + var rootNode = (node.ownerDocument || node); - return rootNode.defaultView || rootNode.parentWindow || module.exports.window; + return rootNode.defaultView || rootNode.parentWindow || win.window; + } }; + +if (typeof window !== 'undefined') { + if (isShadowDom()) { + win.window = window.wrap(window); + } else { + win.window = window; + } +} + +module.exports = win; \ No newline at end of file From f625191dea7f38ca8c19782d2a638ef614f2f33c Mon Sep 17 00:00:00 2001 From: stephen-james Date: Tue, 2 Jun 2015 22:08:22 +0100 Subject: [PATCH 028/131] removing trailing comma --- src/defaultOptions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/defaultOptions.js b/src/defaultOptions.js index 8ffcde372..d0a046963 100644 --- a/src/defaultOptions.js +++ b/src/defaultOptions.js @@ -25,7 +25,7 @@ module.exports = { inertia: null, autoScroll: null, - axis: 'xy', + axis: 'xy' }, drop: { From f5e471e54797a5773f581c3413a4252bbf2b060d Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 2 Jun 2015 01:25:54 +0100 Subject: [PATCH 029/131] Clear targets on pointerUp even if not interacting Fixes some issues with touch interactions. Same changes as commit cd3b8c9. --- src/Interaction.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 33e5f10a5..f7a095b91 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -781,7 +781,7 @@ Interaction.prototype = { if (starting && (this.target.options[this.prepared.name].manualStart || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { - this.stop(); + this.stop(event); return; } @@ -1492,10 +1492,10 @@ Interaction.prototype = { if (this.dragging) { this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; } - - this.clearTargets(); } + this.clearTargets(); + this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; this.prepared.name = this.prevEvent = null; this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; @@ -2077,4 +2077,4 @@ Interaction.prototype = { }; -module.exports = Interaction; \ No newline at end of file +module.exports = Interaction; From 9e99d435089d323a38b42f5f2564fc111fce25c2 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 2 Jun 2015 01:32:32 +0100 Subject: [PATCH 030/131] Remove duplicated pointerUtils methods --- src/interact.js | 106 ------------------------------------------------ 1 file changed, 106 deletions(-) diff --git a/src/interact.js b/src/interact.js index dd6f1c819..f045ce4bd 100644 --- a/src/interact.js +++ b/src/interact.js @@ -185,112 +185,6 @@ }; }; - utils.getTouchPair = function (event) { - var touches = []; - - // array of touches is supplied - if (scope.isArray(event)) { - touches[0] = event[0]; - touches[1] = event[1]; - } - // an event - else { - if (event.type === 'touchend') { - if (event.touches.length === 1) { - touches[0] = event.touches[0]; - touches[1] = event.changedTouches[0]; - } - else if (event.touches.length === 0) { - touches[0] = event.changedTouches[0]; - touches[1] = event.changedTouches[1]; - } - } - else { - touches[0] = event.touches[0]; - touches[1] = event.touches[1]; - } - } - - return touches; - }; - - utils.touchAverage = function (event) { - var touches = utils.getTouchPair(event); - - return { - pageX: (touches[0].pageX + touches[1].pageX) / 2, - pageY: (touches[0].pageY + touches[1].pageY) / 2, - clientX: (touches[0].clientX + touches[1].clientX) / 2, - clientY: (touches[0].clientY + touches[1].clientY) / 2 - }; - }; - - utils.touchBBox = function (event) { - if (!event.length && !(event.touches && event.touches.length > 1)) { - return; - } - - var touches = utils.getTouchPair(event), - minX = Math.min(touches[0].pageX, touches[1].pageX), - minY = Math.min(touches[0].pageY, touches[1].pageY), - maxX = Math.max(touches[0].pageX, touches[1].pageX), - maxY = Math.max(touches[0].pageY, touches[1].pageY); - - return { - x: minX, - y: minY, - left: minX, - top: minY, - width: maxX - minX, - height: maxY - minY - }; - }; - - utils.touchDistance = function (event, deltaSource) { - deltaSource = deltaSource || scope.defaultOptions.deltaSource; - - var sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - touches = utils.getTouchPair(event); - - - var dx = touches[0][sourceX] - touches[1][sourceX], - dy = touches[0][sourceY] - touches[1][sourceY]; - - return utils.hypot(dx, dy); - }; - - utils.touchAngle = function (event, prevAngle, deltaSource) { - deltaSource = deltaSource || scope.defaultOptions.deltaSource; - - var sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - touches = utils.getTouchPair(event), - dx = touches[0][sourceX] - touches[1][sourceX], - dy = touches[0][sourceY] - touches[1][sourceY], - angle = 180 * Math.atan(dy / dx) / Math.PI; - - if (scope.isNumber(prevAngle)) { - var dr = angle - prevAngle, - drClamped = dr % 360; - - if (drClamped > 315) { - angle -= 360 + (angle / 360)|0 * 360; - } - else if (drClamped > 135) { - angle -= 180 + (angle / 360)|0 * 360; - } - else if (drClamped < -315) { - angle += 360 + (angle / 360)|0 * 360; - } - else if (drClamped < -135) { - angle += 180 + (angle / 360)|0 * 360; - } - } - - return angle; - }; - scope.getOriginXY = function (interactable, element) { var origin = interactable ? interactable.options.origin From becbf30e1adeba3244eca45636a27c464cd8faad Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 3 Jun 2015 02:00:42 +0100 Subject: [PATCH 031/131] Move prefixedMatchesSelector from scope to browser --- src/interact.js | 6 +++--- src/utils/browser.js | 10 +++++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/interact.js b/src/interact.js index f045ce4bd..0151a8359 100644 --- a/src/interact.js +++ b/src/interact.js @@ -124,7 +124,7 @@ scope.globalEvents = {}; // prefix matchesSelector - scope.prefixedMatchesSelector = 'matches' in Element.prototype? + browser.prefixedMatchesSelector = 'matches' in Element.prototype? 'matches': 'webkitMatchesSelector' in Element.prototype? 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? @@ -499,7 +499,7 @@ selector = selector.replace(/\/deep\//g, ' '); } - return element[scope.prefixedMatchesSelector](selector); + return element[browser.prefixedMatchesSelector](selector); }; scope.matchesUpTo = function (element, selector, limit) { @@ -520,7 +520,7 @@ // For IE8's lack of an Element#matchesSelector // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { + if (!(browser.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[browser.prefixedMatchesSelector])) { scope.ie8MatchesSelector = function (element, selector, elems) { elems = elems || element.parentNode.querySelectorAll(selector); diff --git a/src/utils/browser.js b/src/utils/browser.js index 945bccae6..9a78afe77 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -20,7 +20,15 @@ var browser = { // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)), - isIe9OrOlder : domObjects.document.all && !win.window.atob + isIe9OrOlder : domObjects.document.all && !win.window.atob, + + // prefix matchesSelector + prefixedMatchesSelector: 'matches' in Element.prototype? + 'matches': 'webkitMatchesSelector' in Element.prototype? + 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? + 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? + 'oMatchesSelector': 'msMatchesSelector' + }; module.exports = browser; From 99476e7ebb5cd90236bbe249f7bc2da2bc3adef5 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 3 Jun 2015 02:42:35 +0100 Subject: [PATCH 032/131] Remove UMD from src/interact.js browserify's standalone option can produce UMD --- src/interact.js | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/src/interact.js b/src/interact.js index 0151a8359..b34b95fc9 100644 --- a/src/interact.js +++ b/src/interact.js @@ -2822,21 +2822,4 @@ scope.Interaction = Interaction; scope.InteractEvent = InteractEvent; - /* global exports: true, module, define */ - - // http://documentcloud.github.io/underscore/docs/underscore.html#section-11 - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = interact; - } - exports.interact = interact; - } - // AMD - else if (typeof define === 'function' && define.amd) { - define('interact', function() { - return interact; - }); - } - else { - scope.realWindow.interact = interact; - } + module.exports = interact; From c2c02f2dc109907e8aa0c777db7a33924b00d5b2 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 3 Jun 2015 02:45:45 +0100 Subject: [PATCH 033/131] Fix dependencies of isTypes and window Implement isTypes.isWindow in a separate module and in window module require('./isWindow') instead of require('./isTypes').isWindow to remove circular dependency. --- src/utils/isType.js | 18 ++++++++++-------- src/utils/isWindow.js | 5 +++++ src/utils/window.js | 4 ++-- 3 files changed, 17 insertions(+), 10 deletions(-) create mode 100644 src/utils/isWindow.js diff --git a/src/utils/isType.js b/src/utils/isType.js index c2c2282bd..e52f740fc 100644 --- a/src/utils/isType.js +++ b/src/utils/isType.js @@ -13,17 +13,13 @@ var isType = { ? o instanceof _window.Element //DOM2 : o.nodeType === 1 && typeof o.nodeName === "string"); }, + + isArray : null, - isWindow : function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }, + isWindow : require('./isWindow'), isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }, - isArray : function (thing) { - return isObject(thing) - && (typeof thing.length !== 'undefined') - && isFunction(thing.splice); - }, - isObject : function (thing) { return !!thing && (typeof thing === 'object'); }, isFunction : function (thing) { return typeof thing === 'function'; }, @@ -36,4 +32,10 @@ var isType = { }; -module.exports = isType; \ No newline at end of file +isType.isArray = function (thing) { + return isType.isObject(thing) + && (typeof thing.length !== 'undefined') + && isType.isFunction(thing.splice); +}; + +module.exports = isType; diff --git a/src/utils/isWindow.js b/src/utils/isWindow.js new file mode 100644 index 000000000..b3ee4b904 --- /dev/null +++ b/src/utils/isWindow.js @@ -0,0 +1,5 @@ +'use strict'; + +module.exports = function isWindow (thing) { + return !!(thing && thing.Window) && (thing instanceof thing.Window); +}; diff --git a/src/utils/window.js b/src/utils/window.js index cabb08016..0e94ec677 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -1,6 +1,6 @@ 'use strict'; -var isWindow = require('./isType').isWindow; +var isWindow = require('./isWindow'); var isShadowDom = function() { // create a TextNode @@ -37,4 +37,4 @@ if (typeof window !== 'undefined') { } } -module.exports = win; \ No newline at end of file +module.exports = win; From 85a229906546a6b14d8680f5ebf72522c6240697 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 3 Jun 2015 02:50:07 +0100 Subject: [PATCH 034/131] utils/index: don't assign new module.exports obj --- src/utils/index.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/utils/index.js b/src/utils/index.js index a909729e7..4368741ad 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,6 @@ 'use strict'; -var utils = {}, +var utils = module.exports, extend = require('./extend'), win = require('./window'); @@ -27,5 +27,3 @@ utils.browser = require('./browser'); extend(utils, require('./arr')); extend(utils, require('./isType')); extend(utils, require('./pointerUtils')); - -module.exports = utils; From fa9a90244339019d0ce32b2e321b31f4ce034d8d Mon Sep 17 00:00:00 2001 From: stephen-james Date: Thu, 4 Jun 2015 23:34:18 +0100 Subject: [PATCH 035/131] Create minified and unminified build using Browserify with source maps - move `interact.min.js` to build folder - modify gulp process to output minified version and source maps --- build/interact.js | 304 ++++++++++++++------------------------- build/interact.js.map | 1 + build/interact.min.js | 5 + gulp/config.js | 5 +- gulp/tasks/browserify.js | 135 +++++++++-------- interact.min.js | 3 - package.json | 1 + 7 files changed, 187 insertions(+), 267 deletions(-) create mode 100644 build/interact.js.map create mode 100644 build/interact.min.js delete mode 100644 interact.min.js diff --git a/build/interact.js b/build/interact.js index 31817f3bb..f38fb636b 100644 --- a/build/interact.js +++ b/build/interact.js @@ -125,7 +125,7 @@ scope.globalEvents = {}; // prefix matchesSelector - scope.prefixedMatchesSelector = 'matches' in Element.prototype? + browser.prefixedMatchesSelector = 'matches' in Element.prototype? 'matches': 'webkitMatchesSelector' in Element.prototype? 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? @@ -186,112 +186,6 @@ }; }; - utils.getTouchPair = function (event) { - var touches = []; - - // array of touches is supplied - if (scope.isArray(event)) { - touches[0] = event[0]; - touches[1] = event[1]; - } - // an event - else { - if (event.type === 'touchend') { - if (event.touches.length === 1) { - touches[0] = event.touches[0]; - touches[1] = event.changedTouches[0]; - } - else if (event.touches.length === 0) { - touches[0] = event.changedTouches[0]; - touches[1] = event.changedTouches[1]; - } - } - else { - touches[0] = event.touches[0]; - touches[1] = event.touches[1]; - } - } - - return touches; - }; - - utils.touchAverage = function (event) { - var touches = utils.getTouchPair(event); - - return { - pageX: (touches[0].pageX + touches[1].pageX) / 2, - pageY: (touches[0].pageY + touches[1].pageY) / 2, - clientX: (touches[0].clientX + touches[1].clientX) / 2, - clientY: (touches[0].clientY + touches[1].clientY) / 2 - }; - }; - - utils.touchBBox = function (event) { - if (!event.length && !(event.touches && event.touches.length > 1)) { - return; - } - - var touches = utils.getTouchPair(event), - minX = Math.min(touches[0].pageX, touches[1].pageX), - minY = Math.min(touches[0].pageY, touches[1].pageY), - maxX = Math.max(touches[0].pageX, touches[1].pageX), - maxY = Math.max(touches[0].pageY, touches[1].pageY); - - return { - x: minX, - y: minY, - left: minX, - top: minY, - width: maxX - minX, - height: maxY - minY - }; - }; - - utils.touchDistance = function (event, deltaSource) { - deltaSource = deltaSource || scope.defaultOptions.deltaSource; - - var sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - touches = utils.getTouchPair(event); - - - var dx = touches[0][sourceX] - touches[1][sourceX], - dy = touches[0][sourceY] - touches[1][sourceY]; - - return utils.hypot(dx, dy); - }; - - utils.touchAngle = function (event, prevAngle, deltaSource) { - deltaSource = deltaSource || scope.defaultOptions.deltaSource; - - var sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - touches = utils.getTouchPair(event), - dx = touches[0][sourceX] - touches[1][sourceX], - dy = touches[0][sourceY] - touches[1][sourceY], - angle = 180 * Math.atan(dy / dx) / Math.PI; - - if (scope.isNumber(prevAngle)) { - var dr = angle - prevAngle, - drClamped = dr % 360; - - if (drClamped > 315) { - angle -= 360 + (angle / 360)|0 * 360; - } - else if (drClamped > 135) { - angle -= 180 + (angle / 360)|0 * 360; - } - else if (drClamped < -315) { - angle += 360 + (angle / 360)|0 * 360; - } - else if (drClamped < -135) { - angle += 180 + (angle / 360)|0 * 360; - } - } - - return angle; - }; - scope.getOriginXY = function (interactable, element) { var origin = interactable ? interactable.options.origin @@ -606,7 +500,7 @@ selector = selector.replace(/\/deep\//g, ' '); } - return element[scope.prefixedMatchesSelector](selector); + return element[browser.prefixedMatchesSelector](selector); }; scope.matchesUpTo = function (element, selector, limit) { @@ -627,7 +521,7 @@ // For IE8's lack of an Element#matchesSelector // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - if (!(scope.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[scope.prefixedMatchesSelector])) { + if (!(browser.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[browser.prefixedMatchesSelector])) { scope.ie8MatchesSelector = function (element, selector, elems) { elems = elems || element.parentNode.querySelectorAll(selector); @@ -2929,26 +2823,9 @@ scope.Interaction = Interaction; scope.InteractEvent = InteractEvent; - /* global exports: true, module, define */ + module.exports = interact; - // http://documentcloud.github.io/underscore/docs/underscore.html#section-11 - if (typeof exports !== 'undefined') { - if (typeof module !== 'undefined' && module.exports) { - exports = module.exports = interact; - } - exports.interact = interact; - } - // AMD - else if (typeof define === 'function' && define.amd) { - define('interact', function() { - return interact; - }); - } - else { - scope.realWindow.interact = interact; - } - -},{"./InteractEvent":2,"./Interaction":3,"./autoScroll":4,"./defaultOptions":5,"./scope":6,"./utils":13,"./utils/events":10,"./utils/window":17}],2:[function(require,module,exports){ +},{"./InteractEvent":2,"./Interaction":3,"./autoScroll":4,"./defaultOptions":5,"./scope":6,"./utils":13,"./utils/events":10,"./utils/window":18}],2:[function(require,module,exports){ 'use strict'; var scope = require('./scope'); @@ -4003,7 +3880,7 @@ Interaction.prototype = { if (starting && (this.target.options[this.prepared.name].manualStart || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { - this.stop(); + this.stop(event); return; } @@ -4714,10 +4591,10 @@ Interaction.prototype = { if (this.dragging) { this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; } - - this.clearTargets(); } + this.clearTargets(); + this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; this.prepared.name = this.prevEvent = null; this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; @@ -5300,6 +5177,7 @@ Interaction.prototype = { }; module.exports = Interaction; + },{"./InteractEvent":2,"./scope":6,"./utils":13,"./utils/browser":8,"./utils/events":10}],4:[function(require,module,exports){ 'use strict'; @@ -5361,7 +5239,7 @@ var autoScroll = { module.exports = autoScroll; -},{"./utils/isType":14,"./utils/raf":16,"./utils/window":17}],5:[function(require,module,exports){ +},{"./utils/isType":14,"./utils/raf":17,"./utils/window":18}],5:[function(require,module,exports){ 'use strict'; module.exports = { @@ -5389,7 +5267,7 @@ module.exports = { inertia: null, autoScroll: null, - axis: 'xy', + axis: 'xy' }, drop: { @@ -5491,7 +5369,7 @@ extend(scope, require('./utils/isType')); module.exports = scope; -},{"./utils/arr.js":7,"./utils/domObjects":9,"./utils/extend":11,"./utils/isType":14,"./utils/window":17}],7:[function(require,module,exports){ +},{"./utils/arr.js":7,"./utils/domObjects":9,"./utils/extend":11,"./utils/isType":14,"./utils/window":18}],7:[function(require,module,exports){ 'use strict'; function indexOf (array, target) { @@ -5536,12 +5414,20 @@ var browser = { // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)), - isIe9OrOlder : domObjects.document.all && !win.window.atob + isIe9OrOlder : domObjects.document.all && !win.window.atob, + + // prefix matchesSelector + prefixedMatchesSelector: 'matches' in Element.prototype? + 'matches': 'webkitMatchesSelector' in Element.prototype? + 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? + 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? + 'oMatchesSelector': 'msMatchesSelector' + }; module.exports = browser; -},{"./domObjects":9,"./window":17}],9:[function(require,module,exports){ +},{"./domObjects":9,"./window":18}],9:[function(require,module,exports){ 'use strict'; var domObjects = {}, @@ -5559,7 +5445,7 @@ domObjects.PointerEvent = (win.PointerEvent || win.MSPointerEvent); module.exports = domObjects; -},{"./window":17}],10:[function(require,module,exports){ +},{"./window":18}],10:[function(require,module,exports){ 'use strict'; var arr = require('./arr'), @@ -5626,7 +5512,7 @@ function add (element, type, listener, useCapture) { } }; - ret = element[addEvent](on + type, wrapped, Boolean(useCapture)); + ret = element[addEvent](on + type, wrapped, !!useCapture); if (listenerIndex === -1) { listeners.supplied.push(listener); @@ -5638,7 +5524,7 @@ function add (element, type, listener, useCapture) { } } else { - ret = element[addEvent](type, listener, useCapture || false); + ret = element[addEvent](type, listener, !!useCapture); } target.events[type].push(listener); @@ -5678,13 +5564,13 @@ function remove (element, type, listener, useCapture) { if (listener === 'all') { for (i = 0; i < len; i++) { - remove(element, type, target.events[type][i], Boolean(useCapture)); + remove(element, type, target.events[type][i], !!useCapture); } return; } else { for (i = 0; i < len; i++) { if (target.events[type][i] === listener) { - element[removeEvent](on + type, wrapped, useCapture || false); + element[removeEvent](on + type, wrapped, !!useCapture); target.events[type].splice(i, 1); if (useAttachEvent && listeners) { @@ -5737,7 +5623,7 @@ module.exports = { _attachedListeners: attachedListeners }; -},{"./arr":7,"./window":17}],11:[function(require,module,exports){ +},{"./arr":7,"./window":18}],11:[function(require,module,exports){ 'use strict'; module.exports = function extend (dest, source) { @@ -5755,7 +5641,7 @@ module.exports = function hypot (x, y) { return Math.sqrt(x * x + y * y); }; },{}],13:[function(require,module,exports){ 'use strict'; -var utils = {}, +var utils = module.exports, extend = require('./extend'), win = require('./window'); @@ -5783,39 +5669,57 @@ extend(utils, require('./arr')); extend(utils, require('./isType')); extend(utils, require('./pointerUtils')); -module.exports = utils; - -},{"./arr":7,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./pointerUtils":15,"./raf":16,"./window":17}],14:[function(require,module,exports){ +},{"./arr":7,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./pointerUtils":16,"./raf":17,"./window":18}],14:[function(require,module,exports){ 'use strict'; var win = require('./window'), domObjects = require('./domObjects'); -module.exports.isElement = function (o) { - if (!o || (typeof o !== 'object')) { return false; } +var isType = { + isElement : function (o) { + if (!o || (typeof o !== 'object')) { return false; } + + var _window = win.getWindow(o) || win.window; + + return (/object|function/.test(typeof _window.Element) + ? o instanceof _window.Element //DOM2 + : o.nodeType === 1 && typeof o.nodeName === "string"); + }, + + isArray : null, + + isWindow : require('./isWindow'), + + isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }, - var _window = win.getWindow(o) || win.window; + isObject : function (thing) { return !!thing && (typeof thing === 'object'); }, - return (/object|function/.test(typeof _window.Element) - ? o instanceof _window.Element //DOM2 - : o.nodeType === 1 && typeof o.nodeName === "string"); + isFunction : function (thing) { return typeof thing === 'function'; }, + + isNumber : function (thing) { return typeof thing === 'number' ; }, + + isBool : function (thing) { return typeof thing === 'boolean' ; }, + + isString : function (thing) { return typeof thing === 'string' ; } + }; -module.exports.isWindow = function (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }; -module.exports.isDocFrag = function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }; -module.exports.isArray = function (thing) { - return module.exports.isObject(thing) - && (typeof thing.length !== undefined) - && module.exports.isFunction(thing.splice); +isType.isArray = function (thing) { + return isType.isObject(thing) + && (typeof thing.length !== 'undefined') + && isType.isFunction(thing.splice); }; -module.exports.isObject = function (thing) { return !!thing && (typeof thing === 'object'); }; -module.exports.isFunction = function (thing) { return typeof thing === 'function'; }; -module.exports.isNumber = function (thing) { return typeof thing === 'number' ; }; -module.exports.isBool = function (thing) { return typeof thing === 'boolean' ; }; -module.exports.isString = function (thing) { return typeof thing === 'string' ; }; +module.exports = isType; -},{"./domObjects":9,"./window":17}],15:[function(require,module,exports){ +},{"./domObjects":9,"./isWindow":15,"./window":18}],15:[function(require,module,exports){ +'use strict'; + +module.exports = function isWindow (thing) { + return !!(thing && thing.Window) && (thing instanceof thing.Window); +}; + +},{}],16:[function(require,module,exports){ 'use strict'; var pointerUtils = {}, @@ -5824,9 +5728,9 @@ var pointerUtils = {}, win = require('./window'), hypot = require('./hypot'), extend = require('./extend'), - - // scope shouldn't be necessary in this module - scope = require('../scope'); + browser = require('./browser'), + isType = require('./isType'), + InteractEvent = require('../InteractEvent'); pointerUtils.copyCoords = function (dest, src) { dest.page = dest.page || {}; @@ -5893,7 +5797,7 @@ pointerUtils.getXY = function (type, pointer, xy) { pointerUtils.getPageXY = function (pointer, page, interaction) { page = page || {}; - if (pointer instanceof scope.InteractEvent) { + if (pointer instanceof InteractEvent) { if (/inertiastart/.test(pointer.type)) { interaction = interaction || pointer.interaction; @@ -5908,7 +5812,7 @@ pointerUtils.getPageXY = function (pointer, page, interaction) { } } // Opera Mobile handles the viewport and scrolling oddly - else if (scope.isOperaMobile) { + else if (browser.isOperaMobile) { pointerUtils.getXY('screen', pointer, page); page.x += win.window.scrollX; @@ -5924,7 +5828,7 @@ pointerUtils.getPageXY = function (pointer, page, interaction) { pointerUtils.getClientXY = function (pointer, client, interaction) { client = client || {}; - if (pointer instanceof scope.InteractEvent) { + if (pointer instanceof InteractEvent) { if (/inertiastart/.test(pointer.type)) { extend(client, interaction.inertiaStatus.upCoords.client); @@ -5938,19 +5842,19 @@ pointerUtils.getClientXY = function (pointer, client, interaction) { } else { // Opera Mobile handles the viewport and scrolling oddly - pointerUtils.getXY(scope.isOperaMobile? 'screen': 'client', pointer, client); + pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client); } return client; }; pointerUtils.getPointerId = function (pointer) { - return scope.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; + return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; }; module.exports = pointerUtils; -},{"../scope":6,"./extend":11,"./hypot":12,"./window":17}],16:[function(require,module,exports){ +},{"../InteractEvent":2,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./window":18}],17:[function(require,module,exports){ 'use strict'; var lastTime = 0, @@ -5985,44 +5889,46 @@ module.exports = { cancel: cancelFrame }; -},{}],17:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ 'use strict'; -if (typeof window === 'undefined') { - module.exports.window = undefined; - module.exports.realWindow = undefined; -} -else { - // get wrapped window if using Shadow DOM polyfill - - module.exports.realWindow = window; +var isWindow = require('./isWindow'); +var isShadowDom = function() { // create a TextNode var el = window.document.createTextNode(''); // check if it's wrapped by a polyfill - if (el.ownerDocument !== window.document + return el.ownerDocument !== window.document && typeof window.wrap === 'function' - && window.wrap(el) === el) { - // return wrapped window - module.exports.window = window.wrap(window); - } + && window.wrap(el) === el; +}; - // no Shadow DOM polyfil or native implementation - module.exports.window = window; -} +var win = { -var isWindow = require('./isType').isWindow; + window: undefined, -module.exports.getWindow = function getWindow (node) { - if (isWindow(node)) { - return node; - } + realWindow: window, + + getWindow: function getWindow (node) { + if (isWindow(node)) { + return node; + } - var rootNode = (node.ownerDocument || node); + var rootNode = (node.ownerDocument || node); - return rootNode.defaultView || rootNode.parentWindow || module.exports.window; + return rootNode.defaultView || rootNode.parentWindow || win.window; + } }; -},{"./isType":14}]},{},[1]) -//# sourceMappingURL=data:application/json;charset:utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm5vZGVfbW9kdWxlcy9icm93c2VyaWZ5L25vZGVfbW9kdWxlcy9icm93c2VyLXBhY2svX3ByZWx1ZGUuanMiLCJzcmMvaW50ZXJhY3QuanMiLCJzcmMvSW50ZXJhY3RFdmVudC5qcyIsInNyYy9JbnRlcmFjdGlvbi5qcyIsInNyYy9hdXRvU2Nyb2xsLmpzIiwic3JjL2RlZmF1bHRPcHRpb25zLmpzIiwic3JjL3Njb3BlLmpzIiwic3JjL3V0aWxzL2Fyci5qcyIsInNyYy91dGlscy9icm93c2VyLmpzIiwic3JjL3V0aWxzL2RvbU9iamVjdHMuanMiLCJzcmMvdXRpbHMvZXZlbnRzLmpzIiwic3JjL3V0aWxzL2V4dGVuZC5qcyIsInNyYy91dGlscy9oeXBvdC5qcyIsInNyYy91dGlscy9pbmRleC5qcyIsInNyYy91dGlscy9pc1R5cGUuanMiLCJzcmMvdXRpbHMvcG9pbnRlclV0aWxzLmpzIiwic3JjL3V0aWxzL3JhZi5qcyIsInNyYy91dGlscy93aW5kb3cuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7QUNBQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcDRGQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDN1FBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQy9oRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQzNEQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ25IQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDWEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ3BCQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDMUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDaEJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUNoTEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ1JBO0FBQ0E7QUFDQTtBQUNBOztBQ0hBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDL0JBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDNUJBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FDcklBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQ2pDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6ImdlbmVyYXRlZC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzQ29udGVudCI6WyIoZnVuY3Rpb24gZSh0LG4scil7ZnVuY3Rpb24gcyhvLHUpe2lmKCFuW29dKXtpZighdFtvXSl7dmFyIGE9dHlwZW9mIHJlcXVpcmU9PVwiZnVuY3Rpb25cIiYmcmVxdWlyZTtpZighdSYmYSlyZXR1cm4gYShvLCEwKTtpZihpKXJldHVybiBpKG8sITApO3ZhciBmPW5ldyBFcnJvcihcIkNhbm5vdCBmaW5kIG1vZHVsZSAnXCIrbytcIidcIik7dGhyb3cgZi5jb2RlPVwiTU9EVUxFX05PVF9GT1VORFwiLGZ9dmFyIGw9bltvXT17ZXhwb3J0czp7fX07dFtvXVswXS5jYWxsKGwuZXhwb3J0cyxmdW5jdGlvbihlKXt2YXIgbj10W29dWzFdW2VdO3JldHVybiBzKG4/bjplKX0sbCxsLmV4cG9ydHMsZSx0LG4scil9cmV0dXJuIG5bb10uZXhwb3J0c312YXIgaT10eXBlb2YgcmVxdWlyZT09XCJmdW5jdGlvblwiJiZyZXF1aXJlO2Zvcih2YXIgbz0wO288ci5sZW5ndGg7bysrKXMocltvXSk7cmV0dXJuIHN9KSIsIi8qKlxuICogaW50ZXJhY3QuanMgdjEuMi40XG4gKlxuICogQ29weXJpZ2h0IChjKSAyMDEyLTIwMTUgVGF5ZSBBZGV5ZW1pIDxkZXZAdGF5ZS5tZT5cbiAqIE9wZW4gc291cmNlIHVuZGVyIHRoZSBNSVQgTGljZW5zZS5cbiAqIGh0dHBzOi8vcmF3LmdpdGh1Yi5jb20vdGF5ZS9pbnRlcmFjdC5qcy9tYXN0ZXIvTElDRU5TRVxuICovXG5cbiAgICAndXNlIHN0cmljdCc7XG5cbiAgICAvLyByZXR1cm4gZWFybHkgaWYgdGhlcmUncyBubyB3aW5kb3cgdG8gd29yayB3aXRoIChlZy4gTm9kZS5qcylcbiAgICBpZiAoIXJlcXVpcmUoJy4vdXRpbHMvd2luZG93Jykud2luZG93KSB7IHJldHVybjsgfVxuXG4gICAgdmFyIHNjb3BlID0gcmVxdWlyZSgnLi9zY29wZScpLFxuICAgICAgICB1dGlscyA9IHJlcXVpcmUoJy4vdXRpbHMnKSxcbiAgICAgICAgYnJvd3NlciA9IHV0aWxzLmJyb3dzZXI7XG5cbiAgICBzY29wZS5wRXZlbnRUeXBlcyA9IG51bGw7XG5cbiAgICBzY29wZS5kb2N1bWVudHMgICAgICAgPSBbXTsgICAvLyBhbGwgZG9jdW1lbnRzIGJlaW5nIGxpc3RlbmVkIHRvXG5cbiAgICBzY29wZS5pbnRlcmFjdGFibGVzICAgPSBbXTsgICAvLyBhbGwgc2V0IGludGVyYWN0YWJsZXNcbiAgICBzY29wZS5pbnRlcmFjdGlvbnMgICAgPSBbXTsgICAvLyBhbGwgaW50ZXJhY3Rpb25zXG5cbiAgICBzY29wZS5keW5hbWljRHJvcCAgICAgPSBmYWxzZTtcblxuICAgIC8vIHtcbiAgICAvLyAgICAgIHR5cGU6IHtcbiAgICAvLyAgICAgICAgICBzZWxlY3RvcnM6IFsnc2VsZWN0b3InLCAuLi5dLFxuICAgIC8vICAgICAgICAgIGNvbnRleHRzIDogW2RvY3VtZW50LCAuLi5dLFxuICAgIC8vICAgICAgICAgIGxpc3RlbmVyczogW1tsaXN0ZW5lciwgdXNlQ2FwdHVyZV0sIC4uLl1cbiAgICAvLyAgICAgIH1cbiAgICAvLyAgfVxuICAgIHNjb3BlLmRlbGVnYXRlZEV2ZW50cyA9IHt9O1xuXG4gICAgc2NvcGUuZGVmYXVsdE9wdGlvbnMgPSByZXF1aXJlKCcuL2RlZmF1bHRPcHRpb25zJyk7XG5cbiAgICAvLyBUaGluZ3MgcmVsYXRlZCB0byBhdXRvU2Nyb2xsXG4gICAgc2NvcGUuYXV0b1Njcm9sbCA9IHJlcXVpcmUoJy4vYXV0b1Njcm9sbCcpO1xuXG4gICAgLy8gTGVzcyBQcmVjaXNpb24gd2l0aCB0b3VjaCBpbnB1dFxuICAgIHNjb3BlLm1hcmdpbiA9IGJyb3dzZXIuc3VwcG9ydHNUb3VjaCB8fCBicm93c2VyLnN1cHBvcnRzUG9pbnRlckV2ZW50PyAyMDogMTA7XG5cbiAgICBzY29wZS5wb2ludGVyTW92ZVRvbGVyYW5jZSA9IDE7XG5cbiAgICAvLyBmb3IgaWdub3JpbmcgYnJvd3NlcidzIHNpbXVsYXRlZCBtb3VzZSBldmVudHNcbiAgICBzY29wZS5wcmV2VG91Y2hUaW1lID0gMDtcblxuICAgIC8vIEFsbG93IHRoaXMgbWFueSBpbnRlcmFjdGlvbnMgdG8gaGFwcGVuIHNpbXVsdGFuZW91c2x5XG4gICAgc2NvcGUubWF4SW50ZXJhY3Rpb25zID0gSW5maW5pdHk7XG5cbiAgICBzY29wZS5hY3Rpb25DdXJzb3JzID0gYnJvd3Nlci5pc0llOU9yT2xkZXIgPyB7XG4gICAgICAgIGRyYWcgICAgOiAnbW92ZScsXG4gICAgICAgIHJlc2l6ZXggOiAnZS1yZXNpemUnLFxuICAgICAgICByZXNpemV5IDogJ3MtcmVzaXplJyxcbiAgICAgICAgcmVzaXpleHk6ICdzZS1yZXNpemUnLFxuXG4gICAgICAgIHJlc2l6ZXRvcCAgICAgICAgOiAnbi1yZXNpemUnLFxuICAgICAgICByZXNpemVsZWZ0ICAgICAgIDogJ3ctcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tICAgICA6ICdzLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXJpZ2h0ICAgICAgOiAnZS1yZXNpemUnLFxuICAgICAgICByZXNpemV0b3BsZWZ0ICAgIDogJ3NlLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbXJpZ2h0OiAnc2UtcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wcmlnaHQgICA6ICduZS1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b21sZWZ0IDogJ25lLXJlc2l6ZScsXG5cbiAgICAgICAgZ2VzdHVyZSA6ICcnXG4gICAgfSA6IHtcbiAgICAgICAgZHJhZyAgICA6ICdtb3ZlJyxcbiAgICAgICAgcmVzaXpleCA6ICdldy1yZXNpemUnLFxuICAgICAgICByZXNpemV5IDogJ25zLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXh5OiAnbndzZS1yZXNpemUnLFxuXG4gICAgICAgIHJlc2l6ZXRvcCAgICAgICAgOiAnbnMtcmVzaXplJyxcbiAgICAgICAgcmVzaXplbGVmdCAgICAgICA6ICdldy1yZXNpemUnLFxuICAgICAgICByZXNpemVib3R0b20gICAgIDogJ25zLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZXJpZ2h0ICAgICAgOiAnZXctcmVzaXplJyxcbiAgICAgICAgcmVzaXpldG9wbGVmdCAgICA6ICdud3NlLXJlc2l6ZScsXG4gICAgICAgIHJlc2l6ZWJvdHRvbXJpZ2h0OiAnbndzZS1yZXNpemUnLFxuICAgICAgICByZXNpemV0b3ByaWdodCAgIDogJ25lc3ctcmVzaXplJyxcbiAgICAgICAgcmVzaXplYm90dG9tbGVmdCA6ICduZXN3LXJlc2l6ZScsXG5cbiAgICAgICAgZ2VzdHVyZSA6ICcnXG4gICAgfTtcblxuICAgIHNjb3BlLmFjdGlvbklzRW5hYmxlZCA9IHtcbiAgICAgICAgZHJhZyAgIDogdHJ1ZSxcbiAgICAgICAgcmVzaXplIDogdHJ1ZSxcbiAgICAgICAgZ2VzdHVyZTogdHJ1ZVxuICAgIH07XG5cbiAgICAvLyBiZWNhdXNlIFdlYmtpdCBhbmQgT3BlcmEgc3RpbGwgdXNlICdtb3VzZXdoZWVsJyBldmVudCB0eXBlXG4gICAgc2NvcGUud2hlZWxFdmVudCA9ICdvbm1vdXNld2hlZWwnIGluIHNjb3BlLmRvY3VtZW50PyAnbW91c2V3aGVlbCc6ICd3aGVlbCc7XG5cbiAgICBzY29wZS5ldmVudFR5cGVzID0gW1xuICAgICAgICAnZHJhZ3N0YXJ0JyxcbiAgICAgICAgJ2RyYWdtb3ZlJyxcbiAgICAgICAgJ2RyYWdpbmVydGlhc3RhcnQnLFxuICAgICAgICAnZHJhZ2VuZCcsXG4gICAgICAgICdkcmFnZW50ZXInLFxuICAgICAgICAnZHJhZ2xlYXZlJyxcbiAgICAgICAgJ2Ryb3BhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wZGVhY3RpdmF0ZScsXG4gICAgICAgICdkcm9wbW92ZScsXG4gICAgICAgICdkcm9wJyxcbiAgICAgICAgJ3Jlc2l6ZXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZW1vdmUnLFxuICAgICAgICAncmVzaXplaW5lcnRpYXN0YXJ0JyxcbiAgICAgICAgJ3Jlc2l6ZWVuZCcsXG4gICAgICAgICdnZXN0dXJlc3RhcnQnLFxuICAgICAgICAnZ2VzdHVyZW1vdmUnLFxuICAgICAgICAnZ2VzdHVyZWluZXJ0aWFzdGFydCcsXG4gICAgICAgICdnZXN0dXJlZW5kJyxcblxuICAgICAgICAnZG93bicsXG4gICAgICAgICdtb3ZlJyxcbiAgICAgICAgJ3VwJyxcbiAgICAgICAgJ2NhbmNlbCcsXG4gICAgICAgICd0YXAnLFxuICAgICAgICAnZG91YmxldGFwJyxcbiAgICAgICAgJ2hvbGQnXG4gICAgXTtcblxuICAgIHNjb3BlLmdsb2JhbEV2ZW50cyA9IHt9O1xuXG4gICAgLy8gcHJlZml4IG1hdGNoZXNTZWxlY3RvclxuICAgIHNjb3BlLnByZWZpeGVkTWF0Y2hlc1NlbGVjdG9yID0gJ21hdGNoZXMnIGluIEVsZW1lbnQucHJvdG90eXBlP1xuICAgICAgICAgICAgJ21hdGNoZXMnOiAnd2Via2l0TWF0Y2hlc1NlbGVjdG9yJyBpbiBFbGVtZW50LnByb3RvdHlwZT9cbiAgICAgICAgICAgICAgICAnd2Via2l0TWF0Y2hlc1NlbGVjdG9yJzogJ21vek1hdGNoZXNTZWxlY3RvcicgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICAgICAgICAgICAgICdtb3pNYXRjaGVzU2VsZWN0b3InOiAnb01hdGNoZXNTZWxlY3RvcicgaW4gRWxlbWVudC5wcm90b3R5cGU/XG4gICAgICAgICAgICAgICAgICAgICAgICAnb01hdGNoZXNTZWxlY3Rvcic6ICdtc01hdGNoZXNTZWxlY3Rvcic7XG5cbiAgICAvLyB3aWxsIGJlIHBvbHlmaWxsIGZ1bmN0aW9uIGlmIGJyb3dzZXIgaXMgSUU4XG4gICAgc2NvcGUuaWU4TWF0Y2hlc1NlbGVjdG9yID0gbnVsbDtcblxuICAgIC8vIEV2ZW50cyB3cmFwcGVyXG4gICAgdmFyIGV2ZW50cyA9IHJlcXVpcmUoJy4vdXRpbHMvZXZlbnRzJyk7XG5cbiAgICBzY29wZS5saXN0ZW5lcnMgPSB7fTtcblxuICAgIHZhciBpbnRlcmFjdGlvbkxpc3RlbmVycyA9IFtcbiAgICAgICAgJ2RyYWdTdGFydCcsICdkcmFnTW92ZScsICdyZXNpemVTdGFydCcsICdyZXNpemVNb3ZlJywgJ2dlc3R1cmVTdGFydCcsICdnZXN0dXJlTW92ZScsXG4gICAgICAgICdwb2ludGVyT3ZlcicsICdwb2ludGVyT3V0JywgJ3BvaW50ZXJIb3ZlcicsICdzZWxlY3RvckRvd24nLFxuICAgICAgICAncG9pbnRlckRvd24nLCAncG9pbnRlck1vdmUnLCAncG9pbnRlclVwJywgJ3BvaW50ZXJDYW5jZWwnLCAncG9pbnRlckVuZCcsXG4gICAgICAgICdhZGRQb2ludGVyJywgJ3JlbW92ZVBvaW50ZXInLCAncmVjb3JkUG9pbnRlcicsICdhdXRvU2Nyb2xsTW92ZSdcbiAgICBdO1xuXG4gICAgc2NvcGUudHJ5U2VsZWN0b3IgPSBmdW5jdGlvbiAodmFsdWUpIHtcbiAgICAgICAgaWYgKCFzY29wZS5pc1N0cmluZyh2YWx1ZSkpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICAgICAgLy8gYW4gZXhjZXB0aW9uIHdpbGwgYmUgcmFpc2VkIGlmIGl0IGlzIGludmFsaWRcbiAgICAgICAgc2NvcGUuZG9jdW1lbnQucXVlcnlTZWxlY3Rvcih2YWx1ZSk7XG4gICAgICAgIHJldHVybiB0cnVlO1xuICAgIH07XG5cbiAgICBzY29wZS5nZXRTY3JvbGxYWSA9IGZ1bmN0aW9uICh3aW4pIHtcbiAgICAgICAgd2luID0gd2luIHx8IHNjb3BlLndpbmRvdztcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHg6IHdpbi5zY3JvbGxYIHx8IHdpbi5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsTGVmdCxcbiAgICAgICAgICAgIHk6IHdpbi5zY3JvbGxZIHx8IHdpbi5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsVG9wXG4gICAgICAgIH07XG4gICAgfTtcblxuICAgIHNjb3BlLmdldEFjdHVhbEVsZW1lbnQgPSBmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICByZXR1cm4gKGVsZW1lbnQgaW5zdGFuY2VvZiBzY29wZS5TVkdFbGVtZW50SW5zdGFuY2VcbiAgICAgICAgICAgID8gZWxlbWVudC5jb3JyZXNwb25kaW5nVXNlRWxlbWVudFxuICAgICAgICAgICAgOiBlbGVtZW50KTtcbiAgICB9O1xuXG4gICAgc2NvcGUuZ2V0RWxlbWVudFJlY3QgPSBmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICB2YXIgc2Nyb2xsID0gYnJvd3Nlci5pc0lPUzdvckxvd2VyXG4gICAgICAgICAgICAgICAgPyB7IHg6IDAsIHk6IDAgfVxuICAgICAgICAgICAgICAgIDogc2NvcGUuZ2V0U2Nyb2xsWFkoc2NvcGUuZ2V0V2luZG93KGVsZW1lbnQpKSxcbiAgICAgICAgICAgIGNsaWVudFJlY3QgPSAoZWxlbWVudCBpbnN0YW5jZW9mIHNjb3BlLlNWR0VsZW1lbnQpP1xuICAgICAgICAgICAgICAgIGVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk6XG4gICAgICAgICAgICAgICAgZWxlbWVudC5nZXRDbGllbnRSZWN0cygpWzBdO1xuXG4gICAgICAgIHJldHVybiBjbGllbnRSZWN0ICYmIHtcbiAgICAgICAgICAgIGxlZnQgIDogY2xpZW50UmVjdC5sZWZ0ICAgKyBzY3JvbGwueCxcbiAgICAgICAgICAgIHJpZ2h0IDogY2xpZW50UmVjdC5yaWdodCAgKyBzY3JvbGwueCxcbiAgICAgICAgICAgIHRvcCAgIDogY2xpZW50UmVjdC50b3AgICAgKyBzY3JvbGwueSxcbiAgICAgICAgICAgIGJvdHRvbTogY2xpZW50UmVjdC5ib3R0b20gKyBzY3JvbGwueSxcbiAgICAgICAgICAgIHdpZHRoIDogY2xpZW50UmVjdC53aWR0aCB8fCBjbGllbnRSZWN0LnJpZ2h0IC0gY2xpZW50UmVjdC5sZWZ0LFxuICAgICAgICAgICAgaGVpZ2h0OiBjbGllbnRSZWN0LmhlaWdoIHx8IGNsaWVudFJlY3QuYm90dG9tIC0gY2xpZW50UmVjdC50b3BcbiAgICAgICAgfTtcbiAgICB9O1xuXG4gICAgdXRpbHMuZ2V0VG91Y2hQYWlyID0gZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciB0b3VjaGVzID0gW107XG5cbiAgICAgICAgLy8gYXJyYXkgb2YgdG91Y2hlcyBpcyBzdXBwbGllZFxuICAgICAgICBpZiAoc2NvcGUuaXNBcnJheShldmVudCkpIHtcbiAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudFswXTtcbiAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudFsxXTtcbiAgICAgICAgfVxuICAgICAgICAvLyBhbiBldmVudFxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGlmIChldmVudC50eXBlID09PSAndG91Y2hlbmQnKSB7XG4gICAgICAgICAgICAgICAgaWYgKGV2ZW50LnRvdWNoZXMubGVuZ3RoID09PSAxKSB7XG4gICAgICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgICAgICB0b3VjaGVzWzFdID0gZXZlbnQuY2hhbmdlZFRvdWNoZXNbMF07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKGV2ZW50LnRvdWNoZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC5jaGFuZ2VkVG91Y2hlc1swXTtcbiAgICAgICAgICAgICAgICAgICAgdG91Y2hlc1sxXSA9IGV2ZW50LmNoYW5nZWRUb3VjaGVzWzFdO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMF0gPSBldmVudC50b3VjaGVzWzBdO1xuICAgICAgICAgICAgICAgIHRvdWNoZXNbMV0gPSBldmVudC50b3VjaGVzWzFdO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHRvdWNoZXM7XG4gICAgfTtcblxuICAgIHV0aWxzLnRvdWNoQXZlcmFnZSA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgdG91Y2hlcyA9IHV0aWxzLmdldFRvdWNoUGFpcihldmVudCk7XG5cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHBhZ2VYOiAodG91Y2hlc1swXS5wYWdlWCArIHRvdWNoZXNbMV0ucGFnZVgpIC8gMixcbiAgICAgICAgICAgIHBhZ2VZOiAodG91Y2hlc1swXS5wYWdlWSArIHRvdWNoZXNbMV0ucGFnZVkpIC8gMixcbiAgICAgICAgICAgIGNsaWVudFg6ICh0b3VjaGVzWzBdLmNsaWVudFggKyB0b3VjaGVzWzFdLmNsaWVudFgpIC8gMixcbiAgICAgICAgICAgIGNsaWVudFk6ICh0b3VjaGVzWzBdLmNsaWVudFkgKyB0b3VjaGVzWzFdLmNsaWVudFkpIC8gMlxuICAgICAgICB9O1xuICAgIH07XG5cbiAgICB1dGlscy50b3VjaEJCb3ggPSBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgaWYgKCFldmVudC5sZW5ndGggJiYgIShldmVudC50b3VjaGVzICYmIGV2ZW50LnRvdWNoZXMubGVuZ3RoID4gMSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciB0b3VjaGVzID0gdXRpbHMuZ2V0VG91Y2hQYWlyKGV2ZW50KSxcbiAgICAgICAgICAgIG1pblggPSBNYXRoLm1pbih0b3VjaGVzWzBdLnBhZ2VYLCB0b3VjaGVzWzFdLnBhZ2VYKSxcbiAgICAgICAgICAgIG1pblkgPSBNYXRoLm1pbih0b3VjaGVzWzBdLnBhZ2VZLCB0b3VjaGVzWzFdLnBhZ2VZKSxcbiAgICAgICAgICAgIG1heFggPSBNYXRoLm1heCh0b3VjaGVzWzBdLnBhZ2VYLCB0b3VjaGVzWzFdLnBhZ2VYKSxcbiAgICAgICAgICAgIG1heFkgPSBNYXRoLm1heCh0b3VjaGVzWzBdLnBhZ2VZLCB0b3VjaGVzWzFdLnBhZ2VZKTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgeDogbWluWCxcbiAgICAgICAgICAgIHk6IG1pblksXG4gICAgICAgICAgICBsZWZ0OiBtaW5YLFxuICAgICAgICAgICAgdG9wOiBtaW5ZLFxuICAgICAgICAgICAgd2lkdGg6IG1heFggLSBtaW5YLFxuICAgICAgICAgICAgaGVpZ2h0OiBtYXhZIC0gbWluWVxuICAgICAgICB9O1xuICAgIH07XG5cbiAgICB1dGlscy50b3VjaERpc3RhbmNlID0gZnVuY3Rpb24gKGV2ZW50LCBkZWx0YVNvdXJjZSkge1xuICAgICAgICBkZWx0YVNvdXJjZSA9IGRlbHRhU291cmNlIHx8IHNjb3BlLmRlZmF1bHRPcHRpb25zLmRlbHRhU291cmNlO1xuXG4gICAgICAgIHZhciBzb3VyY2VYID0gZGVsdGFTb3VyY2UgKyAnWCcsXG4gICAgICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgICAgICB0b3VjaGVzID0gdXRpbHMuZ2V0VG91Y2hQYWlyKGV2ZW50KTtcblxuXG4gICAgICAgIHZhciBkeCA9IHRvdWNoZXNbMF1bc291cmNlWF0gLSB0b3VjaGVzWzFdW3NvdXJjZVhdLFxuICAgICAgICAgICAgZHkgPSB0b3VjaGVzWzBdW3NvdXJjZVldIC0gdG91Y2hlc1sxXVtzb3VyY2VZXTtcblxuICAgICAgICByZXR1cm4gdXRpbHMuaHlwb3QoZHgsIGR5KTtcbiAgICB9O1xuXG4gICAgdXRpbHMudG91Y2hBbmdsZSA9IGZ1bmN0aW9uIChldmVudCwgcHJldkFuZ2xlLCBkZWx0YVNvdXJjZSkge1xuICAgICAgICBkZWx0YVNvdXJjZSA9IGRlbHRhU291cmNlIHx8IHNjb3BlLmRlZmF1bHRPcHRpb25zLmRlbHRhU291cmNlO1xuXG4gICAgICAgIHZhciBzb3VyY2VYID0gZGVsdGFTb3VyY2UgKyAnWCcsXG4gICAgICAgICAgICBzb3VyY2VZID0gZGVsdGFTb3VyY2UgKyAnWScsXG4gICAgICAgICAgICB0b3VjaGVzID0gdXRpbHMuZ2V0VG91Y2hQYWlyKGV2ZW50KSxcbiAgICAgICAgICAgIGR4ID0gdG91Y2hlc1swXVtzb3VyY2VYXSAtIHRvdWNoZXNbMV1bc291cmNlWF0sXG4gICAgICAgICAgICBkeSA9IHRvdWNoZXNbMF1bc291cmNlWV0gLSB0b3VjaGVzWzFdW3NvdXJjZVldLFxuICAgICAgICAgICAgYW5nbGUgPSAxODAgKiBNYXRoLmF0YW4oZHkgLyBkeCkgLyBNYXRoLlBJO1xuXG4gICAgICAgIGlmIChzY29wZS5pc051bWJlcihwcmV2QW5nbGUpKSB7XG4gICAgICAgICAgICB2YXIgZHIgPSBhbmdsZSAtIHByZXZBbmdsZSxcbiAgICAgICAgICAgICAgICBkckNsYW1wZWQgPSBkciAlIDM2MDtcblxuICAgICAgICAgICAgaWYgKGRyQ2xhbXBlZCA+IDMxNSkge1xuICAgICAgICAgICAgICAgIGFuZ2xlIC09IDM2MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGRyQ2xhbXBlZCA+IDEzNSkge1xuICAgICAgICAgICAgICAgIGFuZ2xlIC09IDE4MCArIChhbmdsZSAvIDM2MCl8MCAqIDM2MDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGRyQ2xhbXBlZCA8IC0zMTUpIHtcbiAgICAgICAgICAgICAgICBhbmdsZSArPSAzNjAgKyAoYW5nbGUgLyAzNjApfDAgKiAzNjA7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChkckNsYW1wZWQgPCAtMTM1KSB7XG4gICAgICAgICAgICAgICAgYW5nbGUgKz0gMTgwICsgKGFuZ2xlIC8gMzYwKXwwICogMzYwO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuICBhbmdsZTtcbiAgICB9O1xuXG4gICAgc2NvcGUuZ2V0T3JpZ2luWFkgPSBmdW5jdGlvbiAoaW50ZXJhY3RhYmxlLCBlbGVtZW50KSB7XG4gICAgICAgIHZhciBvcmlnaW4gPSBpbnRlcmFjdGFibGVcbiAgICAgICAgICAgICAgICA/IGludGVyYWN0YWJsZS5vcHRpb25zLm9yaWdpblxuICAgICAgICAgICAgICAgIDogc2NvcGUuZGVmYXVsdE9wdGlvbnMub3JpZ2luO1xuXG4gICAgICAgIGlmIChvcmlnaW4gPT09ICdwYXJlbnQnKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKG9yaWdpbiA9PT0gJ3NlbGYnKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChzY29wZS50cnlTZWxlY3RvcihvcmlnaW4pKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBzY29wZS5jbG9zZXN0KGVsZW1lbnQsIG9yaWdpbikgfHwgeyB4OiAwLCB5OiAwIH07XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihvcmlnaW4pKSB7XG4gICAgICAgICAgICBvcmlnaW4gPSBvcmlnaW4oaW50ZXJhY3RhYmxlICYmIGVsZW1lbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChvcmlnaW4pKSAge1xuICAgICAgICAgICAgb3JpZ2luID0gc2NvcGUuZ2V0RWxlbWVudFJlY3Qob3JpZ2luKTtcbiAgICAgICAgfVxuXG4gICAgICAgIG9yaWdpbi54ID0gKCd4JyBpbiBvcmlnaW4pPyBvcmlnaW4ueCA6IG9yaWdpbi5sZWZ0O1xuICAgICAgICBvcmlnaW4ueSA9ICgneScgaW4gb3JpZ2luKT8gb3JpZ2luLnkgOiBvcmlnaW4udG9wO1xuXG4gICAgICAgIHJldHVybiBvcmlnaW47XG4gICAgfTtcblxuICAgIC8vIGh0dHA6Ly9zdGFja292ZXJmbG93LmNvbS9hLzU2MzQ1MjgvMjI4MDg4OFxuICAgIHNjb3BlLl9nZXRRQmV6aWVyVmFsdWUgPSBmdW5jdGlvbiAodCwgcDEsIHAyLCBwMykge1xuICAgICAgICB2YXIgaVQgPSAxIC0gdDtcbiAgICAgICAgcmV0dXJuIGlUICogaVQgKiBwMSArIDIgKiBpVCAqIHQgKiBwMiArIHQgKiB0ICogcDM7XG4gICAgfTtcblxuICAgIHNjb3BlLmdldFF1YWRyYXRpY0N1cnZlUG9pbnQgPSBmdW5jdGlvbiAoc3RhcnRYLCBzdGFydFksIGNwWCwgY3BZLCBlbmRYLCBlbmRZLCBwb3NpdGlvbikge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgeDogIHNjb3BlLl9nZXRRQmV6aWVyVmFsdWUocG9zaXRpb24sIHN0YXJ0WCwgY3BYLCBlbmRYKSxcbiAgICAgICAgICAgIHk6ICBzY29wZS5fZ2V0UUJlemllclZhbHVlKHBvc2l0aW9uLCBzdGFydFksIGNwWSwgZW5kWSlcbiAgICAgICAgfTtcbiAgICB9O1xuXG4gICAgLy8gaHR0cDovL2dpem1hLmNvbS9lYXNpbmcvXG4gICAgc2NvcGUuZWFzZU91dFF1YWQgPSBmdW5jdGlvbiAodCwgYiwgYywgZCkge1xuICAgICAgICB0IC89IGQ7XG4gICAgICAgIHJldHVybiAtYyAqIHQqKHQtMikgKyBiO1xuICAgIH07XG5cbiAgICBzY29wZS5ub2RlQ29udGFpbnMgPSBmdW5jdGlvbiAocGFyZW50LCBjaGlsZCkge1xuICAgICAgICB3aGlsZSAoY2hpbGQpIHtcbiAgICAgICAgICAgIGlmIChjaGlsZCA9PT0gcGFyZW50KSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNoaWxkID0gY2hpbGQucGFyZW50Tm9kZTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9O1xuXG4gICAgc2NvcGUuY2xvc2VzdCA9IGZ1bmN0aW9uIChjaGlsZCwgc2VsZWN0b3IpIHtcbiAgICAgICAgdmFyIHBhcmVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoY2hpbGQpO1xuXG4gICAgICAgIHdoaWxlICh1dGlscy5pc0VsZW1lbnQocGFyZW50KSkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLm1hdGNoZXNTZWxlY3RvcihwYXJlbnQsIHNlbGVjdG9yKSkgeyByZXR1cm4gcGFyZW50OyB9XG5cbiAgICAgICAgICAgIHBhcmVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQocGFyZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBudWxsO1xuICAgIH07XG5cbiAgICBzY29wZS5wYXJlbnRFbGVtZW50ID0gZnVuY3Rpb24gKG5vZGUpIHtcbiAgICAgICAgdmFyIHBhcmVudCA9IG5vZGUucGFyZW50Tm9kZTtcblxuICAgICAgICBpZiAoc2NvcGUuaXNEb2NGcmFnKHBhcmVudCkpIHtcbiAgICAgICAgICAgIC8vIHNraXAgcGFzdCAjc2hhZG8tcm9vdCBmcmFnbWVudHNcbiAgICAgICAgICAgIHdoaWxlICgocGFyZW50ID0gcGFyZW50Lmhvc3QpICYmIHNjb3BlLmlzRG9jRnJhZyhwYXJlbnQpKSB7fVxuXG4gICAgICAgICAgICByZXR1cm4gcGFyZW50O1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIHBhcmVudDtcbiAgICB9O1xuXG4gICAgc2NvcGUuaW5Db250ZXh0ID0gZnVuY3Rpb24gKGludGVyYWN0YWJsZSwgZWxlbWVudCkge1xuICAgICAgICByZXR1cm4gaW50ZXJhY3RhYmxlLl9jb250ZXh0ID09PSBlbGVtZW50Lm93bmVyRG9jdW1lbnRcbiAgICAgICAgICAgICAgICB8fCBzY29wZS5ub2RlQ29udGFpbnMoaW50ZXJhY3RhYmxlLl9jb250ZXh0LCBlbGVtZW50KTtcbiAgICB9O1xuXG4gICAgc2NvcGUudGVzdElnbm9yZSA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGludGVyYWN0YWJsZUVsZW1lbnQsIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIGlnbm9yZUZyb20gPSBpbnRlcmFjdGFibGUub3B0aW9ucy5pZ25vcmVGcm9tO1xuXG4gICAgICAgIGlmICghaWdub3JlRnJvbSB8fCAhdXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgICAgIGlmIChzY29wZS5pc1N0cmluZyhpZ25vcmVGcm9tKSkge1xuICAgICAgICAgICAgcmV0dXJuIHNjb3BlLm1hdGNoZXNVcFRvKGVsZW1lbnQsIGlnbm9yZUZyb20sIGludGVyYWN0YWJsZUVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHV0aWxzLmlzRWxlbWVudChpZ25vcmVGcm9tKSkge1xuICAgICAgICAgICAgcmV0dXJuIHNjb3BlLm5vZGVDb250YWlucyhpZ25vcmVGcm9tLCBlbGVtZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICB9O1xuXG4gICAgc2NvcGUudGVzdEFsbG93ID0gZnVuY3Rpb24gKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlRWxlbWVudCwgZWxlbWVudCkge1xuICAgICAgICB2YXIgYWxsb3dGcm9tID0gaW50ZXJhY3RhYmxlLm9wdGlvbnMuYWxsb3dGcm9tO1xuXG4gICAgICAgIGlmICghYWxsb3dGcm9tKSB7IHJldHVybiB0cnVlOyB9XG5cbiAgICAgICAgaWYgKCF1dGlscy5pc0VsZW1lbnQoZWxlbWVudCkpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKGFsbG93RnJvbSkpIHtcbiAgICAgICAgICAgIHJldHVybiBzY29wZS5tYXRjaGVzVXBUbyhlbGVtZW50LCBhbGxvd0Zyb20sIGludGVyYWN0YWJsZUVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHV0aWxzLmlzRWxlbWVudChhbGxvd0Zyb20pKSB7XG4gICAgICAgICAgICByZXR1cm4gc2NvcGUubm9kZUNvbnRhaW5zKGFsbG93RnJvbSwgZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgfTtcblxuICAgIHNjb3BlLmNoZWNrQXhpcyA9IGZ1bmN0aW9uIChheGlzLCBpbnRlcmFjdGFibGUpIHtcbiAgICAgICAgaWYgKCFpbnRlcmFjdGFibGUpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICAgICAgdmFyIHRoaXNBeGlzID0gaW50ZXJhY3RhYmxlLm9wdGlvbnMuZHJhZy5heGlzO1xuXG4gICAgICAgIHJldHVybiAoYXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gJ3h5JyB8fCB0aGlzQXhpcyA9PT0gYXhpcyk7XG4gICAgfTtcblxuICAgIHNjb3BlLmNoZWNrU25hcCA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGFjdGlvbikge1xuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zO1xuXG4gICAgICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgICAgICBhY3Rpb24gPSAncmVzaXplJztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBvcHRpb25zW2FjdGlvbl0uc25hcCAmJiBvcHRpb25zW2FjdGlvbl0uc25hcC5lbmFibGVkO1xuICAgIH07XG5cbiAgICBzY29wZS5jaGVja1Jlc3RyaWN0ID0gZnVuY3Rpb24gKGludGVyYWN0YWJsZSwgYWN0aW9uKSB7XG4gICAgICAgIHZhciBvcHRpb25zID0gaW50ZXJhY3RhYmxlLm9wdGlvbnM7XG5cbiAgICAgICAgaWYgKC9ecmVzaXplLy50ZXN0KGFjdGlvbikpIHtcbiAgICAgICAgICAgIGFjdGlvbiA9ICdyZXNpemUnO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuICBvcHRpb25zW2FjdGlvbl0ucmVzdHJpY3QgJiYgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0LmVuYWJsZWQ7XG4gICAgfTtcblxuICAgIHNjb3BlLmNoZWNrQXV0b1Njcm9sbCA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGFjdGlvbikge1xuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zO1xuXG4gICAgICAgIGlmICgvXnJlc2l6ZS8udGVzdChhY3Rpb24pKSB7XG4gICAgICAgICAgICBhY3Rpb24gPSAncmVzaXplJztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiAgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwgJiYgb3B0aW9uc1thY3Rpb25dLmF1dG9TY3JvbGwuZW5hYmxlZDtcbiAgICB9O1xuXG4gICAgc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdCA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGFjdGlvbikge1xuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICAgICAgbWF4QWN0aW9ucyA9IG9wdGlvbnNbYWN0aW9uLm5hbWVdLm1heCxcbiAgICAgICAgICAgIG1heFBlckVsZW1lbnQgPSBvcHRpb25zW2FjdGlvbi5uYW1lXS5tYXhQZXJFbGVtZW50LFxuICAgICAgICAgICAgYWN0aXZlSW50ZXJhY3Rpb25zID0gMCxcbiAgICAgICAgICAgIHRhcmdldENvdW50ID0gMCxcbiAgICAgICAgICAgIHRhcmdldEVsZW1lbnRDb3VudCA9IDA7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IHNjb3BlLmludGVyYWN0aW9ucy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgdmFyIGludGVyYWN0aW9uID0gc2NvcGUuaW50ZXJhY3Rpb25zW2ldLFxuICAgICAgICAgICAgICAgIG90aGVyQWN0aW9uID0gaW50ZXJhY3Rpb24ucHJlcGFyZWQubmFtZSxcbiAgICAgICAgICAgICAgICBhY3RpdmUgPSBpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgICAgICBpZiAoIWFjdGl2ZSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICBhY3RpdmVJbnRlcmFjdGlvbnMrKztcblxuICAgICAgICAgICAgaWYgKGFjdGl2ZUludGVyYWN0aW9ucyA+PSBzY29wZS5tYXhJbnRlcmFjdGlvbnMpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi50YXJnZXQgIT09IGludGVyYWN0YWJsZSkgeyBjb250aW51ZTsgfVxuXG4gICAgICAgICAgICB0YXJnZXRDb3VudCArPSAob3RoZXJBY3Rpb24gPT09IGFjdGlvbi5uYW1lKXwwO1xuXG4gICAgICAgICAgICBpZiAodGFyZ2V0Q291bnQgPj0gbWF4QWN0aW9ucykge1xuICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLmVsZW1lbnQgPT09IGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICB0YXJnZXRFbGVtZW50Q291bnQrKztcblxuICAgICAgICAgICAgICAgIGlmIChvdGhlckFjdGlvbiAhPT0gYWN0aW9uLm5hbWUgfHwgdGFyZ2V0RWxlbWVudENvdW50ID49IG1heFBlckVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBzY29wZS5tYXhJbnRlcmFjdGlvbnMgPiAwO1xuICAgIH07XG5cbiAgICAvLyBUZXN0IGZvciB0aGUgZWxlbWVudCB0aGF0J3MgXCJhYm92ZVwiIGFsbCBvdGhlciBxdWFsaWZpZXJzXG4gICAgc2NvcGUuaW5kZXhPZkRlZXBlc3RFbGVtZW50ID0gZnVuY3Rpb24gKGVsZW1lbnRzKSB7XG4gICAgICAgIHZhciBkcm9wem9uZSxcbiAgICAgICAgICAgIGRlZXBlc3Rab25lID0gZWxlbWVudHNbMF0sXG4gICAgICAgICAgICBpbmRleCA9IGRlZXBlc3Rab25lPyAwOiAtMSxcbiAgICAgICAgICAgIHBhcmVudCxcbiAgICAgICAgICAgIGRlZXBlc3Rab25lUGFyZW50cyA9IFtdLFxuICAgICAgICAgICAgZHJvcHpvbmVQYXJlbnRzID0gW10sXG4gICAgICAgICAgICBjaGlsZCxcbiAgICAgICAgICAgIGksXG4gICAgICAgICAgICBuO1xuXG4gICAgICAgIGZvciAoaSA9IDE7IGkgPCBlbGVtZW50cy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgZHJvcHpvbmUgPSBlbGVtZW50c1tpXTtcblxuICAgICAgICAgICAgLy8gYW4gZWxlbWVudCBtaWdodCBiZWxvbmcgdG8gbXVsdGlwbGUgc2VsZWN0b3IgZHJvcHpvbmVzXG4gICAgICAgICAgICBpZiAoIWRyb3B6b25lIHx8IGRyb3B6b25lID09PSBkZWVwZXN0Wm9uZSkge1xuICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoIWRlZXBlc3Rab25lKSB7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmUgPSBkcm9wem9uZTtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIGNoZWNrIGlmIHRoZSBkZWVwZXN0IG9yIGN1cnJlbnQgYXJlIGRvY3VtZW50LmRvY3VtZW50RWxlbWVudCBvciBkb2N1bWVudC5yb290RWxlbWVudFxuICAgICAgICAgICAgLy8gLSBpZiB0aGUgY3VycmVudCBkcm9wem9uZSBpcywgZG8gbm90aGluZyBhbmQgY29udGludWVcbiAgICAgICAgICAgIGlmIChkcm9wem9uZS5wYXJlbnROb2RlID09PSBkcm9wem9uZS5vd25lckRvY3VtZW50KSB7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyAtIGlmIGRlZXBlc3QgaXMsIHVwZGF0ZSB3aXRoIHRoZSBjdXJyZW50IGRyb3B6b25lIGFuZCBjb250aW51ZSB0byBuZXh0XG4gICAgICAgICAgICBlbHNlIGlmIChkZWVwZXN0Wm9uZS5wYXJlbnROb2RlID09PSBkcm9wem9uZS5vd25lckRvY3VtZW50KSB7XG4gICAgICAgICAgICAgICAgZGVlcGVzdFpvbmUgPSBkcm9wem9uZTtcbiAgICAgICAgICAgICAgICBpbmRleCA9IGk7XG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICghZGVlcGVzdFpvbmVQYXJlbnRzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgIHBhcmVudCA9IGRlZXBlc3Rab25lO1xuICAgICAgICAgICAgICAgIHdoaWxlIChwYXJlbnQucGFyZW50Tm9kZSAmJiBwYXJlbnQucGFyZW50Tm9kZSAhPT0gcGFyZW50Lm93bmVyRG9jdW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgZGVlcGVzdFpvbmVQYXJlbnRzLnVuc2hpZnQocGFyZW50KTtcbiAgICAgICAgICAgICAgICAgICAgcGFyZW50ID0gcGFyZW50LnBhcmVudE5vZGU7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBpZiB0aGlzIGVsZW1lbnQgaXMgYW4gc3ZnIGVsZW1lbnQgYW5kIHRoZSBjdXJyZW50IGRlZXBlc3QgaXNcbiAgICAgICAgICAgIC8vIGFuIEhUTUxFbGVtZW50XG4gICAgICAgICAgICBpZiAoZGVlcGVzdFpvbmUgaW5zdGFuY2VvZiBzY29wZS5IVE1MRWxlbWVudFxuICAgICAgICAgICAgICAgICYmIGRyb3B6b25lIGluc3RhbmNlb2Ygc2NvcGUuU1ZHRWxlbWVudFxuICAgICAgICAgICAgICAgICYmICEoZHJvcHpvbmUgaW5zdGFuY2VvZiBzY29wZS5TVkdTVkdFbGVtZW50KSkge1xuXG4gICAgICAgICAgICAgICAgaWYgKGRyb3B6b25lID09PSBkZWVwZXN0Wm9uZS5wYXJlbnROb2RlKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHBhcmVudCA9IGRyb3B6b25lLm93bmVyU1ZHRWxlbWVudDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHBhcmVudCA9IGRyb3B6b25lO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBkcm9wem9uZVBhcmVudHMgPSBbXTtcblxuICAgICAgICAgICAgd2hpbGUgKHBhcmVudC5wYXJlbnROb2RlICE9PSBwYXJlbnQub3duZXJEb2N1bWVudCkge1xuICAgICAgICAgICAgICAgIGRyb3B6b25lUGFyZW50cy51bnNoaWZ0KHBhcmVudCk7XG4gICAgICAgICAgICAgICAgcGFyZW50ID0gcGFyZW50LnBhcmVudE5vZGU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIG4gPSAwO1xuXG4gICAgICAgICAgICAvLyBnZXQgKHBvc2l0aW9uIG9mIGxhc3QgY29tbW9uIGFuY2VzdG9yKSArIDFcbiAgICAgICAgICAgIHdoaWxlIChkcm9wem9uZVBhcmVudHNbbl0gJiYgZHJvcHpvbmVQYXJlbnRzW25dID09PSBkZWVwZXN0Wm9uZVBhcmVudHNbbl0pIHtcbiAgICAgICAgICAgICAgICBuKys7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBwYXJlbnRzID0gW1xuICAgICAgICAgICAgICAgIGRyb3B6b25lUGFyZW50c1tuIC0gMV0sXG4gICAgICAgICAgICAgICAgZHJvcHpvbmVQYXJlbnRzW25dLFxuICAgICAgICAgICAgICAgIGRlZXBlc3Rab25lUGFyZW50c1tuXVxuICAgICAgICAgICAgXTtcblxuICAgICAgICAgICAgY2hpbGQgPSBwYXJlbnRzWzBdLmxhc3RDaGlsZDtcblxuICAgICAgICAgICAgd2hpbGUgKGNoaWxkKSB7XG4gICAgICAgICAgICAgICAgaWYgKGNoaWxkID09PSBwYXJlbnRzWzFdKSB7XG4gICAgICAgICAgICAgICAgICAgIGRlZXBlc3Rab25lID0gZHJvcHpvbmU7XG4gICAgICAgICAgICAgICAgICAgIGluZGV4ID0gaTtcbiAgICAgICAgICAgICAgICAgICAgZGVlcGVzdFpvbmVQYXJlbnRzID0gW107XG5cbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKGNoaWxkID09PSBwYXJlbnRzWzJdKSB7XG4gICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNoaWxkID0gY2hpbGQucHJldmlvdXNTaWJsaW5nO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGluZGV4O1xuICAgIH07XG5cbiAgICBzY29wZS5tYXRjaGVzU2VsZWN0b3IgPSBmdW5jdGlvbiAoZWxlbWVudCwgc2VsZWN0b3IsIG5vZGVMaXN0KSB7XG4gICAgICAgIGlmIChzY29wZS5pZThNYXRjaGVzU2VsZWN0b3IpIHtcbiAgICAgICAgICAgIHJldHVybiBzY29wZS5pZThNYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIG5vZGVMaXN0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIHJlbW92ZSAvZGVlcC8gZnJvbSBzZWxlY3RvcnMgaWYgc2hhZG93RE9NIHBvbHlmaWxsIGlzIHVzZWRcbiAgICAgICAgaWYgKHNjb3BlLndpbmRvdyAhPT0gc2NvcGUucmVhbFdpbmRvdykge1xuICAgICAgICAgICAgc2VsZWN0b3IgPSBzZWxlY3Rvci5yZXBsYWNlKC9cXC9kZWVwXFwvL2csICcgJyk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZWxlbWVudFtzY29wZS5wcmVmaXhlZE1hdGNoZXNTZWxlY3Rvcl0oc2VsZWN0b3IpO1xuICAgIH07XG5cbiAgICBzY29wZS5tYXRjaGVzVXBUbyA9IGZ1bmN0aW9uIChlbGVtZW50LCBzZWxlY3RvciwgbGltaXQpIHtcbiAgICAgICAgd2hpbGUgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLm1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBzZWxlY3RvcikpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG5cbiAgICAgICAgICAgIGlmIChlbGVtZW50ID09PSBsaW1pdCkge1xuICAgICAgICAgICAgICAgIHJldHVybiBzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgIH07XG5cbiAgICAvLyBGb3IgSUU4J3MgbGFjayBvZiBhbiBFbGVtZW50I21hdGNoZXNTZWxlY3RvclxuICAgIC8vIHRha2VuIGZyb20gaHR0cDovL3RhbmFsaW4uY29tL2VuL2Jsb2cvMjAxMi8xMi9tYXRjaGVzLXNlbGVjdG9yLWllOC8gYW5kIG1vZGlmaWVkXG4gICAgaWYgKCEoc2NvcGUucHJlZml4ZWRNYXRjaGVzU2VsZWN0b3IgaW4gRWxlbWVudC5wcm90b3R5cGUpIHx8ICFzY29wZS5pc0Z1bmN0aW9uKEVsZW1lbnQucHJvdG90eXBlW3Njb3BlLnByZWZpeGVkTWF0Y2hlc1NlbGVjdG9yXSkpIHtcbiAgICAgICAgc2NvcGUuaWU4TWF0Y2hlc1NlbGVjdG9yID0gZnVuY3Rpb24gKGVsZW1lbnQsIHNlbGVjdG9yLCBlbGVtcykge1xuICAgICAgICAgICAgZWxlbXMgPSBlbGVtcyB8fCBlbGVtZW50LnBhcmVudE5vZGUucXVlcnlTZWxlY3RvckFsbChzZWxlY3Rvcik7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBlbGVtcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIGlmIChlbGVtc1tpXSA9PT0gZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICByZXR1cm4gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgICAgfTtcbiAgICB9XG5cbiAgICB2YXIgSW50ZXJhY3Rpb24gPSByZXF1aXJlKCcuL0ludGVyYWN0aW9uJyk7XG5cbiAgICBmdW5jdGlvbiBnZXRJbnRlcmFjdGlvbkZyb21Qb2ludGVyIChwb2ludGVyLCBldmVudFR5cGUsIGV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciBpID0gMCwgbGVuID0gc2NvcGUuaW50ZXJhY3Rpb25zLmxlbmd0aCxcbiAgICAgICAgICAgIG1vdXNlRXZlbnQgPSAoL21vdXNlL2kudGVzdChwb2ludGVyLnBvaW50ZXJUeXBlIHx8IGV2ZW50VHlwZSlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gTVNQb2ludGVyRXZlbnQuTVNQT0lOVEVSX1RZUEVfTU9VU0VcbiAgICAgICAgICAgICAgICAgICAgICAgICAgfHwgcG9pbnRlci5wb2ludGVyVHlwZSA9PT0gNCksXG4gICAgICAgICAgICBpbnRlcmFjdGlvbjtcblxuICAgICAgICB2YXIgaWQgPSB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcik7XG5cbiAgICAgICAgLy8gdHJ5IHRvIHJlc3VtZSBpbmVydGlhIHdpdGggYSBuZXcgcG9pbnRlclxuICAgICAgICBpZiAoL2Rvd258c3RhcnQvaS50ZXN0KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIGludGVyYWN0aW9uID0gc2NvcGUuaW50ZXJhY3Rpb25zW2ldO1xuXG4gICAgICAgICAgICAgICAgdmFyIGVsZW1lbnQgPSBldmVudFRhcmdldDtcblxuICAgICAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLmFjdGl2ZSAmJiBpbnRlcmFjdGlvbi50YXJnZXQub3B0aW9uc1tpbnRlcmFjdGlvbi5wcmVwYXJlZC5uYW1lXS5pbmVydGlhLmFsbG93UmVzdW1lXG4gICAgICAgICAgICAgICAgICAgICYmIChpbnRlcmFjdGlvbi5tb3VzZSA9PT0gbW91c2VFdmVudCkpIHtcbiAgICAgICAgICAgICAgICAgICAgd2hpbGUgKGVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBlbGVtZW50IGlzIHRoZSBpbnRlcmFjdGlvbiBlbGVtZW50XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudCA9PT0gaW50ZXJhY3Rpb24uZWxlbWVudCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgaW50ZXJhY3Rpb24ncyBwb2ludGVyXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGludGVyYWN0aW9uLnBvaW50ZXJzWzBdKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLnJlbW92ZVBvaW50ZXIoaW50ZXJhY3Rpb24ucG9pbnRlcnNbMF0pO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5hZGRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBpZiBpdCdzIGEgbW91c2UgaW50ZXJhY3Rpb25cbiAgICAgICAgaWYgKG1vdXNlRXZlbnQgfHwgIShicm93c2VyLnN1cHBvcnRzVG91Y2ggfHwgYnJvd3Nlci5zdXBwb3J0c1BvaW50ZXJFdmVudCkpIHtcblxuICAgICAgICAgICAgLy8gZmluZCBhIG1vdXNlIGludGVyYWN0aW9uIHRoYXQncyBub3QgaW4gaW5lcnRpYSBwaGFzZVxuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLmludGVyYWN0aW9uc1tpXS5tb3VzZSAmJiAhc2NvcGUuaW50ZXJhY3Rpb25zW2ldLmluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBzY29wZS5pbnRlcmFjdGlvbnNbaV07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBmaW5kIGFueSBpbnRlcmFjdGlvbiBzcGVjaWZpY2FsbHkgZm9yIG1vdXNlLlxuICAgICAgICAgICAgLy8gaWYgdGhlIGV2ZW50VHlwZSBpcyBhIG1vdXNlZG93biwgYW5kIGluZXJ0aWEgaXMgYWN0aXZlXG4gICAgICAgICAgICAvLyBpZ25vcmUgdGhlIGludGVyYWN0aW9uXG4gICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaW50ZXJhY3Rpb25zW2ldLm1vdXNlICYmICEoL2Rvd24vLnRlc3QoZXZlbnRUeXBlKSAmJiBzY29wZS5pbnRlcmFjdGlvbnNbaV0uaW5lcnRpYVN0YXR1cy5hY3RpdmUpKSB7XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbjtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIGNyZWF0ZSBhIG5ldyBpbnRlcmFjdGlvbiBmb3IgbW91c2VcbiAgICAgICAgICAgIGludGVyYWN0aW9uID0gbmV3IEludGVyYWN0aW9uKCk7XG4gICAgICAgICAgICBpbnRlcmFjdGlvbi5tb3VzZSA9IHRydWU7XG5cbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdGlvbjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGdldCBpbnRlcmFjdGlvbiB0aGF0IGhhcyB0aGlzIHBvaW50ZXJcbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuY29udGFpbnMoc2NvcGUuaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJJZHMsIGlkKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiBzY29wZS5pbnRlcmFjdGlvbnNbaV07XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBhdCB0aGlzIHN0YWdlLCBhIHBvaW50ZXJVcCBzaG91bGQgbm90IHJldHVybiBhbiBpbnRlcmFjdGlvblxuICAgICAgICBpZiAoL3VwfGVuZHxvdXQvaS50ZXN0KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gZ2V0IGZpcnN0IGlkbGUgaW50ZXJhY3Rpb25cbiAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICBpbnRlcmFjdGlvbiA9IHNjb3BlLmludGVyYWN0aW9uc1tpXTtcblxuICAgICAgICAgICAgaWYgKCghaW50ZXJhY3Rpb24ucHJlcGFyZWQubmFtZSB8fCAoaW50ZXJhY3Rpb24udGFyZ2V0Lm9wdGlvbnMuZ2VzdHVyZS5lbmFibGVkKSlcbiAgICAgICAgICAgICAgICAmJiAhaW50ZXJhY3Rpb24uaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgICAgICYmICEoIW1vdXNlRXZlbnQgJiYgaW50ZXJhY3Rpb24ubW91c2UpKSB7XG5cbiAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5hZGRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0aW9uO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG5ldyBJbnRlcmFjdGlvbigpO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIGRvT25JbnRlcmFjdGlvbnMgKG1ldGhvZCkge1xuICAgICAgICByZXR1cm4gKGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICAgICAgdmFyIGludGVyYWN0aW9uLFxuICAgICAgICAgICAgICAgIGV2ZW50VGFyZ2V0ID0gc2NvcGUuZ2V0QWN0dWFsRWxlbWVudChldmVudC5wYXRoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID8gZXZlbnQucGF0aFswXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IGV2ZW50LnRhcmdldCksXG4gICAgICAgICAgICAgICAgY3VyRXZlbnRUYXJnZXQgPSBzY29wZS5nZXRBY3R1YWxFbGVtZW50KGV2ZW50LmN1cnJlbnRUYXJnZXQpLFxuICAgICAgICAgICAgICAgIGk7XG5cbiAgICAgICAgICAgIGlmIChicm93c2VyLnN1cHBvcnRzVG91Y2ggJiYgL3RvdWNoLy50ZXN0KGV2ZW50LnR5cGUpKSB7XG4gICAgICAgICAgICAgICAgc2NvcGUucHJldlRvdWNoVGltZSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuXG4gICAgICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGV2ZW50LmNoYW5nZWRUb3VjaGVzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBwb2ludGVyID0gZXZlbnQuY2hhbmdlZFRvdWNoZXNbaV07XG5cbiAgICAgICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBnZXRJbnRlcmFjdGlvbkZyb21Qb2ludGVyKHBvaW50ZXIsIGV2ZW50LnR5cGUsIGV2ZW50VGFyZ2V0KTtcblxuICAgICAgICAgICAgICAgICAgICBpZiAoIWludGVyYWN0aW9uKSB7IGNvbnRpbnVlOyB9XG5cbiAgICAgICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uX3VwZGF0ZUV2ZW50VGFyZ2V0cyhldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uW21ldGhvZF0ocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgaWYgKCFicm93c2VyLnN1cHBvcnRzUG9pbnRlckV2ZW50ICYmIC9tb3VzZS8udGVzdChldmVudC50eXBlKSkge1xuICAgICAgICAgICAgICAgICAgICAvLyBpZ25vcmUgbW91c2UgZXZlbnRzIHdoaWxlIHRvdWNoIGludGVyYWN0aW9ucyBhcmUgYWN0aXZlXG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBzY29wZS5pbnRlcmFjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmICghc2NvcGUuaW50ZXJhY3Rpb25zW2ldLm1vdXNlICYmIHNjb3BlLmludGVyYWN0aW9uc1tpXS5wb2ludGVySXNEb3duKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgLy8gdHJ5IHRvIGlnbm9yZSBtb3VzZSBldmVudHMgdGhhdCBhcmUgc2ltdWxhdGVkIGJ5IHRoZSBicm93c2VyXG4gICAgICAgICAgICAgICAgICAgIC8vIGFmdGVyIGEgdG91Y2ggZXZlbnRcbiAgICAgICAgICAgICAgICAgICAgaWYgKG5ldyBEYXRlKCkuZ2V0VGltZSgpIC0gc2NvcGUucHJldlRvdWNoVGltZSA8IDUwMCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBnZXRJbnRlcmFjdGlvbkZyb21Qb2ludGVyKGV2ZW50LCBldmVudC50eXBlLCBldmVudFRhcmdldCk7XG5cbiAgICAgICAgICAgICAgICBpZiAoIWludGVyYWN0aW9uKSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24uX3VwZGF0ZUV2ZW50VGFyZ2V0cyhldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQpO1xuXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb25bbWV0aG9kXShldmVudCwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH0pO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIHByZXZlbnRPcmlnaW5hbERlZmF1bHQgKCkge1xuICAgICAgICB0aGlzLm9yaWdpbmFsRXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICB9XG5cbiAgICBmdW5jdGlvbiBjaGVja1Jlc2l6ZUVkZ2UgKG5hbWUsIHZhbHVlLCBwYWdlLCBlbGVtZW50LCBpbnRlcmFjdGFibGVFbGVtZW50LCByZWN0LCBtYXJnaW4pIHtcbiAgICAgICAgLy8gZmFsc2UsICcnLCB1bmRlZmluZWQsIG51bGxcbiAgICAgICAgaWYgKCF2YWx1ZSkgeyByZXR1cm4gZmFsc2U7IH1cblxuICAgICAgICAvLyB0cnVlIHZhbHVlLCB1c2UgcG9pbnRlciBjb29yZHMgYW5kIGVsZW1lbnQgcmVjdFxuICAgICAgICBpZiAodmFsdWUgPT09IHRydWUpIHtcbiAgICAgICAgICAgIC8vIGlmIGRpbWVuc2lvbnMgYXJlIG5lZ2F0aXZlLCBcInN3aXRjaFwiIGVkZ2VzXG4gICAgICAgICAgICB2YXIgd2lkdGggPSBzY29wZS5pc051bWJlcihyZWN0LndpZHRoKT8gcmVjdC53aWR0aCA6IHJlY3QucmlnaHQgLSByZWN0LmxlZnQsXG4gICAgICAgICAgICAgICAgaGVpZ2h0ID0gc2NvcGUuaXNOdW1iZXIocmVjdC5oZWlnaHQpPyByZWN0LmhlaWdodCA6IHJlY3QuYm90dG9tIC0gcmVjdC50b3A7XG5cbiAgICAgICAgICAgIGlmICh3aWR0aCA8IDApIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChuYW1lID09PSAnbGVmdCcgKSB7IG5hbWUgPSAncmlnaHQnOyB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAobmFtZSA9PT0gJ3JpZ2h0JykgeyBuYW1lID0gJ2xlZnQnIDsgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKGhlaWdodCA8IDApIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChuYW1lID09PSAndG9wJyAgICkgeyBuYW1lID0gJ2JvdHRvbSc7IH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChuYW1lID09PSAnYm90dG9tJykgeyBuYW1lID0gJ3RvcCcgICA7IH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKG5hbWUgPT09ICdsZWZ0JyAgKSB7IHJldHVybiBwYWdlLnggPCAoKHdpZHRoICA+PSAwPyByZWN0LmxlZnQ6IHJlY3QucmlnaHQgKSArIG1hcmdpbik7IH1cbiAgICAgICAgICAgIGlmIChuYW1lID09PSAndG9wJyAgICkgeyByZXR1cm4gcGFnZS55IDwgKChoZWlnaHQgPj0gMD8gcmVjdC50b3AgOiByZWN0LmJvdHRvbSkgKyBtYXJnaW4pOyB9XG5cbiAgICAgICAgICAgIGlmIChuYW1lID09PSAncmlnaHQnICkgeyByZXR1cm4gcGFnZS54ID4gKCh3aWR0aCAgPj0gMD8gcmVjdC5yaWdodCA6IHJlY3QubGVmdCkgLSBtYXJnaW4pOyB9XG4gICAgICAgICAgICBpZiAobmFtZSA9PT0gJ2JvdHRvbScpIHsgcmV0dXJuIHBhZ2UueSA+ICgoaGVpZ2h0ID49IDA/IHJlY3QuYm90dG9tOiByZWN0LnRvcCApIC0gbWFyZ2luKTsgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gdGhlIHJlbWFpbmluZyBjaGVja3MgcmVxdWlyZSBhbiBlbGVtZW50XG4gICAgICAgIGlmICghdXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7IHJldHVybiBmYWxzZTsgfVxuXG4gICAgICAgIHJldHVybiB1dGlscy5pc0VsZW1lbnQodmFsdWUpXG4gICAgICAgICAgICAgICAgICAgIC8vIHRoZSB2YWx1ZSBpcyBhbiBlbGVtZW50IHRvIHVzZSBhcyBhIHJlc2l6ZSBoYW5kbGVcbiAgICAgICAgICAgICAgICAgICAgPyB2YWx1ZSA9PT0gZWxlbWVudFxuICAgICAgICAgICAgICAgICAgICAvLyBvdGhlcndpc2UgY2hlY2sgaWYgZWxlbWVudCBtYXRjaGVzIHZhbHVlIGFzIHNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgIDogc2NvcGUubWF0Y2hlc1VwVG8oZWxlbWVudCwgdmFsdWUsIGludGVyYWN0YWJsZUVsZW1lbnQpO1xuICAgIH1cblxuICAgIGZ1bmN0aW9uIGRlZmF1bHRBY3Rpb25DaGVja2VyIChwb2ludGVyLCBpbnRlcmFjdGlvbiwgZWxlbWVudCkge1xuICAgICAgICB2YXIgcmVjdCA9IHRoaXMuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgICAgIHNob3VsZFJlc2l6ZSA9IGZhbHNlLFxuICAgICAgICAgICAgYWN0aW9uID0gbnVsbCxcbiAgICAgICAgICAgIHJlc2l6ZUF4ZXMgPSBudWxsLFxuICAgICAgICAgICAgcmVzaXplRWRnZXMsXG4gICAgICAgICAgICBwYWdlID0gdXRpbHMuZXh0ZW5kKHt9LCBpbnRlcmFjdGlvbi5jdXJDb29yZHMucGFnZSksXG4gICAgICAgICAgICBvcHRpb25zID0gdGhpcy5vcHRpb25zO1xuXG4gICAgICAgIGlmICghcmVjdCkgeyByZXR1cm4gbnVsbDsgfVxuXG4gICAgICAgIGlmIChzY29wZS5hY3Rpb25Jc0VuYWJsZWQucmVzaXplICYmIG9wdGlvbnMucmVzaXplLmVuYWJsZWQpIHtcbiAgICAgICAgICAgIHZhciByZXNpemVPcHRpb25zID0gb3B0aW9ucy5yZXNpemU7XG5cbiAgICAgICAgICAgIHJlc2l6ZUVkZ2VzID0ge1xuICAgICAgICAgICAgICAgIGxlZnQ6IGZhbHNlLCByaWdodDogZmFsc2UsIHRvcDogZmFsc2UsIGJvdHRvbTogZmFsc2VcbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIC8vIGlmIHVzaW5nIHJlc2l6ZS5lZGdlc1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KHJlc2l6ZU9wdGlvbnMuZWRnZXMpKSB7XG4gICAgICAgICAgICAgICAgZm9yICh2YXIgZWRnZSBpbiByZXNpemVFZGdlcykge1xuICAgICAgICAgICAgICAgICAgICByZXNpemVFZGdlc1tlZGdlXSA9IGNoZWNrUmVzaXplRWRnZShlZGdlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXNpemVPcHRpb25zLmVkZ2VzW2VkZ2VdLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYWdlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbi5fZXZlbnRUYXJnZXQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsZW1lbnQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY3QsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc2l6ZU9wdGlvbnMubWFyZ2luIHx8IHNjb3BlLm1hcmdpbik7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmVzaXplRWRnZXMubGVmdCA9IHJlc2l6ZUVkZ2VzLmxlZnQgJiYgIXJlc2l6ZUVkZ2VzLnJpZ2h0O1xuICAgICAgICAgICAgICAgIHJlc2l6ZUVkZ2VzLnRvcCAgPSByZXNpemVFZGdlcy50b3AgICYmICFyZXNpemVFZGdlcy5ib3R0b207XG5cbiAgICAgICAgICAgICAgICBzaG91bGRSZXNpemUgPSByZXNpemVFZGdlcy5sZWZ0IHx8IHJlc2l6ZUVkZ2VzLnJpZ2h0IHx8IHJlc2l6ZUVkZ2VzLnRvcCB8fCByZXNpemVFZGdlcy5ib3R0b207XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB2YXIgcmlnaHQgID0gb3B0aW9ucy5yZXNpemUuYXhpcyAhPT0gJ3knICYmIHBhZ2UueCA+IChyZWN0LnJpZ2h0ICAtIHNjb3BlLm1hcmdpbiksXG4gICAgICAgICAgICAgICAgICAgIGJvdHRvbSA9IG9wdGlvbnMucmVzaXplLmF4aXMgIT09ICd4JyAmJiBwYWdlLnkgPiAocmVjdC5ib3R0b20gLSBzY29wZS5tYXJnaW4pO1xuXG4gICAgICAgICAgICAgICAgc2hvdWxkUmVzaXplID0gcmlnaHQgfHwgYm90dG9tO1xuICAgICAgICAgICAgICAgIHJlc2l6ZUF4ZXMgPSAocmlnaHQ/ICd4JyA6ICcnKSArIChib3R0b20/ICd5JyA6ICcnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGFjdGlvbiA9IHNob3VsZFJlc2l6ZVxuICAgICAgICAgICAgPyAncmVzaXplJ1xuICAgICAgICAgICAgOiBzY29wZS5hY3Rpb25Jc0VuYWJsZWQuZHJhZyAmJiBvcHRpb25zLmRyYWcuZW5hYmxlZFxuICAgICAgICAgICAgICAgID8gJ2RyYWcnXG4gICAgICAgICAgICAgICAgOiBudWxsO1xuXG4gICAgICAgIGlmIChzY29wZS5hY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZVxuICAgICAgICAgICAgJiYgaW50ZXJhY3Rpb24ucG9pbnRlcklkcy5sZW5ndGggPj0yXG4gICAgICAgICAgICAmJiAhKGludGVyYWN0aW9uLmRyYWdnaW5nIHx8IGludGVyYWN0aW9uLnJlc2l6aW5nKSkge1xuICAgICAgICAgICAgYWN0aW9uID0gJ2dlc3R1cmUnO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGFjdGlvbikge1xuICAgICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgICAgICBuYW1lOiBhY3Rpb24sXG4gICAgICAgICAgICAgICAgYXhpczogcmVzaXplQXhlcyxcbiAgICAgICAgICAgICAgICBlZGdlczogcmVzaXplRWRnZXNcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG5cbiAgICB2YXIgSW50ZXJhY3RFdmVudCA9IHJlcXVpcmUoJy4vSW50ZXJhY3RFdmVudCcpO1xuXG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGludGVyYWN0aW9uTGlzdGVuZXJzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIHZhciBsaXN0ZW5lck5hbWUgPSBpbnRlcmFjdGlvbkxpc3RlbmVyc1tpXTtcblxuICAgICAgICBzY29wZS5saXN0ZW5lcnNbbGlzdGVuZXJOYW1lXSA9IGRvT25JbnRlcmFjdGlvbnMobGlzdGVuZXJOYW1lKTtcbiAgICB9XG5cbiAgICAvLyBib3VuZCB0byB0aGUgaW50ZXJhY3RhYmxlIGNvbnRleHQgd2hlbiBhIERPTSBldmVudFxuICAgIC8vIGxpc3RlbmVyIGlzIGFkZGVkIHRvIGEgc2VsZWN0b3IgaW50ZXJhY3RhYmxlXG4gICAgZnVuY3Rpb24gZGVsZWdhdGVMaXN0ZW5lciAoZXZlbnQsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgdmFyIGZha2VFdmVudCA9IHt9LFxuICAgICAgICAgICAgZGVsZWdhdGVkID0gc2NvcGUuZGVsZWdhdGVkRXZlbnRzW2V2ZW50LnR5cGVdLFxuICAgICAgICAgICAgZXZlbnRUYXJnZXQgPSBzY29wZS5nZXRBY3R1YWxFbGVtZW50KGV2ZW50LnBhdGhcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA/IGV2ZW50LnBhdGhbMF1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IGV2ZW50LnRhcmdldCksXG4gICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgICAgIC8vIGR1cGxpY2F0ZSB0aGUgZXZlbnQgc28gdGhhdCBjdXJyZW50VGFyZ2V0IGNhbiBiZSBjaGFuZ2VkXG4gICAgICAgIGZvciAodmFyIHByb3AgaW4gZXZlbnQpIHtcbiAgICAgICAgICAgIGZha2VFdmVudFtwcm9wXSA9IGV2ZW50W3Byb3BdO1xuICAgICAgICB9XG5cbiAgICAgICAgZmFrZUV2ZW50Lm9yaWdpbmFsRXZlbnQgPSBldmVudDtcbiAgICAgICAgZmFrZUV2ZW50LnByZXZlbnREZWZhdWx0ID0gcHJldmVudE9yaWdpbmFsRGVmYXVsdDtcblxuICAgICAgICAvLyBjbGltYiB1cCBkb2N1bWVudCB0cmVlIGxvb2tpbmcgZm9yIHNlbGVjdG9yIG1hdGNoZXNcbiAgICAgICAgd2hpbGUgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgdmFyIHNlbGVjdG9yID0gZGVsZWdhdGVkLnNlbGVjdG9yc1tpXSxcbiAgICAgICAgICAgICAgICAgICAgY29udGV4dCA9IGRlbGVnYXRlZC5jb250ZXh0c1tpXTtcblxuICAgICAgICAgICAgICAgIGlmIChzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgICAgICYmIHNjb3BlLm5vZGVDb250YWlucyhjb250ZXh0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAgICAgJiYgc2NvcGUubm9kZUNvbnRhaW5zKGNvbnRleHQsIGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgICAgICAgICAgdmFyIGxpc3RlbmVycyA9IGRlbGVnYXRlZC5saXN0ZW5lcnNbaV07XG5cbiAgICAgICAgICAgICAgICAgICAgZmFrZUV2ZW50LmN1cnJlbnRUYXJnZXQgPSBlbGVtZW50O1xuXG4gICAgICAgICAgICAgICAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgbGlzdGVuZXJzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAobGlzdGVuZXJzW2pdWzFdID09PSB1c2VDYXB0dXJlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzW2pdWzBdKGZha2VFdmVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGVsZW1lbnQgPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgZnVuY3Rpb24gZGVsZWdhdGVVc2VDYXB0dXJlIChldmVudCkge1xuICAgICAgICByZXR1cm4gZGVsZWdhdGVMaXN0ZW5lci5jYWxsKHRoaXMsIGV2ZW50LCB0cnVlKTtcbiAgICB9XG5cbiAgICBzY29wZS5pbnRlcmFjdGFibGVzLmluZGV4T2ZFbGVtZW50ID0gZnVuY3Rpb24gaW5kZXhPZkVsZW1lbnQgKGVsZW1lbnQsIGNvbnRleHQpIHtcbiAgICAgICAgY29udGV4dCA9IGNvbnRleHQgfHwgc2NvcGUuZG9jdW1lbnQ7XG5cbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gdGhpc1tpXTtcblxuICAgICAgICAgICAgaWYgKChpbnRlcmFjdGFibGUuc2VsZWN0b3IgPT09IGVsZW1lbnRcbiAgICAgICAgICAgICAgICAmJiAoaW50ZXJhY3RhYmxlLl9jb250ZXh0ID09PSBjb250ZXh0KSlcbiAgICAgICAgICAgICAgICB8fCAoIWludGVyYWN0YWJsZS5zZWxlY3RvciAmJiBpbnRlcmFjdGFibGUuX2VsZW1lbnQgPT09IGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gaTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gLTE7XG4gICAgfTtcblxuICAgIHNjb3BlLmludGVyYWN0YWJsZXMuZ2V0ID0gZnVuY3Rpb24gaW50ZXJhY3RhYmxlR2V0IChlbGVtZW50LCBvcHRpb25zKSB7XG4gICAgICAgIHJldHVybiB0aGlzW3RoaXMuaW5kZXhPZkVsZW1lbnQoZWxlbWVudCwgb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQpXTtcbiAgICB9O1xuXG4gICAgc2NvcGUuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IgPSBmdW5jdGlvbiAoY2FsbGJhY2spIHtcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICB2YXIgaW50ZXJhY3RhYmxlID0gdGhpc1tpXTtcblxuICAgICAgICAgICAgaWYgKCFpbnRlcmFjdGFibGUuc2VsZWN0b3IpIHtcbiAgICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIHJldCA9IGNhbGxiYWNrKGludGVyYWN0YWJsZSwgaW50ZXJhY3RhYmxlLnNlbGVjdG9yLCBpbnRlcmFjdGFibGUuX2NvbnRleHQsIGksIHRoaXMpO1xuXG4gICAgICAgICAgICBpZiAocmV0ICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gcmV0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBUaGUgbWV0aG9kcyBvZiB0aGlzIHZhcmlhYmxlIGNhbiBiZSB1c2VkIHRvIHNldCBlbGVtZW50cyBhc1xuICAgICAqIGludGVyYWN0YWJsZXMgYW5kIGFsc28gdG8gY2hhbmdlIHZhcmlvdXMgZGVmYXVsdCBzZXR0aW5ncy5cbiAgICAgKlxuICAgICAqIENhbGxpbmcgaXQgYXMgYSBmdW5jdGlvbiBhbmQgcGFzc2luZyBhbiBlbGVtZW50IG9yIGEgdmFsaWQgQ1NTIHNlbGVjdG9yXG4gICAgICogc3RyaW5nIHJldHVybnMgYW4gSW50ZXJhY3RhYmxlIG9iamVjdCB3aGljaCBoYXMgdmFyaW91cyBtZXRob2RzIHRvXG4gICAgICogY29uZmlndXJlIGl0LlxuICAgICAqXG4gICAgIC0gZWxlbWVudCAoRWxlbWVudCB8IHN0cmluZykgVGhlIEhUTUwgb3IgU1ZHIEVsZW1lbnQgdG8gaW50ZXJhY3Qgd2l0aCBvciBDU1Mgc2VsZWN0b3JcbiAgICAgPSAob2JqZWN0KSBBbiBASW50ZXJhY3RhYmxlXG4gICAgICpcbiAgICAgPiBVc2FnZVxuICAgICB8IGludGVyYWN0KGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkcmFnZ2FibGUnKSkuZHJhZ2dhYmxlKHRydWUpO1xuICAgICB8XG4gICAgIHwgdmFyIHJlY3RhYmxlcyA9IGludGVyYWN0KCdyZWN0Jyk7XG4gICAgIHwgcmVjdGFibGVzXG4gICAgIHwgICAgIC5nZXN0dXJhYmxlKHRydWUpXG4gICAgIHwgICAgIC5vbignZ2VzdHVyZW1vdmUnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgfCAgICAgICAgIC8vIHNvbWV0aGluZyBjb29sLi4uXG4gICAgIHwgICAgIH0pXG4gICAgIHwgICAgIC5hdXRvU2Nyb2xsKHRydWUpO1xuICAgIFxcKi9cbiAgICBmdW5jdGlvbiBpbnRlcmFjdCAoZWxlbWVudCwgb3B0aW9ucykge1xuICAgICAgICByZXR1cm4gc2NvcGUuaW50ZXJhY3RhYmxlcy5nZXQoZWxlbWVudCwgb3B0aW9ucykgfHwgbmV3IEludGVyYWN0YWJsZShlbGVtZW50LCBvcHRpb25zKTtcbiAgICB9XG5cbiAgICAvKlxcXG4gICAgICogSW50ZXJhY3RhYmxlXG4gICAgIFsgcHJvcGVydHkgXVxuICAgICAqKlxuICAgICAqIE9iamVjdCB0eXBlIHJldHVybmVkIGJ5IEBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBmdW5jdGlvbiBJbnRlcmFjdGFibGUgKGVsZW1lbnQsIG9wdGlvbnMpIHtcbiAgICAgICAgdGhpcy5fZWxlbWVudCA9IGVsZW1lbnQ7XG4gICAgICAgIHRoaXMuX2lFdmVudHMgPSB0aGlzLl9pRXZlbnRzIHx8IHt9O1xuXG4gICAgICAgIHZhciBfd2luZG93O1xuXG4gICAgICAgIGlmIChzY29wZS50cnlTZWxlY3RvcihlbGVtZW50KSkge1xuICAgICAgICAgICAgdGhpcy5zZWxlY3RvciA9IGVsZW1lbnQ7XG5cbiAgICAgICAgICAgIHZhciBjb250ZXh0ID0gb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQ7XG5cbiAgICAgICAgICAgIF93aW5kb3cgPSBjb250ZXh0PyBzY29wZS5nZXRXaW5kb3coY29udGV4dCkgOiBzY29wZS53aW5kb3c7XG5cbiAgICAgICAgICAgIGlmIChjb250ZXh0ICYmIChfd2luZG93Lk5vZGVcbiAgICAgICAgICAgICAgICAgICAgPyBjb250ZXh0IGluc3RhbmNlb2YgX3dpbmRvdy5Ob2RlXG4gICAgICAgICAgICAgICAgICAgIDogKHV0aWxzLmlzRWxlbWVudChjb250ZXh0KSB8fCBjb250ZXh0ID09PSBfd2luZG93LmRvY3VtZW50KSkpIHtcblxuICAgICAgICAgICAgICAgIHRoaXMuX2NvbnRleHQgPSBjb250ZXh0O1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgX3dpbmRvdyA9IHNjb3BlLmdldFdpbmRvdyhlbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50LCBfd2luZG93KSkge1xuXG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLlBvaW50ZXJFdmVudCkge1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIHNjb3BlLnBFdmVudFR5cGVzLmRvd24sIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyRG93biApO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsIHNjb3BlLnBFdmVudFR5cGVzLm1vdmUsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAnbW91c2Vkb3duJyAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyRG93biApO1xuICAgICAgICAgICAgICAgICAgICBldmVudHMuYWRkKHRoaXMuX2VsZW1lbnQsICdtb3VzZW1vdmUnICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJIb3Zlcik7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQodGhpcy5fZWxlbWVudCwgJ3RvdWNoc3RhcnQnLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlckRvd24gKTtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLl9lbGVtZW50LCAndG91Y2htb3ZlJyAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuX2RvYyA9IF93aW5kb3cuZG9jdW1lbnQ7XG5cbiAgICAgICAgaWYgKCFzY29wZS5jb250YWlucyhzY29wZS5kb2N1bWVudHMsIHRoaXMuX2RvYykpIHtcbiAgICAgICAgICAgIGxpc3RlblRvRG9jdW1lbnQodGhpcy5fZG9jKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHNjb3BlLmludGVyYWN0YWJsZXMucHVzaCh0aGlzKTtcblxuICAgICAgICB0aGlzLnNldChvcHRpb25zKTtcbiAgICB9XG5cbiAgICBJbnRlcmFjdGFibGUucHJvdG90eXBlID0ge1xuICAgICAgICBzZXRPbkV2ZW50czogZnVuY3Rpb24gKGFjdGlvbiwgcGhhc2VzKSB7XG4gICAgICAgICAgICBpZiAoYWN0aW9uID09PSAnZHJvcCcpIHtcbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wKSAgICAgICAgICApIHsgdGhpcy5vbmRyb3AgICAgICAgICAgID0gcGhhc2VzLm9uZHJvcCAgICAgICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wYWN0aXZhdGUpICApIHsgdGhpcy5vbmRyb3BhY3RpdmF0ZSAgID0gcGhhc2VzLm9uZHJvcGFjdGl2YXRlICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wZGVhY3RpdmF0ZSkpIHsgdGhpcy5vbmRyb3BkZWFjdGl2YXRlID0gcGhhc2VzLm9uZHJvcGRlYWN0aXZhdGU7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcmFnZW50ZXIpICAgICApIHsgdGhpcy5vbmRyYWdlbnRlciAgICAgID0gcGhhc2VzLm9uZHJhZ2VudGVyICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcmFnbGVhdmUpICAgICApIHsgdGhpcy5vbmRyYWdsZWF2ZSAgICAgID0gcGhhc2VzLm9uZHJhZ2xlYXZlICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25kcm9wbW92ZSkgICAgICApIHsgdGhpcy5vbmRyb3Btb3ZlICAgICAgID0gcGhhc2VzLm9uZHJvcG1vdmUgICAgICA7IH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGFjdGlvbiA9ICdvbicgKyBhY3Rpb247XG5cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25zdGFydCkgICAgICAgKSB7IHRoaXNbYWN0aW9uICsgJ3N0YXJ0JyAgICAgICAgIF0gPSBwaGFzZXMub25zdGFydCAgICAgICAgIDsgfVxuICAgICAgICAgICAgICAgIGlmIChzY29wZS5pc0Z1bmN0aW9uKHBoYXNlcy5vbm1vdmUpICAgICAgICApIHsgdGhpc1thY3Rpb24gKyAnbW92ZScgICAgICAgICAgXSA9IHBoYXNlcy5vbm1vdmUgICAgICAgICAgOyB9XG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24ocGhhc2VzLm9uZW5kKSAgICAgICAgICkgeyB0aGlzW2FjdGlvbiArICdlbmQnICAgICAgICAgICBdID0gcGhhc2VzLm9uZW5kICAgICAgICAgICA7IH1cbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihwaGFzZXMub25pbmVydGlhc3RhcnQpKSB7IHRoaXNbYWN0aW9uICsgJ2luZXJ0aWFzdGFydCcgIF0gPSBwaGFzZXMub25pbmVydGlhc3RhcnQgIDsgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5kcmFnZ2FibGVcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgZHJhZyBhY3Rpb25zIGNhbiBiZSBwZXJmb3JtZWQgb24gdGhlXG4gICAgICAgICAqIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgPSAoYm9vbGVhbikgSW5kaWNhdGVzIGlmIHRoaXMgY2FuIGJlIHRoZSB0YXJnZXQgb2YgZHJhZyBldmVudHNcbiAgICAgICAgIHwgdmFyIGlzRHJhZ2dhYmxlID0gaW50ZXJhY3QoJ3VsIGxpJykuZHJhZ2dhYmxlKCk7XG4gICAgICAgICAqIG9yXG4gICAgICAgICAtIG9wdGlvbnMgKGJvb2xlYW4gfCBvYmplY3QpICNvcHRpb25hbCB0cnVlL2ZhbHNlIG9yIEFuIG9iamVjdCB3aXRoIGV2ZW50IGxpc3RlbmVycyB0byBiZSBmaXJlZCBvbiBkcmFnIGV2ZW50cyAob2JqZWN0IG1ha2VzIHRoZSBJbnRlcmFjdGFibGUgZHJhZ2dhYmxlKVxuICAgICAgICAgPSAob2JqZWN0KSBUaGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5kcmFnZ2FibGUoe1xuICAgICAgICAgfCAgICAgb25zdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgICAgIHwgICAgIG9ubW92ZSA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgICAgICB8ICAgICBvbmVuZCAgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gdGhlIGF4aXMgaW4gd2hpY2ggdGhlIGZpcnN0IG1vdmVtZW50IG11c3QgYmVcbiAgICAgICAgIHwgICAgIC8vIGZvciB0aGUgZHJhZyBzZXF1ZW5jZSB0byBzdGFydFxuICAgICAgICAgfCAgICAgLy8gJ3h5JyBieSBkZWZhdWx0IC0gYW55IGRpcmVjdGlvblxuICAgICAgICAgfCAgICAgYXhpczogJ3gnIHx8ICd5JyB8fCAneHknLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbWF4IG51bWJlciBvZiBkcmFncyB0aGF0IGNhbiBoYXBwZW4gY29uY3VycmVudGx5XG4gICAgICAgICB8ICAgICAvLyB3aXRoIGVsZW1lbnRzIG9mIHRoaXMgSW50ZXJhY3RhYmxlLiBJbmZpbml0eSBieSBkZWZhdWx0XG4gICAgICAgICB8ICAgICBtYXg6IEluZmluaXR5LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbWF4IG51bWJlciBvZiBkcmFncyB0aGF0IGNhbiB0YXJnZXQgdGhlIHNhbWUgZWxlbWVudCtJbnRlcmFjdGFibGVcbiAgICAgICAgIHwgICAgIC8vIDEgYnkgZGVmYXVsdFxuICAgICAgICAgfCAgICAgbWF4UGVyRWxlbWVudDogMlxuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICBkcmFnZ2FibGU6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJhZy5lbmFibGVkID0gb3B0aW9ucy5lbmFibGVkID09PSBmYWxzZT8gZmFsc2U6IHRydWU7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRQZXJBY3Rpb24oJ2RyYWcnLCBvcHRpb25zKTtcbiAgICAgICAgICAgICAgICB0aGlzLnNldE9uRXZlbnRzKCdkcmFnJywgb3B0aW9ucyk7XG5cbiAgICAgICAgICAgICAgICBpZiAoL154JHxeeSR8Xnh5JC8udGVzdChvcHRpb25zLmF4aXMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcmFnLmF4aXMgPSBvcHRpb25zLmF4aXM7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9wdGlvbnMuYXhpcyA9PT0gbnVsbCkge1xuICAgICAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmRyYWcuYXhpcztcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcmFnLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJhZztcbiAgICAgICAgfSxcblxuICAgICAgICBzZXRQZXJBY3Rpb246IGZ1bmN0aW9uIChhY3Rpb24sIG9wdGlvbnMpIHtcbiAgICAgICAgICAgIC8vIGZvciBhbGwgdGhlIGRlZmF1bHQgcGVyLWFjdGlvbiBvcHRpb25zXG4gICAgICAgICAgICBmb3IgKHZhciBvcHRpb24gaW4gb3B0aW9ucykge1xuICAgICAgICAgICAgICAgIC8vIGlmIHRoaXMgb3B0aW9uIGV4aXN0cyBmb3IgdGhpcyBhY3Rpb25cbiAgICAgICAgICAgICAgICBpZiAob3B0aW9uIGluIHNjb3BlLmRlZmF1bHRPcHRpb25zW2FjdGlvbl0pIHtcbiAgICAgICAgICAgICAgICAgICAgLy8gaWYgdGhlIG9wdGlvbiBpbiB0aGUgb3B0aW9ucyBhcmcgaXMgYW4gb2JqZWN0IHZhbHVlXG4gICAgICAgICAgICAgICAgICAgIGlmIChzY29wZS5pc09iamVjdChvcHRpb25zW29wdGlvbl0pKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBkdXBsaWNhdGUgdGhlIG9iamVjdFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXSA9IHV0aWxzLmV4dGVuZCh0aGlzLm9wdGlvbnNbYWN0aW9uXVtvcHRpb25dIHx8IHt9LCBvcHRpb25zW29wdGlvbl0pO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3Qoc2NvcGUuZGVmYXVsdE9wdGlvbnMucGVyQWN0aW9uW29wdGlvbl0pICYmICdlbmFibGVkJyBpbiBzY29wZS5kZWZhdWx0T3B0aW9ucy5wZXJBY3Rpb25bb3B0aW9uXSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9uc1thY3Rpb25dW29wdGlvbl0uZW5hYmxlZCA9IG9wdGlvbnNbb3B0aW9uXS5lbmFibGVkID09PSBmYWxzZT8gZmFsc2UgOiB0cnVlO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zW29wdGlvbl0pICYmIHNjb3BlLmlzT2JqZWN0KHNjb3BlLmRlZmF1bHRPcHRpb25zLnBlckFjdGlvbltvcHRpb25dKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXS5lbmFibGVkID0gb3B0aW9uc1tvcHRpb25dO1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9wdGlvbnNbb3B0aW9uXSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAvLyBvciBpZiBpdCdzIG5vdCB1bmRlZmluZWQsIGRvIGEgcGxhaW4gYXNzaWdubWVudFxuICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW2FjdGlvbl1bb3B0aW9uXSA9IG9wdGlvbnNbb3B0aW9uXTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5kcm9wem9uZVxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciBlbGVtZW50cyBjYW4gYmUgZHJvcHBlZCBvbnRvIHRoaXNcbiAgICAgICAgICogSW50ZXJhY3RhYmxlIHRvIHRyaWdnZXIgZHJvcCBldmVudHNcbiAgICAgICAgICpcbiAgICAgICAgICogRHJvcHpvbmVzIGNhbiByZWNlaXZlIHRoZSBmb2xsb3dpbmcgZXZlbnRzOlxuICAgICAgICAgKiAgLSBgZHJvcGFjdGl2YXRlYCBhbmQgYGRyb3BkZWFjdGl2YXRlYCB3aGVuIGFuIGFjY2VwdGFibGUgZHJhZyBzdGFydHMgYW5kIGVuZHNcbiAgICAgICAgICogIC0gYGRyYWdlbnRlcmAgYW5kIGBkcmFnbGVhdmVgIHdoZW4gYSBkcmFnZ2FibGUgZW50ZXJzIGFuZCBsZWF2ZXMgdGhlIGRyb3B6b25lXG4gICAgICAgICAqICAtIGBkcmFnbW92ZWAgd2hlbiBhIGRyYWdnYWJsZSB0aGF0IGhhcyBlbnRlcmVkIHRoZSBkcm9wem9uZSBpcyBtb3ZlZFxuICAgICAgICAgKiAgLSBgZHJvcGAgd2hlbiBhIGRyYWdnYWJsZSBpcyBkcm9wcGVkIGludG8gdGhpcyBkcm9wem9uZVxuICAgICAgICAgKlxuICAgICAgICAgKiAgVXNlIHRoZSBgYWNjZXB0YCBvcHRpb24gdG8gYWxsb3cgb25seSBlbGVtZW50cyB0aGF0IG1hdGNoIHRoZSBnaXZlbiBDU1Mgc2VsZWN0b3Igb3IgZWxlbWVudC5cbiAgICAgICAgICpcbiAgICAgICAgICogIFVzZSB0aGUgYG92ZXJsYXBgIG9wdGlvbiB0byBzZXQgaG93IGRyb3BzIGFyZSBjaGVja2VkIGZvci4gVGhlIGFsbG93ZWQgdmFsdWVzIGFyZTpcbiAgICAgICAgICogICAtIGAncG9pbnRlcidgLCB0aGUgcG9pbnRlciBtdXN0IGJlIG92ZXIgdGhlIGRyb3B6b25lIChkZWZhdWx0KVxuICAgICAgICAgKiAgIC0gYCdjZW50ZXInYCwgdGhlIGRyYWdnYWJsZSBlbGVtZW50J3MgY2VudGVyIG11c3QgYmUgb3ZlciB0aGUgZHJvcHpvbmVcbiAgICAgICAgICogICAtIGEgbnVtYmVyIGZyb20gMC0xIHdoaWNoIGlzIHRoZSBgKGludGVyc2VjdGlvbiBhcmVhKSAvIChkcmFnZ2FibGUgYXJlYSlgLlxuICAgICAgICAgKiAgICAgICBlLmcuIGAwLjVgIGZvciBkcm9wIHRvIGhhcHBlbiB3aGVuIGhhbGYgb2YgdGhlIGFyZWEgb2YgdGhlXG4gICAgICAgICAqICAgICAgIGRyYWdnYWJsZSBpcyBvdmVyIHRoZSBkcm9wem9uZVxuICAgICAgICAgKlxuICAgICAgICAgLSBvcHRpb25zIChib29sZWFuIHwgb2JqZWN0IHwgbnVsbCkgI29wdGlvbmFsIFRoZSBuZXcgdmFsdWUgdG8gYmUgc2V0LlxuICAgICAgICAgfCBpbnRlcmFjdCgnLmRyb3AnKS5kcm9wem9uZSh7XG4gICAgICAgICB8ICAgYWNjZXB0OiAnLmNhbi1kcm9wJyB8fCBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc2luZ2xlLWRyb3AnKSxcbiAgICAgICAgIHwgICBvdmVybGFwOiAncG9pbnRlcicgfHwgJ2NlbnRlcicgfHwgemVyb1RvT25lXG4gICAgICAgICB8IH1cbiAgICAgICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBkcm9wem9uZTogZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS5pc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcbiAgICAgICAgICAgICAgICB0aGlzLnNldE9uRXZlbnRzKCdkcm9wJywgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgdGhpcy5hY2NlcHQob3B0aW9ucy5hY2NlcHQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKC9eKHBvaW50ZXJ8Y2VudGVyKSQvLnRlc3Qob3B0aW9ucy5vdmVybGFwKSkge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJvcC5vdmVybGFwID0gb3B0aW9ucy5vdmVybGFwO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChzY29wZS5pc051bWJlcihvcHRpb25zLm92ZXJsYXApKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLm92ZXJsYXAgPSBNYXRoLm1heChNYXRoLm1pbigxLCBvcHRpb25zLm92ZXJsYXApLCAwKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wLmVuYWJsZWQgPSBvcHRpb25zO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcDtcbiAgICAgICAgfSxcblxuICAgICAgICBkcm9wQ2hlY2s6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZHJhZ2dhYmxlLCBkcmFnZ2FibGVFbGVtZW50LCBkcm9wRWxlbWVudCwgcmVjdCkge1xuICAgICAgICAgICAgdmFyIGRyb3BwZWQgPSBmYWxzZTtcblxuICAgICAgICAgICAgLy8gaWYgdGhlIGRyb3B6b25lIGhhcyBubyByZWN0IChlZy4gZGlzcGxheTogbm9uZSlcbiAgICAgICAgICAgIC8vIGNhbGwgdGhlIGN1c3RvbSBkcm9wQ2hlY2tlciBvciBqdXN0IHJldHVybiBmYWxzZVxuICAgICAgICAgICAgaWYgKCEocmVjdCA9IHJlY3QgfHwgdGhpcy5nZXRSZWN0KGRyb3BFbGVtZW50KSkpIHtcbiAgICAgICAgICAgICAgICByZXR1cm4gKHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlclxuICAgICAgICAgICAgICAgICAgICA/IHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlcihwb2ludGVyLCBldmVudCwgZHJvcHBlZCwgdGhpcywgZHJvcEVsZW1lbnQsIGRyYWdnYWJsZSwgZHJhZ2dhYmxlRWxlbWVudClcbiAgICAgICAgICAgICAgICAgICAgOiBmYWxzZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBkcm9wT3ZlcmxhcCA9IHRoaXMub3B0aW9ucy5kcm9wLm92ZXJsYXA7XG5cbiAgICAgICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ3BvaW50ZXInKSB7XG4gICAgICAgICAgICAgICAgdmFyIHBhZ2UgPSB1dGlscy5nZXRQYWdlWFkocG9pbnRlciksXG4gICAgICAgICAgICAgICAgICAgIG9yaWdpbiA9IHNjb3BlLmdldE9yaWdpblhZKGRyYWdnYWJsZSwgZHJhZ2dhYmxlRWxlbWVudCksXG4gICAgICAgICAgICAgICAgICAgIGhvcml6b250YWwsXG4gICAgICAgICAgICAgICAgICAgIHZlcnRpY2FsO1xuXG4gICAgICAgICAgICAgICAgcGFnZS54ICs9IG9yaWdpbi54O1xuICAgICAgICAgICAgICAgIHBhZ2UueSArPSBvcmlnaW4ueTtcblxuICAgICAgICAgICAgICAgIGhvcml6b250YWwgPSAocGFnZS54ID4gcmVjdC5sZWZ0KSAmJiAocGFnZS54IDwgcmVjdC5yaWdodCk7XG4gICAgICAgICAgICAgICAgdmVydGljYWwgICA9IChwYWdlLnkgPiByZWN0LnRvcCApICYmIChwYWdlLnkgPCByZWN0LmJvdHRvbSk7XG5cbiAgICAgICAgICAgICAgICBkcm9wcGVkID0gaG9yaXpvbnRhbCAmJiB2ZXJ0aWNhbDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIGRyYWdSZWN0ID0gZHJhZ2dhYmxlLmdldFJlY3QoZHJhZ2dhYmxlRWxlbWVudCk7XG5cbiAgICAgICAgICAgIGlmIChkcm9wT3ZlcmxhcCA9PT0gJ2NlbnRlcicpIHtcbiAgICAgICAgICAgICAgICB2YXIgY3ggPSBkcmFnUmVjdC5sZWZ0ICsgZHJhZ1JlY3Qud2lkdGggIC8gMixcbiAgICAgICAgICAgICAgICAgICAgY3kgPSBkcmFnUmVjdC50b3AgICsgZHJhZ1JlY3QuaGVpZ2h0IC8gMjtcblxuICAgICAgICAgICAgICAgIGRyb3BwZWQgPSBjeCA+PSByZWN0LmxlZnQgJiYgY3ggPD0gcmVjdC5yaWdodCAmJiBjeSA+PSByZWN0LnRvcCAmJiBjeSA8PSByZWN0LmJvdHRvbTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzTnVtYmVyKGRyb3BPdmVybGFwKSkge1xuICAgICAgICAgICAgICAgIHZhciBvdmVybGFwQXJlYSAgPSAoTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5yaWdodCAsIGRyYWdSZWN0LnJpZ2h0ICkgLSBNYXRoLm1heChyZWN0LmxlZnQsIGRyYWdSZWN0LmxlZnQpKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICogTWF0aC5tYXgoMCwgTWF0aC5taW4ocmVjdC5ib3R0b20sIGRyYWdSZWN0LmJvdHRvbSkgLSBNYXRoLm1heChyZWN0LnRvcCAsIGRyYWdSZWN0LnRvcCApKSksXG4gICAgICAgICAgICAgICAgICAgIG92ZXJsYXBSYXRpbyA9IG92ZXJsYXBBcmVhIC8gKGRyYWdSZWN0LndpZHRoICogZHJhZ1JlY3QuaGVpZ2h0KTtcblxuICAgICAgICAgICAgICAgIGRyb3BwZWQgPSBvdmVybGFwUmF0aW8gPj0gZHJvcE92ZXJsYXA7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh0aGlzLm9wdGlvbnMuZHJvcENoZWNrZXIpIHtcbiAgICAgICAgICAgICAgICBkcm9wcGVkID0gdGhpcy5vcHRpb25zLmRyb3BDaGVja2VyKHBvaW50ZXIsIGRyb3BwZWQsIHRoaXMsIGRyb3BFbGVtZW50LCBkcmFnZ2FibGUsIGRyYWdnYWJsZUVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gZHJvcHBlZDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5kcm9wQ2hlY2tlclxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBHZXRzIG9yIHNldHMgdGhlIGZ1bmN0aW9uIHVzZWQgdG8gY2hlY2sgaWYgYSBkcmFnZ2VkIGVsZW1lbnQgaXNcbiAgICAgICAgICogb3ZlciB0aGlzIEludGVyYWN0YWJsZS5cbiAgICAgICAgICpcbiAgICAgICAgIC0gY2hlY2tlciAoZnVuY3Rpb24pICNvcHRpb25hbCBUaGUgZnVuY3Rpb24gdGhhdCB3aWxsIGJlIGNhbGxlZCB3aGVuIGNoZWNraW5nIGZvciBhIGRyb3BcbiAgICAgICAgID0gKEZ1bmN0aW9uIHwgSW50ZXJhY3RhYmxlKSBUaGUgY2hlY2tlciBmdW5jdGlvbiBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgKiBUaGUgY2hlY2tlciBmdW5jdGlvbiB0YWtlcyB0aGUgZm9sbG93aW5nIGFyZ3VtZW50czpcbiAgICAgICAgICpcbiAgICAgICAgIC0gcG9pbnRlciAoVG91Y2ggfCBQb2ludGVyRXZlbnQgfCBNb3VzZUV2ZW50KSBUaGUgcG9pbnRlci9ldmVudCB0aGF0IGVuZHMgYSBkcmFnXG4gICAgICAgICAtIGV2ZW50IChUb3VjaEV2ZW50IHwgUG9pbnRlckV2ZW50IHwgTW91c2VFdmVudCkgVGhlIGV2ZW50IHJlbGF0ZWQgdG8gdGhlIHBvaW50ZXJcbiAgICAgICAgIC0gZHJvcHBlZCAoYm9vbGVhbikgVGhlIHZhbHVlIGZyb20gdGhlIGRlZmF1bHQgZHJvcCBjaGVja1xuICAgICAgICAgLSBkcm9wem9uZSAoSW50ZXJhY3RhYmxlKSBUaGUgZHJvcHpvbmUgaW50ZXJhY3RhYmxlXG4gICAgICAgICAtIGRyb3BFbGVtZW50IChFbGVtZW50KSBUaGUgZHJvcHpvbmUgZWxlbWVudFxuICAgICAgICAgLSBkcmFnZ2FibGUgKEludGVyYWN0YWJsZSkgVGhlIEludGVyYWN0YWJsZSBiZWluZyBkcmFnZ2VkXG4gICAgICAgICAtIGRyYWdnYWJsZUVsZW1lbnQgKEVsZW1lbnQpIFRoZSBhY3R1YWwgZWxlbWVudCB0aGF0J3MgYmVpbmcgZHJhZ2dlZFxuICAgICAgICAgKlxuICAgICAgICAgPiBVc2FnZTpcbiAgICAgICAgIHwgaW50ZXJhY3QodGFyZ2V0KVxuICAgICAgICAgfCAuZHJvcENoZWNrZXIoZnVuY3Rpb24ocG9pbnRlciwgICAgICAgICAgIC8vIFRvdWNoL1BvaW50ZXJFdmVudC9Nb3VzZUV2ZW50XG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBldmVudCwgICAgICAgICAgICAgLy8gVG91Y2hFdmVudC9Qb2ludGVyRXZlbnQvTW91c2VFdmVudFxuICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJvcHBlZCwgICAgICAgICAgIC8vIHJlc3VsdCBvZiB0aGUgZGVmYXVsdCBjaGVja2VyXG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcm9wem9uZSwgICAgICAgICAgLy8gZHJvcHpvbmUgSW50ZXJhY3RhYmxlXG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcm9wRWxlbWVudCwgICAgICAgLy8gZHJvcHpvbmUgZWxlbW50XG4gICAgICAgICB8ICAgICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUsICAgICAgICAgLy8gZHJhZ2dhYmxlIEludGVyYWN0YWJsZVxuICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgZHJhZ2dhYmxlRWxlbWVudCkgey8vIGRyYWdnYWJsZSBlbGVtZW50XG4gICAgICAgICB8XG4gICAgICAgICB8ICAgcmV0dXJuIGRyb3BwZWQgJiYgZXZlbnQudGFyZ2V0Lmhhc0F0dHJpYnV0ZSgnYWxsb3ctZHJvcCcpO1xuICAgICAgICAgfCB9XG4gICAgICAgIFxcKi9cbiAgICAgICAgZHJvcENoZWNrZXI6IGZ1bmN0aW9uIChjaGVja2VyKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihjaGVja2VyKSkge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kcm9wQ2hlY2tlciA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChjaGVja2VyID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5nZXRSZWN0O1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZHJvcENoZWNrZXI7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYWNjZXB0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIERlcHJlY2F0ZWQuIGFkZCBhbiBgYWNjZXB0YCBwcm9wZXJ0eSB0byB0aGUgb3B0aW9ucyBvYmplY3QgcGFzc2VkIHRvXG4gICAgICAgICAqIEBJbnRlcmFjdGFibGUuZHJvcHpvbmUgaW5zdGVhZC5cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHRoZSBFbGVtZW50IG9yIENTUyBzZWxlY3RvciBtYXRjaCB0aGF0IHRoaXNcbiAgICAgICAgICogSW50ZXJhY3RhYmxlIGFjY2VwdHMgaWYgaXQgaXMgYSBkcm9wem9uZS5cbiAgICAgICAgICpcbiAgICAgICAgIC0gbmV3VmFsdWUgKEVsZW1lbnQgfCBzdHJpbmcgfCBudWxsKSAjb3B0aW9uYWxcbiAgICAgICAgICogSWYgaXQgaXMgYW4gRWxlbWVudCwgdGhlbiBvbmx5IHRoYXQgZWxlbWVudCBjYW4gYmUgZHJvcHBlZCBpbnRvIHRoaXMgZHJvcHpvbmUuXG4gICAgICAgICAqIElmIGl0IGlzIGEgc3RyaW5nLCB0aGUgZWxlbWVudCBiZWluZyBkcmFnZ2VkIG11c3QgbWF0Y2ggaXQgYXMgYSBzZWxlY3Rvci5cbiAgICAgICAgICogSWYgaXQgaXMgbnVsbCwgdGhlIGFjY2VwdCBvcHRpb25zIGlzIGNsZWFyZWQgLSBpdCBhY2NlcHRzIGFueSBlbGVtZW50LlxuICAgICAgICAgKlxuICAgICAgICAgPSAoc3RyaW5nIHwgRWxlbWVudCB8IG51bGwgfCBJbnRlcmFjdGFibGUpIFRoZSBjdXJyZW50IGFjY2VwdCBvcHRpb24gaWYgZ2l2ZW4gYHVuZGVmaW5lZGAgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBhY2NlcHQ6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZHJvcC5hY2NlcHQgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyB0ZXN0IGlmIGl0IGlzIGEgdmFsaWQgQ1NTIHNlbGVjdG9yXG4gICAgICAgICAgICBpZiAoc2NvcGUudHJ5U2VsZWN0b3IobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0ID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5kcm9wLmFjY2VwdDtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmRyb3AuYWNjZXB0O1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLnJlc2l6YWJsZVxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBHZXRzIG9yIHNldHMgd2hldGhlciByZXNpemUgYWN0aW9ucyBjYW4gYmUgcGVyZm9ybWVkIG9uIHRoZVxuICAgICAgICAgKiBJbnRlcmFjdGFibGVcbiAgICAgICAgICpcbiAgICAgICAgID0gKGJvb2xlYW4pIEluZGljYXRlcyBpZiB0aGlzIGNhbiBiZSB0aGUgdGFyZ2V0IG9mIHJlc2l6ZSBlbGVtZW50c1xuICAgICAgICAgfCB2YXIgaXNSZXNpemVhYmxlID0gaW50ZXJhY3QoJ2lucHV0W3R5cGU9dGV4dF0nKS5yZXNpemFibGUoKTtcbiAgICAgICAgICogb3JcbiAgICAgICAgIC0gb3B0aW9ucyAoYm9vbGVhbiB8IG9iamVjdCkgI29wdGlvbmFsIHRydWUvZmFsc2Ugb3IgQW4gb2JqZWN0IHdpdGggZXZlbnQgbGlzdGVuZXJzIHRvIGJlIGZpcmVkIG9uIHJlc2l6ZSBldmVudHMgKG9iamVjdCBtYWtlcyB0aGUgSW50ZXJhY3RhYmxlIHJlc2l6YWJsZSlcbiAgICAgICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCkucmVzaXphYmxlKHtcbiAgICAgICAgIHwgICAgIG9uc3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgICAgICB8ICAgICBvbm1vdmUgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICAgICAgfCAgICAgb25lbmQgIDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIGVkZ2VzOiB7XG4gICAgICAgICB8ICAgICAgIHRvcCAgIDogdHJ1ZSwgICAgICAgLy8gVXNlIHBvaW50ZXIgY29vcmRzIHRvIGNoZWNrIGZvciByZXNpemUuXG4gICAgICAgICB8ICAgICAgIGxlZnQgIDogZmFsc2UsICAgICAgLy8gRGlzYWJsZSByZXNpemluZyBmcm9tIGxlZnQgZWRnZS5cbiAgICAgICAgIHwgICAgICAgYm90dG9tOiAnLnJlc2l6ZS1zJywvLyBSZXNpemUgaWYgcG9pbnRlciB0YXJnZXQgbWF0Y2hlcyBzZWxlY3RvclxuICAgICAgICAgfCAgICAgICByaWdodCA6IGhhbmRsZUVsICAgIC8vIFJlc2l6ZSBpZiBwb2ludGVyIHRhcmdldCBpcyB0aGUgZ2l2ZW4gRWxlbWVudFxuICAgICAgICAgfCAgICAgfSxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIC8vIGEgdmFsdWUgb2YgJ25vbmUnIHdpbGwgbGltaXQgdGhlIHJlc2l6ZSByZWN0IHRvIGEgbWluaW11bSBvZiAweDBcbiAgICAgICAgIHwgICAgIC8vICduZWdhdGUnIHdpbGwgYWxsb3cgdGhlIHJlY3QgdG8gaGF2ZSBuZWdhdGl2ZSB3aWR0aC9oZWlnaHRcbiAgICAgICAgIHwgICAgIC8vICdyZXBvc2l0aW9uJyB3aWxsIGtlZXAgdGhlIHdpZHRoL2hlaWdodCBwb3NpdGl2ZSBieSBzd2FwcGluZ1xuICAgICAgICAgfCAgICAgLy8gdGhlIHRvcCBhbmQgYm90dG9tIGVkZ2VzIGFuZC9vciBzd2FwcGluZyB0aGUgbGVmdCBhbmQgcmlnaHQgZWRnZXNcbiAgICAgICAgIHwgICAgIGludmVydDogJ25vbmUnIHx8ICduZWdhdGUnIHx8ICdyZXBvc2l0aW9uJ1xuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgcmVzaXplcy5cbiAgICAgICAgIHwgICAgIC8vIFNlZSB0aGUgZXhwbGFuYXRpb24gaW4gdGhlIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIGV4YW1wbGVcbiAgICAgICAgIHwgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICByZXNpemFibGU6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3Qob3B0aW9ucykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMucmVzaXplLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcbiAgICAgICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbigncmVzaXplJywgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygncmVzaXplJywgb3B0aW9ucyk7XG5cbiAgICAgICAgICAgICAgICBpZiAoL154JHxeeSR8Xnh5JC8udGVzdChvcHRpb25zLmF4aXMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuYXhpcyA9IG9wdGlvbnMuYXhpcztcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAob3B0aW9ucy5heGlzID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuYXhpcyA9IHNjb3BlLmRlZmF1bHRPcHRpb25zLnJlc2l6ZS5heGlzO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wob3B0aW9ucy5zcXVhcmUpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5yZXNpemUuc3F1YXJlID0gb3B0aW9ucy5zcXVhcmU7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnJlc2l6ZS5lbmFibGVkID0gb3B0aW9ucztcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5yZXNpemU7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuc3F1YXJlUmVzaXplXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhIGBzcXVhcmU6IHRydWUgfHwgZmFsc2VgIHByb3BlcnR5IHRvIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWRcbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgcmVzaXppbmcgaXMgZm9yY2VkIDE6MSBhc3BlY3RcbiAgICAgICAgICpcbiAgICAgICAgID0gKGJvb2xlYW4pIEN1cnJlbnQgc2V0dGluZ1xuICAgICAgICAgKlxuICAgICAgICAgKiBvclxuICAgICAgICAgKlxuICAgICAgICAgLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsXG4gICAgICAgICA9IChvYmplY3QpIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgc3F1YXJlUmVzaXplOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnJlc2l6ZS5zcXVhcmUgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAobmV3VmFsdWUgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLnJlc2l6ZS5zcXVhcmU7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5yZXNpemUuc3F1YXJlO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmdlc3R1cmFibGVcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHdoZXRoZXIgbXVsdGl0b3VjaCBnZXN0dXJlcyBjYW4gYmUgcGVyZm9ybWVkIG9uIHRoZVxuICAgICAgICAgKiBJbnRlcmFjdGFibGUncyBlbGVtZW50XG4gICAgICAgICAqXG4gICAgICAgICA9IChib29sZWFuKSBJbmRpY2F0ZXMgaWYgdGhpcyBjYW4gYmUgdGhlIHRhcmdldCBvZiBnZXN0dXJlIGV2ZW50c1xuICAgICAgICAgfCB2YXIgaXNHZXN0dXJlYWJsZSA9IGludGVyYWN0KGVsZW1lbnQpLmdlc3R1cmFibGUoKTtcbiAgICAgICAgICogb3JcbiAgICAgICAgIC0gb3B0aW9ucyAoYm9vbGVhbiB8IG9iamVjdCkgI29wdGlvbmFsIHRydWUvZmFsc2Ugb3IgQW4gb2JqZWN0IHdpdGggZXZlbnQgbGlzdGVuZXJzIHRvIGJlIGZpcmVkIG9uIGdlc3R1cmUgZXZlbnRzIChtYWtlcyB0aGUgSW50ZXJhY3RhYmxlIGdlc3R1cmFibGUpXG4gICAgICAgICA9IChvYmplY3QpIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICB8IGludGVyYWN0KGVsZW1lbnQpLmdlc3R1cmFibGUoe1xuICAgICAgICAgfCAgICAgb25zdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7fSxcbiAgICAgICAgIHwgICAgIG9ubW92ZSA6IGZ1bmN0aW9uIChldmVudCkge30sXG4gICAgICAgICB8ICAgICBvbmVuZCAgOiBmdW5jdGlvbiAoZXZlbnQpIHt9LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gbGltaXQgbXVsdGlwbGUgZ2VzdHVyZXMuXG4gICAgICAgICB8ICAgICAvLyBTZWUgdGhlIGV4cGxhbmF0aW9uIGluIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIGV4YW1wbGVcbiAgICAgICAgIHwgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgICB8ICAgICBtYXhQZXJFbGVtZW50OiAxLFxuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICBnZXN0dXJhYmxlOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmdlc3R1cmUuZW5hYmxlZCA9IG9wdGlvbnMuZW5hYmxlZCA9PT0gZmFsc2U/IGZhbHNlOiB0cnVlO1xuICAgICAgICAgICAgICAgIHRoaXMuc2V0UGVyQWN0aW9uKCdnZXN0dXJlJywgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgdGhpcy5zZXRPbkV2ZW50cygnZ2VzdHVyZScsIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wob3B0aW9ucykpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuZ2VzdHVyZS5lbmFibGVkID0gb3B0aW9ucztcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmdlc3R1cmU7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYXV0b1Njcm9sbFxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKipcbiAgICAgICAgICogRGVwcmVjYXRlZC4gQWRkIGFuIGBhdXRvc2Nyb2xsYCBwcm9wZXJ0eSB0byB0aGUgb3B0aW9ucyBvYmplY3RcbiAgICAgICAgICogcGFzc2VkIHRvIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIG9yIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWQuXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIGRyYWdnaW5nIGFuZCByZXNpemluZyBuZWFyIHRoZSBlZGdlcyBvZiB0aGVcbiAgICAgICAgICogd2luZG93L2NvbnRhaW5lciB0cmlnZ2VyIGF1dG9TY3JvbGwgZm9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICAqXG4gICAgICAgICA9IChvYmplY3QpIE9iamVjdCB3aXRoIGF1dG9TY3JvbGwgcHJvcGVydGllc1xuICAgICAgICAgKlxuICAgICAgICAgKiBvclxuICAgICAgICAgKlxuICAgICAgICAgLSBvcHRpb25zIChvYmplY3QgfCBib29sZWFuKSAjb3B0aW9uYWxcbiAgICAgICAgICogb3B0aW9ucyBjYW4gYmU6XG4gICAgICAgICAqIC0gYW4gb2JqZWN0IHdpdGggbWFyZ2luLCBkaXN0YW5jZSBhbmQgaW50ZXJ2YWwgcHJvcGVydGllcyxcbiAgICAgICAgICogLSB0cnVlIG9yIGZhbHNlIHRvIGVuYWJsZSBvciBkaXNhYmxlIGF1dG9TY3JvbGwgb3JcbiAgICAgICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBhdXRvU2Nyb2xsOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgb3B0aW9ucyA9IHV0aWxzLmV4dGVuZCh7IGFjdGlvbnM6IFsnZHJhZycsICdyZXNpemUnXX0sIG9wdGlvbnMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoc2NvcGUuaXNCb29sKG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgb3B0aW9ucyA9IHsgYWN0aW9uczogWydkcmFnJywgJ3Jlc2l6ZSddLCBlbmFibGVkOiBvcHRpb25zIH07XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLnNldE9wdGlvbnMoJ2F1dG9TY3JvbGwnLCBvcHRpb25zKTtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5zbmFwXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqKlxuICAgICAgICAgKiBEZXByZWNhdGVkLiBBZGQgYSBgc25hcGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZFxuICAgICAgICAgKiB0byBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSBvciBASW50ZXJhY3RhYmxlLnJlc2l6YWJsZSBpbnN0ZWFkLlxuICAgICAgICAgKlxuICAgICAgICAgKiBSZXR1cm5zIG9yIHNldHMgaWYgYW5kIGhvdyBhY3Rpb24gY29vcmRpbmF0ZXMgYXJlIHNuYXBwZWQuIEJ5XG4gICAgICAgICAqIGRlZmF1bHQsIHNuYXBwaW5nIGlzIHJlbGF0aXZlIHRvIHRoZSBwb2ludGVyIGNvb3JkaW5hdGVzLiBZb3UgY2FuXG4gICAgICAgICAqIGNoYW5nZSB0aGlzIGJ5IHNldHRpbmcgdGhlXG4gICAgICAgICAqIFtgZWxlbWVudE9yaWdpbmBdKGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL3B1bGwvNzIpLlxuICAgICAgICAgKipcbiAgICAgICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIGBmYWxzZWAgaWYgc25hcCBpcyBkaXNhYmxlZDsgb2JqZWN0IHdpdGggc25hcCBwcm9wZXJ0aWVzIGlmIHNuYXAgaXMgZW5hYmxlZFxuICAgICAgICAgKipcbiAgICAgICAgICogb3JcbiAgICAgICAgICoqXG4gICAgICAgICAtIG9wdGlvbnMgKG9iamVjdCB8IGJvb2xlYW4gfCBudWxsKSAjb3B0aW9uYWxcbiAgICAgICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgID4gVXNhZ2VcbiAgICAgICAgIHwgaW50ZXJhY3QoZG9jdW1lbnQucXVlcnlTZWxlY3RvcignI3RoaW5nJykpLnNuYXAoe1xuICAgICAgICAgfCAgICAgdGFyZ2V0czogW1xuICAgICAgICAgfCAgICAgICAgIC8vIHNuYXAgdG8gdGhpcyBzcGVjaWZpYyBwb2ludFxuICAgICAgICAgfCAgICAgICAgIHtcbiAgICAgICAgIHwgICAgICAgICAgICAgeDogMTAwLFxuICAgICAgICAgfCAgICAgICAgICAgICB5OiAxMDAsXG4gICAgICAgICB8ICAgICAgICAgICAgIHJhbmdlOiAyNVxuICAgICAgICAgfCAgICAgICAgIH0sXG4gICAgICAgICB8ICAgICAgICAgLy8gZ2l2ZSB0aGlzIGZ1bmN0aW9uIHRoZSB4IGFuZCB5IHBhZ2UgY29vcmRzIGFuZCBzbmFwIHRvIHRoZSBvYmplY3QgcmV0dXJuZWRcbiAgICAgICAgIHwgICAgICAgICBmdW5jdGlvbiAoeCwgeSkge1xuICAgICAgICAgfCAgICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgfCAgICAgICAgICAgICAgICAgeDogeCxcbiAgICAgICAgIHwgICAgICAgICAgICAgICAgIHk6ICg3NSArIDUwICogTWF0aC5zaW4oeCAqIDAuMDQpKSxcbiAgICAgICAgIHwgICAgICAgICAgICAgICAgIHJhbmdlOiA0MFxuICAgICAgICAgfCAgICAgICAgICAgICB9O1xuICAgICAgICAgfCAgICAgICAgIH0sXG4gICAgICAgICB8ICAgICAgICAgLy8gY3JlYXRlIGEgZnVuY3Rpb24gdGhhdCBzbmFwcyB0byBhIGdyaWRcbiAgICAgICAgIHwgICAgICAgICBpbnRlcmFjdC5jcmVhdGVTbmFwR3JpZCh7XG4gICAgICAgICB8ICAgICAgICAgICAgIHg6IDUwLFxuICAgICAgICAgfCAgICAgICAgICAgICB5OiA1MCxcbiAgICAgICAgIHwgICAgICAgICAgICAgcmFuZ2U6IDEwLCAgICAgICAgICAgICAgLy8gb3B0aW9uYWxcbiAgICAgICAgIHwgICAgICAgICAgICAgb2Zmc2V0OiB7IHg6IDUsIHk6IDEwIH0gLy8gb3B0aW9uYWxcbiAgICAgICAgIHwgICAgICAgICB9KVxuICAgICAgICAgfCAgICAgXSxcbiAgICAgICAgIHwgICAgIC8vIGRvIG5vdCBzbmFwIGR1cmluZyBub3JtYWwgbW92ZW1lbnQuXG4gICAgICAgICB8ICAgICAvLyBJbnN0ZWFkLCB0cmlnZ2VyIG9ubHkgb25lIHNuYXBwZWQgbW92ZSBldmVudFxuICAgICAgICAgfCAgICAgLy8gaW1tZWRpYXRlbHkgYmVmb3JlIHRoZSBlbmQgZXZlbnQuXG4gICAgICAgICB8ICAgICBlbmRPbmx5OiB0cnVlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgcmVsYXRpdmVQb2ludHM6IFtcbiAgICAgICAgIHwgICAgICAgICB7IHg6IDAsIHk6IDAgfSwgIC8vIHNuYXAgcmVsYXRpdmUgdG8gdGhlIHRvcCBsZWZ0IG9mIHRoZSBlbGVtZW50XG4gICAgICAgICB8ICAgICAgICAgeyB4OiAxLCB5OiAxIH0sICAvLyBhbmQgYWxzbyB0byB0aGUgYm90dG9tIHJpZ2h0XG4gICAgICAgICB8ICAgICBdLCAgXG4gICAgICAgICB8XG4gICAgICAgICB8ICAgICAvLyBvZmZzZXQgdGhlIHNuYXAgdGFyZ2V0IGNvb3JkaW5hdGVzXG4gICAgICAgICB8ICAgICAvLyBjYW4gYmUgYW4gb2JqZWN0IHdpdGggeC95IG9yICdzdGFydENvb3JkcydcbiAgICAgICAgIHwgICAgIG9mZnNldDogeyB4OiA1MCwgeTogNTAgfVxuICAgICAgICAgfCAgIH1cbiAgICAgICAgIHwgfSk7XG4gICAgICAgIFxcKi9cbiAgICAgICAgc25hcDogZnVuY3Rpb24gKG9wdGlvbnMpIHtcbiAgICAgICAgICAgIHZhciByZXQgPSB0aGlzLnNldE9wdGlvbnMoJ3NuYXAnLCBvcHRpb25zKTtcblxuICAgICAgICAgICAgaWYgKHJldCA9PT0gdGhpcykgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgICAgICByZXR1cm4gcmV0LmRyYWc7XG4gICAgICAgIH0sXG5cbiAgICAgICAgc2V0T3B0aW9uczogZnVuY3Rpb24gKG9wdGlvbiwgb3B0aW9ucykge1xuICAgICAgICAgICAgdmFyIGFjdGlvbnMgPSBvcHRpb25zICYmIHNjb3BlLmlzQXJyYXkob3B0aW9ucy5hY3Rpb25zKVxuICAgICAgICAgICAgICAgICAgICA/IG9wdGlvbnMuYWN0aW9uc1xuICAgICAgICAgICAgICAgICAgICA6IFsnZHJhZyddO1xuXG4gICAgICAgICAgICB2YXIgaTtcblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpIHx8IHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBhY3Rpb24gPSAvcmVzaXplLy50ZXN0KGFjdGlvbnNbaV0pPyAncmVzaXplJyA6IGFjdGlvbnNbaV07XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKCFzY29wZS5pc09iamVjdCh0aGlzLm9wdGlvbnNbYWN0aW9uXSkpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgICAgICAgICB2YXIgdGhpc09wdGlvbiA9IHRoaXMub3B0aW9uc1thY3Rpb25dW29wdGlvbl07XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKHNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB1dGlscy5leHRlbmQodGhpc09wdGlvbiwgb3B0aW9ucyk7XG4gICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLmVuYWJsZWQgPSBvcHRpb25zLmVuYWJsZWQgPT09IGZhbHNlPyBmYWxzZTogdHJ1ZTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG9wdGlvbiA9PT0gJ3NuYXAnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHRoaXNPcHRpb24ubW9kZSA9PT0gJ2dyaWQnKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXNPcHRpb24udGFyZ2V0cyA9IFtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGludGVyYWN0LmNyZWF0ZVNuYXBHcmlkKHV0aWxzLmV4dGVuZCh7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb2Zmc2V0OiB0aGlzT3B0aW9uLmdyaWRPZmZzZXQgfHwgeyB4OiAwLCB5OiAwIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0sIHRoaXNPcHRpb24uZ3JpZCB8fCB7fSkpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIF07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKHRoaXNPcHRpb24ubW9kZSA9PT0gJ2FuY2hvcicpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi50YXJnZXRzID0gdGhpc09wdGlvbi5hbmNob3JzO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbHNlIGlmICh0aGlzT3B0aW9uLm1vZGUgPT09ICdwYXRoJykge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnRhcmdldHMgPSB0aGlzT3B0aW9uLnBhdGhzO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICgnZWxlbWVudE9yaWdpbicgaW4gb3B0aW9ucykge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzT3B0aW9uLnJlbGF0aXZlUG9pbnRzID0gW29wdGlvbnMuZWxlbWVudE9yaWdpbl07XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgIGVsc2UgaWYgKHNjb3BlLmlzQm9vbChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdGhpc09wdGlvbi5lbmFibGVkID0gb3B0aW9ucztcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgcmV0ID0ge30sXG4gICAgICAgICAgICAgICAgYWxsQWN0aW9ucyA9IFsnZHJhZycsICdyZXNpemUnLCAnZ2VzdHVyZSddO1xuXG4gICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgYWxsQWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIGlmIChvcHRpb24gaW4gc2NvcGUuZGVmYXVsdE9wdGlvbnNbYWxsQWN0aW9uc1tpXV0pIHtcbiAgICAgICAgICAgICAgICAgICAgcmV0W2FsbEFjdGlvbnNbaV1dID0gdGhpcy5vcHRpb25zW2FsbEFjdGlvbnNbaV1dW29wdGlvbl07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gcmV0O1xuICAgICAgICB9LFxuXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuaW5lcnRpYVxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKipcbiAgICAgICAgICogRGVwcmVjYXRlZC4gQWRkIGFuIGBpbmVydGlhYCBwcm9wZXJ0eSB0byB0aGUgb3B0aW9ucyBvYmplY3QgcGFzc2VkXG4gICAgICAgICAqIHRvIEBJbnRlcmFjdGFibGUuZHJhZ2dhYmxlIG9yIEBJbnRlcmFjdGFibGUucmVzaXphYmxlIGluc3RlYWQuXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyBpZiBhbmQgaG93IGV2ZW50cyBjb250aW51ZSB0byBydW4gYWZ0ZXIgdGhlIHBvaW50ZXIgaXMgcmVsZWFzZWRcbiAgICAgICAgICoqXG4gICAgICAgICA9IChib29sZWFuIHwgb2JqZWN0KSBgZmFsc2VgIGlmIGluZXJ0aWEgaXMgZGlzYWJsZWQ7IGBvYmplY3RgIHdpdGggaW5lcnRpYSBwcm9wZXJ0aWVzIGlmIGluZXJ0aWEgaXMgZW5hYmxlZFxuICAgICAgICAgKipcbiAgICAgICAgICogb3JcbiAgICAgICAgICoqXG4gICAgICAgICAtIG9wdGlvbnMgKG9iamVjdCB8IGJvb2xlYW4gfCBudWxsKSAjb3B0aW9uYWxcbiAgICAgICAgID0gKEludGVyYWN0YWJsZSkgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgID4gVXNhZ2VcbiAgICAgICAgIHwgLy8gZW5hYmxlIGFuZCB1c2UgZGVmYXVsdCBzZXR0aW5nc1xuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pbmVydGlhKHRydWUpO1xuICAgICAgICAgfFxuICAgICAgICAgfCAvLyBlbmFibGUgYW5kIHVzZSBjdXN0b20gc2V0dGluZ3NcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYSh7XG4gICAgICAgICB8ICAgICAvLyB2YWx1ZSBncmVhdGVyIHRoYW4gMFxuICAgICAgICAgfCAgICAgLy8gaGlnaCB2YWx1ZXMgc2xvdyB0aGUgb2JqZWN0IGRvd24gbW9yZSBxdWlja2x5XG4gICAgICAgICB8ICAgICByZXNpc3RhbmNlICAgICA6IDE2LFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gdGhlIG1pbmltdW0gbGF1bmNoIHNwZWVkIChwaXhlbHMgcGVyIHNlY29uZCkgdGhhdCByZXN1bHRzIGluIGluZXJ0aWEgc3RhcnRcbiAgICAgICAgIHwgICAgIG1pblNwZWVkICAgICAgIDogMjAwLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gaW5lcnRpYSB3aWxsIHN0b3Agd2hlbiB0aGUgb2JqZWN0IHNsb3dzIGRvd24gdG8gdGhpcyBzcGVlZFxuICAgICAgICAgfCAgICAgZW5kU3BlZWQgICAgICAgOiAyMCxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIC8vIGJvb2xlYW47IHNob3VsZCBhY3Rpb25zIGJlIHJlc3VtZWQgd2hlbiB0aGUgcG9pbnRlciBnb2VzIGRvd24gZHVyaW5nIGluZXJ0aWFcbiAgICAgICAgIHwgICAgIGFsbG93UmVzdW1lICAgIDogdHJ1ZSxcbiAgICAgICAgIHxcbiAgICAgICAgIHwgICAgIC8vIGJvb2xlYW47IHNob3VsZCB0aGUganVtcCB3aGVuIHJlc3VtaW5nIGZyb20gaW5lcnRpYSBiZSBpZ25vcmVkIGluIGV2ZW50LmR4L2R5XG4gICAgICAgICB8ICAgICB6ZXJvUmVzdW1lRGVsdGE6IGZhbHNlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gaWYgc25hcC9yZXN0cmljdCBhcmUgc2V0IHRvIGJlIGVuZE9ubHkgYW5kIGluZXJ0aWEgaXMgZW5hYmxlZCwgcmVsZWFzaW5nXG4gICAgICAgICB8ICAgICAvLyB0aGUgcG9pbnRlciB3aXRob3V0IHRyaWdnZXJpbmcgaW5lcnRpYSB3aWxsIGFuaW1hdGUgZnJvbSB0aGUgcmVsZWFzZVxuICAgICAgICAgfCAgICAgLy8gcG9pbnQgdG8gdGhlIHNuYXBlZC9yZXN0cmljdGVkIHBvaW50IGluIHRoZSBnaXZlbiBhbW91bnQgb2YgdGltZSAobXMpXG4gICAgICAgICB8ICAgICBzbW9vdGhFbmREdXJhdGlvbjogMzAwLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gYW4gYXJyYXkgb2YgYWN0aW9uIHR5cGVzIHRoYXQgY2FuIGhhdmUgaW5lcnRpYSAobm8gZ2VzdHVyZSlcbiAgICAgICAgIHwgICAgIGFjdGlvbnMgICAgICAgIDogWydkcmFnJywgJ3Jlc2l6ZSddXG4gICAgICAgICB8IH0pO1xuICAgICAgICAgfFxuICAgICAgICAgfCAvLyByZXNldCBjdXN0b20gc2V0dGluZ3MgYW5kIHVzZSBhbGwgZGVmYXVsdHNcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCkuaW5lcnRpYShudWxsKTtcbiAgICAgICAgXFwqL1xuICAgICAgICBpbmVydGlhOiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgdmFyIHJldCA9IHRoaXMuc2V0T3B0aW9ucygnaW5lcnRpYScsIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICBpZiAocmV0ID09PSB0aGlzKSB7IHJldHVybiB0aGlzOyB9XG5cbiAgICAgICAgICAgIHJldHVybiByZXQuZHJhZztcbiAgICAgICAgfSxcblxuICAgICAgICBnZXRBY3Rpb246IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgaW50ZXJhY3Rpb24sIGVsZW1lbnQpIHtcbiAgICAgICAgICAgIHZhciBhY3Rpb24gPSB0aGlzLmRlZmF1bHRBY3Rpb25DaGVja2VyKHBvaW50ZXIsIGludGVyYWN0aW9uLCBlbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyKHBvaW50ZXIsIGV2ZW50LCBhY3Rpb24sIHRoaXMsIGVsZW1lbnQsIGludGVyYWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIGFjdGlvbjtcbiAgICAgICAgfSxcblxuICAgICAgICBkZWZhdWx0QWN0aW9uQ2hlY2tlcjogZGVmYXVsdEFjdGlvbkNoZWNrZXIsXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYWN0aW9uQ2hlY2tlclxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBHZXRzIG9yIHNldHMgdGhlIGZ1bmN0aW9uIHVzZWQgdG8gY2hlY2sgYWN0aW9uIHRvIGJlIHBlcmZvcm1lZCBvblxuICAgICAgICAgKiBwb2ludGVyRG93blxuICAgICAgICAgKlxuICAgICAgICAgLSBjaGVja2VyIChmdW5jdGlvbiB8IG51bGwpICNvcHRpb25hbCBBIGZ1bmN0aW9uIHdoaWNoIHRha2VzIGEgcG9pbnRlciBldmVudCwgZGVmYXVsdEFjdGlvbiBzdHJpbmcsIGludGVyYWN0YWJsZSwgZWxlbWVudCBhbmQgaW50ZXJhY3Rpb24gYXMgcGFyYW1ldGVycyBhbmQgcmV0dXJucyBhbiBvYmplY3Qgd2l0aCBuYW1lIHByb3BlcnR5ICdkcmFnJyAncmVzaXplJyBvciAnZ2VzdHVyZScgYW5kIG9wdGlvbmFsbHkgYW4gYGVkZ2VzYCBvYmplY3Qgd2l0aCBib29sZWFuICd0b3AnLCAnbGVmdCcsICdib3R0b20nIGFuZCByaWdodCBwcm9wcy5cbiAgICAgICAgID0gKEZ1bmN0aW9uIHwgSW50ZXJhY3RhYmxlKSBUaGUgY2hlY2tlciBmdW5jdGlvbiBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgfCBpbnRlcmFjdCgnLnJlc2l6ZS1kcmFnJylcbiAgICAgICAgIHwgICAucmVzaXphYmxlKHRydWUpXG4gICAgICAgICB8ICAgLmRyYWdnYWJsZSh0cnVlKVxuICAgICAgICAgfCAgIC5hY3Rpb25DaGVja2VyKGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGludGVyYWN0aW9uKSB7XG4gICAgICAgICB8XG4gICAgICAgICB8ICAgaWYgKGludGVyYWN0Lm1hdGNoZXNTZWxlY3RvcihldmVudC50YXJnZXQsICcuZHJhZy1oYW5kbGUnKSB7XG4gICAgICAgICB8ICAgICAvLyBmb3JjZSBkcmFnIHdpdGggaGFuZGxlIHRhcmdldFxuICAgICAgICAgfCAgICAgYWN0aW9uLm5hbWUgPSBkcmFnO1xuICAgICAgICAgfCAgIH1cbiAgICAgICAgIHwgICBlbHNlIHtcbiAgICAgICAgIHwgICAgIC8vIHJlc2l6ZSBmcm9tIHRoZSB0b3AgYW5kIHJpZ2h0IGVkZ2VzXG4gICAgICAgICB8ICAgICBhY3Rpb24ubmFtZSAgPSAncmVzaXplJztcbiAgICAgICAgIHwgICAgIGFjdGlvbi5lZGdlcyA9IHsgdG9wOiB0cnVlLCByaWdodDogdHJ1ZSB9O1xuICAgICAgICAgfCAgIH1cbiAgICAgICAgIHxcbiAgICAgICAgIHwgICByZXR1cm4gYWN0aW9uO1xuICAgICAgICAgfCB9KTtcbiAgICAgICAgXFwqL1xuICAgICAgICBhY3Rpb25DaGVja2VyOiBmdW5jdGlvbiAoY2hlY2tlcikge1xuICAgICAgICAgICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24oY2hlY2tlcikpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuYWN0aW9uQ2hlY2tlciA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmFjdGlvbkNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5hY3Rpb25DaGVja2VyO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmdldFJlY3RcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogVGhlIGRlZmF1bHQgZnVuY3Rpb24gdG8gZ2V0IGFuIEludGVyYWN0YWJsZXMgYm91bmRpbmcgcmVjdC4gQ2FuIGJlXG4gICAgICAgICAqIG92ZXJyaWRkZW4gdXNpbmcgQEludGVyYWN0YWJsZS5yZWN0Q2hlY2tlci5cbiAgICAgICAgICpcbiAgICAgICAgIC0gZWxlbWVudCAoRWxlbWVudCkgI29wdGlvbmFsIFRoZSBlbGVtZW50IHRvIG1lYXN1cmUuXG4gICAgICAgICA9IChvYmplY3QpIFRoZSBvYmplY3QncyBib3VuZGluZyByZWN0YW5nbGUuXG4gICAgICAgICBvIHtcbiAgICAgICAgIG8gICAgIHRvcCAgIDogMCxcbiAgICAgICAgIG8gICAgIGxlZnQgIDogMCxcbiAgICAgICAgIG8gICAgIGJvdHRvbTogMCxcbiAgICAgICAgIG8gICAgIHJpZ2h0IDogMCxcbiAgICAgICAgIG8gICAgIHdpZHRoIDogMCxcbiAgICAgICAgIG8gICAgIGhlaWdodDogMFxuICAgICAgICAgbyB9XG4gICAgICAgIFxcKi9cbiAgICAgICAgZ2V0UmVjdDogZnVuY3Rpb24gcmVjdENoZWNrIChlbGVtZW50KSB7XG4gICAgICAgICAgICBlbGVtZW50ID0gZWxlbWVudCB8fCB0aGlzLl9lbGVtZW50O1xuXG4gICAgICAgICAgICBpZiAodGhpcy5zZWxlY3RvciAmJiAhKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkpIHtcbiAgICAgICAgICAgICAgICBlbGVtZW50ID0gdGhpcy5fY29udGV4dC5xdWVyeVNlbGVjdG9yKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gc2NvcGUuZ2V0RWxlbWVudFJlY3QoZWxlbWVudCk7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUucmVjdENoZWNrZXJcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogUmV0dXJucyBvciBzZXRzIHRoZSBmdW5jdGlvbiB1c2VkIHRvIGNhbGN1bGF0ZSB0aGUgaW50ZXJhY3RhYmxlJ3NcbiAgICAgICAgICogZWxlbWVudCdzIHJlY3RhbmdsZVxuICAgICAgICAgKlxuICAgICAgICAgLSBjaGVja2VyIChmdW5jdGlvbikgI29wdGlvbmFsIEEgZnVuY3Rpb24gd2hpY2ggcmV0dXJucyB0aGlzIEludGVyYWN0YWJsZSdzIGJvdW5kaW5nIHJlY3RhbmdsZS4gU2VlIEBJbnRlcmFjdGFibGUuZ2V0UmVjdFxuICAgICAgICAgPSAoZnVuY3Rpb24gfCBvYmplY3QpIFRoZSBjaGVja2VyIGZ1bmN0aW9uIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgcmVjdENoZWNrZXI6IGZ1bmN0aW9uIChjaGVja2VyKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihjaGVja2VyKSkge1xuICAgICAgICAgICAgICAgIHRoaXMuZ2V0UmVjdCA9IGNoZWNrZXI7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKGNoZWNrZXIgPT09IG51bGwpIHtcbiAgICAgICAgICAgICAgICBkZWxldGUgdGhpcy5vcHRpb25zLmdldFJlY3Q7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMuZ2V0UmVjdDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5zdHlsZUN1cnNvclxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciB0aGUgYWN0aW9uIHRoYXQgd291bGQgYmUgcGVyZm9ybWVkIHdoZW4gdGhlXG4gICAgICAgICAqIG1vdXNlIG9uIHRoZSBlbGVtZW50IGFyZSBjaGVja2VkIG9uIGBtb3VzZW1vdmVgIHNvIHRoYXQgdGhlIGN1cnNvclxuICAgICAgICAgKiBtYXkgYmUgc3R5bGVkIGFwcHJvcHJpYXRlbHlcbiAgICAgICAgICpcbiAgICAgICAgIC0gbmV3VmFsdWUgKGJvb2xlYW4pICNvcHRpb25hbFxuICAgICAgICAgPSAoYm9vbGVhbiB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIHN0eWxlQ3Vyc29yOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKG5ld1ZhbHVlID09PSBudWxsKSB7XG4gICAgICAgICAgICAgICAgZGVsZXRlIHRoaXMub3B0aW9ucy5zdHlsZUN1cnNvcjtcblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLnN0eWxlQ3Vyc29yO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLnByZXZlbnREZWZhdWx0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIHRvIHByZXZlbnQgdGhlIGJyb3dzZXIncyBkZWZhdWx0IGJlaGF2aW91clxuICAgICAgICAgKiBpbiByZXNwb25zZSB0byBwb2ludGVyIGV2ZW50cy4gQ2FuIGJlIHNldCB0bzpcbiAgICAgICAgICogIC0gYCdhbHdheXMnYCB0byBhbHdheXMgcHJldmVudFxuICAgICAgICAgKiAgLSBgJ25ldmVyJ2AgdG8gbmV2ZXIgcHJldmVudFxuICAgICAgICAgKiAgLSBgJ2F1dG8nYCB0byBsZXQgaW50ZXJhY3QuanMgdHJ5IHRvIGRldGVybWluZSB3aGF0IHdvdWxkIGJlIGJlc3RcbiAgICAgICAgICpcbiAgICAgICAgIC0gbmV3VmFsdWUgKHN0cmluZykgI29wdGlvbmFsIGB0cnVlYCwgYGZhbHNlYCBvciBgJ2F1dG8nYFxuICAgICAgICAgPSAoc3RyaW5nIHwgSW50ZXJhY3RhYmxlKSBUaGUgY3VycmVudCBzZXR0aW5nIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgcHJldmVudERlZmF1bHQ6IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICAgICAgaWYgKC9eKGFsd2F5c3xuZXZlcnxhdXRvKSQvLnRlc3QobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnByZXZlbnREZWZhdWx0ID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Jvb2wobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLnByZXZlbnREZWZhdWx0ID0gbmV3VmFsdWU/ICdhbHdheXMnIDogJ25ldmVyJztcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHRoaXMub3B0aW9ucy5wcmV2ZW50RGVmYXVsdDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5vcmlnaW5cbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogR2V0cyBvciBzZXRzIHRoZSBvcmlnaW4gb2YgdGhlIEludGVyYWN0YWJsZSdzIGVsZW1lbnQuICBUaGUgeCBhbmQgeVxuICAgICAgICAgKiBvZiB0aGUgb3JpZ2luIHdpbGwgYmUgc3VidHJhY3RlZCBmcm9tIGFjdGlvbiBldmVudCBjb29yZGluYXRlcy5cbiAgICAgICAgICpcbiAgICAgICAgIC0gb3JpZ2luIChvYmplY3QgfCBzdHJpbmcpICNvcHRpb25hbCBBbiBvYmplY3QgZWcuIHsgeDogMCwgeTogMCB9IG9yIHN0cmluZyAncGFyZW50JywgJ3NlbGYnIG9yIGFueSBDU1Mgc2VsZWN0b3JcbiAgICAgICAgICogT1JcbiAgICAgICAgIC0gb3JpZ2luIChFbGVtZW50KSAjb3B0aW9uYWwgQW4gSFRNTCBvciBTVkcgRWxlbWVudCB3aG9zZSByZWN0IHdpbGwgYmUgdXNlZFxuICAgICAgICAgKipcbiAgICAgICAgID0gKG9iamVjdCkgVGhlIGN1cnJlbnQgb3JpZ2luIG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgIFxcKi9cbiAgICAgICAgb3JpZ2luOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS50cnlTZWxlY3RvcihuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMub3JpZ2luID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIGlmIChzY29wZS5pc09iamVjdChuZXdWYWx1ZSkpIHtcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMub3JpZ2luID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMub3JpZ2luO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmRlbHRhU291cmNlXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgbW91c2UgY29vcmRpbmF0ZSB0eXBlcyB1c2VkIHRvIGNhbGN1bGF0ZSB0aGVcbiAgICAgICAgICogbW92ZW1lbnQgb2YgdGhlIHBvaW50ZXIuXG4gICAgICAgICAqXG4gICAgICAgICAtIG5ld1ZhbHVlIChzdHJpbmcpICNvcHRpb25hbCBVc2UgJ2NsaWVudCcgaWYgeW91IHdpbGwgYmUgc2Nyb2xsaW5nIHdoaWxlIGludGVyYWN0aW5nOyBVc2UgJ3BhZ2UnIGlmIHlvdSB3YW50IGF1dG9TY3JvbGwgdG8gd29ya1xuICAgICAgICAgPSAoc3RyaW5nIHwgb2JqZWN0KSBUaGUgY3VycmVudCBkZWx0YVNvdXJjZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIGRlbHRhU291cmNlOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChuZXdWYWx1ZSA9PT0gJ3BhZ2UnIHx8IG5ld1ZhbHVlID09PSAnY2xpZW50Jykge1xuICAgICAgICAgICAgICAgIHRoaXMub3B0aW9ucy5kZWx0YVNvdXJjZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLm9wdGlvbnMuZGVsdGFTb3VyY2U7XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUucmVzdHJpY3RcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICoqXG4gICAgICAgICAqIERlcHJlY2F0ZWQuIEFkZCBhIGByZXN0cmljdGAgcHJvcGVydHkgdG8gdGhlIG9wdGlvbnMgb2JqZWN0IHBhc3NlZCB0b1xuICAgICAgICAgKiBASW50ZXJhY3RhYmxlLmRyYWdnYWJsZSwgQEludGVyYWN0YWJsZS5yZXNpemFibGUgb3IgQEludGVyYWN0YWJsZS5nZXN0dXJhYmxlIGluc3RlYWQuXG4gICAgICAgICAqXG4gICAgICAgICAqIFJldHVybnMgb3Igc2V0cyB0aGUgcmVjdGFuZ2xlcyB3aXRoaW4gd2hpY2ggYWN0aW9ucyBvbiB0aGlzXG4gICAgICAgICAqIGludGVyYWN0YWJsZSAoYWZ0ZXIgc25hcCBjYWxjdWxhdGlvbnMpIGFyZSByZXN0cmljdGVkLiBCeSBkZWZhdWx0LFxuICAgICAgICAgKiByZXN0cmljdGluZyBpcyByZWxhdGl2ZSB0byB0aGUgcG9pbnRlciBjb29yZGluYXRlcy4gWW91IGNhbiBjaGFuZ2VcbiAgICAgICAgICogdGhpcyBieSBzZXR0aW5nIHRoZVxuICAgICAgICAgKiBbYGVsZW1lbnRSZWN0YF0oaHR0cHM6Ly9naXRodWIuY29tL3RheWUvaW50ZXJhY3QuanMvcHVsbC83MikuXG4gICAgICAgICAqKlxuICAgICAgICAgLSBvcHRpb25zIChvYmplY3QpICNvcHRpb25hbCBhbiBvYmplY3Qgd2l0aCBrZXlzIGRyYWcsIHJlc2l6ZSwgYW5kL29yIGdlc3R1cmUgd2hvc2UgdmFsdWVzIGFyZSByZWN0cywgRWxlbWVudHMsIENTUyBzZWxlY3RvcnMsIG9yICdwYXJlbnQnIG9yICdzZWxmJ1xuICAgICAgICAgPSAob2JqZWN0KSBUaGUgY3VycmVudCByZXN0cmljdGlvbnMgb2JqZWN0IG9yIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICAqKlxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5yZXN0cmljdCh7XG4gICAgICAgICB8ICAgICAvLyB0aGUgcmVjdCB3aWxsIGJlIGBpbnRlcmFjdC5nZXRFbGVtZW50UmVjdChlbGVtZW50LnBhcmVudE5vZGUpYFxuICAgICAgICAgfCAgICAgZHJhZzogZWxlbWVudC5wYXJlbnROb2RlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8geCBhbmQgeSBhcmUgcmVsYXRpdmUgdG8gdGhlIHRoZSBpbnRlcmFjdGFibGUncyBvcmlnaW5cbiAgICAgICAgIHwgICAgIHJlc2l6ZTogeyB4OiAxMDAsIHk6IDEwMCwgd2lkdGg6IDIwMCwgaGVpZ2h0OiAyMDAgfVxuICAgICAgICAgfCB9KVxuICAgICAgICAgfFxuICAgICAgICAgfCBpbnRlcmFjdCgnLmRyYWdnYWJsZScpLnJlc3RyaWN0KHtcbiAgICAgICAgIHwgICAgIC8vIHRoZSByZWN0IHdpbGwgYmUgdGhlIHNlbGVjdGVkIGVsZW1lbnQncyBwYXJlbnRcbiAgICAgICAgIHwgICAgIGRyYWc6ICdwYXJlbnQnLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gZG8gbm90IHJlc3RyaWN0IGR1cmluZyBub3JtYWwgbW92ZW1lbnQuXG4gICAgICAgICB8ICAgICAvLyBJbnN0ZWFkLCB0cmlnZ2VyIG9ubHkgb25lIHJlc3RyaWN0ZWQgbW92ZSBldmVudFxuICAgICAgICAgfCAgICAgLy8gaW1tZWRpYXRlbHkgYmVmb3JlIHRoZSBlbmQgZXZlbnQuXG4gICAgICAgICB8ICAgICBlbmRPbmx5OiB0cnVlLFxuICAgICAgICAgfFxuICAgICAgICAgfCAgICAgLy8gaHR0cHM6Ly9naXRodWIuY29tL3RheWUvaW50ZXJhY3QuanMvcHVsbC83MiNpc3N1ZS00MTgxMzQ5M1xuICAgICAgICAgfCAgICAgZWxlbWVudFJlY3Q6IHsgdG9wOiAwLCBsZWZ0OiAwLCBib3R0b206IDEsIHJpZ2h0OiAxIH1cbiAgICAgICAgIHwgfSk7XG4gICAgICAgIFxcKi9cbiAgICAgICAgcmVzdHJpY3Q6IGZ1bmN0aW9uIChvcHRpb25zKSB7XG4gICAgICAgICAgICBpZiAoIXNjb3BlLmlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXMuc2V0T3B0aW9ucygncmVzdHJpY3QnLCBvcHRpb25zKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdmFyIGFjdGlvbnMgPSBbJ2RyYWcnLCAncmVzaXplJywgJ2dlc3R1cmUnXSxcbiAgICAgICAgICAgICAgICByZXQ7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgYWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgIHZhciBhY3Rpb24gPSBhY3Rpb25zW2ldO1xuXG4gICAgICAgICAgICAgICAgaWYgKGFjdGlvbiBpbiBvcHRpb25zKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBwZXJBY3Rpb24gPSB1dGlscy5leHRlbmQoe1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjdGlvbnM6IFthY3Rpb25dLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uOiBvcHRpb25zW2FjdGlvbl1cbiAgICAgICAgICAgICAgICAgICAgICAgIH0sIG9wdGlvbnMpO1xuXG4gICAgICAgICAgICAgICAgICAgIHJldCA9IHRoaXMuc2V0T3B0aW9ucygncmVzdHJpY3QnLCBwZXJBY3Rpb24pO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIHJldDtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5jb250ZXh0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIEdldHMgdGhlIHNlbGVjdG9yIGNvbnRleHQgTm9kZSBvZiB0aGUgSW50ZXJhY3RhYmxlLiBUaGUgZGVmYXVsdCBpcyBgd2luZG93LmRvY3VtZW50YC5cbiAgICAgICAgICpcbiAgICAgICAgID0gKE5vZGUpIFRoZSBjb250ZXh0IE5vZGUgb2YgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgICoqXG4gICAgICAgIFxcKi9cbiAgICAgICAgY29udGV4dDogZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgcmV0dXJuIHRoaXMuX2NvbnRleHQ7XG4gICAgICAgIH0sXG5cbiAgICAgICAgX2NvbnRleHQ6IHNjb3BlLmRvY3VtZW50LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmlnbm9yZUZyb21cbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogSWYgdGhlIHRhcmdldCBvZiB0aGUgYG1vdXNlZG93bmAsIGBwb2ludGVyZG93bmAgb3IgYHRvdWNoc3RhcnRgXG4gICAgICAgICAqIGV2ZW50IG9yIGFueSBvZiBpdCdzIHBhcmVudHMgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvclxuICAgICAgICAgKiBFbGVtZW50LCBubyBkcmFnL3Jlc2l6ZS9nZXN0dXJlIGlzIHN0YXJ0ZWQuXG4gICAgICAgICAqXG4gICAgICAgICAtIG5ld1ZhbHVlIChzdHJpbmcgfCBFbGVtZW50IHwgbnVsbCkgI29wdGlvbmFsIGEgQ1NTIHNlbGVjdG9yIHN0cmluZywgYW4gRWxlbWVudCBvciBgbnVsbGAgdG8gbm90IGlnbm9yZSBhbnkgZWxlbWVudHNcbiAgICAgICAgID0gKHN0cmluZyB8IEVsZW1lbnQgfCBvYmplY3QpIFRoZSBjdXJyZW50IGlnbm9yZUZyb20gdmFsdWUgb3IgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgICoqXG4gICAgICAgICB8IGludGVyYWN0KGVsZW1lbnQsIHsgaWdub3JlRnJvbTogZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ25vLWFjdGlvbicpIH0pO1xuICAgICAgICAgfCAvLyBvclxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5pZ25vcmVGcm9tKCdpbnB1dCwgdGV4dGFyZWEsIGEnKTtcbiAgICAgICAgXFwqL1xuICAgICAgICBpZ25vcmVGcm9tOiBmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgICAgIGlmIChzY29wZS50cnlTZWxlY3RvcihuZXdWYWx1ZSkpIHsgICAgICAgICAgICAvLyBDU1Mgc2VsZWN0b3IgdG8gbWF0Y2ggZXZlbnQudGFyZ2V0XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmlnbm9yZUZyb20gPSBuZXdWYWx1ZTtcbiAgICAgICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHV0aWxzLmlzRWxlbWVudChuZXdWYWx1ZSkpIHsgICAgICAgICAgICAgIC8vIHNwZWNpZmljIGVsZW1lbnRcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuaWdub3JlRnJvbSA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmlnbm9yZUZyb207XG4gICAgICAgIH0sXG5cbiAgICAgICAgLypcXFxuICAgICAgICAgKiBJbnRlcmFjdGFibGUuYWxsb3dGcm9tXG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIEEgZHJhZy9yZXNpemUvZ2VzdHVyZSBpcyBzdGFydGVkIG9ubHkgSWYgdGhlIHRhcmdldCBvZiB0aGVcbiAgICAgICAgICogYG1vdXNlZG93bmAsIGBwb2ludGVyZG93bmAgb3IgYHRvdWNoc3RhcnRgIGV2ZW50IG9yIGFueSBvZiBpdCdzXG4gICAgICAgICAqIHBhcmVudHMgbWF0Y2ggdGhlIGdpdmVuIENTUyBzZWxlY3RvciBvciBFbGVtZW50LlxuICAgICAgICAgKlxuICAgICAgICAgLSBuZXdWYWx1ZSAoc3RyaW5nIHwgRWxlbWVudCB8IG51bGwpICNvcHRpb25hbCBhIENTUyBzZWxlY3RvciBzdHJpbmcsIGFuIEVsZW1lbnQgb3IgYG51bGxgIHRvIGFsbG93IGZyb20gYW55IGVsZW1lbnRcbiAgICAgICAgID0gKHN0cmluZyB8IEVsZW1lbnQgfCBvYmplY3QpIFRoZSBjdXJyZW50IGFsbG93RnJvbSB2YWx1ZSBvciB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKipcbiAgICAgICAgIHwgaW50ZXJhY3QoZWxlbWVudCwgeyBhbGxvd0Zyb206IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdkcmFnLWhhbmRsZScpIH0pO1xuICAgICAgICAgfCAvLyBvclxuICAgICAgICAgfCBpbnRlcmFjdChlbGVtZW50KS5hbGxvd0Zyb20oJy5oYW5kbGUnKTtcbiAgICAgICAgXFwqL1xuICAgICAgICBhbGxvd0Zyb206IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLnRyeVNlbGVjdG9yKG5ld1ZhbHVlKSkgeyAgICAgICAgICAgIC8vIENTUyBzZWxlY3RvciB0byBtYXRjaCBldmVudC50YXJnZXRcbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnMuYWxsb3dGcm9tID0gbmV3VmFsdWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmICh1dGlscy5pc0VsZW1lbnQobmV3VmFsdWUpKSB7ICAgICAgICAgICAgICAvLyBzcGVjaWZpYyBlbGVtZW50XG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zLmFsbG93RnJvbSA9IG5ld1ZhbHVlO1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcy5vcHRpb25zLmFsbG93RnJvbTtcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5lbGVtZW50XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIElmIHRoaXMgaXMgbm90IGEgc2VsZWN0b3IgSW50ZXJhY3RhYmxlLCBpdCByZXR1cm5zIHRoZSBlbGVtZW50IHRoaXNcbiAgICAgICAgICogaW50ZXJhY3RhYmxlIHJlcHJlc2VudHNcbiAgICAgICAgICpcbiAgICAgICAgID0gKEVsZW1lbnQpIEhUTUwgLyBTVkcgRWxlbWVudFxuICAgICAgICBcXCovXG4gICAgICAgIGVsZW1lbnQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLl9lbGVtZW50O1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLmZpcmVcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogQ2FsbHMgbGlzdGVuZXJzIGZvciB0aGUgZ2l2ZW4gSW50ZXJhY3RFdmVudCB0eXBlIGJvdW5kIGdsb2JhbGx5XG4gICAgICAgICAqIGFuZCBkaXJlY3RseSB0byB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgKlxuICAgICAgICAgLSBpRXZlbnQgKEludGVyYWN0RXZlbnQpIFRoZSBJbnRlcmFjdEV2ZW50IG9iamVjdCB0byBiZSBmaXJlZCBvbiB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICAgPSAoSW50ZXJhY3RhYmxlKSB0aGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIGZpcmU6IGZ1bmN0aW9uIChpRXZlbnQpIHtcbiAgICAgICAgICAgIGlmICghKGlFdmVudCAmJiBpRXZlbnQudHlwZSkgfHwgIXNjb3BlLmNvbnRhaW5zKHNjb3BlLmV2ZW50VHlwZXMsIGlFdmVudC50eXBlKSkge1xuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgbGlzdGVuZXJzLFxuICAgICAgICAgICAgICAgIGksXG4gICAgICAgICAgICAgICAgbGVuLFxuICAgICAgICAgICAgICAgIG9uRXZlbnQgPSAnb24nICsgaUV2ZW50LnR5cGUsXG4gICAgICAgICAgICAgICAgZnVuY05hbWUgPSAnJztcblxuICAgICAgICAgICAgLy8gSW50ZXJhY3RhYmxlI29uKCkgbGlzdGVuZXJzXG4gICAgICAgICAgICBpZiAoaUV2ZW50LnR5cGUgaW4gdGhpcy5faUV2ZW50cykge1xuICAgICAgICAgICAgICAgIGxpc3RlbmVycyA9IHRoaXMuX2lFdmVudHNbaUV2ZW50LnR5cGVdO1xuXG4gICAgICAgICAgICAgICAgZm9yIChpID0gMCwgbGVuID0gbGlzdGVuZXJzLmxlbmd0aDsgaSA8IGxlbiAmJiAhaUV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIGZ1bmNOYW1lID0gbGlzdGVuZXJzW2ldLm5hbWU7XG4gICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyc1tpXShpRXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gaW50ZXJhY3RhYmxlLm9uZXZlbnQgbGlzdGVuZXJcbiAgICAgICAgICAgIGlmIChzY29wZS5pc0Z1bmN0aW9uKHRoaXNbb25FdmVudF0pKSB7XG4gICAgICAgICAgICAgICAgZnVuY05hbWUgPSB0aGlzW29uRXZlbnRdLm5hbWU7XG4gICAgICAgICAgICAgICAgdGhpc1tvbkV2ZW50XShpRXZlbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBpbnRlcmFjdC5vbigpIGxpc3RlbmVyc1xuICAgICAgICAgICAgaWYgKGlFdmVudC50eXBlIGluIHNjb3BlLmdsb2JhbEV2ZW50cyAmJiAobGlzdGVuZXJzID0gc2NvcGUuZ2xvYmFsRXZlbnRzW2lFdmVudC50eXBlXSkpICB7XG5cbiAgICAgICAgICAgICAgICBmb3IgKGkgPSAwLCBsZW4gPSBsaXN0ZW5lcnMubGVuZ3RoOyBpIDwgbGVuICYmICFpRXZlbnQuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgZnVuY05hbWUgPSBsaXN0ZW5lcnNbaV0ubmFtZTtcbiAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzW2ldKGlFdmVudCk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5vblxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBCaW5kcyBhIGxpc3RlbmVyIGZvciBhbiBJbnRlcmFjdEV2ZW50IG9yIERPTSBldmVudC5cbiAgICAgICAgICpcbiAgICAgICAgIC0gZXZlbnRUeXBlICAoc3RyaW5nIHwgYXJyYXkgfCBvYmplY3QpIFRoZSB0eXBlcyBvZiBldmVudHMgdG8gbGlzdGVuIGZvclxuICAgICAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCBvbiB0aGUgZ2l2ZW4gZXZlbnQocylcbiAgICAgICAgIC0gdXNlQ2FwdHVyZSAoYm9vbGVhbikgI29wdGlvbmFsIHVzZUNhcHR1cmUgZmxhZyBmb3IgYWRkRXZlbnRMaXN0ZW5lclxuICAgICAgICAgPSAob2JqZWN0KSBUaGlzIEludGVyYWN0YWJsZVxuICAgICAgICBcXCovXG4gICAgICAgIG9uOiBmdW5jdGlvbiAoZXZlbnRUeXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgICAgICAgICAgdmFyIGk7XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc1N0cmluZyhldmVudFR5cGUpICYmIGV2ZW50VHlwZS5zZWFyY2goJyAnKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICBldmVudFR5cGUgPSBldmVudFR5cGUudHJpbSgpLnNwbGl0KC8gKy8pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNBcnJheShldmVudFR5cGUpKSB7XG4gICAgICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGV2ZW50VHlwZS5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm9uKGV2ZW50VHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QoZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gZXZlbnRUeXBlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub24ocHJvcCwgZXZlbnRUeXBlW3Byb3BdLCBsaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChldmVudFR5cGUgPT09ICd3aGVlbCcpIHtcbiAgICAgICAgICAgICAgICBldmVudFR5cGUgPSBzY29wZS53aGVlbEV2ZW50O1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBjb252ZXJ0IHRvIGJvb2xlYW5cbiAgICAgICAgICAgIHVzZUNhcHR1cmUgPSB1c2VDYXB0dXJlPyB0cnVlOiBmYWxzZTtcblxuICAgICAgICAgICAgaWYgKHNjb3BlLmNvbnRhaW5zKHNjb3BlLmV2ZW50VHlwZXMsIGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgICAgICAvLyBpZiB0aGlzIHR5cGUgb2YgZXZlbnQgd2FzIG5ldmVyIGJvdW5kIHRvIHRoaXMgSW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgaWYgKCEoZXZlbnRUeXBlIGluIHRoaXMuX2lFdmVudHMpKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuX2lFdmVudHNbZXZlbnRUeXBlXSA9IFtsaXN0ZW5lcl07XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLl9pRXZlbnRzW2V2ZW50VHlwZV0ucHVzaChsaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gZGVsZWdhdGVkIGV2ZW50IGZvciBzZWxlY3RvclxuICAgICAgICAgICAgZWxzZSBpZiAodGhpcy5zZWxlY3Rvcikge1xuICAgICAgICAgICAgICAgIGlmICghc2NvcGUuZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0pIHtcbiAgICAgICAgICAgICAgICAgICAgc2NvcGUuZGVsZWdhdGVkRXZlbnRzW2V2ZW50VHlwZV0gPSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3RvcnM6IFtdLFxuICAgICAgICAgICAgICAgICAgICAgICAgY29udGV4dHMgOiBbXSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVyczogW11cbiAgICAgICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgICAgICAvLyBhZGQgZGVsZWdhdGUgbGlzdGVuZXIgZnVuY3Rpb25zXG4gICAgICAgICAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBzY29wZS5kb2N1bWVudHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQoc2NvcGUuZG9jdW1lbnRzW2ldLCBldmVudFR5cGUsIGRlbGVnYXRlTGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZChzY29wZS5kb2N1bWVudHNbaV0sIGV2ZW50VHlwZSwgZGVsZWdhdGVVc2VDYXB0dXJlLCB0cnVlKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHZhciBkZWxlZ2F0ZWQgPSBzY29wZS5kZWxlZ2F0ZWRFdmVudHNbZXZlbnRUeXBlXSxcbiAgICAgICAgICAgICAgICAgICAgaW5kZXg7XG5cbiAgICAgICAgICAgICAgICBmb3IgKGluZGV4ID0gZGVsZWdhdGVkLnNlbGVjdG9ycy5sZW5ndGggLSAxOyBpbmRleCA+PSAwOyBpbmRleC0tKSB7XG4gICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2luZGV4XSA9PT0gdGhpcy5zZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2luZGV4XSA9PT0gdGhpcy5fY29udGV4dCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgICAgIGluZGV4ID0gZGVsZWdhdGVkLnNlbGVjdG9ycy5sZW5ndGg7XG5cbiAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5wdXNoKHRoaXMuc2VsZWN0b3IpO1xuICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnB1c2godGhpcy5fY29udGV4dCk7XG4gICAgICAgICAgICAgICAgICAgIGRlbGVnYXRlZC5saXN0ZW5lcnMucHVzaChbXSk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgLy8ga2VlcCBsaXN0ZW5lciBhbmQgdXNlQ2FwdHVyZSBmbGFnXG4gICAgICAgICAgICAgICAgZGVsZWdhdGVkLmxpc3RlbmVyc1tpbmRleF0ucHVzaChbbGlzdGVuZXIsIHVzZUNhcHR1cmVdKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQodGhpcy5fZWxlbWVudCwgZXZlbnRUeXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLm9mZlxuICAgICAgICAgWyBtZXRob2QgXVxuICAgICAgICAgKlxuICAgICAgICAgKiBSZW1vdmVzIGFuIEludGVyYWN0RXZlbnQgb3IgRE9NIGV2ZW50IGxpc3RlbmVyXG4gICAgICAgICAqXG4gICAgICAgICAtIGV2ZW50VHlwZSAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAgICAgICAgIC0gbGlzdGVuZXIgICAoZnVuY3Rpb24pIFRoZSBsaXN0ZW5lciBmdW5jdGlvbiB0byBiZSByZW1vdmVkXG4gICAgICAgICAtIHVzZUNhcHR1cmUgKGJvb2xlYW4pICNvcHRpb25hbCB1c2VDYXB0dXJlIGZsYWcgZm9yIHJlbW92ZUV2ZW50TGlzdGVuZXJcbiAgICAgICAgID0gKG9iamVjdCkgVGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgXFwqL1xuICAgICAgICBvZmY6IGZ1bmN0aW9uIChldmVudFR5cGUsIGxpc3RlbmVyLCB1c2VDYXB0dXJlKSB7XG4gICAgICAgICAgICB2YXIgaTtcblxuICAgICAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKGV2ZW50VHlwZSkgJiYgZXZlbnRUeXBlLnNlYXJjaCgnICcpICE9PSAtMSkge1xuICAgICAgICAgICAgICAgIGV2ZW50VHlwZSA9IGV2ZW50VHlwZS50cmltKCkuc3BsaXQoLyArLyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzY29wZS5pc0FycmF5KGV2ZW50VHlwZSkpIHtcbiAgICAgICAgICAgICAgICBmb3IgKGkgPSAwOyBpIDwgZXZlbnRUeXBlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub2ZmKGV2ZW50VHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QoZXZlbnRUeXBlKSkge1xuICAgICAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gZXZlbnRUeXBlKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMub2ZmKHByb3AsIGV2ZW50VHlwZVtwcm9wXSwgbGlzdGVuZXIpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgZXZlbnRMaXN0LFxuICAgICAgICAgICAgICAgIGluZGV4ID0gLTE7XG5cbiAgICAgICAgICAgIC8vIGNvbnZlcnQgdG8gYm9vbGVhblxuICAgICAgICAgICAgdXNlQ2FwdHVyZSA9IHVzZUNhcHR1cmU/IHRydWU6IGZhbHNlO1xuXG4gICAgICAgICAgICBpZiAoZXZlbnRUeXBlID09PSAnd2hlZWwnKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRUeXBlID0gc2NvcGUud2hlZWxFdmVudDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgLy8gaWYgaXQgaXMgYW4gYWN0aW9uIGV2ZW50IHR5cGVcbiAgICAgICAgICAgIGlmIChzY29wZS5jb250YWlucyhzY29wZS5ldmVudFR5cGVzLCBldmVudFR5cGUpKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRMaXN0ID0gdGhpcy5faUV2ZW50c1tldmVudFR5cGVdO1xuXG4gICAgICAgICAgICAgICAgaWYgKGV2ZW50TGlzdCAmJiAoaW5kZXggPSBzY29wZS5pbmRleE9mKGV2ZW50TGlzdCwgbGlzdGVuZXIpKSAhPT0gLTEpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5faUV2ZW50c1tldmVudFR5cGVdLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgLy8gZGVsZWdhdGVkIGV2ZW50XG4gICAgICAgICAgICBlbHNlIGlmICh0aGlzLnNlbGVjdG9yKSB7XG4gICAgICAgICAgICAgICAgdmFyIGRlbGVnYXRlZCA9IHNjb3BlLmRlbGVnYXRlZEV2ZW50c1tldmVudFR5cGVdLFxuICAgICAgICAgICAgICAgICAgICBtYXRjaEZvdW5kID0gZmFsc2U7XG5cbiAgICAgICAgICAgICAgICBpZiAoIWRlbGVnYXRlZCkgeyByZXR1cm4gdGhpczsgfVxuXG4gICAgICAgICAgICAgICAgLy8gY291bnQgZnJvbSBsYXN0IGluZGV4IG9mIGRlbGVnYXRlZCB0byAwXG4gICAgICAgICAgICAgICAgZm9yIChpbmRleCA9IGRlbGVnYXRlZC5zZWxlY3RvcnMubGVuZ3RoIC0gMTsgaW5kZXggPj0gMDsgaW5kZXgtLSkge1xuICAgICAgICAgICAgICAgICAgICAvLyBsb29rIGZvciBtYXRjaGluZyBzZWxlY3RvciBhbmQgY29udGV4dCBOb2RlXG4gICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2luZGV4XSA9PT0gdGhpcy5zZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2luZGV4XSA9PT0gdGhpcy5fY29udGV4dCkge1xuXG4gICAgICAgICAgICAgICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gZGVsZWdhdGVkLmxpc3RlbmVyc1tpbmRleF07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGVhY2ggaXRlbSBvZiB0aGUgbGlzdGVuZXJzIGFycmF5IGlzIGFuIGFycmF5OiBbZnVuY3Rpb24sIHVzZUNhcHR1cmVGbGFnXVxuICAgICAgICAgICAgICAgICAgICAgICAgZm9yIChpID0gbGlzdGVuZXJzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGZuID0gbGlzdGVuZXJzW2ldWzBdLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1c2VDYXAgPSBsaXN0ZW5lcnNbaV1bMV07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBjaGVjayBpZiB0aGUgbGlzdGVuZXIgZnVuY3Rpb25zIGFuZCB1c2VDYXB0dXJlIGZsYWdzIG1hdGNoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKGZuID09PSBsaXN0ZW5lciAmJiB1c2VDYXAgPT09IHVzZUNhcHR1cmUpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gcmVtb3ZlIHRoZSBsaXN0ZW5lciBmcm9tIHRoZSBhcnJheSBvZiBsaXN0ZW5lcnNcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXJzLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBpZiBhbGwgbGlzdGVuZXJzIGZvciB0aGlzIGludGVyYWN0YWJsZSBoYXZlIGJlZW4gcmVtb3ZlZFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgdGhlIGludGVyYWN0YWJsZSBmcm9tIHRoZSBkZWxlZ2F0ZWQgYXJyYXlzXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghbGlzdGVuZXJzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLnNlbGVjdG9ycy5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLmNvbnRleHRzIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVsZWdhdGVkLmxpc3RlbmVycy5zcGxpY2UoaW5kZXgsIDEpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyByZW1vdmUgZGVsZWdhdGUgZnVuY3Rpb24gZnJvbSBjb250ZXh0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIGV2ZW50VHlwZSwgZGVsZWdhdGVMaXN0ZW5lcik7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2NvbnRleHQsIGV2ZW50VHlwZSwgZGVsZWdhdGVVc2VDYXB0dXJlLCB0cnVlKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gcmVtb3ZlIHRoZSBhcnJheXMgaWYgdGhleSBhcmUgZW1wdHlcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmICghZGVsZWdhdGVkLnNlbGVjdG9ycy5sZW5ndGgpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY29wZS5kZWxlZ2F0ZWRFdmVudHNbZXZlbnRUeXBlXSA9IG51bGw7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBvbmx5IHJlbW92ZSBvbmUgbGlzdGVuZXJcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF0Y2hGb3VuZCA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgaWYgKG1hdGNoRm91bmQpIHsgYnJlYWs7IH1cbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIC8vIHJlbW92ZSBsaXN0ZW5lciBmcm9tIHRoaXMgSW50ZXJhdGFibGUncyBlbGVtZW50XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICBldmVudHMucmVtb3ZlKHRoaXMuX2VsZW1lbnQsIGV2ZW50VHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfSxcblxuICAgICAgICAvKlxcXG4gICAgICAgICAqIEludGVyYWN0YWJsZS5zZXRcbiAgICAgICAgIFsgbWV0aG9kIF1cbiAgICAgICAgICpcbiAgICAgICAgICogUmVzZXQgdGhlIG9wdGlvbnMgb2YgdGhpcyBJbnRlcmFjdGFibGVcbiAgICAgICAgIC0gb3B0aW9ucyAob2JqZWN0KSBUaGUgbmV3IHNldHRpbmdzIHRvIGFwcGx5XG4gICAgICAgICA9IChvYmplY3QpIFRoaXMgSW50ZXJhY3RhYmx3XG4gICAgICAgIFxcKi9cbiAgICAgICAgc2V0OiBmdW5jdGlvbiAob3B0aW9ucykge1xuICAgICAgICAgICAgaWYgKCFzY29wZS5pc09iamVjdChvcHRpb25zKSkge1xuICAgICAgICAgICAgICAgIG9wdGlvbnMgPSB7fTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5vcHRpb25zID0gdXRpbHMuZXh0ZW5kKHt9LCBzY29wZS5kZWZhdWx0T3B0aW9ucy5iYXNlKTtcblxuICAgICAgICAgICAgdmFyIGksXG4gICAgICAgICAgICAgICAgYWN0aW9ucyA9IFsnZHJhZycsICdkcm9wJywgJ3Jlc2l6ZScsICdnZXN0dXJlJ10sXG4gICAgICAgICAgICAgICAgbWV0aG9kcyA9IFsnZHJhZ2dhYmxlJywgJ2Ryb3B6b25lJywgJ3Jlc2l6YWJsZScsICdnZXN0dXJhYmxlJ10sXG4gICAgICAgICAgICAgICAgcGVyQWN0aW9ucyA9IHV0aWxzLmV4dGVuZCh1dGlscy5leHRlbmQoe30sIHNjb3BlLmRlZmF1bHRPcHRpb25zLnBlckFjdGlvbiksIG9wdGlvbnNbYWN0aW9uXSB8fCB7fSk7XG5cbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgdmFyIGFjdGlvbiA9IGFjdGlvbnNbaV07XG5cbiAgICAgICAgICAgICAgICB0aGlzLm9wdGlvbnNbYWN0aW9uXSA9IHV0aWxzLmV4dGVuZCh7fSwgc2NvcGUuZGVmYXVsdE9wdGlvbnNbYWN0aW9uXSk7XG5cbiAgICAgICAgICAgICAgICB0aGlzLnNldFBlckFjdGlvbihhY3Rpb24sIHBlckFjdGlvbnMpO1xuXG4gICAgICAgICAgICAgICAgdGhpc1ttZXRob2RzW2ldXShvcHRpb25zW2FjdGlvbl0pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc2V0dGluZ3MgPSBbXG4gICAgICAgICAgICAgICAgICAgICdhY2NlcHQnLCAnYWN0aW9uQ2hlY2tlcicsICdhbGxvd0Zyb20nLCAnZGVsdGFTb3VyY2UnLFxuICAgICAgICAgICAgICAgICAgICAnZHJvcENoZWNrZXInLCAnaWdub3JlRnJvbScsICdvcmlnaW4nLCAncHJldmVudERlZmF1bHQnLFxuICAgICAgICAgICAgICAgICAgICAncmVjdENoZWNrZXInXG4gICAgICAgICAgICAgICAgXTtcblxuICAgICAgICAgICAgZm9yIChpID0gMCwgbGVuID0gc2V0dGluZ3MubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgICAgICB2YXIgc2V0dGluZyA9IHNldHRpbmdzW2ldO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5vcHRpb25zW3NldHRpbmddID0gc2NvcGUuZGVmYXVsdE9wdGlvbnMuYmFzZVtzZXR0aW5nXTtcblxuICAgICAgICAgICAgICAgIGlmIChzZXR0aW5nIGluIG9wdGlvbnMpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpc1tzZXR0aW5nXShvcHRpb25zW3NldHRpbmddKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzO1xuICAgICAgICB9LFxuXG4gICAgICAgIC8qXFxcbiAgICAgICAgICogSW50ZXJhY3RhYmxlLnVuc2V0XG4gICAgICAgICBbIG1ldGhvZCBdXG4gICAgICAgICAqXG4gICAgICAgICAqIFJlbW92ZSB0aGlzIGludGVyYWN0YWJsZSBmcm9tIHRoZSBsaXN0IG9mIGludGVyYWN0YWJsZXMgYW5kIHJlbW92ZVxuICAgICAgICAgKiBpdCdzIGRyYWcsIGRyb3AsIHJlc2l6ZSBhbmQgZ2VzdHVyZSBjYXBhYmlsaXRpZXNcbiAgICAgICAgICpcbiAgICAgICAgID0gKG9iamVjdCkgQGludGVyYWN0XG4gICAgICAgIFxcKi9cbiAgICAgICAgdW5zZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fZWxlbWVudCwgJ2FsbCcpO1xuXG4gICAgICAgICAgICBpZiAoIXNjb3BlLmlzU3RyaW5nKHRoaXMuc2VsZWN0b3IpKSB7XG4gICAgICAgICAgICAgICAgZXZlbnRzLnJlbW92ZSh0aGlzLCAnYWxsJyk7XG4gICAgICAgICAgICAgICAgaWYgKHRoaXMub3B0aW9ucy5zdHlsZUN1cnNvcikge1xuICAgICAgICAgICAgICAgICAgICB0aGlzLl9lbGVtZW50LnN0eWxlLmN1cnNvciA9ICcnO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIHJlbW92ZSBkZWxlZ2F0ZWQgZXZlbnRzXG4gICAgICAgICAgICAgICAgZm9yICh2YXIgdHlwZSBpbiBzY29wZS5kZWxlZ2F0ZWRFdmVudHMpIHtcbiAgICAgICAgICAgICAgICAgICAgdmFyIGRlbGVnYXRlZCA9IHNjb3BlLmRlbGVnYXRlZEV2ZW50c1t0eXBlXTtcblxuICAgICAgICAgICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGRlbGVnYXRlZC5zZWxlY3RvcnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChkZWxlZ2F0ZWQuc2VsZWN0b3JzW2ldID09PSB0aGlzLnNlbGVjdG9yXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgZGVsZWdhdGVkLmNvbnRleHRzW2ldID09PSB0aGlzLl9jb250ZXh0KSB7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuc2VsZWN0b3JzLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQuY29udGV4dHMgLnNwbGljZShpLCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWxlZ2F0ZWQubGlzdGVuZXJzLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIC8vIHJlbW92ZSB0aGUgYXJyYXlzIGlmIHRoZXkgYXJlIGVtcHR5XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCFkZWxlZ2F0ZWQuc2VsZWN0b3JzLmxlbmd0aCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzY29wZS5kZWxlZ2F0ZWRFdmVudHNbdHlwZV0gPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnRzLnJlbW92ZSh0aGlzLl9jb250ZXh0LCB0eXBlLCBkZWxlZ2F0ZUxpc3RlbmVyKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUodGhpcy5fY29udGV4dCwgdHlwZSwgZGVsZWdhdGVVc2VDYXB0dXJlLCB0cnVlKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMuZHJvcHpvbmUoZmFsc2UpO1xuXG4gICAgICAgICAgICBzY29wZS5pbnRlcmFjdGFibGVzLnNwbGljZShzY29wZS5pbmRleE9mKHNjb3BlLmludGVyYWN0YWJsZXMsIHRoaXMpLCAxKTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIEludGVyYWN0YWJsZS5wcm90b3R5cGUuc25hcCA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuc25hcCxcbiAgICAgICAgICdJbnRlcmFjdGFibGUjc25hcCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBzbmFwcGluZyBhdCBodHRwOi8vaW50ZXJhY3Rqcy5pby9kb2NzL3NuYXBwaW5nJyk7XG4gICAgSW50ZXJhY3RhYmxlLnByb3RvdHlwZS5yZXN0cmljdCA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUucmVzdHJpY3QsXG4gICAgICAgICAnSW50ZXJhY3RhYmxlI3Jlc3RyaWN0IGlzIGRlcHJlY2F0ZWQuIFNlZSB0aGUgbmV3IGRvY3VtZW50YXRpb24gZm9yIHJlc3RpY3RpbmcgYXQgaHR0cDovL2ludGVyYWN0anMuaW8vZG9jcy9yZXN0cmljdGlvbicpO1xuICAgIEludGVyYWN0YWJsZS5wcm90b3R5cGUuaW5lcnRpYSA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuaW5lcnRpYSxcbiAgICAgICAgICdJbnRlcmFjdGFibGUjaW5lcnRpYSBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBpbmVydGlhIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvaW5lcnRpYScpO1xuICAgIEludGVyYWN0YWJsZS5wcm90b3R5cGUuYXV0b1Njcm9sbCA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuYXV0b1Njcm9sbCxcbiAgICAgICAgICdJbnRlcmFjdGFibGUjYXV0b1Njcm9sbCBpcyBkZXByZWNhdGVkLiBTZWUgdGhlIG5ldyBkb2N1bWVudGF0aW9uIGZvciBhdXRvU2Nyb2xsIGF0IGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI2F1dG9zY3JvbGwnKTtcbiAgICBJbnRlcmFjdGFibGUucHJvdG90eXBlLnNxdWFyZVJlc2l6ZSA9IHV0aWxzLndhcm5PbmNlKEludGVyYWN0YWJsZS5wcm90b3R5cGUuc3F1YXJlUmVzaXplLFxuICAgICAgICAgJ0ludGVyYWN0YWJsZSNzcXVhcmVSZXNpemUgaXMgZGVwcmVjYXRlZC4gU2VlIGh0dHA6Ly9pbnRlcmFjdGpzLmlvL2RvY3MvI3Jlc2l6ZS1zcXVhcmUnKTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5pc1NldFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBDaGVjayBpZiBhbiBlbGVtZW50IGhhcyBiZWVuIHNldFxuICAgICAtIGVsZW1lbnQgKEVsZW1lbnQpIFRoZSBFbGVtZW50IGJlaW5nIHNlYXJjaGVkIGZvclxuICAgICA9IChib29sZWFuKSBJbmRpY2F0ZXMgaWYgdGhlIGVsZW1lbnQgb3IgQ1NTIHNlbGVjdG9yIHdhcyBwcmV2aW91c2x5IHBhc3NlZCB0byBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5pc1NldCA9IGZ1bmN0aW9uKGVsZW1lbnQsIG9wdGlvbnMpIHtcbiAgICAgICAgcmV0dXJuIHNjb3BlLmludGVyYWN0YWJsZXMuaW5kZXhPZkVsZW1lbnQoZWxlbWVudCwgb3B0aW9ucyAmJiBvcHRpb25zLmNvbnRleHQpICE9PSAtMTtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0Lm9uXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIEFkZHMgYSBnbG9iYWwgbGlzdGVuZXIgZm9yIGFuIEludGVyYWN0RXZlbnQgb3IgYWRkcyBhIERPTSBldmVudCB0b1xuICAgICAqIGBkb2N1bWVudGBcbiAgICAgKlxuICAgICAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRvIGxpc3RlbiBmb3JcbiAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGZ1bmN0aW9uIHRvIGJlIGNhbGxlZCBvbiB0aGUgZ2l2ZW4gZXZlbnQocylcbiAgICAgLSB1c2VDYXB0dXJlIChib29sZWFuKSAjb3B0aW9uYWwgdXNlQ2FwdHVyZSBmbGFnIGZvciBhZGRFdmVudExpc3RlbmVyXG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICBcXCovXG4gICAgaW50ZXJhY3Qub24gPSBmdW5jdGlvbiAodHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKHR5cGUpICYmIHR5cGUuc2VhcmNoKCcgJykgIT09IC0xKSB7XG4gICAgICAgICAgICB0eXBlID0gdHlwZS50cmltKCkuc3BsaXQoLyArLyk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNBcnJheSh0eXBlKSkge1xuICAgICAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0eXBlLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaW50ZXJhY3Qub24odHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QodHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gdHlwZSkge1xuICAgICAgICAgICAgICAgIGludGVyYWN0Lm9uKHByb3AsIHR5cGVbcHJvcF0sIGxpc3RlbmVyKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gaWYgaXQgaXMgYW4gSW50ZXJhY3RFdmVudCB0eXBlLCBhZGQgbGlzdGVuZXIgdG8gZ2xvYmFsRXZlbnRzXG4gICAgICAgIGlmIChzY29wZS5jb250YWlucyhzY29wZS5ldmVudFR5cGVzLCB0eXBlKSkge1xuICAgICAgICAgICAgLy8gaWYgdGhpcyB0eXBlIG9mIGV2ZW50IHdhcyBuZXZlciBib3VuZFxuICAgICAgICAgICAgaWYgKCFzY29wZS5nbG9iYWxFdmVudHNbdHlwZV0pIHtcbiAgICAgICAgICAgICAgICBzY29wZS5nbG9iYWxFdmVudHNbdHlwZV0gPSBbbGlzdGVuZXJdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgc2NvcGUuZ2xvYmFsRXZlbnRzW3R5cGVdLnB1c2gobGlzdGVuZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIC8vIElmIG5vbiBJbnRlcmFjdEV2ZW50IHR5cGUsIGFkZEV2ZW50TGlzdGVuZXIgdG8gZG9jdW1lbnRcbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBldmVudHMuYWRkKHNjb3BlLmRvY3VtZW50LCB0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5vZmZcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmVtb3ZlcyBhIGdsb2JhbCBJbnRlcmFjdEV2ZW50IGxpc3RlbmVyIG9yIERPTSBldmVudCBmcm9tIGBkb2N1bWVudGBcbiAgICAgKlxuICAgICAtIHR5cGUgICAgICAgKHN0cmluZyB8IGFycmF5IHwgb2JqZWN0KSBUaGUgdHlwZXMgb2YgZXZlbnRzIHRoYXQgd2VyZSBsaXN0ZW5lZCBmb3JcbiAgICAgLSBsaXN0ZW5lciAgIChmdW5jdGlvbikgVGhlIGxpc3RlbmVyIGZ1bmN0aW9uIHRvIGJlIHJlbW92ZWRcbiAgICAgLSB1c2VDYXB0dXJlIChib29sZWFuKSAjb3B0aW9uYWwgdXNlQ2FwdHVyZSBmbGFnIGZvciByZW1vdmVFdmVudExpc3RlbmVyXG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICAgXFwqL1xuICAgIGludGVyYWN0Lm9mZiA9IGZ1bmN0aW9uICh0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNTdHJpbmcodHlwZSkgJiYgdHlwZS5zZWFyY2goJyAnKSAhPT0gLTEpIHtcbiAgICAgICAgICAgIHR5cGUgPSB0eXBlLnRyaW0oKS5zcGxpdCgvICsvKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChzY29wZS5pc0FycmF5KHR5cGUpKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHR5cGUubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICBpbnRlcmFjdC5vZmYodHlwZVtpXSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QodHlwZSkpIHtcbiAgICAgICAgICAgIGZvciAodmFyIHByb3AgaW4gdHlwZSkge1xuICAgICAgICAgICAgICAgIGludGVyYWN0Lm9mZihwcm9wLCB0eXBlW3Byb3BdLCBsaXN0ZW5lcik7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghc2NvcGUuY29udGFpbnMoc2NvcGUuZXZlbnRUeXBlcywgdHlwZSkpIHtcbiAgICAgICAgICAgIGV2ZW50cy5yZW1vdmUoc2NvcGUuZG9jdW1lbnQsIHR5cGUsIGxpc3RlbmVyLCB1c2VDYXB0dXJlKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZhciBpbmRleDtcblxuICAgICAgICAgICAgaWYgKHR5cGUgaW4gc2NvcGUuZ2xvYmFsRXZlbnRzXG4gICAgICAgICAgICAgICAgJiYgKGluZGV4ID0gc2NvcGUuaW5kZXhPZihzY29wZS5nbG9iYWxFdmVudHNbdHlwZV0sIGxpc3RlbmVyKSkgIT09IC0xKSB7XG4gICAgICAgICAgICAgICAgc2NvcGUuZ2xvYmFsRXZlbnRzW3R5cGVdLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5lbmFibGVEcmFnZ2luZ1xuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBEZXByZWNhdGVkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZHJhZ2dpbmcgaXMgZW5hYmxlZCBmb3IgYW55IEludGVyYWN0YWJsZXNcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChib29sZWFuKSAjb3B0aW9uYWwgYHRydWVgIHRvIGFsbG93IHRoZSBhY3Rpb247IGBmYWxzZWAgdG8gZGlzYWJsZSBhY3Rpb24gZm9yIGFsbCBJbnRlcmFjdGFibGVzXG4gICAgID0gKGJvb2xlYW4gfCBvYmplY3QpIFRoZSBjdXJyZW50IHNldHRpbmcgb3IgaW50ZXJhY3RcbiAgICBcXCovXG4gICAgaW50ZXJhY3QuZW5hYmxlRHJhZ2dpbmcgPSB1dGlscy53YXJuT25jZShmdW5jdGlvbiAobmV3VmFsdWUpIHtcbiAgICAgICAgaWYgKG5ld1ZhbHVlICE9PSBudWxsICYmIG5ld1ZhbHVlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICAgIHNjb3BlLmFjdGlvbklzRW5hYmxlZC5kcmFnID0gbmV3VmFsdWU7XG5cbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4gc2NvcGUuYWN0aW9uSXNFbmFibGVkLmRyYWc7XG4gICAgfSwgJ2ludGVyYWN0LmVuYWJsZURyYWdnaW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LmVuYWJsZVJlc2l6aW5nXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIERlcHJlY2F0ZWQuXG4gICAgICpcbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgd2hldGhlciByZXNpemluZyBpcyBlbmFibGVkIGZvciBhbnkgSW50ZXJhY3RhYmxlc1xuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKGJvb2xlYW4pICNvcHRpb25hbCBgdHJ1ZWAgdG8gYWxsb3cgdGhlIGFjdGlvbjsgYGZhbHNlYCB0byBkaXNhYmxlIGFjdGlvbiBmb3IgYWxsIEludGVyYWN0YWJsZXNcbiAgICAgPSAoYm9vbGVhbiB8IG9iamVjdCkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5lbmFibGVSZXNpemluZyA9IHV0aWxzLndhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAobmV3VmFsdWUgIT09IG51bGwgJiYgbmV3VmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgc2NvcGUuYWN0aW9uSXNFbmFibGVkLnJlc2l6ZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gaW50ZXJhY3Q7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHNjb3BlLmFjdGlvbklzRW5hYmxlZC5yZXNpemU7XG4gICAgfSwgJ2ludGVyYWN0LmVuYWJsZVJlc2l6aW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LmVuYWJsZUdlc3R1cmluZ1xuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBEZXByZWNhdGVkLlxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHdoZXRoZXIgZ2VzdHVyaW5nIGlzIGVuYWJsZWQgZm9yIGFueSBJbnRlcmFjdGFibGVzXG4gICAgICpcbiAgICAgLSBuZXdWYWx1ZSAoYm9vbGVhbikgI29wdGlvbmFsIGB0cnVlYCB0byBhbGxvdyB0aGUgYWN0aW9uOyBgZmFsc2VgIHRvIGRpc2FibGUgYWN0aW9uIGZvciBhbGwgSW50ZXJhY3RhYmxlc1xuICAgICA9IChib29sZWFuIHwgb2JqZWN0KSBUaGUgY3VycmVudCBzZXR0aW5nIG9yIGludGVyYWN0XG4gICAgXFwqL1xuICAgIGludGVyYWN0LmVuYWJsZUdlc3R1cmluZyA9IHV0aWxzLndhcm5PbmNlKGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAobmV3VmFsdWUgIT09IG51bGwgJiYgbmV3VmFsdWUgIT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgICAgc2NvcGUuYWN0aW9uSXNFbmFibGVkLmdlc3R1cmUgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzY29wZS5hY3Rpb25Jc0VuYWJsZWQuZ2VzdHVyZTtcbiAgICB9LCAnaW50ZXJhY3QuZW5hYmxlR2VzdHVyaW5nIGlzIGRlcHJlY2F0ZWQgYW5kIHdpbGwgc29vbiBiZSByZW1vdmVkLicpO1xuXG4gICAgaW50ZXJhY3QuZXZlbnRUeXBlcyA9IHNjb3BlLmV2ZW50VHlwZXM7XG5cbiAgICAvKlxcXG4gICAgICogaW50ZXJhY3QuZGVidWdcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmV0dXJucyBkZWJ1Z2dpbmcgZGF0YVxuICAgICA9IChvYmplY3QpIEFuIG9iamVjdCB3aXRoIHByb3BlcnRpZXMgdGhhdCBvdXRsaW5lIHRoZSBjdXJyZW50IHN0YXRlIGFuZCBleHBvc2UgaW50ZXJuYWwgZnVuY3Rpb25zIGFuZCB2YXJpYWJsZXNcbiAgICBcXCovXG4gICAgaW50ZXJhY3QuZGVidWcgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBpbnRlcmFjdGlvbiA9IHNjb3BlLmludGVyYWN0aW9uc1swXSB8fCBuZXcgSW50ZXJhY3Rpb24oKTtcblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb25zICAgICAgICAgIDogc2NvcGUuaW50ZXJhY3Rpb25zLFxuICAgICAgICAgICAgdGFyZ2V0ICAgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24udGFyZ2V0LFxuICAgICAgICAgICAgZHJhZ2dpbmcgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24uZHJhZ2dpbmcsXG4gICAgICAgICAgICByZXNpemluZyAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5yZXNpemluZyxcbiAgICAgICAgICAgIGdlc3R1cmluZyAgICAgICAgICAgICA6IGludGVyYWN0aW9uLmdlc3R1cmluZyxcbiAgICAgICAgICAgIHByZXBhcmVkICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXBhcmVkLFxuICAgICAgICAgICAgbWF0Y2hlcyAgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ubWF0Y2hlcyxcbiAgICAgICAgICAgIG1hdGNoRWxlbWVudHMgICAgICAgICA6IGludGVyYWN0aW9uLm1hdGNoRWxlbWVudHMsXG5cbiAgICAgICAgICAgIHByZXZDb29yZHMgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXZDb29yZHMsXG4gICAgICAgICAgICBzdGFydENvb3JkcyAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5zdGFydENvb3JkcyxcblxuICAgICAgICAgICAgcG9pbnRlcklkcyAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ucG9pbnRlcklkcyxcbiAgICAgICAgICAgIHBvaW50ZXJzICAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICAgICAgYWRkUG9pbnRlciAgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLmFkZFBvaW50ZXIsXG4gICAgICAgICAgICByZW1vdmVQb2ludGVyICAgICAgICAgOiBzY29wZS5saXN0ZW5lcnMucmVtb3ZlUG9pbnRlcixcbiAgICAgICAgICAgIHJlY29yZFBvaW50ZXIgICAgICAgICA6IHNjb3BlLmxpc3RlbmVycy5yZWNvcmRQb2ludGVyLFxuXG4gICAgICAgICAgICBzbmFwICAgICAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5zbmFwU3RhdHVzLFxuICAgICAgICAgICAgcmVzdHJpY3QgICAgICAgICAgICAgIDogaW50ZXJhY3Rpb24ucmVzdHJpY3RTdGF0dXMsXG4gICAgICAgICAgICBpbmVydGlhICAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzLFxuXG4gICAgICAgICAgICBkb3duVGltZSAgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF0sXG4gICAgICAgICAgICBkb3duRXZlbnQgICAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duRXZlbnQsXG4gICAgICAgICAgICBkb3duUG9pbnRlciAgICAgICAgICAgOiBpbnRlcmFjdGlvbi5kb3duUG9pbnRlcixcbiAgICAgICAgICAgIHByZXZFdmVudCAgICAgICAgICAgICA6IGludGVyYWN0aW9uLnByZXZFdmVudCxcblxuICAgICAgICAgICAgSW50ZXJhY3RhYmxlICAgICAgICAgIDogSW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgaW50ZXJhY3RhYmxlcyAgICAgICAgIDogc2NvcGUuaW50ZXJhY3RhYmxlcyxcbiAgICAgICAgICAgIHBvaW50ZXJJc0Rvd24gICAgICAgICA6IGludGVyYWN0aW9uLnBvaW50ZXJJc0Rvd24sXG4gICAgICAgICAgICBkZWZhdWx0T3B0aW9ucyAgICAgICAgOiBzY29wZS5kZWZhdWx0T3B0aW9ucyxcbiAgICAgICAgICAgIGRlZmF1bHRBY3Rpb25DaGVja2VyICA6IGRlZmF1bHRBY3Rpb25DaGVja2VyLFxuXG4gICAgICAgICAgICBhY3Rpb25DdXJzb3JzICAgICAgICAgOiBzY29wZS5hY3Rpb25DdXJzb3JzLFxuICAgICAgICAgICAgZHJhZ01vdmUgICAgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLmRyYWdNb3ZlLFxuICAgICAgICAgICAgcmVzaXplTW92ZSAgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnJlc2l6ZU1vdmUsXG4gICAgICAgICAgICBnZXN0dXJlTW92ZSAgICAgICAgICAgOiBzY29wZS5saXN0ZW5lcnMuZ2VzdHVyZU1vdmUsXG4gICAgICAgICAgICBwb2ludGVyVXAgICAgICAgICAgICAgOiBzY29wZS5saXN0ZW5lcnMucG9pbnRlclVwLFxuICAgICAgICAgICAgcG9pbnRlckRvd24gICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJEb3duLFxuICAgICAgICAgICAgcG9pbnRlck1vdmUgICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJNb3ZlLFxuICAgICAgICAgICAgcG9pbnRlckhvdmVyICAgICAgICAgIDogc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJIb3ZlcixcblxuICAgICAgICAgICAgZXZlbnRUeXBlcyAgICAgICAgICAgIDogc2NvcGUuZXZlbnRUeXBlcyxcblxuICAgICAgICAgICAgZXZlbnRzICAgICAgICAgICAgICAgIDogZXZlbnRzLFxuICAgICAgICAgICAgZ2xvYmFsRXZlbnRzICAgICAgICAgIDogc2NvcGUuZ2xvYmFsRXZlbnRzLFxuICAgICAgICAgICAgZGVsZWdhdGVkRXZlbnRzICAgICAgIDogc2NvcGUuZGVsZWdhdGVkRXZlbnRzXG4gICAgICAgIH07XG4gICAgfTtcblxuICAgIC8vIGV4cG9zZSB0aGUgZnVuY3Rpb25zIHVzZWQgdG8gY2FsY3VsYXRlIG11bHRpLXRvdWNoIHByb3BlcnRpZXNcbiAgICBpbnRlcmFjdC5nZXRUb3VjaEF2ZXJhZ2UgID0gdXRpbHMudG91Y2hBdmVyYWdlO1xuICAgIGludGVyYWN0LmdldFRvdWNoQkJveCAgICAgPSB1dGlscy50b3VjaEJCb3g7XG4gICAgaW50ZXJhY3QuZ2V0VG91Y2hEaXN0YW5jZSA9IHV0aWxzLnRvdWNoRGlzdGFuY2U7XG4gICAgaW50ZXJhY3QuZ2V0VG91Y2hBbmdsZSAgICA9IHV0aWxzLnRvdWNoQW5nbGU7XG5cbiAgICBpbnRlcmFjdC5nZXRFbGVtZW50UmVjdCAgID0gc2NvcGUuZ2V0RWxlbWVudFJlY3Q7XG4gICAgaW50ZXJhY3QubWF0Y2hlc1NlbGVjdG9yICA9IHNjb3BlLm1hdGNoZXNTZWxlY3RvcjtcbiAgICBpbnRlcmFjdC5jbG9zZXN0ICAgICAgICAgID0gc2NvcGUuY2xvc2VzdDtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5tYXJnaW5cbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHRoZSBtYXJnaW4gZm9yIGF1dG9jaGVjayByZXNpemluZyB1c2VkIGluXG4gICAgICogQEludGVyYWN0YWJsZS5nZXRBY3Rpb24uIFRoYXQgaXMgdGhlIGRpc3RhbmNlIGZyb20gdGhlIGJvdHRvbSBhbmQgcmlnaHRcbiAgICAgKiBlZGdlcyBvZiBhbiBlbGVtZW50IGNsaWNraW5nIGluIHdoaWNoIHdpbGwgc3RhcnQgcmVzaXppbmdcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChudW1iZXIpICNvcHRpb25hbFxuICAgICA9IChudW1iZXIgfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgbWFyZ2luIHZhbHVlIG9yIGludGVyYWN0XG4gICAgXFwqL1xuICAgIGludGVyYWN0Lm1hcmdpbiA9IGZ1bmN0aW9uIChuZXd2YWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNOdW1iZXIobmV3dmFsdWUpKSB7XG4gICAgICAgICAgICBzY29wZS5tYXJnaW4gPSBuZXd2YWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzY29wZS5tYXJnaW47XG4gICAgfTtcblxuICAgIC8qXFxcbiAgICAgKiBpbnRlcmFjdC5zdXBwb3J0c1RvdWNoXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICA9IChib29sZWFuKSBXaGV0aGVyIG9yIG5vdCB0aGUgYnJvd3NlciBzdXBwb3J0cyB0b3VjaCBpbnB1dFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5zdXBwb3J0c1RvdWNoID0gZnVuY3Rpb24gKCkge1xuICAgICAgICByZXR1cm4gYnJvd3Nlci5zdXBwb3J0c1RvdWNoO1xuICAgIH07XG5cbiAgICAvKlxcXG4gICAgICogaW50ZXJhY3Quc3VwcG9ydHNQb2ludGVyRXZlbnRcbiAgICAgWyBtZXRob2QgXVxuICAgICAqXG4gICAgID0gKGJvb2xlYW4pIFdoZXRoZXIgb3Igbm90IHRoZSBicm93c2VyIHN1cHBvcnRzIFBvaW50ZXJFdmVudHNcbiAgICBcXCovXG4gICAgaW50ZXJhY3Quc3VwcG9ydHNQb2ludGVyRXZlbnQgPSBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiBicm93c2VyLnN1cHBvcnRzUG9pbnRlckV2ZW50O1xuICAgIH07XG5cbiAgICAvKlxcXG4gICAgICogaW50ZXJhY3Quc3RvcFxuICAgICBbIG1ldGhvZCBdXG4gICAgICpcbiAgICAgKiBDYW5jZWxzIGFsbCBpbnRlcmFjdGlvbnMgKGVuZCBldmVudHMgYXJlIG5vdCBmaXJlZClcbiAgICAgKlxuICAgICAtIGV2ZW50IChFdmVudCkgQW4gZXZlbnQgb24gd2hpY2ggdG8gY2FsbCBwcmV2ZW50RGVmYXVsdCgpXG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICBcXCovXG4gICAgaW50ZXJhY3Quc3RvcCA9IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBmb3IgKHZhciBpID0gc2NvcGUuaW50ZXJhY3Rpb25zLmxlbmd0aCAtIDE7IGkgPiAwOyBpLS0pIHtcbiAgICAgICAgICAgIHNjb3BlLmludGVyYWN0aW9uc1tpXS5zdG9wKGV2ZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LmR5bmFtaWNEcm9wXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFJldHVybnMgb3Igc2V0cyB3aGV0aGVyIHRoZSBkaW1lbnNpb25zIG9mIGRyb3B6b25lIGVsZW1lbnRzIGFyZVxuICAgICAqIGNhbGN1bGF0ZWQgb24gZXZlcnkgZHJhZ21vdmUgb3Igb25seSBvbiBkcmFnc3RhcnQgZm9yIHRoZSBkZWZhdWx0XG4gICAgICogZHJvcENoZWNrZXJcbiAgICAgKlxuICAgICAtIG5ld1ZhbHVlIChib29sZWFuKSAjb3B0aW9uYWwgVHJ1ZSB0byBjaGVjayBvbiBlYWNoIG1vdmUuIEZhbHNlIHRvIGNoZWNrIG9ubHkgYmVmb3JlIHN0YXJ0XG4gICAgID0gKGJvb2xlYW4gfCBpbnRlcmFjdCkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5keW5hbWljRHJvcCA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNCb29sKG5ld1ZhbHVlKSkge1xuICAgICAgICAgICAgLy9pZiAoZHJhZ2dpbmcgJiYgZHluYW1pY0Ryb3AgIT09IG5ld1ZhbHVlICYmICFuZXdWYWx1ZSkge1xuICAgICAgICAgICAgICAgIC8vY2FsY1JlY3RzKGRyb3B6b25lcyk7XG4gICAgICAgICAgICAvL31cblxuICAgICAgICAgICAgc2NvcGUuZHluYW1pY0Ryb3AgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiBzY29wZS5keW5hbWljRHJvcDtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0LnBvaW50ZXJNb3ZlVG9sZXJhbmNlXG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKiBSZXR1cm5zIG9yIHNldHMgdGhlIGRpc3RhbmNlIHRoZSBwb2ludGVyIG11c3QgYmUgbW92ZWQgYmVmb3JlIGFuIGFjdGlvblxuICAgICAqIHNlcXVlbmNlIG9jY3Vycy4gVGhpcyBhbHNvIGFmZmVjdHMgdG9sZXJhbmNlIGZvciB0YXAgZXZlbnRzLlxuICAgICAqXG4gICAgIC0gbmV3VmFsdWUgKG51bWJlcikgI29wdGlvbmFsIFRoZSBtb3ZlbWVudCBmcm9tIHRoZSBzdGFydCBwb3NpdGlvbiBtdXN0IGJlIGdyZWF0ZXIgdGhhbiB0aGlzIHZhbHVlXG4gICAgID0gKG51bWJlciB8IEludGVyYWN0YWJsZSkgVGhlIGN1cnJlbnQgc2V0dGluZyBvciBpbnRlcmFjdFxuICAgIFxcKi9cbiAgICBpbnRlcmFjdC5wb2ludGVyTW92ZVRvbGVyYW5jZSA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICBzY29wZS5wb2ludGVyTW92ZVRvbGVyYW5jZSA9IG5ld1ZhbHVlO1xuXG4gICAgICAgICAgICByZXR1cm4gdGhpcztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBzY29wZS5wb2ludGVyTW92ZVRvbGVyYW5jZTtcbiAgICB9O1xuXG4gICAgLypcXFxuICAgICAqIGludGVyYWN0Lm1heEludGVyYWN0aW9uc1xuICAgICBbIG1ldGhvZCBdXG4gICAgICoqXG4gICAgICogUmV0dXJucyBvciBzZXRzIHRoZSBtYXhpbXVtIG51bWJlciBvZiBjb25jdXJyZW50IGludGVyYWN0aW9ucyBhbGxvd2VkLlxuICAgICAqIEJ5IGRlZmF1bHQgb25seSAxIGludGVyYWN0aW9uIGlzIGFsbG93ZWQgYXQgYSB0aW1lIChmb3IgYmFja3dhcmRzXG4gICAgICogY29tcGF0aWJpbGl0eSkuIFRvIGFsbG93IG11bHRpcGxlIGludGVyYWN0aW9ucyBvbiB0aGUgc2FtZSBJbnRlcmFjdGFibGVzXG4gICAgICogYW5kIGVsZW1lbnRzLCB5b3UgbmVlZCB0byBlbmFibGUgaXQgaW4gdGhlIGRyYWdnYWJsZSwgcmVzaXphYmxlIGFuZFxuICAgICAqIGdlc3R1cmFibGUgYCdtYXgnYCBhbmQgYCdtYXhQZXJFbGVtZW50J2Agb3B0aW9ucy5cbiAgICAgKipcbiAgICAgLSBuZXdWYWx1ZSAobnVtYmVyKSAjb3B0aW9uYWwgQW55IG51bWJlci4gbmV3VmFsdWUgPD0gMCBtZWFucyBubyBpbnRlcmFjdGlvbnMuXG4gICAgXFwqL1xuICAgIGludGVyYWN0Lm1heEludGVyYWN0aW9ucyA9IGZ1bmN0aW9uIChuZXdWYWx1ZSkge1xuICAgICAgICBpZiAoc2NvcGUuaXNOdW1iZXIobmV3VmFsdWUpKSB7XG4gICAgICAgICAgICBzY29wZS5tYXhJbnRlcmFjdGlvbnMgPSBuZXdWYWx1ZTtcblxuICAgICAgICAgICAgcmV0dXJuIHRoaXM7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gc2NvcGUubWF4SW50ZXJhY3Rpb25zO1xuICAgIH07XG5cbiAgICBpbnRlcmFjdC5jcmVhdGVTbmFwR3JpZCA9IGZ1bmN0aW9uIChncmlkKSB7XG4gICAgICAgIHJldHVybiBmdW5jdGlvbiAoeCwgeSkge1xuICAgICAgICAgICAgdmFyIG9mZnNldFggPSAwLFxuICAgICAgICAgICAgICAgIG9mZnNldFkgPSAwO1xuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaXNPYmplY3QoZ3JpZC5vZmZzZXQpKSB7XG4gICAgICAgICAgICAgICAgb2Zmc2V0WCA9IGdyaWQub2Zmc2V0Lng7XG4gICAgICAgICAgICAgICAgb2Zmc2V0WSA9IGdyaWQub2Zmc2V0Lnk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHZhciBncmlkeCA9IE1hdGgucm91bmQoKHggLSBvZmZzZXRYKSAvIGdyaWQueCksXG4gICAgICAgICAgICAgICAgZ3JpZHkgPSBNYXRoLnJvdW5kKCh5IC0gb2Zmc2V0WSkgLyBncmlkLnkpLFxuXG4gICAgICAgICAgICAgICAgbmV3WCA9IGdyaWR4ICogZ3JpZC54ICsgb2Zmc2V0WCxcbiAgICAgICAgICAgICAgICBuZXdZID0gZ3JpZHkgKiBncmlkLnkgKyBvZmZzZXRZO1xuXG4gICAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgICAgIHg6IG5ld1gsXG4gICAgICAgICAgICAgICAgeTogbmV3WSxcbiAgICAgICAgICAgICAgICByYW5nZTogZ3JpZC5yYW5nZVxuICAgICAgICAgICAgfTtcbiAgICAgICAgfTtcbiAgICB9O1xuXG4gICAgZnVuY3Rpb24gZW5kQWxsSW50ZXJhY3Rpb25zIChldmVudCkge1xuICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNjb3BlLmludGVyYWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3Rpb25zW2ldLnBvaW50ZXJFbmQoZXZlbnQsIGV2ZW50KTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGZ1bmN0aW9uIGxpc3RlblRvRG9jdW1lbnQgKGRvYykge1xuICAgICAgICBpZiAoc2NvcGUuY29udGFpbnMoc2NvcGUuZG9jdW1lbnRzLCBkb2MpKSB7IHJldHVybjsgfVxuXG4gICAgICAgIHZhciB3aW4gPSBkb2MuZGVmYXVsdFZpZXcgfHwgZG9jLnBhcmVudFdpbmRvdztcblxuICAgICAgICAvLyBhZGQgZGVsZWdhdGUgZXZlbnQgbGlzdGVuZXJcbiAgICAgICAgZm9yICh2YXIgZXZlbnRUeXBlIGluIHNjb3BlLmRlbGVnYXRlZEV2ZW50cykge1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsIGV2ZW50VHlwZSwgZGVsZWdhdGVMaXN0ZW5lcik7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgZXZlbnRUeXBlLCBkZWxlZ2F0ZVVzZUNhcHR1cmUsIHRydWUpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHNjb3BlLlBvaW50ZXJFdmVudCkge1xuICAgICAgICAgICAgaWYgKHNjb3BlLlBvaW50ZXJFdmVudCA9PT0gd2luLk1TUG9pbnRlckV2ZW50KSB7XG4gICAgICAgICAgICAgICAgc2NvcGUucEV2ZW50VHlwZXMgPSB7XG4gICAgICAgICAgICAgICAgICAgIHVwOiAnTVNQb2ludGVyVXAnLCBkb3duOiAnTVNQb2ludGVyRG93bicsIG92ZXI6ICdtb3VzZW92ZXInLFxuICAgICAgICAgICAgICAgICAgICBvdXQ6ICdtb3VzZW91dCcsIG1vdmU6ICdNU1BvaW50ZXJNb3ZlJywgY2FuY2VsOiAnTVNQb2ludGVyQ2FuY2VsJyB9O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgc2NvcGUucEV2ZW50VHlwZXMgPSB7XG4gICAgICAgICAgICAgICAgICAgIHVwOiAncG9pbnRlcnVwJywgZG93bjogJ3BvaW50ZXJkb3duJywgb3ZlcjogJ3BvaW50ZXJvdmVyJyxcbiAgICAgICAgICAgICAgICAgICAgb3V0OiAncG9pbnRlcm91dCcsIG1vdmU6ICdwb2ludGVybW92ZScsIGNhbmNlbDogJ3BvaW50ZXJjYW5jZWwnIH07XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5kb3duICAsIHNjb3BlLmxpc3RlbmVycy5zZWxlY3RvckRvd24gKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5tb3ZlICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyTW92ZSAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5vdmVyICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyT3ZlciAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5vdXQgICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyT3V0ICAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy51cCAgICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyVXAgICAgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCBzY29wZS5wRXZlbnRUeXBlcy5jYW5jZWwsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyQ2FuY2VsKTtcblxuICAgICAgICAgICAgLy8gYXV0b3Njcm9sbFxuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsIHNjb3BlLnBFdmVudFR5cGVzLm1vdmUsIHNjb3BlLmxpc3RlbmVycy5hdXRvU2Nyb2xsTW92ZSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgJ21vdXNlZG93bicsIHNjb3BlLmxpc3RlbmVycy5zZWxlY3RvckRvd24pO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlck1vdmUgKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnbW91c2V1cCcgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJVcCAgICk7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgJ21vdXNlb3ZlcicsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyT3ZlciApO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW91dCcgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlck91dCAgKTtcblxuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICd0b3VjaHN0YXJ0JyAsIHNjb3BlLmxpc3RlbmVycy5zZWxlY3RvckRvd24gKTtcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAndG91Y2htb3ZlJyAgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlck1vdmUgICk7XG4gICAgICAgICAgICBldmVudHMuYWRkKGRvYywgJ3RvdWNoZW5kJyAgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJVcCAgICApO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICd0b3VjaGNhbmNlbCcsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyQ2FuY2VsKTtcblxuICAgICAgICAgICAgLy8gYXV0b3Njcm9sbFxuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICdtb3VzZW1vdmUnLCBzY29wZS5saXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgICAgICAgICAgZXZlbnRzLmFkZChkb2MsICd0b3VjaG1vdmUnLCBzY29wZS5saXN0ZW5lcnMuYXV0b1Njcm9sbE1vdmUpO1xuICAgICAgICB9XG5cbiAgICAgICAgZXZlbnRzLmFkZCh3aW4sICdibHVyJywgZW5kQWxsSW50ZXJhY3Rpb25zKTtcblxuICAgICAgICB0cnkge1xuICAgICAgICAgICAgaWYgKHdpbi5mcmFtZUVsZW1lbnQpIHtcbiAgICAgICAgICAgICAgICB2YXIgcGFyZW50RG9jID0gd2luLmZyYW1lRWxlbWVudC5vd25lckRvY3VtZW50LFxuICAgICAgICAgICAgICAgICAgICBwYXJlbnRXaW5kb3cgPSBwYXJlbnREb2MuZGVmYXVsdFZpZXc7XG5cbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHBhcmVudERvYyAgICwgJ21vdXNldXAnICAgICAgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlckVuZCk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICd0b3VjaGVuZCcgICAgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQocGFyZW50RG9jICAgLCAndG91Y2hjYW5jZWwnICAsIHNjb3BlLmxpc3RlbmVycy5wb2ludGVyRW5kKTtcbiAgICAgICAgICAgICAgICBldmVudHMuYWRkKHBhcmVudERvYyAgICwgJ3BvaW50ZXJ1cCcgICAgLCBzY29wZS5saXN0ZW5lcnMucG9pbnRlckVuZCk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZChwYXJlbnREb2MgICAsICdNU1BvaW50ZXJVcCcgICwgc2NvcGUubGlzdGVuZXJzLnBvaW50ZXJFbmQpO1xuICAgICAgICAgICAgICAgIGV2ZW50cy5hZGQocGFyZW50V2luZG93LCAnYmx1cicgICAgICAgICAsIGVuZEFsbEludGVyYWN0aW9ucyApO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGNhdGNoIChlcnJvcikge1xuICAgICAgICAgICAgaW50ZXJhY3Qud2luZG93UGFyZW50RXJyb3IgPSBlcnJvcjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChldmVudHMudXNlQXR0YWNoRXZlbnQpIHtcbiAgICAgICAgICAgIC8vIEZvciBJRSdzIGxhY2sgb2YgRXZlbnQjcHJldmVudERlZmF1bHRcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnc2VsZWN0c3RhcnQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgICAgICB2YXIgaW50ZXJhY3Rpb24gPSBzY29wZS5pbnRlcmFjdGlvbnNbMF07XG5cbiAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3Rpb24uY3VycmVudEFjdGlvbigpKSB7XG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uLmNoZWNrQW5kUHJldmVudERlZmF1bHQoZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAvLyBGb3IgSUUncyBiYWQgZGJsY2xpY2sgZXZlbnQgc2VxdWVuY2VcbiAgICAgICAgICAgIGV2ZW50cy5hZGQoZG9jLCAnZGJsY2xpY2snLCBkb09uSW50ZXJhY3Rpb25zKCdpZThEYmxjbGljaycpKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHNjb3BlLmRvY3VtZW50cy5wdXNoKGRvYyk7XG4gICAgfVxuXG4gICAgbGlzdGVuVG9Eb2N1bWVudChzY29wZS5kb2N1bWVudCk7XG5cbiAgICBzY29wZS5pbnRlcmFjdCA9IGludGVyYWN0O1xuICAgIHNjb3BlLkludGVyYWN0YWJsZSA9IEludGVyYWN0YWJsZTtcbiAgICBzY29wZS5JbnRlcmFjdGlvbiA9IEludGVyYWN0aW9uO1xuICAgIHNjb3BlLkludGVyYWN0RXZlbnQgPSBJbnRlcmFjdEV2ZW50O1xuXG4gICAgLyogZ2xvYmFsIGV4cG9ydHM6IHRydWUsIG1vZHVsZSwgZGVmaW5lICovXG5cbiAgICAvLyBodHRwOi8vZG9jdW1lbnRjbG91ZC5naXRodWIuaW8vdW5kZXJzY29yZS9kb2NzL3VuZGVyc2NvcmUuaHRtbCNzZWN0aW9uLTExXG4gICAgaWYgKHR5cGVvZiBleHBvcnRzICE9PSAndW5kZWZpbmVkJykge1xuICAgICAgICBpZiAodHlwZW9mIG1vZHVsZSAhPT0gJ3VuZGVmaW5lZCcgJiYgbW9kdWxlLmV4cG9ydHMpIHtcbiAgICAgICAgICAgIGV4cG9ydHMgPSBtb2R1bGUuZXhwb3J0cyA9IGludGVyYWN0O1xuICAgICAgICB9XG4gICAgICAgIGV4cG9ydHMuaW50ZXJhY3QgPSBpbnRlcmFjdDtcbiAgICB9XG4gICAgLy8gQU1EXG4gICAgZWxzZSBpZiAodHlwZW9mIGRlZmluZSA9PT0gJ2Z1bmN0aW9uJyAmJiBkZWZpbmUuYW1kKSB7XG4gICAgICAgIGRlZmluZSgnaW50ZXJhY3QnLCBmdW5jdGlvbigpIHtcbiAgICAgICAgICAgIHJldHVybiBpbnRlcmFjdDtcbiAgICAgICAgfSk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBzY29wZS5yZWFsV2luZG93LmludGVyYWN0ID0gaW50ZXJhY3Q7XG4gICAgfVxuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgc2NvcGUgPSByZXF1aXJlKCcuL3Njb3BlJyk7XG52YXIgdXRpbHMgPSByZXF1aXJlKCcuL3V0aWxzJyk7XG5cbmZ1bmN0aW9uIEludGVyYWN0RXZlbnQgKGludGVyYWN0aW9uLCBldmVudCwgYWN0aW9uLCBwaGFzZSwgZWxlbWVudCwgcmVsYXRlZCkge1xuICAgIHZhciBjbGllbnQsXG4gICAgICAgIHBhZ2UsXG4gICAgICAgIHRhcmdldCAgICAgID0gaW50ZXJhY3Rpb24udGFyZ2V0LFxuICAgICAgICBzbmFwU3RhdHVzICA9IGludGVyYWN0aW9uLnNuYXBTdGF0dXMsXG4gICAgICAgIHJlc3RyaWN0U3RhdHVzICA9IGludGVyYWN0aW9uLnJlc3RyaWN0U3RhdHVzLFxuICAgICAgICBwb2ludGVycyAgICA9IGludGVyYWN0aW9uLnBvaW50ZXJzLFxuICAgICAgICBkZWx0YVNvdXJjZSA9ICh0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMgfHwgc2NvcGUuZGVmYXVsdE9wdGlvbnMpLmRlbHRhU291cmNlLFxuICAgICAgICBzb3VyY2VYICAgICA9IGRlbHRhU291cmNlICsgJ1gnLFxuICAgICAgICBzb3VyY2VZICAgICA9IGRlbHRhU291cmNlICsgJ1knLFxuICAgICAgICBvcHRpb25zICAgICA9IHRhcmdldD8gdGFyZ2V0Lm9wdGlvbnM6IHNjb3BlLmRlZmF1bHRPcHRpb25zLFxuICAgICAgICBvcmlnaW4gICAgICA9IHNjb3BlLmdldE9yaWdpblhZKHRhcmdldCwgZWxlbWVudCksXG4gICAgICAgIHN0YXJ0aW5nICAgID0gcGhhc2UgPT09ICdzdGFydCcsXG4gICAgICAgIGVuZGluZyAgICAgID0gcGhhc2UgPT09ICdlbmQnLFxuICAgICAgICBjb29yZHMgICAgICA9IHN0YXJ0aW5nPyBpbnRlcmFjdGlvbi5zdGFydENvb3JkcyA6IGludGVyYWN0aW9uLmN1ckNvb3JkcztcblxuICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IGludGVyYWN0aW9uLmVsZW1lbnQ7XG5cbiAgICBwYWdlICAgPSB1dGlscy5leHRlbmQoe30sIGNvb3Jkcy5wYWdlKTtcbiAgICBjbGllbnQgPSB1dGlscy5leHRlbmQoe30sIGNvb3Jkcy5jbGllbnQpO1xuXG4gICAgcGFnZS54IC09IG9yaWdpbi54O1xuICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcblxuICAgIGNsaWVudC54IC09IG9yaWdpbi54O1xuICAgIGNsaWVudC55IC09IG9yaWdpbi55O1xuXG4gICAgdmFyIHJlbGF0aXZlUG9pbnRzID0gb3B0aW9uc1thY3Rpb25dLnNuYXAgJiYgb3B0aW9uc1thY3Rpb25dLnNuYXAucmVsYXRpdmVQb2ludHMgO1xuXG4gICAgaWYgKHNjb3BlLmNoZWNrU25hcCh0YXJnZXQsIGFjdGlvbikgJiYgIShzdGFydGluZyAmJiByZWxhdGl2ZVBvaW50cyAmJiByZWxhdGl2ZVBvaW50cy5sZW5ndGgpKSB7XG4gICAgICAgIHRoaXMuc25hcCA9IHtcbiAgICAgICAgICAgIHJhbmdlICA6IHNuYXBTdGF0dXMucmFuZ2UsXG4gICAgICAgICAgICBsb2NrZWQgOiBzbmFwU3RhdHVzLmxvY2tlZCxcbiAgICAgICAgICAgIHggICAgICA6IHNuYXBTdGF0dXMuc25hcHBlZFgsXG4gICAgICAgICAgICB5ICAgICAgOiBzbmFwU3RhdHVzLnNuYXBwZWRZLFxuICAgICAgICAgICAgcmVhbFggIDogc25hcFN0YXR1cy5yZWFsWCxcbiAgICAgICAgICAgIHJlYWxZICA6IHNuYXBTdGF0dXMucmVhbFksXG4gICAgICAgICAgICBkeCAgICAgOiBzbmFwU3RhdHVzLmR4LFxuICAgICAgICAgICAgZHkgICAgIDogc25hcFN0YXR1cy5keVxuICAgICAgICB9O1xuXG4gICAgICAgIGlmIChzbmFwU3RhdHVzLmxvY2tlZCkge1xuICAgICAgICAgICAgcGFnZS54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBwYWdlLnkgKz0gc25hcFN0YXR1cy5keTtcbiAgICAgICAgICAgIGNsaWVudC54ICs9IHNuYXBTdGF0dXMuZHg7XG4gICAgICAgICAgICBjbGllbnQueSArPSBzbmFwU3RhdHVzLmR5O1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKHNjb3BlLmNoZWNrUmVzdHJpY3QodGFyZ2V0LCBhY3Rpb24pICYmICEoc3RhcnRpbmcgJiYgb3B0aW9uc1thY3Rpb25dLnJlc3RyaWN0LmVsZW1lbnRSZWN0KSAmJiByZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkKSB7XG4gICAgICAgIHBhZ2UueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgcGFnZS55ICs9IHJlc3RyaWN0U3RhdHVzLmR5O1xuICAgICAgICBjbGllbnQueCArPSByZXN0cmljdFN0YXR1cy5keDtcbiAgICAgICAgY2xpZW50LnkgKz0gcmVzdHJpY3RTdGF0dXMuZHk7XG5cbiAgICAgICAgdGhpcy5yZXN0cmljdCA9IHtcbiAgICAgICAgICAgIGR4OiByZXN0cmljdFN0YXR1cy5keCxcbiAgICAgICAgICAgIGR5OiByZXN0cmljdFN0YXR1cy5keVxuICAgICAgICB9O1xuICAgIH1cblxuICAgIHRoaXMucGFnZVggICAgID0gcGFnZS54O1xuICAgIHRoaXMucGFnZVkgICAgID0gcGFnZS55O1xuICAgIHRoaXMuY2xpZW50WCAgID0gY2xpZW50Lng7XG4gICAgdGhpcy5jbGllbnRZICAgPSBjbGllbnQueTtcblxuICAgIHRoaXMueDAgICAgICAgID0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54IC0gb3JpZ2luLng7XG4gICAgdGhpcy55MCAgICAgICAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueTtcbiAgICB0aGlzLmNsaWVudFgwICA9IGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54IC0gb3JpZ2luLng7XG4gICAgdGhpcy5jbGllbnRZMCAgPSBpbnRlcmFjdGlvbi5zdGFydENvb3Jkcy5jbGllbnQueSAtIG9yaWdpbi55O1xuICAgIHRoaXMuY3RybEtleSAgID0gZXZlbnQuY3RybEtleTtcbiAgICB0aGlzLmFsdEtleSAgICA9IGV2ZW50LmFsdEtleTtcbiAgICB0aGlzLnNoaWZ0S2V5ICA9IGV2ZW50LnNoaWZ0S2V5O1xuICAgIHRoaXMubWV0YUtleSAgID0gZXZlbnQubWV0YUtleTtcbiAgICB0aGlzLmJ1dHRvbiAgICA9IGV2ZW50LmJ1dHRvbjtcbiAgICB0aGlzLnRhcmdldCAgICA9IGVsZW1lbnQ7XG4gICAgdGhpcy50MCAgICAgICAgPSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG4gICAgdGhpcy50eXBlICAgICAgPSBhY3Rpb24gKyAocGhhc2UgfHwgJycpO1xuXG4gICAgdGhpcy5pbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uO1xuICAgIHRoaXMuaW50ZXJhY3RhYmxlID0gdGFyZ2V0O1xuXG4gICAgdmFyIGluZXJ0aWFTdGF0dXMgPSBpbnRlcmFjdGlvbi5pbmVydGlhU3RhdHVzO1xuXG4gICAgaWYgKGluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7XG4gICAgICAgIHRoaXMuZGV0YWlsID0gJ2luZXJ0aWEnO1xuICAgIH1cblxuICAgIGlmIChyZWxhdGVkKSB7XG4gICAgICAgIHRoaXMucmVsYXRlZFRhcmdldCA9IHJlbGF0ZWQ7XG4gICAgfVxuXG4gICAgLy8gZW5kIGV2ZW50IGR4LCBkeSBpcyBkaWZmZXJlbmNlIGJldHdlZW4gc3RhcnQgYW5kIGVuZCBwb2ludHNcbiAgICBpZiAoZW5kaW5nKSB7XG4gICAgICAgIGlmIChkZWx0YVNvdXJjZSA9PT0gJ2NsaWVudCcpIHtcbiAgICAgICAgICAgIHRoaXMuZHggPSBjbGllbnQueCAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLmNsaWVudC54O1xuICAgICAgICAgICAgdGhpcy5keSA9IGNsaWVudC55IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMuY2xpZW50Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gcGFnZS54IC0gaW50ZXJhY3Rpb24uc3RhcnRDb29yZHMucGFnZS54O1xuICAgICAgICAgICAgdGhpcy5keSA9IHBhZ2UueSAtIGludGVyYWN0aW9uLnN0YXJ0Q29vcmRzLnBhZ2UueTtcbiAgICAgICAgfVxuICAgIH1cbiAgICBlbHNlIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLmR4ID0gMDtcbiAgICAgICAgdGhpcy5keSA9IDA7XG4gICAgfVxuICAgIC8vIGNvcHkgcHJvcGVydGllcyBmcm9tIHByZXZpb3VzbW92ZSBpZiBzdGFydGluZyBpbmVydGlhXG4gICAgZWxzZSBpZiAocGhhc2UgPT09ICdpbmVydGlhc3RhcnQnKSB7XG4gICAgICAgIHRoaXMuZHggPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHg7XG4gICAgICAgIHRoaXMuZHkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZHk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBpZiAoZGVsdGFTb3VyY2UgPT09ICdjbGllbnQnKSB7XG4gICAgICAgICAgICB0aGlzLmR4ID0gY2xpZW50LnggLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuY2xpZW50WDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBjbGllbnQueSAtIGludGVyYWN0aW9uLnByZXZFdmVudC5jbGllbnRZO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdGhpcy5keCA9IHBhZ2UueCAtIGludGVyYWN0aW9uLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgICAgIHRoaXMuZHkgPSBwYWdlLnkgLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIH1cbiAgICB9XG4gICAgaWYgKGludGVyYWN0aW9uLnByZXZFdmVudCAmJiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGV0YWlsID09PSAnaW5lcnRpYSdcbiAgICAgICAgJiYgIWluZXJ0aWFTdGF0dXMuYWN0aXZlXG4gICAgICAgICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhICYmIG9wdGlvbnNbYWN0aW9uXS5pbmVydGlhLnplcm9SZXN1bWVEZWx0YSkge1xuXG4gICAgICAgIGluZXJ0aWFTdGF0dXMucmVzdW1lRHggKz0gdGhpcy5keDtcbiAgICAgICAgaW5lcnRpYVN0YXR1cy5yZXN1bWVEeSArPSB0aGlzLmR5O1xuXG4gICAgICAgIHRoaXMuZHggPSB0aGlzLmR5ID0gMDtcbiAgICB9XG5cbiAgICBpZiAoYWN0aW9uID09PSAncmVzaXplJyAmJiBpbnRlcmFjdGlvbi5yZXNpemVBeGVzKSB7XG4gICAgICAgIGlmIChvcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneScpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR4ID0gdGhpcy5keTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHRoaXMuZHkgPSB0aGlzLmR4O1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgdGhpcy5heGVzID0gJ3h5JztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuYXhlcyA9IGludGVyYWN0aW9uLnJlc2l6ZUF4ZXM7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGlvbi5yZXNpemVBeGVzID09PSAneCcpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmR5ID0gMDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2UgaWYgKGludGVyYWN0aW9uLnJlc2l6ZUF4ZXMgPT09ICd5Jykge1xuICAgICAgICAgICAgICAgIHRoaXMuZHggPSAwO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKGFjdGlvbiA9PT0gJ2dlc3R1cmUnKSB7XG4gICAgICAgIHRoaXMudG91Y2hlcyA9IFtwb2ludGVyc1swXSwgcG9pbnRlcnNbMV1dO1xuXG4gICAgICAgIGlmIChzdGFydGluZykge1xuICAgICAgICAgICAgdGhpcy5kaXN0YW5jZSA9IHV0aWxzLnRvdWNoRGlzdGFuY2UocG9pbnRlcnMsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuYm94ICAgICAgPSB1dGlscy50b3VjaEJCb3gocG9pbnRlcnMpO1xuICAgICAgICAgICAgdGhpcy5zY2FsZSAgICA9IDE7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gMDtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB1dGlscy50b3VjaEFuZ2xlKHBvaW50ZXJzLCB1bmRlZmluZWQsIGRlbHRhU291cmNlKTtcbiAgICAgICAgICAgIHRoaXMuZGEgICAgICAgPSAwO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGVuZGluZyB8fCBldmVudCBpbnN0YW5jZW9mIEludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgICAgIHRoaXMuZGlzdGFuY2UgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuZGlzdGFuY2U7XG4gICAgICAgICAgICB0aGlzLmJveCAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmJveDtcbiAgICAgICAgICAgIHRoaXMuc2NhbGUgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc2NhbGU7XG4gICAgICAgICAgICB0aGlzLmRzICAgICAgID0gdGhpcy5zY2FsZSAtIDE7XG4gICAgICAgICAgICB0aGlzLmFuZ2xlICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmFuZ2xlO1xuICAgICAgICAgICAgdGhpcy5kYSAgICAgICA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnN0YXJ0QW5nbGU7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLmRpc3RhbmNlID0gdXRpbHMudG91Y2hEaXN0YW5jZShwb2ludGVycywgZGVsdGFTb3VyY2UpO1xuICAgICAgICAgICAgdGhpcy5ib3ggICAgICA9IHV0aWxzLnRvdWNoQkJveChwb2ludGVycyk7XG4gICAgICAgICAgICB0aGlzLnNjYWxlICAgID0gdGhpcy5kaXN0YW5jZSAvIGludGVyYWN0aW9uLmdlc3R1cmUuc3RhcnREaXN0YW5jZTtcbiAgICAgICAgICAgIHRoaXMuYW5nbGUgICAgPSB1dGlscy50b3VjaEFuZ2xlKHBvaW50ZXJzLCBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZSwgZGVsdGFTb3VyY2UpO1xuXG4gICAgICAgICAgICB0aGlzLmRzID0gdGhpcy5zY2FsZSAtIGludGVyYWN0aW9uLmdlc3R1cmUucHJldlNjYWxlO1xuICAgICAgICAgICAgdGhpcy5kYSA9IHRoaXMuYW5nbGUgLSBpbnRlcmFjdGlvbi5nZXN0dXJlLnByZXZBbmdsZTtcbiAgICAgICAgfVxuICAgIH1cblxuICAgIGlmIChzdGFydGluZykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLmRvd25UaW1lc1swXTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSAwO1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IDA7XG4gICAgICAgIHRoaXMuc3BlZWQgICAgID0gMDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVggPSAwO1xuICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IDA7XG4gICAgfVxuICAgIGVsc2UgaWYgKHBoYXNlID09PSAnaW5lcnRpYXN0YXJ0Jykge1xuICAgICAgICB0aGlzLnRpbWVTdGFtcCA9IGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHQgICAgICAgID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LmR0O1xuICAgICAgICB0aGlzLmR1cmF0aW9uICA9IGludGVyYWN0aW9uLnByZXZFdmVudC5kdXJhdGlvbjtcbiAgICAgICAgdGhpcy5zcGVlZCAgICAgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQuc3BlZWQ7XG4gICAgICAgIHRoaXMudmVsb2NpdHlYID0gaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WDtcbiAgICAgICAgdGhpcy52ZWxvY2l0eVkgPSBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgICAgdGhpcy50aW1lU3RhbXAgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKTtcbiAgICAgICAgdGhpcy5kdCAgICAgICAgPSB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXA7XG4gICAgICAgIHRoaXMuZHVyYXRpb24gID0gdGhpcy50aW1lU3RhbXAgLSBpbnRlcmFjdGlvbi5kb3duVGltZXNbMF07XG5cbiAgICAgICAgaWYgKGV2ZW50IGluc3RhbmNlb2YgSW50ZXJhY3RFdmVudCkge1xuICAgICAgICAgICAgdmFyIGR4ID0gdGhpc1tzb3VyY2VYXSAtIGludGVyYWN0aW9uLnByZXZFdmVudFtzb3VyY2VYXSxcbiAgICAgICAgICAgICAgICBkeSA9IHRoaXNbc291cmNlWV0gLSBpbnRlcmFjdGlvbi5wcmV2RXZlbnRbc291cmNlWV0sXG4gICAgICAgICAgICAgICAgZHQgPSB0aGlzLmR0IC8gMTAwMDtcblxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IHV0aWxzLmh5cG90KGR4LCBkeSkgLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlYID0gZHggLyBkdDtcbiAgICAgICAgICAgIHRoaXMudmVsb2NpdHlZID0gZHkgLyBkdDtcbiAgICAgICAgfVxuICAgICAgICAvLyBpZiBub3JtYWwgbW92ZSBvciBlbmQgZXZlbnQsIHVzZSBwcmV2aW91cyB1c2VyIGV2ZW50IGNvb3Jkc1xuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIC8vIHNwZWVkIGFuZCB2ZWxvY2l0eSBpbiBwaXhlbHMgcGVyIHNlY29uZFxuICAgICAgICAgICAgdGhpcy5zcGVlZCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0uc3BlZWQ7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WCA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udng7XG4gICAgICAgICAgICB0aGlzLnZlbG9jaXR5WSA9IGludGVyYWN0aW9uLnBvaW50ZXJEZWx0YVtkZWx0YVNvdXJjZV0udnk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoKGVuZGluZyB8fCBwaGFzZSA9PT0gJ2luZXJ0aWFzdGFydCcpXG4gICAgICAgICYmIGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCA+IDYwMCAmJiB0aGlzLnRpbWVTdGFtcCAtIGludGVyYWN0aW9uLnByZXZFdmVudC50aW1lU3RhbXAgPCAxNTApIHtcblxuICAgICAgICB2YXIgYW5nbGUgPSAxODAgKiBNYXRoLmF0YW4yKGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVksIGludGVyYWN0aW9uLnByZXZFdmVudC52ZWxvY2l0eVgpIC8gTWF0aC5QSSxcbiAgICAgICAgICAgIG92ZXJsYXAgPSAyMi41O1xuXG4gICAgICAgIGlmIChhbmdsZSA8IDApIHtcbiAgICAgICAgICAgIGFuZ2xlICs9IDM2MDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBsZWZ0ID0gMTM1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDIyNSArIG92ZXJsYXAsXG4gICAgICAgICAgICB1cCAgID0gMjI1IC0gb3ZlcmxhcCA8PSBhbmdsZSAmJiBhbmdsZSA8IDMxNSArIG92ZXJsYXAsXG5cbiAgICAgICAgICAgIHJpZ2h0ID0gIWxlZnQgJiYgKDMxNSAtIG92ZXJsYXAgPD0gYW5nbGUgfHwgYW5nbGUgPCAgNDUgKyBvdmVybGFwKSxcbiAgICAgICAgICAgIGRvd24gID0gIXVwICAgJiYgICA0NSAtIG92ZXJsYXAgPD0gYW5nbGUgJiYgYW5nbGUgPCAxMzUgKyBvdmVybGFwO1xuXG4gICAgICAgIHRoaXMuc3dpcGUgPSB7XG4gICAgICAgICAgICB1cCAgIDogdXAsXG4gICAgICAgICAgICBkb3duIDogZG93bixcbiAgICAgICAgICAgIGxlZnQgOiBsZWZ0LFxuICAgICAgICAgICAgcmlnaHQ6IHJpZ2h0LFxuICAgICAgICAgICAgYW5nbGU6IGFuZ2xlLFxuICAgICAgICAgICAgc3BlZWQ6IGludGVyYWN0aW9uLnByZXZFdmVudC5zcGVlZCxcbiAgICAgICAgICAgIHZlbG9jaXR5OiB7XG4gICAgICAgICAgICAgICAgeDogaW50ZXJhY3Rpb24ucHJldkV2ZW50LnZlbG9jaXR5WCxcbiAgICAgICAgICAgICAgICB5OiBpbnRlcmFjdGlvbi5wcmV2RXZlbnQudmVsb2NpdHlZXG4gICAgICAgICAgICB9XG4gICAgICAgIH07XG4gICAgfVxufVxuXG5JbnRlcmFjdEV2ZW50LnByb3RvdHlwZSA9IHtcbiAgICBwcmV2ZW50RGVmYXVsdDogdXRpbHMuYmxhbmssXG4gICAgc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMuaW1tZWRpYXRlUHJvcGFnYXRpb25TdG9wcGVkID0gdGhpcy5wcm9wYWdhdGlvblN0b3BwZWQgPSB0cnVlO1xuICAgIH0sXG4gICAgc3RvcFByb3BhZ2F0aW9uOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHRoaXMucHJvcGFnYXRpb25TdG9wcGVkID0gdHJ1ZTtcbiAgICB9XG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IEludGVyYWN0RXZlbnQ7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBzY29wZSA9IHJlcXVpcmUoJy4vc2NvcGUnKTtcbnZhciB1dGlscyA9IHJlcXVpcmUoJy4vdXRpbHMnKTtcbnZhciBhbmltYXRpb25GcmFtZSA9IHV0aWxzLnJhZjtcbnZhciBJbnRlcmFjdEV2ZW50ID0gcmVxdWlyZSgnLi9JbnRlcmFjdEV2ZW50Jyk7XG52YXIgZXZlbnRzID0gcmVxdWlyZSgnLi91dGlscy9ldmVudHMnKTtcbnZhciBicm93c2VyID0gcmVxdWlyZSgnLi91dGlscy9icm93c2VyJyk7XG5cbmZ1bmN0aW9uIEludGVyYWN0aW9uICgpIHtcbiAgICB0aGlzLnRhcmdldCAgICAgICAgICA9IG51bGw7IC8vIGN1cnJlbnQgaW50ZXJhY3RhYmxlIGJlaW5nIGludGVyYWN0ZWQgd2l0aFxuICAgIHRoaXMuZWxlbWVudCAgICAgICAgID0gbnVsbDsgLy8gdGhlIHRhcmdldCBlbGVtZW50IG9mIHRoZSBpbnRlcmFjdGFibGVcbiAgICB0aGlzLmRyb3BUYXJnZXQgICAgICA9IG51bGw7IC8vIHRoZSBkcm9wem9uZSBhIGRyYWcgdGFyZ2V0IG1pZ2h0IGJlIGRyb3BwZWQgaW50b1xuICAgIHRoaXMuZHJvcEVsZW1lbnQgICAgID0gbnVsbDsgLy8gdGhlIGVsZW1lbnQgYXQgdGhlIHRpbWUgb2YgY2hlY2tpbmdcbiAgICB0aGlzLnByZXZEcm9wVGFyZ2V0ICA9IG51bGw7IC8vIHRoZSBkcm9wem9uZSB0aGF0IHdhcyByZWNlbnRseSBkcmFnZ2VkIGF3YXkgZnJvbVxuICAgIHRoaXMucHJldkRyb3BFbGVtZW50ID0gbnVsbDsgLy8gdGhlIGVsZW1lbnQgYXQgdGhlIHRpbWUgb2YgY2hlY2tpbmdcblxuICAgIHRoaXMucHJlcGFyZWQgICAgICAgID0geyAgICAgLy8gYWN0aW9uIHRoYXQncyByZWFkeSB0byBiZSBmaXJlZCBvbiBuZXh0IG1vdmUgZXZlbnRcbiAgICAgICAgbmFtZSA6IG51bGwsXG4gICAgICAgIGF4aXMgOiBudWxsLFxuICAgICAgICBlZGdlczogbnVsbFxuICAgIH07XG5cbiAgICB0aGlzLm1hdGNoZXMgICAgICAgICA9IFtdOyAgIC8vIGFsbCBzZWxlY3RvcnMgdGhhdCBhcmUgbWF0Y2hlZCBieSB0YXJnZXQgZWxlbWVudFxuICAgIHRoaXMubWF0Y2hFbGVtZW50cyAgID0gW107ICAgLy8gY29ycmVzcG9uZGluZyBlbGVtZW50c1xuXG4gICAgdGhpcy5pbmVydGlhU3RhdHVzID0ge1xuICAgICAgICBhY3RpdmUgICAgICAgOiBmYWxzZSxcbiAgICAgICAgc21vb3RoRW5kICAgIDogZmFsc2UsXG5cbiAgICAgICAgc3RhcnRFdmVudDogbnVsbCxcbiAgICAgICAgdXBDb29yZHM6IHt9LFxuXG4gICAgICAgIHhlOiAwLCB5ZTogMCxcbiAgICAgICAgc3g6IDAsIHN5OiAwLFxuXG4gICAgICAgIHQwOiAwLFxuICAgICAgICB2eDA6IDAsIHZ5czogMCxcbiAgICAgICAgZHVyYXRpb246IDAsXG5cbiAgICAgICAgcmVzdW1lRHg6IDAsXG4gICAgICAgIHJlc3VtZUR5OiAwLFxuXG4gICAgICAgIGxhbWJkYV92MDogMCxcbiAgICAgICAgb25lX3ZlX3YwOiAwLFxuICAgICAgICBpICA6IG51bGxcbiAgICB9O1xuXG4gICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24oRnVuY3Rpb24ucHJvdG90eXBlLmJpbmQpKSB7XG4gICAgICAgIHRoaXMuYm91bmRJbmVydGlhRnJhbWUgPSB0aGlzLmluZXJ0aWFGcmFtZS5iaW5kKHRoaXMpO1xuICAgICAgICB0aGlzLmJvdW5kU21vb3RoRW5kRnJhbWUgPSB0aGlzLnNtb290aEVuZEZyYW1lLmJpbmQodGhpcyk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICB2YXIgdGhhdCA9IHRoaXM7XG5cbiAgICAgICAgdGhpcy5ib3VuZEluZXJ0aWFGcmFtZSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIHRoYXQuaW5lcnRpYUZyYW1lKCk7IH07XG4gICAgICAgIHRoaXMuYm91bmRTbW9vdGhFbmRGcmFtZSA9IGZ1bmN0aW9uICgpIHsgcmV0dXJuIHRoYXQuc21vb3RoRW5kRnJhbWUoKTsgfTtcbiAgICB9XG5cbiAgICB0aGlzLmFjdGl2ZURyb3BzID0ge1xuICAgICAgICBkcm9wem9uZXM6IFtdLCAgICAgIC8vIHRoZSBkcm9wem9uZXMgdGhhdCBhcmUgbWVudGlvbmVkIGJlbG93XG4gICAgICAgIGVsZW1lbnRzIDogW10sICAgICAgLy8gZWxlbWVudHMgb2YgZHJvcHpvbmVzIHRoYXQgYWNjZXB0IHRoZSB0YXJnZXQgZHJhZ2dhYmxlXG4gICAgICAgIHJlY3RzICAgIDogW10gICAgICAgLy8gdGhlIHJlY3RzIG9mIHRoZSBlbGVtZW50cyBtZW50aW9uZWQgYWJvdmVcbiAgICB9O1xuXG4gICAgLy8ga2VlcCB0cmFjayBvZiBhZGRlZCBwb2ludGVyc1xuICAgIHRoaXMucG9pbnRlcnMgICAgPSBbXTtcbiAgICB0aGlzLnBvaW50ZXJJZHMgID0gW107XG4gICAgdGhpcy5kb3duVGFyZ2V0cyA9IFtdO1xuICAgIHRoaXMuZG93blRpbWVzICAgPSBbXTtcbiAgICB0aGlzLmhvbGRUaW1lcnMgID0gW107XG5cbiAgICAvLyBQcmV2aW91cyBuYXRpdmUgcG9pbnRlciBtb3ZlIGV2ZW50IGNvb3JkaW5hdGVzXG4gICAgdGhpcy5wcmV2Q29vcmRzID0ge1xuICAgICAgICBwYWdlICAgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICBjbGllbnQgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICB0aW1lU3RhbXA6IDBcbiAgICB9O1xuICAgIC8vIGN1cnJlbnQgbmF0aXZlIHBvaW50ZXIgbW92ZSBldmVudCBjb29yZGluYXRlc1xuICAgIHRoaXMuY3VyQ29vcmRzID0ge1xuICAgICAgICBwYWdlICAgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICBjbGllbnQgICA6IHsgeDogMCwgeTogMCB9LFxuICAgICAgICB0aW1lU3RhbXA6IDBcbiAgICB9O1xuXG4gICAgLy8gU3RhcnRpbmcgSW50ZXJhY3RFdmVudCBwb2ludGVyIGNvb3JkaW5hdGVzXG4gICAgdGhpcy5zdGFydENvb3JkcyA9IHtcbiAgICAgICAgcGFnZSAgICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgY2xpZW50ICAgOiB7IHg6IDAsIHk6IDAgfSxcbiAgICAgICAgdGltZVN0YW1wOiAwXG4gICAgfTtcblxuICAgIC8vIENoYW5nZSBpbiBjb29yZGluYXRlcyBhbmQgdGltZSBvZiB0aGUgcG9pbnRlclxuICAgIHRoaXMucG9pbnRlckRlbHRhID0ge1xuICAgICAgICBwYWdlICAgICA6IHsgeDogMCwgeTogMCwgdng6IDAsIHZ5OiAwLCBzcGVlZDogMCB9LFxuICAgICAgICBjbGllbnQgICA6IHsgeDogMCwgeTogMCwgdng6IDAsIHZ5OiAwLCBzcGVlZDogMCB9LFxuICAgICAgICB0aW1lU3RhbXA6IDBcbiAgICB9O1xuXG4gICAgdGhpcy5kb3duRXZlbnQgICA9IG51bGw7ICAgIC8vIHBvaW50ZXJkb3duL21vdXNlZG93bi90b3VjaHN0YXJ0IGV2ZW50XG4gICAgdGhpcy5kb3duUG9pbnRlciA9IHt9O1xuXG4gICAgdGhpcy5fZXZlbnRUYXJnZXQgICAgPSBudWxsO1xuICAgIHRoaXMuX2N1ckV2ZW50VGFyZ2V0ID0gbnVsbDtcblxuICAgIHRoaXMucHJldkV2ZW50ID0gbnVsbDsgICAgICAvLyBwcmV2aW91cyBhY3Rpb24gZXZlbnRcbiAgICB0aGlzLnRhcFRpbWUgICA9IDA7ICAgICAgICAgLy8gdGltZSBvZiB0aGUgbW9zdCByZWNlbnQgdGFwIGV2ZW50XG4gICAgdGhpcy5wcmV2VGFwICAgPSBudWxsO1xuXG4gICAgdGhpcy5zdGFydE9mZnNldCAgICA9IHsgbGVmdDogMCwgcmlnaHQ6IDAsIHRvcDogMCwgYm90dG9tOiAwIH07XG4gICAgdGhpcy5yZXN0cmljdE9mZnNldCA9IHsgbGVmdDogMCwgcmlnaHQ6IDAsIHRvcDogMCwgYm90dG9tOiAwIH07XG4gICAgdGhpcy5zbmFwT2Zmc2V0cyAgICA9IFtdO1xuXG4gICAgdGhpcy5nZXN0dXJlID0ge1xuICAgICAgICBzdGFydDogeyB4OiAwLCB5OiAwIH0sXG5cbiAgICAgICAgc3RhcnREaXN0YW5jZTogMCwgICAvLyBkaXN0YW5jZSBiZXR3ZWVuIHR3byB0b3VjaGVzIG9mIHRvdWNoU3RhcnRcbiAgICAgICAgcHJldkRpc3RhbmNlIDogMCxcbiAgICAgICAgZGlzdGFuY2UgICAgIDogMCxcblxuICAgICAgICBzY2FsZTogMSwgICAgICAgICAgIC8vIGdlc3R1cmUuZGlzdGFuY2UgLyBnZXN0dXJlLnN0YXJ0RGlzdGFuY2VcblxuICAgICAgICBzdGFydEFuZ2xlOiAwLCAgICAgIC8vIGFuZ2xlIG9mIGxpbmUgam9pbmluZyB0d28gdG91Y2hlc1xuICAgICAgICBwcmV2QW5nbGUgOiAwICAgICAgIC8vIGFuZ2xlIG9mIHRoZSBwcmV2aW91cyBnZXN0dXJlIGV2ZW50XG4gICAgfTtcblxuICAgIHRoaXMuc25hcFN0YXR1cyA9IHtcbiAgICAgICAgeCAgICAgICA6IDAsIHkgICAgICAgOiAwLFxuICAgICAgICBkeCAgICAgIDogMCwgZHkgICAgICA6IDAsXG4gICAgICAgIHJlYWxYICAgOiAwLCByZWFsWSAgIDogMCxcbiAgICAgICAgc25hcHBlZFg6IDAsIHNuYXBwZWRZOiAwLFxuICAgICAgICB0YXJnZXRzIDogW10sXG4gICAgICAgIGxvY2tlZCAgOiBmYWxzZSxcbiAgICAgICAgY2hhbmdlZCA6IGZhbHNlXG4gICAgfTtcblxuICAgIHRoaXMucmVzdHJpY3RTdGF0dXMgPSB7XG4gICAgICAgIGR4ICAgICAgICAgOiAwLCBkeSAgICAgICAgIDogMCxcbiAgICAgICAgcmVzdHJpY3RlZFg6IDAsIHJlc3RyaWN0ZWRZOiAwLFxuICAgICAgICBzbmFwICAgICAgIDogbnVsbCxcbiAgICAgICAgcmVzdHJpY3RlZCA6IGZhbHNlLFxuICAgICAgICBjaGFuZ2VkICAgIDogZmFsc2VcbiAgICB9O1xuXG4gICAgdGhpcy5yZXN0cmljdFN0YXR1cy5zbmFwID0gdGhpcy5zbmFwU3RhdHVzO1xuXG4gICAgdGhpcy5wb2ludGVySXNEb3duICAgPSBmYWxzZTtcbiAgICB0aGlzLnBvaW50ZXJXYXNNb3ZlZCA9IGZhbHNlO1xuICAgIHRoaXMuZ2VzdHVyaW5nICAgICAgID0gZmFsc2U7XG4gICAgdGhpcy5kcmFnZ2luZyAgICAgICAgPSBmYWxzZTtcbiAgICB0aGlzLnJlc2l6aW5nICAgICAgICA9IGZhbHNlO1xuICAgIHRoaXMucmVzaXplQXhlcyAgICAgID0gJ3h5JztcblxuICAgIHRoaXMubW91c2UgPSBmYWxzZTtcblxuICAgIHNjb3BlLmludGVyYWN0aW9ucy5wdXNoKHRoaXMpO1xufVxuXG4vLyBDaGVjayBpZiBhY3Rpb24gaXMgZW5hYmxlZCBnbG9iYWxseSBhbmQgdGhlIGN1cnJlbnQgdGFyZ2V0IHN1cHBvcnRzIGl0XG4vLyBJZiBzbywgcmV0dXJuIHRoZSB2YWxpZGF0ZWQgYWN0aW9uLiBPdGhlcndpc2UsIHJldHVybiBudWxsXG5mdW5jdGlvbiB2YWxpZGF0ZUFjdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUpIHtcbiAgICBpZiAoIXNjb3BlLmlzT2JqZWN0KGFjdGlvbikpIHsgcmV0dXJuIG51bGw7IH1cblxuICAgIHZhciBhY3Rpb25OYW1lID0gYWN0aW9uLm5hbWUsXG4gICAgICAgIG9wdGlvbnMgPSBpbnRlcmFjdGFibGUub3B0aW9ucztcblxuICAgIGlmICgoICAoYWN0aW9uTmFtZSAgPT09ICdyZXNpemUnICAgJiYgb3B0aW9ucy5yZXNpemUuZW5hYmxlZCApXG4gICAgICAgIHx8IChhY3Rpb25OYW1lICAgICAgPT09ICdkcmFnJyAgICAgJiYgb3B0aW9ucy5kcmFnLmVuYWJsZWQgIClcbiAgICAgICAgfHwgKGFjdGlvbk5hbWUgICAgICA9PT0gJ2dlc3R1cmUnICAmJiBvcHRpb25zLmdlc3R1cmUuZW5hYmxlZCkpXG4gICAgICAgICYmIHNjb3BlLmFjdGlvbklzRW5hYmxlZFthY3Rpb25OYW1lXSkge1xuXG4gICAgICAgIGlmIChhY3Rpb25OYW1lID09PSAncmVzaXplJyB8fCBhY3Rpb25OYW1lID09PSAncmVzaXpleXgnKSB7XG4gICAgICAgICAgICBhY3Rpb25OYW1lID0gJ3Jlc2l6ZXh5JztcbiAgICAgICAgfVxuXG4gICAgICAgIHJldHVybiBhY3Rpb247XG4gICAgfVxuICAgIHJldHVybiBudWxsO1xufVxuXG5mdW5jdGlvbiBnZXRBY3Rpb25DdXJzb3IgKGFjdGlvbikge1xuICAgIHZhciBjdXJzb3IgPSAnJztcblxuICAgIGlmIChhY3Rpb24ubmFtZSA9PT0gJ2RyYWcnKSB7XG4gICAgICAgIGN1cnNvciA9ICBzY29wZS5hY3Rpb25DdXJzb3JzLmRyYWc7XG4gICAgfVxuICAgIGlmIChhY3Rpb24ubmFtZSA9PT0gJ3Jlc2l6ZScpIHtcbiAgICAgICAgaWYgKGFjdGlvbi5heGlzKSB7XG4gICAgICAgICAgICBjdXJzb3IgPSAgc2NvcGUuYWN0aW9uQ3Vyc29yc1thY3Rpb24ubmFtZSArIGFjdGlvbi5heGlzXTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChhY3Rpb24uZWRnZXMpIHtcbiAgICAgICAgICAgIHZhciBjdXJzb3JLZXkgPSAncmVzaXplJyxcbiAgICAgICAgICAgICAgICBlZGdlTmFtZXMgPSBbJ3RvcCcsICdib3R0b20nLCAnbGVmdCcsICdyaWdodCddO1xuXG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IDQ7IGkrKykge1xuICAgICAgICAgICAgICAgIGlmIChhY3Rpb24uZWRnZXNbZWRnZU5hbWVzW2ldXSkge1xuICAgICAgICAgICAgICAgICAgICBjdXJzb3JLZXkgKz0gZWRnZU5hbWVzW2ldO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY3Vyc29yID0gc2NvcGUuYWN0aW9uQ3Vyc29yc1tjdXJzb3JLZXldO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgcmV0dXJuIGN1cnNvcjtcbn1cblxuZnVuY3Rpb24gcHJldmVudE9yaWdpbmFsRGVmYXVsdCAoKSB7XG4gICAgdGhpcy5vcmlnaW5hbEV2ZW50LnByZXZlbnREZWZhdWx0KCk7XG59XG5cbkludGVyYWN0aW9uLnByb3RvdHlwZSA9IHtcbiAgICBnZXRQYWdlWFkgIDogZnVuY3Rpb24gKHBvaW50ZXIsIHh5KSB7IHJldHVybiAgIHV0aWxzLmdldFBhZ2VYWShwb2ludGVyLCB4eSwgdGhpcyk7IH0sXG4gICAgZ2V0Q2xpZW50WFk6IGZ1bmN0aW9uIChwb2ludGVyLCB4eSkgeyByZXR1cm4gdXRpbHMuZ2V0Q2xpZW50WFkocG9pbnRlciwgeHksIHRoaXMpOyB9LFxuICAgIHNldEV2ZW50WFkgOiBmdW5jdGlvbiAodGFyZ2V0LCBwdHIpIHsgcmV0dXJuICB1dGlscy5zZXRFdmVudFhZKHRhcmdldCwgcHRyLCB0aGlzKTsgfSxcblxuICAgIHBvaW50ZXJPdmVyOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIGlmICh0aGlzLnByZXBhcmVkLm5hbWUgfHwgIXRoaXMubW91c2UpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgdmFyIGN1ck1hdGNoZXMgPSBbXSxcbiAgICAgICAgICAgIGN1ck1hdGNoRWxlbWVudHMgPSBbXSxcbiAgICAgICAgICAgIHByZXZUYXJnZXRFbGVtZW50ID0gdGhpcy5lbGVtZW50O1xuXG4gICAgICAgIHRoaXMuYWRkUG9pbnRlcihwb2ludGVyKTtcblxuICAgICAgICBpZiAodGhpcy50YXJnZXRcbiAgICAgICAgICAgICYmIChzY29wZS50ZXN0SWdub3JlKHRoaXMudGFyZ2V0LCB0aGlzLmVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgfHwgIXNjb3BlLnRlc3RBbGxvdyh0aGlzLnRhcmdldCwgdGhpcy5lbGVtZW50LCBldmVudFRhcmdldCkpKSB7XG4gICAgICAgICAgICAvLyBpZiB0aGUgZXZlbnRUYXJnZXQgc2hvdWxkIGJlIGlnbm9yZWQgb3Igc2hvdWxkbid0IGJlIGFsbG93ZWRcbiAgICAgICAgICAgIC8vIGNsZWFyIHRoZSBwcmV2aW91cyB0YXJnZXRcbiAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gbnVsbDtcbiAgICAgICAgICAgIHRoaXMuZWxlbWVudCA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hFbGVtZW50cyA9IFtdO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIGVsZW1lbnRJbnRlcmFjdGFibGUgPSBzY29wZS5pbnRlcmFjdGFibGVzLmdldChldmVudFRhcmdldCksXG4gICAgICAgICAgICBlbGVtZW50QWN0aW9uID0gKGVsZW1lbnRJbnRlcmFjdGFibGVcbiAgICAgICAgICAgICYmICFzY29wZS50ZXN0SWdub3JlKGVsZW1lbnRJbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICYmIHNjb3BlLnRlc3RBbGxvdyhlbGVtZW50SW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAmJiB2YWxpZGF0ZUFjdGlvbihcbiAgICAgICAgICAgICAgICBlbGVtZW50SW50ZXJhY3RhYmxlLmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgZXZlbnRUYXJnZXQpLFxuICAgICAgICAgICAgICAgIGVsZW1lbnRJbnRlcmFjdGFibGUpKTtcblxuICAgICAgICBpZiAoZWxlbWVudEFjdGlvbiAmJiAhc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdChlbGVtZW50SW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCwgZWxlbWVudEFjdGlvbikpIHtcbiAgICAgICAgICAgIGVsZW1lbnRBY3Rpb24gPSBudWxsO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gcHVzaEN1ck1hdGNoZXMgKGludGVyYWN0YWJsZSwgc2VsZWN0b3IpIHtcbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGFibGVcbiAgICAgICAgICAgICAgICAmJiBzY29wZS5pbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS50ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgc2NvcGUubWF0Y2hlc1NlbGVjdG9yKGV2ZW50VGFyZ2V0LCBzZWxlY3RvcikpIHtcblxuICAgICAgICAgICAgICAgIGN1ck1hdGNoZXMucHVzaChpbnRlcmFjdGFibGUpO1xuICAgICAgICAgICAgICAgIGN1ck1hdGNoRWxlbWVudHMucHVzaChldmVudFRhcmdldCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoZWxlbWVudEFjdGlvbikge1xuICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBlbGVtZW50SW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hFbGVtZW50cyA9IFtdO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IocHVzaEN1ck1hdGNoZXMpO1xuXG4gICAgICAgICAgICBpZiAodGhpcy52YWxpZGF0ZVNlbGVjdG9yKHBvaW50ZXIsIGV2ZW50LCBjdXJNYXRjaGVzLCBjdXJNYXRjaEVsZW1lbnRzKSkge1xuICAgICAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IGN1ck1hdGNoZXM7XG4gICAgICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gY3VyTWF0Y2hFbGVtZW50cztcblxuICAgICAgICAgICAgICAgIHRoaXMucG9pbnRlckhvdmVyKHBvaW50ZXIsIGV2ZW50LCB0aGlzLm1hdGNoZXMsIHRoaXMubWF0Y2hFbGVtZW50cyk7XG4gICAgICAgICAgICAgICAgZXZlbnRzLmFkZChldmVudFRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgc2NvcGUuUG9pbnRlckV2ZW50PyBzY29wZS5wRXZlbnRUeXBlcy5tb3ZlIDogJ21vdXNlbW92ZScsXG4gICAgICAgICAgICAgICAgICAgIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAodGhpcy50YXJnZXQpIHtcbiAgICAgICAgICAgICAgICBpZiAoc2NvcGUubm9kZUNvbnRhaW5zKHByZXZUYXJnZXRFbGVtZW50LCBldmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wb2ludGVySG92ZXIocG9pbnRlciwgZXZlbnQsIHRoaXMubWF0Y2hlcywgdGhpcy5tYXRjaEVsZW1lbnRzKTtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnRzLmFkZCh0aGlzLmVsZW1lbnQsXG4gICAgICAgICAgICAgICAgICAgICAgICBzY29wZS5Qb2ludGVyRXZlbnQ/IHNjb3BlLnBFdmVudFR5cGVzLm1vdmUgOiAnbW91c2Vtb3ZlJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLmVsZW1lbnQgPSBudWxsO1xuICAgICAgICAgICAgICAgICAgICB0aGlzLm1hdGNoZXMgPSBbXTtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfSxcblxuICAgIC8vIENoZWNrIHdoYXQgYWN0aW9uIHdvdWxkIGJlIHBlcmZvcm1lZCBvbiBwb2ludGVyTW92ZSB0YXJnZXQgaWYgYSBtb3VzZVxuICAgIC8vIGJ1dHRvbiB3ZXJlIHByZXNzZWQgYW5kIGNoYW5nZSB0aGUgY3Vyc29yIGFjY29yZGluZ2x5XG4gICAgcG9pbnRlckhvdmVyOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cykge1xuICAgICAgICB2YXIgdGFyZ2V0ID0gdGhpcy50YXJnZXQ7XG5cbiAgICAgICAgaWYgKCF0aGlzLnByZXBhcmVkLm5hbWUgJiYgdGhpcy5tb3VzZSkge1xuXG4gICAgICAgICAgICB2YXIgYWN0aW9uO1xuXG4gICAgICAgICAgICAvLyB1cGRhdGUgcG9pbnRlciBjb29yZHMgZm9yIGRlZmF1bHRBY3Rpb25DaGVja2VyIHRvIHVzZVxuICAgICAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuY3VyQ29vcmRzLCBwb2ludGVyKTtcblxuICAgICAgICAgICAgaWYgKG1hdGNoZXMpIHtcbiAgICAgICAgICAgICAgICBhY3Rpb24gPSB0aGlzLnZhbGlkYXRlU2VsZWN0b3IocG9pbnRlciwgZXZlbnQsIG1hdGNoZXMsIG1hdGNoRWxlbWVudHMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAodGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgYWN0aW9uID0gdmFsaWRhdGVBY3Rpb24odGFyZ2V0LmdldEFjdGlvbih0aGlzLnBvaW50ZXJzWzBdLCBldmVudCwgdGhpcywgdGhpcy5lbGVtZW50KSwgdGhpcy50YXJnZXQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zLnN0eWxlQ3Vyc29yKSB7XG4gICAgICAgICAgICAgICAgaWYgKGFjdGlvbikge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gZ2V0QWN0aW9uQ3Vyc29yKGFjdGlvbik7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHRoaXMucHJlcGFyZWQubmFtZSkge1xuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgcG9pbnRlck91dDogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCkge1xuICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5uYW1lKSB7IHJldHVybjsgfVxuXG4gICAgICAgIC8vIFJlbW92ZSB0ZW1wb3JhcnkgZXZlbnQgbGlzdGVuZXJzIGZvciBzZWxlY3RvciBJbnRlcmFjdGFibGVzXG4gICAgICAgIGlmICghc2NvcGUuaW50ZXJhY3RhYmxlcy5nZXQoZXZlbnRUYXJnZXQpKSB7XG4gICAgICAgICAgICBldmVudHMucmVtb3ZlKGV2ZW50VGFyZ2V0LFxuICAgICAgICAgICAgICAgIHNjb3BlLlBvaW50ZXJFdmVudD8gc2NvcGUucEV2ZW50VHlwZXMubW92ZSA6ICdtb3VzZW1vdmUnLFxuICAgICAgICAgICAgICAgIHNjb3BlLmxpc3RlbmVycy5wb2ludGVySG92ZXIpO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMudGFyZ2V0ICYmIHRoaXMudGFyZ2V0Lm9wdGlvbnMuc3R5bGVDdXJzb3IgJiYgIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgdGhpcy50YXJnZXQuX2RvYy5kb2N1bWVudEVsZW1lbnQuc3R5bGUuY3Vyc29yID0gJyc7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc2VsZWN0b3JEb3duOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgdGhhdCA9IHRoaXMsXG4gICAgICAgIC8vIGNvcHkgZXZlbnQgdG8gYmUgdXNlZCBpbiB0aW1lb3V0IGZvciBJRThcbiAgICAgICAgICAgIGV2ZW50Q29weSA9IGV2ZW50cy51c2VBdHRhY2hFdmVudD8gdXRpbHMuZXh0ZW5kKHt9LCBldmVudCkgOiBldmVudCxcbiAgICAgICAgICAgIGVsZW1lbnQgPSBldmVudFRhcmdldCxcbiAgICAgICAgICAgIHBvaW50ZXJJbmRleCA9IHRoaXMuYWRkUG9pbnRlcihwb2ludGVyKSxcbiAgICAgICAgICAgIGFjdGlvbjtcblxuICAgICAgICB0aGlzLmhvbGRUaW1lcnNbcG9pbnRlckluZGV4XSA9IHNldFRpbWVvdXQoZnVuY3Rpb24gKCkge1xuICAgICAgICAgICAgdGhhdC5wb2ludGVySG9sZChldmVudHMudXNlQXR0YWNoRXZlbnQ/IGV2ZW50Q29weSA6IHBvaW50ZXIsIGV2ZW50Q29weSwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KTtcbiAgICAgICAgfSwgc2NvcGUuZGVmYXVsdE9wdGlvbnMuX2hvbGREdXJhdGlvbik7XG5cbiAgICAgICAgdGhpcy5wb2ludGVySXNEb3duID0gdHJ1ZTtcblxuICAgICAgICAvLyBDaGVjayBpZiB0aGUgZG93biBldmVudCBoaXRzIHRoZSBjdXJyZW50IGluZXJ0aWEgdGFyZ2V0XG4gICAgICAgIGlmICh0aGlzLmluZXJ0aWFTdGF0dXMuYWN0aXZlICYmIHRoaXMudGFyZ2V0LnNlbGVjdG9yKSB7XG4gICAgICAgICAgICAvLyBjbGltYiB1cCB0aGUgRE9NIHRyZWUgZnJvbSB0aGUgZXZlbnQgdGFyZ2V0XG4gICAgICAgICAgICB3aGlsZSAodXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7XG5cbiAgICAgICAgICAgICAgICAvLyBpZiB0aGlzIGVsZW1lbnQgaXMgdGhlIGN1cnJlbnQgaW5lcnRpYSB0YXJnZXQgZWxlbWVudFxuICAgICAgICAgICAgICAgIGlmIChlbGVtZW50ID09PSB0aGlzLmVsZW1lbnRcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGFuZCB0aGUgcHJvc3BlY3RpdmUgYWN0aW9uIGlzIHRoZSBzYW1lIGFzIHRoZSBvbmdvaW5nIG9uZVxuICAgICAgICAgICAgICAgICAgICAmJiB2YWxpZGF0ZUFjdGlvbih0aGlzLnRhcmdldC5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIHRoaXMuZWxlbWVudCksIHRoaXMudGFyZ2V0KS5uYW1lID09PSB0aGlzLnByZXBhcmVkLm5hbWUpIHtcblxuICAgICAgICAgICAgICAgICAgICAvLyBzdG9wIGluZXJ0aWEgc28gdGhhdCB0aGUgbmV4dCBtb3ZlIHdpbGwgYmUgYSBub3JtYWwgb25lXG4gICAgICAgICAgICAgICAgICAgIGFuaW1hdGlvbkZyYW1lLmNhbmNlbCh0aGlzLmluZXJ0aWFTdGF0dXMuaSk7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcblxuICAgICAgICAgICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnZG93bicpO1xuICAgICAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsZW1lbnQgPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gZG8gbm90aGluZyBpZiBpbnRlcmFjdGluZ1xuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnZG93bicpO1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgZnVuY3Rpb24gcHVzaE1hdGNoZXMgKGludGVyYWN0YWJsZSwgc2VsZWN0b3IsIGNvbnRleHQpIHtcbiAgICAgICAgICAgIHZhciBlbGVtZW50cyA9IHNjb3BlLmllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgID8gY29udGV4dC5xdWVyeVNlbGVjdG9yQWxsKHNlbGVjdG9yKVxuICAgICAgICAgICAgICAgIDogdW5kZWZpbmVkO1xuXG4gICAgICAgICAgICBpZiAoc2NvcGUuaW5Db250ZXh0KGludGVyYWN0YWJsZSwgZWxlbWVudClcbiAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIHNjb3BlLnRlc3RBbGxvdyhpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICYmIHNjb3BlLm1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBzZWxlY3RvciwgZWxlbWVudHMpKSB7XG5cbiAgICAgICAgICAgICAgICB0aGF0Lm1hdGNoZXMucHVzaChpbnRlcmFjdGFibGUpO1xuICAgICAgICAgICAgICAgIHRoYXQubWF0Y2hFbGVtZW50cy5wdXNoKGVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gdXBkYXRlIHBvaW50ZXIgY29vcmRzIGZvciBkZWZhdWx0QWN0aW9uQ2hlY2tlciB0byB1c2VcbiAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuY3VyQ29vcmRzLCBwb2ludGVyKTtcbiAgICAgICAgdGhpcy5kb3duRXZlbnQgPSBldmVudDtcblxuICAgICAgICB3aGlsZSAodXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpICYmICFhY3Rpb24pIHtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG5cbiAgICAgICAgICAgIHNjb3BlLmludGVyYWN0YWJsZXMuZm9yRWFjaFNlbGVjdG9yKHB1c2hNYXRjaGVzKTtcblxuICAgICAgICAgICAgYWN0aW9uID0gdGhpcy52YWxpZGF0ZVNlbGVjdG9yKHBvaW50ZXIsIGV2ZW50LCB0aGlzLm1hdGNoZXMsIHRoaXMubWF0Y2hFbGVtZW50cyk7XG4gICAgICAgICAgICBlbGVtZW50ID0gc2NvcGUucGFyZW50RWxlbWVudChlbGVtZW50KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChhY3Rpb24pIHtcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSAgPSBhY3Rpb24ubmFtZTtcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuZWRnZXMgPSBhY3Rpb24uZWRnZXM7XG5cbiAgICAgICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICdkb3duJyk7XG5cbiAgICAgICAgICAgIHJldHVybiB0aGlzLnBvaW50ZXJEb3duKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgY3VyRXZlbnRUYXJnZXQsIGFjdGlvbik7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAvLyBkbyB0aGVzZSBub3cgc2luY2UgcG9pbnRlckRvd24gaXNuJ3QgYmVpbmcgY2FsbGVkIGZyb20gaGVyZVxuICAgICAgICAgICAgdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgICAgICAgdGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdID0gZXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB1dGlscy5leHRlbmQodGhpcy5kb3duUG9pbnRlciwgcG9pbnRlcik7XG5cbiAgICAgICAgICAgIHV0aWxzLmNvcHlDb29yZHModGhpcy5wcmV2Q29vcmRzLCB0aGlzLmN1ckNvb3Jkcyk7XG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJXYXNNb3ZlZCA9IGZhbHNlO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2Rvd24nKTtcbiAgICB9LFxuXG4gICAgLy8gRGV0ZXJtaW5lIGFjdGlvbiB0byBiZSBwZXJmb3JtZWQgb24gbmV4dCBwb2ludGVyTW92ZSBhbmQgYWRkIGFwcHJvcHJpYXRlXG4gICAgLy8gc3R5bGUgYW5kIGV2ZW50IExpc3RlbmVyc1xuICAgIHBvaW50ZXJEb3duOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCwgZm9yY2VBY3Rpb24pIHtcbiAgICAgICAgaWYgKCFmb3JjZUFjdGlvbiAmJiAhdGhpcy5pbmVydGlhU3RhdHVzLmFjdGl2ZSAmJiB0aGlzLnBvaW50ZXJXYXNNb3ZlZCAmJiB0aGlzLnByZXBhcmVkLm5hbWUpIHtcbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMucG9pbnRlcklzRG93biA9IHRydWU7XG4gICAgICAgIHRoaXMuZG93bkV2ZW50ID0gZXZlbnQ7XG5cbiAgICAgICAgdmFyIHBvaW50ZXJJbmRleCA9IHRoaXMuYWRkUG9pbnRlcihwb2ludGVyKSxcbiAgICAgICAgICAgIGFjdGlvbjtcblxuICAgICAgICAvLyBJZiBpdCBpcyB0aGUgc2Vjb25kIHRvdWNoIG9mIGEgbXVsdGktdG91Y2ggZ2VzdHVyZSwga2VlcCB0aGUgdGFyZ2V0XG4gICAgICAgIC8vIHRoZSBzYW1lIGlmIGEgdGFyZ2V0IHdhcyBzZXQgYnkgdGhlIGZpcnN0IHRvdWNoXG4gICAgICAgIC8vIE90aGVyd2lzZSwgc2V0IHRoZSB0YXJnZXQgaWYgdGhlcmUgaXMgbm8gYWN0aW9uIHByZXBhcmVkXG4gICAgICAgIGlmICgodGhpcy5wb2ludGVySWRzLmxlbmd0aCA8IDIgJiYgIXRoaXMudGFyZ2V0KSB8fCAhdGhpcy5wcmVwYXJlZC5uYW1lKSB7XG5cbiAgICAgICAgICAgIHZhciBpbnRlcmFjdGFibGUgPSBzY29wZS5pbnRlcmFjdGFibGVzLmdldChjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGFibGVcbiAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGN1ckV2ZW50VGFyZ2V0LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS50ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBjdXJFdmVudFRhcmdldCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgJiYgKGFjdGlvbiA9IHZhbGlkYXRlQWN0aW9uKGZvcmNlQWN0aW9uIHx8IGludGVyYWN0YWJsZS5nZXRBY3Rpb24ocG9pbnRlciwgZXZlbnQsIHRoaXMsIGN1ckV2ZW50VGFyZ2V0KSwgaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldCkpXG4gICAgICAgICAgICAgICAgJiYgc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdChpbnRlcmFjdGFibGUsIGN1ckV2ZW50VGFyZ2V0LCBhY3Rpb24pKSB7XG4gICAgICAgICAgICAgICAgdGhpcy50YXJnZXQgPSBpbnRlcmFjdGFibGU7XG4gICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gY3VyRXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdGFyZ2V0ID0gdGhpcy50YXJnZXQsXG4gICAgICAgICAgICBvcHRpb25zID0gdGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zO1xuXG4gICAgICAgIGlmICh0YXJnZXQgJiYgKGZvcmNlQWN0aW9uIHx8ICF0aGlzLnByZXBhcmVkLm5hbWUpKSB7XG4gICAgICAgICAgICBhY3Rpb24gPSBhY3Rpb24gfHwgdmFsaWRhdGVBY3Rpb24oZm9yY2VBY3Rpb24gfHwgdGFyZ2V0LmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgY3VyRXZlbnRUYXJnZXQpLCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLnN0YXJ0Q29vcmRzKTtcblxuICAgICAgICAgICAgaWYgKCFhY3Rpb24pIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgICAgIGlmIChvcHRpb25zLnN0eWxlQ3Vyc29yKSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0Ll9kb2MuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9IGdldEFjdGlvbkN1cnNvcihhY3Rpb24pO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0aGlzLnJlc2l6ZUF4ZXMgPSBhY3Rpb24ubmFtZSA9PT0gJ3Jlc2l6ZSc/IGFjdGlvbi5heGlzIDogbnVsbDtcblxuICAgICAgICAgICAgaWYgKGFjdGlvbiA9PT0gJ2dlc3R1cmUnICYmIHRoaXMucG9pbnRlcklkcy5sZW5ndGggPCAyKSB7XG4gICAgICAgICAgICAgICAgYWN0aW9uID0gbnVsbDtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5wcmVwYXJlZC5uYW1lICA9IGFjdGlvbi5uYW1lO1xuICAgICAgICAgICAgdGhpcy5wcmVwYXJlZC5heGlzICA9IGFjdGlvbi5heGlzO1xuICAgICAgICAgICAgdGhpcy5wcmVwYXJlZC5lZGdlcyA9IGFjdGlvbi5lZGdlcztcblxuICAgICAgICAgICAgdGhpcy5zbmFwU3RhdHVzLnNuYXBwZWRYID0gdGhpcy5zbmFwU3RhdHVzLnNuYXBwZWRZID1cbiAgICAgICAgICAgICAgICB0aGlzLnJlc3RyaWN0U3RhdHVzLnJlc3RyaWN0ZWRYID0gdGhpcy5yZXN0cmljdFN0YXR1cy5yZXN0cmljdGVkWSA9IE5hTjtcblxuICAgICAgICAgICAgdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XSA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgICAgICAgdGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdID0gZXZlbnRUYXJnZXQ7XG4gICAgICAgICAgICB1dGlscy5leHRlbmQodGhpcy5kb3duUG9pbnRlciwgcG9pbnRlcik7XG5cbiAgICAgICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLnByZXZDb29yZHMpO1xuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gaWYgaW5lcnRpYSBpcyBhY3RpdmUgdHJ5IHRvIHJlc3VtZSBhY3Rpb25cbiAgICAgICAgZWxzZSBpZiAodGhpcy5pbmVydGlhU3RhdHVzLmFjdGl2ZVxuICAgICAgICAgICAgJiYgY3VyRXZlbnRUYXJnZXQgPT09IHRoaXMuZWxlbWVudFxuICAgICAgICAgICAgJiYgdmFsaWRhdGVBY3Rpb24odGFyZ2V0LmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgdGhpcy5lbGVtZW50KSwgdGFyZ2V0KS5uYW1lID09PSB0aGlzLnByZXBhcmVkLm5hbWUpIHtcblxuICAgICAgICAgICAgYW5pbWF0aW9uRnJhbWUuY2FuY2VsKHRoaXMuaW5lcnRpYVN0YXR1cy5pKTtcbiAgICAgICAgICAgIHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5jaGVja0FuZFByZXZlbnREZWZhdWx0KGV2ZW50LCB0YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc2V0TW9kaWZpY2F0aW9uczogZnVuY3Rpb24gKGNvb3JkcywgcHJlRW5kKSB7XG4gICAgICAgIHZhciB0YXJnZXQgICAgICAgICA9IHRoaXMudGFyZ2V0LFxuICAgICAgICAgICAgc2hvdWxkTW92ZSAgICAgPSB0cnVlLFxuICAgICAgICAgICAgc2hvdWxkU25hcCAgICAgPSBzY29wZS5jaGVja1NuYXAodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICAgICAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uc25hcC5lbmRPbmx5ICAgICB8fCBwcmVFbmQpLFxuICAgICAgICAgICAgc2hvdWxkUmVzdHJpY3QgPSBzY29wZS5jaGVja1Jlc3RyaWN0KHRhcmdldCwgdGhpcy5wcmVwYXJlZC5uYW1lKSAmJiAoIXRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QuZW5kT25seSB8fCBwcmVFbmQpO1xuXG4gICAgICAgIGlmIChzaG91bGRTbmFwICAgICkgeyB0aGlzLnNldFNuYXBwaW5nICAgKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMuc25hcFN0YXR1cyAgICAubG9ja2VkICAgICA9IGZhbHNlOyB9XG4gICAgICAgIGlmIChzaG91bGRSZXN0cmljdCkgeyB0aGlzLnNldFJlc3RyaWN0aW9uKGNvb3Jkcyk7IH0gZWxzZSB7IHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlOyB9XG5cbiAgICAgICAgaWYgKHNob3VsZFNuYXAgJiYgdGhpcy5zbmFwU3RhdHVzLmxvY2tlZCAmJiAhdGhpcy5zbmFwU3RhdHVzLmNoYW5nZWQpIHtcbiAgICAgICAgICAgIHNob3VsZE1vdmUgPSBzaG91bGRSZXN0cmljdCAmJiB0aGlzLnJlc3RyaWN0U3RhdHVzLnJlc3RyaWN0ZWQgJiYgdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHNob3VsZFJlc3RyaWN0ICYmIHRoaXMucmVzdHJpY3RTdGF0dXMucmVzdHJpY3RlZCAmJiAhdGhpcy5yZXN0cmljdFN0YXR1cy5jaGFuZ2VkKSB7XG4gICAgICAgICAgICBzaG91bGRNb3ZlID0gZmFsc2U7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gc2hvdWxkTW92ZTtcbiAgICB9LFxuXG4gICAgc2V0U3RhcnRPZmZzZXRzOiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgdmFyIHJlY3QgPSBpbnRlcmFjdGFibGUuZ2V0UmVjdChlbGVtZW50KSxcbiAgICAgICAgICAgIG9yaWdpbiA9IHNjb3BlLmdldE9yaWdpblhZKGludGVyYWN0YWJsZSwgZWxlbWVudCksXG4gICAgICAgICAgICBzbmFwID0gaW50ZXJhY3RhYmxlLm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgcmVzdHJpY3QgPSBpbnRlcmFjdGFibGUub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnJlc3RyaWN0LFxuICAgICAgICAgICAgd2lkdGgsIGhlaWdodDtcblxuICAgICAgICBpZiAocmVjdCkge1xuICAgICAgICAgICAgdGhpcy5zdGFydE9mZnNldC5sZWZ0ID0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnggLSByZWN0LmxlZnQ7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueSAtIHJlY3QudG9wO1xuXG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICA9IHJlY3QucmlnaHQgIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLng7XG4gICAgICAgICAgICB0aGlzLnN0YXJ0T2Zmc2V0LmJvdHRvbSA9IHJlY3QuYm90dG9tIC0gdGhpcy5zdGFydENvb3Jkcy5wYWdlLnk7XG5cbiAgICAgICAgICAgIGlmICgnd2lkdGgnIGluIHJlY3QpIHsgd2lkdGggPSByZWN0LndpZHRoOyB9XG4gICAgICAgICAgICBlbHNlIHsgd2lkdGggPSByZWN0LnJpZ2h0IC0gcmVjdC5sZWZ0OyB9XG4gICAgICAgICAgICBpZiAoJ2hlaWdodCcgaW4gcmVjdCkgeyBoZWlnaHQgPSByZWN0LmhlaWdodDsgfVxuICAgICAgICAgICAgZWxzZSB7IGhlaWdodCA9IHJlY3QuYm90dG9tIC0gcmVjdC50b3A7IH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc3RhcnRPZmZzZXQubGVmdCA9IHRoaXMuc3RhcnRPZmZzZXQudG9wID0gdGhpcy5zdGFydE9mZnNldC5yaWdodCA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tID0gMDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMuc25hcE9mZnNldHMuc3BsaWNlKDApO1xuXG4gICAgICAgIHZhciBzbmFwT2Zmc2V0ID0gc25hcCAmJiBzbmFwLm9mZnNldCA9PT0gJ3N0YXJ0Q29vcmRzJ1xuICAgICAgICAgICAgPyB7XG4gICAgICAgICAgICB4OiB0aGlzLnN0YXJ0Q29vcmRzLnBhZ2UueCAtIG9yaWdpbi54LFxuICAgICAgICAgICAgeTogdGhpcy5zdGFydENvb3Jkcy5wYWdlLnkgLSBvcmlnaW4ueVxuICAgICAgICB9XG4gICAgICAgICAgICA6IHNuYXAgJiYgc25hcC5vZmZzZXQgfHwgeyB4OiAwLCB5OiAwIH07XG5cbiAgICAgICAgaWYgKHJlY3QgJiYgc25hcCAmJiBzbmFwLnJlbGF0aXZlUG9pbnRzICYmIHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoKSB7XG4gICAgICAgICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHNuYXAucmVsYXRpdmVQb2ludHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLnNuYXBPZmZzZXRzLnB1c2goe1xuICAgICAgICAgICAgICAgICAgICB4OiB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogc25hcC5yZWxhdGl2ZVBvaW50c1tpXS54KSArIHNuYXBPZmZzZXQueCxcbiAgICAgICAgICAgICAgICAgICAgeTogdGhpcy5zdGFydE9mZnNldC50b3AgIC0gKGhlaWdodCAqIHNuYXAucmVsYXRpdmVQb2ludHNbaV0ueSkgKyBzbmFwT2Zmc2V0LnlcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHRoaXMuc25hcE9mZnNldHMucHVzaChzbmFwT2Zmc2V0KTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChyZWN0ICYmIHJlc3RyaWN0LmVsZW1lbnRSZWN0KSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnN0YXJ0T2Zmc2V0LmxlZnQgLSAod2lkdGggICogcmVzdHJpY3QuZWxlbWVudFJlY3QubGVmdCk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnRvcCAgLSAoaGVpZ2h0ICogcmVzdHJpY3QuZWxlbWVudFJlY3QudG9wKTtcblxuICAgICAgICAgICAgdGhpcy5yZXN0cmljdE9mZnNldC5yaWdodCAgPSB0aGlzLnN0YXJ0T2Zmc2V0LnJpZ2h0ICAtICh3aWR0aCAgKiAoMSAtIHJlc3RyaWN0LmVsZW1lbnRSZWN0LnJpZ2h0KSk7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IHRoaXMuc3RhcnRPZmZzZXQuYm90dG9tIC0gKGhlaWdodCAqICgxIC0gcmVzdHJpY3QuZWxlbWVudFJlY3QuYm90dG9tKSk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB0aGlzLnJlc3RyaWN0T2Zmc2V0LmxlZnQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnRvcCA9IHRoaXMucmVzdHJpY3RPZmZzZXQucmlnaHQgPSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSA9IDA7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLypcXFxuICAgICAqIEludGVyYWN0aW9uLnN0YXJ0XG4gICAgIFsgbWV0aG9kIF1cbiAgICAgKlxuICAgICAqIFN0YXJ0IGFuIGFjdGlvbiB3aXRoIHRoZSBnaXZlbiBJbnRlcmFjdGFibGUgYW5kIEVsZW1lbnQgYXMgdGFydGdldHMuIFRoZVxuICAgICAqIGFjdGlvbiBtdXN0IGJlIGVuYWJsZWQgZm9yIHRoZSB0YXJnZXQgSW50ZXJhY3RhYmxlIGFuZCBhbiBhcHByb3ByaWF0ZSBudW1iZXJcbiAgICAgKiBvZiBwb2ludGVycyBtdXN0IGJlIGhlbGQgZG93biDigJMgMSBmb3IgZHJhZy9yZXNpemUsIDIgZm9yIGdlc3R1cmUuXG4gICAgICpcbiAgICAgKiBVc2UgaXQgd2l0aCBgaW50ZXJhY3RhYmxlLjxhY3Rpb24+YWJsZSh7IG1hbnVhbFN0YXJ0OiBmYWxzZSB9KWAgdG8gYWx3YXlzXG4gICAgICogW3N0YXJ0IGFjdGlvbnMgbWFudWFsbHldKGh0dHBzOi8vZ2l0aHViLmNvbS90YXllL2ludGVyYWN0LmpzL2lzc3Vlcy8xMTQpXG4gICAgICpcbiAgICAgLSBhY3Rpb24gICAgICAgKG9iamVjdCkgIFRoZSBhY3Rpb24gdG8gYmUgcGVyZm9ybWVkIC0gZHJhZywgcmVzaXplLCBldGMuXG4gICAgIC0gaW50ZXJhY3RhYmxlIChJbnRlcmFjdGFibGUpIFRoZSBJbnRlcmFjdGFibGUgdG8gdGFyZ2V0XG4gICAgIC0gZWxlbWVudCAgICAgIChFbGVtZW50KSBUaGUgRE9NIEVsZW1lbnQgdG8gdGFyZ2V0XG4gICAgID0gKG9iamVjdCkgaW50ZXJhY3RcbiAgICAgKipcbiAgICAgfCBpbnRlcmFjdCh0YXJnZXQpXG4gICAgIHwgICAuZHJhZ2dhYmxlKHtcbiAgICAgfCAgICAgLy8gZGlzYWJsZSB0aGUgZGVmYXVsdCBkcmFnIHN0YXJ0IGJ5IGRvd24tPm1vdmVcbiAgICAgfCAgICAgbWFudWFsU3RhcnQ6IHRydWVcbiAgICAgfCAgIH0pXG4gICAgIHwgICAvLyBzdGFydCBkcmFnZ2luZyBhZnRlciB0aGUgdXNlciBob2xkcyB0aGUgcG9pbnRlciBkb3duXG4gICAgIHwgICAub24oJ2hvbGQnLCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgfCAgICAgdmFyIGludGVyYWN0aW9uID0gZXZlbnQuaW50ZXJhY3Rpb247XG4gICAgIHxcbiAgICAgfCAgICAgaWYgKCFpbnRlcmFjdGlvbi5pbnRlcmFjdGluZygpKSB7XG4gICAgIHwgICAgICAgaW50ZXJhY3Rpb24uc3RhcnQoeyBuYW1lOiAnZHJhZycgfSxcbiAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICBldmVudC5pbnRlcmFjdGFibGUsXG4gICAgIHwgICAgICAgICAgICAgICAgICAgICAgICAgZXZlbnQuY3VycmVudFRhcmdldCk7XG4gICAgIHwgICAgIH1cbiAgICAgfCB9KTtcbiAgICAgXFwqL1xuICAgIHN0YXJ0OiBmdW5jdGlvbiAoYWN0aW9uLCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgfHwgIXRoaXMucG9pbnRlcklzRG93blxuICAgICAgICAgICAgfHwgdGhpcy5wb2ludGVySWRzLmxlbmd0aCA8IChhY3Rpb24ubmFtZSA9PT0gJ2dlc3R1cmUnPyAyIDogMSkpIHtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIC8vIGlmIHRoaXMgaW50ZXJhY3Rpb24gaGFkIGJlZW4gcmVtb3ZlZCBhZnRlciBzdG9wcGluZ1xuICAgICAgICAvLyBhZGQgaXQgYmFja1xuICAgICAgICBpZiAoc2NvcGUuaW5kZXhPZihzY29wZS5pbnRlcmFjdGlvbnMsIHRoaXMpID09PSAtMSkge1xuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3Rpb25zLnB1c2godGhpcyk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgID0gYWN0aW9uLm5hbWU7XG4gICAgICAgIHRoaXMucHJlcGFyZWQuYXhpcyAgPSBhY3Rpb24uYXhpcztcbiAgICAgICAgdGhpcy5wcmVwYXJlZC5lZGdlcyA9IGFjdGlvbi5lZGdlcztcbiAgICAgICAgdGhpcy50YXJnZXQgICAgICAgICA9IGludGVyYWN0YWJsZTtcbiAgICAgICAgdGhpcy5lbGVtZW50ICAgICAgICA9IGVsZW1lbnQ7XG5cbiAgICAgICAgdGhpcy5zZXRFdmVudFhZKHRoaXMuc3RhcnRDb29yZHMpO1xuICAgICAgICB0aGlzLnNldFN0YXJ0T2Zmc2V0cyhhY3Rpb24ubmFtZSwgaW50ZXJhY3RhYmxlLCBlbGVtZW50KTtcbiAgICAgICAgdGhpcy5zZXRNb2RpZmljYXRpb25zKHRoaXMuc3RhcnRDb29yZHMucGFnZSk7XG5cbiAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdTdGFydCddKHRoaXMuZG93bkV2ZW50KTtcbiAgICB9LFxuXG4gICAgcG9pbnRlck1vdmU6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCBwcmVFbmQpIHtcbiAgICAgICAgdGhpcy5yZWNvcmRQb2ludGVyKHBvaW50ZXIpO1xuXG4gICAgICAgIHRoaXMuc2V0RXZlbnRYWSh0aGlzLmN1ckNvb3JkcywgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50KVxuICAgICAgICAgICAgPyB0aGlzLmluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudFxuICAgICAgICAgICAgOiB1bmRlZmluZWQpO1xuXG4gICAgICAgIHZhciBkdXBsaWNhdGVNb3ZlID0gKHRoaXMuY3VyQ29vcmRzLnBhZ2UueCA9PT0gdGhpcy5wcmV2Q29vcmRzLnBhZ2UueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5wYWdlLnkgPT09IHRoaXMucHJldkNvb3Jkcy5wYWdlLnlcbiAgICAgICAgJiYgdGhpcy5jdXJDb29yZHMuY2xpZW50LnggPT09IHRoaXMucHJldkNvb3Jkcy5jbGllbnQueFxuICAgICAgICAmJiB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueSA9PT0gdGhpcy5wcmV2Q29vcmRzLmNsaWVudC55KTtcblxuICAgICAgICB2YXIgZHgsIGR5LFxuICAgICAgICAgICAgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IHNjb3BlLmluZGV4T2YodGhpcy5wb2ludGVySWRzLCB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIC8vIHJlZ2lzdGVyIG1vdmVtZW50IGdyZWF0ZXIgdGhhbiBwb2ludGVyTW92ZVRvbGVyYW5jZVxuICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duICYmICF0aGlzLnBvaW50ZXJXYXNNb3ZlZCkge1xuICAgICAgICAgICAgZHggPSB0aGlzLmN1ckNvb3Jkcy5jbGllbnQueCAtIHRoaXMuc3RhcnRDb29yZHMuY2xpZW50Lng7XG4gICAgICAgICAgICBkeSA9IHRoaXMuY3VyQ29vcmRzLmNsaWVudC55IC0gdGhpcy5zdGFydENvb3Jkcy5jbGllbnQueTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyV2FzTW92ZWQgPSB1dGlscy5oeXBvdChkeCwgZHkpID4gc2NvcGUucG9pbnRlck1vdmVUb2xlcmFuY2U7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoIWR1cGxpY2F0ZU1vdmUgJiYgKCF0aGlzLnBvaW50ZXJJc0Rvd24gfHwgdGhpcy5wb2ludGVyV2FzTW92ZWQpKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5wb2ludGVySXNEb3duKSB7XG4gICAgICAgICAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ21vdmUnKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmICghdGhpcy5wb2ludGVySXNEb3duKSB7IHJldHVybjsgfVxuXG4gICAgICAgIGlmIChkdXBsaWNhdGVNb3ZlICYmIHRoaXMucG9pbnRlcldhc01vdmVkICYmICFwcmVFbmQpIHtcbiAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICAvLyBzZXQgcG9pbnRlciBjb29yZGluYXRlLCB0aW1lIGNoYW5nZXMgYW5kIHNwZWVkc1xuICAgICAgICB1dGlscy5zZXRFdmVudERlbHRhcyh0aGlzLnBvaW50ZXJEZWx0YSwgdGhpcy5wcmV2Q29vcmRzLCB0aGlzLmN1ckNvb3Jkcyk7XG5cbiAgICAgICAgaWYgKCF0aGlzLnByZXBhcmVkLm5hbWUpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgaWYgKHRoaXMucG9pbnRlcldhc01vdmVkXG4gICAgICAgICAgICAgICAgLy8gaWdub3JlIG1vdmVtZW50IHdoaWxlIGluZXJ0aWEgaXMgYWN0aXZlXG4gICAgICAgICAgICAmJiAoIXRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUgfHwgKHBvaW50ZXIgaW5zdGFuY2VvZiBJbnRlcmFjdEV2ZW50ICYmIC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkpKSB7XG5cbiAgICAgICAgICAgIC8vIGlmIGp1c3Qgc3RhcnRpbmcgYW4gYWN0aW9uLCBjYWxjdWxhdGUgdGhlIHBvaW50ZXIgc3BlZWQgbm93XG4gICAgICAgICAgICBpZiAoIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHV0aWxzLnNldEV2ZW50RGVsdGFzKHRoaXMucG9pbnRlckRlbHRhLCB0aGlzLnByZXZDb29yZHMsIHRoaXMuY3VyQ29vcmRzKTtcblxuICAgICAgICAgICAgICAgIC8vIGNoZWNrIGlmIGEgZHJhZyBpcyBpbiB0aGUgY29ycmVjdCBheGlzXG4gICAgICAgICAgICAgICAgaWYgKHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnKSB7XG4gICAgICAgICAgICAgICAgICAgIHZhciBhYnNYID0gTWF0aC5hYnMoZHgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgYWJzWSA9IE1hdGguYWJzKGR5KSxcbiAgICAgICAgICAgICAgICAgICAgICAgIHRhcmdldEF4aXMgPSB0aGlzLnRhcmdldC5vcHRpb25zLmRyYWcuYXhpcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMgPSAoYWJzWCA+IGFic1kgPyAneCcgOiBhYnNYIDwgYWJzWSA/ICd5JyA6ICd4eScpO1xuXG4gICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZSBtb3ZlbWVudCBpc24ndCBpbiB0aGUgYXhpcyBvZiB0aGUgaW50ZXJhY3RhYmxlXG4gICAgICAgICAgICAgICAgICAgIGlmIChheGlzICE9PSAneHknICYmIHRhcmdldEF4aXMgIT09ICd4eScgJiYgdGFyZ2V0QXhpcyAhPT0gYXhpcykge1xuICAgICAgICAgICAgICAgICAgICAgICAgLy8gY2FuY2VsIHRoZSBwcmVwYXJlZCBhY3Rpb25cbiAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQubmFtZSA9IG51bGw7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIHRoZW4gdHJ5IHRvIGdldCBhIGRyYWcgZnJvbSBhbm90aGVyIGluZXJhY3RhYmxlXG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIGVsZW1lbnQgaW50ZXJhY3RhYmxlc1xuICAgICAgICAgICAgICAgICAgICAgICAgd2hpbGUgKHV0aWxzLmlzRWxlbWVudChlbGVtZW50KSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50SW50ZXJhY3RhYmxlID0gc2NvcGUuaW50ZXJhY3RhYmxlcy5nZXQoZWxlbWVudCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoZWxlbWVudEludGVyYWN0YWJsZVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBlbGVtZW50SW50ZXJhY3RhYmxlICE9PSB0aGlzLnRhcmdldFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhZWxlbWVudEludGVyYWN0YWJsZS5vcHRpb25zLmRyYWcubWFudWFsU3RhcnRcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgZWxlbWVudEludGVyYWN0YWJsZS5nZXRBY3Rpb24odGhpcy5kb3duUG9pbnRlciwgdGhpcy5kb3duRXZlbnQsIHRoaXMsIGVsZW1lbnQpLm5hbWUgPT09ICdkcmFnJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBzY29wZS5jaGVja0F4aXMoYXhpcywgZWxlbWVudEludGVyYWN0YWJsZSkpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gZWxlbWVudEludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGlmIHRoZXJlJ3Mgbm8gZHJhZyBmcm9tIGVsZW1lbnQgaW50ZXJhY3RhYmxlcyxcbiAgICAgICAgICAgICAgICAgICAgICAgIC8vIGNoZWNrIHRoZSBzZWxlY3RvciBpbnRlcmFjdGFibGVzXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAoIXRoaXMucHJlcGFyZWQubmFtZSkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciB0aGlzSW50ZXJhY3Rpb24gPSB0aGlzO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyIGdldERyYWdnYWJsZSA9IGZ1bmN0aW9uIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBlbGVtZW50cyA9IHNjb3BlLmllOE1hdGNoZXNTZWxlY3RvclxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPyBjb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA6IHVuZGVmaW5lZDtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW50ZXJhY3RhYmxlID09PSB0aGlzSW50ZXJhY3Rpb24udGFyZ2V0KSB7IHJldHVybjsgfVxuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzY29wZS5pbkNvbnRleHQoaW50ZXJhY3RhYmxlLCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYmICFpbnRlcmFjdGFibGUub3B0aW9ucy5kcmFnLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiAhc2NvcGUudGVzdElnbm9yZShpbnRlcmFjdGFibGUsIGVsZW1lbnQsIGV2ZW50VGFyZ2V0KVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgc2NvcGUudGVzdEFsbG93KGludGVyYWN0YWJsZSwgZWxlbWVudCwgZXZlbnRUYXJnZXQpXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVsZW1lbnRzKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgaW50ZXJhY3RhYmxlLmdldEFjdGlvbih0aGlzSW50ZXJhY3Rpb24uZG93blBvaW50ZXIsIHRoaXNJbnRlcmFjdGlvbi5kb3duRXZlbnQsIHRoaXNJbnRlcmFjdGlvbiwgZWxlbWVudCkubmFtZSA9PT0gJ2RyYWcnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmJiBzY29wZS5jaGVja0F4aXMoYXhpcywgaW50ZXJhY3RhYmxlKVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiYgc2NvcGUud2l0aGluSW50ZXJhY3Rpb25MaW1pdChpbnRlcmFjdGFibGUsIGVsZW1lbnQsICdkcmFnJykpIHtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGludGVyYWN0YWJsZTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBlbGVtZW50ID0gZXZlbnRUYXJnZXQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICB3aGlsZSAodXRpbHMuaXNFbGVtZW50KGVsZW1lbnQpKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhciBzZWxlY3RvckludGVyYWN0YWJsZSA9IHNjb3BlLmludGVyYWN0YWJsZXMuZm9yRWFjaFNlbGVjdG9yKGdldERyYWdnYWJsZSk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWYgKHNlbGVjdG9ySW50ZXJhY3RhYmxlKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLm5hbWUgPSAnZHJhZyc7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLnRhcmdldCA9IHNlbGVjdG9ySW50ZXJhY3RhYmxlO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gZWxlbWVudDtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWxlbWVudCA9IHNjb3BlLnBhcmVudEVsZW1lbnQoZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB2YXIgc3RhcnRpbmcgPSAhIXRoaXMucHJlcGFyZWQubmFtZSAmJiAhdGhpcy5pbnRlcmFjdGluZygpO1xuXG4gICAgICAgICAgICBpZiAoc3RhcnRpbmdcbiAgICAgICAgICAgICAgICAmJiAodGhpcy50YXJnZXQub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgfHwgIXNjb3BlLndpdGhpbkludGVyYWN0aW9uTGltaXQodGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCwgdGhpcy5wcmVwYXJlZCkpKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5zdG9wKCk7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5uYW1lICYmIHRoaXMudGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgaWYgKHN0YXJ0aW5nKSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc3RhcnQodGhpcy5wcmVwYXJlZCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgdmFyIHNob3VsZE1vdmUgPSB0aGlzLnNldE1vZGlmaWNhdGlvbnModGhpcy5jdXJDb29yZHMucGFnZSwgcHJlRW5kKTtcblxuICAgICAgICAgICAgICAgIC8vIG1vdmUgaWYgc25hcHBpbmcgb3IgcmVzdHJpY3Rpb24gZG9lc24ndCBwcmV2ZW50IGl0XG4gICAgICAgICAgICAgICAgaWYgKHNob3VsZE1vdmUgfHwgc3RhcnRpbmcpIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5wcmV2RXZlbnQgPSB0aGlzW3RoaXMucHJlcGFyZWQubmFtZSArICdNb3ZlJ10oZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGhpcy50YXJnZXQsIHRoaXMuZWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB1dGlscy5jb3B5Q29vcmRzKHRoaXMucHJldkNvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgIGlmICh0aGlzLmRyYWdnaW5nIHx8IHRoaXMucmVzaXppbmcpIHtcbiAgICAgICAgICAgIHRoaXMuYXV0b1Njcm9sbE1vdmUocG9pbnRlcik7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgZHJhZ1N0YXJ0OiBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgdmFyIGRyYWdFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZHJhZycsICdzdGFydCcsIHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcmFnZ2luZyA9IHRydWU7XG4gICAgICAgIHRoaXMudGFyZ2V0LmZpcmUoZHJhZ0V2ZW50KTtcblxuICAgICAgICAvLyByZXNldCBhY3RpdmUgZHJvcHpvbmVzXG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHMgID0gW107XG4gICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgID0gW107XG5cbiAgICAgICAgaWYgKCF0aGlzLmR5bmFtaWNEcm9wKSB7XG4gICAgICAgICAgICB0aGlzLnNldEFjdGl2ZURyb3BzKHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5hY3RpdmF0ZSkge1xuICAgICAgICAgICAgdGhpcy5maXJlQWN0aXZlRHJvcHMoZHJvcEV2ZW50cy5hY3RpdmF0ZSk7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICBkcmFnTW92ZTogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIGRyYWdFdmVudCAgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ2RyYWcnLCAnbW92ZScsIHRoaXMuZWxlbWVudCksXG4gICAgICAgICAgICBkcmFnZ2FibGVFbGVtZW50ID0gdGhpcy5lbGVtZW50LFxuICAgICAgICAgICAgZHJvcCA9IHRoaXMuZ2V0RHJvcChldmVudCwgZHJhZ2dhYmxlRWxlbWVudCk7XG5cbiAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gZHJvcC5kcm9wem9uZTtcbiAgICAgICAgdGhpcy5kcm9wRWxlbWVudCA9IGRyb3AuZWxlbWVudDtcblxuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHRoaXMuZ2V0RHJvcEV2ZW50cyhldmVudCwgZHJhZ0V2ZW50KTtcblxuICAgICAgICB0YXJnZXQuZmlyZShkcmFnRXZlbnQpO1xuXG4gICAgICAgIGlmIChkcm9wRXZlbnRzLmxlYXZlKSB7IHRoaXMucHJldkRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmxlYXZlKTsgfVxuICAgICAgICBpZiAoZHJvcEV2ZW50cy5lbnRlcikgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5lbnRlcik7IH1cbiAgICAgICAgaWYgKGRyb3BFdmVudHMubW92ZSApIHsgICAgIHRoaXMuZHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMubW92ZSApOyB9XG5cbiAgICAgICAgdGhpcy5wcmV2RHJvcFRhcmdldCAgPSB0aGlzLmRyb3BUYXJnZXQ7XG4gICAgICAgIHRoaXMucHJldkRyb3BFbGVtZW50ID0gdGhpcy5kcm9wRWxlbWVudDtcblxuICAgICAgICByZXR1cm4gZHJhZ0V2ZW50O1xuICAgIH0sXG5cbiAgICByZXNpemVTdGFydDogZnVuY3Rpb24gKGV2ZW50KSB7XG4gICAgICAgIHZhciByZXNpemVFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAncmVzaXplJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBpZiAodGhpcy5wcmVwYXJlZC5lZGdlcykge1xuICAgICAgICAgICAgdmFyIHN0YXJ0UmVjdCA9IHRoaXMudGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgaWYgKHRoaXMudGFyZ2V0Lm9wdGlvbnMucmVzaXplLnNxdWFyZSkge1xuICAgICAgICAgICAgICAgIHZhciBzcXVhcmVFZGdlcyA9IHV0aWxzLmV4dGVuZCh7fSwgdGhpcy5wcmVwYXJlZC5lZGdlcyk7XG5cbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy50b3AgICAgPSBzcXVhcmVFZGdlcy50b3AgICAgfHwgKHNxdWFyZUVkZ2VzLmxlZnQgICAmJiAhc3F1YXJlRWRnZXMuYm90dG9tKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5sZWZ0ICAgPSBzcXVhcmVFZGdlcy5sZWZ0ICAgfHwgKHNxdWFyZUVkZ2VzLnRvcCAgICAmJiAhc3F1YXJlRWRnZXMucmlnaHQgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5ib3R0b20gPSBzcXVhcmVFZGdlcy5ib3R0b20gfHwgKHNxdWFyZUVkZ2VzLnJpZ2h0ICAmJiAhc3F1YXJlRWRnZXMudG9wICAgKTtcbiAgICAgICAgICAgICAgICBzcXVhcmVFZGdlcy5yaWdodCAgPSBzcXVhcmVFZGdlcy5yaWdodCAgfHwgKHNxdWFyZUVkZ2VzLmJvdHRvbSAmJiAhc3F1YXJlRWRnZXMubGVmdCAgKTtcblxuICAgICAgICAgICAgICAgIHRoaXMucHJlcGFyZWQuX3NxdWFyZUVkZ2VzID0gc3F1YXJlRWRnZXM7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcyA9IG51bGw7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIHRoaXMucmVzaXplUmVjdHMgPSB7XG4gICAgICAgICAgICAgICAgc3RhcnQgICAgIDogc3RhcnRSZWN0LFxuICAgICAgICAgICAgICAgIGN1cnJlbnQgICA6IHV0aWxzLmV4dGVuZCh7fSwgc3RhcnRSZWN0KSxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkOiB1dGlscy5leHRlbmQoe30sIHN0YXJ0UmVjdCksXG4gICAgICAgICAgICAgICAgcHJldmlvdXMgIDogdXRpbHMuZXh0ZW5kKHt9LCBzdGFydFJlY3QpLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICA6IHtcbiAgICAgICAgICAgICAgICAgICAgbGVmdDogMCwgcmlnaHQgOiAwLCB3aWR0aCA6IDAsXG4gICAgICAgICAgICAgICAgICAgIHRvcCA6IDAsIGJvdHRvbTogMCwgaGVpZ2h0OiAwXG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgcmVzaXplRXZlbnQucmVjdCA9IHRoaXMucmVzaXplUmVjdHMucmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IHRoaXMucmVzaXplUmVjdHMuZGVsdGE7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKHJlc2l6ZUV2ZW50KTtcblxuICAgICAgICB0aGlzLnJlc2l6aW5nID0gdHJ1ZTtcblxuICAgICAgICByZXR1cm4gcmVzaXplRXZlbnQ7XG4gICAgfSxcblxuICAgIHJlc2l6ZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgcmVzaXplRXZlbnQgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ3Jlc2l6ZScsICdtb3ZlJywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICB2YXIgZWRnZXMgPSB0aGlzLnByZXBhcmVkLmVkZ2VzLFxuICAgICAgICAgICAgaW52ZXJ0ID0gdGhpcy50YXJnZXQub3B0aW9ucy5yZXNpemUuaW52ZXJ0LFxuICAgICAgICAgICAgaW52ZXJ0aWJsZSA9IGludmVydCA9PT0gJ3JlcG9zaXRpb24nIHx8IGludmVydCA9PT0gJ25lZ2F0ZSc7XG5cbiAgICAgICAgaWYgKGVkZ2VzKSB7XG4gICAgICAgICAgICB2YXIgZHggPSByZXNpemVFdmVudC5keCxcbiAgICAgICAgICAgICAgICBkeSA9IHJlc2l6ZUV2ZW50LmR5LFxuXG4gICAgICAgICAgICAgICAgc3RhcnQgICAgICA9IHRoaXMucmVzaXplUmVjdHMuc3RhcnQsXG4gICAgICAgICAgICAgICAgY3VycmVudCAgICA9IHRoaXMucmVzaXplUmVjdHMuY3VycmVudCxcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkID0gdGhpcy5yZXNpemVSZWN0cy5yZXN0cmljdGVkLFxuICAgICAgICAgICAgICAgIGRlbHRhICAgICAgPSB0aGlzLnJlc2l6ZVJlY3RzLmRlbHRhLFxuICAgICAgICAgICAgICAgIHByZXZpb3VzICAgPSB1dGlscy5leHRlbmQodGhpcy5yZXNpemVSZWN0cy5wcmV2aW91cywgcmVzdHJpY3RlZCk7XG5cbiAgICAgICAgICAgIGlmICh0aGlzLnRhcmdldC5vcHRpb25zLnJlc2l6ZS5zcXVhcmUpIHtcbiAgICAgICAgICAgICAgICB2YXIgb3JpZ2luYWxFZGdlcyA9IGVkZ2VzO1xuXG4gICAgICAgICAgICAgICAgZWRnZXMgPSB0aGlzLnByZXBhcmVkLl9zcXVhcmVFZGdlcztcblxuICAgICAgICAgICAgICAgIGlmICgob3JpZ2luYWxFZGdlcy5sZWZ0ICYmIG9yaWdpbmFsRWRnZXMuYm90dG9tKVxuICAgICAgICAgICAgICAgICAgICB8fCAob3JpZ2luYWxFZGdlcy5yaWdodCAmJiBvcmlnaW5hbEVkZ2VzLnRvcCkpIHtcbiAgICAgICAgICAgICAgICAgICAgZHkgPSAtZHg7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2UgaWYgKG9yaWdpbmFsRWRnZXMubGVmdCB8fCBvcmlnaW5hbEVkZ2VzLnJpZ2h0KSB7IGR5ID0gZHg7IH1cbiAgICAgICAgICAgICAgICBlbHNlIGlmIChvcmlnaW5hbEVkZ2VzLnRvcCB8fCBvcmlnaW5hbEVkZ2VzLmJvdHRvbSkgeyBkeCA9IGR5OyB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHVwZGF0ZSB0aGUgJ2N1cnJlbnQnIHJlY3Qgd2l0aG91dCBtb2RpZmljYXRpb25zXG4gICAgICAgICAgICBpZiAoZWRnZXMudG9wICAgKSB7IGN1cnJlbnQudG9wICAgICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMuYm90dG9tKSB7IGN1cnJlbnQuYm90dG9tICs9IGR5OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMubGVmdCAgKSB7IGN1cnJlbnQubGVmdCAgICs9IGR4OyB9XG4gICAgICAgICAgICBpZiAoZWRnZXMucmlnaHQgKSB7IGN1cnJlbnQucmlnaHQgICs9IGR4OyB9XG5cbiAgICAgICAgICAgIGlmIChpbnZlcnRpYmxlKSB7XG4gICAgICAgICAgICAgICAgLy8gaWYgaW52ZXJ0aWJsZSwgY29weSB0aGUgY3VycmVudCByZWN0XG4gICAgICAgICAgICAgICAgdXRpbHMuZXh0ZW5kKHJlc3RyaWN0ZWQsIGN1cnJlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGludmVydCA9PT0gJ3JlcG9zaXRpb24nKSB7XG4gICAgICAgICAgICAgICAgICAgIC8vIHN3YXAgZWRnZSB2YWx1ZXMgaWYgbmVjZXNzYXJ5IHRvIGtlZXAgd2lkdGgvaGVpZ2h0IHBvc2l0aXZlXG4gICAgICAgICAgICAgICAgICAgIHZhciBzd2FwO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChyZXN0cmljdGVkLnRvcCA+IHJlc3RyaWN0ZWQuYm90dG9tKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBzd2FwID0gcmVzdHJpY3RlZC50b3A7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQudG9wID0gcmVzdHJpY3RlZC5ib3R0b207XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IHN3YXA7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgaWYgKHJlc3RyaWN0ZWQubGVmdCA+IHJlc3RyaWN0ZWQucmlnaHQpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHN3YXAgPSByZXN0cmljdGVkLmxlZnQ7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCA9IHJlc3RyaWN0ZWQucmlnaHQ7XG4gICAgICAgICAgICAgICAgICAgICAgICByZXN0cmljdGVkLnJpZ2h0ID0gc3dhcDtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIC8vIGlmIG5vdCBpbnZlcnRpYmxlLCByZXN0cmljdCB0byBtaW5pbXVtIG9mIDB4MCByZWN0XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC50b3AgICAgPSBNYXRoLm1pbihjdXJyZW50LnRvcCwgc3RhcnQuYm90dG9tKTtcbiAgICAgICAgICAgICAgICByZXN0cmljdGVkLmJvdHRvbSA9IE1hdGgubWF4KGN1cnJlbnQuYm90dG9tLCBzdGFydC50b3ApO1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0ZWQubGVmdCAgID0gTWF0aC5taW4oY3VycmVudC5sZWZ0LCBzdGFydC5yaWdodCk7XG4gICAgICAgICAgICAgICAgcmVzdHJpY3RlZC5yaWdodCAgPSBNYXRoLm1heChjdXJyZW50LnJpZ2h0LCBzdGFydC5sZWZ0KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcmVzdHJpY3RlZC53aWR0aCAgPSByZXN0cmljdGVkLnJpZ2h0ICAtIHJlc3RyaWN0ZWQubGVmdDtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWQuaGVpZ2h0ID0gcmVzdHJpY3RlZC5ib3R0b20gLSByZXN0cmljdGVkLnRvcCA7XG5cbiAgICAgICAgICAgIGZvciAodmFyIGVkZ2UgaW4gcmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgIGRlbHRhW2VkZ2VdID0gcmVzdHJpY3RlZFtlZGdlXSAtIHByZXZpb3VzW2VkZ2VdO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICByZXNpemVFdmVudC5lZGdlcyA9IHRoaXMucHJlcGFyZWQuZWRnZXM7XG4gICAgICAgICAgICByZXNpemVFdmVudC5yZWN0ID0gcmVzdHJpY3RlZDtcbiAgICAgICAgICAgIHJlc2l6ZUV2ZW50LmRlbHRhUmVjdCA9IGRlbHRhO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShyZXNpemVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIHJlc2l6ZUV2ZW50O1xuICAgIH0sXG5cbiAgICBnZXN0dXJlU3RhcnQ6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ3N0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSAwO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5zdGFydERpc3RhbmNlID0gdGhpcy5nZXN0dXJlLnByZXZEaXN0YW5jZSA9IGdlc3R1cmVFdmVudC5kaXN0YW5jZTtcbiAgICAgICAgdGhpcy5nZXN0dXJlLnN0YXJ0QW5nbGUgPSB0aGlzLmdlc3R1cmUucHJldkFuZ2xlID0gZ2VzdHVyZUV2ZW50LmFuZ2xlO1xuICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSAxO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyaW5nID0gdHJ1ZTtcblxuICAgICAgICB0aGlzLnRhcmdldC5maXJlKGdlc3R1cmVFdmVudCk7XG5cbiAgICAgICAgcmV0dXJuIGdlc3R1cmVFdmVudDtcbiAgICB9LFxuXG4gICAgZ2VzdHVyZU1vdmU6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBpZiAoIXRoaXMucG9pbnRlcklkcy5sZW5ndGgpIHtcbiAgICAgICAgICAgIHJldHVybiB0aGlzLnByZXZFdmVudDtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBnZXN0dXJlRXZlbnQ7XG5cbiAgICAgICAgZ2VzdHVyZUV2ZW50ID0gbmV3IEludGVyYWN0RXZlbnQodGhpcywgZXZlbnQsICdnZXN0dXJlJywgJ21vdmUnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICBnZXN0dXJlRXZlbnQuZHMgPSBnZXN0dXJlRXZlbnQuc2NhbGUgLSB0aGlzLmdlc3R1cmUuc2NhbGU7XG5cbiAgICAgICAgdGhpcy50YXJnZXQuZmlyZShnZXN0dXJlRXZlbnQpO1xuXG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2QW5nbGUgPSBnZXN0dXJlRXZlbnQuYW5nbGU7XG4gICAgICAgIHRoaXMuZ2VzdHVyZS5wcmV2RGlzdGFuY2UgPSBnZXN0dXJlRXZlbnQuZGlzdGFuY2U7XG5cbiAgICAgICAgaWYgKGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gSW5maW5pdHkgJiZcbiAgICAgICAgICAgIGdlc3R1cmVFdmVudC5zY2FsZSAhPT0gbnVsbCAmJlxuICAgICAgICAgICAgZ2VzdHVyZUV2ZW50LnNjYWxlICE9PSB1bmRlZmluZWQgICYmXG4gICAgICAgICAgICAhaXNOYU4oZ2VzdHVyZUV2ZW50LnNjYWxlKSkge1xuXG4gICAgICAgICAgICB0aGlzLmdlc3R1cmUuc2NhbGUgPSBnZXN0dXJlRXZlbnQuc2NhbGU7XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4gZ2VzdHVyZUV2ZW50O1xuICAgIH0sXG5cbiAgICBwb2ludGVySG9sZDogZnVuY3Rpb24gKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCkge1xuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAnaG9sZCcpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyVXA6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0KSB7XG4gICAgICAgIHZhciBwb2ludGVySW5kZXggPSB0aGlzLm1vdXNlPyAwIDogc2NvcGUuaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIHV0aWxzLmdldFBvaW50ZXJJZChwb2ludGVyKSk7XG5cbiAgICAgICAgY2xlYXJUaW1lb3V0KHRoaXMuaG9sZFRpbWVyc1twb2ludGVySW5kZXhdKTtcblxuICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndXAnICk7XG4gICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsICd0YXAnKTtcblxuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICBwb2ludGVyQ2FuY2VsOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgcG9pbnRlckluZGV4ID0gdGhpcy5tb3VzZT8gMCA6IHNjb3BlLmluZGV4T2YodGhpcy5wb2ludGVySWRzLCB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIGNsZWFyVGltZW91dCh0aGlzLmhvbGRUaW1lcnNbcG9pbnRlckluZGV4XSk7XG5cbiAgICAgICAgdGhpcy5jb2xsZWN0RXZlbnRUYXJnZXRzKHBvaW50ZXIsIGV2ZW50LCBldmVudFRhcmdldCwgJ2NhbmNlbCcpO1xuICAgICAgICB0aGlzLnBvaW50ZXJFbmQocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCk7XG5cbiAgICAgICAgdGhpcy5yZW1vdmVQb2ludGVyKHBvaW50ZXIpO1xuICAgIH0sXG5cbiAgICAvLyBodHRwOi8vd3d3LnF1aXJrc21vZGUub3JnL2RvbS9ldmVudHMvY2xpY2suaHRtbFxuICAgIC8vID5FdmVudHMgbGVhZGluZyB0byBkYmxjbGlja1xuICAgIC8vXG4gICAgLy8gSUU4IGRvZXNuJ3QgZmlyZSBkb3duIGV2ZW50IGJlZm9yZSBkYmxjbGljay5cbiAgICAvLyBUaGlzIHdvcmthcm91bmQgdHJpZXMgdG8gZmlyZSBhIHRhcCBhbmQgZG91YmxldGFwIGFmdGVyIGRibGNsaWNrXG4gICAgaWU4RGJsY2xpY2s6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQpIHtcbiAgICAgICAgaWYgKHRoaXMucHJldlRhcFxuICAgICAgICAgICAgJiYgZXZlbnQuY2xpZW50WCA9PT0gdGhpcy5wcmV2VGFwLmNsaWVudFhcbiAgICAgICAgICAgICYmIGV2ZW50LmNsaWVudFkgPT09IHRoaXMucHJldlRhcC5jbGllbnRZXG4gICAgICAgICAgICAmJiBldmVudFRhcmdldCAgID09PSB0aGlzLnByZXZUYXAudGFyZ2V0KSB7XG5cbiAgICAgICAgICAgIHRoaXMuZG93blRhcmdldHNbMF0gPSBldmVudFRhcmdldDtcbiAgICAgICAgICAgIHRoaXMuZG93blRpbWVzWzBdID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgICAgICB0aGlzLmNvbGxlY3RFdmVudFRhcmdldHMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCAndGFwJyk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gRW5kIGludGVyYWN0IG1vdmUgZXZlbnRzIGFuZCBzdG9wIGF1dG8tc2Nyb2xsIHVubGVzcyBpbmVydGlhIGlzIGVuYWJsZWRcbiAgICBwb2ludGVyRW5kOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBjdXJFdmVudFRhcmdldCkge1xuICAgICAgICB2YXIgZW5kRXZlbnQsXG4gICAgICAgICAgICB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIG9wdGlvbnMgPSB0YXJnZXQgJiYgdGFyZ2V0Lm9wdGlvbnMsXG4gICAgICAgICAgICBpbmVydGlhT3B0aW9ucyA9IG9wdGlvbnMgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5pbmVydGlhLFxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cyA9IHRoaXMuaW5lcnRpYVN0YXR1cztcblxuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhU3RhdHVzLmFjdGl2ZSkgeyByZXR1cm47IH1cblxuICAgICAgICAgICAgdmFyIHBvaW50ZXJTcGVlZCxcbiAgICAgICAgICAgICAgICBub3cgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSBmYWxzZSxcbiAgICAgICAgICAgICAgICBpbmVydGlhID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgc21vb3RoRW5kID0gZmFsc2UsXG4gICAgICAgICAgICAgICAgZW5kU25hcCA9IHNjb3BlLmNoZWNrU25hcCh0YXJnZXQsIHRoaXMucHJlcGFyZWQubmFtZSkgJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLnNuYXAuZW5kT25seSxcbiAgICAgICAgICAgICAgICBlbmRSZXN0cmljdCA9IHNjb3BlLmNoZWNrUmVzdHJpY3QodGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpICYmIG9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5yZXN0cmljdC5lbmRPbmx5LFxuICAgICAgICAgICAgICAgIGR4ID0gMCxcbiAgICAgICAgICAgICAgICBkeSA9IDAsXG4gICAgICAgICAgICAgICAgc3RhcnRFdmVudDtcblxuICAgICAgICAgICAgaWYgKHRoaXMuZHJhZ2dpbmcpIHtcbiAgICAgICAgICAgICAgICBpZiAgICAgIChvcHRpb25zLmRyYWcuYXhpcyA9PT0gJ3gnICkgeyBwb2ludGVyU3BlZWQgPSBNYXRoLmFicyh0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudngpOyB9XG4gICAgICAgICAgICAgICAgZWxzZSBpZiAob3B0aW9ucy5kcmFnLmF4aXMgPT09ICd5JyApIHsgcG9pbnRlclNwZWVkID0gTWF0aC5hYnModGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5KTsgfVxuICAgICAgICAgICAgICAgIGVsc2UgICAvKm9wdGlvbnMuZHJhZy5heGlzID09PSAneHknKi97IHBvaW50ZXJTcGVlZCA9IHRoaXMucG9pbnRlckRlbHRhLmNsaWVudC5zcGVlZDsgfVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgcG9pbnRlclNwZWVkID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnNwZWVkO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBjaGVjayBpZiBpbmVydGlhIHNob3VsZCBiZSBzdGFydGVkXG4gICAgICAgICAgICBpbmVydGlhUG9zc2libGUgPSAoaW5lcnRpYU9wdGlvbnMgJiYgaW5lcnRpYU9wdGlvbnMuZW5hYmxlZFxuICAgICAgICAgICAgJiYgdGhpcy5wcmVwYXJlZC5uYW1lICE9PSAnZ2VzdHVyZSdcbiAgICAgICAgICAgICYmIGV2ZW50ICE9PSBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhID0gKGluZXJ0aWFQb3NzaWJsZVxuICAgICAgICAgICAgJiYgKG5vdyAtIHRoaXMuY3VyQ29vcmRzLnRpbWVTdGFtcCkgPCA1MFxuICAgICAgICAgICAgJiYgcG9pbnRlclNwZWVkID4gaW5lcnRpYU9wdGlvbnMubWluU3BlZWRcbiAgICAgICAgICAgICYmIHBvaW50ZXJTcGVlZCA+IGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkKTtcblxuICAgICAgICAgICAgaWYgKGluZXJ0aWFQb3NzaWJsZSAmJiAhaW5lcnRpYSAmJiAoZW5kU25hcCB8fCBlbmRSZXN0cmljdCkpIHtcblxuICAgICAgICAgICAgICAgIHZhciBzbmFwUmVzdHJpY3QgPSB7fTtcblxuICAgICAgICAgICAgICAgIHNuYXBSZXN0cmljdC5zbmFwID0gc25hcFJlc3RyaWN0LnJlc3RyaWN0ID0gc25hcFJlc3RyaWN0O1xuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFNuYXApIHtcbiAgICAgICAgICAgICAgICAgICAgdGhpcy5zZXRTbmFwcGluZyh0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzbmFwUmVzdHJpY3QpO1xuICAgICAgICAgICAgICAgICAgICBpZiAoc25hcFJlc3RyaWN0LmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgZHggKz0gc25hcFJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcFJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKGVuZFJlc3RyaWN0KSB7XG4gICAgICAgICAgICAgICAgICAgIHRoaXMuc2V0UmVzdHJpY3Rpb24odGhpcy5jdXJDb29yZHMucGFnZSwgc25hcFJlc3RyaWN0KTtcbiAgICAgICAgICAgICAgICAgICAgaWYgKHNuYXBSZXN0cmljdC5yZXN0cmljdGVkKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeCArPSBzbmFwUmVzdHJpY3QuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICBkeSArPSBzbmFwUmVzdHJpY3QuZHk7XG4gICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpZiAoZHggfHwgZHkpIHtcbiAgICAgICAgICAgICAgICAgICAgc21vb3RoRW5kID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhIHx8IHNtb290aEVuZCkge1xuICAgICAgICAgICAgICAgIHV0aWxzLmNvcHlDb29yZHMoaW5lcnRpYVN0YXR1cy51cENvb3JkcywgdGhpcy5jdXJDb29yZHMpO1xuXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVyc1swXSA9IGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCA9IHN0YXJ0RXZlbnQgPVxuICAgICAgICAgICAgICAgICAgICBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgdGhpcy5wcmVwYXJlZC5uYW1lLCAnaW5lcnRpYXN0YXJ0JywgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudDAgPSBub3c7XG5cbiAgICAgICAgICAgICAgICB0YXJnZXQuZmlyZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICAgICAgaWYgKGluZXJ0aWEpIHtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy52eDAgPSB0aGlzLnBvaW50ZXJEZWx0YS5jbGllbnQudng7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMudnkwID0gdGhpcy5wb2ludGVyRGVsdGEuY2xpZW50LnZ5O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnYwID0gcG9pbnRlclNwZWVkO1xuXG4gICAgICAgICAgICAgICAgICAgIHRoaXMuY2FsY0luZXJ0aWEoaW5lcnRpYVN0YXR1cyk7XG5cbiAgICAgICAgICAgICAgICAgICAgdmFyIHBhZ2UgPSB1dGlscy5leHRlbmQoe30sIHRoaXMuY3VyQ29vcmRzLnBhZ2UpLFxuICAgICAgICAgICAgICAgICAgICAgICAgb3JpZ2luID0gc2NvcGUuZ2V0T3JpZ2luWFkodGFyZ2V0LCB0aGlzLmVsZW1lbnQpLFxuICAgICAgICAgICAgICAgICAgICAgICAgc3RhdHVzT2JqZWN0O1xuXG4gICAgICAgICAgICAgICAgICAgIHBhZ2UueCA9IHBhZ2UueCArIGluZXJ0aWFTdGF0dXMueGUgLSBvcmlnaW4ueDtcbiAgICAgICAgICAgICAgICAgICAgcGFnZS55ID0gcGFnZS55ICsgaW5lcnRpYVN0YXR1cy55ZSAtIG9yaWdpbi55O1xuXG4gICAgICAgICAgICAgICAgICAgIHN0YXR1c09iamVjdCA9IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHVzZVN0YXR1c1hZOiB0cnVlLFxuICAgICAgICAgICAgICAgICAgICAgICAgeDogcGFnZS54LFxuICAgICAgICAgICAgICAgICAgICAgICAgeTogcGFnZS55LFxuICAgICAgICAgICAgICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICAgICAgICAgICAgICBkeTogMCxcbiAgICAgICAgICAgICAgICAgICAgICAgIHNuYXA6IG51bGxcbiAgICAgICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgICAgICBzdGF0dXNPYmplY3Quc25hcCA9IHN0YXR1c09iamVjdDtcblxuICAgICAgICAgICAgICAgICAgICBkeCA9IGR5ID0gMDtcblxuICAgICAgICAgICAgICAgICAgICBpZiAoZW5kU25hcCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHNuYXAgPSB0aGlzLnNldFNuYXBwaW5nKHRoaXMuY3VyQ29vcmRzLnBhZ2UsIHN0YXR1c09iamVjdCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChzbmFwLmxvY2tlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHNuYXAuZHg7XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgZHkgKz0gc25hcC5keTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIGlmIChlbmRSZXN0cmljdCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgdmFyIHJlc3RyaWN0ID0gdGhpcy5zZXRSZXN0cmljdGlvbih0aGlzLmN1ckNvb3Jkcy5wYWdlLCBzdGF0dXNPYmplY3QpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICBpZiAocmVzdHJpY3QucmVzdHJpY3RlZCkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR4ICs9IHJlc3RyaWN0LmR4O1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGR5ICs9IHJlc3RyaWN0LmR5O1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFhlICs9IGR4O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWWUgKz0gZHk7XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5pID0gYW5pbWF0aW9uRnJhbWUucmVxdWVzdCh0aGlzLmJvdW5kSW5lcnRpYUZyYW1lKTtcbiAgICAgICAgICAgICAgICB9XG4gICAgICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc21vb3RoRW5kID0gdHJ1ZTtcbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy54ZSA9IGR4O1xuICAgICAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnllID0gZHk7XG5cbiAgICAgICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeCA9IGluZXJ0aWFTdGF0dXMuc3kgPSAwO1xuXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuaSA9IGFuaW1hdGlvbkZyYW1lLnJlcXVlc3QodGhpcy5ib3VuZFNtb290aEVuZEZyYW1lKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLmFjdGl2ZSA9IHRydWU7XG4gICAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoZW5kU25hcCB8fCBlbmRSZXN0cmljdCkge1xuICAgICAgICAgICAgICAgIC8vIGZpcmUgYSBtb3ZlIGV2ZW50IGF0IHRoZSBzbmFwcGVkIGNvb3JkaW5hdGVzXG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVyTW92ZShwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIGN1ckV2ZW50VGFyZ2V0LCB0cnVlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmICh0aGlzLmRyYWdnaW5nKSB7XG4gICAgICAgICAgICBlbmRFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZHJhZycsICdlbmQnLCB0aGlzLmVsZW1lbnQpO1xuXG4gICAgICAgICAgICB2YXIgZHJhZ2dhYmxlRWxlbWVudCA9IHRoaXMuZWxlbWVudCxcbiAgICAgICAgICAgICAgICBkcm9wID0gdGhpcy5nZXREcm9wKGV2ZW50LCBkcmFnZ2FibGVFbGVtZW50KTtcblxuICAgICAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gZHJvcC5kcm9wem9uZTtcbiAgICAgICAgICAgIHRoaXMuZHJvcEVsZW1lbnQgPSBkcm9wLmVsZW1lbnQ7XG5cbiAgICAgICAgICAgIHZhciBkcm9wRXZlbnRzID0gdGhpcy5nZXREcm9wRXZlbnRzKGV2ZW50LCBlbmRFdmVudCk7XG5cbiAgICAgICAgICAgIGlmIChkcm9wRXZlbnRzLmxlYXZlKSB7IHRoaXMucHJldkRyb3BUYXJnZXQuZmlyZShkcm9wRXZlbnRzLmxlYXZlKTsgfVxuICAgICAgICAgICAgaWYgKGRyb3BFdmVudHMuZW50ZXIpIHsgICAgIHRoaXMuZHJvcFRhcmdldC5maXJlKGRyb3BFdmVudHMuZW50ZXIpOyB9XG4gICAgICAgICAgICBpZiAoZHJvcEV2ZW50cy5kcm9wICkgeyAgICAgdGhpcy5kcm9wVGFyZ2V0LmZpcmUoZHJvcEV2ZW50cy5kcm9wICk7IH1cbiAgICAgICAgICAgIGlmIChkcm9wRXZlbnRzLmRlYWN0aXZhdGUpIHtcbiAgICAgICAgICAgICAgICB0aGlzLmZpcmVBY3RpdmVEcm9wcyhkcm9wRXZlbnRzLmRlYWN0aXZhdGUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0YXJnZXQuZmlyZShlbmRFdmVudCk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAodGhpcy5yZXNpemluZykge1xuICAgICAgICAgICAgZW5kRXZlbnQgPSBuZXcgSW50ZXJhY3RFdmVudCh0aGlzLCBldmVudCwgJ3Jlc2l6ZScsICdlbmQnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgdGFyZ2V0LmZpcmUoZW5kRXZlbnQpO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKHRoaXMuZ2VzdHVyaW5nKSB7XG4gICAgICAgICAgICBlbmRFdmVudCA9IG5ldyBJbnRlcmFjdEV2ZW50KHRoaXMsIGV2ZW50LCAnZ2VzdHVyZScsICdlbmQnLCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgdGFyZ2V0LmZpcmUoZW5kRXZlbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5zdG9wKGV2ZW50KTtcbiAgICB9LFxuXG4gICAgY29sbGVjdERyb3BzOiBmdW5jdGlvbiAoZWxlbWVudCkge1xuICAgICAgICB2YXIgZHJvcHMgPSBbXSxcbiAgICAgICAgICAgIGVsZW1lbnRzID0gW10sXG4gICAgICAgICAgICBpO1xuXG4gICAgICAgIGVsZW1lbnQgPSBlbGVtZW50IHx8IHRoaXMuZWxlbWVudDtcblxuICAgICAgICAvLyBjb2xsZWN0IGFsbCBkcm9wem9uZXMgYW5kIHRoZWlyIGVsZW1lbnRzIHdoaWNoIHF1YWxpZnkgZm9yIGEgZHJvcFxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgc2NvcGUuaW50ZXJhY3RhYmxlcy5sZW5ndGg7IGkrKykge1xuICAgICAgICAgICAgaWYgKCFzY29wZS5pbnRlcmFjdGFibGVzW2ldLm9wdGlvbnMuZHJvcC5lbmFibGVkKSB7IGNvbnRpbnVlOyB9XG5cbiAgICAgICAgICAgIHZhciBjdXJyZW50ID0gc2NvcGUuaW50ZXJhY3RhYmxlc1tpXSxcbiAgICAgICAgICAgICAgICBhY2NlcHQgPSBjdXJyZW50Lm9wdGlvbnMuZHJvcC5hY2NlcHQ7XG5cbiAgICAgICAgICAgIC8vIHRlc3QgdGhlIGRyYWdnYWJsZSBlbGVtZW50IGFnYWluc3QgdGhlIGRyb3B6b25lJ3MgYWNjZXB0IHNldHRpbmdcbiAgICAgICAgICAgIGlmICgodXRpbHMuaXNFbGVtZW50KGFjY2VwdCkgJiYgYWNjZXB0ICE9PSBlbGVtZW50KVxuICAgICAgICAgICAgICAgIHx8IChzY29wZS5pc1N0cmluZyhhY2NlcHQpXG4gICAgICAgICAgICAgICAgJiYgIXNjb3BlLm1hdGNoZXNTZWxlY3RvcihlbGVtZW50LCBhY2NlcHQpKSkge1xuXG4gICAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHF1ZXJ5IGZvciBuZXcgZWxlbWVudHMgaWYgbmVjZXNzYXJ5XG4gICAgICAgICAgICB2YXIgZHJvcEVsZW1lbnRzID0gY3VycmVudC5zZWxlY3Rvcj8gY3VycmVudC5fY29udGV4dC5xdWVyeVNlbGVjdG9yQWxsKGN1cnJlbnQuc2VsZWN0b3IpIDogW2N1cnJlbnQuX2VsZW1lbnRdO1xuXG4gICAgICAgICAgICBmb3IgKHZhciBqID0gMCwgbGVuID0gZHJvcEVsZW1lbnRzLmxlbmd0aDsgaiA8IGxlbjsgaisrKSB7XG4gICAgICAgICAgICAgICAgdmFyIGN1cnJlbnRFbGVtZW50ID0gZHJvcEVsZW1lbnRzW2pdO1xuXG4gICAgICAgICAgICAgICAgaWYgKGN1cnJlbnRFbGVtZW50ID09PSBlbGVtZW50KSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGRyb3BzLnB1c2goY3VycmVudCk7XG4gICAgICAgICAgICAgICAgZWxlbWVudHMucHVzaChjdXJyZW50RWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgZHJvcHpvbmVzOiBkcm9wcyxcbiAgICAgICAgICAgIGVsZW1lbnRzOiBlbGVtZW50c1xuICAgICAgICB9O1xuICAgIH0sXG5cbiAgICBmaXJlQWN0aXZlRHJvcHM6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICB2YXIgaSxcbiAgICAgICAgICAgIGN1cnJlbnQsXG4gICAgICAgICAgICBjdXJyZW50RWxlbWVudCxcbiAgICAgICAgICAgIHByZXZFbGVtZW50O1xuXG4gICAgICAgIC8vIGxvb3AgdGhyb3VnaCBhbGwgYWN0aXZlIGRyb3B6b25lcyBhbmQgdHJpZ2dlciBldmVudFxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIGN1cnJlbnQgPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tpXTtcbiAgICAgICAgICAgIGN1cnJlbnRFbGVtZW50ID0gdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyBbaV07XG5cbiAgICAgICAgICAgIC8vIHByZXZlbnQgdHJpZ2dlciBvZiBkdXBsaWNhdGUgZXZlbnRzIG9uIHNhbWUgZWxlbWVudFxuICAgICAgICAgICAgaWYgKGN1cnJlbnRFbGVtZW50ICE9PSBwcmV2RWxlbWVudCkge1xuICAgICAgICAgICAgICAgIC8vIHNldCBjdXJyZW50IGVsZW1lbnQgYXMgZXZlbnQgdGFyZ2V0XG4gICAgICAgICAgICAgICAgZXZlbnQudGFyZ2V0ID0gY3VycmVudEVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgY3VycmVudC5maXJlKGV2ZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHByZXZFbGVtZW50ID0gY3VycmVudEVsZW1lbnQ7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgLy8gQ29sbGVjdCBhIG5ldyBzZXQgb2YgcG9zc2libGUgZHJvcHMgYW5kIHNhdmUgdGhlbSBpbiBhY3RpdmVEcm9wcy5cbiAgICAvLyBzZXRBY3RpdmVEcm9wcyBzaG91bGQgYWx3YXlzIGJlIGNhbGxlZCB3aGVuIGEgZHJhZyBoYXMganVzdCBzdGFydGVkIG9yIGFcbiAgICAvLyBkcmFnIGV2ZW50IGhhcHBlbnMgd2hpbGUgZHluYW1pY0Ryb3AgaXMgdHJ1ZVxuICAgIHNldEFjdGl2ZURyb3BzOiBmdW5jdGlvbiAoZHJhZ0VsZW1lbnQpIHtcbiAgICAgICAgLy8gZ2V0IGRyb3B6b25lcyBhbmQgdGhlaXIgZWxlbWVudHMgdGhhdCBjb3VsZCByZWNlaXZlIHRoZSBkcmFnZ2FibGVcbiAgICAgICAgdmFyIHBvc3NpYmxlRHJvcHMgPSB0aGlzLmNvbGxlY3REcm9wcyhkcmFnRWxlbWVudCwgdHJ1ZSk7XG5cbiAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMgPSBwb3NzaWJsZURyb3BzLmRyb3B6b25lcztcbiAgICAgICAgdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyAgPSBwb3NzaWJsZURyb3BzLmVsZW1lbnRzO1xuICAgICAgICB0aGlzLmFjdGl2ZURyb3BzLnJlY3RzICAgICA9IFtdO1xuXG4gICAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHRoaXMuYWN0aXZlRHJvcHMucmVjdHNbaV0gPSB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lc1tpXS5nZXRSZWN0KHRoaXMuYWN0aXZlRHJvcHMuZWxlbWVudHNbaV0pO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIGdldERyb3A6IGZ1bmN0aW9uIChldmVudCwgZHJhZ0VsZW1lbnQpIHtcbiAgICAgICAgdmFyIHZhbGlkRHJvcHMgPSBbXTtcblxuICAgICAgICBpZiAoc2NvcGUuZHluYW1pY0Ryb3ApIHtcbiAgICAgICAgICAgIHRoaXMuc2V0QWN0aXZlRHJvcHMoZHJhZ0VsZW1lbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gY29sbGVjdCBhbGwgZHJvcHpvbmVzIGFuZCB0aGVpciBlbGVtZW50cyB3aGljaCBxdWFsaWZ5IGZvciBhIGRyb3BcbiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCB0aGlzLmFjdGl2ZURyb3BzLmRyb3B6b25lcy5sZW5ndGg7IGorKykge1xuICAgICAgICAgICAgdmFyIGN1cnJlbnQgICAgICAgID0gdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXNbal0sXG4gICAgICAgICAgICAgICAgY3VycmVudEVsZW1lbnQgPSB0aGlzLmFjdGl2ZURyb3BzLmVsZW1lbnRzIFtqXSxcbiAgICAgICAgICAgICAgICByZWN0ICAgICAgICAgICA9IHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgICAgW2pdO1xuXG4gICAgICAgICAgICB2YWxpZERyb3BzLnB1c2goY3VycmVudC5kcm9wQ2hlY2sodGhpcy5wb2ludGVyc1swXSwgZXZlbnQsIHRoaXMudGFyZ2V0LCBkcmFnRWxlbWVudCwgY3VycmVudEVsZW1lbnQsIHJlY3QpXG4gICAgICAgICAgICAgICAgPyBjdXJyZW50RWxlbWVudFxuICAgICAgICAgICAgICAgIDogbnVsbCk7XG4gICAgICAgIH1cblxuICAgICAgICAvLyBnZXQgdGhlIG1vc3QgYXBwcm9wcmlhdGUgZHJvcHpvbmUgYmFzZWQgb24gRE9NIGRlcHRoIGFuZCBvcmRlclxuICAgICAgICB2YXIgZHJvcEluZGV4ID0gc2NvcGUuaW5kZXhPZkRlZXBlc3RFbGVtZW50KHZhbGlkRHJvcHMpLFxuICAgICAgICAgICAgZHJvcHpvbmUgID0gdGhpcy5hY3RpdmVEcm9wcy5kcm9wem9uZXNbZHJvcEluZGV4XSB8fCBudWxsLFxuICAgICAgICAgICAgZWxlbWVudCAgID0gdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyBbZHJvcEluZGV4XSB8fCBudWxsO1xuXG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBkcm9wem9uZTogZHJvcHpvbmUsXG4gICAgICAgICAgICBlbGVtZW50OiBlbGVtZW50XG4gICAgICAgIH07XG4gICAgfSxcblxuICAgIGdldERyb3BFdmVudHM6IGZ1bmN0aW9uIChwb2ludGVyRXZlbnQsIGRyYWdFdmVudCkge1xuICAgICAgICB2YXIgZHJvcEV2ZW50cyA9IHtcbiAgICAgICAgICAgIGVudGVyICAgICA6IG51bGwsXG4gICAgICAgICAgICBsZWF2ZSAgICAgOiBudWxsLFxuICAgICAgICAgICAgYWN0aXZhdGUgIDogbnVsbCxcbiAgICAgICAgICAgIGRlYWN0aXZhdGU6IG51bGwsXG4gICAgICAgICAgICBtb3ZlICAgICAgOiBudWxsLFxuICAgICAgICAgICAgZHJvcCAgICAgIDogbnVsbFxuICAgICAgICB9O1xuXG4gICAgICAgIGlmICh0aGlzLmRyb3BFbGVtZW50ICE9PSB0aGlzLnByZXZEcm9wRWxlbWVudCkge1xuICAgICAgICAgICAgLy8gaWYgdGhlcmUgd2FzIGEgcHJldkRyb3BUYXJnZXQsIGNyZWF0ZSBhIGRyYWdsZWF2ZSBldmVudFxuICAgICAgICAgICAgaWYgKHRoaXMucHJldkRyb3BUYXJnZXQpIHtcbiAgICAgICAgICAgICAgICBkcm9wRXZlbnRzLmxlYXZlID0ge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgICAgICAgOiB0aGlzLnByZXZEcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5wcmV2RHJvcFRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgcmVsYXRlZFRhcmdldDogZHJhZ0V2ZW50LnRhcmdldCxcbiAgICAgICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICAgICAgZHJhZ0V2ZW50ICAgIDogZHJhZ0V2ZW50LFxuICAgICAgICAgICAgICAgICAgICBpbnRlcmFjdGlvbiAgOiB0aGlzLFxuICAgICAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgICAgICB0eXBlICAgICAgICAgOiAnZHJhZ2xlYXZlJ1xuICAgICAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQuZHJhZ0xlYXZlID0gdGhpcy5wcmV2RHJvcEVsZW1lbnQ7XG4gICAgICAgICAgICAgICAgZHJhZ0V2ZW50LnByZXZEcm9wem9uZSA9IHRoaXMucHJldkRyb3BUYXJnZXQ7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICAvLyBpZiB0aGUgZHJvcFRhcmdldCBpcyBub3QgbnVsbCwgY3JlYXRlIGEgZHJhZ2VudGVyIGV2ZW50XG4gICAgICAgICAgICBpZiAodGhpcy5kcm9wVGFyZ2V0KSB7XG4gICAgICAgICAgICAgICAgZHJvcEV2ZW50cy5lbnRlciA9IHtcbiAgICAgICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogdGhpcy5kcm9wRWxlbWVudCxcbiAgICAgICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5kcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICByZWxhdGVkVGFyZ2V0OiBkcmFnRXZlbnQudGFyZ2V0LFxuICAgICAgICAgICAgICAgICAgICBkcmFnZ2FibGUgICAgOiBkcmFnRXZlbnQuaW50ZXJhY3RhYmxlLFxuICAgICAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgICAgIGludGVyYWN0aW9uICA6IHRoaXMsXG4gICAgICAgICAgICAgICAgICAgIHRpbWVTdGFtcCAgICA6IGRyYWdFdmVudC50aW1lU3RhbXAsXG4gICAgICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcmFnZW50ZXInXG4gICAgICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgICAgIGRyYWdFdmVudC5kcmFnRW50ZXIgPSB0aGlzLmRyb3BFbGVtZW50O1xuICAgICAgICAgICAgICAgIGRyYWdFdmVudC5kcm9wem9uZSA9IHRoaXMuZHJvcFRhcmdldDtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChkcmFnRXZlbnQudHlwZSA9PT0gJ2RyYWdlbmQnICYmIHRoaXMuZHJvcFRhcmdldCkge1xuICAgICAgICAgICAgZHJvcEV2ZW50cy5kcm9wID0ge1xuICAgICAgICAgICAgICAgIHRhcmdldCAgICAgICA6IHRoaXMuZHJvcEVsZW1lbnQsXG4gICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5kcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wJ1xuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgZHJhZ0V2ZW50LmRyb3B6b25lID0gdGhpcy5kcm9wVGFyZ2V0O1xuICAgICAgICB9XG4gICAgICAgIGlmIChkcmFnRXZlbnQudHlwZSA9PT0gJ2RyYWdzdGFydCcpIHtcbiAgICAgICAgICAgIGRyb3BFdmVudHMuYWN0aXZhdGUgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogbnVsbCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wYWN0aXZhdGUnXG4gICAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICAgIGlmIChkcmFnRXZlbnQudHlwZSA9PT0gJ2RyYWdlbmQnKSB7XG4gICAgICAgICAgICBkcm9wRXZlbnRzLmRlYWN0aXZhdGUgPSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0ICAgICAgIDogbnVsbCxcbiAgICAgICAgICAgICAgICBkcm9wem9uZSAgICAgOiBudWxsLFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICB0aW1lU3RhbXAgICAgOiBkcmFnRXZlbnQudGltZVN0YW1wLFxuICAgICAgICAgICAgICAgIHR5cGUgICAgICAgICA6ICdkcm9wZGVhY3RpdmF0ZSdcbiAgICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGRyYWdFdmVudC50eXBlID09PSAnZHJhZ21vdmUnICYmIHRoaXMuZHJvcFRhcmdldCkge1xuICAgICAgICAgICAgZHJvcEV2ZW50cy5tb3ZlID0ge1xuICAgICAgICAgICAgICAgIHRhcmdldCAgICAgICA6IHRoaXMuZHJvcEVsZW1lbnQsXG4gICAgICAgICAgICAgICAgZHJvcHpvbmUgICAgIDogdGhpcy5kcm9wVGFyZ2V0LFxuICAgICAgICAgICAgICAgIHJlbGF0ZWRUYXJnZXQ6IGRyYWdFdmVudC50YXJnZXQsXG4gICAgICAgICAgICAgICAgZHJhZ2dhYmxlICAgIDogZHJhZ0V2ZW50LmludGVyYWN0YWJsZSxcbiAgICAgICAgICAgICAgICBkcmFnRXZlbnQgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgaW50ZXJhY3Rpb24gIDogdGhpcyxcbiAgICAgICAgICAgICAgICBkcmFnbW92ZSAgICAgOiBkcmFnRXZlbnQsXG4gICAgICAgICAgICAgICAgdGltZVN0YW1wICAgIDogZHJhZ0V2ZW50LnRpbWVTdGFtcCxcbiAgICAgICAgICAgICAgICB0eXBlICAgICAgICAgOiAnZHJvcG1vdmUnXG4gICAgICAgICAgICB9O1xuICAgICAgICAgICAgZHJhZ0V2ZW50LmRyb3B6b25lID0gdGhpcy5kcm9wVGFyZ2V0O1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIGRyb3BFdmVudHM7XG4gICAgfSxcblxuICAgIGN1cnJlbnRBY3Rpb246IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgcmV0dXJuICh0aGlzLmRyYWdnaW5nICYmICdkcmFnJykgfHwgKHRoaXMucmVzaXppbmcgJiYgJ3Jlc2l6ZScpIHx8ICh0aGlzLmdlc3R1cmluZyAmJiAnZ2VzdHVyZScpIHx8IG51bGw7XG4gICAgfSxcblxuICAgIGludGVyYWN0aW5nOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHJldHVybiB0aGlzLmRyYWdnaW5nIHx8IHRoaXMucmVzaXppbmcgfHwgdGhpcy5nZXN0dXJpbmc7XG4gICAgfSxcblxuICAgIGNsZWFyVGFyZ2V0czogZnVuY3Rpb24gKCkge1xuICAgICAgICB0aGlzLnRhcmdldCA9IHRoaXMuZWxlbWVudCA9IG51bGw7XG5cbiAgICAgICAgdGhpcy5kcm9wVGFyZ2V0ID0gdGhpcy5kcm9wRWxlbWVudCA9IHRoaXMucHJldkRyb3BUYXJnZXQgPSB0aGlzLnByZXZEcm9wRWxlbWVudCA9IG51bGw7XG4gICAgfSxcblxuICAgIHN0b3A6IGZ1bmN0aW9uIChldmVudCkge1xuICAgICAgICBpZiAodGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLnN0b3AoKTtcbiAgICAgICAgICAgIHRoaXMubWF0Y2hlcyA9IFtdO1xuICAgICAgICAgICAgdGhpcy5tYXRjaEVsZW1lbnRzID0gW107XG5cbiAgICAgICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldDtcblxuICAgICAgICAgICAgaWYgKHRhcmdldC5vcHRpb25zLnN0eWxlQ3Vyc29yKSB7XG4gICAgICAgICAgICAgICAgdGFyZ2V0Ll9kb2MuZG9jdW1lbnRFbGVtZW50LnN0eWxlLmN1cnNvciA9ICcnO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAvLyBwcmV2ZW50IERlZmF1bHQgb25seSBpZiB3ZXJlIHByZXZpb3VzbHkgaW50ZXJhY3RpbmdcbiAgICAgICAgICAgIGlmIChldmVudCAmJiBzY29wZS5pc0Z1bmN0aW9uKGV2ZW50LnByZXZlbnREZWZhdWx0KSkge1xuICAgICAgICAgICAgICAgIHRoaXMuY2hlY2tBbmRQcmV2ZW50RGVmYXVsdChldmVudCwgdGFyZ2V0LCB0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAodGhpcy5kcmFnZ2luZykge1xuICAgICAgICAgICAgICAgIHRoaXMuYWN0aXZlRHJvcHMuZHJvcHpvbmVzID0gdGhpcy5hY3RpdmVEcm9wcy5lbGVtZW50cyA9IHRoaXMuYWN0aXZlRHJvcHMucmVjdHMgPSBudWxsO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0aGlzLmNsZWFyVGFyZ2V0cygpO1xuICAgICAgICB9XG5cbiAgICAgICAgdGhpcy5wb2ludGVySXNEb3duID0gdGhpcy5zbmFwU3RhdHVzLmxvY2tlZCA9IHRoaXMuZHJhZ2dpbmcgPSB0aGlzLnJlc2l6aW5nID0gdGhpcy5nZXN0dXJpbmcgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5wcmVwYXJlZC5uYW1lID0gdGhpcy5wcmV2RXZlbnQgPSBudWxsO1xuICAgICAgICB0aGlzLmluZXJ0aWFTdGF0dXMucmVzdW1lRHggPSB0aGlzLmluZXJ0aWFTdGF0dXMucmVzdW1lRHkgPSAwO1xuXG4gICAgICAgIC8vIHJlbW92ZSBwb2ludGVycyBpZiB0aGVpciBJRCBpc24ndCBpbiB0aGlzLnBvaW50ZXJJZHNcbiAgICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLnBvaW50ZXJzLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICBpZiAoc2NvcGUuaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIHV0aWxzLmdldFBvaW50ZXJJZCh0aGlzLnBvaW50ZXJzW2ldKSkgPT09IC0xKSB7XG4gICAgICAgICAgICAgICAgdGhpcy5wb2ludGVycy5zcGxpY2UoaSwgMSk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBmb3IgKGkgPSAwOyBpIDwgc2NvcGUuaW50ZXJhY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgICAgICAvLyByZW1vdmUgdGhpcyBpbnRlcmFjdGlvbiBpZiBpdCdzIG5vdCB0aGUgb25seSBvbmUgb2YgaXQncyB0eXBlXG4gICAgICAgICAgICBpZiAoc2NvcGUuaW50ZXJhY3Rpb25zW2ldICE9PSB0aGlzICYmIHNjb3BlLmludGVyYWN0aW9uc1tpXS5tb3VzZSA9PT0gdGhpcy5tb3VzZSkge1xuICAgICAgICAgICAgICAgIHNjb3BlLmludGVyYWN0aW9ucy5zcGxpY2Uoc2NvcGUuaW5kZXhPZihzY29wZS5pbnRlcmFjdGlvbnMsIHRoaXMpLCAxKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBpbmVydGlhRnJhbWU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIGluZXJ0aWFTdGF0dXMgPSB0aGlzLmluZXJ0aWFTdGF0dXMsXG4gICAgICAgICAgICBvcHRpb25zID0gdGhpcy50YXJnZXQub3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLmluZXJ0aWEsXG4gICAgICAgICAgICBsYW1iZGEgPSBvcHRpb25zLnJlc2lzdGFuY2UsXG4gICAgICAgICAgICB0ID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLyAxMDAwIC0gaW5lcnRpYVN0YXR1cy50MDtcblxuICAgICAgICBpZiAodCA8IGluZXJ0aWFTdGF0dXMudGUpIHtcblxuICAgICAgICAgICAgdmFyIHByb2dyZXNzID0gIDEgLSAoTWF0aC5leHAoLWxhbWJkYSAqIHQpIC0gaW5lcnRpYVN0YXR1cy5sYW1iZGFfdjApIC8gaW5lcnRpYVN0YXR1cy5vbmVfdmVfdjA7XG5cbiAgICAgICAgICAgIGlmIChpbmVydGlhU3RhdHVzLm1vZGlmaWVkWGUgPT09IGluZXJ0aWFTdGF0dXMueGUgJiYgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllID09PSBpbmVydGlhU3RhdHVzLnllKSB7XG4gICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeCA9IGluZXJ0aWFTdGF0dXMueGUgKiBwcm9ncmVzcztcbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gaW5lcnRpYVN0YXR1cy55ZSAqIHByb2dyZXNzO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgdmFyIHF1YWRQb2ludCA9IHNjb3BlLmdldFF1YWRyYXRpY0N1cnZlUG9pbnQoXG4gICAgICAgICAgICAgICAgICAgIDAsIDAsXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMueGUsIGluZXJ0aWFTdGF0dXMueWUsXG4gICAgICAgICAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMubW9kaWZpZWRYZSwgaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllLFxuICAgICAgICAgICAgICAgICAgICBwcm9ncmVzcyk7XG5cbiAgICAgICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gcXVhZFBvaW50Lng7XG4gICAgICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IHF1YWRQb2ludC55O1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5pID0gYW5pbWF0aW9uRnJhbWUucmVxdWVzdCh0aGlzLmJvdW5kSW5lcnRpYUZyYW1lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLm1vZGlmaWVkWGU7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN5ID0gaW5lcnRpYVN0YXR1cy5tb2RpZmllZFllO1xuXG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJNb3ZlKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcblxuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5hY3RpdmUgPSBmYWxzZTtcbiAgICAgICAgICAgIHRoaXMucG9pbnRlckVuZChpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgc21vb3RoRW5kRnJhbWU6IGZ1bmN0aW9uICgpIHtcbiAgICAgICAgdmFyIGluZXJ0aWFTdGF0dXMgPSB0aGlzLmluZXJ0aWFTdGF0dXMsXG4gICAgICAgICAgICB0ID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLSBpbmVydGlhU3RhdHVzLnQwLFxuICAgICAgICAgICAgZHVyYXRpb24gPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYS5zbW9vdGhFbmREdXJhdGlvbjtcblxuICAgICAgICBpZiAodCA8IGR1cmF0aW9uKSB7XG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLnN4ID0gc2NvcGUuZWFzZU91dFF1YWQodCwgMCwgaW5lcnRpYVN0YXR1cy54ZSwgZHVyYXRpb24pO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IHNjb3BlLmVhc2VPdXRRdWFkKHQsIDAsIGluZXJ0aWFTdGF0dXMueWUsIGR1cmF0aW9uKTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyTW92ZShpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQsIGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCk7XG5cbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuaSA9IGFuaW1hdGlvbkZyYW1lLnJlcXVlc3QodGhpcy5ib3VuZFNtb290aEVuZEZyYW1lKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIGluZXJ0aWFTdGF0dXMuc3ggPSBpbmVydGlhU3RhdHVzLnhlO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zeSA9IGluZXJ0aWFTdGF0dXMueWU7XG5cbiAgICAgICAgICAgIHRoaXMucG9pbnRlck1vdmUoaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50LCBpbmVydGlhU3RhdHVzLnN0YXJ0RXZlbnQpO1xuXG4gICAgICAgICAgICBpbmVydGlhU3RhdHVzLmFjdGl2ZSA9IGZhbHNlO1xuICAgICAgICAgICAgaW5lcnRpYVN0YXR1cy5zbW9vdGhFbmQgPSBmYWxzZTtcblxuICAgICAgICAgICAgdGhpcy5wb2ludGVyRW5kKGluZXJ0aWFTdGF0dXMuc3RhcnRFdmVudCwgaW5lcnRpYVN0YXR1cy5zdGFydEV2ZW50KTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBhZGRQb2ludGVyOiBmdW5jdGlvbiAocG9pbnRlcikge1xuICAgICAgICB2YXIgaWQgPSB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlciksXG4gICAgICAgICAgICBpbmRleCA9IHRoaXMubW91c2U/IDAgOiBzY29wZS5pbmRleE9mKHRoaXMucG9pbnRlcklkcywgaWQpO1xuXG4gICAgICAgIGlmIChpbmRleCA9PT0gLTEpIHtcbiAgICAgICAgICAgIGluZGV4ID0gdGhpcy5wb2ludGVySWRzLmxlbmd0aDtcbiAgICAgICAgfVxuXG4gICAgICAgIHRoaXMucG9pbnRlcklkc1tpbmRleF0gPSBpZDtcbiAgICAgICAgdGhpcy5wb2ludGVyc1tpbmRleF0gPSBwb2ludGVyO1xuXG4gICAgICAgIHJldHVybiBpbmRleDtcbiAgICB9LFxuXG4gICAgcmVtb3ZlUG9pbnRlcjogZnVuY3Rpb24gKHBvaW50ZXIpIHtcbiAgICAgICAgdmFyIGlkID0gdXRpbHMuZ2V0UG9pbnRlcklkKHBvaW50ZXIpLFxuICAgICAgICAgICAgaW5kZXggPSB0aGlzLm1vdXNlPyAwIDogc2NvcGUuaW5kZXhPZih0aGlzLnBvaW50ZXJJZHMsIGlkKTtcblxuICAgICAgICBpZiAoaW5kZXggPT09IC0xKSB7IHJldHVybjsgfVxuXG4gICAgICAgIGlmICghdGhpcy5pbnRlcmFjdGluZygpKSB7XG4gICAgICAgICAgICB0aGlzLnBvaW50ZXJzLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgIH1cblxuICAgICAgICB0aGlzLnBvaW50ZXJJZHMgLnNwbGljZShpbmRleCwgMSk7XG4gICAgICAgIHRoaXMuZG93blRhcmdldHMuc3BsaWNlKGluZGV4LCAxKTtcbiAgICAgICAgdGhpcy5kb3duVGltZXMgIC5zcGxpY2UoaW5kZXgsIDEpO1xuICAgICAgICB0aGlzLmhvbGRUaW1lcnMgLnNwbGljZShpbmRleCwgMSk7XG4gICAgfSxcblxuICAgIHJlY29yZFBvaW50ZXI6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIC8vIERvIG5vdCB1cGRhdGUgcG9pbnRlcnMgd2hpbGUgaW5lcnRpYSBpcyBhY3RpdmUuXG4gICAgICAgIC8vIFRoZSBpbmVydGlhIHN0YXJ0IGV2ZW50IHNob3VsZCBiZSB0aGlzLnBvaW50ZXJzWzBdXG4gICAgICAgIGlmICh0aGlzLmluZXJ0aWFTdGF0dXMuYWN0aXZlKSB7IHJldHVybjsgfVxuXG4gICAgICAgIHZhciBpbmRleCA9IHRoaXMubW91c2U/IDA6IHNjb3BlLmluZGV4T2YodGhpcy5wb2ludGVySWRzLCB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcikpO1xuXG4gICAgICAgIGlmIChpbmRleCA9PT0gLTEpIHsgcmV0dXJuOyB9XG5cbiAgICAgICAgdGhpcy5wb2ludGVyc1tpbmRleF0gPSBwb2ludGVyO1xuICAgIH0sXG5cbiAgICBjb2xsZWN0RXZlbnRUYXJnZXRzOiBmdW5jdGlvbiAocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCBldmVudFR5cGUpIHtcbiAgICAgICAgdmFyIHBvaW50ZXJJbmRleCA9IHRoaXMubW91c2U/IDAgOiBzY29wZS5pbmRleE9mKHRoaXMucG9pbnRlcklkcywgdXRpbHMuZ2V0UG9pbnRlcklkKHBvaW50ZXIpKTtcblxuICAgICAgICAvLyBkbyBub3QgZmlyZSBhIHRhcCBldmVudCBpZiB0aGUgcG9pbnRlciB3YXMgbW92ZWQgYmVmb3JlIGJlaW5nIGxpZnRlZFxuICAgICAgICBpZiAoZXZlbnRUeXBlID09PSAndGFwJyAmJiAodGhpcy5wb2ludGVyV2FzTW92ZWRcbiAgICAgICAgICAgICAgICAvLyBvciBpZiB0aGUgcG9pbnRlcnVwIHRhcmdldCBpcyBkaWZmZXJlbnQgdG8gdGhlIHBvaW50ZXJkb3duIHRhcmdldFxuICAgICAgICAgICAgfHwgISh0aGlzLmRvd25UYXJnZXRzW3BvaW50ZXJJbmRleF0gJiYgdGhpcy5kb3duVGFyZ2V0c1twb2ludGVySW5kZXhdID09PSBldmVudFRhcmdldCkpKSB7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdGFyZ2V0cyA9IFtdLFxuICAgICAgICAgICAgZWxlbWVudHMgPSBbXSxcbiAgICAgICAgICAgIGVsZW1lbnQgPSBldmVudFRhcmdldDtcblxuICAgICAgICBmdW5jdGlvbiBjb2xsZWN0U2VsZWN0b3JzIChpbnRlcmFjdGFibGUsIHNlbGVjdG9yLCBjb250ZXh0KSB7XG4gICAgICAgICAgICB2YXIgZWxzID0gc2NvcGUuaWU4TWF0Y2hlc1NlbGVjdG9yXG4gICAgICAgICAgICAgICAgPyBjb250ZXh0LnF1ZXJ5U2VsZWN0b3JBbGwoc2VsZWN0b3IpXG4gICAgICAgICAgICAgICAgOiB1bmRlZmluZWQ7XG5cbiAgICAgICAgICAgIGlmIChpbnRlcmFjdGFibGUuX2lFdmVudHNbZXZlbnRUeXBlXVxuICAgICAgICAgICAgICAgICYmIHV0aWxzLmlzRWxlbWVudChlbGVtZW50KVxuICAgICAgICAgICAgICAgICYmIHNjb3BlLmluQ29udGV4dChpbnRlcmFjdGFibGUsIGVsZW1lbnQpXG4gICAgICAgICAgICAgICAgJiYgIXNjb3BlLnRlc3RJZ25vcmUoaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS50ZXN0QWxsb3coaW50ZXJhY3RhYmxlLCBlbGVtZW50LCBldmVudFRhcmdldClcbiAgICAgICAgICAgICAgICAmJiBzY29wZS5tYXRjaGVzU2VsZWN0b3IoZWxlbWVudCwgc2VsZWN0b3IsIGVscykpIHtcblxuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaChpbnRlcmFjdGFibGUpO1xuICAgICAgICAgICAgICAgIGVsZW1lbnRzLnB1c2goZWxlbWVudCk7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuXG4gICAgICAgIHZhciBpbnRlcmFjdCA9IHNjb3BlLmludGVyYWN0O1xuXG4gICAgICAgIHdoaWxlIChlbGVtZW50KSB7XG4gICAgICAgICAgICBpZiAoaW50ZXJhY3QuaXNTZXQoZWxlbWVudCkgJiYgaW50ZXJhY3QoZWxlbWVudCkuX2lFdmVudHNbZXZlbnRUeXBlXSkge1xuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaChpbnRlcmFjdChlbGVtZW50KSk7XG4gICAgICAgICAgICAgICAgZWxlbWVudHMucHVzaChlbGVtZW50KTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgc2NvcGUuaW50ZXJhY3RhYmxlcy5mb3JFYWNoU2VsZWN0b3IoY29sbGVjdFNlbGVjdG9ycyk7XG5cbiAgICAgICAgICAgIGVsZW1lbnQgPSBzY29wZS5wYXJlbnRFbGVtZW50KGVsZW1lbnQpO1xuICAgICAgICB9XG5cbiAgICAgICAgLy8gY3JlYXRlIHRoZSB0YXAgZXZlbnQgZXZlbiBpZiB0aGVyZSBhcmUgbm8gbGlzdGVuZXJzIHNvIHRoYXRcbiAgICAgICAgLy8gZG91YmxldGFwIGNhbiBzdGlsbCBiZSBjcmVhdGVkIGFuZCBmaXJlZFxuICAgICAgICBpZiAodGFyZ2V0cy5sZW5ndGggfHwgZXZlbnRUeXBlID09PSAndGFwJykge1xuICAgICAgICAgICAgdGhpcy5maXJlUG9pbnRlcnMocG9pbnRlciwgZXZlbnQsIGV2ZW50VGFyZ2V0LCB0YXJnZXRzLCBlbGVtZW50cywgZXZlbnRUeXBlKTtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBmaXJlUG9pbnRlcnM6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgZXZlbnRUYXJnZXQsIHRhcmdldHMsIGVsZW1lbnRzLCBldmVudFR5cGUpIHtcbiAgICAgICAgdmFyIHBvaW50ZXJJbmRleCA9IHRoaXMubW91c2U/IDAgOiBzY29wZS5pbmRleE9mKHV0aWxzLmdldFBvaW50ZXJJZChwb2ludGVyKSksXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQgPSB7fSxcbiAgICAgICAgICAgIGksXG4gICAgICAgIC8vIGZvciB0YXAgZXZlbnRzXG4gICAgICAgICAgICBpbnRlcnZhbCwgY3JlYXRlTmV3RG91YmxlVGFwO1xuXG4gICAgICAgIC8vIGlmIGl0J3MgYSBkb3VibGV0YXAgdGhlbiB0aGUgZXZlbnQgcHJvcGVydGllcyB3b3VsZCBoYXZlIGJlZW5cbiAgICAgICAgLy8gY29waWVkIGZyb20gdGhlIHRhcCBldmVudCBhbmQgcHJvdmlkZWQgYXMgdGhlIHBvaW50ZXIgYXJndW1lbnRcbiAgICAgICAgaWYgKGV2ZW50VHlwZSA9PT0gJ2RvdWJsZXRhcCcpIHtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudCA9IHBvaW50ZXI7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICB1dGlscy5leHRlbmQocG9pbnRlckV2ZW50LCBldmVudCk7XG4gICAgICAgICAgICBpZiAoZXZlbnQgIT09IHBvaW50ZXIpIHtcbiAgICAgICAgICAgICAgICB1dGlscy5leHRlbmQocG9pbnRlckV2ZW50LCBwb2ludGVyKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnByZXZlbnREZWZhdWx0ICAgICAgICAgICA9IHByZXZlbnRPcmlnaW5hbERlZmF1bHQ7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuc3RvcFByb3BhZ2F0aW9uICAgICAgICAgID0gSW50ZXJhY3RFdmVudC5wcm90b3R5cGUuc3RvcFByb3BhZ2F0aW9uO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbiA9IEludGVyYWN0RXZlbnQucHJvdG90eXBlLnN0b3BJbW1lZGlhdGVQcm9wYWdhdGlvbjtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5pbnRlcmFjdGlvbiAgICAgICAgICAgICAgPSB0aGlzO1xuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQudGltZVN0YW1wICAgICA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50Lm9yaWdpbmFsRXZlbnQgPSBldmVudDtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC50eXBlICAgICAgICAgID0gZXZlbnRUeXBlO1xuICAgICAgICAgICAgcG9pbnRlckV2ZW50LnBvaW50ZXJJZCAgICAgPSB1dGlscy5nZXRQb2ludGVySWQocG9pbnRlcik7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQucG9pbnRlclR5cGUgICA9IHRoaXMubW91c2U/ICdtb3VzZScgOiAhYnJvd3Nlci5zdXBwb3J0c1BvaW50ZXJFdmVudD8gJ3RvdWNoJ1xuICAgICAgICAgICAgICAgIDogc2NvcGUuaXNTdHJpbmcocG9pbnRlci5wb2ludGVyVHlwZSlcbiAgICAgICAgICAgICAgICA/IHBvaW50ZXIucG9pbnRlclR5cGVcbiAgICAgICAgICAgICAgICA6IFssLCd0b3VjaCcsICdwZW4nLCAnbW91c2UnXVtwb2ludGVyLnBvaW50ZXJUeXBlXTtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChldmVudFR5cGUgPT09ICd0YXAnKSB7XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZHQgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy5kb3duVGltZXNbcG9pbnRlckluZGV4XTtcblxuICAgICAgICAgICAgaW50ZXJ2YWwgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wIC0gdGhpcy50YXBUaW1lO1xuICAgICAgICAgICAgY3JlYXRlTmV3RG91YmxlVGFwID0gISEodGhpcy5wcmV2VGFwICYmIHRoaXMucHJldlRhcC50eXBlICE9PSAnZG91YmxldGFwJ1xuICAgICAgICAgICAgJiYgdGhpcy5wcmV2VGFwLnRhcmdldCA9PT0gcG9pbnRlckV2ZW50LnRhcmdldFxuICAgICAgICAgICAgJiYgaW50ZXJ2YWwgPCA1MDApO1xuXG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuZG91YmxlID0gY3JlYXRlTmV3RG91YmxlVGFwO1xuXG4gICAgICAgICAgICB0aGlzLnRhcFRpbWUgPSBwb2ludGVyRXZlbnQudGltZVN0YW1wO1xuICAgICAgICB9XG5cbiAgICAgICAgZm9yIChpID0gMDsgaSA8IHRhcmdldHMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgIHBvaW50ZXJFdmVudC5jdXJyZW50VGFyZ2V0ID0gZWxlbWVudHNbaV07XG4gICAgICAgICAgICBwb2ludGVyRXZlbnQuaW50ZXJhY3RhYmxlID0gdGFyZ2V0c1tpXTtcbiAgICAgICAgICAgIHRhcmdldHNbaV0uZmlyZShwb2ludGVyRXZlbnQpO1xuXG4gICAgICAgICAgICBpZiAocG9pbnRlckV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZFxuICAgICAgICAgICAgICAgIHx8KHBvaW50ZXJFdmVudC5wcm9wYWdhdGlvblN0b3BwZWQgJiYgZWxlbWVudHNbaSArIDFdICE9PSBwb2ludGVyRXZlbnQuY3VycmVudFRhcmdldCkpIHtcbiAgICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChjcmVhdGVOZXdEb3VibGVUYXApIHtcbiAgICAgICAgICAgIHZhciBkb3VibGVUYXAgPSB7fTtcblxuICAgICAgICAgICAgdXRpbHMuZXh0ZW5kKGRvdWJsZVRhcCwgcG9pbnRlckV2ZW50KTtcblxuICAgICAgICAgICAgZG91YmxlVGFwLmR0ICAgPSBpbnRlcnZhbDtcbiAgICAgICAgICAgIGRvdWJsZVRhcC50eXBlID0gJ2RvdWJsZXRhcCc7XG5cbiAgICAgICAgICAgIHRoaXMuY29sbGVjdEV2ZW50VGFyZ2V0cyhkb3VibGVUYXAsIGV2ZW50LCBldmVudFRhcmdldCwgJ2RvdWJsZXRhcCcpO1xuXG4gICAgICAgICAgICB0aGlzLnByZXZUYXAgPSBkb3VibGVUYXA7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoZXZlbnRUeXBlID09PSAndGFwJykge1xuICAgICAgICAgICAgdGhpcy5wcmV2VGFwID0gcG9pbnRlckV2ZW50O1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIHZhbGlkYXRlU2VsZWN0b3I6IGZ1bmN0aW9uIChwb2ludGVyLCBldmVudCwgbWF0Y2hlcywgbWF0Y2hFbGVtZW50cykge1xuICAgICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gbWF0Y2hlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgdmFyIG1hdGNoID0gbWF0Y2hlc1tpXSxcbiAgICAgICAgICAgICAgICBtYXRjaEVsZW1lbnQgPSBtYXRjaEVsZW1lbnRzW2ldLFxuICAgICAgICAgICAgICAgIGFjdGlvbiA9IHZhbGlkYXRlQWN0aW9uKG1hdGNoLmdldEFjdGlvbihwb2ludGVyLCBldmVudCwgdGhpcywgbWF0Y2hFbGVtZW50KSwgbWF0Y2gpO1xuXG4gICAgICAgICAgICBpZiAoYWN0aW9uICYmIHNjb3BlLndpdGhpbkludGVyYWN0aW9uTGltaXQobWF0Y2gsIG1hdGNoRWxlbWVudCwgYWN0aW9uKSkge1xuICAgICAgICAgICAgICAgIHRoaXMudGFyZ2V0ID0gbWF0Y2g7XG4gICAgICAgICAgICAgICAgdGhpcy5lbGVtZW50ID0gbWF0Y2hFbGVtZW50O1xuXG4gICAgICAgICAgICAgICAgcmV0dXJuIGFjdGlvbjtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBzZXRTbmFwcGluZzogZnVuY3Rpb24gKHBhZ2VDb29yZHMsIHN0YXR1cykge1xuICAgICAgICB2YXIgc25hcCA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5zbmFwLFxuICAgICAgICAgICAgdGFyZ2V0cyA9IFtdLFxuICAgICAgICAgICAgdGFyZ2V0LFxuICAgICAgICAgICAgcGFnZSxcbiAgICAgICAgICAgIGk7XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMuc25hcFN0YXR1cztcblxuICAgICAgICBpZiAoc3RhdHVzLnVzZVN0YXR1c1hZKSB7XG4gICAgICAgICAgICBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHZhciBvcmlnaW4gPSBzY29wZS5nZXRPcmlnaW5YWSh0aGlzLnRhcmdldCwgdGhpcy5lbGVtZW50KTtcblxuICAgICAgICAgICAgcGFnZSA9IHV0aWxzLmV4dGVuZCh7fSwgcGFnZUNvb3Jkcyk7XG5cbiAgICAgICAgICAgIHBhZ2UueCAtPSBvcmlnaW4ueDtcbiAgICAgICAgICAgIHBhZ2UueSAtPSBvcmlnaW4ueTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5yZWFsWCA9IHBhZ2UueDtcbiAgICAgICAgc3RhdHVzLnJlYWxZID0gcGFnZS55O1xuXG4gICAgICAgIHBhZ2UueCA9IHBhZ2UueCAtIHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55ID0gcGFnZS55IC0gdGhpcy5pbmVydGlhU3RhdHVzLnJlc3VtZUR5O1xuXG4gICAgICAgIHZhciBsZW4gPSBzbmFwLnRhcmdldHM/IHNuYXAudGFyZ2V0cy5sZW5ndGggOiAwO1xuXG4gICAgICAgIGZvciAodmFyIHJlbEluZGV4ID0gMDsgcmVsSW5kZXggPCB0aGlzLnNuYXBPZmZzZXRzLmxlbmd0aDsgcmVsSW5kZXgrKykge1xuICAgICAgICAgICAgdmFyIHJlbGF0aXZlID0ge1xuICAgICAgICAgICAgICAgIHg6IHBhZ2UueCAtIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngsXG4gICAgICAgICAgICAgICAgeTogcGFnZS55IC0gdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueVxuICAgICAgICAgICAgfTtcblxuICAgICAgICAgICAgZm9yIChpID0gMDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgICAgICAgICAgaWYgKHNjb3BlLmlzRnVuY3Rpb24oc25hcC50YXJnZXRzW2ldKSkge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV0ocmVsYXRpdmUueCwgcmVsYXRpdmUueSwgdGhpcyk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQgPSBzbmFwLnRhcmdldHNbaV07XG4gICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgaWYgKCF0YXJnZXQpIHsgY29udGludWU7IH1cblxuICAgICAgICAgICAgICAgIHRhcmdldHMucHVzaCh7XG4gICAgICAgICAgICAgICAgICAgIHg6IHNjb3BlLmlzTnVtYmVyKHRhcmdldC54KSA/ICh0YXJnZXQueCArIHRoaXMuc25hcE9mZnNldHNbcmVsSW5kZXhdLngpIDogcmVsYXRpdmUueCxcbiAgICAgICAgICAgICAgICAgICAgeTogc2NvcGUuaXNOdW1iZXIodGFyZ2V0LnkpID8gKHRhcmdldC55ICsgdGhpcy5zbmFwT2Zmc2V0c1tyZWxJbmRleF0ueSkgOiByZWxhdGl2ZS55LFxuXG4gICAgICAgICAgICAgICAgICAgIHJhbmdlOiBzY29wZS5pc051bWJlcih0YXJnZXQucmFuZ2UpPyB0YXJnZXQucmFuZ2U6IHNuYXAucmFuZ2VcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBjbG9zZXN0ID0ge1xuICAgICAgICAgICAgdGFyZ2V0OiBudWxsLFxuICAgICAgICAgICAgaW5SYW5nZTogZmFsc2UsXG4gICAgICAgICAgICBkaXN0YW5jZTogMCxcbiAgICAgICAgICAgIHJhbmdlOiAwLFxuICAgICAgICAgICAgZHg6IDAsXG4gICAgICAgICAgICBkeTogMFxuICAgICAgICB9O1xuXG4gICAgICAgIGZvciAoaSA9IDAsIGxlbiA9IHRhcmdldHMubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgICAgIHRhcmdldCA9IHRhcmdldHNbaV07XG5cbiAgICAgICAgICAgIHZhciByYW5nZSA9IHRhcmdldC5yYW5nZSxcbiAgICAgICAgICAgICAgICBkeCA9IHRhcmdldC54IC0gcGFnZS54LFxuICAgICAgICAgICAgICAgIGR5ID0gdGFyZ2V0LnkgLSBwYWdlLnksXG4gICAgICAgICAgICAgICAgZGlzdGFuY2UgPSB1dGlscy5oeXBvdChkeCwgZHkpLFxuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBkaXN0YW5jZSA8PSByYW5nZTtcblxuICAgICAgICAgICAgLy8gSW5maW5pdGUgdGFyZ2V0cyBjb3VudCBhcyBiZWluZyBvdXQgb2YgcmFuZ2VcbiAgICAgICAgICAgIC8vIGNvbXBhcmVkIHRvIG5vbiBpbmZpbml0ZSBvbmVzIHRoYXQgYXJlIGluIHJhbmdlXG4gICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5ICYmIGNsb3Nlc3QuaW5SYW5nZSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSkge1xuICAgICAgICAgICAgICAgIGluUmFuZ2UgPSBmYWxzZTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFjbG9zZXN0LnRhcmdldCB8fCAoaW5SYW5nZVxuICAgICAgICAgICAgICAgICAgICAvLyBpcyB0aGUgY2xvc2VzdCB0YXJnZXQgaW4gcmFuZ2U/XG4gICAgICAgICAgICAgICAgICAgID8gKGNsb3Nlc3QuaW5SYW5nZSAmJiByYW5nZSAhPT0gSW5maW5pdHlcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhlIHBvaW50ZXIgaXMgcmVsYXRpdmVseSBkZWVwZXIgaW4gdGhpcyB0YXJnZXRcbiAgICAgICAgICAgICAgICAgICAgPyBkaXN0YW5jZSAvIHJhbmdlIDwgY2xvc2VzdC5kaXN0YW5jZSAvIGNsb3Nlc3QucmFuZ2VcbiAgICAgICAgICAgICAgICAgICAgLy8gdGhpcyB0YXJnZXQgaGFzIEluZmluaXRlIHJhbmdlIGFuZCB0aGUgY2xvc2VzdCBkb2Vzbid0XG4gICAgICAgICAgICAgICAgICAgIDogKHJhbmdlID09PSBJbmZpbml0eSAmJiBjbG9zZXN0LnJhbmdlICE9PSBJbmZpbml0eSlcbiAgICAgICAgICAgICAgICAgICAgLy8gT1IgdGhpcyB0YXJnZXQgaXMgY2xvc2VyIHRoYXQgdGhlIHByZXZpb3VzIGNsb3Nlc3RcbiAgICAgICAgICAgICAgICB8fCBkaXN0YW5jZSA8IGNsb3Nlc3QuZGlzdGFuY2UpXG4gICAgICAgICAgICAgICAgICAgIC8vIFRoZSBvdGhlciBpcyBub3QgaW4gcmFuZ2UgYW5kIHRoZSBwb2ludGVyIGlzIGNsb3NlciB0byB0aGlzIHRhcmdldFxuICAgICAgICAgICAgICAgICAgICA6ICghY2xvc2VzdC5pblJhbmdlICYmIGRpc3RhbmNlIDwgY2xvc2VzdC5kaXN0YW5jZSkpKSB7XG5cbiAgICAgICAgICAgICAgICBpZiAocmFuZ2UgPT09IEluZmluaXR5KSB7XG4gICAgICAgICAgICAgICAgICAgIGluUmFuZ2UgPSB0cnVlO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNsb3Nlc3QudGFyZ2V0ID0gdGFyZ2V0O1xuICAgICAgICAgICAgICAgIGNsb3Nlc3QuZGlzdGFuY2UgPSBkaXN0YW5jZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5pblJhbmdlID0gaW5SYW5nZTtcbiAgICAgICAgICAgICAgICBjbG9zZXN0LmR4ID0gZHg7XG4gICAgICAgICAgICAgICAgY2xvc2VzdC5keSA9IGR5O1xuXG4gICAgICAgICAgICAgICAgc3RhdHVzLnJhbmdlID0gcmFuZ2U7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc25hcENoYW5nZWQ7XG5cbiAgICAgICAgaWYgKGNsb3Nlc3QudGFyZ2V0KSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IChzdGF0dXMuc25hcHBlZFggIT09IGNsb3Nlc3QudGFyZ2V0LnggfHwgc3RhdHVzLnNuYXBwZWRZICE9PSBjbG9zZXN0LnRhcmdldC55KTtcblxuICAgICAgICAgICAgc3RhdHVzLnNuYXBwZWRYID0gY2xvc2VzdC50YXJnZXQueDtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IGNsb3Nlc3QudGFyZ2V0Lnk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBzbmFwQ2hhbmdlZCA9IHRydWU7XG5cbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWCA9IE5hTjtcbiAgICAgICAgICAgIHN0YXR1cy5zbmFwcGVkWSA9IE5hTjtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IGNsb3Nlc3QuZHg7XG4gICAgICAgIHN0YXR1cy5keSA9IGNsb3Nlc3QuZHk7XG5cbiAgICAgICAgc3RhdHVzLmNoYW5nZWQgPSAoc25hcENoYW5nZWQgfHwgKGNsb3Nlc3QuaW5SYW5nZSAmJiAhc3RhdHVzLmxvY2tlZCkpO1xuICAgICAgICBzdGF0dXMubG9ja2VkID0gY2xvc2VzdC5pblJhbmdlO1xuXG4gICAgICAgIHJldHVybiBzdGF0dXM7XG4gICAgfSxcblxuICAgIHNldFJlc3RyaWN0aW9uOiBmdW5jdGlvbiAocGFnZUNvb3Jkcywgc3RhdHVzKSB7XG4gICAgICAgIHZhciB0YXJnZXQgPSB0aGlzLnRhcmdldCxcbiAgICAgICAgICAgIHJlc3RyaWN0ID0gdGFyZ2V0ICYmIHRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0ucmVzdHJpY3QsXG4gICAgICAgICAgICByZXN0cmljdGlvbiA9IHJlc3RyaWN0ICYmIHJlc3RyaWN0LnJlc3RyaWN0aW9uLFxuICAgICAgICAgICAgcGFnZTtcblxuICAgICAgICBpZiAoIXJlc3RyaWN0aW9uKSB7XG4gICAgICAgICAgICByZXR1cm4gc3RhdHVzO1xuICAgICAgICB9XG5cbiAgICAgICAgc3RhdHVzID0gc3RhdHVzIHx8IHRoaXMucmVzdHJpY3RTdGF0dXM7XG5cbiAgICAgICAgcGFnZSA9IHN0YXR1cy51c2VTdGF0dXNYWVxuICAgICAgICAgICAgPyBwYWdlID0geyB4OiBzdGF0dXMueCwgeTogc3RhdHVzLnkgfVxuICAgICAgICAgICAgOiBwYWdlID0gdXRpbHMuZXh0ZW5kKHt9LCBwYWdlQ29vcmRzKTtcblxuICAgICAgICBpZiAoc3RhdHVzLnNuYXAgJiYgc3RhdHVzLnNuYXAubG9ja2VkKSB7XG4gICAgICAgICAgICBwYWdlLnggKz0gc3RhdHVzLnNuYXAuZHggfHwgMDtcbiAgICAgICAgICAgIHBhZ2UueSArPSBzdGF0dXMuc25hcC5keSB8fCAwO1xuICAgICAgICB9XG5cbiAgICAgICAgcGFnZS54IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeDtcbiAgICAgICAgcGFnZS55IC09IHRoaXMuaW5lcnRpYVN0YXR1cy5yZXN1bWVEeTtcblxuICAgICAgICBzdGF0dXMuZHggPSAwO1xuICAgICAgICBzdGF0dXMuZHkgPSAwO1xuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZCA9IGZhbHNlO1xuXG4gICAgICAgIHZhciByZWN0LCByZXN0cmljdGVkWCwgcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgaWYgKHNjb3BlLmlzU3RyaW5nKHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgaWYgKHJlc3RyaWN0aW9uID09PSAncGFyZW50Jykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gc2NvcGUucGFyZW50RWxlbWVudCh0aGlzLmVsZW1lbnQpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAocmVzdHJpY3Rpb24gPT09ICdzZWxmJykge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gdGFyZ2V0LmdldFJlY3QodGhpcy5lbGVtZW50KTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gc2NvcGUuY2xvc2VzdCh0aGlzLmVsZW1lbnQsIHJlc3RyaWN0aW9uKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKCFyZXN0cmljdGlvbikgeyByZXR1cm4gc3RhdHVzOyB9XG4gICAgICAgIH1cblxuICAgICAgICBpZiAoc2NvcGUuaXNGdW5jdGlvbihyZXN0cmljdGlvbikpIHtcbiAgICAgICAgICAgIHJlc3RyaWN0aW9uID0gcmVzdHJpY3Rpb24ocGFnZS54LCBwYWdlLnksIHRoaXMuZWxlbWVudCk7XG4gICAgICAgIH1cblxuICAgICAgICBpZiAodXRpbHMuaXNFbGVtZW50KHJlc3RyaWN0aW9uKSkge1xuICAgICAgICAgICAgcmVzdHJpY3Rpb24gPSBzY29wZS5nZXRFbGVtZW50UmVjdChyZXN0cmljdGlvbik7XG4gICAgICAgIH1cblxuICAgICAgICByZWN0ID0gcmVzdHJpY3Rpb247XG5cbiAgICAgICAgaWYgKCFyZXN0cmljdGlvbikge1xuICAgICAgICAgICAgcmVzdHJpY3RlZFggPSBwYWdlLng7XG4gICAgICAgICAgICByZXN0cmljdGVkWSA9IHBhZ2UueTtcbiAgICAgICAgfVxuICAgICAgICAvLyBvYmplY3QgaXMgYXNzdW1lZCB0byBoYXZlXG4gICAgICAgIC8vIHgsIHksIHdpZHRoLCBoZWlnaHQgb3JcbiAgICAgICAgLy8gbGVmdCwgdG9wLCByaWdodCwgYm90dG9tXG4gICAgICAgIGVsc2UgaWYgKCd4JyBpbiByZXN0cmljdGlvbiAmJiAneScgaW4gcmVzdHJpY3Rpb24pIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC54ICsgcmVjdC53aWR0aCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC54ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC55ICsgcmVjdC5oZWlnaHQgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC55ICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRYID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5yaWdodCAgLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LnJpZ2h0ICwgcGFnZS54KSwgcmVjdC5sZWZ0ICsgdGhpcy5yZXN0cmljdE9mZnNldC5sZWZ0KTtcbiAgICAgICAgICAgIHJlc3RyaWN0ZWRZID0gTWF0aC5tYXgoTWF0aC5taW4ocmVjdC5ib3R0b20gLSB0aGlzLnJlc3RyaWN0T2Zmc2V0LmJvdHRvbSwgcGFnZS55KSwgcmVjdC50b3AgICsgdGhpcy5yZXN0cmljdE9mZnNldC50b3AgKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHN0YXR1cy5keCA9IHJlc3RyaWN0ZWRYIC0gcGFnZS54O1xuICAgICAgICBzdGF0dXMuZHkgPSByZXN0cmljdGVkWSAtIHBhZ2UueTtcblxuICAgICAgICBzdGF0dXMuY2hhbmdlZCA9IHN0YXR1cy5yZXN0cmljdGVkWCAhPT0gcmVzdHJpY3RlZFggfHwgc3RhdHVzLnJlc3RyaWN0ZWRZICE9PSByZXN0cmljdGVkWTtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWQgPSAhIShzdGF0dXMuZHggfHwgc3RhdHVzLmR5KTtcblxuICAgICAgICBzdGF0dXMucmVzdHJpY3RlZFggPSByZXN0cmljdGVkWDtcbiAgICAgICAgc3RhdHVzLnJlc3RyaWN0ZWRZID0gcmVzdHJpY3RlZFk7XG5cbiAgICAgICAgcmV0dXJuIHN0YXR1cztcbiAgICB9LFxuXG4gICAgY2hlY2tBbmRQcmV2ZW50RGVmYXVsdDogZnVuY3Rpb24gKGV2ZW50LCBpbnRlcmFjdGFibGUsIGVsZW1lbnQpIHtcbiAgICAgICAgaWYgKCEoaW50ZXJhY3RhYmxlID0gaW50ZXJhY3RhYmxlIHx8IHRoaXMudGFyZ2V0KSkgeyByZXR1cm47IH1cblxuICAgICAgICB2YXIgb3B0aW9ucyA9IGludGVyYWN0YWJsZS5vcHRpb25zLFxuICAgICAgICAgICAgcHJldmVudCA9IG9wdGlvbnMucHJldmVudERlZmF1bHQ7XG5cbiAgICAgICAgaWYgKHByZXZlbnQgPT09ICdhdXRvJyAmJiBlbGVtZW50ICYmICEvXihpbnB1dHxzZWxlY3R8dGV4dGFyZWEpJC9pLnRlc3QoZXZlbnQudGFyZ2V0Lm5vZGVOYW1lKSkge1xuICAgICAgICAgICAgLy8gZG8gbm90IHByZXZlbnREZWZhdWx0IG9uIHBvaW50ZXJkb3duIGlmIHRoZSBwcmVwYXJlZCBhY3Rpb24gaXMgYSBkcmFnXG4gICAgICAgICAgICAvLyBhbmQgZHJhZ2dpbmcgY2FuIG9ubHkgc3RhcnQgZnJvbSBhIGNlcnRhaW4gZGlyZWN0aW9uIC0gdGhpcyBhbGxvd3NcbiAgICAgICAgICAgIC8vIGEgdG91Y2ggdG8gcGFuIHRoZSB2aWV3cG9ydCBpZiBhIGRyYWcgaXNuJ3QgaW4gdGhlIHJpZ2h0IGRpcmVjdGlvblxuICAgICAgICAgICAgaWYgKC9kb3dufHN0YXJ0L2kudGVzdChldmVudC50eXBlKVxuICAgICAgICAgICAgICAgICYmIHRoaXMucHJlcGFyZWQubmFtZSA9PT0gJ2RyYWcnICYmIG9wdGlvbnMuZHJhZy5heGlzICE9PSAneHknKSB7XG5cbiAgICAgICAgICAgICAgICByZXR1cm47XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIC8vIHdpdGggbWFudWFsU3RhcnQsIG9ubHkgcHJldmVudERlZmF1bHQgd2hpbGUgaW50ZXJhY3RpbmdcbiAgICAgICAgICAgIGlmIChvcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0gJiYgb3B0aW9uc1t0aGlzLnByZXBhcmVkLm5hbWVdLm1hbnVhbFN0YXJ0XG4gICAgICAgICAgICAgICAgJiYgIXRoaXMuaW50ZXJhY3RpbmcoKSkge1xuICAgICAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuXG4gICAgICAgIGlmIChwcmV2ZW50ID09PSAnYWx3YXlzJykge1xuICAgICAgICAgICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfVxuICAgIH0sXG5cbiAgICBjYWxjSW5lcnRpYTogZnVuY3Rpb24gKHN0YXR1cykge1xuICAgICAgICB2YXIgaW5lcnRpYU9wdGlvbnMgPSB0aGlzLnRhcmdldC5vcHRpb25zW3RoaXMucHJlcGFyZWQubmFtZV0uaW5lcnRpYSxcbiAgICAgICAgICAgIGxhbWJkYSA9IGluZXJ0aWFPcHRpb25zLnJlc2lzdGFuY2UsXG4gICAgICAgICAgICBpbmVydGlhRHVyID0gLU1hdGgubG9nKGluZXJ0aWFPcHRpb25zLmVuZFNwZWVkIC8gc3RhdHVzLnYwKSAvIGxhbWJkYTtcblxuICAgICAgICBzdGF0dXMueDAgPSB0aGlzLnByZXZFdmVudC5wYWdlWDtcbiAgICAgICAgc3RhdHVzLnkwID0gdGhpcy5wcmV2RXZlbnQucGFnZVk7XG4gICAgICAgIHN0YXR1cy50MCA9IHN0YXR1cy5zdGFydEV2ZW50LnRpbWVTdGFtcCAvIDEwMDA7XG4gICAgICAgIHN0YXR1cy5zeCA9IHN0YXR1cy5zeSA9IDA7XG5cbiAgICAgICAgc3RhdHVzLm1vZGlmaWVkWGUgPSBzdGF0dXMueGUgPSAoc3RhdHVzLnZ4MCAtIGluZXJ0aWFEdXIpIC8gbGFtYmRhO1xuICAgICAgICBzdGF0dXMubW9kaWZpZWRZZSA9IHN0YXR1cy55ZSA9IChzdGF0dXMudnkwIC0gaW5lcnRpYUR1cikgLyBsYW1iZGE7XG4gICAgICAgIHN0YXR1cy50ZSA9IGluZXJ0aWFEdXI7XG5cbiAgICAgICAgc3RhdHVzLmxhbWJkYV92MCA9IGxhbWJkYSAvIHN0YXR1cy52MDtcbiAgICAgICAgc3RhdHVzLm9uZV92ZV92MCA9IDEgLSBpbmVydGlhT3B0aW9ucy5lbmRTcGVlZCAvIHN0YXR1cy52MDtcbiAgICB9LFxuXG4gICAgYXV0b1Njcm9sbE1vdmU6IGZ1bmN0aW9uIChwb2ludGVyKSB7XG4gICAgICAgIGlmICghKHRoaXMuaW50ZXJhY3RpbmcoKVxuICAgICAgICAgICAgJiYgc2NvcGUuY2hlY2tBdXRvU2Nyb2xsKHRoaXMudGFyZ2V0LCB0aGlzLnByZXBhcmVkLm5hbWUpKSkge1xuICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRoaXMuaW5lcnRpYVN0YXR1cy5hY3RpdmUpIHtcbiAgICAgICAgICAgIHNjb3BlLmF1dG9TY3JvbGwueCA9IHNjb3BlLmF1dG9TY3JvbGwueSA9IDA7XG4gICAgICAgICAgICByZXR1cm47XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgdG9wLFxuICAgICAgICAgICAgcmlnaHQsXG4gICAgICAgICAgICBib3R0b20sXG4gICAgICAgICAgICBsZWZ0LFxuICAgICAgICAgICAgb3B0aW9ucyA9IHRoaXMudGFyZ2V0Lm9wdGlvbnNbdGhpcy5wcmVwYXJlZC5uYW1lXS5hdXRvU2Nyb2xsLFxuICAgICAgICAgICAgY29udGFpbmVyID0gb3B0aW9ucy5jb250YWluZXIgfHwgc2NvcGUuZ2V0V2luZG93KHRoaXMuZWxlbWVudCk7XG5cbiAgICAgICAgaWYgKHNjb3BlLmlzV2luZG93KGNvbnRhaW5lcikpIHtcbiAgICAgICAgICAgIGxlZnQgICA9IHBvaW50ZXIuY2xpZW50WCA8IHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgc2NvcGUuYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICByaWdodCAgPSBwb2ludGVyLmNsaWVudFggPiBjb250YWluZXIuaW5uZXJXaWR0aCAgLSBzY29wZS5hdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIGJvdHRvbSA9IHBvaW50ZXIuY2xpZW50WSA+IGNvbnRhaW5lci5pbm5lckhlaWdodCAtIHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgICAgdmFyIHJlY3QgPSBzY29wZS5nZXRFbGVtZW50UmVjdChjb250YWluZXIpO1xuXG4gICAgICAgICAgICBsZWZ0ICAgPSBwb2ludGVyLmNsaWVudFggPCByZWN0LmxlZnQgICArIHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICAgICAgdG9wICAgID0gcG9pbnRlci5jbGllbnRZIDwgcmVjdC50b3AgICAgKyBzY29wZS5hdXRvU2Nyb2xsLm1hcmdpbjtcbiAgICAgICAgICAgIHJpZ2h0ICA9IHBvaW50ZXIuY2xpZW50WCA+IHJlY3QucmlnaHQgIC0gc2NvcGUuYXV0b1Njcm9sbC5tYXJnaW47XG4gICAgICAgICAgICBib3R0b20gPSBwb2ludGVyLmNsaWVudFkgPiByZWN0LmJvdHRvbSAtIHNjb3BlLmF1dG9TY3JvbGwubWFyZ2luO1xuICAgICAgICB9XG5cbiAgICAgICAgc2NvcGUuYXV0b1Njcm9sbC54ID0gKHJpZ2h0ID8gMTogbGVmdD8gLTE6IDApO1xuICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLnkgPSAoYm90dG9tPyAxOiAgdG9wPyAtMTogMCk7XG5cbiAgICAgICAgaWYgKCFzY29wZS5hdXRvU2Nyb2xsLmlzU2Nyb2xsaW5nKSB7XG4gICAgICAgICAgICAvLyBzZXQgdGhlIGF1dG9TY3JvbGwgcHJvcGVydGllcyB0byB0aG9zZSBvZiB0aGUgdGFyZ2V0XG4gICAgICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLm1hcmdpbiA9IG9wdGlvbnMubWFyZ2luO1xuICAgICAgICAgICAgc2NvcGUuYXV0b1Njcm9sbC5zcGVlZCAgPSBvcHRpb25zLnNwZWVkO1xuXG4gICAgICAgICAgICBzY29wZS5hdXRvU2Nyb2xsLnN0YXJ0KHRoaXMpO1xuICAgICAgICB9XG4gICAgfSxcblxuICAgIF91cGRhdGVFdmVudFRhcmdldHM6IGZ1bmN0aW9uICh0YXJnZXQsIGN1cnJlbnRUYXJnZXQpIHtcbiAgICAgICAgdGhpcy5fZXZlbnRUYXJnZXQgICAgPSB0YXJnZXQ7XG4gICAgICAgIHRoaXMuX2N1ckV2ZW50VGFyZ2V0ID0gY3VycmVudFRhcmdldDtcbiAgICB9XG5cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gSW50ZXJhY3Rpb247IiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgcmFmICAgICAgID0gcmVxdWlyZSgnLi91dGlscy9yYWYnKSxcbiAgICBnZXRXaW5kb3cgPSByZXF1aXJlKCcuL3V0aWxzL3dpbmRvdycpLmdldFdpbmRvdyxcbiAgICBpc1dpbmRvdyAgPSByZXF1aXJlKCcuL3V0aWxzL2lzVHlwZScpLmlzV2luZG93O1xuXG52YXIgYXV0b1Njcm9sbCA9IHtcblxuICAgIGludGVyYWN0aW9uOiBudWxsLFxuICAgIGk6IG51bGwsICAgIC8vIHRoZSBoYW5kbGUgcmV0dXJuZWQgYnkgd2luZG93LnNldEludGVydmFsXG4gICAgeDogMCwgeTogMCwgLy8gRGlyZWN0aW9uIGVhY2ggcHVsc2UgaXMgdG8gc2Nyb2xsIGluXG5cbiAgICBpc1Njcm9sbGluZzogZmFsc2UsXG4gICAgcHJldlRpbWU6IDAsXG5cbiAgICBzdGFydDogZnVuY3Rpb24gKGludGVyYWN0aW9uKSB7XG4gICAgICAgIGF1dG9TY3JvbGwuaXNTY3JvbGxpbmcgPSB0cnVlO1xuICAgICAgICByYWYuY2FuY2VsKGF1dG9TY3JvbGwuaSk7XG5cbiAgICAgICAgYXV0b1Njcm9sbC5pbnRlcmFjdGlvbiA9IGludGVyYWN0aW9uO1xuICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG4gICAgICAgIGF1dG9TY3JvbGwuaSA9IHJhZi5yZXF1ZXN0KGF1dG9TY3JvbGwuc2Nyb2xsKTtcbiAgICB9LFxuXG4gICAgc3RvcDogZnVuY3Rpb24gKCkge1xuICAgICAgICBhdXRvU2Nyb2xsLmlzU2Nyb2xsaW5nID0gZmFsc2U7XG4gICAgICAgIHJhZi5jYW5jZWwoYXV0b1Njcm9sbC5pKTtcbiAgICB9LFxuXG4gICAgLy8gc2Nyb2xsIHRoZSB3aW5kb3cgYnkgdGhlIHZhbHVlcyBpbiBzY3JvbGwueC95XG4gICAgc2Nyb2xsOiBmdW5jdGlvbiAoKSB7XG4gICAgICAgIHZhciBvcHRpb25zID0gYXV0b1Njcm9sbC5pbnRlcmFjdGlvbi50YXJnZXQub3B0aW9uc1thdXRvU2Nyb2xsLmludGVyYWN0aW9uLnByZXBhcmVkLm5hbWVdLmF1dG9TY3JvbGwsXG4gICAgICAgICAgICBjb250YWluZXIgPSBvcHRpb25zLmNvbnRhaW5lciB8fCBnZXRXaW5kb3coYXV0b1Njcm9sbC5pbnRlcmFjdGlvbi5lbGVtZW50KSxcbiAgICAgICAgICAgIG5vdyA9IG5ldyBEYXRlKCkuZ2V0VGltZSgpLFxuICAgICAgICAgICAgLy8gY2hhbmdlIGluIHRpbWUgaW4gc2Vjb25kc1xuICAgICAgICAgICAgZHQgPSAobm93IC0gYXV0b1Njcm9sbC5wcmV2VGltZSkgLyAxMDAwLFxuICAgICAgICAgICAgLy8gZGlzcGxhY2VtZW50XG4gICAgICAgICAgICBzID0gb3B0aW9ucy5zcGVlZCAqIGR0O1xuXG4gICAgICAgIGlmIChzID49IDEpIHtcbiAgICAgICAgICAgIGlmIChpc1dpbmRvdyhjb250YWluZXIpKSB7XG4gICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbEJ5KGF1dG9TY3JvbGwueCAqIHMsIGF1dG9TY3JvbGwueSAqIHMpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSBpZiAoY29udGFpbmVyKSB7XG4gICAgICAgICAgICAgICAgY29udGFpbmVyLnNjcm9sbExlZnQgKz0gYXV0b1Njcm9sbC54ICogcztcbiAgICAgICAgICAgICAgICBjb250YWluZXIuc2Nyb2xsVG9wICArPSBhdXRvU2Nyb2xsLnkgKiBzO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBhdXRvU2Nyb2xsLnByZXZUaW1lID0gbm93O1xuICAgICAgICB9XG5cbiAgICAgICAgaWYgKGF1dG9TY3JvbGwuaXNTY3JvbGxpbmcpIHtcbiAgICAgICAgICAgIHJhZi5jYW5jZWwoYXV0b1Njcm9sbC5pKTtcbiAgICAgICAgICAgIGF1dG9TY3JvbGwuaSA9IHJhZi5yZXF1ZXN0KGF1dG9TY3JvbGwuc2Nyb2xsKTtcbiAgICAgICAgfVxuICAgIH1cbn07XG5cbm1vZHVsZS5leHBvcnRzID0gYXV0b1Njcm9sbDtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSB7XG4gICAgYmFzZToge1xuICAgICAgICBhY2NlcHQgICAgICAgIDogbnVsbCxcbiAgICAgICAgYWN0aW9uQ2hlY2tlciA6IG51bGwsXG4gICAgICAgIHN0eWxlQ3Vyc29yICAgOiB0cnVlLFxuICAgICAgICBwcmV2ZW50RGVmYXVsdDogJ2F1dG8nLFxuICAgICAgICBvcmlnaW4gICAgICAgIDogeyB4OiAwLCB5OiAwIH0sXG4gICAgICAgIGRlbHRhU291cmNlICAgOiAncGFnZScsXG4gICAgICAgIGFsbG93RnJvbSAgICAgOiBudWxsLFxuICAgICAgICBpZ25vcmVGcm9tICAgIDogbnVsbCxcbiAgICAgICAgX2NvbnRleHQgICAgICA6IHJlcXVpcmUoJy4vdXRpbHMvZG9tT2JqZWN0cycpLmRvY3VtZW50LFxuICAgICAgICBkcm9wQ2hlY2tlciAgIDogbnVsbFxuICAgIH0sXG5cbiAgICBkcmFnOiB7XG4gICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICBtYW51YWxTdGFydDogdHJ1ZSxcbiAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICBzbmFwOiBudWxsLFxuICAgICAgICByZXN0cmljdDogbnVsbCxcbiAgICAgICAgaW5lcnRpYTogbnVsbCxcbiAgICAgICAgYXV0b1Njcm9sbDogbnVsbCxcblxuICAgICAgICBheGlzOiAneHknLFxuICAgIH0sXG5cbiAgICBkcm9wOiB7XG4gICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICBhY2NlcHQ6IG51bGwsXG4gICAgICAgIG92ZXJsYXA6ICdwb2ludGVyJ1xuICAgIH0sXG5cbiAgICByZXNpemU6IHtcbiAgICAgICAgZW5hYmxlZDogZmFsc2UsXG4gICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICBzbmFwOiBudWxsLFxuICAgICAgICByZXN0cmljdDogbnVsbCxcbiAgICAgICAgaW5lcnRpYTogbnVsbCxcbiAgICAgICAgYXV0b1Njcm9sbDogbnVsbCxcblxuICAgICAgICBzcXVhcmU6IGZhbHNlLFxuICAgICAgICBheGlzOiAneHknLFxuXG4gICAgICAgIC8vIHVzZSBkZWZhdWx0IG1hcmdpblxuICAgICAgICBtYXJnaW46IE5hTixcblxuICAgICAgICAvLyBvYmplY3Qgd2l0aCBwcm9wcyBsZWZ0LCByaWdodCwgdG9wLCBib3R0b20gd2hpY2ggYXJlXG4gICAgICAgIC8vIHRydWUvZmFsc2UgdmFsdWVzIHRvIHJlc2l6ZSB3aGVuIHRoZSBwb2ludGVyIGlzIG92ZXIgdGhhdCBlZGdlLFxuICAgICAgICAvLyBDU1Mgc2VsZWN0b3JzIHRvIG1hdGNoIHRoZSBoYW5kbGVzIGZvciBlYWNoIGRpcmVjdGlvblxuICAgICAgICAvLyBvciB0aGUgRWxlbWVudHMgZm9yIGVhY2ggaGFuZGxlXG4gICAgICAgIGVkZ2VzOiBudWxsLFxuXG4gICAgICAgIC8vIGEgdmFsdWUgb2YgJ25vbmUnIHdpbGwgbGltaXQgdGhlIHJlc2l6ZSByZWN0IHRvIGEgbWluaW11bSBvZiAweDBcbiAgICAgICAgLy8gJ25lZ2F0ZScgd2lsbCBhbG93IHRoZSByZWN0IHRvIGhhdmUgbmVnYXRpdmUgd2lkdGgvaGVpZ2h0XG4gICAgICAgIC8vICdyZXBvc2l0aW9uJyB3aWxsIGtlZXAgdGhlIHdpZHRoL2hlaWdodCBwb3NpdGl2ZSBieSBzd2FwcGluZ1xuICAgICAgICAvLyB0aGUgdG9wIGFuZCBib3R0b20gZWRnZXMgYW5kL29yIHN3YXBwaW5nIHRoZSBsZWZ0IGFuZCByaWdodCBlZGdlc1xuICAgICAgICBpbnZlcnQ6ICdub25lJ1xuICAgIH0sXG5cbiAgICBnZXN0dXJlOiB7XG4gICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgZW5hYmxlZDogZmFsc2UsXG4gICAgICAgIG1heDogSW5maW5pdHksXG4gICAgICAgIG1heFBlckVsZW1lbnQ6IDEsXG5cbiAgICAgICAgcmVzdHJpY3Q6IG51bGxcbiAgICB9LFxuXG4gICAgcGVyQWN0aW9uOiB7XG4gICAgICAgIG1hbnVhbFN0YXJ0OiBmYWxzZSxcbiAgICAgICAgbWF4OiBJbmZpbml0eSxcbiAgICAgICAgbWF4UGVyRWxlbWVudDogMSxcblxuICAgICAgICBzbmFwOiB7XG4gICAgICAgICAgICBlbmFibGVkICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgZW5kT25seSAgICAgOiBmYWxzZSxcbiAgICAgICAgICAgIHJhbmdlICAgICAgIDogSW5maW5pdHksXG4gICAgICAgICAgICB0YXJnZXRzICAgICA6IG51bGwsXG4gICAgICAgICAgICBvZmZzZXRzICAgICA6IG51bGwsXG5cbiAgICAgICAgICAgIHJlbGF0aXZlUG9pbnRzOiBudWxsXG4gICAgICAgIH0sXG5cbiAgICAgICAgcmVzdHJpY3Q6IHtcbiAgICAgICAgICAgIGVuYWJsZWQ6IGZhbHNlLFxuICAgICAgICAgICAgZW5kT25seTogZmFsc2VcbiAgICAgICAgfSxcblxuICAgICAgICBhdXRvU2Nyb2xsOiB7XG4gICAgICAgICAgICBlbmFibGVkICAgICA6IGZhbHNlLFxuICAgICAgICAgICAgY29udGFpbmVyICAgOiBudWxsLCAgICAgLy8gdGhlIGl0ZW0gdGhhdCBpcyBzY3JvbGxlZCAoV2luZG93IG9yIEhUTUxFbGVtZW50KVxuICAgICAgICAgICAgbWFyZ2luICAgICAgOiA2MCxcbiAgICAgICAgICAgIHNwZWVkICAgICAgIDogMzAwICAgICAgIC8vIHRoZSBzY3JvbGwgc3BlZWQgaW4gcGl4ZWxzIHBlciBzZWNvbmRcbiAgICAgICAgfSxcblxuICAgICAgICBpbmVydGlhOiB7XG4gICAgICAgICAgICBlbmFibGVkICAgICAgICAgIDogZmFsc2UsXG4gICAgICAgICAgICByZXNpc3RhbmNlICAgICAgIDogMTAsICAgIC8vIHRoZSBsYW1iZGEgaW4gZXhwb25lbnRpYWwgZGVjYXlcbiAgICAgICAgICAgIG1pblNwZWVkICAgICAgICAgOiAxMDAsICAgLy8gdGFyZ2V0IHNwZWVkIG11c3QgYmUgYWJvdmUgdGhpcyBmb3IgaW5lcnRpYSB0byBzdGFydFxuICAgICAgICAgICAgZW5kU3BlZWQgICAgICAgICA6IDEwLCAgICAvLyB0aGUgc3BlZWQgYXQgd2hpY2ggaW5lcnRpYSBpcyBzbG93IGVub3VnaCB0byBzdG9wXG4gICAgICAgICAgICBhbGxvd1Jlc3VtZSAgICAgIDogdHJ1ZSwgIC8vIGFsbG93IHJlc3VtaW5nIGFuIGFjdGlvbiBpbiBpbmVydGlhIHBoYXNlXG4gICAgICAgICAgICB6ZXJvUmVzdW1lRGVsdGEgIDogdHJ1ZSwgIC8vIGlmIGFuIGFjdGlvbiBpcyByZXN1bWVkIGFmdGVyIGxhdW5jaCwgc2V0IGR4L2R5IHRvIDBcbiAgICAgICAgICAgIHNtb290aEVuZER1cmF0aW9uOiAzMDAgICAgLy8gYW5pbWF0ZSB0byBzbmFwL3Jlc3RyaWN0IGVuZE9ubHkgaWYgdGhlcmUncyBubyBpbmVydGlhXG4gICAgICAgIH1cbiAgICB9LFxuXG4gICAgX2hvbGREdXJhdGlvbjogNjAwXG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgc2NvcGUgPSB7fSxcbiAgICBleHRlbmQgPSByZXF1aXJlKCcuL3V0aWxzL2V4dGVuZCcpO1xuXG5leHRlbmQoc2NvcGUsIHJlcXVpcmUoJy4vdXRpbHMvd2luZG93JykpO1xuZXh0ZW5kKHNjb3BlLCByZXF1aXJlKCcuL3V0aWxzL2RvbU9iamVjdHMnKSk7XG5leHRlbmQoc2NvcGUsIHJlcXVpcmUoJy4vdXRpbHMvYXJyLmpzJykpO1xuZXh0ZW5kKHNjb3BlLCByZXF1aXJlKCcuL3V0aWxzL2lzVHlwZScpKTtcblxubW9kdWxlLmV4cG9ydHMgPSBzY29wZTtcbiIsIid1c2Ugc3RyaWN0JztcblxuZnVuY3Rpb24gaW5kZXhPZiAoYXJyYXksIHRhcmdldCkge1xuICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBhcnJheS5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBpZiAoYXJyYXlbaV0gPT09IHRhcmdldCkge1xuICAgICAgICAgICAgcmV0dXJuIGk7XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICByZXR1cm4gLTE7XG59XG5cbmZ1bmN0aW9uIGNvbnRhaW5zIChhcnJheSwgdGFyZ2V0KSB7XG4gICAgcmV0dXJuIGluZGV4T2YoYXJyYXksIHRhcmdldCkgIT09IC0xO1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHtcbiAgICBpbmRleE9mOiBpbmRleE9mLFxuICAgIGNvbnRhaW5zOiBjb250YWluc1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxudmFyIHdpbiA9IHJlcXVpcmUoJy4vd2luZG93JyksXG4gICAgZG9tT2JqZWN0cyA9IHJlcXVpcmUoJy4vZG9tT2JqZWN0cycpO1xuXG52YXIgYnJvd3NlciA9IHtcbiAgICAvLyBEb2VzIHRoZSBicm93c2VyIHN1cHBvcnQgdG91Y2ggaW5wdXQ/XG4gICAgc3VwcG9ydHNUb3VjaCA6ICEhKCgnb250b3VjaHN0YXJ0JyBpbiB3aW4pIHx8IHdpbi53aW5kb3cuRG9jdW1lbnRUb3VjaFxuICAgICAgICAmJiBkb21PYmplY3RzLmRvY3VtZW50IGluc3RhbmNlb2Ygd2luLkRvY3VtZW50VG91Y2gpLFxuXG4gICAgLy8gRG9lcyB0aGUgYnJvd3NlciBzdXBwb3J0IFBvaW50ZXJFdmVudHNcbiAgICBzdXBwb3J0c1BvaW50ZXJFdmVudCA6ICEhZG9tT2JqZWN0cy5Qb2ludGVyRXZlbnQsXG5cbiAgICAvLyBPcGVyYSBNb2JpbGUgbXVzdCBiZSBoYW5kbGVkIGRpZmZlcmVudGx5XG4gICAgaXNPcGVyYU1vYmlsZSA6IChuYXZpZ2F0b3IuYXBwTmFtZSA9PT0gJ09wZXJhJ1xuICAgICAgICAmJiBicm93c2VyLnN1cHBvcnRzVG91Y2hcbiAgICAgICAgJiYgbmF2aWdhdG9yLnVzZXJBZ2VudC5tYXRjaCgnUHJlc3RvJykpLFxuXG4gICAgLy8gc2Nyb2xsaW5nIGRvZXNuJ3QgY2hhbmdlIHRoZSByZXN1bHQgb2ZcbiAgICAvLyBnZXRCb3VuZGluZ0NsaWVudFJlY3QvZ2V0Q2xpZW50UmVjdHMgb24gaU9TIDw9NyBidXQgaXQgZG9lcyBvbiBpT1MgOFxuICAgIGlzSU9TN29yTG93ZXIgOiAoL2lQKGhvbmV8b2R8YWQpLy50ZXN0KG5hdmlnYXRvci5wbGF0Zm9ybSkgJiYgL09TIFsxLTddW15cXGRdLy50ZXN0KG5hdmlnYXRvci5hcHBWZXJzaW9uKSksXG5cbiAgICBpc0llOU9yT2xkZXIgOiBkb21PYmplY3RzLmRvY3VtZW50LmFsbCAmJiAhd2luLndpbmRvdy5hdG9iXG59O1xuXG5tb2R1bGUuZXhwb3J0cyA9IGJyb3dzZXI7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciBkb21PYmplY3RzID0ge30sXG4gICAgd2luID0gcmVxdWlyZSgnLi93aW5kb3cnKS53aW5kb3csXG4gICAgYmxhbmsgPSBmdW5jdGlvbiAoKSB7fTtcblxuZG9tT2JqZWN0cy5kb2N1bWVudCAgICAgICAgICAgPSB3aW4uZG9jdW1lbnQ7XG5kb21PYmplY3RzLkRvY3VtZW50RnJhZ21lbnQgICA9IHdpbi5Eb2N1bWVudEZyYWdtZW50ICAgfHwgYmxhbms7XG5kb21PYmplY3RzLlNWR0VsZW1lbnQgICAgICAgICA9IHdpbi5TVkdFbGVtZW50ICAgICAgICAgfHwgYmxhbms7XG5kb21PYmplY3RzLlNWR1NWR0VsZW1lbnQgICAgICA9IHdpbi5TVkdTVkdFbGVtZW50ICAgICAgfHwgYmxhbms7XG5kb21PYmplY3RzLlNWR0VsZW1lbnRJbnN0YW5jZSA9IHdpbi5TVkdFbGVtZW50SW5zdGFuY2UgfHwgYmxhbms7XG5kb21PYmplY3RzLkhUTUxFbGVtZW50ICAgICAgICA9IHdpbi5IVE1MRWxlbWVudCAgICAgICAgfHwgd2luLkVsZW1lbnQ7XG5cbmRvbU9iamVjdHMuUG9pbnRlckV2ZW50ID0gKHdpbi5Qb2ludGVyRXZlbnQgfHwgd2luLk1TUG9pbnRlckV2ZW50KTtcblxubW9kdWxlLmV4cG9ydHMgPSBkb21PYmplY3RzO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgYXJyID0gcmVxdWlyZSgnLi9hcnInKSxcbiAgICBpbmRleE9mICA9IGFyci5pbmRleE9mLFxuICAgIGNvbnRhaW5zID0gYXJyLmNvbnRhaW5zLFxuICAgIGdldFdpbmRvdyA9IHJlcXVpcmUoJy4vd2luZG93JykuZ2V0V2luZG93LFxuXG4gICAgdXNlQXR0YWNoRXZlbnQgPSAoJ2F0dGFjaEV2ZW50JyBpbiB3aW5kb3cpICYmICEoJ2FkZEV2ZW50TGlzdGVuZXInIGluIHdpbmRvdyksXG4gICAgYWRkRXZlbnQgICAgICAgPSB1c2VBdHRhY2hFdmVudD8gICdhdHRhY2hFdmVudCc6ICdhZGRFdmVudExpc3RlbmVyJyxcbiAgICByZW1vdmVFdmVudCAgICA9IHVzZUF0dGFjaEV2ZW50PyAgJ2RldGFjaEV2ZW50JzogJ3JlbW92ZUV2ZW50TGlzdGVuZXInLFxuICAgIG9uICAgICAgICAgICAgID0gdXNlQXR0YWNoRXZlbnQ/ICdvbic6ICcnLFxuXG4gICAgZWxlbWVudHMgICAgICAgICAgPSBbXSxcbiAgICB0YXJnZXRzICAgICAgICAgICA9IFtdLFxuICAgIGF0dGFjaGVkTGlzdGVuZXJzID0gW107XG5cbmZ1bmN0aW9uIGFkZCAoZWxlbWVudCwgdHlwZSwgbGlzdGVuZXIsIHVzZUNhcHR1cmUpIHtcbiAgICB2YXIgZWxlbWVudEluZGV4ID0gaW5kZXhPZihlbGVtZW50cywgZWxlbWVudCksXG4gICAgICAgIHRhcmdldCA9IHRhcmdldHNbZWxlbWVudEluZGV4XTtcblxuICAgIGlmICghdGFyZ2V0KSB7XG4gICAgICAgIHRhcmdldCA9IHtcbiAgICAgICAgICAgIGV2ZW50czoge30sXG4gICAgICAgICAgICB0eXBlQ291bnQ6IDBcbiAgICAgICAgfTtcblxuICAgICAgICBlbGVtZW50SW5kZXggPSBlbGVtZW50cy5wdXNoKGVsZW1lbnQpIC0gMTtcbiAgICAgICAgdGFyZ2V0cy5wdXNoKHRhcmdldCk7XG5cbiAgICAgICAgYXR0YWNoZWRMaXN0ZW5lcnMucHVzaCgodXNlQXR0YWNoRXZlbnQgPyB7XG4gICAgICAgICAgICAgICAgc3VwcGxpZWQ6IFtdLFxuICAgICAgICAgICAgICAgIHdyYXBwZWQgOiBbXSxcbiAgICAgICAgICAgICAgICB1c2VDb3VudDogW11cbiAgICAgICAgICAgIH0gOiBudWxsKSk7XG4gICAgfVxuXG4gICAgaWYgKCF0YXJnZXQuZXZlbnRzW3R5cGVdKSB7XG4gICAgICAgIHRhcmdldC5ldmVudHNbdHlwZV0gPSBbXTtcbiAgICAgICAgdGFyZ2V0LnR5cGVDb3VudCsrO1xuICAgIH1cblxuICAgIGlmICghY29udGFpbnModGFyZ2V0LmV2ZW50c1t0eXBlXSwgbGlzdGVuZXIpKSB7XG4gICAgICAgIHZhciByZXQ7XG5cbiAgICAgICAgaWYgKHVzZUF0dGFjaEV2ZW50KSB7XG4gICAgICAgICAgICB2YXIgbGlzdGVuZXJzID0gYXR0YWNoZWRMaXN0ZW5lcnNbZWxlbWVudEluZGV4XSxcbiAgICAgICAgICAgICAgICBsaXN0ZW5lckluZGV4ID0gaW5kZXhPZihsaXN0ZW5lcnMuc3VwcGxpZWQsIGxpc3RlbmVyKTtcblxuICAgICAgICAgICAgdmFyIHdyYXBwZWQgPSBsaXN0ZW5lcnMud3JhcHBlZFtsaXN0ZW5lckluZGV4XSB8fCBmdW5jdGlvbiAoZXZlbnQpIHtcbiAgICAgICAgICAgICAgICBpZiAoIWV2ZW50LmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZCkge1xuICAgICAgICAgICAgICAgICAgICBldmVudC50YXJnZXQgPSBldmVudC5zcmNFbGVtZW50O1xuICAgICAgICAgICAgICAgICAgICBldmVudC5jdXJyZW50VGFyZ2V0ID0gZWxlbWVudDtcblxuICAgICAgICAgICAgICAgICAgICBldmVudC5wcmV2ZW50RGVmYXVsdCA9IGV2ZW50LnByZXZlbnREZWZhdWx0IHx8IHByZXZlbnREZWY7XG4gICAgICAgICAgICAgICAgICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbiA9IGV2ZW50LnN0b3BQcm9wYWdhdGlvbiB8fCBzdG9wUHJvcDtcbiAgICAgICAgICAgICAgICAgICAgZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uID0gZXZlbnQuc3RvcEltbWVkaWF0ZVByb3BhZ2F0aW9uIHx8IHN0b3BJbW1Qcm9wO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmICgvbW91c2V8Y2xpY2svLnRlc3QoZXZlbnQudHlwZSkpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50LnBhZ2VYID0gZXZlbnQuY2xpZW50WCArIGdldFdpbmRvdyhlbGVtZW50KS5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsTGVmdDtcbiAgICAgICAgICAgICAgICAgICAgICAgIGV2ZW50LnBhZ2VZID0gZXZlbnQuY2xpZW50WSArIGdldFdpbmRvdyhlbGVtZW50KS5kb2N1bWVudC5kb2N1bWVudEVsZW1lbnQuc2Nyb2xsVG9wO1xuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgbGlzdGVuZXIoZXZlbnQpO1xuICAgICAgICAgICAgICAgIH1cbiAgICAgICAgICAgIH07XG5cbiAgICAgICAgICAgIHJldCA9IGVsZW1lbnRbYWRkRXZlbnRdKG9uICsgdHlwZSwgd3JhcHBlZCwgQm9vbGVhbih1c2VDYXB0dXJlKSk7XG5cbiAgICAgICAgICAgIGlmIChsaXN0ZW5lckluZGV4ID09PSAtMSkge1xuICAgICAgICAgICAgICAgIGxpc3RlbmVycy5zdXBwbGllZC5wdXNoKGxpc3RlbmVyKTtcbiAgICAgICAgICAgICAgICBsaXN0ZW5lcnMud3JhcHBlZC5wdXNoKHdyYXBwZWQpO1xuICAgICAgICAgICAgICAgIGxpc3RlbmVycy51c2VDb3VudC5wdXNoKDEpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICAgICAgbGlzdGVuZXJzLnVzZUNvdW50W2xpc3RlbmVySW5kZXhdKys7XG4gICAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICByZXQgPSBlbGVtZW50W2FkZEV2ZW50XSh0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSB8fCBmYWxzZSk7XG4gICAgICAgIH1cbiAgICAgICAgdGFyZ2V0LmV2ZW50c1t0eXBlXS5wdXNoKGxpc3RlbmVyKTtcblxuICAgICAgICByZXR1cm4gcmV0O1xuICAgIH1cbn1cblxuZnVuY3Rpb24gcmVtb3ZlIChlbGVtZW50LCB0eXBlLCBsaXN0ZW5lciwgdXNlQ2FwdHVyZSkge1xuICAgIHZhciBpLFxuICAgICAgICBlbGVtZW50SW5kZXggPSBpbmRleE9mKGVsZW1lbnRzLCBlbGVtZW50KSxcbiAgICAgICAgdGFyZ2V0ID0gdGFyZ2V0c1tlbGVtZW50SW5kZXhdLFxuICAgICAgICBsaXN0ZW5lcnMsXG4gICAgICAgIGxpc3RlbmVySW5kZXgsXG4gICAgICAgIHdyYXBwZWQgPSBsaXN0ZW5lcjtcblxuICAgIGlmICghdGFyZ2V0IHx8ICF0YXJnZXQuZXZlbnRzKSB7XG4gICAgICAgIHJldHVybjtcbiAgICB9XG5cbiAgICBpZiAodXNlQXR0YWNoRXZlbnQpIHtcbiAgICAgICAgbGlzdGVuZXJzID0gYXR0YWNoZWRMaXN0ZW5lcnNbZWxlbWVudEluZGV4XTtcbiAgICAgICAgbGlzdGVuZXJJbmRleCA9IGluZGV4T2YobGlzdGVuZXJzLnN1cHBsaWVkLCBsaXN0ZW5lcik7XG4gICAgICAgIHdyYXBwZWQgPSBsaXN0ZW5lcnMud3JhcHBlZFtsaXN0ZW5lckluZGV4XTtcbiAgICB9XG5cbiAgICBpZiAodHlwZSA9PT0gJ2FsbCcpIHtcbiAgICAgICAgZm9yICh0eXBlIGluIHRhcmdldC5ldmVudHMpIHtcbiAgICAgICAgICAgIGlmICh0YXJnZXQuZXZlbnRzLmhhc093blByb3BlcnR5KHR5cGUpKSB7XG4gICAgICAgICAgICAgICAgcmVtb3ZlKGVsZW1lbnQsIHR5cGUsICdhbGwnKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICByZXR1cm47XG4gICAgfVxuXG4gICAgaWYgKHRhcmdldC5ldmVudHNbdHlwZV0pIHtcbiAgICAgICAgdmFyIGxlbiA9IHRhcmdldC5ldmVudHNbdHlwZV0ubGVuZ3RoO1xuXG4gICAgICAgIGlmIChsaXN0ZW5lciA9PT0gJ2FsbCcpIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIHJlbW92ZShlbGVtZW50LCB0eXBlLCB0YXJnZXQuZXZlbnRzW3R5cGVdW2ldLCBCb29sZWFuKHVzZUNhcHR1cmUpKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIGZvciAoaSA9IDA7IGkgPCBsZW47IGkrKykge1xuICAgICAgICAgICAgICAgIGlmICh0YXJnZXQuZXZlbnRzW3R5cGVdW2ldID09PSBsaXN0ZW5lcikge1xuICAgICAgICAgICAgICAgICAgICBlbGVtZW50W3JlbW92ZUV2ZW50XShvbiArIHR5cGUsIHdyYXBwZWQsIHVzZUNhcHR1cmUgfHwgZmFsc2UpO1xuICAgICAgICAgICAgICAgICAgICB0YXJnZXQuZXZlbnRzW3R5cGVdLnNwbGljZShpLCAxKTtcblxuICAgICAgICAgICAgICAgICAgICBpZiAodXNlQXR0YWNoRXZlbnQgJiYgbGlzdGVuZXJzKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0tLTtcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIChsaXN0ZW5lcnMudXNlQ291bnRbbGlzdGVuZXJJbmRleF0gPT09IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMuc3VwcGxpZWQuc3BsaWNlKGxpc3RlbmVySW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3RlbmVycy53cmFwcGVkLnNwbGljZShsaXN0ZW5lckluZGV4LCAxKTtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0ZW5lcnMudXNlQ291bnQuc3BsaWNlKGxpc3RlbmVySW5kZXgsIDEpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKHRhcmdldC5ldmVudHNbdHlwZV0gJiYgdGFyZ2V0LmV2ZW50c1t0eXBlXS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICAgIHRhcmdldC5ldmVudHNbdHlwZV0gPSBudWxsO1xuICAgICAgICAgICAgdGFyZ2V0LnR5cGVDb3VudC0tO1xuICAgICAgICB9XG4gICAgfVxuXG4gICAgaWYgKCF0YXJnZXQudHlwZUNvdW50KSB7XG4gICAgICAgIHRhcmdldHMuc3BsaWNlKGVsZW1lbnRJbmRleCwgMSk7XG4gICAgICAgIGVsZW1lbnRzLnNwbGljZShlbGVtZW50SW5kZXgsIDEpO1xuICAgICAgICBhdHRhY2hlZExpc3RlbmVycy5zcGxpY2UoZWxlbWVudEluZGV4LCAxKTtcbiAgICB9XG59XG5cbmZ1bmN0aW9uIHByZXZlbnREZWYgKCkge1xuICAgIHRoaXMucmV0dXJuVmFsdWUgPSBmYWxzZTtcbn1cblxuZnVuY3Rpb24gc3RvcFByb3AgKCkge1xuICAgIHRoaXMuY2FuY2VsQnViYmxlID0gdHJ1ZTtcbn1cblxuZnVuY3Rpb24gc3RvcEltbVByb3AgKCkge1xuICAgIHRoaXMuY2FuY2VsQnViYmxlID0gdHJ1ZTtcbiAgICB0aGlzLmltbWVkaWF0ZVByb3BhZ2F0aW9uU3RvcHBlZCA9IHRydWU7XG59XG5cbm1vZHVsZS5leHBvcnRzID0ge1xuICAgIGFkZDogYWRkLFxuICAgIHJlbW92ZTogcmVtb3ZlLFxuICAgIHVzZUF0dGFjaEV2ZW50OiB1c2VBdHRhY2hFdmVudCxcblxuICAgIF9lbGVtZW50czogZWxlbWVudHMsXG4gICAgX3RhcmdldHM6IHRhcmdldHMsXG4gICAgX2F0dGFjaGVkTGlzdGVuZXJzOiBhdHRhY2hlZExpc3RlbmVyc1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBleHRlbmQgKGRlc3QsIHNvdXJjZSkge1xuICAgIGZvciAodmFyIHByb3AgaW4gc291cmNlKSB7XG4gICAgICAgIGRlc3RbcHJvcF0gPSBzb3VyY2VbcHJvcF07XG4gICAgfVxuICAgIHJldHVybiBkZXN0O1xufTtcbiIsIid1c2Ugc3RyaWN0JztcblxubW9kdWxlLmV4cG9ydHMgPSBmdW5jdGlvbiBoeXBvdCAoeCwgeSkgeyByZXR1cm4gTWF0aC5zcXJ0KHggKiB4ICsgeSAqIHkpOyB9O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgdXRpbHMgPSB7fSxcbiAgICBleHRlbmQgPSByZXF1aXJlKCcuL2V4dGVuZCcpLFxuICAgIHdpbiA9IHJlcXVpcmUoJy4vd2luZG93Jyk7XG5cbnV0aWxzLmJsYW5rICA9IGZ1bmN0aW9uICgpIHt9O1xuXG51dGlscy53YXJuT25jZSA9IGZ1bmN0aW9uIChtZXRob2QsIG1lc3NhZ2UpIHtcbiAgICB2YXIgd2FybmVkID0gZmFsc2U7XG5cbiAgICByZXR1cm4gZnVuY3Rpb24gKCkge1xuICAgICAgICBpZiAoIXdhcm5lZCkge1xuICAgICAgICAgICAgd2luLndpbmRvdy5jb25zb2xlLndhcm4obWVzc2FnZSk7XG4gICAgICAgICAgICB3YXJuZWQgPSB0cnVlO1xuICAgICAgICB9XG5cbiAgICAgICAgcmV0dXJuIG1ldGhvZC5hcHBseSh0aGlzLCBhcmd1bWVudHMpO1xuICAgIH07XG59O1xuXG51dGlscy5leHRlbmQgID0gZXh0ZW5kO1xudXRpbHMuaHlwb3QgICA9IHJlcXVpcmUoJy4vaHlwb3QnKTtcbnV0aWxzLnJhZiAgICAgPSByZXF1aXJlKCcuL3JhZicpO1xudXRpbHMuYnJvd3NlciA9IHJlcXVpcmUoJy4vYnJvd3NlcicpO1xuXG5leHRlbmQodXRpbHMsIHJlcXVpcmUoJy4vYXJyJykpO1xuZXh0ZW5kKHV0aWxzLCByZXF1aXJlKCcuL2lzVHlwZScpKTtcbmV4dGVuZCh1dGlscywgcmVxdWlyZSgnLi9wb2ludGVyVXRpbHMnKSk7XG5cbm1vZHVsZS5leHBvcnRzID0gdXRpbHM7XG4iLCIndXNlIHN0cmljdCc7XG5cbnZhciB3aW4gPSByZXF1aXJlKCcuL3dpbmRvdycpLFxuICAgIGRvbU9iamVjdHMgPSByZXF1aXJlKCcuL2RvbU9iamVjdHMnKTtcblxubW9kdWxlLmV4cG9ydHMuaXNFbGVtZW50ID0gZnVuY3Rpb24gKG8pIHtcbiAgICBpZiAoIW8gfHwgKHR5cGVvZiBvICE9PSAnb2JqZWN0JykpIHsgcmV0dXJuIGZhbHNlOyB9XG5cbiAgICB2YXIgX3dpbmRvdyA9IHdpbi5nZXRXaW5kb3cobykgfHwgd2luLndpbmRvdztcblxuICAgIHJldHVybiAoL29iamVjdHxmdW5jdGlvbi8udGVzdCh0eXBlb2YgX3dpbmRvdy5FbGVtZW50KVxuICAgICAgICA/IG8gaW5zdGFuY2VvZiBfd2luZG93LkVsZW1lbnQgLy9ET00yXG4gICAgICAgIDogby5ub2RlVHlwZSA9PT0gMSAmJiB0eXBlb2Ygby5ub2RlTmFtZSA9PT0gXCJzdHJpbmdcIik7XG59O1xuXG5tb2R1bGUuZXhwb3J0cy5pc1dpbmRvdyAgID0gZnVuY3Rpb24gKHRoaW5nKSB7IHJldHVybiAhISh0aGluZyAmJiB0aGluZy5XaW5kb3cpICYmICh0aGluZyBpbnN0YW5jZW9mIHRoaW5nLldpbmRvdyk7IH07XG5tb2R1bGUuZXhwb3J0cy5pc0RvY0ZyYWcgID0gZnVuY3Rpb24gKHRoaW5nKSB7IHJldHVybiAhIXRoaW5nICYmIHRoaW5nIGluc3RhbmNlb2YgZG9tT2JqZWN0cy5Eb2N1bWVudEZyYWdtZW50OyB9O1xubW9kdWxlLmV4cG9ydHMuaXNBcnJheSAgICA9IGZ1bmN0aW9uICh0aGluZykge1xuICAgIHJldHVybiBtb2R1bGUuZXhwb3J0cy5pc09iamVjdCh0aGluZylcbiAgICAmJiAodHlwZW9mIHRoaW5nLmxlbmd0aCAhPT0gdW5kZWZpbmVkKVxuICAgICYmIG1vZHVsZS5leHBvcnRzLmlzRnVuY3Rpb24odGhpbmcuc3BsaWNlKTtcbn07XG5tb2R1bGUuZXhwb3J0cy5pc09iamVjdCAgID0gZnVuY3Rpb24gKHRoaW5nKSB7IHJldHVybiAhIXRoaW5nICYmICh0eXBlb2YgdGhpbmcgPT09ICdvYmplY3QnKTsgfTtcbm1vZHVsZS5leHBvcnRzLmlzRnVuY3Rpb24gPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ2Z1bmN0aW9uJzsgfTtcbm1vZHVsZS5leHBvcnRzLmlzTnVtYmVyICAgPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ251bWJlcicgIDsgfTtcbm1vZHVsZS5leHBvcnRzLmlzQm9vbCAgICAgPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ2Jvb2xlYW4nIDsgfTtcbm1vZHVsZS5leHBvcnRzLmlzU3RyaW5nICAgPSBmdW5jdGlvbiAodGhpbmcpIHsgcmV0dXJuIHR5cGVvZiB0aGluZyA9PT0gJ3N0cmluZycgIDsgfTtcblxuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgcG9pbnRlclV0aWxzID0ge30sXG4gICAgLy8gcmVkdWNlIG9iamVjdCBjcmVhdGlvbiBpbiBnZXRYWSgpXG4gICAgdG1wWFkgPSB7fSxcbiAgICB3aW4gPSByZXF1aXJlKCcuL3dpbmRvdycpLFxuICAgIGh5cG90ID0gcmVxdWlyZSgnLi9oeXBvdCcpLFxuICAgIGV4dGVuZCA9IHJlcXVpcmUoJy4vZXh0ZW5kJyksXG5cbiAgICAvLyBzY29wZSBzaG91bGRuJ3QgYmUgbmVjZXNzYXJ5IGluIHRoaXMgbW9kdWxlXG4gICAgc2NvcGUgPSByZXF1aXJlKCcuLi9zY29wZScpO1xuXG5wb2ludGVyVXRpbHMuY29weUNvb3JkcyA9IGZ1bmN0aW9uIChkZXN0LCBzcmMpIHtcbiAgICBkZXN0LnBhZ2UgPSBkZXN0LnBhZ2UgfHwge307XG4gICAgZGVzdC5wYWdlLnggPSBzcmMucGFnZS54O1xuICAgIGRlc3QucGFnZS55ID0gc3JjLnBhZ2UueTtcblxuICAgIGRlc3QuY2xpZW50ID0gZGVzdC5jbGllbnQgfHwge307XG4gICAgZGVzdC5jbGllbnQueCA9IHNyYy5jbGllbnQueDtcbiAgICBkZXN0LmNsaWVudC55ID0gc3JjLmNsaWVudC55O1xuXG4gICAgZGVzdC50aW1lU3RhbXAgPSBzcmMudGltZVN0YW1wO1xufTtcblxucG9pbnRlclV0aWxzLnNldEV2ZW50WFkgPSBmdW5jdGlvbiAodGFyZ2V0T2JqLCBwb2ludGVyLCBpbnRlcmFjdGlvbikge1xuICAgIGlmICghcG9pbnRlcikge1xuICAgICAgICBpZiAoaW50ZXJhY3Rpb24ucG9pbnRlcklkcy5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICBwb2ludGVyID0gcG9pbnRlclV0aWxzLnRvdWNoQXZlcmFnZShpbnRlcmFjdGlvbi5wb2ludGVycyk7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBwb2ludGVyID0gaW50ZXJhY3Rpb24ucG9pbnRlcnNbMF07XG4gICAgICAgIH1cbiAgICB9XG5cbiAgICBwb2ludGVyVXRpbHMuZ2V0UGFnZVhZKHBvaW50ZXIsIHRtcFhZLCBpbnRlcmFjdGlvbik7XG4gICAgdGFyZ2V0T2JqLnBhZ2UueCA9IHRtcFhZLng7XG4gICAgdGFyZ2V0T2JqLnBhZ2UueSA9IHRtcFhZLnk7XG5cbiAgICBwb2ludGVyVXRpbHMuZ2V0Q2xpZW50WFkocG9pbnRlciwgdG1wWFksIGludGVyYWN0aW9uKTtcbiAgICB0YXJnZXRPYmouY2xpZW50LnggPSB0bXBYWS54O1xuICAgIHRhcmdldE9iai5jbGllbnQueSA9IHRtcFhZLnk7XG5cbiAgICB0YXJnZXRPYmoudGltZVN0YW1wID0gbmV3IERhdGUoKS5nZXRUaW1lKCk7XG59O1xuXG5wb2ludGVyVXRpbHMuc2V0RXZlbnREZWx0YXMgPSBmdW5jdGlvbiAodGFyZ2V0T2JqLCBwcmV2LCBjdXIpIHtcbiAgICB0YXJnZXRPYmoucGFnZS54ICAgICA9IGN1ci5wYWdlLnggICAgICAtIHByZXYucGFnZS54O1xuICAgIHRhcmdldE9iai5wYWdlLnkgICAgID0gY3VyLnBhZ2UueSAgICAgIC0gcHJldi5wYWdlLnk7XG4gICAgdGFyZ2V0T2JqLmNsaWVudC54ICAgPSBjdXIuY2xpZW50LnggICAgLSBwcmV2LmNsaWVudC54O1xuICAgIHRhcmdldE9iai5jbGllbnQueSAgID0gY3VyLmNsaWVudC55ICAgIC0gcHJldi5jbGllbnQueTtcbiAgICB0YXJnZXRPYmoudGltZVN0YW1wID0gbmV3IERhdGUoKS5nZXRUaW1lKCkgLSBwcmV2LnRpbWVTdGFtcDtcblxuICAgIC8vIHNldCBwb2ludGVyIHZlbG9jaXR5XG4gICAgdmFyIGR0ID0gTWF0aC5tYXgodGFyZ2V0T2JqLnRpbWVTdGFtcCAvIDEwMDAsIDAuMDAxKTtcbiAgICB0YXJnZXRPYmoucGFnZS5zcGVlZCAgID0gaHlwb3QodGFyZ2V0T2JqLnBhZ2UueCwgdGFyZ2V0T2JqLnBhZ2UueSkgLyBkdDtcbiAgICB0YXJnZXRPYmoucGFnZS52eCAgICAgID0gdGFyZ2V0T2JqLnBhZ2UueCAvIGR0O1xuICAgIHRhcmdldE9iai5wYWdlLnZ5ICAgICAgPSB0YXJnZXRPYmoucGFnZS55IC8gZHQ7XG5cbiAgICB0YXJnZXRPYmouY2xpZW50LnNwZWVkID0gaHlwb3QodGFyZ2V0T2JqLmNsaWVudC54LCB0YXJnZXRPYmoucGFnZS55KSAvIGR0O1xuICAgIHRhcmdldE9iai5jbGllbnQudnggICAgPSB0YXJnZXRPYmouY2xpZW50LnggLyBkdDtcbiAgICB0YXJnZXRPYmouY2xpZW50LnZ5ICAgID0gdGFyZ2V0T2JqLmNsaWVudC55IC8gZHQ7XG59O1xuXG4vLyBHZXQgc3BlY2lmaWVkIFgvWSBjb29yZHMgZm9yIG1vdXNlIG9yIGV2ZW50LnRvdWNoZXNbMF1cbnBvaW50ZXJVdGlscy5nZXRYWSA9IGZ1bmN0aW9uICh0eXBlLCBwb2ludGVyLCB4eSkge1xuICAgIHh5ID0geHkgfHwge307XG4gICAgdHlwZSA9IHR5cGUgfHwgJ3BhZ2UnO1xuXG4gICAgeHkueCA9IHBvaW50ZXJbdHlwZSArICdYJ107XG4gICAgeHkueSA9IHBvaW50ZXJbdHlwZSArICdZJ107XG5cbiAgICByZXR1cm4geHk7XG59O1xuXG5wb2ludGVyVXRpbHMuZ2V0UGFnZVhZID0gZnVuY3Rpb24gKHBvaW50ZXIsIHBhZ2UsIGludGVyYWN0aW9uKSB7XG4gICAgcGFnZSA9IHBhZ2UgfHwge307XG5cbiAgICBpZiAocG9pbnRlciBpbnN0YW5jZW9mIHNjb3BlLkludGVyYWN0RXZlbnQpIHtcbiAgICAgICAgaWYgKC9pbmVydGlhc3RhcnQvLnRlc3QocG9pbnRlci50eXBlKSkge1xuICAgICAgICAgICAgaW50ZXJhY3Rpb24gPSBpbnRlcmFjdGlvbiB8fCBwb2ludGVyLmludGVyYWN0aW9uO1xuXG4gICAgICAgICAgICBleHRlbmQocGFnZSwgaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy51cENvb3Jkcy5wYWdlKTtcblxuICAgICAgICAgICAgcGFnZS54ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3g7XG4gICAgICAgICAgICBwYWdlLnkgKz0gaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy5zeTtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICAgIHBhZ2UueCA9IHBvaW50ZXIucGFnZVg7XG4gICAgICAgICAgICBwYWdlLnkgPSBwb2ludGVyLnBhZ2VZO1xuICAgICAgICB9XG4gICAgfVxuICAgIC8vIE9wZXJhIE1vYmlsZSBoYW5kbGVzIHRoZSB2aWV3cG9ydCBhbmQgc2Nyb2xsaW5nIG9kZGx5XG4gICAgZWxzZSBpZiAoc2NvcGUuaXNPcGVyYU1vYmlsZSkge1xuICAgICAgICBwb2ludGVyVXRpbHMuZ2V0WFkoJ3NjcmVlbicsIHBvaW50ZXIsIHBhZ2UpO1xuXG4gICAgICAgIHBhZ2UueCArPSB3aW4ud2luZG93LnNjcm9sbFg7XG4gICAgICAgIHBhZ2UueSArPSB3aW4ud2luZG93LnNjcm9sbFk7XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICBwb2ludGVyVXRpbHMuZ2V0WFkoJ3BhZ2UnLCBwb2ludGVyLCBwYWdlKTtcbiAgICB9XG5cbiAgICByZXR1cm4gcGFnZTtcbn07XG5cbnBvaW50ZXJVdGlscy5nZXRDbGllbnRYWSA9IGZ1bmN0aW9uIChwb2ludGVyLCBjbGllbnQsIGludGVyYWN0aW9uKSB7XG4gICAgY2xpZW50ID0gY2xpZW50IHx8IHt9O1xuXG4gICAgaWYgKHBvaW50ZXIgaW5zdGFuY2VvZiBzY29wZS5JbnRlcmFjdEV2ZW50KSB7XG4gICAgICAgIGlmICgvaW5lcnRpYXN0YXJ0Ly50ZXN0KHBvaW50ZXIudHlwZSkpIHtcbiAgICAgICAgICAgIGV4dGVuZChjbGllbnQsIGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMudXBDb29yZHMuY2xpZW50KTtcblxuICAgICAgICAgICAgY2xpZW50LnggKz0gaW50ZXJhY3Rpb24uaW5lcnRpYVN0YXR1cy5zeDtcbiAgICAgICAgICAgIGNsaWVudC55ICs9IGludGVyYWN0aW9uLmluZXJ0aWFTdGF0dXMuc3k7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgICBjbGllbnQueCA9IHBvaW50ZXIuY2xpZW50WDtcbiAgICAgICAgICAgIGNsaWVudC55ID0gcG9pbnRlci5jbGllbnRZO1xuICAgICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgICAvLyBPcGVyYSBNb2JpbGUgaGFuZGxlcyB0aGUgdmlld3BvcnQgYW5kIHNjcm9sbGluZyBvZGRseVxuICAgICAgICBwb2ludGVyVXRpbHMuZ2V0WFkoc2NvcGUuaXNPcGVyYU1vYmlsZT8gJ3NjcmVlbic6ICdjbGllbnQnLCBwb2ludGVyLCBjbGllbnQpO1xuICAgIH1cblxuICAgIHJldHVybiBjbGllbnQ7XG59O1xuXG5wb2ludGVyVXRpbHMuZ2V0UG9pbnRlcklkID0gZnVuY3Rpb24gKHBvaW50ZXIpIHtcbiAgICByZXR1cm4gc2NvcGUuaXNOdW1iZXIocG9pbnRlci5wb2ludGVySWQpPyBwb2ludGVyLnBvaW50ZXJJZCA6IHBvaW50ZXIuaWRlbnRpZmllcjtcbn07XG5cbm1vZHVsZS5leHBvcnRzID0gcG9pbnRlclV0aWxzO1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG52YXIgbGFzdFRpbWUgPSAwLFxuICAgIHZlbmRvcnMgPSBbJ21zJywgJ21veicsICd3ZWJraXQnLCAnbyddLFxuICAgIHJlcUZyYW1lLFxuICAgIGNhbmNlbEZyYW1lO1xuXG5mb3IodmFyIHggPSAwOyB4IDwgdmVuZG9ycy5sZW5ndGggJiYgIXdpbmRvdy5yZXF1ZXN0QW5pbWF0aW9uRnJhbWU7ICsreCkge1xuICAgIHJlcUZyYW1lID0gd2luZG93W3ZlbmRvcnNbeF0rJ1JlcXVlc3RBbmltYXRpb25GcmFtZSddO1xuICAgIGNhbmNlbEZyYW1lID0gd2luZG93W3ZlbmRvcnNbeF0rJ0NhbmNlbEFuaW1hdGlvbkZyYW1lJ10gfHwgd2luZG93W3ZlbmRvcnNbeF0rJ0NhbmNlbFJlcXVlc3RBbmltYXRpb25GcmFtZSddO1xufVxuXG5pZiAoIXJlcUZyYW1lKSB7XG4gICAgcmVxRnJhbWUgPSBmdW5jdGlvbihjYWxsYmFjaykge1xuICAgICAgICB2YXIgY3VyclRpbWUgPSBuZXcgRGF0ZSgpLmdldFRpbWUoKSxcbiAgICAgICAgICAgIHRpbWVUb0NhbGwgPSBNYXRoLm1heCgwLCAxNiAtIChjdXJyVGltZSAtIGxhc3RUaW1lKSksXG4gICAgICAgICAgICBpZCA9IHNldFRpbWVvdXQoZnVuY3Rpb24oKSB7IGNhbGxiYWNrKGN1cnJUaW1lICsgdGltZVRvQ2FsbCk7IH0sXG4gICAgICAgICAgdGltZVRvQ2FsbCk7XG4gICAgICAgIGxhc3RUaW1lID0gY3VyclRpbWUgKyB0aW1lVG9DYWxsO1xuICAgICAgICByZXR1cm4gaWQ7XG4gICAgfTtcbn1cblxuaWYgKCFjYW5jZWxGcmFtZSkge1xuICAgIGNhbmNlbEZyYW1lID0gZnVuY3Rpb24oaWQpIHtcbiAgICAgICAgY2xlYXJUaW1lb3V0KGlkKTtcbiAgICB9O1xufVxuXG5tb2R1bGUuZXhwb3J0cyA9IHtcbiAgICByZXF1ZXN0OiByZXFGcmFtZSxcbiAgICBjYW5jZWw6IGNhbmNlbEZyYW1lXG59O1xuIiwiJ3VzZSBzdHJpY3QnO1xuXG5pZiAodHlwZW9mIHdpbmRvdyA9PT0gJ3VuZGVmaW5lZCcpIHtcbiAgICBtb2R1bGUuZXhwb3J0cy53aW5kb3cgICAgID0gdW5kZWZpbmVkO1xuICAgIG1vZHVsZS5leHBvcnRzLnJlYWxXaW5kb3cgPSB1bmRlZmluZWQ7XG59XG5lbHNlIHtcbiAgICAvLyBnZXQgd3JhcHBlZCB3aW5kb3cgaWYgdXNpbmcgU2hhZG93IERPTSBwb2x5ZmlsbFxuXG4gICAgbW9kdWxlLmV4cG9ydHMucmVhbFdpbmRvdyA9IHdpbmRvdztcblxuICAgIC8vIGNyZWF0ZSBhIFRleHROb2RlXG4gICAgdmFyIGVsID0gd2luZG93LmRvY3VtZW50LmNyZWF0ZVRleHROb2RlKCcnKTtcblxuICAgIC8vIGNoZWNrIGlmIGl0J3Mgd3JhcHBlZCBieSBhIHBvbHlmaWxsXG4gICAgaWYgKGVsLm93bmVyRG9jdW1lbnQgIT09IHdpbmRvdy5kb2N1bWVudFxuICAgICAgICAmJiB0eXBlb2Ygd2luZG93LndyYXAgPT09ICdmdW5jdGlvbidcbiAgICAgICAgJiYgd2luZG93LndyYXAoZWwpID09PSBlbCkge1xuICAgICAgICAvLyByZXR1cm4gd3JhcHBlZCB3aW5kb3dcbiAgICAgICAgbW9kdWxlLmV4cG9ydHMud2luZG93ID0gd2luZG93LndyYXAod2luZG93KTtcbiAgICB9XG5cbiAgICAvLyBubyBTaGFkb3cgRE9NIHBvbHlmaWwgb3IgbmF0aXZlIGltcGxlbWVudGF0aW9uXG4gICAgbW9kdWxlLmV4cG9ydHMud2luZG93ID0gd2luZG93O1xufVxuXG52YXIgaXNXaW5kb3cgPSByZXF1aXJlKCcuL2lzVHlwZScpLmlzV2luZG93O1xuXG5tb2R1bGUuZXhwb3J0cy5nZXRXaW5kb3cgPSBmdW5jdGlvbiBnZXRXaW5kb3cgKG5vZGUpIHtcbiAgICBpZiAoaXNXaW5kb3cobm9kZSkpIHtcbiAgICAgICAgcmV0dXJuIG5vZGU7XG4gICAgfVxuXG4gICAgdmFyIHJvb3ROb2RlID0gKG5vZGUub3duZXJEb2N1bWVudCB8fCBub2RlKTtcblxuICAgIHJldHVybiByb290Tm9kZS5kZWZhdWx0VmlldyB8fCByb290Tm9kZS5wYXJlbnRXaW5kb3cgfHwgbW9kdWxlLmV4cG9ydHMud2luZG93O1xufTtcbiJdfQ== +if (typeof window !== 'undefined') { + if (isShadowDom()) { + win.window = window.wrap(window); + } else { + win.window = window; + } +} + +module.exports = win; + +},{"./isWindow":15}]},{},[1]); diff --git a/build/interact.js.map b/build/interact.js.map new file mode 100644 index 000000000..5b010a45e --- /dev/null +++ b/build/interact.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["interact.js"],"names":["e","t","n","r","s","o","u","a","require","i","f","Error","code","l","exports","call","length",1,"module","getInteractionFromPointer","pointer","eventType","eventTarget","interaction","len","scope","interactions","mouseEvent","test","pointerType","id","utils","getPointerId","element","inertiaStatus","active","target","options","prepared","name","inertia","allowResume","mouse","pointers","removePointer","addPointer","parentElement","browser","supportsTouch","supportsPointerEvent","Interaction","contains","pointerIds","gesture","interacting","doOnInteractions","method","event","getActualElement","path","curEventTarget","currentTarget","type","prevTouchTime","Date","getTime","changedTouches","_updateEventTargets","pointerIsDown","preventOriginalDefault","this","originalEvent","preventDefault","checkResizeEdge","value","page","interactableElement","rect","margin","width","isNumber","right","left","height","bottom","top","x","y","isElement","matchesUpTo","defaultActionChecker","resizeEdges","getRect","shouldResize","action","resizeAxes","extend","curCoords","actionIsEnabled","resize","enabled","resizeOptions","isObject","edges","edge","_eventTarget","axis","drag","dragging","resizing","delegateListener","useCapture","fakeEvent","delegated","delegatedEvents","prop","selectors","selector","context","contexts","matchesSelector","nodeContains","listeners","j","delegateUseCapture","interact","interactables","get","Interactable","_element","_iEvents","_window","trySelector","getWindow","window","Node","document","_context","PointerEvent","events","add","pEventTypes","down","pointerDown","move","pointerHover","_doc","documents","listenToDocument","push","set","endAllInteractions","pointerEnd","doc","win","defaultView","parentWindow","MSPointerEvent","up","over","out","cancel","selectorDown","pointerMove","pointerOver","pointerOut","pointerUp","pointerCancel","autoScrollMove","frameElement","parentDoc","ownerDocument","error","windowParentError","useAttachEvent","currentAction","checkAndPreventDefault","dynamicDrop","defaultOptions","autoScroll","pointerMoveTolerance","maxInteractions","Infinity","actionCursors","isIe9OrOlder","resizex","resizey","resizexy","resizetop","resizeleft","resizebottom","resizeright","resizetopleft","resizebottomright","resizetopright","resizebottomleft","wheelEvent","eventTypes","globalEvents","prefixedMatchesSelector","Element","prototype","ie8MatchesSelector","interactionListeners","isString","querySelector","getScrollXY","scrollX","documentElement","scrollLeft","scrollY","scrollTop","SVGElementInstance","correspondingUseElement","getElementRect","scroll","isIOS7orLower","clientRect","SVGElement","getBoundingClientRect","getClientRects","heigh","getOriginXY","interactable","origin","closest","isFunction","_getQBezierValue","p1","p2","p3","iT","getQuadraticCurvePoint","startX","startY","cpX","cpY","endX","endY","position","easeOutQuad","b","c","d","parent","child","parentNode","node","isDocFrag","host","inContext","testIgnore","ignoreFrom","testAllow","allowFrom","checkAxis","thisAxis","checkSnap","snap","checkRestrict","restrict","checkAutoScroll","withinInteractionLimit","maxActions","max","maxPerElement","activeInteractions","targetCount","targetElementCount","otherAction","indexOfDeepestElement","elements","dropzone","deepestZone","index","deepestZoneParents","dropzoneParents","unshift","HTMLElement","SVGSVGElement","ownerSVGElement","parents","lastChild","previousSibling","nodeList","realWindow","replace","limit","elems","querySelectorAll","InteractEvent","listenerName","indexOfElement","forEachSelector","callback","ret","undefined","setOnEvents","phases","ondrop","ondropactivate","ondropdeactivate","ondragenter","ondragleave","ondropmove","onstart","onmove","onend","oninertiastart","draggable","setPerAction","isBool","option","perAction","drop","accept","overlap","Math","min","dropCheck","draggableElement","dropElement","dropped","dropChecker","dropOverlap","horizontal","vertical","getPageXY","dragRect","cx","cy","overlapArea","overlapRatio","checker","newValue","resizable","square","squareResize","gesturable","actions","setOptions","isArray","thisOption","mode","targets","createSnapGrid","offset","gridOffset","grid","anchors","paths","relativePoints","elementOrigin","allActions","getAction","actionChecker","rectChecker","styleCursor","deltaSource","restriction","fire","iEvent","onEvent","funcName","immediatePropagationStopped","on","listener","search","trim","split","off","eventList","indexOf","splice","matchFound","fn","useCap","remove","base","methods","perActions","settings","setting","unset","style","cursor","warnOnce","isSet","enableDragging","enableResizing","enableGesturing","debug","gesturing","matches","matchElements","prevCoords","startCoords","recordPointer","snapStatus","restrictStatus","downTime","downTimes","downEvent","downPointer","prevEvent","dragMove","resizeMove","gestureMove","getTouchAverage","touchAverage","getTouchBBox","touchBBox","getTouchDistance","touchDistance","getTouchAngle","touchAngle","newvalue","stop","offsetX","offsetY","gridx","round","gridy","newX","newY","range","./InteractEvent","./Interaction","./autoScroll","./defaultOptions","./scope","./utils","./utils/events","./utils/window",2,"phase","related","client","sourceX","sourceY","starting","ending","coords","locked","snappedX","snappedY","realX","realY","dx","dy","elementRect","restricted","pageX","pageY","clientX","clientY","x0","y0","clientX0","clientY0","ctrlKey","altKey","shiftKey","metaKey","button","t0","detail","relatedTarget","zeroResumeDelta","resumeDx","resumeDy","axes","touches","distance","box","scale","ds","angle","da","startAngle","startDistance","prevAngle","prevScale","timeStamp","dt","duration","speed","velocityX","velocityY","hypot","pointerDelta","vx","vy","atan2","PI","swipe","velocity","blank","stopImmediatePropagation","propagationStopped","stopPropagation",3,"dropTarget","prevDropTarget","prevDropElement","smoothEnd","startEvent","upCoords","xe","ye","sx","sy","vx0","vys","lambda_v0","one_ve_v0","Function","bind","boundInertiaFrame","inertiaFrame","boundSmoothEndFrame","smoothEndFrame","that","activeDrops","dropzones","rects","downTargets","holdTimers","_curEventTarget","tapTime","prevTap","startOffset","restrictOffset","snapOffsets","start","prevDistance","changed","restrictedX","restrictedY","pointerWasMoved","validateAction","actionName","getActionCursor","cursorKey","edgeNames","animationFrame","raf","xy","getClientXY","setEventXY","ptr","pushCurMatches","curMatches","curMatchElements","prevTargetElement","elementInteractable","elementAction","validateSelector","pushMatches","eventCopy","pointerIndex","setTimeout","pointerHold","_holdDuration","collectEventTargets","copyCoords","forceAction","NaN","setModifications","preEnd","shouldMove","shouldSnap","endOnly","shouldRestrict","setSnapping","setRestriction","setStartOffsets","snapOffset","duplicateMove","clearTimeout","setEventDeltas","absX","abs","absY","targetAxis","manualStart","thisInteraction","getDraggable","selectorInteractable","dragStart","dragEvent","setActiveDrops","dropEvents","getDropEvents","activate","fireActiveDrops","getDrop","leave","enter","resizeStart","resizeEvent","startRect","squareEdges","_squareEdges","resizeRects","current","previous","delta","deltaRect","invert","invertible","originalEdges","swap","gestureStart","gestureEvent","isNaN","ie8Dblclick","endEvent","inertiaOptions","pointerSpeed","now","inertiaPossible","endSnap","endRestrict","minSpeed","endSpeed","snapRestrict","vy0","v0","calcInertia","statusObject","useStatusXY","modifiedXe","modifiedYe","request","deactivate","collectDrops","drops","dropElements","currentElement","prevElement","dragElement","possibleDrops","validDrops","dropIndex","pointerEvent","dragLeave","prevDropzone","dragEnter","dragmove","clearTargets","lambda","resistance","te","progress","exp","quadPoint","smoothEndDuration","collectSelectors","els","firePointers","interval","createNewDoubleTap","pointerId","doubleTap","match","matchElement","pageCoords","status","relIndex","relative","inRange","snapChanged","prevent","nodeName","inertiaDur","log","container","isWindow","innerWidth","innerHeight","isScrolling","./utils/browser",4,"prevTime","scrollBy","./utils/isType","./utils/raf",5,"offsets","./utils/domObjects",6,"./utils/arr.js","./utils/extend",7,"array",8,"domObjects","DocumentTouch","isOperaMobile","navigator","appName","userAgent","platform","appVersion","all","atob","./domObjects","./window",9,"DocumentFragment",10,"elementIndex","typeCount","attachedListeners","supplied","wrapped","useCount","listenerIndex","srcElement","preventDef","stopProp","stopImmProp","addEvent","removeEvent","hasOwnProperty","returnValue","cancelBubble","arr","_elements","_targets","_attachedListeners","./arr",11,"dest","source",12,"sqrt",13,"message","warned","console","warn","apply","arguments","./browser","./extend","./hypot","./isType","./pointerUtils","./raf",14,"isType","nodeType","thing","./isWindow",15,"Window",16,"pointerUtils","tmpXY","src","targetObj","prev","cur","getXY","identifier","../InteractEvent",17,"reqFrame","cancelFrame","lastTime","vendors","requestAnimationFrame","currTime","timeToCall",18,"isShadowDom","el","createTextNode","wrap","rootNode"],"mappings":"CAAA,QAAUA,GAAEC,EAAEC,EAAEC,GAAG,QAASC,GAAEC,EAAEC,GAAG,IAAIJ,EAAEG,GAAG,CAAC,IAAIJ,EAAEI,GAAG,CAAC,GAAIE,GAAkB,kBAATC,UAAqBA,OAAQ,KAAIF,GAAGC,EAAE,MAAOA,GAAEF,GAAE,EAAI,IAAGI,EAAE,MAAOA,GAAEJ,GAAE,EAAI,IAAIK,GAAE,GAAIC,OAAM,uBAAuBN,EAAE,IAAK,MAAMK,GAAEE,KAAK,mBAAmBF,EAAE,GAAIG,GAAEX,EAAEG,IAAIS,WAAYb,GAAEI,GAAG,GAAGU,KAAKF,EAAEC,QAAQ,SAASd,GAAG,GAAIE,GAAED,EAAEI,GAAG,GAAGL,EAAG,OAAOI,GAAEF,EAAEA,EAAEF,IAAIa,EAAEA,EAAEC,QAAQd,EAAEC,EAAEC,EAAEC,GAAG,MAAOD,GAAEG,GAAGS,QAAkD,IAAI,GAA1CL,GAAkB,kBAATD,UAAqBA,QAAgBH,EAAE,EAAEA,EAAEF,EAAEa,OAAOX,IAAID,EAAED,EAAEE,GAAI,OAAOD,KAAKa,GAAG,SAAST,EAAQU,EAAOJ,GASnd,YAkhBA,SAASK,GAA2BC,EAASC,EAAWC,GACpD,GAIIC,GAJAd,EAAI,EAAGe,EAAMC,EAAMC,aAAaV,OAChCW,EAAc,SAASC,KAAKR,EAAQS,aAAeR,IAEV,IAAxBD,EAAQS,YAGzBC,EAAKC,EAAMC,aAAaZ,EAG5B,IAAI,cAAcQ,KAAKP,GACnB,IAAKZ,EAAI,EAAOe,EAAJf,EAASA,IAAK,CACtBc,EAAcE,EAAMC,aAAajB,EAEjC,IAAIwB,GAAUX,CAEd,IAAIC,EAAYW,cAAcC,QAAUZ,EAAYa,OAAOC,QAAQd,EAAYe,SAASC,MAAMC,QAAQC,aAC9FlB,EAAYmB,QAAUf,EAC1B,KAAOM,GAAS,CAEZ,GAAIA,IAAYV,EAAYU,QAOxB,MALIV,GAAYoB,SAAS,IACrBpB,EAAYqB,cAAcrB,EAAYoB,SAAS,IAEnDpB,EAAYsB,WAAWzB,GAEhBG,CAEXU,GAAUR,EAAMqB,cAAcb,IAO9C,GAAIN,IAAgBoB,EAAQC,gBAAiBD,EAAQE,qBAAuB,CAGxE,IAAKxC,EAAI,EAAOe,EAAJf,EAASA,IACjB,GAAIgB,EAAMC,aAAajB,GAAGiC,QAAUjB,EAAMC,aAAajB,GAAGyB,cAAcC,OACpE,MAAOV,GAAMC,aAAajB,EAOlC,KAAKA,EAAI,EAAOe,EAAJf,EAASA,IACjB,GAAIgB,EAAMC,aAAajB,GAAGiC,SAAW,OAAOd,KAAKP,KAAcI,EAAMC,aAAajB,GAAGyB,cAAcC,QAC/F,MAAOZ,EAQf,OAHAA,GAAc,GAAI2B,GAClB3B,EAAYmB,OAAQ,EAEbnB,EAIX,IAAKd,EAAI,EAAOe,EAAJf,EAASA,IACjB,GAAIgB,EAAM0B,SAAS1B,EAAMC,aAAajB,GAAG2C,WAAYtB,GACjD,MAAOL,GAAMC,aAAajB,EAKlC,IAAI,cAAcmB,KAAKP,GACnB,MAAO,KAIX,KAAKZ,EAAI,EAAOe,EAAJf,EAASA,IAGjB,GAFAc,EAAcE,EAAMC,aAAajB,KAE3Bc,EAAYe,SAASC,OAAShB,EAAYa,OAAOC,QAAQgB,QAAe,SACtE9B,EAAY+B,gBACV3B,GAAcJ,EAAYmB,OAIhC,MAFAnB,GAAYsB,WAAWzB,GAEhBG,CAIf,OAAO,IAAI2B,GAGf,QAASK,GAAkBC,GACvB,MAAO,UAAWC,GACd,GAAIlC,GAKAd,EAJAa,EAAcG,EAAMiC,iBAAiBD,EAAME,KACVF,EAAME,KAAK,GACXF,EAAMrB,QACvCwB,EAAiBnC,EAAMiC,iBAAiBD,EAAMI,cAGlD,IAAId,EAAQC,eAAiB,QAAQpB,KAAK6B,EAAMK,MAG5C,IAFArC,EAAMsC,eAAgB,GAAIC,OAAOC,UAE5BxD,EAAI,EAAGA,EAAIgD,EAAMS,eAAelD,OAAQP,IAAK,CAC9C,GAAIW,GAAUqC,EAAMS,eAAezD,EAEnCc,GAAcJ,EAA0BC,EAASqC,EAAMK,KAAMxC,GAExDC,IAELA,EAAY4C,oBAAoB7C,EAAasC,GAE7CrC,EAAYiC,GAAQpC,EAASqC,EAAOnC,EAAasC,QAGpD,CACD,IAAKb,EAAQE,sBAAwB,QAAQrB,KAAK6B,EAAMK,MAAO,CAE3D,IAAKrD,EAAI,EAAGA,EAAIgB,EAAMC,aAAaV,OAAQP,IACvC,IAAKgB,EAAMC,aAAajB,GAAGiC,OAASjB,EAAMC,aAAajB,GAAG2D,cACtD,MAMR,KAAI,GAAIJ,OAAOC,UAAYxC,EAAMsC,cAAgB,IAC7C,OAMR,GAFAxC,EAAcJ,EAA0BsC,EAAOA,EAAMK,KAAMxC,IAEtDC,EAAe,MAEpBA,GAAY4C,oBAAoB7C,EAAasC,GAE7CrC,EAAYiC,GAAQC,EAAOA,EAAOnC,EAAasC,KAK3D,QAASS,KACLC,KAAKC,cAAcC,iBAGvB,QAASC,GAAiBlC,EAAMmC,EAAOC,EAAM1C,EAAS2C,EAAqBC,EAAMC,GAE7E,IAAKJ,EAAS,OAAO,CAGrB,IAAIA,KAAU,EAAM,CAEhB,GAAIK,GAAQtD,EAAMuD,SAASH,EAAKE,OAAQF,EAAKE,MAAQF,EAAKI,MAAQJ,EAAKK,KACnEC,EAAS1D,EAAMuD,SAASH,EAAKM,QAASN,EAAKM,OAASN,EAAKO,OAASP,EAAKQ,GAW3E,IATY,EAARN,IACkB,SAATxC,EAAoBA,EAAO,QAClB,UAATA,IAAoBA,EAAO,SAE3B,EAAT4C,IACkB,QAAT5C,EAAqBA,EAAO,SACnB,WAATA,IAAqBA,EAAO,QAG5B,SAATA,EAAqB,MAAOoC,GAAKW,GAAMP,GAAU,EAAGF,EAAKK,KAAML,EAAKI,OAAUH,CAClF,IAAa,QAATvC,EAAqB,MAAOoC,GAAKY,GAAMJ,GAAU,EAAGN,EAAKQ,IAAMR,EAAKO,QAAUN,CAElF,IAAa,UAATvC,EAAqB,MAAOoC,GAAKW,GAAMP,GAAU,EAAGF,EAAKI,MAAQJ,EAAKK,MAAQJ,CAClF,IAAa,WAATvC,EAAqB,MAAOoC,GAAKY,GAAMJ,GAAU,EAAGN,EAAKO,OAAQP,EAAKQ,KAAQP,EAItF,MAAK/C,GAAMyD,UAAUvD,GAEdF,EAAMyD,UAAUd,GAETA,IAAUzC,EAEVR,EAAMgE,YAAYxD,EAASyC,EAAOE,IANR,EAS5C,QAASc,GAAsBtE,EAASG,EAAaU,GACjD,GAII0D,GAJAd,EAAOP,KAAKsB,QAAQ3D,GACpB4D,GAAe,EACfC,EAAS,KACTC,EAAa,KAEbpB,EAAO5C,EAAMiE,UAAWzE,EAAY0E,UAAUtB,MAC9CtC,EAAUiC,KAAKjC,OAEnB,KAAKwC,EAAQ,MAAO,KAEpB,IAAIpD,EAAMyE,gBAAgBC,QAAU9D,EAAQ8D,OAAOC,QAAS,CACxD,GAAIC,GAAgBhE,EAAQ8D,MAO5B,IALAR,GACIT,MAAM,EAAOD,OAAO,EAAOI,KAAK,EAAOD,QAAQ,GAI/C3D,EAAM6E,SAASD,EAAcE,OAAQ,CACrC,IAAK,GAAIC,KAAQb,GACbA,EAAYa,GAAQ/B,EAAgB+B,EACAH,EAAcE,MAAMC,GACpB7B,EACApD,EAAYkF,aACZxE,EACA4C,EACAwB,EAAcvB,QAAUrD,EAAMqD,OAGtEa,GAAYT,KAAOS,EAAYT,OAASS,EAAYV,MACpDU,EAAYN,IAAOM,EAAYN,MAASM,EAAYP,OAEpDS,EAAeF,EAAYT,MAAQS,EAAYV,OAASU,EAAYN,KAAOM,EAAYP,WAEtF,CACD,GAAIH,GAAiC,MAAxB5C,EAAQ8D,OAAOO,MAAgB/B,EAAKW,EAAKT,EAAKI,MAASxD,EAAMqD,OACtEM,EAAiC,MAAxB/C,EAAQ8D,OAAOO,MAAgB/B,EAAKY,EAAKV,EAAKO,OAAS3D,EAAMqD,MAE1Ee,GAAeZ,GAASG,EACxBW,GAAcd,EAAO,IAAM,KAAOG,EAAQ,IAAM,KAgBxD,MAZAU,GAASD,EACH,SACApE,EAAMyE,gBAAgBS,MAAQtE,EAAQsE,KAAKP,QACvC,OACA,KAEN3E,EAAMyE,gBAAgB7C,SACnB9B,EAAY6B,WAAWpC,QAAS,IAC9BO,EAAYqF,WAAYrF,EAAYsF,WACzCf,EAAS,WAGTA,GAEIvD,KAAMuD,EACNY,KAAMX,EACNQ,MAAOZ,GAIR,KAaX,QAASmB,GAAkBrD,EAAOsD,GAC9B,GAAIC,MACAC,EAAYxF,EAAMyF,gBAAgBzD,EAAMK,MACxCxC,EAAcG,EAAMiC,iBAAiBD,EAAME,KACVF,EAAME,KAAK,GACXF,EAAMrB,QACvCH,EAAUX,CAEdyF,GAAaA,GAAY,GAAM,CAG/B,KAAK,GAAII,KAAQ1D,GACbuD,EAAUG,GAAQ1D,EAAM0D,EAO5B,KAJAH,EAAUzC,cAAgBd,EAC1BuD,EAAUxC,eAAiBH,EAGpBtC,EAAMyD,UAAUvD,IAAU,CAC7B,IAAK,GAAIxB,GAAI,EAAGA,EAAIwG,EAAUG,UAAUpG,OAAQP,IAAK,CACjD,GAAI4G,GAAWJ,EAAUG,UAAU3G,GAC/B6G,EAAUL,EAAUM,SAAS9G,EAEjC,IAAIgB,EAAM+F,gBAAgBvF,EAASoF,IAC5B5F,EAAMgG,aAAaH,EAAShG,IAC5BG,EAAMgG,aAAaH,EAASrF,GAAU,CAEzC,GAAIyF,GAAYT,EAAUS,UAAUjH,EAEpCuG,GAAUnD,cAAgB5B,CAE1B,KAAK,GAAI0F,GAAI,EAAGA,EAAID,EAAU1G,OAAQ2G,IAC9BD,EAAUC,GAAG,KAAOZ,GACpBW,EAAUC,GAAG,GAAGX,IAMhC/E,EAAUR,EAAMqB,cAAcb,IAItC,QAAS2F,GAAoBnE,GACzB,MAAOqD,GAAiB/F,KAAKuD,KAAMb,GAAO,GAgE9C,QAASoE,GAAU5F,EAASI,GACxB,MAAOZ,GAAMqG,cAAcC,IAAI9F,EAASI,IAAY,GAAI2F,GAAa/F,EAASI,GASlF,QAAS2F,GAAc/F,EAASI,GAC5BiC,KAAK2D,SAAWhG,EAChBqC,KAAK4D,SAAW5D,KAAK4D,YAErB,IAAIC,EAEJ,IAAI1G,EAAM2G,YAAYnG,GAAU,CAC5BqC,KAAK+C,SAAWpF,CAEhB,IAAIqF,GAAUjF,GAAWA,EAAQiF,OAEjCa,GAAUb,EAAS7F,EAAM4G,UAAUf,GAAW7F,EAAM6G,OAEhDhB,IAAYa,EAAQI,KACdjB,YAAmBa,GAAQI,KAC1BxG,EAAMyD,UAAU8B,IAAYA,IAAYa,EAAQK,YAEvDlE,KAAKmE,SAAWnB,OAIpBa,GAAU1G,EAAM4G,UAAUpG,GAEtBF,EAAMyD,UAAUvD,EAASkG,KAErB1G,EAAMiH,cACNC,EAAOC,IAAItE,KAAK2D,SAAUxG,EAAMoH,YAAYC,KAAMrH,EAAMiG,UAAUqB,aAClEJ,EAAOC,IAAItE,KAAK2D,SAAUxG,EAAMoH,YAAYG,KAAMvH,EAAMiG,UAAUuB,gBAGlEN,EAAOC,IAAItE,KAAK2D,SAAU,YAAcxG,EAAMiG,UAAUqB,aACxDJ,EAAOC,IAAItE,KAAK2D,SAAU,YAAcxG,EAAMiG,UAAUuB,cACxDN,EAAOC,IAAItE,KAAK2D,SAAU,aAAcxG,EAAMiG,UAAUqB,aACxDJ,EAAOC,IAAItE,KAAK2D,SAAU,YAAcxG,EAAMiG,UAAUuB,eAKpE3E,MAAK4E,KAAOf,EAAQK,SAEf/G,EAAM0B,SAAS1B,EAAM0H,UAAW7E,KAAK4E,OACtCE,EAAiB9E,KAAK4E,MAG1BzH,EAAMqG,cAAcuB,KAAK/E,MAEzBA,KAAKgF,IAAIjH,GAouDb,QAASkH,GAAoB9F,GACzB,IAAK,GAAIhD,GAAI,EAAGA,EAAIgB,EAAMC,aAAaV,OAAQP,IAC3CgB,EAAMC,aAAajB,GAAG+I,WAAW/F,EAAOA,GAIhD,QAAS2F,GAAkBK,GACvB,IAAIhI,EAAM0B,SAAS1B,EAAM0H,UAAWM,GAApC,CAEA,GAAIC,GAAMD,EAAIE,aAAeF,EAAIG,YAGjC,KAAK,GAAIvI,KAAaI,GAAMyF,gBACxByB,EAAOC,IAAIa,EAAKpI,EAAWyF,GAC3B6B,EAAOC,IAAIa,EAAKpI,EAAWuG,GAAoB,EAG/CnG,GAAMiH,cAEFjH,EAAMoH,YADNpH,EAAMiH,eAAiBgB,EAAIG,gBAEvBC,GAAI,cAAehB,KAAM,gBAAiBiB,KAAM,YAChDC,IAAK,WAAYhB,KAAM,gBAAiBiB,OAAQ,oBAIhDH,GAAI,YAAahB,KAAM,cAAeiB,KAAM,cAC5CC,IAAK,aAAchB,KAAM,cAAeiB,OAAQ,iBAGxDtB,EAAOC,IAAIa,EAAKhI,EAAMoH,YAAYC,KAAQrH,EAAMiG,UAAUwC,cAC1DvB,EAAOC,IAAIa,EAAKhI,EAAMoH,YAAYG,KAAQvH,EAAMiG,UAAUyC,aAC1DxB,EAAOC,IAAIa,EAAKhI,EAAMoH,YAAYkB,KAAQtI,EAAMiG,UAAU0C,aAC1DzB,EAAOC,IAAIa,EAAKhI,EAAMoH,YAAYmB,IAAQvI,EAAMiG,UAAU2C,YAC1D1B,EAAOC,IAAIa,EAAKhI,EAAMoH,YAAYiB,GAAQrI,EAAMiG,UAAU4C,WAC1D3B,EAAOC,IAAIa,EAAKhI,EAAMoH,YAAYoB,OAAQxI,EAAMiG,UAAU6C,eAG1D5B,EAAOC,IAAIa,EAAKhI,EAAMoH,YAAYG,KAAMvH,EAAMiG,UAAU8C,kBAGxD7B,EAAOC,IAAIa,EAAK,YAAahI,EAAMiG,UAAUwC,cAC7CvB,EAAOC,IAAIa,EAAK,YAAahI,EAAMiG,UAAUyC,aAC7CxB,EAAOC,IAAIa,EAAK,UAAahI,EAAMiG,UAAU4C,WAC7C3B,EAAOC,IAAIa,EAAK,YAAahI,EAAMiG,UAAU0C,aAC7CzB,EAAOC,IAAIa,EAAK,WAAahI,EAAMiG,UAAU2C,YAE7C1B,EAAOC,IAAIa,EAAK,aAAehI,EAAMiG,UAAUwC,cAC/CvB,EAAOC,IAAIa,EAAK,YAAehI,EAAMiG,UAAUyC,aAC/CxB,EAAOC,IAAIa,EAAK,WAAehI,EAAMiG,UAAU4C,WAC/C3B,EAAOC,IAAIa,EAAK,cAAehI,EAAMiG,UAAU6C,eAG/C5B,EAAOC,IAAIa,EAAK,YAAahI,EAAMiG,UAAU8C,gBAC7C7B,EAAOC,IAAIa,EAAK,YAAahI,EAAMiG,UAAU8C,iBAGjD7B,EAAOC,IAAIc,EAAK,OAAQH,EAExB,KACI,GAAIG,EAAIe,aAAc,CAClB,GAAIC,GAAYhB,EAAIe,aAAaE,cAC7Bf,EAAec,EAAUf,WAE7BhB,GAAOC,IAAI8B,EAAc,UAAiBjJ,EAAMiG,UAAU8B,YAC1Db,EAAOC,IAAI8B,EAAc,WAAiBjJ,EAAMiG,UAAU8B,YAC1Db,EAAOC,IAAI8B,EAAc,cAAiBjJ,EAAMiG,UAAU8B,YAC1Db,EAAOC,IAAI8B,EAAc,YAAiBjJ,EAAMiG,UAAU8B,YAC1Db,EAAOC,IAAI8B,EAAc,cAAiBjJ,EAAMiG,UAAU8B,YAC1Db,EAAOC,IAAIgB,EAAc,OAAiBL,IAGlD,MAAOqB,GACH/C,EAASgD,kBAAoBD,EAG7BjC,EAAOmC,iBAEPnC,EAAOC,IAAIa,EAAK,cAAe,SAAUhG,GACrC,GAAIlC,GAAcE,EAAMC,aAAa,EAEjCH,GAAYwJ,iBACZxJ,EAAYyJ,uBAAuBvH,KAK3CkF,EAAOC,IAAIa,EAAK,WAAYlG,EAAiB,iBAGjD9B,EAAM0H,UAAUE,KAAKI,IAnvFzB,GAAKjJ,EAAQ,kBAAkB8H,OAA/B,CAEA,GAAI7G,GAAQjB,EAAQ,WAChBuB,EAAQvB,EAAQ,WAChBuC,EAAUhB,EAAMgB,OAEpBtB,GAAMoH,YAAc,KAEpBpH,EAAM0H,aAEN1H,EAAMqG,iBACNrG,EAAMC,gBAEND,EAAMwJ,aAAkB,EASxBxJ,EAAMyF,mBAENzF,EAAMyJ,eAAiB1K,EAAQ,oBAG/BiB,EAAM0J,WAAa3K,EAAQ,gBAG3BiB,EAAMqD,OAAS/B,EAAQC,eAAiBD,EAAQE,qBAAsB,GAAI,GAE1ExB,EAAM2J,qBAAuB,EAG7B3J,EAAMsC,cAAgB,EAGtBtC,EAAM4J,gBAAkBC,EAAAA,EAExB7J,EAAM8J,cAAgBxI,EAAQyI,cAC1B7E,KAAU,OACV8E,QAAU,WACVC,QAAU,WACVC,SAAU,YAEVC,UAAmB,WACnBC,WAAmB,WACnBC,aAAmB,WACnBC,YAAmB,WACnBC,cAAmB,YACnBC,kBAAmB,YACnBC,eAAmB,YACnBC,iBAAmB,YAEnB9I,QAAU,KAEVsD,KAAU,OACV8E,QAAU,YACVC,QAAU,YACVC,SAAU,cAEVC,UAAmB,YACnBC,WAAmB,YACnBC,aAAmB,YACnBC,YAAmB,YACnBC,cAAmB,cACnBC,kBAAmB,cACnBC,eAAmB,cACnBC,iBAAmB,cAEnB9I,QAAU,IAGd5B,EAAMyE,iBACFS,MAAS,EACTR,QAAS,EACT9C,SAAS,GAIb5B,EAAM2K,WAAa,gBAAkB3K,GAAM+G,SAAU,aAAc,QAEnE/G,EAAM4K,YACF,YACA,WACA,mBACA,UACA,YACA,YACA,eACA,iBACA,WACA,OACA,cACA,aACA,qBACA,YACA,eACA,cACA,sBACA,aAEA,OACA,OACA,KACA,SACA,MACA,YACA,QAGJ5K,EAAM6K,gBAGNvJ,EAAQwJ,wBAA0B,WAAaC,SAAQC,UAC/C,UAAW,yBAA2BD,SAAQC,UAC1C,wBAAyB,sBAAwBD,SAAQC,UACrD,qBAAsB,oBAAsBD,SAAQC,UAChD,mBAAoB,oBAGxChL,EAAMiL,mBAAqB,IAG3B,IAAI/D,GAASnI,EAAQ,iBAErBiB,GAAMiG,YAEN,IAAIiF,IACA,YAAa,WAAY,cAAe,aAAc,eAAgB,cACtE,cAAe,aAAc,eAAgB,eAC7C,cAAe,cAAe,YAAa,gBAAiB,aAC5D,aAAc,gBAAiB,gBAAiB,iBAGpDlL,GAAM2G,YAAc,SAAU1D,GAC1B,MAAKjD,GAAMmL,SAASlI,IAGpBjD,EAAM+G,SAASqE,cAAcnI,IACtB,IAJ8B,GAOzCjD,EAAMqL,YAAc,SAAUpD,GAE1B,MADAA,GAAMA,GAAOjI,EAAM6G,QAEfhD,EAAGoE,EAAIqD,SAAWrD,EAAIlB,SAASwE,gBAAgBC,WAC/C1H,EAAGmE,EAAIwD,SAAWxD,EAAIlB,SAASwE,gBAAgBG,YAIvD1L,EAAMiC,iBAAmB,SAAUzB,GAC/B,MAAQA,aAAmBR,GAAM2L,mBAC3BnL,EAAQoL,wBACRpL,GAGVR,EAAM6L,eAAiB,SAAUrL,GAC7B,GAAIsL,GAASxK,EAAQyK,eACTlI,EAAG,EAAGC,EAAG,GACX9D,EAAMqL,YAAYrL,EAAM4G,UAAUpG,IACxCwL,EAAcxL,YAAmBR,GAAMiM,WACnCzL,EAAQ0L,wBACR1L,EAAQ2L,iBAAiB,EAEjC,OAAOH,KACHvI,KAAQuI,EAAWvI,KAASqI,EAAOjI,EACnCL,MAAQwI,EAAWxI,MAASsI,EAAOjI,EACnCD,IAAQoI,EAAWpI,IAASkI,EAAOhI,EACnCH,OAAQqI,EAAWrI,OAASmI,EAAOhI,EACnCR,MAAQ0I,EAAW1I,OAAS0I,EAAWxI,MAAQwI,EAAWvI,KAC1DC,OAAQsI,EAAWI,OAASJ,EAAWrI,OAASqI,EAAWpI,MAInE5D,EAAMqM,YAAc,SAAUC,EAAc9L,GACxC,GAAI+L,GAASD,EACHA,EAAa1L,QAAQ2L,OACrBvM,EAAMyJ,eAAe8C,MAuB/B,OArBe,WAAXA,EACAA,EAASvM,EAAMqB,cAAcb,GAEb,SAAX+L,EACLA,EAASD,EAAanI,QAAQ3D,GAEzBR,EAAM2G,YAAY4F,KACvBA,EAASvM,EAAMwM,QAAQhM,EAAS+L,KAAa1I,EAAG,EAAGC,EAAG,IAGtD9D,EAAMyM,WAAWF,KACjBA,EAASA,EAAOD,GAAgB9L,IAGhCF,EAAMyD,UAAUwI,KAChBA,EAASvM,EAAM6L,eAAeU,IAGlCA,EAAO1I,EAAK,KAAO0I,GAASA,EAAO1I,EAAI0I,EAAO9I,KAC9C8I,EAAOzI,EAAK,KAAOyI,GAASA,EAAOzI,EAAIyI,EAAO3I,IAEvC2I,GAIXvM,EAAM0M,iBAAmB,SAAUlO,EAAGmO,EAAIC,EAAIC,GAC1C,GAAIC,GAAK,EAAItO,CACb,OAAOsO,GAAKA,EAAKH,EAAK,EAAIG,EAAKtO,EAAIoO,EAAKpO,EAAIA,EAAIqO,GAGpD7M,EAAM+M,uBAAyB,SAAUC,EAAQC,EAAQC,EAAKC,EAAKC,EAAMC,EAAMC,GAC3E,OACIzJ,EAAI7D,EAAM0M,iBAAiBY,EAAUN,EAAQE,EAAKE,GAClDtJ,EAAI9D,EAAM0M,iBAAiBY,EAAUL,EAAQE,EAAKE,KAK1DrN,EAAMuN,YAAc,SAAU/O,EAAGgP,EAAGC,EAAGC,GAEnC,MADAlP,IAAKkP,GACGD,EAAIjP,GAAGA,EAAE,GAAKgP,GAG1BxN,EAAMgG,aAAe,SAAU2H,EAAQC,GACnC,KAAOA,GAAO,CACV,GAAIA,IAAUD,EACV,OAAO,CAGXC,GAAQA,EAAMC,WAGlB,OAAO,GAGX7N,EAAMwM,QAAU,SAAUoB,EAAOhI,GAG7B,IAFA,GAAI+H,GAAS3N,EAAMqB,cAAcuM,GAE1BtN,EAAMyD,UAAU4J,IAAS,CAC5B,GAAI3N,EAAM+F,gBAAgB4H,EAAQ/H,GAAa,MAAO+H,EAEtDA,GAAS3N,EAAMqB,cAAcsM,GAGjC,MAAO,OAGX3N,EAAMqB,cAAgB,SAAUyM,GAC5B,GAAIH,GAASG,EAAKD,UAElB,IAAI7N,EAAM+N,UAAUJ,GAAS,CAEzB,MAAQA,EAASA,EAAOK,OAAShO,EAAM+N,UAAUJ,KAEjD,MAAOA,GAGX,MAAOA,IAGX3N,EAAMiO,UAAY,SAAU3B,EAAc9L,GACtC,MAAO8L,GAAatF,WAAaxG,EAAQ0I,eAC9BlJ,EAAMgG,aAAasG,EAAatF,SAAUxG,IAGzDR,EAAMkO,WAAa,SAAU5B,EAAcnJ,EAAqB3C,GAC5D,GAAI2N,GAAa7B,EAAa1L,QAAQuN,UAEtC,OAAKA,IAAe7N,EAAMyD,UAAUvD,GAEhCR,EAAMmL,SAASgD,GACRnO,EAAMgE,YAAYxD,EAAS2N,EAAYhL,GAEzC7C,EAAMyD,UAAUoK,GACdnO,EAAMgG,aAAamI,EAAY3N,IAGnC,GATgD,GAY3DR,EAAMoO,UAAY,SAAU9B,EAAcnJ,EAAqB3C,GAC3D,GAAI6N,GAAY/B,EAAa1L,QAAQyN,SAErC,OAAKA,GAEA/N,EAAMyD,UAAUvD,GAEjBR,EAAMmL,SAASkD,GACRrO,EAAMgE,YAAYxD,EAAS6N,EAAWlL,GAExC7C,EAAMyD,UAAUsK,GACdrO,EAAMgG,aAAaqI,EAAW7N,IAGlC,GATiC,GAFf,GAc7BR,EAAMsO,UAAY,SAAUrJ,EAAMqH,GAC9B,IAAKA,EAAgB,OAAO,CAE5B,IAAIiC,GAAWjC,EAAa1L,QAAQsE,KAAKD,IAEzC,OAAiB,OAATA,GAA8B,OAAbsJ,GAAqBA,IAAatJ,GAG/DjF,EAAMwO,UAAY,SAAUlC,EAAcjI,GACtC,GAAIzD,GAAU0L,EAAa1L,OAM3B,OAJI,UAAUT,KAAKkE,KACfA,EAAS,UAGNzD,EAAQyD,GAAQoK,MAAQ7N,EAAQyD,GAAQoK,KAAK9J,SAGxD3E,EAAM0O,cAAgB,SAAUpC,EAAcjI,GAC1C,GAAIzD,GAAU0L,EAAa1L,OAM3B,OAJI,UAAUT,KAAKkE,KACfA,EAAS,UAGLzD,EAAQyD,GAAQsK,UAAY/N,EAAQyD,GAAQsK,SAAShK,SAGjE3E,EAAM4O,gBAAkB,SAAUtC,EAAcjI,GAC5C,GAAIzD,GAAU0L,EAAa1L,OAM3B,OAJI,UAAUT,KAAKkE,KACfA,EAAS,UAGLzD,EAAQyD,GAAQqF,YAAc9I,EAAQyD,GAAQqF,WAAW/E,SAGrE3E,EAAM6O,uBAAyB,SAAUvC,EAAc9L,EAAS6D,GAQ5D,IAAK,GAPDzD,GAAU0L,EAAa1L,QACvBkO,EAAalO,EAAQyD,EAAOvD,MAAMiO,IAClCC,EAAgBpO,EAAQyD,EAAOvD,MAAMkO,cACrCC,EAAqB,EACrBC,EAAc,EACdC,EAAqB,EAEhBnQ,EAAI,EAAGe,EAAMC,EAAMC,aAAaV,OAAYQ,EAAJf,EAASA,IAAK,CAC3D,GAAIc,GAAcE,EAAMC,aAAajB,GACjCoQ,EAActP,EAAYe,SAASC,KACnCJ,EAASZ,EAAY+B,aAEzB,IAAKnB,EAAL,CAIA,GAFAuO,IAEIA,GAAsBjP,EAAM4J,gBAC5B,OAAO,CAGX,IAAI9J,EAAYa,SAAW2L,EAA3B,CAIA,GAFA4C,GAAgBE,IAAgB/K,EAAOvD,KAAM,EAEzCoO,GAAeJ,EACf,OAAO,CAGX,IAAIhP,EAAYU,UAAYA,IACxB2O,IAEIC,IAAgB/K,EAAOvD,MAAQqO,GAAsBH,GACrD,OAAO,IAKnB,MAAOhP,GAAM4J,gBAAkB,GAInC5J,EAAMqP,sBAAwB,SAAUC,GACpC,GAAIC,GAGA5B,EAGAC,EACA5O,EACAP,EAPA+Q,EAAcF,EAAS,GACvBG,EAAQD,EAAa,EAAG,GAExBE,KACAC,IAKJ,KAAK3Q,EAAI,EAAGA,EAAIsQ,EAAS/P,OAAQP,IAI7B,GAHAuQ,EAAWD,EAAStQ,GAGfuQ,GAAYA,IAAaC,EAI9B,GAAKA,GAQL,GAAID,EAAS1B,aAAe0B,EAASrG,cAIhC,GAAIsG,EAAY3B,aAAe0B,EAASrG,cAAxC,CAML,IAAKwG,EAAmBnQ,OAEpB,IADAoO,EAAS6B,EACF7B,EAAOE,YAAcF,EAAOE,aAAeF,EAAOzE,eACrDwG,EAAmBE,QAAQjC,GAC3BA,EAASA,EAAOE,UAMxB,IAAI2B,YAAuBxP,GAAM6P,aAC1BN,YAAoBvP,GAAMiM,cACxBsD,YAAoBvP,GAAM8P,eAAgB,CAE/C,GAAIP,IAAaC,EAAY3B,WACzB,QAGJF,GAAS4B,EAASQ,oBAGlBpC,GAAS4B,CAKb,KAFAI,KAEOhC,EAAOE,aAAeF,EAAOzE,eAChCyG,EAAgBC,QAAQjC,GACxBA,EAASA,EAAOE,UAMpB,KAHApP,EAAI,EAGGkR,EAAgBlR,IAAMkR,EAAgBlR,KAAOiR,EAAmBjR,IACnEA,GAGJ,IAAIuR,IACAL,EAAgBlR,EAAI,GACpBkR,EAAgBlR,GAChBiR,EAAmBjR,GAKvB,KAFAmP,EAAQoC,EAAQ,GAAGC,UAEZrC,GAAO,CACV,GAAIA,IAAUoC,EAAQ,GAAI,CACtBR,EAAcD,EACdE,EAAQzQ,EACR0Q,IAEA,OAEC,GAAI9B,IAAUoC,EAAQ,GACvB,KAGJpC,GAAQA,EAAMsC,qBA/DdV,GAAcD,EACdE,EAAQzQ,MAbRwQ,GAAcD,EACdE,EAAQzQ,CA8EhB,OAAOyQ,IAGXzP,EAAM+F,gBAAkB,SAAUvF,EAASoF,EAAUuK,GACjD,MAAInQ,GAAMiL,mBACCjL,EAAMiL,mBAAmBzK,EAASoF,EAAUuK,IAInDnQ,EAAM6G,SAAW7G,EAAMoQ,aACvBxK,EAAWA,EAASyK,QAAQ,YAAa,MAGtC7P,EAAQc,EAAQwJ,yBAAyBlF,KAGpD5F,EAAMgE,YAAc,SAAUxD,EAASoF,EAAU0K,GAC7C,KAAOhQ,EAAMyD,UAAUvD,IAAU,CAC7B,GAAIR,EAAM+F,gBAAgBvF,EAASoF,GAC/B,OAAO,CAKX,IAFApF,EAAUR,EAAMqB,cAAcb,GAE1BA,IAAY8P,EACZ,MAAOtQ,GAAM+F,gBAAgBvF,EAASoF,GAI9C,OAAO,GAKLtE,EAAQwJ,0BAA2BC,SAAQC,WAAehL,EAAMyM,WAAW1B,QAAQC,UAAU1J,EAAQwJ,4BACvG9K,EAAMiL,mBAAqB,SAAUzK,EAASoF,EAAU2K,GACpDA,EAAQA,GAAS/P,EAAQqN,WAAW2C,iBAAiB5K,EAErD,KAAK,GAAI5G,GAAI,EAAGe,EAAMwQ,EAAMhR,OAAYQ,EAAJf,EAASA,IACzC,GAAIuR,EAAMvR,KAAOwB,EACb,OAAO,CAIf,QAAO,GAgQf,KAAK,GA5PDiB,GAAc1C,EAAQ,iBA0PtB0R,EAAgB1R,EAAQ,mBAEnBC,EAAI,EAAGe,EAAMmL,EAAqB3L,OAAYQ,EAAJf,EAASA,IAAK,CAC7D,GAAI0R,GAAexF,EAAqBlM,EAExCgB,GAAMiG,UAAUyK,GAAgB5O,EAAiB4O,GAqDrD1Q,EAAMqG,cAAcsK,eAAiB,SAAyBnQ,EAASqF,GACnEA,EAAUA,GAAW7F,EAAM+G,QAE3B,KAAK,GAAI/H,GAAI,EAAGA,EAAI6D,KAAKtD,OAAQP,IAAK,CAClC,GAAIsN,GAAezJ,KAAK7D,EAExB,IAAKsN,EAAa1G,WAAapF,GACvB8L,EAAatF,WAAanB,IACzByG,EAAa1G,UAAY0G,EAAa9F,WAAahG,EAExD,MAAOxB,GAGf,MAAO,IAGXgB,EAAMqG,cAAcC,IAAM,SAA0B9F,EAASI,GACzD,MAAOiC,MAAKA,KAAK8N,eAAenQ,EAASI,GAAWA,EAAQiF,WAGhE7F,EAAMqG,cAAcuK,gBAAkB,SAAUC,GAC5C,IAAK,GAAI7R,GAAI,EAAGA,EAAI6D,KAAKtD,OAAQP,IAAK,CAClC,GAAIsN,GAAezJ,KAAK7D,EAExB,IAAKsN,EAAa1G,SAAlB,CAIA,GAAIkL,GAAMD,EAASvE,EAAcA,EAAa1G,SAAU0G,EAAatF,SAAUhI,EAAG6D,KAElF,IAAYkO,SAARD,EACA,MAAOA,MAyFnBvK,EAAayE,WACTgG,YAAa,SAAU3M,EAAQ4M,GAkB3B,MAjBe,SAAX5M,GACIrE,EAAMyM,WAAWwE,EAAOC,UAAqBrO,KAAKqO,OAAmBD,EAAOC,QAC5ElR,EAAMyM,WAAWwE,EAAOE,kBAAqBtO,KAAKsO,eAAmBF,EAAOE,gBAC5EnR,EAAMyM,WAAWwE,EAAOG,oBAAqBvO,KAAKuO,iBAAmBH,EAAOG,kBAC5EpR,EAAMyM,WAAWwE,EAAOI,eAAqBxO,KAAKwO,YAAmBJ,EAAOI,aAC5ErR,EAAMyM,WAAWwE,EAAOK,eAAqBzO,KAAKyO,YAAmBL,EAAOK,aAC5EtR,EAAMyM,WAAWwE,EAAOM,cAAqB1O,KAAK0O,WAAmBN,EAAOM,cAGhFlN,EAAS,KAAOA,EAEZrE,EAAMyM,WAAWwE,EAAOO,WAAmB3O,KAAKwB,EAAS,SAAoB4M,EAAOO,SACpFxR,EAAMyM,WAAWwE,EAAOQ,UAAmB5O,KAAKwB,EAAS,QAAoB4M,EAAOQ,QACpFzR,EAAMyM,WAAWwE,EAAOS,SAAmB7O,KAAKwB,EAAS,OAAoB4M,EAAOS,OACpF1R,EAAMyM,WAAWwE,EAAOU,kBAAmB9O,KAAKwB,EAAS,gBAAoB4M,EAAOU,iBAGrF9O,MAkCX+O,UAAW,SAAUhR,GACjB,MAAIZ,GAAM6E,SAASjE,IACfiC,KAAKjC,QAAQsE,KAAKP,QAAU/D,EAAQ+D,WAAY,GAAO,GAAO,EAC9D9B,KAAKgP,aAAa,OAAQjR,GAC1BiC,KAAKmO,YAAY,OAAQpQ,GAErB,eAAeT,KAAKS,EAAQqE,MAC5BpC,KAAKjC,QAAQsE,KAAKD,KAAOrE,EAAQqE,KAEX,OAAjBrE,EAAQqE,YACNpC,MAAKjC,QAAQsE,KAAKD,KAGtBpC,MAGP7C,EAAM8R,OAAOlR,IACbiC,KAAKjC,QAAQsE,KAAKP,QAAU/D,EAErBiC,MAGJA,KAAKjC,QAAQsE,MAGxB2M,aAAc,SAAUxN,EAAQzD,GAE5B,IAAK,GAAImR,KAAUnR,GAEXmR,IAAU/R,GAAMyJ,eAAepF,KAE3BrE,EAAM6E,SAASjE,EAAQmR,KAEvBlP,KAAKjC,QAAQyD,GAAQ0N,GAAUzR,EAAMiE,OAAO1B,KAAKjC,QAAQyD,GAAQ0N,OAAenR,EAAQmR,IAEpF/R,EAAM6E,SAAS7E,EAAMyJ,eAAeuI,UAAUD,KAAY,WAAa/R,GAAMyJ,eAAeuI,UAAUD,KACtGlP,KAAKjC,QAAQyD,GAAQ0N,GAAQpN,QAAU/D,EAAQmR,GAAQpN,WAAY,GAAO,GAAQ,IAGjF3E,EAAM8R,OAAOlR,EAAQmR,KAAY/R,EAAM6E,SAAS7E,EAAMyJ,eAAeuI,UAAUD,IACpFlP,KAAKjC,QAAQyD,GAAQ0N,GAAQpN,QAAU/D,EAAQmR,GAEtBhB,SAApBnQ,EAAQmR,KAEblP,KAAKjC,QAAQyD,GAAQ0N,GAAUnR,EAAQmR,MAmCvDxC,SAAU,SAAU3O,GAChB,MAAIZ,GAAM6E,SAASjE,IACfiC,KAAKjC,QAAQqR,KAAKtN,QAAU/D,EAAQ+D,WAAY,GAAO,GAAO,EAC9D9B,KAAKmO,YAAY,OAAQpQ,GACzBiC,KAAKqP,OAAOtR,EAAQsR,QAEhB,qBAAqB/R,KAAKS,EAAQuR,SAClCtP,KAAKjC,QAAQqR,KAAKE,QAAUvR,EAAQuR,QAE/BnS,EAAMuD,SAAS3C,EAAQuR,WAC5BtP,KAAKjC,QAAQqR,KAAKE,QAAUC,KAAKrD,IAAIqD,KAAKC,IAAI,EAAGzR,EAAQuR,SAAU,IAGhEtP,MAGP7C,EAAM8R,OAAOlR,IACbiC,KAAKjC,QAAQqR,KAAKtN,QAAU/D,EAErBiC,MAGJA,KAAKjC,QAAQqR,MAGxBK,UAAW,SAAU3S,EAASqC,EAAO4P,EAAWW,EAAkBC,EAAapP,GAC3E,GAAIqP,IAAU,CAId,MAAMrP,EAAOA,GAAQP,KAAKsB,QAAQqO,IAC9B,MAAQ3P,MAAKjC,QAAQ8R,YACf7P,KAAKjC,QAAQ8R,YAAY/S,EAASqC,EAAOyQ,EAAS5P,KAAM2P,EAAaZ,EAAWW,IAChF,CAGV,IAAII,GAAc9P,KAAKjC,QAAQqR,KAAKE,OAEpC,IAAoB,YAAhBQ,EAA2B,CAC3B,GAEIC,GACAC,EAHA3P,EAAO5C,EAAMwS,UAAUnT,GACvB4M,EAASvM,EAAMqM,YAAYuF,EAAWW,EAI1CrP,GAAKW,GAAK0I,EAAO1I,EACjBX,EAAKY,GAAKyI,EAAOzI,EAEjB8O,EAAc1P,EAAKW,EAAIT,EAAKK,MAAUP,EAAKW,EAAIT,EAAKI,MACpDqP,EAAc3P,EAAKY,EAAIV,EAAKQ,KAAUV,EAAKY,EAAIV,EAAKO,OAEpD8O,EAAUG,GAAcC,EAG5B,GAAIE,GAAWnB,EAAUzN,QAAQoO,EAEjC,IAAoB,WAAhBI,EAA0B,CAC1B,GAAIK,GAAKD,EAAStP,KAAOsP,EAASzP,MAAS,EACvC2P,EAAKF,EAASnP,IAAOmP,EAASrP,OAAS,CAE3C+O,GAAUO,GAAM5P,EAAKK,MAAQuP,GAAM5P,EAAKI,OAASyP,GAAM7P,EAAKQ,KAAOqP,GAAM7P,EAAKO,OAGlF,GAAI3D,EAAMuD,SAASoP,GAAc,CAC7B,GAAIO,GAAgBd,KAAKrD,IAAI,EAAGqD,KAAKC,IAAIjP,EAAKI,MAAQuP,EAASvP,OAAU4O,KAAKrD,IAAI3L,EAAKK,KAAMsP,EAAStP,OAClF2O,KAAKrD,IAAI,EAAGqD,KAAKC,IAAIjP,EAAKO,OAAQoP,EAASpP,QAAUyO,KAAKrD,IAAI3L,EAAKQ,IAAMmP,EAASnP,MAClGuP,EAAeD,GAAeH,EAASzP,MAAQyP,EAASrP,OAE5D+O,GAAUU,GAAgBR,EAO9B,MAJI9P,MAAKjC,QAAQ8R,cACbD,EAAU5P,KAAKjC,QAAQ8R,YAAY/S,EAAS8S,EAAS5P,KAAM2P,EAAaZ,EAAWW,IAGhFE,GAoCXC,YAAa,SAAUU,GACnB,MAAIpT,GAAMyM,WAAW2G,IACjBvQ,KAAKjC,QAAQ8R,YAAcU,EAEpBvQ,MAEK,OAAZuQ,SACOvQ,MAAKjC,QAAQuD,QAEbtB,MAGJA,KAAKjC,QAAQ8R,aAoBxBR,OAAQ,SAAUmB,GACd,MAAI/S,GAAMyD,UAAUsP,IAChBxQ,KAAKjC,QAAQqR,KAAKC,OAASmB,EAEpBxQ,MAIP7C,EAAM2G,YAAY0M,IAClBxQ,KAAKjC,QAAQqR,KAAKC,OAASmB,EAEpBxQ,MAGM,OAAbwQ,SACOxQ,MAAKjC,QAAQqR,KAAKC,OAElBrP,MAGJA,KAAKjC,QAAQqR,KAAKC,QAuC7BoB,UAAW,SAAU1S,GACjB,MAAIZ,GAAM6E,SAASjE,IACfiC,KAAKjC,QAAQ8D,OAAOC,QAAU/D,EAAQ+D,WAAY,GAAO,GAAO,EAChE9B,KAAKgP,aAAa,SAAUjR,GAC5BiC,KAAKmO,YAAY,SAAUpQ,GAEvB,eAAeT,KAAKS,EAAQqE,MAC5BpC,KAAKjC,QAAQ8D,OAAOO,KAAOrE,EAAQqE,KAEb,OAAjBrE,EAAQqE,OACbpC,KAAKjC,QAAQ8D,OAAOO,KAAOjF,EAAMyJ,eAAe/E,OAAOO,MAGvDjF,EAAM8R,OAAOlR,EAAQ2S,UACrB1Q,KAAKjC,QAAQ8D,OAAO6O,OAAS3S,EAAQ2S,QAGlC1Q,MAEP7C,EAAM8R,OAAOlR,IACbiC,KAAKjC,QAAQ8D,OAAOC,QAAU/D,EAEvBiC,MAEJA,KAAKjC,QAAQ8D,QAkBxB8O,aAAc,SAAUH,GACpB,MAAIrT,GAAM8R,OAAOuB,IACbxQ,KAAKjC,QAAQ8D,OAAO6O,OAASF,EAEtBxQ,MAGM,OAAbwQ,SACOxQ,MAAKjC,QAAQ8D,OAAO6O,OAEpB1Q,MAGJA,KAAKjC,QAAQ8D,OAAO6O,QA0B/BE,WAAY,SAAU7S,GAClB,MAAIZ,GAAM6E,SAASjE,IACfiC,KAAKjC,QAAQgB,QAAQ+C,QAAU/D,EAAQ+D,WAAY,GAAO,GAAO,EACjE9B,KAAKgP,aAAa,UAAWjR,GAC7BiC,KAAKmO,YAAY,UAAWpQ,GAErBiC,MAGP7C,EAAM8R,OAAOlR,IACbiC,KAAKjC,QAAQgB,QAAQ+C,QAAU/D,EAExBiC,MAGJA,KAAKjC,QAAQgB,SAuBxB8H,WAAY,SAAU9I,GAQlB,MAPIZ,GAAM6E,SAASjE,GACfA,EAAUN,EAAMiE,QAASmP,SAAU,OAAQ,WAAY9S,GAElDZ,EAAM8R,OAAOlR,KAClBA,GAAY8S,SAAU,OAAQ,UAAW/O,QAAS/D,IAG/CiC,KAAK8Q,WAAW,aAAc/S,IA8DzC6N,KAAM,SAAU7N,GACZ,GAAIkQ,GAAMjO,KAAK8Q,WAAW,OAAQ/S,EAElC,OAAIkQ,KAAQjO,KAAeA,KAEpBiO,EAAI5L,MAGfyO,WAAY,SAAU5B,EAAQnR,GAC1B,GAII5B,GAJA0U,EAAU9S,GAAWZ,EAAM4T,QAAQhT,EAAQ8S,SACrC9S,EAAQ8S,SACP,OAIX,IAAI1T,EAAM6E,SAASjE,IAAYZ,EAAM8R,OAAOlR,GAAU,CAClD,IAAK5B,EAAI,EAAGA,EAAI0U,EAAQnU,OAAQP,IAAK,CACjC,GAAIqF,GAAS,SAASlE,KAAKuT,EAAQ1U,IAAK,SAAW0U,EAAQ1U,EAE3D,IAAKgB,EAAM6E,SAAShC,KAAKjC,QAAQyD,IAAjC,CAEA,GAAIwP,GAAahR,KAAKjC,QAAQyD,GAAQ0N,EAElC/R,GAAM6E,SAASjE,IACfN,EAAMiE,OAAOsP,EAAYjT,GACzBiT,EAAWlP,QAAU/D,EAAQ+D,WAAY,GAAO,GAAO,EAExC,SAAXoN,IACwB,SAApB8B,EAAWC,KACXD,EAAWE,SACP3N,EAAS4N,eAAe1T,EAAMiE,QAC1B0P,OAAQJ,EAAWK,aAAgBrQ,EAAG,EAAGC,EAAG,IAC7C+P,EAAWM,YAGO,WAApBN,EAAWC,KAChBD,EAAWE,QAAUF,EAAWO,QAEP,SAApBP,EAAWC,OAChBD,EAAWE,QAAUF,EAAWQ,OAGhC,iBAAmBzT,KACnBiT,EAAWS,gBAAkB1T,EAAQ2T,kBAIxCvU,EAAM8R,OAAOlR,KAClBiT,EAAWlP,QAAU/D,IAI7B,MAAOiC,MAGX,GAAIiO,MACA0D,GAAc,OAAQ,SAAU,UAEpC,KAAKxV,EAAI,EAAGA,EAAIwV,EAAWjV,OAAQP,IAC3B+S,IAAU/R,GAAMyJ,eAAe+K,EAAWxV,MAC1C8R,EAAI0D,EAAWxV,IAAM6D,KAAKjC,QAAQ4T,EAAWxV,IAAI+S,GAIzD,OAAOjB,IAqDX/P,QAAS,SAAUH,GACf,GAAIkQ,GAAMjO,KAAK8Q,WAAW,UAAW/S,EAErC,OAAIkQ,KAAQjO,KAAeA,KAEpBiO,EAAI5L,MAGfuP,UAAW,SAAU9U,EAASqC,EAAOlC,EAAaU,GAC9C,GAAI6D,GAASxB,KAAKoB,qBAAqBtE,EAASG,EAAaU,EAE7D,OAAIqC,MAAKjC,QAAQ8T,cACN7R,KAAKjC,QAAQ8T,cAAc/U,EAASqC,EAAOqC,EAAQxB,KAAMrC,EAASV,GAGtEuE,GAGXJ,qBAAsBA,EA8BtByQ,cAAe,SAAUtB,GACrB,MAAIpT,GAAMyM,WAAW2G,IACjBvQ,KAAKjC,QAAQ8T,cAAgBtB,EAEtBvQ,MAGK,OAAZuQ,SACOvQ,MAAKjC,QAAQ8T,cAEb7R,MAGJA,KAAKjC,QAAQ8T,eAqBxBvQ,QAAS,SAAoB3D,GAOzB,MANAA,GAAUA,GAAWqC,KAAK2D,SAEtB3D,KAAK+C,WAActF,EAAMyD,UAAUvD,KACnCA,EAAUqC,KAAKmE,SAASoE,cAAcvI,KAAK+C,WAGxC5F,EAAM6L,eAAerL,IAahCmU,YAAa,SAAUvB,GACnB,MAAIpT,GAAMyM,WAAW2G,IACjBvQ,KAAKsB,QAAUiP,EAERvQ,MAGK,OAAZuQ,SACOvQ,MAAKjC,QAAQuD,QAEbtB,MAGJA,KAAKsB,SAchByQ,YAAa,SAAUvB,GACnB,MAAIrT,GAAM8R,OAAOuB,IACbxQ,KAAKjC,QAAQgU,YAAcvB,EAEpBxQ,MAGM,OAAbwQ,SACOxQ,MAAKjC,QAAQgU,YAEb/R,MAGJA,KAAKjC,QAAQgU,aAgBxB7R,eAAgB,SAAUsQ,GACtB,MAAI,wBAAwBlT,KAAKkT,IAC7BxQ,KAAKjC,QAAQmC,eAAiBsQ,EACvBxQ,MAGP7C,EAAM8R,OAAOuB,IACbxQ,KAAKjC,QAAQmC,eAAiBsQ,EAAU,SAAW,QAC5CxQ,MAGJA,KAAKjC,QAAQmC,gBAgBxBwJ,OAAQ,SAAU8G,GACd,MAAIrT,GAAM2G,YAAY0M,IAClBxQ,KAAKjC,QAAQ2L,OAAS8G,EACfxQ,MAEF7C,EAAM6E,SAASwO,IACpBxQ,KAAKjC,QAAQ2L,OAAS8G,EACfxQ,MAGJA,KAAKjC,QAAQ2L,QAaxBsI,YAAa,SAAUxB,GACnB,MAAiB,SAAbA,GAAoC,WAAbA,GACvBxQ,KAAKjC,QAAQiU,YAAcxB,EAEpBxQ,MAGJA,KAAKjC,QAAQiU,aAwCxBlG,SAAU,SAAU/N,GAChB,IAAKZ,EAAM6E,SAASjE,GAChB,MAAOiC,MAAK8Q,WAAW,WAAY/S,EAMvC,KAAK,GAFDkQ,GADA4C,GAAW,OAAQ,SAAU,WAGxB1U,EAAI,EAAGA,EAAI0U,EAAQnU,OAAQP,IAAK,CACrC,GAAIqF,GAASqP,EAAQ1U,EAErB,IAAIqF,IAAUzD,GAAS,CACnB,GAAIoR,GAAY1R,EAAMiE,QACdmP,SAAUrP,GACVyQ,YAAalU,EAAQyD,IACtBzD,EAEPkQ,GAAMjO,KAAK8Q,WAAW,WAAY3B,IAI1C,MAAOlB,IAYXjL,QAAS,WACL,MAAOhD,MAAKmE,UAGhBA,SAAUhH,EAAM+G,SAiBhBoH,WAAY,SAAUkF,GAClB,MAAIrT,GAAM2G,YAAY0M,IAClBxQ,KAAKjC,QAAQuN,WAAakF,EACnBxQ,MAGPvC,EAAMyD,UAAUsP,IAChBxQ,KAAKjC,QAAQuN,WAAakF,EACnBxQ,MAGJA,KAAKjC,QAAQuN,YAkBxBE,UAAW,SAAUgF,GACjB,MAAIrT,GAAM2G,YAAY0M,IAClBxQ,KAAKjC,QAAQyN,UAAYgF,EAClBxQ,MAGPvC,EAAMyD,UAAUsP,IAChBxQ,KAAKjC,QAAQyN,UAAYgF,EAClBxQ,MAGJA,KAAKjC,QAAQyN,WAYxB7N,QAAS,WACL,MAAOqC,MAAK2D,UAahBuO,KAAM,SAAUC,GACZ,IAAMA,IAAUA,EAAO3S,OAAUrC,EAAM0B,SAAS1B,EAAM4K,WAAYoK,EAAO3S,MACrE,MAAOQ,KAGX,IAAIoD,GACAjH,EACAe,EACAkV,EAAU,KAAOD,EAAO3S,KACxB6S,EAAW,EAGf,IAAIF,EAAO3S,OAAQQ,MAAK4D,SAGpB,IAFAR,EAAYpD,KAAK4D,SAASuO,EAAO3S,MAE5BrD,EAAI,EAAGe,EAAMkG,EAAU1G,OAAYQ,EAAJf,IAAYgW,EAAOG,4BAA6BnW,IAChFkW,EAAWjP,EAAUjH,GAAG8B,KACxBmF,EAAUjH,GAAGgW,EAWrB,IANIhV,EAAMyM,WAAW5J,KAAKoS,MACtBC,EAAWrS,KAAKoS,GAASnU,KACzB+B,KAAKoS,GAASD,IAIdA,EAAO3S,OAAQrC,GAAM6K,eAAiB5E,EAAYjG,EAAM6K,aAAamK,EAAO3S,OAE5E,IAAKrD,EAAI,EAAGe,EAAMkG,EAAU1G,OAAYQ,EAAJf,IAAYgW,EAAOG,4BAA6BnW,IAChFkW,EAAWjP,EAAUjH,GAAG8B,KACxBmF,EAAUjH,GAAGgW,EAIrB,OAAOnS,OAcXuS,GAAI,SAAUxV,EAAWyV,EAAU/P,GAC/B,GAAItG,EAMJ,IAJIgB,EAAMmL,SAASvL,IAAwC,KAA1BA,EAAU0V,OAAO,OAC9C1V,EAAYA,EAAU2V,OAAOC,MAAM,OAGnCxV,EAAM4T,QAAQhU,GAAY,CAC1B,IAAKZ,EAAI,EAAGA,EAAIY,EAAUL,OAAQP,IAC9B6D,KAAKuS,GAAGxV,EAAUZ,GAAIqW,EAAU/P,EAGpC,OAAOzC,MAGX,GAAI7C,EAAM6E,SAASjF,GAAY,CAC3B,IAAK,GAAI8F,KAAQ9F,GACbiD,KAAKuS,GAAG1P,EAAM9F,EAAU8F,GAAO2P,EAGnC,OAAOxS,MAUX,GAPkB,UAAdjD,IACAA,EAAYI,EAAM2K,YAItBrF,EAAaA,GAAY,GAAM,EAE3BtF,EAAM0B,SAAS1B,EAAM4K,WAAYhL,GAE3BA,IAAaiD,MAAK4D,SAIpB5D,KAAK4D,SAAS7G,GAAWgI,KAAKyN,GAH9BxS,KAAK4D,SAAS7G,IAAcyV,OAO/B,IAAIxS,KAAK+C,SAAU,CACpB,IAAK5F,EAAMyF,gBAAgB7F,GAQvB,IAPAI,EAAMyF,gBAAgB7F,IAClB+F,aACAG,YACAG,cAICjH,EAAI,EAAGA,EAAIgB,EAAM0H,UAAUnI,OAAQP,IACpCkI,EAAOC,IAAInH,EAAM0H,UAAU1I,GAAIY,EAAWyF,GAC1C6B,EAAOC,IAAInH,EAAM0H,UAAU1I,GAAIY,EAAWuG,GAAoB,EAItE,IACIsJ,GADAjK,EAAYxF,EAAMyF,gBAAgB7F,EAGtC,KAAK6P,EAAQjK,EAAUG,UAAUpG,OAAS,EAAGkQ,GAAS,IAC9CjK,EAAUG,UAAU8J,KAAW5M,KAAK+C,UACjCJ,EAAUM,SAAS2J,KAAW5M,KAAKmE,UAFWyI,KAO3C,KAAVA,IACAA,EAAQjK,EAAUG,UAAUpG,OAE5BiG,EAAUG,UAAUiC,KAAK/E,KAAK+C,UAC9BJ,EAAUM,SAAU8B,KAAK/E,KAAKmE,UAC9BxB,EAAUS,UAAU2B,UAIxBpC,EAAUS,UAAUwJ,GAAO7H,MAAMyN,EAAU/P,QAG3C4B,GAAOC,IAAItE,KAAK2D,SAAU5G,EAAWyV,EAAU/P,EAGnD,OAAOzC,OAcX4S,IAAK,SAAU7V,EAAWyV,EAAU/P,GAChC,GAAItG,EAMJ,IAJIgB,EAAMmL,SAASvL,IAAwC,KAA1BA,EAAU0V,OAAO,OAC9C1V,EAAYA,EAAU2V,OAAOC,MAAM,OAGnCxV,EAAM4T,QAAQhU,GAAY,CAC1B,IAAKZ,EAAI,EAAGA,EAAIY,EAAUL,OAAQP,IAC9B6D,KAAK4S,IAAI7V,EAAUZ,GAAIqW,EAAU/P,EAGrC,OAAOzC,MAGX,GAAI7C,EAAM6E,SAASjF,GAAY,CAC3B,IAAK,GAAI8F,KAAQ9F,GACbiD,KAAK4S,IAAI/P,EAAM9F,EAAU8F,GAAO2P,EAGpC,OAAOxS,MAGX,GAAI6S,GACAjG,EAAQ,EAUZ,IAPAnK,EAAaA,GAAY,GAAM,EAEb,UAAd1F,IACAA,EAAYI,EAAM2K,YAIlB3K,EAAM0B,SAAS1B,EAAM4K,WAAYhL,GACjC8V,EAAY7S,KAAK4D,SAAS7G,GAEtB8V,GAA8D,MAAhDjG,EAAQzP,EAAM2V,QAAQD,EAAWL,KAC/CxS,KAAK4D,SAAS7G,GAAWgW,OAAOnG,EAAO,OAI1C,IAAI5M,KAAK+C,SAAU,CACpB,GAAIJ,GAAYxF,EAAMyF,gBAAgB7F,GAClCiW,GAAa,CAEjB,KAAKrQ,EAAa,MAAO3C,KAGzB,KAAK4M,EAAQjK,EAAUG,UAAUpG,OAAS,EAAGkQ,GAAS,EAAGA,IAErD,GAAIjK,EAAUG,UAAU8J,KAAW5M,KAAK+C,UACjCJ,EAAUM,SAAS2J,KAAW5M,KAAKmE,SAAU,CAEhD,GAAIf,GAAYT,EAAUS,UAAUwJ,EAGpC,KAAKzQ,EAAIiH,EAAU1G,OAAS,EAAGP,GAAK,EAAGA,IAAK,CACxC,GAAI8W,GAAK7P,EAAUjH,GAAG,GAClB+W,EAAS9P,EAAUjH,GAAG,EAG1B,IAAI8W,IAAOT,GAAYU,IAAWzQ,EAAY,CAE1CW,EAAU2P,OAAO5W,EAAG,GAIfiH,EAAU1G,SACXiG,EAAUG,UAAUiQ,OAAOnG,EAAO,GAClCjK,EAAUM,SAAU8P,OAAOnG,EAAO,GAClCjK,EAAUS,UAAU2P,OAAOnG,EAAO,GAGlCvI,EAAO8O,OAAOnT,KAAKmE,SAAUpH,EAAWyF,GACxC6B,EAAO8O,OAAOnT,KAAKmE,SAAUpH,EAAWuG,GAAoB,GAGvDX,EAAUG,UAAUpG,SACrBS,EAAMyF,gBAAgB7F,GAAa,OAK3CiW,GAAa,CACb,QAIR,GAAIA,EAAc,WAM1B3O,GAAO8O,OAAOnT,KAAK2D,SAAU5G,EAAWyV,EAAU/P,EAGtD,OAAOzC,OAWXgF,IAAK,SAAUjH,GACNZ,EAAM6E,SAASjE,KAChBA,MAGJiC,KAAKjC,QAAUN,EAAMiE,UAAWvE,EAAMyJ,eAAewM,KAErD,IAAIjX,GACA0U,GAAW,OAAQ,OAAQ,SAAU,WACrCwC,GAAW,YAAa,WAAY,YAAa,cACjDC,EAAa7V,EAAMiE,OAAOjE,EAAMiE,UAAWvE,EAAMyJ,eAAeuI,WAAYpR,EAAQyD,OAExF,KAAKrF,EAAI,EAAGA,EAAI0U,EAAQnU,OAAQP,IAAK,CACjC,GAAIqF,GAASqP,EAAQ1U,EAErB6D,MAAKjC,QAAQyD,GAAU/D,EAAMiE,UAAWvE,EAAMyJ,eAAepF,IAE7DxB,KAAKgP,aAAaxN,EAAQ8R,GAE1BtT,KAAKqT,EAAQlX,IAAI4B,EAAQyD,IAG7B,GAAI+R,IACI,SAAU,gBAAiB,YAAa,cACxC,cAAe,aAAc,SAAU,iBACvC,cAGR,KAAKpX,EAAI,EAAGe,EAAMqW,EAAS7W,OAAYQ,EAAJf,EAASA,IAAK,CAC7C,GAAIqX,GAAUD,EAASpX,EAEvB6D,MAAKjC,QAAQyV,GAAWrW,EAAMyJ,eAAewM,KAAKI,GAE9CA,IAAWzV,IACXiC,KAAKwT,GAASzV,EAAQyV,IAI9B,MAAOxT,OAYXyT,MAAO,WAGH,GAFApP,EAAO8O,OAAOnT,KAAK2D,SAAU,OAExBxG,EAAMmL,SAAStI,KAAK+C,UAQrB,IAAK,GAAIvD,KAAQrC,GAAMyF,gBAGnB,IAAK,GAFDD,GAAYxF,EAAMyF,gBAAgBpD,GAE7BrD,EAAI,EAAGA,EAAIwG,EAAUG,UAAUpG,OAAQP,IAAK,CAC7CwG,EAAUG,UAAU3G,KAAO6D,KAAK+C,UAC7BJ,EAAUM,SAAS9G,KAAO6D,KAAKmE,WAElCxB,EAAUG,UAAUiQ,OAAO5W,EAAG,GAC9BwG,EAAUM,SAAU8P,OAAO5W,EAAG,GAC9BwG,EAAUS,UAAU2P,OAAO5W,EAAG,GAGzBwG,EAAUG,UAAUpG,SACrBS,EAAMyF,gBAAgBpD,GAAQ,OAItC6E,EAAO8O,OAAOnT,KAAKmE,SAAU3E,EAAMgD,GACnC6B,EAAO8O,OAAOnT,KAAKmE,SAAU3E,EAAM8D,GAAoB,EAEvD,WA3BRe,GAAO8O,OAAOnT,KAAM,OAChBA,KAAKjC,QAAQgU,cACb/R,KAAK2D,SAAS+P,MAAMC,OAAS,GAkCrC,OAJA3T,MAAK0M,UAAS,GAEdvP,EAAMqG,cAAcuP,OAAO5V,EAAM2V,QAAQ3V,EAAMqG,cAAexD,MAAO,GAE9DuD,IAIfG,EAAayE,UAAUyD,KAAOnO,EAAMmW,SAASlQ,EAAayE,UAAUyD,KAC/D,iHACLlI,EAAayE,UAAU2D,SAAWrO,EAAMmW,SAASlQ,EAAayE,UAAU2D,SACnE,0HACLpI,EAAayE,UAAUjK,QAAUT,EAAMmW,SAASlQ,EAAayE,UAAUjK,QAClE,kHACLwF,EAAayE,UAAUtB,WAAapJ,EAAMmW,SAASlQ,EAAayE,UAAUtB,WACrE,4HACLnD,EAAayE,UAAUwI,aAAelT,EAAMmW,SAASlQ,EAAayE,UAAUwI,aACvE,yFAULpN,EAASsQ,MAAQ,SAASlW,EAASI,GAC/B,MAAmF,KAA5EZ,EAAMqG,cAAcsK,eAAenQ,EAASI,GAAWA,EAAQiF,UAe1EO,EAASgP,GAAK,SAAU/S,EAAMgT,EAAU/P,GAKpC,GAJItF,EAAMmL,SAAS9I,IAA8B,KAArBA,EAAKiT,OAAO,OACpCjT,EAAOA,EAAKkT,OAAOC,MAAM,OAGzBxV,EAAM4T,QAAQvR,GAAO,CACrB,IAAK,GAAIrD,GAAI,EAAGA,EAAIqD,EAAK9C,OAAQP,IAC7BoH,EAASgP,GAAG/S,EAAKrD,GAAIqW,EAAU/P,EAGnC,OAAOc,GAGX,GAAIpG,EAAM6E,SAASxC,GAAO,CACtB,IAAK,GAAIqD,KAAQrD,GACb+D,EAASgP,GAAG1P,EAAMrD,EAAKqD,GAAO2P,EAGlC,OAAOjP,GAkBX,MAdIpG,GAAM0B,SAAS1B,EAAM4K,WAAYvI,GAE5BrC,EAAM6K,aAAaxI,GAIpBrC,EAAM6K,aAAaxI,GAAMuF,KAAKyN,GAH9BrV,EAAM6K,aAAaxI,IAASgT,GAQhCnO,EAAOC,IAAInH,EAAM+G,SAAU1E,EAAMgT,EAAU/P,GAGxCc,GAcXA,EAASqP,IAAM,SAAUpT,EAAMgT,EAAU/P,GAKrC,GAJItF,EAAMmL,SAAS9I,IAA8B,KAArBA,EAAKiT,OAAO,OACpCjT,EAAOA,EAAKkT,OAAOC,MAAM,OAGzBxV,EAAM4T,QAAQvR,GAAO,CACrB,IAAK,GAAIrD,GAAI,EAAGA,EAAIqD,EAAK9C,OAAQP,IAC7BoH,EAASqP,IAAIpT,EAAKrD,GAAIqW,EAAU/P,EAGpC,OAAOc,GAGX,GAAIpG,EAAM6E,SAASxC,GAAO,CACtB,IAAK,GAAIqD,KAAQrD,GACb+D,EAASqP,IAAI/P,EAAMrD,EAAKqD,GAAO2P,EAGnC,OAAOjP,GAGX,GAAKpG,EAAM0B,SAAS1B,EAAM4K,WAAYvI,GAGjC,CACD,GAAIoN,EAEApN,KAAQrC,GAAM6K,cACqD,MAA/D4E,EAAQzP,EAAM2V,QAAQ3V,EAAM6K,aAAaxI,GAAOgT,KACpDrV,EAAM6K,aAAaxI,GAAMuT,OAAOnG,EAAO,OAP3CvI,GAAO8O,OAAOhW,EAAM+G,SAAU1E,EAAMgT,EAAU/P,EAWlD,OAAOc,IAcXA,EAASuQ,eAAiBrW,EAAMmW,SAAS,SAAUpD,GAC/C,MAAiB,QAAbA,GAAkCtC,SAAbsC,GACrBrT,EAAMyE,gBAAgBS,KAAOmO,EAEtBjN,GAEJpG,EAAMyE,gBAAgBS,MAC9B,mEAaHkB,EAASwQ,eAAiBtW,EAAMmW,SAAS,SAAUpD,GAC/C,MAAiB,QAAbA,GAAkCtC,SAAbsC,GACrBrT,EAAMyE,gBAAgBC,OAAS2O,EAExBjN,GAEJpG,EAAMyE,gBAAgBC,QAC9B,mEAaH0B,EAASyQ,gBAAkBvW,EAAMmW,SAAS,SAAUpD,GAChD,MAAiB,QAAbA,GAAkCtC,SAAbsC,GACrBrT,EAAMyE,gBAAgB7C,QAAUyR,EAEzBjN,GAEJpG,EAAMyE,gBAAgB7C,SAC9B,oEAEHwE,EAASwE,WAAa5K,EAAM4K,WAS5BxE,EAAS0Q,MAAQ,WACb,GAAIhX,GAAcE,EAAMC,aAAa,IAAM,GAAIwB,EAE/C,QACIxB,aAAwBD,EAAMC,aAC9BU,OAAwBb,EAAYa,OACpCwE,SAAwBrF,EAAYqF,SACpCC,SAAwBtF,EAAYsF,SACpC2R,UAAwBjX,EAAYiX,UACpClW,SAAwBf,EAAYe,SACpCmW,QAAwBlX,EAAYkX,QACpCC,cAAwBnX,EAAYmX,cAEpCC,WAAwBpX,EAAYoX,WACpCC,YAAwBrX,EAAYqX,YAEpCxV,WAAwB7B,EAAY6B,WACpCT,SAAwBpB,EAAYoB,SACpCE,WAAwBpB,EAAMiG,UAAU7E,WACxCD,cAAwBnB,EAAMiG,UAAU9E,cACxCiW,cAAwBpX,EAAMiG,UAAUmR,cAExC3I,KAAwB3O,EAAYuX,WACpC1I,SAAwB7O,EAAYwX,eACpCvW,QAAwBjB,EAAYW,cAEpC8W,SAAwBzX,EAAY0X,UAAU,GAC9CC,UAAwB3X,EAAY2X,UACpCC,YAAwB5X,EAAY4X,YACpCC,UAAwB7X,EAAY6X,UAEpCpR,aAAwBA,EACxBF,cAAwBrG,EAAMqG,cAC9B1D,cAAwB7C,EAAY6C,cACpC8G,eAAwBzJ,EAAMyJ,eAC9BxF,qBAAwBA,EAExB6F,cAAwB9J,EAAM8J,cAC9B8N,SAAwB5X,EAAMiG,UAAU2R,SACxCC,WAAwB7X,EAAMiG,UAAU4R,WACxCC,YAAwB9X,EAAMiG,UAAU6R,YACxCjP,UAAwB7I,EAAMiG,UAAU4C,UACxCvB,YAAwBtH,EAAMiG,UAAUqB,YACxCoB,YAAwB1I,EAAMiG,UAAUyC,YACxClB,aAAwBxH,EAAMiG,UAAUuB,aAExCoD,WAAwB5K,EAAM4K,WAE9B1D,OAAwBA,EACxB2D,aAAwB7K,EAAM6K,aAC9BpF,gBAAwBzF,EAAMyF,kBAKtCW,EAAS2R,gBAAmBzX,EAAM0X,aAClC5R,EAAS6R,aAAmB3X,EAAM4X,UAClC9R,EAAS+R,iBAAmB7X,EAAM8X,cAClChS,EAASiS,cAAmB/X,EAAMgY,WAElClS,EAASyF,eAAmB7L,EAAM6L,eAClCzF,EAASL,gBAAmB/F,EAAM+F,gBAClCK,EAASoG,QAAmBxM,EAAMwM,QAalCpG,EAAS/C,OAAS,SAAUkV,GACxB,MAAIvY,GAAMuD,SAASgV,IACfvY,EAAMqD,OAASkV,EAERnS,GAEJpG,EAAMqD,QASjB+C,EAAS7E,cAAgB,WACrB,MAAOD,GAAQC,eASnB6E,EAAS5E,qBAAuB,WAC5B,MAAOF,GAAQE,sBAYnB4E,EAASoS,KAAO,SAAUxW,GACtB,IAAK,GAAIhD,GAAIgB,EAAMC,aAAaV,OAAS,EAAGP,EAAI,EAAGA,IAC/CgB,EAAMC,aAAajB,GAAGwZ,KAAKxW,EAG/B,OAAOoE,IAcXA,EAASoD,YAAc,SAAU6J,GAC7B,MAAIrT,GAAM8R,OAAOuB,IAKbrT,EAAMwJ,YAAc6J,EAEbjN,GAEJpG,EAAMwJ,aAYjBpD,EAASuD,qBAAuB,SAAU0J,GACtC,MAAIrT,GAAMuD,SAAS8P,IACfrT,EAAM2J,qBAAuB0J,EAEtBxQ,MAGJ7C,EAAM2J,sBAejBvD,EAASwD,gBAAkB,SAAUyJ,GACjC,MAAIrT,GAAMuD,SAAS8P,IACfrT,EAAM4J,gBAAkByJ,EAEjBxQ,MAGJ7C,EAAM4J,iBAGjBxD,EAAS4N,eAAiB,SAAUG,GAChC,MAAO,UAAUtQ,EAAGC,GAChB,GAAI2U,GAAU,EACVC,EAAU,CAEV1Y,GAAM6E,SAASsP,EAAKF,UACpBwE,EAAUtE,EAAKF,OAAOpQ,EACtB6U,EAAUvE,EAAKF,OAAOnQ,EAG1B,IAAI6U,GAAQvG,KAAKwG,OAAO/U,EAAI4U,GAAWtE,EAAKtQ,GACxCgV,EAAQzG,KAAKwG,OAAO9U,EAAI4U,GAAWvE,EAAKrQ,GAExCgV,EAAOH,EAAQxE,EAAKtQ,EAAI4U,EACxBM,EAAOF,EAAQ1E,EAAKrQ,EAAI4U,CAE5B,QACI7U,EAAGiV,EACHhV,EAAGiV,EACHC,MAAO7E,EAAK6E,SAiGxBrR,EAAiB3H,EAAM+G,UAEvB/G,EAAMoG,SAAWA,EACjBpG,EAAMuG,aAAeA,EACrBvG,EAAMyB,YAAcA,EACpBzB,EAAMyQ,cAAgBA,EAEtBhR,EAAOJ,QAAU+G,KAElB6S,kBAAkB,EAAEC,gBAAgB,EAAEC,eAAe,EAAEC,mBAAmB,EAAEC,UAAU,EAAEC,UAAU,GAAGC,iBAAiB,GAAGC,iBAAiB,KAAKC,GAAG,SAAS1a,EAAQU,EAAOJ,GAC7K,YAKA,SAASoR,GAAe3Q,EAAakC,EAAOqC,EAAQqV,EAAOlZ,EAASmZ,GAChE,GAAIC,GACA1W,EACAvC,EAAcb,EAAYa,OAC1B0W,EAAcvX,EAAYuX,WAC1BC,EAAkBxX,EAAYwX,eAC9BpW,EAAcpB,EAAYoB,SAC1B2T,GAAelU,GAAUA,EAAOC,SAAWZ,EAAMyJ,gBAAgBoL,YACjEgF,EAAchF,EAAc,IAC5BiF,EAAcjF,EAAc,IAC5BjU,EAAcD,EAAQA,EAAOC,QAASZ,EAAMyJ,eAC5C8C,EAAcvM,EAAMqM,YAAY1L,EAAQH,GACxCuZ,EAAwB,UAAVL,EACdM,EAAwB,QAAVN,EACdO,EAAcF,EAAUja,EAAYqX,YAAcrX,EAAY0E,SAElEhE,GAAUA,GAAWV,EAAYU,QAEjC0C,EAAS5C,EAAMiE,UAAW0V,EAAO/W,MACjC0W,EAAStZ,EAAMiE,UAAW0V,EAAOL,QAEjC1W,EAAKW,GAAK0I,EAAO1I,EACjBX,EAAKY,GAAKyI,EAAOzI,EAEjB8V,EAAO/V,GAAK0I,EAAO1I,EACnB+V,EAAO9V,GAAKyI,EAAOzI,CAEnB,IAAIwQ,GAAiB1T,EAAQyD,GAAQoK,MAAQ7N,EAAQyD,GAAQoK,KAAK6F,gBAE9DtU,EAAMwO,UAAU7N,EAAQ0D,IAAa0V,GAAYzF,GAAkBA,EAAe/U,SAClFsD,KAAK4L,MACDuK,MAAS3B,EAAW2B,MACpBkB,OAAS7C,EAAW6C,OACpBrW,EAASwT,EAAW8C,SACpBrW,EAASuT,EAAW+C,SACpBC,MAAShD,EAAWgD,MACpBC,MAASjD,EAAWiD,MACpBC,GAASlD,EAAWkD,GACpBC,GAASnD,EAAWmD,IAGpBnD,EAAW6C,SACXhX,EAAKW,GAAKwT,EAAWkD,GACrBrX,EAAKY,GAAKuT,EAAWmD,GACrBZ,EAAO/V,GAAKwT,EAAWkD,GACvBX,EAAO9V,GAAKuT,EAAWmD,MAI3Bxa,EAAM0O,cAAc/N,EAAQ0D,IAAa0V,GAAYnZ,EAAQyD,GAAQsK,SAAS8L,cAAgBnD,EAAeoD,aAC7GxX,EAAKW,GAAKyT,EAAeiD,GACzBrX,EAAKY,GAAKwT,EAAekD,GACzBZ,EAAO/V,GAAKyT,EAAeiD,GAC3BX,EAAO9V,GAAKwT,EAAekD,GAE3B3X,KAAK8L,UACD4L,GAAIjD,EAAeiD,GACnBC,GAAIlD,EAAekD,KAI3B3X,KAAK8X,MAAYzX,EAAKW,EACtBhB,KAAK+X,MAAY1X,EAAKY,EACtBjB,KAAKgY,QAAYjB,EAAO/V,EACxBhB,KAAKiY,QAAYlB,EAAO9V,EAExBjB,KAAKkY,GAAYjb,EAAYqX,YAAYjU,KAAKW,EAAI0I,EAAO1I,EACzDhB,KAAKmY,GAAYlb,EAAYqX,YAAYjU,KAAKY,EAAIyI,EAAOzI,EACzDjB,KAAKoY,SAAYnb,EAAYqX,YAAYyC,OAAO/V,EAAI0I,EAAO1I,EAC3DhB,KAAKqY,SAAYpb,EAAYqX,YAAYyC,OAAO9V,EAAIyI,EAAOzI,EAC3DjB,KAAKsY,QAAYnZ,EAAMmZ,QACvBtY,KAAKuY,OAAYpZ,EAAMoZ,OACvBvY,KAAKwY,SAAYrZ,EAAMqZ,SACvBxY,KAAKyY,QAAYtZ,EAAMsZ,QACvBzY,KAAK0Y,OAAYvZ,EAAMuZ,OACvB1Y,KAAKlC,OAAYH,EACjBqC,KAAK2Y,GAAY1b,EAAY0X,UAAU,GACvC3U,KAAKR,KAAYgC,GAAUqV,GAAS,IAEpC7W,KAAK/C,YAAcA,EACnB+C,KAAKyJ,aAAe3L,CAEpB,IAAIF,GAAgBX,EAAYW,aAqGhC,IAnGIA,EAAcC,SACdmC,KAAK4Y,OAAS,WAGd9B,IACA9W,KAAK6Y,cAAgB/B,GAIrBK,EACoB,WAAhBnF,GACAhS,KAAK0X,GAAKX,EAAO/V,EAAI/D,EAAYqX,YAAYyC,OAAO/V,EACpDhB,KAAK2X,GAAKZ,EAAO9V,EAAIhE,EAAYqX,YAAYyC,OAAO9V,IAGpDjB,KAAK0X,GAAKrX,EAAKW,EAAI/D,EAAYqX,YAAYjU,KAAKW,EAChDhB,KAAK2X,GAAKtX,EAAKY,EAAIhE,EAAYqX,YAAYjU,KAAKY,GAG/CiW,GACLlX,KAAK0X,GAAK,EACV1X,KAAK2X,GAAK,GAGK,iBAAVd,GACL7W,KAAK0X,GAAKza,EAAY6X,UAAU4C,GAChC1X,KAAK2X,GAAK1a,EAAY6X,UAAU6C,IAGZ,WAAhB3F,GACAhS,KAAK0X,GAAKX,EAAO/V,EAAI/D,EAAY6X,UAAUkD,QAC3ChY,KAAK2X,GAAKZ,EAAO9V,EAAIhE,EAAY6X,UAAUmD,UAG3CjY,KAAK0X,GAAKrX,EAAKW,EAAI/D,EAAY6X,UAAUgD,MACzC9X,KAAK2X,GAAKtX,EAAKY,EAAIhE,EAAY6X,UAAUiD,OAG7C9a,EAAY6X,WAA8C,YAAjC7X,EAAY6X,UAAU8D,SAC3Chb,EAAcC,QACfE,EAAQyD,GAAQtD,SAAWH,EAAQyD,GAAQtD,QAAQ4a,kBAEtDlb,EAAcmb,UAAY/Y,KAAK0X,GAC/B9Z,EAAcob,UAAYhZ,KAAK2X,GAE/B3X,KAAK0X,GAAK1X,KAAK2X,GAAK,GAGT,WAAXnW,GAAuBvE,EAAYwE,WAC/B1D,EAAQ8D,OAAO6O,QACgB,MAA3BzT,EAAYwE,WACZzB,KAAK0X,GAAK1X,KAAK2X,GAGf3X,KAAK2X,GAAK3X,KAAK0X,GAEnB1X,KAAKiZ,KAAO,OAGZjZ,KAAKiZ,KAAOhc,EAAYwE,WAEO,MAA3BxE,EAAYwE,WACZzB,KAAK2X,GAAK,EAEsB,MAA3B1a,EAAYwE,aACjBzB,KAAK0X,GAAK,IAIF,YAAXlW,IACLxB,KAAKkZ,SAAW7a,EAAS,GAAIA,EAAS,IAElC6Y,GACAlX,KAAKmZ,SAAW1b,EAAM8X,cAAclX,EAAU2T,GAC9ChS,KAAKoZ,IAAW3b,EAAM4X,UAAUhX,GAChC2B,KAAKqZ,MAAW,EAChBrZ,KAAKsZ,GAAW,EAChBtZ,KAAKuZ,MAAW9b,EAAMgY,WAAWpX,EAAU6P,OAAW8D,GACtDhS,KAAKwZ,GAAW,GAEXrC,GAAUhY,YAAiByO,IAChC5N,KAAKmZ,SAAWlc,EAAY6X,UAAUqE,SACtCnZ,KAAKoZ,IAAWnc,EAAY6X,UAAUsE,IACtCpZ,KAAKqZ,MAAWpc,EAAY6X,UAAUuE,MACtCrZ,KAAKsZ,GAAWtZ,KAAKqZ,MAAQ,EAC7BrZ,KAAKuZ,MAAWtc,EAAY6X,UAAUyE,MACtCvZ,KAAKwZ,GAAWxZ,KAAKuZ,MAAQtc,EAAY8B,QAAQ0a,aAGjDzZ,KAAKmZ,SAAW1b,EAAM8X,cAAclX,EAAU2T,GAC9ChS,KAAKoZ,IAAW3b,EAAM4X,UAAUhX,GAChC2B,KAAKqZ,MAAWrZ,KAAKmZ,SAAWlc,EAAY8B,QAAQ2a,cACpD1Z,KAAKuZ,MAAW9b,EAAMgY,WAAWpX,EAAUpB,EAAY8B,QAAQ4a,UAAW3H,GAE1EhS,KAAKsZ,GAAKtZ,KAAKqZ,MAAQpc,EAAY8B,QAAQ6a,UAC3C5Z,KAAKwZ,GAAKxZ,KAAKuZ,MAAQtc,EAAY8B,QAAQ4a,YAI/CzC,EACAlX,KAAK6Z,UAAY5c,EAAY0X,UAAU,GACvC3U,KAAK8Z,GAAY,EACjB9Z,KAAK+Z,SAAY,EACjB/Z,KAAKga,MAAY,EACjBha,KAAKia,UAAY,EACjBja,KAAKka,UAAY,MAEhB,IAAc,iBAAVrD,EACL7W,KAAK6Z,UAAY5c,EAAY6X,UAAU+E,UACvC7Z,KAAK8Z,GAAY7c,EAAY6X,UAAUgF,GACvC9Z,KAAK+Z,SAAY9c,EAAY6X,UAAUiF,SACvC/Z,KAAKga,MAAY/c,EAAY6X,UAAUkF,MACvCha,KAAKia,UAAYhd,EAAY6X,UAAUmF,UACvCja,KAAKka,UAAYjd,EAAY6X,UAAUoF,cAOvC,IAJAla,KAAK6Z,WAAY,GAAIna,OAAOC,UAC5BK,KAAK8Z,GAAY9Z,KAAK6Z,UAAY5c,EAAY6X,UAAU+E,UACxD7Z,KAAK+Z,SAAY/Z,KAAK6Z,UAAY5c,EAAY0X,UAAU,GAEpDxV,YAAiByO,GAAe,CAChC,GAAI8J,GAAK1X,KAAKgX,GAAW/Z,EAAY6X,UAAUkC,GAC3CW,EAAK3X,KAAKiX,GAAWha,EAAY6X,UAAUmC,GAC3C6C,EAAK9Z,KAAK8Z,GAAK,GAEnB9Z,MAAKga,MAAQvc,EAAM0c,MAAMzC,EAAIC,GAAMmC,EACnC9Z,KAAKia,UAAYvC,EAAKoC,EACtB9Z,KAAKka,UAAYvC,EAAKmC,MAKtB9Z,MAAKga,MAAQ/c,EAAYmd,aAAapI,GAAagI,MACnDha,KAAKia,UAAYhd,EAAYmd,aAAapI,GAAaqI,GACvDra,KAAKka,UAAYjd,EAAYmd,aAAapI,GAAasI,EAI/D,KAAKnD,GAAoB,iBAAVN,IACR5Z,EAAY6X,UAAUkF,MAAQ,KAAOha,KAAK6Z,UAAY5c,EAAY6X,UAAU+E,UAAY,IAAK,CAEhG,GAAIN,GAAQ,IAAMhK,KAAKgL,MAAMtd,EAAY6X,UAAUoF,UAAWjd,EAAY6X,UAAUmF,WAAa1K,KAAKiL,GAClGlL,EAAU,IAEF,GAARiK,IACAA,GAAS,IAGb,IAAI3Y,GAAwB2Y,GAAjB,IAAMjK,GAA4B,IAAMA,EAAdiK,EACjC/T,EAAwB+T,GAAjB,IAAMjK,GAA4B,IAAMA,EAAdiK,EAEjC5Y,GAASC,IAA0B2Y,GAAjB,IAAMjK,GAA6B,GAAKA,EAAdiK,GAC5C/U,GAASgB,GAA0B+T,GAAhB,GAAKjK,GAA4B,IAAMA,EAAdiK,CAEhDvZ,MAAKya,OACDjV,GAAOA,EACPhB,KAAOA,EACP5D,KAAOA,EACPD,MAAOA,EACP4Y,MAAOA,EACPS,MAAO/c,EAAY6X,UAAUkF,MAC7BU,UACI1Z,EAAG/D,EAAY6X,UAAUmF,UACzBhZ,EAAGhE,EAAY6X,UAAUoF,aA1PzC,GAAI/c,GAAQjB,EAAQ,WAChBuB,EAAQvB,EAAQ,UA+PpB0R,GAAczF,WACVjI,eAAgBzC,EAAMkd,MACtBC,yBAA0B,WACtB5a,KAAKsS,4BAA8BtS,KAAK6a,oBAAqB,GAEjEC,gBAAiB,WACb9a,KAAK6a,oBAAqB,IAIlCje,EAAOJ,QAAUoR,IAEd4I,UAAU,EAAEC,UAAU,KAAKsE,GAAG,SAAS7e,EAAQU,EAAOJ,GACzD,YASA,SAASoC,KAuCL,GAtCAoB,KAAKlC,OAAkB,KACvBkC,KAAKrC,QAAkB,KACvBqC,KAAKgb,WAAkB,KACvBhb,KAAK2P,YAAkB;AACvB3P,KAAKib,eAAkB,KACvBjb,KAAKkb,gBAAkB,KAEvBlb,KAAKhC,UACDC,KAAO,KACPmE,KAAO,KACPH,MAAO,MAGXjC,KAAKmU,WACLnU,KAAKoU,iBAELpU,KAAKpC,eACDC,QAAe,EACfsd,WAAe,EAEfC,WAAY,KACZC,YAEAC,GAAI,EAAGC,GAAI,EACXC,GAAI,EAAGC,GAAI,EAEX9C,GAAI,EACJ+C,IAAK,EAAGC,IAAK,EACb5B,SAAU,EAEVhB,SAAU,EACVC,SAAU,EAEV4C,UAAW,EACXC,UAAW,EACX1f,EAAK,MAGLgB,EAAMyM,WAAWkS,SAAS3T,UAAU4T,MACpC/b,KAAKgc,kBAAoBhc,KAAKic,aAAaF,KAAK/b,MAChDA,KAAKkc,oBAAsBlc,KAAKmc,eAAeJ,KAAK/b,UAEnD,CACD,GAAIoc,GAAOpc,IAEXA,MAAKgc,kBAAoB,WAAc,MAAOI,GAAKH,gBACnDjc,KAAKkc,oBAAsB,WAAc,MAAOE,GAAKD,kBAGzDnc,KAAKqc,aACDC,aACA7P,YACA8P,UAIJvc,KAAK3B,YACL2B,KAAKlB,cACLkB,KAAKwc,eACLxc,KAAK2U,aACL3U,KAAKyc,cAGLzc,KAAKqU,YACDhU,MAAaW,EAAG,EAAGC,EAAG,GACtB8V,QAAa/V,EAAG,EAAGC,EAAG,GACtB4Y,UAAW,GAGf7Z,KAAK2B,WACDtB,MAAaW,EAAG,EAAGC,EAAG,GACtB8V,QAAa/V,EAAG,EAAGC,EAAG,GACtB4Y,UAAW,GAIf7Z,KAAKsU,aACDjU,MAAaW,EAAG,EAAGC,EAAG,GACtB8V,QAAa/V,EAAG,EAAGC,EAAG,GACtB4Y,UAAW,GAIf7Z,KAAKoa,cACD/Z,MAAaW,EAAG,EAAGC,EAAG,EAAGoZ,GAAI,EAAGC,GAAI,EAAGN,MAAO,GAC9CjD,QAAa/V,EAAG,EAAGC,EAAG,EAAGoZ,GAAI,EAAGC,GAAI,EAAGN,MAAO,GAC9CH,UAAW,GAGf7Z,KAAK4U,UAAc,KACnB5U,KAAK6U,eAEL7U,KAAKmC,aAAkB,KACvBnC,KAAK0c,gBAAkB,KAEvB1c,KAAK8U,UAAY,KACjB9U,KAAK2c,QAAY,EACjB3c,KAAK4c,QAAY,KAEjB5c,KAAK6c,aAAmBjc,KAAM,EAAGD,MAAO,EAAGI,IAAK,EAAGD,OAAQ,GAC3Dd,KAAK8c,gBAAmBlc,KAAM,EAAGD,MAAO,EAAGI,IAAK,EAAGD,OAAQ,GAC3Dd,KAAK+c,eAEL/c,KAAKjB,SACDie,OAAShc,EAAG,EAAGC,EAAG,GAElByY,cAAe,EACfuD,aAAe,EACf9D,SAAe,EAEfE,MAAO,EAEPI,WAAY,EACZE,UAAY,GAGhB3Z,KAAKwU,YACDxT,EAAU,EAAGC,EAAU,EACvByW,GAAU,EAAGC,GAAU,EACvBH,MAAU,EAAGC,MAAU,EACvBH,SAAU,EAAGC,SAAU,EACvBrG,WACAmG,QAAU,EACV6F,SAAU,GAGdld,KAAKyU,gBACDiD,GAAa,EAAGC,GAAa,EAC7BwF,YAAa,EAAGC,YAAa,EAC7BxR,KAAa,KACbiM,YAAa,EACbqF,SAAa,GAGjBld,KAAKyU,eAAe7I,KAAO5L,KAAKwU,WAEhCxU,KAAKF,eAAkB,EACvBE,KAAKqd,iBAAkB,EACvBrd,KAAKkU,WAAkB,EACvBlU,KAAKsC,UAAkB,EACvBtC,KAAKuC,UAAkB,EACvBvC,KAAKyB,WAAkB,KAEvBzB,KAAK5B,OAAQ,EAEbjB,EAAMC,aAAa2H,KAAK/E,MAK5B,QAASsd,GAAgB9b,EAAQiI,GAC7B,IAAKtM,EAAM6E,SAASR,GAAW,MAAO,KAEtC,IAAI+b,GAAa/b,EAAOvD,KACpBF,EAAU0L,EAAa1L,OAE3B,QAAwB,WAAhBwf,GAA8Bxf,EAAQ8D,OAAOC,SACzB,SAApByb,GAAkCxf,EAAQsE,KAAKP,SAC3B,YAApByb,GAAkCxf,EAAQgB,QAAQ+C,UACnD3E,EAAMyE,gBAAgB2b,KAEN,WAAfA,GAA0C,aAAfA,KAC3BA,EAAa,YAGV/b,GAEJ,KAGX,QAASgc,GAAiBhc,GACtB,GAAImS,GAAS,EAKb,IAHoB,SAAhBnS,EAAOvD,OACP0V,EAAUxW,EAAM8J,cAAc5E,MAEd,WAAhBb,EAAOvD,KACP,GAAIuD,EAAOY,KACPuR,EAAUxW,EAAM8J,cAAczF,EAAOvD,KAAOuD,EAAOY,UAElD,IAAIZ,EAAOS,MAAO,CAInB,IAAK,GAHDwb,GAAY,SACZC,GAAa,MAAO,SAAU,OAAQ,SAEjCvhB,EAAI,EAAO,EAAJA,EAAOA,IACfqF,EAAOS,MAAMyb,EAAUvhB,MACvBshB,GAAaC,EAAUvhB,GAI/BwX,GAASxW,EAAM8J,cAAcwW,GAIrC,MAAO9J,GAGX,QAAS5T,KACLC,KAAKC,cAAcC,iBA9MvB,GAAI/C,GAAQjB,EAAQ,WAChBuB,EAAQvB,EAAQ,WAChByhB,EAAiBlgB,EAAMmgB,IACvBhQ,EAAgB1R,EAAQ,mBACxBmI,EAASnI,EAAQ,kBACjBuC,EAAUvC,EAAQ,kBA4MtB0C,GAAYuJ,WACR8H,UAAa,SAAUnT,EAAS+gB,GAAM,MAASpgB,GAAMwS,UAAUnT,EAAS+gB,EAAI7d,OAC5E8d,YAAa,SAAUhhB,EAAS+gB,GAAM,MAAOpgB,GAAMqgB,YAAYhhB,EAAS+gB,EAAI7d,OAC5E+d,WAAa,SAAUjgB,EAAQkgB,GAAO,MAAQvgB,GAAMsgB,WAAWjgB,EAAQkgB,EAAKhe,OAE5E8F,YAAa,SAAUhJ,EAASqC,EAAOnC,GAgCnC,QAASihB,GAAgBxU,EAAc1G,GAC/B0G,GACGtM,EAAMiO,UAAU3B,EAAczM,KAC7BG,EAAMkO,WAAW5B,EAAczM,EAAaA,IAC7CG,EAAMoO,UAAU9B,EAAczM,EAAaA,IAC3CG,EAAM+F,gBAAgBlG,EAAa+F,KAEtCmb,EAAWnZ,KAAK0E,GAChB0U,EAAiBpZ,KAAK/H,IAvC9B,IAAIgD,KAAKhC,SAASC,MAAS+B,KAAK5B,MAAhC,CAEA,GAAI8f,MACAC,KACAC,EAAoBpe,KAAKrC,OAE7BqC,MAAKzB,WAAWzB,IAEZkD,KAAKlC,SACDX,EAAMkO,WAAWrL,KAAKlC,OAAQkC,KAAKrC,QAASX,IAC5CG,EAAMoO,UAAUvL,KAAKlC,OAAQkC,KAAKrC,QAASX,KAG/CgD,KAAKlC,OAAS,KACdkC,KAAKrC,QAAU,KACfqC,KAAKmU,WACLnU,KAAKoU,iBAGT,IAAIiK,GAAsBlhB,EAAMqG,cAAcC,IAAIzG,GAC9CshB,EAAiBD,IACblhB,EAAMkO,WAAWgT,EAAqBrhB,EAAaA,IACpDG,EAAMoO,UAAU8S,EAAqBrhB,EAAaA,IAClDsgB,EACCe,EAAoBzM,UAAU9U,EAASqC,EAAOa,KAAMhD,GACpDqhB,EAEJC,KAAkBnhB,EAAM6O,uBAAuBqS,EAAqBrhB,EAAashB,KACjFA,EAAgB,MAehBA,GACAte,KAAKlC,OAASugB,EACdre,KAAKrC,QAAUX,EACfgD,KAAKmU,WACLnU,KAAKoU,mBAGLjX,EAAMqG,cAAcuK,gBAAgBkQ,GAEhCje,KAAKue,iBAAiBzhB,EAASqC,EAAO+e,EAAYC,IAClDne,KAAKmU,QAAU+J,EACfle,KAAKoU,cAAgB+J,EAErBne,KAAK2E,aAAa7H,EAASqC,EAAOa,KAAKmU,QAASnU,KAAKoU,eACrD/P,EAAOC,IAAItH,EACPG,EAAMiH,aAAcjH,EAAMoH,YAAYG,KAAO,YAC7CvH,EAAMiG,UAAUuB,eAEf3E,KAAKlC,SACNX,EAAMgG,aAAaib,EAAmBphB,IACtCgD,KAAK2E,aAAa7H,EAASqC,EAAOa,KAAKmU,QAASnU,KAAKoU,eACrD/P,EAAOC,IAAItE,KAAKrC,QACZR,EAAMiH,aAAcjH,EAAMoH,YAAYG,KAAO,YAC7CvH,EAAMiG,UAAUuB,gBAGpB3E,KAAKlC,OAAS,KACdkC,KAAKrC,QAAU,KACfqC,KAAKmU,WACLnU,KAAKoU,sBAQrBzP,aAAc,SAAU7H,EAASqC,EAAOnC,EAAasC,EAAgB6U,EAASC,GAC1E,GAAItW,GAASkC,KAAKlC,MAElB,KAAKkC,KAAKhC,SAASC,MAAQ+B,KAAK5B,MAAO,CAEnC,GAAIoD,EAGJxB,MAAK+d,WAAW/d,KAAK2B,UAAW7E,GAE5BqX,EACA3S,EAASxB,KAAKue,iBAAiBzhB,EAASqC,EAAOgV,EAASC,GAEnDtW,IACL0D,EAAS8b,EAAexf,EAAO8T,UAAU5R,KAAK3B,SAAS,GAAIc,EAAOa,KAAMA,KAAKrC,SAAUqC,KAAKlC,SAG5FA,GAAUA,EAAOC,QAAQgU,cAErBjU,EAAO8G,KAAK8D,gBAAgBgL,MAAMC,OADlCnS,EAC2Cgc,EAAgBhc,GAGhB,QAI9CxB,MAAKhC,SAASC,MACnB+B,KAAK0G,uBAAuBvH,EAAOrB,EAAQkC,KAAKrC,UAIxDoI,WAAY,SAAUjJ,EAASqC,EAAOnC,GAC9BgD,KAAKhC,SAASC,OAGbd,EAAMqG,cAAcC,IAAIzG,IACzBqH,EAAO8O,OAAOnW,EACVG,EAAMiH,aAAcjH,EAAMoH,YAAYG,KAAO,YAC7CvH,EAAMiG,UAAUuB,cAGpB3E,KAAKlC,QAAUkC,KAAKlC,OAAOC,QAAQgU,cAAgB/R,KAAKhB,gBACxDgB,KAAKlC,OAAO8G,KAAK8D,gBAAgBgL,MAAMC,OAAS,MAIxD/N,aAAc,SAAU9I,EAASqC,EAAOnC,EAAasC,GAyCjD,QAASkf,GAAa/U,EAAc1G,EAAUC,GAC1C,GAAIyJ,GAAWtP,EAAMiL,mBACfpF,EAAQ2K,iBAAiB5K,GACzBmL,MAEF/Q,GAAMiO,UAAU3B,EAAc9L,KAC1BR,EAAMkO,WAAW5B,EAAc9L,EAASX,IACzCG,EAAMoO,UAAU9B,EAAc9L,EAASX,IACvCG,EAAM+F,gBAAgBvF,EAASoF,EAAU0J,KAE5C2P,EAAKjI,QAAQpP,KAAK0E,GAClB2S,EAAKhI,cAAcrP,KAAKpH,IAnDhC,GAKI6D,GALA4a,EAAOpc,KAEPye,EAAYpa,EAAOmC,eAAgB/I,EAAMiE,UAAWvC,GAASA,EAC7DxB,EAAUX,EACV0hB,EAAe1e,KAAKzB,WAAWzB,EAUnC,IAPAkD,KAAKyc,WAAWiC,GAAgBC,WAAW,WACvCvC,EAAKwC,YAAYva,EAAOmC,eAAgBiY,EAAY3hB,EAAS2hB,EAAWzhB,EAAasC,IACtFnC,EAAMyJ,eAAeiY,eAExB7e,KAAKF,eAAgB,EAGjBE,KAAKpC,cAAcC,QAAUmC,KAAKlC,OAAOiF,SAEzC,KAAOtF,EAAMyD,UAAUvD,IAAU,CAG7B,GAAIA,IAAYqC,KAAKrC,SAEd2f,EAAetd,KAAKlC,OAAO8T,UAAU9U,EAASqC,EAAOa,KAAMA,KAAKrC,SAAUqC,KAAKlC,QAAQG,OAAS+B,KAAKhC,SAASC,KAOjH,MAJA0f,GAAehY,OAAO3F,KAAKpC,cAAczB,GACzC6D,KAAKpC,cAAcC,QAAS,MAE5BmC,MAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,OAG1DW,GAAUR,EAAMqB,cAAcb,GAKtC,GAAIqC,KAAKhB,cAEL,WADAgB,MAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,OAuB1D,KAHAgD,KAAK+d,WAAW/d,KAAK2B,UAAW7E,GAChCkD,KAAK4U,UAAYzV,EAEV1B,EAAMyD,UAAUvD,KAAa6D,GAChCxB,KAAKmU,WACLnU,KAAKoU,iBAELjX,EAAMqG,cAAcuK,gBAAgByQ,GAEpChd,EAASxB,KAAKue,iBAAiBzhB,EAASqC,EAAOa,KAAKmU,QAASnU,KAAKoU,eAClEzW,EAAUR,EAAMqB,cAAcb,EAGlC,OAAI6D,IACAxB,KAAKhC,SAASC,KAAQuD,EAAOvD,KAC7B+B,KAAKhC,SAASoE,KAAQZ,EAAOY,KAC7BpC,KAAKhC,SAASiE,MAAQT,EAAOS,MAE7BjC,KAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,QAE/CgD,KAAKyE,YAAY3H,EAASqC,EAAOnC,EAAasC,EAAgBkC,KAIrExB,KAAK2U,UAAU+J,IAAgB,GAAIhf,OAAOC,UAC1CK,KAAKwc,YAAYkC,GAAgB1hB,EACjCS,EAAMiE,OAAO1B,KAAK6U,YAAa/X,GAE/BW,EAAMshB,WAAW/e,KAAKqU,WAAYrU,KAAK2B,WACvC3B,KAAKqd,iBAAkB,MAG3Brd,MAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,UAK1DyH,YAAa,SAAU3H,EAASqC,EAAOnC,EAAasC,EAAgB0f,GAChE,IAAKA,IAAgBhf,KAAKpC,cAAcC,QAAUmC,KAAKqd,iBAAmBrd,KAAKhC,SAASC,KAGpF,WAFA+B,MAAK0G,uBAAuBvH,EAAOa,KAAKlC,OAAQkC,KAAKrC,QAKzDqC,MAAKF,eAAgB,EACrBE,KAAK4U,UAAYzV,CAEjB,IACIqC,GADAkd,EAAe1e,KAAKzB,WAAWzB,EAMnC,IAAKkD,KAAKlB,WAAWpC,OAAS,IAAMsD,KAAKlC,SAAYkC,KAAKhC,SAASC,KAAM,CAErE,GAAIwL,GAAetM,EAAMqG,cAAcC,IAAInE,EAEvCmK,KACItM,EAAMkO,WAAW5B,EAAcnK,EAAgBtC,IAChDG,EAAMoO,UAAU9B,EAAcnK,EAAgBtC,KAC7CwE,EAAS8b,EAAe0B,GAAevV,EAAamI,UAAU9U,EAASqC,EAAOa,KAAMV,GAAiBmK,EAAczM,KACpHG,EAAM6O,uBAAuBvC,EAAcnK,EAAgBkC,KAC9DxB,KAAKlC,OAAS2L,EACdzJ,KAAKrC,QAAU2B,GAIvB,GAAIxB,GAASkC,KAAKlC,OACdC,EAAUD,GAAUA,EAAOC,OAE/B,KAAID,IAAWkhB,GAAgBhf,KAAKhC,SAASC,KAkCpC+B,KAAKpC,cAAcC,QACrByB,IAAmBU,KAAKrC,SACxB2f,EAAexf,EAAO8T,UAAU9U,EAASqC,EAAOa,KAAMA,KAAKrC,SAAUG,GAAQG,OAAS+B,KAAKhC,SAASC,OAEvG0f,EAAehY,OAAO3F,KAAKpC,cAAczB,GACzC6D,KAAKpC,cAAcC,QAAS,EAE5BmC,KAAK0G,uBAAuBvH,EAAOrB,EAAQkC,KAAKrC,cAzCA,CAKhD,GAJA6D,EAASA,GAAU8b,EAAe0B,GAAelhB,EAAO8T,UAAU9U,EAASqC,EAAOa,KAAMV,GAAiBxB,EAAQkC,KAAKrC,SAEtHqC,KAAK+d,WAAW/d,KAAKsU,cAEhB9S,EAAU,MAEXzD,GAAQgU,cACRjU,EAAO8G,KAAK8D,gBAAgBgL,MAAMC,OAAS6J,EAAgBhc,IAG/DxB,KAAKyB,WAA6B,WAAhBD,EAAOvD,KAAmBuD,EAAOY,KAAO,KAE3C,YAAXZ,GAAwBxB,KAAKlB,WAAWpC,OAAS,IACjD8E,EAAS,MAGbxB,KAAKhC,SAASC,KAAQuD,EAAOvD,KAC7B+B,KAAKhC,SAASoE,KAAQZ,EAAOY,KAC7BpC,KAAKhC,SAASiE,MAAQT,EAAOS,MAE7BjC,KAAKwU,WAAW8C,SAAWtX,KAAKwU,WAAW+C,SACvCvX,KAAKyU,eAAe0I,YAAcnd,KAAKyU,eAAe2I,YAAc6B,EAAAA,EAExEjf,KAAK2U,UAAU+J,IAAgB,GAAIhf,OAAOC,UAC1CK,KAAKwc,YAAYkC,GAAgB1hB,EACjCS,EAAMiE,OAAO1B,KAAK6U,YAAa/X,GAE/BkD,KAAK+d,WAAW/d,KAAKqU,YACrBrU,KAAKqd,iBAAkB,EAEvBrd,KAAK0G,uBAAuBvH,EAAOrB,EAAQkC,KAAKrC,WAcxDuhB,iBAAkB,SAAU9H,EAAQ+H,GAChC,GAAIrhB,GAAiBkC,KAAKlC,OACtBshB,GAAiB,EACjBC,EAAiBliB,EAAMwO,UAAU7N,EAAQkC,KAAKhC,SAASC,SAAeH,EAAOC,QAAQiC,KAAKhC,SAASC,MAAM2N,KAAK0T,SAAeH,GAC7HI,EAAiBpiB,EAAM0O,cAAc/N,EAAQkC,KAAKhC,SAASC,SAAWH,EAAOC,QAAQiC,KAAKhC,SAASC,MAAM6N,SAASwT,SAAWH,EAYjI,OAVIE,GAAkBrf,KAAKwf,YAAepI,GAAkBpX,KAAKwU,WAAe6C,QAAa,EACzFkI,EAAkBvf,KAAKyf,eAAerI,GAAkBpX,KAAKyU,eAAeoD,YAAa,EAEzFwH,GAAcrf,KAAKwU,WAAW6C,SAAWrX,KAAKwU,WAAW0I,QACzDkC,EAAaG,GAAkBvf,KAAKyU,eAAeoD,YAAc7X,KAAKyU,eAAeyI,QAEhFqC,GAAkBvf,KAAKyU,eAAeoD,aAAe7X,KAAKyU,eAAeyI,UAC9EkC,GAAa,GAGVA,GAGXM,gBAAiB,SAAUle,EAAQiI,EAAc9L,GAC7C,GAII8C,GAAOI,EAJPN,EAAOkJ,EAAanI,QAAQ3D,GAC5B+L,EAASvM,EAAMqM,YAAYC,EAAc9L,GACzCiO,EAAOnC,EAAa1L,QAAQiC,KAAKhC,SAASC,MAAM2N,KAChDE,EAAWrC,EAAa1L,QAAQiC,KAAKhC,SAASC,MAAM6N,QAGpDvL,IACAP,KAAK6c,YAAYjc,KAAOZ,KAAKsU,YAAYjU,KAAKW,EAAIT,EAAKK,KACvDZ,KAAK6c,YAAY9b,IAAOf,KAAKsU,YAAYjU,KAAKY,EAAIV,EAAKQ,IAEvDf,KAAK6c,YAAYlc,MAASJ,EAAKI,MAASX,KAAKsU,YAAYjU,KAAKW,EAC9DhB,KAAK6c,YAAY/b,OAASP,EAAKO,OAASd,KAAKsU,YAAYjU,KAAKY,EAEvCR,EAAnB,SAAWF,GAAgBA,EAAKE,MACrBF,EAAKI,MAAQJ,EAAKK,KACTC,EAApB,UAAYN,GAAiBA,EAAKM,OACtBN,EAAKO,OAASP,EAAKQ,KAGnCf,KAAK6c,YAAYjc,KAAOZ,KAAK6c,YAAY9b,IAAMf,KAAK6c,YAAYlc,MAAQX,KAAK6c,YAAY/b,OAAS,EAGtGd,KAAK+c,YAAYhK,OAAO,EAExB,IAAI4M,GAAa/T,GAAwB,gBAAhBA,EAAKwF,QAE1BpQ,EAAGhB,KAAKsU,YAAYjU,KAAKW,EAAI0I,EAAO1I,EACpCC,EAAGjB,KAAKsU,YAAYjU,KAAKY,EAAIyI,EAAOzI,GAElC2K,GAAQA,EAAKwF,SAAYpQ,EAAG,EAAGC,EAAG,EAExC,IAAIV,GAAQqL,GAAQA,EAAK6F,gBAAkB7F,EAAK6F,eAAe/U,OAC3D,IAAK,GAAIP,GAAI,EAAGA,EAAIyP,EAAK6F,eAAe/U,OAAQP,IAC5C6D,KAAK+c,YAAYhY,MACb/D,EAAGhB,KAAK6c,YAAYjc,KAAQH,EAASmL,EAAK6F,eAAetV,GAAG6E,EAAK2e,EAAW3e,EAC5EC,EAAGjB,KAAK6c,YAAY9b,IAAQF,EAAS+K,EAAK6F,eAAetV,GAAG8E,EAAK0e,EAAW1e,QAKpFjB,MAAK+c,YAAYhY,KAAK4a,EAGtBpf,IAAQuL,EAAS8L,aACjB5X,KAAK8c,eAAelc,KAAOZ,KAAK6c,YAAYjc,KAAQH,EAASqL,EAAS8L,YAAYhX,KAClFZ,KAAK8c,eAAe/b,IAAOf,KAAK6c,YAAY9b,IAAQF,EAASiL,EAAS8L,YAAY7W,IAElFf,KAAK8c,eAAenc,MAASX,KAAK6c,YAAYlc,MAAUF,GAAU,EAAIqL,EAAS8L,YAAYjX,OAC3FX,KAAK8c,eAAehc,OAASd,KAAK6c,YAAY/b,OAAUD,GAAU,EAAIiL,EAAS8L,YAAY9W,SAG3Fd,KAAK8c,eAAelc,KAAOZ,KAAK8c,eAAe/b,IAAMf,KAAK8c,eAAenc,MAAQX,KAAK8c,eAAehc,OAAS,GAoCtHkc,MAAO,SAAUxb,EAAQiI,EAAc9L,GAC/BqC,KAAKhB,gBACDgB,KAAKF,eACNE,KAAKlB,WAAWpC,QAA0B,YAAhB8E,EAAOvD,KAAoB,EAAI,KAMhB,KAA5Cd,EAAM2V,QAAQ3V,EAAMC,aAAc4C,OAClC7C,EAAMC,aAAa2H,KAAK/E,MAG5BA,KAAKhC,SAASC,KAAQuD,EAAOvD,KAC7B+B,KAAKhC,SAASoE,KAAQZ,EAAOY,KAC7BpC,KAAKhC,SAASiE,MAAQT,EAAOS,MAC7BjC,KAAKlC,OAAiB2L,EACtBzJ,KAAKrC,QAAiBA,EAEtBqC,KAAK+d,WAAW/d,KAAKsU,aACrBtU,KAAK0f,gBAAgBle,EAAOvD,KAAMwL,EAAc9L,GAChDqC,KAAKkf,iBAAiBlf,KAAKsU,YAAYjU,MAEvCL,KAAK8U,UAAY9U,KAAKA,KAAKhC,SAASC,KAAO,SAAS+B,KAAK4U,aAG7D/O,YAAa,SAAU/I,EAASqC,EAAOnC,EAAasC,EAAgB6f,GAChEnf,KAAKuU,cAAczX,GAEnBkD,KAAK+d,WAAW/d,KAAK2B,UAAY7E,YAAmB8Q,GAC9C5N,KAAKpC,cAAcwd,WACnBlN,OAEN,IAKIwJ,GAAIC,EALJiI,EAAiB5f,KAAK2B,UAAUtB,KAAKW,IAAMhB,KAAKqU,WAAWhU,KAAKW,GACjEhB,KAAK2B,UAAUtB,KAAKY,IAAMjB,KAAKqU,WAAWhU,KAAKY,GAC/CjB,KAAK2B,UAAUoV,OAAO/V,IAAMhB,KAAKqU,WAAW0C,OAAO/V,GACnDhB,KAAK2B,UAAUoV,OAAO9V,IAAMjB,KAAKqU,WAAW0C,OAAO9V,EAGlDyd,EAAe1e,KAAK5B,MAAO,EAAIjB,EAAM2V,QAAQ9S,KAAKlB,WAAYrB,EAAMC,aAAaZ,GAkBrF,IAfIkD,KAAKF,gBAAkBE,KAAKqd,kBAC5B3F,EAAK1X,KAAK2B,UAAUoV,OAAO/V,EAAIhB,KAAKsU,YAAYyC,OAAO/V,EACvD2W,EAAK3X,KAAK2B,UAAUoV,OAAO9V,EAAIjB,KAAKsU,YAAYyC,OAAO9V,EAEvDjB,KAAKqd,gBAAkB5f,EAAM0c,MAAMzC,EAAIC,GAAMxa,EAAM2J,sBAGlD8Y,GAAmB5f,KAAKF,gBAAiBE,KAAKqd,kBAC3Crd,KAAKF,eACL+f,aAAa7f,KAAKyc,WAAWiC,IAGjC1e,KAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,SAGrDgD,KAAKF,cAAV,CAEA,GAAI8f,GAAiB5f,KAAKqd,kBAAoB8B,EAE1C,WADAnf,MAAK0G,uBAAuBvH,EAAOa,KAAKlC,OAAQkC,KAAKrC,QAOzD,IAFAF,EAAMqiB,eAAe9f,KAAKoa,aAAcpa,KAAKqU,WAAYrU,KAAK2B,WAEzD3B,KAAKhC,SAASC,KAAnB,CAEA,GAAI+B,KAAKqd,mBAEArd,KAAKpC,cAAcC,QAAWf,YAAmB8Q,IAAiB,eAAetQ,KAAKR,EAAQ0C,OAAS,CAG5G,IAAKQ,KAAKhB,gBACNvB,EAAMqiB,eAAe9f,KAAKoa,aAAcpa,KAAKqU,WAAYrU,KAAK2B,WAGnC,SAAvB3B,KAAKhC,SAASC,MAAiB,CAC/B,GAAI8hB,GAAOxQ,KAAKyQ,IAAItI,GAChBuI,EAAO1Q,KAAKyQ,IAAIrI,GAChBuI,EAAalgB,KAAKlC,OAAOC,QAAQsE,KAAKD,KACtCA,EAAQ2d,EAAOE,EAAO,IAAaA,EAAPF,EAAc,IAAM,IAGpD,IAAa,OAAT3d,GAAgC,OAAf8d,GAAuBA,IAAe9d,EAAM,CAE7DpC,KAAKhC,SAASC,KAAO,IAOrB,KAHA,GAAIN,GAAUX,EAGPS,EAAMyD,UAAUvD,IAAU,CAC7B,GAAI0gB,GAAsBlhB,EAAMqG,cAAcC,IAAI9F,EAElD,IAAI0gB,GACGA,IAAwBre,KAAKlC,SAC5BugB,EAAoBtgB,QAAQsE,KAAK8d,aACsD,SAAxF9B,EAAoBzM,UAAU5R,KAAK6U,YAAa7U,KAAK4U,UAAW5U,KAAMrC,GAASM,MAC/Ed,EAAMsO,UAAUrJ,EAAMic,GAAsB,CAE/Cre,KAAKhC,SAASC,KAAO,OACrB+B,KAAKlC,OAASugB,EACdre,KAAKrC,QAAUA,CACf,OAGJA,EAAUR,EAAMqB,cAAcb,GAKlC,IAAKqC,KAAKhC,SAASC,KAAM,CACrB,GAAImiB,GAAkBpgB,KAElBqgB,EAAe,SAAU5W,EAAc1G,EAAUC,GACjD,GAAIyJ,GAAWtP,EAAMiL,mBACfpF,EAAQ2K,iBAAiB5K,GACzBmL,MAEN,IAAIzE,IAAiB2W,EAAgBtiB,OAErC,MAAIX,GAAMiO,UAAU3B,EAAczM,KAC1ByM,EAAa1L,QAAQsE,KAAK8d,cAC1BhjB,EAAMkO,WAAW5B,EAAc9L,EAASX,IACzCG,EAAMoO,UAAU9B,EAAc9L,EAASX,IACvCG,EAAM+F,gBAAgBvF,EAASoF,EAAU0J,IACyE,SAAlHhD,EAAamI,UAAUwO,EAAgBvL,YAAauL,EAAgBxL,UAAWwL,EAAiBziB,GAASM,MACzGd,EAAMsO,UAAUrJ,EAAMqH,IACtBtM,EAAM6O,uBAAuBvC,EAAc9L,EAAS,QAEhD8L,EATX,OAeJ,KAFA9L,EAAUX,EAEHS,EAAMyD,UAAUvD,IAAU,CAC7B,GAAI2iB,GAAuBnjB,EAAMqG,cAAcuK,gBAAgBsS,EAE/D,IAAIC,EAAsB,CACtBtgB,KAAKhC,SAASC,KAAO,OACrB+B,KAAKlC,OAASwiB,EACdtgB,KAAKrC,QAAUA,CACf,OAGJA,EAAUR,EAAMqB,cAAcb,MAOlD,GAAIuZ,KAAalX,KAAKhC,SAASC,OAAS+B,KAAKhB,aAE7C,IAAIkY,IACIlX,KAAKlC,OAAOC,QAAQiC,KAAKhC,SAASC,MAAMkiB,cACxChjB,EAAM6O,uBAAuBhM,KAAKlC,OAAQkC,KAAKrC,QAASqC,KAAKhC,WAEjE,WADAgC,MAAK2V,KAAKxW,EAId,IAAIa,KAAKhC,SAASC,MAAQ+B,KAAKlC,OAAQ,CAC/BoZ,GACAlX,KAAKgd,MAAMhd,KAAKhC,SAAUgC,KAAKlC,OAAQkC,KAAKrC,QAGhD,IAAIyhB,GAAapf,KAAKkf,iBAAiBlf,KAAK2B,UAAUtB,KAAM8e,IAGxDC,GAAclI,KACdlX,KAAK8U,UAAY9U,KAAKA,KAAKhC,SAASC,KAAO,QAAQkB,IAGvDa,KAAK0G,uBAAuBvH,EAAOa,KAAKlC,OAAQkC,KAAKrC,UAI7DF,EAAMshB,WAAW/e,KAAKqU,WAAYrU,KAAK2B,YAEnC3B,KAAKsC,UAAYtC,KAAKuC,WACtBvC,KAAKkG,eAAepJ,MAI5ByjB,UAAW,SAAUphB,GACjB,GAAIqhB,GAAY,GAAI5S,GAAc5N,KAAMb,EAAO,OAAQ,QAASa,KAAKrC,QAErEqC,MAAKsC,UAAW,EAChBtC,KAAKlC,OAAOoU,KAAKsO,GAGjBxgB,KAAKqc,YAAYC,aACjBtc,KAAKqc,YAAY5P,YACjBzM,KAAKqc,YAAYE,SAEZvc,KAAK2G,aACN3G,KAAKygB,eAAezgB,KAAKrC,QAG7B,IAAI+iB,GAAa1gB,KAAK2gB,cAAcxhB,EAAOqhB,EAM3C,OAJIE,GAAWE,UACX5gB,KAAK6gB,gBAAgBH,EAAWE,UAG7BJ,GAGXzL,SAAU,SAAU5V,GAChB,GAAIrB,GAASkC,KAAKlC,OACd0iB,EAAa,GAAI5S,GAAc5N,KAAMb,EAAO,OAAQ,OAAQa,KAAKrC,SACjE+R,EAAmB1P,KAAKrC,QACxByR,EAAOpP,KAAK8gB,QAAQ3hB,EAAOuQ,EAE/B1P,MAAKgb,WAAa5L,EAAK1C,SACvB1M,KAAK2P,YAAcP,EAAKzR,OAExB,IAAI+iB,GAAa1gB,KAAK2gB,cAAcxhB,EAAOqhB,EAW3C,OATA1iB,GAAOoU,KAAKsO,GAERE,EAAWK,OAAS/gB,KAAKib,eAAe/I,KAAKwO,EAAWK,OACxDL,EAAWM,OAAahhB,KAAKgb,WAAW9I,KAAKwO,EAAWM,OACxDN,EAAWhc,MAAa1E,KAAKgb,WAAW9I,KAAKwO,EAAWhc,MAE5D1E,KAAKib,eAAkBjb,KAAKgb,WAC5Bhb,KAAKkb,gBAAkBlb,KAAK2P,YAErB6Q,GAGXS,YAAa,SAAU9hB,GACnB,GAAI+hB,GAAc,GAAItT,GAAc5N,KAAMb,EAAO,SAAU,QAASa,KAAKrC,QAEzE,IAAIqC,KAAKhC,SAASiE,MAAO,CACrB,GAAIkf,GAAYnhB,KAAKlC,OAAOwD,QAAQtB,KAAKrC,QAEzC,IAAIqC,KAAKlC,OAAOC,QAAQ8D,OAAO6O,OAAQ,CACnC,GAAI0Q,GAAc3jB,EAAMiE,UAAW1B,KAAKhC,SAASiE,MAEjDmf,GAAYrgB,IAASqgB,EAAYrgB,KAAWqgB,EAAYxgB,OAAWwgB,EAAYtgB,OAC/EsgB,EAAYxgB,KAASwgB,EAAYxgB,MAAWwgB,EAAYrgB,MAAWqgB,EAAYzgB,MAC/EygB,EAAYtgB,OAASsgB,EAAYtgB,QAAWsgB,EAAYzgB,QAAWygB,EAAYrgB,IAC/EqgB,EAAYzgB,MAASygB,EAAYzgB,OAAWygB,EAAYtgB,SAAWsgB,EAAYxgB,KAE/EZ,KAAKhC,SAASqjB,aAAeD,MAG7BphB,MAAKhC,SAASqjB,aAAe,IAGjCrhB,MAAKshB,aACDtE,MAAYmE,EACZI,QAAY9jB,EAAMiE,UAAWyf,GAC7BtJ,WAAYpa,EAAMiE,UAAWyf,GAC7BK,SAAY/jB,EAAMiE,UAAWyf,GAC7BM,OACI7gB,KAAM,EAAGD,MAAQ,EAAGF,MAAQ,EAC5BM,IAAM,EAAGD,OAAQ,EAAGD,OAAQ,IAIpCqgB,EAAY3gB,KAAOP,KAAKshB,YAAYzJ,WACpCqJ,EAAYQ,UAAY1hB,KAAKshB,YAAYG,MAO7C,MAJAzhB,MAAKlC,OAAOoU,KAAKgP,GAEjBlhB,KAAKuC,UAAW,EAET2e,GAGXlM,WAAY,SAAU7V,GAClB,GAAI+hB,GAAc,GAAItT,GAAc5N,KAAMb,EAAO,SAAU,OAAQa,KAAKrC,SAEpEsE,EAAQjC,KAAKhC,SAASiE,MACtB0f,EAAS3hB,KAAKlC,OAAOC,QAAQ8D,OAAO8f,OACpCC,EAAwB,eAAXD,GAAsC,WAAXA,CAE5C,IAAI1f,EAAO,CACP,GAAIyV,GAAKwJ,EAAYxJ,GACjBC,EAAKuJ,EAAYvJ,GAEjBqF,EAAahd,KAAKshB,YAAYtE,MAC9BuE,EAAavhB,KAAKshB,YAAYC,QAC9B1J,EAAa7X,KAAKshB,YAAYzJ,WAC9B4J,EAAazhB,KAAKshB,YAAYG,MAC9BD,EAAa/jB,EAAMiE,OAAO1B,KAAKshB,YAAYE,SAAU3J,EAEzD,IAAI7X,KAAKlC,OAAOC,QAAQ8D,OAAO6O,OAAQ,CACnC,GAAImR,GAAgB5f,CAEpBA,GAAQjC,KAAKhC,SAASqjB,aAEjBQ,EAAcjhB,MAAQihB,EAAc/gB,QACjC+gB,EAAclhB,OAASkhB,EAAc9gB,IACzC4W,GAAMD,EAEDmK,EAAcjhB,MAAQihB,EAAclhB,MAASgX,EAAKD,GAClDmK,EAAc9gB,KAAO8gB,EAAc/gB,UAAU4W,EAAKC,GAS/D,GALI1V,EAAMlB,MAAUwgB,EAAQxgB,KAAU4W,GAClC1V,EAAMnB,SAAUygB,EAAQzgB,QAAU6W,GAClC1V,EAAMrB,OAAU2gB,EAAQ3gB,MAAU8W,GAClCzV,EAAMtB,QAAU4gB,EAAQ5gB,OAAU+W,GAElCkK,GAIA,GAFAnkB,EAAMiE,OAAOmW,EAAY0J,GAEV,eAAXI,EAAyB,CAEzB,GAAIG,EAEAjK,GAAW9W,IAAM8W,EAAW/W,SAC5BghB,EAAOjK,EAAW9W,IAElB8W,EAAW9W,IAAM8W,EAAW/W,OAC5B+W,EAAW/W,OAASghB,GAEpBjK,EAAWjX,KAAOiX,EAAWlX,QAC7BmhB,EAAOjK,EAAWjX,KAElBiX,EAAWjX,KAAOiX,EAAWlX,MAC7BkX,EAAWlX,MAAQmhB,QAM3BjK,GAAW9W,IAASwO,KAAKC,IAAI+R,EAAQxgB,IAAKic,EAAMlc,QAChD+W,EAAW/W,OAASyO,KAAKrD,IAAIqV,EAAQzgB,OAAQkc,EAAMjc,KACnD8W,EAAWjX,KAAS2O,KAAKC,IAAI+R,EAAQ3gB,KAAMoc,EAAMrc,OACjDkX,EAAWlX,MAAS4O,KAAKrD,IAAIqV,EAAQ5gB,MAAOqc,EAAMpc,KAGtDiX,GAAWpX,MAASoX,EAAWlX,MAASkX,EAAWjX,KACnDiX,EAAWhX,OAASgX,EAAW/W,OAAS+W,EAAW9W,GAEnD,KAAK,GAAImB,KAAQ2V,GACb4J,EAAMvf,GAAQ2V,EAAW3V,GAAQsf,EAAStf,EAG9Cgf,GAAYjf,MAAQjC,KAAKhC,SAASiE,MAClCif,EAAY3gB,KAAOsX,EACnBqJ,EAAYQ,UAAYD,EAK5B,MAFAzhB,MAAKlC,OAAOoU,KAAKgP,GAEVA,GAGXa,aAAc,SAAU5iB,GACpB,GAAI6iB,GAAe,GAAIpU,GAAc5N,KAAMb,EAAO,UAAW,QAASa,KAAKrC,QAY3E,OAVAqkB,GAAa1I,GAAK,EAElBtZ,KAAKjB,QAAQ2a,cAAgB1Z,KAAKjB,QAAQke,aAAe+E,EAAa7I,SACtEnZ,KAAKjB,QAAQ0a,WAAazZ,KAAKjB,QAAQ4a,UAAYqI,EAAazI,MAChEvZ,KAAKjB,QAAQsa,MAAQ,EAErBrZ,KAAKkU,WAAY,EAEjBlU,KAAKlC,OAAOoU,KAAK8P,GAEVA,GAGX/M,YAAa,SAAU9V,GACnB,IAAKa,KAAKlB,WAAWpC,OACjB,MAAOsD,MAAK8U,SAGhB,IAAIkN,EAkBJ,OAhBAA,GAAe,GAAIpU,GAAc5N,KAAMb,EAAO,UAAW,OAAQa,KAAKrC,SACtEqkB,EAAa1I,GAAK0I,EAAa3I,MAAQrZ,KAAKjB,QAAQsa,MAEpDrZ,KAAKlC,OAAOoU,KAAK8P,GAEjBhiB,KAAKjB,QAAQ4a,UAAYqI,EAAazI,MACtCvZ,KAAKjB,QAAQke,aAAe+E,EAAa7I,SAErC6I,EAAa3I,QAAUrS,EAAAA,GACA,OAAvBgb,EAAa3I,OACUnL,SAAvB8T,EAAa3I,OACZ4I,MAAMD,EAAa3I,SAEpBrZ,KAAKjB,QAAQsa,MAAQ2I,EAAa3I,OAG/B2I,GAGXpD,YAAa,SAAU9hB,EAASqC,EAAOnC,GACnCgD,KAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,SAG1DgJ,UAAW,SAAUlJ,EAASqC,EAAOnC,EAAasC,GAC9C,GAAIof,GAAe1e,KAAK5B,MAAO,EAAIjB,EAAM2V,QAAQ9S,KAAKlB,WAAYrB,EAAMC,aAAaZ,GAErF+iB,cAAa7f,KAAKyc,WAAWiC,IAE7B1e,KAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,MACtDgD,KAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,OAEtDgD,KAAKkF,WAAWpI,EAASqC,EAAOnC,EAAasC,GAE7CU,KAAK1B,cAAcxB,IAGvBmJ,cAAe,SAAUnJ,EAASqC,EAAOnC,EAAasC,GAClD,GAAIof,GAAe1e,KAAK5B,MAAO,EAAIjB,EAAM2V,QAAQ9S,KAAKlB,WAAYrB,EAAMC,aAAaZ,GAErF+iB,cAAa7f,KAAKyc,WAAWiC,IAE7B1e,KAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,UACtDgD,KAAKkF,WAAWpI,EAASqC,EAAOnC,EAAasC,GAE7CU,KAAK1B,cAAcxB,IAQvBolB,YAAa,SAAUplB,EAASqC,EAAOnC,GAC/BgD,KAAK4c,SACFzd,EAAM6Y,UAAYhY,KAAK4c,QAAQ5E,SAC/B7Y,EAAM8Y,UAAYjY,KAAK4c,QAAQ3E,SAC/Bjb,IAAkBgD,KAAK4c,QAAQ9e,SAElCkC,KAAKwc,YAAY,GAAKxf,EACtBgD,KAAK2U,UAAU,IAAK,GAAIjV,OAAOC,UAC/BK,KAAK8e,oBAAoBhiB,EAASqC,EAAOnC,EAAa,SAK9DkI,WAAY,SAAUpI,EAASqC,EAAOnC,EAAasC,GAC/C,GAAI6iB,GACArkB,EAASkC,KAAKlC,OACdC,EAAUD,GAAUA,EAAOC,QAC3BqkB,EAAiBrkB,GAAWiC,KAAKhC,SAASC,MAAQF,EAAQiC,KAAKhC,SAASC,MAAMC,QAC9EN,EAAgBoC,KAAKpC,aAEzB,IAAIoC,KAAKhB,cAAe,CAEpB,GAAIpB,EAAcC,OAAU,MAE5B,IAAIwkB,GASAjH,EARAkH,GAAM,GAAI5iB,OAAOC,UACjB4iB,GAAkB,EAClBrkB,GAAU,EACVid,GAAY,EACZqH,EAAUrlB,EAAMwO,UAAU7N,EAAQkC,KAAKhC,SAASC,OAASF,EAAQiC,KAAKhC,SAASC,MAAM2N,KAAK0T,QAC1FmD,EAActlB,EAAM0O,cAAc/N,EAAQkC,KAAKhC,SAASC,OAASF,EAAQiC,KAAKhC,SAASC,MAAM6N,SAASwT,QACtG5H,EAAK,EACLC,EAAK,CAsBT,IAlB2C0K,EADvCriB,KAAKsC,SAC0B,MAAtBvE,EAAQsE,KAAKD,KAAgCmN,KAAKyQ,IAAIhgB,KAAKoa,aAAarD,OAAOsD,IACzD,MAAtBtc,EAAQsE,KAAKD,KAAgCmN,KAAKyQ,IAAIhgB,KAAKoa,aAAarD,OAAOuD,IAClCta,KAAKoa,aAAarD,OAAOiD,MAGhEha,KAAKoa,aAAarD,OAAOiD,MAI5CuI,EAAmBH,GAAkBA,EAAetgB,SAC1B,YAAvB9B,KAAKhC,SAASC,MACdkB,IAAUvB,EAAcwd,WAE3Bld,EAAWqkB,GACPD,EAAMtiB,KAAK2B,UAAUkY,UAAa,IACnCwI,EAAeD,EAAeM,UAC9BL,EAAeD,EAAeO,SAE7BJ,IAAoBrkB,IAAYskB,GAAWC,GAAc,CAEzD,GAAIG,KAEJA,GAAahX,KAAOgX,EAAa9W,SAAW8W,EAExCJ,IACAxiB,KAAKwf,YAAYxf,KAAK2B,UAAUtB,KAAMuiB,GAClCA,EAAavL,SACbK,GAAMkL,EAAalL,GACnBC,GAAMiL,EAAajL,KAIvB8K,IACAziB,KAAKyf,eAAezf,KAAK2B,UAAUtB,KAAMuiB,GACrCA,EAAa/K,aACbH,GAAMkL,EAAalL,GACnBC,GAAMiL,EAAajL,MAIvBD,GAAMC,KACNwD,GAAY,GAIpB,GAAIjd,GAAWid,EAAW,CAUtB,GATA1d,EAAMshB,WAAWnhB,EAAcyd,SAAUrb,KAAK2B,WAE9C3B,KAAK3B,SAAS,GAAKT,EAAcwd,WAAaA,EAC1C,GAAIxN,GAAc5N,KAAMb,EAAOa,KAAKhC,SAASC,KAAM,eAAgB+B,KAAKrC,SAE5EC,EAAc+a,GAAK2J,EAEnBxkB,EAAOoU,KAAKtU,EAAcwd,YAEtBld,EAAS,CACTN,EAAc8d,IAAM1b,KAAKoa,aAAarD,OAAOsD,GAC7Czc,EAAcilB,IAAM7iB,KAAKoa,aAAarD,OAAOuD,GAC7C1c,EAAcklB,GAAKT,EAEnBriB,KAAK+iB,YAAYnlB,EAEjB,IAEIolB,GAFA3iB,EAAO5C,EAAMiE,UAAW1B,KAAK2B,UAAUtB,MACvCqJ,EAASvM,EAAMqM,YAAY1L,EAAQkC,KAAKrC,QAmB5C,IAhBA0C,EAAKW,EAAIX,EAAKW,EAAIpD,EAAc0d,GAAK5R,EAAO1I,EAC5CX,EAAKY,EAAIZ,EAAKY,EAAIrD,EAAc2d,GAAK7R,EAAOzI,EAE5C+hB,GACIC,aAAa,EACbjiB,EAAGX,EAAKW,EACRC,EAAGZ,EAAKY,EACRyW,GAAI,EACJC,GAAI,EACJ/L,KAAM,MAGVoX,EAAapX,KAAOoX,EAEpBtL,EAAKC,EAAK,EAEN6K,EAAS,CACT,GAAI5W,GAAO5L,KAAKwf,YAAYxf,KAAK2B,UAAUtB,KAAM2iB,EAE7CpX,GAAKyL,SACLK,GAAM9L,EAAK8L,GACXC,GAAM/L,EAAK+L,IAInB,GAAI8K,EAAa,CACb,GAAI3W,GAAW9L,KAAKyf,eAAezf,KAAK2B,UAAUtB,KAAM2iB,EAEpDlX,GAAS+L,aACTH,GAAM5L,EAAS4L,GACfC,GAAM7L,EAAS6L,IAIvB/Z,EAAcslB,YAAcxL,EAC5B9Z,EAAculB,YAAcxL,EAE5B/Z,EAAczB,EAAIwhB,EAAeyF,QAAQpjB,KAAKgc,uBAG9Cpe,GAAcud,WAAY,EAC1Bvd,EAAc0d,GAAK5D,EACnB9Z,EAAc2d,GAAK5D,EAEnB/Z,EAAc4d,GAAK5d,EAAc6d,GAAK,EAEtC7d,EAAczB,EAAIwhB,EAAeyF,QAAQpjB,KAAKkc,oBAIlD,aADAte,EAAcC,QAAS,IAIvB2kB,GAAWC,IAEXziB,KAAK6F,YAAY/I,EAASqC,EAAOnC,EAAasC,GAAgB,GAItE,GAAIU,KAAKsC,SAAU,CACf6f,EAAW,GAAIvU,GAAc5N,KAAMb,EAAO,OAAQ,MAAOa,KAAKrC,QAE9D,IAAI+R,GAAmB1P,KAAKrC,QACxByR,EAAOpP,KAAK8gB,QAAQ3hB,EAAOuQ,EAE/B1P,MAAKgb,WAAa5L,EAAK1C,SACvB1M,KAAK2P,YAAcP,EAAKzR,OAExB,IAAI+iB,GAAa1gB,KAAK2gB,cAAcxhB,EAAOgjB,EAEvCzB,GAAWK,OAAS/gB,KAAKib,eAAe/I,KAAKwO,EAAWK,OACxDL,EAAWM,OAAahhB,KAAKgb,WAAW9I,KAAKwO,EAAWM,OACxDN,EAAWtR,MAAapP,KAAKgb,WAAW9I,KAAKwO,EAAWtR,MACxDsR,EAAW2C,YACXrjB,KAAK6gB,gBAAgBH,EAAW2C,YAGpCvlB,EAAOoU,KAAKiQ,OAEPniB,MAAKuC,UACV4f,EAAW,GAAIvU,GAAc5N,KAAMb,EAAO,SAAU,MAAOa,KAAKrC,SAChEG,EAAOoU,KAAKiQ,IAEPniB,KAAKkU,YACViO,EAAW,GAAIvU,GAAc5N,KAAMb,EAAO,UAAW,MAAOa,KAAKrC,SACjEG,EAAOoU,KAAKiQ,GAGhBniB,MAAK2V,KAAKxW,IAGdmkB,aAAc,SAAU3lB,GACpB,GAEIxB,GAFAonB,KACA9W,IAMJ,KAHA9O,EAAUA,GAAWqC,KAAKrC,QAGrBxB,EAAI,EAAGA,EAAIgB,EAAMqG,cAAc9G,OAAQP,IACxC,GAAKgB,EAAMqG,cAAcrH,GAAG4B,QAAQqR,KAAKtN,QAAzC,CAEA,GAAIyf,GAAUpkB,EAAMqG,cAAcrH,GAC9BkT,EAASkS,EAAQxjB,QAAQqR,KAAKC,MAGlC,MAAK5R,EAAMyD,UAAUmO,IAAWA,IAAW1R,GACnCR,EAAMmL,SAAS+G,KACflS,EAAM+F,gBAAgBvF,EAAS0R,IAQvC,IAAK,GAFDmU,GAAejC,EAAQxe,SAAUwe,EAAQpd,SAASwJ,iBAAiB4T,EAAQxe,WAAawe,EAAQ5d,UAE3FN,EAAI,EAAGnG,EAAMsmB,EAAa9mB,OAAYQ,EAAJmG,EAASA,IAAK,CACrD,GAAIogB,GAAiBD,EAAangB,EAE9BogB,KAAmB9lB,IAIvB4lB,EAAMxe,KAAKwc,GACX9U,EAAS1H,KAAK0e,KAItB,OACInH,UAAWiH,EACX9W,SAAUA,IAIlBoU,gBAAiB,SAAU1hB,GACvB,GAAIhD,GACAolB,EACAkC,EACAC,CAGJ,KAAKvnB,EAAI,EAAGA,EAAI6D,KAAKqc,YAAYC,UAAU5f,OAAQP,IAC/ColB,EAAUvhB,KAAKqc,YAAYC,UAAUngB,GACrCsnB,EAAiBzjB,KAAKqc,YAAY5P,SAAUtQ,GAGxCsnB,IAAmBC,IAEnBvkB,EAAMrB,OAAS2lB,EACflC,EAAQrP,KAAK/S,IAEjBukB,EAAcD,GAOtBhD,eAAgB,SAAUkD,GAEtB,GAAIC,GAAgB5jB,KAAKsjB,aAAaK,GAAa,EAEnD3jB,MAAKqc,YAAYC,UAAYsH,EAActH,UAC3Ctc,KAAKqc,YAAY5P,SAAYmX,EAAcnX,SAC3CzM,KAAKqc,YAAYE,QAEjB,KAAK,GAAIpgB,GAAI,EAAGA,EAAI6D,KAAKqc,YAAYC,UAAU5f,OAAQP,IACnD6D,KAAKqc,YAAYE,MAAMpgB,GAAK6D,KAAKqc,YAAYC,UAAUngB,GAAGmF,QAAQtB,KAAKqc,YAAY5P,SAAStQ,KAIpG2kB,QAAS,SAAU3hB,EAAOwkB,GACtB,GAAIE,KAEA1mB,GAAMwJ,aACN3G,KAAKygB,eAAekD,EAIxB,KAAK,GAAItgB,GAAI,EAAGA,EAAIrD,KAAKqc,YAAYC,UAAU5f,OAAQ2G,IAAK,CACxD,GAAIke,GAAiBvhB,KAAKqc,YAAYC,UAAUjZ,GAC5CogB,EAAiBzjB,KAAKqc,YAAY5P,SAAUpJ,GAC5C9C,EAAiBP,KAAKqc,YAAYE,MAAUlZ,EAEhDwgB,GAAW9e,KAAKwc,EAAQ9R,UAAUzP,KAAK3B,SAAS,GAAIc,EAAOa,KAAKlC,OAAQ6lB,EAAaF,EAAgBljB,GAC/FkjB,EACA,MAIV,GAAIK,GAAY3mB,EAAMqP,sBAAsBqX,GACxCnX,EAAY1M,KAAKqc,YAAYC,UAAUwH,IAAc,KACrDnmB,EAAYqC,KAAKqc,YAAY5P,SAAUqX,IAAc,IAEzD,QACIpX,SAAUA,EACV/O,QAASA,IAIjBgjB,cAAe,SAAUoD,EAAcvD,GACnC,GAAIE,IACAM,MAAY,KACZD,MAAY,KACZH,SAAY,KACZyC,WAAY,KACZ3e,KAAY,KACZ0K,KAAY,KA2FhB,OAxFIpP,MAAK2P,cAAgB3P,KAAKkb,kBAEtBlb,KAAKib,iBACLyF,EAAWK,OACPjjB,OAAekC,KAAKkb,gBACpBxO,SAAe1M,KAAKib,eACpBpC,cAAe2H,EAAU1iB,OACzBiR,UAAeyR,EAAU/W,aACzB+W,UAAeA,EACfvjB,YAAe+C,KACf6Z,UAAe2G,EAAU3G,UACzBra,KAAe,aAGnBghB,EAAUwD,UAAYhkB,KAAKkb,gBAC3BsF,EAAUyD,aAAejkB,KAAKib,gBAG9Bjb,KAAKgb,aACL0F,EAAWM,OACPljB,OAAekC,KAAK2P,YACpBjD,SAAe1M,KAAKgb,WACpBnC,cAAe2H,EAAU1iB,OACzBiR,UAAeyR,EAAU/W,aACzB+W,UAAeA,EACfvjB,YAAe+C,KACf6Z,UAAe2G,EAAU3G,UACzBra,KAAe,aAGnBghB,EAAU0D,UAAYlkB,KAAK2P,YAC3B6Q,EAAU9T,SAAW1M,KAAKgb,aAIX,YAAnBwF,EAAUhhB,MAAsBQ,KAAKgb,aACrC0F,EAAWtR,MACPtR,OAAekC,KAAK2P,YACpBjD,SAAe1M,KAAKgb,WACpBnC,cAAe2H,EAAU1iB,OACzBiR,UAAeyR,EAAU/W,aACzB+W,UAAeA,EACfvjB,YAAe+C,KACf6Z,UAAe2G,EAAU3G,UACzBra,KAAe,QAGnBghB,EAAU9T,SAAW1M,KAAKgb,YAEP,cAAnBwF,EAAUhhB,OACVkhB,EAAWE,UACP9iB,OAAe,KACf4O,SAAe,KACfmM,cAAe2H,EAAU1iB,OACzBiR,UAAeyR,EAAU/W,aACzB+W,UAAeA,EACfvjB,YAAe+C,KACf6Z,UAAe2G,EAAU3G,UACzBra,KAAe,iBAGA,YAAnBghB,EAAUhhB,OACVkhB,EAAW2C,YACPvlB,OAAe,KACf4O,SAAe,KACfmM,cAAe2H,EAAU1iB,OACzBiR,UAAeyR,EAAU/W,aACzB+W,UAAeA,EACfvjB,YAAe+C,KACf6Z,UAAe2G,EAAU3G,UACzBra,KAAe,mBAGA,aAAnBghB,EAAUhhB,MAAuBQ,KAAKgb,aACtC0F,EAAWhc,MACP5G,OAAekC,KAAK2P,YACpBjD,SAAe1M,KAAKgb,WACpBnC,cAAe2H,EAAU1iB,OACzBiR,UAAeyR,EAAU/W,aACzB+W,UAAeA,EACfvjB,YAAe+C,KACfmkB,SAAe3D,EACf3G,UAAe2G,EAAU3G,UACzBra,KAAe,YAEnBghB,EAAU9T,SAAW1M,KAAKgb,YAGvB0F,GAGXja,cAAe,WACX,MAAQzG,MAAKsC,UAAY,QAAYtC,KAAKuC,UAAY,UAAcvC,KAAKkU,WAAa,WAAc,MAGxGlV,YAAa,WACT,MAAOgB,MAAKsC,UAAYtC,KAAKuC,UAAYvC,KAAKkU,WAGlDkQ,aAAc,WACVpkB,KAAKlC,OAASkC,KAAKrC,QAAU,KAE7BqC,KAAKgb,WAAahb,KAAK2P,YAAc3P,KAAKib,eAAiBjb,KAAKkb,gBAAkB,MAGtFvF,KAAM,SAAUxW,GACZ,GAAIa,KAAKhB,cAAe,CACpB7B,EAAM0J,WAAW8O,OACjB3V,KAAKmU,WACLnU,KAAKoU,gBAEL,IAAItW,GAASkC,KAAKlC,MAEdA,GAAOC,QAAQgU,cACfjU,EAAO8G,KAAK8D,gBAAgBgL,MAAMC,OAAS,IAI3CxU,GAAShC,EAAMyM,WAAWzK,EAAMe,iBAChCF,KAAK0G,uBAAuBvH,EAAOrB,EAAQkC,KAAKrC,SAGhDqC,KAAKsC,WACLtC,KAAKqc,YAAYC,UAAYtc,KAAKqc,YAAY5P,SAAWzM,KAAKqc,YAAYE,MAAQ,MAI1Fvc,KAAKokB,eAELpkB,KAAKF,cAAgBE,KAAKwU,WAAW6C,OAASrX,KAAKsC,SAAWtC,KAAKuC,SAAWvC,KAAKkU,WAAY,EAC/FlU,KAAKhC,SAASC,KAAO+B,KAAK8U,UAAY,KACtC9U,KAAKpC,cAAcmb,SAAW/Y,KAAKpC,cAAcob,SAAW,CAG5D,KAAK,GAAI7c,GAAI,EAAGA,EAAI6D,KAAK3B,SAAS3B,OAAQP,IACuC,KAAzEgB,EAAM2V,QAAQ9S,KAAKlB,WAAYrB,EAAMC,aAAasC,KAAK3B,SAASlC,MAChE6D,KAAK3B,SAAS0U,OAAO5W,EAAG,EAIhC,KAAKA,EAAI,EAAGA,EAAIgB,EAAMC,aAAaV,OAAQP,IAEnCgB,EAAMC,aAAajB,KAAO6D,MAAQ7C,EAAMC,aAAajB,GAAGiC,QAAU4B,KAAK5B,OACvEjB,EAAMC,aAAa2V,OAAO5V,EAAM2V,QAAQ3V,EAAMC,aAAc4C,MAAO,IAK/Eic,aAAc,WACV,GAAIre,GAAgBoC,KAAKpC,cACrBG,EAAUiC,KAAKlC,OAAOC,QAAQiC,KAAKhC,SAASC,MAAMC,QAClDmmB,EAAStmB,EAAQumB,WACjB3oB,GAAI,GAAI+D,OAAOC,UAAY,IAAO/B,EAAc+a,EAEpD,IAAIhd,EAAIiC,EAAc2mB,GAAI,CAEtB,GAAIC,GAAY,GAAKjV,KAAKkV,KAAKJ,EAAS1oB,GAAKiC,EAAcge,WAAahe,EAAcie,SAEtF,IAAIje,EAAcslB,aAAetlB,EAAc0d,IAAM1d,EAAculB,aAAevlB,EAAc2d,GAC5F3d,EAAc4d,GAAK5d,EAAc0d,GAAKkJ,EACtC5mB,EAAc6d,GAAK7d,EAAc2d,GAAKiJ,MAErC,CACD,GAAIE,GAAYvnB,EAAM+M,uBAClB,EAAG,EACHtM,EAAc0d,GAAI1d,EAAc2d,GAChC3d,EAAcslB,WAAYtlB,EAAculB,WACxCqB,EAEJ5mB,GAAc4d,GAAKkJ,EAAU1jB,EAC7BpD,EAAc6d,GAAKiJ,EAAUzjB,EAGjCjB,KAAK6F,YAAYjI,EAAcwd,WAAYxd,EAAcwd,YAEzDxd,EAAczB,EAAIwhB,EAAeyF,QAAQpjB,KAAKgc,uBAG9Cpe,GAAc4d,GAAK5d,EAAcslB,WACjCtlB,EAAc6d,GAAK7d,EAAculB,WAEjCnjB,KAAK6F,YAAYjI,EAAcwd,WAAYxd,EAAcwd,YAEzDxd,EAAcC,QAAS,EACvBmC,KAAKkF,WAAWtH,EAAcwd,WAAYxd,EAAcwd,aAIhEe,eAAgB,WACZ,GAAIve,GAAgBoC,KAAKpC,cACrBjC,GAAI,GAAI+D,OAAOC,UAAY/B,EAAc+a,GACzCoB,EAAW/Z,KAAKlC,OAAOC,QAAQiC,KAAKhC,SAASC,MAAMC,QAAQymB,iBAEvD5K,GAAJpe,GACAiC,EAAc4d,GAAKre,EAAMuN,YAAY/O,EAAG,EAAGiC,EAAc0d,GAAIvB,GAC7Dnc,EAAc6d,GAAKte,EAAMuN,YAAY/O,EAAG,EAAGiC,EAAc2d,GAAIxB,GAE7D/Z,KAAK6F,YAAYjI,EAAcwd,WAAYxd,EAAcwd,YAEzDxd,EAAczB,EAAIwhB,EAAeyF,QAAQpjB,KAAKkc,uBAG9Cte,EAAc4d,GAAK5d,EAAc0d,GACjC1d,EAAc6d,GAAK7d,EAAc2d,GAEjCvb,KAAK6F,YAAYjI,EAAcwd,WAAYxd,EAAcwd,YAEzDxd,EAAcC,QAAS,EACvBD,EAAcud,WAAY,EAE1Bnb,KAAKkF,WAAWtH,EAAcwd,WAAYxd,EAAcwd,cAIhE7c,WAAY,SAAUzB,GAClB,GAAIU,GAAKC,EAAMC,aAAaZ,GACxB8P,EAAQ5M,KAAK5B,MAAO,EAAIjB,EAAM2V,QAAQ9S,KAAKlB,WAAYtB,EAS3D,OAPc,KAAVoP,IACAA,EAAQ5M,KAAKlB,WAAWpC,QAG5BsD,KAAKlB,WAAW8N,GAASpP,EACzBwC,KAAK3B,SAASuO,GAAS9P,EAEhB8P,GAGXtO,cAAe,SAAUxB,GACrB,GAAIU,GAAKC,EAAMC,aAAaZ,GACxB8P,EAAQ5M,KAAK5B,MAAO,EAAIjB,EAAM2V,QAAQ9S,KAAKlB,WAAYtB,EAE7C,MAAVoP,IAEC5M,KAAKhB,eACNgB,KAAK3B,SAAS0U,OAAOnG,EAAO,GAGhC5M,KAAKlB,WAAYiU,OAAOnG,EAAO,GAC/B5M,KAAKwc,YAAYzJ,OAAOnG,EAAO,GAC/B5M,KAAK2U,UAAY5B,OAAOnG,EAAO,GAC/B5M,KAAKyc,WAAY1J,OAAOnG,EAAO,KAGnC2H,cAAe,SAAUzX,GAGrB,IAAIkD,KAAKpC,cAAcC,OAAvB,CAEA,GAAI+O,GAAQ5M,KAAK5B,MAAO,EAAGjB,EAAM2V,QAAQ9S,KAAKlB,WAAYrB,EAAMC,aAAaZ,GAE/D,MAAV8P,IAEJ5M,KAAK3B,SAASuO,GAAS9P,KAG3BgiB,oBAAqB,SAAUhiB,EAASqC,EAAOnC,EAAaD,GAcxD,QAAS6nB,GAAkBnb,EAAc1G,EAAUC,GAC/C,GAAI6hB,GAAM1nB,EAAMiL,mBACVpF,EAAQ2K,iBAAiB5K,GACzBmL,MAEFzE,GAAa7F,SAAS7G,IACnBU,EAAMyD,UAAUvD,IAChBR,EAAMiO,UAAU3B,EAAc9L,KAC7BR,EAAMkO,WAAW5B,EAAc9L,EAASX,IACzCG,EAAMoO,UAAU9B,EAAc9L,EAASX,IACvCG,EAAM+F,gBAAgBvF,EAASoF,EAAU8hB,KAE5C3T,EAAQnM,KAAK0E,GACbgD,EAAS1H,KAAKpH,IA1BtB,GAAI+gB,GAAe1e,KAAK5B,MAAO,EAAIjB,EAAM2V,QAAQ9S,KAAKlB,WAAYrB,EAAMC,aAAaZ,GAGrF,IAAkB,QAAdC,IAAwBiD,KAAKqd,iBAExBrd,KAAKwc,YAAYkC,IAAiB1e,KAAKwc,YAAYkC,KAAkB1hB,EAF9E,CA8BA,IAxBA,GAAIkU,MACAzE,KACA9O,EAAUX,EAoBVuG,EAAWpG,EAAMoG,SAEd5F,GACC4F,EAASsQ,MAAMlW,IAAY4F,EAAS5F,GAASiG,SAAS7G,KACtDmU,EAAQnM,KAAKxB,EAAS5F,IACtB8O,EAAS1H,KAAKpH,IAGlBR,EAAMqG,cAAcuK,gBAAgB6W,GAEpCjnB,EAAUR,EAAMqB,cAAcb,IAK9BuT,EAAQxU,QAAwB,QAAdK,IAClBiD,KAAK8kB,aAAahoB,EAASqC,EAAOnC,EAAakU,EAASzE,EAAU1P,KAI1E+nB,aAAc,SAAUhoB,EAASqC,EAAOnC,EAAakU,EAASzE,EAAU1P,GACpE,GAEIZ,GAEA4oB,EAAUC,EAJVtG,EAAe1e,KAAK5B,MAAO,EAAIjB,EAAM2V,QAAQrV,EAAMC,aAAaZ,IAChEinB,IA4CJ,KArCkB,cAAdhnB,EACAgnB,EAAejnB,GAGfW,EAAMiE,OAAOqiB,EAAc5kB,GACvBA,IAAUrC,GACVW,EAAMiE,OAAOqiB,EAAcjnB,GAG/BinB,EAAa7jB,eAA2BH,EACxCgkB,EAAajJ,gBAA2BlN,EAAczF,UAAU2S,gBAChEiJ,EAAanJ,yBAA2BhN,EAAczF,UAAUyS,yBAChEmJ,EAAa9mB,YAA2B+C,KAExC+jB,EAAalK,WAAgB,GAAIna,OAAOC,UACxCokB,EAAa9jB,cAAgBd,EAC7B4kB,EAAavkB,KAAgBzC,EAC7BgnB,EAAakB,UAAgBxnB,EAAMC,aAAaZ,GAChDinB,EAAaxmB,YAAgByC,KAAK5B,MAAO,QAAWK,EAAQE,qBACtDxB,EAAMmL,SAASxL,EAAQS,aACvBT,EAAQS,aACP,CAAC,CAAC,QAAS,MAAO,SAAST,EAAQS,aAHwC,SAMpE,QAAdR,IACAgnB,EAAajK,GAAKiK,EAAalK,UAAY7Z,KAAK2U,UAAU+J,GAE1DqG,EAAWhB,EAAalK,UAAY7Z,KAAK2c,QACzCqI,KAAwBhlB,KAAK4c,SAAiC,cAAtB5c,KAAK4c,QAAQpd,MAClDQ,KAAK4c,QAAQ9e,SAAWimB,EAAajmB,QAC1B,IAAXinB,GAEHhB,EAAAA,UAAsBiB,EAEtBhlB,KAAK2c,QAAUoH,EAAalK,WAG3B1d,EAAI,EAAGA,EAAI+U,EAAQxU,SACpBqnB,EAAaxkB,cAAgBkN,EAAStQ,GACtC4nB,EAAata,aAAeyH,EAAQ/U,GACpC+U,EAAQ/U,GAAG+V,KAAK6R,KAEZA,EAAazR,6BACVyR,EAAalJ,oBAAsBpO,EAAStQ,EAAI,KAAO4nB,EAAaxkB,gBAN/CpD,KAWhC,GAAI6oB,EAAoB,CACpB,GAAIE,KAEJznB,GAAMiE,OAAOwjB,EAAWnB,GAExBmB,EAAUpL,GAAOiL,EACjBG,EAAU1lB,KAAO,YAEjBQ,KAAK8e,oBAAoBoG,EAAW/lB,EAAOnC,EAAa,aAExDgD,KAAK4c,QAAUsI,MAEI,QAAdnoB,IACLiD,KAAK4c,QAAUmH,IAIvBxF,iBAAkB,SAAUzhB,EAASqC,EAAOgV,EAASC,GACjD,IAAK,GAAIjY,GAAI,EAAGe,EAAMiX,EAAQzX,OAAYQ,EAAJf,EAASA,IAAK,CAChD,GAAIgpB,GAAQhR,EAAQhY,GAChBipB,EAAehR,EAAcjY,GAC7BqF,EAAS8b,EAAe6H,EAAMvT,UAAU9U,EAASqC,EAAOa,KAAMolB,GAAeD,EAEjF,IAAI3jB,GAAUrE,EAAM6O,uBAAuBmZ,EAAOC,EAAc5jB,GAI5D,MAHAxB,MAAKlC,OAASqnB,EACdnlB,KAAKrC,QAAUynB,EAER5jB,IAKnBge,YAAa,SAAU6F,EAAYC,GAC/B,GAEIxnB,GACAuC,EACAlE,EAJAyP,EAAO5L,KAAKlC,OAAOC,QAAQiC,KAAKhC,SAASC,MAAM2N,KAC/CsF,IAOJ,IAFAoU,EAASA,GAAUtlB,KAAKwU,WAEpB8Q,EAAOrC,YACP5iB,GAASW,EAAGskB,EAAOtkB,EAAGC,EAAGqkB,EAAOrkB,OAE/B,CACD,GAAIyI,GAASvM,EAAMqM,YAAYxJ,KAAKlC,OAAQkC,KAAKrC,QAEjD0C,GAAO5C,EAAMiE,UAAW2jB,GAExBhlB,EAAKW,GAAK0I,EAAO1I,EACjBX,EAAKY,GAAKyI,EAAOzI,EAGrBqkB,EAAO9N,MAAQnX,EAAKW,EACpBskB,EAAO7N,MAAQpX,EAAKY,EAEpBZ,EAAKW,EAAIX,EAAKW,EAAIhB,KAAKpC,cAAcmb,SACrC1Y,EAAKY,EAAIZ,EAAKY,EAAIjB,KAAKpC,cAAcob,QAIrC,KAAK,GAFD9b,GAAM0O,EAAKsF,QAAStF,EAAKsF,QAAQxU,OAAS,EAErC6oB,EAAW,EAAGA,EAAWvlB,KAAK+c,YAAYrgB,OAAQ6oB,IAAY,CACnE,GAAIC,IACAxkB,EAAGX,EAAKW,EAAIhB,KAAK+c,YAAYwI,GAAUvkB,EACvCC,EAAGZ,EAAKY,EAAIjB,KAAK+c,YAAYwI,GAAUtkB,EAG3C,KAAK9E,EAAI,EAAOe,EAAJf,EAASA,IAEb2B,EADAX,EAAMyM,WAAWgC,EAAKsF,QAAQ/U,IACrByP,EAAKsF,QAAQ/U,GAAGqpB,EAASxkB,EAAGwkB,EAASvkB,EAAGjB,MAGxC4L,EAAKsF,QAAQ/U,GAGrB2B,GAELoT,EAAQnM,MACJ/D,EAAG7D,EAAMuD,SAAS5C,EAAOkD,GAAMlD,EAAOkD,EAAIhB,KAAK+c,YAAYwI,GAAUvkB,EAAKwkB,EAASxkB,EACnFC,EAAG9D,EAAMuD,SAAS5C,EAAOmD,GAAMnD,EAAOmD,EAAIjB,KAAK+c,YAAYwI,GAAUtkB,EAAKukB,EAASvkB,EAEnFkV,MAAOhZ,EAAMuD,SAAS5C,EAAOqY,OAAQrY,EAAOqY,MAAOvK,EAAKuK,QAKpE,GAAIxM,IACA7L,OAAQ,KACR2nB,SAAS,EACTtM,SAAU,EACVhD,MAAO,EACPuB,GAAI,EACJC,GAAI,EAGR,KAAKxb,EAAI,EAAGe,EAAMgU,EAAQxU,OAAYQ,EAAJf,EAASA,IAAK,CAC5C2B,EAASoT,EAAQ/U,EAEjB,IAAIga,GAAQrY,EAAOqY,MACfuB,EAAK5Z,EAAOkD,EAAIX,EAAKW,EACrB2W,EAAK7Z,EAAOmD,EAAIZ,EAAKY,EACrBkY,EAAW1b,EAAM0c,MAAMzC,EAAIC,GAC3B8N,EAAsBtP,GAAZgD,CAIVhD,KAAUnP,EAAAA,GAAY2C,EAAQ8b,SAAW9b,EAAQwM,QAAUnP,EAAAA,IAC3Dye,GAAU,KAGT9b,EAAQ7L,SAAW2nB,EAEb9b,EAAQ8b,SAAWtP,IAAUnP,EAAAA,EAE9BmS,EAAWhD,EAAQxM,EAAQwP,SAAWxP,EAAQwM,MAE7CA,IAAUnP,EAAAA,GAAY2C,EAAQwM,QAAUnP,EAAAA,GAE5CmS,EAAWxP,EAAQwP,UAEdxP,EAAQ8b,SAAWtM,EAAWxP,EAAQwP,aAE1ChD,IAAUnP,EAAAA,IACVye,GAAU,GAGd9b,EAAQ7L,OAASA,EACjB6L,EAAQwP,SAAWA,EACnBxP,EAAQwM,MAAQA,EAChBxM,EAAQ8b,QAAUA,EAClB9b,EAAQ+N,GAAKA,EACb/N,EAAQgO,GAAKA,EAEb2N,EAAOnP,MAAQA,GAIvB,GAAIuP,EAqBJ,OAnBI/b,GAAQ7L,QACR4nB,EAAeJ,EAAOhO,WAAa3N,EAAQ7L,OAAOkD,GAAKskB,EAAO/N,WAAa5N,EAAQ7L,OAAOmD,EAE1FqkB,EAAOhO,SAAW3N,EAAQ7L,OAAOkD,EACjCskB,EAAO/N,SAAW5N,EAAQ7L,OAAOmD,IAGjCykB,GAAc,EAEdJ,EAAOhO,SAAW2H,EAAAA,EAClBqG,EAAO/N,SAAW0H,EAAAA,GAGtBqG,EAAO5N,GAAK/N,EAAQ+N,GACpB4N,EAAO3N,GAAKhO,EAAQgO,GAEpB2N,EAAOpI,QAAWwI,GAAgB/b,EAAQ8b,UAAYH,EAAOjO,OAC7DiO,EAAOjO,OAAS1N,EAAQ8b,QAEjBH,GAGX7F,eAAgB,SAAU4F,EAAYC,GAClC,GAGIjlB,GAHAvC,EAASkC,KAAKlC,OACdgO,EAAWhO,GAAUA,EAAOC,QAAQiC,KAAKhC,SAASC,MAAM6N,SACxDmG,EAAcnG,GAAYA,EAASmG,WAGvC,KAAKA,EACD,MAAOqT,EAGXA,GAASA,GAAUtlB,KAAKyU,eAExBpU,EACMA,EADCilB,EAAOrC,aACCjiB,EAAGskB,EAAOtkB,EAAGC,EAAGqkB,EAAOrkB,GACzBxD,EAAMiE,UAAW2jB,GAE1BC,EAAO1Z,MAAQ0Z,EAAO1Z,KAAKyL,SAC3BhX,EAAKW,GAAKskB,EAAO1Z,KAAK8L,IAAM,EAC5BrX,EAAKY,GAAKqkB,EAAO1Z,KAAK+L,IAAM,GAGhCtX,EAAKW,GAAKhB,KAAKpC,cAAcmb,SAC7B1Y,EAAKY,GAAKjB,KAAKpC,cAAcob,SAE7BsM,EAAO5N,GAAK,EACZ4N,EAAO3N,GAAK,EACZ2N,EAAOzN,YAAa,CAEpB,IAAItX,GAAM4c,EAAaC,CAEvB,OAAIjgB,GAAMmL,SAAS2J,KAEXA,EADgB,WAAhBA,EACc9U,EAAMqB,cAAcwB,KAAKrC,SAElB,SAAhBsU,EACSnU,EAAOwD,QAAQtB,KAAKrC,SAGpBR,EAAMwM,QAAQ3J,KAAKrC,QAASsU,IAGzCA,GAAsBqT,GAG3BnoB,EAAMyM,WAAWqI,KACjBA,EAAcA,EAAY5R,EAAKW,EAAGX,EAAKY,EAAGjB,KAAKrC,UAG/CF,EAAMyD,UAAU+Q,KAChBA,EAAc9U,EAAM6L,eAAeiJ,IAGvC1R,EAAO0R,EAEFA,EAOI,KAAOA,IAAe,KAAOA,IAClCkL,EAAc5N,KAAKrD,IAAIqD,KAAKC,IAAIjP,EAAKS,EAAIT,EAAKE,MAAST,KAAK8c,eAAenc,MAAQN,EAAKW,GAAIT,EAAKS,EAAIhB,KAAK8c,eAAelc,MACzHwc,EAAc7N,KAAKrD,IAAIqD,KAAKC,IAAIjP,EAAKU,EAAIV,EAAKM,OAASb,KAAK8c,eAAehc,OAAQT,EAAKY,GAAIV,EAAKU,EAAIjB,KAAK8c,eAAe/b,OAGzHoc,EAAc5N,KAAKrD,IAAIqD,KAAKC,IAAIjP,EAAKI,MAASX,KAAK8c,eAAenc,MAAQN,EAAKW,GAAIT,EAAKK,KAAOZ,KAAK8c,eAAelc,MACnHwc,EAAc7N,KAAKrD,IAAIqD,KAAKC,IAAIjP,EAAKO,OAASd,KAAK8c,eAAehc,OAAQT,EAAKY,GAAIV,EAAKQ,IAAOf,KAAK8c,eAAe/b,OAZnHoc,EAAc9c,EAAKW,EACnBoc,EAAc/c,EAAKY,GAcvBqkB,EAAO5N,GAAKyF,EAAc9c,EAAKW,EAC/BskB,EAAO3N,GAAKyF,EAAc/c,EAAKY,EAE/BqkB,EAAOpI,QAAUoI,EAAOnI,cAAgBA,GAAemI,EAAOlI,cAAgBA,EAC9EkI,EAAOzN,cAAgByN,EAAO5N,KAAM4N,EAAO3N,IAE3C2N,EAAOnI,YAAcA,EACrBmI,EAAOlI,YAAcA,EAEdkI,IAGX5e,uBAAwB,SAAUvH,EAAOsK,EAAc9L,GACnD,GAAM8L,EAAeA,GAAgBzJ,KAAKlC,OAA1C,CAEA,GAAIC,GAAU0L,EAAa1L,QACvB4nB,EAAU5nB,EAAQmC,cAEtB,IAAgB,SAAZylB,GAAsBhoB,IAAY,6BAA6BL,KAAK6B,EAAMrB,OAAO8nB,UAAW,CAI5F,GAAI,cAActoB,KAAK6B,EAAMK,OACC,SAAvBQ,KAAKhC,SAASC,MAAyC,OAAtBF,EAAQsE,KAAKD,KAEjD,MAIJ,IAAIrE,EAAQiC,KAAKhC,SAASC,OAASF,EAAQiC,KAAKhC,SAASC,MAAMkiB,cACvDngB,KAAKhB,cACT,MAIJ,YADAG,GAAMe,iBAIV,MAAgB,WAAZylB,MACAxmB,GAAMe,iBADV,SAMJ6iB,YAAa,SAAUuC,GACnB,GAAIlD,GAAiBpiB,KAAKlC,OAAOC,QAAQiC,KAAKhC,SAASC,MAAMC,QACzDmmB,EAASjC,EAAekC,WACxBuB,GAActW,KAAKuW,IAAI1D,EAAeO,SAAW2C,EAAOxC,IAAMuB,CAElEiB,GAAOpN,GAAKlY,KAAK8U,UAAUgD,MAC3BwN,EAAOnN,GAAKnY,KAAK8U,UAAUiD,MAC3BuN,EAAO3M,GAAK2M,EAAOlK,WAAWvB,UAAY,IAC1CyL,EAAO9J,GAAK8J,EAAO7J,GAAK,EAExB6J,EAAOpC,WAAaoC,EAAOhK,IAAMgK,EAAO5J,IAAMmK,GAAcxB,EAC5DiB,EAAOnC,WAAamC,EAAO/J,IAAM+J,EAAOzC,IAAMgD,GAAcxB,EAC5DiB,EAAOf,GAAKsB,EAEZP,EAAO1J,UAAYyI,EAASiB,EAAOxC,GACnCwC,EAAOzJ,UAAY,EAAIuG,EAAeO,SAAW2C,EAAOxC,IAG5D5c,eAAgB,SAAUpJ,GACtB,GAAMkD,KAAKhB,eACJ7B,EAAM4O,gBAAgB/L,KAAKlC,OAAQkC,KAAKhC,SAASC,MADxD,CAKA,GAAI+B,KAAKpC,cAAcC,OAEnB,YADAV,EAAM0J,WAAW7F,EAAI7D,EAAM0J,WAAW5F,EAAI,EAI9C,IAAIF,GACAJ,EACAG,EACAF,EACA7C,EAAUiC,KAAKlC,OAAOC,QAAQiC,KAAKhC,SAASC,MAAM4I,WAClDkf,EAAYhoB,EAAQgoB,WAAa5oB,EAAM4G,UAAU/D,KAAKrC,QAE1D,IAAIR,EAAM6oB,SAASD,GACfnlB,EAAS9D,EAAQkb,QAAU7a,EAAM0J,WAAWrG,OAC5CO,EAASjE,EAAQmb,QAAU9a,EAAM0J,WAAWrG,OAC5CG,EAAS7D,EAAQkb,QAAU+N,EAAUE,WAAc9oB,EAAM0J,WAAWrG,OACpEM,EAAShE,EAAQmb,QAAU8N,EAAUG,YAAc/oB,EAAM0J,WAAWrG,WAEnE,CACD,GAAID,GAAOpD,EAAM6L,eAAe+c,EAEhCnlB,GAAS9D,EAAQkb,QAAUzX,EAAKK,KAASzD,EAAM0J,WAAWrG,OAC1DO,EAASjE,EAAQmb,QAAU1X,EAAKQ,IAAS5D,EAAM0J,WAAWrG,OAC1DG,EAAS7D,EAAQkb,QAAUzX,EAAKI,MAASxD,EAAM0J,WAAWrG,OAC1DM,EAAShE,EAAQmb,QAAU1X,EAAKO,OAAS3D,EAAM0J,WAAWrG,OAG9DrD,EAAM0J,WAAW7F,EAAKL,EAAQ,EAAGC,EAAM,GAAI,EAC3CzD,EAAM0J,WAAW5F,EAAKH,EAAQ,EAAIC,EAAK,GAAI,EAEtC5D,EAAM0J,WAAWsf,cAElBhpB,EAAM0J,WAAWrG,OAASzC,EAAQyC,OAClCrD,EAAM0J,WAAWmT,MAASjc,EAAQic,MAElC7c,EAAM0J,WAAWmW,MAAMhd,SAI/BH,oBAAqB,SAAU/B,EAAQyB,GACnCS,KAAKmC,aAAkBrE,EACvBkC,KAAK0c,gBAAkBnd,IAK/B3C,EAAOJ,QAAUoC,IAEdwX,kBAAkB,EAAEI,UAAU,EAAEC,UAAU,GAAG2P,kBAAkB,EAAE1P,iBAAiB,KAAK2P,GAAG,SAASnqB,EAAQU,EAAOJ,GACrH,YAEA,IAAIohB,GAAY1hB,EAAQ,eACpB6H,EAAY7H,EAAQ,kBAAkB6H,UACtCiiB,EAAY9pB,EAAQ,kBAAkB8pB,SAEtCnf,GAEA5J,YAAa,KACbd,EAAG,KACH6E,EAAG,EAAGC,EAAG,EAETklB,aAAa,EACbG,SAAU,EAEVtJ,MAAO,SAAU/f,GACb4J,EAAWsf,aAAc,EACzBvI,EAAIjY,OAAOkB,EAAW1K,GAEtB0K,EAAW5J,YAAcA,EACzB4J,EAAWyf,UAAW,GAAI5mB,OAAOC,UACjCkH,EAAW1K,EAAIyhB,EAAIwF,QAAQvc,EAAWoC,SAG1C0M,KAAM,WACF9O,EAAWsf,aAAc,EACzBvI,EAAIjY,OAAOkB,EAAW1K,IAI1B8M,OAAQ,WACJ,GAAIlL,GAAU8I,EAAW5J,YAAYa,OAAOC,QAAQ8I,EAAW5J,YAAYe,SAASC,MAAM4I,WACtFkf,EAAYhoB,EAAQgoB,WAAahiB,EAAU8C,EAAW5J,YAAYU,SAClE2kB,GAAM,GAAI5iB,OAAOC,UAEjBma,GAAMwI,EAAMzb,EAAWyf,UAAY,IAEnCxqB,EAAIiC,EAAQic,MAAQF,CAEpBhe,IAAK,IACDkqB,EAASD,GACTA,EAAUQ,SAAS1f,EAAW7F,EAAIlF,EAAG+K,EAAW5F,EAAInF,GAE/CiqB,IACLA,EAAUpd,YAAc9B,EAAW7F,EAAIlF,EACvCiqB,EAAUld,WAAchC,EAAW5F,EAAInF,GAG3C+K,EAAWyf,SAAWhE,GAGtBzb,EAAWsf,cACXvI,EAAIjY,OAAOkB,EAAW1K,GACtB0K,EAAW1K,EAAIyhB,EAAIwF,QAAQvc,EAAWoC,UAKlDrM,GAAOJ,QAAUqK,IAEd2f,iBAAiB,GAAGC,cAAc,GAAG9P,iBAAiB,KAAK+P,GAAG,SAASxqB,EAAQU,EAAOJ,GACzF,YAEAI,GAAOJ,SACH4W,MACI/D,OAAgB,KAChBwC,cAAgB,KAChBE,aAAgB,EAChB7R,eAAgB,OAChBwJ,QAAkB1I,EAAG,EAAGC,EAAG,GAC3B+Q,YAAgB,OAChBxG,UAAgB,KAChBF,WAAgB,KAChBnH,SAAgBjI,EAAQ,sBAAsBgI,SAC9C2L,YAAgB,MAGpBxN,MACIP,SAAS,EACTqe,aAAa,EACbjU,IAAKlF,EAAAA,EACLmF,cAAe,EAEfP,KAAM,KACNE,SAAU,KACV5N,QAAS,KACT2I,WAAY,KAEZzE,KAAM,MAGVgN,MACItN,SAAS,EACTuN,OAAQ,KACRC,QAAS,WAGbzN,QACIC,SAAS,EACTqe,aAAa,EACbjU,IAAKlF,EAAAA,EACLmF,cAAe,EAEfP,KAAM,KACNE,SAAU,KACV5N,QAAS,KACT2I,WAAY,KAEZ6J,QAAQ,EACRtO,KAAM,KAGN5B,OAAQye,EAAAA,EAMRhd,MAAO,KAMP0f,OAAQ,QAGZ5iB,SACIohB,aAAa,EACbre,SAAS,EACToK,IAAKlF,EAAAA,EACLmF,cAAe,EAEfL,SAAU,MAGdqD,WACIgR,aAAa,EACbjU,IAAKlF,EAAAA,EACLmF,cAAe,EAEfP,MACI9J,SAAc,EACdwd,SAAc,EACdnJ,MAAcnP,EAAAA,EACdkK,QAAc,KACdyV,QAAc,KAEdlV,eAAgB,MAGpB3F,UACIhK,SAAS,EACTwd,SAAS,GAGbzY,YACI/E,SAAc,EACdikB,UAAc,KACdvlB,OAAc,GACdwZ,MAAc,KAGlB9b,SACI4D,SAAmB,EACnBwiB,WAAmB,GACnB5B,SAAmB,IACnBC,SAAmB,GACnBxkB,aAAmB,EACnB2a,iBAAmB,EACnB6L,kBAAmB,MAI3B9F,cAAe,OAGhB+H,qBAAqB,IAAIC,GAAG,SAAS3qB,EAAQU,EAAOJ,GACvD;;AAEA,GAAIW,MACAuE,EAASxF,EAAQ,iBAErBwF,GAAOvE,EAAOjB,EAAQ,mBACtBwF,EAAOvE,EAAOjB,EAAQ,uBACtBwF,EAAOvE,EAAOjB,EAAQ,mBACtBwF,EAAOvE,EAAOjB,EAAQ,mBAEtBU,EAAOJ,QAAUW,IAEd2pB,iBAAiB,EAAEF,qBAAqB,EAAEG,iBAAiB,GAAGP,iBAAiB,GAAG7P,iBAAiB,KAAKqQ,GAAG,SAAS9qB,EAAQU,EAAOJ,GACtI,YAEA,SAASsW,GAASmU,EAAOnpB,GACrB,IAAK,GAAI3B,GAAI,EAAGe,EAAM+pB,EAAMvqB,OAAYQ,EAAJf,EAASA,IACzC,GAAI8qB,EAAM9qB,KAAO2B,EACb,MAAO3B,EAIf,OAAO,GAGX,QAAS0C,GAAUooB,EAAOnpB,GACtB,MAAkC,KAA3BgV,EAAQmU,EAAOnpB,GAG1BlB,EAAOJ,SACHsW,QAASA,EACTjU,SAAUA,QAGRqoB,GAAG,SAAShrB,EAAQU,EAAOJ,GACjC,YAEA,IAAI4I,GAAMlJ,EAAQ,YACdirB,EAAajrB,EAAQ,gBAErBuC,GAEAC,iBAAoB,gBAAkB0G,IAAQA,EAAIpB,OAAOojB,eAClDD,EAAWjjB,mBAAoBkB,GAAIgiB,eAG1CzoB,uBAAyBwoB,EAAW/iB,aAGpCijB,cAAuC,UAAtBC,UAAUC,SACpB9oB,EAAQC,eACR4oB,UAAUE,UAAUrC,MAAM,UAIjCjc,cAAiB,iBAAiB5L,KAAKgqB,UAAUG,WAAa,gBAAgBnqB,KAAKgqB,UAAUI,YAE7FxgB,aAAeigB,EAAWjjB,SAASyjB,MAAQviB,EAAIpB,OAAO4jB,KAGtD3f,wBAAyB,WAAaC,SAAQC,UACtC,UAAW,yBAA2BD,SAAQC,UAC1C,wBAAyB,sBAAwBD,SAAQC,UACrD,qBAAsB,oBAAsBD,SAAQC,UAChD,mBAAoB,oBAI5CvL,GAAOJ,QAAUiC,IAEdopB,eAAe,EAAEC,WAAW,KAAKC,GAAG,SAAS7rB,EAAQU,EAAOJ,GAC/D,YAEA,IAAI2qB,MACA/hB,EAAMlJ,EAAQ,YAAY8H,OAC1B2W,EAAQ,YAEZwM,GAAWjjB,SAAqBkB,EAAIlB,SACpCijB,EAAWa,iBAAqB5iB,EAAI4iB,kBAAsBrN,EAC1DwM,EAAW/d,WAAqBhE,EAAIgE,YAAsBuR,EAC1DwM,EAAWla,cAAqB7H,EAAI6H,eAAsB0N,EAC1DwM,EAAWre,mBAAqB1D,EAAI0D,oBAAsB6R,EAC1DwM,EAAWna,YAAqB5H,EAAI4H,aAAsB5H,EAAI8C,QAE9Dif,EAAW/iB,aAAgBgB,EAAIhB,cAAgBgB,EAAIG,eAEnD3I,EAAOJ,QAAU2qB,IAEdW,WAAW,KAAKG,IAAI,SAAS/rB,EAAQU,EAAOJ,GAC/C,YAgBA,SAAS8H,GAAK3G,EAAS6B,EAAMgT,EAAU/P,GACnC,GAAIylB,GAAepV,EAAQrG,EAAU9O,GACjCG,EAASoT,EAAQgX,EAuBrB,IArBKpqB,IACDA,GACIuG,UACA8jB,UAAW,GAGfD,EAAezb,EAAS1H,KAAKpH,GAAW,EACxCuT,EAAQnM,KAAKjH,GAEbsqB,EAAkBrjB,KAAMyB,GAChB6hB,YACAC,WACAC,aACA,OAGPzqB,EAAOuG,OAAO7E,KACf1B,EAAOuG,OAAO7E,MACd1B,EAAOqqB,cAGNtpB,EAASf,EAAOuG,OAAO7E,GAAOgT,GAAW,CAC1C,GAAIvE,EAEJ,IAAIzH,EAAgB,CAChB,GAAIpD,GAAYglB,EAAkBF,GAC9BM,EAAgB1V,EAAQ1P,EAAUilB,SAAU7V,GAE5C8V,EAAUllB,EAAUklB,QAAQE,IAAkB,SAAUrpB,GACnDA,EAAMmT,8BACPnT,EAAMrB,OAASqB,EAAMspB,WACrBtpB,EAAMI,cAAgB5B,EAEtBwB,EAAMe,eAAiBf,EAAMe,gBAAkBwoB,EAC/CvpB,EAAM2b,gBAAkB3b,EAAM2b,iBAAmB6N,EACjDxpB,EAAMyb,yBAA2Bzb,EAAMyb,0BAA4BgO,EAE/D,cAActrB,KAAK6B,EAAMK,QACzBL,EAAM2Y,MAAQ3Y,EAAM6Y,QAAUjU,EAAUpG,GAASuG,SAASwE,gBAAgBC,WAC1ExJ,EAAM4Y,MAAQ5Y,EAAM8Y,QAAUlU,EAAUpG,GAASuG,SAASwE,gBAAgBG,WAG9E2J,EAASrT,IAIjB8O,GAAMtQ,EAAQkrB,GAAUtW,EAAK/S,EAAM8oB,IAAW7lB,GAExB,KAAlB+lB,GACAplB,EAAUilB,SAAStjB,KAAKyN,GACxBpP,EAAUklB,QAAQvjB,KAAKujB,GACvBllB,EAAUmlB,SAASxjB,KAAK,IAGxB3B,EAAUmlB,SAASC,SAIvBva,GAAMtQ,EAAQkrB,GAAUrpB,EAAMgT,IAAY/P,EAI9C,OAFA3E,GAAOuG,OAAO7E,GAAMuF,KAAKyN,GAElBvE,GAIf,QAASkF,GAAQxV,EAAS6B,EAAMgT,EAAU/P,GACtC,GAAItG,GAGAiH,EACAolB,EAHAN,EAAepV,EAAQrG,EAAU9O,GACjCG,EAASoT,EAAQgX,GAGjBI,EAAU9V,CAEd,IAAK1U,GAAWA,EAAOuG,OAUvB,GANImC,IACApD,EAAYglB,EAAkBF,GAC9BM,EAAgB1V,EAAQ1P,EAAUilB,SAAU7V,GAC5C8V,EAAUllB,EAAUklB,QAAQE,IAGnB,QAAThpB,EAAJ,CASA,GAAI1B,EAAOuG,OAAO7E,GAAO,CACrB,GAAItC,GAAMY,EAAOuG,OAAO7E,GAAM9C,MAE9B,IAAiB,QAAb8V,EAAoB,CACpB,IAAKrW,EAAI,EAAOe,EAAJf,EAASA,IACjBgX,EAAOxV,EAAS6B,EAAM1B,EAAOuG,OAAO7E,GAAMrD,KAAMsG,EAEpD,QAEA,IAAKtG,EAAI,EAAOe,EAAJf,EAASA,IACjB,GAAI2B,EAAOuG,OAAO7E,GAAMrD,KAAOqW,EAAU,CACrC7U,EAAQmrB,GAAavW,EAAK/S,EAAM8oB,IAAW7lB,GAC3C3E,EAAOuG,OAAO7E,GAAMuT,OAAO5W,EAAG,GAE1BqK,GAAkBpD,IAClBA,EAAUmlB,SAASC,KACuB,IAAtCplB,EAAUmlB,SAASC,KACnBplB,EAAUilB,SAAStV,OAAOyV,EAAe,GACzCplB,EAAUklB,QAAQvV,OAAOyV,EAAe,GACxCplB,EAAUmlB,SAASxV,OAAOyV,EAAe,IAIjD,OAKR1qB,EAAOuG,OAAO7E,IAAwC,IAA/B1B,EAAOuG,OAAO7E,GAAM9C,SAC3CoB,EAAOuG,OAAO7E,GAAQ,KACtB1B,EAAOqqB,aAIVrqB,EAAOqqB,YACRjX,EAAQ6B,OAAOmV,EAAc,GAC7Bzb,EAASsG,OAAOmV,EAAc,GAC9BE,EAAkBrV,OAAOmV,EAAc,QA7CvC,KAAK1oB,IAAQ1B,GAAOuG,OACZvG,EAAOuG,OAAO0kB,eAAevpB,IAC7B2T,EAAOxV,EAAS6B,EAAM,OA+CtC,QAASkpB,KACL1oB,KAAKgpB,aAAc,EAGvB,QAASL,KACL3oB,KAAKipB,cAAe,EAGxB,QAASL,KACL5oB,KAAKipB,cAAe,EACpBjpB,KAAKsS,6BAA8B,EAlKvC,GAAI4W,GAAMhtB,EAAQ,SACd4W,EAAWoW,EAAIpW,QACfjU,EAAWqqB,EAAIrqB,SACfkF,EAAY7H,EAAQ,YAAY6H,UAEhCyC,EAAkB,eAAiBxC,WAAa,oBAAsBA,SACtE6kB,EAAiBriB,EAAiB,cAAe,mBACjDsiB,EAAiBtiB,EAAiB,cAAe,sBACjD+L,EAAiB/L,EAAgB,KAAM,GAEvCiG,KACAyE,KACAkX,IAyJJxrB,GAAOJ,SACH8H,IAAKA,EACL6O,OAAQA,EACR3M,eAAgBA,EAEhB2iB,UAAW1c,EACX2c,SAAUlY,EACVmY,mBAAoBjB,KAGrBkB,QAAQ,EAAExB,WAAW,KAAKyB,IAAI,SAASrtB,EAAQU,EAAOJ,GACzD,YAEAI,GAAOJ,QAAU,SAAiBgtB,EAAMC,GACpC,IAAK,GAAI5mB,KAAQ4mB,GACbD,EAAK3mB,GAAQ4mB,EAAO5mB,EAExB,OAAO2mB,SAGLE,IAAI,SAASxtB,EAAQU,EAAOJ,GAClC,YAEAI,GAAOJ,QAAU,SAAgBwE,EAAGC,GAAK,MAAOsO,MAAKoa,KAAK3oB,EAAIA,EAAIC,EAAIA,SAEhE2oB,IAAI,SAAS1tB,EAAQU,EAAOJ,GAClC,YAEA,IAAIiB,GAAQb,EAAOJ,QACfkF,EAASxF,EAAQ,YACjBkJ,EAAMlJ,EAAQ,WAElBuB,GAAMkd,MAAS,aAEfld,EAAMmW,SAAW,SAAU1U,EAAQ2qB,GAC/B,GAAIC,IAAS,CAEb,OAAO,YAMH,MALKA,KACD1kB,EAAIpB,OAAO+lB,QAAQC,KAAKH,GACxBC,GAAS,GAGN5qB,EAAO+qB,MAAMjqB,KAAMkqB,aAIlCzsB,EAAMiE,OAAUA,EAChBjE,EAAM0c,MAAUje,EAAQ,WACxBuB,EAAMmgB,IAAU1hB,EAAQ,SACxBuB,EAAMgB,QAAUvC,EAAQ,aAExBwF,EAAOjE,EAAOvB,EAAQ,UACtBwF,EAAOjE,EAAOvB,EAAQ,aACtBwF,EAAOjE,EAAOvB,EAAQ,qBAEnBotB,QAAQ,EAAEa,YAAY,EAAEC,WAAW,GAAGC,UAAU,GAAGC,WAAW,GAAGC,iBAAiB,GAAGC,QAAQ,GAAG1C,WAAW,KAAK2C,IAAI,SAASvuB,EAAQU,EAAOJ,GAC/I,YAEA,IAAI4I,GAAMlJ,EAAQ,YACdirB,EAAajrB,EAAQ,gBAErBwuB,GACAxpB,UAAY,SAAUnF,GAClB,IAAKA,GAAmB,gBAANA,GAAmB,OAAO,CAE5C,IAAI8H,GAAUuB,EAAIrB,UAAUhI,IAAMqJ,EAAIpB,MAEtC,OAAQ,kBAAkB1G,WAAYuG,GAAQqE,SACxCnM,YAAa8H,GAAQqE,QACN,IAAfnM,EAAE4uB,UAAwC,gBAAf5uB,GAAE6pB,UAGvC7U,QAAa,KAEbiV,SAAa9pB,EAAQ,cAErBgP,UAAa,SAAU0f,GAAS,QAASA,GAASA,YAAiBzD,GAAWa,kBAE9EhmB,SAAa,SAAU4oB,GAAS,QAASA,GAA2B,gBAAVA,IAE1DhhB,WAAa,SAAUghB,GAAS,MAAwB,kBAAVA,IAE9ClqB,SAAa,SAAUkqB,GAAS,MAAwB,gBAAVA,IAE9C3b,OAAa,SAAU2b,GAAS,MAAwB,iBAAVA,IAE9CtiB,SAAa,SAAUsiB,GAAS,MAAwB,gBAAVA,IAIlDF,GAAO3Z,QAAU,SAAU6Z,GACvB,MAAOF,GAAO1oB,SAAS4oB,IACS,mBAAjBA,GAAMluB,QACdguB,EAAO9gB,WAAWghB,EAAM7X,SAGnCnW,EAAOJ,QAAUkuB,IAEd7C,eAAe,EAAEgD,aAAa,GAAG/C,WAAW,KAAKgD,IAAI,SAAS5uB,EAAQU,EAAOJ,GAChF,YAEAI,GAAOJ,QAAU,SAAmBouB,GAChC,SAAUA,IAASA,EAAMG,SAAYH,YAAiBA,GAAMG,aAG1DC,IAAI,SAAS9uB,EAAQU,EAAOJ,GAClC,YAEA,IAAIyuB,MAEAC,KACA9lB,EAAMlJ,EAAQ,YACdie,EAAQje,EAAQ,WAChBwF,EAASxF,EAAQ,YACjBuC,EAAUvC,EAAQ,aAClBwuB,EAASxuB,EAAQ,YACjB0R,EAAgB1R,EAAQ,mBAE5B+uB,GAAalM,WAAa,SAAUyK,EAAM2B,GACtC3B,EAAKnpB,KAAOmpB,EAAKnpB,SACjBmpB,EAAKnpB,KAAKW,EAAImqB,EAAI9qB,KAAKW,EACvBwoB,EAAKnpB,KAAKY,EAAIkqB,EAAI9qB,KAAKY,EAEvBuoB,EAAKzS,OAASyS,EAAKzS,WACnByS,EAAKzS,OAAO/V,EAAImqB,EAAIpU,OAAO/V,EAC3BwoB,EAAKzS,OAAO9V,EAAIkqB,EAAIpU,OAAO9V,EAE3BuoB,EAAK3P,UAAYsR,EAAItR,WAGzBoR,EAAalN,WAAa,SAAUqN,EAAWtuB,EAASG,GAC/CH,IAEGA,EADAG,EAAY6B,WAAWpC,OAAS,EACtBuuB,EAAa9V,aAAalY,EAAYoB,UAGtCpB,EAAYoB,SAAS,IAIvC4sB,EAAahb,UAAUnT,EAASouB,EAAOjuB,GACvCmuB,EAAU/qB,KAAKW,EAAIkqB,EAAMlqB,EACzBoqB,EAAU/qB,KAAKY,EAAIiqB,EAAMjqB,EAEzBgqB,EAAanN,YAAYhhB,EAASouB,EAAOjuB,GACzCmuB,EAAUrU,OAAO/V,EAAIkqB,EAAMlqB,EAC3BoqB,EAAUrU,OAAO9V,EAAIiqB,EAAMjqB,EAE3BmqB,EAAUvR,WAAY,GAAIna,OAAOC,WAGrCsrB,EAAanL,eAAiB,SAAUsL,EAAWC,EAAMC,GACrDF,EAAU/qB,KAAKW,EAAQsqB,EAAIjrB,KAAKW,EAASqqB,EAAKhrB,KAAKW,EACnDoqB,EAAU/qB,KAAKY,EAAQqqB,EAAIjrB,KAAKY,EAASoqB,EAAKhrB,KAAKY,EACnDmqB,EAAUrU,OAAO/V,EAAMsqB,EAAIvU,OAAO/V,EAAOqqB,EAAKtU,OAAO/V,EACrDoqB,EAAUrU,OAAO9V,EAAMqqB,EAAIvU,OAAO9V,EAAOoqB,EAAKtU,OAAO9V,EACrDmqB,EAAUvR,WAAY,GAAIna,OAAOC,UAAY0rB,EAAKxR,SAGlD,IAAIC,GAAKvK,KAAKrD,IAAIkf,EAAUvR,UAAY,IAAM,KAC9CuR,GAAU/qB,KAAK2Z,MAAUG,EAAMiR,EAAU/qB,KAAKW,EAAGoqB,EAAU/qB,KAAKY,GAAK6Y,EACrEsR,EAAU/qB,KAAKga,GAAU+Q,EAAU/qB,KAAKW,EAAI8Y,EAC5CsR,EAAU/qB,KAAKia,GAAU8Q,EAAU/qB,KAAKY,EAAI6Y,EAE5CsR,EAAUrU,OAAOiD,MAAQG,EAAMiR,EAAUrU,OAAO/V,EAAGoqB,EAAU/qB,KAAKY,GAAK6Y,EACvEsR,EAAUrU,OAAOsD,GAAQ+Q,EAAUrU,OAAO/V,EAAI8Y,EAC9CsR,EAAUrU,OAAOuD,GAAQ8Q,EAAUrU,OAAO9V,EAAI6Y,GAIlDmR,EAAaM,MAAQ,SAAU/rB,EAAM1C,EAAS+gB,GAO1C,MANAA,GAAKA,MACLre,EAAOA,GAAQ,OAEfqe,EAAG7c,EAAIlE,EAAQ0C,EAAO,KACtBqe,EAAG5c,EAAInE,EAAQ0C,EAAO,KAEfqe,GAGXoN,EAAahb,UAAY,SAAUnT,EAASuD,EAAMpD,GA4B9C,MA3BAoD,GAAOA,MAEHvD,YAAmB8Q,GACf,eAAetQ,KAAKR,EAAQ0C,OAC5BvC,EAAcA,GAAeH,EAAQG,YAErCyE,EAAOrB,EAAMpD,EAAYW,cAAcyd,SAAShb,MAEhDA,EAAKW,GAAK/D,EAAYW,cAAc4d,GACpCnb,EAAKY,GAAKhE,EAAYW,cAAc6d,KAGpCpb,EAAKW,EAAIlE,EAAQgb,MACjBzX,EAAKY,EAAInE,EAAQib,OAIhBtZ,EAAQ4oB,eACb4D,EAAaM,MAAM,SAAUzuB,EAASuD,GAEtCA,EAAKW,GAAKoE,EAAIpB,OAAOyE,QACrBpI,EAAKY,GAAKmE,EAAIpB,OAAO4E,SAGrBqiB,EAAaM,MAAM,OAAQzuB,EAASuD,GAGjCA,GAGX4qB,EAAanN,YAAc,SAAUhhB,EAASia,EAAQ9Z,GAoBlD,MAnBA8Z,GAASA,MAELja,YAAmB8Q,GACf,eAAetQ,KAAKR,EAAQ0C,OAC5BkC,EAAOqV,EAAQ9Z,EAAYW,cAAcyd,SAAStE,QAElDA,EAAO/V,GAAK/D,EAAYW,cAAc4d,GACtCzE,EAAO9V,GAAKhE,EAAYW,cAAc6d,KAGtC1E,EAAO/V,EAAIlE,EAAQkb,QACnBjB,EAAO9V,EAAInE,EAAQmb,SAKvBgT,EAAaM,MAAM9sB,EAAQ4oB,cAAe,SAAU,SAAUvqB,EAASia,GAGpEA,GAGXkU,EAAavtB,aAAe,SAAUZ,GAClC,MAAO4tB,GAAOhqB,SAAS5D,EAAQmoB,WAAYnoB,EAAQmoB,UAAYnoB,EAAQ0uB,YAG3E5uB,EAAOJ,QAAUyuB,IAEdQ,mBAAmB,EAAEtB,YAAY,EAAEC,WAAW,GAAGC,UAAU,GAAGC,WAAW,GAAGxC,WAAW,KAAK4D,IAAI,SAASxvB,EAAQU,EAAOJ,GAC3H,YAOA,KAAI,GAHAmvB,GACAC,EAHAC,EAAW,EACXC,GAAW,KAAM,MAAO,SAAU,KAI9B9qB,EAAI,EAAGA,EAAI8qB,EAAQpvB,SAAWsH,OAAO+nB,wBAAyB/qB,EAClE2qB,EAAW3nB,OAAO8nB,EAAQ9qB,GAAG,yBAC7B4qB,EAAc5nB,OAAO8nB,EAAQ9qB,GAAG,yBAA2BgD,OAAO8nB,EAAQ9qB,GAAG,8BAG5E2qB,KACDA,EAAW,SAAS3d,GAChB,GAAIge,IAAW,GAAItsB,OAAOC,UACtBssB,EAAa1c,KAAKrD,IAAI,EAAG,IAAM8f,EAAWH,IAC1CruB,EAAKmhB,WAAW,WAAa3Q,EAASge,EAAWC,IACnDA,EAEF,OADAJ,GAAWG,EAAWC,EACfzuB,IAIVouB,IACDA,EAAc,SAASpuB,GACnBqiB,aAAariB,KAIrBZ,EAAOJ,SACH4mB,QAASuI,EACThmB,OAAQimB,QAGNM,IAAI,SAAShwB,EAAQU,EAAOJ,GAClC,YAEA,IAAIwpB,GAAW9pB,EAAQ,cAEnBiwB,EAAc,WAEd,GAAIC,GAAKpoB,OAAOE,SAASmoB,eAAe,GAGxC,OAAOD,GAAG/lB,gBAAkBrC,OAAOE,UACL,kBAAhBF,QAAOsoB,MACdtoB,OAAOsoB,KAAKF,KAAQA,GAG3BhnB,GAEApB,OAAQkK,OAERX,WAAYvJ,OAEZD,UAAW,SAAoBkH,GAC3B,GAAI+a,EAAS/a,GACT,MAAOA,EAGX,IAAIshB,GAAYthB,EAAK5E,eAAiB4E,CAEtC,OAAOshB,GAASlnB,aAAeknB,EAASjnB,cAAgBF,EAAIpB,QAI9C,oBAAXA,UAEHoB,EAAIpB,OADJmoB,IACanoB,OAAOsoB,KAAKtoB,QAEZA,QAIrBpH,EAAOJ,QAAU4I,IAEdylB,aAAa,UAAU","file":"interact.js","sourcesContent":["(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o\n * Open source under the MIT License.\n * https://raw.github.com/taye/interact.js/master/LICENSE\n */\n\n 'use strict';\n\n // return early if there's no window to work with (eg. Node.js)\n if (!require('./utils/window').window) { return; }\n\n var scope = require('./scope'),\n utils = require('./utils'),\n browser = utils.browser;\n\n scope.pEventTypes = null;\n\n scope.documents = []; // all documents being listened to\n\n scope.interactables = []; // all set interactables\n scope.interactions = []; // all interactions\n\n scope.dynamicDrop = false;\n\n // {\n // type: {\n // selectors: ['selector', ...],\n // contexts : [document, ...],\n // listeners: [[listener, useCapture], ...]\n // }\n // }\n scope.delegatedEvents = {};\n\n scope.defaultOptions = require('./defaultOptions');\n\n // Things related to autoScroll\n scope.autoScroll = require('./autoScroll');\n\n // Less Precision with touch input\n scope.margin = browser.supportsTouch || browser.supportsPointerEvent? 20: 10;\n\n scope.pointerMoveTolerance = 1;\n\n // for ignoring browser's simulated mouse events\n scope.prevTouchTime = 0;\n\n // Allow this many interactions to happen simultaneously\n scope.maxInteractions = Infinity;\n\n scope.actionCursors = browser.isIe9OrOlder ? {\n drag : 'move',\n resizex : 'e-resize',\n resizey : 's-resize',\n resizexy: 'se-resize',\n\n resizetop : 'n-resize',\n resizeleft : 'w-resize',\n resizebottom : 's-resize',\n resizeright : 'e-resize',\n resizetopleft : 'se-resize',\n resizebottomright: 'se-resize',\n resizetopright : 'ne-resize',\n resizebottomleft : 'ne-resize',\n\n gesture : ''\n } : {\n drag : 'move',\n resizex : 'ew-resize',\n resizey : 'ns-resize',\n resizexy: 'nwse-resize',\n\n resizetop : 'ns-resize',\n resizeleft : 'ew-resize',\n resizebottom : 'ns-resize',\n resizeright : 'ew-resize',\n resizetopleft : 'nwse-resize',\n resizebottomright: 'nwse-resize',\n resizetopright : 'nesw-resize',\n resizebottomleft : 'nesw-resize',\n\n gesture : ''\n };\n\n scope.actionIsEnabled = {\n drag : true,\n resize : true,\n gesture: true\n };\n\n // because Webkit and Opera still use 'mousewheel' event type\n scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel';\n\n scope.eventTypes = [\n 'dragstart',\n 'dragmove',\n 'draginertiastart',\n 'dragend',\n 'dragenter',\n 'dragleave',\n 'dropactivate',\n 'dropdeactivate',\n 'dropmove',\n 'drop',\n 'resizestart',\n 'resizemove',\n 'resizeinertiastart',\n 'resizeend',\n 'gesturestart',\n 'gesturemove',\n 'gestureinertiastart',\n 'gestureend',\n\n 'down',\n 'move',\n 'up',\n 'cancel',\n 'tap',\n 'doubletap',\n 'hold'\n ];\n\n scope.globalEvents = {};\n\n // prefix matchesSelector\n browser.prefixedMatchesSelector = 'matches' in Element.prototype?\n 'matches': 'webkitMatchesSelector' in Element.prototype?\n 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?\n 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?\n 'oMatchesSelector': 'msMatchesSelector';\n\n // will be polyfill function if browser is IE8\n scope.ie8MatchesSelector = null;\n\n // Events wrapper\n var events = require('./utils/events');\n\n scope.listeners = {};\n\n var interactionListeners = [\n 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove',\n 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown',\n 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd',\n 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove'\n ];\n\n scope.trySelector = function (value) {\n if (!scope.isString(value)) { return false; }\n\n // an exception will be raised if it is invalid\n scope.document.querySelector(value);\n return true;\n };\n\n scope.getScrollXY = function (win) {\n win = win || scope.window;\n return {\n x: win.scrollX || win.document.documentElement.scrollLeft,\n y: win.scrollY || win.document.documentElement.scrollTop\n };\n };\n\n scope.getActualElement = function (element) {\n return (element instanceof scope.SVGElementInstance\n ? element.correspondingUseElement\n : element);\n };\n\n scope.getElementRect = function (element) {\n var scroll = browser.isIOS7orLower\n ? { x: 0, y: 0 }\n : scope.getScrollXY(scope.getWindow(element)),\n clientRect = (element instanceof scope.SVGElement)?\n element.getBoundingClientRect():\n element.getClientRects()[0];\n\n return clientRect && {\n left : clientRect.left + scroll.x,\n right : clientRect.right + scroll.x,\n top : clientRect.top + scroll.y,\n bottom: clientRect.bottom + scroll.y,\n width : clientRect.width || clientRect.right - clientRect.left,\n height: clientRect.heigh || clientRect.bottom - clientRect.top\n };\n };\n\n scope.getOriginXY = function (interactable, element) {\n var origin = interactable\n ? interactable.options.origin\n : scope.defaultOptions.origin;\n\n if (origin === 'parent') {\n origin = scope.parentElement(element);\n }\n else if (origin === 'self') {\n origin = interactable.getRect(element);\n }\n else if (scope.trySelector(origin)) {\n origin = scope.closest(element, origin) || { x: 0, y: 0 };\n }\n\n if (scope.isFunction(origin)) {\n origin = origin(interactable && element);\n }\n\n if (utils.isElement(origin)) {\n origin = scope.getElementRect(origin);\n }\n\n origin.x = ('x' in origin)? origin.x : origin.left;\n origin.y = ('y' in origin)? origin.y : origin.top;\n\n return origin;\n };\n\n // http://stackoverflow.com/a/5634528/2280888\n scope._getQBezierValue = function (t, p1, p2, p3) {\n var iT = 1 - t;\n return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;\n };\n\n scope.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) {\n return {\n x: scope._getQBezierValue(position, startX, cpX, endX),\n y: scope._getQBezierValue(position, startY, cpY, endY)\n };\n };\n\n // http://gizma.com/easing/\n scope.easeOutQuad = function (t, b, c, d) {\n t /= d;\n return -c * t*(t-2) + b;\n };\n\n scope.nodeContains = function (parent, child) {\n while (child) {\n if (child === parent) {\n return true;\n }\n\n child = child.parentNode;\n }\n\n return false;\n };\n\n scope.closest = function (child, selector) {\n var parent = scope.parentElement(child);\n\n while (utils.isElement(parent)) {\n if (scope.matchesSelector(parent, selector)) { return parent; }\n\n parent = scope.parentElement(parent);\n }\n\n return null;\n };\n\n scope.parentElement = function (node) {\n var parent = node.parentNode;\n\n if (scope.isDocFrag(parent)) {\n // skip past #shado-root fragments\n while ((parent = parent.host) && scope.isDocFrag(parent)) {}\n\n return parent;\n }\n\n return parent;\n };\n\n scope.inContext = function (interactable, element) {\n return interactable._context === element.ownerDocument\n || scope.nodeContains(interactable._context, element);\n };\n\n scope.testIgnore = function (interactable, interactableElement, element) {\n var ignoreFrom = interactable.options.ignoreFrom;\n\n if (!ignoreFrom || !utils.isElement(element)) { return false; }\n\n if (scope.isString(ignoreFrom)) {\n return scope.matchesUpTo(element, ignoreFrom, interactableElement);\n }\n else if (utils.isElement(ignoreFrom)) {\n return scope.nodeContains(ignoreFrom, element);\n }\n\n return false;\n };\n\n scope.testAllow = function (interactable, interactableElement, element) {\n var allowFrom = interactable.options.allowFrom;\n\n if (!allowFrom) { return true; }\n\n if (!utils.isElement(element)) { return false; }\n\n if (scope.isString(allowFrom)) {\n return scope.matchesUpTo(element, allowFrom, interactableElement);\n }\n else if (utils.isElement(allowFrom)) {\n return scope.nodeContains(allowFrom, element);\n }\n\n return false;\n };\n\n scope.checkAxis = function (axis, interactable) {\n if (!interactable) { return false; }\n\n var thisAxis = interactable.options.drag.axis;\n\n return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis);\n };\n\n scope.checkSnap = function (interactable, action) {\n var options = interactable.options;\n\n if (/^resize/.test(action)) {\n action = 'resize';\n }\n\n return options[action].snap && options[action].snap.enabled;\n };\n\n scope.checkRestrict = function (interactable, action) {\n var options = interactable.options;\n\n if (/^resize/.test(action)) {\n action = 'resize';\n }\n\n return options[action].restrict && options[action].restrict.enabled;\n };\n\n scope.checkAutoScroll = function (interactable, action) {\n var options = interactable.options;\n\n if (/^resize/.test(action)) {\n action = 'resize';\n }\n\n return options[action].autoScroll && options[action].autoScroll.enabled;\n };\n\n scope.withinInteractionLimit = function (interactable, element, action) {\n var options = interactable.options,\n maxActions = options[action.name].max,\n maxPerElement = options[action.name].maxPerElement,\n activeInteractions = 0,\n targetCount = 0,\n targetElementCount = 0;\n\n for (var i = 0, len = scope.interactions.length; i < len; i++) {\n var interaction = scope.interactions[i],\n otherAction = interaction.prepared.name,\n active = interaction.interacting();\n\n if (!active) { continue; }\n\n activeInteractions++;\n\n if (activeInteractions >= scope.maxInteractions) {\n return false;\n }\n\n if (interaction.target !== interactable) { continue; }\n\n targetCount += (otherAction === action.name)|0;\n\n if (targetCount >= maxActions) {\n return false;\n }\n\n if (interaction.element === element) {\n targetElementCount++;\n\n if (otherAction !== action.name || targetElementCount >= maxPerElement) {\n return false;\n }\n }\n }\n\n return scope.maxInteractions > 0;\n };\n\n // Test for the element that's \"above\" all other qualifiers\n scope.indexOfDeepestElement = function (elements) {\n var dropzone,\n deepestZone = elements[0],\n index = deepestZone? 0: -1,\n parent,\n deepestZoneParents = [],\n dropzoneParents = [],\n child,\n i,\n n;\n\n for (i = 1; i < elements.length; i++) {\n dropzone = elements[i];\n\n // an element might belong to multiple selector dropzones\n if (!dropzone || dropzone === deepestZone) {\n continue;\n }\n\n if (!deepestZone) {\n deepestZone = dropzone;\n index = i;\n continue;\n }\n\n // check if the deepest or current are document.documentElement or document.rootElement\n // - if the current dropzone is, do nothing and continue\n if (dropzone.parentNode === dropzone.ownerDocument) {\n continue;\n }\n // - if deepest is, update with the current dropzone and continue to next\n else if (deepestZone.parentNode === dropzone.ownerDocument) {\n deepestZone = dropzone;\n index = i;\n continue;\n }\n\n if (!deepestZoneParents.length) {\n parent = deepestZone;\n while (parent.parentNode && parent.parentNode !== parent.ownerDocument) {\n deepestZoneParents.unshift(parent);\n parent = parent.parentNode;\n }\n }\n\n // if this element is an svg element and the current deepest is\n // an HTMLElement\n if (deepestZone instanceof scope.HTMLElement\n && dropzone instanceof scope.SVGElement\n && !(dropzone instanceof scope.SVGSVGElement)) {\n\n if (dropzone === deepestZone.parentNode) {\n continue;\n }\n\n parent = dropzone.ownerSVGElement;\n }\n else {\n parent = dropzone;\n }\n\n dropzoneParents = [];\n\n while (parent.parentNode !== parent.ownerDocument) {\n dropzoneParents.unshift(parent);\n parent = parent.parentNode;\n }\n\n n = 0;\n\n // get (position of last common ancestor) + 1\n while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) {\n n++;\n }\n\n var parents = [\n dropzoneParents[n - 1],\n dropzoneParents[n],\n deepestZoneParents[n]\n ];\n\n child = parents[0].lastChild;\n\n while (child) {\n if (child === parents[1]) {\n deepestZone = dropzone;\n index = i;\n deepestZoneParents = [];\n\n break;\n }\n else if (child === parents[2]) {\n break;\n }\n\n child = child.previousSibling;\n }\n }\n\n return index;\n };\n\n scope.matchesSelector = function (element, selector, nodeList) {\n if (scope.ie8MatchesSelector) {\n return scope.ie8MatchesSelector(element, selector, nodeList);\n }\n\n // remove /deep/ from selectors if shadowDOM polyfill is used\n if (scope.window !== scope.realWindow) {\n selector = selector.replace(/\\/deep\\//g, ' ');\n }\n\n return element[browser.prefixedMatchesSelector](selector);\n };\n\n scope.matchesUpTo = function (element, selector, limit) {\n while (utils.isElement(element)) {\n if (scope.matchesSelector(element, selector)) {\n return true;\n }\n\n element = scope.parentElement(element);\n\n if (element === limit) {\n return scope.matchesSelector(element, selector);\n }\n }\n\n return false;\n };\n\n // For IE8's lack of an Element#matchesSelector\n // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified\n if (!(browser.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[browser.prefixedMatchesSelector])) {\n scope.ie8MatchesSelector = function (element, selector, elems) {\n elems = elems || element.parentNode.querySelectorAll(selector);\n\n for (var i = 0, len = elems.length; i < len; i++) {\n if (elems[i] === element) {\n return true;\n }\n }\n\n return false;\n };\n }\n\n var Interaction = require('./Interaction');\n\n function getInteractionFromPointer (pointer, eventType, eventTarget) {\n var i = 0, len = scope.interactions.length,\n mouseEvent = (/mouse/i.test(pointer.pointerType || eventType)\n // MSPointerEvent.MSPOINTER_TYPE_MOUSE\n || pointer.pointerType === 4),\n interaction;\n\n var id = utils.getPointerId(pointer);\n\n // try to resume inertia with a new pointer\n if (/down|start/i.test(eventType)) {\n for (i = 0; i < len; i++) {\n interaction = scope.interactions[i];\n\n var element = eventTarget;\n\n if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume\n && (interaction.mouse === mouseEvent)) {\n while (element) {\n // if the element is the interaction element\n if (element === interaction.element) {\n // update the interaction's pointer\n if (interaction.pointers[0]) {\n interaction.removePointer(interaction.pointers[0]);\n }\n interaction.addPointer(pointer);\n\n return interaction;\n }\n element = scope.parentElement(element);\n }\n }\n }\n }\n\n // if it's a mouse interaction\n if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) {\n\n // find a mouse interaction that's not in inertia phase\n for (i = 0; i < len; i++) {\n if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) {\n return scope.interactions[i];\n }\n }\n\n // find any interaction specifically for mouse.\n // if the eventType is a mousedown, and inertia is active\n // ignore the interaction\n for (i = 0; i < len; i++) {\n if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) {\n return interaction;\n }\n }\n\n // create a new interaction for mouse\n interaction = new Interaction();\n interaction.mouse = true;\n\n return interaction;\n }\n\n // get interaction that has this pointer\n for (i = 0; i < len; i++) {\n if (scope.contains(scope.interactions[i].pointerIds, id)) {\n return scope.interactions[i];\n }\n }\n\n // at this stage, a pointerUp should not return an interaction\n if (/up|end|out/i.test(eventType)) {\n return null;\n }\n\n // get first idle interaction\n for (i = 0; i < len; i++) {\n interaction = scope.interactions[i];\n\n if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled))\n && !interaction.interacting()\n && !(!mouseEvent && interaction.mouse)) {\n\n interaction.addPointer(pointer);\n\n return interaction;\n }\n }\n\n return new Interaction();\n }\n\n function doOnInteractions (method) {\n return (function (event) {\n var interaction,\n eventTarget = scope.getActualElement(event.path\n ? event.path[0]\n : event.target),\n curEventTarget = scope.getActualElement(event.currentTarget),\n i;\n\n if (browser.supportsTouch && /touch/.test(event.type)) {\n scope.prevTouchTime = new Date().getTime();\n\n for (i = 0; i < event.changedTouches.length; i++) {\n var pointer = event.changedTouches[i];\n\n interaction = getInteractionFromPointer(pointer, event.type, eventTarget);\n\n if (!interaction) { continue; }\n\n interaction._updateEventTargets(eventTarget, curEventTarget);\n\n interaction[method](pointer, event, eventTarget, curEventTarget);\n }\n }\n else {\n if (!browser.supportsPointerEvent && /mouse/.test(event.type)) {\n // ignore mouse events while touch interactions are active\n for (i = 0; i < scope.interactions.length; i++) {\n if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) {\n return;\n }\n }\n\n // try to ignore mouse events that are simulated by the browser\n // after a touch event\n if (new Date().getTime() - scope.prevTouchTime < 500) {\n return;\n }\n }\n\n interaction = getInteractionFromPointer(event, event.type, eventTarget);\n\n if (!interaction) { return; }\n\n interaction._updateEventTargets(eventTarget, curEventTarget);\n\n interaction[method](event, event, eventTarget, curEventTarget);\n }\n });\n }\n\n function preventOriginalDefault () {\n this.originalEvent.preventDefault();\n }\n\n function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) {\n // false, '', undefined, null\n if (!value) { return false; }\n\n // true value, use pointer coords and element rect\n if (value === true) {\n // if dimensions are negative, \"switch\" edges\n var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left,\n height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top;\n\n if (width < 0) {\n if (name === 'left' ) { name = 'right'; }\n else if (name === 'right') { name = 'left' ; }\n }\n if (height < 0) {\n if (name === 'top' ) { name = 'bottom'; }\n else if (name === 'bottom') { name = 'top' ; }\n }\n\n if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); }\n if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); }\n\n if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); }\n if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); }\n }\n\n // the remaining checks require an element\n if (!utils.isElement(element)) { return false; }\n\n return utils.isElement(value)\n // the value is an element to use as a resize handle\n ? value === element\n // otherwise check if element matches value as selector\n : scope.matchesUpTo(element, value, interactableElement);\n }\n\n function defaultActionChecker (pointer, interaction, element) {\n var rect = this.getRect(element),\n shouldResize = false,\n action = null,\n resizeAxes = null,\n resizeEdges,\n page = utils.extend({}, interaction.curCoords.page),\n options = this.options;\n\n if (!rect) { return null; }\n\n if (scope.actionIsEnabled.resize && options.resize.enabled) {\n var resizeOptions = options.resize;\n\n resizeEdges = {\n left: false, right: false, top: false, bottom: false\n };\n\n // if using resize.edges\n if (scope.isObject(resizeOptions.edges)) {\n for (var edge in resizeEdges) {\n resizeEdges[edge] = checkResizeEdge(edge,\n resizeOptions.edges[edge],\n page,\n interaction._eventTarget,\n element,\n rect,\n resizeOptions.margin || scope.margin);\n }\n\n resizeEdges.left = resizeEdges.left && !resizeEdges.right;\n resizeEdges.top = resizeEdges.top && !resizeEdges.bottom;\n\n shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom;\n }\n else {\n var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin),\n bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin);\n\n shouldResize = right || bottom;\n resizeAxes = (right? 'x' : '') + (bottom? 'y' : '');\n }\n }\n\n action = shouldResize\n ? 'resize'\n : scope.actionIsEnabled.drag && options.drag.enabled\n ? 'drag'\n : null;\n\n if (scope.actionIsEnabled.gesture\n && interaction.pointerIds.length >=2\n && !(interaction.dragging || interaction.resizing)) {\n action = 'gesture';\n }\n\n if (action) {\n return {\n name: action,\n axis: resizeAxes,\n edges: resizeEdges\n };\n }\n\n return null;\n }\n\n var InteractEvent = require('./InteractEvent');\n\n for (var i = 0, len = interactionListeners.length; i < len; i++) {\n var listenerName = interactionListeners[i];\n\n scope.listeners[listenerName] = doOnInteractions(listenerName);\n }\n\n // bound to the interactable context when a DOM event\n // listener is added to a selector interactable\n function delegateListener (event, useCapture) {\n var fakeEvent = {},\n delegated = scope.delegatedEvents[event.type],\n eventTarget = scope.getActualElement(event.path\n ? event.path[0]\n : event.target),\n element = eventTarget;\n\n useCapture = useCapture? true: false;\n\n // duplicate the event so that currentTarget can be changed\n for (var prop in event) {\n fakeEvent[prop] = event[prop];\n }\n\n fakeEvent.originalEvent = event;\n fakeEvent.preventDefault = preventOriginalDefault;\n\n // climb up document tree looking for selector matches\n while (utils.isElement(element)) {\n for (var i = 0; i < delegated.selectors.length; i++) {\n var selector = delegated.selectors[i],\n context = delegated.contexts[i];\n\n if (scope.matchesSelector(element, selector)\n && scope.nodeContains(context, eventTarget)\n && scope.nodeContains(context, element)) {\n\n var listeners = delegated.listeners[i];\n\n fakeEvent.currentTarget = element;\n\n for (var j = 0; j < listeners.length; j++) {\n if (listeners[j][1] === useCapture) {\n listeners[j][0](fakeEvent);\n }\n }\n }\n }\n\n element = scope.parentElement(element);\n }\n }\n\n function delegateUseCapture (event) {\n return delegateListener.call(this, event, true);\n }\n\n scope.interactables.indexOfElement = function indexOfElement (element, context) {\n context = context || scope.document;\n\n for (var i = 0; i < this.length; i++) {\n var interactable = this[i];\n\n if ((interactable.selector === element\n && (interactable._context === context))\n || (!interactable.selector && interactable._element === element)) {\n\n return i;\n }\n }\n return -1;\n };\n\n scope.interactables.get = function interactableGet (element, options) {\n return this[this.indexOfElement(element, options && options.context)];\n };\n\n scope.interactables.forEachSelector = function (callback) {\n for (var i = 0; i < this.length; i++) {\n var interactable = this[i];\n\n if (!interactable.selector) {\n continue;\n }\n\n var ret = callback(interactable, interactable.selector, interactable._context, i, this);\n\n if (ret !== undefined) {\n return ret;\n }\n }\n };\n\n /*\\\n * interact\n [ method ]\n *\n * The methods of this variable can be used to set elements as\n * interactables and also to change various default settings.\n *\n * Calling it as a function and passing an element or a valid CSS selector\n * string returns an Interactable object which has various methods to\n * configure it.\n *\n - element (Element | string) The HTML or SVG Element to interact with or CSS selector\n = (object) An @Interactable\n *\n > Usage\n | interact(document.getElementById('draggable')).draggable(true);\n |\n | var rectables = interact('rect');\n | rectables\n | .gesturable(true)\n | .on('gesturemove', function (event) {\n | // something cool...\n | })\n | .autoScroll(true);\n \\*/\n function interact (element, options) {\n return scope.interactables.get(element, options) || new Interactable(element, options);\n }\n\n /*\\\n * Interactable\n [ property ]\n **\n * Object type returned by @interact\n \\*/\n function Interactable (element, options) {\n this._element = element;\n this._iEvents = this._iEvents || {};\n\n var _window;\n\n if (scope.trySelector(element)) {\n this.selector = element;\n\n var context = options && options.context;\n\n _window = context? scope.getWindow(context) : scope.window;\n\n if (context && (_window.Node\n ? context instanceof _window.Node\n : (utils.isElement(context) || context === _window.document))) {\n\n this._context = context;\n }\n }\n else {\n _window = scope.getWindow(element);\n\n if (utils.isElement(element, _window)) {\n\n if (scope.PointerEvent) {\n events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown );\n events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover);\n }\n else {\n events.add(this._element, 'mousedown' , scope.listeners.pointerDown );\n events.add(this._element, 'mousemove' , scope.listeners.pointerHover);\n events.add(this._element, 'touchstart', scope.listeners.pointerDown );\n events.add(this._element, 'touchmove' , scope.listeners.pointerHover);\n }\n }\n }\n\n this._doc = _window.document;\n\n if (!scope.contains(scope.documents, this._doc)) {\n listenToDocument(this._doc);\n }\n\n scope.interactables.push(this);\n\n this.set(options);\n }\n\n Interactable.prototype = {\n setOnEvents: function (action, phases) {\n if (action === 'drop') {\n if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; }\n if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; }\n if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; }\n if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; }\n if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; }\n if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; }\n }\n else {\n action = 'on' + action;\n\n if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; }\n if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; }\n if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; }\n if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; }\n }\n\n return this;\n },\n\n /*\\\n * Interactable.draggable\n [ method ]\n *\n * Gets or sets whether drag actions can be performed on the\n * Interactable\n *\n = (boolean) Indicates if this can be the target of drag events\n | var isDraggable = interact('ul li').draggable();\n * or\n - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable)\n = (object) This Interactable\n | interact(element).draggable({\n | onstart: function (event) {},\n | onmove : function (event) {},\n | onend : function (event) {},\n |\n | // the axis in which the first movement must be\n | // for the drag sequence to start\n | // 'xy' by default - any direction\n | axis: 'x' || 'y' || 'xy',\n |\n | // max number of drags that can happen concurrently\n | // with elements of this Interactable. Infinity by default\n | max: Infinity,\n |\n | // max number of drags that can target the same element+Interactable\n | // 1 by default\n | maxPerElement: 2\n | });\n \\*/\n draggable: function (options) {\n if (scope.isObject(options)) {\n this.options.drag.enabled = options.enabled === false? false: true;\n this.setPerAction('drag', options);\n this.setOnEvents('drag', options);\n\n if (/^x$|^y$|^xy$/.test(options.axis)) {\n this.options.drag.axis = options.axis;\n }\n else if (options.axis === null) {\n delete this.options.drag.axis;\n }\n\n return this;\n }\n\n if (scope.isBool(options)) {\n this.options.drag.enabled = options;\n\n return this;\n }\n\n return this.options.drag;\n },\n\n setPerAction: function (action, options) {\n // for all the default per-action options\n for (var option in options) {\n // if this option exists for this action\n if (option in scope.defaultOptions[action]) {\n // if the option in the options arg is an object value\n if (scope.isObject(options[option])) {\n // duplicate the object\n this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]);\n\n if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) {\n this.options[action][option].enabled = options[option].enabled === false? false : true;\n }\n }\n else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) {\n this.options[action][option].enabled = options[option];\n }\n else if (options[option] !== undefined) {\n // or if it's not undefined, do a plain assignment\n this.options[action][option] = options[option];\n }\n }\n }\n },\n\n /*\\\n * Interactable.dropzone\n [ method ]\n *\n * Returns or sets whether elements can be dropped onto this\n * Interactable to trigger drop events\n *\n * Dropzones can receive the following events:\n * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends\n * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone\n * - `dragmove` when a draggable that has entered the dropzone is moved\n * - `drop` when a draggable is dropped into this dropzone\n *\n * Use the `accept` option to allow only elements that match the given CSS selector or element.\n *\n * Use the `overlap` option to set how drops are checked for. The allowed values are:\n * - `'pointer'`, the pointer must be over the dropzone (default)\n * - `'center'`, the draggable element's center must be over the dropzone\n * - a number from 0-1 which is the `(intersection area) / (draggable area)`.\n * e.g. `0.5` for drop to happen when half of the area of the\n * draggable is over the dropzone\n *\n - options (boolean | object | null) #optional The new value to be set.\n | interact('.drop').dropzone({\n | accept: '.can-drop' || document.getElementById('single-drop'),\n | overlap: 'pointer' || 'center' || zeroToOne\n | }\n = (boolean | object) The current setting or this Interactable\n \\*/\n dropzone: function (options) {\n if (scope.isObject(options)) {\n this.options.drop.enabled = options.enabled === false? false: true;\n this.setOnEvents('drop', options);\n this.accept(options.accept);\n\n if (/^(pointer|center)$/.test(options.overlap)) {\n this.options.drop.overlap = options.overlap;\n }\n else if (scope.isNumber(options.overlap)) {\n this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);\n }\n\n return this;\n }\n\n if (scope.isBool(options)) {\n this.options.drop.enabled = options;\n\n return this;\n }\n\n return this.options.drop;\n },\n\n dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) {\n var dropped = false;\n\n // if the dropzone has no rect (eg. display: none)\n // call the custom dropChecker or just return false\n if (!(rect = rect || this.getRect(dropElement))) {\n return (this.options.dropChecker\n ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement)\n : false);\n }\n\n var dropOverlap = this.options.drop.overlap;\n\n if (dropOverlap === 'pointer') {\n var page = utils.getPageXY(pointer),\n origin = scope.getOriginXY(draggable, draggableElement),\n horizontal,\n vertical;\n\n page.x += origin.x;\n page.y += origin.y;\n\n horizontal = (page.x > rect.left) && (page.x < rect.right);\n vertical = (page.y > rect.top ) && (page.y < rect.bottom);\n\n dropped = horizontal && vertical;\n }\n\n var dragRect = draggable.getRect(draggableElement);\n\n if (dropOverlap === 'center') {\n var cx = dragRect.left + dragRect.width / 2,\n cy = dragRect.top + dragRect.height / 2;\n\n dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;\n }\n\n if (scope.isNumber(dropOverlap)) {\n var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))\n * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))),\n overlapRatio = overlapArea / (dragRect.width * dragRect.height);\n\n dropped = overlapRatio >= dropOverlap;\n }\n\n if (this.options.dropChecker) {\n dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement);\n }\n\n return dropped;\n },\n\n /*\\\n * Interactable.dropChecker\n [ method ]\n *\n * Gets or sets the function used to check if a dragged element is\n * over this Interactable.\n *\n - checker (function) #optional The function that will be called when checking for a drop\n = (Function | Interactable) The checker function or this Interactable\n *\n * The checker function takes the following arguments:\n *\n - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag\n - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer\n - dropped (boolean) The value from the default drop check\n - dropzone (Interactable) The dropzone interactable\n - dropElement (Element) The dropzone element\n - draggable (Interactable) The Interactable being dragged\n - draggableElement (Element) The actual element that's being dragged\n *\n > Usage:\n | interact(target)\n | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent\n | event, // TouchEvent/PointerEvent/MouseEvent\n | dropped, // result of the default checker\n | dropzone, // dropzone Interactable\n | dropElement, // dropzone elemnt\n | draggable, // draggable Interactable\n | draggableElement) {// draggable element\n |\n | return dropped && event.target.hasAttribute('allow-drop');\n | }\n \\*/\n dropChecker: function (checker) {\n if (scope.isFunction(checker)) {\n this.options.dropChecker = checker;\n\n return this;\n }\n if (checker === null) {\n delete this.options.getRect;\n\n return this;\n }\n\n return this.options.dropChecker;\n },\n\n /*\\\n * Interactable.accept\n [ method ]\n *\n * Deprecated. add an `accept` property to the options object passed to\n * @Interactable.dropzone instead.\n *\n * Gets or sets the Element or CSS selector match that this\n * Interactable accepts if it is a dropzone.\n *\n - newValue (Element | string | null) #optional\n * If it is an Element, then only that element can be dropped into this dropzone.\n * If it is a string, the element being dragged must match it as a selector.\n * If it is null, the accept options is cleared - it accepts any element.\n *\n = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable\n \\*/\n accept: function (newValue) {\n if (utils.isElement(newValue)) {\n this.options.drop.accept = newValue;\n\n return this;\n }\n\n // test if it is a valid CSS selector\n if (scope.trySelector(newValue)) {\n this.options.drop.accept = newValue;\n\n return this;\n }\n\n if (newValue === null) {\n delete this.options.drop.accept;\n\n return this;\n }\n\n return this.options.drop.accept;\n },\n\n /*\\\n * Interactable.resizable\n [ method ]\n *\n * Gets or sets whether resize actions can be performed on the\n * Interactable\n *\n = (boolean) Indicates if this can be the target of resize elements\n | var isResizeable = interact('input[type=text]').resizable();\n * or\n - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable)\n = (object) This Interactable\n | interact(element).resizable({\n | onstart: function (event) {},\n | onmove : function (event) {},\n | onend : function (event) {},\n |\n | edges: {\n | top : true, // Use pointer coords to check for resize.\n | left : false, // Disable resizing from left edge.\n | bottom: '.resize-s',// Resize if pointer target matches selector\n | right : handleEl // Resize if pointer target is the given Element\n | },\n |\n | // a value of 'none' will limit the resize rect to a minimum of 0x0\n | // 'negate' will allow the rect to have negative width/height\n | // 'reposition' will keep the width/height positive by swapping\n | // the top and bottom edges and/or swapping the left and right edges\n | invert: 'none' || 'negate' || 'reposition'\n |\n | // limit multiple resizes.\n | // See the explanation in the @Interactable.draggable example\n | max: Infinity,\n | maxPerElement: 1,\n | });\n \\*/\n resizable: function (options) {\n if (scope.isObject(options)) {\n this.options.resize.enabled = options.enabled === false? false: true;\n this.setPerAction('resize', options);\n this.setOnEvents('resize', options);\n\n if (/^x$|^y$|^xy$/.test(options.axis)) {\n this.options.resize.axis = options.axis;\n }\n else if (options.axis === null) {\n this.options.resize.axis = scope.defaultOptions.resize.axis;\n }\n\n if (scope.isBool(options.square)) {\n this.options.resize.square = options.square;\n }\n\n return this;\n }\n if (scope.isBool(options)) {\n this.options.resize.enabled = options;\n\n return this;\n }\n return this.options.resize;\n },\n\n /*\\\n * Interactable.squareResize\n [ method ]\n *\n * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead\n *\n * Gets or sets whether resizing is forced 1:1 aspect\n *\n = (boolean) Current setting\n *\n * or\n *\n - newValue (boolean) #optional\n = (object) this Interactable\n \\*/\n squareResize: function (newValue) {\n if (scope.isBool(newValue)) {\n this.options.resize.square = newValue;\n\n return this;\n }\n\n if (newValue === null) {\n delete this.options.resize.square;\n\n return this;\n }\n\n return this.options.resize.square;\n },\n\n /*\\\n * Interactable.gesturable\n [ method ]\n *\n * Gets or sets whether multitouch gestures can be performed on the\n * Interactable's element\n *\n = (boolean) Indicates if this can be the target of gesture events\n | var isGestureable = interact(element).gesturable();\n * or\n - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable)\n = (object) this Interactable\n | interact(element).gesturable({\n | onstart: function (event) {},\n | onmove : function (event) {},\n | onend : function (event) {},\n |\n | // limit multiple gestures.\n | // See the explanation in @Interactable.draggable example\n | max: Infinity,\n | maxPerElement: 1,\n | });\n \\*/\n gesturable: function (options) {\n if (scope.isObject(options)) {\n this.options.gesture.enabled = options.enabled === false? false: true;\n this.setPerAction('gesture', options);\n this.setOnEvents('gesture', options);\n\n return this;\n }\n\n if (scope.isBool(options)) {\n this.options.gesture.enabled = options;\n\n return this;\n }\n\n return this.options.gesture;\n },\n\n /*\\\n * Interactable.autoScroll\n [ method ]\n **\n * Deprecated. Add an `autoscroll` property to the options object\n * passed to @Interactable.draggable or @Interactable.resizable instead.\n *\n * Returns or sets whether dragging and resizing near the edges of the\n * window/container trigger autoScroll for this Interactable\n *\n = (object) Object with autoScroll properties\n *\n * or\n *\n - options (object | boolean) #optional\n * options can be:\n * - an object with margin, distance and interval properties,\n * - true or false to enable or disable autoScroll or\n = (Interactable) this Interactable\n \\*/\n autoScroll: function (options) {\n if (scope.isObject(options)) {\n options = utils.extend({ actions: ['drag', 'resize']}, options);\n }\n else if (scope.isBool(options)) {\n options = { actions: ['drag', 'resize'], enabled: options };\n }\n\n return this.setOptions('autoScroll', options);\n },\n\n /*\\\n * Interactable.snap\n [ method ]\n **\n * Deprecated. Add a `snap` property to the options object passed\n * to @Interactable.draggable or @Interactable.resizable instead.\n *\n * Returns or sets if and how action coordinates are snapped. By\n * default, snapping is relative to the pointer coordinates. You can\n * change this by setting the\n * [`elementOrigin`](https://github.com/taye/interact.js/pull/72).\n **\n = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled\n **\n * or\n **\n - options (object | boolean | null) #optional\n = (Interactable) this Interactable\n > Usage\n | interact(document.querySelector('#thing')).snap({\n | targets: [\n | // snap to this specific point\n | {\n | x: 100,\n | y: 100,\n | range: 25\n | },\n | // give this function the x and y page coords and snap to the object returned\n | function (x, y) {\n | return {\n | x: x,\n | y: (75 + 50 * Math.sin(x * 0.04)),\n | range: 40\n | };\n | },\n | // create a function that snaps to a grid\n | interact.createSnapGrid({\n | x: 50,\n | y: 50,\n | range: 10, // optional\n | offset: { x: 5, y: 10 } // optional\n | })\n | ],\n | // do not snap during normal movement.\n | // Instead, trigger only one snapped move event\n | // immediately before the end event.\n | endOnly: true,\n |\n | relativePoints: [\n | { x: 0, y: 0 }, // snap relative to the top left of the element\n | { x: 1, y: 1 }, // and also to the bottom right\n | ], \n |\n | // offset the snap target coordinates\n | // can be an object with x/y or 'startCoords'\n | offset: { x: 50, y: 50 }\n | }\n | });\n \\*/\n snap: function (options) {\n var ret = this.setOptions('snap', options);\n\n if (ret === this) { return this; }\n\n return ret.drag;\n },\n\n setOptions: function (option, options) {\n var actions = options && scope.isArray(options.actions)\n ? options.actions\n : ['drag'];\n\n var i;\n\n if (scope.isObject(options) || scope.isBool(options)) {\n for (i = 0; i < actions.length; i++) {\n var action = /resize/.test(actions[i])? 'resize' : actions[i];\n\n if (!scope.isObject(this.options[action])) { continue; }\n\n var thisOption = this.options[action][option];\n\n if (scope.isObject(options)) {\n utils.extend(thisOption, options);\n thisOption.enabled = options.enabled === false? false: true;\n\n if (option === 'snap') {\n if (thisOption.mode === 'grid') {\n thisOption.targets = [\n interact.createSnapGrid(utils.extend({\n offset: thisOption.gridOffset || { x: 0, y: 0 }\n }, thisOption.grid || {}))\n ];\n }\n else if (thisOption.mode === 'anchor') {\n thisOption.targets = thisOption.anchors;\n }\n else if (thisOption.mode === 'path') {\n thisOption.targets = thisOption.paths;\n }\n\n if ('elementOrigin' in options) {\n thisOption.relativePoints = [options.elementOrigin];\n }\n }\n }\n else if (scope.isBool(options)) {\n thisOption.enabled = options;\n }\n }\n\n return this;\n }\n\n var ret = {},\n allActions = ['drag', 'resize', 'gesture'];\n\n for (i = 0; i < allActions.length; i++) {\n if (option in scope.defaultOptions[allActions[i]]) {\n ret[allActions[i]] = this.options[allActions[i]][option];\n }\n }\n\n return ret;\n },\n\n\n /*\\\n * Interactable.inertia\n [ method ]\n **\n * Deprecated. Add an `inertia` property to the options object passed\n * to @Interactable.draggable or @Interactable.resizable instead.\n *\n * Returns or sets if and how events continue to run after the pointer is released\n **\n = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled\n **\n * or\n **\n - options (object | boolean | null) #optional\n = (Interactable) this Interactable\n > Usage\n | // enable and use default settings\n | interact(element).inertia(true);\n |\n | // enable and use custom settings\n | interact(element).inertia({\n | // value greater than 0\n | // high values slow the object down more quickly\n | resistance : 16,\n |\n | // the minimum launch speed (pixels per second) that results in inertia start\n | minSpeed : 200,\n |\n | // inertia will stop when the object slows down to this speed\n | endSpeed : 20,\n |\n | // boolean; should actions be resumed when the pointer goes down during inertia\n | allowResume : true,\n |\n | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy\n | zeroResumeDelta: false,\n |\n | // if snap/restrict are set to be endOnly and inertia is enabled, releasing\n | // the pointer without triggering inertia will animate from the release\n | // point to the snaped/restricted point in the given amount of time (ms)\n | smoothEndDuration: 300,\n |\n | // an array of action types that can have inertia (no gesture)\n | actions : ['drag', 'resize']\n | });\n |\n | // reset custom settings and use all defaults\n | interact(element).inertia(null);\n \\*/\n inertia: function (options) {\n var ret = this.setOptions('inertia', options);\n\n if (ret === this) { return this; }\n\n return ret.drag;\n },\n\n getAction: function (pointer, event, interaction, element) {\n var action = this.defaultActionChecker(pointer, interaction, element);\n\n if (this.options.actionChecker) {\n return this.options.actionChecker(pointer, event, action, this, element, interaction);\n }\n\n return action;\n },\n\n defaultActionChecker: defaultActionChecker,\n\n /*\\\n * Interactable.actionChecker\n [ method ]\n *\n * Gets or sets the function used to check action to be performed on\n * pointerDown\n *\n - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props.\n = (Function | Interactable) The checker function or this Interactable\n *\n | interact('.resize-drag')\n | .resizable(true)\n | .draggable(true)\n | .actionChecker(function (pointer, event, action, interactable, element, interaction) {\n |\n | if (interact.matchesSelector(event.target, '.drag-handle') {\n | // force drag with handle target\n | action.name = drag;\n | }\n | else {\n | // resize from the top and right edges\n | action.name = 'resize';\n | action.edges = { top: true, right: true };\n | }\n |\n | return action;\n | });\n \\*/\n actionChecker: function (checker) {\n if (scope.isFunction(checker)) {\n this.options.actionChecker = checker;\n\n return this;\n }\n\n if (checker === null) {\n delete this.options.actionChecker;\n\n return this;\n }\n\n return this.options.actionChecker;\n },\n\n /*\\\n * Interactable.getRect\n [ method ]\n *\n * The default function to get an Interactables bounding rect. Can be\n * overridden using @Interactable.rectChecker.\n *\n - element (Element) #optional The element to measure.\n = (object) The object's bounding rectangle.\n o {\n o top : 0,\n o left : 0,\n o bottom: 0,\n o right : 0,\n o width : 0,\n o height: 0\n o }\n \\*/\n getRect: function rectCheck (element) {\n element = element || this._element;\n\n if (this.selector && !(utils.isElement(element))) {\n element = this._context.querySelector(this.selector);\n }\n\n return scope.getElementRect(element);\n },\n\n /*\\\n * Interactable.rectChecker\n [ method ]\n *\n * Returns or sets the function used to calculate the interactable's\n * element's rectangle\n *\n - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect\n = (function | object) The checker function or this Interactable\n \\*/\n rectChecker: function (checker) {\n if (scope.isFunction(checker)) {\n this.getRect = checker;\n\n return this;\n }\n\n if (checker === null) {\n delete this.options.getRect;\n\n return this;\n }\n\n return this.getRect;\n },\n\n /*\\\n * Interactable.styleCursor\n [ method ]\n *\n * Returns or sets whether the action that would be performed when the\n * mouse on the element are checked on `mousemove` so that the cursor\n * may be styled appropriately\n *\n - newValue (boolean) #optional\n = (boolean | Interactable) The current setting or this Interactable\n \\*/\n styleCursor: function (newValue) {\n if (scope.isBool(newValue)) {\n this.options.styleCursor = newValue;\n\n return this;\n }\n\n if (newValue === null) {\n delete this.options.styleCursor;\n\n return this;\n }\n\n return this.options.styleCursor;\n },\n\n /*\\\n * Interactable.preventDefault\n [ method ]\n *\n * Returns or sets whether to prevent the browser's default behaviour\n * in response to pointer events. Can be set to:\n * - `'always'` to always prevent\n * - `'never'` to never prevent\n * - `'auto'` to let interact.js try to determine what would be best\n *\n - newValue (string) #optional `true`, `false` or `'auto'`\n = (string | Interactable) The current setting or this Interactable\n \\*/\n preventDefault: function (newValue) {\n if (/^(always|never|auto)$/.test(newValue)) {\n this.options.preventDefault = newValue;\n return this;\n }\n\n if (scope.isBool(newValue)) {\n this.options.preventDefault = newValue? 'always' : 'never';\n return this;\n }\n\n return this.options.preventDefault;\n },\n\n /*\\\n * Interactable.origin\n [ method ]\n *\n * Gets or sets the origin of the Interactable's element. The x and y\n * of the origin will be subtracted from action event coordinates.\n *\n - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector\n * OR\n - origin (Element) #optional An HTML or SVG Element whose rect will be used\n **\n = (object) The current origin or this Interactable\n \\*/\n origin: function (newValue) {\n if (scope.trySelector(newValue)) {\n this.options.origin = newValue;\n return this;\n }\n else if (scope.isObject(newValue)) {\n this.options.origin = newValue;\n return this;\n }\n\n return this.options.origin;\n },\n\n /*\\\n * Interactable.deltaSource\n [ method ]\n *\n * Returns or sets the mouse coordinate types used to calculate the\n * movement of the pointer.\n *\n - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work\n = (string | object) The current deltaSource or this Interactable\n \\*/\n deltaSource: function (newValue) {\n if (newValue === 'page' || newValue === 'client') {\n this.options.deltaSource = newValue;\n\n return this;\n }\n\n return this.options.deltaSource;\n },\n\n /*\\\n * Interactable.restrict\n [ method ]\n **\n * Deprecated. Add a `restrict` property to the options object passed to\n * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead.\n *\n * Returns or sets the rectangles within which actions on this\n * interactable (after snap calculations) are restricted. By default,\n * restricting is relative to the pointer coordinates. You can change\n * this by setting the\n * [`elementRect`](https://github.com/taye/interact.js/pull/72).\n **\n - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self'\n = (object) The current restrictions object or this Interactable\n **\n | interact(element).restrict({\n | // the rect will be `interact.getElementRect(element.parentNode)`\n | drag: element.parentNode,\n |\n | // x and y are relative to the the interactable's origin\n | resize: { x: 100, y: 100, width: 200, height: 200 }\n | })\n |\n | interact('.draggable').restrict({\n | // the rect will be the selected element's parent\n | drag: 'parent',\n |\n | // do not restrict during normal movement.\n | // Instead, trigger only one restricted move event\n | // immediately before the end event.\n | endOnly: true,\n |\n | // https://github.com/taye/interact.js/pull/72#issue-41813493\n | elementRect: { top: 0, left: 0, bottom: 1, right: 1 }\n | });\n \\*/\n restrict: function (options) {\n if (!scope.isObject(options)) {\n return this.setOptions('restrict', options);\n }\n\n var actions = ['drag', 'resize', 'gesture'],\n ret;\n\n for (var i = 0; i < actions.length; i++) {\n var action = actions[i];\n\n if (action in options) {\n var perAction = utils.extend({\n actions: [action],\n restriction: options[action]\n }, options);\n\n ret = this.setOptions('restrict', perAction);\n }\n }\n\n return ret;\n },\n\n /*\\\n * Interactable.context\n [ method ]\n *\n * Gets the selector context Node of the Interactable. The default is `window.document`.\n *\n = (Node) The context Node of this Interactable\n **\n \\*/\n context: function () {\n return this._context;\n },\n\n _context: scope.document,\n\n /*\\\n * Interactable.ignoreFrom\n [ method ]\n *\n * If the target of the `mousedown`, `pointerdown` or `touchstart`\n * event or any of it's parents match the given CSS selector or\n * Element, no drag/resize/gesture is started.\n *\n - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements\n = (string | Element | object) The current ignoreFrom value or this Interactable\n **\n | interact(element, { ignoreFrom: document.getElementById('no-action') });\n | // or\n | interact(element).ignoreFrom('input, textarea, a');\n \\*/\n ignoreFrom: function (newValue) {\n if (scope.trySelector(newValue)) { // CSS selector to match event.target\n this.options.ignoreFrom = newValue;\n return this;\n }\n\n if (utils.isElement(newValue)) { // specific element\n this.options.ignoreFrom = newValue;\n return this;\n }\n\n return this.options.ignoreFrom;\n },\n\n /*\\\n * Interactable.allowFrom\n [ method ]\n *\n * A drag/resize/gesture is started only If the target of the\n * `mousedown`, `pointerdown` or `touchstart` event or any of it's\n * parents match the given CSS selector or Element.\n *\n - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element\n = (string | Element | object) The current allowFrom value or this Interactable\n **\n | interact(element, { allowFrom: document.getElementById('drag-handle') });\n | // or\n | interact(element).allowFrom('.handle');\n \\*/\n allowFrom: function (newValue) {\n if (scope.trySelector(newValue)) { // CSS selector to match event.target\n this.options.allowFrom = newValue;\n return this;\n }\n\n if (utils.isElement(newValue)) { // specific element\n this.options.allowFrom = newValue;\n return this;\n }\n\n return this.options.allowFrom;\n },\n\n /*\\\n * Interactable.element\n [ method ]\n *\n * If this is not a selector Interactable, it returns the element this\n * interactable represents\n *\n = (Element) HTML / SVG Element\n \\*/\n element: function () {\n return this._element;\n },\n\n /*\\\n * Interactable.fire\n [ method ]\n *\n * Calls listeners for the given InteractEvent type bound globally\n * and directly to this Interactable\n *\n - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable\n = (Interactable) this Interactable\n \\*/\n fire: function (iEvent) {\n if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) {\n return this;\n }\n\n var listeners,\n i,\n len,\n onEvent = 'on' + iEvent.type,\n funcName = '';\n\n // Interactable#on() listeners\n if (iEvent.type in this._iEvents) {\n listeners = this._iEvents[iEvent.type];\n\n for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {\n funcName = listeners[i].name;\n listeners[i](iEvent);\n }\n }\n\n // interactable.onevent listener\n if (scope.isFunction(this[onEvent])) {\n funcName = this[onEvent].name;\n this[onEvent](iEvent);\n }\n\n // interact.on() listeners\n if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) {\n\n for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {\n funcName = listeners[i].name;\n listeners[i](iEvent);\n }\n }\n\n return this;\n },\n\n /*\\\n * Interactable.on\n [ method ]\n *\n * Binds a listener for an InteractEvent or DOM event.\n *\n - eventType (string | array | object) The types of events to listen for\n - listener (function) The function to be called on the given event(s)\n - useCapture (boolean) #optional useCapture flag for addEventListener\n = (object) This Interactable\n \\*/\n on: function (eventType, listener, useCapture) {\n var i;\n\n if (scope.isString(eventType) && eventType.search(' ') !== -1) {\n eventType = eventType.trim().split(/ +/);\n }\n\n if (scope.isArray(eventType)) {\n for (i = 0; i < eventType.length; i++) {\n this.on(eventType[i], listener, useCapture);\n }\n\n return this;\n }\n\n if (scope.isObject(eventType)) {\n for (var prop in eventType) {\n this.on(prop, eventType[prop], listener);\n }\n\n return this;\n }\n\n if (eventType === 'wheel') {\n eventType = scope.wheelEvent;\n }\n\n // convert to boolean\n useCapture = useCapture? true: false;\n\n if (scope.contains(scope.eventTypes, eventType)) {\n // if this type of event was never bound to this Interactable\n if (!(eventType in this._iEvents)) {\n this._iEvents[eventType] = [listener];\n }\n else {\n this._iEvents[eventType].push(listener);\n }\n }\n // delegated event for selector\n else if (this.selector) {\n if (!scope.delegatedEvents[eventType]) {\n scope.delegatedEvents[eventType] = {\n selectors: [],\n contexts : [],\n listeners: []\n };\n\n // add delegate listener functions\n for (i = 0; i < scope.documents.length; i++) {\n events.add(scope.documents[i], eventType, delegateListener);\n events.add(scope.documents[i], eventType, delegateUseCapture, true);\n }\n }\n\n var delegated = scope.delegatedEvents[eventType],\n index;\n\n for (index = delegated.selectors.length - 1; index >= 0; index--) {\n if (delegated.selectors[index] === this.selector\n && delegated.contexts[index] === this._context) {\n break;\n }\n }\n\n if (index === -1) {\n index = delegated.selectors.length;\n\n delegated.selectors.push(this.selector);\n delegated.contexts .push(this._context);\n delegated.listeners.push([]);\n }\n\n // keep listener and useCapture flag\n delegated.listeners[index].push([listener, useCapture]);\n }\n else {\n events.add(this._element, eventType, listener, useCapture);\n }\n\n return this;\n },\n\n /*\\\n * Interactable.off\n [ method ]\n *\n * Removes an InteractEvent or DOM event listener\n *\n - eventType (string | array | object) The types of events that were listened for\n - listener (function) The listener function to be removed\n - useCapture (boolean) #optional useCapture flag for removeEventListener\n = (object) This Interactable\n \\*/\n off: function (eventType, listener, useCapture) {\n var i;\n\n if (scope.isString(eventType) && eventType.search(' ') !== -1) {\n eventType = eventType.trim().split(/ +/);\n }\n\n if (scope.isArray(eventType)) {\n for (i = 0; i < eventType.length; i++) {\n this.off(eventType[i], listener, useCapture);\n }\n\n return this;\n }\n\n if (scope.isObject(eventType)) {\n for (var prop in eventType) {\n this.off(prop, eventType[prop], listener);\n }\n\n return this;\n }\n\n var eventList,\n index = -1;\n\n // convert to boolean\n useCapture = useCapture? true: false;\n\n if (eventType === 'wheel') {\n eventType = scope.wheelEvent;\n }\n\n // if it is an action event type\n if (scope.contains(scope.eventTypes, eventType)) {\n eventList = this._iEvents[eventType];\n\n if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) {\n this._iEvents[eventType].splice(index, 1);\n }\n }\n // delegated event\n else if (this.selector) {\n var delegated = scope.delegatedEvents[eventType],\n matchFound = false;\n\n if (!delegated) { return this; }\n\n // count from last index of delegated to 0\n for (index = delegated.selectors.length - 1; index >= 0; index--) {\n // look for matching selector and context Node\n if (delegated.selectors[index] === this.selector\n && delegated.contexts[index] === this._context) {\n\n var listeners = delegated.listeners[index];\n\n // each item of the listeners array is an array: [function, useCaptureFlag]\n for (i = listeners.length - 1; i >= 0; i--) {\n var fn = listeners[i][0],\n useCap = listeners[i][1];\n\n // check if the listener functions and useCapture flags match\n if (fn === listener && useCap === useCapture) {\n // remove the listener from the array of listeners\n listeners.splice(i, 1);\n\n // if all listeners for this interactable have been removed\n // remove the interactable from the delegated arrays\n if (!listeners.length) {\n delegated.selectors.splice(index, 1);\n delegated.contexts .splice(index, 1);\n delegated.listeners.splice(index, 1);\n\n // remove delegate function from context\n events.remove(this._context, eventType, delegateListener);\n events.remove(this._context, eventType, delegateUseCapture, true);\n\n // remove the arrays if they are empty\n if (!delegated.selectors.length) {\n scope.delegatedEvents[eventType] = null;\n }\n }\n\n // only remove one listener\n matchFound = true;\n break;\n }\n }\n\n if (matchFound) { break; }\n }\n }\n }\n // remove listener from this Interatable's element\n else {\n events.remove(this._element, eventType, listener, useCapture);\n }\n\n return this;\n },\n\n /*\\\n * Interactable.set\n [ method ]\n *\n * Reset the options of this Interactable\n - options (object) The new settings to apply\n = (object) This Interactablw\n \\*/\n set: function (options) {\n if (!scope.isObject(options)) {\n options = {};\n }\n\n this.options = utils.extend({}, scope.defaultOptions.base);\n\n var i,\n actions = ['drag', 'drop', 'resize', 'gesture'],\n methods = ['draggable', 'dropzone', 'resizable', 'gesturable'],\n perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {});\n\n for (i = 0; i < actions.length; i++) {\n var action = actions[i];\n\n this.options[action] = utils.extend({}, scope.defaultOptions[action]);\n\n this.setPerAction(action, perActions);\n\n this[methods[i]](options[action]);\n }\n\n var settings = [\n 'accept', 'actionChecker', 'allowFrom', 'deltaSource',\n 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault',\n 'rectChecker'\n ];\n\n for (i = 0, len = settings.length; i < len; i++) {\n var setting = settings[i];\n\n this.options[setting] = scope.defaultOptions.base[setting];\n\n if (setting in options) {\n this[setting](options[setting]);\n }\n }\n\n return this;\n },\n\n /*\\\n * Interactable.unset\n [ method ]\n *\n * Remove this interactable from the list of interactables and remove\n * it's drag, drop, resize and gesture capabilities\n *\n = (object) @interact\n \\*/\n unset: function () {\n events.remove(this._element, 'all');\n\n if (!scope.isString(this.selector)) {\n events.remove(this, 'all');\n if (this.options.styleCursor) {\n this._element.style.cursor = '';\n }\n }\n else {\n // remove delegated events\n for (var type in scope.delegatedEvents) {\n var delegated = scope.delegatedEvents[type];\n\n for (var i = 0; i < delegated.selectors.length; i++) {\n if (delegated.selectors[i] === this.selector\n && delegated.contexts[i] === this._context) {\n\n delegated.selectors.splice(i, 1);\n delegated.contexts .splice(i, 1);\n delegated.listeners.splice(i, 1);\n\n // remove the arrays if they are empty\n if (!delegated.selectors.length) {\n scope.delegatedEvents[type] = null;\n }\n }\n\n events.remove(this._context, type, delegateListener);\n events.remove(this._context, type, delegateUseCapture, true);\n\n break;\n }\n }\n }\n\n this.dropzone(false);\n\n scope.interactables.splice(scope.indexOf(scope.interactables, this), 1);\n\n return interact;\n }\n };\n\n Interactable.prototype.snap = utils.warnOnce(Interactable.prototype.snap,\n 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping');\n Interactable.prototype.restrict = utils.warnOnce(Interactable.prototype.restrict,\n 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction');\n Interactable.prototype.inertia = utils.warnOnce(Interactable.prototype.inertia,\n 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia');\n Interactable.prototype.autoScroll = utils.warnOnce(Interactable.prototype.autoScroll,\n 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll');\n Interactable.prototype.squareResize = utils.warnOnce(Interactable.prototype.squareResize,\n 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square');\n\n /*\\\n * interact.isSet\n [ method ]\n *\n * Check if an element has been set\n - element (Element) The Element being searched for\n = (boolean) Indicates if the element or CSS selector was previously passed to interact\n \\*/\n interact.isSet = function(element, options) {\n return scope.interactables.indexOfElement(element, options && options.context) !== -1;\n };\n\n /*\\\n * interact.on\n [ method ]\n *\n * Adds a global listener for an InteractEvent or adds a DOM event to\n * `document`\n *\n - type (string | array | object) The types of events to listen for\n - listener (function) The function to be called on the given event(s)\n - useCapture (boolean) #optional useCapture flag for addEventListener\n = (object) interact\n \\*/\n interact.on = function (type, listener, useCapture) {\n if (scope.isString(type) && type.search(' ') !== -1) {\n type = type.trim().split(/ +/);\n }\n\n if (scope.isArray(type)) {\n for (var i = 0; i < type.length; i++) {\n interact.on(type[i], listener, useCapture);\n }\n\n return interact;\n }\n\n if (scope.isObject(type)) {\n for (var prop in type) {\n interact.on(prop, type[prop], listener);\n }\n\n return interact;\n }\n\n // if it is an InteractEvent type, add listener to globalEvents\n if (scope.contains(scope.eventTypes, type)) {\n // if this type of event was never bound\n if (!scope.globalEvents[type]) {\n scope.globalEvents[type] = [listener];\n }\n else {\n scope.globalEvents[type].push(listener);\n }\n }\n // If non InteractEvent type, addEventListener to document\n else {\n events.add(scope.document, type, listener, useCapture);\n }\n\n return interact;\n };\n\n /*\\\n * interact.off\n [ method ]\n *\n * Removes a global InteractEvent listener or DOM event from `document`\n *\n - type (string | array | object) The types of events that were listened for\n - listener (function) The listener function to be removed\n - useCapture (boolean) #optional useCapture flag for removeEventListener\n = (object) interact\n \\*/\n interact.off = function (type, listener, useCapture) {\n if (scope.isString(type) && type.search(' ') !== -1) {\n type = type.trim().split(/ +/);\n }\n\n if (scope.isArray(type)) {\n for (var i = 0; i < type.length; i++) {\n interact.off(type[i], listener, useCapture);\n }\n\n return interact;\n }\n\n if (scope.isObject(type)) {\n for (var prop in type) {\n interact.off(prop, type[prop], listener);\n }\n\n return interact;\n }\n\n if (!scope.contains(scope.eventTypes, type)) {\n events.remove(scope.document, type, listener, useCapture);\n }\n else {\n var index;\n\n if (type in scope.globalEvents\n && (index = scope.indexOf(scope.globalEvents[type], listener)) !== -1) {\n scope.globalEvents[type].splice(index, 1);\n }\n }\n\n return interact;\n };\n\n /*\\\n * interact.enableDragging\n [ method ]\n *\n * Deprecated.\n *\n * Returns or sets whether dragging is enabled for any Interactables\n *\n - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables\n = (boolean | object) The current setting or interact\n \\*/\n interact.enableDragging = utils.warnOnce(function (newValue) {\n if (newValue !== null && newValue !== undefined) {\n scope.actionIsEnabled.drag = newValue;\n\n return interact;\n }\n return scope.actionIsEnabled.drag;\n }, 'interact.enableDragging is deprecated and will soon be removed.');\n\n /*\\\n * interact.enableResizing\n [ method ]\n *\n * Deprecated.\n *\n * Returns or sets whether resizing is enabled for any Interactables\n *\n - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables\n = (boolean | object) The current setting or interact\n \\*/\n interact.enableResizing = utils.warnOnce(function (newValue) {\n if (newValue !== null && newValue !== undefined) {\n scope.actionIsEnabled.resize = newValue;\n\n return interact;\n }\n return scope.actionIsEnabled.resize;\n }, 'interact.enableResizing is deprecated and will soon be removed.');\n\n /*\\\n * interact.enableGesturing\n [ method ]\n *\n * Deprecated.\n *\n * Returns or sets whether gesturing is enabled for any Interactables\n *\n - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables\n = (boolean | object) The current setting or interact\n \\*/\n interact.enableGesturing = utils.warnOnce(function (newValue) {\n if (newValue !== null && newValue !== undefined) {\n scope.actionIsEnabled.gesture = newValue;\n\n return interact;\n }\n return scope.actionIsEnabled.gesture;\n }, 'interact.enableGesturing is deprecated and will soon be removed.');\n\n interact.eventTypes = scope.eventTypes;\n\n /*\\\n * interact.debug\n [ method ]\n *\n * Returns debugging data\n = (object) An object with properties that outline the current state and expose internal functions and variables\n \\*/\n interact.debug = function () {\n var interaction = scope.interactions[0] || new Interaction();\n\n return {\n interactions : scope.interactions,\n target : interaction.target,\n dragging : interaction.dragging,\n resizing : interaction.resizing,\n gesturing : interaction.gesturing,\n prepared : interaction.prepared,\n matches : interaction.matches,\n matchElements : interaction.matchElements,\n\n prevCoords : interaction.prevCoords,\n startCoords : interaction.startCoords,\n\n pointerIds : interaction.pointerIds,\n pointers : interaction.pointers,\n addPointer : scope.listeners.addPointer,\n removePointer : scope.listeners.removePointer,\n recordPointer : scope.listeners.recordPointer,\n\n snap : interaction.snapStatus,\n restrict : interaction.restrictStatus,\n inertia : interaction.inertiaStatus,\n\n downTime : interaction.downTimes[0],\n downEvent : interaction.downEvent,\n downPointer : interaction.downPointer,\n prevEvent : interaction.prevEvent,\n\n Interactable : Interactable,\n interactables : scope.interactables,\n pointerIsDown : interaction.pointerIsDown,\n defaultOptions : scope.defaultOptions,\n defaultActionChecker : defaultActionChecker,\n\n actionCursors : scope.actionCursors,\n dragMove : scope.listeners.dragMove,\n resizeMove : scope.listeners.resizeMove,\n gestureMove : scope.listeners.gestureMove,\n pointerUp : scope.listeners.pointerUp,\n pointerDown : scope.listeners.pointerDown,\n pointerMove : scope.listeners.pointerMove,\n pointerHover : scope.listeners.pointerHover,\n\n eventTypes : scope.eventTypes,\n\n events : events,\n globalEvents : scope.globalEvents,\n delegatedEvents : scope.delegatedEvents\n };\n };\n\n // expose the functions used to calculate multi-touch properties\n interact.getTouchAverage = utils.touchAverage;\n interact.getTouchBBox = utils.touchBBox;\n interact.getTouchDistance = utils.touchDistance;\n interact.getTouchAngle = utils.touchAngle;\n\n interact.getElementRect = scope.getElementRect;\n interact.matchesSelector = scope.matchesSelector;\n interact.closest = scope.closest;\n\n /*\\\n * interact.margin\n [ method ]\n *\n * Returns or sets the margin for autocheck resizing used in\n * @Interactable.getAction. That is the distance from the bottom and right\n * edges of an element clicking in which will start resizing\n *\n - newValue (number) #optional\n = (number | interact) The current margin value or interact\n \\*/\n interact.margin = function (newvalue) {\n if (scope.isNumber(newvalue)) {\n scope.margin = newvalue;\n\n return interact;\n }\n return scope.margin;\n };\n\n /*\\\n * interact.supportsTouch\n [ method ]\n *\n = (boolean) Whether or not the browser supports touch input\n \\*/\n interact.supportsTouch = function () {\n return browser.supportsTouch;\n };\n\n /*\\\n * interact.supportsPointerEvent\n [ method ]\n *\n = (boolean) Whether or not the browser supports PointerEvents\n \\*/\n interact.supportsPointerEvent = function () {\n return browser.supportsPointerEvent;\n };\n\n /*\\\n * interact.stop\n [ method ]\n *\n * Cancels all interactions (end events are not fired)\n *\n - event (Event) An event on which to call preventDefault()\n = (object) interact\n \\*/\n interact.stop = function (event) {\n for (var i = scope.interactions.length - 1; i > 0; i--) {\n scope.interactions[i].stop(event);\n }\n\n return interact;\n };\n\n /*\\\n * interact.dynamicDrop\n [ method ]\n *\n * Returns or sets whether the dimensions of dropzone elements are\n * calculated on every dragmove or only on dragstart for the default\n * dropChecker\n *\n - newValue (boolean) #optional True to check on each move. False to check only before start\n = (boolean | interact) The current setting or interact\n \\*/\n interact.dynamicDrop = function (newValue) {\n if (scope.isBool(newValue)) {\n //if (dragging && dynamicDrop !== newValue && !newValue) {\n //calcRects(dropzones);\n //}\n\n scope.dynamicDrop = newValue;\n\n return interact;\n }\n return scope.dynamicDrop;\n };\n\n /*\\\n * interact.pointerMoveTolerance\n [ method ]\n * Returns or sets the distance the pointer must be moved before an action\n * sequence occurs. This also affects tolerance for tap events.\n *\n - newValue (number) #optional The movement from the start position must be greater than this value\n = (number | Interactable) The current setting or interact\n \\*/\n interact.pointerMoveTolerance = function (newValue) {\n if (scope.isNumber(newValue)) {\n scope.pointerMoveTolerance = newValue;\n\n return this;\n }\n\n return scope.pointerMoveTolerance;\n };\n\n /*\\\n * interact.maxInteractions\n [ method ]\n **\n * Returns or sets the maximum number of concurrent interactions allowed.\n * By default only 1 interaction is allowed at a time (for backwards\n * compatibility). To allow multiple interactions on the same Interactables\n * and elements, you need to enable it in the draggable, resizable and\n * gesturable `'max'` and `'maxPerElement'` options.\n **\n - newValue (number) #optional Any number. newValue <= 0 means no interactions.\n \\*/\n interact.maxInteractions = function (newValue) {\n if (scope.isNumber(newValue)) {\n scope.maxInteractions = newValue;\n\n return this;\n }\n\n return scope.maxInteractions;\n };\n\n interact.createSnapGrid = function (grid) {\n return function (x, y) {\n var offsetX = 0,\n offsetY = 0;\n\n if (scope.isObject(grid.offset)) {\n offsetX = grid.offset.x;\n offsetY = grid.offset.y;\n }\n\n var gridx = Math.round((x - offsetX) / grid.x),\n gridy = Math.round((y - offsetY) / grid.y),\n\n newX = gridx * grid.x + offsetX,\n newY = gridy * grid.y + offsetY;\n\n return {\n x: newX,\n y: newY,\n range: grid.range\n };\n };\n };\n\n function endAllInteractions (event) {\n for (var i = 0; i < scope.interactions.length; i++) {\n scope.interactions[i].pointerEnd(event, event);\n }\n }\n\n function listenToDocument (doc) {\n if (scope.contains(scope.documents, doc)) { return; }\n\n var win = doc.defaultView || doc.parentWindow;\n\n // add delegate event listener\n for (var eventType in scope.delegatedEvents) {\n events.add(doc, eventType, delegateListener);\n events.add(doc, eventType, delegateUseCapture, true);\n }\n\n if (scope.PointerEvent) {\n if (scope.PointerEvent === win.MSPointerEvent) {\n scope.pEventTypes = {\n up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',\n out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' };\n }\n else {\n scope.pEventTypes = {\n up: 'pointerup', down: 'pointerdown', over: 'pointerover',\n out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' };\n }\n\n events.add(doc, scope.pEventTypes.down , scope.listeners.selectorDown );\n events.add(doc, scope.pEventTypes.move , scope.listeners.pointerMove );\n events.add(doc, scope.pEventTypes.over , scope.listeners.pointerOver );\n events.add(doc, scope.pEventTypes.out , scope.listeners.pointerOut );\n events.add(doc, scope.pEventTypes.up , scope.listeners.pointerUp );\n events.add(doc, scope.pEventTypes.cancel, scope.listeners.pointerCancel);\n\n // autoscroll\n events.add(doc, scope.pEventTypes.move, scope.listeners.autoScrollMove);\n }\n else {\n events.add(doc, 'mousedown', scope.listeners.selectorDown);\n events.add(doc, 'mousemove', scope.listeners.pointerMove );\n events.add(doc, 'mouseup' , scope.listeners.pointerUp );\n events.add(doc, 'mouseover', scope.listeners.pointerOver );\n events.add(doc, 'mouseout' , scope.listeners.pointerOut );\n\n events.add(doc, 'touchstart' , scope.listeners.selectorDown );\n events.add(doc, 'touchmove' , scope.listeners.pointerMove );\n events.add(doc, 'touchend' , scope.listeners.pointerUp );\n events.add(doc, 'touchcancel', scope.listeners.pointerCancel);\n\n // autoscroll\n events.add(doc, 'mousemove', scope.listeners.autoScrollMove);\n events.add(doc, 'touchmove', scope.listeners.autoScrollMove);\n }\n\n events.add(win, 'blur', endAllInteractions);\n\n try {\n if (win.frameElement) {\n var parentDoc = win.frameElement.ownerDocument,\n parentWindow = parentDoc.defaultView;\n\n events.add(parentDoc , 'mouseup' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'touchend' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'touchcancel' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'pointerup' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'MSPointerUp' , scope.listeners.pointerEnd);\n events.add(parentWindow, 'blur' , endAllInteractions );\n }\n }\n catch (error) {\n interact.windowParentError = error;\n }\n\n if (events.useAttachEvent) {\n // For IE's lack of Event#preventDefault\n events.add(doc, 'selectstart', function (event) {\n var interaction = scope.interactions[0];\n\n if (interaction.currentAction()) {\n interaction.checkAndPreventDefault(event);\n }\n });\n\n // For IE's bad dblclick event sequence\n events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick'));\n }\n\n scope.documents.push(doc);\n }\n\n listenToDocument(scope.document);\n\n scope.interact = interact;\n scope.Interactable = Interactable;\n scope.Interaction = Interaction;\n scope.InteractEvent = InteractEvent;\n\n module.exports = interact;\n\n},{\"./InteractEvent\":2,\"./Interaction\":3,\"./autoScroll\":4,\"./defaultOptions\":5,\"./scope\":6,\"./utils\":13,\"./utils/events\":10,\"./utils/window\":18}],2:[function(require,module,exports){\n'use strict';\n\nvar scope = require('./scope');\nvar utils = require('./utils');\n\nfunction InteractEvent (interaction, event, action, phase, element, related) {\n var client,\n page,\n target = interaction.target,\n snapStatus = interaction.snapStatus,\n restrictStatus = interaction.restrictStatus,\n pointers = interaction.pointers,\n deltaSource = (target && target.options || scope.defaultOptions).deltaSource,\n sourceX = deltaSource + 'X',\n sourceY = deltaSource + 'Y',\n options = target? target.options: scope.defaultOptions,\n origin = scope.getOriginXY(target, element),\n starting = phase === 'start',\n ending = phase === 'end',\n coords = starting? interaction.startCoords : interaction.curCoords;\n\n element = element || interaction.element;\n\n page = utils.extend({}, coords.page);\n client = utils.extend({}, coords.client);\n\n page.x -= origin.x;\n page.y -= origin.y;\n\n client.x -= origin.x;\n client.y -= origin.y;\n\n var relativePoints = options[action].snap && options[action].snap.relativePoints ;\n\n if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) {\n this.snap = {\n range : snapStatus.range,\n locked : snapStatus.locked,\n x : snapStatus.snappedX,\n y : snapStatus.snappedY,\n realX : snapStatus.realX,\n realY : snapStatus.realY,\n dx : snapStatus.dx,\n dy : snapStatus.dy\n };\n\n if (snapStatus.locked) {\n page.x += snapStatus.dx;\n page.y += snapStatus.dy;\n client.x += snapStatus.dx;\n client.y += snapStatus.dy;\n }\n }\n\n if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) {\n page.x += restrictStatus.dx;\n page.y += restrictStatus.dy;\n client.x += restrictStatus.dx;\n client.y += restrictStatus.dy;\n\n this.restrict = {\n dx: restrictStatus.dx,\n dy: restrictStatus.dy\n };\n }\n\n this.pageX = page.x;\n this.pageY = page.y;\n this.clientX = client.x;\n this.clientY = client.y;\n\n this.x0 = interaction.startCoords.page.x - origin.x;\n this.y0 = interaction.startCoords.page.y - origin.y;\n this.clientX0 = interaction.startCoords.client.x - origin.x;\n this.clientY0 = interaction.startCoords.client.y - origin.y;\n this.ctrlKey = event.ctrlKey;\n this.altKey = event.altKey;\n this.shiftKey = event.shiftKey;\n this.metaKey = event.metaKey;\n this.button = event.button;\n this.target = element;\n this.t0 = interaction.downTimes[0];\n this.type = action + (phase || '');\n\n this.interaction = interaction;\n this.interactable = target;\n\n var inertiaStatus = interaction.inertiaStatus;\n\n if (inertiaStatus.active) {\n this.detail = 'inertia';\n }\n\n if (related) {\n this.relatedTarget = related;\n }\n\n // end event dx, dy is difference between start and end points\n if (ending) {\n if (deltaSource === 'client') {\n this.dx = client.x - interaction.startCoords.client.x;\n this.dy = client.y - interaction.startCoords.client.y;\n }\n else {\n this.dx = page.x - interaction.startCoords.page.x;\n this.dy = page.y - interaction.startCoords.page.y;\n }\n }\n else if (starting) {\n this.dx = 0;\n this.dy = 0;\n }\n // copy properties from previousmove if starting inertia\n else if (phase === 'inertiastart') {\n this.dx = interaction.prevEvent.dx;\n this.dy = interaction.prevEvent.dy;\n }\n else {\n if (deltaSource === 'client') {\n this.dx = client.x - interaction.prevEvent.clientX;\n this.dy = client.y - interaction.prevEvent.clientY;\n }\n else {\n this.dx = page.x - interaction.prevEvent.pageX;\n this.dy = page.y - interaction.prevEvent.pageY;\n }\n }\n if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia'\n && !inertiaStatus.active\n && options[action].inertia && options[action].inertia.zeroResumeDelta) {\n\n inertiaStatus.resumeDx += this.dx;\n inertiaStatus.resumeDy += this.dy;\n\n this.dx = this.dy = 0;\n }\n\n if (action === 'resize' && interaction.resizeAxes) {\n if (options.resize.square) {\n if (interaction.resizeAxes === 'y') {\n this.dx = this.dy;\n }\n else {\n this.dy = this.dx;\n }\n this.axes = 'xy';\n }\n else {\n this.axes = interaction.resizeAxes;\n\n if (interaction.resizeAxes === 'x') {\n this.dy = 0;\n }\n else if (interaction.resizeAxes === 'y') {\n this.dx = 0;\n }\n }\n }\n else if (action === 'gesture') {\n this.touches = [pointers[0], pointers[1]];\n\n if (starting) {\n this.distance = utils.touchDistance(pointers, deltaSource);\n this.box = utils.touchBBox(pointers);\n this.scale = 1;\n this.ds = 0;\n this.angle = utils.touchAngle(pointers, undefined, deltaSource);\n this.da = 0;\n }\n else if (ending || event instanceof InteractEvent) {\n this.distance = interaction.prevEvent.distance;\n this.box = interaction.prevEvent.box;\n this.scale = interaction.prevEvent.scale;\n this.ds = this.scale - 1;\n this.angle = interaction.prevEvent.angle;\n this.da = this.angle - interaction.gesture.startAngle;\n }\n else {\n this.distance = utils.touchDistance(pointers, deltaSource);\n this.box = utils.touchBBox(pointers);\n this.scale = this.distance / interaction.gesture.startDistance;\n this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource);\n\n this.ds = this.scale - interaction.gesture.prevScale;\n this.da = this.angle - interaction.gesture.prevAngle;\n }\n }\n\n if (starting) {\n this.timeStamp = interaction.downTimes[0];\n this.dt = 0;\n this.duration = 0;\n this.speed = 0;\n this.velocityX = 0;\n this.velocityY = 0;\n }\n else if (phase === 'inertiastart') {\n this.timeStamp = interaction.prevEvent.timeStamp;\n this.dt = interaction.prevEvent.dt;\n this.duration = interaction.prevEvent.duration;\n this.speed = interaction.prevEvent.speed;\n this.velocityX = interaction.prevEvent.velocityX;\n this.velocityY = interaction.prevEvent.velocityY;\n }\n else {\n this.timeStamp = new Date().getTime();\n this.dt = this.timeStamp - interaction.prevEvent.timeStamp;\n this.duration = this.timeStamp - interaction.downTimes[0];\n\n if (event instanceof InteractEvent) {\n var dx = this[sourceX] - interaction.prevEvent[sourceX],\n dy = this[sourceY] - interaction.prevEvent[sourceY],\n dt = this.dt / 1000;\n\n this.speed = utils.hypot(dx, dy) / dt;\n this.velocityX = dx / dt;\n this.velocityY = dy / dt;\n }\n // if normal move or end event, use previous user event coords\n else {\n // speed and velocity in pixels per second\n this.speed = interaction.pointerDelta[deltaSource].speed;\n this.velocityX = interaction.pointerDelta[deltaSource].vx;\n this.velocityY = interaction.pointerDelta[deltaSource].vy;\n }\n }\n\n if ((ending || phase === 'inertiastart')\n && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) {\n\n var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI,\n overlap = 22.5;\n\n if (angle < 0) {\n angle += 360;\n }\n\n var left = 135 - overlap <= angle && angle < 225 + overlap,\n up = 225 - overlap <= angle && angle < 315 + overlap,\n\n right = !left && (315 - overlap <= angle || angle < 45 + overlap),\n down = !up && 45 - overlap <= angle && angle < 135 + overlap;\n\n this.swipe = {\n up : up,\n down : down,\n left : left,\n right: right,\n angle: angle,\n speed: interaction.prevEvent.speed,\n velocity: {\n x: interaction.prevEvent.velocityX,\n y: interaction.prevEvent.velocityY\n }\n };\n }\n}\n\nInteractEvent.prototype = {\n preventDefault: utils.blank,\n stopImmediatePropagation: function () {\n this.immediatePropagationStopped = this.propagationStopped = true;\n },\n stopPropagation: function () {\n this.propagationStopped = true;\n }\n};\n\nmodule.exports = InteractEvent;\n\n},{\"./scope\":6,\"./utils\":13}],3:[function(require,module,exports){\n'use strict';\n\nvar scope = require('./scope');\nvar utils = require('./utils');\nvar animationFrame = utils.raf;\nvar InteractEvent = require('./InteractEvent');\nvar events = require('./utils/events');\nvar browser = require('./utils/browser');\n\nfunction Interaction () {\n this.target = null; // current interactable being interacted with\n this.element = null; // the target element of the interactable\n this.dropTarget = null; // the dropzone a drag target might be dropped into\n this.dropElement = null; // the element at the time of checking\n this.prevDropTarget = null; // the dropzone that was recently dragged away from\n this.prevDropElement = null; // the element at the time of checking\n\n this.prepared = { // action that's ready to be fired on next move event\n name : null,\n axis : null,\n edges: null\n };\n\n this.matches = []; // all selectors that are matched by target element\n this.matchElements = []; // corresponding elements\n\n this.inertiaStatus = {\n active : false,\n smoothEnd : false,\n\n startEvent: null,\n upCoords: {},\n\n xe: 0, ye: 0,\n sx: 0, sy: 0,\n\n t0: 0,\n vx0: 0, vys: 0,\n duration: 0,\n\n resumeDx: 0,\n resumeDy: 0,\n\n lambda_v0: 0,\n one_ve_v0: 0,\n i : null\n };\n\n if (scope.isFunction(Function.prototype.bind)) {\n this.boundInertiaFrame = this.inertiaFrame.bind(this);\n this.boundSmoothEndFrame = this.smoothEndFrame.bind(this);\n }\n else {\n var that = this;\n\n this.boundInertiaFrame = function () { return that.inertiaFrame(); };\n this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); };\n }\n\n this.activeDrops = {\n dropzones: [], // the dropzones that are mentioned below\n elements : [], // elements of dropzones that accept the target draggable\n rects : [] // the rects of the elements mentioned above\n };\n\n // keep track of added pointers\n this.pointers = [];\n this.pointerIds = [];\n this.downTargets = [];\n this.downTimes = [];\n this.holdTimers = [];\n\n // Previous native pointer move event coordinates\n this.prevCoords = {\n page : { x: 0, y: 0 },\n client : { x: 0, y: 0 },\n timeStamp: 0\n };\n // current native pointer move event coordinates\n this.curCoords = {\n page : { x: 0, y: 0 },\n client : { x: 0, y: 0 },\n timeStamp: 0\n };\n\n // Starting InteractEvent pointer coordinates\n this.startCoords = {\n page : { x: 0, y: 0 },\n client : { x: 0, y: 0 },\n timeStamp: 0\n };\n\n // Change in coordinates and time of the pointer\n this.pointerDelta = {\n page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },\n client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },\n timeStamp: 0\n };\n\n this.downEvent = null; // pointerdown/mousedown/touchstart event\n this.downPointer = {};\n\n this._eventTarget = null;\n this._curEventTarget = null;\n\n this.prevEvent = null; // previous action event\n this.tapTime = 0; // time of the most recent tap event\n this.prevTap = null;\n\n this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 };\n this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 };\n this.snapOffsets = [];\n\n this.gesture = {\n start: { x: 0, y: 0 },\n\n startDistance: 0, // distance between two touches of touchStart\n prevDistance : 0,\n distance : 0,\n\n scale: 1, // gesture.distance / gesture.startDistance\n\n startAngle: 0, // angle of line joining two touches\n prevAngle : 0 // angle of the previous gesture event\n };\n\n this.snapStatus = {\n x : 0, y : 0,\n dx : 0, dy : 0,\n realX : 0, realY : 0,\n snappedX: 0, snappedY: 0,\n targets : [],\n locked : false,\n changed : false\n };\n\n this.restrictStatus = {\n dx : 0, dy : 0,\n restrictedX: 0, restrictedY: 0,\n snap : null,\n restricted : false,\n changed : false\n };\n\n this.restrictStatus.snap = this.snapStatus;\n\n this.pointerIsDown = false;\n this.pointerWasMoved = false;\n this.gesturing = false;\n this.dragging = false;\n this.resizing = false;\n this.resizeAxes = 'xy';\n\n this.mouse = false;\n\n scope.interactions.push(this);\n}\n\n// Check if action is enabled globally and the current target supports it\n// If so, return the validated action. Otherwise, return null\nfunction validateAction (action, interactable) {\n if (!scope.isObject(action)) { return null; }\n\n var actionName = action.name,\n options = interactable.options;\n\n if (( (actionName === 'resize' && options.resize.enabled )\n || (actionName === 'drag' && options.drag.enabled )\n || (actionName === 'gesture' && options.gesture.enabled))\n && scope.actionIsEnabled[actionName]) {\n\n if (actionName === 'resize' || actionName === 'resizeyx') {\n actionName = 'resizexy';\n }\n\n return action;\n }\n return null;\n}\n\nfunction getActionCursor (action) {\n var cursor = '';\n\n if (action.name === 'drag') {\n cursor = scope.actionCursors.drag;\n }\n if (action.name === 'resize') {\n if (action.axis) {\n cursor = scope.actionCursors[action.name + action.axis];\n }\n else if (action.edges) {\n var cursorKey = 'resize',\n edgeNames = ['top', 'bottom', 'left', 'right'];\n\n for (var i = 0; i < 4; i++) {\n if (action.edges[edgeNames[i]]) {\n cursorKey += edgeNames[i];\n }\n }\n\n cursor = scope.actionCursors[cursorKey];\n }\n }\n\n return cursor;\n}\n\nfunction preventOriginalDefault () {\n this.originalEvent.preventDefault();\n}\n\nInteraction.prototype = {\n getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); },\n getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); },\n setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); },\n\n pointerOver: function (pointer, event, eventTarget) {\n if (this.prepared.name || !this.mouse) { return; }\n\n var curMatches = [],\n curMatchElements = [],\n prevTargetElement = this.element;\n\n this.addPointer(pointer);\n\n if (this.target\n && (scope.testIgnore(this.target, this.element, eventTarget)\n || !scope.testAllow(this.target, this.element, eventTarget))) {\n // if the eventTarget should be ignored or shouldn't be allowed\n // clear the previous target\n this.target = null;\n this.element = null;\n this.matches = [];\n this.matchElements = [];\n }\n\n var elementInteractable = scope.interactables.get(eventTarget),\n elementAction = (elementInteractable\n && !scope.testIgnore(elementInteractable, eventTarget, eventTarget)\n && scope.testAllow(elementInteractable, eventTarget, eventTarget)\n && validateAction(\n elementInteractable.getAction(pointer, event, this, eventTarget),\n elementInteractable));\n\n if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) {\n elementAction = null;\n }\n\n function pushCurMatches (interactable, selector) {\n if (interactable\n && scope.inContext(interactable, eventTarget)\n && !scope.testIgnore(interactable, eventTarget, eventTarget)\n && scope.testAllow(interactable, eventTarget, eventTarget)\n && scope.matchesSelector(eventTarget, selector)) {\n\n curMatches.push(interactable);\n curMatchElements.push(eventTarget);\n }\n }\n\n if (elementAction) {\n this.target = elementInteractable;\n this.element = eventTarget;\n this.matches = [];\n this.matchElements = [];\n }\n else {\n scope.interactables.forEachSelector(pushCurMatches);\n\n if (this.validateSelector(pointer, event, curMatches, curMatchElements)) {\n this.matches = curMatches;\n this.matchElements = curMatchElements;\n\n this.pointerHover(pointer, event, this.matches, this.matchElements);\n events.add(eventTarget,\n scope.PointerEvent? scope.pEventTypes.move : 'mousemove',\n scope.listeners.pointerHover);\n }\n else if (this.target) {\n if (scope.nodeContains(prevTargetElement, eventTarget)) {\n this.pointerHover(pointer, event, this.matches, this.matchElements);\n events.add(this.element,\n scope.PointerEvent? scope.pEventTypes.move : 'mousemove',\n scope.listeners.pointerHover);\n }\n else {\n this.target = null;\n this.element = null;\n this.matches = [];\n this.matchElements = [];\n }\n }\n }\n },\n\n // Check what action would be performed on pointerMove target if a mouse\n // button were pressed and change the cursor accordingly\n pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) {\n var target = this.target;\n\n if (!this.prepared.name && this.mouse) {\n\n var action;\n\n // update pointer coords for defaultActionChecker to use\n this.setEventXY(this.curCoords, pointer);\n\n if (matches) {\n action = this.validateSelector(pointer, event, matches, matchElements);\n }\n else if (target) {\n action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target);\n }\n\n if (target && target.options.styleCursor) {\n if (action) {\n target._doc.documentElement.style.cursor = getActionCursor(action);\n }\n else {\n target._doc.documentElement.style.cursor = '';\n }\n }\n }\n else if (this.prepared.name) {\n this.checkAndPreventDefault(event, target, this.element);\n }\n },\n\n pointerOut: function (pointer, event, eventTarget) {\n if (this.prepared.name) { return; }\n\n // Remove temporary event listeners for selector Interactables\n if (!scope.interactables.get(eventTarget)) {\n events.remove(eventTarget,\n scope.PointerEvent? scope.pEventTypes.move : 'mousemove',\n scope.listeners.pointerHover);\n }\n\n if (this.target && this.target.options.styleCursor && !this.interacting()) {\n this.target._doc.documentElement.style.cursor = '';\n }\n },\n\n selectorDown: function (pointer, event, eventTarget, curEventTarget) {\n var that = this,\n // copy event to be used in timeout for IE8\n eventCopy = events.useAttachEvent? utils.extend({}, event) : event,\n element = eventTarget,\n pointerIndex = this.addPointer(pointer),\n action;\n\n this.holdTimers[pointerIndex] = setTimeout(function () {\n that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget);\n }, scope.defaultOptions._holdDuration);\n\n this.pointerIsDown = true;\n\n // Check if the down event hits the current inertia target\n if (this.inertiaStatus.active && this.target.selector) {\n // climb up the DOM tree from the event target\n while (utils.isElement(element)) {\n\n // if this element is the current inertia target element\n if (element === this.element\n // and the prospective action is the same as the ongoing one\n && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) {\n\n // stop inertia so that the next move will be a normal one\n animationFrame.cancel(this.inertiaStatus.i);\n this.inertiaStatus.active = false;\n\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n return;\n }\n element = scope.parentElement(element);\n }\n }\n\n // do nothing if interacting\n if (this.interacting()) {\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n return;\n }\n\n function pushMatches (interactable, selector, context) {\n var elements = scope.ie8MatchesSelector\n ? context.querySelectorAll(selector)\n : undefined;\n\n if (scope.inContext(interactable, element)\n && !scope.testIgnore(interactable, element, eventTarget)\n && scope.testAllow(interactable, element, eventTarget)\n && scope.matchesSelector(element, selector, elements)) {\n\n that.matches.push(interactable);\n that.matchElements.push(element);\n }\n }\n\n // update pointer coords for defaultActionChecker to use\n this.setEventXY(this.curCoords, pointer);\n this.downEvent = event;\n\n while (utils.isElement(element) && !action) {\n this.matches = [];\n this.matchElements = [];\n\n scope.interactables.forEachSelector(pushMatches);\n\n action = this.validateSelector(pointer, event, this.matches, this.matchElements);\n element = scope.parentElement(element);\n }\n\n if (action) {\n this.prepared.name = action.name;\n this.prepared.axis = action.axis;\n this.prepared.edges = action.edges;\n\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n\n return this.pointerDown(pointer, event, eventTarget, curEventTarget, action);\n }\n else {\n // do these now since pointerDown isn't being called from here\n this.downTimes[pointerIndex] = new Date().getTime();\n this.downTargets[pointerIndex] = eventTarget;\n utils.extend(this.downPointer, pointer);\n\n utils.copyCoords(this.prevCoords, this.curCoords);\n this.pointerWasMoved = false;\n }\n\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n },\n\n // Determine action to be performed on next pointerMove and add appropriate\n // style and event Listeners\n pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) {\n if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) {\n this.checkAndPreventDefault(event, this.target, this.element);\n\n return;\n }\n\n this.pointerIsDown = true;\n this.downEvent = event;\n\n var pointerIndex = this.addPointer(pointer),\n action;\n\n // If it is the second touch of a multi-touch gesture, keep the target\n // the same if a target was set by the first touch\n // Otherwise, set the target if there is no action prepared\n if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) {\n\n var interactable = scope.interactables.get(curEventTarget);\n\n if (interactable\n && !scope.testIgnore(interactable, curEventTarget, eventTarget)\n && scope.testAllow(interactable, curEventTarget, eventTarget)\n && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget))\n && scope.withinInteractionLimit(interactable, curEventTarget, action)) {\n this.target = interactable;\n this.element = curEventTarget;\n }\n }\n\n var target = this.target,\n options = target && target.options;\n\n if (target && (forceAction || !this.prepared.name)) {\n action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element);\n\n this.setEventXY(this.startCoords);\n\n if (!action) { return; }\n\n if (options.styleCursor) {\n target._doc.documentElement.style.cursor = getActionCursor(action);\n }\n\n this.resizeAxes = action.name === 'resize'? action.axis : null;\n\n if (action === 'gesture' && this.pointerIds.length < 2) {\n action = null;\n }\n\n this.prepared.name = action.name;\n this.prepared.axis = action.axis;\n this.prepared.edges = action.edges;\n\n this.snapStatus.snappedX = this.snapStatus.snappedY =\n this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN;\n\n this.downTimes[pointerIndex] = new Date().getTime();\n this.downTargets[pointerIndex] = eventTarget;\n utils.extend(this.downPointer, pointer);\n\n this.setEventXY(this.prevCoords);\n this.pointerWasMoved = false;\n\n this.checkAndPreventDefault(event, target, this.element);\n }\n // if inertia is active try to resume action\n else if (this.inertiaStatus.active\n && curEventTarget === this.element\n && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) {\n\n animationFrame.cancel(this.inertiaStatus.i);\n this.inertiaStatus.active = false;\n\n this.checkAndPreventDefault(event, target, this.element);\n }\n },\n\n setModifications: function (coords, preEnd) {\n var target = this.target,\n shouldMove = true,\n shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd),\n shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd);\n\n if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; }\n if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; }\n\n if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) {\n shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed;\n }\n else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) {\n shouldMove = false;\n }\n\n return shouldMove;\n },\n\n setStartOffsets: function (action, interactable, element) {\n var rect = interactable.getRect(element),\n origin = scope.getOriginXY(interactable, element),\n snap = interactable.options[this.prepared.name].snap,\n restrict = interactable.options[this.prepared.name].restrict,\n width, height;\n\n if (rect) {\n this.startOffset.left = this.startCoords.page.x - rect.left;\n this.startOffset.top = this.startCoords.page.y - rect.top;\n\n this.startOffset.right = rect.right - this.startCoords.page.x;\n this.startOffset.bottom = rect.bottom - this.startCoords.page.y;\n\n if ('width' in rect) { width = rect.width; }\n else { width = rect.right - rect.left; }\n if ('height' in rect) { height = rect.height; }\n else { height = rect.bottom - rect.top; }\n }\n else {\n this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0;\n }\n\n this.snapOffsets.splice(0);\n\n var snapOffset = snap && snap.offset === 'startCoords'\n ? {\n x: this.startCoords.page.x - origin.x,\n y: this.startCoords.page.y - origin.y\n }\n : snap && snap.offset || { x: 0, y: 0 };\n\n if (rect && snap && snap.relativePoints && snap.relativePoints.length) {\n for (var i = 0; i < snap.relativePoints.length; i++) {\n this.snapOffsets.push({\n x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x,\n y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y\n });\n }\n }\n else {\n this.snapOffsets.push(snapOffset);\n }\n\n if (rect && restrict.elementRect) {\n this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left);\n this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top);\n\n this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right));\n this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom));\n }\n else {\n this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0;\n }\n },\n\n /*\\\n * Interaction.start\n [ method ]\n *\n * Start an action with the given Interactable and Element as tartgets. The\n * action must be enabled for the target Interactable and an appropriate number\n * of pointers must be held down – 1 for drag/resize, 2 for gesture.\n *\n * Use it with `interactable.able({ manualStart: false })` to always\n * [start actions manually](https://github.com/taye/interact.js/issues/114)\n *\n - action (object) The action to be performed - drag, resize, etc.\n - interactable (Interactable) The Interactable to target\n - element (Element) The DOM Element to target\n = (object) interact\n **\n | interact(target)\n | .draggable({\n | // disable the default drag start by down->move\n | manualStart: true\n | })\n | // start dragging after the user holds the pointer down\n | .on('hold', function (event) {\n | var interaction = event.interaction;\n |\n | if (!interaction.interacting()) {\n | interaction.start({ name: 'drag' },\n | event.interactable,\n | event.currentTarget);\n | }\n | });\n \\*/\n start: function (action, interactable, element) {\n if (this.interacting()\n || !this.pointerIsDown\n || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) {\n return;\n }\n\n // if this interaction had been removed after stopping\n // add it back\n if (scope.indexOf(scope.interactions, this) === -1) {\n scope.interactions.push(this);\n }\n\n this.prepared.name = action.name;\n this.prepared.axis = action.axis;\n this.prepared.edges = action.edges;\n this.target = interactable;\n this.element = element;\n\n this.setEventXY(this.startCoords);\n this.setStartOffsets(action.name, interactable, element);\n this.setModifications(this.startCoords.page);\n\n this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent);\n },\n\n pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) {\n this.recordPointer(pointer);\n\n this.setEventXY(this.curCoords, (pointer instanceof InteractEvent)\n ? this.inertiaStatus.startEvent\n : undefined);\n\n var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x\n && this.curCoords.page.y === this.prevCoords.page.y\n && this.curCoords.client.x === this.prevCoords.client.x\n && this.curCoords.client.y === this.prevCoords.client.y);\n\n var dx, dy,\n pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n // register movement greater than pointerMoveTolerance\n if (this.pointerIsDown && !this.pointerWasMoved) {\n dx = this.curCoords.client.x - this.startCoords.client.x;\n dy = this.curCoords.client.y - this.startCoords.client.y;\n\n this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance;\n }\n\n if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) {\n if (this.pointerIsDown) {\n clearTimeout(this.holdTimers[pointerIndex]);\n }\n\n this.collectEventTargets(pointer, event, eventTarget, 'move');\n }\n\n if (!this.pointerIsDown) { return; }\n\n if (duplicateMove && this.pointerWasMoved && !preEnd) {\n this.checkAndPreventDefault(event, this.target, this.element);\n return;\n }\n\n // set pointer coordinate, time changes and speeds\n utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);\n\n if (!this.prepared.name) { return; }\n\n if (this.pointerWasMoved\n // ignore movement while inertia is active\n && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) {\n\n // if just starting an action, calculate the pointer speed now\n if (!this.interacting()) {\n utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);\n\n // check if a drag is in the correct axis\n if (this.prepared.name === 'drag') {\n var absX = Math.abs(dx),\n absY = Math.abs(dy),\n targetAxis = this.target.options.drag.axis,\n axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy');\n\n // if the movement isn't in the axis of the interactable\n if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) {\n // cancel the prepared action\n this.prepared.name = null;\n\n // then try to get a drag from another ineractable\n\n var element = eventTarget;\n\n // check element interactables\n while (utils.isElement(element)) {\n var elementInteractable = scope.interactables.get(element);\n\n if (elementInteractable\n && elementInteractable !== this.target\n && !elementInteractable.options.drag.manualStart\n && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag'\n && scope.checkAxis(axis, elementInteractable)) {\n\n this.prepared.name = 'drag';\n this.target = elementInteractable;\n this.element = element;\n break;\n }\n\n element = scope.parentElement(element);\n }\n\n // if there's no drag from element interactables,\n // check the selector interactables\n if (!this.prepared.name) {\n var thisInteraction = this;\n\n var getDraggable = function (interactable, selector, context) {\n var elements = scope.ie8MatchesSelector\n ? context.querySelectorAll(selector)\n : undefined;\n\n if (interactable === thisInteraction.target) { return; }\n\n if (scope.inContext(interactable, eventTarget)\n && !interactable.options.drag.manualStart\n && !scope.testIgnore(interactable, element, eventTarget)\n && scope.testAllow(interactable, element, eventTarget)\n && scope.matchesSelector(element, selector, elements)\n && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag'\n && scope.checkAxis(axis, interactable)\n && scope.withinInteractionLimit(interactable, element, 'drag')) {\n\n return interactable;\n }\n };\n\n element = eventTarget;\n\n while (utils.isElement(element)) {\n var selectorInteractable = scope.interactables.forEachSelector(getDraggable);\n\n if (selectorInteractable) {\n this.prepared.name = 'drag';\n this.target = selectorInteractable;\n this.element = element;\n break;\n }\n\n element = scope.parentElement(element);\n }\n }\n }\n }\n }\n\n var starting = !!this.prepared.name && !this.interacting();\n\n if (starting\n && (this.target.options[this.prepared.name].manualStart\n || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) {\n this.stop(event);\n return;\n }\n\n if (this.prepared.name && this.target) {\n if (starting) {\n this.start(this.prepared, this.target, this.element);\n }\n\n var shouldMove = this.setModifications(this.curCoords.page, preEnd);\n\n // move if snapping or restriction doesn't prevent it\n if (shouldMove || starting) {\n this.prevEvent = this[this.prepared.name + 'Move'](event);\n }\n\n this.checkAndPreventDefault(event, this.target, this.element);\n }\n }\n\n utils.copyCoords(this.prevCoords, this.curCoords);\n\n if (this.dragging || this.resizing) {\n this.autoScrollMove(pointer);\n }\n },\n\n dragStart: function (event) {\n var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element);\n\n this.dragging = true;\n this.target.fire(dragEvent);\n\n // reset active dropzones\n this.activeDrops.dropzones = [];\n this.activeDrops.elements = [];\n this.activeDrops.rects = [];\n\n if (!this.dynamicDrop) {\n this.setActiveDrops(this.element);\n }\n\n var dropEvents = this.getDropEvents(event, dragEvent);\n\n if (dropEvents.activate) {\n this.fireActiveDrops(dropEvents.activate);\n }\n\n return dragEvent;\n },\n\n dragMove: function (event) {\n var target = this.target,\n dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element),\n draggableElement = this.element,\n drop = this.getDrop(event, draggableElement);\n\n this.dropTarget = drop.dropzone;\n this.dropElement = drop.element;\n\n var dropEvents = this.getDropEvents(event, dragEvent);\n\n target.fire(dragEvent);\n\n if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }\n if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }\n if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); }\n\n this.prevDropTarget = this.dropTarget;\n this.prevDropElement = this.dropElement;\n\n return dragEvent;\n },\n\n resizeStart: function (event) {\n var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element);\n\n if (this.prepared.edges) {\n var startRect = this.target.getRect(this.element);\n\n if (this.target.options.resize.square) {\n var squareEdges = utils.extend({}, this.prepared.edges);\n\n squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom);\n squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right );\n squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top );\n squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left );\n\n this.prepared._squareEdges = squareEdges;\n }\n else {\n this.prepared._squareEdges = null;\n }\n\n this.resizeRects = {\n start : startRect,\n current : utils.extend({}, startRect),\n restricted: utils.extend({}, startRect),\n previous : utils.extend({}, startRect),\n delta : {\n left: 0, right : 0, width : 0,\n top : 0, bottom: 0, height: 0\n }\n };\n\n resizeEvent.rect = this.resizeRects.restricted;\n resizeEvent.deltaRect = this.resizeRects.delta;\n }\n\n this.target.fire(resizeEvent);\n\n this.resizing = true;\n\n return resizeEvent;\n },\n\n resizeMove: function (event) {\n var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element);\n\n var edges = this.prepared.edges,\n invert = this.target.options.resize.invert,\n invertible = invert === 'reposition' || invert === 'negate';\n\n if (edges) {\n var dx = resizeEvent.dx,\n dy = resizeEvent.dy,\n\n start = this.resizeRects.start,\n current = this.resizeRects.current,\n restricted = this.resizeRects.restricted,\n delta = this.resizeRects.delta,\n previous = utils.extend(this.resizeRects.previous, restricted);\n\n if (this.target.options.resize.square) {\n var originalEdges = edges;\n\n edges = this.prepared._squareEdges;\n\n if ((originalEdges.left && originalEdges.bottom)\n || (originalEdges.right && originalEdges.top)) {\n dy = -dx;\n }\n else if (originalEdges.left || originalEdges.right) { dy = dx; }\n else if (originalEdges.top || originalEdges.bottom) { dx = dy; }\n }\n\n // update the 'current' rect without modifications\n if (edges.top ) { current.top += dy; }\n if (edges.bottom) { current.bottom += dy; }\n if (edges.left ) { current.left += dx; }\n if (edges.right ) { current.right += dx; }\n\n if (invertible) {\n // if invertible, copy the current rect\n utils.extend(restricted, current);\n\n if (invert === 'reposition') {\n // swap edge values if necessary to keep width/height positive\n var swap;\n\n if (restricted.top > restricted.bottom) {\n swap = restricted.top;\n\n restricted.top = restricted.bottom;\n restricted.bottom = swap;\n }\n if (restricted.left > restricted.right) {\n swap = restricted.left;\n\n restricted.left = restricted.right;\n restricted.right = swap;\n }\n }\n }\n else {\n // if not invertible, restrict to minimum of 0x0 rect\n restricted.top = Math.min(current.top, start.bottom);\n restricted.bottom = Math.max(current.bottom, start.top);\n restricted.left = Math.min(current.left, start.right);\n restricted.right = Math.max(current.right, start.left);\n }\n\n restricted.width = restricted.right - restricted.left;\n restricted.height = restricted.bottom - restricted.top ;\n\n for (var edge in restricted) {\n delta[edge] = restricted[edge] - previous[edge];\n }\n\n resizeEvent.edges = this.prepared.edges;\n resizeEvent.rect = restricted;\n resizeEvent.deltaRect = delta;\n }\n\n this.target.fire(resizeEvent);\n\n return resizeEvent;\n },\n\n gestureStart: function (event) {\n var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element);\n\n gestureEvent.ds = 0;\n\n this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance;\n this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle;\n this.gesture.scale = 1;\n\n this.gesturing = true;\n\n this.target.fire(gestureEvent);\n\n return gestureEvent;\n },\n\n gestureMove: function (event) {\n if (!this.pointerIds.length) {\n return this.prevEvent;\n }\n\n var gestureEvent;\n\n gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element);\n gestureEvent.ds = gestureEvent.scale - this.gesture.scale;\n\n this.target.fire(gestureEvent);\n\n this.gesture.prevAngle = gestureEvent.angle;\n this.gesture.prevDistance = gestureEvent.distance;\n\n if (gestureEvent.scale !== Infinity &&\n gestureEvent.scale !== null &&\n gestureEvent.scale !== undefined &&\n !isNaN(gestureEvent.scale)) {\n\n this.gesture.scale = gestureEvent.scale;\n }\n\n return gestureEvent;\n },\n\n pointerHold: function (pointer, event, eventTarget) {\n this.collectEventTargets(pointer, event, eventTarget, 'hold');\n },\n\n pointerUp: function (pointer, event, eventTarget, curEventTarget) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n clearTimeout(this.holdTimers[pointerIndex]);\n\n this.collectEventTargets(pointer, event, eventTarget, 'up' );\n this.collectEventTargets(pointer, event, eventTarget, 'tap');\n\n this.pointerEnd(pointer, event, eventTarget, curEventTarget);\n\n this.removePointer(pointer);\n },\n\n pointerCancel: function (pointer, event, eventTarget, curEventTarget) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n clearTimeout(this.holdTimers[pointerIndex]);\n\n this.collectEventTargets(pointer, event, eventTarget, 'cancel');\n this.pointerEnd(pointer, event, eventTarget, curEventTarget);\n\n this.removePointer(pointer);\n },\n\n // http://www.quirksmode.org/dom/events/click.html\n // >Events leading to dblclick\n //\n // IE8 doesn't fire down event before dblclick.\n // This workaround tries to fire a tap and doubletap after dblclick\n ie8Dblclick: function (pointer, event, eventTarget) {\n if (this.prevTap\n && event.clientX === this.prevTap.clientX\n && event.clientY === this.prevTap.clientY\n && eventTarget === this.prevTap.target) {\n\n this.downTargets[0] = eventTarget;\n this.downTimes[0] = new Date().getTime();\n this.collectEventTargets(pointer, event, eventTarget, 'tap');\n }\n },\n\n // End interact move events and stop auto-scroll unless inertia is enabled\n pointerEnd: function (pointer, event, eventTarget, curEventTarget) {\n var endEvent,\n target = this.target,\n options = target && target.options,\n inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia,\n inertiaStatus = this.inertiaStatus;\n\n if (this.interacting()) {\n\n if (inertiaStatus.active) { return; }\n\n var pointerSpeed,\n now = new Date().getTime(),\n inertiaPossible = false,\n inertia = false,\n smoothEnd = false,\n endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly,\n endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly,\n dx = 0,\n dy = 0,\n startEvent;\n\n if (this.dragging) {\n if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); }\n else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); }\n else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; }\n }\n else {\n pointerSpeed = this.pointerDelta.client.speed;\n }\n\n // check if inertia should be started\n inertiaPossible = (inertiaOptions && inertiaOptions.enabled\n && this.prepared.name !== 'gesture'\n && event !== inertiaStatus.startEvent);\n\n inertia = (inertiaPossible\n && (now - this.curCoords.timeStamp) < 50\n && pointerSpeed > inertiaOptions.minSpeed\n && pointerSpeed > inertiaOptions.endSpeed);\n\n if (inertiaPossible && !inertia && (endSnap || endRestrict)) {\n\n var snapRestrict = {};\n\n snapRestrict.snap = snapRestrict.restrict = snapRestrict;\n\n if (endSnap) {\n this.setSnapping(this.curCoords.page, snapRestrict);\n if (snapRestrict.locked) {\n dx += snapRestrict.dx;\n dy += snapRestrict.dy;\n }\n }\n\n if (endRestrict) {\n this.setRestriction(this.curCoords.page, snapRestrict);\n if (snapRestrict.restricted) {\n dx += snapRestrict.dx;\n dy += snapRestrict.dy;\n }\n }\n\n if (dx || dy) {\n smoothEnd = true;\n }\n }\n\n if (inertia || smoothEnd) {\n utils.copyCoords(inertiaStatus.upCoords, this.curCoords);\n\n this.pointers[0] = inertiaStatus.startEvent = startEvent =\n new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element);\n\n inertiaStatus.t0 = now;\n\n target.fire(inertiaStatus.startEvent);\n\n if (inertia) {\n inertiaStatus.vx0 = this.pointerDelta.client.vx;\n inertiaStatus.vy0 = this.pointerDelta.client.vy;\n inertiaStatus.v0 = pointerSpeed;\n\n this.calcInertia(inertiaStatus);\n\n var page = utils.extend({}, this.curCoords.page),\n origin = scope.getOriginXY(target, this.element),\n statusObject;\n\n page.x = page.x + inertiaStatus.xe - origin.x;\n page.y = page.y + inertiaStatus.ye - origin.y;\n\n statusObject = {\n useStatusXY: true,\n x: page.x,\n y: page.y,\n dx: 0,\n dy: 0,\n snap: null\n };\n\n statusObject.snap = statusObject;\n\n dx = dy = 0;\n\n if (endSnap) {\n var snap = this.setSnapping(this.curCoords.page, statusObject);\n\n if (snap.locked) {\n dx += snap.dx;\n dy += snap.dy;\n }\n }\n\n if (endRestrict) {\n var restrict = this.setRestriction(this.curCoords.page, statusObject);\n\n if (restrict.restricted) {\n dx += restrict.dx;\n dy += restrict.dy;\n }\n }\n\n inertiaStatus.modifiedXe += dx;\n inertiaStatus.modifiedYe += dy;\n\n inertiaStatus.i = animationFrame.request(this.boundInertiaFrame);\n }\n else {\n inertiaStatus.smoothEnd = true;\n inertiaStatus.xe = dx;\n inertiaStatus.ye = dy;\n\n inertiaStatus.sx = inertiaStatus.sy = 0;\n\n inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame);\n }\n\n inertiaStatus.active = true;\n return;\n }\n\n if (endSnap || endRestrict) {\n // fire a move event at the snapped coordinates\n this.pointerMove(pointer, event, eventTarget, curEventTarget, true);\n }\n }\n\n if (this.dragging) {\n endEvent = new InteractEvent(this, event, 'drag', 'end', this.element);\n\n var draggableElement = this.element,\n drop = this.getDrop(event, draggableElement);\n\n this.dropTarget = drop.dropzone;\n this.dropElement = drop.element;\n\n var dropEvents = this.getDropEvents(event, endEvent);\n\n if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }\n if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }\n if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); }\n if (dropEvents.deactivate) {\n this.fireActiveDrops(dropEvents.deactivate);\n }\n\n target.fire(endEvent);\n }\n else if (this.resizing) {\n endEvent = new InteractEvent(this, event, 'resize', 'end', this.element);\n target.fire(endEvent);\n }\n else if (this.gesturing) {\n endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element);\n target.fire(endEvent);\n }\n\n this.stop(event);\n },\n\n collectDrops: function (element) {\n var drops = [],\n elements = [],\n i;\n\n element = element || this.element;\n\n // collect all dropzones and their elements which qualify for a drop\n for (i = 0; i < scope.interactables.length; i++) {\n if (!scope.interactables[i].options.drop.enabled) { continue; }\n\n var current = scope.interactables[i],\n accept = current.options.drop.accept;\n\n // test the draggable element against the dropzone's accept setting\n if ((utils.isElement(accept) && accept !== element)\n || (scope.isString(accept)\n && !scope.matchesSelector(element, accept))) {\n\n continue;\n }\n\n // query for new elements if necessary\n var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element];\n\n for (var j = 0, len = dropElements.length; j < len; j++) {\n var currentElement = dropElements[j];\n\n if (currentElement === element) {\n continue;\n }\n\n drops.push(current);\n elements.push(currentElement);\n }\n }\n\n return {\n dropzones: drops,\n elements: elements\n };\n },\n\n fireActiveDrops: function (event) {\n var i,\n current,\n currentElement,\n prevElement;\n\n // loop through all active dropzones and trigger event\n for (i = 0; i < this.activeDrops.dropzones.length; i++) {\n current = this.activeDrops.dropzones[i];\n currentElement = this.activeDrops.elements [i];\n\n // prevent trigger of duplicate events on same element\n if (currentElement !== prevElement) {\n // set current element as event target\n event.target = currentElement;\n current.fire(event);\n }\n prevElement = currentElement;\n }\n },\n\n // Collect a new set of possible drops and save them in activeDrops.\n // setActiveDrops should always be called when a drag has just started or a\n // drag event happens while dynamicDrop is true\n setActiveDrops: function (dragElement) {\n // get dropzones and their elements that could receive the draggable\n var possibleDrops = this.collectDrops(dragElement, true);\n\n this.activeDrops.dropzones = possibleDrops.dropzones;\n this.activeDrops.elements = possibleDrops.elements;\n this.activeDrops.rects = [];\n\n for (var i = 0; i < this.activeDrops.dropzones.length; i++) {\n this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]);\n }\n },\n\n getDrop: function (event, dragElement) {\n var validDrops = [];\n\n if (scope.dynamicDrop) {\n this.setActiveDrops(dragElement);\n }\n\n // collect all dropzones and their elements which qualify for a drop\n for (var j = 0; j < this.activeDrops.dropzones.length; j++) {\n var current = this.activeDrops.dropzones[j],\n currentElement = this.activeDrops.elements [j],\n rect = this.activeDrops.rects [j];\n\n validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect)\n ? currentElement\n : null);\n }\n\n // get the most appropriate dropzone based on DOM depth and order\n var dropIndex = scope.indexOfDeepestElement(validDrops),\n dropzone = this.activeDrops.dropzones[dropIndex] || null,\n element = this.activeDrops.elements [dropIndex] || null;\n\n return {\n dropzone: dropzone,\n element: element\n };\n },\n\n getDropEvents: function (pointerEvent, dragEvent) {\n var dropEvents = {\n enter : null,\n leave : null,\n activate : null,\n deactivate: null,\n move : null,\n drop : null\n };\n\n if (this.dropElement !== this.prevDropElement) {\n // if there was a prevDropTarget, create a dragleave event\n if (this.prevDropTarget) {\n dropEvents.leave = {\n target : this.prevDropElement,\n dropzone : this.prevDropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dragleave'\n };\n\n dragEvent.dragLeave = this.prevDropElement;\n dragEvent.prevDropzone = this.prevDropTarget;\n }\n // if the dropTarget is not null, create a dragenter event\n if (this.dropTarget) {\n dropEvents.enter = {\n target : this.dropElement,\n dropzone : this.dropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dragenter'\n };\n\n dragEvent.dragEnter = this.dropElement;\n dragEvent.dropzone = this.dropTarget;\n }\n }\n\n if (dragEvent.type === 'dragend' && this.dropTarget) {\n dropEvents.drop = {\n target : this.dropElement,\n dropzone : this.dropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'drop'\n };\n\n dragEvent.dropzone = this.dropTarget;\n }\n if (dragEvent.type === 'dragstart') {\n dropEvents.activate = {\n target : null,\n dropzone : null,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dropactivate'\n };\n }\n if (dragEvent.type === 'dragend') {\n dropEvents.deactivate = {\n target : null,\n dropzone : null,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dropdeactivate'\n };\n }\n if (dragEvent.type === 'dragmove' && this.dropTarget) {\n dropEvents.move = {\n target : this.dropElement,\n dropzone : this.dropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n dragmove : dragEvent,\n timeStamp : dragEvent.timeStamp,\n type : 'dropmove'\n };\n dragEvent.dropzone = this.dropTarget;\n }\n\n return dropEvents;\n },\n\n currentAction: function () {\n return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null;\n },\n\n interacting: function () {\n return this.dragging || this.resizing || this.gesturing;\n },\n\n clearTargets: function () {\n this.target = this.element = null;\n\n this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null;\n },\n\n stop: function (event) {\n if (this.interacting()) {\n scope.autoScroll.stop();\n this.matches = [];\n this.matchElements = [];\n\n var target = this.target;\n\n if (target.options.styleCursor) {\n target._doc.documentElement.style.cursor = '';\n }\n\n // prevent Default only if were previously interacting\n if (event && scope.isFunction(event.preventDefault)) {\n this.checkAndPreventDefault(event, target, this.element);\n }\n\n if (this.dragging) {\n this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null;\n }\n }\n\n this.clearTargets();\n\n this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false;\n this.prepared.name = this.prevEvent = null;\n this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0;\n\n // remove pointers if their ID isn't in this.pointerIds\n for (var i = 0; i < this.pointers.length; i++) {\n if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) {\n this.pointers.splice(i, 1);\n }\n }\n\n for (i = 0; i < scope.interactions.length; i++) {\n // remove this interaction if it's not the only one of it's type\n if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) {\n scope.interactions.splice(scope.indexOf(scope.interactions, this), 1);\n }\n }\n },\n\n inertiaFrame: function () {\n var inertiaStatus = this.inertiaStatus,\n options = this.target.options[this.prepared.name].inertia,\n lambda = options.resistance,\n t = new Date().getTime() / 1000 - inertiaStatus.t0;\n\n if (t < inertiaStatus.te) {\n\n var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0;\n\n if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) {\n inertiaStatus.sx = inertiaStatus.xe * progress;\n inertiaStatus.sy = inertiaStatus.ye * progress;\n }\n else {\n var quadPoint = scope.getQuadraticCurvePoint(\n 0, 0,\n inertiaStatus.xe, inertiaStatus.ye,\n inertiaStatus.modifiedXe, inertiaStatus.modifiedYe,\n progress);\n\n inertiaStatus.sx = quadPoint.x;\n inertiaStatus.sy = quadPoint.y;\n }\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.i = animationFrame.request(this.boundInertiaFrame);\n }\n else {\n inertiaStatus.sx = inertiaStatus.modifiedXe;\n inertiaStatus.sy = inertiaStatus.modifiedYe;\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.active = false;\n this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);\n }\n },\n\n smoothEndFrame: function () {\n var inertiaStatus = this.inertiaStatus,\n t = new Date().getTime() - inertiaStatus.t0,\n duration = this.target.options[this.prepared.name].inertia.smoothEndDuration;\n\n if (t < duration) {\n inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration);\n inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration);\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame);\n }\n else {\n inertiaStatus.sx = inertiaStatus.xe;\n inertiaStatus.sy = inertiaStatus.ye;\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.active = false;\n inertiaStatus.smoothEnd = false;\n\n this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);\n }\n },\n\n addPointer: function (pointer) {\n var id = utils.getPointerId(pointer),\n index = this.mouse? 0 : scope.indexOf(this.pointerIds, id);\n\n if (index === -1) {\n index = this.pointerIds.length;\n }\n\n this.pointerIds[index] = id;\n this.pointers[index] = pointer;\n\n return index;\n },\n\n removePointer: function (pointer) {\n var id = utils.getPointerId(pointer),\n index = this.mouse? 0 : scope.indexOf(this.pointerIds, id);\n\n if (index === -1) { return; }\n\n if (!this.interacting()) {\n this.pointers.splice(index, 1);\n }\n\n this.pointerIds .splice(index, 1);\n this.downTargets.splice(index, 1);\n this.downTimes .splice(index, 1);\n this.holdTimers .splice(index, 1);\n },\n\n recordPointer: function (pointer) {\n // Do not update pointers while inertia is active.\n // The inertia start event should be this.pointers[0]\n if (this.inertiaStatus.active) { return; }\n\n var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n if (index === -1) { return; }\n\n this.pointers[index] = pointer;\n },\n\n collectEventTargets: function (pointer, event, eventTarget, eventType) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n // do not fire a tap event if the pointer was moved before being lifted\n if (eventType === 'tap' && (this.pointerWasMoved\n // or if the pointerup target is different to the pointerdown target\n || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) {\n return;\n }\n\n var targets = [],\n elements = [],\n element = eventTarget;\n\n function collectSelectors (interactable, selector, context) {\n var els = scope.ie8MatchesSelector\n ? context.querySelectorAll(selector)\n : undefined;\n\n if (interactable._iEvents[eventType]\n && utils.isElement(element)\n && scope.inContext(interactable, element)\n && !scope.testIgnore(interactable, element, eventTarget)\n && scope.testAllow(interactable, element, eventTarget)\n && scope.matchesSelector(element, selector, els)) {\n\n targets.push(interactable);\n elements.push(element);\n }\n }\n\n\n var interact = scope.interact;\n\n while (element) {\n if (interact.isSet(element) && interact(element)._iEvents[eventType]) {\n targets.push(interact(element));\n elements.push(element);\n }\n\n scope.interactables.forEachSelector(collectSelectors);\n\n element = scope.parentElement(element);\n }\n\n // create the tap event even if there are no listeners so that\n // doubletap can still be created and fired\n if (targets.length || eventType === 'tap') {\n this.firePointers(pointer, event, eventTarget, targets, elements, eventType);\n }\n },\n\n firePointers: function (pointer, event, eventTarget, targets, elements, eventType) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)),\n pointerEvent = {},\n i,\n // for tap events\n interval, createNewDoubleTap;\n\n // if it's a doubletap then the event properties would have been\n // copied from the tap event and provided as the pointer argument\n if (eventType === 'doubletap') {\n pointerEvent = pointer;\n }\n else {\n utils.extend(pointerEvent, event);\n if (event !== pointer) {\n utils.extend(pointerEvent, pointer);\n }\n\n pointerEvent.preventDefault = preventOriginalDefault;\n pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation;\n pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation;\n pointerEvent.interaction = this;\n\n pointerEvent.timeStamp = new Date().getTime();\n pointerEvent.originalEvent = event;\n pointerEvent.type = eventType;\n pointerEvent.pointerId = utils.getPointerId(pointer);\n pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch'\n : scope.isString(pointer.pointerType)\n ? pointer.pointerType\n : [,,'touch', 'pen', 'mouse'][pointer.pointerType];\n }\n\n if (eventType === 'tap') {\n pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex];\n\n interval = pointerEvent.timeStamp - this.tapTime;\n createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap'\n && this.prevTap.target === pointerEvent.target\n && interval < 500);\n\n pointerEvent.double = createNewDoubleTap;\n\n this.tapTime = pointerEvent.timeStamp;\n }\n\n for (i = 0; i < targets.length; i++) {\n pointerEvent.currentTarget = elements[i];\n pointerEvent.interactable = targets[i];\n targets[i].fire(pointerEvent);\n\n if (pointerEvent.immediatePropagationStopped\n ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) {\n break;\n }\n }\n\n if (createNewDoubleTap) {\n var doubleTap = {};\n\n utils.extend(doubleTap, pointerEvent);\n\n doubleTap.dt = interval;\n doubleTap.type = 'doubletap';\n\n this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap');\n\n this.prevTap = doubleTap;\n }\n else if (eventType === 'tap') {\n this.prevTap = pointerEvent;\n }\n },\n\n validateSelector: function (pointer, event, matches, matchElements) {\n for (var i = 0, len = matches.length; i < len; i++) {\n var match = matches[i],\n matchElement = matchElements[i],\n action = validateAction(match.getAction(pointer, event, this, matchElement), match);\n\n if (action && scope.withinInteractionLimit(match, matchElement, action)) {\n this.target = match;\n this.element = matchElement;\n\n return action;\n }\n }\n },\n\n setSnapping: function (pageCoords, status) {\n var snap = this.target.options[this.prepared.name].snap,\n targets = [],\n target,\n page,\n i;\n\n status = status || this.snapStatus;\n\n if (status.useStatusXY) {\n page = { x: status.x, y: status.y };\n }\n else {\n var origin = scope.getOriginXY(this.target, this.element);\n\n page = utils.extend({}, pageCoords);\n\n page.x -= origin.x;\n page.y -= origin.y;\n }\n\n status.realX = page.x;\n status.realY = page.y;\n\n page.x = page.x - this.inertiaStatus.resumeDx;\n page.y = page.y - this.inertiaStatus.resumeDy;\n\n var len = snap.targets? snap.targets.length : 0;\n\n for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) {\n var relative = {\n x: page.x - this.snapOffsets[relIndex].x,\n y: page.y - this.snapOffsets[relIndex].y\n };\n\n for (i = 0; i < len; i++) {\n if (scope.isFunction(snap.targets[i])) {\n target = snap.targets[i](relative.x, relative.y, this);\n }\n else {\n target = snap.targets[i];\n }\n\n if (!target) { continue; }\n\n targets.push({\n x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x,\n y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y,\n\n range: scope.isNumber(target.range)? target.range: snap.range\n });\n }\n }\n\n var closest = {\n target: null,\n inRange: false,\n distance: 0,\n range: 0,\n dx: 0,\n dy: 0\n };\n\n for (i = 0, len = targets.length; i < len; i++) {\n target = targets[i];\n\n var range = target.range,\n dx = target.x - page.x,\n dy = target.y - page.y,\n distance = utils.hypot(dx, dy),\n inRange = distance <= range;\n\n // Infinite targets count as being out of range\n // compared to non infinite ones that are in range\n if (range === Infinity && closest.inRange && closest.range !== Infinity) {\n inRange = false;\n }\n\n if (!closest.target || (inRange\n // is the closest target in range?\n ? (closest.inRange && range !== Infinity\n // the pointer is relatively deeper in this target\n ? distance / range < closest.distance / closest.range\n // this target has Infinite range and the closest doesn't\n : (range === Infinity && closest.range !== Infinity)\n // OR this target is closer that the previous closest\n || distance < closest.distance)\n // The other is not in range and the pointer is closer to this target\n : (!closest.inRange && distance < closest.distance))) {\n\n if (range === Infinity) {\n inRange = true;\n }\n\n closest.target = target;\n closest.distance = distance;\n closest.range = range;\n closest.inRange = inRange;\n closest.dx = dx;\n closest.dy = dy;\n\n status.range = range;\n }\n }\n\n var snapChanged;\n\n if (closest.target) {\n snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y);\n\n status.snappedX = closest.target.x;\n status.snappedY = closest.target.y;\n }\n else {\n snapChanged = true;\n\n status.snappedX = NaN;\n status.snappedY = NaN;\n }\n\n status.dx = closest.dx;\n status.dy = closest.dy;\n\n status.changed = (snapChanged || (closest.inRange && !status.locked));\n status.locked = closest.inRange;\n\n return status;\n },\n\n setRestriction: function (pageCoords, status) {\n var target = this.target,\n restrict = target && target.options[this.prepared.name].restrict,\n restriction = restrict && restrict.restriction,\n page;\n\n if (!restriction) {\n return status;\n }\n\n status = status || this.restrictStatus;\n\n page = status.useStatusXY\n ? page = { x: status.x, y: status.y }\n : page = utils.extend({}, pageCoords);\n\n if (status.snap && status.snap.locked) {\n page.x += status.snap.dx || 0;\n page.y += status.snap.dy || 0;\n }\n\n page.x -= this.inertiaStatus.resumeDx;\n page.y -= this.inertiaStatus.resumeDy;\n\n status.dx = 0;\n status.dy = 0;\n status.restricted = false;\n\n var rect, restrictedX, restrictedY;\n\n if (scope.isString(restriction)) {\n if (restriction === 'parent') {\n restriction = scope.parentElement(this.element);\n }\n else if (restriction === 'self') {\n restriction = target.getRect(this.element);\n }\n else {\n restriction = scope.closest(this.element, restriction);\n }\n\n if (!restriction) { return status; }\n }\n\n if (scope.isFunction(restriction)) {\n restriction = restriction(page.x, page.y, this.element);\n }\n\n if (utils.isElement(restriction)) {\n restriction = scope.getElementRect(restriction);\n }\n\n rect = restriction;\n\n if (!restriction) {\n restrictedX = page.x;\n restrictedY = page.y;\n }\n // object is assumed to have\n // x, y, width, height or\n // left, top, right, bottom\n else if ('x' in restriction && 'y' in restriction) {\n restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left);\n restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top );\n }\n else {\n restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left);\n restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top );\n }\n\n status.dx = restrictedX - page.x;\n status.dy = restrictedY - page.y;\n\n status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY;\n status.restricted = !!(status.dx || status.dy);\n\n status.restrictedX = restrictedX;\n status.restrictedY = restrictedY;\n\n return status;\n },\n\n checkAndPreventDefault: function (event, interactable, element) {\n if (!(interactable = interactable || this.target)) { return; }\n\n var options = interactable.options,\n prevent = options.preventDefault;\n\n if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) {\n // do not preventDefault on pointerdown if the prepared action is a drag\n // and dragging can only start from a certain direction - this allows\n // a touch to pan the viewport if a drag isn't in the right direction\n if (/down|start/i.test(event.type)\n && this.prepared.name === 'drag' && options.drag.axis !== 'xy') {\n\n return;\n }\n\n // with manualStart, only preventDefault while interacting\n if (options[this.prepared.name] && options[this.prepared.name].manualStart\n && !this.interacting()) {\n return;\n }\n\n event.preventDefault();\n return;\n }\n\n if (prevent === 'always') {\n event.preventDefault();\n return;\n }\n },\n\n calcInertia: function (status) {\n var inertiaOptions = this.target.options[this.prepared.name].inertia,\n lambda = inertiaOptions.resistance,\n inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda;\n\n status.x0 = this.prevEvent.pageX;\n status.y0 = this.prevEvent.pageY;\n status.t0 = status.startEvent.timeStamp / 1000;\n status.sx = status.sy = 0;\n\n status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;\n status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;\n status.te = inertiaDur;\n\n status.lambda_v0 = lambda / status.v0;\n status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0;\n },\n\n autoScrollMove: function (pointer) {\n if (!(this.interacting()\n && scope.checkAutoScroll(this.target, this.prepared.name))) {\n return;\n }\n\n if (this.inertiaStatus.active) {\n scope.autoScroll.x = scope.autoScroll.y = 0;\n return;\n }\n\n var top,\n right,\n bottom,\n left,\n options = this.target.options[this.prepared.name].autoScroll,\n container = options.container || scope.getWindow(this.element);\n\n if (scope.isWindow(container)) {\n left = pointer.clientX < scope.autoScroll.margin;\n top = pointer.clientY < scope.autoScroll.margin;\n right = pointer.clientX > container.innerWidth - scope.autoScroll.margin;\n bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin;\n }\n else {\n var rect = scope.getElementRect(container);\n\n left = pointer.clientX < rect.left + scope.autoScroll.margin;\n top = pointer.clientY < rect.top + scope.autoScroll.margin;\n right = pointer.clientX > rect.right - scope.autoScroll.margin;\n bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin;\n }\n\n scope.autoScroll.x = (right ? 1: left? -1: 0);\n scope.autoScroll.y = (bottom? 1: top? -1: 0);\n\n if (!scope.autoScroll.isScrolling) {\n // set the autoScroll properties to those of the target\n scope.autoScroll.margin = options.margin;\n scope.autoScroll.speed = options.speed;\n\n scope.autoScroll.start(this);\n }\n },\n\n _updateEventTargets: function (target, currentTarget) {\n this._eventTarget = target;\n this._curEventTarget = currentTarget;\n }\n\n};\n\nmodule.exports = Interaction;\n\n},{\"./InteractEvent\":2,\"./scope\":6,\"./utils\":13,\"./utils/browser\":8,\"./utils/events\":10}],4:[function(require,module,exports){\n'use strict';\n\nvar raf = require('./utils/raf'),\n getWindow = require('./utils/window').getWindow,\n isWindow = require('./utils/isType').isWindow;\n\nvar autoScroll = {\n\n interaction: null,\n i: null, // the handle returned by window.setInterval\n x: 0, y: 0, // Direction each pulse is to scroll in\n\n isScrolling: false,\n prevTime: 0,\n\n start: function (interaction) {\n autoScroll.isScrolling = true;\n raf.cancel(autoScroll.i);\n\n autoScroll.interaction = interaction;\n autoScroll.prevTime = new Date().getTime();\n autoScroll.i = raf.request(autoScroll.scroll);\n },\n\n stop: function () {\n autoScroll.isScrolling = false;\n raf.cancel(autoScroll.i);\n },\n\n // scroll the window by the values in scroll.x/y\n scroll: function () {\n var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll,\n container = options.container || getWindow(autoScroll.interaction.element),\n now = new Date().getTime(),\n // change in time in seconds\n dt = (now - autoScroll.prevTime) / 1000,\n // displacement\n s = options.speed * dt;\n\n if (s >= 1) {\n if (isWindow(container)) {\n container.scrollBy(autoScroll.x * s, autoScroll.y * s);\n }\n else if (container) {\n container.scrollLeft += autoScroll.x * s;\n container.scrollTop += autoScroll.y * s;\n }\n\n autoScroll.prevTime = now;\n }\n\n if (autoScroll.isScrolling) {\n raf.cancel(autoScroll.i);\n autoScroll.i = raf.request(autoScroll.scroll);\n }\n }\n};\n\nmodule.exports = autoScroll;\n\n},{\"./utils/isType\":14,\"./utils/raf\":17,\"./utils/window\":18}],5:[function(require,module,exports){\n'use strict';\n\nmodule.exports = {\n base: {\n accept : null,\n actionChecker : null,\n styleCursor : true,\n preventDefault: 'auto',\n origin : { x: 0, y: 0 },\n deltaSource : 'page',\n allowFrom : null,\n ignoreFrom : null,\n _context : require('./utils/domObjects').document,\n dropChecker : null\n },\n\n drag: {\n enabled: false,\n manualStart: true,\n max: Infinity,\n maxPerElement: 1,\n\n snap: null,\n restrict: null,\n inertia: null,\n autoScroll: null,\n\n axis: 'xy'\n },\n\n drop: {\n enabled: false,\n accept: null,\n overlap: 'pointer'\n },\n\n resize: {\n enabled: false,\n manualStart: false,\n max: Infinity,\n maxPerElement: 1,\n\n snap: null,\n restrict: null,\n inertia: null,\n autoScroll: null,\n\n square: false,\n axis: 'xy',\n\n // use default margin\n margin: NaN,\n\n // object with props left, right, top, bottom which are\n // true/false values to resize when the pointer is over that edge,\n // CSS selectors to match the handles for each direction\n // or the Elements for each handle\n edges: null,\n\n // a value of 'none' will limit the resize rect to a minimum of 0x0\n // 'negate' will alow the rect to have negative width/height\n // 'reposition' will keep the width/height positive by swapping\n // the top and bottom edges and/or swapping the left and right edges\n invert: 'none'\n },\n\n gesture: {\n manualStart: false,\n enabled: false,\n max: Infinity,\n maxPerElement: 1,\n\n restrict: null\n },\n\n perAction: {\n manualStart: false,\n max: Infinity,\n maxPerElement: 1,\n\n snap: {\n enabled : false,\n endOnly : false,\n range : Infinity,\n targets : null,\n offsets : null,\n\n relativePoints: null\n },\n\n restrict: {\n enabled: false,\n endOnly: false\n },\n\n autoScroll: {\n enabled : false,\n container : null, // the item that is scrolled (Window or HTMLElement)\n margin : 60,\n speed : 300 // the scroll speed in pixels per second\n },\n\n inertia: {\n enabled : false,\n resistance : 10, // the lambda in exponential decay\n minSpeed : 100, // target speed must be above this for inertia to start\n endSpeed : 10, // the speed at which inertia is slow enough to stop\n allowResume : true, // allow resuming an action in inertia phase\n zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0\n smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia\n }\n },\n\n _holdDuration: 600\n};\n\n},{\"./utils/domObjects\":9}],6:[function(require,module,exports){\n'use strict';\n\nvar scope = {},\n extend = require('./utils/extend');\n\nextend(scope, require('./utils/window'));\nextend(scope, require('./utils/domObjects'));\nextend(scope, require('./utils/arr.js'));\nextend(scope, require('./utils/isType'));\n\nmodule.exports = scope;\n\n},{\"./utils/arr.js\":7,\"./utils/domObjects\":9,\"./utils/extend\":11,\"./utils/isType\":14,\"./utils/window\":18}],7:[function(require,module,exports){\n'use strict';\n\nfunction indexOf (array, target) {\n for (var i = 0, len = array.length; i < len; i++) {\n if (array[i] === target) {\n return i;\n }\n }\n\n return -1;\n}\n\nfunction contains (array, target) {\n return indexOf(array, target) !== -1;\n}\n\nmodule.exports = {\n indexOf: indexOf,\n contains: contains\n};\n\n},{}],8:[function(require,module,exports){\n'use strict';\n\nvar win = require('./window'),\n domObjects = require('./domObjects');\n\nvar browser = {\n // Does the browser support touch input?\n supportsTouch : !!(('ontouchstart' in win) || win.window.DocumentTouch\n && domObjects.document instanceof win.DocumentTouch),\n\n // Does the browser support PointerEvents\n supportsPointerEvent : !!domObjects.PointerEvent,\n\n // Opera Mobile must be handled differently\n isOperaMobile : (navigator.appName === 'Opera'\n && browser.supportsTouch\n && navigator.userAgent.match('Presto')),\n\n // scrolling doesn't change the result of\n // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8\n isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\\d]/.test(navigator.appVersion)),\n\n isIe9OrOlder : domObjects.document.all && !win.window.atob,\n\n // prefix matchesSelector\n prefixedMatchesSelector: 'matches' in Element.prototype?\n 'matches': 'webkitMatchesSelector' in Element.prototype?\n 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?\n 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?\n 'oMatchesSelector': 'msMatchesSelector'\n\n};\n\nmodule.exports = browser;\n\n},{\"./domObjects\":9,\"./window\":18}],9:[function(require,module,exports){\n'use strict';\n\nvar domObjects = {},\n win = require('./window').window,\n blank = function () {};\n\ndomObjects.document = win.document;\ndomObjects.DocumentFragment = win.DocumentFragment || blank;\ndomObjects.SVGElement = win.SVGElement || blank;\ndomObjects.SVGSVGElement = win.SVGSVGElement || blank;\ndomObjects.SVGElementInstance = win.SVGElementInstance || blank;\ndomObjects.HTMLElement = win.HTMLElement || win.Element;\n\ndomObjects.PointerEvent = (win.PointerEvent || win.MSPointerEvent);\n\nmodule.exports = domObjects;\n\n},{\"./window\":18}],10:[function(require,module,exports){\n'use strict';\n\nvar arr = require('./arr'),\n indexOf = arr.indexOf,\n contains = arr.contains,\n getWindow = require('./window').getWindow,\n\n useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window),\n addEvent = useAttachEvent? 'attachEvent': 'addEventListener',\n removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener',\n on = useAttachEvent? 'on': '',\n\n elements = [],\n targets = [],\n attachedListeners = [];\n\nfunction add (element, type, listener, useCapture) {\n var elementIndex = indexOf(elements, element),\n target = targets[elementIndex];\n\n if (!target) {\n target = {\n events: {},\n typeCount: 0\n };\n\n elementIndex = elements.push(element) - 1;\n targets.push(target);\n\n attachedListeners.push((useAttachEvent ? {\n supplied: [],\n wrapped : [],\n useCount: []\n } : null));\n }\n\n if (!target.events[type]) {\n target.events[type] = [];\n target.typeCount++;\n }\n\n if (!contains(target.events[type], listener)) {\n var ret;\n\n if (useAttachEvent) {\n var listeners = attachedListeners[elementIndex],\n listenerIndex = indexOf(listeners.supplied, listener);\n\n var wrapped = listeners.wrapped[listenerIndex] || function (event) {\n if (!event.immediatePropagationStopped) {\n event.target = event.srcElement;\n event.currentTarget = element;\n\n event.preventDefault = event.preventDefault || preventDef;\n event.stopPropagation = event.stopPropagation || stopProp;\n event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp;\n\n if (/mouse|click/.test(event.type)) {\n event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft;\n event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop;\n }\n\n listener(event);\n }\n };\n\n ret = element[addEvent](on + type, wrapped, !!useCapture);\n\n if (listenerIndex === -1) {\n listeners.supplied.push(listener);\n listeners.wrapped.push(wrapped);\n listeners.useCount.push(1);\n }\n else {\n listeners.useCount[listenerIndex]++;\n }\n }\n else {\n ret = element[addEvent](type, listener, !!useCapture);\n }\n target.events[type].push(listener);\n\n return ret;\n }\n}\n\nfunction remove (element, type, listener, useCapture) {\n var i,\n elementIndex = indexOf(elements, element),\n target = targets[elementIndex],\n listeners,\n listenerIndex,\n wrapped = listener;\n\n if (!target || !target.events) {\n return;\n }\n\n if (useAttachEvent) {\n listeners = attachedListeners[elementIndex];\n listenerIndex = indexOf(listeners.supplied, listener);\n wrapped = listeners.wrapped[listenerIndex];\n }\n\n if (type === 'all') {\n for (type in target.events) {\n if (target.events.hasOwnProperty(type)) {\n remove(element, type, 'all');\n }\n }\n return;\n }\n\n if (target.events[type]) {\n var len = target.events[type].length;\n\n if (listener === 'all') {\n for (i = 0; i < len; i++) {\n remove(element, type, target.events[type][i], !!useCapture);\n }\n return;\n } else {\n for (i = 0; i < len; i++) {\n if (target.events[type][i] === listener) {\n element[removeEvent](on + type, wrapped, !!useCapture);\n target.events[type].splice(i, 1);\n\n if (useAttachEvent && listeners) {\n listeners.useCount[listenerIndex]--;\n if (listeners.useCount[listenerIndex] === 0) {\n listeners.supplied.splice(listenerIndex, 1);\n listeners.wrapped.splice(listenerIndex, 1);\n listeners.useCount.splice(listenerIndex, 1);\n }\n }\n\n break;\n }\n }\n }\n\n if (target.events[type] && target.events[type].length === 0) {\n target.events[type] = null;\n target.typeCount--;\n }\n }\n\n if (!target.typeCount) {\n targets.splice(elementIndex, 1);\n elements.splice(elementIndex, 1);\n attachedListeners.splice(elementIndex, 1);\n }\n}\n\nfunction preventDef () {\n this.returnValue = false;\n}\n\nfunction stopProp () {\n this.cancelBubble = true;\n}\n\nfunction stopImmProp () {\n this.cancelBubble = true;\n this.immediatePropagationStopped = true;\n}\n\nmodule.exports = {\n add: add,\n remove: remove,\n useAttachEvent: useAttachEvent,\n\n _elements: elements,\n _targets: targets,\n _attachedListeners: attachedListeners\n};\n\n},{\"./arr\":7,\"./window\":18}],11:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function extend (dest, source) {\n for (var prop in source) {\n dest[prop] = source[prop];\n }\n return dest;\n};\n\n},{}],12:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function hypot (x, y) { return Math.sqrt(x * x + y * y); };\n\n},{}],13:[function(require,module,exports){\n'use strict';\n\nvar utils = module.exports,\n extend = require('./extend'),\n win = require('./window');\n\nutils.blank = function () {};\n\nutils.warnOnce = function (method, message) {\n var warned = false;\n\n return function () {\n if (!warned) {\n win.window.console.warn(message);\n warned = true;\n }\n\n return method.apply(this, arguments);\n };\n};\n\nutils.extend = extend;\nutils.hypot = require('./hypot');\nutils.raf = require('./raf');\nutils.browser = require('./browser');\n\nextend(utils, require('./arr'));\nextend(utils, require('./isType'));\nextend(utils, require('./pointerUtils'));\n\n},{\"./arr\":7,\"./browser\":8,\"./extend\":11,\"./hypot\":12,\"./isType\":14,\"./pointerUtils\":16,\"./raf\":17,\"./window\":18}],14:[function(require,module,exports){\n'use strict';\n\nvar win = require('./window'),\n domObjects = require('./domObjects');\n\nvar isType = {\n isElement : function (o) {\n if (!o || (typeof o !== 'object')) { return false; }\n \n var _window = win.getWindow(o) || win.window;\n \n return (/object|function/.test(typeof _window.Element)\n ? o instanceof _window.Element //DOM2\n : o.nodeType === 1 && typeof o.nodeName === \"string\");\n },\n\n isArray : null,\n \n isWindow : require('./isWindow'),\n\n isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; },\n\n isObject : function (thing) { return !!thing && (typeof thing === 'object'); },\n\n isFunction : function (thing) { return typeof thing === 'function'; },\n\n isNumber : function (thing) { return typeof thing === 'number' ; },\n\n isBool : function (thing) { return typeof thing === 'boolean' ; },\n\n isString : function (thing) { return typeof thing === 'string' ; }\n \n};\n\nisType.isArray = function (thing) {\n return isType.isObject(thing)\n && (typeof thing.length !== 'undefined')\n && isType.isFunction(thing.splice);\n};\n\nmodule.exports = isType;\n\n},{\"./domObjects\":9,\"./isWindow\":15,\"./window\":18}],15:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function isWindow (thing) {\n return !!(thing && thing.Window) && (thing instanceof thing.Window);\n};\n\n},{}],16:[function(require,module,exports){\n'use strict';\n\nvar pointerUtils = {},\n // reduce object creation in getXY()\n tmpXY = {},\n win = require('./window'),\n hypot = require('./hypot'),\n extend = require('./extend'),\n browser = require('./browser'),\n isType = require('./isType'),\n InteractEvent = require('../InteractEvent');\n\npointerUtils.copyCoords = function (dest, src) {\n dest.page = dest.page || {};\n dest.page.x = src.page.x;\n dest.page.y = src.page.y;\n\n dest.client = dest.client || {};\n dest.client.x = src.client.x;\n dest.client.y = src.client.y;\n\n dest.timeStamp = src.timeStamp;\n};\n\npointerUtils.setEventXY = function (targetObj, pointer, interaction) {\n if (!pointer) {\n if (interaction.pointerIds.length > 1) {\n pointer = pointerUtils.touchAverage(interaction.pointers);\n }\n else {\n pointer = interaction.pointers[0];\n }\n }\n\n pointerUtils.getPageXY(pointer, tmpXY, interaction);\n targetObj.page.x = tmpXY.x;\n targetObj.page.y = tmpXY.y;\n\n pointerUtils.getClientXY(pointer, tmpXY, interaction);\n targetObj.client.x = tmpXY.x;\n targetObj.client.y = tmpXY.y;\n\n targetObj.timeStamp = new Date().getTime();\n};\n\npointerUtils.setEventDeltas = function (targetObj, prev, cur) {\n targetObj.page.x = cur.page.x - prev.page.x;\n targetObj.page.y = cur.page.y - prev.page.y;\n targetObj.client.x = cur.client.x - prev.client.x;\n targetObj.client.y = cur.client.y - prev.client.y;\n targetObj.timeStamp = new Date().getTime() - prev.timeStamp;\n\n // set pointer velocity\n var dt = Math.max(targetObj.timeStamp / 1000, 0.001);\n targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt;\n targetObj.page.vx = targetObj.page.x / dt;\n targetObj.page.vy = targetObj.page.y / dt;\n\n targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt;\n targetObj.client.vx = targetObj.client.x / dt;\n targetObj.client.vy = targetObj.client.y / dt;\n};\n\n// Get specified X/Y coords for mouse or event.touches[0]\npointerUtils.getXY = function (type, pointer, xy) {\n xy = xy || {};\n type = type || 'page';\n\n xy.x = pointer[type + 'X'];\n xy.y = pointer[type + 'Y'];\n\n return xy;\n};\n\npointerUtils.getPageXY = function (pointer, page, interaction) {\n page = page || {};\n\n if (pointer instanceof InteractEvent) {\n if (/inertiastart/.test(pointer.type)) {\n interaction = interaction || pointer.interaction;\n\n extend(page, interaction.inertiaStatus.upCoords.page);\n\n page.x += interaction.inertiaStatus.sx;\n page.y += interaction.inertiaStatus.sy;\n }\n else {\n page.x = pointer.pageX;\n page.y = pointer.pageY;\n }\n }\n // Opera Mobile handles the viewport and scrolling oddly\n else if (browser.isOperaMobile) {\n pointerUtils.getXY('screen', pointer, page);\n\n page.x += win.window.scrollX;\n page.y += win.window.scrollY;\n }\n else {\n pointerUtils.getXY('page', pointer, page);\n }\n\n return page;\n};\n\npointerUtils.getClientXY = function (pointer, client, interaction) {\n client = client || {};\n\n if (pointer instanceof InteractEvent) {\n if (/inertiastart/.test(pointer.type)) {\n extend(client, interaction.inertiaStatus.upCoords.client);\n\n client.x += interaction.inertiaStatus.sx;\n client.y += interaction.inertiaStatus.sy;\n }\n else {\n client.x = pointer.clientX;\n client.y = pointer.clientY;\n }\n }\n else {\n // Opera Mobile handles the viewport and scrolling oddly\n pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client);\n }\n\n return client;\n};\n\npointerUtils.getPointerId = function (pointer) {\n return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier;\n};\n\nmodule.exports = pointerUtils;\n\n},{\"../InteractEvent\":2,\"./browser\":8,\"./extend\":11,\"./hypot\":12,\"./isType\":14,\"./window\":18}],17:[function(require,module,exports){\n'use strict';\n\nvar lastTime = 0,\n vendors = ['ms', 'moz', 'webkit', 'o'],\n reqFrame,\n cancelFrame;\n\nfor(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n reqFrame = window[vendors[x]+'RequestAnimationFrame'];\n cancelFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];\n}\n\nif (!reqFrame) {\n reqFrame = function(callback) {\n var currTime = new Date().getTime(),\n timeToCall = Math.max(0, 16 - (currTime - lastTime)),\n id = setTimeout(function() { callback(currTime + timeToCall); },\n timeToCall);\n lastTime = currTime + timeToCall;\n return id;\n };\n}\n\nif (!cancelFrame) {\n cancelFrame = function(id) {\n clearTimeout(id);\n };\n}\n\nmodule.exports = {\n request: reqFrame,\n cancel: cancelFrame\n};\n\n},{}],18:[function(require,module,exports){\n'use strict';\n\nvar isWindow = require('./isWindow');\n\nvar isShadowDom = function() {\n // create a TextNode\n var el = window.document.createTextNode('');\n\n // check if it's wrapped by a polyfill\n return el.ownerDocument !== window.document\n && typeof window.wrap === 'function'\n && window.wrap(el) === el;\n};\n\nvar win = {\n\n window: undefined,\n\n realWindow: window,\n\n getWindow: function getWindow (node) {\n if (isWindow(node)) {\n return node;\n }\n\n var rootNode = (node.ownerDocument || node);\n\n return rootNode.defaultView || rootNode.parentWindow || win.window;\n }\n};\n\nif (typeof window !== 'undefined') {\n if (isShadowDom()) {\n win.window = window.wrap(window);\n } else {\n win.window = window;\n }\n}\n\nmodule.exports = win;\n\n},{\"./isWindow\":15}]},{},[1]);\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/build/interact.min.js b/build/interact.min.js new file mode 100644 index 000000000..c348c7fb6 --- /dev/null +++ b/build/interact.min.js @@ -0,0 +1,5 @@ +!function t(e,i,r){function n(o,a){if(!i[o]){if(!e[o]){var l="function"==typeof require&&require;if(!a&&l)return l(o,!0);if(s)return s(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var p=i[o]={exports:{}};e[o][0].call(p.exports,function(t){var i=e[o][1][t];return n(i?i:t)},p,p.exports,t,e,i,r)}return i[o].exports}for(var s="function"==typeof require&&require,o=0;on;n++){r=g.interactions[n];var l=i;if(r.inertiaStatus.active&&r.target.options[r.prepared.name].inertia.allowResume&&r.mouse===o)for(;l;){if(l===r.element)return r.pointers[0]&&r.removePointer(r.pointers[0]),r.addPointer(t),r;l=g.parentElement(l)}}if(o||!v.supportsTouch&&!v.supportsPointerEvent){for(n=0;s>n;n++)if(g.interactions[n].mouse&&!g.interactions[n].inertiaStatus.active)return g.interactions[n];for(n=0;s>n;n++)if(g.interactions[n].mouse&&(!/down/.test(e)||!g.interactions[n].inertiaStatus.active))return r;return r=new x,r.mouse=!0,r}for(n=0;s>n;n++)if(g.contains(g.interactions[n].pointerIds,a))return g.interactions[n];if(/up|end|out/i.test(e))return null;for(n=0;s>n;n++)if(r=g.interactions[n],!(r.prepared.name&&!r.target.options.gesture.enabled||r.interacting()||!o&&r.mouse))return r.addPointer(t),r;return new x}function n(t){return function(e){var i,n,s=g.getActualElement(e.path?e.path[0]:e.target),o=g.getActualElement(e.currentTarget);if(v.supportsTouch&&/touch/.test(e.type))for(g.prevTouchTime=(new Date).getTime(),n=0;na&&("left"===t?t="right":"right"===t&&(t="left")),0>l&&("top"===t?t="bottom":"bottom"===t&&(t="top")),"left"===t)return i.x<(a>=0?s.left:s.right)+o;if("top"===t)return i.y<(l>=0?s.top:s.bottom)+o;if("right"===t)return i.x>(a>=0?s.right:s.left)-o;if("bottom"===t)return i.y>(l>=0?s.bottom:s.top)-o}return m.isElement(r)?m.isElement(e)?e===r:g.matchesUpTo(r,e,n):!1}function a(t,e,i){var r,n=this.getRect(i),s=!1,a=null,l=null,c=m.extend({},e.curCoords.page),p=this.options;if(!n)return null;if(g.actionIsEnabled.resize&&p.resize.enabled){var h=p.resize;if(r={left:!1,right:!1,top:!1,bottom:!1},g.isObject(h.edges)){for(var d in r)r[d]=o(d,h.edges[d],c,e._eventTarget,i,n,h.margin||g.margin);r.left=r.left&&!r.right,r.top=r.top&&!r.bottom,s=r.left||r.right||r.top||r.bottom}else{var u="y"!==p.resize.axis&&c.x>n.right-g.margin,v="x"!==p.resize.axis&&c.y>n.bottom-g.margin;s=u||v,l=(u?"x":"")+(v?"y":"")}}return a=s?"resize":g.actionIsEnabled.drag&&p.drag.enabled?"drag":null,g.actionIsEnabled.gesture&&e.pointerIds.length>=2&&!e.dragging&&!e.resizing&&(a="gesture"),a?{name:a,axis:l,edges:r}:null}function l(t,e){var i={},r=g.delegatedEvents[t.type],n=g.getActualElement(t.path?t.path[0]:t.target),o=n;e=e?!0:!1;for(var a in t)i[a]=t[a];for(i.originalEvent=t,i.preventDefault=s;m.isElement(o);){for(var l=0;lc;c++){var h=g.interactions[c],d=h.prepared.name,u=h.interacting();if(u){if(o++,o>=g.maxInteractions)return!1;if(h.target===t){if(a+=d===i.name|0,a>=n)return!1;if(h.element===e&&(l++,d!==i.name||l>=s))return!1}}}return g.maxInteractions>0},g.indexOfDeepestElement=function(t){var e,i,r,n,s,o=t[0],a=o?0:-1,l=[],c=[];for(n=1;nr;r++)if(i[r]===t)return!0;return!1});for(var x=t("./Interaction"),E=t("./InteractEvent"),w=0,S=y.length;S>w;w++){var b=y[w];g.listeners[b]=n(b)}g.interactables.indexOfElement=function(t,e){e=e||g.document;for(var i=0;is.left&&p.xs.top&&p.y=s.left&&u<=s.right&&v>=s.top&&v<=s.bottom}if(g.isNumber(a)){var f=Math.max(0,Math.min(s.right,d.right)-Math.max(s.left,d.left))*Math.max(0,Math.min(s.bottom,d.bottom)-Math.max(s.top,d.top)),y=f/(d.width*d.height);o=y>=a}return this.options.dropChecker&&(o=this.options.dropChecker(t,o,this,n,i,r)),o},dropChecker:function(t){return g.isFunction(t)?(this.options.dropChecker=t,this):null===t?(delete this.options.getRect,this):this.options.dropChecker},accept:function(t){return m.isElement(t)?(this.options.drop.accept=t,this):g.trySelector(t)?(this.options.drop.accept=t,this):null===t?(delete this.options.drop.accept,this):this.options.drop.accept},resizable:function(t){return g.isObject(t)?(this.options.resize.enabled=t.enabled===!1?!1:!0,this.setPerAction("resize",t),this.setOnEvents("resize",t),/^x$|^y$|^xy$/.test(t.axis)?this.options.resize.axis=t.axis:null===t.axis&&(this.options.resize.axis=g.defaultOptions.resize.axis),g.isBool(t.square)&&(this.options.resize.square=t.square),this):g.isBool(t)?(this.options.resize.enabled=t,this):this.options.resize},squareResize:function(t){return g.isBool(t)?(this.options.resize.square=t,this):null===t?(delete this.options.resize.square,this):this.options.resize.square},gesturable:function(t){return g.isObject(t)?(this.options.gesture.enabled=t.enabled===!1?!1:!0,this.setPerAction("gesture",t),this.setOnEvents("gesture",t),this):g.isBool(t)?(this.options.gesture.enabled=t,this):this.options.gesture},autoScroll:function(t){return g.isObject(t)?t=m.extend({actions:["drag","resize"]},t):g.isBool(t)&&(t={actions:["drag","resize"],enabled:t}),this.setOptions("autoScroll",t)},snap:function(t){var e=this.setOptions("snap",t);return e===this?this:e.drag},setOptions:function(t,e){var i,r=e&&g.isArray(e.actions)?e.actions:["drag"];if(g.isObject(e)||g.isBool(e)){for(i=0;ii&&!t.immediatePropagationStopped;i++)s=e[i].name,e[i](t);if(g.isFunction(this[n])&&(s=this[n].name,this[n](t)),t.type in g.globalEvents&&(e=g.globalEvents[t.type]))for(i=0,r=e.length;r>i&&!t.immediatePropagationStopped;i++)s=e[i].name,e[i](t);return this},on:function(t,e,i){var r;if(g.isString(t)&&-1!==t.search(" ")&&(t=t.trim().split(/ +/)),g.isArray(t)){for(r=0;r=0&&(o.selectors[s]!==this.selector||o.contexts[s]!==this._context);s--);-1===s&&(s=o.selectors.length,o.selectors.push(this.selector),o.contexts.push(this._context),o.listeners.push([])),o.listeners[s].push([e,i])}else f.add(this._element,t,e,i);return this},off:function(t,e,i){var r;if(g.isString(t)&&-1!==t.search(" ")&&(t=t.trim().split(/ +/)),g.isArray(t)){for(r=0;r=0;o--)if(a.selectors[o]===this.selector&&a.contexts[o]===this._context){var h=a.listeners[o];for(r=h.length-1;r>=0;r--){var d=h[r][0],u=h[r][1];if(d===e&&u===i){h.splice(r,1),h.length||(a.selectors.splice(o,1),a.contexts.splice(o,1),a.listeners.splice(o,1),f.remove(this._context,t,l),f.remove(this._context,t,c,!0),a.selectors.length||(g.delegatedEvents[t]=null)),p=!0;break}}if(p)break}}else f.remove(this._element,t,e,i);return this},set:function(t){g.isObject(t)||(t={}),this.options=m.extend({},g.defaultOptions.base);var e,i=["drag","drop","resize","gesture"],r=["draggable","dropzone","resizable","gesturable"],n=m.extend(m.extend({},g.defaultOptions.perAction),t[s]||{});for(e=0;ee;e++){var a=o[e];this.options[a]=g.defaultOptions.base[a],a in t&&this[a](t[a])}return this},unset:function(){if(f.remove(this._element,"all"),g.isString(this.selector))for(var t in g.delegatedEvents)for(var e=g.delegatedEvents[t],i=0;i0;e--)g.interactions[e].stop(t);return p},p.dynamicDrop=function(t){return g.isBool(t)?(g.dynamicDrop=t,p):g.dynamicDrop},p.pointerMoveTolerance=function(t){return g.isNumber(t)?(g.pointerMoveTolerance=t,this):g.pointerMoveTolerance},p.maxInteractions=function(t){return g.isNumber(t)?(g.maxInteractions=t,this):g.maxInteractions},p.createSnapGrid=function(t){return function(e,i){var r=0,n=0;g.isObject(t.offset)&&(r=t.offset.x,n=t.offset.y);var s=Math.round((e-r)/t.x),o=Math.round((i-n)/t.y),a=s*t.x+r,l=o*t.y+n;return{x:a,y:l,range:t.range}}},u(g.document),g.interact=p,g.Interactable=h,g.Interaction=x,g.InteractEvent=E,e.exports=p}},{"./InteractEvent":2,"./Interaction":3,"./autoScroll":4,"./defaultOptions":5,"./scope":6,"./utils":13,"./utils/events":10,"./utils/window":18}],2:[function(t,e,i){"use strict";function r(t,e,i,o,a,l){var c,p,h=t.target,d=t.snapStatus,u=t.restrictStatus,g=t.pointers,m=(h&&h.options||n.defaultOptions).deltaSource,v=m+"X",f=m+"Y",y=h?h.options:n.defaultOptions,x=n.getOriginXY(h,a),E="start"===o,w="end"===o,S=E?t.startCoords:t.curCoords;a=a||t.element,p=s.extend({},S.page),c=s.extend({},S.client),p.x-=x.x,p.y-=x.y,c.x-=x.x,c.y-=x.y;var b=y[i].snap&&y[i].snap.relativePoints;!n.checkSnap(h,i)||E&&b&&b.length||(this.snap={range:d.range,locked:d.locked,x:d.snappedX,y:d.snappedY,realX:d.realX,realY:d.realY,dx:d.dx,dy:d.dy},d.locked&&(p.x+=d.dx,p.y+=d.dy,c.x+=d.dx,c.y+=d.dy)),!n.checkRestrict(h,i)||E&&y[i].restrict.elementRect||!u.restricted||(p.x+=u.dx,p.y+=u.dy,c.x+=u.dx,c.y+=u.dy,this.restrict={dx:u.dx,dy:u.dy}),this.pageX=p.x,this.pageY=p.y,this.clientX=c.x,this.clientY=c.y,this.x0=t.startCoords.page.x-x.x,this.y0=t.startCoords.page.y-x.y,this.clientX0=t.startCoords.client.x-x.x,this.clientY0=t.startCoords.client.y-x.y,this.ctrlKey=e.ctrlKey,this.altKey=e.altKey,this.shiftKey=e.shiftKey,this.metaKey=e.metaKey,this.button=e.button,this.target=a,this.t0=t.downTimes[0],this.type=i+(o||""),this.interaction=t,this.interactable=h;var T=t.inertiaStatus;if(T.active&&(this.detail="inertia"),l&&(this.relatedTarget=l),w?"client"===m?(this.dx=c.x-t.startCoords.client.x,this.dy=c.y-t.startCoords.client.y):(this.dx=p.x-t.startCoords.page.x,this.dy=p.y-t.startCoords.page.y):E?(this.dx=0,this.dy=0):"inertiastart"===o?(this.dx=t.prevEvent.dx,this.dy=t.prevEvent.dy):"client"===m?(this.dx=c.x-t.prevEvent.clientX,this.dy=c.y-t.prevEvent.clientY):(this.dx=p.x-t.prevEvent.pageX,this.dy=p.y-t.prevEvent.pageY),t.prevEvent&&"inertia"===t.prevEvent.detail&&!T.active&&y[i].inertia&&y[i].inertia.zeroResumeDelta&&(T.resumeDx+=this.dx,T.resumeDy+=this.dy,this.dx=this.dy=0),"resize"===i&&t.resizeAxes?y.resize.square?("y"===t.resizeAxes?this.dx=this.dy:this.dy=this.dx,this.axes="xy"):(this.axes=t.resizeAxes,"x"===t.resizeAxes?this.dy=0:"y"===t.resizeAxes&&(this.dx=0)):"gesture"===i&&(this.touches=[g[0],g[1]],E?(this.distance=s.touchDistance(g,m),this.box=s.touchBBox(g),this.scale=1,this.ds=0,this.angle=s.touchAngle(g,void 0,m),this.da=0):w||e instanceof r?(this.distance=t.prevEvent.distance,this.box=t.prevEvent.box,this.scale=t.prevEvent.scale,this.ds=this.scale-1,this.angle=t.prevEvent.angle,this.da=this.angle-t.gesture.startAngle):(this.distance=s.touchDistance(g,m),this.box=s.touchBBox(g),this.scale=this.distance/t.gesture.startDistance,this.angle=s.touchAngle(g,t.gesture.prevAngle,m),this.ds=this.scale-t.gesture.prevScale,this.da=this.angle-t.gesture.prevAngle)),E)this.timeStamp=t.downTimes[0],this.dt=0,this.duration=0,this.speed=0,this.velocityX=0,this.velocityY=0;else if("inertiastart"===o)this.timeStamp=t.prevEvent.timeStamp,this.dt=t.prevEvent.dt,this.duration=t.prevEvent.duration,this.speed=t.prevEvent.speed,this.velocityX=t.prevEvent.velocityX,this.velocityY=t.prevEvent.velocityY;else if(this.timeStamp=(new Date).getTime(),this.dt=this.timeStamp-t.prevEvent.timeStamp,this.duration=this.timeStamp-t.downTimes[0],e instanceof r){var D=this[v]-t.prevEvent[v],z=this[f]-t.prevEvent[f],O=this.dt/1e3;this.speed=s.hypot(D,z)/O,this.velocityX=D/O,this.velocityY=z/O}else this.speed=t.pointerDelta[m].speed,this.velocityX=t.pointerDelta[m].vx,this.velocityY=t.pointerDelta[m].vy;if((w||"inertiastart"===o)&&t.prevEvent.speed>600&&this.timeStamp-t.prevEvent.timeStamp<150){var C=180*Math.atan2(t.prevEvent.velocityY,t.prevEvent.velocityX)/Math.PI,M=22.5;0>C&&(C+=360);var P=C>=135-M&&225+M>C,I=C>=225-M&&315+M>C,A=!P&&(C>=315-M||45+M>C),_=!I&&C>=45-M&&135+M>C;this.swipe={up:I,down:_,left:P,right:A,angle:C,speed:t.prevEvent.speed,velocity:{x:t.prevEvent.velocityX,y:t.prevEvent.velocityY}}}}var n=t("./scope"),s=t("./utils");r.prototype={preventDefault:s.blank,stopImmediatePropagation:function(){this.immediatePropagationStopped=this.propagationStopped=!0},stopPropagation:function(){this.propagationStopped=!0}},e.exports=r},{"./scope":6,"./utils":13}],3:[function(t,e,i){"use strict";function r(){if(this.target=null,this.element=null,this.dropTarget=null,this.dropElement=null, +this.prevDropTarget=null,this.prevDropElement=null,this.prepared={name:null,axis:null,edges:null},this.matches=[],this.matchElements=[],this.inertiaStatus={active:!1,smoothEnd:!1,startEvent:null,upCoords:{},xe:0,ye:0,sx:0,sy:0,t0:0,vx0:0,vys:0,duration:0,resumeDx:0,resumeDy:0,lambda_v0:0,one_ve_v0:0,i:null},a.isFunction(Function.prototype.bind))this.boundInertiaFrame=this.inertiaFrame.bind(this),this.boundSmoothEndFrame=this.smoothEndFrame.bind(this);else{var t=this;this.boundInertiaFrame=function(){return t.inertiaFrame()},this.boundSmoothEndFrame=function(){return t.smoothEndFrame()}}this.activeDrops={dropzones:[],elements:[],rects:[]},this.pointers=[],this.pointerIds=[],this.downTargets=[],this.downTimes=[],this.holdTimers=[],this.prevCoords={page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},this.curCoords={page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},this.startCoords={page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},this.pointerDelta={page:{x:0,y:0,vx:0,vy:0,speed:0},client:{x:0,y:0,vx:0,vy:0,speed:0},timeStamp:0},this.downEvent=null,this.downPointer={},this._eventTarget=null,this._curEventTarget=null,this.prevEvent=null,this.tapTime=0,this.prevTap=null,this.startOffset={left:0,right:0,top:0,bottom:0},this.restrictOffset={left:0,right:0,top:0,bottom:0},this.snapOffsets=[],this.gesture={start:{x:0,y:0},startDistance:0,prevDistance:0,distance:0,scale:1,startAngle:0,prevAngle:0},this.snapStatus={x:0,y:0,dx:0,dy:0,realX:0,realY:0,snappedX:0,snappedY:0,targets:[],locked:!1,changed:!1},this.restrictStatus={dx:0,dy:0,restrictedX:0,restrictedY:0,snap:null,restricted:!1,changed:!1},this.restrictStatus.snap=this.snapStatus,this.pointerIsDown=!1,this.pointerWasMoved=!1,this.gesturing=!1,this.dragging=!1,this.resizing=!1,this.resizeAxes="xy",this.mouse=!1,a.interactions.push(this)}function n(t,e){if(!a.isObject(t))return null;var i=t.name,r=e.options;return("resize"===i&&r.resize.enabled||"drag"===i&&r.drag.enabled||"gesture"===i&&r.gesture.enabled)&&a.actionIsEnabled[i]?(("resize"===i||"resizeyx"===i)&&(i="resizexy"),t):null}function s(t){var e="";if("drag"===t.name&&(e=a.actionCursors.drag),"resize"===t.name)if(t.axis)e=a.actionCursors[t.name+t.axis];else if(t.edges){for(var i="resize",r=["top","bottom","left","right"],n=0;4>n;n++)t.edges[r[n]]&&(i+=r[n]);e=a.actionCursors[i]}return e}function o(){this.originalEvent.preventDefault()}var a=t("./scope"),l=t("./utils"),c=l.raf,p=t("./InteractEvent"),h=t("./utils/events"),d=t("./utils/browser");r.prototype={getPageXY:function(t,e){return l.getPageXY(t,e,this)},getClientXY:function(t,e){return l.getClientXY(t,e,this)},setEventXY:function(t,e){return l.setEventXY(t,e,this)},pointerOver:function(t,e,i){function r(t,e){t&&a.inContext(t,i)&&!a.testIgnore(t,i,i)&&a.testAllow(t,i,i)&&a.matchesSelector(i,e)&&(s.push(t),o.push(i))}if(!this.prepared.name&&this.mouse){var s=[],o=[],l=this.element;this.addPointer(t),!this.target||!a.testIgnore(this.target,this.element,i)&&a.testAllow(this.target,this.element,i)||(this.target=null,this.element=null,this.matches=[],this.matchElements=[]);var c=a.interactables.get(i),p=c&&!a.testIgnore(c,i,i)&&a.testAllow(c,i,i)&&n(c.getAction(t,e,this,i),c);p&&!a.withinInteractionLimit(c,i,p)&&(p=null),p?(this.target=c,this.element=i,this.matches=[],this.matchElements=[]):(a.interactables.forEachSelector(r),this.validateSelector(t,e,s,o)?(this.matches=s,this.matchElements=o,this.pointerHover(t,e,this.matches,this.matchElements),h.add(i,a.PointerEvent?a.pEventTypes.move:"mousemove",a.listeners.pointerHover)):this.target&&(a.nodeContains(l,i)?(this.pointerHover(t,e,this.matches,this.matchElements),h.add(this.element,a.PointerEvent?a.pEventTypes.move:"mousemove",a.listeners.pointerHover)):(this.target=null,this.element=null,this.matches=[],this.matchElements=[])))}},pointerHover:function(t,e,i,r,o,a){var l=this.target;if(!this.prepared.name&&this.mouse){var c;this.setEventXY(this.curCoords,t),o?c=this.validateSelector(t,e,o,a):l&&(c=n(l.getAction(this.pointers[0],e,this,this.element),this.target)),l&&l.options.styleCursor&&(l._doc.documentElement.style.cursor=c?s(c):"")}else this.prepared.name&&this.checkAndPreventDefault(e,l,this.element)},pointerOut:function(t,e,i){this.prepared.name||(a.interactables.get(i)||h.remove(i,a.PointerEvent?a.pEventTypes.move:"mousemove",a.listeners.pointerHover),this.target&&this.target.options.styleCursor&&!this.interacting()&&(this.target._doc.documentElement.style.cursor=""))},selectorDown:function(t,e,i,r){function s(t,e,r){var n=a.ie8MatchesSelector?r.querySelectorAll(e):void 0;a.inContext(t,u)&&!a.testIgnore(t,u,i)&&a.testAllow(t,u,i)&&a.matchesSelector(u,e,n)&&(p.matches.push(t),p.matchElements.push(u))}var o,p=this,d=h.useAttachEvent?l.extend({},e):e,u=i,g=this.addPointer(t);if(this.holdTimers[g]=setTimeout(function(){p.pointerHold(h.useAttachEvent?d:t,d,i,r)},a.defaultOptions._holdDuration),this.pointerIsDown=!0,this.inertiaStatus.active&&this.target.selector)for(;l.isElement(u);){if(u===this.element&&n(this.target.getAction(t,e,this,this.element),this.target).name===this.prepared.name)return c.cancel(this.inertiaStatus.i),this.inertiaStatus.active=!1,void this.collectEventTargets(t,e,i,"down");u=a.parentElement(u)}if(this.interacting())return void this.collectEventTargets(t,e,i,"down");for(this.setEventXY(this.curCoords,t),this.downEvent=e;l.isElement(u)&&!o;)this.matches=[],this.matchElements=[],a.interactables.forEachSelector(s),o=this.validateSelector(t,e,this.matches,this.matchElements),u=a.parentElement(u);return o?(this.prepared.name=o.name,this.prepared.axis=o.axis,this.prepared.edges=o.edges,this.collectEventTargets(t,e,i,"down"),this.pointerDown(t,e,i,r,o)):(this.downTimes[g]=(new Date).getTime(),this.downTargets[g]=i,l.extend(this.downPointer,t),l.copyCoords(this.prevCoords,this.curCoords),this.pointerWasMoved=!1,void this.collectEventTargets(t,e,i,"down"))},pointerDown:function(t,e,i,r,o){if(!o&&!this.inertiaStatus.active&&this.pointerWasMoved&&this.prepared.name)return void this.checkAndPreventDefault(e,this.target,this.element);this.pointerIsDown=!0,this.downEvent=e;var p,h=this.addPointer(t);if(this.pointerIds.length<2&&!this.target||!this.prepared.name){var d=a.interactables.get(r);d&&!a.testIgnore(d,r,i)&&a.testAllow(d,r,i)&&(p=n(o||d.getAction(t,e,this,r),d,i))&&a.withinInteractionLimit(d,r,p)&&(this.target=d,this.element=r)}var u=this.target,g=u&&u.options;if(!u||!o&&this.prepared.name)this.inertiaStatus.active&&r===this.element&&n(u.getAction(t,e,this,this.element),u).name===this.prepared.name&&(c.cancel(this.inertiaStatus.i),this.inertiaStatus.active=!1,this.checkAndPreventDefault(e,u,this.element));else{if(p=p||n(o||u.getAction(t,e,this,r),u,this.element),this.setEventXY(this.startCoords),!p)return;g.styleCursor&&(u._doc.documentElement.style.cursor=s(p)),this.resizeAxes="resize"===p.name?p.axis:null,"gesture"===p&&this.pointerIds.length<2&&(p=null),this.prepared.name=p.name,this.prepared.axis=p.axis,this.prepared.edges=p.edges,this.snapStatus.snappedX=this.snapStatus.snappedY=this.restrictStatus.restrictedX=this.restrictStatus.restrictedY=0/0,this.downTimes[h]=(new Date).getTime(),this.downTargets[h]=i,l.extend(this.downPointer,t),this.setEventXY(this.prevCoords),this.pointerWasMoved=!1,this.checkAndPreventDefault(e,u,this.element)}},setModifications:function(t,e){var i=this.target,r=!0,n=a.checkSnap(i,this.prepared.name)&&(!i.options[this.prepared.name].snap.endOnly||e),s=a.checkRestrict(i,this.prepared.name)&&(!i.options[this.prepared.name].restrict.endOnly||e);return n?this.setSnapping(t):this.snapStatus.locked=!1,s?this.setRestriction(t):this.restrictStatus.restricted=!1,n&&this.snapStatus.locked&&!this.snapStatus.changed?r=s&&this.restrictStatus.restricted&&this.restrictStatus.changed:s&&this.restrictStatus.restricted&&!this.restrictStatus.changed&&(r=!1),r},setStartOffsets:function(t,e,i){var r,n,s=e.getRect(i),o=a.getOriginXY(e,i),l=e.options[this.prepared.name].snap,c=e.options[this.prepared.name].restrict;s?(this.startOffset.left=this.startCoords.page.x-s.left,this.startOffset.top=this.startCoords.page.y-s.top,this.startOffset.right=s.right-this.startCoords.page.x,this.startOffset.bottom=s.bottom-this.startCoords.page.y,r="width"in s?s.width:s.right-s.left,n="height"in s?s.height:s.bottom-s.top):this.startOffset.left=this.startOffset.top=this.startOffset.right=this.startOffset.bottom=0,this.snapOffsets.splice(0);var p=l&&"startCoords"===l.offset?{x:this.startCoords.page.x-o.x,y:this.startCoords.page.y-o.y}:l&&l.offset||{x:0,y:0};if(s&&l&&l.relativePoints&&l.relativePoints.length)for(var h=0;ha.pointerMoveTolerance),c||this.pointerIsDown&&!this.pointerWasMoved||(this.pointerIsDown&&clearTimeout(this.holdTimers[h]),this.collectEventTargets(t,e,i,"move")),this.pointerIsDown){if(c&&this.pointerWasMoved&&!n)return void this.checkAndPreventDefault(e,this.target,this.element);if(l.setEventDeltas(this.pointerDelta,this.prevCoords,this.curCoords),this.prepared.name){if(this.pointerWasMoved&&(!this.inertiaStatus.active||t instanceof p&&/inertiastart/.test(t.type))){if(!this.interacting()&&(l.setEventDeltas(this.pointerDelta,this.prevCoords,this.curCoords),"drag"===this.prepared.name)){var d=Math.abs(s),u=Math.abs(o),g=this.target.options.drag.axis,m=d>u?"x":u>d?"y":"xy";if("xy"!==m&&"xy"!==g&&g!==m){this.prepared.name=null;for(var v=i;l.isElement(v);){var f=a.interactables.get(v);if(f&&f!==this.target&&!f.options.drag.manualStart&&"drag"===f.getAction(this.downPointer,this.downEvent,this,v).name&&a.checkAxis(m,f)){this.prepared.name="drag",this.target=f,this.element=v;break}v=a.parentElement(v)}if(!this.prepared.name){var y=this,x=function(t,e,r){var n=a.ie8MatchesSelector?r.querySelectorAll(e):void 0;if(t!==y.target)return a.inContext(t,i)&&!t.options.drag.manualStart&&!a.testIgnore(t,v,i)&&a.testAllow(t,v,i)&&a.matchesSelector(v,e,n)&&"drag"===t.getAction(y.downPointer,y.downEvent,y,v).name&&a.checkAxis(m,t)&&a.withinInteractionLimit(t,v,"drag")?t:void 0};for(v=i;l.isElement(v);){var E=a.interactables.forEachSelector(x);if(E){this.prepared.name="drag",this.target=E,this.element=v;break}v=a.parentElement(v)}}}}var w=!!this.prepared.name&&!this.interacting();if(w&&(this.target.options[this.prepared.name].manualStart||!a.withinInteractionLimit(this.target,this.element,this.prepared)))return void this.stop(e);if(this.prepared.name&&this.target){w&&this.start(this.prepared,this.target,this.element);var S=this.setModifications(this.curCoords.page,n);(S||w)&&(this.prevEvent=this[this.prepared.name+"Move"](e)),this.checkAndPreventDefault(e,this.target,this.element)}}l.copyCoords(this.prevCoords,this.curCoords),(this.dragging||this.resizing)&&this.autoScrollMove(t)}}},dragStart:function(t){var e=new p(this,t,"drag","start",this.element);this.dragging=!0,this.target.fire(e),this.activeDrops.dropzones=[],this.activeDrops.elements=[],this.activeDrops.rects=[],this.dynamicDrop||this.setActiveDrops(this.element);var i=this.getDropEvents(t,e);return i.activate&&this.fireActiveDrops(i.activate),e},dragMove:function(t){var e=this.target,i=new p(this,t,"drag","move",this.element),r=this.element,n=this.getDrop(t,r);this.dropTarget=n.dropzone,this.dropElement=n.element;var s=this.getDropEvents(t,i);return e.fire(i),s.leave&&this.prevDropTarget.fire(s.leave),s.enter&&this.dropTarget.fire(s.enter),s.move&&this.dropTarget.fire(s.move),this.prevDropTarget=this.dropTarget,this.prevDropElement=this.dropElement,i},resizeStart:function(t){var e=new p(this,t,"resize","start",this.element);if(this.prepared.edges){var i=this.target.getRect(this.element);if(this.target.options.resize.square){var r=l.extend({},this.prepared.edges);r.top=r.top||r.left&&!r.bottom,r.left=r.left||r.top&&!r.right,r.bottom=r.bottom||r.right&&!r.top,r.right=r.right||r.bottom&&!r.left,this.prepared._squareEdges=r}else this.prepared._squareEdges=null;this.resizeRects={start:i,current:l.extend({},i),restricted:l.extend({},i),previous:l.extend({},i),delta:{left:0,right:0,width:0,top:0,bottom:0,height:0}},e.rect=this.resizeRects.restricted,e.deltaRect=this.resizeRects.delta}return this.target.fire(e),this.resizing=!0,e},resizeMove:function(t){var e=new p(this,t,"resize","move",this.element),i=this.prepared.edges,r=this.target.options.resize.invert,n="reposition"===r||"negate"===r;if(i){var s=e.dx,o=e.dy,a=this.resizeRects.start,c=this.resizeRects.current,h=this.resizeRects.restricted,d=this.resizeRects.delta,u=l.extend(this.resizeRects.previous,h);if(this.target.options.resize.square){var g=i;i=this.prepared._squareEdges,g.left&&g.bottom||g.right&&g.top?o=-s:g.left||g.right?o=s:(g.top||g.bottom)&&(s=o)}if(i.top&&(c.top+=o),i.bottom&&(c.bottom+=o),i.left&&(c.left+=s),i.right&&(c.right+=s),n){if(l.extend(h,c),"reposition"===r){var m;h.top>h.bottom&&(m=h.top,h.top=h.bottom,h.bottom=m),h.left>h.right&&(m=h.left,h.left=h.right,h.right=m)}}else h.top=Math.min(c.top,a.bottom),h.bottom=Math.max(c.bottom,a.top),h.left=Math.min(c.left,a.right),h.right=Math.max(c.right,a.left);h.width=h.right-h.left,h.height=h.bottom-h.top;for(var v in h)d[v]=h[v]-u[v];e.edges=this.prepared.edges,e.rect=h,e.deltaRect=d}return this.target.fire(e),e},gestureStart:function(t){var e=new p(this,t,"gesture","start",this.element);return e.ds=0,this.gesture.startDistance=this.gesture.prevDistance=e.distance,this.gesture.startAngle=this.gesture.prevAngle=e.angle,this.gesture.scale=1,this.gesturing=!0,this.target.fire(e),e},gestureMove:function(t){if(!this.pointerIds.length)return this.prevEvent;var e;return e=new p(this,t,"gesture","move",this.element),e.ds=e.scale-this.gesture.scale,this.target.fire(e),this.gesture.prevAngle=e.angle,this.gesture.prevDistance=e.distance,e.scale===1/0||null===e.scale||void 0===e.scale||isNaN(e.scale)||(this.gesture.scale=e.scale),e},pointerHold:function(t,e,i){this.collectEventTargets(t,e,i,"hold")},pointerUp:function(t,e,i,r){var n=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));clearTimeout(this.holdTimers[n]),this.collectEventTargets(t,e,i,"up"),this.collectEventTargets(t,e,i,"tap"),this.pointerEnd(t,e,i,r),this.removePointer(t)},pointerCancel:function(t,e,i,r){var n=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));clearTimeout(this.holdTimers[n]),this.collectEventTargets(t,e,i,"cancel"),this.pointerEnd(t,e,i,r),this.removePointer(t)},ie8Dblclick:function(t,e,i){this.prevTap&&e.clientX===this.prevTap.clientX&&e.clientY===this.prevTap.clientY&&i===this.prevTap.target&&(this.downTargets[0]=i,this.downTimes[0]=(new Date).getTime(),this.collectEventTargets(t,e,i,"tap"))},pointerEnd:function(t,e,i,r){var n,s=this.target,o=s&&s.options,h=o&&this.prepared.name&&o[this.prepared.name].inertia,d=this.inertiaStatus;if(this.interacting()){if(d.active)return;var u,g,m=(new Date).getTime(),v=!1,f=!1,y=!1,x=a.checkSnap(s,this.prepared.name)&&o[this.prepared.name].snap.endOnly,E=a.checkRestrict(s,this.prepared.name)&&o[this.prepared.name].restrict.endOnly,w=0,S=0;if(u=this.dragging?"x"===o.drag.axis?Math.abs(this.pointerDelta.client.vx):"y"===o.drag.axis?Math.abs(this.pointerDelta.client.vy):this.pointerDelta.client.speed:this.pointerDelta.client.speed,v=h&&h.enabled&&"gesture"!==this.prepared.name&&e!==d.startEvent,f=v&&m-this.curCoords.timeStamp<50&&u>h.minSpeed&&u>h.endSpeed,v&&!f&&(x||E)){var b={};b.snap=b.restrict=b,x&&(this.setSnapping(this.curCoords.page,b),b.locked&&(w+=b.dx,S+=b.dy)),E&&(this.setRestriction(this.curCoords.page,b),b.restricted&&(w+=b.dx,S+=b.dy)),(w||S)&&(y=!0)}if(f||y){if(l.copyCoords(d.upCoords,this.curCoords),this.pointers[0]=d.startEvent=g=new p(this,e,this.prepared.name,"inertiastart",this.element),d.t0=m,s.fire(d.startEvent),f){d.vx0=this.pointerDelta.client.vx,d.vy0=this.pointerDelta.client.vy,d.v0=u,this.calcInertia(d);var T,D=l.extend({},this.curCoords.page),z=a.getOriginXY(s,this.element);if(D.x=D.x+d.xe-z.x,D.y=D.y+d.ye-z.y,T={useStatusXY:!0,x:D.x,y:D.y,dx:0,dy:0,snap:null},T.snap=T,w=S=0,x){var O=this.setSnapping(this.curCoords.page,T);O.locked&&(w+=O.dx,S+=O.dy)}if(E){var C=this.setRestriction(this.curCoords.page,T);C.restricted&&(w+=C.dx,S+=C.dy)}d.modifiedXe+=w,d.modifiedYe+=S,d.i=c.request(this.boundInertiaFrame)}else d.smoothEnd=!0,d.xe=w,d.ye=S,d.sx=d.sy=0,d.i=c.request(this.boundSmoothEndFrame);return void(d.active=!0)}(x||E)&&this.pointerMove(t,e,i,r,!0)}if(this.dragging){n=new p(this,e,"drag","end",this.element);var M=this.element,P=this.getDrop(e,M);this.dropTarget=P.dropzone,this.dropElement=P.element;var I=this.getDropEvents(e,n);I.leave&&this.prevDropTarget.fire(I.leave),I.enter&&this.dropTarget.fire(I.enter),I.drop&&this.dropTarget.fire(I.drop),I.deactivate&&this.fireActiveDrops(I.deactivate),s.fire(n)}else this.resizing?(n=new p(this,e,"resize","end",this.element),s.fire(n)):this.gesturing&&(n=new p(this,e,"gesture","end",this.element),s.fire(n));this.stop(e)},collectDrops:function(t){var e,i=[],r=[];for(t=t||this.element,e=0;ec;c++){var h=o[c];h!==t&&(i.push(n),r.push(h))}}return{dropzones:i,elements:r}},fireActiveDrops:function(t){var e,i,r,n;for(e=0;ee?(t.sx=a.easeOutQuad(e,0,t.xe,i),t.sy=a.easeOutQuad(e,0,t.ye,i),this.pointerMove(t.startEvent,t.startEvent),t.i=c.request(this.boundSmoothEndFrame)):(t.sx=t.xe,t.sy=t.ye,this.pointerMove(t.startEvent,t.startEvent),t.active=!1,t.smoothEnd=!1,this.pointerEnd(t.startEvent,t.startEvent))},addPointer:function(t){var e=l.getPointerId(t),i=this.mouse?0:a.indexOf(this.pointerIds,e);return-1===i&&(i=this.pointerIds.length),this.pointerIds[i]=e,this.pointers[i]=t,i},removePointer:function(t){var e=l.getPointerId(t),i=this.mouse?0:a.indexOf(this.pointerIds,e);-1!==i&&(this.interacting()||this.pointers.splice(i,1),this.pointerIds.splice(i,1),this.downTargets.splice(i,1),this.downTimes.splice(i,1),this.holdTimers.splice(i,1))},recordPointer:function(t){if(!this.inertiaStatus.active){var e=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));-1!==e&&(this.pointers[e]=t)}},collectEventTargets:function(t,e,i,r){function n(t,e,n){var s=a.ie8MatchesSelector?n.querySelectorAll(e):void 0;t._iEvents[r]&&l.isElement(p)&&a.inContext(t,p)&&!a.testIgnore(t,p,i)&&a.testAllow(t,p,i)&&a.matchesSelector(p,e,s)&&(o.push(t),c.push(p))}var s=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));if("tap"!==r||!this.pointerWasMoved&&this.downTargets[s]&&this.downTargets[s]===i){for(var o=[],c=[],p=i,h=a.interact;p;)h.isSet(p)&&h(p)._iEvents[r]&&(o.push(h(p)),c.push(p)),a.interactables.forEachSelector(n),p=a.parentElement(p);(o.length||"tap"===r)&&this.firePointers(t,e,i,o,c,r)}},firePointers:function(t,e,i,r,n,s){var c,h,u,g=this.mouse?0:a.indexOf(l.getPointerId(t)),m={};for("doubletap"===s?m=t:(l.extend(m,e),e!==t&&l.extend(m,t),m.preventDefault=o,m.stopPropagation=p.prototype.stopPropagation,m.stopImmediatePropagation=p.prototype.stopImmediatePropagation,m.interaction=this,m.timeStamp=(new Date).getTime(),m.originalEvent=e,m.type=s,m.pointerId=l.getPointerId(t),m.pointerType=this.mouse?"mouse":d.supportsPointerEvent?a.isString(t.pointerType)?t.pointerType:[,,"touch","pen","mouse"][t.pointerType]:"touch"),"tap"===s&&(m.dt=m.timeStamp-this.downTimes[g],h=m.timeStamp-this.tapTime,u=!!(this.prevTap&&"doubletap"!==this.prevTap.type&&this.prevTap.target===m.target&&500>h),m["double"]=u,this.tapTime=m.timeStamp),c=0;cs;s++){var l=i[s],c=r[s],p=n(l.getAction(t,e,this,c),l);if(p&&a.withinInteractionLimit(l,c,p))return this.target=l,this.element=c,p}},setSnapping:function(t,e){var i,r,n,s=this.target.options[this.prepared.name].snap,o=[];if(e=e||this.snapStatus,e.useStatusXY)r={x:e.x,y:e.y};else{var c=a.getOriginXY(this.target,this.element);r=l.extend({},t),r.x-=c.x,r.y-=c.y}e.realX=r.x,e.realY=r.y,r.x=r.x-this.inertiaStatus.resumeDx,r.y=r.y-this.inertiaStatus.resumeDy;for(var p=s.targets?s.targets.length:0,h=0;hn;n++)i=a.isFunction(s.targets[n])?s.targets[n](d.x,d.y,this):s.targets[n],i&&o.push({x:a.isNumber(i.x)?i.x+this.snapOffsets[h].x:d.x,y:a.isNumber(i.y)?i.y+this.snapOffsets[h].y:d.y,range:a.isNumber(i.range)?i.range:s.range})}var u={target:null,inRange:!1,distance:0,range:0,dx:0,dy:0};for(n=0,p=o.length;p>n;n++){i=o[n];var g=i.range,m=i.x-r.x,v=i.y-r.y,f=l.hypot(m,v),y=g>=f;g===1/0&&u.inRange&&u.range!==1/0&&(y=!1),(!u.target||(y?u.inRange&&g!==1/0?f/go.innerWidth-a.autoScroll.margin,r=t.clientY>o.innerHeight-a.autoScroll.margin;else{var l=a.getElementRect(o);n=t.clientXl.right-a.autoScroll.margin,r=t.clientY>l.bottom-a.autoScroll.margin}a.autoScroll.x=i?1:n?-1:0,a.autoScroll.y=r?1:e?-1:0,a.autoScroll.isScrolling||(a.autoScroll.margin=s.margin,a.autoScroll.speed=s.speed,a.autoScroll.start(this))}},_updateEventTargets:function(t,e){this._eventTarget=t,this._curEventTarget=e}},e.exports=r},{"./InteractEvent":2,"./scope":6,"./utils":13,"./utils/browser":8,"./utils/events":10}],4:[function(t,e,i){"use strict";var r=t("./utils/raf"),n=t("./utils/window").getWindow,s=t("./utils/isType").isWindow,o={interaction:null,i:null,x:0,y:0,isScrolling:!1,prevTime:0,start:function(t){o.isScrolling=!0,r.cancel(o.i),o.interaction=t,o.prevTime=(new Date).getTime(),o.i=r.request(o.scroll)},stop:function(){o.isScrolling=!1,r.cancel(o.i)},scroll:function(){var t=o.interaction.target.options[o.interaction.prepared.name].autoScroll,e=t.container||n(o.interaction.element),i=(new Date).getTime(),a=(i-o.prevTime)/1e3,l=t.speed*a;l>=1&&(s(e)?e.scrollBy(o.x*l,o.y*l):e&&(e.scrollLeft+=o.x*l,e.scrollTop+=o.y*l),o.prevTime=i),o.isScrolling&&(r.cancel(o.i),o.i=r.request(o.scroll))}};e.exports=o},{"./utils/isType":14,"./utils/raf":17,"./utils/window":18}],5:[function(t,e,i){"use strict";e.exports={base:{accept:null,actionChecker:null,styleCursor:!0,preventDefault:"auto",origin:{x:0,y:0},deltaSource:"page",allowFrom:null,ignoreFrom:null,_context:t("./utils/domObjects").document,dropChecker:null},drag:{enabled:!1,manualStart:!0,max:1/0,maxPerElement:1,snap:null,restrict:null,inertia:null,autoScroll:null,axis:"xy"},drop:{enabled:!1,accept:null,overlap:"pointer"},resize:{enabled:!1,manualStart:!1,max:1/0,maxPerElement:1,snap:null,restrict:null,inertia:null,autoScroll:null,square:!1,axis:"xy",margin:0/0,edges:null,invert:"none"},gesture:{manualStart:!1,enabled:!1,max:1/0,maxPerElement:1,restrict:null},perAction:{manualStart:!1,max:1/0,maxPerElement:1,snap:{enabled:!1,endOnly:!1,range:1/0,targets:null,offsets:null,relativePoints:null},restrict:{enabled:!1,endOnly:!1},autoScroll:{enabled:!1,container:null,margin:60,speed:300},inertia:{enabled:!1,resistance:10,minSpeed:100,endSpeed:10,allowResume:!0,zeroResumeDelta:!0,smoothEndDuration:300}},_holdDuration:600}},{"./utils/domObjects":9}],6:[function(t,e,i){"use strict"; + +var r={},n=t("./utils/extend");n(r,t("./utils/window")),n(r,t("./utils/domObjects")),n(r,t("./utils/arr.js")),n(r,t("./utils/isType")),e.exports=r},{"./utils/arr.js":7,"./utils/domObjects":9,"./utils/extend":11,"./utils/isType":14,"./utils/window":18}],7:[function(t,e,i){"use strict";function r(t,e){for(var i=0,r=t.length;r>i;i++)if(t[i]===e)return i;return-1}function n(t,e){return-1!==r(t,e)}e.exports={indexOf:r,contains:n}},{}],8:[function(t,e,i){"use strict";var r=t("./window"),n=t("./domObjects"),s={supportsTouch:!!("ontouchstart"in r||r.window.DocumentTouch&&n.document instanceof r.DocumentTouch),supportsPointerEvent:!!n.PointerEvent,isOperaMobile:"Opera"===navigator.appName&&s.supportsTouch&&navigator.userAgent.match("Presto"),isIOS7orLower:/iP(hone|od|ad)/.test(navigator.platform)&&/OS [1-7][^\d]/.test(navigator.appVersion),isIe9OrOlder:n.document.all&&!r.window.atob,prefixedMatchesSelector:"matches"in Element.prototype?"matches":"webkitMatchesSelector"in Element.prototype?"webkitMatchesSelector":"mozMatchesSelector"in Element.prototype?"mozMatchesSelector":"oMatchesSelector"in Element.prototype?"oMatchesSelector":"msMatchesSelector"};e.exports=s},{"./domObjects":9,"./window":18}],9:[function(t,e,i){"use strict";var r={},n=t("./window").window,s=function(){};r.document=n.document,r.DocumentFragment=n.DocumentFragment||s,r.SVGElement=n.SVGElement||s,r.SVGSVGElement=n.SVGSVGElement||s,r.SVGElementInstance=n.SVGElementInstance||s,r.HTMLElement=n.HTMLElement||n.Element,r.PointerEvent=n.PointerEvent||n.MSPointerEvent,e.exports=r},{"./window":18}],10:[function(t,e,i){"use strict";function r(t,e,i,r){var n=c(v,t),l=f[n];if(l||(l={events:{},typeCount:0},n=v.push(t)-1,f.push(l),y.push(d?{supplied:[],wrapped:[],useCount:[]}:null)),l.events[e]||(l.events[e]=[],l.typeCount++),!p(l.events[e],i)){var g;if(d){var x=y[n],E=c(x.supplied,i),w=x.wrapped[E]||function(e){e.immediatePropagationStopped||(e.target=e.srcElement,e.currentTarget=t,e.preventDefault=e.preventDefault||s,e.stopPropagation=e.stopPropagation||o,e.stopImmediatePropagation=e.stopImmediatePropagation||a,/mouse|click/.test(e.type)&&(e.pageX=e.clientX+h(t).document.documentElement.scrollLeft,e.pageY=e.clientY+h(t).document.documentElement.scrollTop),i(e))};g=t[u](m+e,w,!!r),-1===E?(x.supplied.push(i),x.wrapped.push(w),x.useCount.push(1)):x.useCount[E]++}else g=t[u](e,i,!!r);return l.events[e].push(i),g}}function n(t,e,i,r){var s,o,a,l=c(v,t),p=f[l],h=i;if(p&&p.events)if(d&&(o=y[l],a=c(o.supplied,i),h=o.wrapped[a]),"all"!==e){if(p.events[e]){var u=p.events[e].length;if("all"===i){for(s=0;u>s;s++)n(t,e,p.events[e][s],!!r);return}for(s=0;u>s;s++)if(p.events[e][s]===i){t[g](m+e,h,!!r),p.events[e].splice(s,1),d&&o&&(o.useCount[a]--,0===o.useCount[a]&&(o.supplied.splice(a,1),o.wrapped.splice(a,1),o.useCount.splice(a,1)));break}p.events[e]&&0===p.events[e].length&&(p.events[e]=null,p.typeCount--)}p.typeCount||(f.splice(l,1),v.splice(l,1),y.splice(l,1))}else for(e in p.events)p.events.hasOwnProperty(e)&&n(t,e,"all")}function s(){this.returnValue=!1}function o(){this.cancelBubble=!0}function a(){this.cancelBubble=!0,this.immediatePropagationStopped=!0}var l=t("./arr"),c=l.indexOf,p=l.contains,h=t("./window").getWindow,d="attachEvent"in window&&!("addEventListener"in window),u=d?"attachEvent":"addEventListener",g=d?"detachEvent":"removeEventListener",m=d?"on":"",v=[],f=[],y=[];e.exports={add:r,remove:n,useAttachEvent:d,_elements:v,_targets:f,_attachedListeners:y}},{"./arr":7,"./window":18}],11:[function(t,e,i){"use strict";e.exports=function(t,e){for(var i in e)t[i]=e[i];return t}},{}],12:[function(t,e,i){"use strict";e.exports=function(t,e){return Math.sqrt(t*t+e*e)}},{}],13:[function(t,e,i){"use strict";var r=e.exports,n=t("./extend"),s=t("./window");r.blank=function(){},r.warnOnce=function(t,e){var i=!1;return function(){return i||(s.window.console.warn(e),i=!0),t.apply(this,arguments)}},r.extend=n,r.hypot=t("./hypot"),r.raf=t("./raf"),r.browser=t("./browser"),n(r,t("./arr")),n(r,t("./isType")),n(r,t("./pointerUtils"))},{"./arr":7,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./pointerUtils":16,"./raf":17,"./window":18}],14:[function(t,e,i){"use strict";var r=t("./window"),n=t("./domObjects"),s={isElement:function(t){if(!t||"object"!=typeof t)return!1;var e=r.getWindow(t)||r.window;return/object|function/.test(typeof e.Element)?t instanceof e.Element:1===t.nodeType&&"string"==typeof t.nodeName},isArray:null,isWindow:t("./isWindow"),isDocFrag:function(t){return!!t&&t instanceof n.DocumentFragment},isObject:function(t){return!!t&&"object"==typeof t},isFunction:function(t){return"function"==typeof t},isNumber:function(t){return"number"==typeof t},isBool:function(t){return"boolean"==typeof t},isString:function(t){return"string"==typeof t}};s.isArray=function(t){return s.isObject(t)&&"undefined"!=typeof t.length&&s.isFunction(t.splice)},e.exports=s},{"./domObjects":9,"./isWindow":15,"./window":18}],15:[function(t,e,i){"use strict";e.exports=function(t){return!(!t||!t.Window)&&t instanceof t.Window}},{}],16:[function(t,e,i){"use strict";var r={},n={},s=t("./window"),o=t("./hypot"),a=t("./extend"),l=t("./browser"),c=t("./isType"),p=t("../InteractEvent");r.copyCoords=function(t,e){t.page=t.page||{},t.page.x=e.page.x,t.page.y=e.page.y,t.client=t.client||{},t.client.x=e.client.x,t.client.y=e.client.y,t.timeStamp=e.timeStamp},r.setEventXY=function(t,e,i){e||(e=i.pointerIds.length>1?r.touchAverage(i.pointers):i.pointers[0]),r.getPageXY(e,n,i),t.page.x=n.x,t.page.y=n.y,r.getClientXY(e,n,i),t.client.x=n.x,t.client.y=n.y,t.timeStamp=(new Date).getTime()},r.setEventDeltas=function(t,e,i){t.page.x=i.page.x-e.page.x,t.page.y=i.page.y-e.page.y,t.client.x=i.client.x-e.client.x,t.client.y=i.client.y-e.client.y,t.timeStamp=(new Date).getTime()-e.timeStamp;var r=Math.max(t.timeStamp/1e3,.001);t.page.speed=o(t.page.x,t.page.y)/r,t.page.vx=t.page.x/r,t.page.vy=t.page.y/r,t.client.speed=o(t.client.x,t.page.y)/r,t.client.vx=t.client.x/r,t.client.vy=t.client.y/r},r.getXY=function(t,e,i){return i=i||{},t=t||"page",i.x=e[t+"X"],i.y=e[t+"Y"],i},r.getPageXY=function(t,e,i){return e=e||{},t instanceof p?/inertiastart/.test(t.type)?(i=i||t.interaction,a(e,i.inertiaStatus.upCoords.page),e.x+=i.inertiaStatus.sx,e.y+=i.inertiaStatus.sy):(e.x=t.pageX,e.y=t.pageY):l.isOperaMobile?(r.getXY("screen",t,e),e.x+=s.window.scrollX,e.y+=s.window.scrollY):r.getXY("page",t,e),e},r.getClientXY=function(t,e,i){return e=e||{},t instanceof p?/inertiastart/.test(t.type)?(a(e,i.inertiaStatus.upCoords.client),e.x+=i.inertiaStatus.sx,e.y+=i.inertiaStatus.sy):(e.x=t.clientX,e.y=t.clientY):r.getXY(l.isOperaMobile?"screen":"client",t,e),e},r.getPointerId=function(t){return c.isNumber(t.pointerId)?t.pointerId:t.identifier},e.exports=r},{"../InteractEvent":2,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./window":18}],17:[function(t,e,i){"use strict";for(var r,n,s=0,o=["ms","moz","webkit","o"],a=0;a1?z(i.pointers):i.pointers[0]),f(e,be,i),t.page.x=be.x,t.page.y=be.y,y(e,be,i),t.client.x=be.x,t.client.y=be.y,t.timeStamp=(new Date).getTime()}function v(t,e,i){t.page.x=i.page.x-e.page.x,t.page.y=i.page.y-e.page.y,t.client.x=i.client.x-e.client.x,t.client.y=i.client.y-e.client.y,t.timeStamp=(new Date).getTime()-e.timeStamp;var r=Math.max(t.timeStamp/1e3,.001);t.page.speed=Se(t.page.x,t.page.y)/r,t.page.vx=t.page.x/r,t.page.vy=t.page.y/r,t.client.speed=Se(t.client.x,t.page.y)/r,t.client.vx=t.client.x/r,t.client.vy=t.client.y/r}function m(t,e,i){return i=i||{},t=t||"page",i.x=e[t+"X"],i.y=e[t+"Y"],i}function f(t,e,i){return e=e||{},t instanceof B?/inertiastart/.test(t.type)?(i=i||t.interaction,d(e,i.inertiaStatus.upCoords.page),e.x+=i.inertiaStatus.sx,e.y+=i.inertiaStatus.sy):(e.x=t.pageX,e.y=t.pageY):He?(m("screen",t,e),e.x+=ue.scrollX,e.y+=ue.scrollY):m("page",t,e),e}function y(t,e,i){return e=e||{},t instanceof B?/inertiastart/.test(t.type)?(d(e,i.inertiaStatus.upCoords.client),e.x+=i.inertiaStatus.sx,e.y+=i.inertiaStatus.sy):(e.x=t.clientX,e.y=t.clientY):m(He?"screen":"client",t,e),e}function x(t){return t=t||ue,{x:t.scrollX||t.document.documentElement.scrollLeft,y:t.scrollY||t.document.documentElement.scrollTop}}function E(t){return h(t.pointerId)?t.pointerId:t.identifier}function S(t){return t instanceof ye?t.correspondingUseElement:t}function b(t){if(r(t))return t;var e=t.ownerDocument||t;return e.defaultView||e.parentWindow||ue}function w(t){var e=We?{x:0,y:0}:x(b(t)),i=t instanceof me?t.getBoundingClientRect():t.getClientRects()[0];return i&&{left:i.left+e.x,right:i.right+e.x,top:i.top+e.y,bottom:i.bottom+e.y,width:i.width||i.right-i.left,height:i.heigh||i.bottom-i.top}}function D(t){var e=[];return n(t)?(e[0]=t[0],e[1]=t[1]):"touchend"===t.type?1===t.touches.length?(e[0]=t.touches[0],e[1]=t.changedTouches[0]):0===t.touches.length&&(e[0]=t.changedTouches[0],e[1]=t.changedTouches[1]):(e[0]=t.touches[0],e[1]=t.touches[1]),e}function z(t){var e=D(t);return{pageX:(e[0].pageX+e[1].pageX)/2,pageY:(e[0].pageY+e[1].pageY)/2,clientX:(e[0].clientX+e[1].clientX)/2,clientY:(e[0].clientY+e[1].clientY)/2}}function T(t){if(t.length||t.touches&&t.touches.length>1){var e=D(t),i=Math.min(e[0].pageX,e[1].pageX),r=Math.min(e[0].pageY,e[1].pageY),s=Math.max(e[0].pageX,e[1].pageX),n=Math.max(e[0].pageY,e[1].pageY);return{x:i,y:r,left:i,top:r,width:s-i,height:n-r}}}function C(t,e){e=e||Me.deltaSource;var i=e+"X",r=e+"Y",s=D(t),n=s[0][i]-s[1][i],o=s[0][r]-s[1][r];return Se(n,o)}function M(t,e,i){i=i||Me.deltaSource;var r=i+"X",s=i+"Y",n=D(t),o=n[0][r]-n[1][r],a=n[0][s]-n[1][s],p=180*Math.atan(a/o)/Math.PI;if(h(e)){var l=p-e,c=l%360;c>315?p-=360+p/360|0:c>135?p-=180+p/360|0:-315>c?p+=360+p/360|0:-135>c&&(p+=180+p/360|0)}return p}function P(t,e){var r=t?t.options.origin:Me.origin;return"parent"===r?r=k(e):"self"===r?r=t.getRect(e):c(r)&&(r=Y(e,r)||{x:0,y:0}),a(r)&&(r=r(t&&e)),i(r)&&(r=w(r)),r.x="x"in r?r.x:r.left,r.y="y"in r?r.y:r.top,r}function O(t,e,i,r){var s=1-t;return s*s*e+2*s*t*i+t*t*r}function _(t,e,i,r,s,n,o){return{x:O(o,t,i,s),y:O(o,e,r,n)}}function A(t,e,i,r){return t/=r,-i*t*(t-2)+e}function X(t,e){for(;e;){if(e===t)return!0;e=e.parentNode}return!1}function Y(t,e){for(var r=k(t);i(r);){if(pe(r,e))return r;r=k(r)}return null}function k(t){var e=t.parentNode;if(s(e)){for(;(e=e.host)&&s(e););return e}return e}function I(t,e){return t._context===e.ownerDocument||X(t._context,e)}function R(t,e,r){var s=t.options.ignoreFrom;return s&&i(r)?l(s)?le(r,s,e):i(s)?X(s,r):!1:!1}function F(t,e,r){var s=t.options.allowFrom;return s?i(r)?l(s)?le(r,s,e):i(s)?X(s,r):!1:!1:!0}function q(t,e){if(!e)return!1;var i=e.options.drag.axis;return"xy"===t||"xy"===i||i===t}function N(t,e){var i=t.options;return/^resize/.test(e)&&(e="resize"),i[e].snap&&i[e].snap.enabled}function H(t,e){var i=t.options;return/^resize/.test(e)&&(e="resize"),i[e].restrict&&i[e].restrict.enabled}function W(t,e){var i=t.options;return/^resize/.test(e)&&(e="resize"),i[e].autoScroll&&i[e].autoScroll.enabled}function U(t,e,i){for(var r=t.options,s=r[i.name].max,n=r[i.name].maxPerElement,o=0,a=0,h=0,p=0,l=ze.length;l>p;p++){var c=ze[p],d=c.prepared.name,u=c.interacting();if(u){if(o++,o>=ke)return!1;if(c.target===t){if(a+=d===i.name|0,a>=s)return!1;if(c.element===e&&(h++,d!==i.name||h>=n))return!1}}}return ke>0}function V(t){var e,i,r,s,n,o=t[0],a=o?0:-1,h=[],p=[];for(s=1;ss;s++){r=ze[s];var h=i;if(r.inertiaStatus.active&&r.target.options[r.prepared.name].inertia.allowResume&&r.mouse===o)for(;h;){if(h===r.element)return r.pointers[0]&&r.removePointer(r.pointers[0]),r.addPointer(t),r;h=k(h)}}if(o||!Oe&&!_e){for(s=0;n>s;s++)if(ze[s].mouse&&!ze[s].inertiaStatus.active)return ze[s];for(s=0;n>s;s++)if(ze[s].mouse&&(!/down/.test(e)||!ze[s].inertiaStatus.active))return r;return r=new $,r.mouse=!0,r}for(s=0;n>s;s++)if(he(ze[s].pointerIds,a))return ze[s];if(/up|end|out/i.test(e))return null;for(s=0;n>s;s++)if(r=ze[s],!(r.prepared.name&&!r.target.options.gesture.enabled||r.interacting()||!o&&r.mouse))return r.addPointer(t),r;return new $}function L(t){return function(e){var i,r,s=S(e.path?e.path[0]:e.target),n=S(e.currentTarget);if(Oe&&/touch/.test(e.type))for(Ye=(new Date).getTime(),r=0;r600&&this.timeStamp-t.prevEvent.timeStamp<150){var O=180*Math.atan2(t.prevEvent.velocityY,t.prevEvent.velocityX)/Math.PI,_=22.5;0>O&&(O+=360);var A=O>=135-_&&225+_>O,X=O>=225-_&&315+_>O,Y=!A&&(O>=315-_||45+_>O),k=!X&&O>=45-_&&135+_>O;this.swipe={up:X,down:k,left:A,right:Y,angle:O,speed:t.prevEvent.speed,velocity:{x:t.prevEvent.velocityX,y:t.prevEvent.velocityY}}}}function K(){this.originalEvent.preventDefault()}function j(t){var e="";if("drag"===t.name&&(e=Ie.drag),"resize"===t.name)if(t.axis)e=Ie[t.name+t.axis];else if(t.edges){for(var i="resize",r=["top","bottom","left","right"],s=0;4>s;s++)t.edges[r[s]]&&(i+=r[s]);e=Ie[i]}return e}function J(t,e,r,s,n,o){if(!e)return!1;if(e===!0){var a=h(o.width)?o.width:o.right-o.left,p=h(o.height)?o.height:o.bottom-o.top;if(0>a&&("left"===t?t="right":"right"===t&&(t="left")),0>p&&("top"===t?t="bottom":"bottom"===t&&(t="top")),"left"===t)return r.x<(a>=0?o.left:o.right)+Ae;if("top"===t)return r.y<(p>=0?o.top:o.bottom)+Ae;if("right"===t)return r.x>(a>=0?o.right:o.left)-Ae;if("bottom"===t)return r.y>(p>=0?o.bottom:o.top)-Ae}return i(s)?i(e)?e===s:le(s,e,n):!1}function Q(t,e,i){var r,s=this.getRect(i),n=!1,a=null,h=null,p=d({},e.curCoords.page),l=this.options;if(!s)return null;if(Re.resize&&l.resize.enabled){var c=l.resize;if(r={left:!1,right:!1,top:!1,bottom:!1},o(c.edges)){for(var u in r)r[u]=J(u,c.edges[u],p,e._eventTarget,i,s);r.left=r.left&&!r.right,r.top=r.top&&!r.bottom,n=r.left||r.right||r.top||r.bottom}else{var g="y"!==l.resize.axis&&p.x>s.right-Ae,v="x"!==l.resize.axis&&p.y>s.bottom-Ae;n=g||v,h=(g?"x":"")+(v?"y":"")}}return a=n?"resize":Re.drag&&l.drag.enabled?"drag":null,Re.gesture&&e.pointerIds.length>=2&&!e.dragging&&!e.resizing&&(a="gesture"),a?{name:a,axis:h,edges:r}:null}function Z(t,e){if(!o(t))return null;var i=t.name,r=e.options;return("resize"===i&&r.resize.enabled||"drag"===i&&r.drag.enabled||"gesture"===i&&r.gesture.enabled)&&Re[i]?(("resize"===i||"resizeyx"===i)&&(i="resizexy"),t):null}function te(t,e){var r={},s=Ce[t.type],n=S(t.path?t.path[0]:t.target),o=n;e=e?!0:!1;for(var a in t)r[a]=t[a];for(r.originalEvent=t,r.preventDefault=K;i(o);){for(var h=0;hi;i++)if(t[i]===e)return i;return-1}function he(t,e){return-1!==ae(t,e)}function pe(e,i,r){return de?de(e,i,r):(ue!==t&&(i=i.replace(/\/deep\//g," ")),e[Ue](i))}function le(t,e,r){for(;i(t);){if(pe(t,e))return!0;if(t=k(t),t===r)return pe(t,e)}return!1}var ce,de,ue=function(){var e=t.document.createTextNode("");return e.ownerDocument!==t.document&&"function"==typeof t.wrap&&t.wrap(e)===e?t.wrap(t):t}(),ge=ue.document,ve=ue.DocumentFragment||e,me=ue.SVGElement||e,fe=ue.SVGSVGElement||e,ye=ue.SVGElementInstance||e,xe=ue.HTMLElement||ue.Element,Ee=ue.PointerEvent||ue.MSPointerEvent,Se=Math.hypot||function(t,e){return Math.sqrt(t*t+e*e)},be={},we=[],De=[],ze=[],Te=!1,Ce={},Me={base:{accept:null,actionChecker:null,styleCursor:!0,preventDefault:"auto",origin:{x:0,y:0},deltaSource:"page",allowFrom:null,ignoreFrom:null,_context:ge,dropChecker:null},drag:{enabled:!1,manualStart:!0,max:1/0,maxPerElement:1,snap:null,restrict:null,inertia:null,autoScroll:null,axis:"xy"},drop:{enabled:!1,accept:null,overlap:"pointer"},resize:{enabled:!1,manualStart:!1,max:1/0,maxPerElement:1,snap:null,restrict:null,inertia:null,autoScroll:null,square:!1,axis:"xy",edges:null,invert:"none"},gesture:{manualStart:!1,enabled:!1,max:1/0,maxPerElement:1,restrict:null},perAction:{manualStart:!1,max:1/0,maxPerElement:1,snap:{enabled:!1,endOnly:!1,range:1/0,targets:null,offsets:null,relativePoints:null},restrict:{enabled:!1,endOnly:!1},autoScroll:{enabled:!1,container:null,margin:60,speed:300},inertia:{enabled:!1,resistance:10,minSpeed:100,endSpeed:10,allowResume:!0,zeroResumeDelta:!0,smoothEndDuration:300}},_holdDuration:600},Pe={interaction:null,i:null,x:0,y:0,scroll:function(){var t=Pe.interaction.target.options[Pe.interaction.prepared.name].autoScroll,e=t.container||b(Pe.interaction.element),i=(new Date).getTime(),s=(i-Pe.prevTime)/1e3,n=t.speed*s;n>=1&&(r(e)?e.scrollBy(Pe.x*n,Pe.y*n):e&&(e.scrollLeft+=Pe.x*n,e.scrollTop+=Pe.y*n),Pe.prevTime=i),Pe.isScrolling&&($e(Pe.i),Pe.i=Ve(Pe.scroll))},edgeMove:function(t){for(var e,i,s=!1,n=0;nc.innerWidth-Pe.margin,h=t.clientY>c.innerHeight-Pe.margin;else{var d=w(c);p=t.clientXd.right-Pe.margin,h=t.clientY>d.bottom-Pe.margin}Pe.x=a?1:p?-1:0,Pe.y=h?1:o?-1:0,Pe.isScrolling||(Pe.margin=l.margin,Pe.speed=l.speed,Pe.start(e))}},isScrolling:!1,prevTime:0,start:function(t){Pe.isScrolling=!0,$e(Pe.i),Pe.interaction=t,Pe.prevTime=(new Date).getTime(),Pe.i=Ve(Pe.scroll)},stop:function(){Pe.isScrolling=!1,$e(Pe.i)}},Oe="ontouchstart"in ue||ue.DocumentTouch&&ge instanceof ue.DocumentTouch,_e=!!Ee,Ae=Oe||_e?20:10,Xe=1,Ye=0,ke=1/0,Ie=ge.all&&!ue.atob?{drag:"move",resizex:"e-resize",resizey:"s-resize",resizexy:"se-resize",resizetop:"n-resize",resizeleft:"w-resize",resizebottom:"s-resize",resizeright:"e-resize",resizetopleft:"se-resize",resizebottomright:"se-resize",resizetopright:"ne-resize",resizebottomleft:"ne-resize",gesture:""}:{drag:"move",resizex:"ew-resize",resizey:"ns-resize",resizexy:"nwse-resize",resizetop:"ns-resize",resizeleft:"ew-resize",resizebottom:"ns-resize",resizeright:"ew-resize",resizetopleft:"nwse-resize",resizebottomright:"nwse-resize",resizetopright:"nesw-resize",resizebottomleft:"nesw-resize",gesture:""},Re={drag:!0,resize:!0,gesture:!0},Fe="onmousewheel"in ge?"mousewheel":"wheel",qe=["dragstart","dragmove","draginertiastart","dragend","dragenter","dragleave","dropactivate","dropdeactivate","dropmove","drop","resizestart","resizemove","resizeinertiastart","resizeend","gesturestart","gesturemove","gestureinertiastart","gestureend","down","move","up","cancel","tap","doubletap","hold"],Ne={},He="Opera"==navigator.appName&&Oe&&navigator.userAgent.match("Presto"),We=/iP(hone|od|ad)/.test(navigator.platform)&&/OS [1-7][^\d]/.test(navigator.appVersion),Ue="matches"in Element.prototype?"matches":"webkitMatchesSelector"in Element.prototype?"webkitMatchesSelector":"mozMatchesSelector"in Element.prototype?"mozMatchesSelector":"oMatchesSelector"in Element.prototype?"oMatchesSelector":"msMatchesSelector",Ve=t.requestAnimationFrame,$e=t.cancelAnimationFrame,Ge=function(){function t(t,e,a,d){var u=ae(p,t),g=l[u];if(g||(g={events:{},typeCount:0},u=p.push(t)-1,l.push(g),c.push(n?{supplied:[],wrapped:[],useCount:[]}:null)),g.events[e]||(g.events[e]=[],g.typeCount++),!he(g.events[e],a)){var v;if(n){var m=c[u],f=ae(m.supplied,a),y=m.wrapped[f]||function(e){e.immediatePropagationStopped||(e.target=e.srcElement,e.currentTarget=t,e.preventDefault=e.preventDefault||i,e.stopPropagation=e.stopPropagation||r,e.stopImmediatePropagation=e.stopImmediatePropagation||s,/mouse|click/.test(e.type)&&(e.pageX=e.clientX+b(t).document.documentElement.scrollLeft,e.pageY=e.clientY+b(t).document.documentElement.scrollTop),a(e))};v=t[o](h+e,y,Boolean(d)),-1===f?(m.supplied.push(a),m.wrapped.push(y),m.useCount.push(1)):m.useCount[f]++}else v=t[o](e,a,d||!1);return g.events[e].push(a),v}}function e(t,i,r,s){var o,d,u,g=ae(p,t),v=l[g],m=r;if(v&&v.events)if(n&&(d=c[g],u=ae(d.supplied,r),m=d.wrapped[u]),"all"!==i){if(v.events[i]){var f=v.events[i].length;if("all"===r)for(o=0;f>o;o++)e(t,i,v.events[i][o],Boolean(s));else for(o=0;f>o;o++)if(v.events[i][o]===r){t[a](h+i,m,s||!1),v.events[i].splice(o,1),n&&d&&(d.useCount[u]--,0===d.useCount[u]&&(d.supplied.splice(u,1),d.wrapped.splice(u,1),d.useCount.splice(u,1)));break}v.events[i]&&0===v.events[i].length&&(v.events[i]=null,v.typeCount--)}v.typeCount||(l.splice(g),p.splice(g),c.splice(g))}else for(i in v.events)v.events.hasOwnProperty(i)&&e(t,i,"all")}function i(){this.returnValue=!1}function r(){this.cancelBubble=!0}function s(){this.cancelBubble=!0,this.immediatePropagationStopped=!0}var n="attachEvent"in ue&&!("addEventListener"in ue),o=n?"attachEvent":"addEventListener",a=n?"detachEvent":"removeEventListener",h=n?"on":"",p=[],l=[],c=[];return{add:t,remove:e,useAttachEvent:n,_elements:p,_targets:l,_attachedListeners:c}}();$.prototype={getPageXY:function(t,e){return f(t,e,this)},getClientXY:function(t,e){return y(t,e,this)},setEventXY:function(t,e){return g(t,e,this)},pointerOver:function(t,e,i){function r(t,e){t&&I(t,i)&&!R(t,i,i)&&F(t,i,i)&&pe(i,e)&&(s.push(t),n.push(i))}if(!this.prepared.name&&this.mouse){var s=[],n=[],o=this.element;this.addPointer(t),!this.target||!R(this.target,this.element,i)&&F(this.target,this.element,i)||(this.target=null,this.element=null,this.matches=[],this.matchElements=[]);var a=De.get(i),h=a&&!R(a,i,i)&&F(a,i,i)&&Z(a.getAction(t,this,i),a);h&&!U(a,i,h)&&(h=null),h?(this.target=a,this.element=i,this.matches=[],this.matchElements=[]):(De.forEachSelector(r),this.validateSelector(t,s,n)?(this.matches=s,this.matchElements=n,this.pointerHover(t,e,this.matches,this.matchElements),Ge.add(i,Ee?ce.move:"mousemove",Le.pointerHover)):this.target&&(X(o,i)?(this.pointerHover(t,e,this.matches,this.matchElements),Ge.add(this.element,Ee?ce.move:"mousemove",Le.pointerHover)):(this.target=null,this.element=null,this.matches=[],this.matchElements=[])))}},pointerHover:function(t,e,i,r,s,n){var o=this.target;if(!this.prepared.name&&this.mouse){var a;this.setEventXY(this.curCoords,t),s?a=this.validateSelector(t,s,n):o&&(a=Z(o.getAction(this.pointers[0],this,this.element),this.target)),o&&o.options.styleCursor&&(o._doc.documentElement.style.cursor=a?j(a):"")}else this.prepared.name&&this.checkAndPreventDefault(e,o,this.element)},pointerOut:function(t,e,i){this.prepared.name||(De.get(i)||Ge.remove(i,Ee?ce.move:"mousemove",Le.pointerHover),this.target&&this.target.options.styleCursor&&!this.interacting()&&(this.target._doc.documentElement.style.cursor=""))},selectorDown:function(t,e,r,s){function n(t,e,i){var s=de?i.querySelectorAll(e):void 0;I(t,p)&&!R(t,p,r)&&F(t,p,r)&&pe(p,e,s)&&(a.matches.push(t),a.matchElements.push(p))}var o,a=this,h=Ge.useAttachEvent?d({},e):e,p=r,l=this.addPointer(t);if(this.holdTimers[l]=setTimeout(function(){a.pointerHold(Ge.useAttachEvent?h:t,h,r,s)},Me._holdDuration),this.pointerIsDown=!0,this.inertiaStatus.active&&this.target.selector)for(;i(p);){if(p===this.element&&Z(this.target.getAction(t,this,this.element),this.target).name===this.prepared.name)return $e(this.inertiaStatus.i),this.inertiaStatus.active=!1,void this.collectEventTargets(t,e,r,"down");p=k(p)}if(this.interacting())return void this.collectEventTargets(t,e,r,"down");for(this.setEventXY(this.curCoords,t);i(p)&&!o;)this.matches=[],this.matchElements=[],De.forEachSelector(n),o=this.validateSelector(t,this.matches,this.matchElements),p=k(p);return o?(this.prepared.name=o.name,this.prepared.axis=o.axis,this.prepared.edges=o.edges,this.collectEventTargets(t,e,r,"down"),this.pointerDown(t,e,r,s,o)):(this.downTimes[l]=(new Date).getTime(),this.downTargets[l]=r,this.downEvent=e,d(this.downPointer,t),u(this.prevCoords,this.curCoords),this.pointerWasMoved=!1,void this.collectEventTargets(t,e,r,"down"))},pointerDown:function(t,e,i,r,s){if(!s&&!this.inertiaStatus.active&&this.pointerWasMoved&&this.prepared.name)return void this.checkAndPreventDefault(e,this.target,this.element);this.pointerIsDown=!0;var n,o=this.addPointer(t);if(this.pointerIds.length<2&&!this.target||!this.prepared.name){var a=De.get(r);a&&!R(a,r,i)&&F(a,r,i)&&(n=Z(s||a.getAction(t,this,r),a,i))&&U(a,r,n)&&(this.target=a,this.element=r)}var h=this.target,p=h&&h.options;if(h&&!this.interacting()){if(n=n||Z(s||h.getAction(t,this,r),h,this.element),this.setEventXY(this.startCoords),!n)return;p.styleCursor&&(h._doc.documentElement.style.cursor=j(n)),this.resizeAxes="resize"===n.name?n.axis:null,"gesture"===n&&this.pointerIds.length<2&&(n=null),this.prepared.name=n.name,this.prepared.axis=n.axis,this.prepared.edges=n.edges,this.snapStatus.snappedX=this.snapStatus.snappedY=this.restrictStatus.restrictedX=this.restrictStatus.restrictedY=0/0,this.downTimes[o]=(new Date).getTime(),this.downTargets[o]=i,this.downEvent=e,d(this.downPointer,t),this.setEventXY(this.prevCoords),this.pointerWasMoved=!1,this.checkAndPreventDefault(e,h,this.element)}else this.inertiaStatus.active&&r===this.element&&Z(h.getAction(t,this,this.element),h).name===this.prepared.name&&($e(this.inertiaStatus.i),this.inertiaStatus.active=!1,this.checkAndPreventDefault(e,h,this.element))},setModifications:function(t,e){var i=this.target,r=!0,s=N(i,this.prepared.name)&&(!i.options[this.prepared.name].snap.endOnly||e),n=H(i,this.prepared.name)&&(!i.options[this.prepared.name].restrict.endOnly||e);return s?this.setSnapping(t):this.snapStatus.locked=!1,n?this.setRestriction(t):this.restrictStatus.restricted=!1,s&&this.snapStatus.locked&&!this.snapStatus.changed?r=n&&this.restrictStatus.restricted&&this.restrictStatus.changed:n&&this.restrictStatus.restricted&&!this.restrictStatus.changed&&(r=!1),r},setStartOffsets:function(t,e,i){var r,s,n=e.getRect(i),o=P(e,i),a=e.options[this.prepared.name].snap,h=e.options[this.prepared.name].restrict;n?(this.startOffset.left=this.startCoords.page.x-n.left,this.startOffset.top=this.startCoords.page.y-n.top,this.startOffset.right=n.right-this.startCoords.page.x,this.startOffset.bottom=n.bottom-this.startCoords.page.y,r="width"in n?n.width:n.right-n.left,s="height"in n?n.height:n.bottom-n.top):this.startOffset.left=this.startOffset.top=this.startOffset.right=this.startOffset.bottom=0,this.snapOffsets.splice(0);var p=a&&"startCoords"===a.offset?{x:this.startCoords.page.x-o.x,y:this.startCoords.page.y-o.y}:a&&a.offset||{x:0,y:0};if(n&&a&&a.relativePoints&&a.relativePoints.length)for(var l=0;lXe),h||this.pointerIsDown&&!this.pointerWasMoved||(this.pointerIsDown&&clearTimeout(this.holdTimers[p]),this.collectEventTargets(t,e,r,"move")),this.pointerIsDown){if(h&&this.pointerWasMoved&&!n)return void this.checkAndPreventDefault(e,this.target,this.element);if(v(this.pointerDelta,this.prevCoords,this.curCoords),this.prepared.name){if(this.pointerWasMoved&&(!this.inertiaStatus.active||t instanceof B&&/inertiastart/.test(t.type))){if(!this.interacting()&&(v(this.pointerDelta,this.prevCoords,this.curCoords),"drag"===this.prepared.name)){var l=Math.abs(o),c=Math.abs(a),d=this.target.options.drag.axis,g=l>c?"x":c>l?"y":"xy";if("xy"!==g&&"xy"!==d&&d!==g){this.prepared.name=null;for(var m=r;i(m);){var f=De.get(m);if(f&&f!==this.target&&!f.options.drag.manualStart&&"drag"===f.getAction(this.downPointer,this,m).name&&q(g,f)){this.prepared.name="drag",this.target=f,this.element=m;break}m=k(m)}if(!this.prepared.name){var y=function(t,e,i){var s=de?i.querySelectorAll(e):void 0;if(t!==this.target)return I(t,r)&&!t.options.drag.manualStart&&!R(t,m,r)&&F(t,m,r)&&pe(m,e,s)&&"drag"===t.getAction(this.downPointer,this,m).name&&q(g,t)&&U(t,m,"drag")?t:void 0};for(m=r;i(m);){var x=De.forEachSelector(y);if(x){this.prepared.name="drag",this.target=x,this.element=m;break}m=k(m)}}}}var S=!!this.prepared.name&&!this.interacting();if(S&&(this.target.options[this.prepared.name].manualStart||!U(this.target,this.element,this.prepared)))return void this.stop();if(this.prepared.name&&this.target){S&&this.start(this.prepared,this.target,this.element);var b=this.setModifications(this.curCoords.page,n);(b||S)&&(this.prevEvent=this[this.prepared.name+"Move"](e)),this.checkAndPreventDefault(e,this.target,this.element)}}u(this.prevCoords,this.curCoords),(this.dragging||this.resizing)&&Pe.edgeMove(e)}}},dragStart:function(t){var e=new B(this,t,"drag","start",this.element); -this.dragging=!0,this.target.fire(e),this.activeDrops.dropzones=[],this.activeDrops.elements=[],this.activeDrops.rects=[],this.dynamicDrop||this.setActiveDrops(this.element);var i=this.getDropEvents(t,e);return i.activate&&this.fireActiveDrops(i.activate),e},dragMove:function(t){var e=this.target,i=new B(this,t,"drag","move",this.element),r=this.element,s=this.getDrop(i,r);this.dropTarget=s.dropzone,this.dropElement=s.element;var n=this.getDropEvents(t,i);return e.fire(i),n.leave&&this.prevDropTarget.fire(n.leave),n.enter&&this.dropTarget.fire(n.enter),n.move&&this.dropTarget.fire(n.move),this.prevDropTarget=this.dropTarget,this.prevDropElement=this.dropElement,i},resizeStart:function(t){var e=new B(this,t,"resize","start",this.element);if(this.prepared.edges){var i=this.target.getRect(this.element);if(this.target.options.resize.square){var r=d({},this.prepared.edges);r.top=r.top||r.left&&!r.bottom,r.left=r.left||r.top&&!r.right,r.bottom=r.bottom||r.right&&!r.top,r.right=r.right||r.bottom&&!r.left,this.prepared._squareEdges=r}else this.prepared._squareEdges=null;this.resizeRects={start:i,current:d({},i),restricted:d({},i),previous:d({},i),delta:{left:0,right:0,width:0,top:0,bottom:0,height:0}},e.rect=this.resizeRects.restricted,e.deltaRect=this.resizeRects.delta}return this.target.fire(e),this.resizing=!0,e},resizeMove:function(t){var e=new B(this,t,"resize","move",this.element),i=this.prepared.edges,r=this.target.options.resize.invert,s="reposition"===r||"negate"===r;if(i){var n=e.dx,o=e.dy,a=this.resizeRects.start,h=this.resizeRects.current,p=this.resizeRects.restricted,l=this.resizeRects.delta,c=d(this.resizeRects.previous,p);if(this.target.options.resize.square){var u=i;i=this.prepared._squareEdges,u.left&&u.bottom||u.right&&u.top?o=-n:u.left||u.right?o=n:(u.top||u.bottom)&&(n=o)}if(i.top&&(h.top+=o),i.bottom&&(h.bottom+=o),i.left&&(h.left+=n),i.right&&(h.right+=n),s){if(d(p,h),"reposition"===r){var g;p.top>p.bottom&&(g=p.top,p.top=p.bottom,p.bottom=g),p.left>p.right&&(g=p.left,p.left=p.right,p.right=g)}}else p.top=Math.min(h.top,a.bottom),p.bottom=Math.max(h.bottom,a.top),p.left=Math.min(h.left,a.right),p.right=Math.max(h.right,a.left);p.width=p.right-p.left,p.height=p.bottom-p.top;for(var v in p)l[v]=p[v]-c[v];e.edges=this.prepared.edges,e.rect=p,e.deltaRect=l}return this.target.fire(e),e},gestureStart:function(t){var e=new B(this,t,"gesture","start",this.element);return e.ds=0,this.gesture.startDistance=this.gesture.prevDistance=e.distance,this.gesture.startAngle=this.gesture.prevAngle=e.angle,this.gesture.scale=1,this.gesturing=!0,this.target.fire(e),e},gestureMove:function(t){if(!this.pointerIds.length)return this.prevEvent;var e;return e=new B(this,t,"gesture","move",this.element),e.ds=e.scale-this.gesture.scale,this.target.fire(e),this.gesture.prevAngle=e.angle,this.gesture.prevDistance=e.distance,1/0===e.scale||null===e.scale||void 0===e.scale||isNaN(e.scale)||(this.gesture.scale=e.scale),e},pointerHold:function(t,e,i){this.collectEventTargets(t,e,i,"hold")},pointerUp:function(t,e,i,r){var s=this.mouse?0:ae(this.pointerIds,E(t));clearTimeout(this.holdTimers[s]),this.collectEventTargets(t,e,i,"up"),this.collectEventTargets(t,e,i,"tap"),this.pointerEnd(t,e,i,r),this.removePointer(t)},pointerCancel:function(t,e,i,r){var s=this.mouse?0:ae(this.pointerIds,E(t));clearTimeout(this.holdTimers[s]),this.collectEventTargets(t,e,i,"cancel"),this.pointerEnd(t,e,i,r),this.removePointer(t)},ie8Dblclick:function(t,e,i){this.prevTap&&e.clientX===this.prevTap.clientX&&e.clientY===this.prevTap.clientY&&i===this.prevTap.target&&(this.downTargets[0]=i,this.downTimes[0]=(new Date).getTime(),this.collectEventTargets(t,e,i,"tap"))},pointerEnd:function(t,e,i,r){var s,n=this.target,o=n&&n.options,a=o&&this.prepared.name&&o[this.prepared.name].inertia,h=this.inertiaStatus;if(this.interacting()){if(h.active)return;var p,l,c=(new Date).getTime(),g=!1,v=!1,m=!1,f=N(n,this.prepared.name)&&o[this.prepared.name].snap.endOnly,y=H(n,this.prepared.name)&&o[this.prepared.name].restrict.endOnly,x=0,E=0;if(p=this.dragging?"x"===o.drag.axis?Math.abs(this.pointerDelta.client.vx):"y"===o.drag.axis?Math.abs(this.pointerDelta.client.vy):this.pointerDelta.client.speed:this.pointerDelta.client.speed,g=a&&a.enabled&&"gesture"!==this.prepared.name&&e!==h.startEvent,v=g&&c-this.curCoords.timeStamp<50&&p>a.minSpeed&&p>a.endSpeed,g&&!v&&(f||y)){var S={};S.snap=S.restrict=S,f&&(this.setSnapping(this.curCoords.page,S),S.locked&&(x+=S.dx,E+=S.dy)),y&&(this.setRestriction(this.curCoords.page,S),S.restricted&&(x+=S.dx,E+=S.dy)),(x||E)&&(m=!0)}if(v||m){if(u(h.upCoords,this.curCoords),this.pointers[0]=h.startEvent=l=new B(this,e,this.prepared.name,"inertiastart",this.element),h.t0=c,n.fire(h.startEvent),v){h.vx0=this.pointerDelta.client.vx,h.vy0=this.pointerDelta.client.vy,h.v0=p,this.calcInertia(h);var b,w=d({},this.curCoords.page),D=P(n,this.element);if(w.x=w.x+h.xe-D.x,w.y=w.y+h.ye-D.y,b={useStatusXY:!0,x:w.x,y:w.y,dx:0,dy:0,snap:null},b.snap=b,x=E=0,f){var z=this.setSnapping(this.curCoords.page,b);z.locked&&(x+=z.dx,E+=z.dy)}if(y){var T=this.setRestriction(this.curCoords.page,b);T.restricted&&(x+=T.dx,E+=T.dy)}h.modifiedXe+=x,h.modifiedYe+=E,h.i=Ve(this.boundInertiaFrame)}else h.smoothEnd=!0,h.xe=x,h.ye=E,h.sx=h.sy=0,h.i=Ve(this.boundSmoothEndFrame);return void(h.active=!0)}(f||y)&&this.pointerMove(t,e,i,r,!0)}if(this.dragging){s=new B(this,e,"drag","end",this.element);var C=this.element,M=this.getDrop(s,C);this.dropTarget=M.dropzone,this.dropElement=M.element;var O=this.getDropEvents(e,s);O.leave&&this.prevDropTarget.fire(O.leave),O.enter&&this.dropTarget.fire(O.enter),O.drop&&this.dropTarget.fire(O.drop),O.deactivate&&this.fireActiveDrops(O.deactivate),n.fire(s)}else this.resizing?(s=new B(this,e,"resize","end",this.element),n.fire(s)):this.gesturing&&(s=new B(this,e,"gesture","end",this.element),n.fire(s));this.stop(e)},collectDrops:function(t){var e,r=[],s=[];for(t=t||this.element,e=0;eh;h++){var c=a[h];c!==t&&(r.push(n),s.push(c))}}return{dropzones:r,elements:s}},fireActiveDrops:function(t){var e,i,r,s;for(e=0;e1&&ze.splice(ae(ze,this),1)},inertiaFrame:function(){var t=this.inertiaStatus,e=this.target.options[this.prepared.name].inertia,i=e.resistance,r=(new Date).getTime()/1e3-t.t0;if(re?(t.sx=A(e,0,t.xe,i),t.sy=A(e,0,t.ye,i),this.pointerMove(t.startEvent,t.startEvent),t.i=Ve(this.boundSmoothEndFrame)):(t.sx=t.xe,t.sy=t.ye,this.pointerMove(t.startEvent,t.startEvent),t.active=!1,t.smoothEnd=!1,this.pointerEnd(t.startEvent,t.startEvent))},addPointer:function(t){var e=E(t),i=this.mouse?0:ae(this.pointerIds,e);return-1===i&&(i=this.pointerIds.length),this.pointerIds[i]=e,this.pointers[i]=t,i},removePointer:function(t){var e=E(t),i=this.mouse?0:ae(this.pointerIds,e);-1!==i&&(this.interacting()||this.pointers.splice(i,1),this.pointerIds.splice(i,1),this.downTargets.splice(i,1),this.downTimes.splice(i,1),this.holdTimers.splice(i,1))},recordPointer:function(t){if(!this.inertiaStatus.active){var e=this.mouse?0:ae(this.pointerIds,E(t));-1!==e&&(this.pointers[e]=t)}},collectEventTargets:function(t,e,r,s){function n(t,e,n){var o=de?n.querySelectorAll(e):void 0;t._iEvents[s]&&i(p)&&I(t,p)&&!R(t,p,r)&&F(t,p,r)&&pe(p,e,o)&&(a.push(t),h.push(p))}var o=this.mouse?0:ae(this.pointerIds,E(t));if("tap"!==s||!this.pointerWasMoved&&this.downTargets[o]&&this.downTargets[o]===r){for(var a=[],h=[],p=r;p;)ie.isSet(p)&&ie(p)._iEvents[s]&&(a.push(ie(p)),h.push(p)),De.forEachSelector(n),p=k(p);(a.length||"tap"===s)&&this.firePointers(t,e,r,a,h,s)}},firePointers:function(t,e,i,r,s,n){var o,a,h,p=this.mouse?0:ae(E(t)),c={};for("doubletap"===n?c=t:(d(c,e),e!==t&&d(c,t),c.preventDefault=K,c.stopPropagation=B.prototype.stopPropagation,c.stopImmediatePropagation=B.prototype.stopImmediatePropagation,c.interaction=this,c.timeStamp=(new Date).getTime(),c.originalEvent=e,c.type=n,c.pointerId=E(t),c.pointerType=this.mouse?"mouse":_e?l(t.pointerType)?t.pointerType:[,,"touch","pen","mouse"][t.pointerType]:"touch"),"tap"===n&&(c.dt=c.timeStamp-this.downTimes[p],a=c.timeStamp-this.tapTime,h=!!(this.prevTap&&"doubletap"!==this.prevTap.type&&this.prevTap.target===c.target&&500>a),c.double=h,this.tapTime=c.timeStamp),o=0;or;r++){var n=e[r],o=i[r],a=Z(n.getAction(t,this,o),n);if(a&&U(n,o,a))return this.target=n,this.element=o,a}},setSnapping:function(t,e){var i,r,s,n=this.target.options[this.prepared.name].snap,o=[];if(e=e||this.snapStatus,e.useStatusXY)r={x:e.x,y:e.y};else{var p=P(this.target,this.element);r=d({},t),r.x-=p.x,r.y-=p.y}e.realX=r.x,e.realY=r.y,r.x=r.x-this.inertiaStatus.resumeDx,r.y=r.y-this.inertiaStatus.resumeDy;for(var l=n.targets?n.targets.length:0,c=0;cs;s++)i=a(n.targets[s])?n.targets[s](u.x,u.y,this):n.targets[s],i&&o.push({x:h(i.x)?i.x+this.snapOffsets[c].x:u.x,y:h(i.y)?i.y+this.snapOffsets[c].y:u.y,range:h(i.range)?i.range:n.range})}var g={target:null,inRange:!1,distance:0,range:0,dx:0,dy:0};for(s=0,l=o.length;l>s;s++){i=o[s];var v=i.range,m=i.x-r.x,f=i.y-r.y,y=Se(m,f),x=v>=y;1/0===v&&g.inRange&&1/0!==g.range&&(x=!1),(!g.target||(x?g.inRange&&1/0!==v?y/vKe;Ke++){var Je=Be[Ke];Le[Je]=L(Je)}De.indexOfElement=function(t,e){e=e||ge;for(var i=0;is.left&&l.xs.top&&l.y=s.left&&u<=s.right&&g>=s.top&&g<=s.bottom}if(h(o)){var v=Math.max(0,Math.min(s.right,d.right)-Math.max(s.left,d.left))*Math.max(0,Math.min(s.bottom,d.bottom)-Math.max(s.top,d.top)),m=v/(d.width*d.height);n=m>=o}return this.options.dropChecker&&(n=this.options.dropChecker(t,n,this,r,e,i)),n},dropChecker:function(t){return a(t)?(this.options.dropChecker=t,this):null===t?(delete this.options.getRect,this):this.options.dropChecker},accept:function(t){return i(t)?(this.options.drop.accept=t,this):c(t)?(this.options.drop.accept=t,this):null===t?(delete this.options.drop.accept,this):this.options.drop.accept},resizable:function(t){return o(t)?(this.options.resize.enabled=t.enabled===!1?!1:!0,this.setPerAction("resize",t),this.setOnEvents("resize",t),/^x$|^y$|^xy$/.test(t.axis)?this.options.resize.axis=t.axis:null===t.axis&&(this.options.resize.axis=Me.resize.axis),p(t.square)&&(this.options.resize.square=t.square),this):p(t)?(this.options.resize.enabled=t,this):this.options.resize},squareResize:function(t){return p(t)?(this.options.resize.square=t,this):null===t?(delete this.options.resize.square,this):this.options.resize.square},gesturable:function(t){return o(t)?(this.options.gesture.enabled=t.enabled===!1?!1:!0,this.setPerAction("gesture",t),this.setOnEvents("gesture",t),this):p(t)?(this.options.gesture.enabled=t,this):this.options.gesture},autoScroll:function(t){return o(t)?t=d({actions:["drag","resize"]},t):p(t)&&(t={actions:["drag","resize"],enabled:t}),this.setOptions("autoScroll",t)},snap:function(t){var e=this.setOptions("snap",t);return e===this?this:e.drag},setOptions:function(t,e){var i,r=e&&n(e.actions)?e.actions:["drag"];if(o(e)||p(e)){for(i=0;ii&&!t.immediatePropagationStopped;i++)n=e[i].name,e[i](t);if(a(this[s])&&(n=this[s].name,this[s](t)),t.type in Ne&&(e=Ne[t.type]))for(i=0,r=e.length;r>i&&!t.immediatePropagationStopped;i++)n=e[i].name,e[i](t);return this},on:function(t,e,i){var r;if(l(t)&&-1!==t.search(" ")&&(t=t.trim().split(/ +/)),n(t)){for(r=0;r=0&&(h.selectors[a]!==this.selector||h.contexts[a]!==this._context);a--);-1===a&&(a=h.selectors.length,h.selectors.push(this.selector),h.contexts.push(this._context),h.listeners.push([])),h.listeners[a].push([e,i])}else Ge.add(this._element,t,e,i);return this},off:function(t,e,i){var r;if(l(t)&&-1!==t.search(" ")&&(t=t.trim().split(/ +/)),n(t)){for(r=0;r=0;h--)if(p.selectors[h]===this.selector&&p.contexts[h]===this._context){var d=p.listeners[h];for(r=d.length-1;r>=0;r--){var u=d[r][0],g=d[r][1];if(u===e&&g===i){d.splice(r,1),d.length||(p.selectors.splice(h,1),p.contexts.splice(h,1),p.listeners.splice(h,1),Ge.remove(this._context,t,te),Ge.remove(this._context,t,ee,!0),p.selectors.length||(Ce[t]=null)),c=!0;break}}if(c)break}}else Ge.remove(this._element,t,e,i);return this},set:function(t){o(t)||(t={}),this.options=d({},Me.base);var e,i=["drag","drop","resize","gesture"],r=["draggable","dropzone","resizable","gesturable"],s=d(d({},Me.perAction),t[n]||{});for(e=0;ee;e++){var h=a[e];this.options[h]=Me.base[h],h in t&&this[h](t[h])}return this},unset:function(){if(Ge.remove(this,"all"),l(this.selector))for(var t in Ce)for(var e=Ce[t],i=0;i0;e--)ze[e].stop(t);return ie},ie.dynamicDrop=function(t){return p(t)?(Te=t,ie):Te},ie.pointerMoveTolerance=function(t){return h(t)?(Xe=t,this):Xe},ie.maxInteractions=function(t){return h(t)?(ke=t,this):ke},ie.createSnapGrid=function(t){return function(e,i){var r=0,s=0;o(t.offset)&&(r=t.offset.x,s=t.offset.y);var n=Math.round((e-r)/t.x),a=Math.round((i-s)/t.y),h=n*t.x+r,p=a*t.y+s;return{x:h,y:p,range:t.range}}},oe(ge),Ue in Element.prototype&&a(Element.prototype[Ue])||(de=function(t,e,i){i=i||t.parentNode.querySelectorAll(e);for(var r=0,s=i.length;s>r;r++)if(i[r]===t)return!0;return!1}),function(){for(var e=0,i=["ms","moz","webkit","o"],r=0;r Date: Mon, 22 Jun 2015 20:17:05 +0200 Subject: [PATCH 036/131] Remove unnecessary gulp tasks --- gulp/config.js | 12 ------------ gulp/tasks/images.js | 13 ------------- gulp/tasks/minifyCss.js | 11 ----------- gulp/tasks/production.js | 7 ------- gulp/tasks/sass.js | 18 ------------------ package.json | 4 ---- 6 files changed, 65 deletions(-) delete mode 100644 gulp/tasks/images.js delete mode 100644 gulp/tasks/minifyCss.js delete mode 100644 gulp/tasks/production.js delete mode 100644 gulp/tasks/sass.js diff --git a/gulp/config.js b/gulp/config.js index 65543421c..62820e015 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -8,18 +8,6 @@ module.exports = { baseDir: dest } }, - sass: { - src: src + "/sass/**/*.{sass,scss}", - dest: dest, - settings: { - indentedSyntax: true, // Enable .sass syntax! - imagePath: 'images' // Used by the image-url helper - } - }, - images: { - src: src + "/images/**", - dest: dest + "/images" - }, markup: { src: src + "/htdocs/**", dest: dest diff --git a/gulp/tasks/images.js b/gulp/tasks/images.js deleted file mode 100644 index 439b5dcda..000000000 --- a/gulp/tasks/images.js +++ /dev/null @@ -1,13 +0,0 @@ -var changed = require('gulp-changed'); -var gulp = require('gulp'); -var imagemin = require('gulp-imagemin'); -var config = require('../config').images; -var browserSync = require('browser-sync'); - -gulp.task('images', function() { - return gulp.src(config.src) - .pipe(changed(config.dest)) // Ignore unchanged files - .pipe(imagemin()) // Optimize - .pipe(gulp.dest(config.dest)) - .pipe(browserSync.reload({stream:true})); -}); diff --git a/gulp/tasks/minifyCss.js b/gulp/tasks/minifyCss.js deleted file mode 100644 index 56bfbdc02..000000000 --- a/gulp/tasks/minifyCss.js +++ /dev/null @@ -1,11 +0,0 @@ -var gulp = require('gulp'); -var config = require('../config').production; -var minifyCSS = require('gulp-minify-css'); -var size = require('gulp-filesize'); - -gulp.task('minifyCss', ['sass'], function() { - return gulp.src(config.cssSrc) - .pipe(minifyCSS({keepBreaks:true})) - .pipe(gulp.dest(config.dest)) - .pipe(size()); -}) diff --git a/gulp/tasks/production.js b/gulp/tasks/production.js deleted file mode 100644 index aee8e5dc7..000000000 --- a/gulp/tasks/production.js +++ /dev/null @@ -1,7 +0,0 @@ -var gulp = require('gulp'); - -// Run this to compress all the things! -gulp.task('production', ['karma'], function(){ - // This runs only if the karma tests pass - gulp.start(['markup', 'images', 'minifyCss', 'uglifyJs']) -}); diff --git a/gulp/tasks/sass.js b/gulp/tasks/sass.js deleted file mode 100644 index 036f486d0..000000000 --- a/gulp/tasks/sass.js +++ /dev/null @@ -1,18 +0,0 @@ -var gulp = require('gulp'); -var browserSync = require('browser-sync'); -var sass = require('gulp-sass'); -var sourcemaps = require('gulp-sourcemaps'); -var handleErrors = require('../util/handleErrors'); -var config = require('../config').sass; -var autoprefixer = require('gulp-autoprefixer'); - -gulp.task('sass', function () { - return gulp.src(config.src) - .pipe(sourcemaps.init()) - .pipe(sass(config.settings)) - .on('error', handleErrors) - .pipe(sourcemaps.write()) - .pipe(autoprefixer({ browsers: ['last 2 version'] })) - .pipe(gulp.dest(config.dest)) - .pipe(browserSync.reload({stream:true})); -}); diff --git a/package.json b/package.json index 5b4c1fc41..a8b0b28a5 100644 --- a/package.json +++ b/package.json @@ -47,15 +47,11 @@ "browserify": "^10.2.1", "chai": "^2.3.0", "gulp": "^3.8.11", - "gulp-autoprefixer": "^2.3.0", "gulp-changed": "^1.2.1", "gulp-filesize": "0.0.6", - "gulp-imagemin": "^2.2.1", "gulp-jshint": "^1.11.0", - "gulp-minify-css": "^1.1.1", "gulp-notify": "^2.2.0", "gulp-rename": "^1.2.2", - "gulp-sass": "^2.0.1", "gulp-sourcemaps": "^1.5.2", "gulp-uglify": "^1.2.0", "gulp-util": "^3.0.4", From de0f694dbd07f64eb3142b0d0b3315b93121f49d Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 22 Jun 2015 22:12:19 +0200 Subject: [PATCH 037/131] Add browserify standalone option --- gulp/config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gulp/config.js b/gulp/config.js index 62820e015..cb18a0959 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -21,6 +21,7 @@ module.exports = { dest: dest, outputName: 'interact.js', outputNameMin: 'interact.min.js', + standalone: 'interact', // Additional file extentions to make optional extensions: [] } From c5d7c69ef76fa46c5f4b07d31189178ac7d1227a Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 23 Jun 2015 00:03:53 +0200 Subject: [PATCH 038/131] Revert "clean up module implementation by removing conditional exports" This reverts commit de34cc3e55cb4580220a26e0157a61db9ae4f8c4. The commit negated the intention of commit 642323e - "Return early if no `window` object (eg. Node.js)". --- src/utils/window.js | 49 +++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/utils/window.js b/src/utils/window.js index 0e94ec677..01ccef80b 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -2,39 +2,36 @@ var isWindow = require('./isWindow'); -var isShadowDom = function() { +if (typeof window === 'undefined') { + module.exports.window = undefined; + module.exports.realWindow = undefined; +} +else { + // get wrapped window if using Shadow DOM polyfill + + module.exports.realWindow = window; + // create a TextNode var el = window.document.createTextNode(''); // check if it's wrapped by a polyfill - return el.ownerDocument !== window.document + if (el.ownerDocument !== window.document && typeof window.wrap === 'function' - && window.wrap(el) === el; -}; - -var win = { - - window: undefined, - - realWindow: window, - - getWindow: function getWindow (node) { - if (isWindow(node)) { - return node; - } + && window.wrap(el) === el) { + // return wrapped window + module.exports.window = window.wrap(window); + } - var rootNode = (node.ownerDocument || node); + // no Shadow DOM polyfil or native implementation + module.exports.window = window; +} - return rootNode.defaultView || rootNode.parentWindow || win.window; +module.exports.getWindow = function getWindow (node) { + if (isWindow(node)) { + return node; } -}; -if (typeof window !== 'undefined') { - if (isShadowDom()) { - win.window = window.wrap(window); - } else { - win.window = window; - } -} + var rootNode = (node.ownerDocument || node); -module.exports = win; + return rootNode.defaultView || rootNode.parentWindow || module.exports.window; +}; From 7b0728bb520687c9073d7968d2ad690a968fc68c Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 27 Jun 2015 15:18:46 +0200 Subject: [PATCH 039/131] Move event delegation code into utils/events event.addDelegate(selector, context, eventType, listener, useCapture); Involved moving several methods from scope to various utility modules including the new 'utils/domUtils' module. --- src/Interaction.js | 32 +++--- src/interact.js | 248 +++--------------------------------------- src/utils/browser.js | 6 +- src/utils/domUtils.js | 72 ++++++++++++ src/utils/events.js | 174 ++++++++++++++++++++++++++++- src/utils/index.js | 1 + 6 files changed, 285 insertions(+), 248 deletions(-) create mode 100644 src/utils/domUtils.js diff --git a/src/Interaction.js b/src/Interaction.js index f7a095b91..52968ddea 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -251,7 +251,7 @@ Interaction.prototype = { && scope.inContext(interactable, eventTarget) && !scope.testIgnore(interactable, eventTarget, eventTarget) && scope.testAllow(interactable, eventTarget, eventTarget) - && scope.matchesSelector(eventTarget, selector)) { + && utils.matchesSelector(eventTarget, selector)) { curMatches.push(interactable); curMatchElements.push(eventTarget); @@ -277,7 +277,7 @@ Interaction.prototype = { scope.listeners.pointerHover); } else if (this.target) { - if (scope.nodeContains(prevTargetElement, eventTarget)) { + if (utils.nodeContains(prevTargetElement, eventTarget)) { this.pointerHover(pointer, event, this.matches, this.matchElements); events.add(this.element, scope.PointerEvent? scope.pEventTypes.move : 'mousemove', @@ -372,7 +372,7 @@ Interaction.prototype = { this.collectEventTargets(pointer, event, eventTarget, 'down'); return; } - element = scope.parentElement(element); + element = utils.parentElement(element); } } @@ -383,14 +383,14 @@ Interaction.prototype = { } function pushMatches (interactable, selector, context) { - var elements = scope.ie8MatchesSelector + var elements = browser.useMatchesSelectorPolyfill ? context.querySelectorAll(selector) : undefined; if (scope.inContext(interactable, element) && !scope.testIgnore(interactable, element, eventTarget) && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, elements)) { + && utils.matchesSelector(element, selector, elements)) { that.matches.push(interactable); that.matchElements.push(element); @@ -408,7 +408,7 @@ Interaction.prototype = { scope.interactables.forEachSelector(pushMatches); action = this.validateSelector(pointer, event, this.matches, this.matchElements); - element = scope.parentElement(element); + element = utils.parentElement(element); } if (action) { @@ -729,7 +729,7 @@ Interaction.prototype = { break; } - element = scope.parentElement(element); + element = utils.parentElement(element); } // if there's no drag from element interactables, @@ -738,7 +738,7 @@ Interaction.prototype = { var thisInteraction = this; var getDraggable = function (interactable, selector, context) { - var elements = scope.ie8MatchesSelector + var elements = browser.useMatchesSelectorPolyfill ? context.querySelectorAll(selector) : undefined; @@ -748,7 +748,7 @@ Interaction.prototype = { && !interactable.options.drag.manualStart && !scope.testIgnore(interactable, element, eventTarget) && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, elements) + && utils.matchesSelector(element, selector, elements) && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' && scope.checkAxis(axis, interactable) && scope.withinInteractionLimit(interactable, element, 'drag')) { @@ -769,7 +769,7 @@ Interaction.prototype = { break; } - element = scope.parentElement(element); + element = utils.parentElement(element); } } } @@ -1265,7 +1265,7 @@ Interaction.prototype = { // test the draggable element against the dropzone's accept setting if ((utils.isElement(accept) && accept !== element) || (scope.isString(accept) - && !scope.matchesSelector(element, accept))) { + && !utils.matchesSelector(element, accept))) { continue; } @@ -1638,7 +1638,7 @@ Interaction.prototype = { element = eventTarget; function collectSelectors (interactable, selector, context) { - var els = scope.ie8MatchesSelector + var els = browser.useMatchesSelectorPolyfill ? context.querySelectorAll(selector) : undefined; @@ -1647,7 +1647,7 @@ Interaction.prototype = { && scope.inContext(interactable, element) && !scope.testIgnore(interactable, element, eventTarget) && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, els)) { + && utils.matchesSelector(element, selector, els)) { targets.push(interactable); elements.push(element); @@ -1665,7 +1665,7 @@ Interaction.prototype = { scope.interactables.forEachSelector(collectSelectors); - element = scope.parentElement(element); + element = utils.parentElement(element); } // create the tap event even if there are no listeners so that @@ -1925,13 +1925,13 @@ Interaction.prototype = { if (scope.isString(restriction)) { if (restriction === 'parent') { - restriction = scope.parentElement(this.element); + restriction = utils.parentElement(this.element); } else if (restriction === 'self') { restriction = target.getRect(this.element); } else { - restriction = scope.closest(this.element, restriction); + restriction = utils.closest(this.element, restriction); } if (!restriction) { return status; } diff --git a/src/interact.js b/src/interact.js index b34b95fc9..ed86cc43d 100644 --- a/src/interact.js +++ b/src/interact.js @@ -24,15 +24,6 @@ scope.dynamicDrop = false; - // { - // type: { - // selectors: ['selector', ...], - // contexts : [document, ...], - // listeners: [[listener, useCapture], ...] - // } - // } - scope.delegatedEvents = {}; - scope.defaultOptions = require('./defaultOptions'); // Things related to autoScroll @@ -130,9 +121,6 @@ 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? 'oMatchesSelector': 'msMatchesSelector'; - // will be polyfill function if browser is IE8 - scope.ie8MatchesSelector = null; - // Events wrapper var events = require('./utils/events'); @@ -191,13 +179,13 @@ : scope.defaultOptions.origin; if (origin === 'parent') { - origin = scope.parentElement(element); + origin = utils.parentElement(element); } else if (origin === 'self') { origin = interactable.getRect(element); } else if (scope.trySelector(origin)) { - origin = scope.closest(element, origin) || { x: 0, y: 0 }; + origin = utils.closest(element, origin) || { x: 0, y: 0 }; } if (scope.isFunction(origin)) { @@ -233,46 +221,9 @@ return -c * t*(t-2) + b; }; - scope.nodeContains = function (parent, child) { - while (child) { - if (child === parent) { - return true; - } - - child = child.parentNode; - } - - return false; - }; - - scope.closest = function (child, selector) { - var parent = scope.parentElement(child); - - while (utils.isElement(parent)) { - if (scope.matchesSelector(parent, selector)) { return parent; } - - parent = scope.parentElement(parent); - } - - return null; - }; - - scope.parentElement = function (node) { - var parent = node.parentNode; - - if (scope.isDocFrag(parent)) { - // skip past #shado-root fragments - while ((parent = parent.host) && scope.isDocFrag(parent)) {} - - return parent; - } - - return parent; - }; - scope.inContext = function (interactable, element) { return interactable._context === element.ownerDocument - || scope.nodeContains(interactable._context, element); + || utils.nodeContains(interactable._context, element); }; scope.testIgnore = function (interactable, interactableElement, element) { @@ -284,7 +235,7 @@ return scope.matchesUpTo(element, ignoreFrom, interactableElement); } else if (utils.isElement(ignoreFrom)) { - return scope.nodeContains(ignoreFrom, element); + return utils.nodeContains(ignoreFrom, element); } return false; @@ -301,7 +252,7 @@ return scope.matchesUpTo(element, allowFrom, interactableElement); } else if (utils.isElement(allowFrom)) { - return scope.nodeContains(allowFrom, element); + return utils.nodeContains(allowFrom, element); } return false; @@ -489,51 +440,22 @@ return index; }; - scope.matchesSelector = function (element, selector, nodeList) { - if (scope.ie8MatchesSelector) { - return scope.ie8MatchesSelector(element, selector, nodeList); - } - - // remove /deep/ from selectors if shadowDOM polyfill is used - if (scope.window !== scope.realWindow) { - selector = selector.replace(/\/deep\//g, ' '); - } - - return element[browser.prefixedMatchesSelector](selector); - }; - scope.matchesUpTo = function (element, selector, limit) { while (utils.isElement(element)) { - if (scope.matchesSelector(element, selector)) { + if (utils.matchesSelector(element, selector)) { return true; } - element = scope.parentElement(element); + element = utils.parentElement(element); if (element === limit) { - return scope.matchesSelector(element, selector); + return utils.matchesSelector(element, selector); } } return false; }; - // For IE8's lack of an Element#matchesSelector - // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - if (!(browser.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[browser.prefixedMatchesSelector])) { - scope.ie8MatchesSelector = function (element, selector, elems) { - elems = elems || element.parentNode.querySelectorAll(selector); - - for (var i = 0, len = elems.length; i < len; i++) { - if (elems[i] === element) { - return true; - } - } - - return false; - }; - } - var Interaction = require('./Interaction'); function getInteractionFromPointer (pointer, eventType, eventTarget) { @@ -565,7 +487,7 @@ return interaction; } - element = scope.parentElement(element); + element = utils.parentElement(element); } } } @@ -677,10 +599,6 @@ }); } - function preventOriginalDefault () { - this.originalEvent.preventDefault(); - } - function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { // false, '', undefined, null if (!value) { return false; } @@ -792,56 +710,6 @@ scope.listeners[listenerName] = doOnInteractions(listenerName); } - // bound to the interactable context when a DOM event - // listener is added to a selector interactable - function delegateListener (event, useCapture) { - var fakeEvent = {}, - delegated = scope.delegatedEvents[event.type], - eventTarget = scope.getActualElement(event.path - ? event.path[0] - : event.target), - element = eventTarget; - - useCapture = useCapture? true: false; - - // duplicate the event so that currentTarget can be changed - for (var prop in event) { - fakeEvent[prop] = event[prop]; - } - - fakeEvent.originalEvent = event; - fakeEvent.preventDefault = preventOriginalDefault; - - // climb up document tree looking for selector matches - while (utils.isElement(element)) { - for (var i = 0; i < delegated.selectors.length; i++) { - var selector = delegated.selectors[i], - context = delegated.contexts[i]; - - if (scope.matchesSelector(element, selector) - && scope.nodeContains(context, eventTarget) - && scope.nodeContains(context, element)) { - - var listeners = delegated.listeners[i]; - - fakeEvent.currentTarget = element; - - for (var j = 0; j < listeners.length; j++) { - if (listeners[j][1] === useCapture) { - listeners[j][0](fakeEvent); - } - } - } - } - - element = scope.parentElement(element); - } - } - - function delegateUseCapture (event) { - return delegateListener.call(this, event, true); - } - scope.interactables.indexOfElement = function indexOfElement (element, context) { context = context || scope.document; @@ -2067,40 +1935,7 @@ } // delegated event for selector else if (this.selector) { - if (!scope.delegatedEvents[eventType]) { - scope.delegatedEvents[eventType] = { - selectors: [], - contexts : [], - listeners: [] - }; - - // add delegate listener functions - for (i = 0; i < scope.documents.length; i++) { - events.add(scope.documents[i], eventType, delegateListener); - events.add(scope.documents[i], eventType, delegateUseCapture, true); - } - } - - var delegated = scope.delegatedEvents[eventType], - index; - - for (index = delegated.selectors.length - 1; index >= 0; index--) { - if (delegated.selectors[index] === this.selector - && delegated.contexts[index] === this._context) { - break; - } - } - - if (index === -1) { - index = delegated.selectors.length; - - delegated.selectors.push(this.selector); - delegated.contexts .push(this._context); - delegated.listeners.push([]); - } - - // keep listener and useCapture flag - delegated.listeners[index].push([listener, useCapture]); + events.addDelegate(this.selector, this._context, eventType, listener, useCapture); } else { events.add(this._element, eventType, listener, useCapture); @@ -2163,55 +1998,7 @@ } // delegated event else if (this.selector) { - var delegated = scope.delegatedEvents[eventType], - matchFound = false; - - if (!delegated) { return this; } - - // count from last index of delegated to 0 - for (index = delegated.selectors.length - 1; index >= 0; index--) { - // look for matching selector and context Node - if (delegated.selectors[index] === this.selector - && delegated.contexts[index] === this._context) { - - var listeners = delegated.listeners[index]; - - // each item of the listeners array is an array: [function, useCaptureFlag] - for (i = listeners.length - 1; i >= 0; i--) { - var fn = listeners[i][0], - useCap = listeners[i][1]; - - // check if the listener functions and useCapture flags match - if (fn === listener && useCap === useCapture) { - // remove the listener from the array of listeners - listeners.splice(i, 1); - - // if all listeners for this interactable have been removed - // remove the interactable from the delegated arrays - if (!listeners.length) { - delegated.selectors.splice(index, 1); - delegated.contexts .splice(index, 1); - delegated.listeners.splice(index, 1); - - // remove delegate function from context - events.remove(this._context, eventType, delegateListener); - events.remove(this._context, eventType, delegateUseCapture, true); - - // remove the arrays if they are empty - if (!delegated.selectors.length) { - scope.delegatedEvents[eventType] = null; - } - } - - // only remove one listener - matchFound = true; - break; - } - } - - if (matchFound) { break; } - } - } + events.removeDelegate(this.selector, this._context, eventType, listener, useCapture); } // remove listener from this Interatable's element else { @@ -2307,8 +2094,8 @@ } } - events.remove(this._context, type, delegateListener); - events.remove(this._context, type, delegateUseCapture, true); + events.remove(this._context, type, events.delegateListener); + events.remove(this._context, type, events.delegateUseCapture, true); break; } @@ -2574,8 +2361,8 @@ interact.getTouchAngle = utils.touchAngle; interact.getElementRect = scope.getElementRect; - interact.matchesSelector = scope.matchesSelector; - interact.closest = scope.closest; + interact.matchesSelector = utils.matchesSelector; + interact.closest = utils.closest; /*\ * interact.margin @@ -2736,8 +2523,8 @@ // add delegate event listener for (var eventType in scope.delegatedEvents) { - events.add(doc, eventType, delegateListener); - events.add(doc, eventType, delegateUseCapture, true); + events.add(doc, eventType, events.delegateListener); + events.add(doc, eventType, events.delegateUseCapture, true); } if (scope.PointerEvent) { @@ -2813,6 +2600,7 @@ } scope.documents.push(doc); + events.documents.push(doc); } listenToDocument(scope.document); diff --git a/src/utils/browser.js b/src/utils/browser.js index 9a78afe77..6b2b28bd9 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -1,6 +1,7 @@ 'use strict'; var win = require('./window'), + isType = require('./isType'), domObjects = require('./domObjects'); var browser = { @@ -27,8 +28,11 @@ var browser = { 'matches': 'webkitMatchesSelector' in Element.prototype? 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? - 'oMatchesSelector': 'msMatchesSelector' + 'oMatchesSelector': 'msMatchesSelector', + useMatchesSelectorPolyfill: false }; +browser.useMatchesSelectorPolyfill = !isType.isFunction(Element.prototype[browser.prefixedMatchesSelector]); + module.exports = browser; diff --git a/src/utils/domUtils.js b/src/utils/domUtils.js new file mode 100644 index 000000000..fb76b6c7c --- /dev/null +++ b/src/utils/domUtils.js @@ -0,0 +1,72 @@ +'use strict'; + +var win = require('./window'), + browser = require('./browser'), + isType = require('./isType'); + +var domUtils = { + nodeContains: function (parent, child) { + while (child) { + if (child === parent) { + return true; + } + + child = child.parentNode; + } + + return false; + }, + + closest: function (child, selector) { + var parent = domUtils.parentElement(child); + + while (isType.isElement(parent)) { + if (domUtils.matchesSelector(parent, selector)) { return parent; } + + parent = domUtils.parentElement(parent); + } + + return null; + }, + + parentElement: function (node) { + var parent = node.parentNode; + + if (isType.isDocFrag(parent)) { + // skip past #shado-root fragments + while ((parent = parent.host) && isType.isDocFrag(parent)) {} + + return parent; + } + + return parent; + }, + + // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified + matchesSelectorPolyfill: (browser.useMatchesSelectorPolyfill? function (element, selector, elems) { + elems = elems || element.parentNode.querySelectorAll(selector); + + for (var i = 0, len = elems.length; i < len; i++) { + if (elems[i] === element) { + return true; + } + } + + return false; + } : null), + + matchesSelector: function (element, selector, nodeList) { + if (browser.useMatchesSelectorPolyfill) { + return domUtils.matchesSelectorPolyfill(element, selector, nodeList); + } + + // remove /deep/ from selectors if shadowDOM polyfill is used + if (win.window !== win.realWindow) { + selector = selector.replace(/\/deep\//g, ' '); + } + + return element[browser.prefixedMatchesSelector](selector); + }, +}; + +module.exports = domUtils; diff --git a/src/utils/events.js b/src/utils/events.js index 4f7767e15..6669a510d 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -1,6 +1,9 @@ 'use strict'; var arr = require('./arr'), + isType = require('./isType'), + domObjects = require('./domObjects'), + domUtils = require('./domUtils'), indexOf = arr.indexOf, contains = arr.contains, getWindow = require('./window').getWindow, @@ -12,7 +15,18 @@ var arr = require('./arr'), elements = [], targets = [], - attachedListeners = []; + attachedListeners = [], + + // { + // type: { + // selectors: ['selector', ...], + // contexts : [document, ...], + // listeners: [[listener, useCapture], ...] + // } + // } + delegatedEvents = {}, + documents = []; + function add (element, type, listener, useCapture) { var elementIndex = indexOf(elements, element), @@ -152,10 +166,154 @@ function remove (element, type, listener, useCapture) { } } +function addDelegate (selector, context, type, listener, useCapture) { + if (!delegatedEvents[type]) { + delegatedEvents[type] = { + selectors: [], + contexts : [], + listeners: [] + }; + + // add delegate listener functions + for (var i = 0; i < documents.length; i++) { + add(documents[i], type, delegateListener); + add(documents[i], type, delegateUseCapture, true); + } + } + + var delegated = delegatedEvents[type], + index; + + for (index = delegated.selectors.length - 1; index >= 0; index--) { + if (delegated.selectors[index] === selector + && delegated.contexts[index] === context) { + break; + } + } + + if (index === -1) { + index = delegated.selectors.length; + + delegated.selectors.push(selector); + delegated.contexts .push(context); + delegated.listeners.push([]); + } + + // keep listener and useCapture flag + delegated.listeners[index].push([listener, useCapture]); +} + +function removeDelegate (selector, context, type, listener, useCapture) { + var delegated = delegatedEvents[type], + matchFound = false, + index; + + if (!delegated) { return; } + + // count from last index of delegated to 0 + for (index = delegated.selectors.length - 1; index >= 0; index--) { + // look for matching selector and context Node + if (delegated.selectors[index] === selector + && delegated.contexts[index] === context) { + + var listeners = delegated.listeners[index]; + + // each item of the listeners array is an array: [function, useCaptureFlag] + for (var i = listeners.length - 1; i >= 0; i--) { + var fn = listeners[i][0], + useCap = listeners[i][1]; + + // check if the listener functions and useCapture flags match + if (fn === listener && useCap === useCapture) { + // remove the listener from the array of listeners + listeners.splice(i, 1); + + // if all listeners for this interactable have been removed + // remove the interactable from the delegated arrays + if (!listeners.length) { + delegated.selectors.splice(index, 1); + delegated.contexts .splice(index, 1); + delegated.listeners.splice(index, 1); + + // remove delegate function from context + remove(context, type, delegateListener); + remove(context, type, delegateUseCapture, true); + + // remove the arrays if they are empty + if (!delegated.selectors.length) { + delegatedEvents[type] = null; + } + } + + // only remove one listener + matchFound = true; + break; + } + } + + if (matchFound) { break; } + } + } +} + +// bound to the interactable context when a DOM event +// listener is added to a selector interactable +function delegateListener (event, useCapture) { + var fakeEvent = {}, + delegated = delegatedEvents[event.type], + eventTarget = getActualElement(event.path + ? event.path[0] + : event.target), + element = eventTarget; + + useCapture = useCapture? true: false; + + // duplicate the event so that currentTarget can be changed + for (var prop in event) { + fakeEvent[prop] = event[prop]; + } + + fakeEvent.originalEvent = event; + fakeEvent.preventDefault = preventOriginalDefault; + + // climb up document tree looking for selector matches + while (isType.isElement(element)) { + for (var i = 0; i < delegated.selectors.length; i++) { + var selector = delegated.selectors[i], + context = delegated.contexts[i]; + + if (domUtils.matchesSelector(element, selector) + && domUtils.nodeContains(context, eventTarget) + && domUtils.nodeContains(context, element)) { + + var listeners = delegated.listeners[i]; + + fakeEvent.currentTarget = element; + + for (var j = 0; j < listeners.length; j++) { + if (listeners[j][1] === useCapture) { + listeners[j][0](fakeEvent); + } + } + } + } + + element = domUtils.parentElement(element); + } +} + +function delegateUseCapture (event) { + return delegateListener.call(this, event, true); +} + function preventDef () { this.returnValue = false; } +function preventOriginalDefault () { + this.originalEvent.preventDefault(); +} + function stopProp () { this.cancelBubble = true; } @@ -165,9 +323,23 @@ function stopImmProp () { this.immediatePropagationStopped = true; } +function getActualElement (element) { + return (element instanceof domObjects.SVGElementInstance + ? element.correspondingUseElement + : element); +} + module.exports = { add: add, remove: remove, + + addDelegate: addDelegate, + removeDelegate: removeDelegate, + + delegateListener: delegateListener, + delegateUseCapture: delegateUseCapture, + documents: documents, + useAttachEvent: useAttachEvent, _elements: elements, diff --git a/src/utils/index.js b/src/utils/index.js index 4368741ad..3193aec78 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -26,4 +26,5 @@ utils.browser = require('./browser'); extend(utils, require('./arr')); extend(utils, require('./isType')); +extend(utils, require('./domUtils')); extend(utils, require('./pointerUtils')); From 16b1772e268b89578100ff49ae95ac82e5d03921 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 27 Jun 2015 16:24:34 +0200 Subject: [PATCH 040/131] Move functions and to more appropriate modules --- src/Interactable.js | 1448 ++++++++++++++++++++++++++++++++ src/Interaction.js | 209 ++++- src/interact.js | 1833 +---------------------------------------- src/utils/domUtils.js | 154 +++- src/utils/events.js | 12 +- src/utils/index.js | 20 + src/utils/isType.js | 9 +- 7 files changed, 1842 insertions(+), 1843 deletions(-) create mode 100644 src/Interactable.js diff --git a/src/Interactable.js b/src/Interactable.js new file mode 100644 index 000000000..dd95dad72 --- /dev/null +++ b/src/Interactable.js @@ -0,0 +1,1448 @@ +'use strict'; + +var scope = require('./scope'), + utils = require('./utils'), + events = require('./utils/events'); + +/*\ + * Interactable + [ property ] + ** + * Object type returned by @interact +\*/ +function Interactable (element, options) { + this._element = element; + this._iEvents = this._iEvents || {}; + + var _window; + + if (scope.trySelector(element)) { + this.selector = element; + + var context = options && options.context; + + _window = context? scope.getWindow(context) : scope.window; + + if (context && (_window.Node + ? context instanceof _window.Node + : (utils.isElement(context) || context === _window.document))) { + + this._context = context; + } + } + else { + _window = scope.getWindow(element); + + if (utils.isElement(element, _window)) { + + if (scope.PointerEvent) { + events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown ); + events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover); + } + else { + events.add(this._element, 'mousedown' , scope.listeners.pointerDown ); + events.add(this._element, 'mousemove' , scope.listeners.pointerHover); + events.add(this._element, 'touchstart', scope.listeners.pointerDown ); + events.add(this._element, 'touchmove' , scope.listeners.pointerHover); + } + } + } + + this._doc = _window.document; + + if (!scope.contains(scope.documents, this._doc)) { + scope.listenToDocument(this._doc); + } + + scope.interactables.push(this); + + this.set(options); +} + +Interactable.prototype = { + setOnEvents: function (action, phases) { + if (action === 'drop') { + if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } + if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } + if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } + if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } + if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } + if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } + } + else { + action = 'on' + action; + + if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } + if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } + if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } + if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } + } + + return this; + }, + + /*\ + * Interactable.draggable + [ method ] + * + * Gets or sets whether drag actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of drag events + | var isDraggable = interact('ul li').draggable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) + = (object) This Interactable + | interact(element).draggable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // the axis in which the first movement must be + | // for the drag sequence to start + | // 'xy' by default - any direction + | axis: 'x' || 'y' || 'xy', + | + | // max number of drags that can happen concurrently + | // with elements of this Interactable. Infinity by default + | max: Infinity, + | + | // max number of drags that can target the same element+Interactable + | // 1 by default + | maxPerElement: 2 + | }); + \*/ + draggable: function (options) { + if (scope.isObject(options)) { + this.options.drag.enabled = options.enabled === false? false: true; + this.setPerAction('drag', options); + this.setOnEvents('drag', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.drag.axis = options.axis; + } + else if (options.axis === null) { + delete this.options.drag.axis; + } + + return this; + } + + if (scope.isBool(options)) { + this.options.drag.enabled = options; + + return this; + } + + return this.options.drag; + }, + + setPerAction: function (action, options) { + // for all the default per-action options + for (var option in options) { + // if this option exists for this action + if (option in scope.defaultOptions[action]) { + // if the option in the options arg is an object value + if (scope.isObject(options[option])) { + // duplicate the object + this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); + + if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { + this.options[action][option].enabled = options[option].enabled === false? false : true; + } + } + else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) { + this.options[action][option].enabled = options[option]; + } + else if (options[option] !== undefined) { + // or if it's not undefined, do a plain assignment + this.options[action][option] = options[option]; + } + } + } + }, + + /*\ + * Interactable.dropzone + [ method ] + * + * Returns or sets whether elements can be dropped onto this + * Interactable to trigger drop events + * + * Dropzones can receive the following events: + * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends + * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone + * - `dragmove` when a draggable that has entered the dropzone is moved + * - `drop` when a draggable is dropped into this dropzone + * + * Use the `accept` option to allow only elements that match the given CSS selector or element. + * + * Use the `overlap` option to set how drops are checked for. The allowed values are: + * - `'pointer'`, the pointer must be over the dropzone (default) + * - `'center'`, the draggable element's center must be over the dropzone + * - a number from 0-1 which is the `(intersection area) / (draggable area)`. + * e.g. `0.5` for drop to happen when half of the area of the + * draggable is over the dropzone + * + - options (boolean | object | null) #optional The new value to be set. + | interact('.drop').dropzone({ + | accept: '.can-drop' || document.getElementById('single-drop'), + | overlap: 'pointer' || 'center' || zeroToOne + | } + = (boolean | object) The current setting or this Interactable + \*/ + dropzone: function (options) { + if (scope.isObject(options)) { + this.options.drop.enabled = options.enabled === false? false: true; + this.setOnEvents('drop', options); + this.accept(options.accept); + + if (/^(pointer|center)$/.test(options.overlap)) { + this.options.drop.overlap = options.overlap; + } + else if (scope.isNumber(options.overlap)) { + this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); + } + + return this; + } + + if (scope.isBool(options)) { + this.options.drop.enabled = options; + + return this; + } + + return this.options.drop; + }, + + dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) { + var dropped = false; + + // if the dropzone has no rect (eg. display: none) + // call the custom dropChecker or just return false + if (!(rect = rect || this.getRect(dropElement))) { + return (this.options.dropChecker + ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) + : false); + } + + var dropOverlap = this.options.drop.overlap; + + if (dropOverlap === 'pointer') { + var page = utils.getPageXY(pointer), + origin = scope.getOriginXY(draggable, draggableElement), + horizontal, + vertical; + + page.x += origin.x; + page.y += origin.y; + + horizontal = (page.x > rect.left) && (page.x < rect.right); + vertical = (page.y > rect.top ) && (page.y < rect.bottom); + + dropped = horizontal && vertical; + } + + var dragRect = draggable.getRect(draggableElement); + + if (dropOverlap === 'center') { + var cx = dragRect.left + dragRect.width / 2, + cy = dragRect.top + dragRect.height / 2; + + dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; + } + + if (scope.isNumber(dropOverlap)) { + var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) + * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), + overlapRatio = overlapArea / (dragRect.width * dragRect.height); + + dropped = overlapRatio >= dropOverlap; + } + + if (this.options.dropChecker) { + dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); + } + + return dropped; + }, + + /*\ + * Interactable.dropChecker + [ method ] + * + * Gets or sets the function used to check if a dragged element is + * over this Interactable. + * + - checker (function) #optional The function that will be called when checking for a drop + = (Function | Interactable) The checker function or this Interactable + * + * The checker function takes the following arguments: + * + - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag + - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer + - dropped (boolean) The value from the default drop check + - dropzone (Interactable) The dropzone interactable + - dropElement (Element) The dropzone element + - draggable (Interactable) The Interactable being dragged + - draggableElement (Element) The actual element that's being dragged + * + > Usage: + | interact(target) + | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent + | event, // TouchEvent/PointerEvent/MouseEvent + | dropped, // result of the default checker + | dropzone, // dropzone Interactable + | dropElement, // dropzone elemnt + | draggable, // draggable Interactable + | draggableElement) {// draggable element + | + | return dropped && event.target.hasAttribute('allow-drop'); + | } + \*/ + dropChecker: function (checker) { + if (scope.isFunction(checker)) { + this.options.dropChecker = checker; + + return this; + } + if (checker === null) { + delete this.options.getRect; + + return this; + } + + return this.options.dropChecker; + }, + + /*\ + * Interactable.accept + [ method ] + * + * Deprecated. add an `accept` property to the options object passed to + * @Interactable.dropzone instead. + * + * Gets or sets the Element or CSS selector match that this + * Interactable accepts if it is a dropzone. + * + - newValue (Element | string | null) #optional + * If it is an Element, then only that element can be dropped into this dropzone. + * If it is a string, the element being dragged must match it as a selector. + * If it is null, the accept options is cleared - it accepts any element. + * + = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable + \*/ + accept: function (newValue) { + if (utils.isElement(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + // test if it is a valid CSS selector + if (scope.trySelector(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.drop.accept; + + return this; + } + + return this.options.drop.accept; + }, + + /*\ + * Interactable.resizable + [ method ] + * + * Gets or sets whether resize actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of resize elements + | var isResizeable = interact('input[type=text]').resizable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) + = (object) This Interactable + | interact(element).resizable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | edges: { + | top : true, // Use pointer coords to check for resize. + | left : false, // Disable resizing from left edge. + | bottom: '.resize-s',// Resize if pointer target matches selector + | right : handleEl // Resize if pointer target is the given Element + | }, + | + | // a value of 'none' will limit the resize rect to a minimum of 0x0 + | // 'negate' will allow the rect to have negative width/height + | // 'reposition' will keep the width/height positive by swapping + | // the top and bottom edges and/or swapping the left and right edges + | invert: 'none' || 'negate' || 'reposition' + | + | // limit multiple resizes. + | // See the explanation in the @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); + \*/ + resizable: function (options) { + if (scope.isObject(options)) { + this.options.resize.enabled = options.enabled === false? false: true; + this.setPerAction('resize', options); + this.setOnEvents('resize', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.resize.axis = options.axis; + } + else if (options.axis === null) { + this.options.resize.axis = scope.defaultOptions.resize.axis; + } + + if (scope.isBool(options.square)) { + this.options.resize.square = options.square; + } + + return this; + } + if (scope.isBool(options)) { + this.options.resize.enabled = options; + + return this; + } + return this.options.resize; + }, + + /*\ + * Interactable.squareResize + [ method ] + * + * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead + * + * Gets or sets whether resizing is forced 1:1 aspect + * + = (boolean) Current setting + * + * or + * + - newValue (boolean) #optional + = (object) this Interactable + \*/ + squareResize: function (newValue) { + if (scope.isBool(newValue)) { + this.options.resize.square = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.resize.square; + + return this; + } + + return this.options.resize.square; + }, + + /*\ + * Interactable.gesturable + [ method ] + * + * Gets or sets whether multitouch gestures can be performed on the + * Interactable's element + * + = (boolean) Indicates if this can be the target of gesture events + | var isGestureable = interact(element).gesturable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) + = (object) this Interactable + | interact(element).gesturable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // limit multiple gestures. + | // See the explanation in @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); + \*/ + gesturable: function (options) { + if (scope.isObject(options)) { + this.options.gesture.enabled = options.enabled === false? false: true; + this.setPerAction('gesture', options); + this.setOnEvents('gesture', options); + + return this; + } + + if (scope.isBool(options)) { + this.options.gesture.enabled = options; + + return this; + } + + return this.options.gesture; + }, + + /*\ + * Interactable.autoScroll + [ method ] + ** + * Deprecated. Add an `autoscroll` property to the options object + * passed to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets whether dragging and resizing near the edges of the + * window/container trigger autoScroll for this Interactable + * + = (object) Object with autoScroll properties + * + * or + * + - options (object | boolean) #optional + * options can be: + * - an object with margin, distance and interval properties, + * - true or false to enable or disable autoScroll or + = (Interactable) this Interactable + \*/ + autoScroll: function (options) { + if (scope.isObject(options)) { + options = utils.extend({ actions: ['drag', 'resize']}, options); + } + else if (scope.isBool(options)) { + options = { actions: ['drag', 'resize'], enabled: options }; + } + + return this.setOptions('autoScroll', options); + }, + + /*\ + * Interactable.snap + [ method ] + ** + * Deprecated. Add a `snap` property to the options object passed + * to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets if and how action coordinates are snapped. By + * default, snapping is relative to the pointer coordinates. You can + * change this by setting the + * [`elementOrigin`](https://github.com/taye/interact.js/pull/72). + ** + = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled + ** + * or + ** + - options (object | boolean | null) #optional + = (Interactable) this Interactable + > Usage + | interact(document.querySelector('#thing')).snap({ + | targets: [ + | // snap to this specific point + | { + | x: 100, + | y: 100, + | range: 25 + | }, + | // give this function the x and y page coords and snap to the object returned + | function (x, y) { + | return { + | x: x, + | y: (75 + 50 * Math.sin(x * 0.04)), + | range: 40 + | }; + | }, + | // create a function that snaps to a grid + | interact.createSnapGrid({ + | x: 50, + | y: 50, + | range: 10, // optional + | offset: { x: 5, y: 10 } // optional + | }) + | ], + | // do not snap during normal movement. + | // Instead, trigger only one snapped move event + | // immediately before the end event. + | endOnly: true, + | + | relativePoints: [ + | { x: 0, y: 0 }, // snap relative to the top left of the element + | { x: 1, y: 1 }, // and also to the bottom right + | ], + | + | // offset the snap target coordinates + | // can be an object with x/y or 'startCoords' + | offset: { x: 50, y: 50 } + | } + | }); + \*/ + snap: function (options) { + var ret = this.setOptions('snap', options); + + if (ret === this) { return this; } + + return ret.drag; + }, + + setOptions: function (option, options) { + var actions = options && scope.isArray(options.actions) + ? options.actions + : ['drag']; + + var i; + + if (scope.isObject(options) || scope.isBool(options)) { + for (i = 0; i < actions.length; i++) { + var action = /resize/.test(actions[i])? 'resize' : actions[i]; + + if (!scope.isObject(this.options[action])) { continue; } + + var thisOption = this.options[action][option]; + + if (scope.isObject(options)) { + utils.extend(thisOption, options); + thisOption.enabled = options.enabled === false? false: true; + + if (option === 'snap') { + if (thisOption.mode === 'grid') { + thisOption.targets = [ + scope.interact.createSnapGrid(utils.extend({ + offset: thisOption.gridOffset || { x: 0, y: 0 } + }, thisOption.grid || {})) + ]; + } + else if (thisOption.mode === 'anchor') { + thisOption.targets = thisOption.anchors; + } + else if (thisOption.mode === 'path') { + thisOption.targets = thisOption.paths; + } + + if ('elementOrigin' in options) { + thisOption.relativePoints = [options.elementOrigin]; + } + } + } + else if (scope.isBool(options)) { + thisOption.enabled = options; + } + } + + return this; + } + + var ret = {}, + allActions = ['drag', 'resize', 'gesture']; + + for (i = 0; i < allActions.length; i++) { + if (option in scope.defaultOptions[allActions[i]]) { + ret[allActions[i]] = this.options[allActions[i]][option]; + } + } + + return ret; + }, + + + /*\ + * Interactable.inertia + [ method ] + ** + * Deprecated. Add an `inertia` property to the options object passed + * to @Interactable.draggable or @Interactable.resizable instead. + * + * Returns or sets if and how events continue to run after the pointer is released + ** + = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled + ** + * or + ** + - options (object | boolean | null) #optional + = (Interactable) this Interactable + > Usage + | // enable and use default settings + | interact(element).inertia(true); + | + | // enable and use custom settings + | interact(element).inertia({ + | // value greater than 0 + | // high values slow the object down more quickly + | resistance : 16, + | + | // the minimum launch speed (pixels per second) that results in inertia start + | minSpeed : 200, + | + | // inertia will stop when the object slows down to this speed + | endSpeed : 20, + | + | // boolean; should actions be resumed when the pointer goes down during inertia + | allowResume : true, + | + | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy + | zeroResumeDelta: false, + | + | // if snap/restrict are set to be endOnly and inertia is enabled, releasing + | // the pointer without triggering inertia will animate from the release + | // point to the snaped/restricted point in the given amount of time (ms) + | smoothEndDuration: 300, + | + | // an array of action types that can have inertia (no gesture) + | actions : ['drag', 'resize'] + | }); + | + | // reset custom settings and use all defaults + | interact(element).inertia(null); + \*/ + inertia: function (options) { + var ret = this.setOptions('inertia', options); + + if (ret === this) { return this; } + + return ret.drag; + }, + + getAction: function (pointer, event, interaction, element) { + var action = this.defaultActionChecker(pointer, interaction, element); + + if (this.options.actionChecker) { + return this.options.actionChecker(pointer, event, action, this, element, interaction); + } + + return action; + }, + + defaultActionChecker: defaultActionChecker, + + /*\ + * Interactable.actionChecker + [ method ] + * + * Gets or sets the function used to check action to be performed on + * pointerDown + * + - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. + = (Function | Interactable) The checker function or this Interactable + * + | interact('.resize-drag') + | .resizable(true) + | .draggable(true) + | .actionChecker(function (pointer, event, action, interactable, element, interaction) { + | + | if (interact.matchesSelector(event.target, '.drag-handle') { + | // force drag with handle target + | action.name = drag; + | } + | else { + | // resize from the top and right edges + | action.name = 'resize'; + | action.edges = { top: true, right: true }; + | } + | + | return action; + | }); + \*/ + actionChecker: function (checker) { + if (scope.isFunction(checker)) { + this.options.actionChecker = checker; + + return this; + } + + if (checker === null) { + delete this.options.actionChecker; + + return this; + } + + return this.options.actionChecker; + }, + + /*\ + * Interactable.getRect + [ method ] + * + * The default function to get an Interactables bounding rect. Can be + * overridden using @Interactable.rectChecker. + * + - element (Element) #optional The element to measure. + = (object) The object's bounding rectangle. + o { + o top : 0, + o left : 0, + o bottom: 0, + o right : 0, + o width : 0, + o height: 0 + o } + \*/ + getRect: function rectCheck (element) { + element = element || this._element; + + if (this.selector && !(utils.isElement(element))) { + element = this._context.querySelector(this.selector); + } + + return utils.getElementRect(element); + }, + + /*\ + * Interactable.rectChecker + [ method ] + * + * Returns or sets the function used to calculate the interactable's + * element's rectangle + * + - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect + = (function | object) The checker function or this Interactable + \*/ + rectChecker: function (checker) { + if (scope.isFunction(checker)) { + this.getRect = checker; + + return this; + } + + if (checker === null) { + delete this.options.getRect; + + return this; + } + + return this.getRect; + }, + + /*\ + * Interactable.styleCursor + [ method ] + * + * Returns or sets whether the action that would be performed when the + * mouse on the element are checked on `mousemove` so that the cursor + * may be styled appropriately + * + - newValue (boolean) #optional + = (boolean | Interactable) The current setting or this Interactable + \*/ + styleCursor: function (newValue) { + if (scope.isBool(newValue)) { + this.options.styleCursor = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.styleCursor; + + return this; + } + + return this.options.styleCursor; + }, + + /*\ + * Interactable.preventDefault + [ method ] + * + * Returns or sets whether to prevent the browser's default behaviour + * in response to pointer events. Can be set to: + * - `'always'` to always prevent + * - `'never'` to never prevent + * - `'auto'` to let interact.js try to determine what would be best + * + - newValue (string) #optional `true`, `false` or `'auto'` + = (string | Interactable) The current setting or this Interactable + \*/ + preventDefault: function (newValue) { + if (/^(always|never|auto)$/.test(newValue)) { + this.options.preventDefault = newValue; + return this; + } + + if (scope.isBool(newValue)) { + this.options.preventDefault = newValue? 'always' : 'never'; + return this; + } + + return this.options.preventDefault; + }, + + /*\ + * Interactable.origin + [ method ] + * + * Gets or sets the origin of the Interactable's element. The x and y + * of the origin will be subtracted from action event coordinates. + * + - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector + * OR + - origin (Element) #optional An HTML or SVG Element whose rect will be used + ** + = (object) The current origin or this Interactable + \*/ + origin: function (newValue) { + if (scope.trySelector(newValue)) { + this.options.origin = newValue; + return this; + } + else if (scope.isObject(newValue)) { + this.options.origin = newValue; + return this; + } + + return this.options.origin; + }, + + /*\ + * Interactable.deltaSource + [ method ] + * + * Returns or sets the mouse coordinate types used to calculate the + * movement of the pointer. + * + - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work + = (string | object) The current deltaSource or this Interactable + \*/ + deltaSource: function (newValue) { + if (newValue === 'page' || newValue === 'client') { + this.options.deltaSource = newValue; + + return this; + } + + return this.options.deltaSource; + }, + + /*\ + * Interactable.restrict + [ method ] + ** + * Deprecated. Add a `restrict` property to the options object passed to + * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead. + * + * Returns or sets the rectangles within which actions on this + * interactable (after snap calculations) are restricted. By default, + * restricting is relative to the pointer coordinates. You can change + * this by setting the + * [`elementRect`](https://github.com/taye/interact.js/pull/72). + ** + - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self' + = (object) The current restrictions object or this Interactable + ** + | interact(element).restrict({ + | // the rect will be `interact.getElementRect(element.parentNode)` + | drag: element.parentNode, + | + | // x and y are relative to the the interactable's origin + | resize: { x: 100, y: 100, width: 200, height: 200 } + | }) + | + | interact('.draggable').restrict({ + | // the rect will be the selected element's parent + | drag: 'parent', + | + | // do not restrict during normal movement. + | // Instead, trigger only one restricted move event + | // immediately before the end event. + | endOnly: true, + | + | // https://github.com/taye/interact.js/pull/72#issue-41813493 + | elementRect: { top: 0, left: 0, bottom: 1, right: 1 } + | }); + \*/ + restrict: function (options) { + if (!scope.isObject(options)) { + return this.setOptions('restrict', options); + } + + var actions = ['drag', 'resize', 'gesture'], + ret; + + for (var i = 0; i < actions.length; i++) { + var action = actions[i]; + + if (action in options) { + var perAction = utils.extend({ + actions: [action], + restriction: options[action] + }, options); + + ret = this.setOptions('restrict', perAction); + } + } + + return ret; + }, + + /*\ + * Interactable.context + [ method ] + * + * Gets the selector context Node of the Interactable. The default is `window.document`. + * + = (Node) The context Node of this Interactable + ** + \*/ + context: function () { + return this._context; + }, + + _context: scope.document, + + /*\ + * Interactable.ignoreFrom + [ method ] + * + * If the target of the `mousedown`, `pointerdown` or `touchstart` + * event or any of it's parents match the given CSS selector or + * Element, no drag/resize/gesture is started. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements + = (string | Element | object) The current ignoreFrom value or this Interactable + ** + | interact(element, { ignoreFrom: document.getElementById('no-action') }); + | // or + | interact(element).ignoreFrom('input, textarea, a'); + \*/ + ignoreFrom: function (newValue) { + if (scope.trySelector(newValue)) { // CSS selector to match event.target + this.options.ignoreFrom = newValue; + return this; + } + + if (utils.isElement(newValue)) { // specific element + this.options.ignoreFrom = newValue; + return this; + } + + return this.options.ignoreFrom; + }, + + /*\ + * Interactable.allowFrom + [ method ] + * + * A drag/resize/gesture is started only If the target of the + * `mousedown`, `pointerdown` or `touchstart` event or any of it's + * parents match the given CSS selector or Element. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element + = (string | Element | object) The current allowFrom value or this Interactable + ** + | interact(element, { allowFrom: document.getElementById('drag-handle') }); + | // or + | interact(element).allowFrom('.handle'); + \*/ + allowFrom: function (newValue) { + if (scope.trySelector(newValue)) { // CSS selector to match event.target + this.options.allowFrom = newValue; + return this; + } + + if (utils.isElement(newValue)) { // specific element + this.options.allowFrom = newValue; + return this; + } + + return this.options.allowFrom; + }, + + /*\ + * Interactable.element + [ method ] + * + * If this is not a selector Interactable, it returns the element this + * interactable represents + * + = (Element) HTML / SVG Element + \*/ + element: function () { + return this._element; + }, + + /*\ + * Interactable.fire + [ method ] + * + * Calls listeners for the given InteractEvent type bound globally + * and directly to this Interactable + * + - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable + = (Interactable) this Interactable + \*/ + fire: function (iEvent) { + if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) { + return this; + } + + var listeners, + i, + len, + onEvent = 'on' + iEvent.type, + funcName = ''; + + // Interactable#on() listeners + if (iEvent.type in this._iEvents) { + listeners = this._iEvents[iEvent.type]; + + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + funcName = listeners[i].name; + listeners[i](iEvent); + } + } + + // interactable.onevent listener + if (scope.isFunction(this[onEvent])) { + funcName = this[onEvent].name; + this[onEvent](iEvent); + } + + // interact.on() listeners + if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { + + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + funcName = listeners[i].name; + listeners[i](iEvent); + } + } + + return this; + }, + + /*\ + * Interactable.on + [ method ] + * + * Binds a listener for an InteractEvent or DOM event. + * + - eventType (string | array | object) The types of events to listen for + - listener (function) The function to be called on the given event(s) + - useCapture (boolean) #optional useCapture flag for addEventListener + = (object) This Interactable + \*/ + on: function (eventType, listener, useCapture) { + var i; + + if (scope.isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } + + if (scope.isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.on(eventType[i], listener, useCapture); + } + + return this; + } + + if (scope.isObject(eventType)) { + for (var prop in eventType) { + this.on(prop, eventType[prop], listener); + } + + return this; + } + + if (eventType === 'wheel') { + eventType = scope.wheelEvent; + } + + // convert to boolean + useCapture = useCapture? true: false; + + if (scope.contains(scope.eventTypes, eventType)) { + // if this type of event was never bound to this Interactable + if (!(eventType in this._iEvents)) { + this._iEvents[eventType] = [listener]; + } + else { + this._iEvents[eventType].push(listener); + } + } + // delegated event for selector + else if (this.selector) { + events.addDelegate(this.selector, this._context, eventType, listener, useCapture); + } + else { + events.add(this._element, eventType, listener, useCapture); + } + + return this; + }, + + /*\ + * Interactable.off + [ method ] + * + * Removes an InteractEvent or DOM event listener + * + - eventType (string | array | object) The types of events that were listened for + - listener (function) The listener function to be removed + - useCapture (boolean) #optional useCapture flag for removeEventListener + = (object) This Interactable + \*/ + off: function (eventType, listener, useCapture) { + var i; + + if (scope.isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } + + if (scope.isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.off(eventType[i], listener, useCapture); + } + + return this; + } + + if (scope.isObject(eventType)) { + for (var prop in eventType) { + this.off(prop, eventType[prop], listener); + } + + return this; + } + + var eventList, + index = -1; + + // convert to boolean + useCapture = useCapture? true: false; + + if (eventType === 'wheel') { + eventType = scope.wheelEvent; + } + + // if it is an action event type + if (scope.contains(scope.eventTypes, eventType)) { + eventList = this._iEvents[eventType]; + + if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) { + this._iEvents[eventType].splice(index, 1); + } + } + // delegated event + else if (this.selector) { + events.removeDelegate(this.selector, this._context, eventType, listener, useCapture); + } + // remove listener from this Interatable's element + else { + events.remove(this._element, eventType, listener, useCapture); + } + + return this; + }, + + /*\ + * Interactable.set + [ method ] + * + * Reset the options of this Interactable + - options (object) The new settings to apply + = (object) This Interactablw + \*/ + set: function (options) { + if (!scope.isObject(options)) { + options = {}; + } + + this.options = utils.extend({}, scope.defaultOptions.base); + + var i, + len, + actions = ['drag', 'drop', 'resize', 'gesture'], + methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], + perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {}); + + for (i = 0; i < actions.length; i++) { + var action = actions[i]; + + this.options[action] = utils.extend({}, scope.defaultOptions[action]); + + this.setPerAction(action, perActions); + + this[methods[i]](options[action]); + } + + var settings = [ + 'accept', 'actionChecker', 'allowFrom', 'deltaSource', + 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', + 'rectChecker' + ]; + + for (i = 0, len = settings.length; i < len; i++) { + var setting = settings[i]; + + this.options[setting] = scope.defaultOptions.base[setting]; + + if (setting in options) { + this[setting](options[setting]); + } + } + + return this; + }, + + /*\ + * Interactable.unset + [ method ] + * + * Remove this interactable from the list of interactables and remove + * it's drag, drop, resize and gesture capabilities + * + = (object) @interact + \*/ + unset: function () { + events.remove(this._element, 'all'); + + if (!scope.isString(this.selector)) { + events.remove(this, 'all'); + if (this.options.styleCursor) { + this._element.style.cursor = ''; + } + } + else { + // remove delegated events + for (var type in scope.delegatedEvents) { + var delegated = scope.delegatedEvents[type]; + + for (var i = 0; i < delegated.selectors.length; i++) { + if (delegated.selectors[i] === this.selector + && delegated.contexts[i] === this._context) { + + delegated.selectors.splice(i, 1); + delegated.contexts .splice(i, 1); + delegated.listeners.splice(i, 1); + + // remove the arrays if they are empty + if (!delegated.selectors.length) { + scope.delegatedEvents[type] = null; + } + } + + events.remove(this._context, type, events.delegateListener); + events.remove(this._context, type, events.delegateUseCapture, true); + + break; + } + } + } + + this.dropzone(false); + + scope.interactables.splice(scope.indexOf(scope.interactables, this), 1); + + return scope.interact; + } +}; + +function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { + // false, '', undefined, null + if (!value) { return false; } + + // true value, use pointer coords and element rect + if (value === true) { + // if dimensions are negative, "switch" edges + var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left, + height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top; + + if (width < 0) { + if (name === 'left' ) { name = 'right'; } + else if (name === 'right') { name = 'left' ; } + } + if (height < 0) { + if (name === 'top' ) { name = 'bottom'; } + else if (name === 'bottom') { name = 'top' ; } + } + + if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } + if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } + + if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } + if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } + } + + // the remaining checks require an element + if (!utils.isElement(element)) { return false; } + + return utils.isElement(value) + // the value is an element to use as a resize handle + ? value === element + // otherwise check if element matches value as selector + : utils.matchesUpTo(element, value, interactableElement); +} + +function defaultActionChecker (pointer, interaction, element) { + var rect = this.getRect(element), + shouldResize = false, + action = null, + resizeAxes = null, + resizeEdges, + page = utils.extend({}, interaction.curCoords.page), + options = this.options; + + if (!rect) { return null; } + + if (scope.actionIsEnabled.resize && options.resize.enabled) { + var resizeOptions = options.resize; + + resizeEdges = { + left: false, right: false, top: false, bottom: false + }; + + // if using resize.edges + if (scope.isObject(resizeOptions.edges)) { + for (var edge in resizeEdges) { + resizeEdges[edge] = checkResizeEdge(edge, + resizeOptions.edges[edge], + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || scope.margin); + } + + resizeEdges.left = resizeEdges.left && !resizeEdges.right; + resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; + + shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; + } + else { + var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), + bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); + + shouldResize = right || bottom; + resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); + } + } + + action = shouldResize + ? 'resize' + : scope.actionIsEnabled.drag && options.drag.enabled + ? 'drag' + : null; + + if (scope.actionIsEnabled.gesture + && interaction.pointerIds.length >=2 + && !(interaction.dragging || interaction.resizing)) { + action = 'gesture'; + } + + if (action) { + return { + name: action, + axis: resizeAxes, + edges: resizeEdges + }; + } + + return null; +} + +scope.defaultActionChecker = defaultActionChecker; + +module.exports = Interactable; diff --git a/src/Interaction.js b/src/Interaction.js index 52968ddea..6970b64a4 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -242,7 +242,7 @@ Interaction.prototype = { elementInteractable.getAction(pointer, event, this, eventTarget), elementInteractable)); - if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { + if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { elementAction = null; } @@ -459,7 +459,7 @@ Interaction.prototype = { && !scope.testIgnore(interactable, curEventTarget, eventTarget) && scope.testAllow(interactable, curEventTarget, eventTarget) && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) - && scope.withinInteractionLimit(interactable, curEventTarget, action)) { + && withinInteractionLimit(interactable, curEventTarget, action)) { this.target = interactable; this.element = curEventTarget; } @@ -751,7 +751,7 @@ Interaction.prototype = { && utils.matchesSelector(element, selector, elements) && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' && scope.checkAxis(axis, interactable) - && scope.withinInteractionLimit(interactable, element, 'drag')) { + && withinInteractionLimit(interactable, element, 'drag')) { return interactable; } @@ -780,7 +780,7 @@ Interaction.prototype = { if (starting && (this.target.options[this.prepared.name].manualStart - || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { + || !withinInteractionLimit(this.target, this.element, this.prepared))) { this.stop(event); return; } @@ -1347,7 +1347,7 @@ Interaction.prototype = { } // get the most appropriate dropzone based on DOM depth and order - var dropIndex = scope.indexOfDeepestElement(validDrops), + var dropIndex = utils.indexOfDeepestElement(validDrops), dropzone = this.activeDrops.dropzones[dropIndex] || null, element = this.activeDrops.elements [dropIndex] || null; @@ -1530,7 +1530,7 @@ Interaction.prototype = { inertiaStatus.sy = inertiaStatus.ye * progress; } else { - var quadPoint = scope.getQuadraticCurvePoint( + var quadPoint = utils.getQuadraticCurvePoint( 0, 0, inertiaStatus.xe, inertiaStatus.ye, inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, @@ -1561,8 +1561,8 @@ Interaction.prototype = { duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; if (t < duration) { - inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration); - inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration); + inertiaStatus.sx = utils.easeOutQuad(t, 0, inertiaStatus.xe, duration); + inertiaStatus.sy = utils.easeOutQuad(t, 0, inertiaStatus.ye, duration); this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); @@ -1755,7 +1755,7 @@ Interaction.prototype = { matchElement = matchElements[i], action = validateAction(match.getAction(pointer, event, this, matchElement), match); - if (action && scope.withinInteractionLimit(match, matchElement, action)) { + if (action && withinInteractionLimit(match, matchElement, action)) { this.target = match; this.element = matchElement; @@ -1942,7 +1942,7 @@ Interaction.prototype = { } if (utils.isElement(restriction)) { - restriction = scope.getElementRect(restriction); + restriction = utils.getElementRect(restriction); } rect = restriction; @@ -2050,7 +2050,7 @@ Interaction.prototype = { bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin; } else { - var rect = scope.getElementRect(container); + var rect = utils.getElementRect(container); left = pointer.clientX < rect.left + scope.autoScroll.margin; top = pointer.clientY < rect.top + scope.autoScroll.margin; @@ -2074,7 +2074,192 @@ Interaction.prototype = { this._eventTarget = target; this._curEventTarget = currentTarget; } - }; +function withinInteractionLimit (interactable, element, action) { + var options = interactable.options, + maxActions = options[action.name].max, + maxPerElement = options[action.name].maxPerElement, + activeInteractions = 0, + targetCount = 0, + targetElementCount = 0; + + for (var i = 0, len = scope.interactions.length; i < len; i++) { + var interaction = scope.interactions[i], + otherAction = interaction.prepared.name, + active = interaction.interacting(); + + if (!active) { continue; } + + activeInteractions++; + + if (activeInteractions >= scope.maxInteractions) { + return false; + } + + if (interaction.target !== interactable) { continue; } + + targetCount += (otherAction === action.name)|0; + + if (targetCount >= maxActions) { + return false; + } + + if (interaction.element === element) { + targetElementCount++; + + if (otherAction !== action.name || targetElementCount >= maxPerElement) { + return false; + } + } + } + + return scope.maxInteractions > 0; +} + + +function getInteractionFromPointer (pointer, eventType, eventTarget) { + var i = 0, len = scope.interactions.length, + mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) + // MSPointerEvent.MSPOINTER_TYPE_MOUSE + || pointer.pointerType === 4), + interaction; + + var id = utils.getPointerId(pointer); + + // try to resume inertia with a new pointer + if (/down|start/i.test(eventType)) { + for (i = 0; i < len; i++) { + interaction = scope.interactions[i]; + + var element = eventTarget; + + if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume + && (interaction.mouse === mouseEvent)) { + while (element) { + // if the element is the interaction element + if (element === interaction.element) { + // update the interaction's pointer + if (interaction.pointers[0]) { + interaction.removePointer(interaction.pointers[0]); + } + interaction.addPointer(pointer); + + return interaction; + } + element = utils.parentElement(element); + } + } + } + } + + // if it's a mouse interaction + if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { + + // find a mouse interaction that's not in inertia phase + for (i = 0; i < len; i++) { + if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { + return scope.interactions[i]; + } + } + + // find any interaction specifically for mouse. + // if the eventType is a mousedown, and inertia is active + // ignore the interaction + for (i = 0; i < len; i++) { + if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { + return interaction; + } + } + + // create a new interaction for mouse + interaction = new Interaction(); + interaction.mouse = true; + + return interaction; + } + + // get interaction that has this pointer + for (i = 0; i < len; i++) { + if (scope.contains(scope.interactions[i].pointerIds, id)) { + return scope.interactions[i]; + } + } + + // at this stage, a pointerUp should not return an interaction + if (/up|end|out/i.test(eventType)) { + return null; + } + + // get first idle interaction + for (i = 0; i < len; i++) { + interaction = scope.interactions[i]; + + if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) + && !interaction.interacting() + && !(!mouseEvent && interaction.mouse)) { + + interaction.addPointer(pointer); + + return interaction; + } + } + + return new Interaction(); +} + +function doOnInteractions (method) { + return (function (event) { + var interaction, + eventTarget = utils.getActualElement(event.path + ? event.path[0] + : event.target), + curEventTarget = utils.getActualElement(event.currentTarget), + i; + + if (browser.supportsTouch && /touch/.test(event.type)) { + scope.prevTouchTime = new Date().getTime(); + + for (i = 0; i < event.changedTouches.length; i++) { + var pointer = event.changedTouches[i]; + + interaction = getInteractionFromPointer(pointer, event.type, eventTarget); + + if (!interaction) { continue; } + + interaction._updateEventTargets(eventTarget, curEventTarget); + + interaction[method](pointer, event, eventTarget, curEventTarget); + } + } + else { + if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { + // ignore mouse events while touch interactions are active + for (i = 0; i < scope.interactions.length; i++) { + if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { + return; + } + } + + // try to ignore mouse events that are simulated by the browser + // after a touch event + if (new Date().getTime() - scope.prevTouchTime < 500) { + return; + } + } + + interaction = getInteractionFromPointer(event, event.type, eventTarget); + + if (!interaction) { return; } + + interaction._updateEventTargets(eventTarget, curEventTarget); + + interaction[method](event, event, eventTarget, curEventTarget); + } + }); +} + +Interaction.getInteractionFromPointer = getInteractionFromPointer; +Interaction.doOnInteractions = doOnInteractions; + module.exports = Interaction; diff --git a/src/interact.js b/src/interact.js index ed86cc43d..85d165086 100644 --- a/src/interact.js +++ b/src/interact.js @@ -13,7 +13,11 @@ var scope = require('./scope'), utils = require('./utils'), - browser = utils.browser; + browser = utils.browser, + events = require('./utils/events'), + Interactable = require('./Interactable'), + InteractEvent = require('./InteractEvent'), + Interaction = require('./Interaction'); scope.pEventTypes = null; @@ -114,16 +118,6 @@ scope.globalEvents = {}; - // prefix matchesSelector - browser.prefixedMatchesSelector = 'matches' in Element.prototype? - 'matches': 'webkitMatchesSelector' in Element.prototype? - 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? - 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? - 'oMatchesSelector': 'msMatchesSelector'; - - // Events wrapper - var events = require('./utils/events'); - scope.listeners = {}; var interactionListeners = [ @@ -133,46 +127,6 @@ 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' ]; - scope.trySelector = function (value) { - if (!scope.isString(value)) { return false; } - - // an exception will be raised if it is invalid - scope.document.querySelector(value); - return true; - }; - - scope.getScrollXY = function (win) { - win = win || scope.window; - return { - x: win.scrollX || win.document.documentElement.scrollLeft, - y: win.scrollY || win.document.documentElement.scrollTop - }; - }; - - scope.getActualElement = function (element) { - return (element instanceof scope.SVGElementInstance - ? element.correspondingUseElement - : element); - }; - - scope.getElementRect = function (element) { - var scroll = browser.isIOS7orLower - ? { x: 0, y: 0 } - : scope.getScrollXY(scope.getWindow(element)), - clientRect = (element instanceof scope.SVGElement)? - element.getBoundingClientRect(): - element.getClientRects()[0]; - - return clientRect && { - left : clientRect.left + scroll.x, - right : clientRect.right + scroll.x, - top : clientRect.top + scroll.y, - bottom: clientRect.bottom + scroll.y, - width : clientRect.width || clientRect.right - clientRect.left, - height: clientRect.heigh || clientRect.bottom - clientRect.top - }; - }; - scope.getOriginXY = function (interactable, element) { var origin = interactable ? interactable.options.origin @@ -193,7 +147,7 @@ } if (utils.isElement(origin)) { - origin = scope.getElementRect(origin); + origin = utils.getElementRect(origin); } origin.x = ('x' in origin)? origin.x : origin.left; @@ -202,25 +156,6 @@ return origin; }; - // http://stackoverflow.com/a/5634528/2280888 - scope._getQBezierValue = function (t, p1, p2, p3) { - var iT = 1 - t; - return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; - }; - - scope.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) { - return { - x: scope._getQBezierValue(position, startX, cpX, endX), - y: scope._getQBezierValue(position, startY, cpY, endY) - }; - }; - - // http://gizma.com/easing/ - scope.easeOutQuad = function (t, b, c, d) { - t /= d; - return -c * t*(t-2) + b; - }; - scope.inContext = function (interactable, element) { return interactable._context === element.ownerDocument || utils.nodeContains(interactable._context, element); @@ -232,7 +167,7 @@ if (!ignoreFrom || !utils.isElement(element)) { return false; } if (scope.isString(ignoreFrom)) { - return scope.matchesUpTo(element, ignoreFrom, interactableElement); + return utils.matchesUpTo(element, ignoreFrom, interactableElement); } else if (utils.isElement(ignoreFrom)) { return utils.nodeContains(ignoreFrom, element); @@ -249,7 +184,7 @@ if (!utils.isElement(element)) { return false; } if (scope.isString(allowFrom)) { - return scope.matchesUpTo(element, allowFrom, interactableElement); + return utils.matchesUpTo(element, allowFrom, interactableElement); } else if (utils.isElement(allowFrom)) { return utils.nodeContains(allowFrom, element); @@ -296,418 +231,10 @@ return options[action].autoScroll && options[action].autoScroll.enabled; }; - scope.withinInteractionLimit = function (interactable, element, action) { - var options = interactable.options, - maxActions = options[action.name].max, - maxPerElement = options[action.name].maxPerElement, - activeInteractions = 0, - targetCount = 0, - targetElementCount = 0; - - for (var i = 0, len = scope.interactions.length; i < len; i++) { - var interaction = scope.interactions[i], - otherAction = interaction.prepared.name, - active = interaction.interacting(); - - if (!active) { continue; } - - activeInteractions++; - - if (activeInteractions >= scope.maxInteractions) { - return false; - } - - if (interaction.target !== interactable) { continue; } - - targetCount += (otherAction === action.name)|0; - - if (targetCount >= maxActions) { - return false; - } - - if (interaction.element === element) { - targetElementCount++; - - if (otherAction !== action.name || targetElementCount >= maxPerElement) { - return false; - } - } - } - - return scope.maxInteractions > 0; - }; - - // Test for the element that's "above" all other qualifiers - scope.indexOfDeepestElement = function (elements) { - var dropzone, - deepestZone = elements[0], - index = deepestZone? 0: -1, - parent, - deepestZoneParents = [], - dropzoneParents = [], - child, - i, - n; - - for (i = 1; i < elements.length; i++) { - dropzone = elements[i]; - - // an element might belong to multiple selector dropzones - if (!dropzone || dropzone === deepestZone) { - continue; - } - - if (!deepestZone) { - deepestZone = dropzone; - index = i; - continue; - } - - // check if the deepest or current are document.documentElement or document.rootElement - // - if the current dropzone is, do nothing and continue - if (dropzone.parentNode === dropzone.ownerDocument) { - continue; - } - // - if deepest is, update with the current dropzone and continue to next - else if (deepestZone.parentNode === dropzone.ownerDocument) { - deepestZone = dropzone; - index = i; - continue; - } - - if (!deepestZoneParents.length) { - parent = deepestZone; - while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { - deepestZoneParents.unshift(parent); - parent = parent.parentNode; - } - } - - // if this element is an svg element and the current deepest is - // an HTMLElement - if (deepestZone instanceof scope.HTMLElement - && dropzone instanceof scope.SVGElement - && !(dropzone instanceof scope.SVGSVGElement)) { - - if (dropzone === deepestZone.parentNode) { - continue; - } - - parent = dropzone.ownerSVGElement; - } - else { - parent = dropzone; - } - - dropzoneParents = []; - - while (parent.parentNode !== parent.ownerDocument) { - dropzoneParents.unshift(parent); - parent = parent.parentNode; - } - - n = 0; - - // get (position of last common ancestor) + 1 - while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { - n++; - } - - var parents = [ - dropzoneParents[n - 1], - dropzoneParents[n], - deepestZoneParents[n] - ]; - - child = parents[0].lastChild; - - while (child) { - if (child === parents[1]) { - deepestZone = dropzone; - index = i; - deepestZoneParents = []; - - break; - } - else if (child === parents[2]) { - break; - } - - child = child.previousSibling; - } - } - - return index; - }; - - scope.matchesUpTo = function (element, selector, limit) { - while (utils.isElement(element)) { - if (utils.matchesSelector(element, selector)) { - return true; - } - - element = utils.parentElement(element); - - if (element === limit) { - return utils.matchesSelector(element, selector); - } - } - - return false; - }; - - var Interaction = require('./Interaction'); - - function getInteractionFromPointer (pointer, eventType, eventTarget) { - var i = 0, len = scope.interactions.length, - mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) - // MSPointerEvent.MSPOINTER_TYPE_MOUSE - || pointer.pointerType === 4), - interaction; - - var id = utils.getPointerId(pointer); - - // try to resume inertia with a new pointer - if (/down|start/i.test(eventType)) { - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; - - var element = eventTarget; - - if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume - && (interaction.mouse === mouseEvent)) { - while (element) { - // if the element is the interaction element - if (element === interaction.element) { - // update the interaction's pointer - if (interaction.pointers[0]) { - interaction.removePointer(interaction.pointers[0]); - } - interaction.addPointer(pointer); - - return interaction; - } - element = utils.parentElement(element); - } - } - } - } - - // if it's a mouse interaction - if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { - - // find a mouse interaction that's not in inertia phase - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { - return scope.interactions[i]; - } - } - - // find any interaction specifically for mouse. - // if the eventType is a mousedown, and inertia is active - // ignore the interaction - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { - return interaction; - } - } - - // create a new interaction for mouse - interaction = new Interaction(); - interaction.mouse = true; - - return interaction; - } - - // get interaction that has this pointer - for (i = 0; i < len; i++) { - if (scope.contains(scope.interactions[i].pointerIds, id)) { - return scope.interactions[i]; - } - } - - // at this stage, a pointerUp should not return an interaction - if (/up|end|out/i.test(eventType)) { - return null; - } - - // get first idle interaction - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; - - if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) - && !interaction.interacting() - && !(!mouseEvent && interaction.mouse)) { - - interaction.addPointer(pointer); - - return interaction; - } - } - - return new Interaction(); - } - - function doOnInteractions (method) { - return (function (event) { - var interaction, - eventTarget = scope.getActualElement(event.path - ? event.path[0] - : event.target), - curEventTarget = scope.getActualElement(event.currentTarget), - i; - - if (browser.supportsTouch && /touch/.test(event.type)) { - scope.prevTouchTime = new Date().getTime(); - - for (i = 0; i < event.changedTouches.length; i++) { - var pointer = event.changedTouches[i]; - - interaction = getInteractionFromPointer(pointer, event.type, eventTarget); - - if (!interaction) { continue; } - - interaction._updateEventTargets(eventTarget, curEventTarget); - - interaction[method](pointer, event, eventTarget, curEventTarget); - } - } - else { - if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { - // ignore mouse events while touch interactions are active - for (i = 0; i < scope.interactions.length; i++) { - if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { - return; - } - } - - // try to ignore mouse events that are simulated by the browser - // after a touch event - if (new Date().getTime() - scope.prevTouchTime < 500) { - return; - } - } - - interaction = getInteractionFromPointer(event, event.type, eventTarget); - - if (!interaction) { return; } - - interaction._updateEventTargets(eventTarget, curEventTarget); - - interaction[method](event, event, eventTarget, curEventTarget); - } - }); - } - - function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { - // false, '', undefined, null - if (!value) { return false; } - - // true value, use pointer coords and element rect - if (value === true) { - // if dimensions are negative, "switch" edges - var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left, - height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top; - - if (width < 0) { - if (name === 'left' ) { name = 'right'; } - else if (name === 'right') { name = 'left' ; } - } - if (height < 0) { - if (name === 'top' ) { name = 'bottom'; } - else if (name === 'bottom') { name = 'top' ; } - } - - if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } - if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } - - if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } - if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } - } - - // the remaining checks require an element - if (!utils.isElement(element)) { return false; } - - return utils.isElement(value) - // the value is an element to use as a resize handle - ? value === element - // otherwise check if element matches value as selector - : scope.matchesUpTo(element, value, interactableElement); - } - - function defaultActionChecker (pointer, interaction, element) { - var rect = this.getRect(element), - shouldResize = false, - action = null, - resizeAxes = null, - resizeEdges, - page = utils.extend({}, interaction.curCoords.page), - options = this.options; - - if (!rect) { return null; } - - if (scope.actionIsEnabled.resize && options.resize.enabled) { - var resizeOptions = options.resize; - - resizeEdges = { - left: false, right: false, top: false, bottom: false - }; - - // if using resize.edges - if (scope.isObject(resizeOptions.edges)) { - for (var edge in resizeEdges) { - resizeEdges[edge] = checkResizeEdge(edge, - resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || scope.margin); - } - - resizeEdges.left = resizeEdges.left && !resizeEdges.right; - resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; - - shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; - } - else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); - - shouldResize = right || bottom; - resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); - } - } - - action = shouldResize - ? 'resize' - : scope.actionIsEnabled.drag && options.drag.enabled - ? 'drag' - : null; - - if (scope.actionIsEnabled.gesture - && interaction.pointerIds.length >=2 - && !(interaction.dragging || interaction.resizing)) { - action = 'gesture'; - } - - if (action) { - return { - name: action, - axis: resizeAxes, - edges: resizeEdges - }; - } - - return null; - } - - var InteractEvent = require('./InteractEvent'); - for (var i = 0, len = interactionListeners.length; i < len; i++) { var listenerName = interactionListeners[i]; - scope.listeners[listenerName] = doOnInteractions(listenerName); + scope.listeners[listenerName] = Interaction.doOnInteractions(listenerName); } scope.interactables.indexOfElement = function indexOfElement (element, context) { @@ -775,1341 +302,6 @@ return scope.interactables.get(element, options) || new Interactable(element, options); } - /*\ - * Interactable - [ property ] - ** - * Object type returned by @interact - \*/ - function Interactable (element, options) { - this._element = element; - this._iEvents = this._iEvents || {}; - - var _window; - - if (scope.trySelector(element)) { - this.selector = element; - - var context = options && options.context; - - _window = context? scope.getWindow(context) : scope.window; - - if (context && (_window.Node - ? context instanceof _window.Node - : (utils.isElement(context) || context === _window.document))) { - - this._context = context; - } - } - else { - _window = scope.getWindow(element); - - if (utils.isElement(element, _window)) { - - if (scope.PointerEvent) { - events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown ); - events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover); - } - else { - events.add(this._element, 'mousedown' , scope.listeners.pointerDown ); - events.add(this._element, 'mousemove' , scope.listeners.pointerHover); - events.add(this._element, 'touchstart', scope.listeners.pointerDown ); - events.add(this._element, 'touchmove' , scope.listeners.pointerHover); - } - } - } - - this._doc = _window.document; - - if (!scope.contains(scope.documents, this._doc)) { - listenToDocument(this._doc); - } - - scope.interactables.push(this); - - this.set(options); - } - - Interactable.prototype = { - setOnEvents: function (action, phases) { - if (action === 'drop') { - if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } - if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } - if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } - if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } - if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } - if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } - } - else { - action = 'on' + action; - - if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } - if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } - if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } - if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } - } - - return this; - }, - - /*\ - * Interactable.draggable - [ method ] - * - * Gets or sets whether drag actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of drag events - | var isDraggable = interact('ul li').draggable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) - = (object) This Interactable - | interact(element).draggable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // the axis in which the first movement must be - | // for the drag sequence to start - | // 'xy' by default - any direction - | axis: 'x' || 'y' || 'xy', - | - | // max number of drags that can happen concurrently - | // with elements of this Interactable. Infinity by default - | max: Infinity, - | - | // max number of drags that can target the same element+Interactable - | // 1 by default - | maxPerElement: 2 - | }); - \*/ - draggable: function (options) { - if (scope.isObject(options)) { - this.options.drag.enabled = options.enabled === false? false: true; - this.setPerAction('drag', options); - this.setOnEvents('drag', options); - - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.drag.axis = options.axis; - } - else if (options.axis === null) { - delete this.options.drag.axis; - } - - return this; - } - - if (scope.isBool(options)) { - this.options.drag.enabled = options; - - return this; - } - - return this.options.drag; - }, - - setPerAction: function (action, options) { - // for all the default per-action options - for (var option in options) { - // if this option exists for this action - if (option in scope.defaultOptions[action]) { - // if the option in the options arg is an object value - if (scope.isObject(options[option])) { - // duplicate the object - this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); - - if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { - this.options[action][option].enabled = options[option].enabled === false? false : true; - } - } - else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) { - this.options[action][option].enabled = options[option]; - } - else if (options[option] !== undefined) { - // or if it's not undefined, do a plain assignment - this.options[action][option] = options[option]; - } - } - } - }, - - /*\ - * Interactable.dropzone - [ method ] - * - * Returns or sets whether elements can be dropped onto this - * Interactable to trigger drop events - * - * Dropzones can receive the following events: - * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends - * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone - * - `dragmove` when a draggable that has entered the dropzone is moved - * - `drop` when a draggable is dropped into this dropzone - * - * Use the `accept` option to allow only elements that match the given CSS selector or element. - * - * Use the `overlap` option to set how drops are checked for. The allowed values are: - * - `'pointer'`, the pointer must be over the dropzone (default) - * - `'center'`, the draggable element's center must be over the dropzone - * - a number from 0-1 which is the `(intersection area) / (draggable area)`. - * e.g. `0.5` for drop to happen when half of the area of the - * draggable is over the dropzone - * - - options (boolean | object | null) #optional The new value to be set. - | interact('.drop').dropzone({ - | accept: '.can-drop' || document.getElementById('single-drop'), - | overlap: 'pointer' || 'center' || zeroToOne - | } - = (boolean | object) The current setting or this Interactable - \*/ - dropzone: function (options) { - if (scope.isObject(options)) { - this.options.drop.enabled = options.enabled === false? false: true; - this.setOnEvents('drop', options); - this.accept(options.accept); - - if (/^(pointer|center)$/.test(options.overlap)) { - this.options.drop.overlap = options.overlap; - } - else if (scope.isNumber(options.overlap)) { - this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); - } - - return this; - } - - if (scope.isBool(options)) { - this.options.drop.enabled = options; - - return this; - } - - return this.options.drop; - }, - - dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) { - var dropped = false; - - // if the dropzone has no rect (eg. display: none) - // call the custom dropChecker or just return false - if (!(rect = rect || this.getRect(dropElement))) { - return (this.options.dropChecker - ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) - : false); - } - - var dropOverlap = this.options.drop.overlap; - - if (dropOverlap === 'pointer') { - var page = utils.getPageXY(pointer), - origin = scope.getOriginXY(draggable, draggableElement), - horizontal, - vertical; - - page.x += origin.x; - page.y += origin.y; - - horizontal = (page.x > rect.left) && (page.x < rect.right); - vertical = (page.y > rect.top ) && (page.y < rect.bottom); - - dropped = horizontal && vertical; - } - - var dragRect = draggable.getRect(draggableElement); - - if (dropOverlap === 'center') { - var cx = dragRect.left + dragRect.width / 2, - cy = dragRect.top + dragRect.height / 2; - - dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; - } - - if (scope.isNumber(dropOverlap)) { - var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) - * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), - overlapRatio = overlapArea / (dragRect.width * dragRect.height); - - dropped = overlapRatio >= dropOverlap; - } - - if (this.options.dropChecker) { - dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); - } - - return dropped; - }, - - /*\ - * Interactable.dropChecker - [ method ] - * - * Gets or sets the function used to check if a dragged element is - * over this Interactable. - * - - checker (function) #optional The function that will be called when checking for a drop - = (Function | Interactable) The checker function or this Interactable - * - * The checker function takes the following arguments: - * - - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag - - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer - - dropped (boolean) The value from the default drop check - - dropzone (Interactable) The dropzone interactable - - dropElement (Element) The dropzone element - - draggable (Interactable) The Interactable being dragged - - draggableElement (Element) The actual element that's being dragged - * - > Usage: - | interact(target) - | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent - | event, // TouchEvent/PointerEvent/MouseEvent - | dropped, // result of the default checker - | dropzone, // dropzone Interactable - | dropElement, // dropzone elemnt - | draggable, // draggable Interactable - | draggableElement) {// draggable element - | - | return dropped && event.target.hasAttribute('allow-drop'); - | } - \*/ - dropChecker: function (checker) { - if (scope.isFunction(checker)) { - this.options.dropChecker = checker; - - return this; - } - if (checker === null) { - delete this.options.getRect; - - return this; - } - - return this.options.dropChecker; - }, - - /*\ - * Interactable.accept - [ method ] - * - * Deprecated. add an `accept` property to the options object passed to - * @Interactable.dropzone instead. - * - * Gets or sets the Element or CSS selector match that this - * Interactable accepts if it is a dropzone. - * - - newValue (Element | string | null) #optional - * If it is an Element, then only that element can be dropped into this dropzone. - * If it is a string, the element being dragged must match it as a selector. - * If it is null, the accept options is cleared - it accepts any element. - * - = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable - \*/ - accept: function (newValue) { - if (utils.isElement(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - // test if it is a valid CSS selector - if (scope.trySelector(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.drop.accept; - - return this; - } - - return this.options.drop.accept; - }, - - /*\ - * Interactable.resizable - [ method ] - * - * Gets or sets whether resize actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of resize elements - | var isResizeable = interact('input[type=text]').resizable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) - = (object) This Interactable - | interact(element).resizable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | edges: { - | top : true, // Use pointer coords to check for resize. - | left : false, // Disable resizing from left edge. - | bottom: '.resize-s',// Resize if pointer target matches selector - | right : handleEl // Resize if pointer target is the given Element - | }, - | - | // a value of 'none' will limit the resize rect to a minimum of 0x0 - | // 'negate' will allow the rect to have negative width/height - | // 'reposition' will keep the width/height positive by swapping - | // the top and bottom edges and/or swapping the left and right edges - | invert: 'none' || 'negate' || 'reposition' - | - | // limit multiple resizes. - | // See the explanation in the @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - resizable: function (options) { - if (scope.isObject(options)) { - this.options.resize.enabled = options.enabled === false? false: true; - this.setPerAction('resize', options); - this.setOnEvents('resize', options); - - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.resize.axis = options.axis; - } - else if (options.axis === null) { - this.options.resize.axis = scope.defaultOptions.resize.axis; - } - - if (scope.isBool(options.square)) { - this.options.resize.square = options.square; - } - - return this; - } - if (scope.isBool(options)) { - this.options.resize.enabled = options; - - return this; - } - return this.options.resize; - }, - - /*\ - * Interactable.squareResize - [ method ] - * - * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead - * - * Gets or sets whether resizing is forced 1:1 aspect - * - = (boolean) Current setting - * - * or - * - - newValue (boolean) #optional - = (object) this Interactable - \*/ - squareResize: function (newValue) { - if (scope.isBool(newValue)) { - this.options.resize.square = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.resize.square; - - return this; - } - - return this.options.resize.square; - }, - - /*\ - * Interactable.gesturable - [ method ] - * - * Gets or sets whether multitouch gestures can be performed on the - * Interactable's element - * - = (boolean) Indicates if this can be the target of gesture events - | var isGestureable = interact(element).gesturable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) - = (object) this Interactable - | interact(element).gesturable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // limit multiple gestures. - | // See the explanation in @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - gesturable: function (options) { - if (scope.isObject(options)) { - this.options.gesture.enabled = options.enabled === false? false: true; - this.setPerAction('gesture', options); - this.setOnEvents('gesture', options); - - return this; - } - - if (scope.isBool(options)) { - this.options.gesture.enabled = options; - - return this; - } - - return this.options.gesture; - }, - - /*\ - * Interactable.autoScroll - [ method ] - ** - * Deprecated. Add an `autoscroll` property to the options object - * passed to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets whether dragging and resizing near the edges of the - * window/container trigger autoScroll for this Interactable - * - = (object) Object with autoScroll properties - * - * or - * - - options (object | boolean) #optional - * options can be: - * - an object with margin, distance and interval properties, - * - true or false to enable or disable autoScroll or - = (Interactable) this Interactable - \*/ - autoScroll: function (options) { - if (scope.isObject(options)) { - options = utils.extend({ actions: ['drag', 'resize']}, options); - } - else if (scope.isBool(options)) { - options = { actions: ['drag', 'resize'], enabled: options }; - } - - return this.setOptions('autoScroll', options); - }, - - /*\ - * Interactable.snap - [ method ] - ** - * Deprecated. Add a `snap` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how action coordinates are snapped. By - * default, snapping is relative to the pointer coordinates. You can - * change this by setting the - * [`elementOrigin`](https://github.com/taye/interact.js/pull/72). - ** - = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | interact(document.querySelector('#thing')).snap({ - | targets: [ - | // snap to this specific point - | { - | x: 100, - | y: 100, - | range: 25 - | }, - | // give this function the x and y page coords and snap to the object returned - | function (x, y) { - | return { - | x: x, - | y: (75 + 50 * Math.sin(x * 0.04)), - | range: 40 - | }; - | }, - | // create a function that snaps to a grid - | interact.createSnapGrid({ - | x: 50, - | y: 50, - | range: 10, // optional - | offset: { x: 5, y: 10 } // optional - | }) - | ], - | // do not snap during normal movement. - | // Instead, trigger only one snapped move event - | // immediately before the end event. - | endOnly: true, - | - | relativePoints: [ - | { x: 0, y: 0 }, // snap relative to the top left of the element - | { x: 1, y: 1 }, // and also to the bottom right - | ], - | - | // offset the snap target coordinates - | // can be an object with x/y or 'startCoords' - | offset: { x: 50, y: 50 } - | } - | }); - \*/ - snap: function (options) { - var ret = this.setOptions('snap', options); - - if (ret === this) { return this; } - - return ret.drag; - }, - - setOptions: function (option, options) { - var actions = options && scope.isArray(options.actions) - ? options.actions - : ['drag']; - - var i; - - if (scope.isObject(options) || scope.isBool(options)) { - for (i = 0; i < actions.length; i++) { - var action = /resize/.test(actions[i])? 'resize' : actions[i]; - - if (!scope.isObject(this.options[action])) { continue; } - - var thisOption = this.options[action][option]; - - if (scope.isObject(options)) { - utils.extend(thisOption, options); - thisOption.enabled = options.enabled === false? false: true; - - if (option === 'snap') { - if (thisOption.mode === 'grid') { - thisOption.targets = [ - interact.createSnapGrid(utils.extend({ - offset: thisOption.gridOffset || { x: 0, y: 0 } - }, thisOption.grid || {})) - ]; - } - else if (thisOption.mode === 'anchor') { - thisOption.targets = thisOption.anchors; - } - else if (thisOption.mode === 'path') { - thisOption.targets = thisOption.paths; - } - - if ('elementOrigin' in options) { - thisOption.relativePoints = [options.elementOrigin]; - } - } - } - else if (scope.isBool(options)) { - thisOption.enabled = options; - } - } - - return this; - } - - var ret = {}, - allActions = ['drag', 'resize', 'gesture']; - - for (i = 0; i < allActions.length; i++) { - if (option in scope.defaultOptions[allActions[i]]) { - ret[allActions[i]] = this.options[allActions[i]][option]; - } - } - - return ret; - }, - - - /*\ - * Interactable.inertia - [ method ] - ** - * Deprecated. Add an `inertia` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how events continue to run after the pointer is released - ** - = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | // enable and use default settings - | interact(element).inertia(true); - | - | // enable and use custom settings - | interact(element).inertia({ - | // value greater than 0 - | // high values slow the object down more quickly - | resistance : 16, - | - | // the minimum launch speed (pixels per second) that results in inertia start - | minSpeed : 200, - | - | // inertia will stop when the object slows down to this speed - | endSpeed : 20, - | - | // boolean; should actions be resumed when the pointer goes down during inertia - | allowResume : true, - | - | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy - | zeroResumeDelta: false, - | - | // if snap/restrict are set to be endOnly and inertia is enabled, releasing - | // the pointer without triggering inertia will animate from the release - | // point to the snaped/restricted point in the given amount of time (ms) - | smoothEndDuration: 300, - | - | // an array of action types that can have inertia (no gesture) - | actions : ['drag', 'resize'] - | }); - | - | // reset custom settings and use all defaults - | interact(element).inertia(null); - \*/ - inertia: function (options) { - var ret = this.setOptions('inertia', options); - - if (ret === this) { return this; } - - return ret.drag; - }, - - getAction: function (pointer, event, interaction, element) { - var action = this.defaultActionChecker(pointer, interaction, element); - - if (this.options.actionChecker) { - return this.options.actionChecker(pointer, event, action, this, element, interaction); - } - - return action; - }, - - defaultActionChecker: defaultActionChecker, - - /*\ - * Interactable.actionChecker - [ method ] - * - * Gets or sets the function used to check action to be performed on - * pointerDown - * - - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. - = (Function | Interactable) The checker function or this Interactable - * - | interact('.resize-drag') - | .resizable(true) - | .draggable(true) - | .actionChecker(function (pointer, event, action, interactable, element, interaction) { - | - | if (interact.matchesSelector(event.target, '.drag-handle') { - | // force drag with handle target - | action.name = drag; - | } - | else { - | // resize from the top and right edges - | action.name = 'resize'; - | action.edges = { top: true, right: true }; - | } - | - | return action; - | }); - \*/ - actionChecker: function (checker) { - if (scope.isFunction(checker)) { - this.options.actionChecker = checker; - - return this; - } - - if (checker === null) { - delete this.options.actionChecker; - - return this; - } - - return this.options.actionChecker; - }, - - /*\ - * Interactable.getRect - [ method ] - * - * The default function to get an Interactables bounding rect. Can be - * overridden using @Interactable.rectChecker. - * - - element (Element) #optional The element to measure. - = (object) The object's bounding rectangle. - o { - o top : 0, - o left : 0, - o bottom: 0, - o right : 0, - o width : 0, - o height: 0 - o } - \*/ - getRect: function rectCheck (element) { - element = element || this._element; - - if (this.selector && !(utils.isElement(element))) { - element = this._context.querySelector(this.selector); - } - - return scope.getElementRect(element); - }, - - /*\ - * Interactable.rectChecker - [ method ] - * - * Returns or sets the function used to calculate the interactable's - * element's rectangle - * - - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect - = (function | object) The checker function or this Interactable - \*/ - rectChecker: function (checker) { - if (scope.isFunction(checker)) { - this.getRect = checker; - - return this; - } - - if (checker === null) { - delete this.options.getRect; - - return this; - } - - return this.getRect; - }, - - /*\ - * Interactable.styleCursor - [ method ] - * - * Returns or sets whether the action that would be performed when the - * mouse on the element are checked on `mousemove` so that the cursor - * may be styled appropriately - * - - newValue (boolean) #optional - = (boolean | Interactable) The current setting or this Interactable - \*/ - styleCursor: function (newValue) { - if (scope.isBool(newValue)) { - this.options.styleCursor = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.styleCursor; - - return this; - } - - return this.options.styleCursor; - }, - - /*\ - * Interactable.preventDefault - [ method ] - * - * Returns or sets whether to prevent the browser's default behaviour - * in response to pointer events. Can be set to: - * - `'always'` to always prevent - * - `'never'` to never prevent - * - `'auto'` to let interact.js try to determine what would be best - * - - newValue (string) #optional `true`, `false` or `'auto'` - = (string | Interactable) The current setting or this Interactable - \*/ - preventDefault: function (newValue) { - if (/^(always|never|auto)$/.test(newValue)) { - this.options.preventDefault = newValue; - return this; - } - - if (scope.isBool(newValue)) { - this.options.preventDefault = newValue? 'always' : 'never'; - return this; - } - - return this.options.preventDefault; - }, - - /*\ - * Interactable.origin - [ method ] - * - * Gets or sets the origin of the Interactable's element. The x and y - * of the origin will be subtracted from action event coordinates. - * - - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector - * OR - - origin (Element) #optional An HTML or SVG Element whose rect will be used - ** - = (object) The current origin or this Interactable - \*/ - origin: function (newValue) { - if (scope.trySelector(newValue)) { - this.options.origin = newValue; - return this; - } - else if (scope.isObject(newValue)) { - this.options.origin = newValue; - return this; - } - - return this.options.origin; - }, - - /*\ - * Interactable.deltaSource - [ method ] - * - * Returns or sets the mouse coordinate types used to calculate the - * movement of the pointer. - * - - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work - = (string | object) The current deltaSource or this Interactable - \*/ - deltaSource: function (newValue) { - if (newValue === 'page' || newValue === 'client') { - this.options.deltaSource = newValue; - - return this; - } - - return this.options.deltaSource; - }, - - /*\ - * Interactable.restrict - [ method ] - ** - * Deprecated. Add a `restrict` property to the options object passed to - * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead. - * - * Returns or sets the rectangles within which actions on this - * interactable (after snap calculations) are restricted. By default, - * restricting is relative to the pointer coordinates. You can change - * this by setting the - * [`elementRect`](https://github.com/taye/interact.js/pull/72). - ** - - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self' - = (object) The current restrictions object or this Interactable - ** - | interact(element).restrict({ - | // the rect will be `interact.getElementRect(element.parentNode)` - | drag: element.parentNode, - | - | // x and y are relative to the the interactable's origin - | resize: { x: 100, y: 100, width: 200, height: 200 } - | }) - | - | interact('.draggable').restrict({ - | // the rect will be the selected element's parent - | drag: 'parent', - | - | // do not restrict during normal movement. - | // Instead, trigger only one restricted move event - | // immediately before the end event. - | endOnly: true, - | - | // https://github.com/taye/interact.js/pull/72#issue-41813493 - | elementRect: { top: 0, left: 0, bottom: 1, right: 1 } - | }); - \*/ - restrict: function (options) { - if (!scope.isObject(options)) { - return this.setOptions('restrict', options); - } - - var actions = ['drag', 'resize', 'gesture'], - ret; - - for (var i = 0; i < actions.length; i++) { - var action = actions[i]; - - if (action in options) { - var perAction = utils.extend({ - actions: [action], - restriction: options[action] - }, options); - - ret = this.setOptions('restrict', perAction); - } - } - - return ret; - }, - - /*\ - * Interactable.context - [ method ] - * - * Gets the selector context Node of the Interactable. The default is `window.document`. - * - = (Node) The context Node of this Interactable - ** - \*/ - context: function () { - return this._context; - }, - - _context: scope.document, - - /*\ - * Interactable.ignoreFrom - [ method ] - * - * If the target of the `mousedown`, `pointerdown` or `touchstart` - * event or any of it's parents match the given CSS selector or - * Element, no drag/resize/gesture is started. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements - = (string | Element | object) The current ignoreFrom value or this Interactable - ** - | interact(element, { ignoreFrom: document.getElementById('no-action') }); - | // or - | interact(element).ignoreFrom('input, textarea, a'); - \*/ - ignoreFrom: function (newValue) { - if (scope.trySelector(newValue)) { // CSS selector to match event.target - this.options.ignoreFrom = newValue; - return this; - } - - if (utils.isElement(newValue)) { // specific element - this.options.ignoreFrom = newValue; - return this; - } - - return this.options.ignoreFrom; - }, - - /*\ - * Interactable.allowFrom - [ method ] - * - * A drag/resize/gesture is started only If the target of the - * `mousedown`, `pointerdown` or `touchstart` event or any of it's - * parents match the given CSS selector or Element. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element - = (string | Element | object) The current allowFrom value or this Interactable - ** - | interact(element, { allowFrom: document.getElementById('drag-handle') }); - | // or - | interact(element).allowFrom('.handle'); - \*/ - allowFrom: function (newValue) { - if (scope.trySelector(newValue)) { // CSS selector to match event.target - this.options.allowFrom = newValue; - return this; - } - - if (utils.isElement(newValue)) { // specific element - this.options.allowFrom = newValue; - return this; - } - - return this.options.allowFrom; - }, - - /*\ - * Interactable.element - [ method ] - * - * If this is not a selector Interactable, it returns the element this - * interactable represents - * - = (Element) HTML / SVG Element - \*/ - element: function () { - return this._element; - }, - - /*\ - * Interactable.fire - [ method ] - * - * Calls listeners for the given InteractEvent type bound globally - * and directly to this Interactable - * - - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable - = (Interactable) this Interactable - \*/ - fire: function (iEvent) { - if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) { - return this; - } - - var listeners, - i, - len, - onEvent = 'on' + iEvent.type, - funcName = ''; - - // Interactable#on() listeners - if (iEvent.type in this._iEvents) { - listeners = this._iEvents[iEvent.type]; - - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } - - // interactable.onevent listener - if (scope.isFunction(this[onEvent])) { - funcName = this[onEvent].name; - this[onEvent](iEvent); - } - - // interact.on() listeners - if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { - - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } - - return this; - }, - - /*\ - * Interactable.on - [ method ] - * - * Binds a listener for an InteractEvent or DOM event. - * - - eventType (string | array | object) The types of events to listen for - - listener (function) The function to be called on the given event(s) - - useCapture (boolean) #optional useCapture flag for addEventListener - = (object) This Interactable - \*/ - on: function (eventType, listener, useCapture) { - var i; - - if (scope.isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } - - if (scope.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.on(eventType[i], listener, useCapture); - } - - return this; - } - - if (scope.isObject(eventType)) { - for (var prop in eventType) { - this.on(prop, eventType[prop], listener); - } - - return this; - } - - if (eventType === 'wheel') { - eventType = scope.wheelEvent; - } - - // convert to boolean - useCapture = useCapture? true: false; - - if (scope.contains(scope.eventTypes, eventType)) { - // if this type of event was never bound to this Interactable - if (!(eventType in this._iEvents)) { - this._iEvents[eventType] = [listener]; - } - else { - this._iEvents[eventType].push(listener); - } - } - // delegated event for selector - else if (this.selector) { - events.addDelegate(this.selector, this._context, eventType, listener, useCapture); - } - else { - events.add(this._element, eventType, listener, useCapture); - } - - return this; - }, - - /*\ - * Interactable.off - [ method ] - * - * Removes an InteractEvent or DOM event listener - * - - eventType (string | array | object) The types of events that were listened for - - listener (function) The listener function to be removed - - useCapture (boolean) #optional useCapture flag for removeEventListener - = (object) This Interactable - \*/ - off: function (eventType, listener, useCapture) { - var i; - - if (scope.isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } - - if (scope.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.off(eventType[i], listener, useCapture); - } - - return this; - } - - if (scope.isObject(eventType)) { - for (var prop in eventType) { - this.off(prop, eventType[prop], listener); - } - - return this; - } - - var eventList, - index = -1; - - // convert to boolean - useCapture = useCapture? true: false; - - if (eventType === 'wheel') { - eventType = scope.wheelEvent; - } - - // if it is an action event type - if (scope.contains(scope.eventTypes, eventType)) { - eventList = this._iEvents[eventType]; - - if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) { - this._iEvents[eventType].splice(index, 1); - } - } - // delegated event - else if (this.selector) { - events.removeDelegate(this.selector, this._context, eventType, listener, useCapture); - } - // remove listener from this Interatable's element - else { - events.remove(this._element, eventType, listener, useCapture); - } - - return this; - }, - - /*\ - * Interactable.set - [ method ] - * - * Reset the options of this Interactable - - options (object) The new settings to apply - = (object) This Interactablw - \*/ - set: function (options) { - if (!scope.isObject(options)) { - options = {}; - } - - this.options = utils.extend({}, scope.defaultOptions.base); - - var i, - actions = ['drag', 'drop', 'resize', 'gesture'], - methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], - perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {}); - - for (i = 0; i < actions.length; i++) { - var action = actions[i]; - - this.options[action] = utils.extend({}, scope.defaultOptions[action]); - - this.setPerAction(action, perActions); - - this[methods[i]](options[action]); - } - - var settings = [ - 'accept', 'actionChecker', 'allowFrom', 'deltaSource', - 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', - 'rectChecker' - ]; - - for (i = 0, len = settings.length; i < len; i++) { - var setting = settings[i]; - - this.options[setting] = scope.defaultOptions.base[setting]; - - if (setting in options) { - this[setting](options[setting]); - } - } - - return this; - }, - - /*\ - * Interactable.unset - [ method ] - * - * Remove this interactable from the list of interactables and remove - * it's drag, drop, resize and gesture capabilities - * - = (object) @interact - \*/ - unset: function () { - events.remove(this._element, 'all'); - - if (!scope.isString(this.selector)) { - events.remove(this, 'all'); - if (this.options.styleCursor) { - this._element.style.cursor = ''; - } - } - else { - // remove delegated events - for (var type in scope.delegatedEvents) { - var delegated = scope.delegatedEvents[type]; - - for (var i = 0; i < delegated.selectors.length; i++) { - if (delegated.selectors[i] === this.selector - && delegated.contexts[i] === this._context) { - - delegated.selectors.splice(i, 1); - delegated.contexts .splice(i, 1); - delegated.listeners.splice(i, 1); - - // remove the arrays if they are empty - if (!delegated.selectors.length) { - scope.delegatedEvents[type] = null; - } - } - - events.remove(this._context, type, events.delegateListener); - events.remove(this._context, type, events.delegateUseCapture, true); - - break; - } - } - } - - this.dropzone(false); - - scope.interactables.splice(scope.indexOf(scope.interactables, this), 1); - - return interact; - } - }; - Interactable.prototype.snap = utils.warnOnce(Interactable.prototype.snap, 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping'); Interactable.prototype.restrict = utils.warnOnce(Interactable.prototype.restrict, @@ -2335,7 +527,7 @@ interactables : scope.interactables, pointerIsDown : interaction.pointerIsDown, defaultOptions : scope.defaultOptions, - defaultActionChecker : defaultActionChecker, + defaultActionChecker : scope.defaultActionChecker, actionCursors : scope.actionCursors, dragMove : scope.listeners.dragMove, @@ -2360,7 +552,7 @@ interact.getTouchDistance = utils.touchDistance; interact.getTouchAngle = utils.touchAngle; - interact.getElementRect = scope.getElementRect; + interact.getElementRect = utils.getElementRect; interact.matchesSelector = utils.matchesSelector; interact.closest = utils.closest; @@ -2596,7 +788,7 @@ }); // For IE's bad dblclick event sequence - events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick')); + events.add(doc, 'dblclick', Interaction.doOnInteractions('ie8Dblclick')); } scope.documents.push(doc); @@ -2609,5 +801,6 @@ scope.Interactable = Interactable; scope.Interaction = Interaction; scope.InteractEvent = InteractEvent; + scope.listenToDocument = scope.listenToDocument; module.exports = interact; diff --git a/src/utils/domUtils.js b/src/utils/domUtils.js index fb76b6c7c..114b96866 100644 --- a/src/utils/domUtils.js +++ b/src/utils/domUtils.js @@ -2,7 +2,8 @@ var win = require('./window'), browser = require('./browser'), - isType = require('./isType'); + isType = require('./isType'), + domObjects = require('./domObjects'); var domUtils = { nodeContains: function (parent, child) { @@ -67,6 +68,157 @@ var domUtils = { return element[browser.prefixedMatchesSelector](selector); }, + + // Test for the element that's "above" all other qualifiers + indexOfDeepestElement: function (elements) { + var dropzone, + deepestZone = elements[0], + index = deepestZone? 0: -1, + parent, + deepestZoneParents = [], + dropzoneParents = [], + child, + i, + n; + + for (i = 1; i < elements.length; i++) { + dropzone = elements[i]; + + // an element might belong to multiple selector dropzones + if (!dropzone || dropzone === deepestZone) { + continue; + } + + if (!deepestZone) { + deepestZone = dropzone; + index = i; + continue; + } + + // check if the deepest or current are document.documentElement or document.rootElement + // - if the current dropzone is, do nothing and continue + if (dropzone.parentNode === dropzone.ownerDocument) { + continue; + } + // - if deepest is, update with the current dropzone and continue to next + else if (deepestZone.parentNode === dropzone.ownerDocument) { + deepestZone = dropzone; + index = i; + continue; + } + + if (!deepestZoneParents.length) { + parent = deepestZone; + while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { + deepestZoneParents.unshift(parent); + parent = parent.parentNode; + } + } + + // if this element is an svg element and the current deepest is + // an HTMLElement + if (deepestZone instanceof domObjects.HTMLElement + && dropzone instanceof domObjects.SVGElement + && !(dropzone instanceof domObjects.SVGSVGElement)) { + + if (dropzone === deepestZone.parentNode) { + continue; + } + + parent = dropzone.ownerSVGElement; + } + else { + parent = dropzone; + } + + dropzoneParents = []; + + while (parent.parentNode !== parent.ownerDocument) { + dropzoneParents.unshift(parent); + parent = parent.parentNode; + } + + n = 0; + + // get (position of last common ancestor) + 1 + while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { + n++; + } + + var parents = [ + dropzoneParents[n - 1], + dropzoneParents[n], + deepestZoneParents[n] + ]; + + child = parents[0].lastChild; + + while (child) { + if (child === parents[1]) { + deepestZone = dropzone; + index = i; + deepestZoneParents = []; + + break; + } + else if (child === parents[2]) { + break; + } + + child = child.previousSibling; + } + } + + return index; + }, + + matchesUpTo: function (element, selector, limit) { + while (domUtils.isElement(element)) { + if (domUtils.matchesSelector(element, selector)) { + return true; + } + + element = domUtils.parentElement(element); + + if (element === limit) { + return domUtils.matchesSelector(element, selector); + } + } + + return false; + }, + + getActualElement: function (element) { + return (element instanceof domObjects.SVGElementInstance + ? element.correspondingUseElement + : element); + }, + + getScrollXY: function (relevantWindow) { + relevantWindow = relevantWindow || win.window; + return { + x: relevantWindow.scrollX || relevantWindow.document.documentElement.scrollLeft, + y: relevantWindow.scrollY || relevantWindow.document.documentElement.scrollTop + }; + }, + + getElementRect: function (element) { + var scroll = browser.isIOS7orLower + ? { x: 0, y: 0 } + : domUtils.getScrollXY(win.getWindow(element)), + clientRect = (element instanceof domObjects.SVGElement)? + element.getBoundingClientRect(): + element.getClientRects()[0]; + + return clientRect && { + left : clientRect.left + scroll.x, + right : clientRect.right + scroll.x, + top : clientRect.top + scroll.y, + bottom: clientRect.bottom + scroll.y, + width : clientRect.width || clientRect.right - clientRect.left, + height: clientRect.heigh || clientRect.bottom - clientRect.top + }; + } }; module.exports = domUtils; diff --git a/src/utils/events.js b/src/utils/events.js index 6669a510d..f8311a61d 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -261,9 +261,9 @@ function removeDelegate (selector, context, type, listener, useCapture) { function delegateListener (event, useCapture) { var fakeEvent = {}, delegated = delegatedEvents[event.type], - eventTarget = getActualElement(event.path - ? event.path[0] - : event.target), + eventTarget = domUtils.getActualElement(event.path + ? event.path[0] + : event.target), element = eventTarget; useCapture = useCapture? true: false; @@ -323,12 +323,6 @@ function stopImmProp () { this.immediatePropagationStopped = true; } -function getActualElement (element) { - return (element instanceof domObjects.SVGElementInstance - ? element.correspondingUseElement - : element); -} - module.exports = { add: add, remove: remove, diff --git a/src/utils/index.js b/src/utils/index.js index 3193aec78..43a010ca4 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -19,6 +19,26 @@ utils.warnOnce = function (method, message) { }; }; +// http://stackoverflow.com/a/5634528/2280888 +utils._getQBezierValue = function (t, p1, p2, p3) { + var iT = 1 - t; + return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; +}; + +utils.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) { + return { + x: utils._getQBezierValue(position, startX, cpX, endX), + y: utils._getQBezierValue(position, startY, cpY, endY) + }; +}; + +// http://gizma.com/easing/ +utils.easeOutQuad = function (t, b, c, d) { + t /= d; + return -c * t*(t-2) + b; +}; + + utils.extend = extend; utils.hypot = require('./hypot'); utils.raf = require('./raf'); diff --git a/src/utils/isType.js b/src/utils/isType.js index e52f740fc..371b1774a 100644 --- a/src/utils/isType.js +++ b/src/utils/isType.js @@ -28,8 +28,15 @@ var isType = { isBool : function (thing) { return typeof thing === 'boolean' ; }, - isString : function (thing) { return typeof thing === 'string' ; } + isString : function (thing) { return typeof thing === 'string' ; }, + trySelector: function (value) { + if (!isType.isString(value)) { return false; } + + // an exception will be raised if it is invalid + domObjects.document.querySelector(value); + return true; + } }; isType.isArray = function (thing) { From ad9c4357eb6f9560d5dc934d94086cb152afb1d7 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 27 Jun 2015 17:18:46 +0200 Subject: [PATCH 041/131] Don't extend scope with isType methods --- src/Interactable.js | 124 ++++++++++++++++++++++---------------------- src/Interaction.js | 48 ++++++++--------- src/interact.js | 45 +++++++--------- src/scope.js | 9 +++- 4 files changed, 112 insertions(+), 114 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index dd95dad72..69e211966 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -16,7 +16,7 @@ function Interactable (element, options) { var _window; - if (scope.trySelector(element)) { + if (utils.trySelector(element)) { this.selector = element; var context = options && options.context; @@ -50,7 +50,7 @@ function Interactable (element, options) { this._doc = _window.document; - if (!scope.contains(scope.documents, this._doc)) { + if (!utils.contains(scope.documents, this._doc)) { scope.listenToDocument(this._doc); } @@ -62,20 +62,20 @@ function Interactable (element, options) { Interactable.prototype = { setOnEvents: function (action, phases) { if (action === 'drop') { - if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } - if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } - if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } - if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } - if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } - if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } + if (utils.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } + if (utils.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } + if (utils.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } + if (utils.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } + if (utils.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } + if (utils.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } } else { action = 'on' + action; - if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } - if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } - if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } - if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } + if (utils.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } + if (utils.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } + if (utils.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } + if (utils.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } } return this; @@ -113,7 +113,7 @@ Interactable.prototype = { | }); \*/ draggable: function (options) { - if (scope.isObject(options)) { + if (utils.isObject(options)) { this.options.drag.enabled = options.enabled === false? false: true; this.setPerAction('drag', options); this.setOnEvents('drag', options); @@ -128,7 +128,7 @@ Interactable.prototype = { return this; } - if (scope.isBool(options)) { + if (utils.isBool(options)) { this.options.drag.enabled = options; return this; @@ -143,15 +143,15 @@ Interactable.prototype = { // if this option exists for this action if (option in scope.defaultOptions[action]) { // if the option in the options arg is an object value - if (scope.isObject(options[option])) { + if (utils.isObject(options[option])) { // duplicate the object this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); - if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { + if (utils.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { this.options[action][option].enabled = options[option].enabled === false? false : true; } } - else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) { + else if (utils.isBool(options[option]) && utils.isObject(scope.defaultOptions.perAction[option])) { this.options[action][option].enabled = options[option]; } else if (options[option] !== undefined) { @@ -192,7 +192,7 @@ Interactable.prototype = { = (boolean | object) The current setting or this Interactable \*/ dropzone: function (options) { - if (scope.isObject(options)) { + if (utils.isObject(options)) { this.options.drop.enabled = options.enabled === false? false: true; this.setOnEvents('drop', options); this.accept(options.accept); @@ -200,14 +200,14 @@ Interactable.prototype = { if (/^(pointer|center)$/.test(options.overlap)) { this.options.drop.overlap = options.overlap; } - else if (scope.isNumber(options.overlap)) { + else if (utils.isNumber(options.overlap)) { this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); } return this; } - if (scope.isBool(options)) { + if (utils.isBool(options)) { this.options.drop.enabled = options; return this; @@ -253,7 +253,7 @@ Interactable.prototype = { dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; } - if (scope.isNumber(dropOverlap)) { + if (utils.isNumber(dropOverlap)) { var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), overlapRatio = overlapArea / (dragRect.width * dragRect.height); @@ -302,7 +302,7 @@ Interactable.prototype = { | } \*/ dropChecker: function (checker) { - if (scope.isFunction(checker)) { + if (utils.isFunction(checker)) { this.options.dropChecker = checker; return this; @@ -341,7 +341,7 @@ Interactable.prototype = { } // test if it is a valid CSS selector - if (scope.trySelector(newValue)) { + if (utils.trySelector(newValue)) { this.options.drop.accept = newValue; return this; @@ -393,7 +393,7 @@ Interactable.prototype = { | }); \*/ resizable: function (options) { - if (scope.isObject(options)) { + if (utils.isObject(options)) { this.options.resize.enabled = options.enabled === false? false: true; this.setPerAction('resize', options); this.setOnEvents('resize', options); @@ -405,13 +405,13 @@ Interactable.prototype = { this.options.resize.axis = scope.defaultOptions.resize.axis; } - if (scope.isBool(options.square)) { + if (utils.isBool(options.square)) { this.options.resize.square = options.square; } return this; } - if (scope.isBool(options)) { + if (utils.isBool(options)) { this.options.resize.enabled = options; return this; @@ -435,7 +435,7 @@ Interactable.prototype = { = (object) this Interactable \*/ squareResize: function (newValue) { - if (scope.isBool(newValue)) { + if (utils.isBool(newValue)) { this.options.resize.square = newValue; return this; @@ -474,7 +474,7 @@ Interactable.prototype = { | }); \*/ gesturable: function (options) { - if (scope.isObject(options)) { + if (utils.isObject(options)) { this.options.gesture.enabled = options.enabled === false? false: true; this.setPerAction('gesture', options); this.setOnEvents('gesture', options); @@ -482,7 +482,7 @@ Interactable.prototype = { return this; } - if (scope.isBool(options)) { + if (utils.isBool(options)) { this.options.gesture.enabled = options; return this; @@ -512,10 +512,10 @@ Interactable.prototype = { = (Interactable) this Interactable \*/ autoScroll: function (options) { - if (scope.isObject(options)) { + if (utils.isObject(options)) { options = utils.extend({ actions: ['drag', 'resize']}, options); } - else if (scope.isBool(options)) { + else if (utils.isBool(options)) { options = { actions: ['drag', 'resize'], enabled: options }; } @@ -590,21 +590,21 @@ Interactable.prototype = { }, setOptions: function (option, options) { - var actions = options && scope.isArray(options.actions) + var actions = options && utils.isArray(options.actions) ? options.actions : ['drag']; var i; - if (scope.isObject(options) || scope.isBool(options)) { + if (utils.isObject(options) || utils.isBool(options)) { for (i = 0; i < actions.length; i++) { var action = /resize/.test(actions[i])? 'resize' : actions[i]; - if (!scope.isObject(this.options[action])) { continue; } + if (!utils.isObject(this.options[action])) { continue; } var thisOption = this.options[action][option]; - if (scope.isObject(options)) { + if (utils.isObject(options)) { utils.extend(thisOption, options); thisOption.enabled = options.enabled === false? false: true; @@ -628,7 +628,7 @@ Interactable.prototype = { } } } - else if (scope.isBool(options)) { + else if (utils.isBool(options)) { thisOption.enabled = options; } } @@ -747,7 +747,7 @@ Interactable.prototype = { | }); \*/ actionChecker: function (checker) { - if (scope.isFunction(checker)) { + if (utils.isFunction(checker)) { this.options.actionChecker = checker; return this; @@ -801,7 +801,7 @@ Interactable.prototype = { = (function | object) The checker function or this Interactable \*/ rectChecker: function (checker) { - if (scope.isFunction(checker)) { + if (utils.isFunction(checker)) { this.getRect = checker; return this; @@ -828,7 +828,7 @@ Interactable.prototype = { = (boolean | Interactable) The current setting or this Interactable \*/ styleCursor: function (newValue) { - if (scope.isBool(newValue)) { + if (utils.isBool(newValue)) { this.options.styleCursor = newValue; return this; @@ -862,7 +862,7 @@ Interactable.prototype = { return this; } - if (scope.isBool(newValue)) { + if (utils.isBool(newValue)) { this.options.preventDefault = newValue? 'always' : 'never'; return this; } @@ -884,11 +884,11 @@ Interactable.prototype = { = (object) The current origin or this Interactable \*/ origin: function (newValue) { - if (scope.trySelector(newValue)) { + if (utils.trySelector(newValue)) { this.options.origin = newValue; return this; } - else if (scope.isObject(newValue)) { + else if (utils.isObject(newValue)) { this.options.origin = newValue; return this; } @@ -954,7 +954,7 @@ Interactable.prototype = { | }); \*/ restrict: function (options) { - if (!scope.isObject(options)) { + if (!utils.isObject(options)) { return this.setOptions('restrict', options); } @@ -1008,7 +1008,7 @@ Interactable.prototype = { | interact(element).ignoreFrom('input, textarea, a'); \*/ ignoreFrom: function (newValue) { - if (scope.trySelector(newValue)) { // CSS selector to match event.target + if (utils.trySelector(newValue)) { // CSS selector to match event.target this.options.ignoreFrom = newValue; return this; } @@ -1037,7 +1037,7 @@ Interactable.prototype = { | interact(element).allowFrom('.handle'); \*/ allowFrom: function (newValue) { - if (scope.trySelector(newValue)) { // CSS selector to match event.target + if (utils.trySelector(newValue)) { // CSS selector to match event.target this.options.allowFrom = newValue; return this; } @@ -1074,7 +1074,7 @@ Interactable.prototype = { = (Interactable) this Interactable \*/ fire: function (iEvent) { - if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) { + if (!(iEvent && iEvent.type) || !utils.contains(scope.eventTypes, iEvent.type)) { return this; } @@ -1095,7 +1095,7 @@ Interactable.prototype = { } // interactable.onevent listener - if (scope.isFunction(this[onEvent])) { + if (utils.isFunction(this[onEvent])) { funcName = this[onEvent].name; this[onEvent](iEvent); } @@ -1126,11 +1126,11 @@ Interactable.prototype = { on: function (eventType, listener, useCapture) { var i; - if (scope.isString(eventType) && eventType.search(' ') !== -1) { + if (utils.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } - if (scope.isArray(eventType)) { + if (utils.isArray(eventType)) { for (i = 0; i < eventType.length; i++) { this.on(eventType[i], listener, useCapture); } @@ -1138,7 +1138,7 @@ Interactable.prototype = { return this; } - if (scope.isObject(eventType)) { + if (utils.isObject(eventType)) { for (var prop in eventType) { this.on(prop, eventType[prop], listener); } @@ -1153,7 +1153,7 @@ Interactable.prototype = { // convert to boolean useCapture = useCapture? true: false; - if (scope.contains(scope.eventTypes, eventType)) { + if (utils.contains(scope.eventTypes, eventType)) { // if this type of event was never bound to this Interactable if (!(eventType in this._iEvents)) { this._iEvents[eventType] = [listener]; @@ -1187,11 +1187,11 @@ Interactable.prototype = { off: function (eventType, listener, useCapture) { var i; - if (scope.isString(eventType) && eventType.search(' ') !== -1) { + if (utils.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } - if (scope.isArray(eventType)) { + if (utils.isArray(eventType)) { for (i = 0; i < eventType.length; i++) { this.off(eventType[i], listener, useCapture); } @@ -1199,7 +1199,7 @@ Interactable.prototype = { return this; } - if (scope.isObject(eventType)) { + if (utils.isObject(eventType)) { for (var prop in eventType) { this.off(prop, eventType[prop], listener); } @@ -1218,10 +1218,10 @@ Interactable.prototype = { } // if it is an action event type - if (scope.contains(scope.eventTypes, eventType)) { + if (utils.contains(scope.eventTypes, eventType)) { eventList = this._iEvents[eventType]; - if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) { + if (eventList && (index = utils.indexOf(eventList, listener)) !== -1) { this._iEvents[eventType].splice(index, 1); } } @@ -1246,7 +1246,7 @@ Interactable.prototype = { = (object) This Interactablw \*/ set: function (options) { - if (!scope.isObject(options)) { + if (!utils.isObject(options)) { options = {}; } @@ -1299,7 +1299,7 @@ Interactable.prototype = { unset: function () { events.remove(this._element, 'all'); - if (!scope.isString(this.selector)) { + if (!utils.isString(this.selector)) { events.remove(this, 'all'); if (this.options.styleCursor) { this._element.style.cursor = ''; @@ -1334,7 +1334,7 @@ Interactable.prototype = { this.dropzone(false); - scope.interactables.splice(scope.indexOf(scope.interactables, this), 1); + scope.interactables.splice(utils.indexOf(scope.interactables, this), 1); return scope.interact; } @@ -1347,8 +1347,8 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, // true value, use pointer coords and element rect if (value === true) { // if dimensions are negative, "switch" edges - var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left, - height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top; + var width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left, + height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; if (width < 0) { if (name === 'left' ) { name = 'right'; } @@ -1395,7 +1395,7 @@ function defaultActionChecker (pointer, interaction, element) { }; // if using resize.edges - if (scope.isObject(resizeOptions.edges)) { + if (utils.isObject(resizeOptions.edges)) { for (var edge in resizeEdges) { resizeEdges[edge] = checkResizeEdge(edge, resizeOptions.edges[edge], diff --git a/src/Interaction.js b/src/Interaction.js index 6970b64a4..a916c53bc 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -46,7 +46,7 @@ function Interaction () { i : null }; - if (scope.isFunction(Function.prototype.bind)) { + if (utils.isFunction(Function.prototype.bind)) { this.boundInertiaFrame = this.inertiaFrame.bind(this); this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); } @@ -159,7 +159,7 @@ function Interaction () { // Check if action is enabled globally and the current target supports it // If so, return the validated action. Otherwise, return null function validateAction (action, interactable) { - if (!scope.isObject(action)) { return null; } + if (!utils.isObject(action)) { return null; } var actionName = action.name, options = interactable.options; @@ -629,7 +629,7 @@ Interaction.prototype = { // if this interaction had been removed after stopping // add it back - if (scope.indexOf(scope.interactions, this) === -1) { + if (utils.indexOf(scope.interactions, this) === -1) { scope.interactions.push(this); } @@ -659,7 +659,7 @@ Interaction.prototype = { && this.curCoords.client.y === this.prevCoords.client.y); var dx, dy, - pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); // register movement greater than pointerMoveTolerance if (this.pointerIsDown && !this.pointerWasMoved) { @@ -1027,7 +1027,7 @@ Interaction.prototype = { }, pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -1040,7 +1040,7 @@ Interaction.prototype = { }, pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -1264,7 +1264,7 @@ Interaction.prototype = { // test the draggable element against the dropzone's accept setting if ((utils.isElement(accept) && accept !== element) - || (scope.isString(accept) + || (utils.isString(accept) && !utils.matchesSelector(element, accept))) { continue; @@ -1485,7 +1485,7 @@ Interaction.prototype = { } // prevent Default only if were previously interacting - if (event && scope.isFunction(event.preventDefault)) { + if (event && utils.isFunction(event.preventDefault)) { this.checkAndPreventDefault(event, target, this.element); } @@ -1502,7 +1502,7 @@ Interaction.prototype = { // remove pointers if their ID isn't in this.pointerIds for (var i = 0; i < this.pointers.length; i++) { - if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { + if (utils.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { this.pointers.splice(i, 1); } } @@ -1510,7 +1510,7 @@ Interaction.prototype = { for (i = 0; i < scope.interactions.length; i++) { // remove this interaction if it's not the only one of it's type if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { - scope.interactions.splice(scope.indexOf(scope.interactions, this), 1); + scope.interactions.splice(utils.indexOf(scope.interactions, this), 1); } } }, @@ -1583,7 +1583,7 @@ Interaction.prototype = { addPointer: function (pointer) { var id = utils.getPointerId(pointer), - index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); + index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); if (index === -1) { index = this.pointerIds.length; @@ -1597,7 +1597,7 @@ Interaction.prototype = { removePointer: function (pointer) { var id = utils.getPointerId(pointer), - index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); + index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); if (index === -1) { return; } @@ -1616,7 +1616,7 @@ Interaction.prototype = { // The inertia start event should be this.pointers[0] if (this.inertiaStatus.active) { return; } - var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + var index = this.mouse? 0: utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); if (index === -1) { return; } @@ -1624,7 +1624,7 @@ Interaction.prototype = { }, collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); + var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); // do not fire a tap event if the pointer was moved before being lifted if (eventType === 'tap' && (this.pointerWasMoved @@ -1676,7 +1676,7 @@ Interaction.prototype = { }, firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)), + var pointerIndex = this.mouse? 0 : utils.indexOf(utils.getPointerId(pointer)), pointerEvent = {}, i, // for tap events @@ -1703,7 +1703,7 @@ Interaction.prototype = { pointerEvent.type = eventType; pointerEvent.pointerId = utils.getPointerId(pointer); pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' - : scope.isString(pointer.pointerType) + : utils.isString(pointer.pointerType) ? pointer.pointerType : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; } @@ -1800,7 +1800,7 @@ Interaction.prototype = { }; for (i = 0; i < len; i++) { - if (scope.isFunction(snap.targets[i])) { + if (utils.isFunction(snap.targets[i])) { target = snap.targets[i](relative.x, relative.y, this); } else { @@ -1810,10 +1810,10 @@ Interaction.prototype = { if (!target) { continue; } targets.push({ - x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, - y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, + x: utils.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, + y: utils.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, - range: scope.isNumber(target.range)? target.range: snap.range + range: utils.isNumber(target.range)? target.range: snap.range }); } } @@ -1923,7 +1923,7 @@ Interaction.prototype = { var rect, restrictedX, restrictedY; - if (scope.isString(restriction)) { + if (utils.isString(restriction)) { if (restriction === 'parent') { restriction = utils.parentElement(this.element); } @@ -1937,7 +1937,7 @@ Interaction.prototype = { if (!restriction) { return status; } } - if (scope.isFunction(restriction)) { + if (utils.isFunction(restriction)) { restriction = restriction(page.x, page.y, this.element); } @@ -2043,7 +2043,7 @@ Interaction.prototype = { options = this.target.options[this.prepared.name].autoScroll, container = options.container || scope.getWindow(this.element); - if (scope.isWindow(container)) { + if (utils.isWindow(container)) { left = pointer.clientX < scope.autoScroll.margin; top = pointer.clientY < scope.autoScroll.margin; right = pointer.clientX > container.innerWidth - scope.autoScroll.margin; @@ -2181,7 +2181,7 @@ function getInteractionFromPointer (pointer, eventType, eventTarget) { // get interaction that has this pointer for (i = 0; i < len; i++) { - if (scope.contains(scope.interactions[i].pointerIds, id)) { + if (utils.contains(scope.interactions[i].pointerIds, id)) { return scope.interactions[i]; } } diff --git a/src/interact.js b/src/interact.js index 85d165086..e4749bd60 100644 --- a/src/interact.js +++ b/src/interact.js @@ -19,13 +19,6 @@ InteractEvent = require('./InteractEvent'), Interaction = require('./Interaction'); - scope.pEventTypes = null; - - scope.documents = []; // all documents being listened to - - scope.interactables = []; // all set interactables - scope.interactions = []; // all interactions - scope.dynamicDrop = false; scope.defaultOptions = require('./defaultOptions'); @@ -138,11 +131,11 @@ else if (origin === 'self') { origin = interactable.getRect(element); } - else if (scope.trySelector(origin)) { + else if (utils.trySelector(origin)) { origin = utils.closest(element, origin) || { x: 0, y: 0 }; } - if (scope.isFunction(origin)) { + if (utils.isFunction(origin)) { origin = origin(interactable && element); } @@ -166,7 +159,7 @@ if (!ignoreFrom || !utils.isElement(element)) { return false; } - if (scope.isString(ignoreFrom)) { + if (utils.isString(ignoreFrom)) { return utils.matchesUpTo(element, ignoreFrom, interactableElement); } else if (utils.isElement(ignoreFrom)) { @@ -183,7 +176,7 @@ if (!utils.isElement(element)) { return false; } - if (scope.isString(allowFrom)) { + if (utils.isString(allowFrom)) { return utils.matchesUpTo(element, allowFrom, interactableElement); } else if (utils.isElement(allowFrom)) { @@ -338,11 +331,11 @@ = (object) interact \*/ interact.on = function (type, listener, useCapture) { - if (scope.isString(type) && type.search(' ') !== -1) { + if (utils.isString(type) && type.search(' ') !== -1) { type = type.trim().split(/ +/); } - if (scope.isArray(type)) { + if (utils.isArray(type)) { for (var i = 0; i < type.length; i++) { interact.on(type[i], listener, useCapture); } @@ -350,7 +343,7 @@ return interact; } - if (scope.isObject(type)) { + if (utils.isObject(type)) { for (var prop in type) { interact.on(prop, type[prop], listener); } @@ -359,7 +352,7 @@ } // if it is an InteractEvent type, add listener to globalEvents - if (scope.contains(scope.eventTypes, type)) { + if (utils.contains(scope.eventTypes, type)) { // if this type of event was never bound if (!scope.globalEvents[type]) { scope.globalEvents[type] = [listener]; @@ -388,11 +381,11 @@ = (object) interact \*/ interact.off = function (type, listener, useCapture) { - if (scope.isString(type) && type.search(' ') !== -1) { + if (utils.isString(type) && type.search(' ') !== -1) { type = type.trim().split(/ +/); } - if (scope.isArray(type)) { + if (utils.isArray(type)) { for (var i = 0; i < type.length; i++) { interact.off(type[i], listener, useCapture); } @@ -400,7 +393,7 @@ return interact; } - if (scope.isObject(type)) { + if (utils.isObject(type)) { for (var prop in type) { interact.off(prop, type[prop], listener); } @@ -408,14 +401,14 @@ return interact; } - if (!scope.contains(scope.eventTypes, type)) { + if (!utils.contains(scope.eventTypes, type)) { events.remove(scope.document, type, listener, useCapture); } else { var index; if (type in scope.globalEvents - && (index = scope.indexOf(scope.globalEvents[type], listener)) !== -1) { + && (index = utils.indexOf(scope.globalEvents[type], listener)) !== -1) { scope.globalEvents[type].splice(index, 1); } } @@ -568,7 +561,7 @@ = (number | interact) The current margin value or interact \*/ interact.margin = function (newvalue) { - if (scope.isNumber(newvalue)) { + if (utils.isNumber(newvalue)) { scope.margin = newvalue; return interact; @@ -625,7 +618,7 @@ = (boolean | interact) The current setting or interact \*/ interact.dynamicDrop = function (newValue) { - if (scope.isBool(newValue)) { + if (utils.isBool(newValue)) { //if (dragging && dynamicDrop !== newValue && !newValue) { //calcRects(dropzones); //} @@ -647,7 +640,7 @@ = (number | Interactable) The current setting or interact \*/ interact.pointerMoveTolerance = function (newValue) { - if (scope.isNumber(newValue)) { + if (utils.isNumber(newValue)) { scope.pointerMoveTolerance = newValue; return this; @@ -669,7 +662,7 @@ - newValue (number) #optional Any number. newValue <= 0 means no interactions. \*/ interact.maxInteractions = function (newValue) { - if (scope.isNumber(newValue)) { + if (utils.isNumber(newValue)) { scope.maxInteractions = newValue; return this; @@ -683,7 +676,7 @@ var offsetX = 0, offsetY = 0; - if (scope.isObject(grid.offset)) { + if (utils.isObject(grid.offset)) { offsetX = grid.offset.x; offsetY = grid.offset.y; } @@ -709,7 +702,7 @@ } function listenToDocument (doc) { - if (scope.contains(scope.documents, doc)) { return; } + if (utils.contains(scope.documents, doc)) { return; } var win = doc.defaultView || doc.parentWindow; diff --git a/src/scope.js b/src/scope.js index 7c6265da5..7854aa135 100644 --- a/src/scope.js +++ b/src/scope.js @@ -3,9 +3,14 @@ var scope = {}, extend = require('./utils/extend'); +scope.pEventTypes = null; + +scope.documents = []; // all documents being listened to + +scope.interactables = []; // all set interactables +scope.interactions = []; // all interactions + extend(scope, require('./utils/window')); extend(scope, require('./utils/domObjects')); -extend(scope, require('./utils/arr.js')); -extend(scope, require('./utils/isType')); module.exports = scope; From bd7c60201abd633a3ef46393b4c42ee0de11a275 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 28 Jun 2015 12:29:41 +0200 Subject: [PATCH 042/131] Return only the scope object from interact.debug() --- src/interact.js | 54 ++----------------------------------------------- src/scope.js | 2 ++ 2 files changed, 4 insertions(+), 52 deletions(-) diff --git a/src/interact.js b/src/interact.js index e4749bd60..2945c3a7c 100644 --- a/src/interact.js +++ b/src/interact.js @@ -482,61 +482,11 @@ * interact.debug [ method ] * - * Returns debugging data + * Returns an object which exposes internal data = (object) An object with properties that outline the current state and expose internal functions and variables \*/ interact.debug = function () { - var interaction = scope.interactions[0] || new Interaction(); - - return { - interactions : scope.interactions, - target : interaction.target, - dragging : interaction.dragging, - resizing : interaction.resizing, - gesturing : interaction.gesturing, - prepared : interaction.prepared, - matches : interaction.matches, - matchElements : interaction.matchElements, - - prevCoords : interaction.prevCoords, - startCoords : interaction.startCoords, - - pointerIds : interaction.pointerIds, - pointers : interaction.pointers, - addPointer : scope.listeners.addPointer, - removePointer : scope.listeners.removePointer, - recordPointer : scope.listeners.recordPointer, - - snap : interaction.snapStatus, - restrict : interaction.restrictStatus, - inertia : interaction.inertiaStatus, - - downTime : interaction.downTimes[0], - downEvent : interaction.downEvent, - downPointer : interaction.downPointer, - prevEvent : interaction.prevEvent, - - Interactable : Interactable, - interactables : scope.interactables, - pointerIsDown : interaction.pointerIsDown, - defaultOptions : scope.defaultOptions, - defaultActionChecker : scope.defaultActionChecker, - - actionCursors : scope.actionCursors, - dragMove : scope.listeners.dragMove, - resizeMove : scope.listeners.resizeMove, - gestureMove : scope.listeners.gestureMove, - pointerUp : scope.listeners.pointerUp, - pointerDown : scope.listeners.pointerDown, - pointerMove : scope.listeners.pointerMove, - pointerHover : scope.listeners.pointerHover, - - eventTypes : scope.eventTypes, - - events : events, - globalEvents : scope.globalEvents, - delegatedEvents : scope.delegatedEvents - }; + return scope; }; // expose the functions used to calculate multi-touch properties diff --git a/src/scope.js b/src/scope.js index 7854aa135..d072a2905 100644 --- a/src/scope.js +++ b/src/scope.js @@ -10,6 +10,8 @@ scope.documents = []; // all documents being listened to scope.interactables = []; // all set interactables scope.interactions = []; // all interactions +scope.events = require('./utils/events'); + extend(scope, require('./utils/window')); extend(scope, require('./utils/domObjects')); From 68996bc3e58383bb9e3ad27aa274a9c20991d2e1 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 29 Jun 2015 23:59:18 +0200 Subject: [PATCH 043/131] Begin the separation of action-specific code --- src/Interactable.js | 495 +----------------------------------- src/Interaction.js | 452 +-------------------------------- src/actions/base.js | 63 +++++ src/actions/drag.js | 556 +++++++++++++++++++++++++++++++++++++++++ src/actions/gesture.js | 115 +++++++++ src/actions/index.js | 112 +++++++++ src/actions/resize.js | 326 ++++++++++++++++++++++++ src/interact.js | 59 +---- src/scope.js | 2 + 9 files changed, 1185 insertions(+), 995 deletions(-) create mode 100644 src/actions/base.js create mode 100644 src/actions/drag.js create mode 100644 src/actions/gesture.js create mode 100644 src/actions/index.js create mode 100644 src/actions/resize.js diff --git a/src/Interactable.js b/src/Interactable.js index 69e211966..d3c41df36 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -2,7 +2,8 @@ var scope = require('./scope'), utils = require('./utils'), - events = require('./utils/events'); + events = require('./utils/events'), + actionBase = require('./actions/base'); /*\ * Interactable @@ -81,62 +82,6 @@ Interactable.prototype = { return this; }, - /*\ - * Interactable.draggable - [ method ] - * - * Gets or sets whether drag actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of drag events - | var isDraggable = interact('ul li').draggable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) - = (object) This Interactable - | interact(element).draggable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // the axis in which the first movement must be - | // for the drag sequence to start - | // 'xy' by default - any direction - | axis: 'x' || 'y' || 'xy', - | - | // max number of drags that can happen concurrently - | // with elements of this Interactable. Infinity by default - | max: Infinity, - | - | // max number of drags that can target the same element+Interactable - | // 1 by default - | maxPerElement: 2 - | }); - \*/ - draggable: function (options) { - if (utils.isObject(options)) { - this.options.drag.enabled = options.enabled === false? false: true; - this.setPerAction('drag', options); - this.setOnEvents('drag', options); - - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.drag.axis = options.axis; - } - else if (options.axis === null) { - delete this.options.drag.axis; - } - - return this; - } - - if (utils.isBool(options)) { - this.options.drag.enabled = options; - - return this; - } - - return this.options.drag; - }, - setPerAction: function (action, options) { // for all the default per-action options for (var option in options) { @@ -162,335 +107,6 @@ Interactable.prototype = { } }, - /*\ - * Interactable.dropzone - [ method ] - * - * Returns or sets whether elements can be dropped onto this - * Interactable to trigger drop events - * - * Dropzones can receive the following events: - * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends - * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone - * - `dragmove` when a draggable that has entered the dropzone is moved - * - `drop` when a draggable is dropped into this dropzone - * - * Use the `accept` option to allow only elements that match the given CSS selector or element. - * - * Use the `overlap` option to set how drops are checked for. The allowed values are: - * - `'pointer'`, the pointer must be over the dropzone (default) - * - `'center'`, the draggable element's center must be over the dropzone - * - a number from 0-1 which is the `(intersection area) / (draggable area)`. - * e.g. `0.5` for drop to happen when half of the area of the - * draggable is over the dropzone - * - - options (boolean | object | null) #optional The new value to be set. - | interact('.drop').dropzone({ - | accept: '.can-drop' || document.getElementById('single-drop'), - | overlap: 'pointer' || 'center' || zeroToOne - | } - = (boolean | object) The current setting or this Interactable - \*/ - dropzone: function (options) { - if (utils.isObject(options)) { - this.options.drop.enabled = options.enabled === false? false: true; - this.setOnEvents('drop', options); - this.accept(options.accept); - - if (/^(pointer|center)$/.test(options.overlap)) { - this.options.drop.overlap = options.overlap; - } - else if (utils.isNumber(options.overlap)) { - this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); - } - - return this; - } - - if (utils.isBool(options)) { - this.options.drop.enabled = options; - - return this; - } - - return this.options.drop; - }, - - dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) { - var dropped = false; - - // if the dropzone has no rect (eg. display: none) - // call the custom dropChecker or just return false - if (!(rect = rect || this.getRect(dropElement))) { - return (this.options.dropChecker - ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) - : false); - } - - var dropOverlap = this.options.drop.overlap; - - if (dropOverlap === 'pointer') { - var page = utils.getPageXY(pointer), - origin = scope.getOriginXY(draggable, draggableElement), - horizontal, - vertical; - - page.x += origin.x; - page.y += origin.y; - - horizontal = (page.x > rect.left) && (page.x < rect.right); - vertical = (page.y > rect.top ) && (page.y < rect.bottom); - - dropped = horizontal && vertical; - } - - var dragRect = draggable.getRect(draggableElement); - - if (dropOverlap === 'center') { - var cx = dragRect.left + dragRect.width / 2, - cy = dragRect.top + dragRect.height / 2; - - dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; - } - - if (utils.isNumber(dropOverlap)) { - var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) - * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), - overlapRatio = overlapArea / (dragRect.width * dragRect.height); - - dropped = overlapRatio >= dropOverlap; - } - - if (this.options.dropChecker) { - dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); - } - - return dropped; - }, - - /*\ - * Interactable.dropChecker - [ method ] - * - * Gets or sets the function used to check if a dragged element is - * over this Interactable. - * - - checker (function) #optional The function that will be called when checking for a drop - = (Function | Interactable) The checker function or this Interactable - * - * The checker function takes the following arguments: - * - - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag - - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer - - dropped (boolean) The value from the default drop check - - dropzone (Interactable) The dropzone interactable - - dropElement (Element) The dropzone element - - draggable (Interactable) The Interactable being dragged - - draggableElement (Element) The actual element that's being dragged - * - > Usage: - | interact(target) - | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent - | event, // TouchEvent/PointerEvent/MouseEvent - | dropped, // result of the default checker - | dropzone, // dropzone Interactable - | dropElement, // dropzone elemnt - | draggable, // draggable Interactable - | draggableElement) {// draggable element - | - | return dropped && event.target.hasAttribute('allow-drop'); - | } - \*/ - dropChecker: function (checker) { - if (utils.isFunction(checker)) { - this.options.dropChecker = checker; - - return this; - } - if (checker === null) { - delete this.options.getRect; - - return this; - } - - return this.options.dropChecker; - }, - - /*\ - * Interactable.accept - [ method ] - * - * Deprecated. add an `accept` property to the options object passed to - * @Interactable.dropzone instead. - * - * Gets or sets the Element or CSS selector match that this - * Interactable accepts if it is a dropzone. - * - - newValue (Element | string | null) #optional - * If it is an Element, then only that element can be dropped into this dropzone. - * If it is a string, the element being dragged must match it as a selector. - * If it is null, the accept options is cleared - it accepts any element. - * - = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable - \*/ - accept: function (newValue) { - if (utils.isElement(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - // test if it is a valid CSS selector - if (utils.trySelector(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.drop.accept; - - return this; - } - - return this.options.drop.accept; - }, - - /*\ - * Interactable.resizable - [ method ] - * - * Gets or sets whether resize actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of resize elements - | var isResizeable = interact('input[type=text]').resizable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) - = (object) This Interactable - | interact(element).resizable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | edges: { - | top : true, // Use pointer coords to check for resize. - | left : false, // Disable resizing from left edge. - | bottom: '.resize-s',// Resize if pointer target matches selector - | right : handleEl // Resize if pointer target is the given Element - | }, - | - | // a value of 'none' will limit the resize rect to a minimum of 0x0 - | // 'negate' will allow the rect to have negative width/height - | // 'reposition' will keep the width/height positive by swapping - | // the top and bottom edges and/or swapping the left and right edges - | invert: 'none' || 'negate' || 'reposition' - | - | // limit multiple resizes. - | // See the explanation in the @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - resizable: function (options) { - if (utils.isObject(options)) { - this.options.resize.enabled = options.enabled === false? false: true; - this.setPerAction('resize', options); - this.setOnEvents('resize', options); - - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.resize.axis = options.axis; - } - else if (options.axis === null) { - this.options.resize.axis = scope.defaultOptions.resize.axis; - } - - if (utils.isBool(options.square)) { - this.options.resize.square = options.square; - } - - return this; - } - if (utils.isBool(options)) { - this.options.resize.enabled = options; - - return this; - } - return this.options.resize; - }, - - /*\ - * Interactable.squareResize - [ method ] - * - * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead - * - * Gets or sets whether resizing is forced 1:1 aspect - * - = (boolean) Current setting - * - * or - * - - newValue (boolean) #optional - = (object) this Interactable - \*/ - squareResize: function (newValue) { - if (utils.isBool(newValue)) { - this.options.resize.square = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.resize.square; - - return this; - } - - return this.options.resize.square; - }, - - /*\ - * Interactable.gesturable - [ method ] - * - * Gets or sets whether multitouch gestures can be performed on the - * Interactable's element - * - = (boolean) Indicates if this can be the target of gesture events - | var isGestureable = interact(element).gesturable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) - = (object) this Interactable - | interact(element).gesturable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // limit multiple gestures. - | // See the explanation in @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - gesturable: function (options) { - if (utils.isObject(options)) { - this.options.gesture.enabled = options.enabled === false? false: true; - this.setPerAction('gesture', options); - this.setOnEvents('gesture', options); - - return this; - } - - if (utils.isBool(options)) { - this.options.gesture.enabled = options; - - return this; - } - - return this.options.gesture; - }, - /*\ * Interactable.autoScroll [ method ] @@ -716,7 +332,7 @@ Interactable.prototype = { return action; }, - defaultActionChecker: defaultActionChecker, + defaultActionChecker: actionBase.defaultActionChecker, /*\ * Interactable.actionChecker @@ -1340,109 +956,4 @@ Interactable.prototype = { } }; -function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { - // false, '', undefined, null - if (!value) { return false; } - - // true value, use pointer coords and element rect - if (value === true) { - // if dimensions are negative, "switch" edges - var width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left, - height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; - - if (width < 0) { - if (name === 'left' ) { name = 'right'; } - else if (name === 'right') { name = 'left' ; } - } - if (height < 0) { - if (name === 'top' ) { name = 'bottom'; } - else if (name === 'bottom') { name = 'top' ; } - } - - if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } - if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } - - if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } - if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } - } - - // the remaining checks require an element - if (!utils.isElement(element)) { return false; } - - return utils.isElement(value) - // the value is an element to use as a resize handle - ? value === element - // otherwise check if element matches value as selector - : utils.matchesUpTo(element, value, interactableElement); -} - -function defaultActionChecker (pointer, interaction, element) { - var rect = this.getRect(element), - shouldResize = false, - action = null, - resizeAxes = null, - resizeEdges, - page = utils.extend({}, interaction.curCoords.page), - options = this.options; - - if (!rect) { return null; } - - if (scope.actionIsEnabled.resize && options.resize.enabled) { - var resizeOptions = options.resize; - - resizeEdges = { - left: false, right: false, top: false, bottom: false - }; - - // if using resize.edges - if (utils.isObject(resizeOptions.edges)) { - for (var edge in resizeEdges) { - resizeEdges[edge] = checkResizeEdge(edge, - resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || scope.margin); - } - - resizeEdges.left = resizeEdges.left && !resizeEdges.right; - resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; - - shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; - } - else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); - - shouldResize = right || bottom; - resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); - } - } - - action = shouldResize - ? 'resize' - : scope.actionIsEnabled.drag && options.drag.enabled - ? 'drag' - : null; - - if (scope.actionIsEnabled.gesture - && interaction.pointerIds.length >=2 - && !(interaction.dragging || interaction.resizing)) { - action = 'gesture'; - } - - if (action) { - return { - name: action, - axis: resizeAxes, - edges: resizeEdges - }; - } - - return null; -} - -scope.defaultActionChecker = defaultActionChecker; - module.exports = Interactable; diff --git a/src/Interaction.js b/src/Interaction.js index a916c53bc..b6e988601 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -808,220 +808,6 @@ Interaction.prototype = { } }, - dragStart: function (event) { - var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); - - this.dragging = true; - this.target.fire(dragEvent); - - // reset active dropzones - this.activeDrops.dropzones = []; - this.activeDrops.elements = []; - this.activeDrops.rects = []; - - if (!this.dynamicDrop) { - this.setActiveDrops(this.element); - } - - var dropEvents = this.getDropEvents(event, dragEvent); - - if (dropEvents.activate) { - this.fireActiveDrops(dropEvents.activate); - } - - return dragEvent; - }, - - dragMove: function (event) { - var target = this.target, - dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), - draggableElement = this.element, - drop = this.getDrop(event, draggableElement); - - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; - - var dropEvents = this.getDropEvents(event, dragEvent); - - target.fire(dragEvent); - - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } - - this.prevDropTarget = this.dropTarget; - this.prevDropElement = this.dropElement; - - return dragEvent; - }, - - resizeStart: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); - - if (this.prepared.edges) { - var startRect = this.target.getRect(this.element); - - if (this.target.options.resize.square) { - var squareEdges = utils.extend({}, this.prepared.edges); - - squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); - squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); - squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); - squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); - - this.prepared._squareEdges = squareEdges; - } - else { - this.prepared._squareEdges = null; - } - - this.resizeRects = { - start : startRect, - current : utils.extend({}, startRect), - restricted: utils.extend({}, startRect), - previous : utils.extend({}, startRect), - delta : { - left: 0, right : 0, width : 0, - top : 0, bottom: 0, height: 0 - } - }; - - resizeEvent.rect = this.resizeRects.restricted; - resizeEvent.deltaRect = this.resizeRects.delta; - } - - this.target.fire(resizeEvent); - - this.resizing = true; - - return resizeEvent; - }, - - resizeMove: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); - - var edges = this.prepared.edges, - invert = this.target.options.resize.invert, - invertible = invert === 'reposition' || invert === 'negate'; - - if (edges) { - var dx = resizeEvent.dx, - dy = resizeEvent.dy, - - start = this.resizeRects.start, - current = this.resizeRects.current, - restricted = this.resizeRects.restricted, - delta = this.resizeRects.delta, - previous = utils.extend(this.resizeRects.previous, restricted); - - if (this.target.options.resize.square) { - var originalEdges = edges; - - edges = this.prepared._squareEdges; - - if ((originalEdges.left && originalEdges.bottom) - || (originalEdges.right && originalEdges.top)) { - dy = -dx; - } - else if (originalEdges.left || originalEdges.right) { dy = dx; } - else if (originalEdges.top || originalEdges.bottom) { dx = dy; } - } - - // update the 'current' rect without modifications - if (edges.top ) { current.top += dy; } - if (edges.bottom) { current.bottom += dy; } - if (edges.left ) { current.left += dx; } - if (edges.right ) { current.right += dx; } - - if (invertible) { - // if invertible, copy the current rect - utils.extend(restricted, current); - - if (invert === 'reposition') { - // swap edge values if necessary to keep width/height positive - var swap; - - if (restricted.top > restricted.bottom) { - swap = restricted.top; - - restricted.top = restricted.bottom; - restricted.bottom = swap; - } - if (restricted.left > restricted.right) { - swap = restricted.left; - - restricted.left = restricted.right; - restricted.right = swap; - } - } - } - else { - // if not invertible, restrict to minimum of 0x0 rect - restricted.top = Math.min(current.top, start.bottom); - restricted.bottom = Math.max(current.bottom, start.top); - restricted.left = Math.min(current.left, start.right); - restricted.right = Math.max(current.right, start.left); - } - - restricted.width = restricted.right - restricted.left; - restricted.height = restricted.bottom - restricted.top ; - - for (var edge in restricted) { - delta[edge] = restricted[edge] - previous[edge]; - } - - resizeEvent.edges = this.prepared.edges; - resizeEvent.rect = restricted; - resizeEvent.deltaRect = delta; - } - - this.target.fire(resizeEvent); - - return resizeEvent; - }, - - gestureStart: function (event) { - var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); - - gestureEvent.ds = 0; - - this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; - this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; - this.gesture.scale = 1; - - this.gesturing = true; - - this.target.fire(gestureEvent); - - return gestureEvent; - }, - - gestureMove: function (event) { - if (!this.pointerIds.length) { - return this.prevEvent; - } - - var gestureEvent; - - gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); - gestureEvent.ds = gestureEvent.scale - this.gesture.scale; - - this.target.fire(gestureEvent); - - this.gesture.prevAngle = gestureEvent.angle; - this.gesture.prevDistance = gestureEvent.distance; - - if (gestureEvent.scale !== Infinity && - gestureEvent.scale !== null && - gestureEvent.scale !== undefined && - !isNaN(gestureEvent.scale)) { - - this.gesture.scale = gestureEvent.scale; - } - - return gestureEvent; - }, - pointerHold: function (pointer, event, eventTarget) { this.collectEventTargets(pointer, event, eventTarget, 'hold'); }, @@ -1069,8 +855,7 @@ Interaction.prototype = { // End interact move events and stop auto-scroll unless inertia is enabled pointerEnd: function (pointer, event, eventTarget, curEventTarget) { - var endEvent, - target = this.target, + var target = this.target, options = target && target.options, inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, inertiaStatus = this.inertiaStatus; @@ -1217,247 +1002,18 @@ Interaction.prototype = { } if (this.dragging) { - endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); - - var draggableElement = this.element, - drop = this.getDrop(event, draggableElement); - - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; - - var dropEvents = this.getDropEvents(event, endEvent); - - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } - if (dropEvents.deactivate) { - this.fireActiveDrops(dropEvents.deactivate); - } - - target.fire(endEvent); + this.dragEnd(event); } else if (this.resizing) { - endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); - target.fire(endEvent); + this.resizeEnd(event); } else if (this.gesturing) { - endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); - target.fire(endEvent); + this.gestureEnd(event); } this.stop(event); }, - collectDrops: function (element) { - var drops = [], - elements = [], - i; - - element = element || this.element; - - // collect all dropzones and their elements which qualify for a drop - for (i = 0; i < scope.interactables.length; i++) { - if (!scope.interactables[i].options.drop.enabled) { continue; } - - var current = scope.interactables[i], - accept = current.options.drop.accept; - - // test the draggable element against the dropzone's accept setting - if ((utils.isElement(accept) && accept !== element) - || (utils.isString(accept) - && !utils.matchesSelector(element, accept))) { - - continue; - } - - // query for new elements if necessary - var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; - - for (var j = 0, len = dropElements.length; j < len; j++) { - var currentElement = dropElements[j]; - - if (currentElement === element) { - continue; - } - - drops.push(current); - elements.push(currentElement); - } - } - - return { - dropzones: drops, - elements: elements - }; - }, - - fireActiveDrops: function (event) { - var i, - current, - currentElement, - prevElement; - - // loop through all active dropzones and trigger event - for (i = 0; i < this.activeDrops.dropzones.length; i++) { - current = this.activeDrops.dropzones[i]; - currentElement = this.activeDrops.elements [i]; - - // prevent trigger of duplicate events on same element - if (currentElement !== prevElement) { - // set current element as event target - event.target = currentElement; - current.fire(event); - } - prevElement = currentElement; - } - }, - - // Collect a new set of possible drops and save them in activeDrops. - // setActiveDrops should always be called when a drag has just started or a - // drag event happens while dynamicDrop is true - setActiveDrops: function (dragElement) { - // get dropzones and their elements that could receive the draggable - var possibleDrops = this.collectDrops(dragElement, true); - - this.activeDrops.dropzones = possibleDrops.dropzones; - this.activeDrops.elements = possibleDrops.elements; - this.activeDrops.rects = []; - - for (var i = 0; i < this.activeDrops.dropzones.length; i++) { - this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); - } - }, - - getDrop: function (event, dragElement) { - var validDrops = []; - - if (scope.dynamicDrop) { - this.setActiveDrops(dragElement); - } - - // collect all dropzones and their elements which qualify for a drop - for (var j = 0; j < this.activeDrops.dropzones.length; j++) { - var current = this.activeDrops.dropzones[j], - currentElement = this.activeDrops.elements [j], - rect = this.activeDrops.rects [j]; - - validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) - ? currentElement - : null); - } - - // get the most appropriate dropzone based on DOM depth and order - var dropIndex = utils.indexOfDeepestElement(validDrops), - dropzone = this.activeDrops.dropzones[dropIndex] || null, - element = this.activeDrops.elements [dropIndex] || null; - - return { - dropzone: dropzone, - element: element - }; - }, - - getDropEvents: function (pointerEvent, dragEvent) { - var dropEvents = { - enter : null, - leave : null, - activate : null, - deactivate: null, - move : null, - drop : null - }; - - if (this.dropElement !== this.prevDropElement) { - // if there was a prevDropTarget, create a dragleave event - if (this.prevDropTarget) { - dropEvents.leave = { - target : this.prevDropElement, - dropzone : this.prevDropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragleave' - }; - - dragEvent.dragLeave = this.prevDropElement; - dragEvent.prevDropzone = this.prevDropTarget; - } - // if the dropTarget is not null, create a dragenter event - if (this.dropTarget) { - dropEvents.enter = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragenter' - }; - - dragEvent.dragEnter = this.dropElement; - dragEvent.dropzone = this.dropTarget; - } - } - - if (dragEvent.type === 'dragend' && this.dropTarget) { - dropEvents.drop = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'drop' - }; - - dragEvent.dropzone = this.dropTarget; - } - if (dragEvent.type === 'dragstart') { - dropEvents.activate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropactivate' - }; - } - if (dragEvent.type === 'dragend') { - dropEvents.deactivate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropdeactivate' - }; - } - if (dragEvent.type === 'dragmove' && this.dropTarget) { - dropEvents.move = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - dragmove : dragEvent, - timeStamp : dragEvent.timeStamp, - type : 'dropmove' - }; - dragEvent.dropzone = this.dropTarget; - } - - return dropEvents; - }, - currentAction: function () { return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; }, diff --git a/src/actions/base.js b/src/actions/base.js new file mode 100644 index 000000000..d838432d0 --- /dev/null +++ b/src/actions/base.js @@ -0,0 +1,63 @@ +'use strict'; + +var scope = require('../scope'), + browser = require('../utils/browser'), + checkers = []; + +var actions = { + scope: scope, + checkers: checkers, + + addEventTypes: function (eventTypes) { + for (var i = 0; i < eventTypes.length; i++) { + scope.eventTypes.push(eventTypes[i]); + } + }, + + defaultActionChecker: function (pointer, interaction, element) { + var rect = this.getRect(element), + action = null; + + for (var i = 0; !action && i < checkers.length; i++) { + action = checkers[i](pointer, event, this, element, interaction, rect); + } + + return action; + } +}; + +scope.actionCursors = browser.isIe9OrOlder ? { + drag : 'move', + resizex : 'e-resize', + resizey : 's-resize', + resizexy: 'se-resize', + + resizetop : 'n-resize', + resizeleft : 'w-resize', + resizebottom : 's-resize', + resizeright : 'e-resize', + resizetopleft : 'se-resize', + resizebottomright: 'se-resize', + resizetopright : 'ne-resize', + resizebottomleft : 'ne-resize', + + gesture : '' +} : { + drag : 'move', + resizex : 'ew-resize', + resizey : 'ns-resize', + resizexy: 'nwse-resize', + + resizetop : 'ns-resize', + resizeleft : 'ew-resize', + resizebottom : 'ns-resize', + resizeright : 'ew-resize', + resizetopleft : 'nwse-resize', + resizebottomright: 'nwse-resize', + resizetopright : 'nesw-resize', + resizebottomleft : 'nesw-resize', + + gesture : '' +}; + +module.exports = actions; diff --git a/src/actions/drag.js b/src/actions/drag.js new file mode 100644 index 000000000..f057e7359 --- /dev/null +++ b/src/actions/drag.js @@ -0,0 +1,556 @@ +'use strict'; + +var base = require('./base'), + scope = base.scope, + utils = require('../utils'), + Interaction = require('../Interaction'), + InteractEvent = require('../InteractEvent'), + Interactable = require('../Interactable'); + + +base.addEventTypes([ + 'dragstart', + 'dragmove', + 'draginertiastart', + 'dragend', + 'dragenter', + 'dragleave', + 'dropactivate', + 'dropdeactivate', + 'dropmove', + 'drop' +]); + +base.checkers.push(function (pointer, event, interactable) { +return scope.actionIsEnabled.drag && interactable.options.drag.enabled + ? { name: 'drag' } + : null; +}); + +Interaction.prototype.dragStart = function (event) { + var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); + + this.dragging = true; + this.target.fire(dragEvent); + + // reset active dropzones + this.activeDrops.dropzones = []; + this.activeDrops.elements = []; + this.activeDrops.rects = []; + + if (!this.dynamicDrop) { + this.setActiveDrops(this.element); + } + + var dropEvents = this.getDropEvents(event, dragEvent); + + if (dropEvents.activate) { + this.fireActiveDrops(dropEvents.activate); + } + + return dragEvent; +}; + +Interaction.prototype.dragMove = function (event) { + var target = this.target, + dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), + draggableElement = this.element, + drop = this.getDrop(event, draggableElement); + + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; + + var dropEvents = this.getDropEvents(event, dragEvent); + + target.fire(dragEvent); + + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } + + this.prevDropTarget = this.dropTarget; + this.prevDropElement = this.dropElement; + + return dragEvent; +}; + +Interaction.prototype.dragEnd = function (event) { + var endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); + + var draggableElement = this.element, + drop = this.getDrop(event, draggableElement); + + this.dropTarget = drop.dropzone; + this.dropElement = drop.element; + + var dropEvents = this.getDropEvents(event, endEvent); + + if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } + if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } + if (dropEvents.deactivate) { + this.fireActiveDrops(dropEvents.deactivate); + } + + this.target.fire(endEvent); +}; + +Interaction.prototype.collectDrops = function (element) { + var drops = [], + elements = [], + i; + + element = element || this.element; + + // collect all dropzones and their elements which qualify for a drop + for (i = 0; i < scope.interactables.length; i++) { + if (!scope.interactables[i].options.drop.enabled) { continue; } + + var current = scope.interactables[i], + accept = current.options.drop.accept; + + // test the draggable element against the dropzone's accept setting + if ((utils.isElement(accept) && accept !== element) + || (utils.isString(accept) + && !utils.matchesSelector(element, accept))) { + + continue; + } + + // query for new elements if necessary + var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; + + for (var j = 0, len = dropElements.length; j < len; j++) { + var currentElement = dropElements[j]; + + if (currentElement === element) { + continue; + } + + drops.push(current); + elements.push(currentElement); + } + } + + return { + dropzones: drops, + elements: elements + }; +}; + +Interaction.prototype.fireActiveDrops = function (event) { + var i, + current, + currentElement, + prevElement; + + // loop through all active dropzones and trigger event + for (i = 0; i < this.activeDrops.dropzones.length; i++) { + current = this.activeDrops.dropzones[i]; + currentElement = this.activeDrops.elements [i]; + + // prevent trigger of duplicate events on same element + if (currentElement !== prevElement) { + // set current element as event target + event.target = currentElement; + current.fire(event); + } + prevElement = currentElement; + } +}; + +// Collect a new set of possible drops and save them in activeDrops. +// setActiveDrops should always be called when a drag has just started or a +// drag event happens while dynamicDrop is true +Interaction.prototype.setActiveDrops = function (dragElement) { + // get dropzones and their elements that could receive the draggable + var possibleDrops = this.collectDrops(dragElement, true); + + this.activeDrops.dropzones = possibleDrops.dropzones; + this.activeDrops.elements = possibleDrops.elements; + this.activeDrops.rects = []; + + for (var i = 0; i < this.activeDrops.dropzones.length; i++) { + this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); + } +}; + +Interaction.prototype.getDrop = function (event, dragElement) { + var validDrops = []; + + if (scope.dynamicDrop) { + this.setActiveDrops(dragElement); + } + + // collect all dropzones and their elements which qualify for a drop + for (var j = 0; j < this.activeDrops.dropzones.length; j++) { + var current = this.activeDrops.dropzones[j], + currentElement = this.activeDrops.elements [j], + rect = this.activeDrops.rects [j]; + + validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) + ? currentElement + : null); + } + + // get the most appropriate dropzone based on DOM depth and order + var dropIndex = utils.indexOfDeepestElement(validDrops), + dropzone = this.activeDrops.dropzones[dropIndex] || null, + element = this.activeDrops.elements [dropIndex] || null; + + return { + dropzone: dropzone, + element: element + }; +}; + +Interaction.prototype.getDropEvents = function (pointerEvent, dragEvent) { + var dropEvents = { + enter : null, + leave : null, + activate : null, + deactivate: null, + move : null, + drop : null + }; + + if (this.dropElement !== this.prevDropElement) { + // if there was a prevDropTarget, create a dragleave event + if (this.prevDropTarget) { + dropEvents.leave = { + target : this.prevDropElement, + dropzone : this.prevDropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragleave' + }; + + dragEvent.dragLeave = this.prevDropElement; + dragEvent.prevDropzone = this.prevDropTarget; + } + // if the dropTarget is not null, create a dragenter event + if (this.dropTarget) { + dropEvents.enter = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dragenter' + }; + + dragEvent.dragEnter = this.dropElement; + dragEvent.dropzone = this.dropTarget; + } + } + + if (dragEvent.type === 'dragend' && this.dropTarget) { + dropEvents.drop = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'drop' + }; + + dragEvent.dropzone = this.dropTarget; + } + if (dragEvent.type === 'dragstart') { + dropEvents.activate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropactivate' + }; + } + if (dragEvent.type === 'dragend') { + dropEvents.deactivate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + timeStamp : dragEvent.timeStamp, + type : 'dropdeactivate' + }; + } + if (dragEvent.type === 'dragmove' && this.dropTarget) { + dropEvents.move = { + target : this.dropElement, + dropzone : this.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : this, + dragmove : dragEvent, + timeStamp : dragEvent.timeStamp, + type : 'dropmove' + }; + dragEvent.dropzone = this.dropTarget; + } + + return dropEvents; +}; + +/*\ + * Interactable.draggable + [ method ] + * + * Gets or sets whether drag actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of drag events + | var isDraggable = interact('ul li').draggable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) + = (object) This Interactable + | interact(element).draggable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // the axis in which the first movement must be + | // for the drag sequence to start + | // 'xy' by default - any direction + | axis: 'x' || 'y' || 'xy', + | + | // max number of drags that can happen concurrently + | // with elements of this Interactable. Infinity by default + | max: Infinity, + | + | // max number of drags that can target the same element+Interactable + | // 1 by default + | maxPerElement: 2 + | }); +\*/ +Interactable.prototype.draggable = function (options) { + if (utils.isObject(options)) { + this.options.drag.enabled = options.enabled === false? false: true; + this.setPerAction('drag', options); + this.setOnEvents('drag', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.drag.axis = options.axis; + } + else if (options.axis === null) { + delete this.options.drag.axis; + } + + return this; + } + + if (utils.isBool(options)) { + this.options.drag.enabled = options; + + return this; + } + + return this.options.drag; +}; + +/*\ + * Interactable.dropzone + [ method ] + * + * Returns or sets whether elements can be dropped onto this + * Interactable to trigger drop events + * + * Dropzones can receive the following events: + * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends + * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone + * - `dragmove` when a draggable that has entered the dropzone is moved + * - `drop` when a draggable is dropped into this dropzone + * + * Use the `accept` option to allow only elements that match the given CSS selector or element. + * + * Use the `overlap` option to set how drops are checked for. The allowed values are: + * - `'pointer'`, the pointer must be over the dropzone (default) + * - `'center'`, the draggable element's center must be over the dropzone + * - a number from 0-1 which is the `(intersection area) / (draggable area)`. + * e.g. `0.5` for drop to happen when half of the area of the + * draggable is over the dropzone + * + - options (boolean | object | null) #optional The new value to be set. + | interact('.drop').dropzone({ + | accept: '.can-drop' || document.getElementById('single-drop'), + | overlap: 'pointer' || 'center' || zeroToOne + | } + = (boolean | object) The current setting or this Interactable +\*/ +Interactable.prototype.dropzone = function (options) { + if (utils.isObject(options)) { + this.options.drop.enabled = options.enabled === false? false: true; + this.setOnEvents('drop', options); + this.accept(options.accept); + + if (/^(pointer|center)$/.test(options.overlap)) { + this.options.drop.overlap = options.overlap; + } + else if (utils.isNumber(options.overlap)) { + this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); + } + + return this; + } + + if (utils.isBool(options)) { + this.options.drop.enabled = options; + + return this; + } + + return this.options.drop; +}; + +Interactable.prototype.dropCheck = function (pointer, event, draggable, draggableElement, dropElement, rect) { + var dropped = false; + + // if the dropzone has no rect (eg. display: none) + // call the custom dropChecker or just return false + if (!(rect = rect || this.getRect(dropElement))) { + return (this.options.dropChecker + ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) + : false); + } + + var dropOverlap = this.options.drop.overlap; + + if (dropOverlap === 'pointer') { + var page = utils.getPageXY(pointer), + origin = scope.getOriginXY(draggable, draggableElement), + horizontal, + vertical; + + page.x += origin.x; + page.y += origin.y; + + horizontal = (page.x > rect.left) && (page.x < rect.right); + vertical = (page.y > rect.top ) && (page.y < rect.bottom); + + dropped = horizontal && vertical; + } + + var dragRect = draggable.getRect(draggableElement); + + if (dropOverlap === 'center') { + var cx = dragRect.left + dragRect.width / 2, + cy = dragRect.top + dragRect.height / 2; + + dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; + } + + if (utils.isNumber(dropOverlap)) { + var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) + * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), + overlapRatio = overlapArea / (dragRect.width * dragRect.height); + + dropped = overlapRatio >= dropOverlap; + } + + if (this.options.dropChecker) { + dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); + } + + return dropped; +}; + +/*\ + * Interactable.dropChecker + [ method ] + * + * Gets or sets the function used to check if a dragged element is + * over this Interactable. + * + - checker (function) #optional The function that will be called when checking for a drop + = (Function | Interactable) The checker function or this Interactable + * + * The checker function takes the following arguments: + * + - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag + - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer + - dropped (boolean) The value from the default drop check + - dropzone (Interactable) The dropzone interactable + - dropElement (Element) The dropzone element + - draggable (Interactable) The Interactable being dragged + - draggableElement (Element) The actual element that's being dragged + * + > Usage: + | interact(target) + | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent + | event, // TouchEvent/PointerEvent/MouseEvent + | dropped, // result of the default checker + | dropzone, // dropzone Interactable + | dropElement, // dropzone elemnt + | draggable, // draggable Interactable + | draggableElement) {// draggable element + | + | return dropped && event.target.hasAttribute('allow-drop'); + | } +\*/ +Interactable.prototype.dropChecker = function (checker) { + if (utils.isFunction(checker)) { + this.options.dropChecker = checker; + + return this; + } + if (checker === null) { + delete this.options.getRect; + + return this; + } + + return this.options.dropChecker; +}; + +/*\ + * Interactable.accept + [ method ] + * + * Deprecated. add an `accept` property to the options object passed to + * @Interactable.dropzone instead. + * + * Gets or sets the Element or CSS selector match that this + * Interactable accepts if it is a dropzone. + * + - newValue (Element | string | null) #optional + * If it is an Element, then only that element can be dropped into this dropzone. + * If it is a string, the element being dragged must match it as a selector. + * If it is null, the accept options is cleared - it accepts any element. + * + = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable +\*/ +Interactable.prototype.accept = function (newValue) { + if (utils.isElement(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + // test if it is a valid CSS selector + if (utils.trySelector(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.drop.accept; + + return this; + } + + return this.options.drop.accept; +}; diff --git a/src/actions/gesture.js b/src/actions/gesture.js new file mode 100644 index 000000000..7223c224e --- /dev/null +++ b/src/actions/gesture.js @@ -0,0 +1,115 @@ +'use strict'; + +var base = require('./base'), + scope = base.scope, + utils = require('../utils'), + Interaction = require('../Interaction'), + InteractEvent = require('../InteractEvent'), + Interactable = require('../Interactable'); + +base.addEventTypes([ + 'gesturestart', + 'gesturemove', + 'gestureinertiastart', + 'gestureend' +]); + +base.checkers.push(function (pointer, event, interactable, element, interaction) { + if (scope.actionIsEnabled.gesture + && interaction.pointerIds.length >=2 + && !(interaction.dragging || interaction.resizing)) { + return { name: 'gesture' }; + } + + return null; +}); + +Interaction.prototype.gestureStart = function (event) { + var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); + + gestureEvent.ds = 0; + + this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; + this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; + this.gesture.scale = 1; + + this.gesturing = true; + + this.target.fire(gestureEvent); + + return gestureEvent; +}; + +Interaction.prototype.gestureMove = function (event) { + if (!this.pointerIds.length) { + return this.prevEvent; + } + + var gestureEvent; + + gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); + gestureEvent.ds = gestureEvent.scale - this.gesture.scale; + + this.target.fire(gestureEvent); + + this.gesture.prevAngle = gestureEvent.angle; + this.gesture.prevDistance = gestureEvent.distance; + + if (gestureEvent.scale !== Infinity && + gestureEvent.scale !== null && + gestureEvent.scale !== undefined && + !isNaN(gestureEvent.scale)) { + + this.gesture.scale = gestureEvent.scale; + } + + return gestureEvent; +}; + +Interaction.prototype.gestureEnd = function (event) { + var endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); + + this.target.fire(endEvent); +}; + +/*\ + * Interactable.gesturable + [ method ] + * + * Gets or sets whether multitouch gestures can be performed on the + * Interactable's element + * + = (boolean) Indicates if this can be the target of gesture events + | var isGestureable = interact(element).gesturable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) + = (object) this Interactable + | interact(element).gesturable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | // limit multiple gestures. + | // See the explanation in @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); +\*/ +Interactable.prototype.gesturable = function (options) { + if (utils.isObject(options)) { + this.options.gesture.enabled = options.enabled === false? false: true; + this.setPerAction('gesture', options); + this.setOnEvents('gesture', options); + + return this; + } + + if (utils.isBool(options)) { + this.options.gesture.enabled = options; + + return this; + } + + return this.options.gesture; +}; + diff --git a/src/actions/index.js b/src/actions/index.js new file mode 100644 index 000000000..1d7a7b7b8 --- /dev/null +++ b/src/actions/index.js @@ -0,0 +1,112 @@ +'use strict'; + +var scope = require('../scope'), + utils = require('../utils'); + +var actions = { + + checkResizeEdge: function (name, value, page, element, interactableElement, rect, margin) { + // false, '', undefined, null + if (!value) { return false; } + + // true value, use pointer coords and element rect + if (value === true) { + // if dimensions are negative, "switch" edges + var width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left, + height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; + + if (width < 0) { + if (name === 'left' ) { name = 'right'; } + else if (name === 'right') { name = 'left' ; } + } + if (height < 0) { + if (name === 'top' ) { name = 'bottom'; } + else if (name === 'bottom') { name = 'top' ; } + } + + if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } + if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } + + if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } + if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } + } + + // the remaining checks require an element + if (!utils.isElement(element)) { return false; } + + return utils.isElement(value) + // the value is an element to use as a resize handle + ? value === element + // otherwise check if element matches value as selector + : utils.matchesUpTo(element, value, interactableElement); + }, + + defaultActionChecker: function (pointer, interaction, element) { + var rect = this.getRect(element), + shouldResize = false, + action = null, + resizeAxes = null, + resizeEdges, + page = utils.extend({}, interaction.curCoords.page), + options = this.options; + + if (!rect) { return null; } + + if (scope.actionIsEnabled.resize && options.resize.enabled) { + var resizeOptions = options.resize; + + resizeEdges = { + left: false, right: false, top: false, bottom: false + }; + + // if using resize.edges + if (utils.isObject(resizeOptions.edges)) { + for (var edge in resizeEdges) { + resizeEdges[edge] = actions.checkResizeEdge(edge, + resizeOptions.edges[edge], + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || scope.margin); + } + + resizeEdges.left = resizeEdges.left && !resizeEdges.right; + resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; + + shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; + } + else { + var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), + bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); + + shouldResize = right || bottom; + resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); + } + } + + action = shouldResize + ? 'resize' + : scope.actionIsEnabled.drag && options.drag.enabled + ? 'drag' + : null; + + if (scope.actionIsEnabled.gesture + && interaction.pointerIds.length >=2 + && !(interaction.dragging || interaction.resizing)) { + action = 'gesture'; + } + + if (action) { + return { + name: action, + axis: resizeAxes, + edges: resizeEdges + }; + } + + return null; + } +}; + +module.exports = actions; diff --git a/src/actions/resize.js b/src/actions/resize.js new file mode 100644 index 000000000..8d2bfed07 --- /dev/null +++ b/src/actions/resize.js @@ -0,0 +1,326 @@ +'use strict'; + +var base = require('./base'), + utils = require('../utils'), + scope = base.scope, + Interaction = require('../Interaction'), + InteractEvent = require('../InteractEvent'), + Interactable = require('../Interactable'); + +base.addEventTypes([ + 'resizestart', + 'resizemove', + 'resizeinertiastart', + 'resizeend' +]); + +base.checkers.push(function (pointer, event, interactable, element, interaction, rect) { + if (!rect) { return null; } + + var page = utils.extend({}, interaction.curCoords.page), + options = interactable.options; + + if (scope.actionIsEnabled.resize && options.resize.enabled) { + var resizeOptions = options.resize, + resizeEdges = { + left: false, right: false, top: false, bottom: false + }; + + // if using resize.edges + if (utils.isObject(resizeOptions.edges)) { + for (var edge in resizeEdges) { + resizeEdges[edge] = checkResizeEdge(edge, + resizeOptions.edges[edge], + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || scope.margin); + } + + resizeEdges.left = resizeEdges.left && !resizeEdges.right; + resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; + + if (resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom) { + return { + name: 'resize', + edges: resizeEdges + }; + } + } + else { + var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), + bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); + + if (right || bottom) { + return { + name: 'resize', + axes: (right? 'x' : '') + (bottom? 'y' : '') + }; + } + } + } + + return null; +}); + +Interaction.prototype.resizeStart = function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); + + if (this.prepared.edges) { + var startRect = this.target.getRect(this.element); + + if (this.target.options.resize.square) { + var squareEdges = utils.extend({}, this.prepared.edges); + + squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); + squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); + squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); + squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); + + this.prepared._squareEdges = squareEdges; + } + else { + this.prepared._squareEdges = null; + } + + this.resizeRects = { + start : startRect, + current : utils.extend({}, startRect), + restricted: utils.extend({}, startRect), + previous : utils.extend({}, startRect), + delta : { + left: 0, right : 0, width : 0, + top : 0, bottom: 0, height: 0 + } + }; + + resizeEvent.rect = this.resizeRects.restricted; + resizeEvent.deltaRect = this.resizeRects.delta; + } + + this.target.fire(resizeEvent); + + this.resizing = true; + + return resizeEvent; +}; + +Interaction.prototype.resizeMove = function (event) { + var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); + + var edges = this.prepared.edges, + invert = this.target.options.resize.invert, + invertible = invert === 'reposition' || invert === 'negate'; + + if (edges) { + var dx = resizeEvent.dx, + dy = resizeEvent.dy, + + start = this.resizeRects.start, + current = this.resizeRects.current, + restricted = this.resizeRects.restricted, + delta = this.resizeRects.delta, + previous = utils.extend(this.resizeRects.previous, restricted); + + if (this.target.options.resize.square) { + var originalEdges = edges; + + edges = this.prepared._squareEdges; + + if ((originalEdges.left && originalEdges.bottom) + || (originalEdges.right && originalEdges.top)) { + dy = -dx; + } + else if (originalEdges.left || originalEdges.right) { dy = dx; } + else if (originalEdges.top || originalEdges.bottom) { dx = dy; } + } + + // update the 'current' rect without modifications + if (edges.top ) { current.top += dy; } + if (edges.bottom) { current.bottom += dy; } + if (edges.left ) { current.left += dx; } + if (edges.right ) { current.right += dx; } + + if (invertible) { + // if invertible, copy the current rect + utils.extend(restricted, current); + + if (invert === 'reposition') { + // swap edge values if necessary to keep width/height positive + var swap; + + if (restricted.top > restricted.bottom) { + swap = restricted.top; + + restricted.top = restricted.bottom; + restricted.bottom = swap; + } + if (restricted.left > restricted.right) { + swap = restricted.left; + + restricted.left = restricted.right; + restricted.right = swap; + } + } + } + else { + // if not invertible, restrict to minimum of 0x0 rect + restricted.top = Math.min(current.top, start.bottom); + restricted.bottom = Math.max(current.bottom, start.top); + restricted.left = Math.min(current.left, start.right); + restricted.right = Math.max(current.right, start.left); + } + + restricted.width = restricted.right - restricted.left; + restricted.height = restricted.bottom - restricted.top ; + + for (var edge in restricted) { + delta[edge] = restricted[edge] - previous[edge]; + } + + resizeEvent.edges = this.prepared.edges; + resizeEvent.rect = restricted; + resizeEvent.deltaRect = delta; + } + + this.target.fire(resizeEvent); + + return resizeEvent; +}; + +Interaction.prototype.resizeEnd = function (event) { + var endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); + + this.target.fire(endEvent); +}; + +/*\ + * Interactable.resizable + [ method ] + * + * Gets or sets whether resize actions can be performed on the + * Interactable + * + = (boolean) Indicates if this can be the target of resize elements + | var isResizeable = interact('input[type=text]').resizable(); + * or + - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) + = (object) This Interactable + | interact(element).resizable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | edges: { + | top : true, // Use pointer coords to check for resize. + | left : false, // Disable resizing from left edge. + | bottom: '.resize-s',// Resize if pointer target matches selector + | right : handleEl // Resize if pointer target is the given Element + | }, + | + | // a value of 'none' will limit the resize rect to a minimum of 0x0 + | // 'negate' will allow the rect to have negative width/height + | // 'reposition' will keep the width/height positive by swapping + | // the top and bottom edges and/or swapping the left and right edges + | invert: 'none' || 'negate' || 'reposition' + | + | // limit multiple resizes. + | // See the explanation in the @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); +\*/ +Interactable.prototype.resizable = function (options) { + if (utils.isObject(options)) { + this.options.resize.enabled = options.enabled === false? false: true; + this.setPerAction('resize', options); + this.setOnEvents('resize', options); + + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.resize.axis = options.axis; + } + else if (options.axis === null) { + this.options.resize.axis = scope.defaultOptions.resize.axis; + } + + if (utils.isBool(options.square)) { + this.options.resize.square = options.square; + } + + return this; + } + if (utils.isBool(options)) { + this.options.resize.enabled = options; + + return this; + } + return this.options.resize; +}; + +/*\ + * Interactable.squareResize + [ method ] + * + * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead + * + * Gets or sets whether resizing is forced 1:1 aspect + * + = (boolean) Current setting + * + * or + * + - newValue (boolean) #optional + = (object) this Interactable +\*/ +Interactable.prototype.squareResize = function (newValue) { + if (utils.isBool(newValue)) { + this.options.resize.square = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.resize.square; + + return this; + } + + return this.options.resize.square; +}; + +function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { + // false, '', undefined, null + if (!value) { return false; } + + // true value, use pointer coords and element rect + if (value === true) { + // if dimensions are negative, "switch" edges + var width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left, + height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; + + if (width < 0) { + if (name === 'left' ) { name = 'right'; } + else if (name === 'right') { name = 'left' ; } + } + if (height < 0) { + if (name === 'top' ) { name = 'bottom'; } + else if (name === 'bottom') { name = 'top' ; } + } + + if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } + if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } + + if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } + if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } + } + + // the remaining checks require an element + if (!utils.isElement(element)) { return false; } + + return utils.isElement(value) + // the value is an element to use as a resize handle + ? value === element + // otherwise check if element matches value as selector + : utils.matchesUpTo(element, value, interactableElement); +} diff --git a/src/interact.js b/src/interact.js index 2945c3a7c..1ed866ded 100644 --- a/src/interact.js +++ b/src/interact.js @@ -21,8 +21,6 @@ scope.dynamicDrop = false; - scope.defaultOptions = require('./defaultOptions'); - // Things related to autoScroll scope.autoScroll = require('./autoScroll'); @@ -37,40 +35,6 @@ // Allow this many interactions to happen simultaneously scope.maxInteractions = Infinity; - scope.actionCursors = browser.isIe9OrOlder ? { - drag : 'move', - resizex : 'e-resize', - resizey : 's-resize', - resizexy: 'se-resize', - - resizetop : 'n-resize', - resizeleft : 'w-resize', - resizebottom : 's-resize', - resizeright : 'e-resize', - resizetopleft : 'se-resize', - resizebottomright: 'se-resize', - resizetopright : 'ne-resize', - resizebottomleft : 'ne-resize', - - gesture : '' - } : { - drag : 'move', - resizex : 'ew-resize', - resizey : 'ns-resize', - resizexy: 'nwse-resize', - - resizetop : 'ns-resize', - resizeleft : 'ew-resize', - resizebottom : 'ns-resize', - resizeright : 'ew-resize', - resizetopleft : 'nwse-resize', - resizebottomright: 'nwse-resize', - resizetopright : 'nesw-resize', - resizebottomleft : 'nesw-resize', - - gesture : '' - }; - scope.actionIsEnabled = { drag : true, resize : true, @@ -81,25 +45,6 @@ scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel'; scope.eventTypes = [ - 'dragstart', - 'dragmove', - 'draginertiastart', - 'dragend', - 'dragenter', - 'dragleave', - 'dropactivate', - 'dropdeactivate', - 'dropmove', - 'drop', - 'resizestart', - 'resizemove', - 'resizeinertiastart', - 'resizeend', - 'gesturestart', - 'gesturemove', - 'gestureinertiastart', - 'gestureend', - 'down', 'move', 'up', @@ -747,3 +692,7 @@ scope.listenToDocument = scope.listenToDocument; module.exports = interact; + + require('./actions/resize'); + require('./actions/drag'); + require('./actions/gesture'); diff --git a/src/scope.js b/src/scope.js index d072a2905..32091a9d5 100644 --- a/src/scope.js +++ b/src/scope.js @@ -10,6 +10,8 @@ scope.documents = []; // all documents being listened to scope.interactables = []; // all set interactables scope.interactions = []; // all interactions +scope.defaultOptions = require('./defaultOptions'); + scope.events = require('./utils/events'); extend(scope, require('./utils/window')); From c400cd309f773983d2043399ab2b11b77dce61f1 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 6 Jul 2015 11:16:18 +0200 Subject: [PATCH 044/131] Move most of snap code to src/modifiers/snap.js --- src/InteractEvent.js | 7 +- src/Interaction.js | 150 ++++---------------------------------- src/interact.js | 12 +--- src/modifiers/index.js | 5 ++ src/modifiers/snap.js | 158 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 150 deletions(-) create mode 100644 src/modifiers/index.js create mode 100644 src/modifiers/snap.js diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 682d58d1b..d667ef9c5 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -1,7 +1,8 @@ 'use strict'; -var scope = require('./scope'); -var utils = require('./utils'); +var scope = require('./scope'), + utils = require('./utils'), + modifiers = require('./modifiers'); function InteractEvent (interaction, event, action, phase, element, related) { var client, @@ -32,7 +33,7 @@ function InteractEvent (interaction, event, action, phase, element, related) { var relativePoints = options[action].snap && options[action].snap.relativePoints ; - if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { + if (modifiers.snap.shouldDo(target, action) && !(starting && relativePoints && relativePoints.length)) { this.snap = { range : snapStatus.range, locked : snapStatus.locked, diff --git a/src/Interaction.js b/src/Interaction.js index b6e988601..a7171d2d7 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -1,11 +1,12 @@ 'use strict'; -var scope = require('./scope'); -var utils = require('./utils'); -var animationFrame = utils.raf; -var InteractEvent = require('./InteractEvent'); -var events = require('./utils/events'); -var browser = require('./utils/browser'); +var scope = require('./scope'), + utils = require('./utils'), + animationFrame = utils.raf, + InteractEvent = require('./InteractEvent'), + events = require('./utils/events'), + browser = require('./utils/browser'), + modifiers = require('./modifiers/'); function Interaction () { this.target = null; // current interactable being interacted with @@ -516,10 +517,10 @@ Interaction.prototype = { setModifications: function (coords, preEnd) { var target = this.target, shouldMove = true, - shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), + shouldSnap = modifiers.snap.shouldDo(target, this.prepared.name, preEnd), shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); - if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } + if (shouldSnap ) { modifiers.snap.set(coords, this); } else { this.snapStatus.locked = false; } if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { @@ -869,7 +870,7 @@ Interaction.prototype = { inertiaPossible = false, inertia = false, smoothEnd = false, - endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, + endSnap = modifiers.snap.shouldDo(target, this.prepared.name, true) && options[this.prepared.name].snap.endOnly, endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, dx = 0, dy = 0, @@ -901,7 +902,7 @@ Interaction.prototype = { snapRestrict.snap = snapRestrict.restrict = snapRestrict; if (endSnap) { - this.setSnapping(this.curCoords.page, snapRestrict); + modifiers.snap.set(this.curCoords.page, this, snapRestrict); if (snapRestrict.locked) { dx += snapRestrict.dx; dy += snapRestrict.dy; @@ -959,7 +960,7 @@ Interaction.prototype = { dx = dy = 0; if (endSnap) { - var snap = this.setSnapping(this.curCoords.page, statusObject); + var snap = modifiers.snap.set(this.curCoords.page, this, statusObject); if (snap.locked) { dx += snap.dx; @@ -1321,132 +1322,7 @@ Interaction.prototype = { }, setSnapping: function (pageCoords, status) { - var snap = this.target.options[this.prepared.name].snap, - targets = [], - target, - page, - i; - - status = status || this.snapStatus; - - if (status.useStatusXY) { - page = { x: status.x, y: status.y }; - } - else { - var origin = scope.getOriginXY(this.target, this.element); - - page = utils.extend({}, pageCoords); - - page.x -= origin.x; - page.y -= origin.y; - } - - status.realX = page.x; - status.realY = page.y; - - page.x = page.x - this.inertiaStatus.resumeDx; - page.y = page.y - this.inertiaStatus.resumeDy; - - var len = snap.targets? snap.targets.length : 0; - - for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) { - var relative = { - x: page.x - this.snapOffsets[relIndex].x, - y: page.y - this.snapOffsets[relIndex].y - }; - - for (i = 0; i < len; i++) { - if (utils.isFunction(snap.targets[i])) { - target = snap.targets[i](relative.x, relative.y, this); - } - else { - target = snap.targets[i]; - } - - if (!target) { continue; } - - targets.push({ - x: utils.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, - y: utils.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, - - range: utils.isNumber(target.range)? target.range: snap.range - }); - } - } - - var closest = { - target: null, - inRange: false, - distance: 0, - range: 0, - dx: 0, - dy: 0 - }; - - for (i = 0, len = targets.length; i < len; i++) { - target = targets[i]; - - var range = target.range, - dx = target.x - page.x, - dy = target.y - page.y, - distance = utils.hypot(dx, dy), - inRange = distance <= range; - - // Infinite targets count as being out of range - // compared to non infinite ones that are in range - if (range === Infinity && closest.inRange && closest.range !== Infinity) { - inRange = false; - } - - if (!closest.target || (inRange - // is the closest target in range? - ? (closest.inRange && range !== Infinity - // the pointer is relatively deeper in this target - ? distance / range < closest.distance / closest.range - // this target has Infinite range and the closest doesn't - : (range === Infinity && closest.range !== Infinity) - // OR this target is closer that the previous closest - || distance < closest.distance) - // The other is not in range and the pointer is closer to this target - : (!closest.inRange && distance < closest.distance))) { - - if (range === Infinity) { - inRange = true; - } - - closest.target = target; - closest.distance = distance; - closest.range = range; - closest.inRange = inRange; - closest.dx = dx; - closest.dy = dy; - - status.range = range; - } - } - - var snapChanged; - - if (closest.target) { - snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); - - status.snappedX = closest.target.x; - status.snappedY = closest.target.y; - } - else { - snapChanged = true; - - status.snappedX = NaN; - status.snappedY = NaN; - } - - status.dx = closest.dx; - status.dy = closest.dy; - - status.changed = (snapChanged || (closest.inRange && !status.locked)); - status.locked = closest.inRange; - - return status; + return modifiers.snap.set(pageCoords, status, this); }, setRestriction: function (pageCoords, status) { diff --git a/src/interact.js b/src/interact.js index 1ed866ded..2d6119a93 100644 --- a/src/interact.js +++ b/src/interact.js @@ -139,16 +139,6 @@ return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); }; - scope.checkSnap = function (interactable, action) { - var options = interactable.options; - - if (/^resize/.test(action)) { - action = 'resize'; - } - - return options[action].snap && options[action].snap.enabled; - }; - scope.checkRestrict = function (interactable, action) { var options = interactable.options; @@ -696,3 +686,5 @@ require('./actions/resize'); require('./actions/drag'); require('./actions/gesture'); + + require('./modifiers/snap'); diff --git a/src/modifiers/index.js b/src/modifiers/index.js new file mode 100644 index 000000000..79276079e --- /dev/null +++ b/src/modifiers/index.js @@ -0,0 +1,5 @@ +'use strict'; + +var modifiers = {}; + +module.exports = modifiers; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js new file mode 100644 index 000000000..71d1c16c5 --- /dev/null +++ b/src/modifiers/snap.js @@ -0,0 +1,158 @@ +'use strict'; + +var modifiers = require('./index'), + scope = require('../scope'), + utils = require('../utils'); + //defaultOptions = require('../defaultOptions'); + +var snap = { + options: { + enabled : false, + endOnly : false, + range : Infinity, + targets : null, + offsets : null, + + relativePoints: null + }, + shouldDo: function (interactable, actionName, preEnd) { + var options = interactable.options; + + return (options[actionName].snap + && options[actionName].snap.enabled + && (preEnd || !interactable.options[actionName].snap.endOnly)); + }, + set: function (pageCoords, interaction, status) { + var snap = interaction.target.options[interaction.prepared.name].snap, + targets = [], + target, + page, + i; + + status = status || interaction.snapStatus; + + if (status.useStatusXY) { + page = { x: status.x, y: status.y }; + } + else { + var origin = scope.getOriginXY(interaction.target, interaction.element); + + page = utils.extend({}, pageCoords); + + page.x -= origin.x; + page.y -= origin.y; + } + + status.realX = page.x; + status.realY = page.y; + + page.x = page.x - interaction.inertiaStatus.resumeDx; + page.y = page.y - interaction.inertiaStatus.resumeDy; + + var len = snap.targets? snap.targets.length : 0; + + for (var relIndex = 0; relIndex < interaction.snapOffsets.length; relIndex++) { + var relative = { + x: page.x - interaction.snapOffsets[relIndex].x, + y: page.y - interaction.snapOffsets[relIndex].y + }; + + for (i = 0; i < len; i++) { + if (utils.isFunction(snap.targets[i])) { + target = snap.targets[i](relative.x, relative.y, interaction); + } + else { + target = snap.targets[i]; + } + + if (!target) { continue; } + + targets.push({ + x: utils.isNumber(target.x) ? (target.x + interaction.snapOffsets[relIndex].x) : relative.x, + y: utils.isNumber(target.y) ? (target.y + interaction.snapOffsets[relIndex].y) : relative.y, + + range: utils.isNumber(target.range)? target.range: snap.range + }); + } + } + + var closest = { + target: null, + inRange: false, + distance: 0, + range: 0, + dx: 0, + dy: 0 + }; + + for (i = 0, len = targets.length; i < len; i++) { + target = targets[i]; + + var range = target.range, + dx = target.x - page.x, + dy = target.y - page.y, + distance = utils.hypot(dx, dy), + inRange = distance <= range; + + // Infinite targets count as being out of range + // compared to non infinite ones that are in range + if (range === Infinity && closest.inRange && closest.range !== Infinity) { + inRange = false; + } + + if (!closest.target || (inRange + // is the closest target in range? + ? (closest.inRange && range !== Infinity + // the pointer is relatively deeper in this target + ? distance / range < closest.distance / closest.range + // this target has Infinite range and the closest doesn't + : (range === Infinity && closest.range !== Infinity) + // OR this target is closer that the previous closest + || distance < closest.distance) + // The other is not in range and the pointer is closer to this target + : (!closest.inRange && distance < closest.distance))) { + + if (range === Infinity) { + inRange = true; + } + + closest.target = target; + closest.distance = distance; + closest.range = range; + closest.inRange = inRange; + closest.dx = dx; + closest.dy = dy; + + status.range = range; + } + } + + var snapChanged; + + if (closest.target) { + snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); + + status.snappedX = closest.target.x; + status.snappedY = closest.target.y; + } + else { + snapChanged = true; + + status.snappedX = NaN; + status.snappedY = NaN; + } + + status.dx = closest.dx; + status.dy = closest.dy; + + status.changed = (snapChanged || (closest.inRange && !status.locked)); + status.locked = closest.inRange; + + return status; + }, + +}; + +modifiers.snap = snap; + +module.exports = snap; From d190c4adbbf4856b35d95424502647cc4bc9f742 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 6 Jul 2015 11:40:19 +0200 Subject: [PATCH 045/131] Remove deprecated methods Interactable - squareResize, snap, restrict, inertia, autoScroll interact - enabbleDragging, enableResizing, enableGesturing --- src/Interactable.js | 276 ------------------------------------------ src/actions/resize.js | 31 ----- src/interact.js | 73 ----------- 3 files changed, 380 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index d3c41df36..0fb6f124e 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -107,221 +107,6 @@ Interactable.prototype = { } }, - /*\ - * Interactable.autoScroll - [ method ] - ** - * Deprecated. Add an `autoscroll` property to the options object - * passed to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets whether dragging and resizing near the edges of the - * window/container trigger autoScroll for this Interactable - * - = (object) Object with autoScroll properties - * - * or - * - - options (object | boolean) #optional - * options can be: - * - an object with margin, distance and interval properties, - * - true or false to enable or disable autoScroll or - = (Interactable) this Interactable - \*/ - autoScroll: function (options) { - if (utils.isObject(options)) { - options = utils.extend({ actions: ['drag', 'resize']}, options); - } - else if (utils.isBool(options)) { - options = { actions: ['drag', 'resize'], enabled: options }; - } - - return this.setOptions('autoScroll', options); - }, - - /*\ - * Interactable.snap - [ method ] - ** - * Deprecated. Add a `snap` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how action coordinates are snapped. By - * default, snapping is relative to the pointer coordinates. You can - * change this by setting the - * [`elementOrigin`](https://github.com/taye/interact.js/pull/72). - ** - = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | interact(document.querySelector('#thing')).snap({ - | targets: [ - | // snap to this specific point - | { - | x: 100, - | y: 100, - | range: 25 - | }, - | // give this function the x and y page coords and snap to the object returned - | function (x, y) { - | return { - | x: x, - | y: (75 + 50 * Math.sin(x * 0.04)), - | range: 40 - | }; - | }, - | // create a function that snaps to a grid - | interact.createSnapGrid({ - | x: 50, - | y: 50, - | range: 10, // optional - | offset: { x: 5, y: 10 } // optional - | }) - | ], - | // do not snap during normal movement. - | // Instead, trigger only one snapped move event - | // immediately before the end event. - | endOnly: true, - | - | relativePoints: [ - | { x: 0, y: 0 }, // snap relative to the top left of the element - | { x: 1, y: 1 }, // and also to the bottom right - | ], - | - | // offset the snap target coordinates - | // can be an object with x/y or 'startCoords' - | offset: { x: 50, y: 50 } - | } - | }); - \*/ - snap: function (options) { - var ret = this.setOptions('snap', options); - - if (ret === this) { return this; } - - return ret.drag; - }, - - setOptions: function (option, options) { - var actions = options && utils.isArray(options.actions) - ? options.actions - : ['drag']; - - var i; - - if (utils.isObject(options) || utils.isBool(options)) { - for (i = 0; i < actions.length; i++) { - var action = /resize/.test(actions[i])? 'resize' : actions[i]; - - if (!utils.isObject(this.options[action])) { continue; } - - var thisOption = this.options[action][option]; - - if (utils.isObject(options)) { - utils.extend(thisOption, options); - thisOption.enabled = options.enabled === false? false: true; - - if (option === 'snap') { - if (thisOption.mode === 'grid') { - thisOption.targets = [ - scope.interact.createSnapGrid(utils.extend({ - offset: thisOption.gridOffset || { x: 0, y: 0 } - }, thisOption.grid || {})) - ]; - } - else if (thisOption.mode === 'anchor') { - thisOption.targets = thisOption.anchors; - } - else if (thisOption.mode === 'path') { - thisOption.targets = thisOption.paths; - } - - if ('elementOrigin' in options) { - thisOption.relativePoints = [options.elementOrigin]; - } - } - } - else if (utils.isBool(options)) { - thisOption.enabled = options; - } - } - - return this; - } - - var ret = {}, - allActions = ['drag', 'resize', 'gesture']; - - for (i = 0; i < allActions.length; i++) { - if (option in scope.defaultOptions[allActions[i]]) { - ret[allActions[i]] = this.options[allActions[i]][option]; - } - } - - return ret; - }, - - - /*\ - * Interactable.inertia - [ method ] - ** - * Deprecated. Add an `inertia` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how events continue to run after the pointer is released - ** - = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | // enable and use default settings - | interact(element).inertia(true); - | - | // enable and use custom settings - | interact(element).inertia({ - | // value greater than 0 - | // high values slow the object down more quickly - | resistance : 16, - | - | // the minimum launch speed (pixels per second) that results in inertia start - | minSpeed : 200, - | - | // inertia will stop when the object slows down to this speed - | endSpeed : 20, - | - | // boolean; should actions be resumed when the pointer goes down during inertia - | allowResume : true, - | - | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy - | zeroResumeDelta: false, - | - | // if snap/restrict are set to be endOnly and inertia is enabled, releasing - | // the pointer without triggering inertia will animate from the release - | // point to the snaped/restricted point in the given amount of time (ms) - | smoothEndDuration: 300, - | - | // an array of action types that can have inertia (no gesture) - | actions : ['drag', 'resize'] - | }); - | - | // reset custom settings and use all defaults - | interact(element).inertia(null); - \*/ - inertia: function (options) { - var ret = this.setOptions('inertia', options); - - if (ret === this) { return this; } - - return ret.drag; - }, - getAction: function (pointer, event, interaction, element) { var action = this.defaultActionChecker(pointer, interaction, element); @@ -532,67 +317,6 @@ Interactable.prototype = { return this.options.deltaSource; }, - /*\ - * Interactable.restrict - [ method ] - ** - * Deprecated. Add a `restrict` property to the options object passed to - * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead. - * - * Returns or sets the rectangles within which actions on this - * interactable (after snap calculations) are restricted. By default, - * restricting is relative to the pointer coordinates. You can change - * this by setting the - * [`elementRect`](https://github.com/taye/interact.js/pull/72). - ** - - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self' - = (object) The current restrictions object or this Interactable - ** - | interact(element).restrict({ - | // the rect will be `interact.getElementRect(element.parentNode)` - | drag: element.parentNode, - | - | // x and y are relative to the the interactable's origin - | resize: { x: 100, y: 100, width: 200, height: 200 } - | }) - | - | interact('.draggable').restrict({ - | // the rect will be the selected element's parent - | drag: 'parent', - | - | // do not restrict during normal movement. - | // Instead, trigger only one restricted move event - | // immediately before the end event. - | endOnly: true, - | - | // https://github.com/taye/interact.js/pull/72#issue-41813493 - | elementRect: { top: 0, left: 0, bottom: 1, right: 1 } - | }); - \*/ - restrict: function (options) { - if (!utils.isObject(options)) { - return this.setOptions('restrict', options); - } - - var actions = ['drag', 'resize', 'gesture'], - ret; - - for (var i = 0; i < actions.length; i++) { - var action = actions[i]; - - if (action in options) { - var perAction = utils.extend({ - actions: [action], - restriction: options[action] - }, options); - - ret = this.setOptions('restrict', perAction); - } - } - - return ret; - }, - /*\ * Interactable.context [ method ] diff --git a/src/actions/resize.js b/src/actions/resize.js index 8d2bfed07..1a59ce393 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -258,37 +258,6 @@ Interactable.prototype.resizable = function (options) { return this.options.resize; }; -/*\ - * Interactable.squareResize - [ method ] - * - * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead - * - * Gets or sets whether resizing is forced 1:1 aspect - * - = (boolean) Current setting - * - * or - * - - newValue (boolean) #optional - = (object) this Interactable -\*/ -Interactable.prototype.squareResize = function (newValue) { - if (utils.isBool(newValue)) { - this.options.resize.square = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.resize.square; - - return this; - } - - return this.options.resize.square; -}; - function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { // false, '', undefined, null if (!value) { return false; } diff --git a/src/interact.js b/src/interact.js index 2d6119a93..f20754be1 100644 --- a/src/interact.js +++ b/src/interact.js @@ -230,17 +230,6 @@ return scope.interactables.get(element, options) || new Interactable(element, options); } - Interactable.prototype.snap = utils.warnOnce(Interactable.prototype.snap, - 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping'); - Interactable.prototype.restrict = utils.warnOnce(Interactable.prototype.restrict, - 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction'); - Interactable.prototype.inertia = utils.warnOnce(Interactable.prototype.inertia, - 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia'); - Interactable.prototype.autoScroll = utils.warnOnce(Interactable.prototype.autoScroll, - 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll'); - Interactable.prototype.squareResize = utils.warnOnce(Interactable.prototype.squareResize, - 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square'); - /*\ * interact.isSet [ method ] @@ -351,68 +340,6 @@ return interact; }; - /*\ - * interact.enableDragging - [ method ] - * - * Deprecated. - * - * Returns or sets whether dragging is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ - interact.enableDragging = utils.warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - scope.actionIsEnabled.drag = newValue; - - return interact; - } - return scope.actionIsEnabled.drag; - }, 'interact.enableDragging is deprecated and will soon be removed.'); - - /*\ - * interact.enableResizing - [ method ] - * - * Deprecated. - * - * Returns or sets whether resizing is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ - interact.enableResizing = utils.warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - scope.actionIsEnabled.resize = newValue; - - return interact; - } - return scope.actionIsEnabled.resize; - }, 'interact.enableResizing is deprecated and will soon be removed.'); - - /*\ - * interact.enableGesturing - [ method ] - * - * Deprecated. - * - * Returns or sets whether gesturing is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ - interact.enableGesturing = utils.warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - scope.actionIsEnabled.gesture = newValue; - - return interact; - } - return scope.actionIsEnabled.gesture; - }, 'interact.enableGesturing is deprecated and will soon be removed.'); - - interact.eventTypes = scope.eventTypes; - /*\ * interact.debug [ method ] From b59761350adad103f29deca11033b8834d00d17d Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 6 Jul 2015 16:22:36 +0200 Subject: [PATCH 046/131] Move some snap-related code out of InteractEvent to modifiers/snap.js modifyCoords method --- src/InteractEvent.js | 44 ++++++++++++------------------------------ src/modifiers/index.js | 4 +++- src/modifiers/snap.js | 26 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index d667ef9c5..dc0c59335 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -8,7 +8,6 @@ function InteractEvent (interaction, event, action, phase, element, related) { var client, page, target = interaction.target, - snapStatus = interaction.snapStatus, restrictStatus = interaction.restrictStatus, pointers = interaction.pointers, deltaSource = (target && target.options || scope.defaultOptions).deltaSource, @@ -31,27 +30,19 @@ function InteractEvent (interaction, event, action, phase, element, related) { client.x -= origin.x; client.y -= origin.y; - var relativePoints = options[action].snap && options[action].snap.relativePoints ; - - if (modifiers.snap.shouldDo(target, action) && !(starting && relativePoints && relativePoints.length)) { - this.snap = { - range : snapStatus.range, - locked : snapStatus.locked, - x : snapStatus.snappedX, - y : snapStatus.snappedY, - realX : snapStatus.realX, - realY : snapStatus.realY, - dx : snapStatus.dx, - dy : snapStatus.dy - }; + this.ctrlKey = event.ctrlKey; + this.altKey = event.altKey; + this.shiftKey = event.shiftKey; + this.metaKey = event.metaKey; + this.button = event.button; + this.target = element; + this.t0 = interaction.downTimes[0]; + this.type = action + (phase || ''); - if (snapStatus.locked) { - page.x += snapStatus.dx; - page.y += snapStatus.dy; - client.x += snapStatus.dx; - client.y += snapStatus.dy; - } - } + this.interaction = interaction; + this.interactable = target; + + this.snap = modifiers.snap.modifyCoords(page, client, target, interaction.snapStatus, action, phase); if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { page.x += restrictStatus.dx; @@ -74,17 +65,6 @@ function InteractEvent (interaction, event, action, phase, element, related) { this.y0 = interaction.startCoords.page.y - origin.y; this.clientX0 = interaction.startCoords.client.x - origin.x; this.clientY0 = interaction.startCoords.client.y - origin.y; - this.ctrlKey = event.ctrlKey; - this.altKey = event.altKey; - this.shiftKey = event.shiftKey; - this.metaKey = event.metaKey; - this.button = event.button; - this.target = element; - this.t0 = interaction.downTimes[0]; - this.type = action + (phase || ''); - - this.interaction = interaction; - this.interactable = target; var inertiaStatus = interaction.inertiaStatus; diff --git a/src/modifiers/index.js b/src/modifiers/index.js index 79276079e..39712f15d 100644 --- a/src/modifiers/index.js +++ b/src/modifiers/index.js @@ -1,5 +1,7 @@ 'use strict'; -var modifiers = {}; +var modifiers = { + names: [] +}; module.exports = modifiers; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index 71d1c16c5..ab6f8197d 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -151,8 +151,34 @@ var snap = { return status; }, + modifyCoords: function (page, client, interactable, status, actionName, phase) { + var relativePoints = interactable.options[actionName].snap && interactable.options.relativePoints; + + if (modifiers.snap.shouldDo(interactable, actionName) + && !(phase === 'start' && relativePoints && relativePoints.length)) { + + if (status.locked) { + page.x += status.dx; + page.y += status.dy; + client.x += status.dx; + client.y += status.dy; + } + + return { + range : status.range, + locked : status.locked, + x : status.snappedX, + y : status.snappedY, + realX : status.realX, + realY : status.realY, + dx : status.dx, + dy : status.dy + }; + } + } }; modifiers.snap = snap; +modifiers.names.push('snap'); module.exports = snap; From 2f77325023e3f56107c10c75fb74bd4dd9eec1fc Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 6 Jul 2015 19:23:08 +0200 Subject: [PATCH 047/131] Move restriction code to src/modifiers/restrict.js --- src/InteractEvent.js | 16 +---- src/Interaction.js | 92 ++------------------------- src/interact.js | 11 +--- src/modifiers/restrict.js | 130 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 140 insertions(+), 109 deletions(-) create mode 100644 src/modifiers/restrict.js diff --git a/src/InteractEvent.js b/src/InteractEvent.js index dc0c59335..98230fa12 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -8,7 +8,6 @@ function InteractEvent (interaction, event, action, phase, element, related) { var client, page, target = interaction.target, - restrictStatus = interaction.restrictStatus, pointers = interaction.pointers, deltaSource = (target && target.options || scope.defaultOptions).deltaSource, sourceX = deltaSource + 'X', @@ -42,19 +41,8 @@ function InteractEvent (interaction, event, action, phase, element, related) { this.interaction = interaction; this.interactable = target; - this.snap = modifiers.snap.modifyCoords(page, client, target, interaction.snapStatus, action, phase); - - if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { - page.x += restrictStatus.dx; - page.y += restrictStatus.dy; - client.x += restrictStatus.dx; - client.y += restrictStatus.dy; - - this.restrict = { - dx: restrictStatus.dx, - dy: restrictStatus.dy - }; - } + this.snap = modifiers.snap.modifyCoords (page, client, target, interaction.snapStatus , action, phase); + this.restrict = modifiers.restrict.modifyCoords(page, client, target, interaction.restrictStatus, action, phase); this.pageX = page.x; this.pageY = page.y; diff --git a/src/Interaction.js b/src/Interaction.js index a7171d2d7..70a66e2a8 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -518,10 +518,10 @@ Interaction.prototype = { var target = this.target, shouldMove = true, shouldSnap = modifiers.snap.shouldDo(target, this.prepared.name, preEnd), - shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); + shouldRestrict = modifiers.restrict.shouldDo(target, this.prepared.name, preEnd); - if (shouldSnap ) { modifiers.snap.set(coords, this); } else { this.snapStatus.locked = false; } - if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } + if (shouldSnap ) { modifiers.snap .set(coords, this); } else { this.snapStatus .locked = false; } + if (shouldRestrict) { modifiers.restrict.set(coords, this); } else { this.restrictStatus.restricted = false; } if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; @@ -871,7 +871,7 @@ Interaction.prototype = { inertia = false, smoothEnd = false, endSnap = modifiers.snap.shouldDo(target, this.prepared.name, true) && options[this.prepared.name].snap.endOnly, - endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, + endRestrict = modifiers.restrict.shouldDo(target, this.prepared.name, true) && options[this.prepared.name].restrict.endOnly, dx = 0, dy = 0, startEvent; @@ -910,7 +910,7 @@ Interaction.prototype = { } if (endRestrict) { - this.setRestriction(this.curCoords.page, snapRestrict); + modifiers.restrict.set(this.curCoords.page, this, snapRestrict); if (snapRestrict.restricted) { dx += snapRestrict.dx; dy += snapRestrict.dy; @@ -969,7 +969,7 @@ Interaction.prototype = { } if (endRestrict) { - var restrict = this.setRestriction(this.curCoords.page, statusObject); + var restrict = modifiers.restrict.set(this.curCoords.page, this, statusObject); if (restrict.restricted) { dx += restrict.dx; @@ -1326,85 +1326,7 @@ Interaction.prototype = { }, setRestriction: function (pageCoords, status) { - var target = this.target, - restrict = target && target.options[this.prepared.name].restrict, - restriction = restrict && restrict.restriction, - page; - - if (!restriction) { - return status; - } - - status = status || this.restrictStatus; - - page = status.useStatusXY - ? page = { x: status.x, y: status.y } - : page = utils.extend({}, pageCoords); - - if (status.snap && status.snap.locked) { - page.x += status.snap.dx || 0; - page.y += status.snap.dy || 0; - } - - page.x -= this.inertiaStatus.resumeDx; - page.y -= this.inertiaStatus.resumeDy; - - status.dx = 0; - status.dy = 0; - status.restricted = false; - - var rect, restrictedX, restrictedY; - - if (utils.isString(restriction)) { - if (restriction === 'parent') { - restriction = utils.parentElement(this.element); - } - else if (restriction === 'self') { - restriction = target.getRect(this.element); - } - else { - restriction = utils.closest(this.element, restriction); - } - - if (!restriction) { return status; } - } - - if (utils.isFunction(restriction)) { - restriction = restriction(page.x, page.y, this.element); - } - - if (utils.isElement(restriction)) { - restriction = utils.getElementRect(restriction); - } - - rect = restriction; - - if (!restriction) { - restrictedX = page.x; - restrictedY = page.y; - } - // object is assumed to have - // x, y, width, height or - // left, top, right, bottom - else if ('x' in restriction && 'y' in restriction) { - restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top ); - } - else { - restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top ); - } - - status.dx = restrictedX - page.x; - status.dy = restrictedY - page.y; - - status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; - status.restricted = !!(status.dx || status.dy); - - status.restrictedX = restrictedX; - status.restrictedY = restrictedY; - - return status; + return modifiers.restrict.set(pageCoords, this, status); }, checkAndPreventDefault: function (event, interactable, element) { diff --git a/src/interact.js b/src/interact.js index f20754be1..bf368ce76 100644 --- a/src/interact.js +++ b/src/interact.js @@ -139,16 +139,6 @@ return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); }; - scope.checkRestrict = function (interactable, action) { - var options = interactable.options; - - if (/^resize/.test(action)) { - action = 'resize'; - } - - return options[action].restrict && options[action].restrict.enabled; - }; - scope.checkAutoScroll = function (interactable, action) { var options = interactable.options; @@ -615,3 +605,4 @@ require('./actions/gesture'); require('./modifiers/snap'); + require('./modifiers/restrict'); diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js new file mode 100644 index 000000000..8b20ba45e --- /dev/null +++ b/src/modifiers/restrict.js @@ -0,0 +1,130 @@ +'use strict'; + +var modifiers = require('./index'), + utils = require('../utils'); + +var restrict = { + options: { + enabled : false, + endOnly : false, + restriction: null, + elementRect: null + }, + shouldDo: function (interactable, actionName, preEnd) { + var options = interactable.options; + + return (options[actionName].restrict + && options[actionName].restrict.enabled + && (preEnd || !interactable.options[actionName].restrict.endOnly)); + }, + set: function (pageCoords, interaction, status) { + var target = interaction.target, + restrict = target && target.options[interaction.prepared.name].restrict, + restriction = restrict && restrict.restriction, + page; + + if (!restriction) { + return status; + } + + status = status || interaction.restrictStatus; + + page = status.useStatusXY + ? page = { x: status.x, y: status.y } + : page = utils.extend({}, pageCoords); + + if (status.snap && status.snap.locked) { + page.x += status.snap.dx || 0; + page.y += status.snap.dy || 0; + } + + page.x -= interaction.inertiaStatus.resumeDx; + page.y -= interaction.inertiaStatus.resumeDy; + + status.dx = 0; + status.dy = 0; + status.restricted = false; + + var rect, restrictedX, restrictedY; + + if (utils.isString(restriction)) { + if (restriction === 'parent') { + restriction = utils.parentElement(interaction.element); + } + else if (restriction === 'self') { + restriction = target.getRect(interaction.element); + } + else { + restriction = utils.closest(interaction.element, restriction); + } + + if (!restriction) { return status; } + } + + if (utils.isFunction(restriction)) { + restriction = restriction(page.x, page.y, interaction.element); + } + + if (utils.isElement(restriction)) { + restriction = utils.getElementRect(restriction); + } + + rect = restriction; + + var offset = interaction.restrictOffset; + + if (!restriction) { + restrictedX = page.x; + restrictedY = page.y; + } + // object is assumed to have + // x, y, width, height or + // left, top, right, bottom + else if ('x' in restriction && 'y' in restriction) { + restrictedX = Math.max(Math.min(rect.x + rect.width - offset.right , page.x), rect.x + offset.left); + restrictedY = Math.max(Math.min(rect.y + rect.height - offset.bottom, page.y), rect.y + offset.top ); + } + else { + restrictedX = Math.max(Math.min(rect.right - offset.right , page.x), rect.left + offset.left); + restrictedY = Math.max(Math.min(rect.bottom - offset.bottom, page.y), rect.top + offset.top ); + } + + status.dx = restrictedX - page.x; + status.dy = restrictedY - page.y; + + status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; + status.restricted = !!(status.dx || status.dy); + + status.restrictedX = restrictedX; + status.restrictedY = restrictedY; + + return status; + }, + + modifyCoords: function (page, client, interactable, status, actionName, phase) { + var options = interactable.options[actionName].restrict, + elementRect = options && options.elementRect; + + if (modifiers.restrict.shouldDo(interactable, actionName) + && !(phase === 'start' && elementRect && status.locked)) { + + if (status.restricted) { + page.x += status.dx; + page.y += status.dy; + client.x += status.dx; + client.y += status.dy; + + return { + dx: status.dx, + dy: status.dy + }; + } + } + } +}; + +modifiers.restrict = restrict; +modifiers.names.push('restrict'); + +module.exports = restrict; + From 781d5d4cbefeaaa868f7a8e4dd5f2ca36248bd3d Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 6 Jul 2015 19:49:49 +0200 Subject: [PATCH 048/131] Rename restrictStatus.restricted to locked Consistent with snapStatus.locked --- src/Interaction.js | 12 ++++++------ src/modifiers/restrict.js | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 70a66e2a8..753fc9119 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -139,7 +139,7 @@ function Interaction () { dx : 0, dy : 0, restrictedX: 0, restrictedY: 0, snap : null, - restricted : false, + locked : false, changed : false }; @@ -521,12 +521,12 @@ Interaction.prototype = { shouldRestrict = modifiers.restrict.shouldDo(target, this.prepared.name, preEnd); if (shouldSnap ) { modifiers.snap .set(coords, this); } else { this.snapStatus .locked = false; } - if (shouldRestrict) { modifiers.restrict.set(coords, this); } else { this.restrictStatus.restricted = false; } + if (shouldRestrict) { modifiers.restrict.set(coords, this); } else { this.restrictStatus.locked = false; } if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { - shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; + shouldMove = shouldRestrict && this.restrictStatus.locked && this.restrictStatus.changed; } - else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) { + else if (shouldRestrict && this.restrictStatus.locked && !this.restrictStatus.changed) { shouldMove = false; } @@ -911,7 +911,7 @@ Interaction.prototype = { if (endRestrict) { modifiers.restrict.set(this.curCoords.page, this, snapRestrict); - if (snapRestrict.restricted) { + if (snapRestrict.locked) { dx += snapRestrict.dx; dy += snapRestrict.dy; } @@ -971,7 +971,7 @@ Interaction.prototype = { if (endRestrict) { var restrict = modifiers.restrict.set(this.curCoords.page, this, statusObject); - if (restrict.restricted) { + if (restrict.locked) { dx += restrict.dx; dy += restrict.dy; } diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index 8b20ba45e..3ee043e29 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -43,7 +43,7 @@ var restrict = { status.dx = 0; status.dy = 0; - status.restricted = false; + status.locked = false; var rect, restrictedX, restrictedY; @@ -93,7 +93,7 @@ var restrict = { status.dy = restrictedY - page.y; status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; - status.restricted = !!(status.dx || status.dy); + status.locked = !!(status.dx || status.dy); status.restrictedX = restrictedX; status.restrictedY = restrictedY; @@ -108,7 +108,7 @@ var restrict = { if (modifiers.restrict.shouldDo(interactable, actionName) && !(phase === 'start' && elementRect && status.locked)) { - if (status.restricted) { + if (status.locked) { page.x += status.dx; page.y += status.dy; client.x += status.dx; From 03bb54be28ccac08f21aead16cbc1abd95a6c3f8 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 6 Jul 2015 22:02:08 +0200 Subject: [PATCH 049/131] Use general modifier interface for snap & restrict --- src/InteractEvent.js | 8 ++++++-- src/Interaction.js | 41 ++++++++++++++++++--------------------- src/modifiers/restrict.js | 11 ++--------- src/modifiers/snap.js | 23 +++++++++++----------- 4 files changed, 38 insertions(+), 45 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 98230fa12..a75fd3620 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -41,8 +41,12 @@ function InteractEvent (interaction, event, action, phase, element, related) { this.interaction = interaction; this.interactable = target; - this.snap = modifiers.snap.modifyCoords (page, client, target, interaction.snapStatus , action, phase); - this.restrict = modifiers.restrict.modifyCoords(page, client, target, interaction.restrictStatus, action, phase); + for (var i = 0; i < modifiers.names.length; i++) { + var modifierName = modifiers.names[i], + modifier = modifiers[modifierName]; + + this[modifierName] = modifier.modifyCoords(page, client, target, interaction[modifierName + 'Status'], action, phase); + } this.pageX = page.x; this.pageY = page.y; diff --git a/src/Interaction.js b/src/Interaction.js index 753fc9119..4953f1d75 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -143,8 +143,6 @@ function Interaction () { changed : false }; - this.restrictStatus.snap = this.snapStatus; - this.pointerIsDown = false; this.pointerWasMoved = false; this.gesturing = false; @@ -515,22 +513,29 @@ Interaction.prototype = { }, setModifications: function (coords, preEnd) { - var target = this.target, - shouldMove = true, - shouldSnap = modifiers.snap.shouldDo(target, this.prepared.name, preEnd), - shouldRestrict = modifiers.restrict.shouldDo(target, this.prepared.name, preEnd); + var target = this.target, + lastStatus = null; - if (shouldSnap ) { modifiers.snap .set(coords, this); } else { this.snapStatus .locked = false; } - if (shouldRestrict) { modifiers.restrict.set(coords, this); } else { this.restrictStatus.locked = false; } + coords = utils.extend({}, coords); - if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { - shouldMove = shouldRestrict && this.restrictStatus.locked && this.restrictStatus.changed; - } - else if (shouldRestrict && this.restrictStatus.locked && !this.restrictStatus.changed) { - shouldMove = false; + for (var i = 0; i < modifiers.names.length; i++) { + var modifierName = modifiers.names[i], + modifier = modifiers[modifierName]; + + if (!modifier.shouldDo(target, this.prepared.name, preEnd)) { continue; } + + var status = modifier.set(coords, this, this[modifierName + 'Status']); + + if (status.locked) { + coords.x += status.dx; + coords.y += status.dy; + } + + lastStatus = status; } - return shouldMove; + // shouldMove + return !lastStatus || !lastStatus.locked || lastStatus.changed; }, setStartOffsets: function (action, interactable, element) { @@ -1321,14 +1326,6 @@ Interaction.prototype = { } }, - setSnapping: function (pageCoords, status) { - return modifiers.snap.set(pageCoords, status, this); - }, - - setRestriction: function (pageCoords, status) { - return modifiers.restrict.set(pageCoords, this, status); - }, - checkAndPreventDefault: function (event, interactable, element) { if (!(interactable = interactable || this.target)) { return; } diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index 3ee043e29..236243c97 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -11,11 +11,9 @@ var restrict = { elementRect: null }, shouldDo: function (interactable, actionName, preEnd) { - var options = interactable.options; + var restrict = interactable.options[actionName].restrict; - return (options[actionName].restrict - && options[actionName].restrict.enabled - && (preEnd || !interactable.options[actionName].restrict.endOnly)); + return (restrict && restrict.enabled && (preEnd || !restrict.endOnly)); }, set: function (pageCoords, interaction, status) { var target = interaction.target, @@ -33,11 +31,6 @@ var restrict = { ? page = { x: status.x, y: status.y } : page = utils.extend({}, pageCoords); - if (status.snap && status.snap.locked) { - page.x += status.snap.dx || 0; - page.y += status.snap.dy || 0; - } - page.x -= interaction.inertiaStatus.resumeDx; page.y -= interaction.inertiaStatus.resumeDy; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index ab6f8197d..96d0bafa4 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -7,20 +7,18 @@ var modifiers = require('./index'), var snap = { options: { - enabled : false, - endOnly : false, - range : Infinity, - targets : null, - offsets : null, + enabled: false, + endOnly: false, + range : Infinity, + targets: null, + offsets: null, relativePoints: null }, shouldDo: function (interactable, actionName, preEnd) { - var options = interactable.options; + var snap = interactable.options[actionName].snap; - return (options[actionName].snap - && options[actionName].snap.enabled - && (preEnd || !interactable.options[actionName].snap.endOnly)); + return (snap && snap.enabled && (preEnd || !snap.endOnly)); }, set: function (pageCoords, interaction, status) { var snap = interaction.target.options[interaction.prepared.name].snap, @@ -46,8 +44,8 @@ var snap = { status.realX = page.x; status.realY = page.y; - page.x = page.x - interaction.inertiaStatus.resumeDx; - page.y = page.y - interaction.inertiaStatus.resumeDy; + page.x -= interaction.inertiaStatus.resumeDx; + page.y -= interaction.inertiaStatus.resumeDy; var len = snap.targets? snap.targets.length : 0; @@ -152,7 +150,8 @@ var snap = { }, modifyCoords: function (page, client, interactable, status, actionName, phase) { - var relativePoints = interactable.options[actionName].snap && interactable.options.relativePoints; + var options = interactable.options[actionName].snap, + relativePoints = options && options.relativePoints; if (modifiers.snap.shouldDo(interactable, actionName) && !(phase === 'start' && relativePoints && relativePoints.length)) { From 20a56e42bd0e02f418eed37dec38368a0ece4fb9 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 11 Jul 2015 09:23:22 +0200 Subject: [PATCH 050/131] Fix "elision" issue with jshint --- .jshintrc | 1 - src/Interaction.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.jshintrc b/.jshintrc index b4ae95036..2019b7dc6 100644 --- a/.jshintrc +++ b/.jshintrc @@ -11,6 +11,5 @@ "undef" : true, "unused" : true, "strict" : true, - "elision" : true, "trailing" : true } diff --git a/src/Interaction.js b/src/Interaction.js index 4953f1d75..840125ec4 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -1267,7 +1267,7 @@ Interaction.prototype = { pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' : utils.isString(pointer.pointerType) ? pointer.pointerType - : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; + : [undefined, undefined,'touch', 'pen', 'mouse'][pointer.pointerType]; } if (eventType === 'tap') { From e96096b9748d37a4c23d3c1b5644483cd916e236 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 14 Jul 2015 23:09:35 +0200 Subject: [PATCH 051/131] Assign to scope.listenToDocument correctly --- src/InteractEvent.js | 2 +- src/Interactable.js | 2 +- src/Interaction.js | 135 ++++++++------------------------------ src/actions/base.js | 2 +- src/interact.js | 2 +- src/modifiers/index.js | 51 +++++++++++++- src/modifiers/restrict.js | 15 +++-- src/modifiers/snap.js | 21 ++++-- 8 files changed, 106 insertions(+), 124 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index a75fd3620..d088f0504 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -45,7 +45,7 @@ function InteractEvent (interaction, event, action, phase, element, related) { var modifierName = modifiers.names[i], modifier = modifiers[modifierName]; - this[modifierName] = modifier.modifyCoords(page, client, target, interaction[modifierName + 'Status'], action, phase); + this[modifierName] = modifier.modifyCoords(page, client, target, interaction.modifierStatuses[modifierName], action, phase); } this.pageX = page.x; diff --git a/src/Interactable.js b/src/Interactable.js index 0fb6f124e..fd50e5e40 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -108,7 +108,7 @@ Interactable.prototype = { }, getAction: function (pointer, event, interaction, element) { - var action = this.defaultActionChecker(pointer, interaction, element); + var action = this.defaultActionChecker(pointer, event, interaction, element); if (this.options.actionChecker) { return this.options.actionChecker(pointer, event, action, this, element, interaction); diff --git a/src/Interaction.js b/src/Interaction.js index 840125ec4..e284597bd 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -125,23 +125,7 @@ function Interaction () { prevAngle : 0 // angle of the previous gesture event }; - this.snapStatus = { - x : 0, y : 0, - dx : 0, dy : 0, - realX : 0, realY : 0, - snappedX: 0, snappedY: 0, - targets : [], - locked : false, - changed : false - }; - - this.restrictStatus = { - dx : 0, dy : 0, - restrictedX: 0, restrictedY: 0, - snap : null, - locked : false, - changed : false - }; + this.modifierStatuses = modifiers.resetStatuses({}); this.pointerIsDown = false; this.pointerWasMoved = false; @@ -488,8 +472,7 @@ Interaction.prototype = { this.prepared.axis = action.axis; this.prepared.edges = action.edges; - this.snapStatus.snappedX = this.snapStatus.snappedY = - this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN; + modifiers.resetStatuses(this.modifierStatuses); this.downTimes[pointerIndex] = new Date().getTime(); this.downTargets[pointerIndex] = eventTarget; @@ -512,32 +495,6 @@ Interaction.prototype = { } }, - setModifications: function (coords, preEnd) { - var target = this.target, - lastStatus = null; - - coords = utils.extend({}, coords); - - for (var i = 0; i < modifiers.names.length; i++) { - var modifierName = modifiers.names[i], - modifier = modifiers[modifierName]; - - if (!modifier.shouldDo(target, this.prepared.name, preEnd)) { continue; } - - var status = modifier.set(coords, this, this[modifierName + 'Status']); - - if (status.locked) { - coords.x += status.dx; - coords.y += status.dy; - } - - lastStatus = status; - } - - // shouldMove - return !lastStatus || !lastStatus.locked || lastStatus.changed; - }, - setStartOffsets: function (action, interactable, element) { var rect = interactable.getRect(element), origin = scope.getOriginXY(interactable, element), @@ -647,7 +604,8 @@ Interaction.prototype = { this.setEventXY(this.startCoords); this.setStartOffsets(action.name, interactable, element); - this.setModifications(this.startCoords.page); + + modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); }, @@ -796,10 +754,10 @@ Interaction.prototype = { this.start(this.prepared, this.target, this.element); } - var shouldMove = this.setModifications(this.curCoords.page, preEnd); + var modifierResult = modifiers.setAll(this, this.curCoords.page, this.modifierStatuses, preEnd); // move if snapping or restriction doesn't prevent it - if (shouldMove || starting) { + if (modifierResult.shouldMove || starting) { this.prevEvent = this[this.prepared.name + 'Move'](event); } @@ -875,8 +833,9 @@ Interaction.prototype = { inertiaPossible = false, inertia = false, smoothEnd = false, - endSnap = modifiers.snap.shouldDo(target, this.prepared.name, true) && options[this.prepared.name].snap.endOnly, - endRestrict = modifiers.restrict.shouldDo(target, this.prepared.name, true) && options[this.prepared.name].restrict.endOnly, + statuses = {}, + modifierResult, + page = utils.extend({}, this.curCoords.page), dx = 0, dy = 0, startEvent; @@ -900,29 +859,13 @@ Interaction.prototype = { && pointerSpeed > inertiaOptions.minSpeed && pointerSpeed > inertiaOptions.endSpeed); - if (inertiaPossible && !inertia && (endSnap || endRestrict)) { - - var snapRestrict = {}; + // smoothEnd + if (inertiaPossible && !inertia) { + modifiers.resetStatuses(statuses); - snapRestrict.snap = snapRestrict.restrict = snapRestrict; + modifierResult = modifiers.setAll(this, page, statuses, true, true); - if (endSnap) { - modifiers.snap.set(this.curCoords.page, this, snapRestrict); - if (snapRestrict.locked) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } - - if (endRestrict) { - modifiers.restrict.set(this.curCoords.page, this, snapRestrict); - if (snapRestrict.locked) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } - - if (dx || dy) { + if (modifierResult.shouldMove && modifierResult.locked) { smoothEnd = true; } } @@ -944,46 +887,17 @@ Interaction.prototype = { this.calcInertia(inertiaStatus); - var page = utils.extend({}, this.curCoords.page), - origin = scope.getOriginXY(target, this.element), - statusObject; - - page.x = page.x + inertiaStatus.xe - origin.x; - page.y = page.y + inertiaStatus.ye - origin.y; + page = utils.extend({}, this.curCoords.page); - statusObject = { - useStatusXY: true, - x: page.x, - y: page.y, - dx: 0, - dy: 0, - snap: null - }; + page.x += inertiaStatus.xe; + page.y += inertiaStatus.ye; - statusObject.snap = statusObject; + modifiers.resetStatuses(statuses); - dx = dy = 0; + modifierResult = modifiers.setAll(this, page, statuses, true, true); - if (endSnap) { - var snap = modifiers.snap.set(this.curCoords.page, this, statusObject); - - if (snap.locked) { - dx += snap.dx; - dy += snap.dy; - } - } - - if (endRestrict) { - var restrict = modifiers.restrict.set(this.curCoords.page, this, statusObject); - - if (restrict.locked) { - dx += restrict.dx; - dy += restrict.dy; - } - } - - inertiaStatus.modifiedXe += dx; - inertiaStatus.modifiedYe += dy; + inertiaStatus.modifiedXe += modifierResult.dx; + inertiaStatus.modifiedYe += modifierResult.dy; inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); } @@ -1001,6 +915,9 @@ Interaction.prototype = { return; } + var endSnap = modifiers.snap.shouldDo(target, this.prepared.name, true, true), + endRestrict = modifiers.restrict.shouldDo(target, this.prepared.name, true, true); + if (endSnap || endRestrict) { // fire a move event at the snapped coordinates this.pointerMove(pointer, event, eventTarget, curEventTarget, true); @@ -1058,10 +975,12 @@ Interaction.prototype = { this.clearTargets(); - this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; + this.pointerIsDown = this.dragging = this.resizing = this.gesturing = false; this.prepared.name = this.prevEvent = null; this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; + modifiers.resetStatuses(this.modifierStatuses); + // remove pointers if their ID isn't in this.pointerIds for (var i = 0; i < this.pointers.length; i++) { if (utils.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { diff --git a/src/actions/base.js b/src/actions/base.js index d838432d0..a5fca9821 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -14,7 +14,7 @@ var actions = { } }, - defaultActionChecker: function (pointer, interaction, element) { + defaultActionChecker: function (pointer, event, interaction, element) { var rect = this.getRect(element), action = null; diff --git a/src/interact.js b/src/interact.js index bf368ce76..d1bc7dbed 100644 --- a/src/interact.js +++ b/src/interact.js @@ -596,7 +596,7 @@ scope.Interactable = Interactable; scope.Interaction = Interaction; scope.InteractEvent = InteractEvent; - scope.listenToDocument = scope.listenToDocument; + scope.listenToDocument = listenToDocument; module.exports = interact; diff --git a/src/modifiers/index.js b/src/modifiers/index.js index 39712f15d..d4bd2615d 100644 --- a/src/modifiers/index.js +++ b/src/modifiers/index.js @@ -1,7 +1,56 @@ 'use strict'; +var utils = require('../utils'); + var modifiers = { - names: [] + names: [], + + setAll: function (interaction, coords, statuses, preEnd, requireEndOnly) { + var result = { + dx: 0, + dy: 0, + changed: false, + locked: false, + shouldMove: true + }, + target = interaction.target, + currentStatus; + + coords = utils.extend({}, coords); + + for (var i = 0; i < modifiers.names.length; i++) { + var modifierName = modifiers.names[i], + modifier = modifiers[modifierName]; + + if (!modifier.shouldDo(target, interaction.prepared.name, preEnd, requireEndOnly)) { continue; } + + currentStatus = modifier.set(coords, interaction, statuses[modifierName]); + + if (currentStatus.locked) { + coords.x += currentStatus.dx; + coords.y += currentStatus.dy; + + result.dx += currentStatus.dx; + result.dy += currentStatus.dy; + } + } + + // a move should be fired if the modified coords of + // the last modifier status that was calculated changes + result.shouldMove = !currentStatus || currentStatus.changed; + + return result; + }, + + resetStatuses: function (statuses) { + for (var i = 0; i < modifiers.names.length; i++) { + var modifierName = modifiers.names[i]; + + statuses[modifierName] = modifiers[modifierName].reset(statuses[modifierName] || {}); + } + + return statuses; + } }; module.exports = modifiers; diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index 236243c97..fbb1a96e1 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -10,10 +10,10 @@ var restrict = { restriction: null, elementRect: null }, - shouldDo: function (interactable, actionName, preEnd) { + shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { var restrict = interactable.options[actionName].restrict; - return (restrict && restrict.enabled && (preEnd || !restrict.endOnly)); + return restrict && restrict.enabled && (preEnd || !restrict.endOnly) && (!requireEndOnly || restrict.endOnly); }, set: function (pageCoords, interaction, status) { var target = interaction.target, @@ -25,8 +25,6 @@ var restrict = { return status; } - status = status || interaction.restrictStatus; - page = status.useStatusXY ? page = { x: status.x, y: status.y } : page = utils.extend({}, pageCoords); @@ -94,6 +92,15 @@ var restrict = { return status; }, + reset: function (status) { + status.dx = status.dy = 0; + status.modifiedX = status.modifiedY = NaN; + status.locked = false; + status.changed = true; + + return status; + }, + modifyCoords: function (page, client, interactable, status, actionName, phase) { var options = interactable.options[actionName].restrict, elementRect = options && options.elementRect; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index 96d0bafa4..058ba7819 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -15,10 +15,10 @@ var snap = { relativePoints: null }, - shouldDo: function (interactable, actionName, preEnd) { + shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { var snap = interactable.options[actionName].snap; - return (snap && snap.enabled && (preEnd || !snap.endOnly)); + return snap && snap.enabled && (preEnd || !snap.endOnly) && (!requireEndOnly || snap.endOnly); }, set: function (pageCoords, interaction, status) { var snap = interaction.target.options[interaction.prepared.name].snap, @@ -27,8 +27,6 @@ var snap = { page, i; - status = status || interaction.snapStatus; - if (status.useStatusXY) { page = { x: status.x, y: status.y }; } @@ -149,11 +147,20 @@ var snap = { return status; }, + reset: function (status) { + status.dx = status.dy = 0; + status.snappedX = status.snappedY = NaN; + status.locked = false; + status.changed = true; + + return status; + }, + modifyCoords: function (page, client, interactable, status, actionName, phase) { - var options = interactable.options[actionName].snap, - relativePoints = options && options.relativePoints; + var snap = interactable.options[actionName].snap, + relativePoints = snap && snap.relativePoints; - if (modifiers.snap.shouldDo(interactable, actionName) + if (snap && snap.enabled && !(phase === 'start' && relativePoints && relativePoints.length)) { if (status.locked) { From 518b39f9979b22542a7657db64c208b6d77d7bd1 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 14 Jul 2015 23:50:35 +0200 Subject: [PATCH 052/131] Update demos --- demo/js/gallery.js | 44 ++++++++++++++++++++++++++++---------------- demo/js/html_svg.js | 9 +++++---- demo/js/iframes.js | 14 +++++++------- demo/js/snap.js | 4 ++-- demo/star.svg | 2 +- 5 files changed, 43 insertions(+), 30 deletions(-) diff --git a/demo/js/gallery.js b/demo/js/gallery.js index 9f173820d..2dbb56283 100644 --- a/demo/js/gallery.js +++ b/demo/js/gallery.js @@ -2,11 +2,21 @@ interact(document).on('DOMContentLoaded', function () { "use strict"; /* global interact, Modernizr */ +/* + * This demo is very broken! + */ + var preTransform = Modernizr.prefixed('transform'), snapTarget = {}; interact('#gallery .thumbnail') .draggable({ + snap: { + targets: [], + relativePoints: [ { x: 0.5, y: 0.5 } ], + endOnly: true + }, + inertia: true, onstart: function (event) { snapTarget = { x: $('#gallery .stage').width() / 2, @@ -34,7 +44,7 @@ interact('#gallery .thumbnail') var $thumb = $(event.target); // if the drag was snapped to the stage - if (event.pageX === snapTarget.x && event.pageY === snapTarget.y) { + if (event.dropzone) { $('#gallery .stage img').removeClass('active'); $('#gallery .thumbnail').removeClass('expanded') .not($thumb).css(preTransform, ''); @@ -49,19 +59,21 @@ interact('#gallery .thumbnail') $thumb.removeClass('dragging'); } }) - .origin($('#gallery')[0]) - .snap({ - mode: 'path', - // If the pointer is far enough above the bottom of the stage - // then snap to the center of the stage - paths: [function (x, y) { - if (y < $('#gallery .stage').height() * 0.7) { - return snapTarget; - } - return {}; - }], - endOnly: true - }) - //.snap(false) - .inertia(true); + .origin($('#gallery')[0]); + + interact('#gallery .stage') + .dropzone({ + accept: ' #gallery .thumbnail', + overlap: 1, + }) + .on('dragenter', function (event) { + event.draggable.draggable({ + snap: { targets: [snapTarget] } + }); + }) + .on('dragleave drop', function (event) { + event.draggable.draggable({ + snap: { targets: [] } + }); + }); }()); diff --git a/demo/js/html_svg.js b/demo/js/html_svg.js index 983e16fef..0b72ac4ed 100644 --- a/demo/js/html_svg.js +++ b/demo/js/html_svg.js @@ -9,7 +9,8 @@ var svg, svgNS = 'http://www.w3.org/2000/svg', - SVGElement = window.SVGElement; + SVGElement = window.SVGElement, + eventTypes = interact.debug().eventTypes; function DemoGraphic(id) { var width = window.innerWidth, @@ -86,7 +87,7 @@ textProp = 'textContent'; nl = '\n'; - if ( target.demo && indexOf(interact.eventTypes, e.type) !== -1 ) { + if ( target.demo && indexOf(eventTypes, e.type) !== -1 ) { target.text[textProp] = nl + e.type; target.text[textProp] += nl + ' x0, y0 : (' + e.x0 + ', ' + e.y0 + ')'; target.text[textProp] += nl + ' dx, dy : (' + e.dx + ', ' + e.dy + ')'; @@ -232,8 +233,8 @@ interact('div.demo-node, .demo-node ellipse') .draggable({ max: 2, - autoScroll: { enabled: true }, - inertia: { enabled: true } + autoScroll: true, + inertia: true }) .gesturable({ max: 1 }) .resizable({ diff --git a/demo/js/iframes.js b/demo/js/iframes.js index 5ac34c38c..243b406ef 100644 --- a/demo/js/iframes.js +++ b/demo/js/iframes.js @@ -2,15 +2,15 @@ function setInteractables () { 'use strict'; interact('.draggable', { context: document }) - .autoScroll(true) .draggable({ onmove: onMove, - }) - .inertia(true) - .restrict({ - drag: "parent", - endOnly: true, - elementRect: { top: 0, left: 0, bottom: 1, right: 1 } + inertia: { enabled: true }, + restrict: { + drag: "parent", + endOnly: true, + elementRect: { top: 0, left: 0, bottom: 1, right: 1 } + }, + autoScroll: true }); function onMove (event) { diff --git a/demo/js/snap.js b/demo/js/snap.js index 6c5d1077b..62cc892d7 100644 --- a/demo/js/snap.js +++ b/demo/js/snap.js @@ -228,14 +228,14 @@ interact(canvas) .draggable({ + inertia: { enabled: status.inertia.checked }, snap: { targets: status.gridMode.checked? [gridFunc] : status.anchorMode.checked? anchors : null, enabled: !status.offMode.checked, endOnly: status.endOnly.checked, offset: status.relative.checked? 'startCoords' : null } - }) - .inertia(status.inertia.checked); + }); if (!status.relative.checked) { snapOffset.x = snapOffset.y = 0; diff --git a/demo/star.svg b/demo/star.svg index 739d794f7..901be5f92 100644 --- a/demo/star.svg +++ b/demo/star.svg @@ -1,7 +1,7 @@ - + Date: Wed, 15 Jul 2015 23:49:26 +0200 Subject: [PATCH 053/131] Remove and ignore the ./build directory --- .gitignore | 2 +- build/interact.js | 5934 ----------------------------------------- build/interact.js.map | 1 - build/interact.min.js | 5 - 4 files changed, 1 insertion(+), 5941 deletions(-) delete mode 100644 build/interact.js delete mode 100644 build/interact.js.map delete mode 100644 build/interact.min.js diff --git a/.gitignore b/.gitignore index a56a7ef43..dd87e2d73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules - +build diff --git a/build/interact.js b/build/interact.js deleted file mode 100644 index f38fb636b..000000000 --- a/build/interact.js +++ /dev/null @@ -1,5934 +0,0 @@ -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o - * Open source under the MIT License. - * https://raw.github.com/taye/interact.js/master/LICENSE - */ - - 'use strict'; - - // return early if there's no window to work with (eg. Node.js) - if (!require('./utils/window').window) { return; } - - var scope = require('./scope'), - utils = require('./utils'), - browser = utils.browser; - - scope.pEventTypes = null; - - scope.documents = []; // all documents being listened to - - scope.interactables = []; // all set interactables - scope.interactions = []; // all interactions - - scope.dynamicDrop = false; - - // { - // type: { - // selectors: ['selector', ...], - // contexts : [document, ...], - // listeners: [[listener, useCapture], ...] - // } - // } - scope.delegatedEvents = {}; - - scope.defaultOptions = require('./defaultOptions'); - - // Things related to autoScroll - scope.autoScroll = require('./autoScroll'); - - // Less Precision with touch input - scope.margin = browser.supportsTouch || browser.supportsPointerEvent? 20: 10; - - scope.pointerMoveTolerance = 1; - - // for ignoring browser's simulated mouse events - scope.prevTouchTime = 0; - - // Allow this many interactions to happen simultaneously - scope.maxInteractions = Infinity; - - scope.actionCursors = browser.isIe9OrOlder ? { - drag : 'move', - resizex : 'e-resize', - resizey : 's-resize', - resizexy: 'se-resize', - - resizetop : 'n-resize', - resizeleft : 'w-resize', - resizebottom : 's-resize', - resizeright : 'e-resize', - resizetopleft : 'se-resize', - resizebottomright: 'se-resize', - resizetopright : 'ne-resize', - resizebottomleft : 'ne-resize', - - gesture : '' - } : { - drag : 'move', - resizex : 'ew-resize', - resizey : 'ns-resize', - resizexy: 'nwse-resize', - - resizetop : 'ns-resize', - resizeleft : 'ew-resize', - resizebottom : 'ns-resize', - resizeright : 'ew-resize', - resizetopleft : 'nwse-resize', - resizebottomright: 'nwse-resize', - resizetopright : 'nesw-resize', - resizebottomleft : 'nesw-resize', - - gesture : '' - }; - - scope.actionIsEnabled = { - drag : true, - resize : true, - gesture: true - }; - - // because Webkit and Opera still use 'mousewheel' event type - scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel'; - - scope.eventTypes = [ - 'dragstart', - 'dragmove', - 'draginertiastart', - 'dragend', - 'dragenter', - 'dragleave', - 'dropactivate', - 'dropdeactivate', - 'dropmove', - 'drop', - 'resizestart', - 'resizemove', - 'resizeinertiastart', - 'resizeend', - 'gesturestart', - 'gesturemove', - 'gestureinertiastart', - 'gestureend', - - 'down', - 'move', - 'up', - 'cancel', - 'tap', - 'doubletap', - 'hold' - ]; - - scope.globalEvents = {}; - - // prefix matchesSelector - browser.prefixedMatchesSelector = 'matches' in Element.prototype? - 'matches': 'webkitMatchesSelector' in Element.prototype? - 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? - 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? - 'oMatchesSelector': 'msMatchesSelector'; - - // will be polyfill function if browser is IE8 - scope.ie8MatchesSelector = null; - - // Events wrapper - var events = require('./utils/events'); - - scope.listeners = {}; - - var interactionListeners = [ - 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove', - 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', - 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', - 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' - ]; - - scope.trySelector = function (value) { - if (!scope.isString(value)) { return false; } - - // an exception will be raised if it is invalid - scope.document.querySelector(value); - return true; - }; - - scope.getScrollXY = function (win) { - win = win || scope.window; - return { - x: win.scrollX || win.document.documentElement.scrollLeft, - y: win.scrollY || win.document.documentElement.scrollTop - }; - }; - - scope.getActualElement = function (element) { - return (element instanceof scope.SVGElementInstance - ? element.correspondingUseElement - : element); - }; - - scope.getElementRect = function (element) { - var scroll = browser.isIOS7orLower - ? { x: 0, y: 0 } - : scope.getScrollXY(scope.getWindow(element)), - clientRect = (element instanceof scope.SVGElement)? - element.getBoundingClientRect(): - element.getClientRects()[0]; - - return clientRect && { - left : clientRect.left + scroll.x, - right : clientRect.right + scroll.x, - top : clientRect.top + scroll.y, - bottom: clientRect.bottom + scroll.y, - width : clientRect.width || clientRect.right - clientRect.left, - height: clientRect.heigh || clientRect.bottom - clientRect.top - }; - }; - - scope.getOriginXY = function (interactable, element) { - var origin = interactable - ? interactable.options.origin - : scope.defaultOptions.origin; - - if (origin === 'parent') { - origin = scope.parentElement(element); - } - else if (origin === 'self') { - origin = interactable.getRect(element); - } - else if (scope.trySelector(origin)) { - origin = scope.closest(element, origin) || { x: 0, y: 0 }; - } - - if (scope.isFunction(origin)) { - origin = origin(interactable && element); - } - - if (utils.isElement(origin)) { - origin = scope.getElementRect(origin); - } - - origin.x = ('x' in origin)? origin.x : origin.left; - origin.y = ('y' in origin)? origin.y : origin.top; - - return origin; - }; - - // http://stackoverflow.com/a/5634528/2280888 - scope._getQBezierValue = function (t, p1, p2, p3) { - var iT = 1 - t; - return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; - }; - - scope.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) { - return { - x: scope._getQBezierValue(position, startX, cpX, endX), - y: scope._getQBezierValue(position, startY, cpY, endY) - }; - }; - - // http://gizma.com/easing/ - scope.easeOutQuad = function (t, b, c, d) { - t /= d; - return -c * t*(t-2) + b; - }; - - scope.nodeContains = function (parent, child) { - while (child) { - if (child === parent) { - return true; - } - - child = child.parentNode; - } - - return false; - }; - - scope.closest = function (child, selector) { - var parent = scope.parentElement(child); - - while (utils.isElement(parent)) { - if (scope.matchesSelector(parent, selector)) { return parent; } - - parent = scope.parentElement(parent); - } - - return null; - }; - - scope.parentElement = function (node) { - var parent = node.parentNode; - - if (scope.isDocFrag(parent)) { - // skip past #shado-root fragments - while ((parent = parent.host) && scope.isDocFrag(parent)) {} - - return parent; - } - - return parent; - }; - - scope.inContext = function (interactable, element) { - return interactable._context === element.ownerDocument - || scope.nodeContains(interactable._context, element); - }; - - scope.testIgnore = function (interactable, interactableElement, element) { - var ignoreFrom = interactable.options.ignoreFrom; - - if (!ignoreFrom || !utils.isElement(element)) { return false; } - - if (scope.isString(ignoreFrom)) { - return scope.matchesUpTo(element, ignoreFrom, interactableElement); - } - else if (utils.isElement(ignoreFrom)) { - return scope.nodeContains(ignoreFrom, element); - } - - return false; - }; - - scope.testAllow = function (interactable, interactableElement, element) { - var allowFrom = interactable.options.allowFrom; - - if (!allowFrom) { return true; } - - if (!utils.isElement(element)) { return false; } - - if (scope.isString(allowFrom)) { - return scope.matchesUpTo(element, allowFrom, interactableElement); - } - else if (utils.isElement(allowFrom)) { - return scope.nodeContains(allowFrom, element); - } - - return false; - }; - - scope.checkAxis = function (axis, interactable) { - if (!interactable) { return false; } - - var thisAxis = interactable.options.drag.axis; - - return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); - }; - - scope.checkSnap = function (interactable, action) { - var options = interactable.options; - - if (/^resize/.test(action)) { - action = 'resize'; - } - - return options[action].snap && options[action].snap.enabled; - }; - - scope.checkRestrict = function (interactable, action) { - var options = interactable.options; - - if (/^resize/.test(action)) { - action = 'resize'; - } - - return options[action].restrict && options[action].restrict.enabled; - }; - - scope.checkAutoScroll = function (interactable, action) { - var options = interactable.options; - - if (/^resize/.test(action)) { - action = 'resize'; - } - - return options[action].autoScroll && options[action].autoScroll.enabled; - }; - - scope.withinInteractionLimit = function (interactable, element, action) { - var options = interactable.options, - maxActions = options[action.name].max, - maxPerElement = options[action.name].maxPerElement, - activeInteractions = 0, - targetCount = 0, - targetElementCount = 0; - - for (var i = 0, len = scope.interactions.length; i < len; i++) { - var interaction = scope.interactions[i], - otherAction = interaction.prepared.name, - active = interaction.interacting(); - - if (!active) { continue; } - - activeInteractions++; - - if (activeInteractions >= scope.maxInteractions) { - return false; - } - - if (interaction.target !== interactable) { continue; } - - targetCount += (otherAction === action.name)|0; - - if (targetCount >= maxActions) { - return false; - } - - if (interaction.element === element) { - targetElementCount++; - - if (otherAction !== action.name || targetElementCount >= maxPerElement) { - return false; - } - } - } - - return scope.maxInteractions > 0; - }; - - // Test for the element that's "above" all other qualifiers - scope.indexOfDeepestElement = function (elements) { - var dropzone, - deepestZone = elements[0], - index = deepestZone? 0: -1, - parent, - deepestZoneParents = [], - dropzoneParents = [], - child, - i, - n; - - for (i = 1; i < elements.length; i++) { - dropzone = elements[i]; - - // an element might belong to multiple selector dropzones - if (!dropzone || dropzone === deepestZone) { - continue; - } - - if (!deepestZone) { - deepestZone = dropzone; - index = i; - continue; - } - - // check if the deepest or current are document.documentElement or document.rootElement - // - if the current dropzone is, do nothing and continue - if (dropzone.parentNode === dropzone.ownerDocument) { - continue; - } - // - if deepest is, update with the current dropzone and continue to next - else if (deepestZone.parentNode === dropzone.ownerDocument) { - deepestZone = dropzone; - index = i; - continue; - } - - if (!deepestZoneParents.length) { - parent = deepestZone; - while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { - deepestZoneParents.unshift(parent); - parent = parent.parentNode; - } - } - - // if this element is an svg element and the current deepest is - // an HTMLElement - if (deepestZone instanceof scope.HTMLElement - && dropzone instanceof scope.SVGElement - && !(dropzone instanceof scope.SVGSVGElement)) { - - if (dropzone === deepestZone.parentNode) { - continue; - } - - parent = dropzone.ownerSVGElement; - } - else { - parent = dropzone; - } - - dropzoneParents = []; - - while (parent.parentNode !== parent.ownerDocument) { - dropzoneParents.unshift(parent); - parent = parent.parentNode; - } - - n = 0; - - // get (position of last common ancestor) + 1 - while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { - n++; - } - - var parents = [ - dropzoneParents[n - 1], - dropzoneParents[n], - deepestZoneParents[n] - ]; - - child = parents[0].lastChild; - - while (child) { - if (child === parents[1]) { - deepestZone = dropzone; - index = i; - deepestZoneParents = []; - - break; - } - else if (child === parents[2]) { - break; - } - - child = child.previousSibling; - } - } - - return index; - }; - - scope.matchesSelector = function (element, selector, nodeList) { - if (scope.ie8MatchesSelector) { - return scope.ie8MatchesSelector(element, selector, nodeList); - } - - // remove /deep/ from selectors if shadowDOM polyfill is used - if (scope.window !== scope.realWindow) { - selector = selector.replace(/\/deep\//g, ' '); - } - - return element[browser.prefixedMatchesSelector](selector); - }; - - scope.matchesUpTo = function (element, selector, limit) { - while (utils.isElement(element)) { - if (scope.matchesSelector(element, selector)) { - return true; - } - - element = scope.parentElement(element); - - if (element === limit) { - return scope.matchesSelector(element, selector); - } - } - - return false; - }; - - // For IE8's lack of an Element#matchesSelector - // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - if (!(browser.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[browser.prefixedMatchesSelector])) { - scope.ie8MatchesSelector = function (element, selector, elems) { - elems = elems || element.parentNode.querySelectorAll(selector); - - for (var i = 0, len = elems.length; i < len; i++) { - if (elems[i] === element) { - return true; - } - } - - return false; - }; - } - - var Interaction = require('./Interaction'); - - function getInteractionFromPointer (pointer, eventType, eventTarget) { - var i = 0, len = scope.interactions.length, - mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) - // MSPointerEvent.MSPOINTER_TYPE_MOUSE - || pointer.pointerType === 4), - interaction; - - var id = utils.getPointerId(pointer); - - // try to resume inertia with a new pointer - if (/down|start/i.test(eventType)) { - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; - - var element = eventTarget; - - if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume - && (interaction.mouse === mouseEvent)) { - while (element) { - // if the element is the interaction element - if (element === interaction.element) { - // update the interaction's pointer - if (interaction.pointers[0]) { - interaction.removePointer(interaction.pointers[0]); - } - interaction.addPointer(pointer); - - return interaction; - } - element = scope.parentElement(element); - } - } - } - } - - // if it's a mouse interaction - if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { - - // find a mouse interaction that's not in inertia phase - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { - return scope.interactions[i]; - } - } - - // find any interaction specifically for mouse. - // if the eventType is a mousedown, and inertia is active - // ignore the interaction - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { - return interaction; - } - } - - // create a new interaction for mouse - interaction = new Interaction(); - interaction.mouse = true; - - return interaction; - } - - // get interaction that has this pointer - for (i = 0; i < len; i++) { - if (scope.contains(scope.interactions[i].pointerIds, id)) { - return scope.interactions[i]; - } - } - - // at this stage, a pointerUp should not return an interaction - if (/up|end|out/i.test(eventType)) { - return null; - } - - // get first idle interaction - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; - - if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) - && !interaction.interacting() - && !(!mouseEvent && interaction.mouse)) { - - interaction.addPointer(pointer); - - return interaction; - } - } - - return new Interaction(); - } - - function doOnInteractions (method) { - return (function (event) { - var interaction, - eventTarget = scope.getActualElement(event.path - ? event.path[0] - : event.target), - curEventTarget = scope.getActualElement(event.currentTarget), - i; - - if (browser.supportsTouch && /touch/.test(event.type)) { - scope.prevTouchTime = new Date().getTime(); - - for (i = 0; i < event.changedTouches.length; i++) { - var pointer = event.changedTouches[i]; - - interaction = getInteractionFromPointer(pointer, event.type, eventTarget); - - if (!interaction) { continue; } - - interaction._updateEventTargets(eventTarget, curEventTarget); - - interaction[method](pointer, event, eventTarget, curEventTarget); - } - } - else { - if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { - // ignore mouse events while touch interactions are active - for (i = 0; i < scope.interactions.length; i++) { - if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { - return; - } - } - - // try to ignore mouse events that are simulated by the browser - // after a touch event - if (new Date().getTime() - scope.prevTouchTime < 500) { - return; - } - } - - interaction = getInteractionFromPointer(event, event.type, eventTarget); - - if (!interaction) { return; } - - interaction._updateEventTargets(eventTarget, curEventTarget); - - interaction[method](event, event, eventTarget, curEventTarget); - } - }); - } - - function preventOriginalDefault () { - this.originalEvent.preventDefault(); - } - - function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { - // false, '', undefined, null - if (!value) { return false; } - - // true value, use pointer coords and element rect - if (value === true) { - // if dimensions are negative, "switch" edges - var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left, - height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top; - - if (width < 0) { - if (name === 'left' ) { name = 'right'; } - else if (name === 'right') { name = 'left' ; } - } - if (height < 0) { - if (name === 'top' ) { name = 'bottom'; } - else if (name === 'bottom') { name = 'top' ; } - } - - if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } - if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } - - if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } - if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } - } - - // the remaining checks require an element - if (!utils.isElement(element)) { return false; } - - return utils.isElement(value) - // the value is an element to use as a resize handle - ? value === element - // otherwise check if element matches value as selector - : scope.matchesUpTo(element, value, interactableElement); - } - - function defaultActionChecker (pointer, interaction, element) { - var rect = this.getRect(element), - shouldResize = false, - action = null, - resizeAxes = null, - resizeEdges, - page = utils.extend({}, interaction.curCoords.page), - options = this.options; - - if (!rect) { return null; } - - if (scope.actionIsEnabled.resize && options.resize.enabled) { - var resizeOptions = options.resize; - - resizeEdges = { - left: false, right: false, top: false, bottom: false - }; - - // if using resize.edges - if (scope.isObject(resizeOptions.edges)) { - for (var edge in resizeEdges) { - resizeEdges[edge] = checkResizeEdge(edge, - resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || scope.margin); - } - - resizeEdges.left = resizeEdges.left && !resizeEdges.right; - resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; - - shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; - } - else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); - - shouldResize = right || bottom; - resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); - } - } - - action = shouldResize - ? 'resize' - : scope.actionIsEnabled.drag && options.drag.enabled - ? 'drag' - : null; - - if (scope.actionIsEnabled.gesture - && interaction.pointerIds.length >=2 - && !(interaction.dragging || interaction.resizing)) { - action = 'gesture'; - } - - if (action) { - return { - name: action, - axis: resizeAxes, - edges: resizeEdges - }; - } - - return null; - } - - var InteractEvent = require('./InteractEvent'); - - for (var i = 0, len = interactionListeners.length; i < len; i++) { - var listenerName = interactionListeners[i]; - - scope.listeners[listenerName] = doOnInteractions(listenerName); - } - - // bound to the interactable context when a DOM event - // listener is added to a selector interactable - function delegateListener (event, useCapture) { - var fakeEvent = {}, - delegated = scope.delegatedEvents[event.type], - eventTarget = scope.getActualElement(event.path - ? event.path[0] - : event.target), - element = eventTarget; - - useCapture = useCapture? true: false; - - // duplicate the event so that currentTarget can be changed - for (var prop in event) { - fakeEvent[prop] = event[prop]; - } - - fakeEvent.originalEvent = event; - fakeEvent.preventDefault = preventOriginalDefault; - - // climb up document tree looking for selector matches - while (utils.isElement(element)) { - for (var i = 0; i < delegated.selectors.length; i++) { - var selector = delegated.selectors[i], - context = delegated.contexts[i]; - - if (scope.matchesSelector(element, selector) - && scope.nodeContains(context, eventTarget) - && scope.nodeContains(context, element)) { - - var listeners = delegated.listeners[i]; - - fakeEvent.currentTarget = element; - - for (var j = 0; j < listeners.length; j++) { - if (listeners[j][1] === useCapture) { - listeners[j][0](fakeEvent); - } - } - } - } - - element = scope.parentElement(element); - } - } - - function delegateUseCapture (event) { - return delegateListener.call(this, event, true); - } - - scope.interactables.indexOfElement = function indexOfElement (element, context) { - context = context || scope.document; - - for (var i = 0; i < this.length; i++) { - var interactable = this[i]; - - if ((interactable.selector === element - && (interactable._context === context)) - || (!interactable.selector && interactable._element === element)) { - - return i; - } - } - return -1; - }; - - scope.interactables.get = function interactableGet (element, options) { - return this[this.indexOfElement(element, options && options.context)]; - }; - - scope.interactables.forEachSelector = function (callback) { - for (var i = 0; i < this.length; i++) { - var interactable = this[i]; - - if (!interactable.selector) { - continue; - } - - var ret = callback(interactable, interactable.selector, interactable._context, i, this); - - if (ret !== undefined) { - return ret; - } - } - }; - - /*\ - * interact - [ method ] - * - * The methods of this variable can be used to set elements as - * interactables and also to change various default settings. - * - * Calling it as a function and passing an element or a valid CSS selector - * string returns an Interactable object which has various methods to - * configure it. - * - - element (Element | string) The HTML or SVG Element to interact with or CSS selector - = (object) An @Interactable - * - > Usage - | interact(document.getElementById('draggable')).draggable(true); - | - | var rectables = interact('rect'); - | rectables - | .gesturable(true) - | .on('gesturemove', function (event) { - | // something cool... - | }) - | .autoScroll(true); - \*/ - function interact (element, options) { - return scope.interactables.get(element, options) || new Interactable(element, options); - } - - /*\ - * Interactable - [ property ] - ** - * Object type returned by @interact - \*/ - function Interactable (element, options) { - this._element = element; - this._iEvents = this._iEvents || {}; - - var _window; - - if (scope.trySelector(element)) { - this.selector = element; - - var context = options && options.context; - - _window = context? scope.getWindow(context) : scope.window; - - if (context && (_window.Node - ? context instanceof _window.Node - : (utils.isElement(context) || context === _window.document))) { - - this._context = context; - } - } - else { - _window = scope.getWindow(element); - - if (utils.isElement(element, _window)) { - - if (scope.PointerEvent) { - events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown ); - events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover); - } - else { - events.add(this._element, 'mousedown' , scope.listeners.pointerDown ); - events.add(this._element, 'mousemove' , scope.listeners.pointerHover); - events.add(this._element, 'touchstart', scope.listeners.pointerDown ); - events.add(this._element, 'touchmove' , scope.listeners.pointerHover); - } - } - } - - this._doc = _window.document; - - if (!scope.contains(scope.documents, this._doc)) { - listenToDocument(this._doc); - } - - scope.interactables.push(this); - - this.set(options); - } - - Interactable.prototype = { - setOnEvents: function (action, phases) { - if (action === 'drop') { - if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } - if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } - if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } - if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } - if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } - if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } - } - else { - action = 'on' + action; - - if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } - if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } - if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } - if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } - } - - return this; - }, - - /*\ - * Interactable.draggable - [ method ] - * - * Gets or sets whether drag actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of drag events - | var isDraggable = interact('ul li').draggable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable) - = (object) This Interactable - | interact(element).draggable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // the axis in which the first movement must be - | // for the drag sequence to start - | // 'xy' by default - any direction - | axis: 'x' || 'y' || 'xy', - | - | // max number of drags that can happen concurrently - | // with elements of this Interactable. Infinity by default - | max: Infinity, - | - | // max number of drags that can target the same element+Interactable - | // 1 by default - | maxPerElement: 2 - | }); - \*/ - draggable: function (options) { - if (scope.isObject(options)) { - this.options.drag.enabled = options.enabled === false? false: true; - this.setPerAction('drag', options); - this.setOnEvents('drag', options); - - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.drag.axis = options.axis; - } - else if (options.axis === null) { - delete this.options.drag.axis; - } - - return this; - } - - if (scope.isBool(options)) { - this.options.drag.enabled = options; - - return this; - } - - return this.options.drag; - }, - - setPerAction: function (action, options) { - // for all the default per-action options - for (var option in options) { - // if this option exists for this action - if (option in scope.defaultOptions[action]) { - // if the option in the options arg is an object value - if (scope.isObject(options[option])) { - // duplicate the object - this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); - - if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { - this.options[action][option].enabled = options[option].enabled === false? false : true; - } - } - else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) { - this.options[action][option].enabled = options[option]; - } - else if (options[option] !== undefined) { - // or if it's not undefined, do a plain assignment - this.options[action][option] = options[option]; - } - } - } - }, - - /*\ - * Interactable.dropzone - [ method ] - * - * Returns or sets whether elements can be dropped onto this - * Interactable to trigger drop events - * - * Dropzones can receive the following events: - * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends - * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone - * - `dragmove` when a draggable that has entered the dropzone is moved - * - `drop` when a draggable is dropped into this dropzone - * - * Use the `accept` option to allow only elements that match the given CSS selector or element. - * - * Use the `overlap` option to set how drops are checked for. The allowed values are: - * - `'pointer'`, the pointer must be over the dropzone (default) - * - `'center'`, the draggable element's center must be over the dropzone - * - a number from 0-1 which is the `(intersection area) / (draggable area)`. - * e.g. `0.5` for drop to happen when half of the area of the - * draggable is over the dropzone - * - - options (boolean | object | null) #optional The new value to be set. - | interact('.drop').dropzone({ - | accept: '.can-drop' || document.getElementById('single-drop'), - | overlap: 'pointer' || 'center' || zeroToOne - | } - = (boolean | object) The current setting or this Interactable - \*/ - dropzone: function (options) { - if (scope.isObject(options)) { - this.options.drop.enabled = options.enabled === false? false: true; - this.setOnEvents('drop', options); - this.accept(options.accept); - - if (/^(pointer|center)$/.test(options.overlap)) { - this.options.drop.overlap = options.overlap; - } - else if (scope.isNumber(options.overlap)) { - this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); - } - - return this; - } - - if (scope.isBool(options)) { - this.options.drop.enabled = options; - - return this; - } - - return this.options.drop; - }, - - dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) { - var dropped = false; - - // if the dropzone has no rect (eg. display: none) - // call the custom dropChecker or just return false - if (!(rect = rect || this.getRect(dropElement))) { - return (this.options.dropChecker - ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) - : false); - } - - var dropOverlap = this.options.drop.overlap; - - if (dropOverlap === 'pointer') { - var page = utils.getPageXY(pointer), - origin = scope.getOriginXY(draggable, draggableElement), - horizontal, - vertical; - - page.x += origin.x; - page.y += origin.y; - - horizontal = (page.x > rect.left) && (page.x < rect.right); - vertical = (page.y > rect.top ) && (page.y < rect.bottom); - - dropped = horizontal && vertical; - } - - var dragRect = draggable.getRect(draggableElement); - - if (dropOverlap === 'center') { - var cx = dragRect.left + dragRect.width / 2, - cy = dragRect.top + dragRect.height / 2; - - dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; - } - - if (scope.isNumber(dropOverlap)) { - var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) - * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), - overlapRatio = overlapArea / (dragRect.width * dragRect.height); - - dropped = overlapRatio >= dropOverlap; - } - - if (this.options.dropChecker) { - dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); - } - - return dropped; - }, - - /*\ - * Interactable.dropChecker - [ method ] - * - * Gets or sets the function used to check if a dragged element is - * over this Interactable. - * - - checker (function) #optional The function that will be called when checking for a drop - = (Function | Interactable) The checker function or this Interactable - * - * The checker function takes the following arguments: - * - - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag - - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer - - dropped (boolean) The value from the default drop check - - dropzone (Interactable) The dropzone interactable - - dropElement (Element) The dropzone element - - draggable (Interactable) The Interactable being dragged - - draggableElement (Element) The actual element that's being dragged - * - > Usage: - | interact(target) - | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent - | event, // TouchEvent/PointerEvent/MouseEvent - | dropped, // result of the default checker - | dropzone, // dropzone Interactable - | dropElement, // dropzone elemnt - | draggable, // draggable Interactable - | draggableElement) {// draggable element - | - | return dropped && event.target.hasAttribute('allow-drop'); - | } - \*/ - dropChecker: function (checker) { - if (scope.isFunction(checker)) { - this.options.dropChecker = checker; - - return this; - } - if (checker === null) { - delete this.options.getRect; - - return this; - } - - return this.options.dropChecker; - }, - - /*\ - * Interactable.accept - [ method ] - * - * Deprecated. add an `accept` property to the options object passed to - * @Interactable.dropzone instead. - * - * Gets or sets the Element or CSS selector match that this - * Interactable accepts if it is a dropzone. - * - - newValue (Element | string | null) #optional - * If it is an Element, then only that element can be dropped into this dropzone. - * If it is a string, the element being dragged must match it as a selector. - * If it is null, the accept options is cleared - it accepts any element. - * - = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable - \*/ - accept: function (newValue) { - if (utils.isElement(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - // test if it is a valid CSS selector - if (scope.trySelector(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.drop.accept; - - return this; - } - - return this.options.drop.accept; - }, - - /*\ - * Interactable.resizable - [ method ] - * - * Gets or sets whether resize actions can be performed on the - * Interactable - * - = (boolean) Indicates if this can be the target of resize elements - | var isResizeable = interact('input[type=text]').resizable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) - = (object) This Interactable - | interact(element).resizable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | edges: { - | top : true, // Use pointer coords to check for resize. - | left : false, // Disable resizing from left edge. - | bottom: '.resize-s',// Resize if pointer target matches selector - | right : handleEl // Resize if pointer target is the given Element - | }, - | - | // a value of 'none' will limit the resize rect to a minimum of 0x0 - | // 'negate' will allow the rect to have negative width/height - | // 'reposition' will keep the width/height positive by swapping - | // the top and bottom edges and/or swapping the left and right edges - | invert: 'none' || 'negate' || 'reposition' - | - | // limit multiple resizes. - | // See the explanation in the @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - resizable: function (options) { - if (scope.isObject(options)) { - this.options.resize.enabled = options.enabled === false? false: true; - this.setPerAction('resize', options); - this.setOnEvents('resize', options); - - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.resize.axis = options.axis; - } - else if (options.axis === null) { - this.options.resize.axis = scope.defaultOptions.resize.axis; - } - - if (scope.isBool(options.square)) { - this.options.resize.square = options.square; - } - - return this; - } - if (scope.isBool(options)) { - this.options.resize.enabled = options; - - return this; - } - return this.options.resize; - }, - - /*\ - * Interactable.squareResize - [ method ] - * - * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead - * - * Gets or sets whether resizing is forced 1:1 aspect - * - = (boolean) Current setting - * - * or - * - - newValue (boolean) #optional - = (object) this Interactable - \*/ - squareResize: function (newValue) { - if (scope.isBool(newValue)) { - this.options.resize.square = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.resize.square; - - return this; - } - - return this.options.resize.square; - }, - - /*\ - * Interactable.gesturable - [ method ] - * - * Gets or sets whether multitouch gestures can be performed on the - * Interactable's element - * - = (boolean) Indicates if this can be the target of gesture events - | var isGestureable = interact(element).gesturable(); - * or - - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) - = (object) this Interactable - | interact(element).gesturable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | // limit multiple gestures. - | // See the explanation in @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); - \*/ - gesturable: function (options) { - if (scope.isObject(options)) { - this.options.gesture.enabled = options.enabled === false? false: true; - this.setPerAction('gesture', options); - this.setOnEvents('gesture', options); - - return this; - } - - if (scope.isBool(options)) { - this.options.gesture.enabled = options; - - return this; - } - - return this.options.gesture; - }, - - /*\ - * Interactable.autoScroll - [ method ] - ** - * Deprecated. Add an `autoscroll` property to the options object - * passed to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets whether dragging and resizing near the edges of the - * window/container trigger autoScroll for this Interactable - * - = (object) Object with autoScroll properties - * - * or - * - - options (object | boolean) #optional - * options can be: - * - an object with margin, distance and interval properties, - * - true or false to enable or disable autoScroll or - = (Interactable) this Interactable - \*/ - autoScroll: function (options) { - if (scope.isObject(options)) { - options = utils.extend({ actions: ['drag', 'resize']}, options); - } - else if (scope.isBool(options)) { - options = { actions: ['drag', 'resize'], enabled: options }; - } - - return this.setOptions('autoScroll', options); - }, - - /*\ - * Interactable.snap - [ method ] - ** - * Deprecated. Add a `snap` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how action coordinates are snapped. By - * default, snapping is relative to the pointer coordinates. You can - * change this by setting the - * [`elementOrigin`](https://github.com/taye/interact.js/pull/72). - ** - = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | interact(document.querySelector('#thing')).snap({ - | targets: [ - | // snap to this specific point - | { - | x: 100, - | y: 100, - | range: 25 - | }, - | // give this function the x and y page coords and snap to the object returned - | function (x, y) { - | return { - | x: x, - | y: (75 + 50 * Math.sin(x * 0.04)), - | range: 40 - | }; - | }, - | // create a function that snaps to a grid - | interact.createSnapGrid({ - | x: 50, - | y: 50, - | range: 10, // optional - | offset: { x: 5, y: 10 } // optional - | }) - | ], - | // do not snap during normal movement. - | // Instead, trigger only one snapped move event - | // immediately before the end event. - | endOnly: true, - | - | relativePoints: [ - | { x: 0, y: 0 }, // snap relative to the top left of the element - | { x: 1, y: 1 }, // and also to the bottom right - | ], - | - | // offset the snap target coordinates - | // can be an object with x/y or 'startCoords' - | offset: { x: 50, y: 50 } - | } - | }); - \*/ - snap: function (options) { - var ret = this.setOptions('snap', options); - - if (ret === this) { return this; } - - return ret.drag; - }, - - setOptions: function (option, options) { - var actions = options && scope.isArray(options.actions) - ? options.actions - : ['drag']; - - var i; - - if (scope.isObject(options) || scope.isBool(options)) { - for (i = 0; i < actions.length; i++) { - var action = /resize/.test(actions[i])? 'resize' : actions[i]; - - if (!scope.isObject(this.options[action])) { continue; } - - var thisOption = this.options[action][option]; - - if (scope.isObject(options)) { - utils.extend(thisOption, options); - thisOption.enabled = options.enabled === false? false: true; - - if (option === 'snap') { - if (thisOption.mode === 'grid') { - thisOption.targets = [ - interact.createSnapGrid(utils.extend({ - offset: thisOption.gridOffset || { x: 0, y: 0 } - }, thisOption.grid || {})) - ]; - } - else if (thisOption.mode === 'anchor') { - thisOption.targets = thisOption.anchors; - } - else if (thisOption.mode === 'path') { - thisOption.targets = thisOption.paths; - } - - if ('elementOrigin' in options) { - thisOption.relativePoints = [options.elementOrigin]; - } - } - } - else if (scope.isBool(options)) { - thisOption.enabled = options; - } - } - - return this; - } - - var ret = {}, - allActions = ['drag', 'resize', 'gesture']; - - for (i = 0; i < allActions.length; i++) { - if (option in scope.defaultOptions[allActions[i]]) { - ret[allActions[i]] = this.options[allActions[i]][option]; - } - } - - return ret; - }, - - - /*\ - * Interactable.inertia - [ method ] - ** - * Deprecated. Add an `inertia` property to the options object passed - * to @Interactable.draggable or @Interactable.resizable instead. - * - * Returns or sets if and how events continue to run after the pointer is released - ** - = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled - ** - * or - ** - - options (object | boolean | null) #optional - = (Interactable) this Interactable - > Usage - | // enable and use default settings - | interact(element).inertia(true); - | - | // enable and use custom settings - | interact(element).inertia({ - | // value greater than 0 - | // high values slow the object down more quickly - | resistance : 16, - | - | // the minimum launch speed (pixels per second) that results in inertia start - | minSpeed : 200, - | - | // inertia will stop when the object slows down to this speed - | endSpeed : 20, - | - | // boolean; should actions be resumed when the pointer goes down during inertia - | allowResume : true, - | - | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy - | zeroResumeDelta: false, - | - | // if snap/restrict are set to be endOnly and inertia is enabled, releasing - | // the pointer without triggering inertia will animate from the release - | // point to the snaped/restricted point in the given amount of time (ms) - | smoothEndDuration: 300, - | - | // an array of action types that can have inertia (no gesture) - | actions : ['drag', 'resize'] - | }); - | - | // reset custom settings and use all defaults - | interact(element).inertia(null); - \*/ - inertia: function (options) { - var ret = this.setOptions('inertia', options); - - if (ret === this) { return this; } - - return ret.drag; - }, - - getAction: function (pointer, event, interaction, element) { - var action = this.defaultActionChecker(pointer, interaction, element); - - if (this.options.actionChecker) { - return this.options.actionChecker(pointer, event, action, this, element, interaction); - } - - return action; - }, - - defaultActionChecker: defaultActionChecker, - - /*\ - * Interactable.actionChecker - [ method ] - * - * Gets or sets the function used to check action to be performed on - * pointerDown - * - - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. - = (Function | Interactable) The checker function or this Interactable - * - | interact('.resize-drag') - | .resizable(true) - | .draggable(true) - | .actionChecker(function (pointer, event, action, interactable, element, interaction) { - | - | if (interact.matchesSelector(event.target, '.drag-handle') { - | // force drag with handle target - | action.name = drag; - | } - | else { - | // resize from the top and right edges - | action.name = 'resize'; - | action.edges = { top: true, right: true }; - | } - | - | return action; - | }); - \*/ - actionChecker: function (checker) { - if (scope.isFunction(checker)) { - this.options.actionChecker = checker; - - return this; - } - - if (checker === null) { - delete this.options.actionChecker; - - return this; - } - - return this.options.actionChecker; - }, - - /*\ - * Interactable.getRect - [ method ] - * - * The default function to get an Interactables bounding rect. Can be - * overridden using @Interactable.rectChecker. - * - - element (Element) #optional The element to measure. - = (object) The object's bounding rectangle. - o { - o top : 0, - o left : 0, - o bottom: 0, - o right : 0, - o width : 0, - o height: 0 - o } - \*/ - getRect: function rectCheck (element) { - element = element || this._element; - - if (this.selector && !(utils.isElement(element))) { - element = this._context.querySelector(this.selector); - } - - return scope.getElementRect(element); - }, - - /*\ - * Interactable.rectChecker - [ method ] - * - * Returns or sets the function used to calculate the interactable's - * element's rectangle - * - - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect - = (function | object) The checker function or this Interactable - \*/ - rectChecker: function (checker) { - if (scope.isFunction(checker)) { - this.getRect = checker; - - return this; - } - - if (checker === null) { - delete this.options.getRect; - - return this; - } - - return this.getRect; - }, - - /*\ - * Interactable.styleCursor - [ method ] - * - * Returns or sets whether the action that would be performed when the - * mouse on the element are checked on `mousemove` so that the cursor - * may be styled appropriately - * - - newValue (boolean) #optional - = (boolean | Interactable) The current setting or this Interactable - \*/ - styleCursor: function (newValue) { - if (scope.isBool(newValue)) { - this.options.styleCursor = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.styleCursor; - - return this; - } - - return this.options.styleCursor; - }, - - /*\ - * Interactable.preventDefault - [ method ] - * - * Returns or sets whether to prevent the browser's default behaviour - * in response to pointer events. Can be set to: - * - `'always'` to always prevent - * - `'never'` to never prevent - * - `'auto'` to let interact.js try to determine what would be best - * - - newValue (string) #optional `true`, `false` or `'auto'` - = (string | Interactable) The current setting or this Interactable - \*/ - preventDefault: function (newValue) { - if (/^(always|never|auto)$/.test(newValue)) { - this.options.preventDefault = newValue; - return this; - } - - if (scope.isBool(newValue)) { - this.options.preventDefault = newValue? 'always' : 'never'; - return this; - } - - return this.options.preventDefault; - }, - - /*\ - * Interactable.origin - [ method ] - * - * Gets or sets the origin of the Interactable's element. The x and y - * of the origin will be subtracted from action event coordinates. - * - - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector - * OR - - origin (Element) #optional An HTML or SVG Element whose rect will be used - ** - = (object) The current origin or this Interactable - \*/ - origin: function (newValue) { - if (scope.trySelector(newValue)) { - this.options.origin = newValue; - return this; - } - else if (scope.isObject(newValue)) { - this.options.origin = newValue; - return this; - } - - return this.options.origin; - }, - - /*\ - * Interactable.deltaSource - [ method ] - * - * Returns or sets the mouse coordinate types used to calculate the - * movement of the pointer. - * - - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work - = (string | object) The current deltaSource or this Interactable - \*/ - deltaSource: function (newValue) { - if (newValue === 'page' || newValue === 'client') { - this.options.deltaSource = newValue; - - return this; - } - - return this.options.deltaSource; - }, - - /*\ - * Interactable.restrict - [ method ] - ** - * Deprecated. Add a `restrict` property to the options object passed to - * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead. - * - * Returns or sets the rectangles within which actions on this - * interactable (after snap calculations) are restricted. By default, - * restricting is relative to the pointer coordinates. You can change - * this by setting the - * [`elementRect`](https://github.com/taye/interact.js/pull/72). - ** - - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self' - = (object) The current restrictions object or this Interactable - ** - | interact(element).restrict({ - | // the rect will be `interact.getElementRect(element.parentNode)` - | drag: element.parentNode, - | - | // x and y are relative to the the interactable's origin - | resize: { x: 100, y: 100, width: 200, height: 200 } - | }) - | - | interact('.draggable').restrict({ - | // the rect will be the selected element's parent - | drag: 'parent', - | - | // do not restrict during normal movement. - | // Instead, trigger only one restricted move event - | // immediately before the end event. - | endOnly: true, - | - | // https://github.com/taye/interact.js/pull/72#issue-41813493 - | elementRect: { top: 0, left: 0, bottom: 1, right: 1 } - | }); - \*/ - restrict: function (options) { - if (!scope.isObject(options)) { - return this.setOptions('restrict', options); - } - - var actions = ['drag', 'resize', 'gesture'], - ret; - - for (var i = 0; i < actions.length; i++) { - var action = actions[i]; - - if (action in options) { - var perAction = utils.extend({ - actions: [action], - restriction: options[action] - }, options); - - ret = this.setOptions('restrict', perAction); - } - } - - return ret; - }, - - /*\ - * Interactable.context - [ method ] - * - * Gets the selector context Node of the Interactable. The default is `window.document`. - * - = (Node) The context Node of this Interactable - ** - \*/ - context: function () { - return this._context; - }, - - _context: scope.document, - - /*\ - * Interactable.ignoreFrom - [ method ] - * - * If the target of the `mousedown`, `pointerdown` or `touchstart` - * event or any of it's parents match the given CSS selector or - * Element, no drag/resize/gesture is started. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements - = (string | Element | object) The current ignoreFrom value or this Interactable - ** - | interact(element, { ignoreFrom: document.getElementById('no-action') }); - | // or - | interact(element).ignoreFrom('input, textarea, a'); - \*/ - ignoreFrom: function (newValue) { - if (scope.trySelector(newValue)) { // CSS selector to match event.target - this.options.ignoreFrom = newValue; - return this; - } - - if (utils.isElement(newValue)) { // specific element - this.options.ignoreFrom = newValue; - return this; - } - - return this.options.ignoreFrom; - }, - - /*\ - * Interactable.allowFrom - [ method ] - * - * A drag/resize/gesture is started only If the target of the - * `mousedown`, `pointerdown` or `touchstart` event or any of it's - * parents match the given CSS selector or Element. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element - = (string | Element | object) The current allowFrom value or this Interactable - ** - | interact(element, { allowFrom: document.getElementById('drag-handle') }); - | // or - | interact(element).allowFrom('.handle'); - \*/ - allowFrom: function (newValue) { - if (scope.trySelector(newValue)) { // CSS selector to match event.target - this.options.allowFrom = newValue; - return this; - } - - if (utils.isElement(newValue)) { // specific element - this.options.allowFrom = newValue; - return this; - } - - return this.options.allowFrom; - }, - - /*\ - * Interactable.element - [ method ] - * - * If this is not a selector Interactable, it returns the element this - * interactable represents - * - = (Element) HTML / SVG Element - \*/ - element: function () { - return this._element; - }, - - /*\ - * Interactable.fire - [ method ] - * - * Calls listeners for the given InteractEvent type bound globally - * and directly to this Interactable - * - - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable - = (Interactable) this Interactable - \*/ - fire: function (iEvent) { - if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) { - return this; - } - - var listeners, - i, - len, - onEvent = 'on' + iEvent.type, - funcName = ''; - - // Interactable#on() listeners - if (iEvent.type in this._iEvents) { - listeners = this._iEvents[iEvent.type]; - - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } - - // interactable.onevent listener - if (scope.isFunction(this[onEvent])) { - funcName = this[onEvent].name; - this[onEvent](iEvent); - } - - // interact.on() listeners - if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { - - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } - - return this; - }, - - /*\ - * Interactable.on - [ method ] - * - * Binds a listener for an InteractEvent or DOM event. - * - - eventType (string | array | object) The types of events to listen for - - listener (function) The function to be called on the given event(s) - - useCapture (boolean) #optional useCapture flag for addEventListener - = (object) This Interactable - \*/ - on: function (eventType, listener, useCapture) { - var i; - - if (scope.isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } - - if (scope.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.on(eventType[i], listener, useCapture); - } - - return this; - } - - if (scope.isObject(eventType)) { - for (var prop in eventType) { - this.on(prop, eventType[prop], listener); - } - - return this; - } - - if (eventType === 'wheel') { - eventType = scope.wheelEvent; - } - - // convert to boolean - useCapture = useCapture? true: false; - - if (scope.contains(scope.eventTypes, eventType)) { - // if this type of event was never bound to this Interactable - if (!(eventType in this._iEvents)) { - this._iEvents[eventType] = [listener]; - } - else { - this._iEvents[eventType].push(listener); - } - } - // delegated event for selector - else if (this.selector) { - if (!scope.delegatedEvents[eventType]) { - scope.delegatedEvents[eventType] = { - selectors: [], - contexts : [], - listeners: [] - }; - - // add delegate listener functions - for (i = 0; i < scope.documents.length; i++) { - events.add(scope.documents[i], eventType, delegateListener); - events.add(scope.documents[i], eventType, delegateUseCapture, true); - } - } - - var delegated = scope.delegatedEvents[eventType], - index; - - for (index = delegated.selectors.length - 1; index >= 0; index--) { - if (delegated.selectors[index] === this.selector - && delegated.contexts[index] === this._context) { - break; - } - } - - if (index === -1) { - index = delegated.selectors.length; - - delegated.selectors.push(this.selector); - delegated.contexts .push(this._context); - delegated.listeners.push([]); - } - - // keep listener and useCapture flag - delegated.listeners[index].push([listener, useCapture]); - } - else { - events.add(this._element, eventType, listener, useCapture); - } - - return this; - }, - - /*\ - * Interactable.off - [ method ] - * - * Removes an InteractEvent or DOM event listener - * - - eventType (string | array | object) The types of events that were listened for - - listener (function) The listener function to be removed - - useCapture (boolean) #optional useCapture flag for removeEventListener - = (object) This Interactable - \*/ - off: function (eventType, listener, useCapture) { - var i; - - if (scope.isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } - - if (scope.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.off(eventType[i], listener, useCapture); - } - - return this; - } - - if (scope.isObject(eventType)) { - for (var prop in eventType) { - this.off(prop, eventType[prop], listener); - } - - return this; - } - - var eventList, - index = -1; - - // convert to boolean - useCapture = useCapture? true: false; - - if (eventType === 'wheel') { - eventType = scope.wheelEvent; - } - - // if it is an action event type - if (scope.contains(scope.eventTypes, eventType)) { - eventList = this._iEvents[eventType]; - - if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) { - this._iEvents[eventType].splice(index, 1); - } - } - // delegated event - else if (this.selector) { - var delegated = scope.delegatedEvents[eventType], - matchFound = false; - - if (!delegated) { return this; } - - // count from last index of delegated to 0 - for (index = delegated.selectors.length - 1; index >= 0; index--) { - // look for matching selector and context Node - if (delegated.selectors[index] === this.selector - && delegated.contexts[index] === this._context) { - - var listeners = delegated.listeners[index]; - - // each item of the listeners array is an array: [function, useCaptureFlag] - for (i = listeners.length - 1; i >= 0; i--) { - var fn = listeners[i][0], - useCap = listeners[i][1]; - - // check if the listener functions and useCapture flags match - if (fn === listener && useCap === useCapture) { - // remove the listener from the array of listeners - listeners.splice(i, 1); - - // if all listeners for this interactable have been removed - // remove the interactable from the delegated arrays - if (!listeners.length) { - delegated.selectors.splice(index, 1); - delegated.contexts .splice(index, 1); - delegated.listeners.splice(index, 1); - - // remove delegate function from context - events.remove(this._context, eventType, delegateListener); - events.remove(this._context, eventType, delegateUseCapture, true); - - // remove the arrays if they are empty - if (!delegated.selectors.length) { - scope.delegatedEvents[eventType] = null; - } - } - - // only remove one listener - matchFound = true; - break; - } - } - - if (matchFound) { break; } - } - } - } - // remove listener from this Interatable's element - else { - events.remove(this._element, eventType, listener, useCapture); - } - - return this; - }, - - /*\ - * Interactable.set - [ method ] - * - * Reset the options of this Interactable - - options (object) The new settings to apply - = (object) This Interactablw - \*/ - set: function (options) { - if (!scope.isObject(options)) { - options = {}; - } - - this.options = utils.extend({}, scope.defaultOptions.base); - - var i, - actions = ['drag', 'drop', 'resize', 'gesture'], - methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], - perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {}); - - for (i = 0; i < actions.length; i++) { - var action = actions[i]; - - this.options[action] = utils.extend({}, scope.defaultOptions[action]); - - this.setPerAction(action, perActions); - - this[methods[i]](options[action]); - } - - var settings = [ - 'accept', 'actionChecker', 'allowFrom', 'deltaSource', - 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', - 'rectChecker' - ]; - - for (i = 0, len = settings.length; i < len; i++) { - var setting = settings[i]; - - this.options[setting] = scope.defaultOptions.base[setting]; - - if (setting in options) { - this[setting](options[setting]); - } - } - - return this; - }, - - /*\ - * Interactable.unset - [ method ] - * - * Remove this interactable from the list of interactables and remove - * it's drag, drop, resize and gesture capabilities - * - = (object) @interact - \*/ - unset: function () { - events.remove(this._element, 'all'); - - if (!scope.isString(this.selector)) { - events.remove(this, 'all'); - if (this.options.styleCursor) { - this._element.style.cursor = ''; - } - } - else { - // remove delegated events - for (var type in scope.delegatedEvents) { - var delegated = scope.delegatedEvents[type]; - - for (var i = 0; i < delegated.selectors.length; i++) { - if (delegated.selectors[i] === this.selector - && delegated.contexts[i] === this._context) { - - delegated.selectors.splice(i, 1); - delegated.contexts .splice(i, 1); - delegated.listeners.splice(i, 1); - - // remove the arrays if they are empty - if (!delegated.selectors.length) { - scope.delegatedEvents[type] = null; - } - } - - events.remove(this._context, type, delegateListener); - events.remove(this._context, type, delegateUseCapture, true); - - break; - } - } - } - - this.dropzone(false); - - scope.interactables.splice(scope.indexOf(scope.interactables, this), 1); - - return interact; - } - }; - - Interactable.prototype.snap = utils.warnOnce(Interactable.prototype.snap, - 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping'); - Interactable.prototype.restrict = utils.warnOnce(Interactable.prototype.restrict, - 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction'); - Interactable.prototype.inertia = utils.warnOnce(Interactable.prototype.inertia, - 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia'); - Interactable.prototype.autoScroll = utils.warnOnce(Interactable.prototype.autoScroll, - 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll'); - Interactable.prototype.squareResize = utils.warnOnce(Interactable.prototype.squareResize, - 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square'); - - /*\ - * interact.isSet - [ method ] - * - * Check if an element has been set - - element (Element) The Element being searched for - = (boolean) Indicates if the element or CSS selector was previously passed to interact - \*/ - interact.isSet = function(element, options) { - return scope.interactables.indexOfElement(element, options && options.context) !== -1; - }; - - /*\ - * interact.on - [ method ] - * - * Adds a global listener for an InteractEvent or adds a DOM event to - * `document` - * - - type (string | array | object) The types of events to listen for - - listener (function) The function to be called on the given event(s) - - useCapture (boolean) #optional useCapture flag for addEventListener - = (object) interact - \*/ - interact.on = function (type, listener, useCapture) { - if (scope.isString(type) && type.search(' ') !== -1) { - type = type.trim().split(/ +/); - } - - if (scope.isArray(type)) { - for (var i = 0; i < type.length; i++) { - interact.on(type[i], listener, useCapture); - } - - return interact; - } - - if (scope.isObject(type)) { - for (var prop in type) { - interact.on(prop, type[prop], listener); - } - - return interact; - } - - // if it is an InteractEvent type, add listener to globalEvents - if (scope.contains(scope.eventTypes, type)) { - // if this type of event was never bound - if (!scope.globalEvents[type]) { - scope.globalEvents[type] = [listener]; - } - else { - scope.globalEvents[type].push(listener); - } - } - // If non InteractEvent type, addEventListener to document - else { - events.add(scope.document, type, listener, useCapture); - } - - return interact; - }; - - /*\ - * interact.off - [ method ] - * - * Removes a global InteractEvent listener or DOM event from `document` - * - - type (string | array | object) The types of events that were listened for - - listener (function) The listener function to be removed - - useCapture (boolean) #optional useCapture flag for removeEventListener - = (object) interact - \*/ - interact.off = function (type, listener, useCapture) { - if (scope.isString(type) && type.search(' ') !== -1) { - type = type.trim().split(/ +/); - } - - if (scope.isArray(type)) { - for (var i = 0; i < type.length; i++) { - interact.off(type[i], listener, useCapture); - } - - return interact; - } - - if (scope.isObject(type)) { - for (var prop in type) { - interact.off(prop, type[prop], listener); - } - - return interact; - } - - if (!scope.contains(scope.eventTypes, type)) { - events.remove(scope.document, type, listener, useCapture); - } - else { - var index; - - if (type in scope.globalEvents - && (index = scope.indexOf(scope.globalEvents[type], listener)) !== -1) { - scope.globalEvents[type].splice(index, 1); - } - } - - return interact; - }; - - /*\ - * interact.enableDragging - [ method ] - * - * Deprecated. - * - * Returns or sets whether dragging is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ - interact.enableDragging = utils.warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - scope.actionIsEnabled.drag = newValue; - - return interact; - } - return scope.actionIsEnabled.drag; - }, 'interact.enableDragging is deprecated and will soon be removed.'); - - /*\ - * interact.enableResizing - [ method ] - * - * Deprecated. - * - * Returns or sets whether resizing is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ - interact.enableResizing = utils.warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - scope.actionIsEnabled.resize = newValue; - - return interact; - } - return scope.actionIsEnabled.resize; - }, 'interact.enableResizing is deprecated and will soon be removed.'); - - /*\ - * interact.enableGesturing - [ method ] - * - * Deprecated. - * - * Returns or sets whether gesturing is enabled for any Interactables - * - - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables - = (boolean | object) The current setting or interact - \*/ - interact.enableGesturing = utils.warnOnce(function (newValue) { - if (newValue !== null && newValue !== undefined) { - scope.actionIsEnabled.gesture = newValue; - - return interact; - } - return scope.actionIsEnabled.gesture; - }, 'interact.enableGesturing is deprecated and will soon be removed.'); - - interact.eventTypes = scope.eventTypes; - - /*\ - * interact.debug - [ method ] - * - * Returns debugging data - = (object) An object with properties that outline the current state and expose internal functions and variables - \*/ - interact.debug = function () { - var interaction = scope.interactions[0] || new Interaction(); - - return { - interactions : scope.interactions, - target : interaction.target, - dragging : interaction.dragging, - resizing : interaction.resizing, - gesturing : interaction.gesturing, - prepared : interaction.prepared, - matches : interaction.matches, - matchElements : interaction.matchElements, - - prevCoords : interaction.prevCoords, - startCoords : interaction.startCoords, - - pointerIds : interaction.pointerIds, - pointers : interaction.pointers, - addPointer : scope.listeners.addPointer, - removePointer : scope.listeners.removePointer, - recordPointer : scope.listeners.recordPointer, - - snap : interaction.snapStatus, - restrict : interaction.restrictStatus, - inertia : interaction.inertiaStatus, - - downTime : interaction.downTimes[0], - downEvent : interaction.downEvent, - downPointer : interaction.downPointer, - prevEvent : interaction.prevEvent, - - Interactable : Interactable, - interactables : scope.interactables, - pointerIsDown : interaction.pointerIsDown, - defaultOptions : scope.defaultOptions, - defaultActionChecker : defaultActionChecker, - - actionCursors : scope.actionCursors, - dragMove : scope.listeners.dragMove, - resizeMove : scope.listeners.resizeMove, - gestureMove : scope.listeners.gestureMove, - pointerUp : scope.listeners.pointerUp, - pointerDown : scope.listeners.pointerDown, - pointerMove : scope.listeners.pointerMove, - pointerHover : scope.listeners.pointerHover, - - eventTypes : scope.eventTypes, - - events : events, - globalEvents : scope.globalEvents, - delegatedEvents : scope.delegatedEvents - }; - }; - - // expose the functions used to calculate multi-touch properties - interact.getTouchAverage = utils.touchAverage; - interact.getTouchBBox = utils.touchBBox; - interact.getTouchDistance = utils.touchDistance; - interact.getTouchAngle = utils.touchAngle; - - interact.getElementRect = scope.getElementRect; - interact.matchesSelector = scope.matchesSelector; - interact.closest = scope.closest; - - /*\ - * interact.margin - [ method ] - * - * Returns or sets the margin for autocheck resizing used in - * @Interactable.getAction. That is the distance from the bottom and right - * edges of an element clicking in which will start resizing - * - - newValue (number) #optional - = (number | interact) The current margin value or interact - \*/ - interact.margin = function (newvalue) { - if (scope.isNumber(newvalue)) { - scope.margin = newvalue; - - return interact; - } - return scope.margin; - }; - - /*\ - * interact.supportsTouch - [ method ] - * - = (boolean) Whether or not the browser supports touch input - \*/ - interact.supportsTouch = function () { - return browser.supportsTouch; - }; - - /*\ - * interact.supportsPointerEvent - [ method ] - * - = (boolean) Whether or not the browser supports PointerEvents - \*/ - interact.supportsPointerEvent = function () { - return browser.supportsPointerEvent; - }; - - /*\ - * interact.stop - [ method ] - * - * Cancels all interactions (end events are not fired) - * - - event (Event) An event on which to call preventDefault() - = (object) interact - \*/ - interact.stop = function (event) { - for (var i = scope.interactions.length - 1; i > 0; i--) { - scope.interactions[i].stop(event); - } - - return interact; - }; - - /*\ - * interact.dynamicDrop - [ method ] - * - * Returns or sets whether the dimensions of dropzone elements are - * calculated on every dragmove or only on dragstart for the default - * dropChecker - * - - newValue (boolean) #optional True to check on each move. False to check only before start - = (boolean | interact) The current setting or interact - \*/ - interact.dynamicDrop = function (newValue) { - if (scope.isBool(newValue)) { - //if (dragging && dynamicDrop !== newValue && !newValue) { - //calcRects(dropzones); - //} - - scope.dynamicDrop = newValue; - - return interact; - } - return scope.dynamicDrop; - }; - - /*\ - * interact.pointerMoveTolerance - [ method ] - * Returns or sets the distance the pointer must be moved before an action - * sequence occurs. This also affects tolerance for tap events. - * - - newValue (number) #optional The movement from the start position must be greater than this value - = (number | Interactable) The current setting or interact - \*/ - interact.pointerMoveTolerance = function (newValue) { - if (scope.isNumber(newValue)) { - scope.pointerMoveTolerance = newValue; - - return this; - } - - return scope.pointerMoveTolerance; - }; - - /*\ - * interact.maxInteractions - [ method ] - ** - * Returns or sets the maximum number of concurrent interactions allowed. - * By default only 1 interaction is allowed at a time (for backwards - * compatibility). To allow multiple interactions on the same Interactables - * and elements, you need to enable it in the draggable, resizable and - * gesturable `'max'` and `'maxPerElement'` options. - ** - - newValue (number) #optional Any number. newValue <= 0 means no interactions. - \*/ - interact.maxInteractions = function (newValue) { - if (scope.isNumber(newValue)) { - scope.maxInteractions = newValue; - - return this; - } - - return scope.maxInteractions; - }; - - interact.createSnapGrid = function (grid) { - return function (x, y) { - var offsetX = 0, - offsetY = 0; - - if (scope.isObject(grid.offset)) { - offsetX = grid.offset.x; - offsetY = grid.offset.y; - } - - var gridx = Math.round((x - offsetX) / grid.x), - gridy = Math.round((y - offsetY) / grid.y), - - newX = gridx * grid.x + offsetX, - newY = gridy * grid.y + offsetY; - - return { - x: newX, - y: newY, - range: grid.range - }; - }; - }; - - function endAllInteractions (event) { - for (var i = 0; i < scope.interactions.length; i++) { - scope.interactions[i].pointerEnd(event, event); - } - } - - function listenToDocument (doc) { - if (scope.contains(scope.documents, doc)) { return; } - - var win = doc.defaultView || doc.parentWindow; - - // add delegate event listener - for (var eventType in scope.delegatedEvents) { - events.add(doc, eventType, delegateListener); - events.add(doc, eventType, delegateUseCapture, true); - } - - if (scope.PointerEvent) { - if (scope.PointerEvent === win.MSPointerEvent) { - scope.pEventTypes = { - up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', - out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' }; - } - else { - scope.pEventTypes = { - up: 'pointerup', down: 'pointerdown', over: 'pointerover', - out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }; - } - - events.add(doc, scope.pEventTypes.down , scope.listeners.selectorDown ); - events.add(doc, scope.pEventTypes.move , scope.listeners.pointerMove ); - events.add(doc, scope.pEventTypes.over , scope.listeners.pointerOver ); - events.add(doc, scope.pEventTypes.out , scope.listeners.pointerOut ); - events.add(doc, scope.pEventTypes.up , scope.listeners.pointerUp ); - events.add(doc, scope.pEventTypes.cancel, scope.listeners.pointerCancel); - - // autoscroll - events.add(doc, scope.pEventTypes.move, scope.listeners.autoScrollMove); - } - else { - events.add(doc, 'mousedown', scope.listeners.selectorDown); - events.add(doc, 'mousemove', scope.listeners.pointerMove ); - events.add(doc, 'mouseup' , scope.listeners.pointerUp ); - events.add(doc, 'mouseover', scope.listeners.pointerOver ); - events.add(doc, 'mouseout' , scope.listeners.pointerOut ); - - events.add(doc, 'touchstart' , scope.listeners.selectorDown ); - events.add(doc, 'touchmove' , scope.listeners.pointerMove ); - events.add(doc, 'touchend' , scope.listeners.pointerUp ); - events.add(doc, 'touchcancel', scope.listeners.pointerCancel); - - // autoscroll - events.add(doc, 'mousemove', scope.listeners.autoScrollMove); - events.add(doc, 'touchmove', scope.listeners.autoScrollMove); - } - - events.add(win, 'blur', endAllInteractions); - - try { - if (win.frameElement) { - var parentDoc = win.frameElement.ownerDocument, - parentWindow = parentDoc.defaultView; - - events.add(parentDoc , 'mouseup' , scope.listeners.pointerEnd); - events.add(parentDoc , 'touchend' , scope.listeners.pointerEnd); - events.add(parentDoc , 'touchcancel' , scope.listeners.pointerEnd); - events.add(parentDoc , 'pointerup' , scope.listeners.pointerEnd); - events.add(parentDoc , 'MSPointerUp' , scope.listeners.pointerEnd); - events.add(parentWindow, 'blur' , endAllInteractions ); - } - } - catch (error) { - interact.windowParentError = error; - } - - if (events.useAttachEvent) { - // For IE's lack of Event#preventDefault - events.add(doc, 'selectstart', function (event) { - var interaction = scope.interactions[0]; - - if (interaction.currentAction()) { - interaction.checkAndPreventDefault(event); - } - }); - - // For IE's bad dblclick event sequence - events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick')); - } - - scope.documents.push(doc); - } - - listenToDocument(scope.document); - - scope.interact = interact; - scope.Interactable = Interactable; - scope.Interaction = Interaction; - scope.InteractEvent = InteractEvent; - - module.exports = interact; - -},{"./InteractEvent":2,"./Interaction":3,"./autoScroll":4,"./defaultOptions":5,"./scope":6,"./utils":13,"./utils/events":10,"./utils/window":18}],2:[function(require,module,exports){ -'use strict'; - -var scope = require('./scope'); -var utils = require('./utils'); - -function InteractEvent (interaction, event, action, phase, element, related) { - var client, - page, - target = interaction.target, - snapStatus = interaction.snapStatus, - restrictStatus = interaction.restrictStatus, - pointers = interaction.pointers, - deltaSource = (target && target.options || scope.defaultOptions).deltaSource, - sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - options = target? target.options: scope.defaultOptions, - origin = scope.getOriginXY(target, element), - starting = phase === 'start', - ending = phase === 'end', - coords = starting? interaction.startCoords : interaction.curCoords; - - element = element || interaction.element; - - page = utils.extend({}, coords.page); - client = utils.extend({}, coords.client); - - page.x -= origin.x; - page.y -= origin.y; - - client.x -= origin.x; - client.y -= origin.y; - - var relativePoints = options[action].snap && options[action].snap.relativePoints ; - - if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) { - this.snap = { - range : snapStatus.range, - locked : snapStatus.locked, - x : snapStatus.snappedX, - y : snapStatus.snappedY, - realX : snapStatus.realX, - realY : snapStatus.realY, - dx : snapStatus.dx, - dy : snapStatus.dy - }; - - if (snapStatus.locked) { - page.x += snapStatus.dx; - page.y += snapStatus.dy; - client.x += snapStatus.dx; - client.y += snapStatus.dy; - } - } - - if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) { - page.x += restrictStatus.dx; - page.y += restrictStatus.dy; - client.x += restrictStatus.dx; - client.y += restrictStatus.dy; - - this.restrict = { - dx: restrictStatus.dx, - dy: restrictStatus.dy - }; - } - - this.pageX = page.x; - this.pageY = page.y; - this.clientX = client.x; - this.clientY = client.y; - - this.x0 = interaction.startCoords.page.x - origin.x; - this.y0 = interaction.startCoords.page.y - origin.y; - this.clientX0 = interaction.startCoords.client.x - origin.x; - this.clientY0 = interaction.startCoords.client.y - origin.y; - this.ctrlKey = event.ctrlKey; - this.altKey = event.altKey; - this.shiftKey = event.shiftKey; - this.metaKey = event.metaKey; - this.button = event.button; - this.target = element; - this.t0 = interaction.downTimes[0]; - this.type = action + (phase || ''); - - this.interaction = interaction; - this.interactable = target; - - var inertiaStatus = interaction.inertiaStatus; - - if (inertiaStatus.active) { - this.detail = 'inertia'; - } - - if (related) { - this.relatedTarget = related; - } - - // end event dx, dy is difference between start and end points - if (ending) { - if (deltaSource === 'client') { - this.dx = client.x - interaction.startCoords.client.x; - this.dy = client.y - interaction.startCoords.client.y; - } - else { - this.dx = page.x - interaction.startCoords.page.x; - this.dy = page.y - interaction.startCoords.page.y; - } - } - else if (starting) { - this.dx = 0; - this.dy = 0; - } - // copy properties from previousmove if starting inertia - else if (phase === 'inertiastart') { - this.dx = interaction.prevEvent.dx; - this.dy = interaction.prevEvent.dy; - } - else { - if (deltaSource === 'client') { - this.dx = client.x - interaction.prevEvent.clientX; - this.dy = client.y - interaction.prevEvent.clientY; - } - else { - this.dx = page.x - interaction.prevEvent.pageX; - this.dy = page.y - interaction.prevEvent.pageY; - } - } - if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' - && !inertiaStatus.active - && options[action].inertia && options[action].inertia.zeroResumeDelta) { - - inertiaStatus.resumeDx += this.dx; - inertiaStatus.resumeDy += this.dy; - - this.dx = this.dy = 0; - } - - if (action === 'resize' && interaction.resizeAxes) { - if (options.resize.square) { - if (interaction.resizeAxes === 'y') { - this.dx = this.dy; - } - else { - this.dy = this.dx; - } - this.axes = 'xy'; - } - else { - this.axes = interaction.resizeAxes; - - if (interaction.resizeAxes === 'x') { - this.dy = 0; - } - else if (interaction.resizeAxes === 'y') { - this.dx = 0; - } - } - } - else if (action === 'gesture') { - this.touches = [pointers[0], pointers[1]]; - - if (starting) { - this.distance = utils.touchDistance(pointers, deltaSource); - this.box = utils.touchBBox(pointers); - this.scale = 1; - this.ds = 0; - this.angle = utils.touchAngle(pointers, undefined, deltaSource); - this.da = 0; - } - else if (ending || event instanceof InteractEvent) { - this.distance = interaction.prevEvent.distance; - this.box = interaction.prevEvent.box; - this.scale = interaction.prevEvent.scale; - this.ds = this.scale - 1; - this.angle = interaction.prevEvent.angle; - this.da = this.angle - interaction.gesture.startAngle; - } - else { - this.distance = utils.touchDistance(pointers, deltaSource); - this.box = utils.touchBBox(pointers); - this.scale = this.distance / interaction.gesture.startDistance; - this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); - - this.ds = this.scale - interaction.gesture.prevScale; - this.da = this.angle - interaction.gesture.prevAngle; - } - } - - if (starting) { - this.timeStamp = interaction.downTimes[0]; - this.dt = 0; - this.duration = 0; - this.speed = 0; - this.velocityX = 0; - this.velocityY = 0; - } - else if (phase === 'inertiastart') { - this.timeStamp = interaction.prevEvent.timeStamp; - this.dt = interaction.prevEvent.dt; - this.duration = interaction.prevEvent.duration; - this.speed = interaction.prevEvent.speed; - this.velocityX = interaction.prevEvent.velocityX; - this.velocityY = interaction.prevEvent.velocityY; - } - else { - this.timeStamp = new Date().getTime(); - this.dt = this.timeStamp - interaction.prevEvent.timeStamp; - this.duration = this.timeStamp - interaction.downTimes[0]; - - if (event instanceof InteractEvent) { - var dx = this[sourceX] - interaction.prevEvent[sourceX], - dy = this[sourceY] - interaction.prevEvent[sourceY], - dt = this.dt / 1000; - - this.speed = utils.hypot(dx, dy) / dt; - this.velocityX = dx / dt; - this.velocityY = dy / dt; - } - // if normal move or end event, use previous user event coords - else { - // speed and velocity in pixels per second - this.speed = interaction.pointerDelta[deltaSource].speed; - this.velocityX = interaction.pointerDelta[deltaSource].vx; - this.velocityY = interaction.pointerDelta[deltaSource].vy; - } - } - - if ((ending || phase === 'inertiastart') - && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) { - - var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI, - overlap = 22.5; - - if (angle < 0) { - angle += 360; - } - - var left = 135 - overlap <= angle && angle < 225 + overlap, - up = 225 - overlap <= angle && angle < 315 + overlap, - - right = !left && (315 - overlap <= angle || angle < 45 + overlap), - down = !up && 45 - overlap <= angle && angle < 135 + overlap; - - this.swipe = { - up : up, - down : down, - left : left, - right: right, - angle: angle, - speed: interaction.prevEvent.speed, - velocity: { - x: interaction.prevEvent.velocityX, - y: interaction.prevEvent.velocityY - } - }; - } -} - -InteractEvent.prototype = { - preventDefault: utils.blank, - stopImmediatePropagation: function () { - this.immediatePropagationStopped = this.propagationStopped = true; - }, - stopPropagation: function () { - this.propagationStopped = true; - } -}; - -module.exports = InteractEvent; - -},{"./scope":6,"./utils":13}],3:[function(require,module,exports){ -'use strict'; - -var scope = require('./scope'); -var utils = require('./utils'); -var animationFrame = utils.raf; -var InteractEvent = require('./InteractEvent'); -var events = require('./utils/events'); -var browser = require('./utils/browser'); - -function Interaction () { - this.target = null; // current interactable being interacted with - this.element = null; // the target element of the interactable - this.dropTarget = null; // the dropzone a drag target might be dropped into - this.dropElement = null; // the element at the time of checking - this.prevDropTarget = null; // the dropzone that was recently dragged away from - this.prevDropElement = null; // the element at the time of checking - - this.prepared = { // action that's ready to be fired on next move event - name : null, - axis : null, - edges: null - }; - - this.matches = []; // all selectors that are matched by target element - this.matchElements = []; // corresponding elements - - this.inertiaStatus = { - active : false, - smoothEnd : false, - - startEvent: null, - upCoords: {}, - - xe: 0, ye: 0, - sx: 0, sy: 0, - - t0: 0, - vx0: 0, vys: 0, - duration: 0, - - resumeDx: 0, - resumeDy: 0, - - lambda_v0: 0, - one_ve_v0: 0, - i : null - }; - - if (scope.isFunction(Function.prototype.bind)) { - this.boundInertiaFrame = this.inertiaFrame.bind(this); - this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); - } - else { - var that = this; - - this.boundInertiaFrame = function () { return that.inertiaFrame(); }; - this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); }; - } - - this.activeDrops = { - dropzones: [], // the dropzones that are mentioned below - elements : [], // elements of dropzones that accept the target draggable - rects : [] // the rects of the elements mentioned above - }; - - // keep track of added pointers - this.pointers = []; - this.pointerIds = []; - this.downTargets = []; - this.downTimes = []; - this.holdTimers = []; - - // Previous native pointer move event coordinates - this.prevCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - // current native pointer move event coordinates - this.curCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - - // Starting InteractEvent pointer coordinates - this.startCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - - // Change in coordinates and time of the pointer - this.pointerDelta = { - page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - timeStamp: 0 - }; - - this.downEvent = null; // pointerdown/mousedown/touchstart event - this.downPointer = {}; - - this._eventTarget = null; - this._curEventTarget = null; - - this.prevEvent = null; // previous action event - this.tapTime = 0; // time of the most recent tap event - this.prevTap = null; - - this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.snapOffsets = []; - - this.gesture = { - start: { x: 0, y: 0 }, - - startDistance: 0, // distance between two touches of touchStart - prevDistance : 0, - distance : 0, - - scale: 1, // gesture.distance / gesture.startDistance - - startAngle: 0, // angle of line joining two touches - prevAngle : 0 // angle of the previous gesture event - }; - - this.snapStatus = { - x : 0, y : 0, - dx : 0, dy : 0, - realX : 0, realY : 0, - snappedX: 0, snappedY: 0, - targets : [], - locked : false, - changed : false - }; - - this.restrictStatus = { - dx : 0, dy : 0, - restrictedX: 0, restrictedY: 0, - snap : null, - restricted : false, - changed : false - }; - - this.restrictStatus.snap = this.snapStatus; - - this.pointerIsDown = false; - this.pointerWasMoved = false; - this.gesturing = false; - this.dragging = false; - this.resizing = false; - this.resizeAxes = 'xy'; - - this.mouse = false; - - scope.interactions.push(this); -} - -// Check if action is enabled globally and the current target supports it -// If so, return the validated action. Otherwise, return null -function validateAction (action, interactable) { - if (!scope.isObject(action)) { return null; } - - var actionName = action.name, - options = interactable.options; - - if (( (actionName === 'resize' && options.resize.enabled ) - || (actionName === 'drag' && options.drag.enabled ) - || (actionName === 'gesture' && options.gesture.enabled)) - && scope.actionIsEnabled[actionName]) { - - if (actionName === 'resize' || actionName === 'resizeyx') { - actionName = 'resizexy'; - } - - return action; - } - return null; -} - -function getActionCursor (action) { - var cursor = ''; - - if (action.name === 'drag') { - cursor = scope.actionCursors.drag; - } - if (action.name === 'resize') { - if (action.axis) { - cursor = scope.actionCursors[action.name + action.axis]; - } - else if (action.edges) { - var cursorKey = 'resize', - edgeNames = ['top', 'bottom', 'left', 'right']; - - for (var i = 0; i < 4; i++) { - if (action.edges[edgeNames[i]]) { - cursorKey += edgeNames[i]; - } - } - - cursor = scope.actionCursors[cursorKey]; - } - } - - return cursor; -} - -function preventOriginalDefault () { - this.originalEvent.preventDefault(); -} - -Interaction.prototype = { - getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); }, - getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); }, - setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); }, - - pointerOver: function (pointer, event, eventTarget) { - if (this.prepared.name || !this.mouse) { return; } - - var curMatches = [], - curMatchElements = [], - prevTargetElement = this.element; - - this.addPointer(pointer); - - if (this.target - && (scope.testIgnore(this.target, this.element, eventTarget) - || !scope.testAllow(this.target, this.element, eventTarget))) { - // if the eventTarget should be ignored or shouldn't be allowed - // clear the previous target - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; - } - - var elementInteractable = scope.interactables.get(eventTarget), - elementAction = (elementInteractable - && !scope.testIgnore(elementInteractable, eventTarget, eventTarget) - && scope.testAllow(elementInteractable, eventTarget, eventTarget) - && validateAction( - elementInteractable.getAction(pointer, event, this, eventTarget), - elementInteractable)); - - if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { - elementAction = null; - } - - function pushCurMatches (interactable, selector) { - if (interactable - && scope.inContext(interactable, eventTarget) - && !scope.testIgnore(interactable, eventTarget, eventTarget) - && scope.testAllow(interactable, eventTarget, eventTarget) - && scope.matchesSelector(eventTarget, selector)) { - - curMatches.push(interactable); - curMatchElements.push(eventTarget); - } - } - - if (elementAction) { - this.target = elementInteractable; - this.element = eventTarget; - this.matches = []; - this.matchElements = []; - } - else { - scope.interactables.forEachSelector(pushCurMatches); - - if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { - this.matches = curMatches; - this.matchElements = curMatchElements; - - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(eventTarget, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', - scope.listeners.pointerHover); - } - else if (this.target) { - if (scope.nodeContains(prevTargetElement, eventTarget)) { - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(this.element, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', - scope.listeners.pointerHover); - } - else { - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; - } - } - } - }, - - // Check what action would be performed on pointerMove target if a mouse - // button were pressed and change the cursor accordingly - pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { - var target = this.target; - - if (!this.prepared.name && this.mouse) { - - var action; - - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); - - if (matches) { - action = this.validateSelector(pointer, event, matches, matchElements); - } - else if (target) { - action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); - } - - if (target && target.options.styleCursor) { - if (action) { - target._doc.documentElement.style.cursor = getActionCursor(action); - } - else { - target._doc.documentElement.style.cursor = ''; - } - } - } - else if (this.prepared.name) { - this.checkAndPreventDefault(event, target, this.element); - } - }, - - pointerOut: function (pointer, event, eventTarget) { - if (this.prepared.name) { return; } - - // Remove temporary event listeners for selector Interactables - if (!scope.interactables.get(eventTarget)) { - events.remove(eventTarget, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', - scope.listeners.pointerHover); - } - - if (this.target && this.target.options.styleCursor && !this.interacting()) { - this.target._doc.documentElement.style.cursor = ''; - } - }, - - selectorDown: function (pointer, event, eventTarget, curEventTarget) { - var that = this, - // copy event to be used in timeout for IE8 - eventCopy = events.useAttachEvent? utils.extend({}, event) : event, - element = eventTarget, - pointerIndex = this.addPointer(pointer), - action; - - this.holdTimers[pointerIndex] = setTimeout(function () { - that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); - }, scope.defaultOptions._holdDuration); - - this.pointerIsDown = true; - - // Check if the down event hits the current inertia target - if (this.inertiaStatus.active && this.target.selector) { - // climb up the DOM tree from the event target - while (utils.isElement(element)) { - - // if this element is the current inertia target element - if (element === this.element - // and the prospective action is the same as the ongoing one - && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { - - // stop inertia so that the next move will be a normal one - animationFrame.cancel(this.inertiaStatus.i); - this.inertiaStatus.active = false; - - this.collectEventTargets(pointer, event, eventTarget, 'down'); - return; - } - element = scope.parentElement(element); - } - } - - // do nothing if interacting - if (this.interacting()) { - this.collectEventTargets(pointer, event, eventTarget, 'down'); - return; - } - - function pushMatches (interactable, selector, context) { - var elements = scope.ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; - - if (scope.inContext(interactable, element) - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, elements)) { - - that.matches.push(interactable); - that.matchElements.push(element); - } - } - - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); - this.downEvent = event; - - while (utils.isElement(element) && !action) { - this.matches = []; - this.matchElements = []; - - scope.interactables.forEachSelector(pushMatches); - - action = this.validateSelector(pointer, event, this.matches, this.matchElements); - element = scope.parentElement(element); - } - - if (action) { - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - - this.collectEventTargets(pointer, event, eventTarget, 'down'); - - return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); - } - else { - // do these now since pointerDown isn't being called from here - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - utils.extend(this.downPointer, pointer); - - utils.copyCoords(this.prevCoords, this.curCoords); - this.pointerWasMoved = false; - } - - this.collectEventTargets(pointer, event, eventTarget, 'down'); - }, - - // Determine action to be performed on next pointerMove and add appropriate - // style and event Listeners - pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { - if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { - this.checkAndPreventDefault(event, this.target, this.element); - - return; - } - - this.pointerIsDown = true; - this.downEvent = event; - - var pointerIndex = this.addPointer(pointer), - action; - - // If it is the second touch of a multi-touch gesture, keep the target - // the same if a target was set by the first touch - // Otherwise, set the target if there is no action prepared - if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { - - var interactable = scope.interactables.get(curEventTarget); - - if (interactable - && !scope.testIgnore(interactable, curEventTarget, eventTarget) - && scope.testAllow(interactable, curEventTarget, eventTarget) - && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) - && scope.withinInteractionLimit(interactable, curEventTarget, action)) { - this.target = interactable; - this.element = curEventTarget; - } - } - - var target = this.target, - options = target && target.options; - - if (target && (forceAction || !this.prepared.name)) { - action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); - - this.setEventXY(this.startCoords); - - if (!action) { return; } - - if (options.styleCursor) { - target._doc.documentElement.style.cursor = getActionCursor(action); - } - - this.resizeAxes = action.name === 'resize'? action.axis : null; - - if (action === 'gesture' && this.pointerIds.length < 2) { - action = null; - } - - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - - this.snapStatus.snappedX = this.snapStatus.snappedY = - this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN; - - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - utils.extend(this.downPointer, pointer); - - this.setEventXY(this.prevCoords); - this.pointerWasMoved = false; - - this.checkAndPreventDefault(event, target, this.element); - } - // if inertia is active try to resume action - else if (this.inertiaStatus.active - && curEventTarget === this.element - && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { - - animationFrame.cancel(this.inertiaStatus.i); - this.inertiaStatus.active = false; - - this.checkAndPreventDefault(event, target, this.element); - } - }, - - setModifications: function (coords, preEnd) { - var target = this.target, - shouldMove = true, - shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd), - shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd); - - if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; } - if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; } - - if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) { - shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed; - } - else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) { - shouldMove = false; - } - - return shouldMove; - }, - - setStartOffsets: function (action, interactable, element) { - var rect = interactable.getRect(element), - origin = scope.getOriginXY(interactable, element), - snap = interactable.options[this.prepared.name].snap, - restrict = interactable.options[this.prepared.name].restrict, - width, height; - - if (rect) { - this.startOffset.left = this.startCoords.page.x - rect.left; - this.startOffset.top = this.startCoords.page.y - rect.top; - - this.startOffset.right = rect.right - this.startCoords.page.x; - this.startOffset.bottom = rect.bottom - this.startCoords.page.y; - - if ('width' in rect) { width = rect.width; } - else { width = rect.right - rect.left; } - if ('height' in rect) { height = rect.height; } - else { height = rect.bottom - rect.top; } - } - else { - this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; - } - - this.snapOffsets.splice(0); - - var snapOffset = snap && snap.offset === 'startCoords' - ? { - x: this.startCoords.page.x - origin.x, - y: this.startCoords.page.y - origin.y - } - : snap && snap.offset || { x: 0, y: 0 }; - - if (rect && snap && snap.relativePoints && snap.relativePoints.length) { - for (var i = 0; i < snap.relativePoints.length; i++) { - this.snapOffsets.push({ - x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x, - y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y - }); - } - } - else { - this.snapOffsets.push(snapOffset); - } - - if (rect && restrict.elementRect) { - this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left); - this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top); - - this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right)); - this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom)); - } - else { - this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0; - } - }, - - /*\ - * Interaction.start - [ method ] - * - * Start an action with the given Interactable and Element as tartgets. The - * action must be enabled for the target Interactable and an appropriate number - * of pointers must be held down – 1 for drag/resize, 2 for gesture. - * - * Use it with `interactable.able({ manualStart: false })` to always - * [start actions manually](https://github.com/taye/interact.js/issues/114) - * - - action (object) The action to be performed - drag, resize, etc. - - interactable (Interactable) The Interactable to target - - element (Element) The DOM Element to target - = (object) interact - ** - | interact(target) - | .draggable({ - | // disable the default drag start by down->move - | manualStart: true - | }) - | // start dragging after the user holds the pointer down - | .on('hold', function (event) { - | var interaction = event.interaction; - | - | if (!interaction.interacting()) { - | interaction.start({ name: 'drag' }, - | event.interactable, - | event.currentTarget); - | } - | }); - \*/ - start: function (action, interactable, element) { - if (this.interacting() - || !this.pointerIsDown - || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { - return; - } - - // if this interaction had been removed after stopping - // add it back - if (scope.indexOf(scope.interactions, this) === -1) { - scope.interactions.push(this); - } - - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - this.target = interactable; - this.element = element; - - this.setEventXY(this.startCoords); - this.setStartOffsets(action.name, interactable, element); - this.setModifications(this.startCoords.page); - - this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); - }, - - pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { - this.recordPointer(pointer); - - this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) - ? this.inertiaStatus.startEvent - : undefined); - - var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x - && this.curCoords.page.y === this.prevCoords.page.y - && this.curCoords.client.x === this.prevCoords.client.x - && this.curCoords.client.y === this.prevCoords.client.y); - - var dx, dy, - pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - // register movement greater than pointerMoveTolerance - if (this.pointerIsDown && !this.pointerWasMoved) { - dx = this.curCoords.client.x - this.startCoords.client.x; - dy = this.curCoords.client.y - this.startCoords.client.y; - - this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; - } - - if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { - if (this.pointerIsDown) { - clearTimeout(this.holdTimers[pointerIndex]); - } - - this.collectEventTargets(pointer, event, eventTarget, 'move'); - } - - if (!this.pointerIsDown) { return; } - - if (duplicateMove && this.pointerWasMoved && !preEnd) { - this.checkAndPreventDefault(event, this.target, this.element); - return; - } - - // set pointer coordinate, time changes and speeds - utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - - if (!this.prepared.name) { return; } - - if (this.pointerWasMoved - // ignore movement while inertia is active - && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { - - // if just starting an action, calculate the pointer speed now - if (!this.interacting()) { - utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - - // check if a drag is in the correct axis - if (this.prepared.name === 'drag') { - var absX = Math.abs(dx), - absY = Math.abs(dy), - targetAxis = this.target.options.drag.axis, - axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); - - // if the movement isn't in the axis of the interactable - if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { - // cancel the prepared action - this.prepared.name = null; - - // then try to get a drag from another ineractable - - var element = eventTarget; - - // check element interactables - while (utils.isElement(element)) { - var elementInteractable = scope.interactables.get(element); - - if (elementInteractable - && elementInteractable !== this.target - && !elementInteractable.options.drag.manualStart - && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' - && scope.checkAxis(axis, elementInteractable)) { - - this.prepared.name = 'drag'; - this.target = elementInteractable; - this.element = element; - break; - } - - element = scope.parentElement(element); - } - - // if there's no drag from element interactables, - // check the selector interactables - if (!this.prepared.name) { - var thisInteraction = this; - - var getDraggable = function (interactable, selector, context) { - var elements = scope.ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; - - if (interactable === thisInteraction.target) { return; } - - if (scope.inContext(interactable, eventTarget) - && !interactable.options.drag.manualStart - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, elements) - && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' - && scope.checkAxis(axis, interactable) - && scope.withinInteractionLimit(interactable, element, 'drag')) { - - return interactable; - } - }; - - element = eventTarget; - - while (utils.isElement(element)) { - var selectorInteractable = scope.interactables.forEachSelector(getDraggable); - - if (selectorInteractable) { - this.prepared.name = 'drag'; - this.target = selectorInteractable; - this.element = element; - break; - } - - element = scope.parentElement(element); - } - } - } - } - } - - var starting = !!this.prepared.name && !this.interacting(); - - if (starting - && (this.target.options[this.prepared.name].manualStart - || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { - this.stop(event); - return; - } - - if (this.prepared.name && this.target) { - if (starting) { - this.start(this.prepared, this.target, this.element); - } - - var shouldMove = this.setModifications(this.curCoords.page, preEnd); - - // move if snapping or restriction doesn't prevent it - if (shouldMove || starting) { - this.prevEvent = this[this.prepared.name + 'Move'](event); - } - - this.checkAndPreventDefault(event, this.target, this.element); - } - } - - utils.copyCoords(this.prevCoords, this.curCoords); - - if (this.dragging || this.resizing) { - this.autoScrollMove(pointer); - } - }, - - dragStart: function (event) { - var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); - - this.dragging = true; - this.target.fire(dragEvent); - - // reset active dropzones - this.activeDrops.dropzones = []; - this.activeDrops.elements = []; - this.activeDrops.rects = []; - - if (!this.dynamicDrop) { - this.setActiveDrops(this.element); - } - - var dropEvents = this.getDropEvents(event, dragEvent); - - if (dropEvents.activate) { - this.fireActiveDrops(dropEvents.activate); - } - - return dragEvent; - }, - - dragMove: function (event) { - var target = this.target, - dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), - draggableElement = this.element, - drop = this.getDrop(event, draggableElement); - - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; - - var dropEvents = this.getDropEvents(event, dragEvent); - - target.fire(dragEvent); - - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } - - this.prevDropTarget = this.dropTarget; - this.prevDropElement = this.dropElement; - - return dragEvent; - }, - - resizeStart: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); - - if (this.prepared.edges) { - var startRect = this.target.getRect(this.element); - - if (this.target.options.resize.square) { - var squareEdges = utils.extend({}, this.prepared.edges); - - squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); - squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); - squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); - squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); - - this.prepared._squareEdges = squareEdges; - } - else { - this.prepared._squareEdges = null; - } - - this.resizeRects = { - start : startRect, - current : utils.extend({}, startRect), - restricted: utils.extend({}, startRect), - previous : utils.extend({}, startRect), - delta : { - left: 0, right : 0, width : 0, - top : 0, bottom: 0, height: 0 - } - }; - - resizeEvent.rect = this.resizeRects.restricted; - resizeEvent.deltaRect = this.resizeRects.delta; - } - - this.target.fire(resizeEvent); - - this.resizing = true; - - return resizeEvent; - }, - - resizeMove: function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); - - var edges = this.prepared.edges, - invert = this.target.options.resize.invert, - invertible = invert === 'reposition' || invert === 'negate'; - - if (edges) { - var dx = resizeEvent.dx, - dy = resizeEvent.dy, - - start = this.resizeRects.start, - current = this.resizeRects.current, - restricted = this.resizeRects.restricted, - delta = this.resizeRects.delta, - previous = utils.extend(this.resizeRects.previous, restricted); - - if (this.target.options.resize.square) { - var originalEdges = edges; - - edges = this.prepared._squareEdges; - - if ((originalEdges.left && originalEdges.bottom) - || (originalEdges.right && originalEdges.top)) { - dy = -dx; - } - else if (originalEdges.left || originalEdges.right) { dy = dx; } - else if (originalEdges.top || originalEdges.bottom) { dx = dy; } - } - - // update the 'current' rect without modifications - if (edges.top ) { current.top += dy; } - if (edges.bottom) { current.bottom += dy; } - if (edges.left ) { current.left += dx; } - if (edges.right ) { current.right += dx; } - - if (invertible) { - // if invertible, copy the current rect - utils.extend(restricted, current); - - if (invert === 'reposition') { - // swap edge values if necessary to keep width/height positive - var swap; - - if (restricted.top > restricted.bottom) { - swap = restricted.top; - - restricted.top = restricted.bottom; - restricted.bottom = swap; - } - if (restricted.left > restricted.right) { - swap = restricted.left; - - restricted.left = restricted.right; - restricted.right = swap; - } - } - } - else { - // if not invertible, restrict to minimum of 0x0 rect - restricted.top = Math.min(current.top, start.bottom); - restricted.bottom = Math.max(current.bottom, start.top); - restricted.left = Math.min(current.left, start.right); - restricted.right = Math.max(current.right, start.left); - } - - restricted.width = restricted.right - restricted.left; - restricted.height = restricted.bottom - restricted.top ; - - for (var edge in restricted) { - delta[edge] = restricted[edge] - previous[edge]; - } - - resizeEvent.edges = this.prepared.edges; - resizeEvent.rect = restricted; - resizeEvent.deltaRect = delta; - } - - this.target.fire(resizeEvent); - - return resizeEvent; - }, - - gestureStart: function (event) { - var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); - - gestureEvent.ds = 0; - - this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; - this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; - this.gesture.scale = 1; - - this.gesturing = true; - - this.target.fire(gestureEvent); - - return gestureEvent; - }, - - gestureMove: function (event) { - if (!this.pointerIds.length) { - return this.prevEvent; - } - - var gestureEvent; - - gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); - gestureEvent.ds = gestureEvent.scale - this.gesture.scale; - - this.target.fire(gestureEvent); - - this.gesture.prevAngle = gestureEvent.angle; - this.gesture.prevDistance = gestureEvent.distance; - - if (gestureEvent.scale !== Infinity && - gestureEvent.scale !== null && - gestureEvent.scale !== undefined && - !isNaN(gestureEvent.scale)) { - - this.gesture.scale = gestureEvent.scale; - } - - return gestureEvent; - }, - - pointerHold: function (pointer, event, eventTarget) { - this.collectEventTargets(pointer, event, eventTarget, 'hold'); - }, - - pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - clearTimeout(this.holdTimers[pointerIndex]); - - this.collectEventTargets(pointer, event, eventTarget, 'up' ); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); - - this.pointerEnd(pointer, event, eventTarget, curEventTarget); - - this.removePointer(pointer); - }, - - pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - clearTimeout(this.holdTimers[pointerIndex]); - - this.collectEventTargets(pointer, event, eventTarget, 'cancel'); - this.pointerEnd(pointer, event, eventTarget, curEventTarget); - - this.removePointer(pointer); - }, - - // http://www.quirksmode.org/dom/events/click.html - // >Events leading to dblclick - // - // IE8 doesn't fire down event before dblclick. - // This workaround tries to fire a tap and doubletap after dblclick - ie8Dblclick: function (pointer, event, eventTarget) { - if (this.prevTap - && event.clientX === this.prevTap.clientX - && event.clientY === this.prevTap.clientY - && eventTarget === this.prevTap.target) { - - this.downTargets[0] = eventTarget; - this.downTimes[0] = new Date().getTime(); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); - } - }, - - // End interact move events and stop auto-scroll unless inertia is enabled - pointerEnd: function (pointer, event, eventTarget, curEventTarget) { - var endEvent, - target = this.target, - options = target && target.options, - inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, - inertiaStatus = this.inertiaStatus; - - if (this.interacting()) { - - if (inertiaStatus.active) { return; } - - var pointerSpeed, - now = new Date().getTime(), - inertiaPossible = false, - inertia = false, - smoothEnd = false, - endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly, - endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly, - dx = 0, - dy = 0, - startEvent; - - if (this.dragging) { - if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } - else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } - else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } - } - else { - pointerSpeed = this.pointerDelta.client.speed; - } - - // check if inertia should be started - inertiaPossible = (inertiaOptions && inertiaOptions.enabled - && this.prepared.name !== 'gesture' - && event !== inertiaStatus.startEvent); - - inertia = (inertiaPossible - && (now - this.curCoords.timeStamp) < 50 - && pointerSpeed > inertiaOptions.minSpeed - && pointerSpeed > inertiaOptions.endSpeed); - - if (inertiaPossible && !inertia && (endSnap || endRestrict)) { - - var snapRestrict = {}; - - snapRestrict.snap = snapRestrict.restrict = snapRestrict; - - if (endSnap) { - this.setSnapping(this.curCoords.page, snapRestrict); - if (snapRestrict.locked) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } - - if (endRestrict) { - this.setRestriction(this.curCoords.page, snapRestrict); - if (snapRestrict.restricted) { - dx += snapRestrict.dx; - dy += snapRestrict.dy; - } - } - - if (dx || dy) { - smoothEnd = true; - } - } - - if (inertia || smoothEnd) { - utils.copyCoords(inertiaStatus.upCoords, this.curCoords); - - this.pointers[0] = inertiaStatus.startEvent = startEvent = - new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); - - inertiaStatus.t0 = now; - - target.fire(inertiaStatus.startEvent); - - if (inertia) { - inertiaStatus.vx0 = this.pointerDelta.client.vx; - inertiaStatus.vy0 = this.pointerDelta.client.vy; - inertiaStatus.v0 = pointerSpeed; - - this.calcInertia(inertiaStatus); - - var page = utils.extend({}, this.curCoords.page), - origin = scope.getOriginXY(target, this.element), - statusObject; - - page.x = page.x + inertiaStatus.xe - origin.x; - page.y = page.y + inertiaStatus.ye - origin.y; - - statusObject = { - useStatusXY: true, - x: page.x, - y: page.y, - dx: 0, - dy: 0, - snap: null - }; - - statusObject.snap = statusObject; - - dx = dy = 0; - - if (endSnap) { - var snap = this.setSnapping(this.curCoords.page, statusObject); - - if (snap.locked) { - dx += snap.dx; - dy += snap.dy; - } - } - - if (endRestrict) { - var restrict = this.setRestriction(this.curCoords.page, statusObject); - - if (restrict.restricted) { - dx += restrict.dx; - dy += restrict.dy; - } - } - - inertiaStatus.modifiedXe += dx; - inertiaStatus.modifiedYe += dy; - - inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); - } - else { - inertiaStatus.smoothEnd = true; - inertiaStatus.xe = dx; - inertiaStatus.ye = dy; - - inertiaStatus.sx = inertiaStatus.sy = 0; - - inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); - } - - inertiaStatus.active = true; - return; - } - - if (endSnap || endRestrict) { - // fire a move event at the snapped coordinates - this.pointerMove(pointer, event, eventTarget, curEventTarget, true); - } - } - - if (this.dragging) { - endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); - - var draggableElement = this.element, - drop = this.getDrop(event, draggableElement); - - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; - - var dropEvents = this.getDropEvents(event, endEvent); - - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } - if (dropEvents.deactivate) { - this.fireActiveDrops(dropEvents.deactivate); - } - - target.fire(endEvent); - } - else if (this.resizing) { - endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); - target.fire(endEvent); - } - else if (this.gesturing) { - endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); - target.fire(endEvent); - } - - this.stop(event); - }, - - collectDrops: function (element) { - var drops = [], - elements = [], - i; - - element = element || this.element; - - // collect all dropzones and their elements which qualify for a drop - for (i = 0; i < scope.interactables.length; i++) { - if (!scope.interactables[i].options.drop.enabled) { continue; } - - var current = scope.interactables[i], - accept = current.options.drop.accept; - - // test the draggable element against the dropzone's accept setting - if ((utils.isElement(accept) && accept !== element) - || (scope.isString(accept) - && !scope.matchesSelector(element, accept))) { - - continue; - } - - // query for new elements if necessary - var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; - - for (var j = 0, len = dropElements.length; j < len; j++) { - var currentElement = dropElements[j]; - - if (currentElement === element) { - continue; - } - - drops.push(current); - elements.push(currentElement); - } - } - - return { - dropzones: drops, - elements: elements - }; - }, - - fireActiveDrops: function (event) { - var i, - current, - currentElement, - prevElement; - - // loop through all active dropzones and trigger event - for (i = 0; i < this.activeDrops.dropzones.length; i++) { - current = this.activeDrops.dropzones[i]; - currentElement = this.activeDrops.elements [i]; - - // prevent trigger of duplicate events on same element - if (currentElement !== prevElement) { - // set current element as event target - event.target = currentElement; - current.fire(event); - } - prevElement = currentElement; - } - }, - - // Collect a new set of possible drops and save them in activeDrops. - // setActiveDrops should always be called when a drag has just started or a - // drag event happens while dynamicDrop is true - setActiveDrops: function (dragElement) { - // get dropzones and their elements that could receive the draggable - var possibleDrops = this.collectDrops(dragElement, true); - - this.activeDrops.dropzones = possibleDrops.dropzones; - this.activeDrops.elements = possibleDrops.elements; - this.activeDrops.rects = []; - - for (var i = 0; i < this.activeDrops.dropzones.length; i++) { - this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); - } - }, - - getDrop: function (event, dragElement) { - var validDrops = []; - - if (scope.dynamicDrop) { - this.setActiveDrops(dragElement); - } - - // collect all dropzones and their elements which qualify for a drop - for (var j = 0; j < this.activeDrops.dropzones.length; j++) { - var current = this.activeDrops.dropzones[j], - currentElement = this.activeDrops.elements [j], - rect = this.activeDrops.rects [j]; - - validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) - ? currentElement - : null); - } - - // get the most appropriate dropzone based on DOM depth and order - var dropIndex = scope.indexOfDeepestElement(validDrops), - dropzone = this.activeDrops.dropzones[dropIndex] || null, - element = this.activeDrops.elements [dropIndex] || null; - - return { - dropzone: dropzone, - element: element - }; - }, - - getDropEvents: function (pointerEvent, dragEvent) { - var dropEvents = { - enter : null, - leave : null, - activate : null, - deactivate: null, - move : null, - drop : null - }; - - if (this.dropElement !== this.prevDropElement) { - // if there was a prevDropTarget, create a dragleave event - if (this.prevDropTarget) { - dropEvents.leave = { - target : this.prevDropElement, - dropzone : this.prevDropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragleave' - }; - - dragEvent.dragLeave = this.prevDropElement; - dragEvent.prevDropzone = this.prevDropTarget; - } - // if the dropTarget is not null, create a dragenter event - if (this.dropTarget) { - dropEvents.enter = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dragenter' - }; - - dragEvent.dragEnter = this.dropElement; - dragEvent.dropzone = this.dropTarget; - } - } - - if (dragEvent.type === 'dragend' && this.dropTarget) { - dropEvents.drop = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'drop' - }; - - dragEvent.dropzone = this.dropTarget; - } - if (dragEvent.type === 'dragstart') { - dropEvents.activate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropactivate' - }; - } - if (dragEvent.type === 'dragend') { - dropEvents.deactivate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - timeStamp : dragEvent.timeStamp, - type : 'dropdeactivate' - }; - } - if (dragEvent.type === 'dragmove' && this.dropTarget) { - dropEvents.move = { - target : this.dropElement, - dropzone : this.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : this, - dragmove : dragEvent, - timeStamp : dragEvent.timeStamp, - type : 'dropmove' - }; - dragEvent.dropzone = this.dropTarget; - } - - return dropEvents; - }, - - currentAction: function () { - return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; - }, - - interacting: function () { - return this.dragging || this.resizing || this.gesturing; - }, - - clearTargets: function () { - this.target = this.element = null; - - this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null; - }, - - stop: function (event) { - if (this.interacting()) { - scope.autoScroll.stop(); - this.matches = []; - this.matchElements = []; - - var target = this.target; - - if (target.options.styleCursor) { - target._doc.documentElement.style.cursor = ''; - } - - // prevent Default only if were previously interacting - if (event && scope.isFunction(event.preventDefault)) { - this.checkAndPreventDefault(event, target, this.element); - } - - if (this.dragging) { - this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; - } - } - - this.clearTargets(); - - this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false; - this.prepared.name = this.prevEvent = null; - this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; - - // remove pointers if their ID isn't in this.pointerIds - for (var i = 0; i < this.pointers.length; i++) { - if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { - this.pointers.splice(i, 1); - } - } - - for (i = 0; i < scope.interactions.length; i++) { - // remove this interaction if it's not the only one of it's type - if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { - scope.interactions.splice(scope.indexOf(scope.interactions, this), 1); - } - } - }, - - inertiaFrame: function () { - var inertiaStatus = this.inertiaStatus, - options = this.target.options[this.prepared.name].inertia, - lambda = options.resistance, - t = new Date().getTime() / 1000 - inertiaStatus.t0; - - if (t < inertiaStatus.te) { - - var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; - - if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { - inertiaStatus.sx = inertiaStatus.xe * progress; - inertiaStatus.sy = inertiaStatus.ye * progress; - } - else { - var quadPoint = scope.getQuadraticCurvePoint( - 0, 0, - inertiaStatus.xe, inertiaStatus.ye, - inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, - progress); - - inertiaStatus.sx = quadPoint.x; - inertiaStatus.sy = quadPoint.y; - } - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); - } - else { - inertiaStatus.sx = inertiaStatus.modifiedXe; - inertiaStatus.sy = inertiaStatus.modifiedYe; - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.active = false; - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); - } - }, - - smoothEndFrame: function () { - var inertiaStatus = this.inertiaStatus, - t = new Date().getTime() - inertiaStatus.t0, - duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; - - if (t < duration) { - inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration); - inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration); - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); - } - else { - inertiaStatus.sx = inertiaStatus.xe; - inertiaStatus.sy = inertiaStatus.ye; - - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.active = false; - inertiaStatus.smoothEnd = false; - - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); - } - }, - - addPointer: function (pointer) { - var id = utils.getPointerId(pointer), - index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); - - if (index === -1) { - index = this.pointerIds.length; - } - - this.pointerIds[index] = id; - this.pointers[index] = pointer; - - return index; - }, - - removePointer: function (pointer) { - var id = utils.getPointerId(pointer), - index = this.mouse? 0 : scope.indexOf(this.pointerIds, id); - - if (index === -1) { return; } - - if (!this.interacting()) { - this.pointers.splice(index, 1); - } - - this.pointerIds .splice(index, 1); - this.downTargets.splice(index, 1); - this.downTimes .splice(index, 1); - this.holdTimers .splice(index, 1); - }, - - recordPointer: function (pointer) { - // Do not update pointers while inertia is active. - // The inertia start event should be this.pointers[0] - if (this.inertiaStatus.active) { return; } - - var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - if (index === -1) { return; } - - this.pointers[index] = pointer; - }, - - collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - // do not fire a tap event if the pointer was moved before being lifted - if (eventType === 'tap' && (this.pointerWasMoved - // or if the pointerup target is different to the pointerdown target - || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) { - return; - } - - var targets = [], - elements = [], - element = eventTarget; - - function collectSelectors (interactable, selector, context) { - var els = scope.ie8MatchesSelector - ? context.querySelectorAll(selector) - : undefined; - - if (interactable._iEvents[eventType] - && utils.isElement(element) - && scope.inContext(interactable, element) - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && scope.matchesSelector(element, selector, els)) { - - targets.push(interactable); - elements.push(element); - } - } - - - var interact = scope.interact; - - while (element) { - if (interact.isSet(element) && interact(element)._iEvents[eventType]) { - targets.push(interact(element)); - elements.push(element); - } - - scope.interactables.forEachSelector(collectSelectors); - - element = scope.parentElement(element); - } - - // create the tap event even if there are no listeners so that - // doubletap can still be created and fired - if (targets.length || eventType === 'tap') { - this.firePointers(pointer, event, eventTarget, targets, elements, eventType); - } - }, - - firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)), - pointerEvent = {}, - i, - // for tap events - interval, createNewDoubleTap; - - // if it's a doubletap then the event properties would have been - // copied from the tap event and provided as the pointer argument - if (eventType === 'doubletap') { - pointerEvent = pointer; - } - else { - utils.extend(pointerEvent, event); - if (event !== pointer) { - utils.extend(pointerEvent, pointer); - } - - pointerEvent.preventDefault = preventOriginalDefault; - pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; - pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; - pointerEvent.interaction = this; - - pointerEvent.timeStamp = new Date().getTime(); - pointerEvent.originalEvent = event; - pointerEvent.type = eventType; - pointerEvent.pointerId = utils.getPointerId(pointer); - pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' - : scope.isString(pointer.pointerType) - ? pointer.pointerType - : [,,'touch', 'pen', 'mouse'][pointer.pointerType]; - } - - if (eventType === 'tap') { - pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex]; - - interval = pointerEvent.timeStamp - this.tapTime; - createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap' - && this.prevTap.target === pointerEvent.target - && interval < 500); - - pointerEvent.double = createNewDoubleTap; - - this.tapTime = pointerEvent.timeStamp; - } - - for (i = 0; i < targets.length; i++) { - pointerEvent.currentTarget = elements[i]; - pointerEvent.interactable = targets[i]; - targets[i].fire(pointerEvent); - - if (pointerEvent.immediatePropagationStopped - ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { - break; - } - } - - if (createNewDoubleTap) { - var doubleTap = {}; - - utils.extend(doubleTap, pointerEvent); - - doubleTap.dt = interval; - doubleTap.type = 'doubletap'; - - this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap'); - - this.prevTap = doubleTap; - } - else if (eventType === 'tap') { - this.prevTap = pointerEvent; - } - }, - - validateSelector: function (pointer, event, matches, matchElements) { - for (var i = 0, len = matches.length; i < len; i++) { - var match = matches[i], - matchElement = matchElements[i], - action = validateAction(match.getAction(pointer, event, this, matchElement), match); - - if (action && scope.withinInteractionLimit(match, matchElement, action)) { - this.target = match; - this.element = matchElement; - - return action; - } - } - }, - - setSnapping: function (pageCoords, status) { - var snap = this.target.options[this.prepared.name].snap, - targets = [], - target, - page, - i; - - status = status || this.snapStatus; - - if (status.useStatusXY) { - page = { x: status.x, y: status.y }; - } - else { - var origin = scope.getOriginXY(this.target, this.element); - - page = utils.extend({}, pageCoords); - - page.x -= origin.x; - page.y -= origin.y; - } - - status.realX = page.x; - status.realY = page.y; - - page.x = page.x - this.inertiaStatus.resumeDx; - page.y = page.y - this.inertiaStatus.resumeDy; - - var len = snap.targets? snap.targets.length : 0; - - for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) { - var relative = { - x: page.x - this.snapOffsets[relIndex].x, - y: page.y - this.snapOffsets[relIndex].y - }; - - for (i = 0; i < len; i++) { - if (scope.isFunction(snap.targets[i])) { - target = snap.targets[i](relative.x, relative.y, this); - } - else { - target = snap.targets[i]; - } - - if (!target) { continue; } - - targets.push({ - x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x, - y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y, - - range: scope.isNumber(target.range)? target.range: snap.range - }); - } - } - - var closest = { - target: null, - inRange: false, - distance: 0, - range: 0, - dx: 0, - dy: 0 - }; - - for (i = 0, len = targets.length; i < len; i++) { - target = targets[i]; - - var range = target.range, - dx = target.x - page.x, - dy = target.y - page.y, - distance = utils.hypot(dx, dy), - inRange = distance <= range; - - // Infinite targets count as being out of range - // compared to non infinite ones that are in range - if (range === Infinity && closest.inRange && closest.range !== Infinity) { - inRange = false; - } - - if (!closest.target || (inRange - // is the closest target in range? - ? (closest.inRange && range !== Infinity - // the pointer is relatively deeper in this target - ? distance / range < closest.distance / closest.range - // this target has Infinite range and the closest doesn't - : (range === Infinity && closest.range !== Infinity) - // OR this target is closer that the previous closest - || distance < closest.distance) - // The other is not in range and the pointer is closer to this target - : (!closest.inRange && distance < closest.distance))) { - - if (range === Infinity) { - inRange = true; - } - - closest.target = target; - closest.distance = distance; - closest.range = range; - closest.inRange = inRange; - closest.dx = dx; - closest.dy = dy; - - status.range = range; - } - } - - var snapChanged; - - if (closest.target) { - snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); - - status.snappedX = closest.target.x; - status.snappedY = closest.target.y; - } - else { - snapChanged = true; - - status.snappedX = NaN; - status.snappedY = NaN; - } - - status.dx = closest.dx; - status.dy = closest.dy; - - status.changed = (snapChanged || (closest.inRange && !status.locked)); - status.locked = closest.inRange; - - return status; - }, - - setRestriction: function (pageCoords, status) { - var target = this.target, - restrict = target && target.options[this.prepared.name].restrict, - restriction = restrict && restrict.restriction, - page; - - if (!restriction) { - return status; - } - - status = status || this.restrictStatus; - - page = status.useStatusXY - ? page = { x: status.x, y: status.y } - : page = utils.extend({}, pageCoords); - - if (status.snap && status.snap.locked) { - page.x += status.snap.dx || 0; - page.y += status.snap.dy || 0; - } - - page.x -= this.inertiaStatus.resumeDx; - page.y -= this.inertiaStatus.resumeDy; - - status.dx = 0; - status.dy = 0; - status.restricted = false; - - var rect, restrictedX, restrictedY; - - if (scope.isString(restriction)) { - if (restriction === 'parent') { - restriction = scope.parentElement(this.element); - } - else if (restriction === 'self') { - restriction = target.getRect(this.element); - } - else { - restriction = scope.closest(this.element, restriction); - } - - if (!restriction) { return status; } - } - - if (scope.isFunction(restriction)) { - restriction = restriction(page.x, page.y, this.element); - } - - if (utils.isElement(restriction)) { - restriction = scope.getElementRect(restriction); - } - - rect = restriction; - - if (!restriction) { - restrictedX = page.x; - restrictedY = page.y; - } - // object is assumed to have - // x, y, width, height or - // left, top, right, bottom - else if ('x' in restriction && 'y' in restriction) { - restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top ); - } - else { - restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left); - restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top ); - } - - status.dx = restrictedX - page.x; - status.dy = restrictedY - page.y; - - status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; - status.restricted = !!(status.dx || status.dy); - - status.restrictedX = restrictedX; - status.restrictedY = restrictedY; - - return status; - }, - - checkAndPreventDefault: function (event, interactable, element) { - if (!(interactable = interactable || this.target)) { return; } - - var options = interactable.options, - prevent = options.preventDefault; - - if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { - // do not preventDefault on pointerdown if the prepared action is a drag - // and dragging can only start from a certain direction - this allows - // a touch to pan the viewport if a drag isn't in the right direction - if (/down|start/i.test(event.type) - && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { - - return; - } - - // with manualStart, only preventDefault while interacting - if (options[this.prepared.name] && options[this.prepared.name].manualStart - && !this.interacting()) { - return; - } - - event.preventDefault(); - return; - } - - if (prevent === 'always') { - event.preventDefault(); - return; - } - }, - - calcInertia: function (status) { - var inertiaOptions = this.target.options[this.prepared.name].inertia, - lambda = inertiaOptions.resistance, - inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; - - status.x0 = this.prevEvent.pageX; - status.y0 = this.prevEvent.pageY; - status.t0 = status.startEvent.timeStamp / 1000; - status.sx = status.sy = 0; - - status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; - status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; - status.te = inertiaDur; - - status.lambda_v0 = lambda / status.v0; - status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; - }, - - autoScrollMove: function (pointer) { - if (!(this.interacting() - && scope.checkAutoScroll(this.target, this.prepared.name))) { - return; - } - - if (this.inertiaStatus.active) { - scope.autoScroll.x = scope.autoScroll.y = 0; - return; - } - - var top, - right, - bottom, - left, - options = this.target.options[this.prepared.name].autoScroll, - container = options.container || scope.getWindow(this.element); - - if (scope.isWindow(container)) { - left = pointer.clientX < scope.autoScroll.margin; - top = pointer.clientY < scope.autoScroll.margin; - right = pointer.clientX > container.innerWidth - scope.autoScroll.margin; - bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin; - } - else { - var rect = scope.getElementRect(container); - - left = pointer.clientX < rect.left + scope.autoScroll.margin; - top = pointer.clientY < rect.top + scope.autoScroll.margin; - right = pointer.clientX > rect.right - scope.autoScroll.margin; - bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin; - } - - scope.autoScroll.x = (right ? 1: left? -1: 0); - scope.autoScroll.y = (bottom? 1: top? -1: 0); - - if (!scope.autoScroll.isScrolling) { - // set the autoScroll properties to those of the target - scope.autoScroll.margin = options.margin; - scope.autoScroll.speed = options.speed; - - scope.autoScroll.start(this); - } - }, - - _updateEventTargets: function (target, currentTarget) { - this._eventTarget = target; - this._curEventTarget = currentTarget; - } - -}; - -module.exports = Interaction; - -},{"./InteractEvent":2,"./scope":6,"./utils":13,"./utils/browser":8,"./utils/events":10}],4:[function(require,module,exports){ -'use strict'; - -var raf = require('./utils/raf'), - getWindow = require('./utils/window').getWindow, - isWindow = require('./utils/isType').isWindow; - -var autoScroll = { - - interaction: null, - i: null, // the handle returned by window.setInterval - x: 0, y: 0, // Direction each pulse is to scroll in - - isScrolling: false, - prevTime: 0, - - start: function (interaction) { - autoScroll.isScrolling = true; - raf.cancel(autoScroll.i); - - autoScroll.interaction = interaction; - autoScroll.prevTime = new Date().getTime(); - autoScroll.i = raf.request(autoScroll.scroll); - }, - - stop: function () { - autoScroll.isScrolling = false; - raf.cancel(autoScroll.i); - }, - - // scroll the window by the values in scroll.x/y - scroll: function () { - var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll, - container = options.container || getWindow(autoScroll.interaction.element), - now = new Date().getTime(), - // change in time in seconds - dt = (now - autoScroll.prevTime) / 1000, - // displacement - s = options.speed * dt; - - if (s >= 1) { - if (isWindow(container)) { - container.scrollBy(autoScroll.x * s, autoScroll.y * s); - } - else if (container) { - container.scrollLeft += autoScroll.x * s; - container.scrollTop += autoScroll.y * s; - } - - autoScroll.prevTime = now; - } - - if (autoScroll.isScrolling) { - raf.cancel(autoScroll.i); - autoScroll.i = raf.request(autoScroll.scroll); - } - } -}; - -module.exports = autoScroll; - -},{"./utils/isType":14,"./utils/raf":17,"./utils/window":18}],5:[function(require,module,exports){ -'use strict'; - -module.exports = { - base: { - accept : null, - actionChecker : null, - styleCursor : true, - preventDefault: 'auto', - origin : { x: 0, y: 0 }, - deltaSource : 'page', - allowFrom : null, - ignoreFrom : null, - _context : require('./utils/domObjects').document, - dropChecker : null - }, - - drag: { - enabled: false, - manualStart: true, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - axis: 'xy' - }, - - drop: { - enabled: false, - accept: null, - overlap: 'pointer' - }, - - resize: { - enabled: false, - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - square: false, - axis: 'xy', - - // use default margin - margin: NaN, - - // object with props left, right, top, bottom which are - // true/false values to resize when the pointer is over that edge, - // CSS selectors to match the handles for each direction - // or the Elements for each handle - edges: null, - - // a value of 'none' will limit the resize rect to a minimum of 0x0 - // 'negate' will alow the rect to have negative width/height - // 'reposition' will keep the width/height positive by swapping - // the top and bottom edges and/or swapping the left and right edges - invert: 'none' - }, - - gesture: { - manualStart: false, - enabled: false, - max: Infinity, - maxPerElement: 1, - - restrict: null - }, - - perAction: { - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: { - enabled : false, - endOnly : false, - range : Infinity, - targets : null, - offsets : null, - - relativePoints: null - }, - - restrict: { - enabled: false, - endOnly: false - }, - - autoScroll: { - enabled : false, - container : null, // the item that is scrolled (Window or HTMLElement) - margin : 60, - speed : 300 // the scroll speed in pixels per second - }, - - inertia: { - enabled : false, - resistance : 10, // the lambda in exponential decay - minSpeed : 100, // target speed must be above this for inertia to start - endSpeed : 10, // the speed at which inertia is slow enough to stop - allowResume : true, // allow resuming an action in inertia phase - zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 - smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia - } - }, - - _holdDuration: 600 -}; - -},{"./utils/domObjects":9}],6:[function(require,module,exports){ -'use strict'; - -var scope = {}, - extend = require('./utils/extend'); - -extend(scope, require('./utils/window')); -extend(scope, require('./utils/domObjects')); -extend(scope, require('./utils/arr.js')); -extend(scope, require('./utils/isType')); - -module.exports = scope; - -},{"./utils/arr.js":7,"./utils/domObjects":9,"./utils/extend":11,"./utils/isType":14,"./utils/window":18}],7:[function(require,module,exports){ -'use strict'; - -function indexOf (array, target) { - for (var i = 0, len = array.length; i < len; i++) { - if (array[i] === target) { - return i; - } - } - - return -1; -} - -function contains (array, target) { - return indexOf(array, target) !== -1; -} - -module.exports = { - indexOf: indexOf, - contains: contains -}; - -},{}],8:[function(require,module,exports){ -'use strict'; - -var win = require('./window'), - domObjects = require('./domObjects'); - -var browser = { - // Does the browser support touch input? - supportsTouch : !!(('ontouchstart' in win) || win.window.DocumentTouch - && domObjects.document instanceof win.DocumentTouch), - - // Does the browser support PointerEvents - supportsPointerEvent : !!domObjects.PointerEvent, - - // Opera Mobile must be handled differently - isOperaMobile : (navigator.appName === 'Opera' - && browser.supportsTouch - && navigator.userAgent.match('Presto')), - - // scrolling doesn't change the result of - // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 - isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)), - - isIe9OrOlder : domObjects.document.all && !win.window.atob, - - // prefix matchesSelector - prefixedMatchesSelector: 'matches' in Element.prototype? - 'matches': 'webkitMatchesSelector' in Element.prototype? - 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? - 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? - 'oMatchesSelector': 'msMatchesSelector' - -}; - -module.exports = browser; - -},{"./domObjects":9,"./window":18}],9:[function(require,module,exports){ -'use strict'; - -var domObjects = {}, - win = require('./window').window, - blank = function () {}; - -domObjects.document = win.document; -domObjects.DocumentFragment = win.DocumentFragment || blank; -domObjects.SVGElement = win.SVGElement || blank; -domObjects.SVGSVGElement = win.SVGSVGElement || blank; -domObjects.SVGElementInstance = win.SVGElementInstance || blank; -domObjects.HTMLElement = win.HTMLElement || win.Element; - -domObjects.PointerEvent = (win.PointerEvent || win.MSPointerEvent); - -module.exports = domObjects; - -},{"./window":18}],10:[function(require,module,exports){ -'use strict'; - -var arr = require('./arr'), - indexOf = arr.indexOf, - contains = arr.contains, - getWindow = require('./window').getWindow, - - useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window), - addEvent = useAttachEvent? 'attachEvent': 'addEventListener', - removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', - on = useAttachEvent? 'on': '', - - elements = [], - targets = [], - attachedListeners = []; - -function add (element, type, listener, useCapture) { - var elementIndex = indexOf(elements, element), - target = targets[elementIndex]; - - if (!target) { - target = { - events: {}, - typeCount: 0 - }; - - elementIndex = elements.push(element) - 1; - targets.push(target); - - attachedListeners.push((useAttachEvent ? { - supplied: [], - wrapped : [], - useCount: [] - } : null)); - } - - if (!target.events[type]) { - target.events[type] = []; - target.typeCount++; - } - - if (!contains(target.events[type], listener)) { - var ret; - - if (useAttachEvent) { - var listeners = attachedListeners[elementIndex], - listenerIndex = indexOf(listeners.supplied, listener); - - var wrapped = listeners.wrapped[listenerIndex] || function (event) { - if (!event.immediatePropagationStopped) { - event.target = event.srcElement; - event.currentTarget = element; - - event.preventDefault = event.preventDefault || preventDef; - event.stopPropagation = event.stopPropagation || stopProp; - event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; - - if (/mouse|click/.test(event.type)) { - event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; - event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; - } - - listener(event); - } - }; - - ret = element[addEvent](on + type, wrapped, !!useCapture); - - if (listenerIndex === -1) { - listeners.supplied.push(listener); - listeners.wrapped.push(wrapped); - listeners.useCount.push(1); - } - else { - listeners.useCount[listenerIndex]++; - } - } - else { - ret = element[addEvent](type, listener, !!useCapture); - } - target.events[type].push(listener); - - return ret; - } -} - -function remove (element, type, listener, useCapture) { - var i, - elementIndex = indexOf(elements, element), - target = targets[elementIndex], - listeners, - listenerIndex, - wrapped = listener; - - if (!target || !target.events) { - return; - } - - if (useAttachEvent) { - listeners = attachedListeners[elementIndex]; - listenerIndex = indexOf(listeners.supplied, listener); - wrapped = listeners.wrapped[listenerIndex]; - } - - if (type === 'all') { - for (type in target.events) { - if (target.events.hasOwnProperty(type)) { - remove(element, type, 'all'); - } - } - return; - } - - if (target.events[type]) { - var len = target.events[type].length; - - if (listener === 'all') { - for (i = 0; i < len; i++) { - remove(element, type, target.events[type][i], !!useCapture); - } - return; - } else { - for (i = 0; i < len; i++) { - if (target.events[type][i] === listener) { - element[removeEvent](on + type, wrapped, !!useCapture); - target.events[type].splice(i, 1); - - if (useAttachEvent && listeners) { - listeners.useCount[listenerIndex]--; - if (listeners.useCount[listenerIndex] === 0) { - listeners.supplied.splice(listenerIndex, 1); - listeners.wrapped.splice(listenerIndex, 1); - listeners.useCount.splice(listenerIndex, 1); - } - } - - break; - } - } - } - - if (target.events[type] && target.events[type].length === 0) { - target.events[type] = null; - target.typeCount--; - } - } - - if (!target.typeCount) { - targets.splice(elementIndex, 1); - elements.splice(elementIndex, 1); - attachedListeners.splice(elementIndex, 1); - } -} - -function preventDef () { - this.returnValue = false; -} - -function stopProp () { - this.cancelBubble = true; -} - -function stopImmProp () { - this.cancelBubble = true; - this.immediatePropagationStopped = true; -} - -module.exports = { - add: add, - remove: remove, - useAttachEvent: useAttachEvent, - - _elements: elements, - _targets: targets, - _attachedListeners: attachedListeners -}; - -},{"./arr":7,"./window":18}],11:[function(require,module,exports){ -'use strict'; - -module.exports = function extend (dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - return dest; -}; - -},{}],12:[function(require,module,exports){ -'use strict'; - -module.exports = function hypot (x, y) { return Math.sqrt(x * x + y * y); }; - -},{}],13:[function(require,module,exports){ -'use strict'; - -var utils = module.exports, - extend = require('./extend'), - win = require('./window'); - -utils.blank = function () {}; - -utils.warnOnce = function (method, message) { - var warned = false; - - return function () { - if (!warned) { - win.window.console.warn(message); - warned = true; - } - - return method.apply(this, arguments); - }; -}; - -utils.extend = extend; -utils.hypot = require('./hypot'); -utils.raf = require('./raf'); -utils.browser = require('./browser'); - -extend(utils, require('./arr')); -extend(utils, require('./isType')); -extend(utils, require('./pointerUtils')); - -},{"./arr":7,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./pointerUtils":16,"./raf":17,"./window":18}],14:[function(require,module,exports){ -'use strict'; - -var win = require('./window'), - domObjects = require('./domObjects'); - -var isType = { - isElement : function (o) { - if (!o || (typeof o !== 'object')) { return false; } - - var _window = win.getWindow(o) || win.window; - - return (/object|function/.test(typeof _window.Element) - ? o instanceof _window.Element //DOM2 - : o.nodeType === 1 && typeof o.nodeName === "string"); - }, - - isArray : null, - - isWindow : require('./isWindow'), - - isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }, - - isObject : function (thing) { return !!thing && (typeof thing === 'object'); }, - - isFunction : function (thing) { return typeof thing === 'function'; }, - - isNumber : function (thing) { return typeof thing === 'number' ; }, - - isBool : function (thing) { return typeof thing === 'boolean' ; }, - - isString : function (thing) { return typeof thing === 'string' ; } - -}; - -isType.isArray = function (thing) { - return isType.isObject(thing) - && (typeof thing.length !== 'undefined') - && isType.isFunction(thing.splice); -}; - -module.exports = isType; - -},{"./domObjects":9,"./isWindow":15,"./window":18}],15:[function(require,module,exports){ -'use strict'; - -module.exports = function isWindow (thing) { - return !!(thing && thing.Window) && (thing instanceof thing.Window); -}; - -},{}],16:[function(require,module,exports){ -'use strict'; - -var pointerUtils = {}, - // reduce object creation in getXY() - tmpXY = {}, - win = require('./window'), - hypot = require('./hypot'), - extend = require('./extend'), - browser = require('./browser'), - isType = require('./isType'), - InteractEvent = require('../InteractEvent'); - -pointerUtils.copyCoords = function (dest, src) { - dest.page = dest.page || {}; - dest.page.x = src.page.x; - dest.page.y = src.page.y; - - dest.client = dest.client || {}; - dest.client.x = src.client.x; - dest.client.y = src.client.y; - - dest.timeStamp = src.timeStamp; -}; - -pointerUtils.setEventXY = function (targetObj, pointer, interaction) { - if (!pointer) { - if (interaction.pointerIds.length > 1) { - pointer = pointerUtils.touchAverage(interaction.pointers); - } - else { - pointer = interaction.pointers[0]; - } - } - - pointerUtils.getPageXY(pointer, tmpXY, interaction); - targetObj.page.x = tmpXY.x; - targetObj.page.y = tmpXY.y; - - pointerUtils.getClientXY(pointer, tmpXY, interaction); - targetObj.client.x = tmpXY.x; - targetObj.client.y = tmpXY.y; - - targetObj.timeStamp = new Date().getTime(); -}; - -pointerUtils.setEventDeltas = function (targetObj, prev, cur) { - targetObj.page.x = cur.page.x - prev.page.x; - targetObj.page.y = cur.page.y - prev.page.y; - targetObj.client.x = cur.client.x - prev.client.x; - targetObj.client.y = cur.client.y - prev.client.y; - targetObj.timeStamp = new Date().getTime() - prev.timeStamp; - - // set pointer velocity - var dt = Math.max(targetObj.timeStamp / 1000, 0.001); - targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; - targetObj.page.vx = targetObj.page.x / dt; - targetObj.page.vy = targetObj.page.y / dt; - - targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; - targetObj.client.vx = targetObj.client.x / dt; - targetObj.client.vy = targetObj.client.y / dt; -}; - -// Get specified X/Y coords for mouse or event.touches[0] -pointerUtils.getXY = function (type, pointer, xy) { - xy = xy || {}; - type = type || 'page'; - - xy.x = pointer[type + 'X']; - xy.y = pointer[type + 'Y']; - - return xy; -}; - -pointerUtils.getPageXY = function (pointer, page, interaction) { - page = page || {}; - - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - interaction = interaction || pointer.interaction; - - extend(page, interaction.inertiaStatus.upCoords.page); - - page.x += interaction.inertiaStatus.sx; - page.y += interaction.inertiaStatus.sy; - } - else { - page.x = pointer.pageX; - page.y = pointer.pageY; - } - } - // Opera Mobile handles the viewport and scrolling oddly - else if (browser.isOperaMobile) { - pointerUtils.getXY('screen', pointer, page); - - page.x += win.window.scrollX; - page.y += win.window.scrollY; - } - else { - pointerUtils.getXY('page', pointer, page); - } - - return page; -}; - -pointerUtils.getClientXY = function (pointer, client, interaction) { - client = client || {}; - - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - extend(client, interaction.inertiaStatus.upCoords.client); - - client.x += interaction.inertiaStatus.sx; - client.y += interaction.inertiaStatus.sy; - } - else { - client.x = pointer.clientX; - client.y = pointer.clientY; - } - } - else { - // Opera Mobile handles the viewport and scrolling oddly - pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client); - } - - return client; -}; - -pointerUtils.getPointerId = function (pointer) { - return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; -}; - -module.exports = pointerUtils; - -},{"../InteractEvent":2,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./window":18}],17:[function(require,module,exports){ -'use strict'; - -var lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o'], - reqFrame, - cancelFrame; - -for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - reqFrame = window[vendors[x]+'RequestAnimationFrame']; - cancelFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; -} - -if (!reqFrame) { - reqFrame = function(callback) { - var currTime = new Date().getTime(), - timeToCall = Math.max(0, 16 - (currTime - lastTime)), - id = setTimeout(function() { callback(currTime + timeToCall); }, - timeToCall); - lastTime = currTime + timeToCall; - return id; - }; -} - -if (!cancelFrame) { - cancelFrame = function(id) { - clearTimeout(id); - }; -} - -module.exports = { - request: reqFrame, - cancel: cancelFrame -}; - -},{}],18:[function(require,module,exports){ -'use strict'; - -var isWindow = require('./isWindow'); - -var isShadowDom = function() { - // create a TextNode - var el = window.document.createTextNode(''); - - // check if it's wrapped by a polyfill - return el.ownerDocument !== window.document - && typeof window.wrap === 'function' - && window.wrap(el) === el; -}; - -var win = { - - window: undefined, - - realWindow: window, - - getWindow: function getWindow (node) { - if (isWindow(node)) { - return node; - } - - var rootNode = (node.ownerDocument || node); - - return rootNode.defaultView || rootNode.parentWindow || win.window; - } -}; - -if (typeof window !== 'undefined') { - if (isShadowDom()) { - win.window = window.wrap(window); - } else { - win.window = window; - } -} - -module.exports = win; - -},{"./isWindow":15}]},{},[1]); diff --git a/build/interact.js.map b/build/interact.js.map deleted file mode 100644 index 5b010a45e..000000000 --- a/build/interact.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"sources":["interact.js"],"names":["e","t","n","r","s","o","u","a","require","i","f","Error","code","l","exports","call","length",1,"module","getInteractionFromPointer","pointer","eventType","eventTarget","interaction","len","scope","interactions","mouseEvent","test","pointerType","id","utils","getPointerId","element","inertiaStatus","active","target","options","prepared","name","inertia","allowResume","mouse","pointers","removePointer","addPointer","parentElement","browser","supportsTouch","supportsPointerEvent","Interaction","contains","pointerIds","gesture","interacting","doOnInteractions","method","event","getActualElement","path","curEventTarget","currentTarget","type","prevTouchTime","Date","getTime","changedTouches","_updateEventTargets","pointerIsDown","preventOriginalDefault","this","originalEvent","preventDefault","checkResizeEdge","value","page","interactableElement","rect","margin","width","isNumber","right","left","height","bottom","top","x","y","isElement","matchesUpTo","defaultActionChecker","resizeEdges","getRect","shouldResize","action","resizeAxes","extend","curCoords","actionIsEnabled","resize","enabled","resizeOptions","isObject","edges","edge","_eventTarget","axis","drag","dragging","resizing","delegateListener","useCapture","fakeEvent","delegated","delegatedEvents","prop","selectors","selector","context","contexts","matchesSelector","nodeContains","listeners","j","delegateUseCapture","interact","interactables","get","Interactable","_element","_iEvents","_window","trySelector","getWindow","window","Node","document","_context","PointerEvent","events","add","pEventTypes","down","pointerDown","move","pointerHover","_doc","documents","listenToDocument","push","set","endAllInteractions","pointerEnd","doc","win","defaultView","parentWindow","MSPointerEvent","up","over","out","cancel","selectorDown","pointerMove","pointerOver","pointerOut","pointerUp","pointerCancel","autoScrollMove","frameElement","parentDoc","ownerDocument","error","windowParentError","useAttachEvent","currentAction","checkAndPreventDefault","dynamicDrop","defaultOptions","autoScroll","pointerMoveTolerance","maxInteractions","Infinity","actionCursors","isIe9OrOlder","resizex","resizey","resizexy","resizetop","resizeleft","resizebottom","resizeright","resizetopleft","resizebottomright","resizetopright","resizebottomleft","wheelEvent","eventTypes","globalEvents","prefixedMatchesSelector","Element","prototype","ie8MatchesSelector","interactionListeners","isString","querySelector","getScrollXY","scrollX","documentElement","scrollLeft","scrollY","scrollTop","SVGElementInstance","correspondingUseElement","getElementRect","scroll","isIOS7orLower","clientRect","SVGElement","getBoundingClientRect","getClientRects","heigh","getOriginXY","interactable","origin","closest","isFunction","_getQBezierValue","p1","p2","p3","iT","getQuadraticCurvePoint","startX","startY","cpX","cpY","endX","endY","position","easeOutQuad","b","c","d","parent","child","parentNode","node","isDocFrag","host","inContext","testIgnore","ignoreFrom","testAllow","allowFrom","checkAxis","thisAxis","checkSnap","snap","checkRestrict","restrict","checkAutoScroll","withinInteractionLimit","maxActions","max","maxPerElement","activeInteractions","targetCount","targetElementCount","otherAction","indexOfDeepestElement","elements","dropzone","deepestZone","index","deepestZoneParents","dropzoneParents","unshift","HTMLElement","SVGSVGElement","ownerSVGElement","parents","lastChild","previousSibling","nodeList","realWindow","replace","limit","elems","querySelectorAll","InteractEvent","listenerName","indexOfElement","forEachSelector","callback","ret","undefined","setOnEvents","phases","ondrop","ondropactivate","ondropdeactivate","ondragenter","ondragleave","ondropmove","onstart","onmove","onend","oninertiastart","draggable","setPerAction","isBool","option","perAction","drop","accept","overlap","Math","min","dropCheck","draggableElement","dropElement","dropped","dropChecker","dropOverlap","horizontal","vertical","getPageXY","dragRect","cx","cy","overlapArea","overlapRatio","checker","newValue","resizable","square","squareResize","gesturable","actions","setOptions","isArray","thisOption","mode","targets","createSnapGrid","offset","gridOffset","grid","anchors","paths","relativePoints","elementOrigin","allActions","getAction","actionChecker","rectChecker","styleCursor","deltaSource","restriction","fire","iEvent","onEvent","funcName","immediatePropagationStopped","on","listener","search","trim","split","off","eventList","indexOf","splice","matchFound","fn","useCap","remove","base","methods","perActions","settings","setting","unset","style","cursor","warnOnce","isSet","enableDragging","enableResizing","enableGesturing","debug","gesturing","matches","matchElements","prevCoords","startCoords","recordPointer","snapStatus","restrictStatus","downTime","downTimes","downEvent","downPointer","prevEvent","dragMove","resizeMove","gestureMove","getTouchAverage","touchAverage","getTouchBBox","touchBBox","getTouchDistance","touchDistance","getTouchAngle","touchAngle","newvalue","stop","offsetX","offsetY","gridx","round","gridy","newX","newY","range","./InteractEvent","./Interaction","./autoScroll","./defaultOptions","./scope","./utils","./utils/events","./utils/window",2,"phase","related","client","sourceX","sourceY","starting","ending","coords","locked","snappedX","snappedY","realX","realY","dx","dy","elementRect","restricted","pageX","pageY","clientX","clientY","x0","y0","clientX0","clientY0","ctrlKey","altKey","shiftKey","metaKey","button","t0","detail","relatedTarget","zeroResumeDelta","resumeDx","resumeDy","axes","touches","distance","box","scale","ds","angle","da","startAngle","startDistance","prevAngle","prevScale","timeStamp","dt","duration","speed","velocityX","velocityY","hypot","pointerDelta","vx","vy","atan2","PI","swipe","velocity","blank","stopImmediatePropagation","propagationStopped","stopPropagation",3,"dropTarget","prevDropTarget","prevDropElement","smoothEnd","startEvent","upCoords","xe","ye","sx","sy","vx0","vys","lambda_v0","one_ve_v0","Function","bind","boundInertiaFrame","inertiaFrame","boundSmoothEndFrame","smoothEndFrame","that","activeDrops","dropzones","rects","downTargets","holdTimers","_curEventTarget","tapTime","prevTap","startOffset","restrictOffset","snapOffsets","start","prevDistance","changed","restrictedX","restrictedY","pointerWasMoved","validateAction","actionName","getActionCursor","cursorKey","edgeNames","animationFrame","raf","xy","getClientXY","setEventXY","ptr","pushCurMatches","curMatches","curMatchElements","prevTargetElement","elementInteractable","elementAction","validateSelector","pushMatches","eventCopy","pointerIndex","setTimeout","pointerHold","_holdDuration","collectEventTargets","copyCoords","forceAction","NaN","setModifications","preEnd","shouldMove","shouldSnap","endOnly","shouldRestrict","setSnapping","setRestriction","setStartOffsets","snapOffset","duplicateMove","clearTimeout","setEventDeltas","absX","abs","absY","targetAxis","manualStart","thisInteraction","getDraggable","selectorInteractable","dragStart","dragEvent","setActiveDrops","dropEvents","getDropEvents","activate","fireActiveDrops","getDrop","leave","enter","resizeStart","resizeEvent","startRect","squareEdges","_squareEdges","resizeRects","current","previous","delta","deltaRect","invert","invertible","originalEdges","swap","gestureStart","gestureEvent","isNaN","ie8Dblclick","endEvent","inertiaOptions","pointerSpeed","now","inertiaPossible","endSnap","endRestrict","minSpeed","endSpeed","snapRestrict","vy0","v0","calcInertia","statusObject","useStatusXY","modifiedXe","modifiedYe","request","deactivate","collectDrops","drops","dropElements","currentElement","prevElement","dragElement","possibleDrops","validDrops","dropIndex","pointerEvent","dragLeave","prevDropzone","dragEnter","dragmove","clearTargets","lambda","resistance","te","progress","exp","quadPoint","smoothEndDuration","collectSelectors","els","firePointers","interval","createNewDoubleTap","pointerId","doubleTap","match","matchElement","pageCoords","status","relIndex","relative","inRange","snapChanged","prevent","nodeName","inertiaDur","log","container","isWindow","innerWidth","innerHeight","isScrolling","./utils/browser",4,"prevTime","scrollBy","./utils/isType","./utils/raf",5,"offsets","./utils/domObjects",6,"./utils/arr.js","./utils/extend",7,"array",8,"domObjects","DocumentTouch","isOperaMobile","navigator","appName","userAgent","platform","appVersion","all","atob","./domObjects","./window",9,"DocumentFragment",10,"elementIndex","typeCount","attachedListeners","supplied","wrapped","useCount","listenerIndex","srcElement","preventDef","stopProp","stopImmProp","addEvent","removeEvent","hasOwnProperty","returnValue","cancelBubble","arr","_elements","_targets","_attachedListeners","./arr",11,"dest","source",12,"sqrt",13,"message","warned","console","warn","apply","arguments","./browser","./extend","./hypot","./isType","./pointerUtils","./raf",14,"isType","nodeType","thing","./isWindow",15,"Window",16,"pointerUtils","tmpXY","src","targetObj","prev","cur","getXY","identifier","../InteractEvent",17,"reqFrame","cancelFrame","lastTime","vendors","requestAnimationFrame","currTime","timeToCall",18,"isShadowDom","el","createTextNode","wrap","rootNode"],"mappings":"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","file":"interact.js","sourcesContent":["(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o\n * Open source under the MIT License.\n * https://raw.github.com/taye/interact.js/master/LICENSE\n */\n\n 'use strict';\n\n // return early if there's no window to work with (eg. Node.js)\n if (!require('./utils/window').window) { return; }\n\n var scope = require('./scope'),\n utils = require('./utils'),\n browser = utils.browser;\n\n scope.pEventTypes = null;\n\n scope.documents = []; // all documents being listened to\n\n scope.interactables = []; // all set interactables\n scope.interactions = []; // all interactions\n\n scope.dynamicDrop = false;\n\n // {\n // type: {\n // selectors: ['selector', ...],\n // contexts : [document, ...],\n // listeners: [[listener, useCapture], ...]\n // }\n // }\n scope.delegatedEvents = {};\n\n scope.defaultOptions = require('./defaultOptions');\n\n // Things related to autoScroll\n scope.autoScroll = require('./autoScroll');\n\n // Less Precision with touch input\n scope.margin = browser.supportsTouch || browser.supportsPointerEvent? 20: 10;\n\n scope.pointerMoveTolerance = 1;\n\n // for ignoring browser's simulated mouse events\n scope.prevTouchTime = 0;\n\n // Allow this many interactions to happen simultaneously\n scope.maxInteractions = Infinity;\n\n scope.actionCursors = browser.isIe9OrOlder ? {\n drag : 'move',\n resizex : 'e-resize',\n resizey : 's-resize',\n resizexy: 'se-resize',\n\n resizetop : 'n-resize',\n resizeleft : 'w-resize',\n resizebottom : 's-resize',\n resizeright : 'e-resize',\n resizetopleft : 'se-resize',\n resizebottomright: 'se-resize',\n resizetopright : 'ne-resize',\n resizebottomleft : 'ne-resize',\n\n gesture : ''\n } : {\n drag : 'move',\n resizex : 'ew-resize',\n resizey : 'ns-resize',\n resizexy: 'nwse-resize',\n\n resizetop : 'ns-resize',\n resizeleft : 'ew-resize',\n resizebottom : 'ns-resize',\n resizeright : 'ew-resize',\n resizetopleft : 'nwse-resize',\n resizebottomright: 'nwse-resize',\n resizetopright : 'nesw-resize',\n resizebottomleft : 'nesw-resize',\n\n gesture : ''\n };\n\n scope.actionIsEnabled = {\n drag : true,\n resize : true,\n gesture: true\n };\n\n // because Webkit and Opera still use 'mousewheel' event type\n scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel';\n\n scope.eventTypes = [\n 'dragstart',\n 'dragmove',\n 'draginertiastart',\n 'dragend',\n 'dragenter',\n 'dragleave',\n 'dropactivate',\n 'dropdeactivate',\n 'dropmove',\n 'drop',\n 'resizestart',\n 'resizemove',\n 'resizeinertiastart',\n 'resizeend',\n 'gesturestart',\n 'gesturemove',\n 'gestureinertiastart',\n 'gestureend',\n\n 'down',\n 'move',\n 'up',\n 'cancel',\n 'tap',\n 'doubletap',\n 'hold'\n ];\n\n scope.globalEvents = {};\n\n // prefix matchesSelector\n browser.prefixedMatchesSelector = 'matches' in Element.prototype?\n 'matches': 'webkitMatchesSelector' in Element.prototype?\n 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?\n 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?\n 'oMatchesSelector': 'msMatchesSelector';\n\n // will be polyfill function if browser is IE8\n scope.ie8MatchesSelector = null;\n\n // Events wrapper\n var events = require('./utils/events');\n\n scope.listeners = {};\n\n var interactionListeners = [\n 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove',\n 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown',\n 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd',\n 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove'\n ];\n\n scope.trySelector = function (value) {\n if (!scope.isString(value)) { return false; }\n\n // an exception will be raised if it is invalid\n scope.document.querySelector(value);\n return true;\n };\n\n scope.getScrollXY = function (win) {\n win = win || scope.window;\n return {\n x: win.scrollX || win.document.documentElement.scrollLeft,\n y: win.scrollY || win.document.documentElement.scrollTop\n };\n };\n\n scope.getActualElement = function (element) {\n return (element instanceof scope.SVGElementInstance\n ? element.correspondingUseElement\n : element);\n };\n\n scope.getElementRect = function (element) {\n var scroll = browser.isIOS7orLower\n ? { x: 0, y: 0 }\n : scope.getScrollXY(scope.getWindow(element)),\n clientRect = (element instanceof scope.SVGElement)?\n element.getBoundingClientRect():\n element.getClientRects()[0];\n\n return clientRect && {\n left : clientRect.left + scroll.x,\n right : clientRect.right + scroll.x,\n top : clientRect.top + scroll.y,\n bottom: clientRect.bottom + scroll.y,\n width : clientRect.width || clientRect.right - clientRect.left,\n height: clientRect.heigh || clientRect.bottom - clientRect.top\n };\n };\n\n scope.getOriginXY = function (interactable, element) {\n var origin = interactable\n ? interactable.options.origin\n : scope.defaultOptions.origin;\n\n if (origin === 'parent') {\n origin = scope.parentElement(element);\n }\n else if (origin === 'self') {\n origin = interactable.getRect(element);\n }\n else if (scope.trySelector(origin)) {\n origin = scope.closest(element, origin) || { x: 0, y: 0 };\n }\n\n if (scope.isFunction(origin)) {\n origin = origin(interactable && element);\n }\n\n if (utils.isElement(origin)) {\n origin = scope.getElementRect(origin);\n }\n\n origin.x = ('x' in origin)? origin.x : origin.left;\n origin.y = ('y' in origin)? origin.y : origin.top;\n\n return origin;\n };\n\n // http://stackoverflow.com/a/5634528/2280888\n scope._getQBezierValue = function (t, p1, p2, p3) {\n var iT = 1 - t;\n return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;\n };\n\n scope.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) {\n return {\n x: scope._getQBezierValue(position, startX, cpX, endX),\n y: scope._getQBezierValue(position, startY, cpY, endY)\n };\n };\n\n // http://gizma.com/easing/\n scope.easeOutQuad = function (t, b, c, d) {\n t /= d;\n return -c * t*(t-2) + b;\n };\n\n scope.nodeContains = function (parent, child) {\n while (child) {\n if (child === parent) {\n return true;\n }\n\n child = child.parentNode;\n }\n\n return false;\n };\n\n scope.closest = function (child, selector) {\n var parent = scope.parentElement(child);\n\n while (utils.isElement(parent)) {\n if (scope.matchesSelector(parent, selector)) { return parent; }\n\n parent = scope.parentElement(parent);\n }\n\n return null;\n };\n\n scope.parentElement = function (node) {\n var parent = node.parentNode;\n\n if (scope.isDocFrag(parent)) {\n // skip past #shado-root fragments\n while ((parent = parent.host) && scope.isDocFrag(parent)) {}\n\n return parent;\n }\n\n return parent;\n };\n\n scope.inContext = function (interactable, element) {\n return interactable._context === element.ownerDocument\n || scope.nodeContains(interactable._context, element);\n };\n\n scope.testIgnore = function (interactable, interactableElement, element) {\n var ignoreFrom = interactable.options.ignoreFrom;\n\n if (!ignoreFrom || !utils.isElement(element)) { return false; }\n\n if (scope.isString(ignoreFrom)) {\n return scope.matchesUpTo(element, ignoreFrom, interactableElement);\n }\n else if (utils.isElement(ignoreFrom)) {\n return scope.nodeContains(ignoreFrom, element);\n }\n\n return false;\n };\n\n scope.testAllow = function (interactable, interactableElement, element) {\n var allowFrom = interactable.options.allowFrom;\n\n if (!allowFrom) { return true; }\n\n if (!utils.isElement(element)) { return false; }\n\n if (scope.isString(allowFrom)) {\n return scope.matchesUpTo(element, allowFrom, interactableElement);\n }\n else if (utils.isElement(allowFrom)) {\n return scope.nodeContains(allowFrom, element);\n }\n\n return false;\n };\n\n scope.checkAxis = function (axis, interactable) {\n if (!interactable) { return false; }\n\n var thisAxis = interactable.options.drag.axis;\n\n return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis);\n };\n\n scope.checkSnap = function (interactable, action) {\n var options = interactable.options;\n\n if (/^resize/.test(action)) {\n action = 'resize';\n }\n\n return options[action].snap && options[action].snap.enabled;\n };\n\n scope.checkRestrict = function (interactable, action) {\n var options = interactable.options;\n\n if (/^resize/.test(action)) {\n action = 'resize';\n }\n\n return options[action].restrict && options[action].restrict.enabled;\n };\n\n scope.checkAutoScroll = function (interactable, action) {\n var options = interactable.options;\n\n if (/^resize/.test(action)) {\n action = 'resize';\n }\n\n return options[action].autoScroll && options[action].autoScroll.enabled;\n };\n\n scope.withinInteractionLimit = function (interactable, element, action) {\n var options = interactable.options,\n maxActions = options[action.name].max,\n maxPerElement = options[action.name].maxPerElement,\n activeInteractions = 0,\n targetCount = 0,\n targetElementCount = 0;\n\n for (var i = 0, len = scope.interactions.length; i < len; i++) {\n var interaction = scope.interactions[i],\n otherAction = interaction.prepared.name,\n active = interaction.interacting();\n\n if (!active) { continue; }\n\n activeInteractions++;\n\n if (activeInteractions >= scope.maxInteractions) {\n return false;\n }\n\n if (interaction.target !== interactable) { continue; }\n\n targetCount += (otherAction === action.name)|0;\n\n if (targetCount >= maxActions) {\n return false;\n }\n\n if (interaction.element === element) {\n targetElementCount++;\n\n if (otherAction !== action.name || targetElementCount >= maxPerElement) {\n return false;\n }\n }\n }\n\n return scope.maxInteractions > 0;\n };\n\n // Test for the element that's \"above\" all other qualifiers\n scope.indexOfDeepestElement = function (elements) {\n var dropzone,\n deepestZone = elements[0],\n index = deepestZone? 0: -1,\n parent,\n deepestZoneParents = [],\n dropzoneParents = [],\n child,\n i,\n n;\n\n for (i = 1; i < elements.length; i++) {\n dropzone = elements[i];\n\n // an element might belong to multiple selector dropzones\n if (!dropzone || dropzone === deepestZone) {\n continue;\n }\n\n if (!deepestZone) {\n deepestZone = dropzone;\n index = i;\n continue;\n }\n\n // check if the deepest or current are document.documentElement or document.rootElement\n // - if the current dropzone is, do nothing and continue\n if (dropzone.parentNode === dropzone.ownerDocument) {\n continue;\n }\n // - if deepest is, update with the current dropzone and continue to next\n else if (deepestZone.parentNode === dropzone.ownerDocument) {\n deepestZone = dropzone;\n index = i;\n continue;\n }\n\n if (!deepestZoneParents.length) {\n parent = deepestZone;\n while (parent.parentNode && parent.parentNode !== parent.ownerDocument) {\n deepestZoneParents.unshift(parent);\n parent = parent.parentNode;\n }\n }\n\n // if this element is an svg element and the current deepest is\n // an HTMLElement\n if (deepestZone instanceof scope.HTMLElement\n && dropzone instanceof scope.SVGElement\n && !(dropzone instanceof scope.SVGSVGElement)) {\n\n if (dropzone === deepestZone.parentNode) {\n continue;\n }\n\n parent = dropzone.ownerSVGElement;\n }\n else {\n parent = dropzone;\n }\n\n dropzoneParents = [];\n\n while (parent.parentNode !== parent.ownerDocument) {\n dropzoneParents.unshift(parent);\n parent = parent.parentNode;\n }\n\n n = 0;\n\n // get (position of last common ancestor) + 1\n while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) {\n n++;\n }\n\n var parents = [\n dropzoneParents[n - 1],\n dropzoneParents[n],\n deepestZoneParents[n]\n ];\n\n child = parents[0].lastChild;\n\n while (child) {\n if (child === parents[1]) {\n deepestZone = dropzone;\n index = i;\n deepestZoneParents = [];\n\n break;\n }\n else if (child === parents[2]) {\n break;\n }\n\n child = child.previousSibling;\n }\n }\n\n return index;\n };\n\n scope.matchesSelector = function (element, selector, nodeList) {\n if (scope.ie8MatchesSelector) {\n return scope.ie8MatchesSelector(element, selector, nodeList);\n }\n\n // remove /deep/ from selectors if shadowDOM polyfill is used\n if (scope.window !== scope.realWindow) {\n selector = selector.replace(/\\/deep\\//g, ' ');\n }\n\n return element[browser.prefixedMatchesSelector](selector);\n };\n\n scope.matchesUpTo = function (element, selector, limit) {\n while (utils.isElement(element)) {\n if (scope.matchesSelector(element, selector)) {\n return true;\n }\n\n element = scope.parentElement(element);\n\n if (element === limit) {\n return scope.matchesSelector(element, selector);\n }\n }\n\n return false;\n };\n\n // For IE8's lack of an Element#matchesSelector\n // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified\n if (!(browser.prefixedMatchesSelector in Element.prototype) || !scope.isFunction(Element.prototype[browser.prefixedMatchesSelector])) {\n scope.ie8MatchesSelector = function (element, selector, elems) {\n elems = elems || element.parentNode.querySelectorAll(selector);\n\n for (var i = 0, len = elems.length; i < len; i++) {\n if (elems[i] === element) {\n return true;\n }\n }\n\n return false;\n };\n }\n\n var Interaction = require('./Interaction');\n\n function getInteractionFromPointer (pointer, eventType, eventTarget) {\n var i = 0, len = scope.interactions.length,\n mouseEvent = (/mouse/i.test(pointer.pointerType || eventType)\n // MSPointerEvent.MSPOINTER_TYPE_MOUSE\n || pointer.pointerType === 4),\n interaction;\n\n var id = utils.getPointerId(pointer);\n\n // try to resume inertia with a new pointer\n if (/down|start/i.test(eventType)) {\n for (i = 0; i < len; i++) {\n interaction = scope.interactions[i];\n\n var element = eventTarget;\n\n if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume\n && (interaction.mouse === mouseEvent)) {\n while (element) {\n // if the element is the interaction element\n if (element === interaction.element) {\n // update the interaction's pointer\n if (interaction.pointers[0]) {\n interaction.removePointer(interaction.pointers[0]);\n }\n interaction.addPointer(pointer);\n\n return interaction;\n }\n element = scope.parentElement(element);\n }\n }\n }\n }\n\n // if it's a mouse interaction\n if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) {\n\n // find a mouse interaction that's not in inertia phase\n for (i = 0; i < len; i++) {\n if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) {\n return scope.interactions[i];\n }\n }\n\n // find any interaction specifically for mouse.\n // if the eventType is a mousedown, and inertia is active\n // ignore the interaction\n for (i = 0; i < len; i++) {\n if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) {\n return interaction;\n }\n }\n\n // create a new interaction for mouse\n interaction = new Interaction();\n interaction.mouse = true;\n\n return interaction;\n }\n\n // get interaction that has this pointer\n for (i = 0; i < len; i++) {\n if (scope.contains(scope.interactions[i].pointerIds, id)) {\n return scope.interactions[i];\n }\n }\n\n // at this stage, a pointerUp should not return an interaction\n if (/up|end|out/i.test(eventType)) {\n return null;\n }\n\n // get first idle interaction\n for (i = 0; i < len; i++) {\n interaction = scope.interactions[i];\n\n if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled))\n && !interaction.interacting()\n && !(!mouseEvent && interaction.mouse)) {\n\n interaction.addPointer(pointer);\n\n return interaction;\n }\n }\n\n return new Interaction();\n }\n\n function doOnInteractions (method) {\n return (function (event) {\n var interaction,\n eventTarget = scope.getActualElement(event.path\n ? event.path[0]\n : event.target),\n curEventTarget = scope.getActualElement(event.currentTarget),\n i;\n\n if (browser.supportsTouch && /touch/.test(event.type)) {\n scope.prevTouchTime = new Date().getTime();\n\n for (i = 0; i < event.changedTouches.length; i++) {\n var pointer = event.changedTouches[i];\n\n interaction = getInteractionFromPointer(pointer, event.type, eventTarget);\n\n if (!interaction) { continue; }\n\n interaction._updateEventTargets(eventTarget, curEventTarget);\n\n interaction[method](pointer, event, eventTarget, curEventTarget);\n }\n }\n else {\n if (!browser.supportsPointerEvent && /mouse/.test(event.type)) {\n // ignore mouse events while touch interactions are active\n for (i = 0; i < scope.interactions.length; i++) {\n if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) {\n return;\n }\n }\n\n // try to ignore mouse events that are simulated by the browser\n // after a touch event\n if (new Date().getTime() - scope.prevTouchTime < 500) {\n return;\n }\n }\n\n interaction = getInteractionFromPointer(event, event.type, eventTarget);\n\n if (!interaction) { return; }\n\n interaction._updateEventTargets(eventTarget, curEventTarget);\n\n interaction[method](event, event, eventTarget, curEventTarget);\n }\n });\n }\n\n function preventOriginalDefault () {\n this.originalEvent.preventDefault();\n }\n\n function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) {\n // false, '', undefined, null\n if (!value) { return false; }\n\n // true value, use pointer coords and element rect\n if (value === true) {\n // if dimensions are negative, \"switch\" edges\n var width = scope.isNumber(rect.width)? rect.width : rect.right - rect.left,\n height = scope.isNumber(rect.height)? rect.height : rect.bottom - rect.top;\n\n if (width < 0) {\n if (name === 'left' ) { name = 'right'; }\n else if (name === 'right') { name = 'left' ; }\n }\n if (height < 0) {\n if (name === 'top' ) { name = 'bottom'; }\n else if (name === 'bottom') { name = 'top' ; }\n }\n\n if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); }\n if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); }\n\n if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); }\n if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); }\n }\n\n // the remaining checks require an element\n if (!utils.isElement(element)) { return false; }\n\n return utils.isElement(value)\n // the value is an element to use as a resize handle\n ? value === element\n // otherwise check if element matches value as selector\n : scope.matchesUpTo(element, value, interactableElement);\n }\n\n function defaultActionChecker (pointer, interaction, element) {\n var rect = this.getRect(element),\n shouldResize = false,\n action = null,\n resizeAxes = null,\n resizeEdges,\n page = utils.extend({}, interaction.curCoords.page),\n options = this.options;\n\n if (!rect) { return null; }\n\n if (scope.actionIsEnabled.resize && options.resize.enabled) {\n var resizeOptions = options.resize;\n\n resizeEdges = {\n left: false, right: false, top: false, bottom: false\n };\n\n // if using resize.edges\n if (scope.isObject(resizeOptions.edges)) {\n for (var edge in resizeEdges) {\n resizeEdges[edge] = checkResizeEdge(edge,\n resizeOptions.edges[edge],\n page,\n interaction._eventTarget,\n element,\n rect,\n resizeOptions.margin || scope.margin);\n }\n\n resizeEdges.left = resizeEdges.left && !resizeEdges.right;\n resizeEdges.top = resizeEdges.top && !resizeEdges.bottom;\n\n shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom;\n }\n else {\n var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin),\n bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin);\n\n shouldResize = right || bottom;\n resizeAxes = (right? 'x' : '') + (bottom? 'y' : '');\n }\n }\n\n action = shouldResize\n ? 'resize'\n : scope.actionIsEnabled.drag && options.drag.enabled\n ? 'drag'\n : null;\n\n if (scope.actionIsEnabled.gesture\n && interaction.pointerIds.length >=2\n && !(interaction.dragging || interaction.resizing)) {\n action = 'gesture';\n }\n\n if (action) {\n return {\n name: action,\n axis: resizeAxes,\n edges: resizeEdges\n };\n }\n\n return null;\n }\n\n var InteractEvent = require('./InteractEvent');\n\n for (var i = 0, len = interactionListeners.length; i < len; i++) {\n var listenerName = interactionListeners[i];\n\n scope.listeners[listenerName] = doOnInteractions(listenerName);\n }\n\n // bound to the interactable context when a DOM event\n // listener is added to a selector interactable\n function delegateListener (event, useCapture) {\n var fakeEvent = {},\n delegated = scope.delegatedEvents[event.type],\n eventTarget = scope.getActualElement(event.path\n ? event.path[0]\n : event.target),\n element = eventTarget;\n\n useCapture = useCapture? true: false;\n\n // duplicate the event so that currentTarget can be changed\n for (var prop in event) {\n fakeEvent[prop] = event[prop];\n }\n\n fakeEvent.originalEvent = event;\n fakeEvent.preventDefault = preventOriginalDefault;\n\n // climb up document tree looking for selector matches\n while (utils.isElement(element)) {\n for (var i = 0; i < delegated.selectors.length; i++) {\n var selector = delegated.selectors[i],\n context = delegated.contexts[i];\n\n if (scope.matchesSelector(element, selector)\n && scope.nodeContains(context, eventTarget)\n && scope.nodeContains(context, element)) {\n\n var listeners = delegated.listeners[i];\n\n fakeEvent.currentTarget = element;\n\n for (var j = 0; j < listeners.length; j++) {\n if (listeners[j][1] === useCapture) {\n listeners[j][0](fakeEvent);\n }\n }\n }\n }\n\n element = scope.parentElement(element);\n }\n }\n\n function delegateUseCapture (event) {\n return delegateListener.call(this, event, true);\n }\n\n scope.interactables.indexOfElement = function indexOfElement (element, context) {\n context = context || scope.document;\n\n for (var i = 0; i < this.length; i++) {\n var interactable = this[i];\n\n if ((interactable.selector === element\n && (interactable._context === context))\n || (!interactable.selector && interactable._element === element)) {\n\n return i;\n }\n }\n return -1;\n };\n\n scope.interactables.get = function interactableGet (element, options) {\n return this[this.indexOfElement(element, options && options.context)];\n };\n\n scope.interactables.forEachSelector = function (callback) {\n for (var i = 0; i < this.length; i++) {\n var interactable = this[i];\n\n if (!interactable.selector) {\n continue;\n }\n\n var ret = callback(interactable, interactable.selector, interactable._context, i, this);\n\n if (ret !== undefined) {\n return ret;\n }\n }\n };\n\n /*\\\n * interact\n [ method ]\n *\n * The methods of this variable can be used to set elements as\n * interactables and also to change various default settings.\n *\n * Calling it as a function and passing an element or a valid CSS selector\n * string returns an Interactable object which has various methods to\n * configure it.\n *\n - element (Element | string) The HTML or SVG Element to interact with or CSS selector\n = (object) An @Interactable\n *\n > Usage\n | interact(document.getElementById('draggable')).draggable(true);\n |\n | var rectables = interact('rect');\n | rectables\n | .gesturable(true)\n | .on('gesturemove', function (event) {\n | // something cool...\n | })\n | .autoScroll(true);\n \\*/\n function interact (element, options) {\n return scope.interactables.get(element, options) || new Interactable(element, options);\n }\n\n /*\\\n * Interactable\n [ property ]\n **\n * Object type returned by @interact\n \\*/\n function Interactable (element, options) {\n this._element = element;\n this._iEvents = this._iEvents || {};\n\n var _window;\n\n if (scope.trySelector(element)) {\n this.selector = element;\n\n var context = options && options.context;\n\n _window = context? scope.getWindow(context) : scope.window;\n\n if (context && (_window.Node\n ? context instanceof _window.Node\n : (utils.isElement(context) || context === _window.document))) {\n\n this._context = context;\n }\n }\n else {\n _window = scope.getWindow(element);\n\n if (utils.isElement(element, _window)) {\n\n if (scope.PointerEvent) {\n events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown );\n events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover);\n }\n else {\n events.add(this._element, 'mousedown' , scope.listeners.pointerDown );\n events.add(this._element, 'mousemove' , scope.listeners.pointerHover);\n events.add(this._element, 'touchstart', scope.listeners.pointerDown );\n events.add(this._element, 'touchmove' , scope.listeners.pointerHover);\n }\n }\n }\n\n this._doc = _window.document;\n\n if (!scope.contains(scope.documents, this._doc)) {\n listenToDocument(this._doc);\n }\n\n scope.interactables.push(this);\n\n this.set(options);\n }\n\n Interactable.prototype = {\n setOnEvents: function (action, phases) {\n if (action === 'drop') {\n if (scope.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; }\n if (scope.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; }\n if (scope.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; }\n if (scope.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; }\n if (scope.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; }\n if (scope.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; }\n }\n else {\n action = 'on' + action;\n\n if (scope.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; }\n if (scope.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; }\n if (scope.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; }\n if (scope.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; }\n }\n\n return this;\n },\n\n /*\\\n * Interactable.draggable\n [ method ]\n *\n * Gets or sets whether drag actions can be performed on the\n * Interactable\n *\n = (boolean) Indicates if this can be the target of drag events\n | var isDraggable = interact('ul li').draggable();\n * or\n - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable)\n = (object) This Interactable\n | interact(element).draggable({\n | onstart: function (event) {},\n | onmove : function (event) {},\n | onend : function (event) {},\n |\n | // the axis in which the first movement must be\n | // for the drag sequence to start\n | // 'xy' by default - any direction\n | axis: 'x' || 'y' || 'xy',\n |\n | // max number of drags that can happen concurrently\n | // with elements of this Interactable. Infinity by default\n | max: Infinity,\n |\n | // max number of drags that can target the same element+Interactable\n | // 1 by default\n | maxPerElement: 2\n | });\n \\*/\n draggable: function (options) {\n if (scope.isObject(options)) {\n this.options.drag.enabled = options.enabled === false? false: true;\n this.setPerAction('drag', options);\n this.setOnEvents('drag', options);\n\n if (/^x$|^y$|^xy$/.test(options.axis)) {\n this.options.drag.axis = options.axis;\n }\n else if (options.axis === null) {\n delete this.options.drag.axis;\n }\n\n return this;\n }\n\n if (scope.isBool(options)) {\n this.options.drag.enabled = options;\n\n return this;\n }\n\n return this.options.drag;\n },\n\n setPerAction: function (action, options) {\n // for all the default per-action options\n for (var option in options) {\n // if this option exists for this action\n if (option in scope.defaultOptions[action]) {\n // if the option in the options arg is an object value\n if (scope.isObject(options[option])) {\n // duplicate the object\n this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]);\n\n if (scope.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) {\n this.options[action][option].enabled = options[option].enabled === false? false : true;\n }\n }\n else if (scope.isBool(options[option]) && scope.isObject(scope.defaultOptions.perAction[option])) {\n this.options[action][option].enabled = options[option];\n }\n else if (options[option] !== undefined) {\n // or if it's not undefined, do a plain assignment\n this.options[action][option] = options[option];\n }\n }\n }\n },\n\n /*\\\n * Interactable.dropzone\n [ method ]\n *\n * Returns or sets whether elements can be dropped onto this\n * Interactable to trigger drop events\n *\n * Dropzones can receive the following events:\n * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends\n * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone\n * - `dragmove` when a draggable that has entered the dropzone is moved\n * - `drop` when a draggable is dropped into this dropzone\n *\n * Use the `accept` option to allow only elements that match the given CSS selector or element.\n *\n * Use the `overlap` option to set how drops are checked for. The allowed values are:\n * - `'pointer'`, the pointer must be over the dropzone (default)\n * - `'center'`, the draggable element's center must be over the dropzone\n * - a number from 0-1 which is the `(intersection area) / (draggable area)`.\n * e.g. `0.5` for drop to happen when half of the area of the\n * draggable is over the dropzone\n *\n - options (boolean | object | null) #optional The new value to be set.\n | interact('.drop').dropzone({\n | accept: '.can-drop' || document.getElementById('single-drop'),\n | overlap: 'pointer' || 'center' || zeroToOne\n | }\n = (boolean | object) The current setting or this Interactable\n \\*/\n dropzone: function (options) {\n if (scope.isObject(options)) {\n this.options.drop.enabled = options.enabled === false? false: true;\n this.setOnEvents('drop', options);\n this.accept(options.accept);\n\n if (/^(pointer|center)$/.test(options.overlap)) {\n this.options.drop.overlap = options.overlap;\n }\n else if (scope.isNumber(options.overlap)) {\n this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);\n }\n\n return this;\n }\n\n if (scope.isBool(options)) {\n this.options.drop.enabled = options;\n\n return this;\n }\n\n return this.options.drop;\n },\n\n dropCheck: function (pointer, event, draggable, draggableElement, dropElement, rect) {\n var dropped = false;\n\n // if the dropzone has no rect (eg. display: none)\n // call the custom dropChecker or just return false\n if (!(rect = rect || this.getRect(dropElement))) {\n return (this.options.dropChecker\n ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement)\n : false);\n }\n\n var dropOverlap = this.options.drop.overlap;\n\n if (dropOverlap === 'pointer') {\n var page = utils.getPageXY(pointer),\n origin = scope.getOriginXY(draggable, draggableElement),\n horizontal,\n vertical;\n\n page.x += origin.x;\n page.y += origin.y;\n\n horizontal = (page.x > rect.left) && (page.x < rect.right);\n vertical = (page.y > rect.top ) && (page.y < rect.bottom);\n\n dropped = horizontal && vertical;\n }\n\n var dragRect = draggable.getRect(draggableElement);\n\n if (dropOverlap === 'center') {\n var cx = dragRect.left + dragRect.width / 2,\n cy = dragRect.top + dragRect.height / 2;\n\n dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;\n }\n\n if (scope.isNumber(dropOverlap)) {\n var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))\n * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))),\n overlapRatio = overlapArea / (dragRect.width * dragRect.height);\n\n dropped = overlapRatio >= dropOverlap;\n }\n\n if (this.options.dropChecker) {\n dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement);\n }\n\n return dropped;\n },\n\n /*\\\n * Interactable.dropChecker\n [ method ]\n *\n * Gets or sets the function used to check if a dragged element is\n * over this Interactable.\n *\n - checker (function) #optional The function that will be called when checking for a drop\n = (Function | Interactable) The checker function or this Interactable\n *\n * The checker function takes the following arguments:\n *\n - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag\n - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer\n - dropped (boolean) The value from the default drop check\n - dropzone (Interactable) The dropzone interactable\n - dropElement (Element) The dropzone element\n - draggable (Interactable) The Interactable being dragged\n - draggableElement (Element) The actual element that's being dragged\n *\n > Usage:\n | interact(target)\n | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent\n | event, // TouchEvent/PointerEvent/MouseEvent\n | dropped, // result of the default checker\n | dropzone, // dropzone Interactable\n | dropElement, // dropzone elemnt\n | draggable, // draggable Interactable\n | draggableElement) {// draggable element\n |\n | return dropped && event.target.hasAttribute('allow-drop');\n | }\n \\*/\n dropChecker: function (checker) {\n if (scope.isFunction(checker)) {\n this.options.dropChecker = checker;\n\n return this;\n }\n if (checker === null) {\n delete this.options.getRect;\n\n return this;\n }\n\n return this.options.dropChecker;\n },\n\n /*\\\n * Interactable.accept\n [ method ]\n *\n * Deprecated. add an `accept` property to the options object passed to\n * @Interactable.dropzone instead.\n *\n * Gets or sets the Element or CSS selector match that this\n * Interactable accepts if it is a dropzone.\n *\n - newValue (Element | string | null) #optional\n * If it is an Element, then only that element can be dropped into this dropzone.\n * If it is a string, the element being dragged must match it as a selector.\n * If it is null, the accept options is cleared - it accepts any element.\n *\n = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable\n \\*/\n accept: function (newValue) {\n if (utils.isElement(newValue)) {\n this.options.drop.accept = newValue;\n\n return this;\n }\n\n // test if it is a valid CSS selector\n if (scope.trySelector(newValue)) {\n this.options.drop.accept = newValue;\n\n return this;\n }\n\n if (newValue === null) {\n delete this.options.drop.accept;\n\n return this;\n }\n\n return this.options.drop.accept;\n },\n\n /*\\\n * Interactable.resizable\n [ method ]\n *\n * Gets or sets whether resize actions can be performed on the\n * Interactable\n *\n = (boolean) Indicates if this can be the target of resize elements\n | var isResizeable = interact('input[type=text]').resizable();\n * or\n - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable)\n = (object) This Interactable\n | interact(element).resizable({\n | onstart: function (event) {},\n | onmove : function (event) {},\n | onend : function (event) {},\n |\n | edges: {\n | top : true, // Use pointer coords to check for resize.\n | left : false, // Disable resizing from left edge.\n | bottom: '.resize-s',// Resize if pointer target matches selector\n | right : handleEl // Resize if pointer target is the given Element\n | },\n |\n | // a value of 'none' will limit the resize rect to a minimum of 0x0\n | // 'negate' will allow the rect to have negative width/height\n | // 'reposition' will keep the width/height positive by swapping\n | // the top and bottom edges and/or swapping the left and right edges\n | invert: 'none' || 'negate' || 'reposition'\n |\n | // limit multiple resizes.\n | // See the explanation in the @Interactable.draggable example\n | max: Infinity,\n | maxPerElement: 1,\n | });\n \\*/\n resizable: function (options) {\n if (scope.isObject(options)) {\n this.options.resize.enabled = options.enabled === false? false: true;\n this.setPerAction('resize', options);\n this.setOnEvents('resize', options);\n\n if (/^x$|^y$|^xy$/.test(options.axis)) {\n this.options.resize.axis = options.axis;\n }\n else if (options.axis === null) {\n this.options.resize.axis = scope.defaultOptions.resize.axis;\n }\n\n if (scope.isBool(options.square)) {\n this.options.resize.square = options.square;\n }\n\n return this;\n }\n if (scope.isBool(options)) {\n this.options.resize.enabled = options;\n\n return this;\n }\n return this.options.resize;\n },\n\n /*\\\n * Interactable.squareResize\n [ method ]\n *\n * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead\n *\n * Gets or sets whether resizing is forced 1:1 aspect\n *\n = (boolean) Current setting\n *\n * or\n *\n - newValue (boolean) #optional\n = (object) this Interactable\n \\*/\n squareResize: function (newValue) {\n if (scope.isBool(newValue)) {\n this.options.resize.square = newValue;\n\n return this;\n }\n\n if (newValue === null) {\n delete this.options.resize.square;\n\n return this;\n }\n\n return this.options.resize.square;\n },\n\n /*\\\n * Interactable.gesturable\n [ method ]\n *\n * Gets or sets whether multitouch gestures can be performed on the\n * Interactable's element\n *\n = (boolean) Indicates if this can be the target of gesture events\n | var isGestureable = interact(element).gesturable();\n * or\n - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable)\n = (object) this Interactable\n | interact(element).gesturable({\n | onstart: function (event) {},\n | onmove : function (event) {},\n | onend : function (event) {},\n |\n | // limit multiple gestures.\n | // See the explanation in @Interactable.draggable example\n | max: Infinity,\n | maxPerElement: 1,\n | });\n \\*/\n gesturable: function (options) {\n if (scope.isObject(options)) {\n this.options.gesture.enabled = options.enabled === false? false: true;\n this.setPerAction('gesture', options);\n this.setOnEvents('gesture', options);\n\n return this;\n }\n\n if (scope.isBool(options)) {\n this.options.gesture.enabled = options;\n\n return this;\n }\n\n return this.options.gesture;\n },\n\n /*\\\n * Interactable.autoScroll\n [ method ]\n **\n * Deprecated. Add an `autoscroll` property to the options object\n * passed to @Interactable.draggable or @Interactable.resizable instead.\n *\n * Returns or sets whether dragging and resizing near the edges of the\n * window/container trigger autoScroll for this Interactable\n *\n = (object) Object with autoScroll properties\n *\n * or\n *\n - options (object | boolean) #optional\n * options can be:\n * - an object with margin, distance and interval properties,\n * - true or false to enable or disable autoScroll or\n = (Interactable) this Interactable\n \\*/\n autoScroll: function (options) {\n if (scope.isObject(options)) {\n options = utils.extend({ actions: ['drag', 'resize']}, options);\n }\n else if (scope.isBool(options)) {\n options = { actions: ['drag', 'resize'], enabled: options };\n }\n\n return this.setOptions('autoScroll', options);\n },\n\n /*\\\n * Interactable.snap\n [ method ]\n **\n * Deprecated. Add a `snap` property to the options object passed\n * to @Interactable.draggable or @Interactable.resizable instead.\n *\n * Returns or sets if and how action coordinates are snapped. By\n * default, snapping is relative to the pointer coordinates. You can\n * change this by setting the\n * [`elementOrigin`](https://github.com/taye/interact.js/pull/72).\n **\n = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled\n **\n * or\n **\n - options (object | boolean | null) #optional\n = (Interactable) this Interactable\n > Usage\n | interact(document.querySelector('#thing')).snap({\n | targets: [\n | // snap to this specific point\n | {\n | x: 100,\n | y: 100,\n | range: 25\n | },\n | // give this function the x and y page coords and snap to the object returned\n | function (x, y) {\n | return {\n | x: x,\n | y: (75 + 50 * Math.sin(x * 0.04)),\n | range: 40\n | };\n | },\n | // create a function that snaps to a grid\n | interact.createSnapGrid({\n | x: 50,\n | y: 50,\n | range: 10, // optional\n | offset: { x: 5, y: 10 } // optional\n | })\n | ],\n | // do not snap during normal movement.\n | // Instead, trigger only one snapped move event\n | // immediately before the end event.\n | endOnly: true,\n |\n | relativePoints: [\n | { x: 0, y: 0 }, // snap relative to the top left of the element\n | { x: 1, y: 1 }, // and also to the bottom right\n | ], \n |\n | // offset the snap target coordinates\n | // can be an object with x/y or 'startCoords'\n | offset: { x: 50, y: 50 }\n | }\n | });\n \\*/\n snap: function (options) {\n var ret = this.setOptions('snap', options);\n\n if (ret === this) { return this; }\n\n return ret.drag;\n },\n\n setOptions: function (option, options) {\n var actions = options && scope.isArray(options.actions)\n ? options.actions\n : ['drag'];\n\n var i;\n\n if (scope.isObject(options) || scope.isBool(options)) {\n for (i = 0; i < actions.length; i++) {\n var action = /resize/.test(actions[i])? 'resize' : actions[i];\n\n if (!scope.isObject(this.options[action])) { continue; }\n\n var thisOption = this.options[action][option];\n\n if (scope.isObject(options)) {\n utils.extend(thisOption, options);\n thisOption.enabled = options.enabled === false? false: true;\n\n if (option === 'snap') {\n if (thisOption.mode === 'grid') {\n thisOption.targets = [\n interact.createSnapGrid(utils.extend({\n offset: thisOption.gridOffset || { x: 0, y: 0 }\n }, thisOption.grid || {}))\n ];\n }\n else if (thisOption.mode === 'anchor') {\n thisOption.targets = thisOption.anchors;\n }\n else if (thisOption.mode === 'path') {\n thisOption.targets = thisOption.paths;\n }\n\n if ('elementOrigin' in options) {\n thisOption.relativePoints = [options.elementOrigin];\n }\n }\n }\n else if (scope.isBool(options)) {\n thisOption.enabled = options;\n }\n }\n\n return this;\n }\n\n var ret = {},\n allActions = ['drag', 'resize', 'gesture'];\n\n for (i = 0; i < allActions.length; i++) {\n if (option in scope.defaultOptions[allActions[i]]) {\n ret[allActions[i]] = this.options[allActions[i]][option];\n }\n }\n\n return ret;\n },\n\n\n /*\\\n * Interactable.inertia\n [ method ]\n **\n * Deprecated. Add an `inertia` property to the options object passed\n * to @Interactable.draggable or @Interactable.resizable instead.\n *\n * Returns or sets if and how events continue to run after the pointer is released\n **\n = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled\n **\n * or\n **\n - options (object | boolean | null) #optional\n = (Interactable) this Interactable\n > Usage\n | // enable and use default settings\n | interact(element).inertia(true);\n |\n | // enable and use custom settings\n | interact(element).inertia({\n | // value greater than 0\n | // high values slow the object down more quickly\n | resistance : 16,\n |\n | // the minimum launch speed (pixels per second) that results in inertia start\n | minSpeed : 200,\n |\n | // inertia will stop when the object slows down to this speed\n | endSpeed : 20,\n |\n | // boolean; should actions be resumed when the pointer goes down during inertia\n | allowResume : true,\n |\n | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy\n | zeroResumeDelta: false,\n |\n | // if snap/restrict are set to be endOnly and inertia is enabled, releasing\n | // the pointer without triggering inertia will animate from the release\n | // point to the snaped/restricted point in the given amount of time (ms)\n | smoothEndDuration: 300,\n |\n | // an array of action types that can have inertia (no gesture)\n | actions : ['drag', 'resize']\n | });\n |\n | // reset custom settings and use all defaults\n | interact(element).inertia(null);\n \\*/\n inertia: function (options) {\n var ret = this.setOptions('inertia', options);\n\n if (ret === this) { return this; }\n\n return ret.drag;\n },\n\n getAction: function (pointer, event, interaction, element) {\n var action = this.defaultActionChecker(pointer, interaction, element);\n\n if (this.options.actionChecker) {\n return this.options.actionChecker(pointer, event, action, this, element, interaction);\n }\n\n return action;\n },\n\n defaultActionChecker: defaultActionChecker,\n\n /*\\\n * Interactable.actionChecker\n [ method ]\n *\n * Gets or sets the function used to check action to be performed on\n * pointerDown\n *\n - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props.\n = (Function | Interactable) The checker function or this Interactable\n *\n | interact('.resize-drag')\n | .resizable(true)\n | .draggable(true)\n | .actionChecker(function (pointer, event, action, interactable, element, interaction) {\n |\n | if (interact.matchesSelector(event.target, '.drag-handle') {\n | // force drag with handle target\n | action.name = drag;\n | }\n | else {\n | // resize from the top and right edges\n | action.name = 'resize';\n | action.edges = { top: true, right: true };\n | }\n |\n | return action;\n | });\n \\*/\n actionChecker: function (checker) {\n if (scope.isFunction(checker)) {\n this.options.actionChecker = checker;\n\n return this;\n }\n\n if (checker === null) {\n delete this.options.actionChecker;\n\n return this;\n }\n\n return this.options.actionChecker;\n },\n\n /*\\\n * Interactable.getRect\n [ method ]\n *\n * The default function to get an Interactables bounding rect. Can be\n * overridden using @Interactable.rectChecker.\n *\n - element (Element) #optional The element to measure.\n = (object) The object's bounding rectangle.\n o {\n o top : 0,\n o left : 0,\n o bottom: 0,\n o right : 0,\n o width : 0,\n o height: 0\n o }\n \\*/\n getRect: function rectCheck (element) {\n element = element || this._element;\n\n if (this.selector && !(utils.isElement(element))) {\n element = this._context.querySelector(this.selector);\n }\n\n return scope.getElementRect(element);\n },\n\n /*\\\n * Interactable.rectChecker\n [ method ]\n *\n * Returns or sets the function used to calculate the interactable's\n * element's rectangle\n *\n - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect\n = (function | object) The checker function or this Interactable\n \\*/\n rectChecker: function (checker) {\n if (scope.isFunction(checker)) {\n this.getRect = checker;\n\n return this;\n }\n\n if (checker === null) {\n delete this.options.getRect;\n\n return this;\n }\n\n return this.getRect;\n },\n\n /*\\\n * Interactable.styleCursor\n [ method ]\n *\n * Returns or sets whether the action that would be performed when the\n * mouse on the element are checked on `mousemove` so that the cursor\n * may be styled appropriately\n *\n - newValue (boolean) #optional\n = (boolean | Interactable) The current setting or this Interactable\n \\*/\n styleCursor: function (newValue) {\n if (scope.isBool(newValue)) {\n this.options.styleCursor = newValue;\n\n return this;\n }\n\n if (newValue === null) {\n delete this.options.styleCursor;\n\n return this;\n }\n\n return this.options.styleCursor;\n },\n\n /*\\\n * Interactable.preventDefault\n [ method ]\n *\n * Returns or sets whether to prevent the browser's default behaviour\n * in response to pointer events. Can be set to:\n * - `'always'` to always prevent\n * - `'never'` to never prevent\n * - `'auto'` to let interact.js try to determine what would be best\n *\n - newValue (string) #optional `true`, `false` or `'auto'`\n = (string | Interactable) The current setting or this Interactable\n \\*/\n preventDefault: function (newValue) {\n if (/^(always|never|auto)$/.test(newValue)) {\n this.options.preventDefault = newValue;\n return this;\n }\n\n if (scope.isBool(newValue)) {\n this.options.preventDefault = newValue? 'always' : 'never';\n return this;\n }\n\n return this.options.preventDefault;\n },\n\n /*\\\n * Interactable.origin\n [ method ]\n *\n * Gets or sets the origin of the Interactable's element. The x and y\n * of the origin will be subtracted from action event coordinates.\n *\n - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector\n * OR\n - origin (Element) #optional An HTML or SVG Element whose rect will be used\n **\n = (object) The current origin or this Interactable\n \\*/\n origin: function (newValue) {\n if (scope.trySelector(newValue)) {\n this.options.origin = newValue;\n return this;\n }\n else if (scope.isObject(newValue)) {\n this.options.origin = newValue;\n return this;\n }\n\n return this.options.origin;\n },\n\n /*\\\n * Interactable.deltaSource\n [ method ]\n *\n * Returns or sets the mouse coordinate types used to calculate the\n * movement of the pointer.\n *\n - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work\n = (string | object) The current deltaSource or this Interactable\n \\*/\n deltaSource: function (newValue) {\n if (newValue === 'page' || newValue === 'client') {\n this.options.deltaSource = newValue;\n\n return this;\n }\n\n return this.options.deltaSource;\n },\n\n /*\\\n * Interactable.restrict\n [ method ]\n **\n * Deprecated. Add a `restrict` property to the options object passed to\n * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead.\n *\n * Returns or sets the rectangles within which actions on this\n * interactable (after snap calculations) are restricted. By default,\n * restricting is relative to the pointer coordinates. You can change\n * this by setting the\n * [`elementRect`](https://github.com/taye/interact.js/pull/72).\n **\n - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self'\n = (object) The current restrictions object or this Interactable\n **\n | interact(element).restrict({\n | // the rect will be `interact.getElementRect(element.parentNode)`\n | drag: element.parentNode,\n |\n | // x and y are relative to the the interactable's origin\n | resize: { x: 100, y: 100, width: 200, height: 200 }\n | })\n |\n | interact('.draggable').restrict({\n | // the rect will be the selected element's parent\n | drag: 'parent',\n |\n | // do not restrict during normal movement.\n | // Instead, trigger only one restricted move event\n | // immediately before the end event.\n | endOnly: true,\n |\n | // https://github.com/taye/interact.js/pull/72#issue-41813493\n | elementRect: { top: 0, left: 0, bottom: 1, right: 1 }\n | });\n \\*/\n restrict: function (options) {\n if (!scope.isObject(options)) {\n return this.setOptions('restrict', options);\n }\n\n var actions = ['drag', 'resize', 'gesture'],\n ret;\n\n for (var i = 0; i < actions.length; i++) {\n var action = actions[i];\n\n if (action in options) {\n var perAction = utils.extend({\n actions: [action],\n restriction: options[action]\n }, options);\n\n ret = this.setOptions('restrict', perAction);\n }\n }\n\n return ret;\n },\n\n /*\\\n * Interactable.context\n [ method ]\n *\n * Gets the selector context Node of the Interactable. The default is `window.document`.\n *\n = (Node) The context Node of this Interactable\n **\n \\*/\n context: function () {\n return this._context;\n },\n\n _context: scope.document,\n\n /*\\\n * Interactable.ignoreFrom\n [ method ]\n *\n * If the target of the `mousedown`, `pointerdown` or `touchstart`\n * event or any of it's parents match the given CSS selector or\n * Element, no drag/resize/gesture is started.\n *\n - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements\n = (string | Element | object) The current ignoreFrom value or this Interactable\n **\n | interact(element, { ignoreFrom: document.getElementById('no-action') });\n | // or\n | interact(element).ignoreFrom('input, textarea, a');\n \\*/\n ignoreFrom: function (newValue) {\n if (scope.trySelector(newValue)) { // CSS selector to match event.target\n this.options.ignoreFrom = newValue;\n return this;\n }\n\n if (utils.isElement(newValue)) { // specific element\n this.options.ignoreFrom = newValue;\n return this;\n }\n\n return this.options.ignoreFrom;\n },\n\n /*\\\n * Interactable.allowFrom\n [ method ]\n *\n * A drag/resize/gesture is started only If the target of the\n * `mousedown`, `pointerdown` or `touchstart` event or any of it's\n * parents match the given CSS selector or Element.\n *\n - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element\n = (string | Element | object) The current allowFrom value or this Interactable\n **\n | interact(element, { allowFrom: document.getElementById('drag-handle') });\n | // or\n | interact(element).allowFrom('.handle');\n \\*/\n allowFrom: function (newValue) {\n if (scope.trySelector(newValue)) { // CSS selector to match event.target\n this.options.allowFrom = newValue;\n return this;\n }\n\n if (utils.isElement(newValue)) { // specific element\n this.options.allowFrom = newValue;\n return this;\n }\n\n return this.options.allowFrom;\n },\n\n /*\\\n * Interactable.element\n [ method ]\n *\n * If this is not a selector Interactable, it returns the element this\n * interactable represents\n *\n = (Element) HTML / SVG Element\n \\*/\n element: function () {\n return this._element;\n },\n\n /*\\\n * Interactable.fire\n [ method ]\n *\n * Calls listeners for the given InteractEvent type bound globally\n * and directly to this Interactable\n *\n - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable\n = (Interactable) this Interactable\n \\*/\n fire: function (iEvent) {\n if (!(iEvent && iEvent.type) || !scope.contains(scope.eventTypes, iEvent.type)) {\n return this;\n }\n\n var listeners,\n i,\n len,\n onEvent = 'on' + iEvent.type,\n funcName = '';\n\n // Interactable#on() listeners\n if (iEvent.type in this._iEvents) {\n listeners = this._iEvents[iEvent.type];\n\n for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {\n funcName = listeners[i].name;\n listeners[i](iEvent);\n }\n }\n\n // interactable.onevent listener\n if (scope.isFunction(this[onEvent])) {\n funcName = this[onEvent].name;\n this[onEvent](iEvent);\n }\n\n // interact.on() listeners\n if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) {\n\n for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {\n funcName = listeners[i].name;\n listeners[i](iEvent);\n }\n }\n\n return this;\n },\n\n /*\\\n * Interactable.on\n [ method ]\n *\n * Binds a listener for an InteractEvent or DOM event.\n *\n - eventType (string | array | object) The types of events to listen for\n - listener (function) The function to be called on the given event(s)\n - useCapture (boolean) #optional useCapture flag for addEventListener\n = (object) This Interactable\n \\*/\n on: function (eventType, listener, useCapture) {\n var i;\n\n if (scope.isString(eventType) && eventType.search(' ') !== -1) {\n eventType = eventType.trim().split(/ +/);\n }\n\n if (scope.isArray(eventType)) {\n for (i = 0; i < eventType.length; i++) {\n this.on(eventType[i], listener, useCapture);\n }\n\n return this;\n }\n\n if (scope.isObject(eventType)) {\n for (var prop in eventType) {\n this.on(prop, eventType[prop], listener);\n }\n\n return this;\n }\n\n if (eventType === 'wheel') {\n eventType = scope.wheelEvent;\n }\n\n // convert to boolean\n useCapture = useCapture? true: false;\n\n if (scope.contains(scope.eventTypes, eventType)) {\n // if this type of event was never bound to this Interactable\n if (!(eventType in this._iEvents)) {\n this._iEvents[eventType] = [listener];\n }\n else {\n this._iEvents[eventType].push(listener);\n }\n }\n // delegated event for selector\n else if (this.selector) {\n if (!scope.delegatedEvents[eventType]) {\n scope.delegatedEvents[eventType] = {\n selectors: [],\n contexts : [],\n listeners: []\n };\n\n // add delegate listener functions\n for (i = 0; i < scope.documents.length; i++) {\n events.add(scope.documents[i], eventType, delegateListener);\n events.add(scope.documents[i], eventType, delegateUseCapture, true);\n }\n }\n\n var delegated = scope.delegatedEvents[eventType],\n index;\n\n for (index = delegated.selectors.length - 1; index >= 0; index--) {\n if (delegated.selectors[index] === this.selector\n && delegated.contexts[index] === this._context) {\n break;\n }\n }\n\n if (index === -1) {\n index = delegated.selectors.length;\n\n delegated.selectors.push(this.selector);\n delegated.contexts .push(this._context);\n delegated.listeners.push([]);\n }\n\n // keep listener and useCapture flag\n delegated.listeners[index].push([listener, useCapture]);\n }\n else {\n events.add(this._element, eventType, listener, useCapture);\n }\n\n return this;\n },\n\n /*\\\n * Interactable.off\n [ method ]\n *\n * Removes an InteractEvent or DOM event listener\n *\n - eventType (string | array | object) The types of events that were listened for\n - listener (function) The listener function to be removed\n - useCapture (boolean) #optional useCapture flag for removeEventListener\n = (object) This Interactable\n \\*/\n off: function (eventType, listener, useCapture) {\n var i;\n\n if (scope.isString(eventType) && eventType.search(' ') !== -1) {\n eventType = eventType.trim().split(/ +/);\n }\n\n if (scope.isArray(eventType)) {\n for (i = 0; i < eventType.length; i++) {\n this.off(eventType[i], listener, useCapture);\n }\n\n return this;\n }\n\n if (scope.isObject(eventType)) {\n for (var prop in eventType) {\n this.off(prop, eventType[prop], listener);\n }\n\n return this;\n }\n\n var eventList,\n index = -1;\n\n // convert to boolean\n useCapture = useCapture? true: false;\n\n if (eventType === 'wheel') {\n eventType = scope.wheelEvent;\n }\n\n // if it is an action event type\n if (scope.contains(scope.eventTypes, eventType)) {\n eventList = this._iEvents[eventType];\n\n if (eventList && (index = scope.indexOf(eventList, listener)) !== -1) {\n this._iEvents[eventType].splice(index, 1);\n }\n }\n // delegated event\n else if (this.selector) {\n var delegated = scope.delegatedEvents[eventType],\n matchFound = false;\n\n if (!delegated) { return this; }\n\n // count from last index of delegated to 0\n for (index = delegated.selectors.length - 1; index >= 0; index--) {\n // look for matching selector and context Node\n if (delegated.selectors[index] === this.selector\n && delegated.contexts[index] === this._context) {\n\n var listeners = delegated.listeners[index];\n\n // each item of the listeners array is an array: [function, useCaptureFlag]\n for (i = listeners.length - 1; i >= 0; i--) {\n var fn = listeners[i][0],\n useCap = listeners[i][1];\n\n // check if the listener functions and useCapture flags match\n if (fn === listener && useCap === useCapture) {\n // remove the listener from the array of listeners\n listeners.splice(i, 1);\n\n // if all listeners for this interactable have been removed\n // remove the interactable from the delegated arrays\n if (!listeners.length) {\n delegated.selectors.splice(index, 1);\n delegated.contexts .splice(index, 1);\n delegated.listeners.splice(index, 1);\n\n // remove delegate function from context\n events.remove(this._context, eventType, delegateListener);\n events.remove(this._context, eventType, delegateUseCapture, true);\n\n // remove the arrays if they are empty\n if (!delegated.selectors.length) {\n scope.delegatedEvents[eventType] = null;\n }\n }\n\n // only remove one listener\n matchFound = true;\n break;\n }\n }\n\n if (matchFound) { break; }\n }\n }\n }\n // remove listener from this Interatable's element\n else {\n events.remove(this._element, eventType, listener, useCapture);\n }\n\n return this;\n },\n\n /*\\\n * Interactable.set\n [ method ]\n *\n * Reset the options of this Interactable\n - options (object) The new settings to apply\n = (object) This Interactablw\n \\*/\n set: function (options) {\n if (!scope.isObject(options)) {\n options = {};\n }\n\n this.options = utils.extend({}, scope.defaultOptions.base);\n\n var i,\n actions = ['drag', 'drop', 'resize', 'gesture'],\n methods = ['draggable', 'dropzone', 'resizable', 'gesturable'],\n perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {});\n\n for (i = 0; i < actions.length; i++) {\n var action = actions[i];\n\n this.options[action] = utils.extend({}, scope.defaultOptions[action]);\n\n this.setPerAction(action, perActions);\n\n this[methods[i]](options[action]);\n }\n\n var settings = [\n 'accept', 'actionChecker', 'allowFrom', 'deltaSource',\n 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault',\n 'rectChecker'\n ];\n\n for (i = 0, len = settings.length; i < len; i++) {\n var setting = settings[i];\n\n this.options[setting] = scope.defaultOptions.base[setting];\n\n if (setting in options) {\n this[setting](options[setting]);\n }\n }\n\n return this;\n },\n\n /*\\\n * Interactable.unset\n [ method ]\n *\n * Remove this interactable from the list of interactables and remove\n * it's drag, drop, resize and gesture capabilities\n *\n = (object) @interact\n \\*/\n unset: function () {\n events.remove(this._element, 'all');\n\n if (!scope.isString(this.selector)) {\n events.remove(this, 'all');\n if (this.options.styleCursor) {\n this._element.style.cursor = '';\n }\n }\n else {\n // remove delegated events\n for (var type in scope.delegatedEvents) {\n var delegated = scope.delegatedEvents[type];\n\n for (var i = 0; i < delegated.selectors.length; i++) {\n if (delegated.selectors[i] === this.selector\n && delegated.contexts[i] === this._context) {\n\n delegated.selectors.splice(i, 1);\n delegated.contexts .splice(i, 1);\n delegated.listeners.splice(i, 1);\n\n // remove the arrays if they are empty\n if (!delegated.selectors.length) {\n scope.delegatedEvents[type] = null;\n }\n }\n\n events.remove(this._context, type, delegateListener);\n events.remove(this._context, type, delegateUseCapture, true);\n\n break;\n }\n }\n }\n\n this.dropzone(false);\n\n scope.interactables.splice(scope.indexOf(scope.interactables, this), 1);\n\n return interact;\n }\n };\n\n Interactable.prototype.snap = utils.warnOnce(Interactable.prototype.snap,\n 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping');\n Interactable.prototype.restrict = utils.warnOnce(Interactable.prototype.restrict,\n 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction');\n Interactable.prototype.inertia = utils.warnOnce(Interactable.prototype.inertia,\n 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia');\n Interactable.prototype.autoScroll = utils.warnOnce(Interactable.prototype.autoScroll,\n 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll');\n Interactable.prototype.squareResize = utils.warnOnce(Interactable.prototype.squareResize,\n 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square');\n\n /*\\\n * interact.isSet\n [ method ]\n *\n * Check if an element has been set\n - element (Element) The Element being searched for\n = (boolean) Indicates if the element or CSS selector was previously passed to interact\n \\*/\n interact.isSet = function(element, options) {\n return scope.interactables.indexOfElement(element, options && options.context) !== -1;\n };\n\n /*\\\n * interact.on\n [ method ]\n *\n * Adds a global listener for an InteractEvent or adds a DOM event to\n * `document`\n *\n - type (string | array | object) The types of events to listen for\n - listener (function) The function to be called on the given event(s)\n - useCapture (boolean) #optional useCapture flag for addEventListener\n = (object) interact\n \\*/\n interact.on = function (type, listener, useCapture) {\n if (scope.isString(type) && type.search(' ') !== -1) {\n type = type.trim().split(/ +/);\n }\n\n if (scope.isArray(type)) {\n for (var i = 0; i < type.length; i++) {\n interact.on(type[i], listener, useCapture);\n }\n\n return interact;\n }\n\n if (scope.isObject(type)) {\n for (var prop in type) {\n interact.on(prop, type[prop], listener);\n }\n\n return interact;\n }\n\n // if it is an InteractEvent type, add listener to globalEvents\n if (scope.contains(scope.eventTypes, type)) {\n // if this type of event was never bound\n if (!scope.globalEvents[type]) {\n scope.globalEvents[type] = [listener];\n }\n else {\n scope.globalEvents[type].push(listener);\n }\n }\n // If non InteractEvent type, addEventListener to document\n else {\n events.add(scope.document, type, listener, useCapture);\n }\n\n return interact;\n };\n\n /*\\\n * interact.off\n [ method ]\n *\n * Removes a global InteractEvent listener or DOM event from `document`\n *\n - type (string | array | object) The types of events that were listened for\n - listener (function) The listener function to be removed\n - useCapture (boolean) #optional useCapture flag for removeEventListener\n = (object) interact\n \\*/\n interact.off = function (type, listener, useCapture) {\n if (scope.isString(type) && type.search(' ') !== -1) {\n type = type.trim().split(/ +/);\n }\n\n if (scope.isArray(type)) {\n for (var i = 0; i < type.length; i++) {\n interact.off(type[i], listener, useCapture);\n }\n\n return interact;\n }\n\n if (scope.isObject(type)) {\n for (var prop in type) {\n interact.off(prop, type[prop], listener);\n }\n\n return interact;\n }\n\n if (!scope.contains(scope.eventTypes, type)) {\n events.remove(scope.document, type, listener, useCapture);\n }\n else {\n var index;\n\n if (type in scope.globalEvents\n && (index = scope.indexOf(scope.globalEvents[type], listener)) !== -1) {\n scope.globalEvents[type].splice(index, 1);\n }\n }\n\n return interact;\n };\n\n /*\\\n * interact.enableDragging\n [ method ]\n *\n * Deprecated.\n *\n * Returns or sets whether dragging is enabled for any Interactables\n *\n - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables\n = (boolean | object) The current setting or interact\n \\*/\n interact.enableDragging = utils.warnOnce(function (newValue) {\n if (newValue !== null && newValue !== undefined) {\n scope.actionIsEnabled.drag = newValue;\n\n return interact;\n }\n return scope.actionIsEnabled.drag;\n }, 'interact.enableDragging is deprecated and will soon be removed.');\n\n /*\\\n * interact.enableResizing\n [ method ]\n *\n * Deprecated.\n *\n * Returns or sets whether resizing is enabled for any Interactables\n *\n - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables\n = (boolean | object) The current setting or interact\n \\*/\n interact.enableResizing = utils.warnOnce(function (newValue) {\n if (newValue !== null && newValue !== undefined) {\n scope.actionIsEnabled.resize = newValue;\n\n return interact;\n }\n return scope.actionIsEnabled.resize;\n }, 'interact.enableResizing is deprecated and will soon be removed.');\n\n /*\\\n * interact.enableGesturing\n [ method ]\n *\n * Deprecated.\n *\n * Returns or sets whether gesturing is enabled for any Interactables\n *\n - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables\n = (boolean | object) The current setting or interact\n \\*/\n interact.enableGesturing = utils.warnOnce(function (newValue) {\n if (newValue !== null && newValue !== undefined) {\n scope.actionIsEnabled.gesture = newValue;\n\n return interact;\n }\n return scope.actionIsEnabled.gesture;\n }, 'interact.enableGesturing is deprecated and will soon be removed.');\n\n interact.eventTypes = scope.eventTypes;\n\n /*\\\n * interact.debug\n [ method ]\n *\n * Returns debugging data\n = (object) An object with properties that outline the current state and expose internal functions and variables\n \\*/\n interact.debug = function () {\n var interaction = scope.interactions[0] || new Interaction();\n\n return {\n interactions : scope.interactions,\n target : interaction.target,\n dragging : interaction.dragging,\n resizing : interaction.resizing,\n gesturing : interaction.gesturing,\n prepared : interaction.prepared,\n matches : interaction.matches,\n matchElements : interaction.matchElements,\n\n prevCoords : interaction.prevCoords,\n startCoords : interaction.startCoords,\n\n pointerIds : interaction.pointerIds,\n pointers : interaction.pointers,\n addPointer : scope.listeners.addPointer,\n removePointer : scope.listeners.removePointer,\n recordPointer : scope.listeners.recordPointer,\n\n snap : interaction.snapStatus,\n restrict : interaction.restrictStatus,\n inertia : interaction.inertiaStatus,\n\n downTime : interaction.downTimes[0],\n downEvent : interaction.downEvent,\n downPointer : interaction.downPointer,\n prevEvent : interaction.prevEvent,\n\n Interactable : Interactable,\n interactables : scope.interactables,\n pointerIsDown : interaction.pointerIsDown,\n defaultOptions : scope.defaultOptions,\n defaultActionChecker : defaultActionChecker,\n\n actionCursors : scope.actionCursors,\n dragMove : scope.listeners.dragMove,\n resizeMove : scope.listeners.resizeMove,\n gestureMove : scope.listeners.gestureMove,\n pointerUp : scope.listeners.pointerUp,\n pointerDown : scope.listeners.pointerDown,\n pointerMove : scope.listeners.pointerMove,\n pointerHover : scope.listeners.pointerHover,\n\n eventTypes : scope.eventTypes,\n\n events : events,\n globalEvents : scope.globalEvents,\n delegatedEvents : scope.delegatedEvents\n };\n };\n\n // expose the functions used to calculate multi-touch properties\n interact.getTouchAverage = utils.touchAverage;\n interact.getTouchBBox = utils.touchBBox;\n interact.getTouchDistance = utils.touchDistance;\n interact.getTouchAngle = utils.touchAngle;\n\n interact.getElementRect = scope.getElementRect;\n interact.matchesSelector = scope.matchesSelector;\n interact.closest = scope.closest;\n\n /*\\\n * interact.margin\n [ method ]\n *\n * Returns or sets the margin for autocheck resizing used in\n * @Interactable.getAction. That is the distance from the bottom and right\n * edges of an element clicking in which will start resizing\n *\n - newValue (number) #optional\n = (number | interact) The current margin value or interact\n \\*/\n interact.margin = function (newvalue) {\n if (scope.isNumber(newvalue)) {\n scope.margin = newvalue;\n\n return interact;\n }\n return scope.margin;\n };\n\n /*\\\n * interact.supportsTouch\n [ method ]\n *\n = (boolean) Whether or not the browser supports touch input\n \\*/\n interact.supportsTouch = function () {\n return browser.supportsTouch;\n };\n\n /*\\\n * interact.supportsPointerEvent\n [ method ]\n *\n = (boolean) Whether or not the browser supports PointerEvents\n \\*/\n interact.supportsPointerEvent = function () {\n return browser.supportsPointerEvent;\n };\n\n /*\\\n * interact.stop\n [ method ]\n *\n * Cancels all interactions (end events are not fired)\n *\n - event (Event) An event on which to call preventDefault()\n = (object) interact\n \\*/\n interact.stop = function (event) {\n for (var i = scope.interactions.length - 1; i > 0; i--) {\n scope.interactions[i].stop(event);\n }\n\n return interact;\n };\n\n /*\\\n * interact.dynamicDrop\n [ method ]\n *\n * Returns or sets whether the dimensions of dropzone elements are\n * calculated on every dragmove or only on dragstart for the default\n * dropChecker\n *\n - newValue (boolean) #optional True to check on each move. False to check only before start\n = (boolean | interact) The current setting or interact\n \\*/\n interact.dynamicDrop = function (newValue) {\n if (scope.isBool(newValue)) {\n //if (dragging && dynamicDrop !== newValue && !newValue) {\n //calcRects(dropzones);\n //}\n\n scope.dynamicDrop = newValue;\n\n return interact;\n }\n return scope.dynamicDrop;\n };\n\n /*\\\n * interact.pointerMoveTolerance\n [ method ]\n * Returns or sets the distance the pointer must be moved before an action\n * sequence occurs. This also affects tolerance for tap events.\n *\n - newValue (number) #optional The movement from the start position must be greater than this value\n = (number | Interactable) The current setting or interact\n \\*/\n interact.pointerMoveTolerance = function (newValue) {\n if (scope.isNumber(newValue)) {\n scope.pointerMoveTolerance = newValue;\n\n return this;\n }\n\n return scope.pointerMoveTolerance;\n };\n\n /*\\\n * interact.maxInteractions\n [ method ]\n **\n * Returns or sets the maximum number of concurrent interactions allowed.\n * By default only 1 interaction is allowed at a time (for backwards\n * compatibility). To allow multiple interactions on the same Interactables\n * and elements, you need to enable it in the draggable, resizable and\n * gesturable `'max'` and `'maxPerElement'` options.\n **\n - newValue (number) #optional Any number. newValue <= 0 means no interactions.\n \\*/\n interact.maxInteractions = function (newValue) {\n if (scope.isNumber(newValue)) {\n scope.maxInteractions = newValue;\n\n return this;\n }\n\n return scope.maxInteractions;\n };\n\n interact.createSnapGrid = function (grid) {\n return function (x, y) {\n var offsetX = 0,\n offsetY = 0;\n\n if (scope.isObject(grid.offset)) {\n offsetX = grid.offset.x;\n offsetY = grid.offset.y;\n }\n\n var gridx = Math.round((x - offsetX) / grid.x),\n gridy = Math.round((y - offsetY) / grid.y),\n\n newX = gridx * grid.x + offsetX,\n newY = gridy * grid.y + offsetY;\n\n return {\n x: newX,\n y: newY,\n range: grid.range\n };\n };\n };\n\n function endAllInteractions (event) {\n for (var i = 0; i < scope.interactions.length; i++) {\n scope.interactions[i].pointerEnd(event, event);\n }\n }\n\n function listenToDocument (doc) {\n if (scope.contains(scope.documents, doc)) { return; }\n\n var win = doc.defaultView || doc.parentWindow;\n\n // add delegate event listener\n for (var eventType in scope.delegatedEvents) {\n events.add(doc, eventType, delegateListener);\n events.add(doc, eventType, delegateUseCapture, true);\n }\n\n if (scope.PointerEvent) {\n if (scope.PointerEvent === win.MSPointerEvent) {\n scope.pEventTypes = {\n up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',\n out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' };\n }\n else {\n scope.pEventTypes = {\n up: 'pointerup', down: 'pointerdown', over: 'pointerover',\n out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' };\n }\n\n events.add(doc, scope.pEventTypes.down , scope.listeners.selectorDown );\n events.add(doc, scope.pEventTypes.move , scope.listeners.pointerMove );\n events.add(doc, scope.pEventTypes.over , scope.listeners.pointerOver );\n events.add(doc, scope.pEventTypes.out , scope.listeners.pointerOut );\n events.add(doc, scope.pEventTypes.up , scope.listeners.pointerUp );\n events.add(doc, scope.pEventTypes.cancel, scope.listeners.pointerCancel);\n\n // autoscroll\n events.add(doc, scope.pEventTypes.move, scope.listeners.autoScrollMove);\n }\n else {\n events.add(doc, 'mousedown', scope.listeners.selectorDown);\n events.add(doc, 'mousemove', scope.listeners.pointerMove );\n events.add(doc, 'mouseup' , scope.listeners.pointerUp );\n events.add(doc, 'mouseover', scope.listeners.pointerOver );\n events.add(doc, 'mouseout' , scope.listeners.pointerOut );\n\n events.add(doc, 'touchstart' , scope.listeners.selectorDown );\n events.add(doc, 'touchmove' , scope.listeners.pointerMove );\n events.add(doc, 'touchend' , scope.listeners.pointerUp );\n events.add(doc, 'touchcancel', scope.listeners.pointerCancel);\n\n // autoscroll\n events.add(doc, 'mousemove', scope.listeners.autoScrollMove);\n events.add(doc, 'touchmove', scope.listeners.autoScrollMove);\n }\n\n events.add(win, 'blur', endAllInteractions);\n\n try {\n if (win.frameElement) {\n var parentDoc = win.frameElement.ownerDocument,\n parentWindow = parentDoc.defaultView;\n\n events.add(parentDoc , 'mouseup' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'touchend' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'touchcancel' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'pointerup' , scope.listeners.pointerEnd);\n events.add(parentDoc , 'MSPointerUp' , scope.listeners.pointerEnd);\n events.add(parentWindow, 'blur' , endAllInteractions );\n }\n }\n catch (error) {\n interact.windowParentError = error;\n }\n\n if (events.useAttachEvent) {\n // For IE's lack of Event#preventDefault\n events.add(doc, 'selectstart', function (event) {\n var interaction = scope.interactions[0];\n\n if (interaction.currentAction()) {\n interaction.checkAndPreventDefault(event);\n }\n });\n\n // For IE's bad dblclick event sequence\n events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick'));\n }\n\n scope.documents.push(doc);\n }\n\n listenToDocument(scope.document);\n\n scope.interact = interact;\n scope.Interactable = Interactable;\n scope.Interaction = Interaction;\n scope.InteractEvent = InteractEvent;\n\n module.exports = interact;\n\n},{\"./InteractEvent\":2,\"./Interaction\":3,\"./autoScroll\":4,\"./defaultOptions\":5,\"./scope\":6,\"./utils\":13,\"./utils/events\":10,\"./utils/window\":18}],2:[function(require,module,exports){\n'use strict';\n\nvar scope = require('./scope');\nvar utils = require('./utils');\n\nfunction InteractEvent (interaction, event, action, phase, element, related) {\n var client,\n page,\n target = interaction.target,\n snapStatus = interaction.snapStatus,\n restrictStatus = interaction.restrictStatus,\n pointers = interaction.pointers,\n deltaSource = (target && target.options || scope.defaultOptions).deltaSource,\n sourceX = deltaSource + 'X',\n sourceY = deltaSource + 'Y',\n options = target? target.options: scope.defaultOptions,\n origin = scope.getOriginXY(target, element),\n starting = phase === 'start',\n ending = phase === 'end',\n coords = starting? interaction.startCoords : interaction.curCoords;\n\n element = element || interaction.element;\n\n page = utils.extend({}, coords.page);\n client = utils.extend({}, coords.client);\n\n page.x -= origin.x;\n page.y -= origin.y;\n\n client.x -= origin.x;\n client.y -= origin.y;\n\n var relativePoints = options[action].snap && options[action].snap.relativePoints ;\n\n if (scope.checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) {\n this.snap = {\n range : snapStatus.range,\n locked : snapStatus.locked,\n x : snapStatus.snappedX,\n y : snapStatus.snappedY,\n realX : snapStatus.realX,\n realY : snapStatus.realY,\n dx : snapStatus.dx,\n dy : snapStatus.dy\n };\n\n if (snapStatus.locked) {\n page.x += snapStatus.dx;\n page.y += snapStatus.dy;\n client.x += snapStatus.dx;\n client.y += snapStatus.dy;\n }\n }\n\n if (scope.checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) {\n page.x += restrictStatus.dx;\n page.y += restrictStatus.dy;\n client.x += restrictStatus.dx;\n client.y += restrictStatus.dy;\n\n this.restrict = {\n dx: restrictStatus.dx,\n dy: restrictStatus.dy\n };\n }\n\n this.pageX = page.x;\n this.pageY = page.y;\n this.clientX = client.x;\n this.clientY = client.y;\n\n this.x0 = interaction.startCoords.page.x - origin.x;\n this.y0 = interaction.startCoords.page.y - origin.y;\n this.clientX0 = interaction.startCoords.client.x - origin.x;\n this.clientY0 = interaction.startCoords.client.y - origin.y;\n this.ctrlKey = event.ctrlKey;\n this.altKey = event.altKey;\n this.shiftKey = event.shiftKey;\n this.metaKey = event.metaKey;\n this.button = event.button;\n this.target = element;\n this.t0 = interaction.downTimes[0];\n this.type = action + (phase || '');\n\n this.interaction = interaction;\n this.interactable = target;\n\n var inertiaStatus = interaction.inertiaStatus;\n\n if (inertiaStatus.active) {\n this.detail = 'inertia';\n }\n\n if (related) {\n this.relatedTarget = related;\n }\n\n // end event dx, dy is difference between start and end points\n if (ending) {\n if (deltaSource === 'client') {\n this.dx = client.x - interaction.startCoords.client.x;\n this.dy = client.y - interaction.startCoords.client.y;\n }\n else {\n this.dx = page.x - interaction.startCoords.page.x;\n this.dy = page.y - interaction.startCoords.page.y;\n }\n }\n else if (starting) {\n this.dx = 0;\n this.dy = 0;\n }\n // copy properties from previousmove if starting inertia\n else if (phase === 'inertiastart') {\n this.dx = interaction.prevEvent.dx;\n this.dy = interaction.prevEvent.dy;\n }\n else {\n if (deltaSource === 'client') {\n this.dx = client.x - interaction.prevEvent.clientX;\n this.dy = client.y - interaction.prevEvent.clientY;\n }\n else {\n this.dx = page.x - interaction.prevEvent.pageX;\n this.dy = page.y - interaction.prevEvent.pageY;\n }\n }\n if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia'\n && !inertiaStatus.active\n && options[action].inertia && options[action].inertia.zeroResumeDelta) {\n\n inertiaStatus.resumeDx += this.dx;\n inertiaStatus.resumeDy += this.dy;\n\n this.dx = this.dy = 0;\n }\n\n if (action === 'resize' && interaction.resizeAxes) {\n if (options.resize.square) {\n if (interaction.resizeAxes === 'y') {\n this.dx = this.dy;\n }\n else {\n this.dy = this.dx;\n }\n this.axes = 'xy';\n }\n else {\n this.axes = interaction.resizeAxes;\n\n if (interaction.resizeAxes === 'x') {\n this.dy = 0;\n }\n else if (interaction.resizeAxes === 'y') {\n this.dx = 0;\n }\n }\n }\n else if (action === 'gesture') {\n this.touches = [pointers[0], pointers[1]];\n\n if (starting) {\n this.distance = utils.touchDistance(pointers, deltaSource);\n this.box = utils.touchBBox(pointers);\n this.scale = 1;\n this.ds = 0;\n this.angle = utils.touchAngle(pointers, undefined, deltaSource);\n this.da = 0;\n }\n else if (ending || event instanceof InteractEvent) {\n this.distance = interaction.prevEvent.distance;\n this.box = interaction.prevEvent.box;\n this.scale = interaction.prevEvent.scale;\n this.ds = this.scale - 1;\n this.angle = interaction.prevEvent.angle;\n this.da = this.angle - interaction.gesture.startAngle;\n }\n else {\n this.distance = utils.touchDistance(pointers, deltaSource);\n this.box = utils.touchBBox(pointers);\n this.scale = this.distance / interaction.gesture.startDistance;\n this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource);\n\n this.ds = this.scale - interaction.gesture.prevScale;\n this.da = this.angle - interaction.gesture.prevAngle;\n }\n }\n\n if (starting) {\n this.timeStamp = interaction.downTimes[0];\n this.dt = 0;\n this.duration = 0;\n this.speed = 0;\n this.velocityX = 0;\n this.velocityY = 0;\n }\n else if (phase === 'inertiastart') {\n this.timeStamp = interaction.prevEvent.timeStamp;\n this.dt = interaction.prevEvent.dt;\n this.duration = interaction.prevEvent.duration;\n this.speed = interaction.prevEvent.speed;\n this.velocityX = interaction.prevEvent.velocityX;\n this.velocityY = interaction.prevEvent.velocityY;\n }\n else {\n this.timeStamp = new Date().getTime();\n this.dt = this.timeStamp - interaction.prevEvent.timeStamp;\n this.duration = this.timeStamp - interaction.downTimes[0];\n\n if (event instanceof InteractEvent) {\n var dx = this[sourceX] - interaction.prevEvent[sourceX],\n dy = this[sourceY] - interaction.prevEvent[sourceY],\n dt = this.dt / 1000;\n\n this.speed = utils.hypot(dx, dy) / dt;\n this.velocityX = dx / dt;\n this.velocityY = dy / dt;\n }\n // if normal move or end event, use previous user event coords\n else {\n // speed and velocity in pixels per second\n this.speed = interaction.pointerDelta[deltaSource].speed;\n this.velocityX = interaction.pointerDelta[deltaSource].vx;\n this.velocityY = interaction.pointerDelta[deltaSource].vy;\n }\n }\n\n if ((ending || phase === 'inertiastart')\n && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) {\n\n var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI,\n overlap = 22.5;\n\n if (angle < 0) {\n angle += 360;\n }\n\n var left = 135 - overlap <= angle && angle < 225 + overlap,\n up = 225 - overlap <= angle && angle < 315 + overlap,\n\n right = !left && (315 - overlap <= angle || angle < 45 + overlap),\n down = !up && 45 - overlap <= angle && angle < 135 + overlap;\n\n this.swipe = {\n up : up,\n down : down,\n left : left,\n right: right,\n angle: angle,\n speed: interaction.prevEvent.speed,\n velocity: {\n x: interaction.prevEvent.velocityX,\n y: interaction.prevEvent.velocityY\n }\n };\n }\n}\n\nInteractEvent.prototype = {\n preventDefault: utils.blank,\n stopImmediatePropagation: function () {\n this.immediatePropagationStopped = this.propagationStopped = true;\n },\n stopPropagation: function () {\n this.propagationStopped = true;\n }\n};\n\nmodule.exports = InteractEvent;\n\n},{\"./scope\":6,\"./utils\":13}],3:[function(require,module,exports){\n'use strict';\n\nvar scope = require('./scope');\nvar utils = require('./utils');\nvar animationFrame = utils.raf;\nvar InteractEvent = require('./InteractEvent');\nvar events = require('./utils/events');\nvar browser = require('./utils/browser');\n\nfunction Interaction () {\n this.target = null; // current interactable being interacted with\n this.element = null; // the target element of the interactable\n this.dropTarget = null; // the dropzone a drag target might be dropped into\n this.dropElement = null; // the element at the time of checking\n this.prevDropTarget = null; // the dropzone that was recently dragged away from\n this.prevDropElement = null; // the element at the time of checking\n\n this.prepared = { // action that's ready to be fired on next move event\n name : null,\n axis : null,\n edges: null\n };\n\n this.matches = []; // all selectors that are matched by target element\n this.matchElements = []; // corresponding elements\n\n this.inertiaStatus = {\n active : false,\n smoothEnd : false,\n\n startEvent: null,\n upCoords: {},\n\n xe: 0, ye: 0,\n sx: 0, sy: 0,\n\n t0: 0,\n vx0: 0, vys: 0,\n duration: 0,\n\n resumeDx: 0,\n resumeDy: 0,\n\n lambda_v0: 0,\n one_ve_v0: 0,\n i : null\n };\n\n if (scope.isFunction(Function.prototype.bind)) {\n this.boundInertiaFrame = this.inertiaFrame.bind(this);\n this.boundSmoothEndFrame = this.smoothEndFrame.bind(this);\n }\n else {\n var that = this;\n\n this.boundInertiaFrame = function () { return that.inertiaFrame(); };\n this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); };\n }\n\n this.activeDrops = {\n dropzones: [], // the dropzones that are mentioned below\n elements : [], // elements of dropzones that accept the target draggable\n rects : [] // the rects of the elements mentioned above\n };\n\n // keep track of added pointers\n this.pointers = [];\n this.pointerIds = [];\n this.downTargets = [];\n this.downTimes = [];\n this.holdTimers = [];\n\n // Previous native pointer move event coordinates\n this.prevCoords = {\n page : { x: 0, y: 0 },\n client : { x: 0, y: 0 },\n timeStamp: 0\n };\n // current native pointer move event coordinates\n this.curCoords = {\n page : { x: 0, y: 0 },\n client : { x: 0, y: 0 },\n timeStamp: 0\n };\n\n // Starting InteractEvent pointer coordinates\n this.startCoords = {\n page : { x: 0, y: 0 },\n client : { x: 0, y: 0 },\n timeStamp: 0\n };\n\n // Change in coordinates and time of the pointer\n this.pointerDelta = {\n page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },\n client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },\n timeStamp: 0\n };\n\n this.downEvent = null; // pointerdown/mousedown/touchstart event\n this.downPointer = {};\n\n this._eventTarget = null;\n this._curEventTarget = null;\n\n this.prevEvent = null; // previous action event\n this.tapTime = 0; // time of the most recent tap event\n this.prevTap = null;\n\n this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 };\n this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 };\n this.snapOffsets = [];\n\n this.gesture = {\n start: { x: 0, y: 0 },\n\n startDistance: 0, // distance between two touches of touchStart\n prevDistance : 0,\n distance : 0,\n\n scale: 1, // gesture.distance / gesture.startDistance\n\n startAngle: 0, // angle of line joining two touches\n prevAngle : 0 // angle of the previous gesture event\n };\n\n this.snapStatus = {\n x : 0, y : 0,\n dx : 0, dy : 0,\n realX : 0, realY : 0,\n snappedX: 0, snappedY: 0,\n targets : [],\n locked : false,\n changed : false\n };\n\n this.restrictStatus = {\n dx : 0, dy : 0,\n restrictedX: 0, restrictedY: 0,\n snap : null,\n restricted : false,\n changed : false\n };\n\n this.restrictStatus.snap = this.snapStatus;\n\n this.pointerIsDown = false;\n this.pointerWasMoved = false;\n this.gesturing = false;\n this.dragging = false;\n this.resizing = false;\n this.resizeAxes = 'xy';\n\n this.mouse = false;\n\n scope.interactions.push(this);\n}\n\n// Check if action is enabled globally and the current target supports it\n// If so, return the validated action. Otherwise, return null\nfunction validateAction (action, interactable) {\n if (!scope.isObject(action)) { return null; }\n\n var actionName = action.name,\n options = interactable.options;\n\n if (( (actionName === 'resize' && options.resize.enabled )\n || (actionName === 'drag' && options.drag.enabled )\n || (actionName === 'gesture' && options.gesture.enabled))\n && scope.actionIsEnabled[actionName]) {\n\n if (actionName === 'resize' || actionName === 'resizeyx') {\n actionName = 'resizexy';\n }\n\n return action;\n }\n return null;\n}\n\nfunction getActionCursor (action) {\n var cursor = '';\n\n if (action.name === 'drag') {\n cursor = scope.actionCursors.drag;\n }\n if (action.name === 'resize') {\n if (action.axis) {\n cursor = scope.actionCursors[action.name + action.axis];\n }\n else if (action.edges) {\n var cursorKey = 'resize',\n edgeNames = ['top', 'bottom', 'left', 'right'];\n\n for (var i = 0; i < 4; i++) {\n if (action.edges[edgeNames[i]]) {\n cursorKey += edgeNames[i];\n }\n }\n\n cursor = scope.actionCursors[cursorKey];\n }\n }\n\n return cursor;\n}\n\nfunction preventOriginalDefault () {\n this.originalEvent.preventDefault();\n}\n\nInteraction.prototype = {\n getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); },\n getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); },\n setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); },\n\n pointerOver: function (pointer, event, eventTarget) {\n if (this.prepared.name || !this.mouse) { return; }\n\n var curMatches = [],\n curMatchElements = [],\n prevTargetElement = this.element;\n\n this.addPointer(pointer);\n\n if (this.target\n && (scope.testIgnore(this.target, this.element, eventTarget)\n || !scope.testAllow(this.target, this.element, eventTarget))) {\n // if the eventTarget should be ignored or shouldn't be allowed\n // clear the previous target\n this.target = null;\n this.element = null;\n this.matches = [];\n this.matchElements = [];\n }\n\n var elementInteractable = scope.interactables.get(eventTarget),\n elementAction = (elementInteractable\n && !scope.testIgnore(elementInteractable, eventTarget, eventTarget)\n && scope.testAllow(elementInteractable, eventTarget, eventTarget)\n && validateAction(\n elementInteractable.getAction(pointer, event, this, eventTarget),\n elementInteractable));\n\n if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) {\n elementAction = null;\n }\n\n function pushCurMatches (interactable, selector) {\n if (interactable\n && scope.inContext(interactable, eventTarget)\n && !scope.testIgnore(interactable, eventTarget, eventTarget)\n && scope.testAllow(interactable, eventTarget, eventTarget)\n && scope.matchesSelector(eventTarget, selector)) {\n\n curMatches.push(interactable);\n curMatchElements.push(eventTarget);\n }\n }\n\n if (elementAction) {\n this.target = elementInteractable;\n this.element = eventTarget;\n this.matches = [];\n this.matchElements = [];\n }\n else {\n scope.interactables.forEachSelector(pushCurMatches);\n\n if (this.validateSelector(pointer, event, curMatches, curMatchElements)) {\n this.matches = curMatches;\n this.matchElements = curMatchElements;\n\n this.pointerHover(pointer, event, this.matches, this.matchElements);\n events.add(eventTarget,\n scope.PointerEvent? scope.pEventTypes.move : 'mousemove',\n scope.listeners.pointerHover);\n }\n else if (this.target) {\n if (scope.nodeContains(prevTargetElement, eventTarget)) {\n this.pointerHover(pointer, event, this.matches, this.matchElements);\n events.add(this.element,\n scope.PointerEvent? scope.pEventTypes.move : 'mousemove',\n scope.listeners.pointerHover);\n }\n else {\n this.target = null;\n this.element = null;\n this.matches = [];\n this.matchElements = [];\n }\n }\n }\n },\n\n // Check what action would be performed on pointerMove target if a mouse\n // button were pressed and change the cursor accordingly\n pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) {\n var target = this.target;\n\n if (!this.prepared.name && this.mouse) {\n\n var action;\n\n // update pointer coords for defaultActionChecker to use\n this.setEventXY(this.curCoords, pointer);\n\n if (matches) {\n action = this.validateSelector(pointer, event, matches, matchElements);\n }\n else if (target) {\n action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target);\n }\n\n if (target && target.options.styleCursor) {\n if (action) {\n target._doc.documentElement.style.cursor = getActionCursor(action);\n }\n else {\n target._doc.documentElement.style.cursor = '';\n }\n }\n }\n else if (this.prepared.name) {\n this.checkAndPreventDefault(event, target, this.element);\n }\n },\n\n pointerOut: function (pointer, event, eventTarget) {\n if (this.prepared.name) { return; }\n\n // Remove temporary event listeners for selector Interactables\n if (!scope.interactables.get(eventTarget)) {\n events.remove(eventTarget,\n scope.PointerEvent? scope.pEventTypes.move : 'mousemove',\n scope.listeners.pointerHover);\n }\n\n if (this.target && this.target.options.styleCursor && !this.interacting()) {\n this.target._doc.documentElement.style.cursor = '';\n }\n },\n\n selectorDown: function (pointer, event, eventTarget, curEventTarget) {\n var that = this,\n // copy event to be used in timeout for IE8\n eventCopy = events.useAttachEvent? utils.extend({}, event) : event,\n element = eventTarget,\n pointerIndex = this.addPointer(pointer),\n action;\n\n this.holdTimers[pointerIndex] = setTimeout(function () {\n that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget);\n }, scope.defaultOptions._holdDuration);\n\n this.pointerIsDown = true;\n\n // Check if the down event hits the current inertia target\n if (this.inertiaStatus.active && this.target.selector) {\n // climb up the DOM tree from the event target\n while (utils.isElement(element)) {\n\n // if this element is the current inertia target element\n if (element === this.element\n // and the prospective action is the same as the ongoing one\n && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) {\n\n // stop inertia so that the next move will be a normal one\n animationFrame.cancel(this.inertiaStatus.i);\n this.inertiaStatus.active = false;\n\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n return;\n }\n element = scope.parentElement(element);\n }\n }\n\n // do nothing if interacting\n if (this.interacting()) {\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n return;\n }\n\n function pushMatches (interactable, selector, context) {\n var elements = scope.ie8MatchesSelector\n ? context.querySelectorAll(selector)\n : undefined;\n\n if (scope.inContext(interactable, element)\n && !scope.testIgnore(interactable, element, eventTarget)\n && scope.testAllow(interactable, element, eventTarget)\n && scope.matchesSelector(element, selector, elements)) {\n\n that.matches.push(interactable);\n that.matchElements.push(element);\n }\n }\n\n // update pointer coords for defaultActionChecker to use\n this.setEventXY(this.curCoords, pointer);\n this.downEvent = event;\n\n while (utils.isElement(element) && !action) {\n this.matches = [];\n this.matchElements = [];\n\n scope.interactables.forEachSelector(pushMatches);\n\n action = this.validateSelector(pointer, event, this.matches, this.matchElements);\n element = scope.parentElement(element);\n }\n\n if (action) {\n this.prepared.name = action.name;\n this.prepared.axis = action.axis;\n this.prepared.edges = action.edges;\n\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n\n return this.pointerDown(pointer, event, eventTarget, curEventTarget, action);\n }\n else {\n // do these now since pointerDown isn't being called from here\n this.downTimes[pointerIndex] = new Date().getTime();\n this.downTargets[pointerIndex] = eventTarget;\n utils.extend(this.downPointer, pointer);\n\n utils.copyCoords(this.prevCoords, this.curCoords);\n this.pointerWasMoved = false;\n }\n\n this.collectEventTargets(pointer, event, eventTarget, 'down');\n },\n\n // Determine action to be performed on next pointerMove and add appropriate\n // style and event Listeners\n pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) {\n if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) {\n this.checkAndPreventDefault(event, this.target, this.element);\n\n return;\n }\n\n this.pointerIsDown = true;\n this.downEvent = event;\n\n var pointerIndex = this.addPointer(pointer),\n action;\n\n // If it is the second touch of a multi-touch gesture, keep the target\n // the same if a target was set by the first touch\n // Otherwise, set the target if there is no action prepared\n if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) {\n\n var interactable = scope.interactables.get(curEventTarget);\n\n if (interactable\n && !scope.testIgnore(interactable, curEventTarget, eventTarget)\n && scope.testAllow(interactable, curEventTarget, eventTarget)\n && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget))\n && scope.withinInteractionLimit(interactable, curEventTarget, action)) {\n this.target = interactable;\n this.element = curEventTarget;\n }\n }\n\n var target = this.target,\n options = target && target.options;\n\n if (target && (forceAction || !this.prepared.name)) {\n action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element);\n\n this.setEventXY(this.startCoords);\n\n if (!action) { return; }\n\n if (options.styleCursor) {\n target._doc.documentElement.style.cursor = getActionCursor(action);\n }\n\n this.resizeAxes = action.name === 'resize'? action.axis : null;\n\n if (action === 'gesture' && this.pointerIds.length < 2) {\n action = null;\n }\n\n this.prepared.name = action.name;\n this.prepared.axis = action.axis;\n this.prepared.edges = action.edges;\n\n this.snapStatus.snappedX = this.snapStatus.snappedY =\n this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN;\n\n this.downTimes[pointerIndex] = new Date().getTime();\n this.downTargets[pointerIndex] = eventTarget;\n utils.extend(this.downPointer, pointer);\n\n this.setEventXY(this.prevCoords);\n this.pointerWasMoved = false;\n\n this.checkAndPreventDefault(event, target, this.element);\n }\n // if inertia is active try to resume action\n else if (this.inertiaStatus.active\n && curEventTarget === this.element\n && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) {\n\n animationFrame.cancel(this.inertiaStatus.i);\n this.inertiaStatus.active = false;\n\n this.checkAndPreventDefault(event, target, this.element);\n }\n },\n\n setModifications: function (coords, preEnd) {\n var target = this.target,\n shouldMove = true,\n shouldSnap = scope.checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd),\n shouldRestrict = scope.checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd);\n\n if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; }\n if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; }\n\n if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) {\n shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed;\n }\n else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) {\n shouldMove = false;\n }\n\n return shouldMove;\n },\n\n setStartOffsets: function (action, interactable, element) {\n var rect = interactable.getRect(element),\n origin = scope.getOriginXY(interactable, element),\n snap = interactable.options[this.prepared.name].snap,\n restrict = interactable.options[this.prepared.name].restrict,\n width, height;\n\n if (rect) {\n this.startOffset.left = this.startCoords.page.x - rect.left;\n this.startOffset.top = this.startCoords.page.y - rect.top;\n\n this.startOffset.right = rect.right - this.startCoords.page.x;\n this.startOffset.bottom = rect.bottom - this.startCoords.page.y;\n\n if ('width' in rect) { width = rect.width; }\n else { width = rect.right - rect.left; }\n if ('height' in rect) { height = rect.height; }\n else { height = rect.bottom - rect.top; }\n }\n else {\n this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0;\n }\n\n this.snapOffsets.splice(0);\n\n var snapOffset = snap && snap.offset === 'startCoords'\n ? {\n x: this.startCoords.page.x - origin.x,\n y: this.startCoords.page.y - origin.y\n }\n : snap && snap.offset || { x: 0, y: 0 };\n\n if (rect && snap && snap.relativePoints && snap.relativePoints.length) {\n for (var i = 0; i < snap.relativePoints.length; i++) {\n this.snapOffsets.push({\n x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x,\n y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y\n });\n }\n }\n else {\n this.snapOffsets.push(snapOffset);\n }\n\n if (rect && restrict.elementRect) {\n this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left);\n this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top);\n\n this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right));\n this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom));\n }\n else {\n this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0;\n }\n },\n\n /*\\\n * Interaction.start\n [ method ]\n *\n * Start an action with the given Interactable and Element as tartgets. The\n * action must be enabled for the target Interactable and an appropriate number\n * of pointers must be held down – 1 for drag/resize, 2 for gesture.\n *\n * Use it with `interactable.able({ manualStart: false })` to always\n * [start actions manually](https://github.com/taye/interact.js/issues/114)\n *\n - action (object) The action to be performed - drag, resize, etc.\n - interactable (Interactable) The Interactable to target\n - element (Element) The DOM Element to target\n = (object) interact\n **\n | interact(target)\n | .draggable({\n | // disable the default drag start by down->move\n | manualStart: true\n | })\n | // start dragging after the user holds the pointer down\n | .on('hold', function (event) {\n | var interaction = event.interaction;\n |\n | if (!interaction.interacting()) {\n | interaction.start({ name: 'drag' },\n | event.interactable,\n | event.currentTarget);\n | }\n | });\n \\*/\n start: function (action, interactable, element) {\n if (this.interacting()\n || !this.pointerIsDown\n || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) {\n return;\n }\n\n // if this interaction had been removed after stopping\n // add it back\n if (scope.indexOf(scope.interactions, this) === -1) {\n scope.interactions.push(this);\n }\n\n this.prepared.name = action.name;\n this.prepared.axis = action.axis;\n this.prepared.edges = action.edges;\n this.target = interactable;\n this.element = element;\n\n this.setEventXY(this.startCoords);\n this.setStartOffsets(action.name, interactable, element);\n this.setModifications(this.startCoords.page);\n\n this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent);\n },\n\n pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) {\n this.recordPointer(pointer);\n\n this.setEventXY(this.curCoords, (pointer instanceof InteractEvent)\n ? this.inertiaStatus.startEvent\n : undefined);\n\n var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x\n && this.curCoords.page.y === this.prevCoords.page.y\n && this.curCoords.client.x === this.prevCoords.client.x\n && this.curCoords.client.y === this.prevCoords.client.y);\n\n var dx, dy,\n pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n // register movement greater than pointerMoveTolerance\n if (this.pointerIsDown && !this.pointerWasMoved) {\n dx = this.curCoords.client.x - this.startCoords.client.x;\n dy = this.curCoords.client.y - this.startCoords.client.y;\n\n this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance;\n }\n\n if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) {\n if (this.pointerIsDown) {\n clearTimeout(this.holdTimers[pointerIndex]);\n }\n\n this.collectEventTargets(pointer, event, eventTarget, 'move');\n }\n\n if (!this.pointerIsDown) { return; }\n\n if (duplicateMove && this.pointerWasMoved && !preEnd) {\n this.checkAndPreventDefault(event, this.target, this.element);\n return;\n }\n\n // set pointer coordinate, time changes and speeds\n utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);\n\n if (!this.prepared.name) { return; }\n\n if (this.pointerWasMoved\n // ignore movement while inertia is active\n && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) {\n\n // if just starting an action, calculate the pointer speed now\n if (!this.interacting()) {\n utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);\n\n // check if a drag is in the correct axis\n if (this.prepared.name === 'drag') {\n var absX = Math.abs(dx),\n absY = Math.abs(dy),\n targetAxis = this.target.options.drag.axis,\n axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy');\n\n // if the movement isn't in the axis of the interactable\n if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) {\n // cancel the prepared action\n this.prepared.name = null;\n\n // then try to get a drag from another ineractable\n\n var element = eventTarget;\n\n // check element interactables\n while (utils.isElement(element)) {\n var elementInteractable = scope.interactables.get(element);\n\n if (elementInteractable\n && elementInteractable !== this.target\n && !elementInteractable.options.drag.manualStart\n && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag'\n && scope.checkAxis(axis, elementInteractable)) {\n\n this.prepared.name = 'drag';\n this.target = elementInteractable;\n this.element = element;\n break;\n }\n\n element = scope.parentElement(element);\n }\n\n // if there's no drag from element interactables,\n // check the selector interactables\n if (!this.prepared.name) {\n var thisInteraction = this;\n\n var getDraggable = function (interactable, selector, context) {\n var elements = scope.ie8MatchesSelector\n ? context.querySelectorAll(selector)\n : undefined;\n\n if (interactable === thisInteraction.target) { return; }\n\n if (scope.inContext(interactable, eventTarget)\n && !interactable.options.drag.manualStart\n && !scope.testIgnore(interactable, element, eventTarget)\n && scope.testAllow(interactable, element, eventTarget)\n && scope.matchesSelector(element, selector, elements)\n && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag'\n && scope.checkAxis(axis, interactable)\n && scope.withinInteractionLimit(interactable, element, 'drag')) {\n\n return interactable;\n }\n };\n\n element = eventTarget;\n\n while (utils.isElement(element)) {\n var selectorInteractable = scope.interactables.forEachSelector(getDraggable);\n\n if (selectorInteractable) {\n this.prepared.name = 'drag';\n this.target = selectorInteractable;\n this.element = element;\n break;\n }\n\n element = scope.parentElement(element);\n }\n }\n }\n }\n }\n\n var starting = !!this.prepared.name && !this.interacting();\n\n if (starting\n && (this.target.options[this.prepared.name].manualStart\n || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) {\n this.stop(event);\n return;\n }\n\n if (this.prepared.name && this.target) {\n if (starting) {\n this.start(this.prepared, this.target, this.element);\n }\n\n var shouldMove = this.setModifications(this.curCoords.page, preEnd);\n\n // move if snapping or restriction doesn't prevent it\n if (shouldMove || starting) {\n this.prevEvent = this[this.prepared.name + 'Move'](event);\n }\n\n this.checkAndPreventDefault(event, this.target, this.element);\n }\n }\n\n utils.copyCoords(this.prevCoords, this.curCoords);\n\n if (this.dragging || this.resizing) {\n this.autoScrollMove(pointer);\n }\n },\n\n dragStart: function (event) {\n var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element);\n\n this.dragging = true;\n this.target.fire(dragEvent);\n\n // reset active dropzones\n this.activeDrops.dropzones = [];\n this.activeDrops.elements = [];\n this.activeDrops.rects = [];\n\n if (!this.dynamicDrop) {\n this.setActiveDrops(this.element);\n }\n\n var dropEvents = this.getDropEvents(event, dragEvent);\n\n if (dropEvents.activate) {\n this.fireActiveDrops(dropEvents.activate);\n }\n\n return dragEvent;\n },\n\n dragMove: function (event) {\n var target = this.target,\n dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element),\n draggableElement = this.element,\n drop = this.getDrop(event, draggableElement);\n\n this.dropTarget = drop.dropzone;\n this.dropElement = drop.element;\n\n var dropEvents = this.getDropEvents(event, dragEvent);\n\n target.fire(dragEvent);\n\n if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }\n if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }\n if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); }\n\n this.prevDropTarget = this.dropTarget;\n this.prevDropElement = this.dropElement;\n\n return dragEvent;\n },\n\n resizeStart: function (event) {\n var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element);\n\n if (this.prepared.edges) {\n var startRect = this.target.getRect(this.element);\n\n if (this.target.options.resize.square) {\n var squareEdges = utils.extend({}, this.prepared.edges);\n\n squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom);\n squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right );\n squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top );\n squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left );\n\n this.prepared._squareEdges = squareEdges;\n }\n else {\n this.prepared._squareEdges = null;\n }\n\n this.resizeRects = {\n start : startRect,\n current : utils.extend({}, startRect),\n restricted: utils.extend({}, startRect),\n previous : utils.extend({}, startRect),\n delta : {\n left: 0, right : 0, width : 0,\n top : 0, bottom: 0, height: 0\n }\n };\n\n resizeEvent.rect = this.resizeRects.restricted;\n resizeEvent.deltaRect = this.resizeRects.delta;\n }\n\n this.target.fire(resizeEvent);\n\n this.resizing = true;\n\n return resizeEvent;\n },\n\n resizeMove: function (event) {\n var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element);\n\n var edges = this.prepared.edges,\n invert = this.target.options.resize.invert,\n invertible = invert === 'reposition' || invert === 'negate';\n\n if (edges) {\n var dx = resizeEvent.dx,\n dy = resizeEvent.dy,\n\n start = this.resizeRects.start,\n current = this.resizeRects.current,\n restricted = this.resizeRects.restricted,\n delta = this.resizeRects.delta,\n previous = utils.extend(this.resizeRects.previous, restricted);\n\n if (this.target.options.resize.square) {\n var originalEdges = edges;\n\n edges = this.prepared._squareEdges;\n\n if ((originalEdges.left && originalEdges.bottom)\n || (originalEdges.right && originalEdges.top)) {\n dy = -dx;\n }\n else if (originalEdges.left || originalEdges.right) { dy = dx; }\n else if (originalEdges.top || originalEdges.bottom) { dx = dy; }\n }\n\n // update the 'current' rect without modifications\n if (edges.top ) { current.top += dy; }\n if (edges.bottom) { current.bottom += dy; }\n if (edges.left ) { current.left += dx; }\n if (edges.right ) { current.right += dx; }\n\n if (invertible) {\n // if invertible, copy the current rect\n utils.extend(restricted, current);\n\n if (invert === 'reposition') {\n // swap edge values if necessary to keep width/height positive\n var swap;\n\n if (restricted.top > restricted.bottom) {\n swap = restricted.top;\n\n restricted.top = restricted.bottom;\n restricted.bottom = swap;\n }\n if (restricted.left > restricted.right) {\n swap = restricted.left;\n\n restricted.left = restricted.right;\n restricted.right = swap;\n }\n }\n }\n else {\n // if not invertible, restrict to minimum of 0x0 rect\n restricted.top = Math.min(current.top, start.bottom);\n restricted.bottom = Math.max(current.bottom, start.top);\n restricted.left = Math.min(current.left, start.right);\n restricted.right = Math.max(current.right, start.left);\n }\n\n restricted.width = restricted.right - restricted.left;\n restricted.height = restricted.bottom - restricted.top ;\n\n for (var edge in restricted) {\n delta[edge] = restricted[edge] - previous[edge];\n }\n\n resizeEvent.edges = this.prepared.edges;\n resizeEvent.rect = restricted;\n resizeEvent.deltaRect = delta;\n }\n\n this.target.fire(resizeEvent);\n\n return resizeEvent;\n },\n\n gestureStart: function (event) {\n var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element);\n\n gestureEvent.ds = 0;\n\n this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance;\n this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle;\n this.gesture.scale = 1;\n\n this.gesturing = true;\n\n this.target.fire(gestureEvent);\n\n return gestureEvent;\n },\n\n gestureMove: function (event) {\n if (!this.pointerIds.length) {\n return this.prevEvent;\n }\n\n var gestureEvent;\n\n gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element);\n gestureEvent.ds = gestureEvent.scale - this.gesture.scale;\n\n this.target.fire(gestureEvent);\n\n this.gesture.prevAngle = gestureEvent.angle;\n this.gesture.prevDistance = gestureEvent.distance;\n\n if (gestureEvent.scale !== Infinity &&\n gestureEvent.scale !== null &&\n gestureEvent.scale !== undefined &&\n !isNaN(gestureEvent.scale)) {\n\n this.gesture.scale = gestureEvent.scale;\n }\n\n return gestureEvent;\n },\n\n pointerHold: function (pointer, event, eventTarget) {\n this.collectEventTargets(pointer, event, eventTarget, 'hold');\n },\n\n pointerUp: function (pointer, event, eventTarget, curEventTarget) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n clearTimeout(this.holdTimers[pointerIndex]);\n\n this.collectEventTargets(pointer, event, eventTarget, 'up' );\n this.collectEventTargets(pointer, event, eventTarget, 'tap');\n\n this.pointerEnd(pointer, event, eventTarget, curEventTarget);\n\n this.removePointer(pointer);\n },\n\n pointerCancel: function (pointer, event, eventTarget, curEventTarget) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n clearTimeout(this.holdTimers[pointerIndex]);\n\n this.collectEventTargets(pointer, event, eventTarget, 'cancel');\n this.pointerEnd(pointer, event, eventTarget, curEventTarget);\n\n this.removePointer(pointer);\n },\n\n // http://www.quirksmode.org/dom/events/click.html\n // >Events leading to dblclick\n //\n // IE8 doesn't fire down event before dblclick.\n // This workaround tries to fire a tap and doubletap after dblclick\n ie8Dblclick: function (pointer, event, eventTarget) {\n if (this.prevTap\n && event.clientX === this.prevTap.clientX\n && event.clientY === this.prevTap.clientY\n && eventTarget === this.prevTap.target) {\n\n this.downTargets[0] = eventTarget;\n this.downTimes[0] = new Date().getTime();\n this.collectEventTargets(pointer, event, eventTarget, 'tap');\n }\n },\n\n // End interact move events and stop auto-scroll unless inertia is enabled\n pointerEnd: function (pointer, event, eventTarget, curEventTarget) {\n var endEvent,\n target = this.target,\n options = target && target.options,\n inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia,\n inertiaStatus = this.inertiaStatus;\n\n if (this.interacting()) {\n\n if (inertiaStatus.active) { return; }\n\n var pointerSpeed,\n now = new Date().getTime(),\n inertiaPossible = false,\n inertia = false,\n smoothEnd = false,\n endSnap = scope.checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly,\n endRestrict = scope.checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly,\n dx = 0,\n dy = 0,\n startEvent;\n\n if (this.dragging) {\n if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); }\n else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); }\n else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; }\n }\n else {\n pointerSpeed = this.pointerDelta.client.speed;\n }\n\n // check if inertia should be started\n inertiaPossible = (inertiaOptions && inertiaOptions.enabled\n && this.prepared.name !== 'gesture'\n && event !== inertiaStatus.startEvent);\n\n inertia = (inertiaPossible\n && (now - this.curCoords.timeStamp) < 50\n && pointerSpeed > inertiaOptions.minSpeed\n && pointerSpeed > inertiaOptions.endSpeed);\n\n if (inertiaPossible && !inertia && (endSnap || endRestrict)) {\n\n var snapRestrict = {};\n\n snapRestrict.snap = snapRestrict.restrict = snapRestrict;\n\n if (endSnap) {\n this.setSnapping(this.curCoords.page, snapRestrict);\n if (snapRestrict.locked) {\n dx += snapRestrict.dx;\n dy += snapRestrict.dy;\n }\n }\n\n if (endRestrict) {\n this.setRestriction(this.curCoords.page, snapRestrict);\n if (snapRestrict.restricted) {\n dx += snapRestrict.dx;\n dy += snapRestrict.dy;\n }\n }\n\n if (dx || dy) {\n smoothEnd = true;\n }\n }\n\n if (inertia || smoothEnd) {\n utils.copyCoords(inertiaStatus.upCoords, this.curCoords);\n\n this.pointers[0] = inertiaStatus.startEvent = startEvent =\n new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element);\n\n inertiaStatus.t0 = now;\n\n target.fire(inertiaStatus.startEvent);\n\n if (inertia) {\n inertiaStatus.vx0 = this.pointerDelta.client.vx;\n inertiaStatus.vy0 = this.pointerDelta.client.vy;\n inertiaStatus.v0 = pointerSpeed;\n\n this.calcInertia(inertiaStatus);\n\n var page = utils.extend({}, this.curCoords.page),\n origin = scope.getOriginXY(target, this.element),\n statusObject;\n\n page.x = page.x + inertiaStatus.xe - origin.x;\n page.y = page.y + inertiaStatus.ye - origin.y;\n\n statusObject = {\n useStatusXY: true,\n x: page.x,\n y: page.y,\n dx: 0,\n dy: 0,\n snap: null\n };\n\n statusObject.snap = statusObject;\n\n dx = dy = 0;\n\n if (endSnap) {\n var snap = this.setSnapping(this.curCoords.page, statusObject);\n\n if (snap.locked) {\n dx += snap.dx;\n dy += snap.dy;\n }\n }\n\n if (endRestrict) {\n var restrict = this.setRestriction(this.curCoords.page, statusObject);\n\n if (restrict.restricted) {\n dx += restrict.dx;\n dy += restrict.dy;\n }\n }\n\n inertiaStatus.modifiedXe += dx;\n inertiaStatus.modifiedYe += dy;\n\n inertiaStatus.i = animationFrame.request(this.boundInertiaFrame);\n }\n else {\n inertiaStatus.smoothEnd = true;\n inertiaStatus.xe = dx;\n inertiaStatus.ye = dy;\n\n inertiaStatus.sx = inertiaStatus.sy = 0;\n\n inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame);\n }\n\n inertiaStatus.active = true;\n return;\n }\n\n if (endSnap || endRestrict) {\n // fire a move event at the snapped coordinates\n this.pointerMove(pointer, event, eventTarget, curEventTarget, true);\n }\n }\n\n if (this.dragging) {\n endEvent = new InteractEvent(this, event, 'drag', 'end', this.element);\n\n var draggableElement = this.element,\n drop = this.getDrop(event, draggableElement);\n\n this.dropTarget = drop.dropzone;\n this.dropElement = drop.element;\n\n var dropEvents = this.getDropEvents(event, endEvent);\n\n if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }\n if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }\n if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); }\n if (dropEvents.deactivate) {\n this.fireActiveDrops(dropEvents.deactivate);\n }\n\n target.fire(endEvent);\n }\n else if (this.resizing) {\n endEvent = new InteractEvent(this, event, 'resize', 'end', this.element);\n target.fire(endEvent);\n }\n else if (this.gesturing) {\n endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element);\n target.fire(endEvent);\n }\n\n this.stop(event);\n },\n\n collectDrops: function (element) {\n var drops = [],\n elements = [],\n i;\n\n element = element || this.element;\n\n // collect all dropzones and their elements which qualify for a drop\n for (i = 0; i < scope.interactables.length; i++) {\n if (!scope.interactables[i].options.drop.enabled) { continue; }\n\n var current = scope.interactables[i],\n accept = current.options.drop.accept;\n\n // test the draggable element against the dropzone's accept setting\n if ((utils.isElement(accept) && accept !== element)\n || (scope.isString(accept)\n && !scope.matchesSelector(element, accept))) {\n\n continue;\n }\n\n // query for new elements if necessary\n var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element];\n\n for (var j = 0, len = dropElements.length; j < len; j++) {\n var currentElement = dropElements[j];\n\n if (currentElement === element) {\n continue;\n }\n\n drops.push(current);\n elements.push(currentElement);\n }\n }\n\n return {\n dropzones: drops,\n elements: elements\n };\n },\n\n fireActiveDrops: function (event) {\n var i,\n current,\n currentElement,\n prevElement;\n\n // loop through all active dropzones and trigger event\n for (i = 0; i < this.activeDrops.dropzones.length; i++) {\n current = this.activeDrops.dropzones[i];\n currentElement = this.activeDrops.elements [i];\n\n // prevent trigger of duplicate events on same element\n if (currentElement !== prevElement) {\n // set current element as event target\n event.target = currentElement;\n current.fire(event);\n }\n prevElement = currentElement;\n }\n },\n\n // Collect a new set of possible drops and save them in activeDrops.\n // setActiveDrops should always be called when a drag has just started or a\n // drag event happens while dynamicDrop is true\n setActiveDrops: function (dragElement) {\n // get dropzones and their elements that could receive the draggable\n var possibleDrops = this.collectDrops(dragElement, true);\n\n this.activeDrops.dropzones = possibleDrops.dropzones;\n this.activeDrops.elements = possibleDrops.elements;\n this.activeDrops.rects = [];\n\n for (var i = 0; i < this.activeDrops.dropzones.length; i++) {\n this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]);\n }\n },\n\n getDrop: function (event, dragElement) {\n var validDrops = [];\n\n if (scope.dynamicDrop) {\n this.setActiveDrops(dragElement);\n }\n\n // collect all dropzones and their elements which qualify for a drop\n for (var j = 0; j < this.activeDrops.dropzones.length; j++) {\n var current = this.activeDrops.dropzones[j],\n currentElement = this.activeDrops.elements [j],\n rect = this.activeDrops.rects [j];\n\n validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect)\n ? currentElement\n : null);\n }\n\n // get the most appropriate dropzone based on DOM depth and order\n var dropIndex = scope.indexOfDeepestElement(validDrops),\n dropzone = this.activeDrops.dropzones[dropIndex] || null,\n element = this.activeDrops.elements [dropIndex] || null;\n\n return {\n dropzone: dropzone,\n element: element\n };\n },\n\n getDropEvents: function (pointerEvent, dragEvent) {\n var dropEvents = {\n enter : null,\n leave : null,\n activate : null,\n deactivate: null,\n move : null,\n drop : null\n };\n\n if (this.dropElement !== this.prevDropElement) {\n // if there was a prevDropTarget, create a dragleave event\n if (this.prevDropTarget) {\n dropEvents.leave = {\n target : this.prevDropElement,\n dropzone : this.prevDropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dragleave'\n };\n\n dragEvent.dragLeave = this.prevDropElement;\n dragEvent.prevDropzone = this.prevDropTarget;\n }\n // if the dropTarget is not null, create a dragenter event\n if (this.dropTarget) {\n dropEvents.enter = {\n target : this.dropElement,\n dropzone : this.dropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dragenter'\n };\n\n dragEvent.dragEnter = this.dropElement;\n dragEvent.dropzone = this.dropTarget;\n }\n }\n\n if (dragEvent.type === 'dragend' && this.dropTarget) {\n dropEvents.drop = {\n target : this.dropElement,\n dropzone : this.dropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'drop'\n };\n\n dragEvent.dropzone = this.dropTarget;\n }\n if (dragEvent.type === 'dragstart') {\n dropEvents.activate = {\n target : null,\n dropzone : null,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dropactivate'\n };\n }\n if (dragEvent.type === 'dragend') {\n dropEvents.deactivate = {\n target : null,\n dropzone : null,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n timeStamp : dragEvent.timeStamp,\n type : 'dropdeactivate'\n };\n }\n if (dragEvent.type === 'dragmove' && this.dropTarget) {\n dropEvents.move = {\n target : this.dropElement,\n dropzone : this.dropTarget,\n relatedTarget: dragEvent.target,\n draggable : dragEvent.interactable,\n dragEvent : dragEvent,\n interaction : this,\n dragmove : dragEvent,\n timeStamp : dragEvent.timeStamp,\n type : 'dropmove'\n };\n dragEvent.dropzone = this.dropTarget;\n }\n\n return dropEvents;\n },\n\n currentAction: function () {\n return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null;\n },\n\n interacting: function () {\n return this.dragging || this.resizing || this.gesturing;\n },\n\n clearTargets: function () {\n this.target = this.element = null;\n\n this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null;\n },\n\n stop: function (event) {\n if (this.interacting()) {\n scope.autoScroll.stop();\n this.matches = [];\n this.matchElements = [];\n\n var target = this.target;\n\n if (target.options.styleCursor) {\n target._doc.documentElement.style.cursor = '';\n }\n\n // prevent Default only if were previously interacting\n if (event && scope.isFunction(event.preventDefault)) {\n this.checkAndPreventDefault(event, target, this.element);\n }\n\n if (this.dragging) {\n this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null;\n }\n }\n\n this.clearTargets();\n\n this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false;\n this.prepared.name = this.prevEvent = null;\n this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0;\n\n // remove pointers if their ID isn't in this.pointerIds\n for (var i = 0; i < this.pointers.length; i++) {\n if (scope.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) {\n this.pointers.splice(i, 1);\n }\n }\n\n for (i = 0; i < scope.interactions.length; i++) {\n // remove this interaction if it's not the only one of it's type\n if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) {\n scope.interactions.splice(scope.indexOf(scope.interactions, this), 1);\n }\n }\n },\n\n inertiaFrame: function () {\n var inertiaStatus = this.inertiaStatus,\n options = this.target.options[this.prepared.name].inertia,\n lambda = options.resistance,\n t = new Date().getTime() / 1000 - inertiaStatus.t0;\n\n if (t < inertiaStatus.te) {\n\n var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0;\n\n if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) {\n inertiaStatus.sx = inertiaStatus.xe * progress;\n inertiaStatus.sy = inertiaStatus.ye * progress;\n }\n else {\n var quadPoint = scope.getQuadraticCurvePoint(\n 0, 0,\n inertiaStatus.xe, inertiaStatus.ye,\n inertiaStatus.modifiedXe, inertiaStatus.modifiedYe,\n progress);\n\n inertiaStatus.sx = quadPoint.x;\n inertiaStatus.sy = quadPoint.y;\n }\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.i = animationFrame.request(this.boundInertiaFrame);\n }\n else {\n inertiaStatus.sx = inertiaStatus.modifiedXe;\n inertiaStatus.sy = inertiaStatus.modifiedYe;\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.active = false;\n this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);\n }\n },\n\n smoothEndFrame: function () {\n var inertiaStatus = this.inertiaStatus,\n t = new Date().getTime() - inertiaStatus.t0,\n duration = this.target.options[this.prepared.name].inertia.smoothEndDuration;\n\n if (t < duration) {\n inertiaStatus.sx = scope.easeOutQuad(t, 0, inertiaStatus.xe, duration);\n inertiaStatus.sy = scope.easeOutQuad(t, 0, inertiaStatus.ye, duration);\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame);\n }\n else {\n inertiaStatus.sx = inertiaStatus.xe;\n inertiaStatus.sy = inertiaStatus.ye;\n\n this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);\n\n inertiaStatus.active = false;\n inertiaStatus.smoothEnd = false;\n\n this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);\n }\n },\n\n addPointer: function (pointer) {\n var id = utils.getPointerId(pointer),\n index = this.mouse? 0 : scope.indexOf(this.pointerIds, id);\n\n if (index === -1) {\n index = this.pointerIds.length;\n }\n\n this.pointerIds[index] = id;\n this.pointers[index] = pointer;\n\n return index;\n },\n\n removePointer: function (pointer) {\n var id = utils.getPointerId(pointer),\n index = this.mouse? 0 : scope.indexOf(this.pointerIds, id);\n\n if (index === -1) { return; }\n\n if (!this.interacting()) {\n this.pointers.splice(index, 1);\n }\n\n this.pointerIds .splice(index, 1);\n this.downTargets.splice(index, 1);\n this.downTimes .splice(index, 1);\n this.holdTimers .splice(index, 1);\n },\n\n recordPointer: function (pointer) {\n // Do not update pointers while inertia is active.\n // The inertia start event should be this.pointers[0]\n if (this.inertiaStatus.active) { return; }\n\n var index = this.mouse? 0: scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n if (index === -1) { return; }\n\n this.pointers[index] = pointer;\n },\n\n collectEventTargets: function (pointer, event, eventTarget, eventType) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(this.pointerIds, utils.getPointerId(pointer));\n\n // do not fire a tap event if the pointer was moved before being lifted\n if (eventType === 'tap' && (this.pointerWasMoved\n // or if the pointerup target is different to the pointerdown target\n || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) {\n return;\n }\n\n var targets = [],\n elements = [],\n element = eventTarget;\n\n function collectSelectors (interactable, selector, context) {\n var els = scope.ie8MatchesSelector\n ? context.querySelectorAll(selector)\n : undefined;\n\n if (interactable._iEvents[eventType]\n && utils.isElement(element)\n && scope.inContext(interactable, element)\n && !scope.testIgnore(interactable, element, eventTarget)\n && scope.testAllow(interactable, element, eventTarget)\n && scope.matchesSelector(element, selector, els)) {\n\n targets.push(interactable);\n elements.push(element);\n }\n }\n\n\n var interact = scope.interact;\n\n while (element) {\n if (interact.isSet(element) && interact(element)._iEvents[eventType]) {\n targets.push(interact(element));\n elements.push(element);\n }\n\n scope.interactables.forEachSelector(collectSelectors);\n\n element = scope.parentElement(element);\n }\n\n // create the tap event even if there are no listeners so that\n // doubletap can still be created and fired\n if (targets.length || eventType === 'tap') {\n this.firePointers(pointer, event, eventTarget, targets, elements, eventType);\n }\n },\n\n firePointers: function (pointer, event, eventTarget, targets, elements, eventType) {\n var pointerIndex = this.mouse? 0 : scope.indexOf(utils.getPointerId(pointer)),\n pointerEvent = {},\n i,\n // for tap events\n interval, createNewDoubleTap;\n\n // if it's a doubletap then the event properties would have been\n // copied from the tap event and provided as the pointer argument\n if (eventType === 'doubletap') {\n pointerEvent = pointer;\n }\n else {\n utils.extend(pointerEvent, event);\n if (event !== pointer) {\n utils.extend(pointerEvent, pointer);\n }\n\n pointerEvent.preventDefault = preventOriginalDefault;\n pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation;\n pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation;\n pointerEvent.interaction = this;\n\n pointerEvent.timeStamp = new Date().getTime();\n pointerEvent.originalEvent = event;\n pointerEvent.type = eventType;\n pointerEvent.pointerId = utils.getPointerId(pointer);\n pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch'\n : scope.isString(pointer.pointerType)\n ? pointer.pointerType\n : [,,'touch', 'pen', 'mouse'][pointer.pointerType];\n }\n\n if (eventType === 'tap') {\n pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex];\n\n interval = pointerEvent.timeStamp - this.tapTime;\n createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap'\n && this.prevTap.target === pointerEvent.target\n && interval < 500);\n\n pointerEvent.double = createNewDoubleTap;\n\n this.tapTime = pointerEvent.timeStamp;\n }\n\n for (i = 0; i < targets.length; i++) {\n pointerEvent.currentTarget = elements[i];\n pointerEvent.interactable = targets[i];\n targets[i].fire(pointerEvent);\n\n if (pointerEvent.immediatePropagationStopped\n ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) {\n break;\n }\n }\n\n if (createNewDoubleTap) {\n var doubleTap = {};\n\n utils.extend(doubleTap, pointerEvent);\n\n doubleTap.dt = interval;\n doubleTap.type = 'doubletap';\n\n this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap');\n\n this.prevTap = doubleTap;\n }\n else if (eventType === 'tap') {\n this.prevTap = pointerEvent;\n }\n },\n\n validateSelector: function (pointer, event, matches, matchElements) {\n for (var i = 0, len = matches.length; i < len; i++) {\n var match = matches[i],\n matchElement = matchElements[i],\n action = validateAction(match.getAction(pointer, event, this, matchElement), match);\n\n if (action && scope.withinInteractionLimit(match, matchElement, action)) {\n this.target = match;\n this.element = matchElement;\n\n return action;\n }\n }\n },\n\n setSnapping: function (pageCoords, status) {\n var snap = this.target.options[this.prepared.name].snap,\n targets = [],\n target,\n page,\n i;\n\n status = status || this.snapStatus;\n\n if (status.useStatusXY) {\n page = { x: status.x, y: status.y };\n }\n else {\n var origin = scope.getOriginXY(this.target, this.element);\n\n page = utils.extend({}, pageCoords);\n\n page.x -= origin.x;\n page.y -= origin.y;\n }\n\n status.realX = page.x;\n status.realY = page.y;\n\n page.x = page.x - this.inertiaStatus.resumeDx;\n page.y = page.y - this.inertiaStatus.resumeDy;\n\n var len = snap.targets? snap.targets.length : 0;\n\n for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) {\n var relative = {\n x: page.x - this.snapOffsets[relIndex].x,\n y: page.y - this.snapOffsets[relIndex].y\n };\n\n for (i = 0; i < len; i++) {\n if (scope.isFunction(snap.targets[i])) {\n target = snap.targets[i](relative.x, relative.y, this);\n }\n else {\n target = snap.targets[i];\n }\n\n if (!target) { continue; }\n\n targets.push({\n x: scope.isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x,\n y: scope.isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y,\n\n range: scope.isNumber(target.range)? target.range: snap.range\n });\n }\n }\n\n var closest = {\n target: null,\n inRange: false,\n distance: 0,\n range: 0,\n dx: 0,\n dy: 0\n };\n\n for (i = 0, len = targets.length; i < len; i++) {\n target = targets[i];\n\n var range = target.range,\n dx = target.x - page.x,\n dy = target.y - page.y,\n distance = utils.hypot(dx, dy),\n inRange = distance <= range;\n\n // Infinite targets count as being out of range\n // compared to non infinite ones that are in range\n if (range === Infinity && closest.inRange && closest.range !== Infinity) {\n inRange = false;\n }\n\n if (!closest.target || (inRange\n // is the closest target in range?\n ? (closest.inRange && range !== Infinity\n // the pointer is relatively deeper in this target\n ? distance / range < closest.distance / closest.range\n // this target has Infinite range and the closest doesn't\n : (range === Infinity && closest.range !== Infinity)\n // OR this target is closer that the previous closest\n || distance < closest.distance)\n // The other is not in range and the pointer is closer to this target\n : (!closest.inRange && distance < closest.distance))) {\n\n if (range === Infinity) {\n inRange = true;\n }\n\n closest.target = target;\n closest.distance = distance;\n closest.range = range;\n closest.inRange = inRange;\n closest.dx = dx;\n closest.dy = dy;\n\n status.range = range;\n }\n }\n\n var snapChanged;\n\n if (closest.target) {\n snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y);\n\n status.snappedX = closest.target.x;\n status.snappedY = closest.target.y;\n }\n else {\n snapChanged = true;\n\n status.snappedX = NaN;\n status.snappedY = NaN;\n }\n\n status.dx = closest.dx;\n status.dy = closest.dy;\n\n status.changed = (snapChanged || (closest.inRange && !status.locked));\n status.locked = closest.inRange;\n\n return status;\n },\n\n setRestriction: function (pageCoords, status) {\n var target = this.target,\n restrict = target && target.options[this.prepared.name].restrict,\n restriction = restrict && restrict.restriction,\n page;\n\n if (!restriction) {\n return status;\n }\n\n status = status || this.restrictStatus;\n\n page = status.useStatusXY\n ? page = { x: status.x, y: status.y }\n : page = utils.extend({}, pageCoords);\n\n if (status.snap && status.snap.locked) {\n page.x += status.snap.dx || 0;\n page.y += status.snap.dy || 0;\n }\n\n page.x -= this.inertiaStatus.resumeDx;\n page.y -= this.inertiaStatus.resumeDy;\n\n status.dx = 0;\n status.dy = 0;\n status.restricted = false;\n\n var rect, restrictedX, restrictedY;\n\n if (scope.isString(restriction)) {\n if (restriction === 'parent') {\n restriction = scope.parentElement(this.element);\n }\n else if (restriction === 'self') {\n restriction = target.getRect(this.element);\n }\n else {\n restriction = scope.closest(this.element, restriction);\n }\n\n if (!restriction) { return status; }\n }\n\n if (scope.isFunction(restriction)) {\n restriction = restriction(page.x, page.y, this.element);\n }\n\n if (utils.isElement(restriction)) {\n restriction = scope.getElementRect(restriction);\n }\n\n rect = restriction;\n\n if (!restriction) {\n restrictedX = page.x;\n restrictedY = page.y;\n }\n // object is assumed to have\n // x, y, width, height or\n // left, top, right, bottom\n else if ('x' in restriction && 'y' in restriction) {\n restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left);\n restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top );\n }\n else {\n restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left);\n restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top );\n }\n\n status.dx = restrictedX - page.x;\n status.dy = restrictedY - page.y;\n\n status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY;\n status.restricted = !!(status.dx || status.dy);\n\n status.restrictedX = restrictedX;\n status.restrictedY = restrictedY;\n\n return status;\n },\n\n checkAndPreventDefault: function (event, interactable, element) {\n if (!(interactable = interactable || this.target)) { return; }\n\n var options = interactable.options,\n prevent = options.preventDefault;\n\n if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) {\n // do not preventDefault on pointerdown if the prepared action is a drag\n // and dragging can only start from a certain direction - this allows\n // a touch to pan the viewport if a drag isn't in the right direction\n if (/down|start/i.test(event.type)\n && this.prepared.name === 'drag' && options.drag.axis !== 'xy') {\n\n return;\n }\n\n // with manualStart, only preventDefault while interacting\n if (options[this.prepared.name] && options[this.prepared.name].manualStart\n && !this.interacting()) {\n return;\n }\n\n event.preventDefault();\n return;\n }\n\n if (prevent === 'always') {\n event.preventDefault();\n return;\n }\n },\n\n calcInertia: function (status) {\n var inertiaOptions = this.target.options[this.prepared.name].inertia,\n lambda = inertiaOptions.resistance,\n inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda;\n\n status.x0 = this.prevEvent.pageX;\n status.y0 = this.prevEvent.pageY;\n status.t0 = status.startEvent.timeStamp / 1000;\n status.sx = status.sy = 0;\n\n status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;\n status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;\n status.te = inertiaDur;\n\n status.lambda_v0 = lambda / status.v0;\n status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0;\n },\n\n autoScrollMove: function (pointer) {\n if (!(this.interacting()\n && scope.checkAutoScroll(this.target, this.prepared.name))) {\n return;\n }\n\n if (this.inertiaStatus.active) {\n scope.autoScroll.x = scope.autoScroll.y = 0;\n return;\n }\n\n var top,\n right,\n bottom,\n left,\n options = this.target.options[this.prepared.name].autoScroll,\n container = options.container || scope.getWindow(this.element);\n\n if (scope.isWindow(container)) {\n left = pointer.clientX < scope.autoScroll.margin;\n top = pointer.clientY < scope.autoScroll.margin;\n right = pointer.clientX > container.innerWidth - scope.autoScroll.margin;\n bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin;\n }\n else {\n var rect = scope.getElementRect(container);\n\n left = pointer.clientX < rect.left + scope.autoScroll.margin;\n top = pointer.clientY < rect.top + scope.autoScroll.margin;\n right = pointer.clientX > rect.right - scope.autoScroll.margin;\n bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin;\n }\n\n scope.autoScroll.x = (right ? 1: left? -1: 0);\n scope.autoScroll.y = (bottom? 1: top? -1: 0);\n\n if (!scope.autoScroll.isScrolling) {\n // set the autoScroll properties to those of the target\n scope.autoScroll.margin = options.margin;\n scope.autoScroll.speed = options.speed;\n\n scope.autoScroll.start(this);\n }\n },\n\n _updateEventTargets: function (target, currentTarget) {\n this._eventTarget = target;\n this._curEventTarget = currentTarget;\n }\n\n};\n\nmodule.exports = Interaction;\n\n},{\"./InteractEvent\":2,\"./scope\":6,\"./utils\":13,\"./utils/browser\":8,\"./utils/events\":10}],4:[function(require,module,exports){\n'use strict';\n\nvar raf = require('./utils/raf'),\n getWindow = require('./utils/window').getWindow,\n isWindow = require('./utils/isType').isWindow;\n\nvar autoScroll = {\n\n interaction: null,\n i: null, // the handle returned by window.setInterval\n x: 0, y: 0, // Direction each pulse is to scroll in\n\n isScrolling: false,\n prevTime: 0,\n\n start: function (interaction) {\n autoScroll.isScrolling = true;\n raf.cancel(autoScroll.i);\n\n autoScroll.interaction = interaction;\n autoScroll.prevTime = new Date().getTime();\n autoScroll.i = raf.request(autoScroll.scroll);\n },\n\n stop: function () {\n autoScroll.isScrolling = false;\n raf.cancel(autoScroll.i);\n },\n\n // scroll the window by the values in scroll.x/y\n scroll: function () {\n var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll,\n container = options.container || getWindow(autoScroll.interaction.element),\n now = new Date().getTime(),\n // change in time in seconds\n dt = (now - autoScroll.prevTime) / 1000,\n // displacement\n s = options.speed * dt;\n\n if (s >= 1) {\n if (isWindow(container)) {\n container.scrollBy(autoScroll.x * s, autoScroll.y * s);\n }\n else if (container) {\n container.scrollLeft += autoScroll.x * s;\n container.scrollTop += autoScroll.y * s;\n }\n\n autoScroll.prevTime = now;\n }\n\n if (autoScroll.isScrolling) {\n raf.cancel(autoScroll.i);\n autoScroll.i = raf.request(autoScroll.scroll);\n }\n }\n};\n\nmodule.exports = autoScroll;\n\n},{\"./utils/isType\":14,\"./utils/raf\":17,\"./utils/window\":18}],5:[function(require,module,exports){\n'use strict';\n\nmodule.exports = {\n base: {\n accept : null,\n actionChecker : null,\n styleCursor : true,\n preventDefault: 'auto',\n origin : { x: 0, y: 0 },\n deltaSource : 'page',\n allowFrom : null,\n ignoreFrom : null,\n _context : require('./utils/domObjects').document,\n dropChecker : null\n },\n\n drag: {\n enabled: false,\n manualStart: true,\n max: Infinity,\n maxPerElement: 1,\n\n snap: null,\n restrict: null,\n inertia: null,\n autoScroll: null,\n\n axis: 'xy'\n },\n\n drop: {\n enabled: false,\n accept: null,\n overlap: 'pointer'\n },\n\n resize: {\n enabled: false,\n manualStart: false,\n max: Infinity,\n maxPerElement: 1,\n\n snap: null,\n restrict: null,\n inertia: null,\n autoScroll: null,\n\n square: false,\n axis: 'xy',\n\n // use default margin\n margin: NaN,\n\n // object with props left, right, top, bottom which are\n // true/false values to resize when the pointer is over that edge,\n // CSS selectors to match the handles for each direction\n // or the Elements for each handle\n edges: null,\n\n // a value of 'none' will limit the resize rect to a minimum of 0x0\n // 'negate' will alow the rect to have negative width/height\n // 'reposition' will keep the width/height positive by swapping\n // the top and bottom edges and/or swapping the left and right edges\n invert: 'none'\n },\n\n gesture: {\n manualStart: false,\n enabled: false,\n max: Infinity,\n maxPerElement: 1,\n\n restrict: null\n },\n\n perAction: {\n manualStart: false,\n max: Infinity,\n maxPerElement: 1,\n\n snap: {\n enabled : false,\n endOnly : false,\n range : Infinity,\n targets : null,\n offsets : null,\n\n relativePoints: null\n },\n\n restrict: {\n enabled: false,\n endOnly: false\n },\n\n autoScroll: {\n enabled : false,\n container : null, // the item that is scrolled (Window or HTMLElement)\n margin : 60,\n speed : 300 // the scroll speed in pixels per second\n },\n\n inertia: {\n enabled : false,\n resistance : 10, // the lambda in exponential decay\n minSpeed : 100, // target speed must be above this for inertia to start\n endSpeed : 10, // the speed at which inertia is slow enough to stop\n allowResume : true, // allow resuming an action in inertia phase\n zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0\n smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia\n }\n },\n\n _holdDuration: 600\n};\n\n},{\"./utils/domObjects\":9}],6:[function(require,module,exports){\n'use strict';\n\nvar scope = {},\n extend = require('./utils/extend');\n\nextend(scope, require('./utils/window'));\nextend(scope, require('./utils/domObjects'));\nextend(scope, require('./utils/arr.js'));\nextend(scope, require('./utils/isType'));\n\nmodule.exports = scope;\n\n},{\"./utils/arr.js\":7,\"./utils/domObjects\":9,\"./utils/extend\":11,\"./utils/isType\":14,\"./utils/window\":18}],7:[function(require,module,exports){\n'use strict';\n\nfunction indexOf (array, target) {\n for (var i = 0, len = array.length; i < len; i++) {\n if (array[i] === target) {\n return i;\n }\n }\n\n return -1;\n}\n\nfunction contains (array, target) {\n return indexOf(array, target) !== -1;\n}\n\nmodule.exports = {\n indexOf: indexOf,\n contains: contains\n};\n\n},{}],8:[function(require,module,exports){\n'use strict';\n\nvar win = require('./window'),\n domObjects = require('./domObjects');\n\nvar browser = {\n // Does the browser support touch input?\n supportsTouch : !!(('ontouchstart' in win) || win.window.DocumentTouch\n && domObjects.document instanceof win.DocumentTouch),\n\n // Does the browser support PointerEvents\n supportsPointerEvent : !!domObjects.PointerEvent,\n\n // Opera Mobile must be handled differently\n isOperaMobile : (navigator.appName === 'Opera'\n && browser.supportsTouch\n && navigator.userAgent.match('Presto')),\n\n // scrolling doesn't change the result of\n // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8\n isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\\d]/.test(navigator.appVersion)),\n\n isIe9OrOlder : domObjects.document.all && !win.window.atob,\n\n // prefix matchesSelector\n prefixedMatchesSelector: 'matches' in Element.prototype?\n 'matches': 'webkitMatchesSelector' in Element.prototype?\n 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?\n 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?\n 'oMatchesSelector': 'msMatchesSelector'\n\n};\n\nmodule.exports = browser;\n\n},{\"./domObjects\":9,\"./window\":18}],9:[function(require,module,exports){\n'use strict';\n\nvar domObjects = {},\n win = require('./window').window,\n blank = function () {};\n\ndomObjects.document = win.document;\ndomObjects.DocumentFragment = win.DocumentFragment || blank;\ndomObjects.SVGElement = win.SVGElement || blank;\ndomObjects.SVGSVGElement = win.SVGSVGElement || blank;\ndomObjects.SVGElementInstance = win.SVGElementInstance || blank;\ndomObjects.HTMLElement = win.HTMLElement || win.Element;\n\ndomObjects.PointerEvent = (win.PointerEvent || win.MSPointerEvent);\n\nmodule.exports = domObjects;\n\n},{\"./window\":18}],10:[function(require,module,exports){\n'use strict';\n\nvar arr = require('./arr'),\n indexOf = arr.indexOf,\n contains = arr.contains,\n getWindow = require('./window').getWindow,\n\n useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window),\n addEvent = useAttachEvent? 'attachEvent': 'addEventListener',\n removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener',\n on = useAttachEvent? 'on': '',\n\n elements = [],\n targets = [],\n attachedListeners = [];\n\nfunction add (element, type, listener, useCapture) {\n var elementIndex = indexOf(elements, element),\n target = targets[elementIndex];\n\n if (!target) {\n target = {\n events: {},\n typeCount: 0\n };\n\n elementIndex = elements.push(element) - 1;\n targets.push(target);\n\n attachedListeners.push((useAttachEvent ? {\n supplied: [],\n wrapped : [],\n useCount: []\n } : null));\n }\n\n if (!target.events[type]) {\n target.events[type] = [];\n target.typeCount++;\n }\n\n if (!contains(target.events[type], listener)) {\n var ret;\n\n if (useAttachEvent) {\n var listeners = attachedListeners[elementIndex],\n listenerIndex = indexOf(listeners.supplied, listener);\n\n var wrapped = listeners.wrapped[listenerIndex] || function (event) {\n if (!event.immediatePropagationStopped) {\n event.target = event.srcElement;\n event.currentTarget = element;\n\n event.preventDefault = event.preventDefault || preventDef;\n event.stopPropagation = event.stopPropagation || stopProp;\n event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp;\n\n if (/mouse|click/.test(event.type)) {\n event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft;\n event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop;\n }\n\n listener(event);\n }\n };\n\n ret = element[addEvent](on + type, wrapped, !!useCapture);\n\n if (listenerIndex === -1) {\n listeners.supplied.push(listener);\n listeners.wrapped.push(wrapped);\n listeners.useCount.push(1);\n }\n else {\n listeners.useCount[listenerIndex]++;\n }\n }\n else {\n ret = element[addEvent](type, listener, !!useCapture);\n }\n target.events[type].push(listener);\n\n return ret;\n }\n}\n\nfunction remove (element, type, listener, useCapture) {\n var i,\n elementIndex = indexOf(elements, element),\n target = targets[elementIndex],\n listeners,\n listenerIndex,\n wrapped = listener;\n\n if (!target || !target.events) {\n return;\n }\n\n if (useAttachEvent) {\n listeners = attachedListeners[elementIndex];\n listenerIndex = indexOf(listeners.supplied, listener);\n wrapped = listeners.wrapped[listenerIndex];\n }\n\n if (type === 'all') {\n for (type in target.events) {\n if (target.events.hasOwnProperty(type)) {\n remove(element, type, 'all');\n }\n }\n return;\n }\n\n if (target.events[type]) {\n var len = target.events[type].length;\n\n if (listener === 'all') {\n for (i = 0; i < len; i++) {\n remove(element, type, target.events[type][i], !!useCapture);\n }\n return;\n } else {\n for (i = 0; i < len; i++) {\n if (target.events[type][i] === listener) {\n element[removeEvent](on + type, wrapped, !!useCapture);\n target.events[type].splice(i, 1);\n\n if (useAttachEvent && listeners) {\n listeners.useCount[listenerIndex]--;\n if (listeners.useCount[listenerIndex] === 0) {\n listeners.supplied.splice(listenerIndex, 1);\n listeners.wrapped.splice(listenerIndex, 1);\n listeners.useCount.splice(listenerIndex, 1);\n }\n }\n\n break;\n }\n }\n }\n\n if (target.events[type] && target.events[type].length === 0) {\n target.events[type] = null;\n target.typeCount--;\n }\n }\n\n if (!target.typeCount) {\n targets.splice(elementIndex, 1);\n elements.splice(elementIndex, 1);\n attachedListeners.splice(elementIndex, 1);\n }\n}\n\nfunction preventDef () {\n this.returnValue = false;\n}\n\nfunction stopProp () {\n this.cancelBubble = true;\n}\n\nfunction stopImmProp () {\n this.cancelBubble = true;\n this.immediatePropagationStopped = true;\n}\n\nmodule.exports = {\n add: add,\n remove: remove,\n useAttachEvent: useAttachEvent,\n\n _elements: elements,\n _targets: targets,\n _attachedListeners: attachedListeners\n};\n\n},{\"./arr\":7,\"./window\":18}],11:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function extend (dest, source) {\n for (var prop in source) {\n dest[prop] = source[prop];\n }\n return dest;\n};\n\n},{}],12:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function hypot (x, y) { return Math.sqrt(x * x + y * y); };\n\n},{}],13:[function(require,module,exports){\n'use strict';\n\nvar utils = module.exports,\n extend = require('./extend'),\n win = require('./window');\n\nutils.blank = function () {};\n\nutils.warnOnce = function (method, message) {\n var warned = false;\n\n return function () {\n if (!warned) {\n win.window.console.warn(message);\n warned = true;\n }\n\n return method.apply(this, arguments);\n };\n};\n\nutils.extend = extend;\nutils.hypot = require('./hypot');\nutils.raf = require('./raf');\nutils.browser = require('./browser');\n\nextend(utils, require('./arr'));\nextend(utils, require('./isType'));\nextend(utils, require('./pointerUtils'));\n\n},{\"./arr\":7,\"./browser\":8,\"./extend\":11,\"./hypot\":12,\"./isType\":14,\"./pointerUtils\":16,\"./raf\":17,\"./window\":18}],14:[function(require,module,exports){\n'use strict';\n\nvar win = require('./window'),\n domObjects = require('./domObjects');\n\nvar isType = {\n isElement : function (o) {\n if (!o || (typeof o !== 'object')) { return false; }\n \n var _window = win.getWindow(o) || win.window;\n \n return (/object|function/.test(typeof _window.Element)\n ? o instanceof _window.Element //DOM2\n : o.nodeType === 1 && typeof o.nodeName === \"string\");\n },\n\n isArray : null,\n \n isWindow : require('./isWindow'),\n\n isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; },\n\n isObject : function (thing) { return !!thing && (typeof thing === 'object'); },\n\n isFunction : function (thing) { return typeof thing === 'function'; },\n\n isNumber : function (thing) { return typeof thing === 'number' ; },\n\n isBool : function (thing) { return typeof thing === 'boolean' ; },\n\n isString : function (thing) { return typeof thing === 'string' ; }\n \n};\n\nisType.isArray = function (thing) {\n return isType.isObject(thing)\n && (typeof thing.length !== 'undefined')\n && isType.isFunction(thing.splice);\n};\n\nmodule.exports = isType;\n\n},{\"./domObjects\":9,\"./isWindow\":15,\"./window\":18}],15:[function(require,module,exports){\n'use strict';\n\nmodule.exports = function isWindow (thing) {\n return !!(thing && thing.Window) && (thing instanceof thing.Window);\n};\n\n},{}],16:[function(require,module,exports){\n'use strict';\n\nvar pointerUtils = {},\n // reduce object creation in getXY()\n tmpXY = {},\n win = require('./window'),\n hypot = require('./hypot'),\n extend = require('./extend'),\n browser = require('./browser'),\n isType = require('./isType'),\n InteractEvent = require('../InteractEvent');\n\npointerUtils.copyCoords = function (dest, src) {\n dest.page = dest.page || {};\n dest.page.x = src.page.x;\n dest.page.y = src.page.y;\n\n dest.client = dest.client || {};\n dest.client.x = src.client.x;\n dest.client.y = src.client.y;\n\n dest.timeStamp = src.timeStamp;\n};\n\npointerUtils.setEventXY = function (targetObj, pointer, interaction) {\n if (!pointer) {\n if (interaction.pointerIds.length > 1) {\n pointer = pointerUtils.touchAverage(interaction.pointers);\n }\n else {\n pointer = interaction.pointers[0];\n }\n }\n\n pointerUtils.getPageXY(pointer, tmpXY, interaction);\n targetObj.page.x = tmpXY.x;\n targetObj.page.y = tmpXY.y;\n\n pointerUtils.getClientXY(pointer, tmpXY, interaction);\n targetObj.client.x = tmpXY.x;\n targetObj.client.y = tmpXY.y;\n\n targetObj.timeStamp = new Date().getTime();\n};\n\npointerUtils.setEventDeltas = function (targetObj, prev, cur) {\n targetObj.page.x = cur.page.x - prev.page.x;\n targetObj.page.y = cur.page.y - prev.page.y;\n targetObj.client.x = cur.client.x - prev.client.x;\n targetObj.client.y = cur.client.y - prev.client.y;\n targetObj.timeStamp = new Date().getTime() - prev.timeStamp;\n\n // set pointer velocity\n var dt = Math.max(targetObj.timeStamp / 1000, 0.001);\n targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt;\n targetObj.page.vx = targetObj.page.x / dt;\n targetObj.page.vy = targetObj.page.y / dt;\n\n targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt;\n targetObj.client.vx = targetObj.client.x / dt;\n targetObj.client.vy = targetObj.client.y / dt;\n};\n\n// Get specified X/Y coords for mouse or event.touches[0]\npointerUtils.getXY = function (type, pointer, xy) {\n xy = xy || {};\n type = type || 'page';\n\n xy.x = pointer[type + 'X'];\n xy.y = pointer[type + 'Y'];\n\n return xy;\n};\n\npointerUtils.getPageXY = function (pointer, page, interaction) {\n page = page || {};\n\n if (pointer instanceof InteractEvent) {\n if (/inertiastart/.test(pointer.type)) {\n interaction = interaction || pointer.interaction;\n\n extend(page, interaction.inertiaStatus.upCoords.page);\n\n page.x += interaction.inertiaStatus.sx;\n page.y += interaction.inertiaStatus.sy;\n }\n else {\n page.x = pointer.pageX;\n page.y = pointer.pageY;\n }\n }\n // Opera Mobile handles the viewport and scrolling oddly\n else if (browser.isOperaMobile) {\n pointerUtils.getXY('screen', pointer, page);\n\n page.x += win.window.scrollX;\n page.y += win.window.scrollY;\n }\n else {\n pointerUtils.getXY('page', pointer, page);\n }\n\n return page;\n};\n\npointerUtils.getClientXY = function (pointer, client, interaction) {\n client = client || {};\n\n if (pointer instanceof InteractEvent) {\n if (/inertiastart/.test(pointer.type)) {\n extend(client, interaction.inertiaStatus.upCoords.client);\n\n client.x += interaction.inertiaStatus.sx;\n client.y += interaction.inertiaStatus.sy;\n }\n else {\n client.x = pointer.clientX;\n client.y = pointer.clientY;\n }\n }\n else {\n // Opera Mobile handles the viewport and scrolling oddly\n pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client);\n }\n\n return client;\n};\n\npointerUtils.getPointerId = function (pointer) {\n return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier;\n};\n\nmodule.exports = pointerUtils;\n\n},{\"../InteractEvent\":2,\"./browser\":8,\"./extend\":11,\"./hypot\":12,\"./isType\":14,\"./window\":18}],17:[function(require,module,exports){\n'use strict';\n\nvar lastTime = 0,\n vendors = ['ms', 'moz', 'webkit', 'o'],\n reqFrame,\n cancelFrame;\n\nfor(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {\n reqFrame = window[vendors[x]+'RequestAnimationFrame'];\n cancelFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame'];\n}\n\nif (!reqFrame) {\n reqFrame = function(callback) {\n var currTime = new Date().getTime(),\n timeToCall = Math.max(0, 16 - (currTime - lastTime)),\n id = setTimeout(function() { callback(currTime + timeToCall); },\n timeToCall);\n lastTime = currTime + timeToCall;\n return id;\n };\n}\n\nif (!cancelFrame) {\n cancelFrame = function(id) {\n clearTimeout(id);\n };\n}\n\nmodule.exports = {\n request: reqFrame,\n cancel: cancelFrame\n};\n\n},{}],18:[function(require,module,exports){\n'use strict';\n\nvar isWindow = require('./isWindow');\n\nvar isShadowDom = function() {\n // create a TextNode\n var el = window.document.createTextNode('');\n\n // check if it's wrapped by a polyfill\n return el.ownerDocument !== window.document\n && typeof window.wrap === 'function'\n && window.wrap(el) === el;\n};\n\nvar win = {\n\n window: undefined,\n\n realWindow: window,\n\n getWindow: function getWindow (node) {\n if (isWindow(node)) {\n return node;\n }\n\n var rootNode = (node.ownerDocument || node);\n\n return rootNode.defaultView || rootNode.parentWindow || win.window;\n }\n};\n\nif (typeof window !== 'undefined') {\n if (isShadowDom()) {\n win.window = window.wrap(window);\n } else {\n win.window = window;\n }\n}\n\nmodule.exports = win;\n\n},{\"./isWindow\":15}]},{},[1]);\n"],"sourceRoot":"/source/"} \ No newline at end of file diff --git a/build/interact.min.js b/build/interact.min.js deleted file mode 100644 index c348c7fb6..000000000 --- a/build/interact.min.js +++ /dev/null @@ -1,5 +0,0 @@ -!function t(e,i,r){function n(o,a){if(!i[o]){if(!e[o]){var l="function"==typeof require&&require;if(!a&&l)return l(o,!0);if(s)return s(o,!0);var c=new Error("Cannot find module '"+o+"'");throw c.code="MODULE_NOT_FOUND",c}var p=i[o]={exports:{}};e[o][0].call(p.exports,function(t){var i=e[o][1][t];return n(i?i:t)},p,p.exports,t,e,i,r)}return i[o].exports}for(var s="function"==typeof require&&require,o=0;on;n++){r=g.interactions[n];var l=i;if(r.inertiaStatus.active&&r.target.options[r.prepared.name].inertia.allowResume&&r.mouse===o)for(;l;){if(l===r.element)return r.pointers[0]&&r.removePointer(r.pointers[0]),r.addPointer(t),r;l=g.parentElement(l)}}if(o||!v.supportsTouch&&!v.supportsPointerEvent){for(n=0;s>n;n++)if(g.interactions[n].mouse&&!g.interactions[n].inertiaStatus.active)return g.interactions[n];for(n=0;s>n;n++)if(g.interactions[n].mouse&&(!/down/.test(e)||!g.interactions[n].inertiaStatus.active))return r;return r=new x,r.mouse=!0,r}for(n=0;s>n;n++)if(g.contains(g.interactions[n].pointerIds,a))return g.interactions[n];if(/up|end|out/i.test(e))return null;for(n=0;s>n;n++)if(r=g.interactions[n],!(r.prepared.name&&!r.target.options.gesture.enabled||r.interacting()||!o&&r.mouse))return r.addPointer(t),r;return new x}function n(t){return function(e){var i,n,s=g.getActualElement(e.path?e.path[0]:e.target),o=g.getActualElement(e.currentTarget);if(v.supportsTouch&&/touch/.test(e.type))for(g.prevTouchTime=(new Date).getTime(),n=0;na&&("left"===t?t="right":"right"===t&&(t="left")),0>l&&("top"===t?t="bottom":"bottom"===t&&(t="top")),"left"===t)return i.x<(a>=0?s.left:s.right)+o;if("top"===t)return i.y<(l>=0?s.top:s.bottom)+o;if("right"===t)return i.x>(a>=0?s.right:s.left)-o;if("bottom"===t)return i.y>(l>=0?s.bottom:s.top)-o}return m.isElement(r)?m.isElement(e)?e===r:g.matchesUpTo(r,e,n):!1}function a(t,e,i){var r,n=this.getRect(i),s=!1,a=null,l=null,c=m.extend({},e.curCoords.page),p=this.options;if(!n)return null;if(g.actionIsEnabled.resize&&p.resize.enabled){var h=p.resize;if(r={left:!1,right:!1,top:!1,bottom:!1},g.isObject(h.edges)){for(var d in r)r[d]=o(d,h.edges[d],c,e._eventTarget,i,n,h.margin||g.margin);r.left=r.left&&!r.right,r.top=r.top&&!r.bottom,s=r.left||r.right||r.top||r.bottom}else{var u="y"!==p.resize.axis&&c.x>n.right-g.margin,v="x"!==p.resize.axis&&c.y>n.bottom-g.margin;s=u||v,l=(u?"x":"")+(v?"y":"")}}return a=s?"resize":g.actionIsEnabled.drag&&p.drag.enabled?"drag":null,g.actionIsEnabled.gesture&&e.pointerIds.length>=2&&!e.dragging&&!e.resizing&&(a="gesture"),a?{name:a,axis:l,edges:r}:null}function l(t,e){var i={},r=g.delegatedEvents[t.type],n=g.getActualElement(t.path?t.path[0]:t.target),o=n;e=e?!0:!1;for(var a in t)i[a]=t[a];for(i.originalEvent=t,i.preventDefault=s;m.isElement(o);){for(var l=0;lc;c++){var h=g.interactions[c],d=h.prepared.name,u=h.interacting();if(u){if(o++,o>=g.maxInteractions)return!1;if(h.target===t){if(a+=d===i.name|0,a>=n)return!1;if(h.element===e&&(l++,d!==i.name||l>=s))return!1}}}return g.maxInteractions>0},g.indexOfDeepestElement=function(t){var e,i,r,n,s,o=t[0],a=o?0:-1,l=[],c=[];for(n=1;nr;r++)if(i[r]===t)return!0;return!1});for(var x=t("./Interaction"),E=t("./InteractEvent"),w=0,S=y.length;S>w;w++){var b=y[w];g.listeners[b]=n(b)}g.interactables.indexOfElement=function(t,e){e=e||g.document;for(var i=0;is.left&&p.xs.top&&p.y=s.left&&u<=s.right&&v>=s.top&&v<=s.bottom}if(g.isNumber(a)){var f=Math.max(0,Math.min(s.right,d.right)-Math.max(s.left,d.left))*Math.max(0,Math.min(s.bottom,d.bottom)-Math.max(s.top,d.top)),y=f/(d.width*d.height);o=y>=a}return this.options.dropChecker&&(o=this.options.dropChecker(t,o,this,n,i,r)),o},dropChecker:function(t){return g.isFunction(t)?(this.options.dropChecker=t,this):null===t?(delete this.options.getRect,this):this.options.dropChecker},accept:function(t){return m.isElement(t)?(this.options.drop.accept=t,this):g.trySelector(t)?(this.options.drop.accept=t,this):null===t?(delete this.options.drop.accept,this):this.options.drop.accept},resizable:function(t){return g.isObject(t)?(this.options.resize.enabled=t.enabled===!1?!1:!0,this.setPerAction("resize",t),this.setOnEvents("resize",t),/^x$|^y$|^xy$/.test(t.axis)?this.options.resize.axis=t.axis:null===t.axis&&(this.options.resize.axis=g.defaultOptions.resize.axis),g.isBool(t.square)&&(this.options.resize.square=t.square),this):g.isBool(t)?(this.options.resize.enabled=t,this):this.options.resize},squareResize:function(t){return g.isBool(t)?(this.options.resize.square=t,this):null===t?(delete this.options.resize.square,this):this.options.resize.square},gesturable:function(t){return g.isObject(t)?(this.options.gesture.enabled=t.enabled===!1?!1:!0,this.setPerAction("gesture",t),this.setOnEvents("gesture",t),this):g.isBool(t)?(this.options.gesture.enabled=t,this):this.options.gesture},autoScroll:function(t){return g.isObject(t)?t=m.extend({actions:["drag","resize"]},t):g.isBool(t)&&(t={actions:["drag","resize"],enabled:t}),this.setOptions("autoScroll",t)},snap:function(t){var e=this.setOptions("snap",t);return e===this?this:e.drag},setOptions:function(t,e){var i,r=e&&g.isArray(e.actions)?e.actions:["drag"];if(g.isObject(e)||g.isBool(e)){for(i=0;ii&&!t.immediatePropagationStopped;i++)s=e[i].name,e[i](t);if(g.isFunction(this[n])&&(s=this[n].name,this[n](t)),t.type in g.globalEvents&&(e=g.globalEvents[t.type]))for(i=0,r=e.length;r>i&&!t.immediatePropagationStopped;i++)s=e[i].name,e[i](t);return this},on:function(t,e,i){var r;if(g.isString(t)&&-1!==t.search(" ")&&(t=t.trim().split(/ +/)),g.isArray(t)){for(r=0;r=0&&(o.selectors[s]!==this.selector||o.contexts[s]!==this._context);s--);-1===s&&(s=o.selectors.length,o.selectors.push(this.selector),o.contexts.push(this._context),o.listeners.push([])),o.listeners[s].push([e,i])}else f.add(this._element,t,e,i);return this},off:function(t,e,i){var r;if(g.isString(t)&&-1!==t.search(" ")&&(t=t.trim().split(/ +/)),g.isArray(t)){for(r=0;r=0;o--)if(a.selectors[o]===this.selector&&a.contexts[o]===this._context){var h=a.listeners[o];for(r=h.length-1;r>=0;r--){var d=h[r][0],u=h[r][1];if(d===e&&u===i){h.splice(r,1),h.length||(a.selectors.splice(o,1),a.contexts.splice(o,1),a.listeners.splice(o,1),f.remove(this._context,t,l),f.remove(this._context,t,c,!0),a.selectors.length||(g.delegatedEvents[t]=null)),p=!0;break}}if(p)break}}else f.remove(this._element,t,e,i);return this},set:function(t){g.isObject(t)||(t={}),this.options=m.extend({},g.defaultOptions.base);var e,i=["drag","drop","resize","gesture"],r=["draggable","dropzone","resizable","gesturable"],n=m.extend(m.extend({},g.defaultOptions.perAction),t[s]||{});for(e=0;ee;e++){var a=o[e];this.options[a]=g.defaultOptions.base[a],a in t&&this[a](t[a])}return this},unset:function(){if(f.remove(this._element,"all"),g.isString(this.selector))for(var t in g.delegatedEvents)for(var e=g.delegatedEvents[t],i=0;i0;e--)g.interactions[e].stop(t);return p},p.dynamicDrop=function(t){return g.isBool(t)?(g.dynamicDrop=t,p):g.dynamicDrop},p.pointerMoveTolerance=function(t){return g.isNumber(t)?(g.pointerMoveTolerance=t,this):g.pointerMoveTolerance},p.maxInteractions=function(t){return g.isNumber(t)?(g.maxInteractions=t,this):g.maxInteractions},p.createSnapGrid=function(t){return function(e,i){var r=0,n=0;g.isObject(t.offset)&&(r=t.offset.x,n=t.offset.y);var s=Math.round((e-r)/t.x),o=Math.round((i-n)/t.y),a=s*t.x+r,l=o*t.y+n;return{x:a,y:l,range:t.range}}},u(g.document),g.interact=p,g.Interactable=h,g.Interaction=x,g.InteractEvent=E,e.exports=p}},{"./InteractEvent":2,"./Interaction":3,"./autoScroll":4,"./defaultOptions":5,"./scope":6,"./utils":13,"./utils/events":10,"./utils/window":18}],2:[function(t,e,i){"use strict";function r(t,e,i,o,a,l){var c,p,h=t.target,d=t.snapStatus,u=t.restrictStatus,g=t.pointers,m=(h&&h.options||n.defaultOptions).deltaSource,v=m+"X",f=m+"Y",y=h?h.options:n.defaultOptions,x=n.getOriginXY(h,a),E="start"===o,w="end"===o,S=E?t.startCoords:t.curCoords;a=a||t.element,p=s.extend({},S.page),c=s.extend({},S.client),p.x-=x.x,p.y-=x.y,c.x-=x.x,c.y-=x.y;var b=y[i].snap&&y[i].snap.relativePoints;!n.checkSnap(h,i)||E&&b&&b.length||(this.snap={range:d.range,locked:d.locked,x:d.snappedX,y:d.snappedY,realX:d.realX,realY:d.realY,dx:d.dx,dy:d.dy},d.locked&&(p.x+=d.dx,p.y+=d.dy,c.x+=d.dx,c.y+=d.dy)),!n.checkRestrict(h,i)||E&&y[i].restrict.elementRect||!u.restricted||(p.x+=u.dx,p.y+=u.dy,c.x+=u.dx,c.y+=u.dy,this.restrict={dx:u.dx,dy:u.dy}),this.pageX=p.x,this.pageY=p.y,this.clientX=c.x,this.clientY=c.y,this.x0=t.startCoords.page.x-x.x,this.y0=t.startCoords.page.y-x.y,this.clientX0=t.startCoords.client.x-x.x,this.clientY0=t.startCoords.client.y-x.y,this.ctrlKey=e.ctrlKey,this.altKey=e.altKey,this.shiftKey=e.shiftKey,this.metaKey=e.metaKey,this.button=e.button,this.target=a,this.t0=t.downTimes[0],this.type=i+(o||""),this.interaction=t,this.interactable=h;var T=t.inertiaStatus;if(T.active&&(this.detail="inertia"),l&&(this.relatedTarget=l),w?"client"===m?(this.dx=c.x-t.startCoords.client.x,this.dy=c.y-t.startCoords.client.y):(this.dx=p.x-t.startCoords.page.x,this.dy=p.y-t.startCoords.page.y):E?(this.dx=0,this.dy=0):"inertiastart"===o?(this.dx=t.prevEvent.dx,this.dy=t.prevEvent.dy):"client"===m?(this.dx=c.x-t.prevEvent.clientX,this.dy=c.y-t.prevEvent.clientY):(this.dx=p.x-t.prevEvent.pageX,this.dy=p.y-t.prevEvent.pageY),t.prevEvent&&"inertia"===t.prevEvent.detail&&!T.active&&y[i].inertia&&y[i].inertia.zeroResumeDelta&&(T.resumeDx+=this.dx,T.resumeDy+=this.dy,this.dx=this.dy=0),"resize"===i&&t.resizeAxes?y.resize.square?("y"===t.resizeAxes?this.dx=this.dy:this.dy=this.dx,this.axes="xy"):(this.axes=t.resizeAxes,"x"===t.resizeAxes?this.dy=0:"y"===t.resizeAxes&&(this.dx=0)):"gesture"===i&&(this.touches=[g[0],g[1]],E?(this.distance=s.touchDistance(g,m),this.box=s.touchBBox(g),this.scale=1,this.ds=0,this.angle=s.touchAngle(g,void 0,m),this.da=0):w||e instanceof r?(this.distance=t.prevEvent.distance,this.box=t.prevEvent.box,this.scale=t.prevEvent.scale,this.ds=this.scale-1,this.angle=t.prevEvent.angle,this.da=this.angle-t.gesture.startAngle):(this.distance=s.touchDistance(g,m),this.box=s.touchBBox(g),this.scale=this.distance/t.gesture.startDistance,this.angle=s.touchAngle(g,t.gesture.prevAngle,m),this.ds=this.scale-t.gesture.prevScale,this.da=this.angle-t.gesture.prevAngle)),E)this.timeStamp=t.downTimes[0],this.dt=0,this.duration=0,this.speed=0,this.velocityX=0,this.velocityY=0;else if("inertiastart"===o)this.timeStamp=t.prevEvent.timeStamp,this.dt=t.prevEvent.dt,this.duration=t.prevEvent.duration,this.speed=t.prevEvent.speed,this.velocityX=t.prevEvent.velocityX,this.velocityY=t.prevEvent.velocityY;else if(this.timeStamp=(new Date).getTime(),this.dt=this.timeStamp-t.prevEvent.timeStamp,this.duration=this.timeStamp-t.downTimes[0],e instanceof r){var D=this[v]-t.prevEvent[v],z=this[f]-t.prevEvent[f],O=this.dt/1e3;this.speed=s.hypot(D,z)/O,this.velocityX=D/O,this.velocityY=z/O}else this.speed=t.pointerDelta[m].speed,this.velocityX=t.pointerDelta[m].vx,this.velocityY=t.pointerDelta[m].vy;if((w||"inertiastart"===o)&&t.prevEvent.speed>600&&this.timeStamp-t.prevEvent.timeStamp<150){var C=180*Math.atan2(t.prevEvent.velocityY,t.prevEvent.velocityX)/Math.PI,M=22.5;0>C&&(C+=360);var P=C>=135-M&&225+M>C,I=C>=225-M&&315+M>C,A=!P&&(C>=315-M||45+M>C),_=!I&&C>=45-M&&135+M>C;this.swipe={up:I,down:_,left:P,right:A,angle:C,speed:t.prevEvent.speed,velocity:{x:t.prevEvent.velocityX,y:t.prevEvent.velocityY}}}}var n=t("./scope"),s=t("./utils");r.prototype={preventDefault:s.blank,stopImmediatePropagation:function(){this.immediatePropagationStopped=this.propagationStopped=!0},stopPropagation:function(){this.propagationStopped=!0}},e.exports=r},{"./scope":6,"./utils":13}],3:[function(t,e,i){"use strict";function r(){if(this.target=null,this.element=null,this.dropTarget=null,this.dropElement=null, -this.prevDropTarget=null,this.prevDropElement=null,this.prepared={name:null,axis:null,edges:null},this.matches=[],this.matchElements=[],this.inertiaStatus={active:!1,smoothEnd:!1,startEvent:null,upCoords:{},xe:0,ye:0,sx:0,sy:0,t0:0,vx0:0,vys:0,duration:0,resumeDx:0,resumeDy:0,lambda_v0:0,one_ve_v0:0,i:null},a.isFunction(Function.prototype.bind))this.boundInertiaFrame=this.inertiaFrame.bind(this),this.boundSmoothEndFrame=this.smoothEndFrame.bind(this);else{var t=this;this.boundInertiaFrame=function(){return t.inertiaFrame()},this.boundSmoothEndFrame=function(){return t.smoothEndFrame()}}this.activeDrops={dropzones:[],elements:[],rects:[]},this.pointers=[],this.pointerIds=[],this.downTargets=[],this.downTimes=[],this.holdTimers=[],this.prevCoords={page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},this.curCoords={page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},this.startCoords={page:{x:0,y:0},client:{x:0,y:0},timeStamp:0},this.pointerDelta={page:{x:0,y:0,vx:0,vy:0,speed:0},client:{x:0,y:0,vx:0,vy:0,speed:0},timeStamp:0},this.downEvent=null,this.downPointer={},this._eventTarget=null,this._curEventTarget=null,this.prevEvent=null,this.tapTime=0,this.prevTap=null,this.startOffset={left:0,right:0,top:0,bottom:0},this.restrictOffset={left:0,right:0,top:0,bottom:0},this.snapOffsets=[],this.gesture={start:{x:0,y:0},startDistance:0,prevDistance:0,distance:0,scale:1,startAngle:0,prevAngle:0},this.snapStatus={x:0,y:0,dx:0,dy:0,realX:0,realY:0,snappedX:0,snappedY:0,targets:[],locked:!1,changed:!1},this.restrictStatus={dx:0,dy:0,restrictedX:0,restrictedY:0,snap:null,restricted:!1,changed:!1},this.restrictStatus.snap=this.snapStatus,this.pointerIsDown=!1,this.pointerWasMoved=!1,this.gesturing=!1,this.dragging=!1,this.resizing=!1,this.resizeAxes="xy",this.mouse=!1,a.interactions.push(this)}function n(t,e){if(!a.isObject(t))return null;var i=t.name,r=e.options;return("resize"===i&&r.resize.enabled||"drag"===i&&r.drag.enabled||"gesture"===i&&r.gesture.enabled)&&a.actionIsEnabled[i]?(("resize"===i||"resizeyx"===i)&&(i="resizexy"),t):null}function s(t){var e="";if("drag"===t.name&&(e=a.actionCursors.drag),"resize"===t.name)if(t.axis)e=a.actionCursors[t.name+t.axis];else if(t.edges){for(var i="resize",r=["top","bottom","left","right"],n=0;4>n;n++)t.edges[r[n]]&&(i+=r[n]);e=a.actionCursors[i]}return e}function o(){this.originalEvent.preventDefault()}var a=t("./scope"),l=t("./utils"),c=l.raf,p=t("./InteractEvent"),h=t("./utils/events"),d=t("./utils/browser");r.prototype={getPageXY:function(t,e){return l.getPageXY(t,e,this)},getClientXY:function(t,e){return l.getClientXY(t,e,this)},setEventXY:function(t,e){return l.setEventXY(t,e,this)},pointerOver:function(t,e,i){function r(t,e){t&&a.inContext(t,i)&&!a.testIgnore(t,i,i)&&a.testAllow(t,i,i)&&a.matchesSelector(i,e)&&(s.push(t),o.push(i))}if(!this.prepared.name&&this.mouse){var s=[],o=[],l=this.element;this.addPointer(t),!this.target||!a.testIgnore(this.target,this.element,i)&&a.testAllow(this.target,this.element,i)||(this.target=null,this.element=null,this.matches=[],this.matchElements=[]);var c=a.interactables.get(i),p=c&&!a.testIgnore(c,i,i)&&a.testAllow(c,i,i)&&n(c.getAction(t,e,this,i),c);p&&!a.withinInteractionLimit(c,i,p)&&(p=null),p?(this.target=c,this.element=i,this.matches=[],this.matchElements=[]):(a.interactables.forEachSelector(r),this.validateSelector(t,e,s,o)?(this.matches=s,this.matchElements=o,this.pointerHover(t,e,this.matches,this.matchElements),h.add(i,a.PointerEvent?a.pEventTypes.move:"mousemove",a.listeners.pointerHover)):this.target&&(a.nodeContains(l,i)?(this.pointerHover(t,e,this.matches,this.matchElements),h.add(this.element,a.PointerEvent?a.pEventTypes.move:"mousemove",a.listeners.pointerHover)):(this.target=null,this.element=null,this.matches=[],this.matchElements=[])))}},pointerHover:function(t,e,i,r,o,a){var l=this.target;if(!this.prepared.name&&this.mouse){var c;this.setEventXY(this.curCoords,t),o?c=this.validateSelector(t,e,o,a):l&&(c=n(l.getAction(this.pointers[0],e,this,this.element),this.target)),l&&l.options.styleCursor&&(l._doc.documentElement.style.cursor=c?s(c):"")}else this.prepared.name&&this.checkAndPreventDefault(e,l,this.element)},pointerOut:function(t,e,i){this.prepared.name||(a.interactables.get(i)||h.remove(i,a.PointerEvent?a.pEventTypes.move:"mousemove",a.listeners.pointerHover),this.target&&this.target.options.styleCursor&&!this.interacting()&&(this.target._doc.documentElement.style.cursor=""))},selectorDown:function(t,e,i,r){function s(t,e,r){var n=a.ie8MatchesSelector?r.querySelectorAll(e):void 0;a.inContext(t,u)&&!a.testIgnore(t,u,i)&&a.testAllow(t,u,i)&&a.matchesSelector(u,e,n)&&(p.matches.push(t),p.matchElements.push(u))}var o,p=this,d=h.useAttachEvent?l.extend({},e):e,u=i,g=this.addPointer(t);if(this.holdTimers[g]=setTimeout(function(){p.pointerHold(h.useAttachEvent?d:t,d,i,r)},a.defaultOptions._holdDuration),this.pointerIsDown=!0,this.inertiaStatus.active&&this.target.selector)for(;l.isElement(u);){if(u===this.element&&n(this.target.getAction(t,e,this,this.element),this.target).name===this.prepared.name)return c.cancel(this.inertiaStatus.i),this.inertiaStatus.active=!1,void this.collectEventTargets(t,e,i,"down");u=a.parentElement(u)}if(this.interacting())return void this.collectEventTargets(t,e,i,"down");for(this.setEventXY(this.curCoords,t),this.downEvent=e;l.isElement(u)&&!o;)this.matches=[],this.matchElements=[],a.interactables.forEachSelector(s),o=this.validateSelector(t,e,this.matches,this.matchElements),u=a.parentElement(u);return o?(this.prepared.name=o.name,this.prepared.axis=o.axis,this.prepared.edges=o.edges,this.collectEventTargets(t,e,i,"down"),this.pointerDown(t,e,i,r,o)):(this.downTimes[g]=(new Date).getTime(),this.downTargets[g]=i,l.extend(this.downPointer,t),l.copyCoords(this.prevCoords,this.curCoords),this.pointerWasMoved=!1,void this.collectEventTargets(t,e,i,"down"))},pointerDown:function(t,e,i,r,o){if(!o&&!this.inertiaStatus.active&&this.pointerWasMoved&&this.prepared.name)return void this.checkAndPreventDefault(e,this.target,this.element);this.pointerIsDown=!0,this.downEvent=e;var p,h=this.addPointer(t);if(this.pointerIds.length<2&&!this.target||!this.prepared.name){var d=a.interactables.get(r);d&&!a.testIgnore(d,r,i)&&a.testAllow(d,r,i)&&(p=n(o||d.getAction(t,e,this,r),d,i))&&a.withinInteractionLimit(d,r,p)&&(this.target=d,this.element=r)}var u=this.target,g=u&&u.options;if(!u||!o&&this.prepared.name)this.inertiaStatus.active&&r===this.element&&n(u.getAction(t,e,this,this.element),u).name===this.prepared.name&&(c.cancel(this.inertiaStatus.i),this.inertiaStatus.active=!1,this.checkAndPreventDefault(e,u,this.element));else{if(p=p||n(o||u.getAction(t,e,this,r),u,this.element),this.setEventXY(this.startCoords),!p)return;g.styleCursor&&(u._doc.documentElement.style.cursor=s(p)),this.resizeAxes="resize"===p.name?p.axis:null,"gesture"===p&&this.pointerIds.length<2&&(p=null),this.prepared.name=p.name,this.prepared.axis=p.axis,this.prepared.edges=p.edges,this.snapStatus.snappedX=this.snapStatus.snappedY=this.restrictStatus.restrictedX=this.restrictStatus.restrictedY=0/0,this.downTimes[h]=(new Date).getTime(),this.downTargets[h]=i,l.extend(this.downPointer,t),this.setEventXY(this.prevCoords),this.pointerWasMoved=!1,this.checkAndPreventDefault(e,u,this.element)}},setModifications:function(t,e){var i=this.target,r=!0,n=a.checkSnap(i,this.prepared.name)&&(!i.options[this.prepared.name].snap.endOnly||e),s=a.checkRestrict(i,this.prepared.name)&&(!i.options[this.prepared.name].restrict.endOnly||e);return n?this.setSnapping(t):this.snapStatus.locked=!1,s?this.setRestriction(t):this.restrictStatus.restricted=!1,n&&this.snapStatus.locked&&!this.snapStatus.changed?r=s&&this.restrictStatus.restricted&&this.restrictStatus.changed:s&&this.restrictStatus.restricted&&!this.restrictStatus.changed&&(r=!1),r},setStartOffsets:function(t,e,i){var r,n,s=e.getRect(i),o=a.getOriginXY(e,i),l=e.options[this.prepared.name].snap,c=e.options[this.prepared.name].restrict;s?(this.startOffset.left=this.startCoords.page.x-s.left,this.startOffset.top=this.startCoords.page.y-s.top,this.startOffset.right=s.right-this.startCoords.page.x,this.startOffset.bottom=s.bottom-this.startCoords.page.y,r="width"in s?s.width:s.right-s.left,n="height"in s?s.height:s.bottom-s.top):this.startOffset.left=this.startOffset.top=this.startOffset.right=this.startOffset.bottom=0,this.snapOffsets.splice(0);var p=l&&"startCoords"===l.offset?{x:this.startCoords.page.x-o.x,y:this.startCoords.page.y-o.y}:l&&l.offset||{x:0,y:0};if(s&&l&&l.relativePoints&&l.relativePoints.length)for(var h=0;ha.pointerMoveTolerance),c||this.pointerIsDown&&!this.pointerWasMoved||(this.pointerIsDown&&clearTimeout(this.holdTimers[h]),this.collectEventTargets(t,e,i,"move")),this.pointerIsDown){if(c&&this.pointerWasMoved&&!n)return void this.checkAndPreventDefault(e,this.target,this.element);if(l.setEventDeltas(this.pointerDelta,this.prevCoords,this.curCoords),this.prepared.name){if(this.pointerWasMoved&&(!this.inertiaStatus.active||t instanceof p&&/inertiastart/.test(t.type))){if(!this.interacting()&&(l.setEventDeltas(this.pointerDelta,this.prevCoords,this.curCoords),"drag"===this.prepared.name)){var d=Math.abs(s),u=Math.abs(o),g=this.target.options.drag.axis,m=d>u?"x":u>d?"y":"xy";if("xy"!==m&&"xy"!==g&&g!==m){this.prepared.name=null;for(var v=i;l.isElement(v);){var f=a.interactables.get(v);if(f&&f!==this.target&&!f.options.drag.manualStart&&"drag"===f.getAction(this.downPointer,this.downEvent,this,v).name&&a.checkAxis(m,f)){this.prepared.name="drag",this.target=f,this.element=v;break}v=a.parentElement(v)}if(!this.prepared.name){var y=this,x=function(t,e,r){var n=a.ie8MatchesSelector?r.querySelectorAll(e):void 0;if(t!==y.target)return a.inContext(t,i)&&!t.options.drag.manualStart&&!a.testIgnore(t,v,i)&&a.testAllow(t,v,i)&&a.matchesSelector(v,e,n)&&"drag"===t.getAction(y.downPointer,y.downEvent,y,v).name&&a.checkAxis(m,t)&&a.withinInteractionLimit(t,v,"drag")?t:void 0};for(v=i;l.isElement(v);){var E=a.interactables.forEachSelector(x);if(E){this.prepared.name="drag",this.target=E,this.element=v;break}v=a.parentElement(v)}}}}var w=!!this.prepared.name&&!this.interacting();if(w&&(this.target.options[this.prepared.name].manualStart||!a.withinInteractionLimit(this.target,this.element,this.prepared)))return void this.stop(e);if(this.prepared.name&&this.target){w&&this.start(this.prepared,this.target,this.element);var S=this.setModifications(this.curCoords.page,n);(S||w)&&(this.prevEvent=this[this.prepared.name+"Move"](e)),this.checkAndPreventDefault(e,this.target,this.element)}}l.copyCoords(this.prevCoords,this.curCoords),(this.dragging||this.resizing)&&this.autoScrollMove(t)}}},dragStart:function(t){var e=new p(this,t,"drag","start",this.element);this.dragging=!0,this.target.fire(e),this.activeDrops.dropzones=[],this.activeDrops.elements=[],this.activeDrops.rects=[],this.dynamicDrop||this.setActiveDrops(this.element);var i=this.getDropEvents(t,e);return i.activate&&this.fireActiveDrops(i.activate),e},dragMove:function(t){var e=this.target,i=new p(this,t,"drag","move",this.element),r=this.element,n=this.getDrop(t,r);this.dropTarget=n.dropzone,this.dropElement=n.element;var s=this.getDropEvents(t,i);return e.fire(i),s.leave&&this.prevDropTarget.fire(s.leave),s.enter&&this.dropTarget.fire(s.enter),s.move&&this.dropTarget.fire(s.move),this.prevDropTarget=this.dropTarget,this.prevDropElement=this.dropElement,i},resizeStart:function(t){var e=new p(this,t,"resize","start",this.element);if(this.prepared.edges){var i=this.target.getRect(this.element);if(this.target.options.resize.square){var r=l.extend({},this.prepared.edges);r.top=r.top||r.left&&!r.bottom,r.left=r.left||r.top&&!r.right,r.bottom=r.bottom||r.right&&!r.top,r.right=r.right||r.bottom&&!r.left,this.prepared._squareEdges=r}else this.prepared._squareEdges=null;this.resizeRects={start:i,current:l.extend({},i),restricted:l.extend({},i),previous:l.extend({},i),delta:{left:0,right:0,width:0,top:0,bottom:0,height:0}},e.rect=this.resizeRects.restricted,e.deltaRect=this.resizeRects.delta}return this.target.fire(e),this.resizing=!0,e},resizeMove:function(t){var e=new p(this,t,"resize","move",this.element),i=this.prepared.edges,r=this.target.options.resize.invert,n="reposition"===r||"negate"===r;if(i){var s=e.dx,o=e.dy,a=this.resizeRects.start,c=this.resizeRects.current,h=this.resizeRects.restricted,d=this.resizeRects.delta,u=l.extend(this.resizeRects.previous,h);if(this.target.options.resize.square){var g=i;i=this.prepared._squareEdges,g.left&&g.bottom||g.right&&g.top?o=-s:g.left||g.right?o=s:(g.top||g.bottom)&&(s=o)}if(i.top&&(c.top+=o),i.bottom&&(c.bottom+=o),i.left&&(c.left+=s),i.right&&(c.right+=s),n){if(l.extend(h,c),"reposition"===r){var m;h.top>h.bottom&&(m=h.top,h.top=h.bottom,h.bottom=m),h.left>h.right&&(m=h.left,h.left=h.right,h.right=m)}}else h.top=Math.min(c.top,a.bottom),h.bottom=Math.max(c.bottom,a.top),h.left=Math.min(c.left,a.right),h.right=Math.max(c.right,a.left);h.width=h.right-h.left,h.height=h.bottom-h.top;for(var v in h)d[v]=h[v]-u[v];e.edges=this.prepared.edges,e.rect=h,e.deltaRect=d}return this.target.fire(e),e},gestureStart:function(t){var e=new p(this,t,"gesture","start",this.element);return e.ds=0,this.gesture.startDistance=this.gesture.prevDistance=e.distance,this.gesture.startAngle=this.gesture.prevAngle=e.angle,this.gesture.scale=1,this.gesturing=!0,this.target.fire(e),e},gestureMove:function(t){if(!this.pointerIds.length)return this.prevEvent;var e;return e=new p(this,t,"gesture","move",this.element),e.ds=e.scale-this.gesture.scale,this.target.fire(e),this.gesture.prevAngle=e.angle,this.gesture.prevDistance=e.distance,e.scale===1/0||null===e.scale||void 0===e.scale||isNaN(e.scale)||(this.gesture.scale=e.scale),e},pointerHold:function(t,e,i){this.collectEventTargets(t,e,i,"hold")},pointerUp:function(t,e,i,r){var n=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));clearTimeout(this.holdTimers[n]),this.collectEventTargets(t,e,i,"up"),this.collectEventTargets(t,e,i,"tap"),this.pointerEnd(t,e,i,r),this.removePointer(t)},pointerCancel:function(t,e,i,r){var n=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));clearTimeout(this.holdTimers[n]),this.collectEventTargets(t,e,i,"cancel"),this.pointerEnd(t,e,i,r),this.removePointer(t)},ie8Dblclick:function(t,e,i){this.prevTap&&e.clientX===this.prevTap.clientX&&e.clientY===this.prevTap.clientY&&i===this.prevTap.target&&(this.downTargets[0]=i,this.downTimes[0]=(new Date).getTime(),this.collectEventTargets(t,e,i,"tap"))},pointerEnd:function(t,e,i,r){var n,s=this.target,o=s&&s.options,h=o&&this.prepared.name&&o[this.prepared.name].inertia,d=this.inertiaStatus;if(this.interacting()){if(d.active)return;var u,g,m=(new Date).getTime(),v=!1,f=!1,y=!1,x=a.checkSnap(s,this.prepared.name)&&o[this.prepared.name].snap.endOnly,E=a.checkRestrict(s,this.prepared.name)&&o[this.prepared.name].restrict.endOnly,w=0,S=0;if(u=this.dragging?"x"===o.drag.axis?Math.abs(this.pointerDelta.client.vx):"y"===o.drag.axis?Math.abs(this.pointerDelta.client.vy):this.pointerDelta.client.speed:this.pointerDelta.client.speed,v=h&&h.enabled&&"gesture"!==this.prepared.name&&e!==d.startEvent,f=v&&m-this.curCoords.timeStamp<50&&u>h.minSpeed&&u>h.endSpeed,v&&!f&&(x||E)){var b={};b.snap=b.restrict=b,x&&(this.setSnapping(this.curCoords.page,b),b.locked&&(w+=b.dx,S+=b.dy)),E&&(this.setRestriction(this.curCoords.page,b),b.restricted&&(w+=b.dx,S+=b.dy)),(w||S)&&(y=!0)}if(f||y){if(l.copyCoords(d.upCoords,this.curCoords),this.pointers[0]=d.startEvent=g=new p(this,e,this.prepared.name,"inertiastart",this.element),d.t0=m,s.fire(d.startEvent),f){d.vx0=this.pointerDelta.client.vx,d.vy0=this.pointerDelta.client.vy,d.v0=u,this.calcInertia(d);var T,D=l.extend({},this.curCoords.page),z=a.getOriginXY(s,this.element);if(D.x=D.x+d.xe-z.x,D.y=D.y+d.ye-z.y,T={useStatusXY:!0,x:D.x,y:D.y,dx:0,dy:0,snap:null},T.snap=T,w=S=0,x){var O=this.setSnapping(this.curCoords.page,T);O.locked&&(w+=O.dx,S+=O.dy)}if(E){var C=this.setRestriction(this.curCoords.page,T);C.restricted&&(w+=C.dx,S+=C.dy)}d.modifiedXe+=w,d.modifiedYe+=S,d.i=c.request(this.boundInertiaFrame)}else d.smoothEnd=!0,d.xe=w,d.ye=S,d.sx=d.sy=0,d.i=c.request(this.boundSmoothEndFrame);return void(d.active=!0)}(x||E)&&this.pointerMove(t,e,i,r,!0)}if(this.dragging){n=new p(this,e,"drag","end",this.element);var M=this.element,P=this.getDrop(e,M);this.dropTarget=P.dropzone,this.dropElement=P.element;var I=this.getDropEvents(e,n);I.leave&&this.prevDropTarget.fire(I.leave),I.enter&&this.dropTarget.fire(I.enter),I.drop&&this.dropTarget.fire(I.drop),I.deactivate&&this.fireActiveDrops(I.deactivate),s.fire(n)}else this.resizing?(n=new p(this,e,"resize","end",this.element),s.fire(n)):this.gesturing&&(n=new p(this,e,"gesture","end",this.element),s.fire(n));this.stop(e)},collectDrops:function(t){var e,i=[],r=[];for(t=t||this.element,e=0;ec;c++){var h=o[c];h!==t&&(i.push(n),r.push(h))}}return{dropzones:i,elements:r}},fireActiveDrops:function(t){var e,i,r,n;for(e=0;ee?(t.sx=a.easeOutQuad(e,0,t.xe,i),t.sy=a.easeOutQuad(e,0,t.ye,i),this.pointerMove(t.startEvent,t.startEvent),t.i=c.request(this.boundSmoothEndFrame)):(t.sx=t.xe,t.sy=t.ye,this.pointerMove(t.startEvent,t.startEvent),t.active=!1,t.smoothEnd=!1,this.pointerEnd(t.startEvent,t.startEvent))},addPointer:function(t){var e=l.getPointerId(t),i=this.mouse?0:a.indexOf(this.pointerIds,e);return-1===i&&(i=this.pointerIds.length),this.pointerIds[i]=e,this.pointers[i]=t,i},removePointer:function(t){var e=l.getPointerId(t),i=this.mouse?0:a.indexOf(this.pointerIds,e);-1!==i&&(this.interacting()||this.pointers.splice(i,1),this.pointerIds.splice(i,1),this.downTargets.splice(i,1),this.downTimes.splice(i,1),this.holdTimers.splice(i,1))},recordPointer:function(t){if(!this.inertiaStatus.active){var e=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));-1!==e&&(this.pointers[e]=t)}},collectEventTargets:function(t,e,i,r){function n(t,e,n){var s=a.ie8MatchesSelector?n.querySelectorAll(e):void 0;t._iEvents[r]&&l.isElement(p)&&a.inContext(t,p)&&!a.testIgnore(t,p,i)&&a.testAllow(t,p,i)&&a.matchesSelector(p,e,s)&&(o.push(t),c.push(p))}var s=this.mouse?0:a.indexOf(this.pointerIds,l.getPointerId(t));if("tap"!==r||!this.pointerWasMoved&&this.downTargets[s]&&this.downTargets[s]===i){for(var o=[],c=[],p=i,h=a.interact;p;)h.isSet(p)&&h(p)._iEvents[r]&&(o.push(h(p)),c.push(p)),a.interactables.forEachSelector(n),p=a.parentElement(p);(o.length||"tap"===r)&&this.firePointers(t,e,i,o,c,r)}},firePointers:function(t,e,i,r,n,s){var c,h,u,g=this.mouse?0:a.indexOf(l.getPointerId(t)),m={};for("doubletap"===s?m=t:(l.extend(m,e),e!==t&&l.extend(m,t),m.preventDefault=o,m.stopPropagation=p.prototype.stopPropagation,m.stopImmediatePropagation=p.prototype.stopImmediatePropagation,m.interaction=this,m.timeStamp=(new Date).getTime(),m.originalEvent=e,m.type=s,m.pointerId=l.getPointerId(t),m.pointerType=this.mouse?"mouse":d.supportsPointerEvent?a.isString(t.pointerType)?t.pointerType:[,,"touch","pen","mouse"][t.pointerType]:"touch"),"tap"===s&&(m.dt=m.timeStamp-this.downTimes[g],h=m.timeStamp-this.tapTime,u=!!(this.prevTap&&"doubletap"!==this.prevTap.type&&this.prevTap.target===m.target&&500>h),m["double"]=u,this.tapTime=m.timeStamp),c=0;cs;s++){var l=i[s],c=r[s],p=n(l.getAction(t,e,this,c),l);if(p&&a.withinInteractionLimit(l,c,p))return this.target=l,this.element=c,p}},setSnapping:function(t,e){var i,r,n,s=this.target.options[this.prepared.name].snap,o=[];if(e=e||this.snapStatus,e.useStatusXY)r={x:e.x,y:e.y};else{var c=a.getOriginXY(this.target,this.element);r=l.extend({},t),r.x-=c.x,r.y-=c.y}e.realX=r.x,e.realY=r.y,r.x=r.x-this.inertiaStatus.resumeDx,r.y=r.y-this.inertiaStatus.resumeDy;for(var p=s.targets?s.targets.length:0,h=0;hn;n++)i=a.isFunction(s.targets[n])?s.targets[n](d.x,d.y,this):s.targets[n],i&&o.push({x:a.isNumber(i.x)?i.x+this.snapOffsets[h].x:d.x,y:a.isNumber(i.y)?i.y+this.snapOffsets[h].y:d.y,range:a.isNumber(i.range)?i.range:s.range})}var u={target:null,inRange:!1,distance:0,range:0,dx:0,dy:0};for(n=0,p=o.length;p>n;n++){i=o[n];var g=i.range,m=i.x-r.x,v=i.y-r.y,f=l.hypot(m,v),y=g>=f;g===1/0&&u.inRange&&u.range!==1/0&&(y=!1),(!u.target||(y?u.inRange&&g!==1/0?f/go.innerWidth-a.autoScroll.margin,r=t.clientY>o.innerHeight-a.autoScroll.margin;else{var l=a.getElementRect(o);n=t.clientXl.right-a.autoScroll.margin,r=t.clientY>l.bottom-a.autoScroll.margin}a.autoScroll.x=i?1:n?-1:0,a.autoScroll.y=r?1:e?-1:0,a.autoScroll.isScrolling||(a.autoScroll.margin=s.margin,a.autoScroll.speed=s.speed,a.autoScroll.start(this))}},_updateEventTargets:function(t,e){this._eventTarget=t,this._curEventTarget=e}},e.exports=r},{"./InteractEvent":2,"./scope":6,"./utils":13,"./utils/browser":8,"./utils/events":10}],4:[function(t,e,i){"use strict";var r=t("./utils/raf"),n=t("./utils/window").getWindow,s=t("./utils/isType").isWindow,o={interaction:null,i:null,x:0,y:0,isScrolling:!1,prevTime:0,start:function(t){o.isScrolling=!0,r.cancel(o.i),o.interaction=t,o.prevTime=(new Date).getTime(),o.i=r.request(o.scroll)},stop:function(){o.isScrolling=!1,r.cancel(o.i)},scroll:function(){var t=o.interaction.target.options[o.interaction.prepared.name].autoScroll,e=t.container||n(o.interaction.element),i=(new Date).getTime(),a=(i-o.prevTime)/1e3,l=t.speed*a;l>=1&&(s(e)?e.scrollBy(o.x*l,o.y*l):e&&(e.scrollLeft+=o.x*l,e.scrollTop+=o.y*l),o.prevTime=i),o.isScrolling&&(r.cancel(o.i),o.i=r.request(o.scroll))}};e.exports=o},{"./utils/isType":14,"./utils/raf":17,"./utils/window":18}],5:[function(t,e,i){"use strict";e.exports={base:{accept:null,actionChecker:null,styleCursor:!0,preventDefault:"auto",origin:{x:0,y:0},deltaSource:"page",allowFrom:null,ignoreFrom:null,_context:t("./utils/domObjects").document,dropChecker:null},drag:{enabled:!1,manualStart:!0,max:1/0,maxPerElement:1,snap:null,restrict:null,inertia:null,autoScroll:null,axis:"xy"},drop:{enabled:!1,accept:null,overlap:"pointer"},resize:{enabled:!1,manualStart:!1,max:1/0,maxPerElement:1,snap:null,restrict:null,inertia:null,autoScroll:null,square:!1,axis:"xy",margin:0/0,edges:null,invert:"none"},gesture:{manualStart:!1,enabled:!1,max:1/0,maxPerElement:1,restrict:null},perAction:{manualStart:!1,max:1/0,maxPerElement:1,snap:{enabled:!1,endOnly:!1,range:1/0,targets:null,offsets:null,relativePoints:null},restrict:{enabled:!1,endOnly:!1},autoScroll:{enabled:!1,container:null,margin:60,speed:300},inertia:{enabled:!1,resistance:10,minSpeed:100,endSpeed:10,allowResume:!0,zeroResumeDelta:!0,smoothEndDuration:300}},_holdDuration:600}},{"./utils/domObjects":9}],6:[function(t,e,i){"use strict"; - -var r={},n=t("./utils/extend");n(r,t("./utils/window")),n(r,t("./utils/domObjects")),n(r,t("./utils/arr.js")),n(r,t("./utils/isType")),e.exports=r},{"./utils/arr.js":7,"./utils/domObjects":9,"./utils/extend":11,"./utils/isType":14,"./utils/window":18}],7:[function(t,e,i){"use strict";function r(t,e){for(var i=0,r=t.length;r>i;i++)if(t[i]===e)return i;return-1}function n(t,e){return-1!==r(t,e)}e.exports={indexOf:r,contains:n}},{}],8:[function(t,e,i){"use strict";var r=t("./window"),n=t("./domObjects"),s={supportsTouch:!!("ontouchstart"in r||r.window.DocumentTouch&&n.document instanceof r.DocumentTouch),supportsPointerEvent:!!n.PointerEvent,isOperaMobile:"Opera"===navigator.appName&&s.supportsTouch&&navigator.userAgent.match("Presto"),isIOS7orLower:/iP(hone|od|ad)/.test(navigator.platform)&&/OS [1-7][^\d]/.test(navigator.appVersion),isIe9OrOlder:n.document.all&&!r.window.atob,prefixedMatchesSelector:"matches"in Element.prototype?"matches":"webkitMatchesSelector"in Element.prototype?"webkitMatchesSelector":"mozMatchesSelector"in Element.prototype?"mozMatchesSelector":"oMatchesSelector"in Element.prototype?"oMatchesSelector":"msMatchesSelector"};e.exports=s},{"./domObjects":9,"./window":18}],9:[function(t,e,i){"use strict";var r={},n=t("./window").window,s=function(){};r.document=n.document,r.DocumentFragment=n.DocumentFragment||s,r.SVGElement=n.SVGElement||s,r.SVGSVGElement=n.SVGSVGElement||s,r.SVGElementInstance=n.SVGElementInstance||s,r.HTMLElement=n.HTMLElement||n.Element,r.PointerEvent=n.PointerEvent||n.MSPointerEvent,e.exports=r},{"./window":18}],10:[function(t,e,i){"use strict";function r(t,e,i,r){var n=c(v,t),l=f[n];if(l||(l={events:{},typeCount:0},n=v.push(t)-1,f.push(l),y.push(d?{supplied:[],wrapped:[],useCount:[]}:null)),l.events[e]||(l.events[e]=[],l.typeCount++),!p(l.events[e],i)){var g;if(d){var x=y[n],E=c(x.supplied,i),w=x.wrapped[E]||function(e){e.immediatePropagationStopped||(e.target=e.srcElement,e.currentTarget=t,e.preventDefault=e.preventDefault||s,e.stopPropagation=e.stopPropagation||o,e.stopImmediatePropagation=e.stopImmediatePropagation||a,/mouse|click/.test(e.type)&&(e.pageX=e.clientX+h(t).document.documentElement.scrollLeft,e.pageY=e.clientY+h(t).document.documentElement.scrollTop),i(e))};g=t[u](m+e,w,!!r),-1===E?(x.supplied.push(i),x.wrapped.push(w),x.useCount.push(1)):x.useCount[E]++}else g=t[u](e,i,!!r);return l.events[e].push(i),g}}function n(t,e,i,r){var s,o,a,l=c(v,t),p=f[l],h=i;if(p&&p.events)if(d&&(o=y[l],a=c(o.supplied,i),h=o.wrapped[a]),"all"!==e){if(p.events[e]){var u=p.events[e].length;if("all"===i){for(s=0;u>s;s++)n(t,e,p.events[e][s],!!r);return}for(s=0;u>s;s++)if(p.events[e][s]===i){t[g](m+e,h,!!r),p.events[e].splice(s,1),d&&o&&(o.useCount[a]--,0===o.useCount[a]&&(o.supplied.splice(a,1),o.wrapped.splice(a,1),o.useCount.splice(a,1)));break}p.events[e]&&0===p.events[e].length&&(p.events[e]=null,p.typeCount--)}p.typeCount||(f.splice(l,1),v.splice(l,1),y.splice(l,1))}else for(e in p.events)p.events.hasOwnProperty(e)&&n(t,e,"all")}function s(){this.returnValue=!1}function o(){this.cancelBubble=!0}function a(){this.cancelBubble=!0,this.immediatePropagationStopped=!0}var l=t("./arr"),c=l.indexOf,p=l.contains,h=t("./window").getWindow,d="attachEvent"in window&&!("addEventListener"in window),u=d?"attachEvent":"addEventListener",g=d?"detachEvent":"removeEventListener",m=d?"on":"",v=[],f=[],y=[];e.exports={add:r,remove:n,useAttachEvent:d,_elements:v,_targets:f,_attachedListeners:y}},{"./arr":7,"./window":18}],11:[function(t,e,i){"use strict";e.exports=function(t,e){for(var i in e)t[i]=e[i];return t}},{}],12:[function(t,e,i){"use strict";e.exports=function(t,e){return Math.sqrt(t*t+e*e)}},{}],13:[function(t,e,i){"use strict";var r=e.exports,n=t("./extend"),s=t("./window");r.blank=function(){},r.warnOnce=function(t,e){var i=!1;return function(){return i||(s.window.console.warn(e),i=!0),t.apply(this,arguments)}},r.extend=n,r.hypot=t("./hypot"),r.raf=t("./raf"),r.browser=t("./browser"),n(r,t("./arr")),n(r,t("./isType")),n(r,t("./pointerUtils"))},{"./arr":7,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./pointerUtils":16,"./raf":17,"./window":18}],14:[function(t,e,i){"use strict";var r=t("./window"),n=t("./domObjects"),s={isElement:function(t){if(!t||"object"!=typeof t)return!1;var e=r.getWindow(t)||r.window;return/object|function/.test(typeof e.Element)?t instanceof e.Element:1===t.nodeType&&"string"==typeof t.nodeName},isArray:null,isWindow:t("./isWindow"),isDocFrag:function(t){return!!t&&t instanceof n.DocumentFragment},isObject:function(t){return!!t&&"object"==typeof t},isFunction:function(t){return"function"==typeof t},isNumber:function(t){return"number"==typeof t},isBool:function(t){return"boolean"==typeof t},isString:function(t){return"string"==typeof t}};s.isArray=function(t){return s.isObject(t)&&"undefined"!=typeof t.length&&s.isFunction(t.splice)},e.exports=s},{"./domObjects":9,"./isWindow":15,"./window":18}],15:[function(t,e,i){"use strict";e.exports=function(t){return!(!t||!t.Window)&&t instanceof t.Window}},{}],16:[function(t,e,i){"use strict";var r={},n={},s=t("./window"),o=t("./hypot"),a=t("./extend"),l=t("./browser"),c=t("./isType"),p=t("../InteractEvent");r.copyCoords=function(t,e){t.page=t.page||{},t.page.x=e.page.x,t.page.y=e.page.y,t.client=t.client||{},t.client.x=e.client.x,t.client.y=e.client.y,t.timeStamp=e.timeStamp},r.setEventXY=function(t,e,i){e||(e=i.pointerIds.length>1?r.touchAverage(i.pointers):i.pointers[0]),r.getPageXY(e,n,i),t.page.x=n.x,t.page.y=n.y,r.getClientXY(e,n,i),t.client.x=n.x,t.client.y=n.y,t.timeStamp=(new Date).getTime()},r.setEventDeltas=function(t,e,i){t.page.x=i.page.x-e.page.x,t.page.y=i.page.y-e.page.y,t.client.x=i.client.x-e.client.x,t.client.y=i.client.y-e.client.y,t.timeStamp=(new Date).getTime()-e.timeStamp;var r=Math.max(t.timeStamp/1e3,.001);t.page.speed=o(t.page.x,t.page.y)/r,t.page.vx=t.page.x/r,t.page.vy=t.page.y/r,t.client.speed=o(t.client.x,t.page.y)/r,t.client.vx=t.client.x/r,t.client.vy=t.client.y/r},r.getXY=function(t,e,i){return i=i||{},t=t||"page",i.x=e[t+"X"],i.y=e[t+"Y"],i},r.getPageXY=function(t,e,i){return e=e||{},t instanceof p?/inertiastart/.test(t.type)?(i=i||t.interaction,a(e,i.inertiaStatus.upCoords.page),e.x+=i.inertiaStatus.sx,e.y+=i.inertiaStatus.sy):(e.x=t.pageX,e.y=t.pageY):l.isOperaMobile?(r.getXY("screen",t,e),e.x+=s.window.scrollX,e.y+=s.window.scrollY):r.getXY("page",t,e),e},r.getClientXY=function(t,e,i){return e=e||{},t instanceof p?/inertiastart/.test(t.type)?(a(e,i.inertiaStatus.upCoords.client),e.x+=i.inertiaStatus.sx,e.y+=i.inertiaStatus.sy):(e.x=t.clientX,e.y=t.clientY):r.getXY(l.isOperaMobile?"screen":"client",t,e),e},r.getPointerId=function(t){return c.isNumber(t.pointerId)?t.pointerId:t.identifier},e.exports=r},{"../InteractEvent":2,"./browser":8,"./extend":11,"./hypot":12,"./isType":14,"./window":18}],17:[function(t,e,i){"use strict";for(var r,n,s=0,o=["ms","moz","webkit","o"],a=0;a Date: Thu, 16 Jul 2015 15:27:39 +0200 Subject: [PATCH 054/131] Add .codeclimate.yml --- .codeclimate.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .codeclimate.yml diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 000000000..89c44dfba --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,8 @@ +languages: + JavaScript: true + + exclude_paths: + - "demo/*" + - "docs/*" + - "gulp/*" + - "test/*" From 53a716160d7bdcf96d8abafa69ecfb127671dc0a Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Thu, 16 Jul 2015 18:54:39 +0200 Subject: [PATCH 055/131] Remove Interaction#get[Page|Client]XY methods --- src/Interaction.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 1b27816bb..818644c6a 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -193,9 +193,9 @@ function preventOriginalDefault () { } Interaction.prototype = { - getPageXY : function (pointer, xy) { return utils.getPageXY(pointer, xy, this); }, - getClientXY: function (pointer, xy) { return utils.getClientXY(pointer, xy, this); }, - setEventXY : function (target, ptr) { return utils.setEventXY(target, ptr, this); }, + setEventXY : function (target, pointer, event) { + utils.setEventXY(target, pointer, event, this); + }, pointerOver: function (pointer, event, eventTarget) { if (this.prepared.name || !this.mouse) { return; } From 4f2a17d715a4f383744461dc9ff1bb3d866db639 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Fri, 17 Jul 2015 11:09:41 +0200 Subject: [PATCH 056/131] Move setEventXY from pointerUtils to Interaction --- src/Interaction.js | 23 +++++++++++++++++++++-- src/utils/pointerUtils.js | 22 ---------------------- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 818644c6a..50b5c3ad7 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -193,8 +193,27 @@ function preventOriginalDefault () { } Interaction.prototype = { - setEventXY : function (target, pointer, event) { - utils.setEventXY(target, pointer, event, this); + setEventXY: function (targetObj, pointer) { + if (!pointer) { + if (this.pointerIds.length > 1) { + pointer = utils.touchAverage(this.pointers); + } + else { + pointer = this.pointers[0]; + } + } + + var tmpXY = {}; + + utils.getPageXY(pointer, tmpXY, this); + targetObj.page.x = tmpXY.x; + targetObj.page.y = tmpXY.y; + + utils.getClientXY(pointer, tmpXY, this); + targetObj.client.x = tmpXY.x; + targetObj.client.y = tmpXY.y; + + targetObj.timeStamp = new Date().getTime(); }, pointerOver: function (pointer, event, eventTarget) { diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 0b32204ee..783a09505 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -2,7 +2,6 @@ var pointerUtils = {}, // reduce object creation in getXY() - tmpXY = {}, win = require('./window'), hypot = require('./hypot'), extend = require('./extend'), @@ -22,27 +21,6 @@ pointerUtils.copyCoords = function (dest, src) { dest.timeStamp = src.timeStamp; }; -pointerUtils.setEventXY = function (targetObj, pointer, interaction) { - if (!pointer) { - if (interaction.pointerIds.length > 1) { - pointer = pointerUtils.touchAverage(interaction.pointers); - } - else { - pointer = interaction.pointers[0]; - } - } - - pointerUtils.getPageXY(pointer, tmpXY, interaction); - targetObj.page.x = tmpXY.x; - targetObj.page.y = tmpXY.y; - - pointerUtils.getClientXY(pointer, tmpXY, interaction); - targetObj.client.x = tmpXY.x; - targetObj.client.y = tmpXY.y; - - targetObj.timeStamp = new Date().getTime(); -}; - pointerUtils.setEventDeltas = function (targetObj, prev, cur) { targetObj.page.x = cur.page.x - prev.page.x; targetObj.page.y = cur.page.y - prev.page.y; From 21bd9d6bf07672b3238d3527997bc494caad7b7f Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Fri, 17 Jul 2015 18:35:07 +0200 Subject: [PATCH 057/131] Delete unused actions/index.js file --- src/actions/index.js | 112 ------------------------------------------- 1 file changed, 112 deletions(-) delete mode 100644 src/actions/index.js diff --git a/src/actions/index.js b/src/actions/index.js deleted file mode 100644 index 1d7a7b7b8..000000000 --- a/src/actions/index.js +++ /dev/null @@ -1,112 +0,0 @@ -'use strict'; - -var scope = require('../scope'), - utils = require('../utils'); - -var actions = { - - checkResizeEdge: function (name, value, page, element, interactableElement, rect, margin) { - // false, '', undefined, null - if (!value) { return false; } - - // true value, use pointer coords and element rect - if (value === true) { - // if dimensions are negative, "switch" edges - var width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left, - height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; - - if (width < 0) { - if (name === 'left' ) { name = 'right'; } - else if (name === 'right') { name = 'left' ; } - } - if (height < 0) { - if (name === 'top' ) { name = 'bottom'; } - else if (name === 'bottom') { name = 'top' ; } - } - - if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } - if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } - - if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } - if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } - } - - // the remaining checks require an element - if (!utils.isElement(element)) { return false; } - - return utils.isElement(value) - // the value is an element to use as a resize handle - ? value === element - // otherwise check if element matches value as selector - : utils.matchesUpTo(element, value, interactableElement); - }, - - defaultActionChecker: function (pointer, interaction, element) { - var rect = this.getRect(element), - shouldResize = false, - action = null, - resizeAxes = null, - resizeEdges, - page = utils.extend({}, interaction.curCoords.page), - options = this.options; - - if (!rect) { return null; } - - if (scope.actionIsEnabled.resize && options.resize.enabled) { - var resizeOptions = options.resize; - - resizeEdges = { - left: false, right: false, top: false, bottom: false - }; - - // if using resize.edges - if (utils.isObject(resizeOptions.edges)) { - for (var edge in resizeEdges) { - resizeEdges[edge] = actions.checkResizeEdge(edge, - resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || scope.margin); - } - - resizeEdges.left = resizeEdges.left && !resizeEdges.right; - resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; - - shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom; - } - else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); - - shouldResize = right || bottom; - resizeAxes = (right? 'x' : '') + (bottom? 'y' : ''); - } - } - - action = shouldResize - ? 'resize' - : scope.actionIsEnabled.drag && options.drag.enabled - ? 'drag' - : null; - - if (scope.actionIsEnabled.gesture - && interaction.pointerIds.length >=2 - && !(interaction.dragging || interaction.resizing)) { - action = 'gesture'; - } - - if (action) { - return { - name: action, - axis: resizeAxes, - edges: resizeEdges - }; - } - - return null; - } -}; - -module.exports = actions; From cac44f87d8a9d6998bf9bc40e4257a5b9b46a2a9 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 18 Jul 2015 10:12:31 +0200 Subject: [PATCH 058/131] .codeclimate.yml: fix indent before exclude_paths --- .codeclimate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 89c44dfba..624c15216 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,7 +1,7 @@ languages: JavaScript: true - exclude_paths: +exclude_paths: - "demo/*" - "docs/*" - "gulp/*" From dbe385d1a9250bf0f9aec810403b5dfbac763416 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 18 Jul 2015 23:50:02 +0200 Subject: [PATCH 059/131] Reorganize action definitions --- src/Interactable.js | 4 +- src/Interaction.js | 15 +-- src/actions/base.js | 16 ++- src/actions/drag.js | 242 ++++++++++++++++----------------- src/actions/gesture.js | 94 ++++++------- src/actions/resize.js | 295 +++++++++++++++++++++-------------------- src/interact.js | 1 - 7 files changed, 338 insertions(+), 329 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index 5b53b321a..7507d2835 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -3,7 +3,7 @@ var scope = require('./scope'), utils = require('./utils'), events = require('./utils/events'), - actionBase = require('./actions/base'); + actions = require('./actions/base'); /*\ * Interactable @@ -117,7 +117,7 @@ Interactable.prototype = { return action; }, - defaultActionChecker: actionBase.defaultActionChecker, + defaultActionChecker: actions.defaultChecker, /*\ * Interactable.actionChecker diff --git a/src/Interaction.js b/src/Interaction.js index 50b5c3ad7..7d5d67a80 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -6,6 +6,7 @@ var scope = require('./scope'), InteractEvent = require('./InteractEvent'), events = require('./utils/events'), browser = require('./utils/browser'), + actions = require('./actions/base'), modifiers = require('./modifiers/'); function Interaction () { @@ -626,7 +627,7 @@ Interaction.prototype = { modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); - this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent); + this.prevEvent = actions[this.prepared.name].start(this, this.downEvent); }, pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { @@ -777,7 +778,7 @@ Interaction.prototype = { // move if snapping or restriction doesn't prevent it if (modifierResult.shouldMove || starting) { - this.prevEvent = this[this.prepared.name + 'Move'](event); + this.prevEvent = actions[this.prepared.name].move(this, event); } this.checkAndPreventDefault(event, this.target, this.element); @@ -943,14 +944,8 @@ Interaction.prototype = { } } - if (this.dragging) { - this.dragEnd(event); - } - else if (this.resizing) { - this.resizeEnd(event); - } - else if (this.gesturing) { - this.gestureEnd(event); + if (this.interacting()) { + actions[this.prepared.name].end(this, event); } this.stop(event); diff --git a/src/actions/base.js b/src/actions/base.js index a5fca9821..31a03bff2 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -1,12 +1,10 @@ 'use strict'; var scope = require('../scope'), - browser = require('../utils/browser'), - checkers = []; + browser = require('../utils/browser'); var actions = { scope: scope, - checkers: checkers, addEventTypes: function (eventTypes) { for (var i = 0; i < eventTypes.length; i++) { @@ -14,16 +12,20 @@ var actions = { } }, - defaultActionChecker: function (pointer, event, interaction, element) { + defaultChecker: function (pointer, event, interaction, element) { var rect = this.getRect(element), action = null; - for (var i = 0; !action && i < checkers.length; i++) { - action = checkers[i](pointer, event, this, element, interaction, rect); + for (var i = 0; !action && i < actions.names.length; i++) { + var actionName = actions.names[i]; + + action = actions[actionName].checker(pointer, event, this, element, interaction, rect); } return action; - } + }, + + names: [] }; scope.actionCursors = browser.isIe9OrOlder ? { diff --git a/src/actions/drag.js b/src/actions/drag.js index f057e7359..00a58dc02 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -3,104 +3,91 @@ var base = require('./base'), scope = base.scope, utils = require('../utils'), - Interaction = require('../Interaction'), InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'); +var drag = { + checker: function (pointer, event, interactable) { + return scope.actionIsEnabled.drag && interactable.options.drag.enabled + ? { name: 'drag' } + : null; + }, -base.addEventTypes([ - 'dragstart', - 'dragmove', - 'draginertiastart', - 'dragend', - 'dragenter', - 'dragleave', - 'dropactivate', - 'dropdeactivate', - 'dropmove', - 'drop' -]); - -base.checkers.push(function (pointer, event, interactable) { -return scope.actionIsEnabled.drag && interactable.options.drag.enabled - ? { name: 'drag' } - : null; -}); + start: function (interaction, event) { + var dragEvent = new InteractEvent(interaction, event, 'drag', 'start', interaction.element); -Interaction.prototype.dragStart = function (event) { - var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element); + interaction.dragging = true; + interaction.target.fire(dragEvent); - this.dragging = true; - this.target.fire(dragEvent); + // reset active dropzones + interaction.activeDrops.dropzones = []; + interaction.activeDrops.elements = []; + interaction.activeDrops.rects = []; - // reset active dropzones - this.activeDrops.dropzones = []; - this.activeDrops.elements = []; - this.activeDrops.rects = []; + if (!interaction.dynamicDrop) { + setActiveDrops(interaction, interaction.element); + } - if (!this.dynamicDrop) { - this.setActiveDrops(this.element); - } + var dropEvents = getDropEvents(interaction, event, dragEvent); - var dropEvents = this.getDropEvents(event, dragEvent); + if (dropEvents.activate) { + fireActiveDrops(interaction, dropEvents.activate); + } - if (dropEvents.activate) { - this.fireActiveDrops(dropEvents.activate); - } + return dragEvent; + }, - return dragEvent; -}; + move: function (interaction, event) { + var target = interaction.target, + dragEvent = new InteractEvent(interaction, event, 'drag', 'move', interaction.element), + draggableElement = interaction.element, + drop = getDrop(interaction, event, draggableElement); -Interaction.prototype.dragMove = function (event) { - var target = this.target, - dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element), - draggableElement = this.element, - drop = this.getDrop(event, draggableElement); + interaction.dropTarget = drop.dropzone; + interaction.dropElement = drop.element; - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; + var dropEvents = getDropEvents(interaction, event, dragEvent); - var dropEvents = this.getDropEvents(event, dragEvent); + target.fire(dragEvent); - target.fire(dragEvent); + if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } + if (dropEvents.move ) { interaction.dropTarget.fire(dropEvents.move ); } - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); } + interaction.prevDropTarget = interaction.dropTarget; + interaction.prevDropElement = interaction.dropElement; - this.prevDropTarget = this.dropTarget; - this.prevDropElement = this.dropElement; + return dragEvent; + }, - return dragEvent; -}; + end: function (interaction, event) { + var endEvent = new InteractEvent(interaction, event, 'drag', 'end', interaction.element); -Interaction.prototype.dragEnd = function (event) { - var endEvent = new InteractEvent(this, event, 'drag', 'end', this.element); + var draggableElement = interaction.element, + drop = getDrop(interaction, event, draggableElement); - var draggableElement = this.element, - drop = this.getDrop(event, draggableElement); + interaction.dropTarget = drop.dropzone; + interaction.dropElement = drop.element; - this.dropTarget = drop.dropzone; - this.dropElement = drop.element; + var dropEvents = getDropEvents(interaction, event, endEvent); - var dropEvents = this.getDropEvents(event, endEvent); + if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } + if (dropEvents.drop ) { interaction.dropTarget.fire(dropEvents.drop ); } + if (dropEvents.deactivate) { + fireActiveDrops(interaction, dropEvents.deactivate); + } - if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); } - if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); } - if (dropEvents.deactivate) { - this.fireActiveDrops(dropEvents.deactivate); + interaction.target.fire(endEvent); } - - this.target.fire(endEvent); }; -Interaction.prototype.collectDrops = function (element) { +function collectDrops (interaction, element) { var drops = [], elements = [], i; - element = element || this.element; + element = element || interaction.element; // collect all dropzones and their elements which qualify for a drop for (i = 0; i < scope.interactables.length; i++) { @@ -136,18 +123,18 @@ Interaction.prototype.collectDrops = function (element) { dropzones: drops, elements: elements }; -}; +} -Interaction.prototype.fireActiveDrops = function (event) { +function fireActiveDrops (interaction, event) { var i, current, currentElement, prevElement; // loop through all active dropzones and trigger event - for (i = 0; i < this.activeDrops.dropzones.length; i++) { - current = this.activeDrops.dropzones[i]; - currentElement = this.activeDrops.elements [i]; + for (i = 0; i < interaction.activeDrops.dropzones.length; i++) { + current = interaction.activeDrops.dropzones[i]; + currentElement = interaction.activeDrops.elements [i]; // prevent trigger of duplicate events on same element if (currentElement !== prevElement) { @@ -157,54 +144,54 @@ Interaction.prototype.fireActiveDrops = function (event) { } prevElement = currentElement; } -}; +} // Collect a new set of possible drops and save them in activeDrops. // setActiveDrops should always be called when a drag has just started or a // drag event happens while dynamicDrop is true -Interaction.prototype.setActiveDrops = function (dragElement) { +function setActiveDrops (interaction, dragElement) { // get dropzones and their elements that could receive the draggable - var possibleDrops = this.collectDrops(dragElement, true); + var possibleDrops = collectDrops(interaction, dragElement, true); - this.activeDrops.dropzones = possibleDrops.dropzones; - this.activeDrops.elements = possibleDrops.elements; - this.activeDrops.rects = []; + interaction.activeDrops.dropzones = possibleDrops.dropzones; + interaction.activeDrops.elements = possibleDrops.elements; + interaction.activeDrops.rects = []; - for (var i = 0; i < this.activeDrops.dropzones.length; i++) { - this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]); + for (var i = 0; i < interaction.activeDrops.dropzones.length; i++) { + interaction.activeDrops.rects[i] = interaction.activeDrops.dropzones[i].getRect(interaction.activeDrops.elements[i]); } -}; +} -Interaction.prototype.getDrop = function (event, dragElement) { +function getDrop (interaction, event, dragElement) { var validDrops = []; if (scope.dynamicDrop) { - this.setActiveDrops(dragElement); + setActiveDrops(interaction, dragElement); } // collect all dropzones and their elements which qualify for a drop - for (var j = 0; j < this.activeDrops.dropzones.length; j++) { - var current = this.activeDrops.dropzones[j], - currentElement = this.activeDrops.elements [j], - rect = this.activeDrops.rects [j]; + for (var j = 0; j < interaction.activeDrops.dropzones.length; j++) { + var current = interaction.activeDrops.dropzones[j], + currentElement = interaction.activeDrops.elements [j], + rect = interaction.activeDrops.rects [j]; - validDrops.push(current.dropCheck(this.pointers[0], event, this.target, dragElement, currentElement, rect) + validDrops.push(current.dropCheck(interaction.pointers[0], event, interaction.target, dragElement, currentElement, rect) ? currentElement : null); } // get the most appropriate dropzone based on DOM depth and order var dropIndex = utils.indexOfDeepestElement(validDrops), - dropzone = this.activeDrops.dropzones[dropIndex] || null, - element = this.activeDrops.elements [dropIndex] || null; + dropzone = interaction.activeDrops.dropzones[dropIndex] || null, + element = interaction.activeDrops.elements [dropIndex] || null; return { dropzone: dropzone, element: element }; -}; +} -Interaction.prototype.getDropEvents = function (pointerEvent, dragEvent) { +function getDropEvents (interaction, pointerEvent, dragEvent) { var dropEvents = { enter : null, leave : null, @@ -214,54 +201,54 @@ Interaction.prototype.getDropEvents = function (pointerEvent, dragEvent) { drop : null }; - if (this.dropElement !== this.prevDropElement) { + if (interaction.dropElement !== interaction.prevDropElement) { // if there was a prevDropTarget, create a dragleave event - if (this.prevDropTarget) { + if (interaction.prevDropTarget) { dropEvents.leave = { - target : this.prevDropElement, - dropzone : this.prevDropTarget, + target : interaction.prevDropElement, + dropzone : interaction.prevDropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, dragEvent : dragEvent, - interaction : this, + interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dragleave' }; - dragEvent.dragLeave = this.prevDropElement; - dragEvent.prevDropzone = this.prevDropTarget; + dragEvent.dragLeave = interaction.prevDropElement; + dragEvent.prevDropzone = interaction.prevDropTarget; } // if the dropTarget is not null, create a dragenter event - if (this.dropTarget) { + if (interaction.dropTarget) { dropEvents.enter = { - target : this.dropElement, - dropzone : this.dropTarget, + target : interaction.dropElement, + dropzone : interaction.dropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, dragEvent : dragEvent, - interaction : this, + interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dragenter' }; - dragEvent.dragEnter = this.dropElement; - dragEvent.dropzone = this.dropTarget; + dragEvent.dragEnter = interaction.dropElement; + dragEvent.dropzone = interaction.dropTarget; } } - if (dragEvent.type === 'dragend' && this.dropTarget) { + if (dragEvent.type === 'dragend' && interaction.dropTarget) { dropEvents.drop = { - target : this.dropElement, - dropzone : this.dropTarget, + target : interaction.dropElement, + dropzone : interaction.dropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, dragEvent : dragEvent, - interaction : this, + interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'drop' }; - dragEvent.dropzone = this.dropTarget; + dragEvent.dropzone = interaction.dropTarget; } if (dragEvent.type === 'dragstart') { dropEvents.activate = { @@ -270,7 +257,7 @@ Interaction.prototype.getDropEvents = function (pointerEvent, dragEvent) { relatedTarget: dragEvent.target, draggable : dragEvent.interactable, dragEvent : dragEvent, - interaction : this, + interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dropactivate' }; @@ -282,28 +269,28 @@ Interaction.prototype.getDropEvents = function (pointerEvent, dragEvent) { relatedTarget: dragEvent.target, draggable : dragEvent.interactable, dragEvent : dragEvent, - interaction : this, + interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dropdeactivate' }; } - if (dragEvent.type === 'dragmove' && this.dropTarget) { + if (dragEvent.type === 'dragmove' && interaction.dropTarget) { dropEvents.move = { - target : this.dropElement, - dropzone : this.dropTarget, + target : interaction.dropElement, + dropzone : interaction.dropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, dragEvent : dragEvent, - interaction : this, + interaction : interaction, dragmove : dragEvent, timeStamp : dragEvent.timeStamp, type : 'dropmove' }; - dragEvent.dropzone = this.dropTarget; + dragEvent.dropzone = interaction.dropTarget; } return dropEvents; -}; +} /*\ * Interactable.draggable @@ -554,3 +541,20 @@ Interactable.prototype.accept = function (newValue) { return this.options.drop.accept; }; + +base.drag = drag; +base.names.push('drag'); +base.addEventTypes([ + 'dragstart', + 'dragmove', + 'draginertiastart', + 'dragend', + 'dragenter', + 'dragleave', + 'dropactivate', + 'dropdeactivate', + 'dropmove', + 'drop' +]); + +module.exports = drag; diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 7223c224e..dce3c64c8 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -3,73 +3,67 @@ var base = require('./base'), scope = base.scope, utils = require('../utils'), - Interaction = require('../Interaction'), InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'); -base.addEventTypes([ - 'gesturestart', - 'gesturemove', - 'gestureinertiastart', - 'gestureend' -]); - -base.checkers.push(function (pointer, event, interactable, element, interaction) { - if (scope.actionIsEnabled.gesture - && interaction.pointerIds.length >=2 +var gesture = { + checker: function (pointer, event, interactable, element, interaction) { + if (scope.actionIsEnabled.gesture + && interaction.pointerIds.length >=2 && !(interaction.dragging || interaction.resizing)) { return { name: 'gesture' }; - } + } - return null; -}); + return null; + }, -Interaction.prototype.gestureStart = function (event) { - var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element); + start: function (interaction, event) { + var gestureEvent = new InteractEvent(interaction, event, 'gesture', 'start', interaction.element); - gestureEvent.ds = 0; + gestureEvent.ds = 0; - this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance; - this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle; - this.gesture.scale = 1; + interaction.gesture.startDistance = interaction.gesture.prevDistance = gestureEvent.distance; + interaction.gesture.startAngle = interaction.gesture.prevAngle = gestureEvent.angle; + interaction.gesture.scale = 1; - this.gesturing = true; + interaction.gesturing = true; - this.target.fire(gestureEvent); + interaction.target.fire(gestureEvent); - return gestureEvent; -}; + return gestureEvent; + }, -Interaction.prototype.gestureMove = function (event) { - if (!this.pointerIds.length) { - return this.prevEvent; - } + move: function (interaction, event) { + if (!interaction.pointerIds.length) { + return interaction.prevEvent; + } - var gestureEvent; + var gestureEvent; - gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element); - gestureEvent.ds = gestureEvent.scale - this.gesture.scale; + gestureEvent = new InteractEvent(interaction, event, 'gesture', 'move', interaction.element); + gestureEvent.ds = gestureEvent.scale - interaction.gesture.scale; - this.target.fire(gestureEvent); + interaction.target.fire(gestureEvent); - this.gesture.prevAngle = gestureEvent.angle; - this.gesture.prevDistance = gestureEvent.distance; + interaction.gesture.prevAngle = gestureEvent.angle; + interaction.gesture.prevDistance = gestureEvent.distance; - if (gestureEvent.scale !== Infinity && - gestureEvent.scale !== null && - gestureEvent.scale !== undefined && - !isNaN(gestureEvent.scale)) { + if (gestureEvent.scale !== Infinity && + gestureEvent.scale !== null && + gestureEvent.scale !== undefined && + !isNaN(gestureEvent.scale)) { - this.gesture.scale = gestureEvent.scale; - } + interaction.gesture.scale = gestureEvent.scale; + } - return gestureEvent; -}; + return gestureEvent; + }, -Interaction.prototype.gestureEnd = function (event) { - var endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element); + end: function (interaction, event) { + var endEvent = new InteractEvent(interaction, event, 'gesture', 'end', interaction.element); - this.target.fire(endEvent); + interaction.target.fire(endEvent); + } }; /*\ @@ -113,3 +107,13 @@ Interactable.prototype.gesturable = function (options) { return this.options.gesture; }; +base.gesture = gesture; +base.names.push('gesture'); +base.addEventTypes([ + 'gesturestart', + 'gesturemove', + 'gestureinertiastart', + 'gestureend' +]); + +module.exports = gesture; diff --git a/src/actions/resize.js b/src/actions/resize.js index 1a59ce393..3ed7b7e94 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -3,196 +3,190 @@ var base = require('./base'), utils = require('../utils'), scope = base.scope, - Interaction = require('../Interaction'), InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'); -base.addEventTypes([ - 'resizestart', - 'resizemove', - 'resizeinertiastart', - 'resizeend' -]); +var resize = { + checker: function (pointer, event, interactable, element, interaction, rect) { + if (!rect) { return null; } -base.checkers.push(function (pointer, event, interactable, element, interaction, rect) { - if (!rect) { return null; } + var page = utils.extend({}, interaction.curCoords.page), + options = interactable.options; - var page = utils.extend({}, interaction.curCoords.page), - options = interactable.options; - - if (scope.actionIsEnabled.resize && options.resize.enabled) { - var resizeOptions = options.resize, - resizeEdges = { - left: false, right: false, top: false, bottom: false - }; + if (scope.actionIsEnabled.resize && options.resize.enabled) { + var resizeOptions = options.resize, + resizeEdges = { + left: false, right: false, top: false, bottom: false + }; - // if using resize.edges - if (utils.isObject(resizeOptions.edges)) { - for (var edge in resizeEdges) { - resizeEdges[edge] = checkResizeEdge(edge, - resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || scope.margin); - } + // if using resize.edges + if (utils.isObject(resizeOptions.edges)) { + for (var edge in resizeEdges) { + resizeEdges[edge] = checkResizeEdge(edge, + resizeOptions.edges[edge], + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || scope.margin); + } - resizeEdges.left = resizeEdges.left && !resizeEdges.right; - resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; + resizeEdges.left = resizeEdges.left && !resizeEdges.right; + resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; - if (resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom) { - return { - name: 'resize', - edges: resizeEdges - }; + if (resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom) { + return { + name: 'resize', + edges: resizeEdges + }; + } } - } - else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); - - if (right || bottom) { - return { - name: 'resize', - axes: (right? 'x' : '') + (bottom? 'y' : '') - }; + else { + var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), + bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); + + if (right || bottom) { + return { + name: 'resize', + axes: (right? 'x' : '') + (bottom? 'y' : '') + }; + } } } - } - - return null; -}); -Interaction.prototype.resizeStart = function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element); + return null; + }, - if (this.prepared.edges) { - var startRect = this.target.getRect(this.element); + start: function (interaction, event) { + var resizeEvent = new InteractEvent(interaction, event, 'resize', 'start', interaction.element); - if (this.target.options.resize.square) { - var squareEdges = utils.extend({}, this.prepared.edges); + if (interaction.prepared.edges) { + var startRect = interaction.target.getRect(interaction.element); - squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); - squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); - squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); - squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); + if (interaction.target.options.resize.square) { + var squareEdges = utils.extend({}, interaction.prepared.edges); - this.prepared._squareEdges = squareEdges; - } - else { - this.prepared._squareEdges = null; - } + squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); + squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); + squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); + squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); - this.resizeRects = { - start : startRect, - current : utils.extend({}, startRect), - restricted: utils.extend({}, startRect), - previous : utils.extend({}, startRect), - delta : { - left: 0, right : 0, width : 0, - top : 0, bottom: 0, height: 0 + interaction.prepared._squareEdges = squareEdges; + } + else { + interaction.prepared._squareEdges = null; } - }; - resizeEvent.rect = this.resizeRects.restricted; - resizeEvent.deltaRect = this.resizeRects.delta; - } + interaction.resizeRects = { + start : startRect, + current : utils.extend({}, startRect), + restricted: utils.extend({}, startRect), + previous : utils.extend({}, startRect), + delta : { + left: 0, right : 0, width : 0, + top : 0, bottom: 0, height: 0 + } + }; - this.target.fire(resizeEvent); + resizeEvent.rect = interaction.resizeRects.restricted; + resizeEvent.deltaRect = interaction.resizeRects.delta; + } - this.resizing = true; + interaction.target.fire(resizeEvent); - return resizeEvent; -}; + interaction.resizing = true; + + return resizeEvent; + }, -Interaction.prototype.resizeMove = function (event) { - var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element); + move: function (interaction, event) { + var resizeEvent = new InteractEvent(interaction, event, 'resize', 'move', interaction.element); - var edges = this.prepared.edges, - invert = this.target.options.resize.invert, - invertible = invert === 'reposition' || invert === 'negate'; + var edges = interaction.prepared.edges, + invert = interaction.target.options.resize.invert, + invertible = invert === 'reposition' || invert === 'negate'; - if (edges) { - var dx = resizeEvent.dx, - dy = resizeEvent.dy, + if (edges) { + var dx = resizeEvent.dx, + dy = resizeEvent.dy, - start = this.resizeRects.start, - current = this.resizeRects.current, - restricted = this.resizeRects.restricted, - delta = this.resizeRects.delta, - previous = utils.extend(this.resizeRects.previous, restricted); + start = interaction.resizeRects.start, + current = interaction.resizeRects.current, + restricted = interaction.resizeRects.restricted, + delta = interaction.resizeRects.delta, + previous = utils.extend(interaction.resizeRects.previous, restricted); - if (this.target.options.resize.square) { - var originalEdges = edges; + if (interaction.target.options.resize.square) { + var originalEdges = edges; - edges = this.prepared._squareEdges; + edges = interaction.prepared._squareEdges; - if ((originalEdges.left && originalEdges.bottom) - || (originalEdges.right && originalEdges.top)) { - dy = -dx; + if ((originalEdges.left && originalEdges.bottom) + || (originalEdges.right && originalEdges.top)) { + dy = -dx; + } + else if (originalEdges.left || originalEdges.right) { dy = dx; } + else if (originalEdges.top || originalEdges.bottom) { dx = dy; } } - else if (originalEdges.left || originalEdges.right) { dy = dx; } - else if (originalEdges.top || originalEdges.bottom) { dx = dy; } - } - // update the 'current' rect without modifications - if (edges.top ) { current.top += dy; } - if (edges.bottom) { current.bottom += dy; } - if (edges.left ) { current.left += dx; } - if (edges.right ) { current.right += dx; } + // update the 'current' rect without modifications + if (edges.top ) { current.top += dy; } + if (edges.bottom) { current.bottom += dy; } + if (edges.left ) { current.left += dx; } + if (edges.right ) { current.right += dx; } - if (invertible) { - // if invertible, copy the current rect - utils.extend(restricted, current); + if (invertible) { + // if invertible, copy the current rect + utils.extend(restricted, current); - if (invert === 'reposition') { - // swap edge values if necessary to keep width/height positive - var swap; + if (invert === 'reposition') { + // swap edge values if necessary to keep width/height positive + var swap; - if (restricted.top > restricted.bottom) { - swap = restricted.top; + if (restricted.top > restricted.bottom) { + swap = restricted.top; - restricted.top = restricted.bottom; - restricted.bottom = swap; - } - if (restricted.left > restricted.right) { - swap = restricted.left; + restricted.top = restricted.bottom; + restricted.bottom = swap; + } + if (restricted.left > restricted.right) { + swap = restricted.left; - restricted.left = restricted.right; - restricted.right = swap; + restricted.left = restricted.right; + restricted.right = swap; + } } } - } - else { - // if not invertible, restrict to minimum of 0x0 rect - restricted.top = Math.min(current.top, start.bottom); - restricted.bottom = Math.max(current.bottom, start.top); - restricted.left = Math.min(current.left, start.right); - restricted.right = Math.max(current.right, start.left); - } + else { + // if not invertible, restrict to minimum of 0x0 rect + restricted.top = Math.min(current.top, start.bottom); + restricted.bottom = Math.max(current.bottom, start.top); + restricted.left = Math.min(current.left, start.right); + restricted.right = Math.max(current.right, start.left); + } - restricted.width = restricted.right - restricted.left; - restricted.height = restricted.bottom - restricted.top ; + restricted.width = restricted.right - restricted.left; + restricted.height = restricted.bottom - restricted.top ; - for (var edge in restricted) { - delta[edge] = restricted[edge] - previous[edge]; - } + for (var edge in restricted) { + delta[edge] = restricted[edge] - previous[edge]; + } - resizeEvent.edges = this.prepared.edges; - resizeEvent.rect = restricted; - resizeEvent.deltaRect = delta; - } + resizeEvent.edges = interaction.prepared.edges; + resizeEvent.rect = restricted; + resizeEvent.deltaRect = delta; + } - this.target.fire(resizeEvent); + interaction.target.fire(resizeEvent); - return resizeEvent; -}; + return resizeEvent; + }, -Interaction.prototype.resizeEnd = function (event) { - var endEvent = new InteractEvent(this, event, 'resize', 'end', this.element); + end: function (interaction, event) { + var endEvent = new InteractEvent(interaction, event, 'resize', 'end', interaction.element); - this.target.fire(endEvent); + interaction.target.fire(endEvent); + } }; /*\ @@ -293,3 +287,14 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, // otherwise check if element matches value as selector : utils.matchesUpTo(element, value, interactableElement); } + +base.resize = resize; +base.names.push('resize'); +base.addEventTypes([ + 'resizestart', + 'resizemove', + 'resizeinertiastart', + 'resizeend' +]); + +module.exports = resize; diff --git a/src/interact.js b/src/interact.js index d1bc7dbed..a7c0355fe 100644 --- a/src/interact.js +++ b/src/interact.js @@ -59,7 +59,6 @@ scope.listeners = {}; var interactionListeners = [ - 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove', 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' From 7190cadb411b037f59d1f53cfe32b9ac1ed01fa7 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 01:40:35 +0200 Subject: [PATCH 060/131] Get cursors from each action module separately --- src/Interaction.js | 31 ++-------------------------- src/actions/base.js | 37 +-------------------------------- src/actions/drag.js | 4 ++++ src/actions/gesture.js | 4 ++++ src/actions/resize.js | 47 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 65 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 7d5d67a80..ada1d2d84 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -162,33 +162,6 @@ function validateAction (action, interactable) { return null; } -function getActionCursor (action) { - var cursor = ''; - - if (action.name === 'drag') { - cursor = scope.actionCursors.drag; - } - if (action.name === 'resize') { - if (action.axis) { - cursor = scope.actionCursors[action.name + action.axis]; - } - else if (action.edges) { - var cursorKey = 'resize', - edgeNames = ['top', 'bottom', 'left', 'right']; - - for (var i = 0; i < 4; i++) { - if (action.edges[edgeNames[i]]) { - cursorKey += edgeNames[i]; - } - } - - cursor = scope.actionCursors[cursorKey]; - } - } - - return cursor; -} - function preventOriginalDefault () { this.originalEvent.preventDefault(); } @@ -317,7 +290,7 @@ Interaction.prototype = { if (target && target.options.styleCursor) { if (action) { - target._doc.documentElement.style.cursor = getActionCursor(action); + target._doc.documentElement.style.cursor = actions[action.name].getCursor(action); } else { target._doc.documentElement.style.cursor = ''; @@ -479,7 +452,7 @@ Interaction.prototype = { if (!action) { return; } if (options.styleCursor) { - target._doc.documentElement.style.cursor = getActionCursor(action); + target._doc.documentElement.style.cursor = actions[action.name].getCursor(action); } this.resizeAxes = action.name === 'resize'? action.axis : null; diff --git a/src/actions/base.js b/src/actions/base.js index 31a03bff2..56d7cbb0d 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -1,7 +1,6 @@ 'use strict'; -var scope = require('../scope'), - browser = require('../utils/browser'); +var scope = require('../scope'); var actions = { scope: scope, @@ -28,38 +27,4 @@ var actions = { names: [] }; -scope.actionCursors = browser.isIe9OrOlder ? { - drag : 'move', - resizex : 'e-resize', - resizey : 's-resize', - resizexy: 'se-resize', - - resizetop : 'n-resize', - resizeleft : 'w-resize', - resizebottom : 's-resize', - resizeright : 'e-resize', - resizetopleft : 'se-resize', - resizebottomright: 'se-resize', - resizetopright : 'ne-resize', - resizebottomleft : 'ne-resize', - - gesture : '' -} : { - drag : 'move', - resizex : 'ew-resize', - resizey : 'ns-resize', - resizexy: 'nwse-resize', - - resizetop : 'ns-resize', - resizeleft : 'ew-resize', - resizebottom : 'ns-resize', - resizeright : 'ew-resize', - resizetopleft : 'nwse-resize', - resizebottomright: 'nwse-resize', - resizetopright : 'nesw-resize', - resizebottomleft : 'nesw-resize', - - gesture : '' -}; - module.exports = actions; diff --git a/src/actions/drag.js b/src/actions/drag.js index 00a58dc02..447d694d0 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -13,6 +13,10 @@ var drag = { : null; }, + getCursor: function () { + return 'move'; + }, + start: function (interaction, event) { var dragEvent = new InteractEvent(interaction, event, 'drag', 'start', interaction.element); diff --git a/src/actions/gesture.js b/src/actions/gesture.js index dce3c64c8..86430fa02 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -17,6 +17,10 @@ var gesture = { return null; }, + getCursor: function () { + return ''; + }, + start: function (interaction, event) { var gestureEvent = new InteractEvent(interaction, event, 'gesture', 'start', interaction.element); diff --git a/src/actions/resize.js b/src/actions/resize.js index 3ed7b7e94..274aa009f 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -2,6 +2,7 @@ var base = require('./base'), utils = require('../utils'), + browser = require('../utils/browser'), scope = base.scope, InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'); @@ -57,6 +58,52 @@ var resize = { return null; }, + cursors: (browser.isIe9OrOlder ? { + x : 'e-resize', + y : 's-resize', + xy: 'se-resize', + + top : 'n-resize', + left : 'w-resize', + bottom : 's-resize', + right : 'e-resize', + topleft : 'se-resize', + bottomright: 'se-resize', + topright : 'ne-resize', + bottomleft : 'ne-resize', + } : { + x : 'ew-resize', + y : 'ns-resize', + xy: 'nwse-resize', + + top : 'ns-resize', + left : 'ew-resize', + bottom : 'ns-resize', + right : 'ew-resize', + topleft : 'nwse-resize', + bottomright: 'nwse-resize', + topright : 'nesw-resize', + bottomleft : 'nesw-resize', + }), + + getCursor: function (action) { + if (action.axis) { + return resize.cursors[action.name + action.axis]; + } + else if (action.edges) { + var cursorKey = '', + edgeNames = ['top', 'bottom', 'left', 'right']; + + for (var i = 0; i < 4; i++) { + if (action.edges[edgeNames[i]]) { + cursorKey += edgeNames[i]; + } + } + + return resize.cursors[cursorKey]; + } + }, + start: function (interaction, event) { var resizeEvent = new InteractEvent(interaction, event, 'resize', 'start', interaction.element); From eb10511f4826dee283b2040ae6648da0889cdd11 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 01:52:00 +0200 Subject: [PATCH 061/131] Remove all references to actionIsEnabled --- src/Interaction.js | 7 +++---- src/actions/drag.js | 2 +- src/actions/gesture.js | 6 ++---- src/actions/resize.js | 2 +- src/interact.js | 6 ------ 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index ada1d2d84..9543ee8c9 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -140,7 +140,7 @@ function Interaction () { scope.interactions.push(this); } -// Check if action is enabled globally and the current target supports it +// Check if the current target supports the action. // If so, return the validated action. Otherwise, return null function validateAction (action, interactable) { if (!utils.isObject(action)) { return null; } @@ -148,10 +148,9 @@ function validateAction (action, interactable) { var actionName = action.name, options = interactable.options; - if (( (actionName === 'resize' && options.resize.enabled ) + if ( (actionName === 'resize' && options.resize.enabled ) || (actionName === 'drag' && options.drag.enabled ) - || (actionName === 'gesture' && options.gesture.enabled)) - && scope.actionIsEnabled[actionName]) { + || (actionName === 'gesture' && options.gesture.enabled)) { if (actionName === 'resize' || actionName === 'resizeyx') { actionName = 'resizexy'; diff --git a/src/actions/drag.js b/src/actions/drag.js index 447d694d0..68eb31705 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -8,7 +8,7 @@ var base = require('./base'), var drag = { checker: function (pointer, event, interactable) { - return scope.actionIsEnabled.drag && interactable.options.drag.enabled + return interactable.options.drag.enabled ? { name: 'drag' } : null; }, diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 86430fa02..4af799edc 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -1,16 +1,14 @@ 'use strict'; var base = require('./base'), - scope = base.scope, utils = require('../utils'), InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'); var gesture = { checker: function (pointer, event, interactable, element, interaction) { - if (scope.actionIsEnabled.gesture - && interaction.pointerIds.length >=2 - && !(interaction.dragging || interaction.resizing)) { + if (interaction.pointerIds.length >=2 + && !(interaction.dragging || interaction.resizing)) { return { name: 'gesture' }; } diff --git a/src/actions/resize.js b/src/actions/resize.js index 274aa009f..2f991fc89 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -14,7 +14,7 @@ var resize = { var page = utils.extend({}, interaction.curCoords.page), options = interactable.options; - if (scope.actionIsEnabled.resize && options.resize.enabled) { + if (options.resize.enabled) { var resizeOptions = options.resize, resizeEdges = { left: false, right: false, top: false, bottom: false diff --git a/src/interact.js b/src/interact.js index a7c0355fe..1d3e1e079 100644 --- a/src/interact.js +++ b/src/interact.js @@ -35,12 +35,6 @@ // Allow this many interactions to happen simultaneously scope.maxInteractions = Infinity; - scope.actionIsEnabled = { - drag : true, - resize : true, - gesture: true - }; - // because Webkit and Opera still use 'mousewheel' event type scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel'; From 61c1ec447c56d64b52e2083b8084ba598e69040a Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 01:58:12 +0200 Subject: [PATCH 062/131] Make validateAction function more concise --- src/Interaction.js | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 9543ee8c9..9f6757c14 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -143,21 +143,10 @@ function Interaction () { // Check if the current target supports the action. // If so, return the validated action. Otherwise, return null function validateAction (action, interactable) { - if (!utils.isObject(action)) { return null; } - - var actionName = action.name, - options = interactable.options; - - if ( (actionName === 'resize' && options.resize.enabled ) - || (actionName === 'drag' && options.drag.enabled ) - || (actionName === 'gesture' && options.gesture.enabled)) { - - if (actionName === 'resize' || actionName === 'resizeyx') { - actionName = 'resizexy'; - } - + if (utils.isObject(action) && interactable.options[action.name].enabled) { return action; } + return null; } From 9d3a03230f7237d0f11d40f65feeab3b94ce9e94 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 21:34:27 +0200 Subject: [PATCH 063/131] Use more general action code where possible - Added actions.methodDict - Gave each action a `stop` function - Replaced interaction.ing bool properties with interaction._interacting --- src/Interactable.js | 20 ++++++++------------ src/Interaction.js | 18 ++++++------------ src/actions/base.js | 3 ++- src/actions/drag.js | 10 +++++++++- src/actions/gesture.js | 10 ++++++---- src/actions/resize.js | 7 +++++-- 6 files changed, 36 insertions(+), 32 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index 7507d2835..cbc84b58b 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -592,20 +592,16 @@ Interactable.prototype = { this.options = utils.extend({}, scope.defaultOptions.base); - var i, - len, - actions = ['drag', 'drop', 'resize', 'gesture'], - methods = ['draggable', 'dropzone', 'resizable', 'gesturable'], - perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[action] || {}); + var perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[actionName] || {}); - for (i = 0; i < actions.length; i++) { - var action = actions[i]; + for (var actionName in actions.methodDict) { + var methodName = actions.methodDict[actionName]; - this.options[action] = utils.extend({}, scope.defaultOptions[action]); + this.options[actionName] = utils.extend({}, scope.defaultOptions[actionName]); - this.setPerAction(action, perActions); + this.setPerAction(actionName, perActions); - this[methods[i]](options[action]); + this[methodName](options[actionName]); } var settings = [ @@ -614,7 +610,7 @@ Interactable.prototype = { 'rectChecker' ]; - for (i = 0, len = settings.length; i < len; i++) { + for (var i = 0, len = settings.length; i < len; i++) { var setting = settings[i]; this.options[setting] = scope.defaultOptions.base[setting]; @@ -632,7 +628,7 @@ Interactable.prototype = { [ method ] * * Remove this interactable from the list of interactables and remove - * it's drag, drop, resize and gesture capabilities + * it's action capabilities and event listeners * = (object) @interact \*/ diff --git a/src/Interaction.js b/src/Interaction.js index 9f6757c14..34c2a4378 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -130,9 +130,7 @@ function Interaction () { this.pointerIsDown = false; this.pointerWasMoved = false; - this.gesturing = false; - this.dragging = false; - this.resizing = false; + this._interacting = false; this.resizeAxes = 'xy'; this.mouse = false; @@ -748,9 +746,7 @@ Interaction.prototype = { utils.copyCoords(this.prevCoords, this.curCoords); - if (this.dragging || this.resizing) { - this.autoScrollMove(pointer); - } + this.autoScrollMove(pointer); }, pointerHold: function (pointer, event, eventTarget) { @@ -913,11 +909,11 @@ Interaction.prototype = { }, currentAction: function () { - return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null; + return this._interacting? this.prepared.name: null; }, interacting: function () { - return this.dragging || this.resizing || this.gesturing; + return this._interacting; }, clearTargets: function () { @@ -943,14 +939,12 @@ Interaction.prototype = { this.checkAndPreventDefault(event, target, this.element); } - if (this.dragging) { - this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null; - } + actions[this.prepared.name].stop(this, event); } this.clearTargets(); - this.pointerIsDown = this.dragging = this.resizing = this.gesturing = false; + this.pointerIsDown = this._interacting = false; this.prepared.name = this.prevEvent = null; this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; diff --git a/src/actions/base.js b/src/actions/base.js index 56d7cbb0d..839713411 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -24,7 +24,8 @@ var actions = { return action; }, - names: [] + names: [], + methodDict: {} }; module.exports = actions; diff --git a/src/actions/drag.js b/src/actions/drag.js index 68eb31705..79f635503 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -20,7 +20,7 @@ var drag = { start: function (interaction, event) { var dragEvent = new InteractEvent(interaction, event, 'drag', 'start', interaction.element); - interaction.dragging = true; + interaction._interacting = true; interaction.target.fire(dragEvent); // reset active dropzones @@ -83,6 +83,12 @@ var drag = { } interaction.target.fire(endEvent); + }, + + stop: function (interaction) { + interaction.activeDrops.dropzones = + interaction.activeDrops.elements = + interaction.activeDrops.rects = null; } }; @@ -560,5 +566,7 @@ base.addEventTypes([ 'dropmove', 'drop' ]); +base.methodDict.drag = 'draggable'; +base.methodDict.drop = 'dropzone'; module.exports = drag; diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 4af799edc..f027c7b03 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -7,8 +7,7 @@ var base = require('./base'), var gesture = { checker: function (pointer, event, interactable, element, interaction) { - if (interaction.pointerIds.length >=2 - && !(interaction.dragging || interaction.resizing)) { + if (interaction.pointerIds.length >= 2) { return { name: 'gesture' }; } @@ -28,7 +27,7 @@ var gesture = { interaction.gesture.startAngle = interaction.gesture.prevAngle = gestureEvent.angle; interaction.gesture.scale = 1; - interaction.gesturing = true; + interaction._interacting = true; interaction.target.fire(gestureEvent); @@ -65,7 +64,9 @@ var gesture = { var endEvent = new InteractEvent(interaction, event, 'gesture', 'end', interaction.element); interaction.target.fire(endEvent); - } + }, + + stop: utils.blank }; /*\ @@ -117,5 +118,6 @@ base.addEventTypes([ 'gestureinertiastart', 'gestureend' ]); +base.methodDict.gesture = 'gesturable'; module.exports = gesture; diff --git a/src/actions/resize.js b/src/actions/resize.js index 2f991fc89..befc2f298 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -141,7 +141,7 @@ var resize = { interaction.target.fire(resizeEvent); - interaction.resizing = true; + interaction._interacting = true; return resizeEvent; }, @@ -233,7 +233,9 @@ var resize = { var endEvent = new InteractEvent(interaction, event, 'resize', 'end', interaction.element); interaction.target.fire(endEvent); - } + }, + + stop: utils.blank }; /*\ @@ -343,5 +345,6 @@ base.addEventTypes([ 'resizeinertiastart', 'resizeend' ]); +base.methodDict.resize = 'resizable'; module.exports = resize; From 405617ecb80bbe5467c0ecd9ef47fc54057b9f4a Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 22:44:25 +0200 Subject: [PATCH 064/131] Move withinInteractionLimit function to scope --- src/Interaction.js | 51 +++++----------------------------------------- src/scope.js | 42 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 34c2a4378..3f4e8a116 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -204,7 +204,7 @@ Interaction.prototype = { elementInteractable.getAction(pointer, event, this, eventTarget), elementInteractable)); - if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { + if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { elementAction = null; } @@ -421,7 +421,7 @@ Interaction.prototype = { && !scope.testIgnore(interactable, curEventTarget, eventTarget) && scope.testAllow(interactable, curEventTarget, eventTarget) && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) - && withinInteractionLimit(interactable, curEventTarget, action)) { + && scope.withinInteractionLimit(interactable, curEventTarget, action)) { this.target = interactable; this.element = curEventTarget; } @@ -723,7 +723,7 @@ Interaction.prototype = { if (starting && (this.target.options[this.prepared.name].manualStart - || !withinInteractionLimit(this.target, this.element, this.prepared))) { + || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { this.stop(event); return; } @@ -1205,7 +1205,7 @@ Interaction.prototype = { matchElement = matchElements[i], action = validateAction(match.getAction(pointer, event, this, matchElement), match); - if (action && withinInteractionLimit(match, matchElement, action)) { + if (action && scope.withinInteractionLimit(match, matchElement, action)) { this.target = match; this.element = matchElement; @@ -1315,48 +1315,6 @@ Interaction.prototype = { } }; -function withinInteractionLimit (interactable, element, action) { - var options = interactable.options, - maxActions = options[action.name].max, - maxPerElement = options[action.name].maxPerElement, - activeInteractions = 0, - targetCount = 0, - targetElementCount = 0; - - for (var i = 0, len = scope.interactions.length; i < len; i++) { - var interaction = scope.interactions[i], - otherAction = interaction.prepared.name, - active = interaction.interacting(); - - if (!active) { continue; } - - activeInteractions++; - - if (activeInteractions >= scope.maxInteractions) { - return false; - } - - if (interaction.target !== interactable) { continue; } - - targetCount += (otherAction === action.name)|0; - - if (targetCount >= maxActions) { - return false; - } - - if (interaction.element === element) { - targetElementCount++; - - if (otherAction !== action.name || targetElementCount >= maxPerElement) { - return false; - } - } - } - - return scope.maxInteractions > 0; -} - - function getInteractionFromPointer (pointer, eventType, eventTarget) { var i = 0, len = scope.interactions.length, mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) @@ -1500,5 +1458,6 @@ function doOnInteractions (method) { Interaction.getInteractionFromPointer = getInteractionFromPointer; Interaction.doOnInteractions = doOnInteractions; +Interaction.withinLimit = scope.withinInteractionLimit; module.exports = Interaction; diff --git a/src/scope.js b/src/scope.js index 32091a9d5..1f975543a 100644 --- a/src/scope.js +++ b/src/scope.js @@ -17,4 +17,46 @@ scope.events = require('./utils/events'); extend(scope, require('./utils/window')); extend(scope, require('./utils/domObjects')); +scope.withinInteractionLimit = function (interactable, element, action) { + var options = interactable.options, + maxActions = options[action.name].max, + maxPerElement = options[action.name].maxPerElement, + activeInteractions = 0, + targetCount = 0, + targetElementCount = 0; + + for (var i = 0, len = scope.interactions.length; i < len; i++) { + var interaction = scope.interactions[i], + otherAction = interaction.prepared.name, + active = interaction.interacting(); + + if (!active) { continue; } + + activeInteractions++; + + if (activeInteractions >= scope.maxInteractions) { + return false; + } + + if (interaction.target !== interactable) { continue; } + + targetCount += (otherAction === action.name)|0; + + if (targetCount >= maxActions) { + return false; + } + + if (interaction.element === element) { + targetElementCount++; + + if (otherAction !== action.name || targetElementCount >= maxPerElement) { + return false; + } + } + } + + return scope.maxInteractions > 0; +}; + + module.exports = scope; From 3a19a16f1ef60f79733bb9f08b8869e086e514f6 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 23:00:52 +0200 Subject: [PATCH 065/131] Add action beforeStart method Moved drag axis checks to actions.drag.beforeStart --- src/Interaction.js | 78 +--------------------------------------- src/actions/drag.js | 82 ++++++++++++++++++++++++++++++++++++++++++ src/actions/gesture.js | 2 ++ src/actions/resize.js | 2 ++ 4 files changed, 87 insertions(+), 77 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 3f4e8a116..9549d4a8a 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -640,83 +640,7 @@ Interaction.prototype = { if (!this.interacting()) { utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - // check if a drag is in the correct axis - if (this.prepared.name === 'drag') { - var absX = Math.abs(dx), - absY = Math.abs(dy), - targetAxis = this.target.options.drag.axis, - axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); - - // if the movement isn't in the axis of the interactable - if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { - // cancel the prepared action - this.prepared.name = null; - - // then try to get a drag from another ineractable - - var element = eventTarget; - - // check element interactables - while (utils.isElement(element)) { - var elementInteractable = scope.interactables.get(element); - - if (elementInteractable - && elementInteractable !== this.target - && !elementInteractable.options.drag.manualStart - && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag' - && scope.checkAxis(axis, elementInteractable)) { - - this.prepared.name = 'drag'; - this.target = elementInteractable; - this.element = element; - break; - } - - element = utils.parentElement(element); - } - - // if there's no drag from element interactables, - // check the selector interactables - if (!this.prepared.name) { - var thisInteraction = this; - - var getDraggable = function (interactable, selector, context) { - var elements = browser.useMatchesSelectorPolyfill - ? context.querySelectorAll(selector) - : undefined; - - if (interactable === thisInteraction.target) { return; } - - if (scope.inContext(interactable, eventTarget) - && !interactable.options.drag.manualStart - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && utils.matchesSelector(element, selector, elements) - && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag' - && scope.checkAxis(axis, interactable) - && withinInteractionLimit(interactable, element, 'drag')) { - - return interactable; - } - }; - - element = eventTarget; - - while (utils.isElement(element)) { - var selectorInteractable = scope.interactables.forEachSelector(getDraggable); - - if (selectorInteractable) { - this.prepared.name = 'drag'; - this.target = selectorInteractable; - this.element = element; - break; - } - - element = utils.parentElement(element); - } - } - } - } + actions[this.prepared.name].beforeStart(this, pointer, event, eventTarget, curEventTarget, dx, dy); } var starting = !!this.prepared.name && !this.interacting(); diff --git a/src/actions/drag.js b/src/actions/drag.js index 79f635503..d5cf846ec 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -3,7 +3,9 @@ var base = require('./base'), scope = base.scope, utils = require('../utils'), + browser = utils.browser, InteractEvent = require('../InteractEvent'), + Interaction = require('../Interaction'), Interactable = require('../Interactable'); var drag = { @@ -17,6 +19,86 @@ var drag = { return 'move'; }, + beforeStart: function (interaction, pointer, event, eventTarget, curEventTarget, dx, dy) { + // check if a drag is in the correct axis + if (interaction.prepared.name === 'drag') { + var absX = Math.abs(dx), + absY = Math.abs(dy), + targetAxis = interaction.target.options.drag.axis, + axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); + + // if the movement isn't in the axis of the interactable + if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { + // cancel the prepared action + interaction.prepared.name = null; + + // then try to get a drag from another ineractable + + var element = eventTarget; + + // check element interactables + while (utils.isElement(element)) { + var elementInteractable = scope.interactables.get(element); + + if (elementInteractable + && elementInteractable !== interaction.target + && !elementInteractable.options.drag.manualStart + && elementInteractable.getAction(interaction.downPointer, interaction.downEvent, interaction, element).name === 'drag' + && scope.checkAxis(axis, elementInteractable)) { + + interaction.prepared.name = 'drag'; + interaction.target = elementInteractable; + interaction.element = element; + break; + } + + element = utils.parentElement(element); + } + + // if there's no drag from element interactables, + // check the selector interactables + if (!interaction.prepared.name) { + var interactionInteraction = interaction; + + var getDraggable = function (interactable, selector, context) { + var elements = browser.useMatchesSelectorPolyfill + ? context.querySelectorAll(selector) + : undefined; + + if (interactable === interactionInteraction.target) { return; } + + if (scope.inContext(interactable, eventTarget) + && !interactable.options.drag.manualStart + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && utils.matchesSelector(element, selector, elements) + && interactable.getAction(interactionInteraction.downPointer, interactionInteraction.downEvent, interactionInteraction, element).name === 'drag' + && scope.checkAxis(axis, interactable) + && scope.withinInteractionLimit(interactable, element, 'drag')) { + + return interactable; + } + }; + + element = eventTarget; + + while (utils.isElement(element)) { + var selectorInteractable = scope.interactables.forEachSelector(getDraggable); + + if (selectorInteractable) { + interaction.prepared.name = 'drag'; + interaction.target = selectorInteractable; + interaction.element = element; + break; + } + + element = utils.parentElement(element); + } + } + } + } + }, + start: function (interaction, event) { var dragEvent = new InteractEvent(interaction, event, 'drag', 'start', interaction.element); diff --git a/src/actions/gesture.js b/src/actions/gesture.js index f027c7b03..86f98c49a 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -18,6 +18,8 @@ var gesture = { return ''; }, + beforeStart: utils.blank, + start: function (interaction, event) { var gestureEvent = new InteractEvent(interaction, event, 'gesture', 'start', interaction.element); diff --git a/src/actions/resize.js b/src/actions/resize.js index befc2f298..44bbd0970 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -104,6 +104,8 @@ var resize = { } }, + beforeStart: utils.blank, + start: function (interaction, event) { var resizeEvent = new InteractEvent(interaction, event, 'resize', 'start', interaction.element); From 160e2f436f9e9d62ef38ac8f667ce97745711aec Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 23:31:54 +0200 Subject: [PATCH 066/131] Fix Interactable#unset delegated event removal --- src/Interactable.js | 6 +++--- src/utils/events.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index cbc84b58b..c4df25ca9 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -643,8 +643,8 @@ Interactable.prototype = { } else { // remove delegated events - for (var type in scope.delegatedEvents) { - var delegated = scope.delegatedEvents[type]; + for (var type in events.delegatedEvents) { + var delegated = events.delegatedEvents[type]; for (var i = 0; i < delegated.selectors.length; i++) { if (delegated.selectors[i] === this.selector @@ -656,7 +656,7 @@ Interactable.prototype = { // remove the arrays if they are empty if (!delegated.selectors.length) { - scope.delegatedEvents[type] = null; + delegated[type] = null; } } diff --git a/src/utils/events.js b/src/utils/events.js index f8311a61d..7c8b78eea 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -332,6 +332,7 @@ module.exports = { delegateListener: delegateListener, delegateUseCapture: delegateUseCapture, + delegatedEvents: delegatedEvents, documents: documents, useAttachEvent: useAttachEvent, From 47a3ea2dc04c628ba08486dd30523d67b607ef12 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 19 Jul 2015 23:32:23 +0200 Subject: [PATCH 067/131] Fix inertia smoothEnd --- src/Interaction.js | 6 ++---- src/modifiers/index.js | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 9549d4a8a..47550629f 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -737,8 +737,6 @@ Interaction.prototype = { statuses = {}, modifierResult, page = utils.extend({}, this.curCoords.page), - dx = 0, - dy = 0, startEvent; if (this.dragging) { @@ -804,8 +802,8 @@ Interaction.prototype = { } else { inertiaStatus.smoothEnd = true; - inertiaStatus.xe = dx; - inertiaStatus.ye = dy; + inertiaStatus.xe = modifierResult.dx; + inertiaStatus.ye = modifierResult.dy; inertiaStatus.sx = inertiaStatus.sy = 0; diff --git a/src/modifiers/index.js b/src/modifiers/index.js index d4bd2615d..03fe198d6 100644 --- a/src/modifiers/index.js +++ b/src/modifiers/index.js @@ -32,6 +32,8 @@ var modifiers = { result.dx += currentStatus.dx; result.dy += currentStatus.dy; + + result.locked = true; } } From 76f0a1d37782fb9c112a17d8d677f7b31e14d9fd Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 20 Jul 2015 08:24:05 +0200 Subject: [PATCH 068/131] actions/drag: remove unused Interaction variable --- src/actions/drag.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/actions/drag.js b/src/actions/drag.js index d5cf846ec..6cc0dca6a 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -5,7 +5,6 @@ var base = require('./base'), utils = require('../utils'), browser = utils.browser, InteractEvent = require('../InteractEvent'), - Interaction = require('../Interaction'), Interactable = require('../Interactable'); var drag = { From ee4face42f6399751b24a1205a035ab8e38f6a4e Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 21 Jul 2015 23:19:00 +0200 Subject: [PATCH 069/131] Move drop code from actions/drag to actions/drop --- src/actions/drag.js | 589 +++++--------------------------------------- src/actions/drop.js | 482 ++++++++++++++++++++++++++++++++++++ 2 files changed, 548 insertions(+), 523 deletions(-) create mode 100644 src/actions/drop.js diff --git a/src/actions/drag.js b/src/actions/drag.js index 6cc0dca6a..b3e45a380 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -1,6 +1,7 @@ 'use strict'; var base = require('./base'), + drop = require('./drop'), scope = base.scope, utils = require('../utils'), browser = utils.browser, @@ -20,80 +21,78 @@ var drag = { beforeStart: function (interaction, pointer, event, eventTarget, curEventTarget, dx, dy) { // check if a drag is in the correct axis - if (interaction.prepared.name === 'drag') { - var absX = Math.abs(dx), - absY = Math.abs(dy), - targetAxis = interaction.target.options.drag.axis, - axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); + var absX = Math.abs(dx), + absY = Math.abs(dy), + targetAxis = interaction.target.options.drag.axis, + axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); + + // if the movement isn't in the axis of the interactable + if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { + // cancel the prepared action + interaction.prepared.name = null; + + // then try to get a drag from another ineractable + + var element = eventTarget; + + // check element interactables + while (utils.isElement(element)) { + var elementInteractable = scope.interactables.get(element); + + if (elementInteractable + && elementInteractable !== interaction.target + && !elementInteractable.options.drag.manualStart + && elementInteractable.getAction(interaction.downPointer, interaction.downEvent, interaction, element).name === 'drag' + && scope.checkAxis(axis, elementInteractable)) { + + interaction.prepared.name = 'drag'; + interaction.target = elementInteractable; + interaction.element = element; + break; + } - // if the movement isn't in the axis of the interactable - if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { - // cancel the prepared action - interaction.prepared.name = null; + element = utils.parentElement(element); + } - // then try to get a drag from another ineractable + // if there's no drag from element interactables, + // check the selector interactables + if (!interaction.prepared.name) { + var interactionInteraction = interaction; - var element = eventTarget; + var getDraggable = function (interactable, selector, context) { + var elements = browser.useMatchesSelectorPolyfill + ? context.querySelectorAll(selector) + : undefined; - // check element interactables - while (utils.isElement(element)) { - var elementInteractable = scope.interactables.get(element); + if (interactable === interactionInteraction.target) { return; } + + if (scope.inContext(interactable, eventTarget) + && !interactable.options.drag.manualStart + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && utils.matchesSelector(element, selector, elements) + && interactable.getAction(interactionInteraction.downPointer, interactionInteraction.downEvent, interactionInteraction, element).name === 'drag' + && scope.checkAxis(axis, interactable) + && scope.withinInteractionLimit(interactable, element, 'drag')) { + + return interactable; + } + }; + + element = eventTarget; - if (elementInteractable - && elementInteractable !== interaction.target - && !elementInteractable.options.drag.manualStart - && elementInteractable.getAction(interaction.downPointer, interaction.downEvent, interaction, element).name === 'drag' - && scope.checkAxis(axis, elementInteractable)) { + while (utils.isElement(element)) { + var selectorInteractable = scope.interactables.forEachSelector(getDraggable); + if (selectorInteractable) { interaction.prepared.name = 'drag'; - interaction.target = elementInteractable; + interaction.target = selectorInteractable; interaction.element = element; break; } element = utils.parentElement(element); } - - // if there's no drag from element interactables, - // check the selector interactables - if (!interaction.prepared.name) { - var interactionInteraction = interaction; - - var getDraggable = function (interactable, selector, context) { - var elements = browser.useMatchesSelectorPolyfill - ? context.querySelectorAll(selector) - : undefined; - - if (interactable === interactionInteraction.target) { return; } - - if (scope.inContext(interactable, eventTarget) - && !interactable.options.drag.manualStart - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && utils.matchesSelector(element, selector, elements) - && interactable.getAction(interactionInteraction.downPointer, interactionInteraction.downEvent, interactionInteraction, element).name === 'drag' - && scope.checkAxis(axis, interactable) - && scope.withinInteractionLimit(interactable, element, 'drag')) { - - return interactable; - } - }; - - element = eventTarget; - - while (utils.isElement(element)) { - var selectorInteractable = scope.interactables.forEachSelector(getDraggable); - - if (selectorInteractable) { - interaction.prepared.name = 'drag'; - interaction.target = selectorInteractable; - interaction.element = element; - break; - } - - element = utils.parentElement(element); - } - } } } }, @@ -104,43 +103,15 @@ var drag = { interaction._interacting = true; interaction.target.fire(dragEvent); - // reset active dropzones - interaction.activeDrops.dropzones = []; - interaction.activeDrops.elements = []; - interaction.activeDrops.rects = []; - - if (!interaction.dynamicDrop) { - setActiveDrops(interaction, interaction.element); - } - - var dropEvents = getDropEvents(interaction, event, dragEvent); - - if (dropEvents.activate) { - fireActiveDrops(interaction, dropEvents.activate); - } + drop.start(interaction, event, dragEvent); return dragEvent; }, move: function (interaction, event) { - var target = interaction.target, - dragEvent = new InteractEvent(interaction, event, 'drag', 'move', interaction.element), - draggableElement = interaction.element, - drop = getDrop(interaction, event, draggableElement); + var dragEvent = new InteractEvent(interaction, event, 'drag', 'move', interaction.element); - interaction.dropTarget = drop.dropzone; - interaction.dropElement = drop.element; - - var dropEvents = getDropEvents(interaction, event, dragEvent); - - target.fire(dragEvent); - - if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } - if (dropEvents.move ) { interaction.dropTarget.fire(dropEvents.move ); } - - interaction.prevDropTarget = interaction.dropTarget; - interaction.prevDropElement = interaction.dropElement; + drop.move(interaction, event, dragEvent); return dragEvent; }, @@ -148,241 +119,14 @@ var drag = { end: function (interaction, event) { var endEvent = new InteractEvent(interaction, event, 'drag', 'end', interaction.element); - var draggableElement = interaction.element, - drop = getDrop(interaction, event, draggableElement); - - interaction.dropTarget = drop.dropzone; - interaction.dropElement = drop.element; - - var dropEvents = getDropEvents(interaction, event, endEvent); - - if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } - if (dropEvents.drop ) { interaction.dropTarget.fire(dropEvents.drop ); } - if (dropEvents.deactivate) { - fireActiveDrops(interaction, dropEvents.deactivate); - } + drop.end(interaction, event, endEvent); interaction.target.fire(endEvent); }, - stop: function (interaction) { - interaction.activeDrops.dropzones = - interaction.activeDrops.elements = - interaction.activeDrops.rects = null; - } + stop: drop.stop }; -function collectDrops (interaction, element) { - var drops = [], - elements = [], - i; - - element = element || interaction.element; - - // collect all dropzones and their elements which qualify for a drop - for (i = 0; i < scope.interactables.length; i++) { - if (!scope.interactables[i].options.drop.enabled) { continue; } - - var current = scope.interactables[i], - accept = current.options.drop.accept; - - // test the draggable element against the dropzone's accept setting - if ((utils.isElement(accept) && accept !== element) - || (utils.isString(accept) - && !utils.matchesSelector(element, accept))) { - - continue; - } - - // query for new elements if necessary - var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; - - for (var j = 0, len = dropElements.length; j < len; j++) { - var currentElement = dropElements[j]; - - if (currentElement === element) { - continue; - } - - drops.push(current); - elements.push(currentElement); - } - } - - return { - dropzones: drops, - elements: elements - }; -} - -function fireActiveDrops (interaction, event) { - var i, - current, - currentElement, - prevElement; - - // loop through all active dropzones and trigger event - for (i = 0; i < interaction.activeDrops.dropzones.length; i++) { - current = interaction.activeDrops.dropzones[i]; - currentElement = interaction.activeDrops.elements [i]; - - // prevent trigger of duplicate events on same element - if (currentElement !== prevElement) { - // set current element as event target - event.target = currentElement; - current.fire(event); - } - prevElement = currentElement; - } -} - -// Collect a new set of possible drops and save them in activeDrops. -// setActiveDrops should always be called when a drag has just started or a -// drag event happens while dynamicDrop is true -function setActiveDrops (interaction, dragElement) { - // get dropzones and their elements that could receive the draggable - var possibleDrops = collectDrops(interaction, dragElement, true); - - interaction.activeDrops.dropzones = possibleDrops.dropzones; - interaction.activeDrops.elements = possibleDrops.elements; - interaction.activeDrops.rects = []; - - for (var i = 0; i < interaction.activeDrops.dropzones.length; i++) { - interaction.activeDrops.rects[i] = interaction.activeDrops.dropzones[i].getRect(interaction.activeDrops.elements[i]); - } -} - -function getDrop (interaction, event, dragElement) { - var validDrops = []; - - if (scope.dynamicDrop) { - setActiveDrops(interaction, dragElement); - } - - // collect all dropzones and their elements which qualify for a drop - for (var j = 0; j < interaction.activeDrops.dropzones.length; j++) { - var current = interaction.activeDrops.dropzones[j], - currentElement = interaction.activeDrops.elements [j], - rect = interaction.activeDrops.rects [j]; - - validDrops.push(current.dropCheck(interaction.pointers[0], event, interaction.target, dragElement, currentElement, rect) - ? currentElement - : null); - } - - // get the most appropriate dropzone based on DOM depth and order - var dropIndex = utils.indexOfDeepestElement(validDrops), - dropzone = interaction.activeDrops.dropzones[dropIndex] || null, - element = interaction.activeDrops.elements [dropIndex] || null; - - return { - dropzone: dropzone, - element: element - }; -} - -function getDropEvents (interaction, pointerEvent, dragEvent) { - var dropEvents = { - enter : null, - leave : null, - activate : null, - deactivate: null, - move : null, - drop : null - }; - - if (interaction.dropElement !== interaction.prevDropElement) { - // if there was a prevDropTarget, create a dragleave event - if (interaction.prevDropTarget) { - dropEvents.leave = { - target : interaction.prevDropElement, - dropzone : interaction.prevDropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dragleave' - }; - - dragEvent.dragLeave = interaction.prevDropElement; - dragEvent.prevDropzone = interaction.prevDropTarget; - } - // if the dropTarget is not null, create a dragenter event - if (interaction.dropTarget) { - dropEvents.enter = { - target : interaction.dropElement, - dropzone : interaction.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dragenter' - }; - - dragEvent.dragEnter = interaction.dropElement; - dragEvent.dropzone = interaction.dropTarget; - } - } - - if (dragEvent.type === 'dragend' && interaction.dropTarget) { - dropEvents.drop = { - target : interaction.dropElement, - dropzone : interaction.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'drop' - }; - - dragEvent.dropzone = interaction.dropTarget; - } - if (dragEvent.type === 'dragstart') { - dropEvents.activate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dropactivate' - }; - } - if (dragEvent.type === 'dragend') { - dropEvents.deactivate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dropdeactivate' - }; - } - if (dragEvent.type === 'dragmove' && interaction.dropTarget) { - dropEvents.move = { - target : interaction.dropElement, - dropzone : interaction.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - dragmove : dragEvent, - timeStamp : dragEvent.timeStamp, - type : 'dropmove' - }; - dragEvent.dropzone = interaction.dropTarget; - } - - return dropEvents; -} - /*\ * Interactable.draggable [ method ] @@ -439,215 +183,14 @@ Interactable.prototype.draggable = function (options) { return this.options.drag; }; -/*\ - * Interactable.dropzone - [ method ] - * - * Returns or sets whether elements can be dropped onto this - * Interactable to trigger drop events - * - * Dropzones can receive the following events: - * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends - * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone - * - `dragmove` when a draggable that has entered the dropzone is moved - * - `drop` when a draggable is dropped into this dropzone - * - * Use the `accept` option to allow only elements that match the given CSS selector or element. - * - * Use the `overlap` option to set how drops are checked for. The allowed values are: - * - `'pointer'`, the pointer must be over the dropzone (default) - * - `'center'`, the draggable element's center must be over the dropzone - * - a number from 0-1 which is the `(intersection area) / (draggable area)`. - * e.g. `0.5` for drop to happen when half of the area of the - * draggable is over the dropzone - * - - options (boolean | object | null) #optional The new value to be set. - | interact('.drop').dropzone({ - | accept: '.can-drop' || document.getElementById('single-drop'), - | overlap: 'pointer' || 'center' || zeroToOne - | } - = (boolean | object) The current setting or this Interactable -\*/ -Interactable.prototype.dropzone = function (options) { - if (utils.isObject(options)) { - this.options.drop.enabled = options.enabled === false? false: true; - this.setOnEvents('drop', options); - this.accept(options.accept); - - if (/^(pointer|center)$/.test(options.overlap)) { - this.options.drop.overlap = options.overlap; - } - else if (utils.isNumber(options.overlap)) { - this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); - } - - return this; - } - - if (utils.isBool(options)) { - this.options.drop.enabled = options; - - return this; - } - - return this.options.drop; -}; - -Interactable.prototype.dropCheck = function (pointer, event, draggable, draggableElement, dropElement, rect) { - var dropped = false; - - // if the dropzone has no rect (eg. display: none) - // call the custom dropChecker or just return false - if (!(rect = rect || this.getRect(dropElement))) { - return (this.options.dropChecker - ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) - : false); - } - - var dropOverlap = this.options.drop.overlap; - - if (dropOverlap === 'pointer') { - var page = utils.getPageXY(pointer), - origin = scope.getOriginXY(draggable, draggableElement), - horizontal, - vertical; - - page.x += origin.x; - page.y += origin.y; - - horizontal = (page.x > rect.left) && (page.x < rect.right); - vertical = (page.y > rect.top ) && (page.y < rect.bottom); - - dropped = horizontal && vertical; - } - - var dragRect = draggable.getRect(draggableElement); - - if (dropOverlap === 'center') { - var cx = dragRect.left + dragRect.width / 2, - cy = dragRect.top + dragRect.height / 2; - - dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; - } - - if (utils.isNumber(dropOverlap)) { - var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) - * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), - overlapRatio = overlapArea / (dragRect.width * dragRect.height); - - dropped = overlapRatio >= dropOverlap; - } - - if (this.options.dropChecker) { - dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); - } - - return dropped; -}; - -/*\ - * Interactable.dropChecker - [ method ] - * - * Gets or sets the function used to check if a dragged element is - * over this Interactable. - * - - checker (function) #optional The function that will be called when checking for a drop - = (Function | Interactable) The checker function or this Interactable - * - * The checker function takes the following arguments: - * - - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag - - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer - - dropped (boolean) The value from the default drop check - - dropzone (Interactable) The dropzone interactable - - dropElement (Element) The dropzone element - - draggable (Interactable) The Interactable being dragged - - draggableElement (Element) The actual element that's being dragged - * - > Usage: - | interact(target) - | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent - | event, // TouchEvent/PointerEvent/MouseEvent - | dropped, // result of the default checker - | dropzone, // dropzone Interactable - | dropElement, // dropzone elemnt - | draggable, // draggable Interactable - | draggableElement) {// draggable element - | - | return dropped && event.target.hasAttribute('allow-drop'); - | } -\*/ -Interactable.prototype.dropChecker = function (checker) { - if (utils.isFunction(checker)) { - this.options.dropChecker = checker; - - return this; - } - if (checker === null) { - delete this.options.getRect; - - return this; - } - - return this.options.dropChecker; -}; - -/*\ - * Interactable.accept - [ method ] - * - * Deprecated. add an `accept` property to the options object passed to - * @Interactable.dropzone instead. - * - * Gets or sets the Element or CSS selector match that this - * Interactable accepts if it is a dropzone. - * - - newValue (Element | string | null) #optional - * If it is an Element, then only that element can be dropped into this dropzone. - * If it is a string, the element being dragged must match it as a selector. - * If it is null, the accept options is cleared - it accepts any element. - * - = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable -\*/ -Interactable.prototype.accept = function (newValue) { - if (utils.isElement(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - // test if it is a valid CSS selector - if (utils.trySelector(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.drop.accept; - - return this; - } - - return this.options.drop.accept; -}; - base.drag = drag; base.names.push('drag'); base.addEventTypes([ 'dragstart', 'dragmove', 'draginertiastart', - 'dragend', - 'dragenter', - 'dragleave', - 'dropactivate', - 'dropdeactivate', - 'dropmove', - 'drop' + 'dragend' ]); base.methodDict.drag = 'draggable'; -base.methodDict.drop = 'dropzone'; module.exports = drag; diff --git a/src/actions/drop.js b/src/actions/drop.js new file mode 100644 index 000000000..96baefb46 --- /dev/null +++ b/src/actions/drop.js @@ -0,0 +1,482 @@ +'use strict'; + +var base = require('./base'), + utils = require('../utils'), + scope = require('../scope'), + Interactable = require('../Interactable'); + +var drop = { + //beforeStart: function + start: function (interaction, event, dragEvent) { + // reset active dropzones + interaction.activeDrops.dropzones = []; + interaction.activeDrops.elements = []; + interaction.activeDrops.rects = []; + + if (!interaction.dynamicDrop) { + setActiveDrops(interaction, interaction.element); + } + + var dropEvents = getDropEvents(interaction, event, dragEvent); + + if (dropEvents.activate) { + fireActiveDrops(interaction, dropEvents.activate); + } + }, + move: function (interaction, event, dragEvent) { + var draggableElement = interaction.element, + drop = getDrop(interaction, event, draggableElement); + + interaction.dropTarget = drop.dropzone; + interaction.dropElement = drop.element; + + var dropEvents = getDropEvents(interaction, event, dragEvent); + + interaction.target.fire(dragEvent); + + if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } + if (dropEvents.move ) { interaction.dropTarget.fire(dropEvents.move ); } + + interaction.prevDropTarget = interaction.dropTarget; + interaction.prevDropElement = interaction.dropElement; + + }, + end: function (interaction, event, endEvent) { + var draggableElement = interaction.element, + drop = getDrop(interaction, event, draggableElement); + + interaction.dropTarget = drop.dropzone; + interaction.dropElement = drop.element; + + var dropEvents = getDropEvents(interaction, event, endEvent); + + if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } + if (dropEvents.drop ) { interaction.dropTarget.fire(dropEvents.drop ); } + if (dropEvents.deactivate) { + fireActiveDrops(interaction, dropEvents.deactivate); + } + + }, + stop: function (interaction) { + interaction.activeDrops.dropzones = + interaction.activeDrops.elements = + interaction.activeDrops.rects = null; + } +}; + +function collectDrops (interaction, element) { + var drops = [], + elements = [], + i; + + element = element || interaction.element; + + // collect all dropzones and their elements which qualify for a drop + for (i = 0; i < scope.interactables.length; i++) { + if (!scope.interactables[i].options.drop.enabled) { continue; } + + var current = scope.interactables[i], + accept = current.options.drop.accept; + + // test the draggable element against the dropzone's accept setting + if ((utils.isElement(accept) && accept !== element) + || (utils.isString(accept) + && !utils.matchesSelector(element, accept))) { + + continue; + } + + // query for new elements if necessary + var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; + + for (var j = 0, len = dropElements.length; j < len; j++) { + var currentElement = dropElements[j]; + + if (currentElement === element) { + continue; + } + + drops.push(current); + elements.push(currentElement); + } + } + + return { + dropzones: drops, + elements: elements + }; +} + +function fireActiveDrops (interaction, event) { + var i, + current, + currentElement, + prevElement; + + // loop through all active dropzones and trigger event + for (i = 0; i < interaction.activeDrops.dropzones.length; i++) { + current = interaction.activeDrops.dropzones[i]; + currentElement = interaction.activeDrops.elements [i]; + + // prevent trigger of duplicate events on same element + if (currentElement !== prevElement) { + // set current element as event target + event.target = currentElement; + current.fire(event); + } + prevElement = currentElement; + } +} + +// Collect a new set of possible drops and save them in activeDrops. +// setActiveDrops should always be called when a drag has just started or a +// drag event happens while dynamicDrop is true +function setActiveDrops (interaction, dragElement) { + // get dropzones and their elements that could receive the draggable + var possibleDrops = collectDrops(interaction, dragElement, true); + + interaction.activeDrops.dropzones = possibleDrops.dropzones; + interaction.activeDrops.elements = possibleDrops.elements; + interaction.activeDrops.rects = []; + + for (var i = 0; i < interaction.activeDrops.dropzones.length; i++) { + interaction.activeDrops.rects[i] = interaction.activeDrops.dropzones[i].getRect(interaction.activeDrops.elements[i]); + } +} + +function getDrop (interaction, event, dragElement) { + var validDrops = []; + + if (scope.dynamicDrop) { + setActiveDrops(interaction, dragElement); + } + + // collect all dropzones and their elements which qualify for a drop + for (var j = 0; j < interaction.activeDrops.dropzones.length; j++) { + var current = interaction.activeDrops.dropzones[j], + currentElement = interaction.activeDrops.elements [j], + rect = interaction.activeDrops.rects [j]; + + validDrops.push(current.dropCheck(interaction.pointers[0], event, interaction.target, dragElement, currentElement, rect) + ? currentElement + : null); + } + + // get the most appropriate dropzone based on DOM depth and order + var dropIndex = utils.indexOfDeepestElement(validDrops), + dropzone = interaction.activeDrops.dropzones[dropIndex] || null, + element = interaction.activeDrops.elements [dropIndex] || null; + + return { + dropzone: dropzone, + element: element + }; +} + +function getDropEvents (interaction, pointerEvent, dragEvent) { + var dropEvents = { + enter : null, + leave : null, + activate : null, + deactivate: null, + move : null, + drop : null + }; + + if (interaction.dropElement !== interaction.prevDropElement) { + // if there was a prevDropTarget, create a dragleave event + if (interaction.prevDropTarget) { + dropEvents.leave = { + target : interaction.prevDropElement, + dropzone : interaction.prevDropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dragleave' + }; + + dragEvent.dragLeave = interaction.prevDropElement; + dragEvent.prevDropzone = interaction.prevDropTarget; + } + // if the dropTarget is not null, create a dragenter event + if (interaction.dropTarget) { + dropEvents.enter = { + target : interaction.dropElement, + dropzone : interaction.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dragenter' + }; + + dragEvent.dragEnter = interaction.dropElement; + dragEvent.dropzone = interaction.dropTarget; + } + } + + if (dragEvent.type === 'dragend' && interaction.dropTarget) { + dropEvents.drop = { + target : interaction.dropElement, + dropzone : interaction.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'drop' + }; + + dragEvent.dropzone = interaction.dropTarget; + } + if (dragEvent.type === 'dragstart') { + dropEvents.activate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dropactivate' + }; + } + if (dragEvent.type === 'dragend') { + dropEvents.deactivate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dropdeactivate' + }; + } + if (dragEvent.type === 'dragmove' && interaction.dropTarget) { + dropEvents.move = { + target : interaction.dropElement, + dropzone : interaction.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + dragmove : dragEvent, + timeStamp : dragEvent.timeStamp, + type : 'dropmove' + }; + dragEvent.dropzone = interaction.dropTarget; + } + + return dropEvents; +} + +/*\ + * Interactable.dropzone + [ method ] + * + * Returns or sets whether elements can be dropped onto this + * Interactable to trigger drop events + * + * Dropzones can receive the following events: + * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends + * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone + * - `dragmove` when a draggable that has entered the dropzone is moved + * - `drop` when a draggable is dropped into this dropzone + * + * Use the `accept` option to allow only elements that match the given CSS selector or element. + * + * Use the `overlap` option to set how drops are checked for. The allowed values are: + * - `'pointer'`, the pointer must be over the dropzone (default) + * - `'center'`, the draggable element's center must be over the dropzone + * - a number from 0-1 which is the `(intersection area) / (draggable area)`. + * e.g. `0.5` for drop to happen when half of the area of the + * draggable is over the dropzone + * + - options (boolean | object | null) #optional The new value to be set. + | interact('.drop').dropzone({ + | accept: '.can-drop' || document.getElementById('single-drop'), + | overlap: 'pointer' || 'center' || zeroToOne + | } + = (boolean | object) The current setting or this Interactable +\*/ +Interactable.prototype.dropzone = function (options) { + if (utils.isObject(options)) { + this.options.drop.enabled = options.enabled === false? false: true; + this.setOnEvents('drop', options); + this.accept(options.accept); + + if (/^(pointer|center)$/.test(options.overlap)) { + this.options.drop.overlap = options.overlap; + } + else if (utils.isNumber(options.overlap)) { + this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); + } + + return this; + } + + if (utils.isBool(options)) { + this.options.drop.enabled = options; + + return this; + } + + return this.options.drop; +}; + +Interactable.prototype.dropCheck = function (pointer, event, draggable, draggableElement, dropElement, rect) { + var dropped = false; + + // if the dropzone has no rect (eg. display: none) + // call the custom dropChecker or just return false + if (!(rect = rect || this.getRect(dropElement))) { + return (this.options.dropChecker + ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) + : false); + } + + var dropOverlap = this.options.drop.overlap; + + if (dropOverlap === 'pointer') { + var page = utils.getPageXY(pointer), + origin = scope.getOriginXY(draggable, draggableElement), + horizontal, + vertical; + + page.x += origin.x; + page.y += origin.y; + + horizontal = (page.x > rect.left) && (page.x < rect.right); + vertical = (page.y > rect.top ) && (page.y < rect.bottom); + + dropped = horizontal && vertical; + } + + var dragRect = draggable.getRect(draggableElement); + + if (dropOverlap === 'center') { + var cx = dragRect.left + dragRect.width / 2, + cy = dragRect.top + dragRect.height / 2; + + dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; + } + + if (utils.isNumber(dropOverlap)) { + var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) + * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), + overlapRatio = overlapArea / (dragRect.width * dragRect.height); + + dropped = overlapRatio >= dropOverlap; + } + + if (this.options.dropChecker) { + dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); + } + + return dropped; +}; + +/*\ + * Interactable.dropChecker + [ method ] + * + * Gets or sets the function used to check if a dragged element is + * over this Interactable. + * + - checker (function) #optional The function that will be called when checking for a drop + = (Function | Interactable) The checker function or this Interactable + * + * The checker function takes the following arguments: + * + - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag + - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer + - dropped (boolean) The value from the default drop check + - dropzone (Interactable) The dropzone interactable + - dropElement (Element) The dropzone element + - draggable (Interactable) The Interactable being dragged + - draggableElement (Element) The actual element that's being dragged + * + > Usage: + | interact(target) + | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent + | event, // TouchEvent/PointerEvent/MouseEvent + | dropped, // result of the default checker + | dropzone, // dropzone Interactable + | dropElement, // dropzone elemnt + | draggable, // draggable Interactable + | draggableElement) {// draggable element + | + | return dropped && event.target.hasAttribute('allow-drop'); + | } +\*/ +Interactable.prototype.dropChecker = function (checker) { + if (utils.isFunction(checker)) { + this.options.dropChecker = checker; + + return this; + } + if (checker === null) { + delete this.options.getRect; + + return this; + } + + return this.options.dropChecker; +}; + +/*\ + * Interactable.accept + [ method ] + * + * Deprecated. add an `accept` property to the options object passed to + * @Interactable.dropzone instead. + * + * Gets or sets the Element or CSS selector match that this + * Interactable accepts if it is a dropzone. + * + - newValue (Element | string | null) #optional + * If it is an Element, then only that element can be dropped into this dropzone. + * If it is a string, the element being dragged must match it as a selector. + * If it is null, the accept options is cleared - it accepts any element. + * + = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable +\*/ +Interactable.prototype.accept = function (newValue) { + if (utils.isElement(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + // test if it is a valid CSS selector + if (utils.trySelector(newValue)) { + this.options.drop.accept = newValue; + + return this; + } + + if (newValue === null) { + delete this.options.drop.accept; + + return this; + } + + return this.options.drop.accept; +}; + +base.addEventTypes([ + 'dragenter', + 'dragleave', + 'dropactivate', + 'dropdeactivate', + 'dropmove', + 'drop' +]); +base.methodDict.drop = 'dropzone'; +module.exports = drop; From 2729c1376bf54ed9529c71c39cc864db5e9690a1 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 21 Jul 2015 23:44:59 +0200 Subject: [PATCH 070/131] Do general modifier endOnly movement --- src/Interaction.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 47550629f..e110b99a5 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -814,12 +814,13 @@ Interaction.prototype = { return; } - var endSnap = modifiers.snap.shouldDo(target, this.prepared.name, true, true), - endRestrict = modifiers.restrict.shouldDo(target, this.prepared.name, true, true); - - if (endSnap || endRestrict) { - // fire a move event at the snapped coordinates - this.pointerMove(pointer, event, eventTarget, curEventTarget, true); + for (var i = 0; i < modifiers.names.length; i++) { + // if the endOnly option is true for any modifier + if (modifiers[modifiers.names[i]].shouldDo(target, this.prepared.name, true, true)) { + // fire a move event at the snapped coordinates + this.pointerMove(pointer, event, eventTarget, curEventTarget, true); + break; + } } } From 9a4a1501d4a5536cf35ffaea911ecaf7fc1c71f3 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Thu, 23 Jul 2015 23:36:56 +0200 Subject: [PATCH 071/131] Improve smoothEnd check --- src/Interaction.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Interaction.js b/src/Interaction.js index e110b99a5..bdbda86bc 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -762,7 +762,7 @@ Interaction.prototype = { if (inertiaPossible && !inertia) { modifiers.resetStatuses(statuses); - modifierResult = modifiers.setAll(this, page, statuses, true, true); + modifierResult = modifiers.setAll(this, page, statuses, true); if (modifierResult.shouldMove && modifierResult.locked) { smoothEnd = true; From c2431451927826e0239effb58a4095bfb546f6da Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 1 Aug 2015 21:58:11 +0200 Subject: [PATCH 072/131] Add interact.createSnapGrid in actions/snap.js --- src/Interactable.js | 5 +-- src/Interaction.js | 60 +++++++----------------------------- src/modifiers/index.js | 11 +++++++ src/modifiers/restrict.js | 21 ++++++++++++- src/modifiers/snap.js | 64 +++++++++++++++++++++++++++++++++++---- 5 files changed, 103 insertions(+), 58 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index c4df25ca9..e8f7eb9f0 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -2,6 +2,7 @@ var scope = require('./scope'), utils = require('./utils'), + browser = require('./utils/browser'), events = require('./utils/events'), actions = require('./actions/base'); @@ -37,8 +38,8 @@ function Interactable (element, options) { if (utils.isElement(element, _window)) { if (scope.PointerEvent) { - events.add(this._element, scope.pEventTypes.down, scope.listeners.pointerDown ); - events.add(this._element, scope.pEventTypes.move, scope.listeners.pointerHover); + events.add(this._element, browser.pEventTypes.down, scope.listeners.pointerDown ); + events.add(this._element, browser.pEventTypes.move, scope.listeners.pointerHover); } else { events.add(this._element, 'mousedown' , scope.listeners.pointerDown ); diff --git a/src/Interaction.js b/src/Interaction.js index bdbda86bc..f2dcc45fe 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -109,9 +109,9 @@ function Interaction () { this.tapTime = 0; // time of the most recent tap event this.prevTap = null; - this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.snapOffsets = []; + this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.modifierOffsets = {}; + this.modifierStatuses = modifiers.resetStatuses({}); this.gesture = { start: { x: 0, y: 0 }, @@ -126,8 +126,6 @@ function Interaction () { prevAngle : 0 // angle of the previous gesture event }; - this.modifierStatuses = modifiers.resetStatuses({}); - this.pointerIsDown = false; this.pointerWasMoved = false; this._interacting = false; @@ -235,14 +233,14 @@ Interaction.prototype = { this.pointerHover(pointer, event, this.matches, this.matchElements); events.add(eventTarget, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.PointerEvent? browser.pEventTypes.move : 'mousemove', scope.listeners.pointerHover); } else if (this.target) { if (utils.nodeContains(prevTargetElement, eventTarget)) { this.pointerHover(pointer, event, this.matches, this.matchElements); events.add(this.element, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.PointerEvent? browser.pEventTypes.move : 'mousemove', scope.listeners.pointerHover); } else { @@ -294,7 +292,7 @@ Interaction.prototype = { // Remove temporary event listeners for selector Interactables if (!scope.interactables.get(eventTarget)) { events.remove(eventTarget, - scope.PointerEvent? scope.pEventTypes.move : 'mousemove', + scope.PointerEvent? browser.pEventTypes.move : 'mousemove', scope.listeners.pointerHover); } @@ -475,11 +473,7 @@ Interaction.prototype = { }, setStartOffsets: function (action, interactable, element) { - var rect = interactable.getRect(element), - origin = scope.getOriginXY(interactable, element), - snap = interactable.options[this.prepared.name].snap, - restrict = interactable.options[this.prepared.name].restrict, - width, height; + var rect = interactable.getRect(element); if (rect) { this.startOffset.left = this.startCoords.page.x - rect.left; @@ -488,46 +482,14 @@ Interaction.prototype = { this.startOffset.right = rect.right - this.startCoords.page.x; this.startOffset.bottom = rect.bottom - this.startCoords.page.y; - if ('width' in rect) { width = rect.width; } - else { width = rect.right - rect.left; } - if ('height' in rect) { height = rect.height; } - else { height = rect.bottom - rect.top; } + if (!('width' in rect)) { rect.width = rect.right - rect.left; } + if (!('height' in rect)) { rect.height = rect.bottom - rect.top ; } } else { this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; } - this.snapOffsets.splice(0); - - var snapOffset = snap && snap.offset === 'startCoords' - ? { - x: this.startCoords.page.x - origin.x, - y: this.startCoords.page.y - origin.y - } - : snap && snap.offset || { x: 0, y: 0 }; - - if (rect && snap && snap.relativePoints && snap.relativePoints.length) { - for (var i = 0; i < snap.relativePoints.length; i++) { - this.snapOffsets.push({ - x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x, - y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y - }); - } - } - else { - this.snapOffsets.push(snapOffset); - } - - if (rect && restrict.elementRect) { - this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left); - this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top); - - this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right)); - this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom)); - } - else { - this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0; - } + modifiers.setOffsets(this, interactable, element, rect, this.modifierOffsets); }, /*\ @@ -582,7 +544,7 @@ Interaction.prototype = { this.element = element; this.setEventXY(this.startCoords); - this.setStartOffsets(action.name, interactable, element); + this.setStartOffsets(action.name, interactable, element, this.modifierOffsets); modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); diff --git a/src/modifiers/index.js b/src/modifiers/index.js index 03fe198d6..4a8fe5c2a 100644 --- a/src/modifiers/index.js +++ b/src/modifiers/index.js @@ -5,6 +5,17 @@ var utils = require('../utils'); var modifiers = { names: [], + setOffsets: function (interaction, interactable, element, rect, offsets) { + for (var i = 0; i < modifiers.names.length; i++) { + var modifierName = modifiers.names[i]; + + offsets[modifierName] = + modifiers[modifiers.names[i]].setOffset(interaction, + interactable, element, rect, + interaction.startOffset); + } + }, + setAll: function (interaction, coords, statuses, preEnd, requireEndOnly) { var result = { dx: 0, diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index fbb1a96e1..ac95f40d3 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -15,6 +15,25 @@ var restrict = { return restrict && restrict.enabled && (preEnd || !restrict.endOnly) && (!requireEndOnly || restrict.endOnly); }, + + setOffset: function (interaction, interactable, element, rect, startOffset) { + var elementRect = interactable.options[interaction.prepared.name].restrict.elementRect, + offset = {}; + + if (rect && elementRect) { + offset.left = startOffset.left - (rect.width * elementRect.left); + offset.top = startOffset.top - (rect.height * elementRect.top); + + offset.right = startOffset.right - (rect.width * (1 - elementRect.right)); + offset.bottom = startOffset.bottom - (rect.height * (1 - elementRect.bottom)); + } + else { + offset.left = offset.top = offset.right = offset.bottom = 0; + } + + return offset; + }, + set: function (pageCoords, interaction, status) { var target = interaction.target, restrict = target && target.options[interaction.prepared.name].restrict, @@ -62,7 +81,7 @@ var restrict = { rect = restriction; - var offset = interaction.restrictOffset; + var offset = interaction.modifierOffsets.restrict; if (!restriction) { restrictedX = page.x; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index 058ba7819..543e8aa99 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -2,6 +2,7 @@ var modifiers = require('./index'), scope = require('../scope'), + interact = require('../interact'), utils = require('../utils'); //defaultOptions = require('../defaultOptions'); @@ -20,6 +21,32 @@ var snap = { return snap && snap.enabled && (preEnd || !snap.endOnly) && (!requireEndOnly || snap.endOnly); }, + + setOffset: function (interaction, interactable, element, rect, startOffset) { + var offsets = [], + origin = scope.getOriginXY(interactable, element), + snapOffset = (snap && snap.offset === 'startCoords' + ? { + x: interaction.startCoords.page.x - origin.x, + y: interaction.startCoords.page.y - origin.y + } + : snap && snap.offset || { x: 0, y: 0 }); + + if (rect && snap && snap.relativePoints && snap.relativePoints.length) { + for (var i = 0; i < snap.relativePoints.length; i++) { + offsets.push({ + x: startOffset.left - (rect.width * snap.relativePoints[i].x) + snapOffset.x, + y: startOffset.top - (rect.height * snap.relativePoints[i].y) + snapOffset.y + }); + } + } + else { + offsets.push(snapOffset); + } + + return offsets; + }, + set: function (pageCoords, interaction, status) { var snap = interaction.target.options[interaction.prepared.name].snap, targets = [], @@ -45,12 +72,13 @@ var snap = { page.x -= interaction.inertiaStatus.resumeDx; page.y -= interaction.inertiaStatus.resumeDy; - var len = snap.targets? snap.targets.length : 0; + var len = snap.targets? snap.targets.length : 0, + offsets = interaction.modifierOffsets.snap; - for (var relIndex = 0; relIndex < interaction.snapOffsets.length; relIndex++) { + for (var relIndex = 0; relIndex < offsets.length; relIndex++) { var relative = { - x: page.x - interaction.snapOffsets[relIndex].x, - y: page.y - interaction.snapOffsets[relIndex].y + x: page.x - offsets[relIndex].x, + y: page.y - offsets[relIndex].y }; for (i = 0; i < len; i++) { @@ -64,8 +92,8 @@ var snap = { if (!target) { continue; } targets.push({ - x: utils.isNumber(target.x) ? (target.x + interaction.snapOffsets[relIndex].x) : relative.x, - y: utils.isNumber(target.y) ? (target.y + interaction.snapOffsets[relIndex].y) : relative.y, + x: utils.isNumber(target.x) ? (target.x + offsets[relIndex].x) : relative.x, + y: utils.isNumber(target.y) ? (target.y + offsets[relIndex].y) : relative.y, range: utils.isNumber(target.range)? target.range: snap.range }); @@ -187,4 +215,28 @@ var snap = { modifiers.snap = snap; modifiers.names.push('snap'); +interact.createSnapGrid = function (grid) { + return function (x, y) { + var offsetX = 0, + offsetY = 0; + + if (utils.isObject(grid.offset)) { + offsetX = grid.offset.x; + offsetY = grid.offset.y; + } + + var gridx = Math.round((x - offsetX) / grid.x), + gridy = Math.round((y - offsetY) / grid.y), + + newX = gridx * grid.x + offsetX, + newY = gridy * grid.y + offsetY; + + return { + x: newX, + y: newY, + range: grid.range + }; + }; +}; + module.exports = snap; From 2180c678ff37211a70bf0d0aedf2e4a47744a613 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 1 Aug 2015 21:59:34 +0200 Subject: [PATCH 073/131] Move pEventTypes from scope to utils/browser --- src/interact.js | 52 ++++++++------------------------------------ src/scope.js | 2 -- src/utils/browser.js | 13 ++++++++++- 3 files changed, 21 insertions(+), 46 deletions(-) diff --git a/src/interact.js b/src/interact.js index 1d3e1e079..0371a69a6 100644 --- a/src/interact.js +++ b/src/interact.js @@ -466,30 +466,6 @@ return scope.maxInteractions; }; - interact.createSnapGrid = function (grid) { - return function (x, y) { - var offsetX = 0, - offsetY = 0; - - if (utils.isObject(grid.offset)) { - offsetX = grid.offset.x; - offsetY = grid.offset.y; - } - - var gridx = Math.round((x - offsetX) / grid.x), - gridy = Math.round((y - offsetY) / grid.y), - - newX = gridx * grid.x + offsetX, - newY = gridy * grid.y + offsetY; - - return { - x: newX, - y: newY, - range: grid.range - }; - }; - }; - function endAllInteractions (event) { for (var i = 0; i < scope.interactions.length; i++) { scope.interactions[i].pointerEnd(event, event); @@ -499,7 +475,8 @@ function listenToDocument (doc) { if (utils.contains(scope.documents, doc)) { return; } - var win = doc.defaultView || doc.parentWindow; + var win = doc.defaultView || doc.parentWindow, + pEventTypes = browser.pEventTypes; // add delegate event listener for (var eventType in scope.delegatedEvents) { @@ -508,26 +485,15 @@ } if (scope.PointerEvent) { - if (scope.PointerEvent === win.MSPointerEvent) { - scope.pEventTypes = { - up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', - out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' }; - } - else { - scope.pEventTypes = { - up: 'pointerup', down: 'pointerdown', over: 'pointerover', - out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }; - } - - events.add(doc, scope.pEventTypes.down , scope.listeners.selectorDown ); - events.add(doc, scope.pEventTypes.move , scope.listeners.pointerMove ); - events.add(doc, scope.pEventTypes.over , scope.listeners.pointerOver ); - events.add(doc, scope.pEventTypes.out , scope.listeners.pointerOut ); - events.add(doc, scope.pEventTypes.up , scope.listeners.pointerUp ); - events.add(doc, scope.pEventTypes.cancel, scope.listeners.pointerCancel); + events.add(doc, pEventTypes.down , scope.listeners.selectorDown ); + events.add(doc, pEventTypes.move , scope.listeners.pointerMove ); + events.add(doc, pEventTypes.over , scope.listeners.pointerOver ); + events.add(doc, pEventTypes.out , scope.listeners.pointerOut ); + events.add(doc, pEventTypes.up , scope.listeners.pointerUp ); + events.add(doc, pEventTypes.cancel, scope.listeners.pointerCancel); // autoscroll - events.add(doc, scope.pEventTypes.move, scope.listeners.autoScrollMove); + events.add(doc, pEventTypes.move, scope.listeners.autoScrollMove); } else { events.add(doc, 'mousedown', scope.listeners.selectorDown); diff --git a/src/scope.js b/src/scope.js index 1f975543a..a8d8cbba9 100644 --- a/src/scope.js +++ b/src/scope.js @@ -3,8 +3,6 @@ var scope = {}, extend = require('./utils/extend'); -scope.pEventTypes = null; - scope.documents = []; // all documents being listened to scope.interactables = []; // all set interactables diff --git a/src/utils/browser.js b/src/utils/browser.js index 6b2b28bd9..9cbb19207 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -30,7 +30,18 @@ var browser = { 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? 'oMatchesSelector': 'msMatchesSelector', - useMatchesSelectorPolyfill: false + useMatchesSelectorPolyfill: false, + + pEventTypes: (domObjects.PointerEvent + ? (domObjects.PointerEvent === win.window.MSPointerEvent + ? { + up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', + out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' } + : { + up: 'pointerup', down: 'pointerdown', over: 'pointerover', + out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }) + : null) + }; browser.useMatchesSelectorPolyfill = !isType.isFunction(Element.prototype[browser.prefixedMatchesSelector]); From db479687f788c1aa7c9e054586a4446f94c1bd4e Mon Sep 17 00:00:00 2001 From: Steffen Baer Date: Wed, 5 Aug 2015 21:52:55 +0800 Subject: [PATCH 074/131] Removed duplicated strict in .jshintrc; fixed jshint error in events.js --- .jshintrc | 1 - src/utils/events.js | 1 - 2 files changed, 2 deletions(-) diff --git a/.jshintrc b/.jshintrc index 2019b7dc6..64ae0ee0e 100644 --- a/.jshintrc +++ b/.jshintrc @@ -10,6 +10,5 @@ "noarg" : true, "undef" : true, "unused" : true, - "strict" : true, "trailing" : true } diff --git a/src/utils/events.js b/src/utils/events.js index 7c8b78eea..32fbbc32b 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -2,7 +2,6 @@ var arr = require('./arr'), isType = require('./isType'), - domObjects = require('./domObjects'), domUtils = require('./domUtils'), indexOf = arr.indexOf, contains = arr.contains, From 38090eef9afa31edd671d3980c915e5a7116af84 Mon Sep 17 00:00:00 2001 From: Steffen Baer Date: Fri, 7 Aug 2015 16:14:38 +0800 Subject: [PATCH 075/131] updated dependencies --- package.json | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index a8b0b28a5..b6134ab15 100644 --- a/package.json +++ b/package.json @@ -43,34 +43,34 @@ ] }, "devDependencies": { - "browser-sync": "^2.7.4", - "browserify": "^10.2.1", - "chai": "^2.3.0", - "gulp": "^3.8.11", - "gulp-changed": "^1.2.1", + "browser-sync": "^2.8.2", + "browserify": "^11.0.1", + "chai": "^3.2.0", + "gulp": "^3.9.0", + "gulp-changed": "^1.3.0", "gulp-filesize": "0.0.6", - "gulp-jshint": "^1.11.0", + "gulp-jshint": "^1.11.2", "gulp-notify": "^2.2.0", "gulp-rename": "^1.2.2", "gulp-sourcemaps": "^1.5.2", "gulp-uglify": "^1.2.0", - "gulp-util": "^3.0.4", - "jshint-stylish": "^2.0.0", - "karma": "^0.12.32", - "karma-browserify": "^4.2.1", + "gulp-util": "^3.0.6", + "jshint-stylish": "^2.0.1", + "karma": "^0.13.8", + "karma-browserify": "^4.3.0", "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^0.1.12", - "karma-fixture": "^0.2.4", + "karma-chrome-launcher": "^0.2.0", + "karma-fixture": "^0.2.5", "karma-html2js-preprocessor": "^0.1.0", - "karma-mocha": "^0.1.10", - "karma-nyan-reporter": "0.0.60", - "lodash": "^3.9.2", - "merge-stream": "^0.1.7", + "karma-mocha": "^0.2.0", + "karma-nyan-reporter": "0.2.1", + "lodash": "^3.10.1", + "merge-stream": "^0.1.8", "mocha": "^2.2.5", "pretty-hrtime": "^1.0.0", "require-dir": "^0.3.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", - "watchify": "^3.2.1" + "watchify": "^3.3.1" } } From 4e211aa3c1712384da3e10c73ed047b2c49499ad Mon Sep 17 00:00:00 2001 From: Steffen Baer Date: Fri, 7 Aug 2015 16:24:44 +0800 Subject: [PATCH 076/131] fixed deprecated karma.server in karma.js --- gulp/tasks/karma.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gulp/tasks/karma.js b/gulp/tasks/karma.js index 505bc5859..1daef89e6 100644 --- a/gulp/tasks/karma.js +++ b/gulp/tasks/karma.js @@ -2,17 +2,17 @@ var gulp = require('gulp'); var karma = require('karma'); var karmaTask = function(done) { - karma.server.start({ + new karma.Server({ configFile: process.cwd() + '/karma.conf.js', singleRun: true - }, done); + }, done).start(); }; var karmaContinuosTask = function(done) { - karma.server.start({ - configFile: process.cwd() + '/karma.conf.js', - action: 'watch' - }, done); + new karma.Server({ + configFile: process.cwd() + '/karma.conf.js', + action: 'watch' + }, done).start(); }; From 87600d665270c2b71ca7f562884923d7471cc7ab Mon Sep 17 00:00:00 2001 From: Steffen Baer Date: Fri, 7 Aug 2015 17:15:06 +0800 Subject: [PATCH 077/131] some fixes for test.js --- test/test.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/test.js b/test/test.js index bb9080210..f8bd040bd 100644 --- a/test/test.js +++ b/test/test.js @@ -166,13 +166,13 @@ describe('Interactable', function () { iDiv.actionChecker(returnActionI); for (i = 0; action = actions[i], i < actions.length; i++) { - debug.pointerDown.call(div, mockEvent({ + debug.listeners.pointerDown.call(div, mockEvent({ target: div, pointerId: 1 })); if (PointerEvent && action === 'gesture') { - debug.pointerDown.call(div, mockEvent({ + debug.listeners.pointerDown.call(div, mockEvent({ target: div, pointerId: 2 })); @@ -210,10 +210,10 @@ describe('Events', function () { return 'drag'; }); - debug.pointerDown(mockEvents[0]); - debug.pointerMove(mockEvents[1]); - debug.pointerMove(mockEvents[2]); - debug.pointerUp(mockEvents[3]); + debug.listeners.pointerDown(mockEvents[0]); + debug.listeners.pointerMove(mockEvents[1]); + debug.listeners.pointerMove(mockEvents[2]); + debug.listeners.pointerUp(mockEvents[3]); it('should be triggered by mousedown -> mousemove -> mouseup sequence', function () { events.length.should.equal(4); @@ -309,19 +309,19 @@ describe('Events', function () { // don't call the related functions. The recorded pointermove events // are used to calculate gesture angle, scale, etc. - debug.pointerDown(mockEvents[0]); - debug.pointerDown(mockEvents[1]); + debug.listeners.pointerDown(mockEvents[0]); + debug.listeners.pointerDown(mockEvents[1]); debugRecord = PointerEvent && debug.recordPointer || debug.recordTouches || debug.recordPointer; - debugRecord(mockEvents[2]); - debug.pointerMove(mockEvents[2]); + debugRecord && debugRecord(mockEvents[2]); + debug.listeners.pointerMove(mockEvents[2]); - debugRecord(mockEvents[3]); - debug.pointerMove(mockEvents[3]); + debugRecord && debugRecord(mockEvents[3]); + debug.listeners.pointerMove(mockEvents[3]); - debug.pointerUp(mockEvents[4]); - debug.pointerUp(mockEvents[5]); + debug.listeners.pointerUp(mockEvents[4]); + debug.listeners.pointerUp(mockEvents[5]); it('should be started by 2 touches starting and moving and end when there are fewer than two active touches', function () { gestureEvents.length.should.equal(4); From 232f34e0109292ca639c7b1b7bdddcad929efd29 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 29 Aug 2015 14:50:06 +0200 Subject: [PATCH 078/131] utils/signals: add simple signals module signals.on('some-signal', function (arg) { }); signals.fire('some-signal', { prop: value }); --- src/utils/signals.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/utils/signals.js diff --git a/src/utils/signals.js b/src/utils/signals.js new file mode 100644 index 000000000..c91e03d86 --- /dev/null +++ b/src/utils/signals.js @@ -0,0 +1,39 @@ +'use strict'; + +var listeners = { + // signalName: [listeners], +}; + +var arr = require('./arr'); + +var signals = { + on: function (name, listener) { + if (!listeners[name]) { + listeners[name] = [listener]; + return; + } + + listeners[name].push(listener); + }, + off: function (name, listener) { + if (!listeners[name]) { return; } + + var index = arr.indexOf(listeners[name], listener); + + if (index !== -1) { + listeners[name].splice(index, 1); + } + }, + fire: function (name, arg) { + var targetListeners = listeners[name]; + + if (!targetListeners) { return; } + + for (var i = 0; i < targetListeners.length; i++) { + targetListeners[i](arg); + } + }, + listeners: listeners +}; + +module.exports = signals; From 88050adc58f8f71d6b96e3aa4054f09dd5cf1035 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 29 Aug 2015 16:02:05 +0200 Subject: [PATCH 079/131] decouple autoScroll from Interaction using signals Fired 'interaction-stop-active', 'interaction-move-done' and 'listen-to-document' signals in places where code that should have belonged in src/autoScroll.js was being called. Then moved the code and listened to those signals in the autoScroll module. --- src/Interaction.js | 57 +++++++-------------------------------- src/autoScroll.js | 66 +++++++++++++++++++++++++++++++++++++++++++--- src/interact.js | 22 +--------------- 3 files changed, 73 insertions(+), 72 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index f2dcc45fe..89ed5b052 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -5,6 +5,7 @@ var scope = require('./scope'), animationFrame = utils.raf, InteractEvent = require('./InteractEvent'), events = require('./utils/events'), + signals = require('./utils/signals'), browser = require('./utils/browser'), actions = require('./actions/base'), modifiers = require('./modifiers/'); @@ -632,7 +633,11 @@ Interaction.prototype = { utils.copyCoords(this.prevCoords, this.curCoords); - this.autoScrollMove(pointer); + signals.fire('interaction-move-done', { + interaction: this, + pointer: pointer, + event: event + }); }, pointerHold: function (pointer, event, eventTarget) { @@ -808,8 +813,9 @@ Interaction.prototype = { }, stop: function (event) { - if (this.interacting()) { - scope.autoScroll.stop(); + if (this._interacting) { + signals.fire('interaction-stop-active', { interaction: this }); + this.matches = []; this.matchElements = []; @@ -1149,51 +1155,6 @@ Interaction.prototype = { status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; }, - autoScrollMove: function (pointer) { - if (!(this.interacting() - && scope.checkAutoScroll(this.target, this.prepared.name))) { - return; - } - - if (this.inertiaStatus.active) { - scope.autoScroll.x = scope.autoScroll.y = 0; - return; - } - - var top, - right, - bottom, - left, - options = this.target.options[this.prepared.name].autoScroll, - container = options.container || scope.getWindow(this.element); - - if (utils.isWindow(container)) { - left = pointer.clientX < scope.autoScroll.margin; - top = pointer.clientY < scope.autoScroll.margin; - right = pointer.clientX > container.innerWidth - scope.autoScroll.margin; - bottom = pointer.clientY > container.innerHeight - scope.autoScroll.margin; - } - else { - var rect = utils.getElementClientRect(container); - - left = pointer.clientX < rect.left + scope.autoScroll.margin; - top = pointer.clientY < rect.top + scope.autoScroll.margin; - right = pointer.clientX > rect.right - scope.autoScroll.margin; - bottom = pointer.clientY > rect.bottom - scope.autoScroll.margin; - } - - scope.autoScroll.x = (right ? 1: left? -1: 0); - scope.autoScroll.y = (bottom? 1: top? -1: 0); - - if (!scope.autoScroll.isScrolling) { - // set the autoScroll properties to those of the target - scope.autoScroll.margin = options.margin; - scope.autoScroll.speed = options.speed; - - scope.autoScroll.start(this); - } - }, - _updateEventTargets: function (target, currentTarget) { this._eventTarget = target; this._curEventTarget = currentTarget; diff --git a/src/autoScroll.js b/src/autoScroll.js index 44ae42e0d..87271fb23 100644 --- a/src/autoScroll.js +++ b/src/autoScroll.js @@ -1,8 +1,10 @@ 'use strict'; -var raf = require('./utils/raf'), - getWindow = require('./utils/window').getWindow, - isWindow = require('./utils/isType').isWindow; +var raf = require('./utils/raf'), + getWindow = require('./utils/window').getWindow, + isWindow = require('./utils/isType').isWindow, + domUtils = require('./utils/domUtils'), + signals = require('./utils/signals'); var autoScroll = { @@ -53,7 +55,65 @@ var autoScroll = { raf.cancel(autoScroll.i); autoScroll.i = raf.request(autoScroll.scroll); } + }, + check: function (interactable, actionName) { + var options = interactable.options; + + return options[actionName].autoScroll && options[actionName].autoScroll.enabled; + }, + onInteractionMove: function (arg) { + var interaction = arg.interaction, + pointer = arg.pointer; + + if (!(interaction.interacting() + && autoScroll.check(interaction.target, interaction.prepared.name))) { + return; + } + + if (interaction.inertiaStatus.active) { + autoScroll.x = autoScroll.y = 0; + return; + } + + var top, + right, + bottom, + left, + options = interaction.target.options[interaction.prepared.name].autoScroll, + container = options.container || getWindow(interaction.element); + + if (isWindow(container)) { + left = pointer.clientX < autoScroll.margin; + top = pointer.clientY < autoScroll.margin; + right = pointer.clientX > container.innerWidth - autoScroll.margin; + bottom = pointer.clientY > container.innerHeight - autoScroll.margin; + } + else { + var rect = domUtils.getElementClientRect(container); + + left = pointer.clientX < rect.left + autoScroll.margin; + top = pointer.clientY < rect.top + autoScroll.margin; + right = pointer.clientX > rect.right - autoScroll.margin; + bottom = pointer.clientY > rect.bottom - autoScroll.margin; + } + + autoScroll.x = (right ? 1: left? -1: 0); + autoScroll.y = (bottom? 1: top? -1: 0); + + if (!autoScroll.isScrolling) { + // set the autoScroll properties to those of the target + autoScroll.margin = options.margin; + autoScroll.speed = options.speed; + + autoScroll.start(interaction); + } } }; +signals.on('interaction-stop-active', function () { + autoScroll.stop(); +}); + +signals.on('interaction-move-done', autoScroll.onInteractionMove); + module.exports = autoScroll; diff --git a/src/interact.js b/src/interact.js index 0371a69a6..504806fc6 100644 --- a/src/interact.js +++ b/src/interact.js @@ -21,9 +21,6 @@ scope.dynamicDrop = false; - // Things related to autoScroll - scope.autoScroll = require('./autoScroll'); - // Less Precision with touch input scope.margin = browser.supportsTouch || browser.supportsPointerEvent? 20: 10; @@ -55,7 +52,7 @@ var interactionListeners = [ 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', - 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove' + 'addPointer', 'removePointer', 'recordPointer' ]; scope.getOriginXY = function (interactable, element) { @@ -132,16 +129,6 @@ return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); }; - scope.checkAutoScroll = function (interactable, action) { - var options = interactable.options; - - if (/^resize/.test(action)) { - action = 'resize'; - } - - return options[action].autoScroll && options[action].autoScroll.enabled; - }; - for (var i = 0, len = interactionListeners.length; i < len; i++) { var listenerName = interactionListeners[i]; @@ -491,9 +478,6 @@ events.add(doc, pEventTypes.out , scope.listeners.pointerOut ); events.add(doc, pEventTypes.up , scope.listeners.pointerUp ); events.add(doc, pEventTypes.cancel, scope.listeners.pointerCancel); - - // autoscroll - events.add(doc, pEventTypes.move, scope.listeners.autoScrollMove); } else { events.add(doc, 'mousedown', scope.listeners.selectorDown); @@ -506,10 +490,6 @@ events.add(doc, 'touchmove' , scope.listeners.pointerMove ); events.add(doc, 'touchend' , scope.listeners.pointerUp ); events.add(doc, 'touchcancel', scope.listeners.pointerCancel); - - // autoscroll - events.add(doc, 'mousemove', scope.listeners.autoScrollMove); - events.add(doc, 'touchmove', scope.listeners.autoScrollMove); } events.add(win, 'blur', endAllInteractions); From 54957d257cd63d9b52c756dd4fc8fca49d54a386 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 29 Aug 2015 22:51:06 +0200 Subject: [PATCH 080/131] Move addEventTypes from actions/base to scope --- src/actions/base.js | 6 ------ src/actions/drag.js | 2 +- src/actions/drop.js | 2 +- src/actions/gesture.js | 5 +++-- src/actions/resize.js | 2 +- src/interact.js | 10 ---------- src/scope.js | 8 ++++++++ 7 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/actions/base.js b/src/actions/base.js index 839713411..19488c5d5 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -5,12 +5,6 @@ var scope = require('../scope'); var actions = { scope: scope, - addEventTypes: function (eventTypes) { - for (var i = 0; i < eventTypes.length; i++) { - scope.eventTypes.push(eventTypes[i]); - } - }, - defaultChecker: function (pointer, event, interaction, element) { var rect = this.getRect(element), action = null; diff --git a/src/actions/drag.js b/src/actions/drag.js index b3e45a380..ac5d5deb9 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -185,7 +185,7 @@ Interactable.prototype.draggable = function (options) { base.drag = drag; base.names.push('drag'); -base.addEventTypes([ +scope.addEventTypes([ 'dragstart', 'dragmove', 'draginertiastart', diff --git a/src/actions/drop.js b/src/actions/drop.js index 96baefb46..cbfac7a07 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -470,7 +470,7 @@ Interactable.prototype.accept = function (newValue) { return this.options.drop.accept; }; -base.addEventTypes([ +scope.addEventTypes([ 'dragenter', 'dragleave', 'dropactivate', diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 86f98c49a..578c38df2 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -3,7 +3,8 @@ var base = require('./base'), utils = require('../utils'), InteractEvent = require('../InteractEvent'), - Interactable = require('../Interactable'); + Interactable = require('../Interactable'), + scope = base.scope; var gesture = { checker: function (pointer, event, interactable, element, interaction) { @@ -114,7 +115,7 @@ Interactable.prototype.gesturable = function (options) { base.gesture = gesture; base.names.push('gesture'); -base.addEventTypes([ +scope.addEventTypes([ 'gesturestart', 'gesturemove', 'gestureinertiastart', diff --git a/src/actions/resize.js b/src/actions/resize.js index 44bbd0970..5a5ea1006 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -341,7 +341,7 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, base.resize = resize; base.names.push('resize'); -base.addEventTypes([ +scope.addEventTypes([ 'resizestart', 'resizemove', 'resizeinertiastart', diff --git a/src/interact.js b/src/interact.js index 504806fc6..a56e10866 100644 --- a/src/interact.js +++ b/src/interact.js @@ -35,16 +35,6 @@ // because Webkit and Opera still use 'mousewheel' event type scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel'; - scope.eventTypes = [ - 'down', - 'move', - 'up', - 'cancel', - 'tap', - 'doubletap', - 'hold' - ]; - scope.globalEvents = {}; scope.listeners = {}; diff --git a/src/scope.js b/src/scope.js index a8d8cbba9..93bbc2cc5 100644 --- a/src/scope.js +++ b/src/scope.js @@ -15,6 +15,14 @@ scope.events = require('./utils/events'); extend(scope, require('./utils/window')); extend(scope, require('./utils/domObjects')); +scope.eventTypes = []; + +scope.addEventTypes = function (eventTypes) { + for (var i = 0; i < eventTypes.length; i++) { + scope.eventTypes.push(eventTypes[i]); + } +}; + scope.withinInteractionLimit = function (interactable, element, action) { var options = interactable.options, maxActions = options[action.name].max, From 273c2fc65e40e92040fdd9b49ac0a28c88ded71f Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 29 Aug 2015 23:06:10 +0200 Subject: [PATCH 081/131] utils/signals: pass signal name as second param signal.on('signal-name', function (arg, signal) { signal === 'signal-name'; // true; }); --- src/utils/signals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/signals.js b/src/utils/signals.js index c91e03d86..697617e69 100644 --- a/src/utils/signals.js +++ b/src/utils/signals.js @@ -30,7 +30,7 @@ var signals = { if (!targetListeners) { return; } for (var i = 0; i < targetListeners.length; i++) { - targetListeners[i](arg); + targetListeners[i](arg, name); } }, listeners: listeners From 225792b3342d9a5e01da2602cacb070493494e07 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 00:53:03 +0200 Subject: [PATCH 082/131] utils/browser: add isIE8 bool property --- src/interact.js | 2 +- src/utils/browser.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/interact.js b/src/interact.js index a56e10866..7574e1128 100644 --- a/src/interact.js +++ b/src/interact.js @@ -501,7 +501,7 @@ interact.windowParentError = error; } - if (events.useAttachEvent) { + if (browser.isIE8) { // For IE's lack of Event#preventDefault events.add(doc, 'selectstart', function (event) { var interaction = scope.interactions[0]; diff --git a/src/utils/browser.js b/src/utils/browser.js index 9cbb19207..85f64994f 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -12,6 +12,8 @@ var browser = { // Does the browser support PointerEvents supportsPointerEvent : !!domObjects.PointerEvent, + isIE8 : ('attachEvent' in win.window) && !('addEventListener' in win.window), + // Opera Mobile must be handled differently isOperaMobile : (navigator.appName === 'Opera' && browser.supportsTouch From a52178df7851424b7cd7a35af24237822a180e4a Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 03:15:37 +0200 Subject: [PATCH 083/131] Create pointerEvents module Required new signals: - interaction-move - interaction-down - interaction-up - interaction-cancel - listen-to-document --- src/Interaction.js | 208 ++++++------------------------------- src/interact.js | 9 +- src/pointerEvents.js | 240 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+), 178 deletions(-) create mode 100644 src/pointerEvents.js diff --git a/src/Interaction.js b/src/Interaction.js index 89ed5b052..bebe2b21b 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -147,10 +147,6 @@ function validateAction (action, interactable) { return null; } -function preventOriginalDefault () { - this.originalEvent.preventDefault(); -} - Interaction.prototype = { setEventXY: function (targetObj, pointer) { if (!pointer) { @@ -304,18 +300,20 @@ Interaction.prototype = { selectorDown: function (pointer, event, eventTarget, curEventTarget) { var that = this, - // copy event to be used in timeout for IE8 - eventCopy = events.useAttachEvent? utils.extend({}, event) : event, element = eventTarget, pointerIndex = this.addPointer(pointer), action; - this.holdTimers[pointerIndex] = setTimeout(function () { - that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget); - }, scope.defaultOptions._holdDuration); - this.pointerIsDown = true; + signals.fire('interaction-down', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget, + pointerIndex: pointerIndex + }); + // Check if the down event hits the current inertia target if (this.inertiaStatus.active && this.target.selector) { // climb up the DOM tree from the event target @@ -330,7 +328,6 @@ Interaction.prototype = { animationFrame.cancel(this.inertiaStatus.i); this.inertiaStatus.active = false; - this.collectEventTargets(pointer, event, eventTarget, 'down'); return; } element = utils.parentElement(element); @@ -339,7 +336,6 @@ Interaction.prototype = { // do nothing if interacting if (this.interacting()) { - this.collectEventTargets(pointer, event, eventTarget, 'down'); return; } @@ -377,8 +373,6 @@ Interaction.prototype = { this.prepared.axis = action.axis; this.prepared.edges = action.edges; - this.collectEventTargets(pointer, event, eventTarget, 'down'); - return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); } else { @@ -390,8 +384,6 @@ Interaction.prototype = { utils.copyCoords(this.prevCoords, this.curCoords); this.pointerWasMoved = false; } - - this.collectEventTargets(pointer, event, eventTarget, 'down'); }, // Determine action to be performed on next pointerMove and add appropriate @@ -564,8 +556,7 @@ Interaction.prototype = { && this.curCoords.client.x === this.prevCoords.client.x && this.curCoords.client.y === this.prevCoords.client.y); - var dx, dy, - pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); + var dx, dy; // register movement greater than pointerMoveTolerance if (this.pointerIsDown && !this.pointerWasMoved) { @@ -575,13 +566,15 @@ Interaction.prototype = { this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; } - if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) { - if (this.pointerIsDown) { - clearTimeout(this.holdTimers[pointerIndex]); - } - - this.collectEventTargets(pointer, event, eventTarget, 'move'); - } + signals.fire('interaction-move', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget, + duplicate: duplicateMove, + dx: dx, + dy: dy + }); if (!this.pointerIsDown) { return; } @@ -640,17 +633,19 @@ Interaction.prototype = { }); }, - pointerHold: function (pointer, event, eventTarget) { - this.collectEventTargets(pointer, event, eventTarget, 'hold'); - }, - pointerUp: function (pointer, event, eventTarget, curEventTarget) { var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); - this.collectEventTargets(pointer, event, eventTarget, 'up' ); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); + signals.fire('interaction-up', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget, + curEventTarget: curEventTarget + }); + this.pointerEnd(pointer, event, eventTarget, curEventTarget); @@ -662,29 +657,18 @@ Interaction.prototype = { clearTimeout(this.holdTimers[pointerIndex]); - this.collectEventTargets(pointer, event, eventTarget, 'cancel'); + signals.fire('interaction-cancel', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget + }); + this.pointerEnd(pointer, event, eventTarget, curEventTarget); this.removePointer(pointer); }, - // http://www.quirksmode.org/dom/events/click.html - // >Events leading to dblclick - // - // IE8 doesn't fire down event before dblclick. - // This workaround tries to fire a tap and doubletap after dblclick - ie8Dblclick: function (pointer, event, eventTarget) { - if (this.prevTap - && event.clientX === this.prevTap.clientX - && event.clientY === this.prevTap.clientY - && eventTarget === this.prevTap.target) { - - this.downTargets[0] = eventTarget; - this.downTimes[0] = new Date().getTime(); - this.collectEventTargets(pointer, event, eventTarget, 'tap'); - } - }, - // End interact move events and stop auto-scroll unless inertia is enabled pointerEnd: function (pointer, event, eventTarget, curEventTarget) { var target = this.target, @@ -964,132 +948,6 @@ Interaction.prototype = { this.pointers[index] = pointer; }, - collectEventTargets: function (pointer, event, eventTarget, eventType) { - var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); - - // do not fire a tap event if the pointer was moved before being lifted - if (eventType === 'tap' && (this.pointerWasMoved - // or if the pointerup target is different to the pointerdown target - || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) { - return; - } - - var targets = [], - elements = [], - element = eventTarget; - - function collectSelectors (interactable, selector, context) { - var els = browser.useMatchesSelectorPolyfill - ? context.querySelectorAll(selector) - : undefined; - - if (interactable._iEvents[eventType] - && utils.isElement(element) - && scope.inContext(interactable, element) - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && utils.matchesSelector(element, selector, els)) { - - targets.push(interactable); - elements.push(element); - } - } - - - var interact = scope.interact; - - while (element) { - if (interact.isSet(element) && interact(element)._iEvents[eventType]) { - targets.push(interact(element)); - elements.push(element); - } - - scope.interactables.forEachSelector(collectSelectors); - - element = utils.parentElement(element); - } - - // create the tap event even if there are no listeners so that - // doubletap can still be created and fired - if (targets.length || eventType === 'tap') { - this.firePointers(pointer, event, eventTarget, targets, elements, eventType); - } - }, - - firePointers: function (pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)), - pointerEvent = {}, - i, - // for tap events - interval, createNewDoubleTap; - - // if it's a doubletap then the event properties would have been - // copied from the tap event and provided as the pointer argument - if (eventType === 'doubletap') { - pointerEvent = pointer; - } - else { - utils.extend(pointerEvent, event); - if (event !== pointer) { - utils.extend(pointerEvent, pointer); - } - - pointerEvent.preventDefault = preventOriginalDefault; - pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; - pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; - pointerEvent.interaction = this; - - pointerEvent.timeStamp = new Date().getTime(); - pointerEvent.originalEvent = event; - pointerEvent.type = eventType; - pointerEvent.pointerId = utils.getPointerId(pointer); - pointerEvent.pointerType = this.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' - : utils.isString(pointer.pointerType) - ? pointer.pointerType - : [undefined, undefined,'touch', 'pen', 'mouse'][pointer.pointerType]; - } - - if (eventType === 'tap') { - pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex]; - - interval = pointerEvent.timeStamp - this.tapTime; - createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap' - && this.prevTap.target === pointerEvent.target - && interval < 500); - - pointerEvent.double = createNewDoubleTap; - - this.tapTime = pointerEvent.timeStamp; - } - - for (i = 0; i < targets.length; i++) { - pointerEvent.currentTarget = elements[i]; - pointerEvent.interactable = targets[i]; - targets[i].fire(pointerEvent); - - if (pointerEvent.immediatePropagationStopped - ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { - break; - } - } - - if (createNewDoubleTap) { - var doubleTap = {}; - - utils.extend(doubleTap, pointerEvent); - - doubleTap.dt = interval; - doubleTap.type = 'doubletap'; - - this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap'); - - this.prevTap = doubleTap; - } - else if (eventType === 'tap') { - this.prevTap = pointerEvent; - } - }, - validateSelector: function (pointer, event, matches, matchElements) { for (var i = 0, len = matches.length; i < len; i++) { var match = matches[i], diff --git a/src/interact.js b/src/interact.js index 7574e1128..47d7b1d30 100644 --- a/src/interact.js +++ b/src/interact.js @@ -15,6 +15,7 @@ utils = require('./utils'), browser = utils.browser, events = require('./utils/events'), + signals = require('./utils/signals'), Interactable = require('./Interactable'), InteractEvent = require('./InteractEvent'), Interaction = require('./Interaction'); @@ -510,13 +511,15 @@ interaction.checkAndPreventDefault(event); } }); - - // For IE's bad dblclick event sequence - events.add(doc, 'dblclick', Interaction.doOnInteractions('ie8Dblclick')); } scope.documents.push(doc); events.documents.push(doc); + + signals.fire('listen-to-document', { + doc: doc, + win: win, + }); } listenToDocument(scope.document); diff --git a/src/pointerEvents.js b/src/pointerEvents.js new file mode 100644 index 000000000..ce0e01b30 --- /dev/null +++ b/src/pointerEvents.js @@ -0,0 +1,240 @@ +'use strict'; + +var scope = require('./scope'), + Interaction = require('./Interaction'), + InteractEvent = require('./InteractEvent'), + utils = require('./utils'), + browser = require('./utils/browser'), + events = require('./utils/events'), + signals = require('./utils/signals'), + simpleSignals = [ + 'interaction-down', + 'interaction-up', + 'interaction-up', + 'interaction-cancel' + ], + simpleEvents = [ + 'down', + 'up', + 'tap', + 'cancel' + ]; + +function preventOriginalDefault () { + this.originalEvent.preventDefault(); +} + +function firePointers (interaction, pointer, event, eventTarget, targets, elements, eventType) { + var pointerIndex = interaction.mouse? 0 : utils.indexOf(interaction.pointerIds, utils.getPointerId(pointer)), + pointerEvent = {}, + i, + // for tap events + interval, createNewDoubleTap; + + // if it's a doubletap then the event properties would have been + // copied from the tap event and provided as the pointer argument + if (eventType === 'doubletap') { + pointerEvent = pointer; + } + else { + utils.extend(pointerEvent, event); + if (event !== pointer) { + utils.extend(pointerEvent, pointer); + } + + pointerEvent.preventDefault = preventOriginalDefault; + pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; + pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; + pointerEvent.interaction = interaction; + + pointerEvent.timeStamp = new Date().getTime(); + pointerEvent.originalEvent = event; + pointerEvent.type = eventType; + pointerEvent.pointerId = utils.getPointerId(pointer); + pointerEvent.pointerType = interaction.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' + : utils.isString(pointer.pointerType) + ? pointer.pointerType + : [undefined, undefined,'touch', 'pen', 'mouse'][pointer.pointerType]; + } + + if (eventType === 'tap') { + pointerEvent.dt = pointerEvent.timeStamp - interaction.downTimes[pointerIndex]; + + interval = pointerEvent.timeStamp - interaction.tapTime; + createNewDoubleTap = !!(interaction.prevTap && interaction.prevTap.type !== 'doubletap' + && interaction.prevTap.target === pointerEvent.target + && interval < 500); + + pointerEvent.double = createNewDoubleTap; + + interaction.tapTime = pointerEvent.timeStamp; + } + + for (i = 0; i < targets.length; i++) { + pointerEvent.currentTarget = elements[i]; + pointerEvent.interactable = targets[i]; + targets[i].fire(pointerEvent); + + if (pointerEvent.immediatePropagationStopped + ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { + break; + } + } + + if (createNewDoubleTap) { + var doubleTap = {}; + + utils.extend(doubleTap, pointerEvent); + + doubleTap.dt = interval; + doubleTap.type = 'doubletap'; + + collectEventTargets(interaction, doubleTap, event, eventTarget, 'doubletap'); + + interaction.prevTap = doubleTap; + } + else if (eventType === 'tap') { + interaction.prevTap = pointerEvent; + } +} + +function collectEventTargets (interaction, pointer, event, eventTarget, eventType) { + var pointerIndex = interaction.mouse? 0 : utils.indexOf(interaction.pointerIds, utils.getPointerId(pointer)); + + // do not fire a tap event if the pointer was moved before being lifted + if (eventType === 'tap' && (interaction.pointerWasMoved + // or if the pointerup target is different to the pointerdown target + || !(interaction.downTargets[pointerIndex] && interaction.downTargets[pointerIndex] === eventTarget))) { + return; + } + + var targets = [], + elements = [], + element = eventTarget; + + function collectSelectors (interactable, selector, context) { + var els = browser.useMatchesSelectorPolyfill + ? context.querySelectorAll(selector) + : undefined; + + if (interactable._iEvents[eventType] + && utils.isElement(element) + && scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && utils.matchesSelector(element, selector, els)) { + + targets.push(interactable); + elements.push(element); + } + } + + var interact = scope.interact; + + while (element) { + if (interact.isSet(element) && interact(element)._iEvents[eventType]) { + targets.push(interact(element)); + elements.push(element); + } + + scope.interactables.forEachSelector(collectSelectors); + + element = utils.parentElement(element); + } + + // create the tap event even if there are no listeners so that + // doubletap can still be created and fired + if (targets.length || eventType === 'tap') { + firePointers(interaction, pointer, event, eventTarget, targets, elements, eventType); + } +} + +signals.on('interaction-move', function (arg) { + var interaction = arg.interaction, + pointerIndex = (interaction.mouse + ? 0 + : utils.indexOf(interaction.pointerIds, utils.getPointerId(arg.pointer))); + + if (!arg.duplicateMove && (!interaction.pointerIsDown || interaction.pointerWasMoved)) { + if (interaction.pointerIsDown) { + clearTimeout(interaction.holdTimers[pointerIndex]); + } + + collectEventTargets(interaction, arg.pointer, arg.event, arg.eventTarget, 'move'); + } +}); + +signals.on('interaction-down', function (arg) { + var interaction = arg.interaction, + // copy event to be used in timeout for IE8 + eventCopy = browser.isIE8? utils.extend({}, arg.event) : arg.event; + + interaction.holdTimers[arg.pointerIndex] = setTimeout(function () { + + collectEventTargets(interaction, + browser.isIE8? eventCopy : arg.pointer, + eventCopy, + arg.eventTarget, + 'hold'); + + }, scope.defaultOptions._holdDuration); +}); + +function createSignalListener (event) { + return function (arg) { + collectEventTargets(arg.interaction, + arg.pointer, + arg.event, + arg.eventTarget, + event); + }; +} + +for (var i = 0; i < simpleSignals.length; i++) { + signals.on(simpleSignals[i], createSignalListener(simpleEvents[i])); +} + +if (browser.ie8) { + // http://www.quirksmode.org/dom/events/click.html + // >Events leading to dblclick + // + // IE8 doesn't fire down event before dblclick. + // This workaround tries to fire a tap and doubletap after dblclick + var onIE8Dblclick = function (event) { + var target = Interaction.getInteractionFromPointer(event); + + if (!target) { return; } + + var interaction = target.interaction; + + if (interaction.prevTap + && event.clientX === interaction.prevTap.clientX + && event.clientY === interaction.prevTap.clientY + && target.eventTarget === interaction.prevTap.target) { + + interaction.downTargets[0] = target.eventTarget; + interaction.downTimes[0] = new Date().getTime(); + collectEventTargets(interaction, target.pointer, target.event, target.eventTarget, 'tap'); + } + }; + + signals.on('listen-to-document', function (arg) { + events.add(arg.doc, 'dblclick', onIE8Dblclick); + }); +} + +scope.addEventTypes([ + 'down', + 'move', + 'up', + 'cancel', + 'tap', + 'doubletap', + 'hold' +]); + +module.exports = { + firePointers: firePointers, + collectEventTargets: collectEventTargets, + preventOriginalDefault: preventOriginalDefault +}; From 2dd19f23561a006da35d782c6bd9f164d7cbcc23 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 04:11:19 +0100 Subject: [PATCH 084/131] utils/browser: fix browser.supportsTouch --- src/utils/browser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/browser.js b/src/utils/browser.js index 85f64994f..3ebb37260 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -6,7 +6,7 @@ var win = require('./window'), var browser = { // Does the browser support touch input? - supportsTouch : !!(('ontouchstart' in win) || win.window.DocumentTouch + supportsTouch : !!(('ontouchstart' in win.window) || win.window.DocumentTouch && domObjects.document instanceof win.DocumentTouch), // Does the browser support PointerEvents From 975f974334cf121679bd0be223b2f72cb2efb3bf Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 10:31:50 +0100 Subject: [PATCH 085/131] Add utils/arr.merge and remove scope.addEventTypes --- src/actions/drag.js | 2 +- src/actions/drop.js | 2 +- src/actions/gesture.js | 2 +- src/actions/resize.js | 2 +- src/pointerEvents.js | 2 +- src/scope.js | 6 ------ src/utils/arr.js | 11 ++++++++++- 7 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/actions/drag.js b/src/actions/drag.js index ac5d5deb9..26229499f 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -185,7 +185,7 @@ Interactable.prototype.draggable = function (options) { base.drag = drag; base.names.push('drag'); -scope.addEventTypes([ +utils.merge(scope.eventTypes, [ 'dragstart', 'dragmove', 'draginertiastart', diff --git a/src/actions/drop.js b/src/actions/drop.js index cbfac7a07..87ae85ebe 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -470,7 +470,7 @@ Interactable.prototype.accept = function (newValue) { return this.options.drop.accept; }; -scope.addEventTypes([ +utils.merge(scope.eventTypes, [ 'dragenter', 'dragleave', 'dropactivate', diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 578c38df2..6f914e9f1 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -115,7 +115,7 @@ Interactable.prototype.gesturable = function (options) { base.gesture = gesture; base.names.push('gesture'); -scope.addEventTypes([ +utils.merge(scope.eventTypes, [ 'gesturestart', 'gesturemove', 'gestureinertiastart', diff --git a/src/actions/resize.js b/src/actions/resize.js index 5a5ea1006..89aeaebb4 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -341,7 +341,7 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, base.resize = resize; base.names.push('resize'); -scope.addEventTypes([ +utils.merge(scope.eventTypes, [ 'resizestart', 'resizemove', 'resizeinertiastart', diff --git a/src/pointerEvents.js b/src/pointerEvents.js index ce0e01b30..b34887407 100644 --- a/src/pointerEvents.js +++ b/src/pointerEvents.js @@ -223,7 +223,7 @@ if (browser.ie8) { }); } -scope.addEventTypes([ +utils.merge(scope.eventTypes, [ 'down', 'move', 'up', diff --git a/src/scope.js b/src/scope.js index 93bbc2cc5..39615ca57 100644 --- a/src/scope.js +++ b/src/scope.js @@ -17,12 +17,6 @@ extend(scope, require('./utils/domObjects')); scope.eventTypes = []; -scope.addEventTypes = function (eventTypes) { - for (var i = 0; i < eventTypes.length; i++) { - scope.eventTypes.push(eventTypes[i]); - } -}; - scope.withinInteractionLimit = function (interactable, element, action) { var options = interactable.options, maxActions = options[action.name].max, diff --git a/src/utils/arr.js b/src/utils/arr.js index 29a076fe6..e460a65ae 100644 --- a/src/utils/arr.js +++ b/src/utils/arr.js @@ -14,7 +14,16 @@ function contains (array, target) { return indexOf(array, target) !== -1; } +function merge (target, source) { + for (var i = 0; i < source.length; i++) { + target.push(source[i]); + } + + return target; +} + module.exports = { indexOf: indexOf, - contains: contains + contains: contains, + merge: merge }; From abd1b1a85c7b18b694cef7f391251237eae95c2f Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 10:50:59 +0100 Subject: [PATCH 086/131] utils/window: allow window to be updated later win.useWindow(window); --- src/utils/window.js | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/src/utils/window.js b/src/utils/window.js index 01ccef80b..19000f693 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -1,15 +1,12 @@ 'use strict'; -var isWindow = require('./isWindow'); +var win = module.exports, + isWindow = require('./isWindow'); -if (typeof window === 'undefined') { - module.exports.window = undefined; - module.exports.realWindow = undefined; -} -else { +function init (window) { // get wrapped window if using Shadow DOM polyfill - module.exports.realWindow = window; + win.realWindow = window; // create a TextNode var el = window.document.createTextNode(''); @@ -19,19 +16,29 @@ else { && typeof window.wrap === 'function' && window.wrap(el) === el) { // return wrapped window - module.exports.window = window.wrap(window); + win.window = window.wrap(window); } // no Shadow DOM polyfil or native implementation - module.exports.window = window; + win.window = window; } -module.exports.getWindow = function getWindow (node) { +if (typeof window === 'undefined') { + win.window = undefined; + win.realWindow = undefined; +} +else { + init(window); +} + +win.getWindow = function getWindow (node) { if (isWindow(node)) { return node; } var rootNode = (node.ownerDocument || node); - return rootNode.defaultView || rootNode.parentWindow || module.exports.window; + return rootNode.defaultView || rootNode.parentWindow || win.window; }; + +win.init = init; From 1976d7e9096c4ba69c5c7d5f76cfc0edcef81bb1 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 11:13:42 +0100 Subject: [PATCH 087/131] Move action and modifier options to own modules --- src/Interactable.js | 20 +++------- src/Interaction.js | 10 ++--- src/actions/drag.js | 31 +++++++++++++-- src/actions/drop.js | 30 +++++++++++++-- src/actions/gesture.js | 14 ++++++- src/actions/resize.js | 35 ++++++++++++++++- src/autoScroll.js | 11 +++++- src/defaultOptions.js | 81 --------------------------------------- src/interact.js | 8 ---- src/modifiers/restrict.js | 5 ++- src/modifiers/snap.js | 14 ++++--- 11 files changed, 132 insertions(+), 127 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index e8f7eb9f0..1e7ecf1e2 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -63,22 +63,12 @@ function Interactable (element, options) { Interactable.prototype = { setOnEvents: function (action, phases) { - if (action === 'drop') { - if (utils.isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; } - if (utils.isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; } - if (utils.isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; } - if (utils.isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; } - if (utils.isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; } - if (utils.isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; } - } - else { - action = 'on' + action; + var onAction = 'on' + action; - if (utils.isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; } - if (utils.isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; } - if (utils.isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; } - if (utils.isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; } - } + if (utils.isFunction(phases.onstart) ) { this[onAction + 'start' ] = phases.onstart ; } + if (utils.isFunction(phases.onmove) ) { this[onAction + 'move' ] = phases.onmove ; } + if (utils.isFunction(phases.onend) ) { this[onAction + 'end' ] = phases.onend ; } + if (utils.isFunction(phases.oninertiastart)) { this[onAction + 'inertiastart' ] = phases.oninertiastart ; } return this; }, diff --git a/src/Interaction.js b/src/Interaction.js index bebe2b21b..05f4adf45 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -790,13 +790,9 @@ Interaction.prototype = { return this._interacting; }, - clearTargets: function () { - this.target = this.element = null; - - this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null; - }, - stop: function (event) { + signals.fire('interaction-stop', { interaction: this }); + if (this._interacting) { signals.fire('interaction-stop-active', { interaction: this }); @@ -817,7 +813,7 @@ Interaction.prototype = { actions[this.prepared.name].stop(this, event); } - this.clearTargets(); + this.target = this.element = null; this.pointerIsDown = this._interacting = false; this.prepared.name = this.prevEvent = null; diff --git a/src/actions/drag.js b/src/actions/drag.js index 26229499f..d985db519 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -6,9 +6,24 @@ var base = require('./base'), utils = require('../utils'), browser = utils.browser, InteractEvent = require('../InteractEvent'), - Interactable = require('../Interactable'); + Interactable = require('../Interactable'), + defaultOptions = require('../defaultOptions'); var drag = { + defaults: { + enabled: false, + manualStart: true, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + axis: 'xy' + }, + checker: function (pointer, event, interactable) { return interactable.options.drag.enabled ? { name: 'drag' } @@ -43,7 +58,7 @@ var drag = { && elementInteractable !== interaction.target && !elementInteractable.options.drag.manualStart && elementInteractable.getAction(interaction.downPointer, interaction.downEvent, interaction, element).name === 'drag' - && scope.checkAxis(axis, elementInteractable)) { + && checkAxis(axis, elementInteractable)) { interaction.prepared.name = 'drag'; interaction.target = elementInteractable; @@ -72,7 +87,7 @@ var drag = { && scope.testAllow(interactable, element, eventTarget) && utils.matchesSelector(element, selector, elements) && interactable.getAction(interactionInteraction.downPointer, interactionInteraction.downEvent, interactionInteraction, element).name === 'drag' - && scope.checkAxis(axis, interactable) + && checkAxis(axis, interactable) && scope.withinInteractionLimit(interactable, element, 'drag')) { return interactable; @@ -127,6 +142,14 @@ var drag = { stop: drop.stop }; +function checkAxis (axis, interactable) { + if (!interactable) { return false; } + + var thisAxis = interactable.options.drag.axis; + + return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); +} + /*\ * Interactable.draggable [ method ] @@ -193,4 +216,6 @@ utils.merge(scope.eventTypes, [ ]); base.methodDict.drag = 'draggable'; +defaultOptions.drag = drag.defaults; + module.exports = drag; diff --git a/src/actions/drop.js b/src/actions/drop.js index 87ae85ebe..8512f0205 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -3,10 +3,17 @@ var base = require('./base'), utils = require('../utils'), scope = require('../scope'), - Interactable = require('../Interactable'); + signals = require('../utils/signals'), + Interactable = require('../Interactable'), + defaultOptions = require('../defaultOptions'); var drop = { - //beforeStart: function + defaults: { + enabled: false, + accept: null, + overlap: 'pointer' + }, + start: function (interaction, event, dragEvent) { // reset active dropzones interaction.activeDrops.dropzones = []; @@ -308,7 +315,14 @@ function getDropEvents (interaction, pointerEvent, dragEvent) { Interactable.prototype.dropzone = function (options) { if (utils.isObject(options)) { this.options.drop.enabled = options.enabled === false? false: true; - this.setOnEvents('drop', options); + + if (utils.isFunction(options.ondrop) ) { this.ondrop = options.ondrop ; } + if (utils.isFunction(options.ondropactivate) ) { this.ondropactivate = options.ondropactivate ; } + if (utils.isFunction(options.ondropdeactivate)) { this.ondropdeactivate = options.ondropdeactivate; } + if (utils.isFunction(options.ondragenter) ) { this.ondragenter = options.ondragenter ; } + if (utils.isFunction(options.ondragleave) ) { this.ondragleave = options.ondragleave ; } + if (utils.isFunction(options.ondropmove) ) { this.ondropmove = options.ondropmove ; } + this.accept(options.accept); if (/^(pointer|center)$/.test(options.overlap)) { @@ -470,6 +484,13 @@ Interactable.prototype.accept = function (newValue) { return this.options.drop.accept; }; +signals.on('interaction-stop', function (arg) { + var interaction = arg.interaction; + + interaction.dropTarget = interaction.dropElement + = interaction.prevDropTarget = interaction.prevDropElement = null; +}); + utils.merge(scope.eventTypes, [ 'dragenter', 'dragleave', @@ -479,4 +500,7 @@ utils.merge(scope.eventTypes, [ 'drop' ]); base.methodDict.drop = 'dropzone'; + +defaultOptions.drop = drop.defaults; + module.exports = drop; diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 6f914e9f1..602749b1a 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -4,9 +4,19 @@ var base = require('./base'), utils = require('../utils'), InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'), - scope = base.scope; + scope = base.scope, + defaultOptions = require('../defaultOptions'); var gesture = { + defaults: { + manualStart: false, + enabled: false, + max: Infinity, + maxPerElement: 1, + + restrict: null + }, + checker: function (pointer, event, interactable, element, interaction) { if (interaction.pointerIds.length >= 2) { return { name: 'gesture' }; @@ -123,4 +133,6 @@ utils.merge(scope.eventTypes, [ ]); base.methodDict.gesture = 'gesturable'; +defaultOptions.gesture = gesture.defaults; + module.exports = gesture; diff --git a/src/actions/resize.js b/src/actions/resize.js index 89aeaebb4..294c81d33 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -5,9 +5,40 @@ var base = require('./base'), browser = require('../utils/browser'), scope = base.scope, InteractEvent = require('../InteractEvent'), - Interactable = require('../Interactable'); + Interactable = require('../Interactable'), + defaultOptions = require('../defaultOptions'); var resize = { + defaults: { + enabled: false, + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + square: false, + axis: 'xy', + + // use default margin + margin: NaN, + + // object with props left, right, top, bottom which are + // true/false values to resize when the pointer is over that edge, + // CSS selectors to match the handles for each direction + // or the Elements for each handle + edges: null, + + // a value of 'none' will limit the resize rect to a minimum of 0x0 + // 'negate' will alow the rect to have negative width/height + // 'reposition' will keep the width/height positive by swapping + // the top and bottom edges and/or swapping the left and right edges + invert: 'none' + }, + checker: function (pointer, event, interactable, element, interaction, rect) { if (!rect) { return null; } @@ -349,4 +380,6 @@ utils.merge(scope.eventTypes, [ ]); base.methodDict.resize = 'resizable'; +defaultOptions.resize = resize.defaults; + module.exports = resize; diff --git a/src/autoScroll.js b/src/autoScroll.js index 87271fb23..503904f37 100644 --- a/src/autoScroll.js +++ b/src/autoScroll.js @@ -4,9 +4,16 @@ var raf = require('./utils/raf'), getWindow = require('./utils/window').getWindow, isWindow = require('./utils/isType').isWindow, domUtils = require('./utils/domUtils'), - signals = require('./utils/signals'); + signals = require('./utils/signals'), + defaultOptions = require('./defaultOptions'); var autoScroll = { + defaults: { + enabled : false, + container : null, // the item that is scrolled (Window or HTMLElement) + margin : 60, + speed : 300 // the scroll speed in pixels per second + }, interaction: null, i: null, // the handle returned by window.setInterval @@ -116,4 +123,6 @@ signals.on('interaction-stop-active', function () { signals.on('interaction-move-done', autoScroll.onInteractionMove); +defaultOptions.perAction.autoScroll = autoScroll.defaults; + module.exports = autoScroll; diff --git a/src/defaultOptions.js b/src/defaultOptions.js index d0a046963..21e4eebcc 100644 --- a/src/defaultOptions.js +++ b/src/defaultOptions.js @@ -14,92 +14,11 @@ module.exports = { dropChecker : null }, - drag: { - enabled: false, - manualStart: true, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - axis: 'xy' - }, - - drop: { - enabled: false, - accept: null, - overlap: 'pointer' - }, - - resize: { - enabled: false, - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - square: false, - axis: 'xy', - - // use default margin - margin: NaN, - - // object with props left, right, top, bottom which are - // true/false values to resize when the pointer is over that edge, - // CSS selectors to match the handles for each direction - // or the Elements for each handle - edges: null, - - // a value of 'none' will limit the resize rect to a minimum of 0x0 - // 'negate' will alow the rect to have negative width/height - // 'reposition' will keep the width/height positive by swapping - // the top and bottom edges and/or swapping the left and right edges - invert: 'none' - }, - - gesture: { - manualStart: false, - enabled: false, - max: Infinity, - maxPerElement: 1, - - restrict: null - }, - perAction: { manualStart: false, max: Infinity, maxPerElement: 1, - snap: { - enabled : false, - endOnly : false, - range : Infinity, - targets : null, - offsets : null, - - relativePoints: null - }, - - restrict: { - enabled: false, - endOnly: false - }, - - autoScroll: { - enabled : false, - container : null, // the item that is scrolled (Window or HTMLElement) - margin : 60, - speed : 300 // the scroll speed in pixels per second - }, - inertia: { enabled : false, resistance : 10, // the lambda in exponential decay diff --git a/src/interact.js b/src/interact.js index 47d7b1d30..de981e9d8 100644 --- a/src/interact.js +++ b/src/interact.js @@ -112,14 +112,6 @@ return false; }; - scope.checkAxis = function (axis, interactable) { - if (!interactable) { return false; } - - var thisAxis = interactable.options.drag.axis; - - return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); - }; - for (var i = 0, len = interactionListeners.length; i < len; i++) { var listenerName = interactionListeners[i]; diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index ac95f40d3..3125d9ac1 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -1,7 +1,8 @@ 'use strict'; var modifiers = require('./index'), - utils = require('../utils'); + utils = require('../utils'), + defaultOptions = require('../defaultOptions'); var restrict = { options: { @@ -145,5 +146,7 @@ var restrict = { modifiers.restrict = restrict; modifiers.names.push('restrict'); +defaultOptions.perAction.restrict = restrict.defaults; + module.exports = restrict; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index 543e8aa99..3ad8b22b4 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -3,11 +3,11 @@ var modifiers = require('./index'), scope = require('../scope'), interact = require('../interact'), - utils = require('../utils'); - //defaultOptions = require('../defaultOptions'); + utils = require('../utils'), + defaultOptions = require('../defaultOptions'); var snap = { - options: { + defaults: { enabled: false, endOnly: false, range : Infinity, @@ -212,9 +212,6 @@ var snap = { } }; -modifiers.snap = snap; -modifiers.names.push('snap'); - interact.createSnapGrid = function (grid) { return function (x, y) { var offsetX = 0, @@ -239,4 +236,9 @@ interact.createSnapGrid = function (grid) { }; }; +modifiers.snap = snap; +modifiers.names.push('snap'); + +defaultOptions.perAction.snap = snap.defaults; + module.exports = snap; From 4ddd2ffc5274331549b05d3108fd7531170fb27e Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 13:17:08 +0100 Subject: [PATCH 088/131] Move getOriginXY from scope to utils --- src/InteractEvent.js | 2 +- src/interact.js | 29 ----------------------------- src/modifiers/snap.js | 4 ++-- src/utils/index.js | 30 ++++++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index d088f0504..9a4be8402 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -13,7 +13,7 @@ function InteractEvent (interaction, event, action, phase, element, related) { sourceX = deltaSource + 'X', sourceY = deltaSource + 'Y', options = target? target.options: scope.defaultOptions, - origin = scope.getOriginXY(target, element), + origin = utils.getOriginXY(target, element), starting = phase === 'start', ending = phase === 'end', coords = starting? interaction.startCoords : interaction.curCoords; diff --git a/src/interact.js b/src/interact.js index de981e9d8..2177848b9 100644 --- a/src/interact.js +++ b/src/interact.js @@ -46,35 +46,6 @@ 'addPointer', 'removePointer', 'recordPointer' ]; - scope.getOriginXY = function (interactable, element) { - var origin = interactable - ? interactable.options.origin - : scope.defaultOptions.origin; - - if (origin === 'parent') { - origin = utils.parentElement(element); - } - else if (origin === 'self') { - origin = interactable.getRect(element); - } - else if (utils.trySelector(origin)) { - origin = utils.closest(element, origin) || { x: 0, y: 0 }; - } - - if (utils.isFunction(origin)) { - origin = origin(interactable && element); - } - - if (utils.isElement(origin)) { - origin = utils.getElementRect(origin); - } - - origin.x = ('x' in origin)? origin.x : origin.left; - origin.y = ('y' in origin)? origin.y : origin.top; - - return origin; - }; - scope.inContext = function (interactable, element) { return interactable._context === element.ownerDocument || utils.nodeContains(interactable._context, element); diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index 3ad8b22b4..9e9bb06ac 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -24,7 +24,7 @@ var snap = { setOffset: function (interaction, interactable, element, rect, startOffset) { var offsets = [], - origin = scope.getOriginXY(interactable, element), + origin = utils.getOriginXY(interactable, element), snapOffset = (snap && snap.offset === 'startCoords' ? { x: interaction.startCoords.page.x - origin.x, @@ -58,7 +58,7 @@ var snap = { page = { x: status.x, y: status.y }; } else { - var origin = scope.getOriginXY(interaction.target, interaction.element); + var origin = utils.getOriginXY(interaction.target, interaction.element); page = utils.extend({}, pageCoords); diff --git a/src/utils/index.js b/src/utils/index.js index 43a010ca4..1ef5c82a8 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -2,6 +2,7 @@ var utils = module.exports, extend = require('./extend'), + defaultOptions = require('../defaultOptions'), win = require('./window'); utils.blank = function () {}; @@ -38,6 +39,35 @@ utils.easeOutQuad = function (t, b, c, d) { return -c * t*(t-2) + b; }; +utils.getOriginXY = function (interactable, element) { + var origin = interactable + ? interactable.options.origin + : defaultOptions.origin; + + if (origin === 'parent') { + origin = utils.parentElement(element); + } + else if (origin === 'self') { + origin = interactable.getRect(element); + } + else if (utils.trySelector(origin)) { + origin = utils.closest(element, origin) || { x: 0, y: 0 }; + } + + if (utils.isFunction(origin)) { + origin = origin(interactable && element); + } + + if (utils.isElement(origin)) { + origin = utils.getElementRect(origin); + } + + origin.x = ('x' in origin)? origin.x : origin.left; + origin.y = ('y' in origin)? origin.y : origin.top; + + return origin; +}; + utils.extend = extend; utils.hypot = require('./hypot'); From 5707e70eb531e528cf81c21113b6832678588a12 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 13:17:56 +0100 Subject: [PATCH 089/131] utils/signals: return false from listener to stop --- src/utils/signals.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/signals.js b/src/utils/signals.js index 697617e69..12dcde4c3 100644 --- a/src/utils/signals.js +++ b/src/utils/signals.js @@ -30,7 +30,9 @@ var signals = { if (!targetListeners) { return; } for (var i = 0; i < targetListeners.length; i++) { - targetListeners[i](arg, name); + if (targetListeners[i](arg, name) === false) { + return; + } } }, listeners: listeners From cc63f5d7083e8d6a02859b1365e5c8085a498e66 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 14:02:42 +0100 Subject: [PATCH 090/131] Move interaction event listening to Interaction --- src/Interactable.js | 32 ++++++----- src/Interaction.js | 128 ++++++++++++++++++++++++++++++++++++++++++-- src/interact.js | 102 +---------------------------------- src/scope.js | 18 ++++++- 4 files changed, 157 insertions(+), 123 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index 1e7ecf1e2..480585253 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -2,8 +2,8 @@ var scope = require('./scope'), utils = require('./utils'), - browser = require('./utils/browser'), events = require('./utils/events'), + signals = require('./utils/signals'), actions = require('./actions/base'); /*\ @@ -34,26 +34,22 @@ function Interactable (element, options) { } else { _window = scope.getWindow(element); - - if (utils.isElement(element, _window)) { - - if (scope.PointerEvent) { - events.add(this._element, browser.pEventTypes.down, scope.listeners.pointerDown ); - events.add(this._element, browser.pEventTypes.move, scope.listeners.pointerHover); - } - else { - events.add(this._element, 'mousedown' , scope.listeners.pointerDown ); - events.add(this._element, 'mousemove' , scope.listeners.pointerHover); - events.add(this._element, 'touchstart', scope.listeners.pointerDown ); - events.add(this._element, 'touchmove' , scope.listeners.pointerHover); - } - } } this._doc = _window.document; - if (!utils.contains(scope.documents, this._doc)) { - scope.listenToDocument(this._doc); + signals.fire('interactable-new', { + interactable: this, + element: element, + options: options, + win: _window + }); + + if (this._doc !== scope.document) { + signals.fire('listen-to-document', { + doc: this._doc, + win: _window + }); } scope.interactables.push(this); @@ -659,6 +655,8 @@ Interactable.prototype = { } } + signals.fire('interactable-unset', { interactable: this }); + this.dropzone(false); scope.interactables.splice(utils.indexOf(scope.interactables, this), 1); diff --git a/src/Interaction.js b/src/Interaction.js index 05f4adf45..7b0299c12 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -8,7 +8,14 @@ var scope = require('./scope'), signals = require('./utils/signals'), browser = require('./utils/browser'), actions = require('./actions/base'), - modifiers = require('./modifiers/'); + modifiers = require('./modifiers/'), + methodNames = [ + 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', + 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', + 'addPointer', 'removePointer', 'recordPointer' + ], + listeners = {}; + function Interaction () { this.target = null; // current interactable being interacted with @@ -231,14 +238,14 @@ Interaction.prototype = { this.pointerHover(pointer, event, this.matches, this.matchElements); events.add(eventTarget, scope.PointerEvent? browser.pEventTypes.move : 'mousemove', - scope.listeners.pointerHover); + listeners.pointerHover); } else if (this.target) { if (utils.nodeContains(prevTargetElement, eventTarget)) { this.pointerHover(pointer, event, this.matches, this.matchElements); events.add(this.element, scope.PointerEvent? browser.pEventTypes.move : 'mousemove', - scope.listeners.pointerHover); + listeners.pointerHover); } else { this.target = null; @@ -290,7 +297,7 @@ Interaction.prototype = { if (!scope.interactables.get(eventTarget)) { events.remove(eventTarget, scope.PointerEvent? browser.pEventTypes.move : 'mousemove', - scope.listeners.pointerHover); + listeners.pointerHover); } if (this.target && this.target.options.styleCursor && !this.interacting()) { @@ -1015,6 +1022,12 @@ Interaction.prototype = { } }; +for (var i = 0, len = methodNames.length; i < len; i++) { + var method = methodNames[i]; + + listeners[method] = doOnInteractions(method); +} + function getInteractionFromPointer (pointer, eventType, eventTarget) { var i = 0, len = scope.interactions.length, mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) @@ -1156,6 +1169,113 @@ function doOnInteractions (method) { }); } +signals.on('interactable-new', function (arg) { + var interactable = arg.interactable, + element = interactable._element; + + if (utils.isElement(element, arg.win)) { + if (scope.PointerEvent) { + events.add(element, browser.pEventTypes.down, listeners.pointerDown ); + events.add(element, browser.pEventTypes.move, listeners.pointerHover); + } + else { + events.add(element, 'mousedown' , listeners.pointerDown ); + events.add(element, 'mousemove' , listeners.pointerHover); + events.add(element, 'touchstart', listeners.pointerDown ); + events.add(element, 'touchmove' , listeners.pointerHover); + } + } +}); + +signals.on('interactable-unset', function (arg) { + var interactable = arg.interactable, + element = interactable._element; + + if (!interactable.selector && utils.isElement(element, arg.win)) { + if (scope.PointerEvent) { + events.remove(element, browser.pEventTypes.down, listeners.pointerDown ); + events.remove(element, browser.pEventTypes.move, listeners.pointerHover); + } + else { + events.remove(element, 'mousedown' , listeners.pointerDown ); + events.remove(element, 'mousemove' , listeners.pointerHover); + events.remove(element, 'touchstart', listeners.pointerDown ); + events.remove(element, 'touchmove' , listeners.pointerHover); + } + } +}); + +signals.on('listen-to-document', function (arg) { + var doc = arg.doc, + win = arg.win, + pEventTypes = browser.pEventTypes; + + // add delegate event listener + for (var eventType in scope.delegatedEvents) { + events.add(doc, eventType, events.delegateListener); + events.add(doc, eventType, events.delegateUseCapture, true); + } + + if (scope.PointerEvent) { + events.add(doc, pEventTypes.down , listeners.selectorDown ); + events.add(doc, pEventTypes.move , listeners.pointerMove ); + events.add(doc, pEventTypes.over , listeners.pointerOver ); + events.add(doc, pEventTypes.out , listeners.pointerOut ); + events.add(doc, pEventTypes.up , listeners.pointerUp ); + events.add(doc, pEventTypes.cancel, listeners.pointerCancel); + } + else { + events.add(doc, 'mousedown', listeners.selectorDown); + events.add(doc, 'mousemove', listeners.pointerMove ); + events.add(doc, 'mouseup' , listeners.pointerUp ); + events.add(doc, 'mouseover', listeners.pointerOver ); + events.add(doc, 'mouseout' , listeners.pointerOut ); + + events.add(doc, 'touchstart' , listeners.selectorDown ); + events.add(doc, 'touchmove' , listeners.pointerMove ); + events.add(doc, 'touchend' , listeners.pointerUp ); + events.add(doc, 'touchcancel', listeners.pointerCancel); + } + + events.add(win, 'blur', scope.endAllInteractions); + + try { + if (win.frameElement) { + var parentDoc = win.frameElement.ownerDocument, + parentWindow = parentDoc.defaultView; + + events.add(parentDoc , 'mouseup' , listeners.pointerEnd); + events.add(parentDoc , 'touchend' , listeners.pointerEnd); + events.add(parentDoc , 'touchcancel' , listeners.pointerEnd); + events.add(parentDoc , 'pointerup' , listeners.pointerEnd); + events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd); + events.add(parentWindow, 'blur' , scope.endAllInteractions ); + } + } + catch (error) { + scope.windowParentError = error; + } + + if (browser.isIE8) { + // For IE's lack of Event#preventDefault + events.add(doc, 'selectstart', function (event) { + var interaction = scope.interactions[0]; + + if (interaction.currentAction()) { + interaction.checkAndPreventDefault(event); + } + }); + } + + scope.documents.push(doc); + events.documents.push(doc); +}); + +signals.fire('listen-to-document', { + win: scope.window, + doc: scope.document +}); + Interaction.getInteractionFromPointer = getInteractionFromPointer; Interaction.doOnInteractions = doOnInteractions; Interaction.withinLimit = scope.withinInteractionLimit; diff --git a/src/interact.js b/src/interact.js index 2177848b9..c70882726 100644 --- a/src/interact.js +++ b/src/interact.js @@ -16,9 +16,7 @@ browser = utils.browser, events = require('./utils/events'), signals = require('./utils/signals'), - Interactable = require('./Interactable'), - InteractEvent = require('./InteractEvent'), - Interaction = require('./Interaction'); + Interactable = require('./Interactable'); scope.dynamicDrop = false; @@ -38,14 +36,6 @@ scope.globalEvents = {}; - scope.listeners = {}; - - var interactionListeners = [ - 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', - 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', - 'addPointer', 'removePointer', 'recordPointer' - ]; - scope.inContext = function (interactable, element) { return interactable._context === element.ownerDocument || utils.nodeContains(interactable._context, element); @@ -83,12 +73,6 @@ return false; }; - for (var i = 0, len = interactionListeners.length; i < len; i++) { - var listenerName = interactionListeners[i]; - - scope.listeners[listenerName] = Interaction.doOnInteractions(listenerName); - } - scope.interactables.indexOfElement = function indexOfElement (element, context) { context = context || scope.document; @@ -407,91 +391,7 @@ return scope.maxInteractions; }; - function endAllInteractions (event) { - for (var i = 0; i < scope.interactions.length; i++) { - scope.interactions[i].pointerEnd(event, event); - } - } - - function listenToDocument (doc) { - if (utils.contains(scope.documents, doc)) { return; } - - var win = doc.defaultView || doc.parentWindow, - pEventTypes = browser.pEventTypes; - - // add delegate event listener - for (var eventType in scope.delegatedEvents) { - events.add(doc, eventType, events.delegateListener); - events.add(doc, eventType, events.delegateUseCapture, true); - } - - if (scope.PointerEvent) { - events.add(doc, pEventTypes.down , scope.listeners.selectorDown ); - events.add(doc, pEventTypes.move , scope.listeners.pointerMove ); - events.add(doc, pEventTypes.over , scope.listeners.pointerOver ); - events.add(doc, pEventTypes.out , scope.listeners.pointerOut ); - events.add(doc, pEventTypes.up , scope.listeners.pointerUp ); - events.add(doc, pEventTypes.cancel, scope.listeners.pointerCancel); - } - else { - events.add(doc, 'mousedown', scope.listeners.selectorDown); - events.add(doc, 'mousemove', scope.listeners.pointerMove ); - events.add(doc, 'mouseup' , scope.listeners.pointerUp ); - events.add(doc, 'mouseover', scope.listeners.pointerOver ); - events.add(doc, 'mouseout' , scope.listeners.pointerOut ); - - events.add(doc, 'touchstart' , scope.listeners.selectorDown ); - events.add(doc, 'touchmove' , scope.listeners.pointerMove ); - events.add(doc, 'touchend' , scope.listeners.pointerUp ); - events.add(doc, 'touchcancel', scope.listeners.pointerCancel); - } - - events.add(win, 'blur', endAllInteractions); - - try { - if (win.frameElement) { - var parentDoc = win.frameElement.ownerDocument, - parentWindow = parentDoc.defaultView; - - events.add(parentDoc , 'mouseup' , scope.listeners.pointerEnd); - events.add(parentDoc , 'touchend' , scope.listeners.pointerEnd); - events.add(parentDoc , 'touchcancel' , scope.listeners.pointerEnd); - events.add(parentDoc , 'pointerup' , scope.listeners.pointerEnd); - events.add(parentDoc , 'MSPointerUp' , scope.listeners.pointerEnd); - events.add(parentWindow, 'blur' , endAllInteractions ); - } - } - catch (error) { - interact.windowParentError = error; - } - - if (browser.isIE8) { - // For IE's lack of Event#preventDefault - events.add(doc, 'selectstart', function (event) { - var interaction = scope.interactions[0]; - - if (interaction.currentAction()) { - interaction.checkAndPreventDefault(event); - } - }); - } - - scope.documents.push(doc); - events.documents.push(doc); - - signals.fire('listen-to-document', { - doc: doc, - win: win, - }); - } - - listenToDocument(scope.document); - scope.interact = interact; - scope.Interactable = Interactable; - scope.Interaction = Interaction; - scope.InteractEvent = InteractEvent; - scope.listenToDocument = listenToDocument; module.exports = interact; diff --git a/src/scope.js b/src/scope.js index 39615ca57..66f38aabd 100644 --- a/src/scope.js +++ b/src/scope.js @@ -1,7 +1,9 @@ 'use strict'; var scope = {}, - extend = require('./utils/extend'); + utils = require('./utils'), + signals = require('./utils/signals'), + extend = utils.extend; scope.documents = []; // all documents being listened to @@ -11,6 +13,7 @@ scope.interactions = []; // all interactions scope.defaultOptions = require('./defaultOptions'); scope.events = require('./utils/events'); +scope.signals = require('./utils/signals'); extend(scope, require('./utils/window')); extend(scope, require('./utils/domObjects')); @@ -58,5 +61,18 @@ scope.withinInteractionLimit = function (interactable, element, action) { return scope.maxInteractions > 0; }; +scope.endAllInteractions = function (event) { + for (var i = 0; i < scope.interactions.length; i++) { + scope.interactions[i].pointerEnd(event, event); + } +}; + +signals.on('listen-to-document', function (arg) { + // if document is already known + if (utils.contains(scope.documents, arg.doc)) { + // don't call any further signal listeners + return false; + } +}); module.exports = scope; From 5fb2e4b7fd75ccea7c6630f92bb530af0a1bee70 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 14:08:33 +0100 Subject: [PATCH 091/131] Add node and browserify entry files ./index.js for node - exports a factory function that takes `window` ./src/index.js for browser - exports the interact function Non-essential modules can be removed from ./src/index.js: actions, modifiers, autoScroll and pointerEvents. --- index.js | 9 +++++++++ package.json | 3 ++- src/index.js | 20 ++++++++++++++++++++ src/interact.js | 7 ------- 4 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 index.js create mode 100644 src/index.js diff --git a/index.js b/index.js new file mode 100644 index 000000000..980413026 --- /dev/null +++ b/index.js @@ -0,0 +1,9 @@ +'use strict'; + +// node entry point + +module.exports = function (window) { + require('./src/utils/window').init(window); + + return require('./src/index'); +}; diff --git a/package.json b/package.json index b6134ab15..9eac26233 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "type": "git", "url": "https://github.com/taye/interact.js.git" }, - "main": "interact.js", + "main": "./index.js", + "browser": "./src/index.js", "description": "Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)", "homepage": "http://interactjs.io", "authors": [ diff --git a/src/index.js b/src/index.js new file mode 100644 index 000000000..7c07ad59f --- /dev/null +++ b/src/index.js @@ -0,0 +1,20 @@ +'use strict'; + +// browser entry point + +module.exports = require('./interact'); + +// actions +require('./actions/resize'); +require('./actions/drag'); +require('./actions/gesture'); + +// autoScroll +require('./autoScroll'); + +// pointerEvents +require('./pointerEvents'); + +// modifiers +require('./modifiers/snap'); +require('./modifiers/restrict'); diff --git a/src/interact.js b/src/interact.js index c70882726..94bae3fab 100644 --- a/src/interact.js +++ b/src/interact.js @@ -394,10 +394,3 @@ scope.interact = interact; module.exports = interact; - - require('./actions/resize'); - require('./actions/drag'); - require('./actions/gesture'); - - require('./modifiers/snap'); - require('./modifiers/restrict'); From a282be496176ede66cc8013c201ebd9cbd6bc8a4 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 14:32:01 +0100 Subject: [PATCH 092/131] gulp/config: use src/index.js as browserify entry --- gulp/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gulp/config.js b/gulp/config.js index cb18a0959..3af39bbc0 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -17,7 +17,7 @@ module.exports = { // bundle config in the list below bundleConfigs: [ { - entries: src + '/interact.js', + entries: src + '/index.js', dest: dest, outputName: 'interact.js', outputNameMin: 'interact.min.js', From 268f3eebb48a71cf191a4a7d6ae0c3ca37e9d016 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 15:24:26 +0100 Subject: [PATCH 093/131] Refactor action-specific code in InteractEvent --- src/InteractEvent.js | 69 +++++++++++------------------------------- src/actions/gesture.js | 38 +++++++++++++++++++++++ src/actions/resize.js | 31 ++++++++++++++++++- 3 files changed, 86 insertions(+), 52 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 9a4be8402..89153f9e2 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -2,6 +2,7 @@ var scope = require('./scope'), utils = require('./utils'), + signals = require('./utils/signals'), modifiers = require('./modifiers'); function InteractEvent (interaction, event, action, phase, element, related) { @@ -58,7 +59,22 @@ function InteractEvent (interaction, event, action, phase, element, related) { this.clientX0 = interaction.startCoords.client.x - origin.x; this.clientY0 = interaction.startCoords.client.y - origin.y; - var inertiaStatus = interaction.inertiaStatus; + var inertiaStatus = interaction.inertiaStatus, + signalArg = { + interactEvent: this, + interaction: interaction, + event: event, + action: action, + phase: phase, + element: element, + related: related, + page: page, + client: client, + coords: coords, + starting: starting, + ending: ending, + deltaSource: deltaSource + }; if (inertiaStatus.active) { this.detail = 'inertia'; @@ -108,56 +124,7 @@ function InteractEvent (interaction, event, action, phase, element, related) { this.dx = this.dy = 0; } - if (action === 'resize' && interaction.resizeAxes) { - if (options.resize.square) { - if (interaction.resizeAxes === 'y') { - this.dx = this.dy; - } - else { - this.dy = this.dx; - } - this.axes = 'xy'; - } - else { - this.axes = interaction.resizeAxes; - - if (interaction.resizeAxes === 'x') { - this.dy = 0; - } - else if (interaction.resizeAxes === 'y') { - this.dx = 0; - } - } - } - else if (action === 'gesture') { - this.touches = [pointers[0], pointers[1]]; - - if (starting) { - this.distance = utils.touchDistance(pointers, deltaSource); - this.box = utils.touchBBox(pointers); - this.scale = 1; - this.ds = 0; - this.angle = utils.touchAngle(pointers, undefined, deltaSource); - this.da = 0; - } - else if (ending || event instanceof InteractEvent) { - this.distance = interaction.prevEvent.distance; - this.box = interaction.prevEvent.box; - this.scale = interaction.prevEvent.scale; - this.ds = this.scale - 1; - this.angle = interaction.prevEvent.angle; - this.da = this.angle - interaction.gesture.startAngle; - } - else { - this.distance = utils.touchDistance(pointers, deltaSource); - this.box = utils.touchBBox(pointers); - this.scale = this.distance / interaction.gesture.startDistance; - this.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); - - this.ds = this.scale - interaction.gesture.prevScale; - this.da = this.angle - interaction.gesture.prevAngle; - } - } + signals.fire('interactevent-set-delta', signalArg); if (starting) { this.timeStamp = interaction.downTimes[0]; diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 602749b1a..97cc4eb3c 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -5,6 +5,7 @@ var base = require('./base'), InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'), scope = base.scope, + signals = require('../utils/signals'), defaultOptions = require('../defaultOptions'); var gesture = { @@ -123,6 +124,43 @@ Interactable.prototype.gesturable = function (options) { return this.options.gesture; }; +signals.on('interactevent-delta', function (arg) { + if (arg.action !== 'gesture') { return; } + + var interaction = arg.interaction, + iEvent = arg.interactEvent, + pointers = interaction.pointers; + + iEvent.touches = [pointers[0], pointers[1]]; + + if (arg.starting) { + iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); + iEvent.box = utils.touchBBox(pointers); + iEvent.scale = 1; + iEvent.ds = 0; + iEvent.angle = utils.touchAngle(pointers, undefined, arg.deltaSource); + iEvent.da = 0; + } + else if (arg.ending || event instanceof InteractEvent) { + iEvent.distance = interaction.prevEvent.distance; + iEvent.box = interaction.prevEvent.box; + iEvent.scale = interaction.prevEvent.scale; + iEvent.ds = iEvent.scale - 1; + iEvent.angle = interaction.prevEvent.angle; + iEvent.da = iEvent.angle - interaction.gesture.startAngle; + } + else { + iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); + iEvent.box = utils.touchBBox(pointers); + iEvent.scale = iEvent.distance / interaction.gesture.startDistance; + iEvent.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, arg.deltaSource); + + iEvent.ds = iEvent.scale - interaction.gesture.prevScale; + iEvent.da = iEvent.angle - interaction.gesture.prevAngle; + } +}); + + base.gesture = gesture; base.names.push('gesture'); utils.merge(scope.eventTypes, [ diff --git a/src/actions/resize.js b/src/actions/resize.js index 294c81d33..3cc8ad3da 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -3,7 +3,8 @@ var base = require('./base'), utils = require('../utils'), browser = require('../utils/browser'), - scope = base.scope, + signals = require('../utils/signals'), + scope = require('../scope'), InteractEvent = require('../InteractEvent'), Interactable = require('../Interactable'), defaultOptions = require('../defaultOptions'); @@ -370,6 +371,34 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, : utils.matchesUpTo(element, value, interactableElement); } +signals.on('interact-event-set-delta', function (arg) { + var interaction = arg.interaction, + iEvent = arg.interactEvent, + options = interaction.target.options; + + if (arg.action !== 'resize' || !interaction.resizeAxes) { return; } + + if (options.resize.square) { + if (interaction.resizeAxes === 'y') { + iEvent.dx = iEvent.dy; + } + else { + iEvent.dy = iEvent.dx; + } + iEvent.axes = 'xy'; + } + else { + iEvent.axes = interaction.resizeAxes; + + if (interaction.resizeAxes === 'x') { + iEvent.dy = 0; + } + else if (interaction.resizeAxes === 'y') { + iEvent.dx = 0; + } + } +}); + base.resize = resize; base.names.push('resize'); utils.merge(scope.eventTypes, [ From 60df916500b670491829e77966c93c92728fc76d Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 30 Aug 2015 15:31:16 +0100 Subject: [PATCH 094/131] Fix jshint warnings --- karma.conf.js | 1 + src/InteractEvent.js | 1 - src/Interaction.js | 2 +- src/interact.js | 1 - src/modifiers/snap.js | 1 - 5 files changed, 2 insertions(+), 4 deletions(-) diff --git a/karma.conf.js b/karma.conf.js index fd99801f5..d437f48ae 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -1,4 +1,5 @@ // Karma configuration +'use strict'; module.exports = function (config) { config.set({ diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 89153f9e2..8f6a6d616 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -9,7 +9,6 @@ function InteractEvent (interaction, event, action, phase, element, related) { var client, page, target = interaction.target, - pointers = interaction.pointers, deltaSource = (target && target.options || scope.defaultOptions).deltaSource, sourceX = deltaSource + 'X', sourceY = deltaSource + 'Y', diff --git a/src/Interaction.js b/src/Interaction.js index 7b0299c12..790534fb4 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -498,7 +498,7 @@ Interaction.prototype = { * * Start an action with the given Interactable and Element as tartgets. The * action must be enabled for the target Interactable and an appropriate number - * of pointers must be held down – 1 for drag/resize, 2 for gesture. + * of pointers must be held down - 1 for drag/resize, 2 for gesture. * * Use it with `interactable.able({ manualStart: false })` to always * [start actions manually](https://github.com/taye/interact.js/issues/114) diff --git a/src/interact.js b/src/interact.js index 94bae3fab..730047fdd 100644 --- a/src/interact.js +++ b/src/interact.js @@ -15,7 +15,6 @@ utils = require('./utils'), browser = utils.browser, events = require('./utils/events'), - signals = require('./utils/signals'), Interactable = require('./Interactable'); scope.dynamicDrop = false; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index 9e9bb06ac..d2e84826f 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -1,7 +1,6 @@ 'use strict'; var modifiers = require('./index'), - scope = require('../scope'), interact = require('../interact'), utils = require('../utils'), defaultOptions = require('../defaultOptions'); From acc34dc65b2eba6531ebc22986a06ed8234a7f9e Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 2 Sep 2015 15:53:56 +0100 Subject: [PATCH 095/131] Fix bugs with drop and restrict --- src/actions/drop.js | 2 +- src/modifiers/restrict.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/drop.js b/src/actions/drop.js index 8512f0205..ac65c1245 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -359,7 +359,7 @@ Interactable.prototype.dropCheck = function (pointer, event, draggable, draggabl if (dropOverlap === 'pointer') { var page = utils.getPageXY(pointer), - origin = scope.getOriginXY(draggable, draggableElement), + origin = utils.getOriginXY(draggable, draggableElement), horizontal, vertical; diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index 3125d9ac1..a3928be3d 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -5,7 +5,7 @@ var modifiers = require('./index'), defaultOptions = require('../defaultOptions'); var restrict = { - options: { + defaults: { enabled : false, endOnly : false, restriction: null, From bd07ca8195c49b12a5738b85430018c23e09830b Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 2 Sep 2015 23:51:05 +0100 Subject: [PATCH 096/131] interact.js: remove check for window --- src/interact.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/interact.js b/src/interact.js index 730047fdd..d2f4fd07b 100644 --- a/src/interact.js +++ b/src/interact.js @@ -8,9 +8,6 @@ 'use strict'; - // return early if there's no window to work with (eg. Node.js) - if (!require('./utils/window').window) { return; } - var scope = require('./scope'), utils = require('./utils'), browser = utils.browser, From 7adc3127d89a36417373993ea6eba910425a8daa Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Thu, 3 Sep 2015 00:23:51 +0100 Subject: [PATCH 097/131] Use babelify for es6 transpilation --- .jshintrc | 1 + gulp/tasks/browserify.js | 14 +++++++++++--- package.json | 1 + 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.jshintrc b/.jshintrc index 64ae0ee0e..8babddb6f 100644 --- a/.jshintrc +++ b/.jshintrc @@ -3,6 +3,7 @@ "validthis": true, "browser" : true, "node" : true, + "esnext" : true, "jquery" : true, "curly" : true, "laxbreak" : true, diff --git a/gulp/tasks/browserify.js b/gulp/tasks/browserify.js index 1216a698f..2c7f173d6 100644 --- a/gulp/tasks/browserify.js +++ b/gulp/tasks/browserify.js @@ -8,6 +8,8 @@ See browserify.bundleConfigs in gulp/config.js */ +'use strict'; + var browserify = require('browserify'); var browserSync = require('browser-sync'); var watchify = require('watchify'); @@ -44,6 +46,7 @@ var browserifyTask = function (devMode) { bundleLogger.start(bundleConfig.outputName); return b + .transform(require('babelify')) .bundle() // Report compile errors .on('error', handleErrors) @@ -73,10 +76,15 @@ var browserifyTask = function (devMode) { } else { // Sort out shared dependencies. // b.require exposes modules externally - if (bundleConfig.require) b.require(bundleConfig.require); + if (bundleConfig.require) { + b.require(bundleConfig.require); + } + // b.external excludes modules from the bundle, and expects // they'll be available externally - if (bundleConfig.external) b.external(bundleConfig.external); + if (bundleConfig.external) { + b.external(bundleConfig.external); + } } return bundle(); @@ -88,7 +96,7 @@ var browserifyTask = function (devMode) { }; gulp.task('browserify', function () { - return browserifyTask() + return browserifyTask(); }); // Exporting the task so we can call it directly in our watch task, with the 'devMode' option diff --git a/package.json b/package.json index 9eac26233..1f6b04578 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ ] }, "devDependencies": { + "babelify": "^6.3.0", "browser-sync": "^2.8.2", "browserify": "^11.0.1", "chai": "^3.2.0", From 2959e95a72fc0414ea9b176db2a2b1dd07d08b38 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Thu, 3 Sep 2015 13:29:47 +0100 Subject: [PATCH 098/131] Use eslint instead of jshint and add tern config --- .eslintrc | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ .jshintrc | 15 --------------- .tern-project | 11 +++++++++++ package.json | 2 ++ 4 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 .eslintrc delete mode 100644 .jshintrc create mode 100644 .tern-project diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..e0e6f6891 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,52 @@ +parser: babel-eslint + +extends: 'eslint:recommended' + +env: + browser: true + es6: true + node: true + +rules: + comma-dangle: [2, always-multiline] + comma-style: [2, last] + curly: 2 + dot-notation: 2 + eol-last: 2 + eqeqeq: 2 + guard-for-in: 0 + indent: [2, 2] + linebreak-style: [2, unix] + no-caller: 2 + no-extra-bind: 2 + no-self-compare: 2 + no-sequences: 2 + no-shadow-restricted-names: 2 + no-trailing-spaces: 2 + no-unused-expressions: 2 + no-var: 2 + one-var: [2, never] + prefer-const: 2 + quotes: [2, single, avoid-escape] + semi: [2, always] + space-after-keywords: [2, always] + space-before-function-paren: [2, always] + strict: [2, never] + use-isnan: 2 + +ecma-features: + arrow-functions: true + block-bindings: true + classes: true + for-of: true + default-params: true + destructuring: true + generators: false + object-literal-computed-properties: true + object-literal-duplicate-properties: false + object-literal-shorthand-methods: true + oobject-literal-shorthand-properties: true + spread: true + super-in-functions: true + template-strings: true + jsx: false diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 8babddb6f..000000000 --- a/.jshintrc +++ /dev/null @@ -1,15 +0,0 @@ -{ - "strict" : true, - "validthis": true, - "browser" : true, - "node" : true, - "esnext" : true, - "jquery" : true, - "curly" : true, - "laxbreak" : true, - "newcap" : true, - "noarg" : true, - "undef" : true, - "unused" : true, - "trailing" : true -} diff --git a/.tern-project b/.tern-project new file mode 100644 index 000000000..60f5f9bb7 --- /dev/null +++ b/.tern-project @@ -0,0 +1,11 @@ +{ + "libs": [ + "browser" + ], + "loadEagerly": [ + "./src/**/*.js" + ], + "plugins": { + "node": {} + } +} diff --git a/package.json b/package.json index 1f6b04578..92e1761c0 100644 --- a/package.json +++ b/package.json @@ -44,10 +44,12 @@ ] }, "devDependencies": { + "babel-eslint": "^4.1.1", "babelify": "^6.3.0", "browser-sync": "^2.8.2", "browserify": "^11.0.1", "chai": "^3.2.0", + "eslint": "^1.3.1", "gulp": "^3.9.0", "gulp-changed": "^1.3.0", "gulp-filesize": "0.0.6", From 79fd36008a82c8df0b7ef50212d6e4ef4c77808d Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Thu, 3 Sep 2015 13:32:32 +0100 Subject: [PATCH 099/131] *: follow new eslint rules Mainly: - 2-space indent - const and let instead of var - 1 const/let per identifier - multiline trailing commas - no "use strict" since babel adds them Also makes use of destructuring and for-of in a few places. --- index.js | 6 +- src/InteractEvent.js | 378 +++---- src/Interactable.js | 1154 +++++++++++---------- src/Interaction.js | 2037 ++++++++++++++++++------------------- src/actions/base.js | 30 +- src/actions/drag.js | 305 +++--- src/actions/drop.js | 668 ++++++------ src/actions/gesture.js | 220 ++-- src/actions/resize.js | 708 +++++++------ src/autoScroll.js | 233 +++-- src/defaultOptions.js | 54 +- src/index.js | 2 - src/interact.js | 54 +- src/modifiers/index.js | 98 +- src/modifiers/restrict.js | 285 +++--- src/modifiers/snap.js | 409 ++++---- src/pointerEvents.js | 386 +++---- src/scope.js | 79 +- src/utils/arr.js | 28 +- src/utils/browser.js | 89 +- src/utils/domObjects.js | 7 +- src/utils/domUtils.js | 410 ++++---- src/utils/events.js | 530 +++++----- src/utils/extend.js | 10 +- src/utils/hypot.js | 4 +- src/utils/index.js | 96 +- src/utils/isType.js | 62 +- src/utils/isWindow.js | 4 +- src/utils/pointerUtils.js | 149 ++- src/utils/raf.js | 43 +- src/utils/signals.js | 70 +- src/utils/window.js | 48 +- 32 files changed, 4287 insertions(+), 4369 deletions(-) diff --git a/index.js b/index.js index 980413026..59d7f26c4 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,7 @@ -'use strict'; - // node entry point module.exports = function (window) { - require('./src/utils/window').init(window); + require('./src/utils/window').init(window); - return require('./src/index'); + return require('./src/index'); }; diff --git a/src/InteractEvent.js b/src/InteractEvent.js index aa87a8057..15673f2a1 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -1,209 +1,209 @@ -'use strict'; - -var scope = require('./scope'), - utils = require('./utils'), - signals = require('./utils/signals'), - modifiers = require('./modifiers'); +const scope = require('./scope'); +const utils = require('./utils'); +const signals = require('./utils/signals'); +const modifiers = require('./modifiers'); function InteractEvent (interaction, event, action, phase, element, related) { - var client, - page, - target = interaction.target, - deltaSource = (target && target.options || scope.defaultOptions).deltaSource, - sourceX = deltaSource + 'X', - sourceY = deltaSource + 'Y', - options = target? target.options: scope.defaultOptions, - origin = utils.getOriginXY(target, element), - starting = phase === 'start', - ending = phase === 'end', - coords = starting? interaction.startCoords : interaction.curCoords; - - element = element || interaction.element; - - page = utils.extend({}, coords.page); - client = utils.extend({}, coords.client); - - page.x -= origin.x; - page.y -= origin.y; - - client.x -= origin.x; - client.y -= origin.y; - - this.ctrlKey = event.ctrlKey; - this.altKey = event.altKey; - this.shiftKey = event.shiftKey; - this.metaKey = event.metaKey; - this.button = event.button; - this.buttons = event.buttons; - this.target = element; - this.t0 = interaction.downTimes[0]; - this.type = action + (phase || ''); - - this.interaction = interaction; - this.interactable = target; - - for (var i = 0; i < modifiers.names.length; i++) { - var modifierName = modifiers.names[i], - modifier = modifiers[modifierName]; - - this[modifierName] = modifier.modifyCoords(page, client, target, interaction.modifierStatuses[modifierName], action, phase); - } - - this.pageX = page.x; - this.pageY = page.y; - this.clientX = client.x; - this.clientY = client.y; - - this.x0 = interaction.startCoords.page.x - origin.x; - this.y0 = interaction.startCoords.page.y - origin.y; - this.clientX0 = interaction.startCoords.client.x - origin.x; - this.clientY0 = interaction.startCoords.client.y - origin.y; - - var inertiaStatus = interaction.inertiaStatus, - signalArg = { - interactEvent: this, - interaction: interaction, - event: event, - action: action, - phase: phase, - element: element, - related: related, - page: page, - client: client, - coords: coords, - starting: starting, - ending: ending, - deltaSource: deltaSource - }; - - if (inertiaStatus.active) { - this.detail = 'inertia'; + const target = interaction.target; + const deltaSource = (target && target.options || scope.defaultOptions).deltaSource; + const sourceX = deltaSource + 'X'; + const sourceY = deltaSource + 'Y'; + const options = target? target.options: scope.defaultOptions; + const origin = utils.getOriginXY(target, element); + const starting = phase === 'start'; + const ending = phase === 'end'; + const coords = starting? interaction.startCoords : interaction.curCoords; + + let client; + let page; + + element = element || interaction.element; + + page = utils.extend({}, coords.page); + client = utils.extend({}, coords.client); + + page.x -= origin.x; + page.y -= origin.y; + + client.x -= origin.x; + client.y -= origin.y; + + this.ctrlKey = event.ctrlKey; + this.altKey = event.altKey; + this.shiftKey = event.shiftKey; + this.metaKey = event.metaKey; + this.button = event.button; + this.buttons = event.buttons; + this.target = element; + this.t0 = interaction.downTimes[0]; + this.type = action + (phase || ''); + + this.interaction = interaction; + this.interactable = target; + + for (let i = 0; i < modifiers.names.length; i++) { + const modifierName = modifiers.names[i]; + const modifier = modifiers[modifierName]; + + this[modifierName] = modifier.modifyCoords(page, client, target, interaction.modifierStatuses[modifierName], action, phase); + } + + this.pageX = page.x; + this.pageY = page.y; + this.clientX = client.x; + this.clientY = client.y; + + this.x0 = interaction.startCoords.page.x - origin.x; + this.y0 = interaction.startCoords.page.y - origin.y; + this.clientX0 = interaction.startCoords.client.x - origin.x; + this.clientY0 = interaction.startCoords.client.y - origin.y; + + const inertiaStatus = interaction.inertiaStatus; + const signalArg = { + interactEvent: this, + interaction: interaction, + event: event, + action: action, + phase: phase, + element: element, + related: related, + page: page, + client: client, + coords: coords, + starting: starting, + ending: ending, + deltaSource: deltaSource, + }; + + if (inertiaStatus.active) { + this.detail = 'inertia'; + } + + if (related) { + this.relatedTarget = related; + } + + // end event dx, dy is difference between start and end points + if (ending) { + if (deltaSource === 'client') { + this.dx = client.x - interaction.startCoords.client.x; + this.dy = client.y - interaction.startCoords.client.y; } - - if (related) { - this.relatedTarget = related; + else { + this.dx = page.x - interaction.startCoords.page.x; + this.dy = page.y - interaction.startCoords.page.y; } - - // end event dx, dy is difference between start and end points - if (ending) { - if (deltaSource === 'client') { - this.dx = client.x - interaction.startCoords.client.x; - this.dy = client.y - interaction.startCoords.client.y; - } - else { - this.dx = page.x - interaction.startCoords.page.x; - this.dy = page.y - interaction.startCoords.page.y; - } + } + else if (starting) { + this.dx = 0; + this.dy = 0; + } + // copy properties from previousmove if starting inertia + else if (phase === 'inertiastart') { + this.dx = interaction.prevEvent.dx; + this.dy = interaction.prevEvent.dy; + } + else { + if (deltaSource === 'client') { + this.dx = client.x - interaction.prevEvent.clientX; + this.dy = client.y - interaction.prevEvent.clientY; } - else if (starting) { - this.dx = 0; - this.dy = 0; + else { + this.dx = page.x - interaction.prevEvent.pageX; + this.dy = page.y - interaction.prevEvent.pageY; } - // copy properties from previousmove if starting inertia - else if (phase === 'inertiastart') { - this.dx = interaction.prevEvent.dx; - this.dy = interaction.prevEvent.dy; + } + if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' + && !inertiaStatus.active + && options[action].inertia && options[action].inertia.zeroResumeDelta) { + + inertiaStatus.resumeDx += this.dx; + inertiaStatus.resumeDy += this.dy; + + this.dx = this.dy = 0; + } + + signals.fire('interactevent-set-delta', signalArg); + + if (starting) { + this.timeStamp = interaction.downTimes[0]; + this.dt = 0; + this.duration = 0; + this.speed = 0; + this.velocityX = 0; + this.velocityY = 0; + } + else if (phase === 'inertiastart') { + this.timeStamp = interaction.prevEvent.timeStamp; + this.dt = interaction.prevEvent.dt; + this.duration = interaction.prevEvent.duration; + this.speed = interaction.prevEvent.speed; + this.velocityX = interaction.prevEvent.velocityX; + this.velocityY = interaction.prevEvent.velocityY; + } + else { + this.timeStamp = new Date().getTime(); + this.dt = this.timeStamp - interaction.prevEvent.timeStamp; + this.duration = this.timeStamp - interaction.downTimes[0]; + + if (event instanceof InteractEvent) { + const dx = this[sourceX] - interaction.prevEvent[sourceX]; + const dy = this[sourceY] - interaction.prevEvent[sourceY]; + const dt = this.dt / 1000; + + this.speed = utils.hypot(dx, dy) / dt; + this.velocityX = dx / dt; + this.velocityY = dy / dt; } + // if normal move or end event, use previous user event coords else { - if (deltaSource === 'client') { - this.dx = client.x - interaction.prevEvent.clientX; - this.dy = client.y - interaction.prevEvent.clientY; - } - else { - this.dx = page.x - interaction.prevEvent.pageX; - this.dy = page.y - interaction.prevEvent.pageY; - } + // speed and velocity in pixels per second + this.speed = interaction.pointerDelta[deltaSource].speed; + this.velocityX = interaction.pointerDelta[deltaSource].vx; + this.velocityY = interaction.pointerDelta[deltaSource].vy; } - if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' - && !inertiaStatus.active - && options[action].inertia && options[action].inertia.zeroResumeDelta) { + } - inertiaStatus.resumeDx += this.dx; - inertiaStatus.resumeDy += this.dy; + if ((ending || phase === 'inertiastart') + && interaction.prevEvent.speed > 600 + && this.timeStamp - interaction.prevEvent.timeStamp < 150) { - this.dx = this.dy = 0; - } - - signals.fire('interactevent-set-delta', signalArg); + let angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI; + const overlap = 22.5; - if (starting) { - this.timeStamp = interaction.downTimes[0]; - this.dt = 0; - this.duration = 0; - this.speed = 0; - this.velocityX = 0; - this.velocityY = 0; - } - else if (phase === 'inertiastart') { - this.timeStamp = interaction.prevEvent.timeStamp; - this.dt = interaction.prevEvent.dt; - this.duration = interaction.prevEvent.duration; - this.speed = interaction.prevEvent.speed; - this.velocityX = interaction.prevEvent.velocityX; - this.velocityY = interaction.prevEvent.velocityY; - } - else { - this.timeStamp = new Date().getTime(); - this.dt = this.timeStamp - interaction.prevEvent.timeStamp; - this.duration = this.timeStamp - interaction.downTimes[0]; - - if (event instanceof InteractEvent) { - var dx = this[sourceX] - interaction.prevEvent[sourceX], - dy = this[sourceY] - interaction.prevEvent[sourceY], - dt = this.dt / 1000; - - this.speed = utils.hypot(dx, dy) / dt; - this.velocityX = dx / dt; - this.velocityY = dy / dt; - } - // if normal move or end event, use previous user event coords - else { - // speed and velocity in pixels per second - this.speed = interaction.pointerDelta[deltaSource].speed; - this.velocityX = interaction.pointerDelta[deltaSource].vx; - this.velocityY = interaction.pointerDelta[deltaSource].vy; - } + if (angle < 0) { + angle += 360; } - if ((ending || phase === 'inertiastart') - && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) { - - var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI, - overlap = 22.5; - - if (angle < 0) { - angle += 360; - } - - var left = 135 - overlap <= angle && angle < 225 + overlap, - up = 225 - overlap <= angle && angle < 315 + overlap, - - right = !left && (315 - overlap <= angle || angle < 45 + overlap), - down = !up && 45 - overlap <= angle && angle < 135 + overlap; - - this.swipe = { - up : up, - down : down, - left : left, - right: right, - angle: angle, - speed: interaction.prevEvent.speed, - velocity: { - x: interaction.prevEvent.velocityX, - y: interaction.prevEvent.velocityY - } - }; - } + const left = 135 - overlap <= angle && angle < 225 + overlap; + const up = 225 - overlap <= angle && angle < 315 + overlap; + + const right = !left && (315 - overlap <= angle || angle < 45 + overlap); + const down = !up && 45 - overlap <= angle && angle < 135 + overlap; + + this.swipe = { + up : up, + down : down, + left : left, + right: right, + angle: angle, + speed: interaction.prevEvent.speed, + velocity: { + x: interaction.prevEvent.velocityX, + y: interaction.prevEvent.velocityY, + }, + }; + } } InteractEvent.prototype = { - preventDefault: utils.blank, - stopImmediatePropagation: function () { - this.immediatePropagationStopped = this.propagationStopped = true; - }, - stopPropagation: function () { - this.propagationStopped = true; - } + preventDefault: utils.blank, + stopImmediatePropagation: function () { + this.immediatePropagationStopped = this.propagationStopped = true; + }, + stopPropagation: function () { + this.propagationStopped = true; + }, }; module.exports = InteractEvent; diff --git a/src/Interactable.js b/src/Interactable.js index 480585253..ba1ba9bf1 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -1,10 +1,8 @@ -'use strict'; - -var scope = require('./scope'), - utils = require('./utils'), - events = require('./utils/events'), - signals = require('./utils/signals'), - actions = require('./actions/base'); +const scope = require('./scope'); +const utils = require('./utils'); +const events = require('./utils/events'); +const signals = require('./utils/signals'); +const actions = require('./actions/base'); /*\ * Interactable @@ -13,656 +11,652 @@ var scope = require('./scope'), * Object type returned by @interact \*/ function Interactable (element, options) { - this._element = element; - this._iEvents = this._iEvents || {}; + this._element = element; + this._iEvents = this._iEvents || {}; - var _window; + let _window; - if (utils.trySelector(element)) { - this.selector = element; + if (utils.trySelector(element)) { + this.selector = element; - var context = options && options.context; + const context = options && options.context; - _window = context? scope.getWindow(context) : scope.window; + _window = context? scope.getWindow(context) : scope.window; - if (context && (_window.Node - ? context instanceof _window.Node - : (utils.isElement(context) || context === _window.document))) { + if (context && (_window.Node + ? context instanceof _window.Node + : (utils.isElement(context) || context === _window.document))) { - this._context = context; - } - } - else { - _window = scope.getWindow(element); + this._context = context; } + } + else { + _window = scope.getWindow(element); + } + + this._doc = _window.document; + + signals.fire('interactable-new', { + interactable: this, + element: element, + options: options, + win: _window, + }); + + if (this._doc !== scope.document) { + signals.fire('listen-to-document', { + doc: this._doc, + win: _window, + }); + } - this._doc = _window.document; + scope.interactables.push(this); - signals.fire('interactable-new', { - interactable: this, - element: element, - options: options, - win: _window - }); + this.set(options); +} - if (this._doc !== scope.document) { - signals.fire('listen-to-document', { - doc: this._doc, - win: _window - }); +Interactable.prototype = { + setOnEvents: function (action, phases) { + const onAction = 'on' + action; + + if (utils.isFunction(phases.onstart) ) { this[onAction + 'start' ] = phases.onstart ; } + if (utils.isFunction(phases.onmove) ) { this[onAction + 'move' ] = phases.onmove ; } + if (utils.isFunction(phases.onend) ) { this[onAction + 'end' ] = phases.onend ; } + if (utils.isFunction(phases.oninertiastart)) { this[onAction + 'inertiastart' ] = phases.oninertiastart ; } + + return this; + }, + + setPerAction: function (action, options) { + // for all the default per-action options + for (const option in options) { + // if this option exists for this action + if (option in scope.defaultOptions[action]) { + // if the option in the options arg is an object value + if (utils.isObject(options[option])) { + // duplicate the object + this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); + + if (utils.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { + this.options[action][option].enabled = options[option].enabled === false? false : true; + } + } + else if (utils.isBool(options[option]) && utils.isObject(scope.defaultOptions.perAction[option])) { + this.options[action][option].enabled = options[option]; + } + else if (options[option] !== undefined) { + // or if it's not undefined, do a plain assignment + this.options[action][option] = options[option]; + } + } } + }, - scope.interactables.push(this); + getAction: function (pointer, event, interaction, element) { + const action = this.defaultActionChecker(pointer, event, interaction, element); - this.set(options); -} + if (this.options.actionChecker) { + return this.options.actionChecker(pointer, event, action, this, element, interaction); + } -Interactable.prototype = { - setOnEvents: function (action, phases) { - var onAction = 'on' + action; - - if (utils.isFunction(phases.onstart) ) { this[onAction + 'start' ] = phases.onstart ; } - if (utils.isFunction(phases.onmove) ) { this[onAction + 'move' ] = phases.onmove ; } - if (utils.isFunction(phases.onend) ) { this[onAction + 'end' ] = phases.onend ; } - if (utils.isFunction(phases.oninertiastart)) { this[onAction + 'inertiastart' ] = phases.oninertiastart ; } - - return this; - }, - - setPerAction: function (action, options) { - // for all the default per-action options - for (var option in options) { - // if this option exists for this action - if (option in scope.defaultOptions[action]) { - // if the option in the options arg is an object value - if (utils.isObject(options[option])) { - // duplicate the object - this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); - - if (utils.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { - this.options[action][option].enabled = options[option].enabled === false? false : true; - } - } - else if (utils.isBool(options[option]) && utils.isObject(scope.defaultOptions.perAction[option])) { - this.options[action][option].enabled = options[option]; - } - else if (options[option] !== undefined) { - // or if it's not undefined, do a plain assignment - this.options[action][option] = options[option]; - } - } - } - }, + return action; + }, + + defaultActionChecker: actions.defaultChecker, + + /*\ + * Interactable.actionChecker + [ method ] + * + * Gets or sets the function used to check action to be performed on + * pointerDown + * + - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. + = (Function | Interactable) The checker function or this Interactable + * + | interact('.resize-drag') + | .resizable(true) + | .draggable(true) + | .actionChecker(function (pointer, event, action, interactable, element, interaction) { + | + | if (interact.matchesSelector(event.target, '.drag-handle') { + | // force drag with handle target + | action.name = drag; + | } + | else { + | // resize from the top and right edges + | action.name = 'resize'; + | action.edges = { top: true, right: true }; + | } + | + | return action; + | }); + \*/ + actionChecker: function (checker) { + if (utils.isFunction(checker)) { + this.options.actionChecker = checker; + + return this; + } - getAction: function (pointer, event, interaction, element) { - var action = this.defaultActionChecker(pointer, event, interaction, element); + if (checker === null) { + delete this.options.actionChecker; - if (this.options.actionChecker) { - return this.options.actionChecker(pointer, event, action, this, element, interaction); - } + return this; + } - return action; - }, - - defaultActionChecker: actions.defaultChecker, - - /*\ - * Interactable.actionChecker - [ method ] - * - * Gets or sets the function used to check action to be performed on - * pointerDown - * - - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props. - = (Function | Interactable) The checker function or this Interactable - * - | interact('.resize-drag') - | .resizable(true) - | .draggable(true) - | .actionChecker(function (pointer, event, action, interactable, element, interaction) { - | - | if (interact.matchesSelector(event.target, '.drag-handle') { - | // force drag with handle target - | action.name = drag; - | } - | else { - | // resize from the top and right edges - | action.name = 'resize'; - | action.edges = { top: true, right: true }; - | } - | - | return action; - | }); - \*/ - actionChecker: function (checker) { - if (utils.isFunction(checker)) { - this.options.actionChecker = checker; - - return this; - } + return this.options.actionChecker; + }, + + /*\ + * Interactable.getRect + [ method ] + * + * The default function to get an Interactables bounding rect. Can be + * overridden using @Interactable.rectChecker. + * + - element (Element) #optional The element to measure. + = (object) The object's bounding rectangle. + o { + o top : 0, + o left : 0, + o bottom: 0, + o right : 0, + o width : 0, + o height: 0 + o } + \*/ + getRect: function rectCheck (element) { + element = element || this._element; + + if (this.selector && !(utils.isElement(element))) { + element = this._context.querySelector(this.selector); + } - if (checker === null) { - delete this.options.actionChecker; + return utils.getElementRect(element); + }, + + /*\ + * Interactable.rectChecker + [ method ] + * + * Returns or sets the function used to calculate the interactable's + * element's rectangle + * + - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect + = (function | object) The checker function or this Interactable + \*/ + rectChecker: function (checker) { + if (utils.isFunction(checker)) { + this.getRect = checker; + + return this; + } - return this; - } + if (checker === null) { + delete this.options.getRect; - return this.options.actionChecker; - }, - - /*\ - * Interactable.getRect - [ method ] - * - * The default function to get an Interactables bounding rect. Can be - * overridden using @Interactable.rectChecker. - * - - element (Element) #optional The element to measure. - = (object) The object's bounding rectangle. - o { - o top : 0, - o left : 0, - o bottom: 0, - o right : 0, - o width : 0, - o height: 0 - o } - \*/ - getRect: function rectCheck (element) { - element = element || this._element; - - if (this.selector && !(utils.isElement(element))) { - element = this._context.querySelector(this.selector); - } + return this; + } - return utils.getElementRect(element); - }, - - /*\ - * Interactable.rectChecker - [ method ] - * - * Returns or sets the function used to calculate the interactable's - * element's rectangle - * - - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect - = (function | object) The checker function or this Interactable - \*/ - rectChecker: function (checker) { - if (utils.isFunction(checker)) { - this.getRect = checker; - - return this; - } + return this.getRect; + }, + + /*\ + * Interactable.styleCursor + [ method ] + * + * Returns or sets whether the action that would be performed when the + * mouse on the element are checked on `mousemove` so that the cursor + * may be styled appropriately + * + - newValue (boolean) #optional + = (boolean | Interactable) The current setting or this Interactable + \*/ + styleCursor: function (newValue) { + if (utils.isBool(newValue)) { + this.options.styleCursor = newValue; + + return this; + } - if (checker === null) { - delete this.options.getRect; + if (newValue === null) { + delete this.options.styleCursor; - return this; - } + return this; + } - return this.getRect; - }, - - /*\ - * Interactable.styleCursor - [ method ] - * - * Returns or sets whether the action that would be performed when the - * mouse on the element are checked on `mousemove` so that the cursor - * may be styled appropriately - * - - newValue (boolean) #optional - = (boolean | Interactable) The current setting or this Interactable - \*/ - styleCursor: function (newValue) { - if (utils.isBool(newValue)) { - this.options.styleCursor = newValue; - - return this; - } + return this.options.styleCursor; + }, + + /*\ + * Interactable.preventDefault + [ method ] + * + * Returns or sets whether to prevent the browser's default behaviour + * in response to pointer events. Can be set to: + * - `'always'` to always prevent + * - `'never'` to never prevent + * - `'auto'` to let interact.js try to determine what would be best + * + - newValue (string) #optional `true`, `false` or `'auto'` + = (string | Interactable) The current setting or this Interactable + \*/ + preventDefault: function (newValue) { + if (/^(always|never|auto)$/.test(newValue)) { + this.options.preventDefault = newValue; + return this; + } - if (newValue === null) { - delete this.options.styleCursor; + if (utils.isBool(newValue)) { + this.options.preventDefault = newValue? 'always' : 'never'; + return this; + } - return this; - } + return this.options.preventDefault; + }, + + /*\ + * Interactable.origin + [ method ] + * + * Gets or sets the origin of the Interactable's element. The x and y + * of the origin will be subtracted from action event coordinates. + * + - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector + * OR + - origin (Element) #optional An HTML or SVG Element whose rect will be used + ** + = (object) The current origin or this Interactable + \*/ + origin: function (newValue) { + if (utils.trySelector(newValue)) { + this.options.origin = newValue; + return this; + } + else if (utils.isObject(newValue)) { + this.options.origin = newValue; + return this; + } - return this.options.styleCursor; - }, - - /*\ - * Interactable.preventDefault - [ method ] - * - * Returns or sets whether to prevent the browser's default behaviour - * in response to pointer events. Can be set to: - * - `'always'` to always prevent - * - `'never'` to never prevent - * - `'auto'` to let interact.js try to determine what would be best - * - - newValue (string) #optional `true`, `false` or `'auto'` - = (string | Interactable) The current setting or this Interactable - \*/ - preventDefault: function (newValue) { - if (/^(always|never|auto)$/.test(newValue)) { - this.options.preventDefault = newValue; - return this; - } + return this.options.origin; + }, + + /*\ + * Interactable.deltaSource + [ method ] + * + * Returns or sets the mouse coordinate types used to calculate the + * movement of the pointer. + * + - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work + = (string | object) The current deltaSource or this Interactable + \*/ + deltaSource: function (newValue) { + if (newValue === 'page' || newValue === 'client') { + this.options.deltaSource = newValue; + + return this; + } - if (utils.isBool(newValue)) { - this.options.preventDefault = newValue? 'always' : 'never'; - return this; - } + return this.options.deltaSource; + }, + + /*\ + * Interactable.context + [ method ] + * + * Gets the selector context Node of the Interactable. The default is `window.document`. + * + = (Node) The context Node of this Interactable + ** + \*/ + context: function () { + return this._context; + }, + + _context: scope.document, + + /*\ + * Interactable.ignoreFrom + [ method ] + * + * If the target of the `mousedown`, `pointerdown` or `touchstart` + * event or any of it's parents match the given CSS selector or + * Element, no drag/resize/gesture is started. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements + = (string | Element | object) The current ignoreFrom value or this Interactable + ** + | interact(element, { ignoreFrom: document.getElementById('no-action') }); + | // or + | interact(element).ignoreFrom('input, textarea, a'); + \*/ + ignoreFrom: function (newValue) { + if (utils.trySelector(newValue)) { // CSS selector to match event.target + this.options.ignoreFrom = newValue; + return this; + } - return this.options.preventDefault; - }, - - /*\ - * Interactable.origin - [ method ] - * - * Gets or sets the origin of the Interactable's element. The x and y - * of the origin will be subtracted from action event coordinates. - * - - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector - * OR - - origin (Element) #optional An HTML or SVG Element whose rect will be used - ** - = (object) The current origin or this Interactable - \*/ - origin: function (newValue) { - if (utils.trySelector(newValue)) { - this.options.origin = newValue; - return this; - } - else if (utils.isObject(newValue)) { - this.options.origin = newValue; - return this; - } + if (utils.isElement(newValue)) { // specific element + this.options.ignoreFrom = newValue; + return this; + } - return this.options.origin; - }, - - /*\ - * Interactable.deltaSource - [ method ] - * - * Returns or sets the mouse coordinate types used to calculate the - * movement of the pointer. - * - - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work - = (string | object) The current deltaSource or this Interactable - \*/ - deltaSource: function (newValue) { - if (newValue === 'page' || newValue === 'client') { - this.options.deltaSource = newValue; - - return this; - } + return this.options.ignoreFrom; + }, + + /*\ + * Interactable.allowFrom + [ method ] + * + * A drag/resize/gesture is started only If the target of the + * `mousedown`, `pointerdown` or `touchstart` event or any of it's + * parents match the given CSS selector or Element. + * + - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element + = (string | Element | object) The current allowFrom value or this Interactable + ** + | interact(element, { allowFrom: document.getElementById('drag-handle') }); + | // or + | interact(element).allowFrom('.handle'); + \*/ + allowFrom: function (newValue) { + if (utils.trySelector(newValue)) { // CSS selector to match event.target + this.options.allowFrom = newValue; + return this; + } - return this.options.deltaSource; - }, - - /*\ - * Interactable.context - [ method ] - * - * Gets the selector context Node of the Interactable. The default is `window.document`. - * - = (Node) The context Node of this Interactable - ** - \*/ - context: function () { - return this._context; - }, - - _context: scope.document, - - /*\ - * Interactable.ignoreFrom - [ method ] - * - * If the target of the `mousedown`, `pointerdown` or `touchstart` - * event or any of it's parents match the given CSS selector or - * Element, no drag/resize/gesture is started. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements - = (string | Element | object) The current ignoreFrom value or this Interactable - ** - | interact(element, { ignoreFrom: document.getElementById('no-action') }); - | // or - | interact(element).ignoreFrom('input, textarea, a'); - \*/ - ignoreFrom: function (newValue) { - if (utils.trySelector(newValue)) { // CSS selector to match event.target - this.options.ignoreFrom = newValue; - return this; - } + if (utils.isElement(newValue)) { // specific element + this.options.allowFrom = newValue; + return this; + } - if (utils.isElement(newValue)) { // specific element - this.options.ignoreFrom = newValue; - return this; - } + return this.options.allowFrom; + }, + + /*\ + * Interactable.element + [ method ] + * + * If this is not a selector Interactable, it returns the element this + * interactable represents + * + = (Element) HTML / SVG Element + \*/ + element: function () { + return this._element; + }, + + /*\ + * Interactable.fire + [ method ] + * + * Calls listeners for the given InteractEvent type bound globally + * and directly to this Interactable + * + - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable + = (Interactable) this Interactable + \*/ + fire: function (iEvent) { + if (!(iEvent && iEvent.type) || !utils.contains(scope.eventTypes, iEvent.type)) { + return this; + } - return this.options.ignoreFrom; - }, - - /*\ - * Interactable.allowFrom - [ method ] - * - * A drag/resize/gesture is started only If the target of the - * `mousedown`, `pointerdown` or `touchstart` event or any of it's - * parents match the given CSS selector or Element. - * - - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element - = (string | Element | object) The current allowFrom value or this Interactable - ** - | interact(element, { allowFrom: document.getElementById('drag-handle') }); - | // or - | interact(element).allowFrom('.handle'); - \*/ - allowFrom: function (newValue) { - if (utils.trySelector(newValue)) { // CSS selector to match event.target - this.options.allowFrom = newValue; - return this; - } + let listeners; + let i; + let len; + const onEvent = 'on' + iEvent.type; - if (utils.isElement(newValue)) { // specific element - this.options.allowFrom = newValue; - return this; - } + // Interactable#on() listeners + if (iEvent.type in this._iEvents) { + listeners = this._iEvents[iEvent.type]; - return this.options.allowFrom; - }, - - /*\ - * Interactable.element - [ method ] - * - * If this is not a selector Interactable, it returns the element this - * interactable represents - * - = (Element) HTML / SVG Element - \*/ - element: function () { - return this._element; - }, - - /*\ - * Interactable.fire - [ method ] - * - * Calls listeners for the given InteractEvent type bound globally - * and directly to this Interactable - * - - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable - = (Interactable) this Interactable - \*/ - fire: function (iEvent) { - if (!(iEvent && iEvent.type) || !utils.contains(scope.eventTypes, iEvent.type)) { - return this; - } + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + listeners[i](iEvent); + } + } - var listeners, - i, - len, - onEvent = 'on' + iEvent.type, - funcName = ''; + // interactable.onevent listener + if (utils.isFunction(this[onEvent])) { + this[onEvent](iEvent); + } - // Interactable#on() listeners - if (iEvent.type in this._iEvents) { - listeners = this._iEvents[iEvent.type]; + // interact.on() listeners + if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } + for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + listeners[i](iEvent); + } + } - // interactable.onevent listener - if (utils.isFunction(this[onEvent])) { - funcName = this[onEvent].name; - this[onEvent](iEvent); - } + return this; + }, + + /*\ + * Interactable.on + [ method ] + * + * Binds a listener for an InteractEvent or DOM event. + * + - eventType (string | array | object) The types of events to listen for + - listener (function) The function event (s) + - useCapture (boolean) #optional useCapture flag for addEventListener + = (object) This Interactable + \*/ + on: function (eventType, listener, useCapture) { + let i; + + if (utils.isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } - // interact.on() listeners - if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { + if (utils.isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.on(eventType[i], listener, useCapture); + } - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { - funcName = listeners[i].name; - listeners[i](iEvent); - } - } + return this; + } - return this; - }, - - /*\ - * Interactable.on - [ method ] - * - * Binds a listener for an InteractEvent or DOM event. - * - - eventType (string | array | object) The types of events to listen for - - listener (function) The function to be called on the given event(s) - - useCapture (boolean) #optional useCapture flag for addEventListener - = (object) This Interactable - \*/ - on: function (eventType, listener, useCapture) { - var i; - - if (utils.isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } + if (utils.isObject(eventType)) { + for (const prop in eventType) { + this.on(prop, eventType[prop], listener); + } - if (utils.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.on(eventType[i], listener, useCapture); - } + return this; + } - return this; - } + if (eventType === 'wheel') { + eventType = scope.wheelEvent; + } - if (utils.isObject(eventType)) { - for (var prop in eventType) { - this.on(prop, eventType[prop], listener); - } + // convert to boolean + useCapture = useCapture? true: false; + + if (utils.contains(scope.eventTypes, eventType)) { + // if this type of event was never bound to this Interactable + if (!(eventType in this._iEvents)) { + this._iEvents[eventType] = [listener]; + } + else { + this._iEvents[eventType].push(listener); + } + } + // delegated event for selector + else if (this.selector) { + events.addDelegate(this.selector, this._context, eventType, listener, useCapture); + } + else { + events.add(this._element, eventType, listener, useCapture); + } - return this; - } + return this; + }, + + /*\ + * Interactable.off + [ method ] + * + * Removes an InteractEvent or DOM event listener + * + - eventType (string | array | object) The types of events that were listened for + - listener (function) The listener function to be removed + - useCapture (boolean) #optional useCapture flag for removeEventListener + = (object) This Interactable + \*/ + off: function (eventType, listener, useCapture) { + let i; + + if (utils.isString(eventType) && eventType.search(' ') !== -1) { + eventType = eventType.trim().split(/ +/); + } - if (eventType === 'wheel') { - eventType = scope.wheelEvent; - } + if (utils.isArray(eventType)) { + for (i = 0; i < eventType.length; i++) { + this.off(eventType[i], listener, useCapture); + } - // convert to boolean - useCapture = useCapture? true: false; + return this; + } - if (utils.contains(scope.eventTypes, eventType)) { - // if this type of event was never bound to this Interactable - if (!(eventType in this._iEvents)) { - this._iEvents[eventType] = [listener]; - } - else { - this._iEvents[eventType].push(listener); - } - } - // delegated event for selector - else if (this.selector) { - events.addDelegate(this.selector, this._context, eventType, listener, useCapture); - } - else { - events.add(this._element, eventType, listener, useCapture); - } + if (utils.isObject(eventType)) { + for (const prop in eventType) { + this.off(prop, eventType[prop], listener); + } - return this; - }, - - /*\ - * Interactable.off - [ method ] - * - * Removes an InteractEvent or DOM event listener - * - - eventType (string | array | object) The types of events that were listened for - - listener (function) The listener function to be removed - - useCapture (boolean) #optional useCapture flag for removeEventListener - = (object) This Interactable - \*/ - off: function (eventType, listener, useCapture) { - var i; - - if (utils.isString(eventType) && eventType.search(' ') !== -1) { - eventType = eventType.trim().split(/ +/); - } + return this; + } - if (utils.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { - this.off(eventType[i], listener, useCapture); - } + let eventList; + let index = -1; - return this; - } + // convert to boolean + useCapture = useCapture? true: false; - if (utils.isObject(eventType)) { - for (var prop in eventType) { - this.off(prop, eventType[prop], listener); - } + if (eventType === 'wheel') { + eventType = scope.wheelEvent; + } - return this; - } + // if it is an action event type + if (utils.contains(scope.eventTypes, eventType)) { + eventList = this._iEvents[eventType]; - var eventList, - index = -1; + if (eventList && (index = utils.indexOf(eventList, listener)) !== -1) { + this._iEvents[eventType].splice(index, 1); + } + } + // delegated event + else if (this.selector) { + events.removeDelegate(this.selector, this._context, eventType, listener, useCapture); + } + // remove listener from this Interatable's element + else { + events.remove(this._element, eventType, listener, useCapture); + } - // convert to boolean - useCapture = useCapture? true: false; + return this; + }, + + /*\ + * Interactable.set + [ method ] + * + * Reset the options of this Interactable + - options (object) The new settings to apply + = (object) This Interactable + \*/ + set: function (options) { + if (!utils.isObject(options)) { + options = {}; + } - if (eventType === 'wheel') { - eventType = scope.wheelEvent; - } + this.options = utils.extend({}, scope.defaultOptions.base); - // if it is an action event type - if (utils.contains(scope.eventTypes, eventType)) { - eventList = this._iEvents[eventType]; + const perActions = utils.extend({}, scope.defaultOptions.perAction); - if (eventList && (index = utils.indexOf(eventList, listener)) !== -1) { - this._iEvents[eventType].splice(index, 1); - } - } - // delegated event - else if (this.selector) { - events.removeDelegate(this.selector, this._context, eventType, listener, useCapture); - } - // remove listener from this Interatable's element - else { - events.remove(this._element, eventType, listener, useCapture); - } + for (const actionName in actions.methodDict) { + const methodName = actions.methodDict[actionName]; - return this; - }, - - /*\ - * Interactable.set - [ method ] - * - * Reset the options of this Interactable - - options (object) The new settings to apply - = (object) This Interactable - \*/ - set: function (options) { - if (!utils.isObject(options)) { - options = {}; - } + this.options[actionName] = utils.extend({}, scope.defaultOptions[actionName]); - this.options = utils.extend({}, scope.defaultOptions.base); + this.setPerAction(actionName, perActions); - var perActions = utils.extend(utils.extend({}, scope.defaultOptions.perAction), options[actionName] || {}); + this[methodName](options[actionName]); + } - for (var actionName in actions.methodDict) { - var methodName = actions.methodDict[actionName]; + const settings = [ + 'accept', 'actionChecker', 'allowFrom', 'deltaSource', + 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', + 'rectChecker', + ]; - this.options[actionName] = utils.extend({}, scope.defaultOptions[actionName]); + for (let i = 0, len = settings.length; i < len; i++) { + const setting = settings[i]; - this.setPerAction(actionName, perActions); + this.options[setting] = scope.defaultOptions.base[setting]; - this[methodName](options[actionName]); - } + if (setting in options) { + this[setting](options[setting]); + } + } - var settings = [ - 'accept', 'actionChecker', 'allowFrom', 'deltaSource', - 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault', - 'rectChecker' - ]; + return this; + }, + + /*\ + * Interactable.unset + [ method ] + * + * Remove this interactable from the list of interactables and remove + * it's action capabilities and event listeners + * + = (object) @interact + \*/ + unset: function () { + events.remove(this._element, 'all'); + + if (!utils.isString(this.selector)) { + events.remove(this, 'all'); + if (this.options.styleCursor) { + this._element.style.cursor = ''; + } + } + else { + // remove delegated events + for (const type in events.delegatedEvents) { + const delegated = events.delegatedEvents[type]; - for (var i = 0, len = settings.length; i < len; i++) { - var setting = settings[i]; + for (let i = 0; i < delegated.selectors.length; i++) { + if (delegated.selectors[i] === this.selector + && delegated.contexts[i] === this._context) { - this.options[setting] = scope.defaultOptions.base[setting]; + delegated.selectors.splice(i, 1); + delegated.contexts .splice(i, 1); + delegated.listeners.splice(i, 1); - if (setting in options) { - this[setting](options[setting]); + // remove the arrays if they are empty + if (!delegated.selectors.length) { + delegated[type] = null; } - } + } - return this; - }, - - /*\ - * Interactable.unset - [ method ] - * - * Remove this interactable from the list of interactables and remove - * it's action capabilities and event listeners - * - = (object) @interact - \*/ - unset: function () { - events.remove(this._element, 'all'); - - if (!utils.isString(this.selector)) { - events.remove(this, 'all'); - if (this.options.styleCursor) { - this._element.style.cursor = ''; - } - } - else { - // remove delegated events - for (var type in events.delegatedEvents) { - var delegated = events.delegatedEvents[type]; - - for (var i = 0; i < delegated.selectors.length; i++) { - if (delegated.selectors[i] === this.selector - && delegated.contexts[i] === this._context) { - - delegated.selectors.splice(i, 1); - delegated.contexts .splice(i, 1); - delegated.listeners.splice(i, 1); - - // remove the arrays if they are empty - if (!delegated.selectors.length) { - delegated[type] = null; - } - } - - events.remove(this._context, type, events.delegateListener); - events.remove(this._context, type, events.delegateUseCapture, true); - - break; - } - } + events.remove(this._context, type, events.delegateListener); + events.remove(this._context, type, events.delegateUseCapture, true); + + break; } + } + } - signals.fire('interactable-unset', { interactable: this }); + signals.fire('interactable-unset', { interactable: this }); - this.dropzone(false); + this.dropzone(false); - scope.interactables.splice(utils.indexOf(scope.interactables, this), 1); + scope.interactables.splice(utils.indexOf(scope.interactables, this), 1); - return scope.interact; - } + return scope.interact; + }, }; module.exports = Interactable; diff --git a/src/Interaction.js b/src/Interaction.js index 790534fb4..9f3fc15b7 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -1,1279 +1,1268 @@ -'use strict'; - -var scope = require('./scope'), - utils = require('./utils'), - animationFrame = utils.raf, - InteractEvent = require('./InteractEvent'), - events = require('./utils/events'), - signals = require('./utils/signals'), - browser = require('./utils/browser'), - actions = require('./actions/base'), - modifiers = require('./modifiers/'), - methodNames = [ - 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', - 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', - 'addPointer', 'removePointer', 'recordPointer' - ], - listeners = {}; +const scope = require('./scope'); +const utils = require('./utils'); +const animationFrame = utils.raf; +const InteractEvent = require('./InteractEvent'); +const events = require('./utils/events'); +const signals = require('./utils/signals'); +const browser = require('./utils/browser'); +const actions = require('./actions/base'); +const modifiers = require('./modifiers/'); +const methodNames = [ + 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', + 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', + 'addPointer', 'removePointer', 'recordPointer', +]; +const listeners = {}; function Interaction () { - this.target = null; // current interactable being interacted with - this.element = null; // the target element of the interactable - this.dropTarget = null; // the dropzone a drag target might be dropped into - this.dropElement = null; // the element at the time of checking - this.prevDropTarget = null; // the dropzone that was recently dragged away from - this.prevDropElement = null; // the element at the time of checking - - this.prepared = { // action that's ready to be fired on next move event - name : null, - axis : null, - edges: null - }; - - this.matches = []; // all selectors that are matched by target element - this.matchElements = []; // corresponding elements - - this.inertiaStatus = { - active : false, - smoothEnd : false, - - startEvent: null, - upCoords: {}, - - xe: 0, ye: 0, - sx: 0, sy: 0, - - t0: 0, - vx0: 0, vys: 0, - duration: 0, - - resumeDx: 0, - resumeDy: 0, - - lambda_v0: 0, - one_ve_v0: 0, - i : null - }; - - if (utils.isFunction(Function.prototype.bind)) { - this.boundInertiaFrame = this.inertiaFrame.bind(this); - this.boundSmoothEndFrame = this.smoothEndFrame.bind(this); - } - else { - var that = this; - - this.boundInertiaFrame = function () { return that.inertiaFrame(); }; - this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); }; - } - - this.activeDrops = { - dropzones: [], // the dropzones that are mentioned below - elements : [], // elements of dropzones that accept the target draggable - rects : [] // the rects of the elements mentioned above - }; - - // keep track of added pointers - this.pointers = []; - this.pointerIds = []; - this.downTargets = []; - this.downTimes = []; - this.holdTimers = []; - - // Previous native pointer move event coordinates - this.prevCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - // current native pointer move event coordinates - this.curCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - - // Starting InteractEvent pointer coordinates - this.startCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0 - }; - - // Change in coordinates and time of the pointer - this.pointerDelta = { - page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - timeStamp: 0 - }; - - this.downEvent = null; // pointerdown/mousedown/touchstart event - this.downPointer = {}; - - this._eventTarget = null; - this._curEventTarget = null; - - this.prevEvent = null; // previous action event - this.tapTime = 0; // time of the most recent tap event - this.prevTap = null; - - this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.modifierOffsets = {}; - this.modifierStatuses = modifiers.resetStatuses({}); - - this.gesture = { - start: { x: 0, y: 0 }, - - startDistance: 0, // distance between two touches of touchStart - prevDistance : 0, - distance : 0, - - scale: 1, // gesture.distance / gesture.startDistance - - startAngle: 0, // angle of line joining two touches - prevAngle : 0 // angle of the previous gesture event - }; - - this.pointerIsDown = false; - this.pointerWasMoved = false; - this._interacting = false; - this.resizeAxes = 'xy'; - - this.mouse = false; - - scope.interactions.push(this); + this.target = null; // current interactable being interacted with + this.element = null; // the target element of the interactable + this.dropTarget = null; // the dropzone a drag target might be dropped into + this.dropElement = null; // the element at the time of checking + this.prevDropTarget = null; // the dropzone that was recently dragged away from + this.prevDropElement = null; // the element at the time of checking + + this.prepared = { // action that's ready to be fired on next move event + name : null, + axis : null, + edges: null, + }; + + this.matches = []; // all selectors that are matched by target element + this.matchElements = []; // corresponding elements + + this.inertiaStatus = { + active : false, + smoothEnd : false, + + startEvent: null, + upCoords: {}, + + xe: 0, ye: 0, + sx: 0, sy: 0, + + t0: 0, + vx0: 0, vys: 0, + duration: 0, + + resumeDx: 0, + resumeDy: 0, + + lambda_v0: 0, + one_ve_v0: 0, + i : null, + }; + + this.boundInertiaFrame = () => { this.inertiaFrame(); }; + this.boundSmoothEndFrame = () => { this.smoothEndFrame(); }; + + this.activeDrops = { + dropzones: [], // the dropzones that are mentioned below + elements : [], // elements of dropzones that accept the target draggable + rects : [], // the rects of the elements mentioned above + }; + + // keep track of added pointers + this.pointers = []; + this.pointerIds = []; + this.downTargets = []; + this.downTimes = []; + this.holdTimers = []; + + // Previous native pointer move event coordinates + this.prevCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0, + }; + // current native pointer move event coordinates + this.curCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0, + }; + + // Starting InteractEvent pointer coordinates + this.startCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0, + }; + + // Change in coordinates and time of the pointer + this.pointerDelta = { + page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + timeStamp: 0, + }; + + this.downEvent = null; // pointerdown/mousedown/touchstart event + this.downPointer = {}; + + this._eventTarget = null; + this._curEventTarget = null; + + this.prevEvent = null; // previous action event + this.tapTime = 0; // time of the most recent tap event + this.prevTap = null; + + this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.modifierOffsets = {}; + this.modifierStatuses = modifiers.resetStatuses({}); + + this.gesture = { + start: { x: 0, y: 0 }, + + startDistance: 0, // distance between two touches of touchStart + prevDistance : 0, + distance : 0, + + scale: 1, // gesture.distance / gesture.startDistance + + startAngle: 0, // angle of line joining two touches + prevAngle : 0, // angle of the previous gesture event + }; + + this.pointerIsDown = false; + this.pointerWasMoved = false; + this._interacting = false; + this.resizeAxes = 'xy'; + + this.mouse = false; + + scope.interactions.push(this); } // Check if the current target supports the action. // If so, return the validated action. Otherwise, return null function validateAction (action, interactable) { - if (utils.isObject(action) && interactable.options[action.name].enabled) { - return action; - } + if (utils.isObject(action) && interactable.options[action.name].enabled) { + return action; + } - return null; + return null; } Interaction.prototype = { - setEventXY: function (targetObj, pointer) { - if (!pointer) { - if (this.pointerIds.length > 1) { - pointer = utils.touchAverage(this.pointers); - } - else { - pointer = this.pointers[0]; - } - } + setEventXY: function (targetObj, pointer) { + if (!pointer) { + if (this.pointerIds.length > 1) { + pointer = utils.touchAverage(this.pointers); + } + else { + pointer = this.pointers[0]; + } + } - var tmpXY = {}; + const tmpXY = {}; - utils.getPageXY(pointer, tmpXY, this); - targetObj.page.x = tmpXY.x; - targetObj.page.y = tmpXY.y; + utils.getPageXY(pointer, tmpXY, this); + targetObj.page.x = tmpXY.x; + targetObj.page.y = tmpXY.y; - utils.getClientXY(pointer, tmpXY, this); - targetObj.client.x = tmpXY.x; - targetObj.client.y = tmpXY.y; + utils.getClientXY(pointer, tmpXY, this); + targetObj.client.x = tmpXY.x; + targetObj.client.y = tmpXY.y; - targetObj.timeStamp = new Date().getTime(); - }, + targetObj.timeStamp = new Date().getTime(); + }, - pointerOver: function (pointer, event, eventTarget) { - if (this.prepared.name || !this.mouse) { return; } + pointerOver: function (pointer, event, eventTarget) { + if (this.prepared.name || !this.mouse) { return; } - var curMatches = [], - curMatchElements = [], - prevTargetElement = this.element; + const curMatches = []; + const curMatchElements = []; + const prevTargetElement = this.element; - this.addPointer(pointer); + this.addPointer(pointer); - if (this.target - && (scope.testIgnore(this.target, this.element, eventTarget) - || !scope.testAllow(this.target, this.element, eventTarget))) { - // if the eventTarget should be ignored or shouldn't be allowed - // clear the previous target - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; - } + if (this.target + && (scope.testIgnore(this.target, this.element, eventTarget) + || !scope.testAllow(this.target, this.element, eventTarget))) { + // if the eventTarget should be ignored or shouldn't be allowed + // clear the previous target + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; + } - var elementInteractable = scope.interactables.get(eventTarget), - elementAction = (elementInteractable - && !scope.testIgnore(elementInteractable, eventTarget, eventTarget) - && scope.testAllow(elementInteractable, eventTarget, eventTarget) - && validateAction( - elementInteractable.getAction(pointer, event, this, eventTarget), - elementInteractable)); + const elementInteractable = scope.interactables.get(eventTarget); + let elementAction = (elementInteractable + && !scope.testIgnore(elementInteractable, eventTarget, eventTarget) + && scope.testAllow(elementInteractable, eventTarget, eventTarget) + && validateAction( + elementInteractable.getAction(pointer, event, this, eventTarget), + elementInteractable)); - if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { - elementAction = null; - } + if (elementAction && !scope.withinInteractionLimit(elementInteractable, eventTarget, elementAction)) { + elementAction = null; + } - function pushCurMatches (interactable, selector) { - if (interactable - && scope.inContext(interactable, eventTarget) - && !scope.testIgnore(interactable, eventTarget, eventTarget) - && scope.testAllow(interactable, eventTarget, eventTarget) - && utils.matchesSelector(eventTarget, selector)) { + function pushCurMatches (interactable, selector) { + if (interactable + && scope.inContext(interactable, eventTarget) + && !scope.testIgnore(interactable, eventTarget, eventTarget) + && scope.testAllow(interactable, eventTarget, eventTarget) + && utils.matchesSelector(eventTarget, selector)) { - curMatches.push(interactable); - curMatchElements.push(eventTarget); - } - } + curMatches.push(interactable); + curMatchElements.push(eventTarget); + } + } - if (elementAction) { - this.target = elementInteractable; - this.element = eventTarget; - this.matches = []; - this.matchElements = []; + if (elementAction) { + this.target = elementInteractable; + this.element = eventTarget; + this.matches = []; + this.matchElements = []; + } + else { + scope.interactables.forEachSelector(pushCurMatches); + + if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { + this.matches = curMatches; + this.matchElements = curMatchElements; + + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(eventTarget, + scope.PointerEvent? browser.pEventTypes.move : 'mousemove', + listeners.pointerHover); + } + else if (this.target) { + if (utils.nodeContains(prevTargetElement, eventTarget)) { + this.pointerHover(pointer, event, this.matches, this.matchElements); + events.add(this.element, + scope.PointerEvent? browser.pEventTypes.move : 'mousemove', + listeners.pointerHover); } else { - scope.interactables.forEachSelector(pushCurMatches); - - if (this.validateSelector(pointer, event, curMatches, curMatchElements)) { - this.matches = curMatches; - this.matchElements = curMatchElements; - - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(eventTarget, - scope.PointerEvent? browser.pEventTypes.move : 'mousemove', - listeners.pointerHover); - } - else if (this.target) { - if (utils.nodeContains(prevTargetElement, eventTarget)) { - this.pointerHover(pointer, event, this.matches, this.matchElements); - events.add(this.element, - scope.PointerEvent? browser.pEventTypes.move : 'mousemove', - listeners.pointerHover); - } - else { - this.target = null; - this.element = null; - this.matches = []; - this.matchElements = []; - } - } + this.target = null; + this.element = null; + this.matches = []; + this.matchElements = []; } - }, - - // Check what action would be performed on pointerMove target if a mouse - // button were pressed and change the cursor accordingly - pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { - var target = this.target; - - if (!this.prepared.name && this.mouse) { - - var action; + } + } + }, - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); + // Check what action would be performed on pointerMove target if a mouse + // button were pressed and change the cursor accordingly + pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { + const target = this.target; - if (matches) { - action = this.validateSelector(pointer, event, matches, matchElements); - } - else if (target) { - action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); - } + if (!this.prepared.name && this.mouse) { - if (target && target.options.styleCursor) { - if (action) { - target._doc.documentElement.style.cursor = actions[action.name].getCursor(action); - } - else { - target._doc.documentElement.style.cursor = ''; - } - } - } - else if (this.prepared.name) { - this.checkAndPreventDefault(event, target, this.element); - } - }, + let action; - pointerOut: function (pointer, event, eventTarget) { - if (this.prepared.name) { return; } + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); - // Remove temporary event listeners for selector Interactables - if (!scope.interactables.get(eventTarget)) { - events.remove(eventTarget, - scope.PointerEvent? browser.pEventTypes.move : 'mousemove', - listeners.pointerHover); - } + if (matches) { + action = this.validateSelector(pointer, event, matches, matchElements); + } + else if (target) { + action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target); + } - if (this.target && this.target.options.styleCursor && !this.interacting()) { - this.target._doc.documentElement.style.cursor = ''; - } - }, - - selectorDown: function (pointer, event, eventTarget, curEventTarget) { - var that = this, - element = eventTarget, - pointerIndex = this.addPointer(pointer), - action; - - this.pointerIsDown = true; - - signals.fire('interaction-down', { - interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget, - pointerIndex: pointerIndex - }); - - // Check if the down event hits the current inertia target - if (this.inertiaStatus.active && this.target.selector) { - // climb up the DOM tree from the event target - while (utils.isElement(element)) { - - // if this element is the current inertia target element - if (element === this.element - // and the prospective action is the same as the ongoing one - && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { - - // stop inertia so that the next move will be a normal one - animationFrame.cancel(this.inertiaStatus.i); - this.inertiaStatus.active = false; - - return; - } - element = utils.parentElement(element); - } + if (target && target.options.styleCursor) { + if (action) { + target._doc.documentElement.style.cursor = actions[action.name].getCursor(action); } - - // do nothing if interacting - if (this.interacting()) { - return; + else { + target._doc.documentElement.style.cursor = ''; } + } + } + else if (this.prepared.name) { + this.checkAndPreventDefault(event, target, this.element); + } + }, - function pushMatches (interactable, selector, context) { - var elements = browser.useMatchesSelectorPolyfill - ? context.querySelectorAll(selector) - : undefined; - - if (scope.inContext(interactable, element) - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && utils.matchesSelector(element, selector, elements)) { - - that.matches.push(interactable); - that.matchElements.push(element); - } - } + pointerOut: function (pointer, event, eventTarget) { + if (this.prepared.name) { return; } - // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); - this.downEvent = event; + // Remove temporary event listeners for selector Interactables + if (!scope.interactables.get(eventTarget)) { + events.remove(eventTarget, + scope.PointerEvent? browser.pEventTypes.move : 'mousemove', + listeners.pointerHover); + } - while (utils.isElement(element) && !action) { - this.matches = []; - this.matchElements = []; + if (this.target && this.target.options.styleCursor && !this.interacting()) { + this.target._doc.documentElement.style.cursor = ''; + } + }, - scope.interactables.forEachSelector(pushMatches); + selectorDown: function (pointer, event, eventTarget, curEventTarget) { + const pointerIndex = this.addPointer(pointer); + let element = eventTarget; + let action; - action = this.validateSelector(pointer, event, this.matches, this.matchElements); - element = utils.parentElement(element); - } + this.pointerIsDown = true; - if (action) { - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; + signals.fire('interaction-down', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget, + pointerIndex: pointerIndex, + }); - return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); - } - else { - // do these now since pointerDown isn't being called from here - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - utils.extend(this.downPointer, pointer); + // Check if the down event hits the current inertia target + if (this.inertiaStatus.active && this.target.selector) { + // climb up the DOM tree from the event target + while (utils.isElement(element)) { - utils.copyCoords(this.prevCoords, this.curCoords); - this.pointerWasMoved = false; - } - }, + // if this element is the current inertia target element + if (element === this.element + // and the prospective action is the same as the ongoing one + && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) { - // Determine action to be performed on next pointerMove and add appropriate - // style and event Listeners - pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { - if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { - this.checkAndPreventDefault(event, this.target, this.element); + // stop inertia so that the next move will be a normal one + animationFrame.cancel(this.inertiaStatus.i); + this.inertiaStatus.active = false; - return; + return; } + element = utils.parentElement(element); + } + } - this.pointerIsDown = true; - this.downEvent = event; - - var pointerIndex = this.addPointer(pointer), - action; + // do nothing if interacting + if (this.interacting()) { + return; + } - // If it is the second touch of a multi-touch gesture, keep the target - // the same if a target was set by the first touch - // Otherwise, set the target if there is no action prepared - if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { + const pushMatches = (interactable, selector, context) => { + const elements = (browser.useMatchesSelectorPolyfill + ? context.querySelectorAll(selector) + : undefined); - var interactable = scope.interactables.get(curEventTarget); + if (scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && utils.matchesSelector(element, selector, elements)) { - if (interactable - && !scope.testIgnore(interactable, curEventTarget, eventTarget) - && scope.testAllow(interactable, curEventTarget, eventTarget) - && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) - && scope.withinInteractionLimit(interactable, curEventTarget, action)) { - this.target = interactable; - this.element = curEventTarget; - } - } + this.matches.push(interactable); + this.matchElements.push(element); + } + }; - var target = this.target, - options = target && target.options; + // update pointer coords for defaultActionChecker to use + this.setEventXY(this.curCoords, pointer); + this.downEvent = event; - if (target && (forceAction || !this.prepared.name)) { - action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); + while (utils.isElement(element) && !action) { + this.matches = []; + this.matchElements = []; - this.setEventXY(this.startCoords); + scope.interactables.forEachSelector(pushMatches); - if (!action) { return; } + action = this.validateSelector(pointer, event, this.matches, this.matchElements); + element = utils.parentElement(element); + } - if (options.styleCursor) { - target._doc.documentElement.style.cursor = actions[action.name].getCursor(action); - } + if (action) { + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; - this.resizeAxes = action.name === 'resize'? action.axis : null; + return this.pointerDown(pointer, event, eventTarget, curEventTarget, action); + } + else { + // do these now since pointerDown isn't being called from here + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + utils.extend(this.downPointer, pointer); - if (action === 'gesture' && this.pointerIds.length < 2) { - action = null; - } + utils.copyCoords(this.prevCoords, this.curCoords); + this.pointerWasMoved = false; + } + }, - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; + // Determine action to be performed on next pointerMove and add appropriate + // style and event Listeners + pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { + if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { + this.checkAndPreventDefault(event, this.target, this.element); - modifiers.resetStatuses(this.modifierStatuses); + return; + } - this.downTimes[pointerIndex] = new Date().getTime(); - this.downTargets[pointerIndex] = eventTarget; - utils.extend(this.downPointer, pointer); + this.pointerIsDown = true; + this.downEvent = event; - this.setEventXY(this.prevCoords); - this.pointerWasMoved = false; + const pointerIndex = this.addPointer(pointer); + let action; - this.checkAndPreventDefault(event, target, this.element); - } - // if inertia is active try to resume action - else if (this.inertiaStatus.active - && curEventTarget === this.element - && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { + // If it is the second touch of a multi-touch gesture, keep the target + // the same if a target was set by the first touch + // Otherwise, set the target if there is no action prepared + if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) { - animationFrame.cancel(this.inertiaStatus.i); - this.inertiaStatus.active = false; + const interactable = scope.interactables.get(curEventTarget); - this.checkAndPreventDefault(event, target, this.element); - } - }, + if (interactable + && !scope.testIgnore(interactable, curEventTarget, eventTarget) + && scope.testAllow(interactable, curEventTarget, eventTarget) + && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget)) + && scope.withinInteractionLimit(interactable, curEventTarget, action)) { + this.target = interactable; + this.element = curEventTarget; + } + } - setStartOffsets: function (action, interactable, element) { - var rect = interactable.getRect(element); + const target = this.target; + const options = target && target.options; - if (rect) { - this.startOffset.left = this.startCoords.page.x - rect.left; - this.startOffset.top = this.startCoords.page.y - rect.top; + if (target && (forceAction || !this.prepared.name)) { + action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); - this.startOffset.right = rect.right - this.startCoords.page.x; - this.startOffset.bottom = rect.bottom - this.startCoords.page.y; + this.setEventXY(this.startCoords); - if (!('width' in rect)) { rect.width = rect.right - rect.left; } - if (!('height' in rect)) { rect.height = rect.bottom - rect.top ; } - } - else { - this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; - } + if (!action) { return; } - modifiers.setOffsets(this, interactable, element, rect, this.modifierOffsets); - }, - - /*\ - * Interaction.start - [ method ] - * - * Start an action with the given Interactable and Element as tartgets. The - * action must be enabled for the target Interactable and an appropriate number - * of pointers must be held down - 1 for drag/resize, 2 for gesture. - * - * Use it with `interactable.able({ manualStart: false })` to always - * [start actions manually](https://github.com/taye/interact.js/issues/114) - * - - action (object) The action to be performed - drag, resize, etc. - - interactable (Interactable) The Interactable to target - - element (Element) The DOM Element to target - = (object) interact - ** - | interact(target) - | .draggable({ - | // disable the default drag start by down->move - | manualStart: true - | }) - | // start dragging after the user holds the pointer down - | .on('hold', function (event) { - | var interaction = event.interaction; - | - | if (!interaction.interacting()) { - | interaction.start({ name: 'drag' }, - | event.interactable, - | event.currentTarget); - | } - | }); - \*/ - start: function (action, interactable, element) { - if (this.interacting() - || !this.pointerIsDown - || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { - return; - } + if (options.styleCursor) { + target._doc.documentElement.style.cursor = actions[action.name].getCursor(action); + } - // if this interaction had been removed after stopping - // add it back - if (utils.indexOf(scope.interactions, this) === -1) { - scope.interactions.push(this); - } + this.resizeAxes = action.name === 'resize'? action.axis : null; - this.prepared.name = action.name; - this.prepared.axis = action.axis; - this.prepared.edges = action.edges; - this.target = interactable; - this.element = element; + if (action === 'gesture' && this.pointerIds.length < 2) { + action = null; + } - this.setEventXY(this.startCoords); - this.setStartOffsets(action.name, interactable, element, this.modifierOffsets); + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; - modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); + modifiers.resetStatuses(this.modifierStatuses); - this.prevEvent = actions[this.prepared.name].start(this, this.downEvent); - }, + this.downTimes[pointerIndex] = new Date().getTime(); + this.downTargets[pointerIndex] = eventTarget; + utils.extend(this.downPointer, pointer); - pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { - this.recordPointer(pointer); + this.setEventXY(this.prevCoords); + this.pointerWasMoved = false; - this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) - ? this.inertiaStatus.startEvent - : undefined); + this.checkAndPreventDefault(event, target, this.element); + } + // if inertia is active try to resume action + else if (this.inertiaStatus.active + && curEventTarget === this.element + && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) { - var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x - && this.curCoords.page.y === this.prevCoords.page.y - && this.curCoords.client.x === this.prevCoords.client.x - && this.curCoords.client.y === this.prevCoords.client.y); + animationFrame.cancel(this.inertiaStatus.i); + this.inertiaStatus.active = false; - var dx, dy; + this.checkAndPreventDefault(event, target, this.element); + } + }, - // register movement greater than pointerMoveTolerance - if (this.pointerIsDown && !this.pointerWasMoved) { - dx = this.curCoords.client.x - this.startCoords.client.x; - dy = this.curCoords.client.y - this.startCoords.client.y; + setStartOffsets: function (action, interactable, element) { + const rect = interactable.getRect(element); - this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; - } + if (rect) { + this.startOffset.left = this.startCoords.page.x - rect.left; + this.startOffset.top = this.startCoords.page.y - rect.top; - signals.fire('interaction-move', { - interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget, - duplicate: duplicateMove, - dx: dx, - dy: dy - }); + this.startOffset.right = rect.right - this.startCoords.page.x; + this.startOffset.bottom = rect.bottom - this.startCoords.page.y; - if (!this.pointerIsDown) { return; } + if (!('width' in rect)) { rect.width = rect.right - rect.left; } + if (!('height' in rect)) { rect.height = rect.bottom - rect.top ; } + } + else { + this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0; + } - if (duplicateMove && this.pointerWasMoved && !preEnd) { - this.checkAndPreventDefault(event, this.target, this.element); - return; - } + modifiers.setOffsets(this, interactable, element, rect, this.modifierOffsets); + }, + + /*\ + * Interaction.start + [ method ] + * + * Start an action with the given Interactable and Element as tartgets. The + * action must be enabled for the target Interactable and an appropriate number + * of pointers must be held down - 1 for drag/resize, 2 for gesture. + * + * Use it with `interactable.able({ manualStart: false })` to always + * [start actions manually](https://github.com/taye/interact.js/issues/114) + * + - action (object) The action to be performed - drag, resize, etc. + - interactable (Interactable) The Interactable to target + - element (Element) The DOM Element to target + = (object) interact + ** + | interact(target) + | .draggable({ + | // disable the default drag start by down->move + | manualStart: true + | }) + | // start dragging after the user holds the pointer down + | .on('hold', function (event) { + | var interaction = event.interaction; + | + | if (!interaction.interacting()) { + | interaction.start({ name: 'drag' }, + | event.interactable, + | event.currentTarget); + | } + | }); + \*/ + start: function (action, interactable, element) { + if (this.interacting() + || !this.pointerIsDown + || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { + return; + } - // set pointer coordinate, time changes and speeds - utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + // if this interaction had been removed after stopping + // add it back + if (utils.indexOf(scope.interactions, this) === -1) { + scope.interactions.push(this); + } - if (!this.prepared.name) { return; } + this.prepared.name = action.name; + this.prepared.axis = action.axis; + this.prepared.edges = action.edges; + this.target = interactable; + this.element = element; - if (this.pointerWasMoved - // ignore movement while inertia is active - && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { + this.setEventXY(this.startCoords); + this.setStartOffsets(action.name, interactable, element, this.modifierOffsets); - // if just starting an action, calculate the pointer speed now - if (!this.interacting()) { - utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); - actions[this.prepared.name].beforeStart(this, pointer, event, eventTarget, curEventTarget, dx, dy); - } + this.prevEvent = actions[this.prepared.name].start(this, this.downEvent); + }, - var starting = !!this.prepared.name && !this.interacting(); + pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { + this.recordPointer(pointer); - if (starting - && (this.target.options[this.prepared.name].manualStart - || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { - this.stop(event); - return; - } + this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) + ? this.inertiaStatus.startEvent + : undefined); - if (this.prepared.name && this.target) { - if (starting) { - this.start(this.prepared, this.target, this.element); - } + const duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x + && this.curCoords.page.y === this.prevCoords.page.y + && this.curCoords.client.x === this.prevCoords.client.x + && this.curCoords.client.y === this.prevCoords.client.y); - var modifierResult = modifiers.setAll(this, this.curCoords.page, this.modifierStatuses, preEnd); + let dx; + let dy; - // move if snapping or restriction doesn't prevent it - if (modifierResult.shouldMove || starting) { - this.prevEvent = actions[this.prepared.name].move(this, event); - } + // register movement greater than pointerMoveTolerance + if (this.pointerIsDown && !this.pointerWasMoved) { + dx = this.curCoords.client.x - this.startCoords.client.x; + dy = this.curCoords.client.y - this.startCoords.client.y; - this.checkAndPreventDefault(event, this.target, this.element); - } - } - - utils.copyCoords(this.prevCoords, this.curCoords); + this.pointerWasMoved = utils.hypot(dx, dy) > scope.pointerMoveTolerance; + } - signals.fire('interaction-move-done', { - interaction: this, - pointer: pointer, - event: event - }); - }, + signals.fire('interaction-move', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget, + duplicate: duplicateMove, + dx: dx, + dy: dy, + }); - pointerUp: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); + if (!this.pointerIsDown) { return; } - clearTimeout(this.holdTimers[pointerIndex]); + if (duplicateMove && this.pointerWasMoved && !preEnd) { + this.checkAndPreventDefault(event, this.target, this.element); + return; + } - signals.fire('interaction-up', { - interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget, - curEventTarget: curEventTarget - }); + // set pointer coordinate, time changes and speeds + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); + if (!this.prepared.name) { return; } - this.pointerEnd(pointer, event, eventTarget, curEventTarget); + if (this.pointerWasMoved + // ignore movement while inertia is active + && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) { - this.removePointer(pointer); - }, + // if just starting an action, calculate the pointer speed now + if (!this.interacting()) { + utils.setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords); - pointerCancel: function (pointer, event, eventTarget, curEventTarget) { - var pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); + actions[this.prepared.name].beforeStart(this, pointer, event, eventTarget, curEventTarget, dx, dy); + } - clearTimeout(this.holdTimers[pointerIndex]); + const starting = !!this.prepared.name && !this.interacting(); - signals.fire('interaction-cancel', { - interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget - }); + if (starting + && (this.target.options[this.prepared.name].manualStart + || !scope.withinInteractionLimit(this.target, this.element, this.prepared))) { + this.stop(event); + return; + } - this.pointerEnd(pointer, event, eventTarget, curEventTarget); + if (this.prepared.name && this.target) { + if (starting) { + this.start(this.prepared, this.target, this.element); + } - this.removePointer(pointer); - }, + const modifierResult = modifiers.setAll(this, this.curCoords.page, this.modifierStatuses, preEnd); - // End interact move events and stop auto-scroll unless inertia is enabled - pointerEnd: function (pointer, event, eventTarget, curEventTarget) { - var target = this.target, - options = target && target.options, - inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia, - inertiaStatus = this.inertiaStatus; + // move if snapping or restriction doesn't prevent it + if (modifierResult.shouldMove || starting) { + this.prevEvent = actions[this.prepared.name].move(this, event); + } - if (this.interacting()) { + this.checkAndPreventDefault(event, this.target, this.element); + } + } - if (inertiaStatus.active) { return; } + utils.copyCoords(this.prevCoords, this.curCoords); - var pointerSpeed, - now = new Date().getTime(), - inertiaPossible = false, - inertia = false, - smoothEnd = false, - statuses = {}, - modifierResult, - page = utils.extend({}, this.curCoords.page), - startEvent; + signals.fire('interaction-move-done', { + interaction: this, + pointer: pointer, + event: event, + }); + }, - if (this.dragging) { - if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } - else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } - else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } - } - else { - pointerSpeed = this.pointerDelta.client.speed; - } + pointerUp: function (pointer, event, eventTarget, curEventTarget) { + const pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); - // check if inertia should be started - inertiaPossible = (inertiaOptions && inertiaOptions.enabled - && this.prepared.name !== 'gesture' - && event !== inertiaStatus.startEvent); + clearTimeout(this.holdTimers[pointerIndex]); - inertia = (inertiaPossible - && (now - this.curCoords.timeStamp) < 50 - && pointerSpeed > inertiaOptions.minSpeed - && pointerSpeed > inertiaOptions.endSpeed); + signals.fire('interaction-up', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget, + curEventTarget: curEventTarget, + }); - // smoothEnd - if (inertiaPossible && !inertia) { - modifiers.resetStatuses(statuses); - modifierResult = modifiers.setAll(this, page, statuses, true); + this.pointerEnd(pointer, event, eventTarget, curEventTarget); - if (modifierResult.shouldMove && modifierResult.locked) { - smoothEnd = true; - } - } + this.removePointer(pointer); + }, - if (inertia || smoothEnd) { - utils.copyCoords(inertiaStatus.upCoords, this.curCoords); + pointerCancel: function (pointer, event, eventTarget, curEventTarget) { + const pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); - this.pointers[0] = inertiaStatus.startEvent = startEvent = - new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); + clearTimeout(this.holdTimers[pointerIndex]); - inertiaStatus.t0 = now; + signals.fire('interaction-cancel', { + interaction: this, + pointer: pointer, + event: event, + eventTarget: eventTarget, + }); - target.fire(inertiaStatus.startEvent); + this.pointerEnd(pointer, event, eventTarget, curEventTarget); + + this.removePointer(pointer); + }, + + // End interact move events and stop auto-scroll unless inertia is enabled + pointerEnd: function (pointer, event, eventTarget, curEventTarget) { + const target = this.target; + const options = target && target.options; + const inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia; + const inertiaStatus = this.inertiaStatus; + + if (this.interacting()) { + + if (inertiaStatus.active) { return; } + + const now = new Date().getTime(); + const statuses = {}; + const page = utils.extend({}, this.curCoords.page); + let pointerSpeed; + let inertiaPossible = false; + let inertia = false; + let smoothEnd = false; + let modifierResult; + + if (this.dragging) { + if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); } + else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); } + else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; } + } + else { + pointerSpeed = this.pointerDelta.client.speed; + } + + // check if inertia should be started + inertiaPossible = (inertiaOptions && inertiaOptions.enabled + && this.prepared.name !== 'gesture' + && event !== inertiaStatus.startEvent); + + inertia = (inertiaPossible + && (now - this.curCoords.timeStamp) < 50 + && pointerSpeed > inertiaOptions.minSpeed + && pointerSpeed > inertiaOptions.endSpeed); + + // smoothEnd + if (inertiaPossible && !inertia) { + modifiers.resetStatuses(statuses); + + modifierResult = modifiers.setAll(this, page, statuses, true); + + if (modifierResult.shouldMove && modifierResult.locked) { + smoothEnd = true; + } + } - if (inertia) { - inertiaStatus.vx0 = this.pointerDelta.client.vx; - inertiaStatus.vy0 = this.pointerDelta.client.vy; - inertiaStatus.v0 = pointerSpeed; + if (inertia || smoothEnd) { + utils.copyCoords(inertiaStatus.upCoords, this.curCoords); - this.calcInertia(inertiaStatus); + this.pointers[0] = inertiaStatus.startEvent = + new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element); - page = utils.extend({}, this.curCoords.page); + inertiaStatus.t0 = now; - page.x += inertiaStatus.xe; - page.y += inertiaStatus.ye; + target.fire(inertiaStatus.startEvent); - modifiers.resetStatuses(statuses); + if (inertia) { + inertiaStatus.vx0 = this.pointerDelta.client.vx; + inertiaStatus.vy0 = this.pointerDelta.client.vy; + inertiaStatus.v0 = pointerSpeed; - modifierResult = modifiers.setAll(this, page, statuses, true, true); + this.calcInertia(inertiaStatus); - inertiaStatus.modifiedXe += modifierResult.dx; - inertiaStatus.modifiedYe += modifierResult.dy; + utils.extend(page, this.curCoords.page); - inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); - } - else { - inertiaStatus.smoothEnd = true; - inertiaStatus.xe = modifierResult.dx; - inertiaStatus.ye = modifierResult.dy; + page.x += inertiaStatus.xe; + page.y += inertiaStatus.ye; - inertiaStatus.sx = inertiaStatus.sy = 0; + modifiers.resetStatuses(statuses); - inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); - } + modifierResult = modifiers.setAll(this, page, statuses, true, true); - inertiaStatus.active = true; - return; - } + inertiaStatus.modifiedXe += modifierResult.dx; + inertiaStatus.modifiedYe += modifierResult.dy; - for (var i = 0; i < modifiers.names.length; i++) { - // if the endOnly option is true for any modifier - if (modifiers[modifiers.names[i]].shouldDo(target, this.prepared.name, true, true)) { - // fire a move event at the snapped coordinates - this.pointerMove(pointer, event, eventTarget, curEventTarget, true); - break; - } - } + inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); } + else { + inertiaStatus.smoothEnd = true; + inertiaStatus.xe = modifierResult.dx; + inertiaStatus.ye = modifierResult.dy; - if (this.interacting()) { - actions[this.prepared.name].end(this, event); + inertiaStatus.sx = inertiaStatus.sy = 0; + + inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); } - this.stop(event); - }, + inertiaStatus.active = true; + return; + } - currentAction: function () { - return this._interacting? this.prepared.name: null; - }, + for (let i = 0; i < modifiers.names.length; i++) { + // if the endOnly option is true for any modifier + if (modifiers[modifiers.names[i]].shouldDo(target, this.prepared.name, true, true)) { + // fire a move event at the snapped coordinates + this.pointerMove(pointer, event, eventTarget, curEventTarget, true); + break; + } + } + } - interacting: function () { - return this._interacting; - }, + if (this.interacting()) { + actions[this.prepared.name].end(this, event); + } - stop: function (event) { - signals.fire('interaction-stop', { interaction: this }); + this.stop(event); + }, - if (this._interacting) { - signals.fire('interaction-stop-active', { interaction: this }); + currentAction: function () { + return this._interacting? this.prepared.name: null; + }, - this.matches = []; - this.matchElements = []; + interacting: function () { + return this._interacting; + }, - var target = this.target; + stop: function (event) { + signals.fire('interaction-stop', { interaction: this }); - if (target.options.styleCursor) { - target._doc.documentElement.style.cursor = ''; - } + if (this._interacting) { + signals.fire('interaction-stop-active', { interaction: this }); - // prevent Default only if were previously interacting - if (event && utils.isFunction(event.preventDefault)) { - this.checkAndPreventDefault(event, target, this.element); - } + this.matches = []; + this.matchElements = []; - actions[this.prepared.name].stop(this, event); - } + const target = this.target; - this.target = this.element = null; + if (target.options.styleCursor) { + target._doc.documentElement.style.cursor = ''; + } - this.pointerIsDown = this._interacting = false; - this.prepared.name = this.prevEvent = null; - this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; + // prevent Default only if were previously interacting + if (event && utils.isFunction(event.preventDefault)) { + this.checkAndPreventDefault(event, target, this.element); + } - modifiers.resetStatuses(this.modifierStatuses); + actions[this.prepared.name].stop(this, event); + } - // remove pointers if their ID isn't in this.pointerIds - for (var i = 0; i < this.pointers.length; i++) { - if (utils.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { - this.pointers.splice(i, 1); - } - } + this.target = this.element = null; - for (i = 0; i < scope.interactions.length; i++) { - // remove this interaction if it's not the only one of it's type - if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { - scope.interactions.splice(utils.indexOf(scope.interactions, this), 1); - } - } - }, + this.pointerIsDown = this._interacting = false; + this.prepared.name = this.prevEvent = null; + this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0; - inertiaFrame: function () { - var inertiaStatus = this.inertiaStatus, - options = this.target.options[this.prepared.name].inertia, - lambda = options.resistance, - t = new Date().getTime() / 1000 - inertiaStatus.t0; + modifiers.resetStatuses(this.modifierStatuses); - if (t < inertiaStatus.te) { + // remove pointers if their ID isn't in this.pointerIds + for (let i = 0; i < this.pointers.length; i++) { + if (utils.indexOf(this.pointerIds, utils.getPointerId(this.pointers[i])) === -1) { + this.pointers.splice(i, 1); + } + } - var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; + for (let i = 0; i < scope.interactions.length; i++) { + // remove this interaction if it's not the only one of it's type + if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { + scope.interactions.splice(utils.indexOf(scope.interactions, this), 1); + } + } + }, - if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { - inertiaStatus.sx = inertiaStatus.xe * progress; - inertiaStatus.sy = inertiaStatus.ye * progress; - } - else { - var quadPoint = utils.getQuadraticCurvePoint( - 0, 0, - inertiaStatus.xe, inertiaStatus.ye, - inertiaStatus.modifiedXe, inertiaStatus.modifiedYe, - progress); - - inertiaStatus.sx = quadPoint.x; - inertiaStatus.sy = quadPoint.y; - } + inertiaFrame: function () { + const inertiaStatus = this.inertiaStatus; + const options = this.target.options[this.prepared.name].inertia; + const lambda = options.resistance; + const t = new Date().getTime() / 1000 - inertiaStatus.t0; - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + if (t < inertiaStatus.te) { - inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); - } - else { - inertiaStatus.sx = inertiaStatus.modifiedXe; - inertiaStatus.sy = inertiaStatus.modifiedYe; + const progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0; - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) { + inertiaStatus.sx = inertiaStatus.xe * progress; + inertiaStatus.sy = inertiaStatus.ye * progress; + } + else { + const quadPoint = utils.getQuadraticCurvePoint(0, 0, + inertiaStatus.xe, + inertiaStatus.ye, + inertiaStatus.modifiedXe, + inertiaStatus.modifiedYe, + progress); - inertiaStatus.active = false; - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); - } - }, + inertiaStatus.sx = quadPoint.x; + inertiaStatus.sy = quadPoint.y; + } - smoothEndFrame: function () { - var inertiaStatus = this.inertiaStatus, - t = new Date().getTime() - inertiaStatus.t0, - duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - if (t < duration) { - inertiaStatus.sx = utils.easeOutQuad(t, 0, inertiaStatus.xe, duration); - inertiaStatus.sy = utils.easeOutQuad(t, 0, inertiaStatus.ye, duration); + inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); + } + else { + inertiaStatus.sx = inertiaStatus.modifiedXe; + inertiaStatus.sy = inertiaStatus.modifiedYe; - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); - } - else { - inertiaStatus.sx = inertiaStatus.xe; - inertiaStatus.sy = inertiaStatus.ye; + inertiaStatus.active = false; + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + } + }, - this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); + smoothEndFrame: function () { + const inertiaStatus = this.inertiaStatus; + const t = new Date().getTime() - inertiaStatus.t0; + const duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; - inertiaStatus.active = false; - inertiaStatus.smoothEnd = false; + if (t < duration) { + inertiaStatus.sx = utils.easeOutQuad(t, 0, inertiaStatus.xe, duration); + inertiaStatus.sy = utils.easeOutQuad(t, 0, inertiaStatus.ye, duration); - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); - } - }, + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - addPointer: function (pointer) { - var id = utils.getPointerId(pointer), - index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); + inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); + } + else { + inertiaStatus.sx = inertiaStatus.xe; + inertiaStatus.sy = inertiaStatus.ye; - if (index === -1) { - index = this.pointerIds.length; - } + this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - this.pointerIds[index] = id; - this.pointers[index] = pointer; + inertiaStatus.active = false; + inertiaStatus.smoothEnd = false; - return index; - }, + this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + } + }, - removePointer: function (pointer) { - var id = utils.getPointerId(pointer), - index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); + addPointer: function (pointer) { + const id = utils.getPointerId(pointer); + let index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); - if (index === -1) { return; } + if (index === -1) { + index = this.pointerIds.length; + } - if (!this.interacting()) { - this.pointers.splice(index, 1); - } + this.pointerIds[index] = id; + this.pointers[index] = pointer; - this.pointerIds .splice(index, 1); - this.downTargets.splice(index, 1); - this.downTimes .splice(index, 1); - this.holdTimers .splice(index, 1); - }, + return index; + }, - recordPointer: function (pointer) { - // Do not update pointers while inertia is active. - // The inertia start event should be this.pointers[0] - if (this.inertiaStatus.active) { return; } + removePointer: function (pointer) { + const id = utils.getPointerId(pointer); + const index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); - var index = this.mouse? 0: utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); + if (index === -1) { return; } - if (index === -1) { return; } + if (!this.interacting()) { + this.pointers.splice(index, 1); + } - this.pointers[index] = pointer; - }, + this.pointerIds .splice(index, 1); + this.downTargets.splice(index, 1); + this.downTimes .splice(index, 1); + this.holdTimers .splice(index, 1); + }, - validateSelector: function (pointer, event, matches, matchElements) { - for (var i = 0, len = matches.length; i < len; i++) { - var match = matches[i], - matchElement = matchElements[i], - action = validateAction(match.getAction(pointer, event, this, matchElement), match); + recordPointer: function (pointer) { + // Do not update pointers while inertia is active. + // The inertia start event should be this.pointers[0] + if (this.inertiaStatus.active) { return; } - if (action && scope.withinInteractionLimit(match, matchElement, action)) { - this.target = match; - this.element = matchElement; + const index = this.mouse? 0: utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); - return action; - } - } - }, + if (index === -1) { return; } - checkAndPreventDefault: function (event, interactable, element) { - if (!(interactable = interactable || this.target)) { return; } + this.pointers[index] = pointer; + }, - var options = interactable.options, - prevent = options.preventDefault; + validateSelector: function (pointer, event, matches, matchElements) { + for (let i = 0, len = matches.length; i < len; i++) { + const match = matches[i]; + const matchElement = matchElements[i]; + const action = validateAction(match.getAction(pointer, event, this, matchElement), match); - if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { - // do not preventDefault on pointerdown if the prepared action is a drag - // and dragging can only start from a certain direction - this allows - // a touch to pan the viewport if a drag isn't in the right direction - if (/down|start/i.test(event.type) - && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { + if (action && scope.withinInteractionLimit(match, matchElement, action)) { + this.target = match; + this.element = matchElement; - return; - } + return action; + } + } + }, - // with manualStart, only preventDefault while interacting - if (options[this.prepared.name] && options[this.prepared.name].manualStart - && !this.interacting()) { - return; - } + checkAndPreventDefault: function (event, interactable, element) { + if (!(interactable = interactable || this.target)) { return; } - event.preventDefault(); - return; - } + const options = interactable.options; + const prevent = options.preventDefault; - if (prevent === 'always') { - event.preventDefault(); - return; - } - }, - - calcInertia: function (status) { - var inertiaOptions = this.target.options[this.prepared.name].inertia, - lambda = inertiaOptions.resistance, - inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; + if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) { + // do not preventDefault on pointerdown if the prepared action is a drag + // and dragging can only start from a certain direction - this allows + // a touch to pan the viewport if a drag isn't in the right direction + if (/down|start/i.test(event.type) + && this.prepared.name === 'drag' && options.drag.axis !== 'xy') { - status.x0 = this.prevEvent.pageX; - status.y0 = this.prevEvent.pageY; - status.t0 = status.startEvent.timeStamp / 1000; - status.sx = status.sy = 0; + return; + } - status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; - status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; - status.te = inertiaDur; + // with manualStart, only preventDefault while interacting + if (options[this.prepared.name] && options[this.prepared.name].manualStart + && !this.interacting()) { + return; + } - status.lambda_v0 = lambda / status.v0; - status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; - }, + event.preventDefault(); + return; + } - _updateEventTargets: function (target, currentTarget) { - this._eventTarget = target; - this._curEventTarget = currentTarget; + if (prevent === 'always') { + event.preventDefault(); + return; } + }, + + calcInertia: function (status) { + const inertiaOptions = this.target.options[this.prepared.name].inertia; + const lambda = inertiaOptions.resistance; + const inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; + + status.x0 = this.prevEvent.pageX; + status.y0 = this.prevEvent.pageY; + status.t0 = status.startEvent.timeStamp / 1000; + status.sx = status.sy = 0; + + status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda; + status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda; + status.te = inertiaDur; + + status.lambda_v0 = lambda / status.v0; + status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; + }, + + _updateEventTargets: function (target, currentTarget) { + this._eventTarget = target; + this._curEventTarget = currentTarget; + }, }; -for (var i = 0, len = methodNames.length; i < len; i++) { - var method = methodNames[i]; +for (let i = 0, len = methodNames.length; i < len; i++) { + const method = methodNames[i]; - listeners[method] = doOnInteractions(method); + listeners[method] = doOnInteractions(method); } function getInteractionFromPointer (pointer, eventType, eventTarget) { - var i = 0, len = scope.interactions.length, - mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) + const mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) // MSPointerEvent.MSPOINTER_TYPE_MOUSE - || pointer.pointerType === 4), - interaction; - - var id = utils.getPointerId(pointer); - - // try to resume inertia with a new pointer - if (/down|start/i.test(eventType)) { - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; - - var element = eventTarget; - - if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume - && (interaction.mouse === mouseEvent)) { - while (element) { - // if the element is the interaction element - if (element === interaction.element) { - // update the interaction's pointer - if (interaction.pointers[0]) { - interaction.removePointer(interaction.pointers[0]); - } - interaction.addPointer(pointer); - - return interaction; - } - element = utils.parentElement(element); - } - } - } - } + || pointer.pointerType === 4); + let i = 0; + const len = scope.interactions.length; + let interaction; - // if it's a mouse interaction - if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { + const id = utils.getPointerId(pointer); - // find a mouse interaction that's not in inertia phase - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { - return scope.interactions[i]; + // try to resume inertia with a new pointer + if (/down|start/i.test(eventType)) { + for (i = 0; i < len; i++) { + interaction = scope.interactions[i]; + + let element = eventTarget; + + if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume + && (interaction.mouse === mouseEvent)) { + while (element) { + // if the element is the interaction element + if (element === interaction.element) { + // update the interaction's pointer + if (interaction.pointers[0]) { + interaction.removePointer(interaction.pointers[0]); } - } + interaction.addPointer(pointer); - // find any interaction specifically for mouse. - // if the eventType is a mousedown, and inertia is active - // ignore the interaction - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { - return interaction; - } + return interaction; + } + element = utils.parentElement(element); } + } + } + } - // create a new interaction for mouse - interaction = new Interaction(); - interaction.mouse = true; + // if it's a mouse interaction + if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { - return interaction; + // find a mouse interaction that's not in inertia phase + for (i = 0; i < len; i++) { + if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { + return scope.interactions[i]; + } } - // get interaction that has this pointer + // find any interaction specifically for mouse. + // if the eventType is a mousedown, and inertia is active + // ignore the interaction for (i = 0; i < len; i++) { - if (utils.contains(scope.interactions[i].pointerIds, id)) { - return scope.interactions[i]; - } + if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { + return interaction; + } } - // at this stage, a pointerUp should not return an interaction - if (/up|end|out/i.test(eventType)) { - return null; + // create a new interaction for mouse + interaction = new Interaction(); + interaction.mouse = true; + + return interaction; + } + + // get interaction that has this pointer + for (i = 0; i < len; i++) { + if (utils.contains(scope.interactions[i].pointerIds, id)) { + return scope.interactions[i]; } + } - // get first idle interaction - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; + // at this stage, a pointerUp should not return an interaction + if (/up|end|out/i.test(eventType)) { + return null; + } - if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) - && !interaction.interacting() - && !(!mouseEvent && interaction.mouse)) { + // get first idle interaction + for (i = 0; i < len; i++) { + interaction = scope.interactions[i]; - interaction.addPointer(pointer); + if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) + && !interaction.interacting() + && !(!mouseEvent && interaction.mouse)) { - return interaction; - } + interaction.addPointer(pointer); + + return interaction; } + } - return new Interaction(); + return new Interaction(); } function doOnInteractions (method) { - return (function (event) { - var interaction, - eventTarget = utils.getActualElement(event.path - ? event.path[0] - : event.target), - curEventTarget = utils.getActualElement(event.currentTarget), - i; + return (function (event) { + let interaction; + const eventTarget = utils.getActualElement(event.path ? event.path[0] : event.target); + const curEventTarget = utils.getActualElement(event.currentTarget); + let i; - if (browser.supportsTouch && /touch/.test(event.type)) { - scope.prevTouchTime = new Date().getTime(); + if (browser.supportsTouch && /touch/.test(event.type)) { + scope.prevTouchTime = new Date().getTime(); - for (i = 0; i < event.changedTouches.length; i++) { - var pointer = event.changedTouches[i]; + for (i = 0; i < event.changedTouches.length; i++) { + const pointer = event.changedTouches[i]; - interaction = getInteractionFromPointer(pointer, event.type, eventTarget); + interaction = getInteractionFromPointer(pointer, event.type, eventTarget); - if (!interaction) { continue; } + if (!interaction) { continue; } - interaction._updateEventTargets(eventTarget, curEventTarget); + interaction._updateEventTargets(eventTarget, curEventTarget); - interaction[method](pointer, event, eventTarget, curEventTarget); - } + interaction[method](pointer, event, eventTarget, curEventTarget); + } + } + else { + if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { + // ignore mouse events while touch interactions are active + for (i = 0; i < scope.interactions.length; i++) { + if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { + return; + } } - else { - if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { - // ignore mouse events while touch interactions are active - for (i = 0; i < scope.interactions.length; i++) { - if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { - return; - } - } - - // try to ignore mouse events that are simulated by the browser - // after a touch event - if (new Date().getTime() - scope.prevTouchTime < 500) { - return; - } - } - interaction = getInteractionFromPointer(event, event.type, eventTarget); - - if (!interaction) { return; } - - interaction._updateEventTargets(eventTarget, curEventTarget); - - interaction[method](event, event, eventTarget, curEventTarget); + // try to ignore mouse events that are simulated by the browser + // after a touch event + if (new Date().getTime() - scope.prevTouchTime < 500) { + return; } - }); -} + } -signals.on('interactable-new', function (arg) { - var interactable = arg.interactable, - element = interactable._element; + interaction = getInteractionFromPointer(event, event.type, eventTarget); - if (utils.isElement(element, arg.win)) { - if (scope.PointerEvent) { - events.add(element, browser.pEventTypes.down, listeners.pointerDown ); - events.add(element, browser.pEventTypes.move, listeners.pointerHover); - } - else { - events.add(element, 'mousedown' , listeners.pointerDown ); - events.add(element, 'mousemove' , listeners.pointerHover); - events.add(element, 'touchstart', listeners.pointerDown ); - events.add(element, 'touchmove' , listeners.pointerHover); - } - } -}); + if (!interaction) { return; } -signals.on('interactable-unset', function (arg) { - var interactable = arg.interactable, - element = interactable._element; + interaction._updateEventTargets(eventTarget, curEventTarget); - if (!interactable.selector && utils.isElement(element, arg.win)) { - if (scope.PointerEvent) { - events.remove(element, browser.pEventTypes.down, listeners.pointerDown ); - events.remove(element, browser.pEventTypes.move, listeners.pointerHover); - } - else { - events.remove(element, 'mousedown' , listeners.pointerDown ); - events.remove(element, 'mousemove' , listeners.pointerHover); - events.remove(element, 'touchstart', listeners.pointerDown ); - events.remove(element, 'touchmove' , listeners.pointerHover); - } + interaction[method](event, event, eventTarget, curEventTarget); } -}); + }); +} -signals.on('listen-to-document', function (arg) { - var doc = arg.doc, - win = arg.win, - pEventTypes = browser.pEventTypes; - - // add delegate event listener - for (var eventType in scope.delegatedEvents) { - events.add(doc, eventType, events.delegateListener); - events.add(doc, eventType, events.delegateUseCapture, true); - } +signals.on('interactable-new', function (arg) { + const interactable = arg.interactable; + const element = interactable._element; + if (utils.isElement(element, arg.win)) { if (scope.PointerEvent) { - events.add(doc, pEventTypes.down , listeners.selectorDown ); - events.add(doc, pEventTypes.move , listeners.pointerMove ); - events.add(doc, pEventTypes.over , listeners.pointerOver ); - events.add(doc, pEventTypes.out , listeners.pointerOut ); - events.add(doc, pEventTypes.up , listeners.pointerUp ); - events.add(doc, pEventTypes.cancel, listeners.pointerCancel); + events.add(element, browser.pEventTypes.down, listeners.pointerDown ); + events.add(element, browser.pEventTypes.move, listeners.pointerHover); } else { - events.add(doc, 'mousedown', listeners.selectorDown); - events.add(doc, 'mousemove', listeners.pointerMove ); - events.add(doc, 'mouseup' , listeners.pointerUp ); - events.add(doc, 'mouseover', listeners.pointerOver ); - events.add(doc, 'mouseout' , listeners.pointerOut ); - - events.add(doc, 'touchstart' , listeners.selectorDown ); - events.add(doc, 'touchmove' , listeners.pointerMove ); - events.add(doc, 'touchend' , listeners.pointerUp ); - events.add(doc, 'touchcancel', listeners.pointerCancel); + events.add(element, 'mousedown' , listeners.pointerDown ); + events.add(element, 'mousemove' , listeners.pointerHover); + events.add(element, 'touchstart', listeners.pointerDown ); + events.add(element, 'touchmove' , listeners.pointerHover); } + } +}); - events.add(win, 'blur', scope.endAllInteractions); - - try { - if (win.frameElement) { - var parentDoc = win.frameElement.ownerDocument, - parentWindow = parentDoc.defaultView; +signals.on('interactable-unset', function (arg) { + const interactable = arg.interactable; + const element = interactable._element; - events.add(parentDoc , 'mouseup' , listeners.pointerEnd); - events.add(parentDoc , 'touchend' , listeners.pointerEnd); - events.add(parentDoc , 'touchcancel' , listeners.pointerEnd); - events.add(parentDoc , 'pointerup' , listeners.pointerEnd); - events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd); - events.add(parentWindow, 'blur' , scope.endAllInteractions ); - } + if (!interactable.selector && utils.isElement(element, arg.win)) { + if (scope.PointerEvent) { + events.remove(element, browser.pEventTypes.down, listeners.pointerDown ); + events.remove(element, browser.pEventTypes.move, listeners.pointerHover); } - catch (error) { - scope.windowParentError = error; + else { + events.remove(element, 'mousedown' , listeners.pointerDown ); + events.remove(element, 'mousemove' , listeners.pointerHover); + events.remove(element, 'touchstart', listeners.pointerDown ); + events.remove(element, 'touchmove' , listeners.pointerHover); } + } +}); - if (browser.isIE8) { - // For IE's lack of Event#preventDefault - events.add(doc, 'selectstart', function (event) { - var interaction = scope.interactions[0]; - - if (interaction.currentAction()) { - interaction.checkAndPreventDefault(event); - } - }); +signals.on('listen-to-document', function (arg) { + const doc = arg.doc; + const win = arg.win; + const pEventTypes = browser.pEventTypes; + + // add delegate event listener + for (const eventType in scope.delegatedEvents) { + events.add(doc, eventType, events.delegateListener); + events.add(doc, eventType, events.delegateUseCapture, true); + } + + if (scope.PointerEvent) { + events.add(doc, pEventTypes.down , listeners.selectorDown ); + events.add(doc, pEventTypes.move , listeners.pointerMove ); + events.add(doc, pEventTypes.over , listeners.pointerOver ); + events.add(doc, pEventTypes.out , listeners.pointerOut ); + events.add(doc, pEventTypes.up , listeners.pointerUp ); + events.add(doc, pEventTypes.cancel, listeners.pointerCancel); + } + else { + events.add(doc, 'mousedown', listeners.selectorDown); + events.add(doc, 'mousemove', listeners.pointerMove ); + events.add(doc, 'mouseup' , listeners.pointerUp ); + events.add(doc, 'mouseover', listeners.pointerOver ); + events.add(doc, 'mouseout' , listeners.pointerOut ); + + events.add(doc, 'touchstart' , listeners.selectorDown ); + events.add(doc, 'touchmove' , listeners.pointerMove ); + events.add(doc, 'touchend' , listeners.pointerUp ); + events.add(doc, 'touchcancel', listeners.pointerCancel); + } + + events.add(win, 'blur', scope.endAllInteractions); + + try { + if (win.frameElement) { + const parentDoc = win.frameElement.ownerDocument; + const parentWindow = parentDoc.defaultView; + + events.add(parentDoc , 'mouseup' , listeners.pointerEnd); + events.add(parentDoc , 'touchend' , listeners.pointerEnd); + events.add(parentDoc , 'touchcancel' , listeners.pointerEnd); + events.add(parentDoc , 'pointerup' , listeners.pointerEnd); + events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd); + events.add(parentWindow, 'blur' , scope.endAllInteractions ); } + } + catch (error) { + scope.windowParentError = error; + } + + if (browser.isIE8) { + // For IE's lack of Event#preventDefault + events.add(doc, 'selectstart', function (event) { + const interaction = scope.interactions[0]; + + if (interaction.currentAction()) { + interaction.checkAndPreventDefault(event); + } + }); + } - scope.documents.push(doc); - events.documents.push(doc); + scope.documents.push(doc); + events.documents.push(doc); }); signals.fire('listen-to-document', { - win: scope.window, - doc: scope.document + win: scope.window, + doc: scope.document, }); Interaction.getInteractionFromPointer = getInteractionFromPointer; diff --git a/src/actions/base.js b/src/actions/base.js index 19488c5d5..771f4589d 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -1,25 +1,23 @@ -'use strict'; +const scope = require('../scope'); -var scope = require('../scope'); +const actions = { + scope: scope, -var actions = { - scope: scope, + defaultChecker: function (pointer, event, interaction, element) { + const rect = this.getRect(element); + let action = null; - defaultChecker: function (pointer, event, interaction, element) { - var rect = this.getRect(element), - action = null; - - for (var i = 0; !action && i < actions.names.length; i++) { - var actionName = actions.names[i]; - - action = actions[actionName].checker(pointer, event, this, element, interaction, rect); - } + for (const actionName of actions.names) { + action = actions[actionName].checker(pointer, event, this, element, interaction, rect); + if (action) { return action; - }, + } + } + }, - names: [], - methodDict: {} + names: [], + methodDict: {}, }; module.exports = actions; diff --git a/src/actions/drag.js b/src/actions/drag.js index d985db519..9a355c9d3 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -1,153 +1,150 @@ -'use strict'; - -var base = require('./base'), - drop = require('./drop'), - scope = base.scope, - utils = require('../utils'), - browser = utils.browser, - InteractEvent = require('../InteractEvent'), - Interactable = require('../Interactable'), - defaultOptions = require('../defaultOptions'); - -var drag = { - defaults: { - enabled: false, - manualStart: true, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - axis: 'xy' - }, - - checker: function (pointer, event, interactable) { - return interactable.options.drag.enabled - ? { name: 'drag' } - : null; - }, - - getCursor: function () { - return 'move'; - }, - - beforeStart: function (interaction, pointer, event, eventTarget, curEventTarget, dx, dy) { - // check if a drag is in the correct axis - var absX = Math.abs(dx), - absY = Math.abs(dy), - targetAxis = interaction.target.options.drag.axis, - axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); - - // if the movement isn't in the axis of the interactable - if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { - // cancel the prepared action - interaction.prepared.name = null; - - // then try to get a drag from another ineractable - - var element = eventTarget; - - // check element interactables - while (utils.isElement(element)) { - var elementInteractable = scope.interactables.get(element); - - if (elementInteractable - && elementInteractable !== interaction.target - && !elementInteractable.options.drag.manualStart - && elementInteractable.getAction(interaction.downPointer, interaction.downEvent, interaction, element).name === 'drag' - && checkAxis(axis, elementInteractable)) { - - interaction.prepared.name = 'drag'; - interaction.target = elementInteractable; - interaction.element = element; - break; - } - - element = utils.parentElement(element); - } - - // if there's no drag from element interactables, - // check the selector interactables - if (!interaction.prepared.name) { - var interactionInteraction = interaction; - - var getDraggable = function (interactable, selector, context) { - var elements = browser.useMatchesSelectorPolyfill - ? context.querySelectorAll(selector) - : undefined; - - if (interactable === interactionInteraction.target) { return; } - - if (scope.inContext(interactable, eventTarget) - && !interactable.options.drag.manualStart - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && utils.matchesSelector(element, selector, elements) - && interactable.getAction(interactionInteraction.downPointer, interactionInteraction.downEvent, interactionInteraction, element).name === 'drag' - && checkAxis(axis, interactable) - && scope.withinInteractionLimit(interactable, element, 'drag')) { - - return interactable; - } - }; - - element = eventTarget; - - while (utils.isElement(element)) { - var selectorInteractable = scope.interactables.forEachSelector(getDraggable); - - if (selectorInteractable) { - interaction.prepared.name = 'drag'; - interaction.target = selectorInteractable; - interaction.element = element; - break; - } - - element = utils.parentElement(element); - } - } +const base = require('./base'); +const drop = require('./drop'); +const scope = base.scope; +const utils = require('../utils'); +const browser = utils.browser; +const InteractEvent = require('../InteractEvent'); +const Interactable = require('../Interactable'); +const defaultOptions = require('../defaultOptions'); + +const drag = { + defaults: { + enabled: false, + manualStart: true, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + axis: 'xy', + }, + + checker: function (pointer, event, interactable) { + return interactable.options.drag.enabled + ? { name: 'drag' } + : null; + }, + + getCursor: function () { + return 'move'; + }, + + beforeStart: function (interaction, pointer, event, eventTarget, curEventTarget, dx, dy) { + // check if a drag is in the correct axis + const absX = Math.abs(dx); + const absY = Math.abs(dy); + const targetAxis = interaction.target.options.drag.axis; + const axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy'); + + // if the movement isn't in the axis of the interactable + if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) { + // cancel the prepared action + interaction.prepared.name = null; + + // then try to get a drag from another ineractable + + let element = eventTarget; + + // check element interactables + while (utils.isElement(element)) { + const elementInteractable = scope.interactables.get(element); + + if (elementInteractable + && elementInteractable !== interaction.target + && !elementInteractable.options.drag.manualStart + && elementInteractable.getAction(interaction.downPointer, interaction.downEvent, interaction, element).name === 'drag' + && checkAxis(axis, elementInteractable)) { + + interaction.prepared.name = 'drag'; + interaction.target = elementInteractable; + interaction.element = element; + break; } - }, - start: function (interaction, event) { - var dragEvent = new InteractEvent(interaction, event, 'drag', 'start', interaction.element); + element = utils.parentElement(element); + } - interaction._interacting = true; - interaction.target.fire(dragEvent); + // if there's no drag from element interactables, + // check the selector interactables + if (!interaction.prepared.name) { - drop.start(interaction, event, dragEvent); + const getDraggable = function (interactable, selector, context) { + const elements = browser.useMatchesSelectorPolyfill + ? context.querySelectorAll(selector) + : undefined; - return dragEvent; - }, + if (interactable === interaction.target) { return; } - move: function (interaction, event) { - var dragEvent = new InteractEvent(interaction, event, 'drag', 'move', interaction.element); + if (scope.inContext(interactable, eventTarget) + && !interactable.options.drag.manualStart + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && utils.matchesSelector(element, selector, elements) + && interactable.getAction(interaction.downPointer, interaction.downEvent, interaction, element).name === 'drag' + && checkAxis(axis, interactable) + && scope.withinInteractionLimit(interactable, element, 'drag')) { - drop.move(interaction, event, dragEvent); + return interactable; + } + }; - return dragEvent; - }, + element = eventTarget; - end: function (interaction, event) { - var endEvent = new InteractEvent(interaction, event, 'drag', 'end', interaction.element); + while (utils.isElement(element)) { + const selectorInteractable = scope.interactables.forEachSelector(getDraggable); - drop.end(interaction, event, endEvent); + if (selectorInteractable) { + interaction.prepared.name = 'drag'; + interaction.target = selectorInteractable; + interaction.element = element; + break; + } - interaction.target.fire(endEvent); - }, + element = utils.parentElement(element); + } + } + } + }, + + start: function (interaction, event) { + const dragEvent = new InteractEvent(interaction, event, 'drag', 'start', interaction.element); + + interaction._interacting = true; + interaction.target.fire(dragEvent); + + drop.start(interaction, event, dragEvent); + + return dragEvent; + }, + + move: function (interaction, event) { + const dragEvent = new InteractEvent(interaction, event, 'drag', 'move', interaction.element); + + drop.move(interaction, event, dragEvent); + + return dragEvent; + }, - stop: drop.stop + end: function (interaction, event) { + const endEvent = new InteractEvent(interaction, event, 'drag', 'end', interaction.element); + + drop.end(interaction, event, endEvent); + + interaction.target.fire(endEvent); + }, + + stop: drop.stop, }; function checkAxis (axis, interactable) { - if (!interactable) { return false; } + if (!interactable) { return false; } - var thisAxis = interactable.options.drag.axis; + const thisAxis = interactable.options.drag.axis; - return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); + return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis); } /*\ @@ -182,37 +179,37 @@ function checkAxis (axis, interactable) { | }); \*/ Interactable.prototype.draggable = function (options) { - if (utils.isObject(options)) { - this.options.drag.enabled = options.enabled === false? false: true; - this.setPerAction('drag', options); - this.setOnEvents('drag', options); + if (utils.isObject(options)) { + this.options.drag.enabled = options.enabled === false? false: true; + this.setPerAction('drag', options); + this.setOnEvents('drag', options); - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.drag.axis = options.axis; - } - else if (options.axis === null) { - delete this.options.drag.axis; - } - - return this; + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.drag.axis = options.axis; + } + else if (options.axis === null) { + delete this.options.drag.axis; } - if (utils.isBool(options)) { - this.options.drag.enabled = options; + return this; + } - return this; - } + if (utils.isBool(options)) { + this.options.drag.enabled = options; + + return this; + } - return this.options.drag; + return this.options.drag; }; base.drag = drag; base.names.push('drag'); utils.merge(scope.eventTypes, [ - 'dragstart', - 'dragmove', - 'draginertiastart', - 'dragend' + 'dragstart', + 'dragmove', + 'draginertiastart', + 'dragend', ]); base.methodDict.drag = 'draggable'; diff --git a/src/actions/drop.js b/src/actions/drop.js index ac65c1245..74904ccdc 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -1,286 +1,277 @@ -'use strict'; - -var base = require('./base'), - utils = require('../utils'), - scope = require('../scope'), - signals = require('../utils/signals'), - Interactable = require('../Interactable'), - defaultOptions = require('../defaultOptions'); - -var drop = { - defaults: { - enabled: false, - accept: null, - overlap: 'pointer' - }, - - start: function (interaction, event, dragEvent) { - // reset active dropzones - interaction.activeDrops.dropzones = []; - interaction.activeDrops.elements = []; - interaction.activeDrops.rects = []; - - if (!interaction.dynamicDrop) { - setActiveDrops(interaction, interaction.element); - } - - var dropEvents = getDropEvents(interaction, event, dragEvent); - - if (dropEvents.activate) { - fireActiveDrops(interaction, dropEvents.activate); - } - }, - move: function (interaction, event, dragEvent) { - var draggableElement = interaction.element, - drop = getDrop(interaction, event, draggableElement); - - interaction.dropTarget = drop.dropzone; - interaction.dropElement = drop.element; - - var dropEvents = getDropEvents(interaction, event, dragEvent); - - interaction.target.fire(dragEvent); - - if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } - if (dropEvents.move ) { interaction.dropTarget.fire(dropEvents.move ); } - - interaction.prevDropTarget = interaction.dropTarget; - interaction.prevDropElement = interaction.dropElement; - - }, - end: function (interaction, event, endEvent) { - var draggableElement = interaction.element, - drop = getDrop(interaction, event, draggableElement); - - interaction.dropTarget = drop.dropzone; - interaction.dropElement = drop.element; - - var dropEvents = getDropEvents(interaction, event, endEvent); - - if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } - if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } - if (dropEvents.drop ) { interaction.dropTarget.fire(dropEvents.drop ); } - if (dropEvents.deactivate) { - fireActiveDrops(interaction, dropEvents.deactivate); - } - - }, - stop: function (interaction) { - interaction.activeDrops.dropzones = - interaction.activeDrops.elements = - interaction.activeDrops.rects = null; +const base = require('./base'); +const utils = require('../utils'); +const scope = require('../scope'); +const signals = require('../utils/signals'); +const Interactable = require('../Interactable'); +const defaultOptions = require('../defaultOptions'); + +const drop = { + defaults: { + enabled: false, + accept: null, + overlap: 'pointer', + }, + + start: function (interaction, event, dragEvent) { + // reset active dropzones + interaction.activeDrops.dropzones = []; + interaction.activeDrops.elements = []; + interaction.activeDrops.rects = []; + + if (!interaction.dynamicDrop) { + setActiveDrops(interaction, interaction.element); } + + const dropEvents = getDropEvents(interaction, event, dragEvent); + + if (dropEvents.activate) { + fireActiveDrops(interaction, dropEvents.activate); + } + }, + move: function (interaction, event, dragEvent) { + const draggableElement = interaction.element; + const drop = getDrop(interaction, event, draggableElement); + + interaction.dropTarget = drop.dropzone; + interaction.dropElement = drop.element; + + const dropEvents = getDropEvents(interaction, event, dragEvent); + + interaction.target.fire(dragEvent); + + if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } + if (dropEvents.move ) { interaction.dropTarget.fire(dropEvents.move ); } + + interaction.prevDropTarget = interaction.dropTarget; + interaction.prevDropElement = interaction.dropElement; + + }, + end: function (interaction, event, endEvent) { + const draggableElement = interaction.element; + const drop = getDrop(interaction, event, draggableElement); + + interaction.dropTarget = drop.dropzone; + interaction.dropElement = drop.element; + + const dropEvents = getDropEvents(interaction, event, endEvent); + + if (dropEvents.leave) { interaction.prevDropTarget.fire(dropEvents.leave); } + if (dropEvents.enter) { interaction.dropTarget.fire(dropEvents.enter); } + if (dropEvents.drop ) { interaction.dropTarget.fire(dropEvents.drop ); } + if (dropEvents.deactivate) { + fireActiveDrops(interaction, dropEvents.deactivate); + } + + }, + stop: function (interaction) { + interaction.activeDrops.dropzones = + interaction.activeDrops.elements = + interaction.activeDrops.rects = null; + }, }; function collectDrops (interaction, element) { - var drops = [], - elements = [], - i; + const drops = []; + const elements = []; - element = element || interaction.element; + element = element || interaction.element; - // collect all dropzones and their elements which qualify for a drop - for (i = 0; i < scope.interactables.length; i++) { - if (!scope.interactables[i].options.drop.enabled) { continue; } + // collect all dropzones and their elements which qualify for a drop + for (const current of scope.interactables) { + if (!current.options.drop.enabled) { continue; } - var current = scope.interactables[i], - accept = current.options.drop.accept; + const accept = current.options.drop.accept; - // test the draggable element against the dropzone's accept setting - if ((utils.isElement(accept) && accept !== element) - || (utils.isString(accept) - && !utils.matchesSelector(element, accept))) { + // test the draggable element against the dropzone's accept setting + if ((utils.isElement(accept) && accept !== element) + || (utils.isString(accept) + && !utils.matchesSelector(element, accept))) { - continue; - } - - // query for new elements if necessary - var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; + continue; + } - for (var j = 0, len = dropElements.length; j < len; j++) { - var currentElement = dropElements[j]; + // query for new elements if necessary + const dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; - if (currentElement === element) { - continue; - } + for (let i = 0; i < dropElements.length; i++) { + const currentElement = dropElements[i]; - drops.push(current); - elements.push(currentElement); - } + if (currentElement !== element) { + drops.push(current); + elements.push(currentElement); + } } + } - return { - dropzones: drops, - elements: elements - }; + return { + dropzones: drops, + elements: elements, + }; } function fireActiveDrops (interaction, event) { - var i, - current, - currentElement, - prevElement; - - // loop through all active dropzones and trigger event - for (i = 0; i < interaction.activeDrops.dropzones.length; i++) { - current = interaction.activeDrops.dropzones[i]; - currentElement = interaction.activeDrops.elements [i]; - - // prevent trigger of duplicate events on same element - if (currentElement !== prevElement) { - // set current element as event target - event.target = currentElement; - current.fire(event); - } - prevElement = currentElement; + let i; + let current; + let currentElement; + let prevElement; + + // loop through all active dropzones and trigger event + for (i = 0; i < interaction.activeDrops.dropzones.length; i++) { + current = interaction.activeDrops.dropzones[i]; + currentElement = interaction.activeDrops.elements [i]; + + // prevent trigger of duplicate events on same element + if (currentElement !== prevElement) { + // set current element as event target + event.target = currentElement; + current.fire(event); } + prevElement = currentElement; + } } // Collect a new set of possible drops and save them in activeDrops. // setActiveDrops should always be called when a drag has just started or a // drag event happens while dynamicDrop is true function setActiveDrops (interaction, dragElement) { - // get dropzones and their elements that could receive the draggable - var possibleDrops = collectDrops(interaction, dragElement, true); + // get dropzones and their elements that could receive the draggable + const possibleDrops = collectDrops(interaction, dragElement, true); - interaction.activeDrops.dropzones = possibleDrops.dropzones; - interaction.activeDrops.elements = possibleDrops.elements; - interaction.activeDrops.rects = []; + interaction.activeDrops.dropzones = possibleDrops.dropzones; + interaction.activeDrops.elements = possibleDrops.elements; + interaction.activeDrops.rects = []; - for (var i = 0; i < interaction.activeDrops.dropzones.length; i++) { - interaction.activeDrops.rects[i] = interaction.activeDrops.dropzones[i].getRect(interaction.activeDrops.elements[i]); - } + for (let i = 0; i < interaction.activeDrops.dropzones.length; i++) { + interaction.activeDrops.rects[i] = interaction.activeDrops.dropzones[i].getRect(interaction.activeDrops.elements[i]); + } } function getDrop (interaction, event, dragElement) { - var validDrops = []; + const validDrops = []; - if (scope.dynamicDrop) { - setActiveDrops(interaction, dragElement); - } + if (scope.dynamicDrop) { + setActiveDrops(interaction, dragElement); + } - // collect all dropzones and their elements which qualify for a drop - for (var j = 0; j < interaction.activeDrops.dropzones.length; j++) { - var current = interaction.activeDrops.dropzones[j], - currentElement = interaction.activeDrops.elements [j], - rect = interaction.activeDrops.rects [j]; + // collect all dropzones and their elements which qualify for a drop + for (let j = 0; j < interaction.activeDrops.dropzones.length; j++) { + const current = interaction.activeDrops.dropzones[j]; + const currentElement = interaction.activeDrops.elements [j]; + const rect = interaction.activeDrops.rects [j]; - validDrops.push(current.dropCheck(interaction.pointers[0], event, interaction.target, dragElement, currentElement, rect) - ? currentElement - : null); - } + validDrops.push(current.dropCheck(interaction.pointers[0], event, interaction.target, dragElement, currentElement, rect) + ? currentElement + : null); + } - // get the most appropriate dropzone based on DOM depth and order - var dropIndex = utils.indexOfDeepestElement(validDrops), - dropzone = interaction.activeDrops.dropzones[dropIndex] || null, - element = interaction.activeDrops.elements [dropIndex] || null; + // get the most appropriate dropzone based on DOM depth and order + const dropIndex = utils.indexOfDeepestElement(validDrops); + const dropzone = interaction.activeDrops.dropzones[dropIndex] || null; + const element = interaction.activeDrops.elements [dropIndex] || null; - return { - dropzone: dropzone, - element: element - }; + return { dropzone, element }; } function getDropEvents (interaction, pointerEvent, dragEvent) { - var dropEvents = { - enter : null, - leave : null, - activate : null, - deactivate: null, - move : null, - drop : null - }; - - if (interaction.dropElement !== interaction.prevDropElement) { - // if there was a prevDropTarget, create a dragleave event - if (interaction.prevDropTarget) { - dropEvents.leave = { - target : interaction.prevDropElement, - dropzone : interaction.prevDropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dragleave' - }; - - dragEvent.dragLeave = interaction.prevDropElement; - dragEvent.prevDropzone = interaction.prevDropTarget; - } - // if the dropTarget is not null, create a dragenter event - if (interaction.dropTarget) { - dropEvents.enter = { - target : interaction.dropElement, - dropzone : interaction.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dragenter' - }; - - dragEvent.dragEnter = interaction.dropElement; - dragEvent.dropzone = interaction.dropTarget; - } + const dropEvents = { + enter : null, + leave : null, + activate : null, + deactivate: null, + move : null, + drop : null, + }; + + if (interaction.dropElement !== interaction.prevDropElement) { + // if there was a prevDropTarget, create a dragleave event + if (interaction.prevDropTarget) { + dropEvents.leave = { + target : interaction.prevDropElement, + dropzone : interaction.prevDropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dragleave', + }; + + dragEvent.dragLeave = interaction.prevDropElement; + dragEvent.prevDropzone = interaction.prevDropTarget; } - - if (dragEvent.type === 'dragend' && interaction.dropTarget) { - dropEvents.drop = { - target : interaction.dropElement, - dropzone : interaction.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'drop' - }; - - dragEvent.dropzone = interaction.dropTarget; - } - if (dragEvent.type === 'dragstart') { - dropEvents.activate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dropactivate' - }; - } - if (dragEvent.type === 'dragend') { - dropEvents.deactivate = { - target : null, - dropzone : null, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - timeStamp : dragEvent.timeStamp, - type : 'dropdeactivate' - }; - } - if (dragEvent.type === 'dragmove' && interaction.dropTarget) { - dropEvents.move = { - target : interaction.dropElement, - dropzone : interaction.dropTarget, - relatedTarget: dragEvent.target, - draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, - dragmove : dragEvent, - timeStamp : dragEvent.timeStamp, - type : 'dropmove' - }; - dragEvent.dropzone = interaction.dropTarget; + // if the dropTarget is not null, create a dragenter event + if (interaction.dropTarget) { + dropEvents.enter = { + target : interaction.dropElement, + dropzone : interaction.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dragenter', + }; + + dragEvent.dragEnter = interaction.dropElement; + dragEvent.dropzone = interaction.dropTarget; } + } + + if (dragEvent.type === 'dragend' && interaction.dropTarget) { + dropEvents.drop = { + target : interaction.dropElement, + dropzone : interaction.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'drop', + }; - return dropEvents; + dragEvent.dropzone = interaction.dropTarget; + } + if (dragEvent.type === 'dragstart') { + dropEvents.activate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dropactivate', + }; + } + if (dragEvent.type === 'dragend') { + dropEvents.deactivate = { + target : null, + dropzone : null, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + timeStamp : dragEvent.timeStamp, + type : 'dropdeactivate', + }; + } + if (dragEvent.type === 'dragmove' && interaction.dropTarget) { + dropEvents.move = { + target : interaction.dropElement, + dropzone : interaction.dropTarget, + relatedTarget: dragEvent.target, + draggable : dragEvent.interactable, + dragEvent : dragEvent, + interaction : interaction, + dragmove : dragEvent, + timeStamp : dragEvent.timeStamp, + type : 'dropmove', + }; + dragEvent.dropzone = interaction.dropTarget; + } + + return dropEvents; } /*\ @@ -313,87 +304,88 @@ function getDropEvents (interaction, pointerEvent, dragEvent) { = (boolean | object) The current setting or this Interactable \*/ Interactable.prototype.dropzone = function (options) { - if (utils.isObject(options)) { - this.options.drop.enabled = options.enabled === false? false: true; - - if (utils.isFunction(options.ondrop) ) { this.ondrop = options.ondrop ; } - if (utils.isFunction(options.ondropactivate) ) { this.ondropactivate = options.ondropactivate ; } - if (utils.isFunction(options.ondropdeactivate)) { this.ondropdeactivate = options.ondropdeactivate; } - if (utils.isFunction(options.ondragenter) ) { this.ondragenter = options.ondragenter ; } - if (utils.isFunction(options.ondragleave) ) { this.ondragleave = options.ondragleave ; } - if (utils.isFunction(options.ondropmove) ) { this.ondropmove = options.ondropmove ; } - - this.accept(options.accept); - - if (/^(pointer|center)$/.test(options.overlap)) { - this.options.drop.overlap = options.overlap; - } - else if (utils.isNumber(options.overlap)) { - this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); - } - - return this; - } + if (utils.isObject(options)) { + this.options.drop.enabled = options.enabled === false? false: true; + + if (utils.isFunction(options.ondrop) ) { this.ondrop = options.ondrop ; } + if (utils.isFunction(options.ondropactivate) ) { this.ondropactivate = options.ondropactivate ; } + if (utils.isFunction(options.ondropdeactivate)) { this.ondropdeactivate = options.ondropdeactivate; } + if (utils.isFunction(options.ondragenter) ) { this.ondragenter = options.ondragenter ; } + if (utils.isFunction(options.ondragleave) ) { this.ondragleave = options.ondragleave ; } + if (utils.isFunction(options.ondropmove) ) { this.ondropmove = options.ondropmove ; } - if (utils.isBool(options)) { - this.options.drop.enabled = options; + this.accept(options.accept); - return this; + if (/^(pointer|center)$/.test(options.overlap)) { + this.options.drop.overlap = options.overlap; } + else if (utils.isNumber(options.overlap)) { + this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); + } + + return this; + } + + if (utils.isBool(options)) { + this.options.drop.enabled = options; - return this.options.drop; + return this; + } + + return this.options.drop; }; Interactable.prototype.dropCheck = function (pointer, event, draggable, draggableElement, dropElement, rect) { - var dropped = false; - - // if the dropzone has no rect (eg. display: none) - // call the custom dropChecker or just return false - if (!(rect = rect || this.getRect(dropElement))) { - return (this.options.dropChecker - ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) - : false); - } + let dropped = false; - var dropOverlap = this.options.drop.overlap; + // if the dropzone has no rect (eg. display: none) + // call the custom dropChecker or just return false + if (!(rect = rect || this.getRect(dropElement))) { + return (this.options.dropChecker + ? this.options.dropChecker(pointer, event, dropped, this, dropElement, draggable, draggableElement) + : false); + } - if (dropOverlap === 'pointer') { - var page = utils.getPageXY(pointer), - origin = utils.getOriginXY(draggable, draggableElement), - horizontal, - vertical; + const dropOverlap = this.options.drop.overlap; - page.x += origin.x; - page.y += origin.y; + if (dropOverlap === 'pointer') { + const origin = utils.getOriginXY(draggable, draggableElement); + const page = utils.getPageXY(pointer); + let horizontal; + let vertical; - horizontal = (page.x > rect.left) && (page.x < rect.right); - vertical = (page.y > rect.top ) && (page.y < rect.bottom); + page.x += origin.x; + page.y += origin.y; - dropped = horizontal && vertical; - } + horizontal = (page.x > rect.left) && (page.x < rect.right); + vertical = (page.y > rect.top ) && (page.y < rect.bottom); - var dragRect = draggable.getRect(draggableElement); + dropped = horizontal && vertical; + } - if (dropOverlap === 'center') { - var cx = dragRect.left + dragRect.width / 2, - cy = dragRect.top + dragRect.height / 2; + const dragRect = draggable.getRect(draggableElement); - dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; - } + if (dropOverlap === 'center') { + const cx = dragRect.left + dragRect.width / 2; + const cy = dragRect.top + dragRect.height / 2; - if (utils.isNumber(dropOverlap)) { - var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) - * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))), - overlapRatio = overlapArea / (dragRect.width * dragRect.height); + dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom; + } - dropped = overlapRatio >= dropOverlap; - } + if (utils.isNumber(dropOverlap)) { + const overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left)) + * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))); - if (this.options.dropChecker) { - dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); - } + const overlapRatio = overlapArea / (dragRect.width * dragRect.height); + + dropped = overlapRatio >= dropOverlap; + } - return dropped; + if (this.options.dropChecker) { + dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement); + } + + return dropped; }; /*\ @@ -418,7 +410,7 @@ Interactable.prototype.dropCheck = function (pointer, event, draggable, draggabl * > Usage: | interact(target) - | .dropChecker(function(pointer, // Touch/PointerEvent/MouseEvent + | .dropChecker(function (pointer, // Touch/PointerEvent/MouseEvent | event, // TouchEvent/PointerEvent/MouseEvent | dropped, // result of the default checker | dropzone, // dropzone Interactable @@ -430,18 +422,18 @@ Interactable.prototype.dropCheck = function (pointer, event, draggable, draggabl | } \*/ Interactable.prototype.dropChecker = function (checker) { - if (utils.isFunction(checker)) { - this.options.dropChecker = checker; + if (utils.isFunction(checker)) { + this.options.dropChecker = checker; - return this; - } - if (checker === null) { - delete this.options.getRect; + return this; + } + if (checker === null) { + delete this.options.getRect; - return this; - } + return this; + } - return this.options.dropChecker; + return this.options.dropChecker; }; /*\ @@ -462,42 +454,42 @@ Interactable.prototype.dropChecker = function (checker) { = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable \*/ Interactable.prototype.accept = function (newValue) { - if (utils.isElement(newValue)) { - this.options.drop.accept = newValue; + if (utils.isElement(newValue)) { + this.options.drop.accept = newValue; - return this; - } + return this; + } - // test if it is a valid CSS selector - if (utils.trySelector(newValue)) { - this.options.drop.accept = newValue; + // test if it is a valid CSS selector + if (utils.trySelector(newValue)) { + this.options.drop.accept = newValue; - return this; - } + return this; + } - if (newValue === null) { - delete this.options.drop.accept; + if (newValue === null) { + delete this.options.drop.accept; - return this; - } + return this; + } - return this.options.drop.accept; + return this.options.drop.accept; }; signals.on('interaction-stop', function (arg) { - var interaction = arg.interaction; + const interaction = arg.interaction; - interaction.dropTarget = interaction.dropElement - = interaction.prevDropTarget = interaction.prevDropElement = null; + interaction.dropTarget = interaction.dropElement = + interaction.prevDropTarget = interaction.prevDropElement = null; }); utils.merge(scope.eventTypes, [ - 'dragenter', - 'dragleave', - 'dropactivate', - 'dropdeactivate', - 'dropmove', - 'drop' + 'dragenter', + 'dragleave', + 'dropactivate', + 'dropdeactivate', + 'dropmove', + 'drop', ]); base.methodDict.drop = 'dropzone'; diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 97cc4eb3c..1525f0971 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -1,86 +1,84 @@ -'use strict'; - -var base = require('./base'), - utils = require('../utils'), - InteractEvent = require('../InteractEvent'), - Interactable = require('../Interactable'), - scope = base.scope, - signals = require('../utils/signals'), - defaultOptions = require('../defaultOptions'); - -var gesture = { - defaults: { - manualStart: false, - enabled: false, - max: Infinity, - maxPerElement: 1, - - restrict: null - }, - - checker: function (pointer, event, interactable, element, interaction) { - if (interaction.pointerIds.length >= 2) { - return { name: 'gesture' }; - } +const base = require('./base'); +const utils = require('../utils'); +const InteractEvent = require('../InteractEvent'); +const Interactable = require('../Interactable'); +const scope = base.scope; +const signals = require('../utils/signals'); +const defaultOptions = require('../defaultOptions'); + +const gesture = { + defaults: { + manualStart: false, + enabled: false, + max: Infinity, + maxPerElement: 1, + + restrict: null, + }, + + checker: function (pointer, event, interactable, element, interaction) { + if (interaction.pointerIds.length >= 2) { + return { name: 'gesture' }; + } - return null; - }, + return null; + }, - getCursor: function () { - return ''; - }, + getCursor: function () { + return ''; + }, - beforeStart: utils.blank, + beforeStart: utils.blank, - start: function (interaction, event) { - var gestureEvent = new InteractEvent(interaction, event, 'gesture', 'start', interaction.element); + start: function (interaction, event) { + const gestureEvent = new InteractEvent(interaction, event, 'gesture', 'start', interaction.element); - gestureEvent.ds = 0; + gestureEvent.ds = 0; - interaction.gesture.startDistance = interaction.gesture.prevDistance = gestureEvent.distance; - interaction.gesture.startAngle = interaction.gesture.prevAngle = gestureEvent.angle; - interaction.gesture.scale = 1; + interaction.gesture.startDistance = interaction.gesture.prevDistance = gestureEvent.distance; + interaction.gesture.startAngle = interaction.gesture.prevAngle = gestureEvent.angle; + interaction.gesture.scale = 1; - interaction._interacting = true; + interaction._interacting = true; - interaction.target.fire(gestureEvent); + interaction.target.fire(gestureEvent); - return gestureEvent; - }, + return gestureEvent; + }, - move: function (interaction, event) { - if (!interaction.pointerIds.length) { - return interaction.prevEvent; - } + move: function (interaction, event) { + if (!interaction.pointerIds.length) { + return interaction.prevEvent; + } - var gestureEvent; + let gestureEvent; - gestureEvent = new InteractEvent(interaction, event, 'gesture', 'move', interaction.element); - gestureEvent.ds = gestureEvent.scale - interaction.gesture.scale; + gestureEvent = new InteractEvent(interaction, event, 'gesture', 'move', interaction.element); + gestureEvent.ds = gestureEvent.scale - interaction.gesture.scale; - interaction.target.fire(gestureEvent); + interaction.target.fire(gestureEvent); - interaction.gesture.prevAngle = gestureEvent.angle; - interaction.gesture.prevDistance = gestureEvent.distance; + interaction.gesture.prevAngle = gestureEvent.angle; + interaction.gesture.prevDistance = gestureEvent.distance; - if (gestureEvent.scale !== Infinity && - gestureEvent.scale !== null && - gestureEvent.scale !== undefined && + if (gestureEvent.scale !== Infinity && + gestureEvent.scale !== null && + gestureEvent.scale !== undefined && !isNaN(gestureEvent.scale)) { - interaction.gesture.scale = gestureEvent.scale; - } + interaction.gesture.scale = gestureEvent.scale; + } - return gestureEvent; - }, + return gestureEvent; + }, - end: function (interaction, event) { - var endEvent = new InteractEvent(interaction, event, 'gesture', 'end', interaction.element); + end: function (interaction, event) { + const endEvent = new InteractEvent(interaction, event, 'gesture', 'end', interaction.element); - interaction.target.fire(endEvent); - }, + interaction.target.fire(endEvent); + }, - stop: utils.blank + stop: utils.blank, }; /*\ @@ -91,7 +89,7 @@ var gesture = { * Interactable's element * = (boolean) Indicates if this can be the target of gesture events - | var isGestureable = interact(element).gesturable(); + | var isGestureable = interact(element).gesturable(); * or - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable) = (object) this Interactable @@ -107,67 +105,65 @@ var gesture = { | }); \*/ Interactable.prototype.gesturable = function (options) { - if (utils.isObject(options)) { - this.options.gesture.enabled = options.enabled === false? false: true; - this.setPerAction('gesture', options); - this.setOnEvents('gesture', options); + if (utils.isObject(options)) { + this.options.gesture.enabled = options.enabled === false? false: true; + this.setPerAction('gesture', options); + this.setOnEvents('gesture', options); - return this; - } + return this; + } - if (utils.isBool(options)) { - this.options.gesture.enabled = options; + if (utils.isBool(options)) { + this.options.gesture.enabled = options; - return this; - } + return this; + } - return this.options.gesture; + return this.options.gesture; }; signals.on('interactevent-delta', function (arg) { - if (arg.action !== 'gesture') { return; } - - var interaction = arg.interaction, - iEvent = arg.interactEvent, - pointers = interaction.pointers; - - iEvent.touches = [pointers[0], pointers[1]]; - - if (arg.starting) { - iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); - iEvent.box = utils.touchBBox(pointers); - iEvent.scale = 1; - iEvent.ds = 0; - iEvent.angle = utils.touchAngle(pointers, undefined, arg.deltaSource); - iEvent.da = 0; - } - else if (arg.ending || event instanceof InteractEvent) { - iEvent.distance = interaction.prevEvent.distance; - iEvent.box = interaction.prevEvent.box; - iEvent.scale = interaction.prevEvent.scale; - iEvent.ds = iEvent.scale - 1; - iEvent.angle = interaction.prevEvent.angle; - iEvent.da = iEvent.angle - interaction.gesture.startAngle; - } - else { - iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); - iEvent.box = utils.touchBBox(pointers); - iEvent.scale = iEvent.distance / interaction.gesture.startDistance; - iEvent.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, arg.deltaSource); - - iEvent.ds = iEvent.scale - interaction.gesture.prevScale; - iEvent.da = iEvent.angle - interaction.gesture.prevAngle; - } + if (arg.action !== 'gesture') { return; } + + const {interaction, iEvent} = {arg}; + const pointers = interaction.pointers; + + iEvent.touches = [pointers[0], pointers[1]]; + + if (arg.starting) { + iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); + iEvent.box = utils.touchBBox(pointers); + iEvent.scale = 1; + iEvent.ds = 0; + iEvent.angle = utils.touchAngle(pointers, undefined, arg.deltaSource); + iEvent.da = 0; + } + else if (arg.ending || event instanceof InteractEvent) { + iEvent.distance = interaction.prevEvent.distance; + iEvent.box = interaction.prevEvent.box; + iEvent.scale = interaction.prevEvent.scale; + iEvent.ds = iEvent.scale - 1; + iEvent.angle = interaction.prevEvent.angle; + iEvent.da = iEvent.angle - interaction.gesture.startAngle; + } + else { + iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); + iEvent.box = utils.touchBBox(pointers); + iEvent.scale = iEvent.distance / interaction.gesture.startDistance; + iEvent.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, arg.deltaSource); + + iEvent.ds = iEvent.scale - interaction.gesture.prevScale; + iEvent.da = iEvent.angle - interaction.gesture.prevAngle; + } }); - base.gesture = gesture; base.names.push('gesture'); utils.merge(scope.eventTypes, [ - 'gesturestart', - 'gesturemove', - 'gestureinertiastart', - 'gestureend' + 'gesturestart', + 'gesturemove', + 'gestureinertiastart', + 'gestureend', ]); base.methodDict.gesture = 'gesturable'; diff --git a/src/actions/resize.js b/src/actions/resize.js index 3cc8ad3da..e05e59257 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -1,275 +1,271 @@ -'use strict'; - -var base = require('./base'), - utils = require('../utils'), - browser = require('../utils/browser'), - signals = require('../utils/signals'), - scope = require('../scope'), - InteractEvent = require('../InteractEvent'), - Interactable = require('../Interactable'), - defaultOptions = require('../defaultOptions'); - -var resize = { - defaults: { - enabled: false, - manualStart: false, - max: Infinity, - maxPerElement: 1, - - snap: null, - restrict: null, - inertia: null, - autoScroll: null, - - square: false, - axis: 'xy', - - // use default margin - margin: NaN, - - // object with props left, right, top, bottom which are - // true/false values to resize when the pointer is over that edge, - // CSS selectors to match the handles for each direction - // or the Elements for each handle - edges: null, - - // a value of 'none' will limit the resize rect to a minimum of 0x0 - // 'negate' will alow the rect to have negative width/height - // 'reposition' will keep the width/height positive by swapping - // the top and bottom edges and/or swapping the left and right edges - invert: 'none' - }, - - checker: function (pointer, event, interactable, element, interaction, rect) { - if (!rect) { return null; } - - var page = utils.extend({}, interaction.curCoords.page), - options = interactable.options; - - if (options.resize.enabled) { - var resizeOptions = options.resize, - resizeEdges = { - left: false, right: false, top: false, bottom: false - }; - - // if using resize.edges - if (utils.isObject(resizeOptions.edges)) { - for (var edge in resizeEdges) { - resizeEdges[edge] = checkResizeEdge(edge, - resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || scope.margin); - } - - resizeEdges.left = resizeEdges.left && !resizeEdges.right; - resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; - - if (resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom) { - return { - name: 'resize', - edges: resizeEdges - }; - } - } - else { - var right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin), - bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); - - if (right || bottom) { - return { - name: 'resize', - axes: (right? 'x' : '') + (bottom? 'y' : '') - }; - } - } +const base = require('./base'); +const utils = require('../utils'); +const browser = require('../utils/browser'); +const signals = require('../utils/signals'); +const scope = require('../scope'); +const InteractEvent = require('../InteractEvent'); +const Interactable = require('../Interactable'); +const defaultOptions = require('../defaultOptions'); + +const resize = { + defaults: { + enabled: false, + manualStart: false, + max: Infinity, + maxPerElement: 1, + + snap: null, + restrict: null, + inertia: null, + autoScroll: null, + + square: false, + axis: 'xy', + + // use default margin + margin: NaN, + + // object with props left, right, top, bottom which are + // true/false values to resize when the pointer is over that edge, + // CSS selectors to match the handles for each direction + // or the Elements for each handle + edges: null, + + // a value of 'none' will limit the resize rect to a minimum of 0x0 + // 'negate' will alow the rect to have negative width/height + // 'reposition' will keep the width/height positive by swapping + // the top and bottom edges and/or swapping the left and right edges + invert: 'none', + }, + + checker: function (pointer, event, interactable, element, interaction, rect) { + if (!rect) { return null; } + + const page = utils.extend({}, interaction.curCoords.page); + const options = interactable.options; + + if (options.resize.enabled) { + const resizeOptions = options.resize; + const resizeEdges = { left: false, right: false, top: false, bottom: false }; + + // if using resize.edges + if (utils.isObject(resizeOptions.edges)) { + for (const edge in resizeEdges) { + resizeEdges[edge] = checkResizeEdge(edge, + resizeOptions.edges[edge], + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || scope.margin); } - return null; - }, - - cursors: (browser.isIe9OrOlder ? { - x : 'e-resize', - y : 's-resize', - xy: 'se-resize', - - top : 'n-resize', - left : 'w-resize', - bottom : 's-resize', - right : 'e-resize', - topleft : 'se-resize', - bottomright: 'se-resize', - topright : 'ne-resize', - bottomleft : 'ne-resize', - } : { - x : 'ew-resize', - y : 'ns-resize', - xy: 'nwse-resize', - - top : 'ns-resize', - left : 'ew-resize', - bottom : 'ns-resize', - right : 'ew-resize', - topleft : 'nwse-resize', - bottomright: 'nwse-resize', - topright : 'nesw-resize', - bottomleft : 'nesw-resize', - }), - - getCursor: function (action) { - if (action.axis) { - return resize.cursors[action.name + action.axis]; + resizeEdges.left = resizeEdges.left && !resizeEdges.right; + resizeEdges.top = resizeEdges.top && !resizeEdges.bottom; + + if (resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom) { + return { + name: 'resize', + edges: resizeEdges, + }; + } + } + else { + const right = options.resize.axis !== 'y' && page.x > (rect.right - scope.margin); + const bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - scope.margin); + + if (right || bottom) { + return { + name: 'resize', + axes: (right? 'x' : '') + (bottom? 'y' : ''), + }; } - else if (action.edges) { - var cursorKey = '', - edgeNames = ['top', 'bottom', 'left', 'right']; + } + } - for (var i = 0; i < 4; i++) { - if (action.edges[edgeNames[i]]) { - cursorKey += edgeNames[i]; - } - } + return null; + }, + + cursors: (browser.isIe9OrOlder ? { + x : 'e-resize', + y : 's-resize', + xy: 'se-resize', + + top : 'n-resize', + left : 'w-resize', + bottom : 's-resize', + right : 'e-resize', + topleft : 'se-resize', + bottomright: 'se-resize', + topright : 'ne-resize', + bottomleft : 'ne-resize', + } : { + x : 'ew-resize', + y : 'ns-resize', + xy: 'nwse-resize', + + top : 'ns-resize', + left : 'ew-resize', + bottom : 'ns-resize', + right : 'ew-resize', + topleft : 'nwse-resize', + bottomright: 'nwse-resize', + topright : 'nesw-resize', + bottomleft : 'nesw-resize', + }), + + getCursor: function (action) { + if (action.axis) { + return resize.cursors[action.name + action.axis]; + } + else if (action.edges) { + let cursorKey = ''; + const edgeNames = ['top', 'bottom', 'left', 'right']; - return resize.cursors[cursorKey]; - } - }, - - beforeStart: utils.blank, - - start: function (interaction, event) { - var resizeEvent = new InteractEvent(interaction, event, 'resize', 'start', interaction.element); - - if (interaction.prepared.edges) { - var startRect = interaction.target.getRect(interaction.element); - - if (interaction.target.options.resize.square) { - var squareEdges = utils.extend({}, interaction.prepared.edges); - - squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); - squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); - squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); - squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); - - interaction.prepared._squareEdges = squareEdges; - } - else { - interaction.prepared._squareEdges = null; - } - - interaction.resizeRects = { - start : startRect, - current : utils.extend({}, startRect), - restricted: utils.extend({}, startRect), - previous : utils.extend({}, startRect), - delta : { - left: 0, right : 0, width : 0, - top : 0, bottom: 0, height: 0 - } - }; - - resizeEvent.rect = interaction.resizeRects.restricted; - resizeEvent.deltaRect = interaction.resizeRects.delta; + for (let i = 0; i < 4; i++) { + if (action.edges[edgeNames[i]]) { + cursorKey += edgeNames[i]; } + } + + return resize.cursors[cursorKey]; + } + }, + + beforeStart: utils.blank, + + start: function (interaction, event) { + const resizeEvent = new InteractEvent(interaction, event, 'resize', 'start', interaction.element); + + if (interaction.prepared.edges) { + const startRect = interaction.target.getRect(interaction.element); + + if (interaction.target.options.resize.square) { + const squareEdges = utils.extend({}, interaction.prepared.edges); + + squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom); + squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right ); + squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top ); + squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left ); + + interaction.prepared._squareEdges = squareEdges; + } + else { + interaction.prepared._squareEdges = null; + } + + interaction.resizeRects = { + start : startRect, + current : utils.extend({}, startRect), + restricted: utils.extend({}, startRect), + previous : utils.extend({}, startRect), + delta : { + left: 0, right : 0, width : 0, + top : 0, bottom: 0, height: 0, + }, + }; + + resizeEvent.rect = interaction.resizeRects.restricted; + resizeEvent.deltaRect = interaction.resizeRects.delta; + } + + interaction.target.fire(resizeEvent); + + interaction._interacting = true; - interaction.target.fire(resizeEvent); - - interaction._interacting = true; - - return resizeEvent; - }, - - move: function (interaction, event) { - var resizeEvent = new InteractEvent(interaction, event, 'resize', 'move', interaction.element); - - var edges = interaction.prepared.edges, - invert = interaction.target.options.resize.invert, - invertible = invert === 'reposition' || invert === 'negate'; - - if (edges) { - var dx = resizeEvent.dx, - dy = resizeEvent.dy, - - start = interaction.resizeRects.start, - current = interaction.resizeRects.current, - restricted = interaction.resizeRects.restricted, - delta = interaction.resizeRects.delta, - previous = utils.extend(interaction.resizeRects.previous, restricted); - - if (interaction.target.options.resize.square) { - var originalEdges = edges; - - edges = interaction.prepared._squareEdges; - - if ((originalEdges.left && originalEdges.bottom) - || (originalEdges.right && originalEdges.top)) { - dy = -dx; - } - else if (originalEdges.left || originalEdges.right) { dy = dx; } - else if (originalEdges.top || originalEdges.bottom) { dx = dy; } - } - - // update the 'current' rect without modifications - if (edges.top ) { current.top += dy; } - if (edges.bottom) { current.bottom += dy; } - if (edges.left ) { current.left += dx; } - if (edges.right ) { current.right += dx; } - - if (invertible) { - // if invertible, copy the current rect - utils.extend(restricted, current); - - if (invert === 'reposition') { - // swap edge values if necessary to keep width/height positive - var swap; - - if (restricted.top > restricted.bottom) { - swap = restricted.top; - - restricted.top = restricted.bottom; - restricted.bottom = swap; - } - if (restricted.left > restricted.right) { - swap = restricted.left; - - restricted.left = restricted.right; - restricted.right = swap; - } - } - } - else { - // if not invertible, restrict to minimum of 0x0 rect - restricted.top = Math.min(current.top, start.bottom); - restricted.bottom = Math.max(current.bottom, start.top); - restricted.left = Math.min(current.left, start.right); - restricted.right = Math.max(current.right, start.left); - } - - restricted.width = restricted.right - restricted.left; - restricted.height = restricted.bottom - restricted.top ; - - for (var edge in restricted) { - delta[edge] = restricted[edge] - previous[edge]; - } - - resizeEvent.edges = interaction.prepared.edges; - resizeEvent.rect = restricted; - resizeEvent.deltaRect = delta; + return resizeEvent; + }, + + move: function (interaction, event) { + const resizeEvent = new InteractEvent(interaction, event, 'resize', 'move', interaction.element); + const invert = interaction.target.options.resize.invert; + const invertible = invert === 'reposition' || invert === 'negate'; + + let edges = interaction.prepared.edges; + + if (edges) { + const start = interaction.resizeRects.start; + const current = interaction.resizeRects.current; + const restricted = interaction.resizeRects.restricted; + const delta = interaction.resizeRects.delta; + const previous = utils.extend(interaction.resizeRects.previous, restricted); + + let dx = resizeEvent.dx; + let dy = resizeEvent.dy; + + if (interaction.target.options.resize.square) { + const originalEdges = edges; + + edges = interaction.prepared._squareEdges; + + if ((originalEdges.left && originalEdges.bottom) + || (originalEdges.right && originalEdges.top)) { + dy = -dx; } + else if (originalEdges.left || originalEdges.right) { dy = dx; } + else if (originalEdges.top || originalEdges.bottom) { dx = dy; } + } + + // update the 'current' rect without modifications + if (edges.top ) { current.top += dy; } + if (edges.bottom) { current.bottom += dy; } + if (edges.left ) { current.left += dx; } + if (edges.right ) { current.right += dx; } + + if (invertible) { + // if invertible, copy the current rect + utils.extend(restricted, current); + + if (invert === 'reposition') { + // swap edge values if necessary to keep width/height positive + let swap; + + if (restricted.top > restricted.bottom) { + swap = restricted.top; + + restricted.top = restricted.bottom; + restricted.bottom = swap; + } + if (restricted.left > restricted.right) { + swap = restricted.left; + + restricted.left = restricted.right; + restricted.right = swap; + } + } + } + else { + // if not invertible, restrict to minimum of 0x0 rect + restricted.top = Math.min(current.top, start.bottom); + restricted.bottom = Math.max(current.bottom, start.top); + restricted.left = Math.min(current.left, start.right); + restricted.right = Math.max(current.right, start.left); + } + + restricted.width = restricted.right - restricted.left; + restricted.height = restricted.bottom - restricted.top ; + + for (const edge in restricted) { + delta[edge] = restricted[edge] - previous[edge]; + } + + resizeEvent.edges = interaction.prepared.edges; + resizeEvent.rect = restricted; + resizeEvent.deltaRect = delta; + } - interaction.target.fire(resizeEvent); + interaction.target.fire(resizeEvent); - return resizeEvent; - }, + return resizeEvent; + }, - end: function (interaction, event) { - var endEvent = new InteractEvent(interaction, event, 'resize', 'end', interaction.element); + end: function (interaction, event) { + const endEvent = new InteractEvent(interaction, event, 'resize', 'end', interaction.element); - interaction.target.fire(endEvent); - }, + interaction.target.fire(endEvent); + }, - stop: utils.blank + stop: utils.blank, }; /*\ @@ -280,132 +276,132 @@ var resize = { * Interactable * = (boolean) Indicates if this can be the target of resize elements - | var isResizeable = interact('input[type=text]').resizable(); + | var isResizeable = interact('input[type=text]').resizable(); * or - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable) = (object) This Interactable - | interact(element).resizable({ - | onstart: function (event) {}, - | onmove : function (event) {}, - | onend : function (event) {}, - | - | edges: { - | top : true, // Use pointer coords to check for resize. - | left : false, // Disable resizing from left edge. - | bottom: '.resize-s',// Resize if pointer target matches selector - | right : handleEl // Resize if pointer target is the given Element - | }, - | - | // a value of 'none' will limit the resize rect to a minimum of 0x0 - | // 'negate' will allow the rect to have negative width/height - | // 'reposition' will keep the width/height positive by swapping - | // the top and bottom edges and/or swapping the left and right edges - | invert: 'none' || 'negate' || 'reposition' - | - | // limit multiple resizes. - | // See the explanation in the @Interactable.draggable example - | max: Infinity, - | maxPerElement: 1, - | }); -\*/ + | interact(element).resizable({ + | onstart: function (event) {}, + | onmove : function (event) {}, + | onend : function (event) {}, + | + | edges: { + | top : true, // Use pointer coords to check for resize. + | left : false, // Disable resizing from left edge. + | bottom: '.resize-s',// Resize if pointer target matches selector + | right : handleEl // Resize if pointer target is the given Element + | }, + | + | // a value of 'none' will limit the resize rect to a minimum of 0x0 + | // 'negate' will allow the rect to have negative width/height + | // 'reposition' will keep the width/height positive by swapping + | // the top and bottom edges and/or swapping the left and right edges + | invert: 'none' || 'negate' || 'reposition' + | + | // limit multiple resizes. + | // See the explanation in the @Interactable.draggable example + | max: Infinity, + | maxPerElement: 1, + | }); + \*/ Interactable.prototype.resizable = function (options) { - if (utils.isObject(options)) { - this.options.resize.enabled = options.enabled === false? false: true; - this.setPerAction('resize', options); - this.setOnEvents('resize', options); + if (utils.isObject(options)) { + this.options.resize.enabled = options.enabled === false? false: true; + this.setPerAction('resize', options); + this.setOnEvents('resize', options); - if (/^x$|^y$|^xy$/.test(options.axis)) { - this.options.resize.axis = options.axis; - } - else if (options.axis === null) { - this.options.resize.axis = scope.defaultOptions.resize.axis; - } - - if (utils.isBool(options.square)) { - this.options.resize.square = options.square; - } - - return this; + if (/^x$|^y$|^xy$/.test(options.axis)) { + this.options.resize.axis = options.axis; + } + else if (options.axis === null) { + this.options.resize.axis = scope.defaultOptions.resize.axis; } - if (utils.isBool(options)) { - this.options.resize.enabled = options; - return this; + if (utils.isBool(options.square)) { + this.options.resize.square = options.square; } - return this.options.resize; + + return this; + } + if (utils.isBool(options)) { + this.options.resize.enabled = options; + + return this; + } + return this.options.resize; }; function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) { - // false, '', undefined, null - if (!value) { return false; } - - // true value, use pointer coords and element rect - if (value === true) { - // if dimensions are negative, "switch" edges - var width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left, - height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; - - if (width < 0) { - if (name === 'left' ) { name = 'right'; } - else if (name === 'right') { name = 'left' ; } - } - if (height < 0) { - if (name === 'top' ) { name = 'bottom'; } - else if (name === 'bottom') { name = 'top' ; } - } + // false, '', undefined, null + if (!value) { return false; } + + // true value, use pointer coords and element rect + if (value === true) { + // if dimensions are negative, "switch" edges + const width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left; + const height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; + + if (width < 0) { + if (name === 'left' ) { name = 'right'; } + else if (name === 'right') { name = 'left' ; } + } + if (height < 0) { + if (name === 'top' ) { name = 'bottom'; } + else if (name === 'bottom') { name = 'top' ; } + } - if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } - if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } + if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); } + if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); } - if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } - if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } - } + if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); } + if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); } + } - // the remaining checks require an element - if (!utils.isElement(element)) { return false; } + // the remaining checks require an element + if (!utils.isElement(element)) { return false; } - return utils.isElement(value) - // the value is an element to use as a resize handle - ? value === element - // otherwise check if element matches value as selector - : utils.matchesUpTo(element, value, interactableElement); + return utils.isElement(value) + // the value is an element to use as a resize handle + ? value === element + // otherwise check if element matches value as selector + : utils.matchesUpTo(element, value, interactableElement); } signals.on('interact-event-set-delta', function (arg) { - var interaction = arg.interaction, - iEvent = arg.interactEvent, - options = interaction.target.options; + const interaction = arg.interaction; + const iEvent = arg.interactEvent; + const options = interaction.target.options; - if (arg.action !== 'resize' || !interaction.resizeAxes) { return; } + if (arg.action !== 'resize' || !interaction.resizeAxes) { return; } - if (options.resize.square) { - if (interaction.resizeAxes === 'y') { - iEvent.dx = iEvent.dy; - } - else { - iEvent.dy = iEvent.dx; - } - iEvent.axes = 'xy'; + if (options.resize.square) { + if (interaction.resizeAxes === 'y') { + iEvent.dx = iEvent.dy; } else { - iEvent.axes = interaction.resizeAxes; + iEvent.dy = iEvent.dx; + } + iEvent.axes = 'xy'; + } + else { + iEvent.axes = interaction.resizeAxes; - if (interaction.resizeAxes === 'x') { - iEvent.dy = 0; - } - else if (interaction.resizeAxes === 'y') { - iEvent.dx = 0; - } + if (interaction.resizeAxes === 'x') { + iEvent.dy = 0; + } + else if (interaction.resizeAxes === 'y') { + iEvent.dx = 0; } + } }); base.resize = resize; base.names.push('resize'); utils.merge(scope.eventTypes, [ - 'resizestart', - 'resizemove', - 'resizeinertiastart', - 'resizeend' + 'resizestart', + 'resizemove', + 'resizeinertiastart', + 'resizeend', ]); base.methodDict.resize = 'resizable'; diff --git a/src/autoScroll.js b/src/autoScroll.js index 503904f37..55a1168be 100644 --- a/src/autoScroll.js +++ b/src/autoScroll.js @@ -1,124 +1,123 @@ -'use strict'; - -var raf = require('./utils/raf'), - getWindow = require('./utils/window').getWindow, - isWindow = require('./utils/isType').isWindow, - domUtils = require('./utils/domUtils'), - signals = require('./utils/signals'), - defaultOptions = require('./defaultOptions'); - -var autoScroll = { - defaults: { - enabled : false, - container : null, // the item that is scrolled (Window or HTMLElement) - margin : 60, - speed : 300 // the scroll speed in pixels per second - }, - - interaction: null, - i: null, // the handle returned by window.setInterval - x: 0, y: 0, // Direction each pulse is to scroll in - - isScrolling: false, - prevTime: 0, - - start: function (interaction) { - autoScroll.isScrolling = true; - raf.cancel(autoScroll.i); - - autoScroll.interaction = interaction; - autoScroll.prevTime = new Date().getTime(); - autoScroll.i = raf.request(autoScroll.scroll); - }, - - stop: function () { - autoScroll.isScrolling = false; - raf.cancel(autoScroll.i); - }, - - // scroll the window by the values in scroll.x/y - scroll: function () { - var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll, - container = options.container || getWindow(autoScroll.interaction.element), - now = new Date().getTime(), - // change in time in seconds - dt = (now - autoScroll.prevTime) / 1000, - // displacement - s = options.speed * dt; - - if (s >= 1) { - if (isWindow(container)) { - container.scrollBy(autoScroll.x * s, autoScroll.y * s); - } - else if (container) { - container.scrollLeft += autoScroll.x * s; - container.scrollTop += autoScroll.y * s; - } - - autoScroll.prevTime = now; - } - - if (autoScroll.isScrolling) { - raf.cancel(autoScroll.i); - autoScroll.i = raf.request(autoScroll.scroll); - } - }, - check: function (interactable, actionName) { - var options = interactable.options; - - return options[actionName].autoScroll && options[actionName].autoScroll.enabled; - }, - onInteractionMove: function (arg) { - var interaction = arg.interaction, - pointer = arg.pointer; - - if (!(interaction.interacting() - && autoScroll.check(interaction.target, interaction.prepared.name))) { - return; - } - - if (interaction.inertiaStatus.active) { - autoScroll.x = autoScroll.y = 0; - return; - } - - var top, - right, - bottom, - left, - options = interaction.target.options[interaction.prepared.name].autoScroll, - container = options.container || getWindow(interaction.element); - - if (isWindow(container)) { - left = pointer.clientX < autoScroll.margin; - top = pointer.clientY < autoScroll.margin; - right = pointer.clientX > container.innerWidth - autoScroll.margin; - bottom = pointer.clientY > container.innerHeight - autoScroll.margin; - } - else { - var rect = domUtils.getElementClientRect(container); - - left = pointer.clientX < rect.left + autoScroll.margin; - top = pointer.clientY < rect.top + autoScroll.margin; - right = pointer.clientX > rect.right - autoScroll.margin; - bottom = pointer.clientY > rect.bottom - autoScroll.margin; - } - - autoScroll.x = (right ? 1: left? -1: 0); - autoScroll.y = (bottom? 1: top? -1: 0); - - if (!autoScroll.isScrolling) { - // set the autoScroll properties to those of the target - autoScroll.margin = options.margin; - autoScroll.speed = options.speed; - - autoScroll.start(interaction); - } +const raf = require('./utils/raf'); +const getWindow = require('./utils/window').getWindow; +const isWindow = require('./utils/isType').isWindow; +const domUtils = require('./utils/domUtils'); +const signals = require('./utils/signals'); +const defaultOptions = require('./defaultOptions'); + +const autoScroll = { + defaults: { + enabled : false, + container : null, // the item that is scrolled (Window or HTMLElement) + margin : 60, + speed : 300, // the scroll speed in pixels per second + }, + + interaction: null, + i: null, // the handle returned by window.setInterval + x: 0, y: 0, // Direction each pulse is to scroll in + + isScrolling: false, + prevTime: 0, + + start: function (interaction) { + autoScroll.isScrolling = true; + raf.cancel(autoScroll.i); + + autoScroll.interaction = interaction; + autoScroll.prevTime = new Date().getTime(); + autoScroll.i = raf.request(autoScroll.scroll); + }, + + stop: function () { + autoScroll.isScrolling = false; + raf.cancel(autoScroll.i); + }, + + // scroll the window by the values in scroll.x/y + scroll: function () { + const options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll; + const container = options.container || getWindow(autoScroll.interaction.element); + const now = new Date().getTime(); + // change in time in seconds + const dt = (now - autoScroll.prevTime) / 1000; + // displacement + const s = options.speed * dt; + + if (s >= 1) { + if (isWindow(container)) { + container.scrollBy(autoScroll.x * s, autoScroll.y * s); + } + else if (container) { + container.scrollLeft += autoScroll.x * s; + container.scrollTop += autoScroll.y * s; + } + + autoScroll.prevTime = now; } + + if (autoScroll.isScrolling) { + raf.cancel(autoScroll.i); + autoScroll.i = raf.request(autoScroll.scroll); + } + }, + check: function (interactable, actionName) { + const options = interactable.options; + + return options[actionName].autoScroll && options[actionName].autoScroll.enabled; + }, + onInteractionMove: function (arg) { + const interaction = arg.interaction; + const pointer = arg.pointer; + + if (!(interaction.interacting() + && autoScroll.check(interaction.target, interaction.prepared.name))) { + return; + } + + if (interaction.inertiaStatus.active) { + autoScroll.x = autoScroll.y = 0; + return; + } + + let top; + let right; + let bottom; + let left; + + const options = interaction.target.options[interaction.prepared.name].autoScroll; + const container = options.container || getWindow(interaction.element); + + if (isWindow(container)) { + left = pointer.clientX < autoScroll.margin; + top = pointer.clientY < autoScroll.margin; + right = pointer.clientX > container.innerWidth - autoScroll.margin; + bottom = pointer.clientY > container.innerHeight - autoScroll.margin; + } + else { + const rect = domUtils.getElementClientRect(container); + + left = pointer.clientX < rect.left + autoScroll.margin; + top = pointer.clientY < rect.top + autoScroll.margin; + right = pointer.clientX > rect.right - autoScroll.margin; + bottom = pointer.clientY > rect.bottom - autoScroll.margin; + } + + autoScroll.x = (right ? 1: left? -1: 0); + autoScroll.y = (bottom? 1: top? -1: 0); + + if (!autoScroll.isScrolling) { + // set the autoScroll properties to those of the target + autoScroll.margin = options.margin; + autoScroll.speed = options.speed; + + autoScroll.start(interaction); + } + }, }; signals.on('interaction-stop-active', function () { - autoScroll.stop(); + autoScroll.stop(); }); signals.on('interaction-move-done', autoScroll.onInteractionMove); diff --git a/src/defaultOptions.js b/src/defaultOptions.js index 21e4eebcc..04461408e 100644 --- a/src/defaultOptions.js +++ b/src/defaultOptions.js @@ -1,34 +1,32 @@ -'use strict'; - module.exports = { - base: { - accept : null, - actionChecker : null, - styleCursor : true, - preventDefault: 'auto', - origin : { x: 0, y: 0 }, - deltaSource : 'page', - allowFrom : null, - ignoreFrom : null, - _context : require('./utils/domObjects').document, - dropChecker : null - }, + base: { + accept : null, + actionChecker : null, + styleCursor : true, + preventDefault: 'auto', + origin : { x: 0, y: 0 }, + deltaSource : 'page', + allowFrom : null, + ignoreFrom : null, + _context : require('./utils/domObjects').document, + dropChecker : null, + }, - perAction: { - manualStart: false, - max: Infinity, - maxPerElement: 1, + perAction: { + manualStart: false, + max: Infinity, + maxPerElement: 1, - inertia: { - enabled : false, - resistance : 10, // the lambda in exponential decay - minSpeed : 100, // target speed must be above this for inertia to start - endSpeed : 10, // the speed at which inertia is slow enough to stop - allowResume : true, // allow resuming an action in inertia phase - zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 - smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia - } + inertia: { + enabled : false, + resistance : 10, // the lambda in exponential decay + minSpeed : 100, // target speed must be above this for inertia to start + endSpeed : 10, // the speed at which inertia is slow enough to stop + allowResume : true, // allow resuming an action in inertia phase + zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0 + smoothEndDuration: 300, // animate to snap/restrict endOnly if there's no inertia }, + }, - _holdDuration: 600 + _holdDuration: 600, }; diff --git a/src/index.js b/src/index.js index 7c07ad59f..6251d5341 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,3 @@ -'use strict'; - // browser entry point module.exports = require('./interact'); diff --git a/src/interact.js b/src/interact.js index e988d12d7..d27f9506a 100644 --- a/src/interact.js +++ b/src/interact.js @@ -6,8 +6,6 @@ * https://raw.github.com/taye/interact.js/master/LICENSE */ -'use strict'; - const scope = require('./scope'); const utils = require('./utils'); const browser = utils.browser; @@ -32,12 +30,12 @@ scope.wheelEvent = 'onmousewheel' in scope.document? 'mousewheel': 'wheel'; scope.globalEvents = {}; -scope.inContext = function(interactable, element) { +scope.inContext = function (interactable, element) { return (interactable._context === element.ownerDocument || utils.nodeContains(interactable._context, element)); }; -scope.testIgnore = function(interactable, interactableElement, element) { +scope.testIgnore = function (interactable, interactableElement, element) { const ignoreFrom = interactable.options.ignoreFrom; if (!ignoreFrom || !utils.isElement(element)) { return false; } @@ -52,7 +50,7 @@ scope.testIgnore = function(interactable, interactableElement, element) { return false; }; -scope.testAllow = function(interactable, interactableElement, element) { +scope.testAllow = function (interactable, interactableElement, element) { const allowFrom = interactable.options.allowFrom; if (!allowFrom) { return true; } @@ -69,7 +67,7 @@ scope.testAllow = function(interactable, interactableElement, element) { return false; }; -scope.interactables.indexOfElement = function indexOfElement(element, context) { +scope.interactables.indexOfElement = function indexOfElement (element, context) { context = context || scope.document; for (let i = 0; i < this.length; i++) { @@ -85,11 +83,11 @@ scope.interactables.indexOfElement = function indexOfElement(element, context) { return -1; }; -scope.interactables.get = function interactableGet(element, options) { +scope.interactables.get = function interactableGet (element, options) { return this[this.indexOfElement(element, options && options.context)]; }; -scope.interactables.forEachSelector = function(callback) { +scope.interactables.forEachSelector = function (callback) { for (let i = 0; i < this.length; i++) { const interactable = this[i]; @@ -125,12 +123,12 @@ scope.interactables.forEachSelector = function(callback) { | var rectables = interact('rect'); | rectables | .gesturable(true) - | .on('gesturemove', function(event) { + | .on('gesturemove', function (event) { | // something cool... | }) | .autoScroll(true); \*/ -function interact(element, options) { +function interact (element, options) { return scope.interactables.get(element, options) || new Interactable(element, options); } @@ -142,7 +140,7 @@ function interact(element, options) { - element (Element) The Element being searched for = (boolean) Indicates if the element or CSS selector was previously passed to interact \*/ -interact.isSet = function(element, options) { +interact.isSet = function (element, options) { return scope.interactables.indexOfElement(element, options && options.context) !== -1; }; @@ -154,25 +152,25 @@ interact.isSet = function(element, options) { * `document` * - type (string | array | object) The types of events to listen for - - listener (function) The function to be called on the given event(s) + - listener (function) The function event (s) - useCapture (boolean) #optional useCapture flag for addEventListener = (object) interact \*/ -interact.on = function(type, listener, useCapture) { +interact.on = function (type, listener, useCapture) { if (utils.isString(type) && type.search(' ') !== -1) { type = type.trim().split(/ +/); } if (utils.isArray(type)) { - for (let i = 0; i < type.length; i++) { - interact.on(type[i], listener, useCapture); + for (const eventType of type) { + interact.on(eventType, listener, useCapture); } return interact; } if (utils.isObject(type)) { - for (let prop in type) { + for (const prop in type) { interact.on(prop, type[prop], listener); } @@ -208,21 +206,21 @@ interact.on = function(type, listener, useCapture) { - useCapture (boolean) #optional useCapture flag for removeEventListener = (object) interact \*/ -interact.off = function(type, listener, useCapture) { +interact.off = function (type, listener, useCapture) { if (utils.isString(type) && type.search(' ') !== -1) { type = type.trim().split(/ +/); } if (utils.isArray(type)) { - for (let i = 0; i < type.length; i++) { - interact.off(type[i], listener, useCapture); + for (const eventType of type) { + interact.off(eventType, listener, useCapture); } return interact; } if (utils.isObject(type)) { - for (let prop in type) { + for (const prop in type) { interact.off(prop, type[prop], listener); } @@ -251,7 +249,7 @@ interact.off = function(type, listener, useCapture) { * Returns an object which exposes internal data = (object) An object with properties that outline the current state and expose internal functions and variables \*/ -interact.debug = function() { +interact.debug = function () { return scope; }; @@ -276,7 +274,7 @@ interact.closest = utils.closest; - newValue (number) #optional = (number | interact) The current margin value or interact \*/ -interact.margin = function(newvalue) { +interact.margin = function (newvalue) { if (utils.isNumber(newvalue)) { scope.margin = newvalue; @@ -291,7 +289,7 @@ interact.margin = function(newvalue) { * = (boolean) Whether or not the browser supports touch input \*/ -interact.supportsTouch = function() { +interact.supportsTouch = function () { return browser.supportsTouch; }; @@ -301,7 +299,7 @@ interact.supportsTouch = function() { * = (boolean) Whether or not the browser supports PointerEvents \*/ -interact.supportsPointerEvent = function() { +interact.supportsPointerEvent = function () { return browser.supportsPointerEvent; }; @@ -314,7 +312,7 @@ interact.supportsPointerEvent = function() { - event (Event) An event on which to call preventDefault() = (object) interact \*/ -interact.stop = function(event) { +interact.stop = function (event) { for (let i = scope.interactions.length - 1; i >= 0; i--) { scope.interactions[i].stop(event); } @@ -333,7 +331,7 @@ interact.stop = function(event) { - newValue (boolean) #optional True to check on each move. False to check only before start = (boolean | interact) The current setting or interact \*/ -interact.dynamicDrop = function(newValue) { +interact.dynamicDrop = function (newValue) { if (utils.isBool(newValue)) { //if (dragging && dynamicDrop !== newValue && !newValue) { //calcRects(dropzones); @@ -355,7 +353,7 @@ interact.dynamicDrop = function(newValue) { - newValue (number) #optional The movement from the start position must be greater than this value = (number | Interactable) The current setting or interact \*/ -interact.pointerMoveTolerance = function(newValue) { +interact.pointerMoveTolerance = function (newValue) { if (utils.isNumber(newValue)) { scope.pointerMoveTolerance = newValue; @@ -377,7 +375,7 @@ interact.pointerMoveTolerance = function(newValue) { ** - newValue (number) #optional Any number. newValue <= 0 means no interactions. \*/ -interact.maxInteractions = function(newValue) { +interact.maxInteractions = function (newValue) { if (utils.isNumber(newValue)) { scope.maxInteractions = newValue; diff --git a/src/modifiers/index.js b/src/modifiers/index.js index 4a8fe5c2a..840e4951d 100644 --- a/src/modifiers/index.js +++ b/src/modifiers/index.js @@ -1,69 +1,67 @@ -'use strict'; +const utils = require('../utils'); -var utils = require('../utils'); +const modifiers = { + names: [], -var modifiers = { - names: [], + setOffsets: function (interaction, interactable, element, rect, offsets) { + for (let i = 0; i < modifiers.names.length; i++) { + const modifierName = modifiers.names[i]; - setOffsets: function (interaction, interactable, element, rect, offsets) { - for (var i = 0; i < modifiers.names.length; i++) { - var modifierName = modifiers.names[i]; - - offsets[modifierName] = - modifiers[modifiers.names[i]].setOffset(interaction, - interactable, element, rect, - interaction.startOffset); - } - }, - - setAll: function (interaction, coords, statuses, preEnd, requireEndOnly) { - var result = { - dx: 0, - dy: 0, - changed: false, - locked: false, - shouldMove: true - }, - target = interaction.target, - currentStatus; + offsets[modifierName] = + modifiers[modifiers.names[i]].setOffset(interaction, + interactable, element, rect, + interaction.startOffset); + } + }, - coords = utils.extend({}, coords); + setAll: function (interaction, coords, statuses, preEnd, requireEndOnly) { + const result = { + dx: 0, + dy: 0, + changed: false, + locked: false, + shouldMove: true, + }; + const target = interaction.target; + let currentStatus; - for (var i = 0; i < modifiers.names.length; i++) { - var modifierName = modifiers.names[i], - modifier = modifiers[modifierName]; + coords = utils.extend({}, coords); - if (!modifier.shouldDo(target, interaction.prepared.name, preEnd, requireEndOnly)) { continue; } + for (let i = 0; i < modifiers.names.length; i++) { + const modifierName = modifiers.names[i]; + const modifier = modifiers[modifierName]; - currentStatus = modifier.set(coords, interaction, statuses[modifierName]); + if (!modifier.shouldDo(target, interaction.prepared.name, preEnd, requireEndOnly)) { continue; } - if (currentStatus.locked) { - coords.x += currentStatus.dx; - coords.y += currentStatus.dy; + currentStatus = modifier.set(coords, interaction, statuses[modifierName]); - result.dx += currentStatus.dx; - result.dy += currentStatus.dy; + if (currentStatus.locked) { + coords.x += currentStatus.dx; + coords.y += currentStatus.dy; - result.locked = true; - } - } + result.dx += currentStatus.dx; + result.dy += currentStatus.dy; - // a move should be fired if the modified coords of - // the last modifier status that was calculated changes - result.shouldMove = !currentStatus || currentStatus.changed; + result.locked = true; + } + } - return result; - }, + // a move should be fired if the modified coords of + // the last modifier status that was calculated changes + result.shouldMove = !currentStatus || currentStatus.changed; - resetStatuses: function (statuses) { - for (var i = 0; i < modifiers.names.length; i++) { - var modifierName = modifiers.names[i]; + return result; + }, - statuses[modifierName] = modifiers[modifierName].reset(statuses[modifierName] || {}); - } + resetStatuses: function (statuses) { + for (let i = 0; i < modifiers.names.length; i++) { + const modifierName = modifiers.names[i]; - return statuses; + statuses[modifierName] = modifiers[modifierName].reset(statuses[modifierName] || {}); } + + return statuses; + }, }; module.exports = modifiers; diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index a3928be3d..ea78849b7 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -1,146 +1,146 @@ -'use strict'; - -var modifiers = require('./index'), - utils = require('../utils'), - defaultOptions = require('../defaultOptions'); - -var restrict = { - defaults: { - enabled : false, - endOnly : false, - restriction: null, - elementRect: null - }, - shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { - var restrict = interactable.options[actionName].restrict; - - return restrict && restrict.enabled && (preEnd || !restrict.endOnly) && (!requireEndOnly || restrict.endOnly); - }, - - setOffset: function (interaction, interactable, element, rect, startOffset) { - var elementRect = interactable.options[interaction.prepared.name].restrict.elementRect, - offset = {}; - - if (rect && elementRect) { - offset.left = startOffset.left - (rect.width * elementRect.left); - offset.top = startOffset.top - (rect.height * elementRect.top); - - offset.right = startOffset.right - (rect.width * (1 - elementRect.right)); - offset.bottom = startOffset.bottom - (rect.height * (1 - elementRect.bottom)); - } - else { - offset.left = offset.top = offset.right = offset.bottom = 0; - } - - return offset; - }, - - set: function (pageCoords, interaction, status) { - var target = interaction.target, - restrict = target && target.options[interaction.prepared.name].restrict, - restriction = restrict && restrict.restriction, - page; - - if (!restriction) { - return status; - } - - page = status.useStatusXY - ? page = { x: status.x, y: status.y } - : page = utils.extend({}, pageCoords); - - page.x -= interaction.inertiaStatus.resumeDx; - page.y -= interaction.inertiaStatus.resumeDy; - - status.dx = 0; - status.dy = 0; - status.locked = false; - - var rect, restrictedX, restrictedY; - - if (utils.isString(restriction)) { - if (restriction === 'parent') { - restriction = utils.parentElement(interaction.element); - } - else if (restriction === 'self') { - restriction = target.getRect(interaction.element); - } - else { - restriction = utils.closest(interaction.element, restriction); - } - - if (!restriction) { return status; } - } - - if (utils.isFunction(restriction)) { - restriction = restriction(page.x, page.y, interaction.element); - } - - if (utils.isElement(restriction)) { - restriction = utils.getElementRect(restriction); - } - - rect = restriction; - - var offset = interaction.modifierOffsets.restrict; - - if (!restriction) { - restrictedX = page.x; - restrictedY = page.y; - } - // object is assumed to have - // x, y, width, height or - // left, top, right, bottom - else if ('x' in restriction && 'y' in restriction) { - restrictedX = Math.max(Math.min(rect.x + rect.width - offset.right , page.x), rect.x + offset.left); - restrictedY = Math.max(Math.min(rect.y + rect.height - offset.bottom, page.y), rect.y + offset.top ); - } - else { - restrictedX = Math.max(Math.min(rect.right - offset.right , page.x), rect.left + offset.left); - restrictedY = Math.max(Math.min(rect.bottom - offset.bottom, page.y), rect.top + offset.top ); - } - - status.dx = restrictedX - page.x; - status.dy = restrictedY - page.y; - - status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; - status.locked = !!(status.dx || status.dy); - - status.restrictedX = restrictedX; - status.restrictedY = restrictedY; - - return status; - }, - - reset: function (status) { - status.dx = status.dy = 0; - status.modifiedX = status.modifiedY = NaN; - status.locked = false; - status.changed = true; - - return status; - }, - - modifyCoords: function (page, client, interactable, status, actionName, phase) { - var options = interactable.options[actionName].restrict, - elementRect = options && options.elementRect; - - if (modifiers.restrict.shouldDo(interactable, actionName) - && !(phase === 'start' && elementRect && status.locked)) { - - if (status.locked) { - page.x += status.dx; - page.y += status.dy; - client.x += status.dx; - client.y += status.dy; - - return { - dx: status.dx, - dy: status.dy - }; - } - } +const modifiers = require('./index'); +const utils = require('../utils'); +const defaultOptions = require('../defaultOptions'); + +const restrict = { + defaults: { + enabled : false, + endOnly : false, + restriction: null, + elementRect: null, + }, + + shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { + const restrict = interactable.options[actionName].restrict; + + return restrict && restrict.enabled && (preEnd || !restrict.endOnly) && (!requireEndOnly || restrict.endOnly); + }, + + setOffset: function (interaction, interactable, element, rect, startOffset) { + const elementRect = interactable.options[interaction.prepared.name].restrict.elementRect; + const offset = {}; + + if (rect && elementRect) { + offset.left = startOffset.left - (rect.width * elementRect.left); + offset.top = startOffset.top - (rect.height * elementRect.top); + + offset.right = startOffset.right - (rect.width * (1 - elementRect.right)); + offset.bottom = startOffset.bottom - (rect.height * (1 - elementRect.bottom)); } + else { + offset.left = offset.top = offset.right = offset.bottom = 0; + } + + return offset; + }, + + set: function (pageCoords, interaction, status) { + const target = interaction.target; + const restrict = target && target.options[interaction.prepared.name].restrict; + let restriction = restrict && restrict.restriction; + + if (!restriction) { + return status; + } + + const page = status.useStatusXY + ? { x: status.x, y: status.y } + : utils.extend({}, pageCoords); + + page.x -= interaction.inertiaStatus.resumeDx; + page.y -= interaction.inertiaStatus.resumeDy; + + status.dx = 0; + status.dy = 0; + status.locked = false; + + let rect; + let restrictedX; + let restrictedY; + + if (utils.isString(restriction)) { + if (restriction === 'parent') { + restriction = utils.parentElement(interaction.element); + } + else if (restriction === 'self') { + restriction = target.getRect(interaction.element); + } + else { + restriction = utils.closest(interaction.element, restriction); + } + + if (!restriction) { return status; } + } + + if (utils.isFunction(restriction)) { + restriction = restriction(page.x, page.y, interaction.element); + } + + if (utils.isElement(restriction)) { + restriction = utils.getElementRect(restriction); + } + + rect = restriction; + + const offset = interaction.modifierOffsets.restrict; + + if (!restriction) { + restrictedX = page.x; + restrictedY = page.y; + } + // object is assumed to have + // x, y, width, height or + // left, top, right, bottom + else if ('x' in restriction && 'y' in restriction) { + restrictedX = Math.max(Math.min(rect.x + rect.width - offset.right , page.x), rect.x + offset.left); + restrictedY = Math.max(Math.min(rect.y + rect.height - offset.bottom, page.y), rect.y + offset.top ); + } + else { + restrictedX = Math.max(Math.min(rect.right - offset.right , page.x), rect.left + offset.left); + restrictedY = Math.max(Math.min(rect.bottom - offset.bottom, page.y), rect.top + offset.top ); + } + + status.dx = restrictedX - page.x; + status.dy = restrictedY - page.y; + + status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY; + status.locked = !!(status.dx || status.dy); + + status.restrictedX = restrictedX; + status.restrictedY = restrictedY; + + return status; + }, + + reset: function (status) { + status.dx = status.dy = 0; + status.modifiedX = status.modifiedY = NaN; + status.locked = false; + status.changed = true; + + return status; + }, + + modifyCoords: function (page, client, interactable, status, actionName, phase) { + const options = interactable.options[actionName].restrict; + const elementRect = options && options.elementRect; + + if (modifiers.restrict.shouldDo(interactable, actionName) + && !(phase === 'start' && elementRect && status.locked)) { + + if (status.locked) { + page.x += status.dx; + page.y += status.dy; + client.x += status.dx; + client.y += status.dy; + + return { + dx: status.dx, + dy: status.dy, + }; + } + } + }, }; modifiers.restrict = restrict; @@ -149,4 +149,3 @@ modifiers.names.push('restrict'); defaultOptions.perAction.restrict = restrict.defaults; module.exports = restrict; - diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index d2e84826f..33294f4ea 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -1,238 +1,233 @@ -'use strict'; - -var modifiers = require('./index'), - interact = require('../interact'), - utils = require('../utils'), - defaultOptions = require('../defaultOptions'); - -var snap = { - defaults: { - enabled: false, - endOnly: false, - range : Infinity, - targets: null, - offsets: null, - - relativePoints: null - }, - shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { - var snap = interactable.options[actionName].snap; - - return snap && snap.enabled && (preEnd || !snap.endOnly) && (!requireEndOnly || snap.endOnly); - }, - - setOffset: function (interaction, interactable, element, rect, startOffset) { - var offsets = [], - origin = utils.getOriginXY(interactable, element), - snapOffset = (snap && snap.offset === 'startCoords' - ? { - x: interaction.startCoords.page.x - origin.x, - y: interaction.startCoords.page.y - origin.y - } - : snap && snap.offset || { x: 0, y: 0 }); - - if (rect && snap && snap.relativePoints && snap.relativePoints.length) { - for (var i = 0; i < snap.relativePoints.length; i++) { - offsets.push({ - x: startOffset.left - (rect.width * snap.relativePoints[i].x) + snapOffset.x, - y: startOffset.top - (rect.height * snap.relativePoints[i].y) + snapOffset.y - }); - } - } - else { - offsets.push(snapOffset); - } +const modifiers = require('./index'); +const interact = require('../interact'); +const utils = require('../utils'); +const defaultOptions = require('../defaultOptions'); + +const snap = { + defaults: { + enabled: false, + endOnly: false, + range : Infinity, + targets: null, + offsets: null, + + relativePoints: null, + }, + + shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { + const snap = interactable.options[actionName].snap; + + return snap && snap.enabled && (preEnd || !snap.endOnly) && (!requireEndOnly || snap.endOnly); + }, + + setOffset: function (interaction, interactable, element, rect, startOffset) { + const offsets = []; + const origin = utils.getOriginXY(interactable, element); + const snapOffset = (snap && snap.offset === 'startCoords' + ? { + x: interaction.startCoords.page.x - origin.x, + y: interaction.startCoords.page.y - origin.y, + } + : snap && snap.offset || { x: 0, y: 0 }); + + if (rect && snap && snap.relativePoints && snap.relativePoints.length) { + for (let i = 0; i < snap.relativePoints.length; i++) { + offsets.push({ + x: startOffset.left - (rect.width * snap.relativePoints[i].x) + snapOffset.x, + y: startOffset.top - (rect.height * snap.relativePoints[i].y) + snapOffset.y, + }); + } + } + else { + offsets.push(snapOffset); + } - return offsets; - }, + return offsets; + }, - set: function (pageCoords, interaction, status) { - var snap = interaction.target.options[interaction.prepared.name].snap, - targets = [], - target, - page, - i; + set: function (pageCoords, interaction, status) { + const snap = interaction.target.options[interaction.prepared.name].snap; + const targets = []; + let target; + let page; + let i; - if (status.useStatusXY) { - page = { x: status.x, y: status.y }; - } - else { - var origin = utils.getOriginXY(interaction.target, interaction.element); + if (status.useStatusXY) { + page = { x: status.x, y: status.y }; + } + else { + const origin = utils.getOriginXY(interaction.target, interaction.element); - page = utils.extend({}, pageCoords); + page = utils.extend({}, pageCoords); - page.x -= origin.x; - page.y -= origin.y; - } + page.x -= origin.x; + page.y -= origin.y; + } - status.realX = page.x; - status.realY = page.y; + status.realX = page.x; + status.realY = page.y; - page.x -= interaction.inertiaStatus.resumeDx; - page.y -= interaction.inertiaStatus.resumeDy; + page.x -= interaction.inertiaStatus.resumeDx; + page.y -= interaction.inertiaStatus.resumeDy; - var len = snap.targets? snap.targets.length : 0, - offsets = interaction.modifierOffsets.snap; + const offsets = interaction.modifierOffsets.snap; + let len = snap.targets? snap.targets.length : 0; - for (var relIndex = 0; relIndex < offsets.length; relIndex++) { - var relative = { - x: page.x - offsets[relIndex].x, - y: page.y - offsets[relIndex].y - }; + for (let relIndex = 0; relIndex < offsets.length; relIndex++) { + const relative = { + x: page.x - offsets[relIndex].x, + y: page.y - offsets[relIndex].y, + }; - for (i = 0; i < len; i++) { - if (utils.isFunction(snap.targets[i])) { - target = snap.targets[i](relative.x, relative.y, interaction); - } - else { - target = snap.targets[i]; - } + for (i = 0; i < len; i++) { + if (utils.isFunction(snap.targets[i])) { + target = snap.targets[i](relative.x, relative.y, interaction); + } + else { + target = snap.targets[i]; + } - if (!target) { continue; } + if (!target) { continue; } - targets.push({ - x: utils.isNumber(target.x) ? (target.x + offsets[relIndex].x) : relative.x, - y: utils.isNumber(target.y) ? (target.y + offsets[relIndex].y) : relative.y, + targets.push({ + x: utils.isNumber(target.x) ? (target.x + offsets[relIndex].x) : relative.x, + y: utils.isNumber(target.y) ? (target.y + offsets[relIndex].y) : relative.y, - range: utils.isNumber(target.range)? target.range: snap.range - }); - } - } + range: utils.isNumber(target.range)? target.range: snap.range, + }); + } + } - var closest = { - target: null, - inRange: false, - distance: 0, - range: 0, - dx: 0, - dy: 0 - }; - - for (i = 0, len = targets.length; i < len; i++) { - target = targets[i]; - - var range = target.range, - dx = target.x - page.x, - dy = target.y - page.y, - distance = utils.hypot(dx, dy), - inRange = distance <= range; - - // Infinite targets count as being out of range - // compared to non infinite ones that are in range - if (range === Infinity && closest.inRange && closest.range !== Infinity) { - inRange = false; - } - - if (!closest.target || (inRange - // is the closest target in range? - ? (closest.inRange && range !== Infinity - // the pointer is relatively deeper in this target - ? distance / range < closest.distance / closest.range - // this target has Infinite range and the closest doesn't - : (range === Infinity && closest.range !== Infinity) - // OR this target is closer that the previous closest - || distance < closest.distance) - // The other is not in range and the pointer is closer to this target - : (!closest.inRange && distance < closest.distance))) { - - if (range === Infinity) { - inRange = true; - } - - closest.target = target; - closest.distance = distance; - closest.range = range; - closest.inRange = inRange; - closest.dx = dx; - closest.dy = dy; - - status.range = range; - } - } + const closest = { + target: null, + inRange: false, + distance: 0, + range: 0, + dx: 0, + dy: 0, + }; - var snapChanged; + for (i = 0, len = targets.length; i < len; i++) { + target = targets[i]; + + const range = target.range; + const dx = target.x - page.x; + const dy = target.y - page.y; + const distance = utils.hypot(dx, dy); + let inRange = distance <= range; + + // Infinite targets count as being out of range + // compared to non infinite ones that are in range + if (range === Infinity && closest.inRange && closest.range !== Infinity) { + inRange = false; + } + + if (!closest.target || (inRange + // is the closest target in range? + ? (closest.inRange && range !== Infinity + // the pointer is relatively deeper in this target + ? distance / range < closest.distance / closest.range + // this target has Infinite range and the closest doesn't + : (range === Infinity && closest.range !== Infinity) + // OR this target is closer that the previous closest + || distance < closest.distance) + // The other is not in range and the pointer is closer to this target + : (!closest.inRange && distance < closest.distance))) { + + closest.target = target; + closest.distance = distance; + closest.range = range; + closest.inRange = inRange; + closest.dx = dx; + closest.dy = dy; + + status.range = range; + } + } - if (closest.target) { - snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); + let snapChanged; - status.snappedX = closest.target.x; - status.snappedY = closest.target.y; - } - else { - snapChanged = true; + if (closest.target) { + snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y); - status.snappedX = NaN; - status.snappedY = NaN; - } + status.snappedX = closest.target.x; + status.snappedY = closest.target.y; + } + else { + snapChanged = true; - status.dx = closest.dx; - status.dy = closest.dy; - - status.changed = (snapChanged || (closest.inRange && !status.locked)); - status.locked = closest.inRange; - - return status; - }, - - reset: function (status) { - status.dx = status.dy = 0; - status.snappedX = status.snappedY = NaN; - status.locked = false; - status.changed = true; - - return status; - }, - - modifyCoords: function (page, client, interactable, status, actionName, phase) { - var snap = interactable.options[actionName].snap, - relativePoints = snap && snap.relativePoints; - - if (snap && snap.enabled - && !(phase === 'start' && relativePoints && relativePoints.length)) { - - if (status.locked) { - page.x += status.dx; - page.y += status.dy; - client.x += status.dx; - client.y += status.dy; - } - - return { - range : status.range, - locked : status.locked, - x : status.snappedX, - y : status.snappedY, - realX : status.realX, - realY : status.realY, - dx : status.dx, - dy : status.dy - }; - } + status.snappedX = NaN; + status.snappedY = NaN; + } + + status.dx = closest.dx; + status.dy = closest.dy; + + status.changed = (snapChanged || (closest.inRange && !status.locked)); + status.locked = closest.inRange; + + return status; + }, + + reset: function (status) { + status.dx = status.dy = 0; + status.snappedX = status.snappedY = NaN; + status.locked = false; + status.changed = true; + + return status; + }, + + modifyCoords: function (page, client, interactable, status, actionName, phase) { + const snap = interactable.options[actionName].snap; + const relativePoints = snap && snap.relativePoints; + + if (snap && snap.enabled + && !(phase === 'start' && relativePoints && relativePoints.length)) { + + if (status.locked) { + page.x += status.dx; + page.y += status.dy; + client.x += status.dx; + client.y += status.dy; + } + + return { + range : status.range, + locked : status.locked, + x : status.snappedX, + y : status.snappedY, + realX : status.realX, + realY : status.realY, + dx : status.dx, + dy : status.dy, + }; } + }, }; interact.createSnapGrid = function (grid) { - return function (x, y) { - var offsetX = 0, - offsetY = 0; + return function (x, y) { + let offsetX = 0; + let offsetY = 0; - if (utils.isObject(grid.offset)) { - offsetX = grid.offset.x; - offsetY = grid.offset.y; - } + if (utils.isObject(grid.offset)) { + offsetX = grid.offset.x; + offsetY = grid.offset.y; + } - var gridx = Math.round((x - offsetX) / grid.x), - gridy = Math.round((y - offsetY) / grid.y), + const gridx = Math.round((x - offsetX) / grid.x); + const gridy = Math.round((y - offsetY) / grid.y); - newX = gridx * grid.x + offsetX, - newY = gridy * grid.y + offsetY; + const newX = gridx * grid.x + offsetX; + const newY = gridy * grid.y + offsetY; - return { - x: newX, - y: newY, - range: grid.range - }; + return { + x: newX, + y: newY, + range: grid.range, }; + }; }; modifiers.snap = snap; diff --git a/src/pointerEvents.js b/src/pointerEvents.js index b34887407..d88a8bc54 100644 --- a/src/pointerEvents.js +++ b/src/pointerEvents.js @@ -1,240 +1,240 @@ -'use strict'; - -var scope = require('./scope'), - Interaction = require('./Interaction'), - InteractEvent = require('./InteractEvent'), - utils = require('./utils'), - browser = require('./utils/browser'), - events = require('./utils/events'), - signals = require('./utils/signals'), - simpleSignals = [ - 'interaction-down', - 'interaction-up', - 'interaction-up', - 'interaction-cancel' - ], - simpleEvents = [ - 'down', - 'up', - 'tap', - 'cancel' - ]; +const scope = require('./scope'); +const Interaction = require('./Interaction'); +const InteractEvent = require('./InteractEvent'); +const utils = require('./utils'); +const browser = require('./utils/browser'); +const events = require('./utils/events'); +const signals = require('./utils/signals'); +const simpleSignals = [ + 'interaction-down', + 'interaction-up', + 'interaction-up', + 'interaction-cancel', +]; +const simpleEvents = [ + 'down', + 'up', + 'tap', + 'cancel', +]; function preventOriginalDefault () { - this.originalEvent.preventDefault(); + this.originalEvent.preventDefault(); } function firePointers (interaction, pointer, event, eventTarget, targets, elements, eventType) { - var pointerIndex = interaction.mouse? 0 : utils.indexOf(interaction.pointerIds, utils.getPointerId(pointer)), - pointerEvent = {}, - i, - // for tap events - interval, createNewDoubleTap; - - // if it's a doubletap then the event properties would have been - // copied from the tap event and provided as the pointer argument - if (eventType === 'doubletap') { - pointerEvent = pointer; + const pointerIndex = interaction.mouse? 0 : utils.indexOf(interaction.pointerIds, utils.getPointerId(pointer)); + let pointerEvent = {}; + let i; + // for tap events + let interval; + let createNewDoubleTap; + + // if it's a doubletap then the event properties would have been + // copied from the tap event and provided as the pointer argument + if (eventType === 'doubletap') { + pointerEvent = pointer; + } + else { + utils.extend(pointerEvent, event); + if (event !== pointer) { + utils.extend(pointerEvent, pointer); } - else { - utils.extend(pointerEvent, event); - if (event !== pointer) { - utils.extend(pointerEvent, pointer); - } - - pointerEvent.preventDefault = preventOriginalDefault; - pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; - pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; - pointerEvent.interaction = interaction; - - pointerEvent.timeStamp = new Date().getTime(); - pointerEvent.originalEvent = event; - pointerEvent.type = eventType; - pointerEvent.pointerId = utils.getPointerId(pointer); - pointerEvent.pointerType = interaction.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' - : utils.isString(pointer.pointerType) - ? pointer.pointerType - : [undefined, undefined,'touch', 'pen', 'mouse'][pointer.pointerType]; - } - - if (eventType === 'tap') { - pointerEvent.dt = pointerEvent.timeStamp - interaction.downTimes[pointerIndex]; - - interval = pointerEvent.timeStamp - interaction.tapTime; - createNewDoubleTap = !!(interaction.prevTap && interaction.prevTap.type !== 'doubletap' - && interaction.prevTap.target === pointerEvent.target - && interval < 500); - - pointerEvent.double = createNewDoubleTap; - - interaction.tapTime = pointerEvent.timeStamp; - } - - for (i = 0; i < targets.length; i++) { - pointerEvent.currentTarget = elements[i]; - pointerEvent.interactable = targets[i]; - targets[i].fire(pointerEvent); - if (pointerEvent.immediatePropagationStopped - ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) { - break; - } + pointerEvent.preventDefault = preventOriginalDefault; + pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation; + pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation; + pointerEvent.interaction = interaction; + + pointerEvent.timeStamp = new Date().getTime(); + pointerEvent.originalEvent = event; + pointerEvent.type = eventType; + pointerEvent.pointerId = utils.getPointerId(pointer); + pointerEvent.pointerType = interaction.mouse? 'mouse' : !browser.supportsPointerEvent? 'touch' + : utils.isString(pointer.pointerType) + ? pointer.pointerType + : [undefined, undefined,'touch', 'pen', 'mouse'][pointer.pointerType]; + } + + if (eventType === 'tap') { + pointerEvent.dt = pointerEvent.timeStamp - interaction.downTimes[pointerIndex]; + + interval = pointerEvent.timeStamp - interaction.tapTime; + createNewDoubleTap = !!(interaction.prevTap && interaction.prevTap.type !== 'doubletap' + && interaction.prevTap.target === pointerEvent.target + && interval < 500); + + pointerEvent.double = createNewDoubleTap; + + interaction.tapTime = pointerEvent.timeStamp; + } + + for (i = 0; i < targets.length; i++) { + pointerEvent.currentTarget = elements[i]; + pointerEvent.interactable = targets[i]; + targets[i].fire(pointerEvent); + + if (pointerEvent.immediatePropagationStopped + || (pointerEvent.propagationStopped + && elements[i + 1] !== pointerEvent.currentTarget)) { + break; } + } - if (createNewDoubleTap) { - var doubleTap = {}; + if (createNewDoubleTap) { + const doubleTap = {}; - utils.extend(doubleTap, pointerEvent); + utils.extend(doubleTap, pointerEvent); - doubleTap.dt = interval; - doubleTap.type = 'doubletap'; + doubleTap.dt = interval; + doubleTap.type = 'doubletap'; - collectEventTargets(interaction, doubleTap, event, eventTarget, 'doubletap'); + collectEventTargets(interaction, doubleTap, event, eventTarget, 'doubletap'); - interaction.prevTap = doubleTap; - } - else if (eventType === 'tap') { - interaction.prevTap = pointerEvent; - } + interaction.prevTap = doubleTap; + } + else if (eventType === 'tap') { + interaction.prevTap = pointerEvent; + } } function collectEventTargets (interaction, pointer, event, eventTarget, eventType) { - var pointerIndex = interaction.mouse? 0 : utils.indexOf(interaction.pointerIds, utils.getPointerId(pointer)); - - // do not fire a tap event if the pointer was moved before being lifted - if (eventType === 'tap' && (interaction.pointerWasMoved - // or if the pointerup target is different to the pointerdown target - || !(interaction.downTargets[pointerIndex] && interaction.downTargets[pointerIndex] === eventTarget))) { - return; + const pointerIndex = interaction.mouse? 0 : utils.indexOf(interaction.pointerIds, utils.getPointerId(pointer)); + + // do not fire a tap event if the pointer was moved before being lifted + if (eventType === 'tap' && (interaction.pointerWasMoved + // or if the pointerup target is different to the pointerdown target + || !(interaction.downTargets[pointerIndex] && interaction.downTargets[pointerIndex] === eventTarget))) { + return; + } + + const targets = []; + const elements = []; + let element = eventTarget; + + function collectSelectors (interactable, selector, context) { + const els = browser.useMatchesSelectorPolyfill + ? context.querySelectorAll(selector) + : undefined; + + if (interactable._iEvents[eventType] + && utils.isElement(element) + && scope.inContext(interactable, element) + && !scope.testIgnore(interactable, element, eventTarget) + && scope.testAllow(interactable, element, eventTarget) + && utils.matchesSelector(element, selector, els)) { + + targets.push(interactable); + elements.push(element); } + } - var targets = [], - elements = [], - element = eventTarget; - - function collectSelectors (interactable, selector, context) { - var els = browser.useMatchesSelectorPolyfill - ? context.querySelectorAll(selector) - : undefined; - - if (interactable._iEvents[eventType] - && utils.isElement(element) - && scope.inContext(interactable, element) - && !scope.testIgnore(interactable, element, eventTarget) - && scope.testAllow(interactable, element, eventTarget) - && utils.matchesSelector(element, selector, els)) { - - targets.push(interactable); - elements.push(element); - } - } + const interact = scope.interact; - var interact = scope.interact; + while (element) { + if (interact.isSet(element) && interact(element)._iEvents[eventType]) { + targets.push(interact(element)); + elements.push(element); + } - while (element) { - if (interact.isSet(element) && interact(element)._iEvents[eventType]) { - targets.push(interact(element)); - elements.push(element); - } + scope.interactables.forEachSelector(collectSelectors); - scope.interactables.forEachSelector(collectSelectors); + element = utils.parentElement(element); + } - element = utils.parentElement(element); - } - - // create the tap event even if there are no listeners so that - // doubletap can still be created and fired - if (targets.length || eventType === 'tap') { - firePointers(interaction, pointer, event, eventTarget, targets, elements, eventType); - } + // create the tap event even if there are no listeners so that + // doubletap can still be created and fired + if (targets.length || eventType === 'tap') { + firePointers(interaction, pointer, event, eventTarget, targets, elements, eventType); + } } signals.on('interaction-move', function (arg) { - var interaction = arg.interaction, - pointerIndex = (interaction.mouse - ? 0 - : utils.indexOf(interaction.pointerIds, utils.getPointerId(arg.pointer))); - - if (!arg.duplicateMove && (!interaction.pointerIsDown || interaction.pointerWasMoved)) { - if (interaction.pointerIsDown) { - clearTimeout(interaction.holdTimers[pointerIndex]); - } - - collectEventTargets(interaction, arg.pointer, arg.event, arg.eventTarget, 'move'); + const interaction = arg.interaction; + const pointerIndex = (interaction.mouse + ? 0 + : utils.indexOf(interaction.pointerIds, utils.getPointerId(arg.pointer))); + + if (!arg.duplicateMove && (!interaction.pointerIsDown || interaction.pointerWasMoved)) { + if (interaction.pointerIsDown) { + clearTimeout(interaction.holdTimers[pointerIndex]); } + + collectEventTargets(interaction, arg.pointer, arg.event, arg.eventTarget, 'move'); + } }); signals.on('interaction-down', function (arg) { - var interaction = arg.interaction, - // copy event to be used in timeout for IE8 - eventCopy = browser.isIE8? utils.extend({}, arg.event) : arg.event; + const interaction = arg.interaction; + // copy event to be used in timeout for IE8 + const eventCopy = browser.isIE8? utils.extend({}, arg.event) : arg.event; - interaction.holdTimers[arg.pointerIndex] = setTimeout(function () { + interaction.holdTimers[arg.pointerIndex] = setTimeout(function () { - collectEventTargets(interaction, - browser.isIE8? eventCopy : arg.pointer, - eventCopy, - arg.eventTarget, - 'hold'); + collectEventTargets(interaction, + browser.isIE8? eventCopy : arg.pointer, + eventCopy, + arg.eventTarget, + 'hold'); - }, scope.defaultOptions._holdDuration); + }, scope.defaultOptions._holdDuration); }); function createSignalListener (event) { - return function (arg) { - collectEventTargets(arg.interaction, - arg.pointer, - arg.event, - arg.eventTarget, - event); - }; + return function (arg) { + collectEventTargets(arg.interaction, + arg.pointer, + arg.event, + arg.eventTarget, + event); + }; } -for (var i = 0; i < simpleSignals.length; i++) { - signals.on(simpleSignals[i], createSignalListener(simpleEvents[i])); +for (let i = 0; i < simpleSignals.length; i++) { + signals.on(simpleSignals[i], createSignalListener(simpleEvents[i])); } if (browser.ie8) { - // http://www.quirksmode.org/dom/events/click.html - // >Events leading to dblclick - // - // IE8 doesn't fire down event before dblclick. - // This workaround tries to fire a tap and doubletap after dblclick - var onIE8Dblclick = function (event) { - var target = Interaction.getInteractionFromPointer(event); - - if (!target) { return; } - - var interaction = target.interaction; - - if (interaction.prevTap - && event.clientX === interaction.prevTap.clientX - && event.clientY === interaction.prevTap.clientY - && target.eventTarget === interaction.prevTap.target) { - - interaction.downTargets[0] = target.eventTarget; - interaction.downTimes[0] = new Date().getTime(); - collectEventTargets(interaction, target.pointer, target.event, target.eventTarget, 'tap'); - } - }; - - signals.on('listen-to-document', function (arg) { - events.add(arg.doc, 'dblclick', onIE8Dblclick); - }); + // http://www.quirksmode.org/dom/events/click.html + // >Events leading to dblclick + // + // IE8 doesn't fire down event before dblclick. + // This workaround tries to fire a tap and doubletap after dblclick + const onIE8Dblclick = function (event) { + const target = Interaction.getInteractionFromPointer(event); + + if (!target) { return; } + + const interaction = target.interaction; + + if (interaction.prevTap + && event.clientX === interaction.prevTap.clientX + && event.clientY === interaction.prevTap.clientY + && target.eventTarget === interaction.prevTap.target) { + + interaction.downTargets[0] = target.eventTarget; + interaction.downTimes[0] = new Date().getTime(); + collectEventTargets(interaction, target.pointer, target.event, target.eventTarget, 'tap'); + } + }; + + signals.on('listen-to-document', function (arg) { + events.add(arg.doc, 'dblclick', onIE8Dblclick); + }); } utils.merge(scope.eventTypes, [ - 'down', - 'move', - 'up', - 'cancel', - 'tap', - 'doubletap', - 'hold' + 'down', + 'move', + 'up', + 'cancel', + 'tap', + 'doubletap', + 'hold', ]); module.exports = { - firePointers: firePointers, - collectEventTargets: collectEventTargets, - preventOriginalDefault: preventOriginalDefault + firePointers: firePointers, + collectEventTargets: collectEventTargets, + preventOriginalDefault: preventOriginalDefault, }; diff --git a/src/scope.js b/src/scope.js index 66f38aabd..95fe903f6 100644 --- a/src/scope.js +++ b/src/scope.js @@ -1,9 +1,7 @@ -'use strict'; - -var scope = {}, - utils = require('./utils'), - signals = require('./utils/signals'), - extend = utils.extend; +const scope = {}; +const utils = require('./utils'); +const signals = require('./utils/signals'); +const extend = utils.extend; scope.documents = []; // all documents being listened to @@ -21,58 +19,57 @@ extend(scope, require('./utils/domObjects')); scope.eventTypes = []; scope.withinInteractionLimit = function (interactable, element, action) { - var options = interactable.options, - maxActions = options[action.name].max, - maxPerElement = options[action.name].maxPerElement, - activeInteractions = 0, - targetCount = 0, - targetElementCount = 0; + const options = interactable.options; + const maxActions = options[action.name].max; + const maxPerElement = options[action.name].maxPerElement; + let activeInteractions = 0; + let targetCount = 0; + let targetElementCount = 0; - for (var i = 0, len = scope.interactions.length; i < len; i++) { - var interaction = scope.interactions[i], - otherAction = interaction.prepared.name, - active = interaction.interacting(); + for (let i = 0, len = scope.interactions.length; i < len; i++) { + const interaction = scope.interactions[i]; + const otherAction = interaction.prepared.name; - if (!active) { continue; } + if (!interaction.interacting()) { continue; } - activeInteractions++; + activeInteractions++; - if (activeInteractions >= scope.maxInteractions) { - return false; - } + if (activeInteractions >= scope.maxInteractions) { + return false; + } - if (interaction.target !== interactable) { continue; } + if (interaction.target !== interactable) { continue; } - targetCount += (otherAction === action.name)|0; + targetCount += (otherAction === action.name)|0; - if (targetCount >= maxActions) { - return false; - } + if (targetCount >= maxActions) { + return false; + } - if (interaction.element === element) { - targetElementCount++; + if (interaction.element === element) { + targetElementCount++; - if (otherAction !== action.name || targetElementCount >= maxPerElement) { - return false; - } - } + if (otherAction !== action.name || targetElementCount >= maxPerElement) { + return false; + } } + } - return scope.maxInteractions > 0; + return scope.maxInteractions > 0; }; scope.endAllInteractions = function (event) { - for (var i = 0; i < scope.interactions.length; i++) { - scope.interactions[i].pointerEnd(event, event); - } + for (let i = 0; i < scope.interactions.length; i++) { + scope.interactions[i].pointerEnd(event, event); + } }; signals.on('listen-to-document', function (arg) { - // if document is already known - if (utils.contains(scope.documents, arg.doc)) { - // don't call any further signal listeners - return false; - } + // if document is already known + if (utils.contains(scope.documents, arg.doc)) { + // don't call any further signal listeners + return false; + } }); module.exports = scope; diff --git a/src/utils/arr.js b/src/utils/arr.js index e460a65ae..4df28f15a 100644 --- a/src/utils/arr.js +++ b/src/utils/arr.js @@ -1,29 +1,27 @@ -'use strict'; - function indexOf (array, target) { - for (var i = 0, len = array.length; i < len; i++) { - if (array[i] === target) { - return i; - } + for (let i = 0, len = array.length; i < len; i++) { + if (array[i] === target) { + return i; } + } - return -1; + return -1; } function contains (array, target) { - return indexOf(array, target) !== -1; + return indexOf(array, target) !== -1; } function merge (target, source) { - for (var i = 0; i < source.length; i++) { - target.push(source[i]); - } + for (let i = 0; i < source.length; i++) { + target.push(source[i]); + } - return target; + return target; } module.exports = { - indexOf: indexOf, - contains: contains, - merge: merge + indexOf, + contains, + merge, }; diff --git a/src/utils/browser.js b/src/utils/browser.js index 3ebb37260..5b4fccd62 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -1,49 +1,46 @@ -'use strict'; - -var win = require('./window'), - isType = require('./isType'), - domObjects = require('./domObjects'); - -var browser = { - // Does the browser support touch input? - supportsTouch : !!(('ontouchstart' in win.window) || win.window.DocumentTouch - && domObjects.document instanceof win.DocumentTouch), - - // Does the browser support PointerEvents - supportsPointerEvent : !!domObjects.PointerEvent, - - isIE8 : ('attachEvent' in win.window) && !('addEventListener' in win.window), - - // Opera Mobile must be handled differently - isOperaMobile : (navigator.appName === 'Opera' - && browser.supportsTouch - && navigator.userAgent.match('Presto')), - - // scrolling doesn't change the result of - // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 - isIOS7orLower : (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)), - - isIe9OrOlder : domObjects.document.all && !win.window.atob, - - // prefix matchesSelector - prefixedMatchesSelector: 'matches' in Element.prototype? - 'matches': 'webkitMatchesSelector' in Element.prototype? - 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype? - 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype? - 'oMatchesSelector': 'msMatchesSelector', - - useMatchesSelectorPolyfill: false, - - pEventTypes: (domObjects.PointerEvent - ? (domObjects.PointerEvent === win.window.MSPointerEvent - ? { - up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', - out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' } - : { - up: 'pointerup', down: 'pointerdown', over: 'pointerover', - out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }) - : null) - +const win = require('./window'); +const isType = require('./isType'); +const domObjects = require('./domObjects'); + +const browser = { + // Does the browser support touch input? + supportsTouch: !!(('ontouchstart' in win.window) || win.window.DocumentTouch + && domObjects.document instanceof win.DocumentTouch), + + // Does the browser support PointerEvents + supportsPointerEvent: !!domObjects.PointerEvent, + + isIE8: ('attachEvent' in win.window) && !('addEventListener' in win.window), + + // Opera Mobile must be handled differently + isOperaMobile: (navigator.appName === 'Opera' + && browser.supportsTouch + && navigator.userAgent.match('Presto')), + + // scrolling doesn't change the result of + // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8 + isIOS7orLower: (/iP(hone|od|ad)/.test(navigator.platform) && /OS [1-7][^\d]/.test(navigator.appVersion)), + + isIe9OrOlder: domObjects.document.all && !win.window.atob, + + // prefix matchesSelector + prefixedMatchesSelector: 'matches' in Element.prototype + ? 'matches': 'webkitMatchesSelector' in Element.prototype + ? 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype + ? 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype + ? 'oMatchesSelector': 'msMatchesSelector', + + useMatchesSelectorPolyfill: false, + + pEventTypes: (domObjects.PointerEvent + ? (domObjects.PointerEvent === win.window.MSPointerEvent + ? { + up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', + out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' } + : { + up: 'pointerup', down: 'pointerdown', over: 'pointerover', + out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }) + : null), }; browser.useMatchesSelectorPolyfill = !isType.isFunction(Element.prototype[browser.prefixedMatchesSelector]); diff --git a/src/utils/domObjects.js b/src/utils/domObjects.js index 563982522..bfbc6810a 100644 --- a/src/utils/domObjects.js +++ b/src/utils/domObjects.js @@ -1,8 +1,7 @@ -'use strict'; +const domObjects = {}; +const win = require('./window').window; -var domObjects = {}, - win = require('./window').window, - blank = function () {}; +function blank () {} domObjects.document = win.document; domObjects.DocumentFragment = win.DocumentFragment || blank; diff --git a/src/utils/domUtils.js b/src/utils/domUtils.js index 18ab68e4e..49af5aa04 100644 --- a/src/utils/domUtils.js +++ b/src/utils/domUtils.js @@ -1,236 +1,236 @@ -'use strict'; +const win = require('./window'); +const browser = require('./browser'); +const isType = require('./isType'); +const domObjects = require('./domObjects'); + +const domUtils = { + nodeContains: function (parent, child) { + while (child) { + if (child === parent) { + return true; + } + + child = child.parentNode; + } -var win = require('./window'), - browser = require('./browser'), - isType = require('./isType'), - domObjects = require('./domObjects'); + return false; + }, -var domUtils = { - nodeContains: function (parent, child) { - while (child) { - if (child === parent) { - return true; - } + closest: function (child, selector) { + let parent = domUtils.parentElement(child); - child = child.parentNode; - } + while (isType.isElement(parent)) { + if (domUtils.matchesSelector(parent, selector)) { return parent; } - return false; - }, + parent = domUtils.parentElement(parent); + } - closest: function (child, selector) { - var parent = domUtils.parentElement(child); + return null; + }, - while (isType.isElement(parent)) { - if (domUtils.matchesSelector(parent, selector)) { return parent; } + parentElement: function (node) { + let parent = node.parentNode; - parent = domUtils.parentElement(parent); - } + if (isType.isDocFrag(parent)) { + // skip past #shado-root fragments + while ((parent = parent.host) && isType.isDocFrag(parent)) { + continue; + } - return null; - }, + return parent; + } - parentElement: function (node) { - var parent = node.parentNode; + return parent; + }, - if (isType.isDocFrag(parent)) { - // skip past #shado-root fragments - while ((parent = parent.host) && isType.isDocFrag(parent)) {} + // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified + matchesSelectorPolyfill: (browser.useMatchesSelectorPolyfill? function (element, selector, elems) { + elems = elems || element.parentNode.querySelectorAll(selector); - return parent; - } + for (let i = 0, len = elems.length; i < len; i++) { + if (elems[i] === element) { + return true; + } + } + + return false; + } : null), - return parent; - }, + matchesSelector: function (element, selector, nodeList) { + if (browser.useMatchesSelectorPolyfill) { + return domUtils.matchesSelectorPolyfill(element, selector, nodeList); + } - // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - matchesSelectorPolyfill: (browser.useMatchesSelectorPolyfill? function (element, selector, elems) { - elems = elems || element.parentNode.querySelectorAll(selector); + // remove /deep/ from selectors if shadowDOM polyfill is used + if (win.window !== win.realWindow) { + selector = selector.replace(/\/deep\//g, ' '); + } - for (var i = 0, len = elems.length; i < len; i++) { - if (elems[i] === element) { - return true; - } + return element[browser.prefixedMatchesSelector](selector); + }, + + // Test for the element that's "above" all other qualifiers + indexOfDeepestElement: function (elements) { + let deepestZoneParents = []; + let dropzoneParents = []; + let dropzone; + let deepestZone = elements[0]; + let index = deepestZone? 0: -1; + let parent; + let child; + let i; + let n; + + for (i = 1; i < elements.length; i++) { + dropzone = elements[i]; + + // an element might belong to multiple selector dropzones + if (!dropzone || dropzone === deepestZone) { + continue; + } + + if (!deepestZone) { + deepestZone = dropzone; + index = i; + continue; + } + + // check if the deepest or current are document.documentElement or document.rootElement + // - if the current dropzone is, do nothing and continue + if (dropzone.parentNode === dropzone.ownerDocument) { + continue; + } + // - if deepest is, update with the current dropzone and continue to next + else if (deepestZone.parentNode === dropzone.ownerDocument) { + deepestZone = dropzone; + index = i; + continue; + } + + if (!deepestZoneParents.length) { + parent = deepestZone; + while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { + deepestZoneParents.unshift(parent); + parent = parent.parentNode; } + } - return false; - } : null), + // if this element is an svg element and the current deepest is + // an HTMLElement + if (deepestZone instanceof domObjects.HTMLElement + && dropzone instanceof domObjects.SVGElement + && !(dropzone instanceof domObjects.SVGSVGElement)) { - matchesSelector: function (element, selector, nodeList) { - if (browser.useMatchesSelectorPolyfill) { - return domUtils.matchesSelectorPolyfill(element, selector, nodeList); + if (dropzone === deepestZone.parentNode) { + continue; } - // remove /deep/ from selectors if shadowDOM polyfill is used - if (win.window !== win.realWindow) { - selector = selector.replace(/\/deep\//g, ' '); - } + parent = dropzone.ownerSVGElement; + } + else { + parent = dropzone; + } - return element[browser.prefixedMatchesSelector](selector); - }, - - // Test for the element that's "above" all other qualifiers - indexOfDeepestElement: function (elements) { - var dropzone, - deepestZone = elements[0], - index = deepestZone? 0: -1, - parent, - deepestZoneParents = [], - dropzoneParents = [], - child, - i, - n; - - for (i = 1; i < elements.length; i++) { - dropzone = elements[i]; - - // an element might belong to multiple selector dropzones - if (!dropzone || dropzone === deepestZone) { - continue; - } - - if (!deepestZone) { - deepestZone = dropzone; - index = i; - continue; - } - - // check if the deepest or current are document.documentElement or document.rootElement - // - if the current dropzone is, do nothing and continue - if (dropzone.parentNode === dropzone.ownerDocument) { - continue; - } - // - if deepest is, update with the current dropzone and continue to next - else if (deepestZone.parentNode === dropzone.ownerDocument) { - deepestZone = dropzone; - index = i; - continue; - } - - if (!deepestZoneParents.length) { - parent = deepestZone; - while (parent.parentNode && parent.parentNode !== parent.ownerDocument) { - deepestZoneParents.unshift(parent); - parent = parent.parentNode; - } - } - - // if this element is an svg element and the current deepest is - // an HTMLElement - if (deepestZone instanceof domObjects.HTMLElement - && dropzone instanceof domObjects.SVGElement - && !(dropzone instanceof domObjects.SVGSVGElement)) { - - if (dropzone === deepestZone.parentNode) { - continue; - } - - parent = dropzone.ownerSVGElement; - } - else { - parent = dropzone; - } - - dropzoneParents = []; - - while (parent.parentNode !== parent.ownerDocument) { - dropzoneParents.unshift(parent); - parent = parent.parentNode; - } - - n = 0; - - // get (position of last common ancestor) + 1 - while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { - n++; - } - - var parents = [ - dropzoneParents[n - 1], - dropzoneParents[n], - deepestZoneParents[n] - ]; - - child = parents[0].lastChild; - - while (child) { - if (child === parents[1]) { - deepestZone = dropzone; - index = i; - deepestZoneParents = []; - - break; - } - else if (child === parents[2]) { - break; - } - - child = child.previousSibling; - } - } + dropzoneParents = []; - return index; - }, + while (parent.parentNode !== parent.ownerDocument) { + dropzoneParents.unshift(parent); + parent = parent.parentNode; + } - matchesUpTo: function (element, selector, limit) { - while (domUtils.isElement(element)) { - if (domUtils.matchesSelector(element, selector)) { - return true; - } + n = 0; - element = domUtils.parentElement(element); + // get (position of last common ancestor) + 1 + while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) { + n++; + } - if (element === limit) { - return domUtils.matchesSelector(element, selector); - } - } + const parents = [ + dropzoneParents[n - 1], + dropzoneParents[n], + deepestZoneParents[n], + ]; + + child = parents[0].lastChild; + + while (child) { + if (child === parents[1]) { + deepestZone = dropzone; + index = i; + deepestZoneParents = []; - return false; - }, - - getActualElement: function (element) { - return (element instanceof domObjects.SVGElementInstance - ? element.correspondingUseElement - : element); - }, - - getScrollXY: function (relevantWindow) { - relevantWindow = relevantWindow || win.window; - return { - x: relevantWindow.scrollX || relevantWindow.document.documentElement.scrollLeft, - y: relevantWindow.scrollY || relevantWindow.document.documentElement.scrollTop - }; - }, - - getElementClientRect: function (element) { - var clientRect = (element instanceof domObjects.SVGElement - ? element.getBoundingClientRect() - : element.getClientRects()[0]); - - return clientRect && { - left : clientRect.left, - right : clientRect.right, - top : clientRect.top, - bottom: clientRect.bottom, - width : clientRect.width || clientRect.right - clientRect.left, - height: clientRect.heigh || clientRect.bottom - clientRect.top - }; - }, - - getElementRect: function (element) { - var clientRect = domUtils.getElementClientRect(element); - - if (!browser.isIOS7orLower && clientRect) { - var scroll = domUtils.getScrollXY(win.getWindow(element)); - - clientRect.left += scroll.x; - clientRect.right += scroll.x; - clientRect.top += scroll.y; - clientRect.bottom += scroll.y; + break; } + else if (child === parents[2]) { + break; + } + + child = child.previousSibling; + } + } - return clientRect; + return index; + }, + + matchesUpTo: function (element, selector, limit) { + while (domUtils.isElement(element)) { + if (domUtils.matchesSelector(element, selector)) { + return true; + } + + element = domUtils.parentElement(element); + + if (element === limit) { + return domUtils.matchesSelector(element, selector); + } } + + return false; + }, + + getActualElement: function (element) { + return (element instanceof domObjects.SVGElementInstance + ? element.correspondingUseElement + : element); + }, + + getScrollXY: function (relevantWindow) { + relevantWindow = relevantWindow || win.window; + return { + x: relevantWindow.scrollX || relevantWindow.document.documentElement.scrollLeft, + y: relevantWindow.scrollY || relevantWindow.document.documentElement.scrollTop, + }; + }, + + getElementClientRect: function (element) { + const clientRect = (element instanceof domObjects.SVGElement + ? element.getBoundingClientRect() + : element.getClientRects()[0]); + + return clientRect && { + left : clientRect.left, + right : clientRect.right, + top : clientRect.top, + bottom: clientRect.bottom, + width : clientRect.width || clientRect.right - clientRect.left, + height: clientRect.heigh || clientRect.bottom - clientRect.top, + }; + }, + + getElementRect: function (element) { + const clientRect = domUtils.getElementClientRect(element); + + if (!browser.isIOS7orLower && clientRect) { + const scroll = domUtils.getScrollXY(win.getWindow(element)); + + clientRect.left += scroll.x; + clientRect.right += scroll.x; + clientRect.top += scroll.y; + clientRect.bottom += scroll.y; + } + + return clientRect; + }, }; module.exports = domUtils; diff --git a/src/utils/events.js b/src/utils/events.js index 32fbbc32b..1daf78642 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -1,342 +1,340 @@ -'use strict'; - -var arr = require('./arr'), - isType = require('./isType'), - domUtils = require('./domUtils'), - indexOf = arr.indexOf, - contains = arr.contains, - getWindow = require('./window').getWindow, - - useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window), - addEvent = useAttachEvent? 'attachEvent': 'addEventListener', - removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener', - on = useAttachEvent? 'on': '', - - elements = [], - targets = [], - attachedListeners = [], - - // { - // type: { - // selectors: ['selector', ...], - // contexts : [document, ...], - // listeners: [[listener, useCapture], ...] - // } - // } - delegatedEvents = {}, - documents = []; - +const arr = require('./arr'); +const isType = require('./isType'); +const domUtils = require('./domUtils'); +const indexOf = arr.indexOf; +const contains = arr.contains; +const getWindow = require('./window').getWindow; + +const useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window); +const addEvent = useAttachEvent? 'attachEvent': 'addEventListener'; +const removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener'; +const on = useAttachEvent? 'on': ''; + +const elements = []; +const targets = []; +const attachedListeners = []; + +// { +// type: { +// selectors: ['selector', ...], +// contexts : [document, ...], +// listeners: [[listener, useCapture], ...] +// } +// } +const delegatedEvents = {}; + +const documents = []; function add (element, type, listener, useCapture) { - var elementIndex = indexOf(elements, element), - target = targets[elementIndex]; - - if (!target) { - target = { - events: {}, - typeCount: 0 - }; - - elementIndex = elements.push(element) - 1; - targets.push(target); - - attachedListeners.push((useAttachEvent ? { - supplied: [], - wrapped : [], - useCount: [] - } : null)); - } + let elementIndex = indexOf(elements, element); + let target = targets[elementIndex]; - if (!target.events[type]) { - target.events[type] = []; - target.typeCount++; - } + if (!target) { + target = { + events: {}, + typeCount: 0, + }; - if (!contains(target.events[type], listener)) { - var ret; + elementIndex = elements.push(element) - 1; + targets.push(target); - if (useAttachEvent) { - var listeners = attachedListeners[elementIndex], - listenerIndex = indexOf(listeners.supplied, listener); + attachedListeners.push((useAttachEvent ? { + supplied: [], + wrapped : [], + useCount: [], + } : null)); + } - var wrapped = listeners.wrapped[listenerIndex] || function (event) { - if (!event.immediatePropagationStopped) { - event.target = event.srcElement; - event.currentTarget = element; + if (!target.events[type]) { + target.events[type] = []; + target.typeCount++; + } - event.preventDefault = event.preventDefault || preventDef; - event.stopPropagation = event.stopPropagation || stopProp; - event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; + if (!contains(target.events[type], listener)) { + let ret; - if (/mouse|click/.test(event.type)) { - event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; - event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; - } + if (useAttachEvent) { + const listeners = attachedListeners[elementIndex]; + const listenerIndex = indexOf(listeners.supplied, listener); - listener(event); - } - }; + const wrapped = listeners.wrapped[listenerIndex] || function (event) { + if (!event.immediatePropagationStopped) { + event.target = event.srcElement; + event.currentTarget = element; - ret = element[addEvent](on + type, wrapped, !!useCapture); + event.preventDefault = event.preventDefault || preventDef; + event.stopPropagation = event.stopPropagation || stopProp; + event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; - if (listenerIndex === -1) { - listeners.supplied.push(listener); - listeners.wrapped.push(wrapped); - listeners.useCount.push(1); - } - else { - listeners.useCount[listenerIndex]++; - } - } - else { - ret = element[addEvent](type, listener, !!useCapture); - } - target.events[type].push(listener); + if (/mouse|click/.test(event.type)) { + event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft; + event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop; + } - return ret; + listener(event); + } + }; + + ret = element[addEvent](on + type, wrapped, !!useCapture); + + if (listenerIndex === -1) { + listeners.supplied.push(listener); + listeners.wrapped.push(wrapped); + listeners.useCount.push(1); + } + else { + listeners.useCount[listenerIndex]++; + } } -} - -function remove (element, type, listener, useCapture) { - var i, - elementIndex = indexOf(elements, element), - target = targets[elementIndex], - listeners, - listenerIndex, - wrapped = listener; - - if (!target || !target.events) { - return; + else { + ret = element[addEvent](type, listener, !!useCapture); } + target.events[type].push(listener); - if (useAttachEvent) { - listeners = attachedListeners[elementIndex]; - listenerIndex = indexOf(listeners.supplied, listener); - wrapped = listeners.wrapped[listenerIndex]; - } + return ret; + } +} - if (type === 'all') { - for (type in target.events) { - if (target.events.hasOwnProperty(type)) { - remove(element, type, 'all'); - } - } - return; +function remove (element, type, listener, useCapture) { + const elementIndex = indexOf(elements, element); + const target = targets[elementIndex]; + let wrapped = listener; + let listeners; + let listenerIndex; + let i; + + if (!target || !target.events) { + return; + } + + if (useAttachEvent) { + listeners = attachedListeners[elementIndex]; + listenerIndex = indexOf(listeners.supplied, listener); + wrapped = listeners.wrapped[listenerIndex]; + } + + if (type === 'all') { + for (type in target.events) { + if (target.events.hasOwnProperty(type)) { + remove(element, type, 'all'); + } } - - if (target.events[type]) { - var len = target.events[type].length; - - if (listener === 'all') { - for (i = 0; i < len; i++) { - remove(element, type, target.events[type][i], !!useCapture); - } - return; - } else { - for (i = 0; i < len; i++) { - if (target.events[type][i] === listener) { - element[removeEvent](on + type, wrapped, !!useCapture); - target.events[type].splice(i, 1); - - if (useAttachEvent && listeners) { - listeners.useCount[listenerIndex]--; - if (listeners.useCount[listenerIndex] === 0) { - listeners.supplied.splice(listenerIndex, 1); - listeners.wrapped.splice(listenerIndex, 1); - listeners.useCount.splice(listenerIndex, 1); - } - } - - break; - } + return; + } + + if (target.events[type]) { + const len = target.events[type].length; + + if (listener === 'all') { + for (i = 0; i < len; i++) { + remove(element, type, target.events[type][i], !!useCapture); + } + return; + } else { + for (i = 0; i < len; i++) { + if (target.events[type][i] === listener) { + element[removeEvent](on + type, wrapped, !!useCapture); + target.events[type].splice(i, 1); + + if (useAttachEvent && listeners) { + listeners.useCount[listenerIndex]--; + if (listeners.useCount[listenerIndex] === 0) { + listeners.supplied.splice(listenerIndex, 1); + listeners.wrapped.splice(listenerIndex, 1); + listeners.useCount.splice(listenerIndex, 1); } - } + } - if (target.events[type] && target.events[type].length === 0) { - target.events[type] = null; - target.typeCount--; + break; } + } } - if (!target.typeCount) { - targets.splice(elementIndex, 1); - elements.splice(elementIndex, 1); - attachedListeners.splice(elementIndex, 1); + if (target.events[type] && target.events[type].length === 0) { + target.events[type] = null; + target.typeCount--; } + } + + if (!target.typeCount) { + targets.splice(elementIndex, 1); + elements.splice(elementIndex, 1); + attachedListeners.splice(elementIndex, 1); + } } function addDelegate (selector, context, type, listener, useCapture) { - if (!delegatedEvents[type]) { - delegatedEvents[type] = { - selectors: [], - contexts : [], - listeners: [] - }; - - // add delegate listener functions - for (var i = 0; i < documents.length; i++) { - add(documents[i], type, delegateListener); - add(documents[i], type, delegateUseCapture, true); - } + if (!delegatedEvents[type]) { + delegatedEvents[type] = { + selectors: [], + contexts : [], + listeners: [], + }; + + // add delegate listener functions + for (let i = 0; i < documents.length; i++) { + add(documents[i], type, delegateListener); + add(documents[i], type, delegateUseCapture, true); } + } - var delegated = delegatedEvents[type], - index; + const delegated = delegatedEvents[type]; + let index; - for (index = delegated.selectors.length - 1; index >= 0; index--) { - if (delegated.selectors[index] === selector - && delegated.contexts[index] === context) { - break; - } + for (index = delegated.selectors.length - 1; index >= 0; index--) { + if (delegated.selectors[index] === selector + && delegated.contexts[index] === context) { + break; } + } - if (index === -1) { - index = delegated.selectors.length; + if (index === -1) { + index = delegated.selectors.length; - delegated.selectors.push(selector); - delegated.contexts .push(context); - delegated.listeners.push([]); - } + delegated.selectors.push(selector); + delegated.contexts .push(context); + delegated.listeners.push([]); + } - // keep listener and useCapture flag - delegated.listeners[index].push([listener, useCapture]); + // keep listener and useCapture flag + delegated.listeners[index].push([listener, useCapture]); } function removeDelegate (selector, context, type, listener, useCapture) { - var delegated = delegatedEvents[type], - matchFound = false, - index; - - if (!delegated) { return; } - - // count from last index of delegated to 0 - for (index = delegated.selectors.length - 1; index >= 0; index--) { - // look for matching selector and context Node - if (delegated.selectors[index] === selector - && delegated.contexts[index] === context) { - - var listeners = delegated.listeners[index]; - - // each item of the listeners array is an array: [function, useCaptureFlag] - for (var i = listeners.length - 1; i >= 0; i--) { - var fn = listeners[i][0], - useCap = listeners[i][1]; - - // check if the listener functions and useCapture flags match - if (fn === listener && useCap === useCapture) { - // remove the listener from the array of listeners - listeners.splice(i, 1); - - // if all listeners for this interactable have been removed - // remove the interactable from the delegated arrays - if (!listeners.length) { - delegated.selectors.splice(index, 1); - delegated.contexts .splice(index, 1); - delegated.listeners.splice(index, 1); - - // remove delegate function from context - remove(context, type, delegateListener); - remove(context, type, delegateUseCapture, true); - - // remove the arrays if they are empty - if (!delegated.selectors.length) { - delegatedEvents[type] = null; - } - } - - // only remove one listener - matchFound = true; - break; - } + const delegated = delegatedEvents[type]; + let matchFound = false; + let index; + + if (!delegated) { return; } + + // count from last index of delegated to 0 + for (index = delegated.selectors.length - 1; index >= 0; index--) { + // look for matching selector and context Node + if (delegated.selectors[index] === selector + && delegated.contexts[index] === context) { + + const listeners = delegated.listeners[index]; + + // each item of the listeners array is an array: [function, useCaptureFlag] + for (let i = listeners.length - 1; i >= 0; i--) { + const fn = listeners[i][0]; + const useCap = listeners[i][1]; + + // check if the listener functions and useCapture flags match + if (fn === listener && useCap === useCapture) { + // remove the listener from the array of listeners + listeners.splice(i, 1); + + // if all listeners for this interactable have been removed + // remove the interactable from the delegated arrays + if (!listeners.length) { + delegated.selectors.splice(index, 1); + delegated.contexts .splice(index, 1); + delegated.listeners.splice(index, 1); + + // remove delegate function from context + remove(context, type, delegateListener); + remove(context, type, delegateUseCapture, true); + + // remove the arrays if they are empty + if (!delegated.selectors.length) { + delegatedEvents[type] = null; } + } - if (matchFound) { break; } + // only remove one listener + matchFound = true; + break; } + } + + if (matchFound) { break; } } + } } // bound to the interactable context when a DOM event // listener is added to a selector interactable function delegateListener (event, useCapture) { - var fakeEvent = {}, - delegated = delegatedEvents[event.type], - eventTarget = domUtils.getActualElement(event.path - ? event.path[0] - : event.target), - element = eventTarget; - - useCapture = useCapture? true: false; - - // duplicate the event so that currentTarget can be changed - for (var prop in event) { - fakeEvent[prop] = event[prop]; - } + const fakeEvent = {}; + const delegated = delegatedEvents[event.type]; + const eventTarget = (domUtils.getActualElement(event.path + ? event.path[0] + : event.target)); + let element = eventTarget; - fakeEvent.originalEvent = event; - fakeEvent.preventDefault = preventOriginalDefault; + useCapture = useCapture? true: false; - // climb up document tree looking for selector matches - while (isType.isElement(element)) { - for (var i = 0; i < delegated.selectors.length; i++) { - var selector = delegated.selectors[i], - context = delegated.contexts[i]; + // duplicate the event so that currentTarget can be changed + for (const prop in event) { + fakeEvent[prop] = event[prop]; + } - if (domUtils.matchesSelector(element, selector) - && domUtils.nodeContains(context, eventTarget) - && domUtils.nodeContains(context, element)) { + fakeEvent.originalEvent = event; + fakeEvent.preventDefault = preventOriginalDefault; - var listeners = delegated.listeners[i]; + // climb up document tree looking for selector matches + while (isType.isElement(element)) { + for (let i = 0; i < delegated.selectors.length; i++) { + const selector = delegated.selectors[i]; + const context = delegated.contexts[i]; - fakeEvent.currentTarget = element; + if (domUtils.matchesSelector(element, selector) + && domUtils.nodeContains(context, eventTarget) + && domUtils.nodeContains(context, element)) { - for (var j = 0; j < listeners.length; j++) { - if (listeners[j][1] === useCapture) { - listeners[j][0](fakeEvent); - } - } - } - } + const listeners = delegated.listeners[i]; + + fakeEvent.currentTarget = element; - element = domUtils.parentElement(element); + for (let j = 0; j < listeners.length; j++) { + if (listeners[j][1] === useCapture) { + listeners[j][0](fakeEvent); + } + } + } } + + element = domUtils.parentElement(element); + } } function delegateUseCapture (event) { - return delegateListener.call(this, event, true); + return delegateListener.call(this, event, true); } function preventDef () { - this.returnValue = false; + this.returnValue = false; } function preventOriginalDefault () { - this.originalEvent.preventDefault(); + this.originalEvent.preventDefault(); } function stopProp () { - this.cancelBubble = true; + this.cancelBubble = true; } function stopImmProp () { - this.cancelBubble = true; - this.immediatePropagationStopped = true; + this.cancelBubble = true; + this.immediatePropagationStopped = true; } module.exports = { - add: add, - remove: remove, + add: add, + remove: remove, - addDelegate: addDelegate, - removeDelegate: removeDelegate, + addDelegate: addDelegate, + removeDelegate: removeDelegate, - delegateListener: delegateListener, - delegateUseCapture: delegateUseCapture, - delegatedEvents: delegatedEvents, - documents: documents, + delegateListener: delegateListener, + delegateUseCapture: delegateUseCapture, + delegatedEvents: delegatedEvents, + documents: documents, - useAttachEvent: useAttachEvent, + useAttachEvent: useAttachEvent, - _elements: elements, - _targets: targets, - _attachedListeners: attachedListeners + _elements: elements, + _targets: targets, + _attachedListeners: attachedListeners, }; diff --git a/src/utils/extend.js b/src/utils/extend.js index 876471dc9..91402bf1c 100644 --- a/src/utils/extend.js +++ b/src/utils/extend.js @@ -1,8 +1,6 @@ -'use strict'; - module.exports = function extend (dest, source) { - for (var prop in source) { - dest[prop] = source[prop]; - } - return dest; + for (const prop in source) { + dest[prop] = source[prop]; + } + return dest; }; diff --git a/src/utils/hypot.js b/src/utils/hypot.js index 82f8f07e7..36c7c0561 100644 --- a/src/utils/hypot.js +++ b/src/utils/hypot.js @@ -1,3 +1 @@ -'use strict'; - -module.exports = function hypot (x, y) { return Math.sqrt(x * x + y * y); }; +module.exports = (x, y) => Math.sqrt(x * x + y * y); diff --git a/src/utils/index.js b/src/utils/index.js index 1ef5c82a8..acce303ca 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,71 +1,69 @@ -'use strict'; +const utils = module.exports; +const extend = require('./extend'); +const defaultOptions = require('../defaultOptions'); +const win = require('./window'); -var utils = module.exports, - extend = require('./extend'), - defaultOptions = require('../defaultOptions'), - win = require('./window'); - -utils.blank = function () {}; +utils.blank = function () {}; utils.warnOnce = function (method, message) { - var warned = false; + let warned = false; - return function () { - if (!warned) { - win.window.console.warn(message); - warned = true; - } + return function () { + if (!warned) { + win.window.console.warn(message); + warned = true; + } - return method.apply(this, arguments); - }; + return method.apply(this, arguments); + }; }; // http://stackoverflow.com/a/5634528/2280888 utils._getQBezierValue = function (t, p1, p2, p3) { - var iT = 1 - t; - return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; + const iT = 1 - t; + return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3; }; utils.getQuadraticCurvePoint = function (startX, startY, cpX, cpY, endX, endY, position) { - return { - x: utils._getQBezierValue(position, startX, cpX, endX), - y: utils._getQBezierValue(position, startY, cpY, endY) - }; + return { + x: utils._getQBezierValue(position, startX, cpX, endX), + y: utils._getQBezierValue(position, startY, cpY, endY), + }; }; // http://gizma.com/easing/ utils.easeOutQuad = function (t, b, c, d) { - t /= d; - return -c * t*(t-2) + b; + t /= d; + return -c * t*(t-2) + b; }; utils.getOriginXY = function (interactable, element) { - var origin = interactable - ? interactable.options.origin - : defaultOptions.origin; - - if (origin === 'parent') { - origin = utils.parentElement(element); - } - else if (origin === 'self') { - origin = interactable.getRect(element); - } - else if (utils.trySelector(origin)) { - origin = utils.closest(element, origin) || { x: 0, y: 0 }; - } - - if (utils.isFunction(origin)) { - origin = origin(interactable && element); - } - - if (utils.isElement(origin)) { - origin = utils.getElementRect(origin); - } - - origin.x = ('x' in origin)? origin.x : origin.left; - origin.y = ('y' in origin)? origin.y : origin.top; - - return origin; + let origin = interactable + ? interactable.options.origin + : defaultOptions.origin; + + if (origin === 'parent') { + origin = utils.parentElement(element); + } + else if (origin === 'self') { + origin = interactable.getRect(element); + } + else if (utils.trySelector(origin)) { + origin = utils.closest(element, origin) || { x: 0, y: 0 }; + } + + if (utils.isFunction(origin)) { + origin = origin(interactable && element); + } + + if (utils.isElement(origin)) { + origin = utils.getElementRect(origin); + } + + origin.x = ('x' in origin)? origin.x : origin.left; + origin.y = ('y' in origin)? origin.y : origin.top; + + return origin; }; diff --git a/src/utils/isType.js b/src/utils/isType.js index 371b1774a..ce2f80387 100644 --- a/src/utils/isType.js +++ b/src/utils/isType.js @@ -1,48 +1,46 @@ -'use strict'; +const win = require('./window'); +const domObjects = require('./domObjects'); -var win = require('./window'), - domObjects = require('./domObjects'); +const isType = { + isElement : function (o) { + if (!o || (typeof o !== 'object')) { return false; } -var isType = { - isElement : function (o) { - if (!o || (typeof o !== 'object')) { return false; } - - var _window = win.getWindow(o) || win.window; - - return (/object|function/.test(typeof _window.Element) - ? o instanceof _window.Element //DOM2 - : o.nodeType === 1 && typeof o.nodeName === "string"); - }, + const _window = win.getWindow(o) || win.window; - isArray : null, - - isWindow : require('./isWindow'), + return (/object|function/.test(typeof _window.Element) + ? o instanceof _window.Element //DOM2 + : o.nodeType === 1 && typeof o.nodeName === 'string'); + }, - isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }, + isArray : null, - isObject : function (thing) { return !!thing && (typeof thing === 'object'); }, + isWindow : require('./isWindow'), - isFunction : function (thing) { return typeof thing === 'function'; }, + isDocFrag : function (thing) { return !!thing && thing instanceof domObjects.DocumentFragment; }, - isNumber : function (thing) { return typeof thing === 'number' ; }, + isObject : function (thing) { return !!thing && (typeof thing === 'object'); }, - isBool : function (thing) { return typeof thing === 'boolean' ; }, + isFunction : function (thing) { return typeof thing === 'function'; }, - isString : function (thing) { return typeof thing === 'string' ; }, - - trySelector: function (value) { - if (!isType.isString(value)) { return false; } + isNumber : function (thing) { return typeof thing === 'number' ; }, - // an exception will be raised if it is invalid - domObjects.document.querySelector(value); - return true; - } + isBool : function (thing) { return typeof thing === 'boolean' ; }, + + isString : function (thing) { return typeof thing === 'string' ; }, + + trySelector: function (value) { + if (!isType.isString(value)) { return false; } + + // an exception will be raised if it is invalid + domObjects.document.querySelector(value); + return true; + }, }; isType.isArray = function (thing) { - return isType.isObject(thing) - && (typeof thing.length !== 'undefined') - && isType.isFunction(thing.splice); + return (isType.isObject(thing) + && (typeof thing.length !== 'undefined') + && isType.isFunction(thing.splice)); }; module.exports = isType; diff --git a/src/utils/isWindow.js b/src/utils/isWindow.js index b3ee4b904..4ade6a81a 100644 --- a/src/utils/isWindow.js +++ b/src/utils/isWindow.js @@ -1,5 +1,3 @@ -'use strict'; - module.exports = function isWindow (thing) { - return !!(thing && thing.Window) && (thing instanceof thing.Window); + return !!(thing && thing.Window) && (thing instanceof thing.Window); }; diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 783a09505..835d7f8a3 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -1,111 +1,110 @@ -'use strict'; - -var pointerUtils = {}, +const pointerUtils = {}; // reduce object creation in getXY() - win = require('./window'), - hypot = require('./hypot'), - extend = require('./extend'), - browser = require('./browser'), - isType = require('./isType'), - InteractEvent = require('../InteractEvent'); +const win = require('./window'); +const hypot = require('./hypot'); +const extend = require('./extend'); +const browser = require('./browser'); +const isType = require('./isType'); +const InteractEvent = require('../InteractEvent'); pointerUtils.copyCoords = function (dest, src) { - dest.page = dest.page || {}; - dest.page.x = src.page.x; - dest.page.y = src.page.y; + dest.page = dest.page || {}; + dest.page.x = src.page.x; + dest.page.y = src.page.y; - dest.client = dest.client || {}; - dest.client.x = src.client.x; - dest.client.y = src.client.y; + dest.client = dest.client || {}; + dest.client.x = src.client.x; + dest.client.y = src.client.y; - dest.timeStamp = src.timeStamp; + dest.timeStamp = src.timeStamp; }; pointerUtils.setEventDeltas = function (targetObj, prev, cur) { - targetObj.page.x = cur.page.x - prev.page.x; - targetObj.page.y = cur.page.y - prev.page.y; - targetObj.client.x = cur.client.x - prev.client.x; - targetObj.client.y = cur.client.y - prev.client.y; - targetObj.timeStamp = new Date().getTime() - prev.timeStamp; - - // set pointer velocity - var dt = Math.max(targetObj.timeStamp / 1000, 0.001); - targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; - targetObj.page.vx = targetObj.page.x / dt; - targetObj.page.vy = targetObj.page.y / dt; - - targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; - targetObj.client.vx = targetObj.client.x / dt; - targetObj.client.vy = targetObj.client.y / dt; + targetObj.page.x = cur.page.x - prev.page.x; + targetObj.page.y = cur.page.y - prev.page.y; + targetObj.client.x = cur.client.x - prev.client.x; + targetObj.client.y = cur.client.y - prev.client.y; + targetObj.timeStamp = new Date().getTime() - prev.timeStamp; + + // set pointer velocity + const dt = Math.max(targetObj.timeStamp / 1000, 0.001); + + targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; + targetObj.page.vx = targetObj.page.x / dt; + targetObj.page.vy = targetObj.page.y / dt; + + targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; + targetObj.client.vx = targetObj.client.x / dt; + targetObj.client.vy = targetObj.client.y / dt; }; // Get specified X/Y coords for mouse or event.touches[0] pointerUtils.getXY = function (type, pointer, xy) { - xy = xy || {}; - type = type || 'page'; + xy = xy || {}; + type = type || 'page'; - xy.x = pointer[type + 'X']; - xy.y = pointer[type + 'Y']; + xy.x = pointer[type + 'X']; + xy.y = pointer[type + 'Y']; - return xy; + return xy; }; pointerUtils.getPageXY = function (pointer, page, interaction) { - page = page || {}; - - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - interaction = interaction || pointer.interaction; + page = page || {}; - extend(page, interaction.inertiaStatus.upCoords.page); + if (pointer instanceof InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + interaction = interaction || pointer.interaction; - page.x += interaction.inertiaStatus.sx; - page.y += interaction.inertiaStatus.sy; - } - else { - page.x = pointer.pageX; - page.y = pointer.pageY; - } - } - // Opera Mobile handles the viewport and scrolling oddly - else if (browser.isOperaMobile) { - pointerUtils.getXY('screen', pointer, page); + extend(page, interaction.inertiaStatus.upCoords.page); - page.x += win.window.scrollX; - page.y += win.window.scrollY; + page.x += interaction.inertiaStatus.sx; + page.y += interaction.inertiaStatus.sy; } else { - pointerUtils.getXY('page', pointer, page); + page.x = pointer.pageX; + page.y = pointer.pageY; } - - return page; + } + // Opera Mobile handles the viewport and scrolling oddly + else if (browser.isOperaMobile) { + pointerUtils.getXY('screen', pointer, page); + + page.x += win.window.scrollX; + page.y += win.window.scrollY; + } + else { + pointerUtils.getXY('page', pointer, page); + } + + return page; }; pointerUtils.getClientXY = function (pointer, client, interaction) { - client = client || {}; - - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - extend(client, interaction.inertiaStatus.upCoords.client); - - client.x += interaction.inertiaStatus.sx; - client.y += interaction.inertiaStatus.sy; - } - else { - client.x = pointer.clientX; - client.y = pointer.clientY; - } + client = client || {}; + + if (pointer instanceof InteractEvent) { + if (/inertiastart/.test(pointer.type)) { + extend(client, interaction.inertiaStatus.upCoords.client); + + client.x += interaction.inertiaStatus.sx; + client.y += interaction.inertiaStatus.sy; } else { - // Opera Mobile handles the viewport and scrolling oddly - pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client); + client.x = pointer.clientX; + client.y = pointer.clientY; } + } + else { + // Opera Mobile handles the viewport and scrolling oddly + pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client); + } - return client; + return client; }; pointerUtils.getPointerId = function (pointer) { - return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; + return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; }; module.exports = pointerUtils; diff --git a/src/utils/raf.js b/src/utils/raf.js index 2fea2d842..096ef9840 100644 --- a/src/utils/raf.js +++ b/src/utils/raf.js @@ -1,33 +1,32 @@ -'use strict'; +const vendors = ['ms', 'moz', 'webkit', 'o']; +let lastTime = 0; +let reqFrame; +let cancelFrame; -var lastTime = 0, - vendors = ['ms', 'moz', 'webkit', 'o'], - reqFrame, - cancelFrame; - -for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { - reqFrame = window[vendors[x]+'RequestAnimationFrame']; - cancelFrame = window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; +for (let x = 0; x < vendors.length && !window.requestAnimationFrame; x++) { + reqFrame = window[vendors[x] + 'RequestAnimationFrame']; + cancelFrame = window[vendors[x] +'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; } if (!reqFrame) { - reqFrame = function(callback) { - var currTime = new Date().getTime(), - timeToCall = Math.max(0, 16 - (currTime - lastTime)), - id = setTimeout(function() { callback(currTime + timeToCall); }, - timeToCall); - lastTime = currTime + timeToCall; - return id; - }; + reqFrame = function (callback) { + const currTime = new Date().getTime(); + const timeToCall = Math.max(0, 16 - (currTime - lastTime)); + const id = setTimeout(function () { callback(currTime + timeToCall); }, + timeToCall); + + lastTime = currTime + timeToCall; + return id; + }; } if (!cancelFrame) { - cancelFrame = function(id) { - clearTimeout(id); - }; + cancelFrame = function (id) { + clearTimeout(id); + }; } module.exports = { - request: reqFrame, - cancel: cancelFrame + request: reqFrame, + cancel: cancelFrame, }; diff --git a/src/utils/signals.js b/src/utils/signals.js index 12dcde4c3..17cbe5d72 100644 --- a/src/utils/signals.js +++ b/src/utils/signals.js @@ -1,41 +1,39 @@ -'use strict'; - -var listeners = { - // signalName: [listeners], +const listeners = { + // signalName: [listeners], }; -var arr = require('./arr'); - -var signals = { - on: function (name, listener) { - if (!listeners[name]) { - listeners[name] = [listener]; - return; - } - - listeners[name].push(listener); - }, - off: function (name, listener) { - if (!listeners[name]) { return; } - - var index = arr.indexOf(listeners[name], listener); - - if (index !== -1) { - listeners[name].splice(index, 1); - } - }, - fire: function (name, arg) { - var targetListeners = listeners[name]; - - if (!targetListeners) { return; } - - for (var i = 0; i < targetListeners.length; i++) { - if (targetListeners[i](arg, name) === false) { - return; - } - } - }, - listeners: listeners +const arr = require('./arr'); + +const signals = { + on: function (name, listener) { + if (!listeners[name]) { + listeners[name] = [listener]; + return; + } + + listeners[name].push(listener); + }, + off: function (name, listener) { + if (!listeners[name]) { return; } + + const index = arr.indexOf(listeners[name], listener); + + if (index !== -1) { + listeners[name].splice(index, 1); + } + }, + fire: function (name, arg) { + const targetListeners = listeners[name]; + + if (!targetListeners) { return; } + + for (let i = 0; i < targetListeners.length; i++) { + if (targetListeners[i](arg, name) === false) { + return; + } + } + }, + listeners: listeners, }; module.exports = signals; diff --git a/src/utils/window.js b/src/utils/window.js index 19000f693..28c29f9b1 100644 --- a/src/utils/window.js +++ b/src/utils/window.js @@ -1,44 +1,42 @@ -'use strict'; - -var win = module.exports, - isWindow = require('./isWindow'); +const win = module.exports; +const isWindow = require('./isWindow'); function init (window) { - // get wrapped window if using Shadow DOM polyfill + // get wrapped window if using Shadow DOM polyfill - win.realWindow = window; + win.realWindow = window; - // create a TextNode - var el = window.document.createTextNode(''); + // create a TextNode + const el = window.document.createTextNode(''); - // check if it's wrapped by a polyfill - if (el.ownerDocument !== window.document - && typeof window.wrap === 'function' - && window.wrap(el) === el) { - // return wrapped window - win.window = window.wrap(window); - } + // check if it's wrapped by a polyfill + if (el.ownerDocument !== window.document + && typeof window.wrap === 'function' + && window.wrap(el) === el) { + // return wrapped window + win.window = window.wrap(window); + } - // no Shadow DOM polyfil or native implementation - win.window = window; + // no Shadow DOM polyfil or native implementation + win.window = window; } if (typeof window === 'undefined') { - win.window = undefined; - win.realWindow = undefined; + win.window = undefined; + win.realWindow = undefined; } else { - init(window); + init(window); } win.getWindow = function getWindow (node) { - if (isWindow(node)) { - return node; - } + if (isWindow(node)) { + return node; + } - var rootNode = (node.ownerDocument || node); + const rootNode = (node.ownerDocument || node); - return rootNode.defaultView || rootNode.parentWindow || win.window; + return rootNode.defaultView || rootNode.parentWindow || win.window; }; win.init = init; From ed1bd85f1f9082b6ab8bbbd4900bf28fe8545fc9 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 7 Sep 2015 01:53:20 +0100 Subject: [PATCH 100/131] package.json: update dependencies --- package.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 92e1761c0..f95f3b702 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "devDependencies": { "babel-eslint": "^4.1.1", "babelify": "^6.3.0", - "browser-sync": "^2.8.2", + "browser-sync": "^2.9.1", "browserify": "^11.0.1", "chai": "^3.2.0", "eslint": "^1.3.1", @@ -57,24 +57,24 @@ "gulp-notify": "^2.2.0", "gulp-rename": "^1.2.2", "gulp-sourcemaps": "^1.5.2", - "gulp-uglify": "^1.2.0", + "gulp-uglify": "^1.4.0", "gulp-util": "^3.0.6", "jshint-stylish": "^2.0.1", - "karma": "^0.13.8", + "karma": "^0.13.9", "karma-browserify": "^4.3.0", "karma-chai": "^0.1.0", "karma-chrome-launcher": "^0.2.0", "karma-fixture": "^0.2.5", "karma-html2js-preprocessor": "^0.1.0", "karma-mocha": "^0.2.0", - "karma-nyan-reporter": "0.2.1", + "karma-nyan-reporter": "^0.2.2", "lodash": "^3.10.1", - "merge-stream": "^0.1.8", - "mocha": "^2.2.5", + "merge-stream": "^1.0.0", + "mocha": "^2.3.0", "pretty-hrtime": "^1.0.0", "require-dir": "^0.3.0", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", - "watchify": "^3.3.1" + "watchify": "^3.4.0" } } From 86fb534ef4d143b8f3c8cde13825810b5c3aeda2 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 7 Sep 2015 01:58:52 +0100 Subject: [PATCH 101/131] .babelrc: add babel config file --- .babelrc | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .babelrc diff --git a/.babelrc b/.babelrc new file mode 100644 index 000000000..a2162ee4f --- /dev/null +++ b/.babelrc @@ -0,0 +1,22 @@ +{ + "stage": 1, + "loose": ["all"], + "whitelist": [ + "es3.memberExpressionLiterals", + "es3.propertyLiterals", + "es6.arrowFunctions", + "es6.blockScoping", + "es6.classes", + "es6.constants", + "es6.destructuring", + "es6.forOf", + "es6.literals", + "es6.objectSuper", + "es6.parameters", + "es6.properties.computed", + "es6.properties.shorthand", + "es6.spread", + "es6.tailCall", + "es6.templateLiterals" + ] +} From c7a6b966f7ab6a5bb5c471a868f0ccc18e7dc384 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 8 Sep 2015 15:06:31 +0100 Subject: [PATCH 102/131] Interact*: Use ES6 classes --- src/InteractEvent.js | 364 ++++++++++++++++++++++--------------------- src/Interactable.js | 149 +++++++++--------- src/Interaction.js | 351 +++++++++++++++++++++-------------------- 3 files changed, 432 insertions(+), 432 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 15673f2a1..5a828cd41 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -3,207 +3,209 @@ const utils = require('./utils'); const signals = require('./utils/signals'); const modifiers = require('./modifiers'); -function InteractEvent (interaction, event, action, phase, element, related) { - const target = interaction.target; - const deltaSource = (target && target.options || scope.defaultOptions).deltaSource; - const sourceX = deltaSource + 'X'; - const sourceY = deltaSource + 'Y'; - const options = target? target.options: scope.defaultOptions; - const origin = utils.getOriginXY(target, element); - const starting = phase === 'start'; - const ending = phase === 'end'; - const coords = starting? interaction.startCoords : interaction.curCoords; - - let client; - let page; - - element = element || interaction.element; - - page = utils.extend({}, coords.page); - client = utils.extend({}, coords.client); - - page.x -= origin.x; - page.y -= origin.y; - - client.x -= origin.x; - client.y -= origin.y; - - this.ctrlKey = event.ctrlKey; - this.altKey = event.altKey; - this.shiftKey = event.shiftKey; - this.metaKey = event.metaKey; - this.button = event.button; - this.buttons = event.buttons; - this.target = element; - this.t0 = interaction.downTimes[0]; - this.type = action + (phase || ''); - - this.interaction = interaction; - this.interactable = target; - - for (let i = 0; i < modifiers.names.length; i++) { - const modifierName = modifiers.names[i]; - const modifier = modifiers[modifierName]; - - this[modifierName] = modifier.modifyCoords(page, client, target, interaction.modifierStatuses[modifierName], action, phase); - } +class InteractEvent { + constructor (interaction, event, action, phase, element, related) { + const target = interaction.target; + const deltaSource = (target && target.options || scope.defaultOptions).deltaSource; + const sourceX = deltaSource + 'X'; + const sourceY = deltaSource + 'Y'; + const options = target? target.options: scope.defaultOptions; + const origin = utils.getOriginXY(target, element); + const starting = phase === 'start'; + const ending = phase === 'end'; + const coords = starting? interaction.startCoords : interaction.curCoords; + + let client; + let page; + + element = element || interaction.element; + + page = utils.extend({}, coords.page); + client = utils.extend({}, coords.client); + + page.x -= origin.x; + page.y -= origin.y; + + client.x -= origin.x; + client.y -= origin.y; + + this.ctrlKey = event.ctrlKey; + this.altKey = event.altKey; + this.shiftKey = event.shiftKey; + this.metaKey = event.metaKey; + this.button = event.button; + this.buttons = event.buttons; + this.target = element; + this.t0 = interaction.downTimes[0]; + this.type = action + (phase || ''); + + this.interaction = interaction; + this.interactable = target; + + for (let i = 0; i < modifiers.names.length; i++) { + const modifierName = modifiers.names[i]; + const modifier = modifiers[modifierName]; + + this[modifierName] = modifier.modifyCoords(page, client, target, interaction.modifierStatuses[modifierName], action, phase); + } - this.pageX = page.x; - this.pageY = page.y; - this.clientX = client.x; - this.clientY = client.y; - - this.x0 = interaction.startCoords.page.x - origin.x; - this.y0 = interaction.startCoords.page.y - origin.y; - this.clientX0 = interaction.startCoords.client.x - origin.x; - this.clientY0 = interaction.startCoords.client.y - origin.y; - - const inertiaStatus = interaction.inertiaStatus; - const signalArg = { - interactEvent: this, - interaction: interaction, - event: event, - action: action, - phase: phase, - element: element, - related: related, - page: page, - client: client, - coords: coords, - starting: starting, - ending: ending, - deltaSource: deltaSource, - }; - - if (inertiaStatus.active) { - this.detail = 'inertia'; - } + this.pageX = page.x; + this.pageY = page.y; + this.clientX = client.x; + this.clientY = client.y; + + this.x0 = interaction.startCoords.page.x - origin.x; + this.y0 = interaction.startCoords.page.y - origin.y; + this.clientX0 = interaction.startCoords.client.x - origin.x; + this.clientY0 = interaction.startCoords.client.y - origin.y; + + const inertiaStatus = interaction.inertiaStatus; + const signalArg = { + interactEvent: this, + interaction: interaction, + event: event, + action: action, + phase: phase, + element: element, + related: related, + page: page, + client: client, + coords: coords, + starting: starting, + ending: ending, + deltaSource: deltaSource, + }; - if (related) { - this.relatedTarget = related; - } + if (inertiaStatus.active) { + this.detail = 'inertia'; + } - // end event dx, dy is difference between start and end points - if (ending) { - if (deltaSource === 'client') { - this.dx = client.x - interaction.startCoords.client.x; - this.dy = client.y - interaction.startCoords.client.y; + if (related) { + this.relatedTarget = related; } - else { - this.dx = page.x - interaction.startCoords.page.x; - this.dy = page.y - interaction.startCoords.page.y; + + // end event dx, dy is difference between start and end points + if (ending) { + if (deltaSource === 'client') { + this.dx = client.x - interaction.startCoords.client.x; + this.dy = client.y - interaction.startCoords.client.y; + } + else { + this.dx = page.x - interaction.startCoords.page.x; + this.dy = page.y - interaction.startCoords.page.y; + } } - } - else if (starting) { - this.dx = 0; - this.dy = 0; - } - // copy properties from previousmove if starting inertia - else if (phase === 'inertiastart') { - this.dx = interaction.prevEvent.dx; - this.dy = interaction.prevEvent.dy; - } - else { - if (deltaSource === 'client') { - this.dx = client.x - interaction.prevEvent.clientX; - this.dy = client.y - interaction.prevEvent.clientY; + else if (starting) { + this.dx = 0; + this.dy = 0; + } + // copy properties from previousmove if starting inertia + else if (phase === 'inertiastart') { + this.dx = interaction.prevEvent.dx; + this.dy = interaction.prevEvent.dy; } else { - this.dx = page.x - interaction.prevEvent.pageX; - this.dy = page.y - interaction.prevEvent.pageY; + if (deltaSource === 'client') { + this.dx = client.x - interaction.prevEvent.clientX; + this.dy = client.y - interaction.prevEvent.clientY; + } + else { + this.dx = page.x - interaction.prevEvent.pageX; + this.dy = page.y - interaction.prevEvent.pageY; + } } - } - if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' - && !inertiaStatus.active - && options[action].inertia && options[action].inertia.zeroResumeDelta) { + if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' + && !inertiaStatus.active + && options[action].inertia && options[action].inertia.zeroResumeDelta) { - inertiaStatus.resumeDx += this.dx; - inertiaStatus.resumeDy += this.dy; + inertiaStatus.resumeDx += this.dx; + inertiaStatus.resumeDy += this.dy; - this.dx = this.dy = 0; - } + this.dx = this.dy = 0; + } - signals.fire('interactevent-set-delta', signalArg); + signals.fire('interactevent-set-delta', signalArg); - if (starting) { - this.timeStamp = interaction.downTimes[0]; - this.dt = 0; - this.duration = 0; - this.speed = 0; - this.velocityX = 0; - this.velocityY = 0; - } - else if (phase === 'inertiastart') { - this.timeStamp = interaction.prevEvent.timeStamp; - this.dt = interaction.prevEvent.dt; - this.duration = interaction.prevEvent.duration; - this.speed = interaction.prevEvent.speed; - this.velocityX = interaction.prevEvent.velocityX; - this.velocityY = interaction.prevEvent.velocityY; - } - else { - this.timeStamp = new Date().getTime(); - this.dt = this.timeStamp - interaction.prevEvent.timeStamp; - this.duration = this.timeStamp - interaction.downTimes[0]; - - if (event instanceof InteractEvent) { - const dx = this[sourceX] - interaction.prevEvent[sourceX]; - const dy = this[sourceY] - interaction.prevEvent[sourceY]; - const dt = this.dt / 1000; - - this.speed = utils.hypot(dx, dy) / dt; - this.velocityX = dx / dt; - this.velocityY = dy / dt; + if (starting) { + this.timeStamp = interaction.downTimes[0]; + this.dt = 0; + this.duration = 0; + this.speed = 0; + this.velocityX = 0; + this.velocityY = 0; + } + else if (phase === 'inertiastart') { + this.timeStamp = interaction.prevEvent.timeStamp; + this.dt = interaction.prevEvent.dt; + this.duration = interaction.prevEvent.duration; + this.speed = interaction.prevEvent.speed; + this.velocityX = interaction.prevEvent.velocityX; + this.velocityY = interaction.prevEvent.velocityY; } - // if normal move or end event, use previous user event coords else { - // speed and velocity in pixels per second - this.speed = interaction.pointerDelta[deltaSource].speed; - this.velocityX = interaction.pointerDelta[deltaSource].vx; - this.velocityY = interaction.pointerDelta[deltaSource].vy; + this.timeStamp = new Date().getTime(); + this.dt = this.timeStamp - interaction.prevEvent.timeStamp; + this.duration = this.timeStamp - interaction.downTimes[0]; + + if (event instanceof InteractEvent) { + const dx = this[sourceX] - interaction.prevEvent[sourceX]; + const dy = this[sourceY] - interaction.prevEvent[sourceY]; + const dt = this.dt / 1000; + + this.speed = utils.hypot(dx, dy) / dt; + this.velocityX = dx / dt; + this.velocityY = dy / dt; + } + // if normal move or end event, use previous user event coords + else { + // speed and velocity in pixels per second + this.speed = interaction.pointerDelta[deltaSource].speed; + this.velocityX = interaction.pointerDelta[deltaSource].vx; + this.velocityY = interaction.pointerDelta[deltaSource].vy; + } } - } - if ((ending || phase === 'inertiastart') - && interaction.prevEvent.speed > 600 - && this.timeStamp - interaction.prevEvent.timeStamp < 150) { - - let angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI; - const overlap = 22.5; - - if (angle < 0) { - angle += 360; + if ((ending || phase === 'inertiastart') + && interaction.prevEvent.speed > 600 + && this.timeStamp - interaction.prevEvent.timeStamp < 150) { + + let angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI; + const overlap = 22.5; + + if (angle < 0) { + angle += 360; + } + + const left = 135 - overlap <= angle && angle < 225 + overlap; + const up = 225 - overlap <= angle && angle < 315 + overlap; + + const right = !left && (315 - overlap <= angle || angle < 45 + overlap); + const down = !up && 45 - overlap <= angle && angle < 135 + overlap; + + this.swipe = { + up : up, + down : down, + left : left, + right: right, + angle: angle, + speed: interaction.prevEvent.speed, + velocity: { + x: interaction.prevEvent.velocityX, + y: interaction.prevEvent.velocityY, + }, + }; } - - const left = 135 - overlap <= angle && angle < 225 + overlap; - const up = 225 - overlap <= angle && angle < 315 + overlap; - - const right = !left && (315 - overlap <= angle || angle < 45 + overlap); - const down = !up && 45 - overlap <= angle && angle < 135 + overlap; - - this.swipe = { - up : up, - down : down, - left : left, - right: right, - angle: angle, - speed: interaction.prevEvent.speed, - velocity: { - x: interaction.prevEvent.velocityX, - y: interaction.prevEvent.velocityY, - }, - }; } -} -InteractEvent.prototype = { - preventDefault: utils.blank, - stopImmediatePropagation: function () { + preventDefault () {} + + stopImmediatePropagation () { this.immediatePropagationStopped = this.propagationStopped = true; - }, - stopPropagation: function () { + } + + stopPropagation () { this.propagationStopped = true; - }, -}; + } +} module.exports = InteractEvent; diff --git a/src/Interactable.js b/src/Interactable.js index ba1ba9bf1..cf93fc67f 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -10,53 +10,54 @@ const actions = require('./actions/base'); ** * Object type returned by @interact \*/ -function Interactable (element, options) { - this._element = element; - this._iEvents = this._iEvents || {}; +class Interactable { + constructor (element, options) { + this._element = element; + this._context = scope.document; + this._iEvents = this._iEvents || {}; - let _window; + let _window; - if (utils.trySelector(element)) { - this.selector = element; + if (utils.trySelector(element)) { + this.selector = element; - const context = options && options.context; + const context = options && options.context; - _window = context? scope.getWindow(context) : scope.window; + _window = context? scope.getWindow(context) : scope.window; - if (context && (_window.Node - ? context instanceof _window.Node - : (utils.isElement(context) || context === _window.document))) { + if (context && (_window.Node + ? context instanceof _window.Node + : (utils.isElement(context) || context === _window.document))) { - this._context = context; + this._context = context; + } + } + else { + _window = scope.getWindow(element); } - } - else { - _window = scope.getWindow(element); - } - - this._doc = _window.document; - signals.fire('interactable-new', { - interactable: this, - element: element, - options: options, - win: _window, - }); + this._doc = _window.document; - if (this._doc !== scope.document) { - signals.fire('listen-to-document', { - doc: this._doc, + signals.fire('interactable-new', { + interactable: this, + element: element, + options: options, win: _window, }); - } - scope.interactables.push(this); + if (this._doc !== scope.document) { + signals.fire('listen-to-document', { + doc: this._doc, + win: _window, + }); + } - this.set(options); -} + scope.interactables.push(this); -Interactable.prototype = { - setOnEvents: function (action, phases) { + this.set(options); + } + + setOnEvents (action, phases) { const onAction = 'on' + action; if (utils.isFunction(phases.onstart) ) { this[onAction + 'start' ] = phases.onstart ; } @@ -65,9 +66,9 @@ Interactable.prototype = { if (utils.isFunction(phases.oninertiastart)) { this[onAction + 'inertiastart' ] = phases.oninertiastart ; } return this; - }, + } - setPerAction: function (action, options) { + setPerAction (action, options) { // for all the default per-action options for (const option in options) { // if this option exists for this action @@ -90,9 +91,9 @@ Interactable.prototype = { } } } - }, + } - getAction: function (pointer, event, interaction, element) { + getAction (pointer, event, interaction, element) { const action = this.defaultActionChecker(pointer, event, interaction, element); if (this.options.actionChecker) { @@ -100,9 +101,7 @@ Interactable.prototype = { } return action; - }, - - defaultActionChecker: actions.defaultChecker, + } /*\ * Interactable.actionChecker @@ -132,7 +131,7 @@ Interactable.prototype = { | return action; | }); \*/ - actionChecker: function (checker) { + actionChecker (checker) { if (utils.isFunction(checker)) { this.options.actionChecker = checker; @@ -146,7 +145,7 @@ Interactable.prototype = { } return this.options.actionChecker; - }, + } /*\ * Interactable.getRect @@ -166,7 +165,7 @@ Interactable.prototype = { o height: 0 o } \*/ - getRect: function rectCheck (element) { + getRect (element) { element = element || this._element; if (this.selector && !(utils.isElement(element))) { @@ -174,7 +173,7 @@ Interactable.prototype = { } return utils.getElementRect(element); - }, + } /*\ * Interactable.rectChecker @@ -186,7 +185,7 @@ Interactable.prototype = { - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect = (function | object) The checker function or this Interactable \*/ - rectChecker: function (checker) { + rectChecker (checker) { if (utils.isFunction(checker)) { this.getRect = checker; @@ -200,7 +199,7 @@ Interactable.prototype = { } return this.getRect; - }, + } /*\ * Interactable.styleCursor @@ -213,7 +212,7 @@ Interactable.prototype = { - newValue (boolean) #optional = (boolean | Interactable) The current setting or this Interactable \*/ - styleCursor: function (newValue) { + styleCursor (newValue) { if (utils.isBool(newValue)) { this.options.styleCursor = newValue; @@ -227,7 +226,7 @@ Interactable.prototype = { } return this.options.styleCursor; - }, + } /*\ * Interactable.preventDefault @@ -242,7 +241,7 @@ Interactable.prototype = { - newValue (string) #optional `true`, `false` or `'auto'` = (string | Interactable) The current setting or this Interactable \*/ - preventDefault: function (newValue) { + preventDefault (newValue) { if (/^(always|never|auto)$/.test(newValue)) { this.options.preventDefault = newValue; return this; @@ -254,7 +253,7 @@ Interactable.prototype = { } return this.options.preventDefault; - }, + } /*\ * Interactable.origin @@ -269,7 +268,7 @@ Interactable.prototype = { ** = (object) The current origin or this Interactable \*/ - origin: function (newValue) { + origin (newValue) { if (utils.trySelector(newValue)) { this.options.origin = newValue; return this; @@ -280,7 +279,7 @@ Interactable.prototype = { } return this.options.origin; - }, + } /*\ * Interactable.deltaSource @@ -292,7 +291,7 @@ Interactable.prototype = { - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work = (string | object) The current deltaSource or this Interactable \*/ - deltaSource: function (newValue) { + deltaSource (newValue) { if (newValue === 'page' || newValue === 'client') { this.options.deltaSource = newValue; @@ -300,7 +299,7 @@ Interactable.prototype = { } return this.options.deltaSource; - }, + } /*\ * Interactable.context @@ -311,11 +310,9 @@ Interactable.prototype = { = (Node) The context Node of this Interactable ** \*/ - context: function () { + context () { return this._context; - }, - - _context: scope.document, + } /*\ * Interactable.ignoreFrom @@ -332,7 +329,7 @@ Interactable.prototype = { | // or | interact(element).ignoreFrom('input, textarea, a'); \*/ - ignoreFrom: function (newValue) { + ignoreFrom (newValue) { if (utils.trySelector(newValue)) { // CSS selector to match event.target this.options.ignoreFrom = newValue; return this; @@ -344,7 +341,7 @@ Interactable.prototype = { } return this.options.ignoreFrom; - }, + } /*\ * Interactable.allowFrom @@ -361,7 +358,7 @@ Interactable.prototype = { | // or | interact(element).allowFrom('.handle'); \*/ - allowFrom: function (newValue) { + allowFrom (newValue) { if (utils.trySelector(newValue)) { // CSS selector to match event.target this.options.allowFrom = newValue; return this; @@ -373,7 +370,7 @@ Interactable.prototype = { } return this.options.allowFrom; - }, + } /*\ * Interactable.element @@ -384,9 +381,9 @@ Interactable.prototype = { * = (Element) HTML / SVG Element \*/ - element: function () { + element () { return this._element; - }, + } /*\ * Interactable.fire @@ -398,7 +395,7 @@ Interactable.prototype = { - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable = (Interactable) this Interactable \*/ - fire: function (iEvent) { + fire (iEvent) { if (!(iEvent && iEvent.type) || !utils.contains(scope.eventTypes, iEvent.type)) { return this; } @@ -431,7 +428,7 @@ Interactable.prototype = { } return this; - }, + } /*\ * Interactable.on @@ -444,7 +441,7 @@ Interactable.prototype = { - useCapture (boolean) #optional useCapture flag for addEventListener = (object) This Interactable \*/ - on: function (eventType, listener, useCapture) { + on (eventType, listener, useCapture) { let i; if (utils.isString(eventType) && eventType.search(' ') !== -1) { @@ -492,7 +489,7 @@ Interactable.prototype = { } return this; - }, + } /*\ * Interactable.off @@ -505,7 +502,7 @@ Interactable.prototype = { - useCapture (boolean) #optional useCapture flag for removeEventListener = (object) This Interactable \*/ - off: function (eventType, listener, useCapture) { + off (eventType, listener, useCapture) { let i; if (utils.isString(eventType) && eventType.search(' ') !== -1) { @@ -556,7 +553,7 @@ Interactable.prototype = { } return this; - }, + } /*\ * Interactable.set @@ -566,7 +563,7 @@ Interactable.prototype = { - options (object) The new settings to apply = (object) This Interactable \*/ - set: function (options) { + set (options) { if (!utils.isObject(options)) { options = {}; } @@ -602,7 +599,7 @@ Interactable.prototype = { } return this; - }, + } /*\ * Interactable.unset @@ -613,7 +610,7 @@ Interactable.prototype = { * = (object) @interact \*/ - unset: function () { + unset () { events.remove(this._element, 'all'); if (!utils.isString(this.selector)) { @@ -656,7 +653,9 @@ Interactable.prototype = { scope.interactables.splice(utils.indexOf(scope.interactables, this), 1); return scope.interact; - }, -}; + } +} + +Interactable.prototype.defaultActionChecker = actions.defaultChecker; module.exports = Interactable; diff --git a/src/Interaction.js b/src/Interaction.js index 9f3fc15b7..9cdd2d754 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -14,138 +14,127 @@ const methodNames = [ ]; const listeners = {}; +class Interaction { + constructor () { + this.target = null; // current interactable being interacted with + this.element = null; // the target element of the interactable + this.dropTarget = null; // the dropzone a drag target might be dropped into + this.dropElement = null; // the element at the time of checking + this.prevDropTarget = null; // the dropzone that was recently dragged away from + this.prevDropElement = null; // the element at the time of checking + + this.prepared = { // action that's ready to be fired on next move event + name : null, + axis : null, + edges: null, + }; -function Interaction () { - this.target = null; // current interactable being interacted with - this.element = null; // the target element of the interactable - this.dropTarget = null; // the dropzone a drag target might be dropped into - this.dropElement = null; // the element at the time of checking - this.prevDropTarget = null; // the dropzone that was recently dragged away from - this.prevDropElement = null; // the element at the time of checking - - this.prepared = { // action that's ready to be fired on next move event - name : null, - axis : null, - edges: null, - }; - - this.matches = []; // all selectors that are matched by target element - this.matchElements = []; // corresponding elements - - this.inertiaStatus = { - active : false, - smoothEnd : false, - - startEvent: null, - upCoords: {}, - - xe: 0, ye: 0, - sx: 0, sy: 0, - - t0: 0, - vx0: 0, vys: 0, - duration: 0, - - resumeDx: 0, - resumeDy: 0, - - lambda_v0: 0, - one_ve_v0: 0, - i : null, - }; - - this.boundInertiaFrame = () => { this.inertiaFrame(); }; - this.boundSmoothEndFrame = () => { this.smoothEndFrame(); }; - - this.activeDrops = { - dropzones: [], // the dropzones that are mentioned below - elements : [], // elements of dropzones that accept the target draggable - rects : [], // the rects of the elements mentioned above - }; - - // keep track of added pointers - this.pointers = []; - this.pointerIds = []; - this.downTargets = []; - this.downTimes = []; - this.holdTimers = []; - - // Previous native pointer move event coordinates - this.prevCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0, - }; - // current native pointer move event coordinates - this.curCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0, - }; - - // Starting InteractEvent pointer coordinates - this.startCoords = { - page : { x: 0, y: 0 }, - client : { x: 0, y: 0 }, - timeStamp: 0, - }; - - // Change in coordinates and time of the pointer - this.pointerDelta = { - page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, - timeStamp: 0, - }; - - this.downEvent = null; // pointerdown/mousedown/touchstart event - this.downPointer = {}; - - this._eventTarget = null; - this._curEventTarget = null; - - this.prevEvent = null; // previous action event - this.tapTime = 0; // time of the most recent tap event - this.prevTap = null; - - this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; - this.modifierOffsets = {}; - this.modifierStatuses = modifiers.resetStatuses({}); - - this.gesture = { - start: { x: 0, y: 0 }, - - startDistance: 0, // distance between two touches of touchStart - prevDistance : 0, - distance : 0, - - scale: 1, // gesture.distance / gesture.startDistance - - startAngle: 0, // angle of line joining two touches - prevAngle : 0, // angle of the previous gesture event - }; - - this.pointerIsDown = false; - this.pointerWasMoved = false; - this._interacting = false; - this.resizeAxes = 'xy'; - - this.mouse = false; - - scope.interactions.push(this); -} + this.matches = []; // all selectors that are matched by target element + this.matchElements = []; // corresponding elements -// Check if the current target supports the action. -// If so, return the validated action. Otherwise, return null -function validateAction (action, interactable) { - if (utils.isObject(action) && interactable.options[action.name].enabled) { - return action; - } + this.inertiaStatus = { + active : false, + smoothEnd: false, - return null; -} + startEvent: null, + upCoords : {}, -Interaction.prototype = { - setEventXY: function (targetObj, pointer) { + xe: 0, ye: 0, + sx: 0, sy: 0, + + t0: 0, + vx0: 0, vys: 0, + duration: 0, + + resumeDx: 0, + resumeDy: 0, + + lambda_v0: 0, + one_ve_v0: 0, + i : null, + }; + + this.boundInertiaFrame = () => this.inertiaFrame (); + this.boundSmoothEndFrame = () => this.smoothEndFrame(); + + this.activeDrops = { + dropzones: [], // the dropzones that are mentioned below + elements : [], // elements of dropzones that accept the target draggable + rects : [], // the rects of the elements mentioned above + }; + + // keep track of added pointers + this.pointers = []; + this.pointerIds = []; + this.downTargets = []; + this.downTimes = []; + this.holdTimers = []; + + // Previous native pointer move event coordinates + this.prevCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0, + }; + // current native pointer move event coordinates + this.curCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0, + }; + + // Starting InteractEvent pointer coordinates + this.startCoords = { + page : { x: 0, y: 0 }, + client : { x: 0, y: 0 }, + timeStamp: 0, + }; + + // Change in coordinates and time of the pointer + this.pointerDelta = { + page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 }, + timeStamp: 0, + }; + + this.downEvent = null; // pointerdown/mousedown/touchstart event + this.downPointer = {}; + + this._eventTarget = null; + this._curEventTarget = null; + + this.prevEvent = null; // previous action event + this.tapTime = 0; // time of the most recent tap event + this.prevTap = null; + + this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; + this.modifierOffsets = {}; + this.modifierStatuses = modifiers.resetStatuses({}); + + this.gesture = { + start: { x: 0, y: 0 }, + + startDistance: 0, // distance between two touches of touchStart + prevDistance : 0, + distance : 0, + + scale: 1, // gesture.distance / gesture.startDistance + + startAngle: 0, // angle of line joining two touches + prevAngle : 0, // angle of the previous gesture event + }; + + this.pointerIsDown = false; + this.pointerWasMoved = false; + this._interacting = false; + this.resizeAxes = 'xy'; + + this.mouse = false; + + scope.interactions.push(this); + } + + setEventXY (targetObj, pointer) { if (!pointer) { if (this.pointerIds.length > 1) { pointer = utils.touchAverage(this.pointers); @@ -166,9 +155,9 @@ Interaction.prototype = { targetObj.client.y = tmpXY.y; targetObj.timeStamp = new Date().getTime(); - }, + } - pointerOver: function (pointer, event, eventTarget) { + pointerOver (pointer, event, eventTarget) { if (this.prepared.name || !this.mouse) { return; } const curMatches = []; @@ -245,11 +234,11 @@ Interaction.prototype = { } } } - }, + } // Check what action would be performed on pointerMove target if a mouse // button were pressed and change the cursor accordingly - pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) { + pointerHover (pointer, event, eventTarget, curEventTarget, matches, matchElements) { const target = this.target; if (!this.prepared.name && this.mouse) { @@ -278,9 +267,9 @@ Interaction.prototype = { else if (this.prepared.name) { this.checkAndPreventDefault(event, target, this.element); } - }, + } - pointerOut: function (pointer, event, eventTarget) { + pointerOut (pointer, event, eventTarget) { if (this.prepared.name) { return; } // Remove temporary event listeners for selector Interactables @@ -293,9 +282,9 @@ Interaction.prototype = { if (this.target && this.target.options.styleCursor && !this.interacting()) { this.target._doc.documentElement.style.cursor = ''; } - }, + } - selectorDown: function (pointer, event, eventTarget, curEventTarget) { + selectorDown (pointer, event, eventTarget, curEventTarget) { const pointerIndex = this.addPointer(pointer); let element = eventTarget; let action; @@ -380,11 +369,11 @@ Interaction.prototype = { utils.copyCoords(this.prevCoords, this.curCoords); this.pointerWasMoved = false; } - }, + } // Determine action to be performed on next pointerMove and add appropriate // style and event Listeners - pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) { + pointerDown (pointer, event, eventTarget, curEventTarget, forceAction) { if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) { this.checkAndPreventDefault(event, this.target, this.element); @@ -459,9 +448,9 @@ Interaction.prototype = { this.checkAndPreventDefault(event, target, this.element); } - }, + } - setStartOffsets: function (action, interactable, element) { + setStartOffsets (action, interactable, element) { const rect = interactable.getRect(element); if (rect) { @@ -479,7 +468,7 @@ Interaction.prototype = { } modifiers.setOffsets(this, interactable, element, rect, this.modifierOffsets); - }, + } /*\ * Interaction.start @@ -513,7 +502,7 @@ Interaction.prototype = { | } | }); \*/ - start: function (action, interactable, element) { + start (action, interactable, element) { if (this.interacting() || !this.pointerIsDown || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) { @@ -538,9 +527,9 @@ Interaction.prototype = { modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); this.prevEvent = actions[this.prepared.name].start(this, this.downEvent); - }, + } - pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) { + pointerMove (pointer, event, eventTarget, curEventTarget, preEnd) { this.recordPointer(pointer); this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) @@ -628,9 +617,9 @@ Interaction.prototype = { pointer: pointer, event: event, }); - }, + } - pointerUp: function (pointer, event, eventTarget, curEventTarget) { + pointerUp (pointer, event, eventTarget, curEventTarget) { const pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -647,9 +636,9 @@ Interaction.prototype = { this.pointerEnd(pointer, event, eventTarget, curEventTarget); this.removePointer(pointer); - }, + } - pointerCancel: function (pointer, event, eventTarget, curEventTarget) { + pointerCancel (pointer, event, eventTarget, curEventTarget) { const pointerIndex = this.mouse? 0 : utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); clearTimeout(this.holdTimers[pointerIndex]); @@ -664,10 +653,10 @@ Interaction.prototype = { this.pointerEnd(pointer, event, eventTarget, curEventTarget); this.removePointer(pointer); - }, + } // End interact move events and stop auto-scroll unless inertia is enabled - pointerEnd: function (pointer, event, eventTarget, curEventTarget) { + pointerEnd (pointer, event, eventTarget, curEventTarget) { const target = this.target; const options = target && target.options; const inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia; @@ -776,17 +765,17 @@ Interaction.prototype = { } this.stop(event); - }, + } - currentAction: function () { + currentAction () { return this._interacting? this.prepared.name: null; - }, + } - interacting: function () { + interacting () { return this._interacting; - }, + } - stop: function (event) { + stop (event) { signals.fire('interaction-stop', { interaction: this }); if (this._interacting) { @@ -830,9 +819,9 @@ Interaction.prototype = { scope.interactions.splice(utils.indexOf(scope.interactions, this), 1); } } - }, + } - inertiaFrame: function () { + inertiaFrame () { const inertiaStatus = this.inertiaStatus; const options = this.target.options[this.prepared.name].inertia; const lambda = options.resistance; @@ -871,9 +860,9 @@ Interaction.prototype = { inertiaStatus.active = false; this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); } - }, + } - smoothEndFrame: function () { + smoothEndFrame () { const inertiaStatus = this.inertiaStatus; const t = new Date().getTime() - inertiaStatus.t0; const duration = this.target.options[this.prepared.name].inertia.smoothEndDuration; @@ -897,9 +886,9 @@ Interaction.prototype = { this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); } - }, + } - addPointer: function (pointer) { + addPointer (pointer) { const id = utils.getPointerId(pointer); let index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); @@ -911,9 +900,9 @@ Interaction.prototype = { this.pointers[index] = pointer; return index; - }, + } - removePointer: function (pointer) { + removePointer (pointer) { const id = utils.getPointerId(pointer); const index = this.mouse? 0 : utils.indexOf(this.pointerIds, id); @@ -927,9 +916,9 @@ Interaction.prototype = { this.downTargets.splice(index, 1); this.downTimes .splice(index, 1); this.holdTimers .splice(index, 1); - }, + } - recordPointer: function (pointer) { + recordPointer (pointer) { // Do not update pointers while inertia is active. // The inertia start event should be this.pointers[0] if (this.inertiaStatus.active) { return; } @@ -939,9 +928,9 @@ Interaction.prototype = { if (index === -1) { return; } this.pointers[index] = pointer; - }, + } - validateSelector: function (pointer, event, matches, matchElements) { + validateSelector (pointer, event, matches, matchElements) { for (let i = 0, len = matches.length; i < len; i++) { const match = matches[i]; const matchElement = matchElements[i]; @@ -954,9 +943,9 @@ Interaction.prototype = { return action; } } - }, + } - checkAndPreventDefault: function (event, interactable, element) { + checkAndPreventDefault (event, interactable, element) { if (!(interactable = interactable || this.target)) { return; } const options = interactable.options; @@ -986,9 +975,9 @@ Interaction.prototype = { event.preventDefault(); return; } - }, + } - calcInertia: function (status) { + calcInertia (status) { const inertiaOptions = this.target.options[this.prepared.name].inertia; const lambda = inertiaOptions.resistance; const inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda; @@ -1004,13 +993,23 @@ Interaction.prototype = { status.lambda_v0 = lambda / status.v0; status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0; - }, + } - _updateEventTargets: function (target, currentTarget) { + _updateEventTargets (target, currentTarget) { this._eventTarget = target; this._curEventTarget = currentTarget; - }, -}; + } +} + +// Check if the current target supports the action. +// If so, return the validated action. Otherwise, return null +function validateAction (action, interactable) { + if (utils.isObject(action) && interactable.options[action.name].enabled) { + return action; + } + + return null; +} for (let i = 0, len = methodNames.length; i < len; i++) { const method = methodNames[i]; From bcd83e85b12c943097d11c8a93f817dbc98dfd16 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 03:02:11 +0100 Subject: [PATCH 103/131] ie8: add omitted ie8.js module --- src/ie8.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/ie8.js diff --git a/src/ie8.js b/src/ie8.js new file mode 100644 index 000000000..0d9a8021e --- /dev/null +++ b/src/ie8.js @@ -0,0 +1,9 @@ +const toString = Object.prototype.toString; + +if (!window.Array.isArray) { + window.Array.isArray = function (obj) { + return toString.call(obj) === '[object Array]'; + }; +} + +module.exports = null; From fef4e8d4865ff605ad1cdc84cba5386767ec81b1 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 03:08:35 +0100 Subject: [PATCH 104/131] *: do more es6; improve formatting Also includes various minor bug fixes/improvements. --- src/InteractEvent.js | 49 ++++++++++++------------- src/Interactable.js | 27 +++++--------- src/Interaction.js | 69 +++++++++++++++++------------------ src/actions/base.js | 2 +- src/actions/drag.js | 12 +++---- src/actions/drop.js | 76 ++++++++++++++++++++------------------- src/actions/gesture.js | 28 +++++++-------- src/actions/resize.js | 38 ++++++++++---------- src/autoScroll.js | 15 ++++---- src/defaultOptions.js | 1 - src/interact.js | 8 ++--- src/modifiers/index.js | 13 +++---- src/modifiers/restrict.js | 12 ++++--- src/modifiers/snap.js | 51 +++++++++++++------------- src/pointerEvents.js | 24 ++++++------- src/scope.js | 28 +++++++-------- src/utils/browser.js | 14 ++++---- src/utils/domUtils.js | 24 +++++++------ src/utils/events.js | 57 ++++++++++++++--------------- src/utils/isType.js | 4 +-- src/utils/isWindow.js | 4 +-- src/utils/pointerUtils.js | 24 +++++++------ src/utils/raf.js | 20 +++++------ src/utils/signals.js | 4 +-- 24 files changed, 291 insertions(+), 313 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 5a828cd41..af4e42d7f 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -1,6 +1,6 @@ -const scope = require('./scope'); -const utils = require('./utils'); -const signals = require('./utils/signals'); +const scope = require('./scope'); +const utils = require('./utils'); +const signals = require('./utils/signals'); const modifiers = require('./modifiers'); class InteractEvent { @@ -15,13 +15,10 @@ class InteractEvent { const ending = phase === 'end'; const coords = starting? interaction.startCoords : interaction.curCoords; - let client; - let page; - element = element || interaction.element; - page = utils.extend({}, coords.page); - client = utils.extend({}, coords.client); + const page = utils.extend({}, coords.page); + const client = utils.extend({}, coords.client); page.x -= origin.x; page.y -= origin.y; @@ -61,19 +58,19 @@ class InteractEvent { const inertiaStatus = interaction.inertiaStatus; const signalArg = { - interactEvent: this, - interaction: interaction, - event: event, - action: action, - phase: phase, - element: element, - related: related, - page: page, - client: client, - coords: coords, - starting: starting, - ending: ending, - deltaSource: deltaSource, + interaction, + event, + action, + phase, + element, + related, + page, + client, + coords, + starting, + ending, + deltaSource, + iEvent: this, }; if (inertiaStatus.active) { @@ -183,11 +180,11 @@ class InteractEvent { const down = !up && 45 - overlap <= angle && angle < 135 + overlap; this.swipe = { - up : up, - down : down, - left : left, - right: right, - angle: angle, + up, + down, + left, + right, + angle, speed: interaction.prevEvent.speed, velocity: { x: interaction.prevEvent.velocityX, diff --git a/src/Interactable.js b/src/Interactable.js index cef4f8c0c..97770023e 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -39,9 +39,9 @@ class Interactable { this._doc = _window.document; signals.fire('interactable-new', { + element, + options, interactable: this, - element: element, - options: options, win: _window, }); @@ -401,15 +401,13 @@ class Interactable { } let listeners; - let i; - let len; const onEvent = 'on' + iEvent.type; // Interactable#on() listeners if (iEvent.type in this._iEvents) { listeners = this._iEvents[iEvent.type]; - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + for (let i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { listeners[i](iEvent); } } @@ -422,7 +420,7 @@ class Interactable { // interact.on() listeners if (iEvent.type in scope.globalEvents && (listeners = scope.globalEvents[iEvent.type])) { - for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { + for (let i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) { listeners[i](iEvent); } } @@ -442,14 +440,12 @@ class Interactable { = (object) This Interactable \*/ on (eventType, listener, useCapture) { - let i; - if (utils.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } if (utils.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { + for (let i = 0; i < eventType.length; i++) { this.on(eventType[i], listener, useCapture); } @@ -503,14 +499,12 @@ class Interactable { = (object) This Interactable \*/ off (eventType, listener, useCapture) { - let i; - if (utils.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } if (utils.isArray(eventType)) { - for (i = 0; i < eventType.length; i++) { + for (let i = 0; i < eventType.length; i++) { this.off(eventType[i], listener, useCapture); } @@ -525,8 +519,6 @@ class Interactable { return this; } - let eventList; - let index = -1; // convert to boolean useCapture = useCapture? true: false; @@ -537,9 +529,10 @@ class Interactable { // if it is an action event type if (utils.contains(scope.eventTypes, eventType)) { - eventList = this._iEvents[eventType]; + const eventList = this._iEvents[eventType]; + const index = eventList? utils.indexOf(eventList, listener) : -1; - if (eventList && (index = utils.indexOf(eventList, listener)) !== -1) { + if (index !== -1) { this._iEvents[eventType].splice(index, 1); } } @@ -648,8 +641,6 @@ class Interactable { signals.fire('interactable-unset', { interactable: this }); - this.dropzone(false); - scope.interactables.splice(utils.indexOf(scope.interactables, this), 1); return scope.interact; diff --git a/src/Interaction.js b/src/Interaction.js index cde5ebb81..ee7cd8a9b 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -1,18 +1,19 @@ -const scope = require('./scope'); -const utils = require('./utils'); +const scope = require('./scope'); +const utils = require('./utils'); +const InteractEvent = require('./InteractEvent'); +const events = require('./utils/events'); +const signals = require('./utils/signals'); +const browser = require('./utils/browser'); +const actions = require('./actions/base'); +const modifiers = require('./modifiers/'); const animationFrame = utils.raf; -const InteractEvent = require('./InteractEvent'); -const events = require('./utils/events'); -const signals = require('./utils/signals'); -const browser = require('./utils/browser'); -const actions = require('./actions/base'); -const modifiers = require('./modifiers/'); + +const listeners = {}; const methodNames = [ 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown', 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd', 'addPointer', 'removePointer', 'recordPointer', ]; -const listeners = {}; class Interaction { constructor () { @@ -292,11 +293,11 @@ class Interaction { this.pointerIsDown = true; signals.fire('interaction-down', { + pointer, + event, + eventTarget, + pointerIndex, interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget, - pointerIndex: pointerIndex, }); // Check if the down event hits the current inertia target @@ -553,13 +554,13 @@ class Interaction { } signals.fire('interaction-move', { + pointer, + event, + eventTarget, + dx, + dy, interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget, duplicate: duplicateMove, - dx: dx, - dy: dy, }); if (!this.pointerIsDown) { return; } @@ -613,9 +614,9 @@ class Interaction { utils.copyCoords(this.prevCoords, this.curCoords); signals.fire('interaction-move-done', { + pointer, + event, interaction: this, - pointer: pointer, - event: event, }); } @@ -625,11 +626,11 @@ class Interaction { clearTimeout(this.holdTimers[pointerIndex]); signals.fire('interaction-up', { + pointer, + event, + eventTarget, + curEventTarget, interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget, - curEventTarget: curEventTarget, }); @@ -644,10 +645,10 @@ class Interaction { clearTimeout(this.holdTimers[pointerIndex]); signals.fire('interaction-cancel', { + pointer, + event, + eventTarget, interaction: this, - pointer: pointer, - event: event, - eventTarget: eventTarget, }); this.pointerEnd(pointer, event, eventTarget, curEventTarget); @@ -1157,11 +1158,10 @@ function doOnInteractions (method) { }); } -signals.on('interactable-new', function (arg) { - const interactable = arg.interactable; +signals.on('interactable-new', function ({ interactable, win }) { const element = interactable._element; - if (utils.isElement(element, arg.win)) { + if (utils.isElement(element, win)) { if (scope.PointerEvent) { events.add(element, browser.pEventTypes.down, listeners.pointerDown ); events.add(element, browser.pEventTypes.move, listeners.pointerHover); @@ -1175,11 +1175,10 @@ signals.on('interactable-new', function (arg) { } }); -signals.on('interactable-unset', function (arg) { - const interactable = arg.interactable; +signals.on('interactable-unset', function ({ interactable, win }) { const element = interactable._element; - if (!interactable.selector && utils.isElement(element, arg.win)) { + if (!interactable.selector && utils.isElement(element, win)) { if (scope.PointerEvent) { events.remove(element, browser.pEventTypes.down, listeners.pointerDown ); events.remove(element, browser.pEventTypes.move, listeners.pointerHover); @@ -1193,9 +1192,7 @@ signals.on('interactable-unset', function (arg) { } }); -signals.on('listen-to-document', function (arg) { - const doc = arg.doc; - const win = arg.win; +signals.on('listen-to-document', function ({ doc, win }) { const pEventTypes = browser.pEventTypes; // add delegate event listener diff --git a/src/actions/base.js b/src/actions/base.js index 771f4589d..56c172327 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -1,7 +1,7 @@ const scope = require('../scope'); const actions = { - scope: scope, + scope, defaultChecker: function (pointer, event, interaction, element) { const rect = this.getRect(element); diff --git a/src/actions/drag.js b/src/actions/drag.js index 9a355c9d3..4edeca215 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -9,14 +9,14 @@ const defaultOptions = require('../defaultOptions'); const drag = { defaults: { - enabled: false, - manualStart: true, - max: Infinity, + enabled : false, + manualStart : true, + max : Infinity, maxPerElement: 1, - snap: null, - restrict: null, - inertia: null, + snap : null, + restrict : null, + inertia : null, autoScroll: null, axis: 'xy', diff --git a/src/actions/drop.js b/src/actions/drop.js index 838bf6437..a83a72ded 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -8,7 +8,7 @@ const defaultOptions = require('../defaultOptions'); const drop = { defaults: { enabled: false, - accept: null, + accept : null, overlap: 'pointer', }, @@ -28,12 +28,13 @@ const drop = { fireActiveDrops(interaction, dropEvents.activate); } }, + move: function (interaction, event, dragEvent) { const draggableElement = interaction.element; - const drop = getDrop(interaction, event, draggableElement); + const dropOptions = getDrop(interaction, event, draggableElement); - interaction.dropTarget = drop.dropzone; - interaction.dropElement = drop.element; + interaction.dropTarget = dropOptions.dropzone; + interaction.dropElement = dropOptions.element; const dropEvents = getDropEvents(interaction, event, dragEvent); @@ -45,14 +46,14 @@ const drop = { interaction.prevDropTarget = interaction.dropTarget; interaction.prevDropElement = interaction.dropElement; - }, + end: function (interaction, event, endEvent) { const draggableElement = interaction.element; - const drop = getDrop(interaction, event, draggableElement); + const dropResult = getDrop(interaction, event, draggableElement); - interaction.dropTarget = drop.dropzone; - interaction.dropElement = drop.element; + interaction.dropTarget = dropResult.dropzone; + interaction.dropElement = dropResult.element; const dropEvents = getDropEvents(interaction, event, endEvent); @@ -62,8 +63,8 @@ const drop = { if (dropEvents.deactivate) { fireActiveDrops(interaction, dropEvents.deactivate); } - }, + stop: function (interaction) { interaction.activeDrops.dropzones = interaction.activeDrops.elements = @@ -92,7 +93,9 @@ function collectDrops (interaction, element) { } // query for new elements if necessary - const dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element]; + const dropElements = current.selector + ? current._context.querySelectorAll(current.selector) + : [current._element]; for (let i = 0; i < dropElements.length; i++) { const currentElement = dropElements[i]; @@ -105,21 +108,18 @@ function collectDrops (interaction, element) { } return { + elements, dropzones: drops, - elements: elements, }; } function fireActiveDrops (interaction, event) { - let i; - let current; - let currentElement; let prevElement; // loop through all active dropzones and trigger event - for (i = 0; i < interaction.activeDrops.dropzones.length; i++) { - current = interaction.activeDrops.dropzones[i]; - currentElement = interaction.activeDrops.elements [i]; + for (let i = 0; i < interaction.activeDrops.dropzones.length; i++) { + const current = interaction.activeDrops.dropzones[i]; + const currentElement = interaction.activeDrops.elements [i]; // prevent trigger of duplicate events on same element if (currentElement !== prevElement) { @@ -143,7 +143,8 @@ function setActiveDrops (interaction, dragElement) { interaction.activeDrops.rects = []; for (let i = 0; i < interaction.activeDrops.dropzones.length; i++) { - interaction.activeDrops.rects[i] = interaction.activeDrops.dropzones[i].getRect(interaction.activeDrops.elements[i]); + interaction.activeDrops.rects[i] = + interaction.activeDrops.dropzones[i].getRect(interaction.activeDrops.elements[i]); } } @@ -167,10 +168,11 @@ function getDrop (interaction, event, dragElement) { // get the most appropriate dropzone based on DOM depth and order const dropIndex = utils.indexOfDeepestElement(validDrops); - const dropzone = interaction.activeDrops.dropzones[dropIndex] || null; - const element = interaction.activeDrops.elements [dropIndex] || null; - return { dropzone, element }; + return { + dropzone: interaction.activeDrops.dropzones[dropIndex] || null, + element : interaction.activeDrops.elements [dropIndex] || null, + }; } function getDropEvents (interaction, pointerEvent, dragEvent) { @@ -187,28 +189,28 @@ function getDropEvents (interaction, pointerEvent, dragEvent) { // if there was a prevDropTarget, create a dragleave event if (interaction.prevDropTarget) { dropEvents.leave = { + dragEvent, + interaction, target : interaction.prevDropElement, dropzone : interaction.prevDropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dragleave', }; - dragEvent.dragLeave = interaction.prevDropElement; + dragEvent.dragLeave = interaction.prevDropElement; dragEvent.prevDropzone = interaction.prevDropTarget; } // if the dropTarget is not null, create a dragenter event if (interaction.dropTarget) { dropEvents.enter = { + dragEvent, + interaction, target : interaction.dropElement, dropzone : interaction.dropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dragenter', }; @@ -220,12 +222,12 @@ function getDropEvents (interaction, pointerEvent, dragEvent) { if (dragEvent.type === 'dragend' && interaction.dropTarget) { dropEvents.drop = { + dragEvent, + interaction, target : interaction.dropElement, dropzone : interaction.dropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'drop', }; @@ -234,36 +236,36 @@ function getDropEvents (interaction, pointerEvent, dragEvent) { } if (dragEvent.type === 'dragstart') { dropEvents.activate = { + dragEvent, + interaction, target : null, dropzone : null, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dropactivate', }; } if (dragEvent.type === 'dragend') { dropEvents.deactivate = { + dragEvent, + interaction, target : null, dropzone : null, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, timeStamp : dragEvent.timeStamp, type : 'dropdeactivate', }; } if (dragEvent.type === 'dragmove' && interaction.dropTarget) { dropEvents.move = { + dragEvent, + interaction, target : interaction.dropElement, dropzone : interaction.dropTarget, relatedTarget: dragEvent.target, draggable : dragEvent.interactable, - dragEvent : dragEvent, - interaction : interaction, dragmove : dragEvent, timeStamp : dragEvent.timeStamp, type : 'dropmove', @@ -482,9 +484,11 @@ Interactable.prototype.accept = utils.warnOnce(function (newValue) { return this.options.drop.accept; }, 'Interactable#accept is deprecated. use Interactable#dropzone({ accept: target }) instead'); -signals.on('interaction-stop', function (arg) { - const interaction = arg.interaction; +signals.on('interactable-unset', function ({ interactable }) { + interactable.dropzone(false); +}); +signals.on('interaction-stop', function ({ interaction }) { interaction.dropTarget = interaction.dropElement = interaction.prevDropTarget = interaction.prevDropElement = null; }); diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 1525f0971..9e03a756f 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -8,9 +8,9 @@ const defaultOptions = require('../defaultOptions'); const gesture = { defaults: { - manualStart: false, - enabled: false, - max: Infinity, + manualStart : false, + enabled : false, + max : Infinity, maxPerElement: 1, restrict: null, @@ -61,10 +61,10 @@ const gesture = { interaction.gesture.prevAngle = gestureEvent.angle; interaction.gesture.prevDistance = gestureEvent.distance; - if (gestureEvent.scale !== Infinity && - gestureEvent.scale !== null && - gestureEvent.scale !== undefined && - !isNaN(gestureEvent.scale)) { + if (gestureEvent.scale !== Infinity + && gestureEvent.scale !== null + && gestureEvent.scale !== undefined + && !isNaN(gestureEvent.scale)) { interaction.gesture.scale = gestureEvent.scale; } @@ -125,20 +125,20 @@ Interactable.prototype.gesturable = function (options) { signals.on('interactevent-delta', function (arg) { if (arg.action !== 'gesture') { return; } - const {interaction, iEvent} = {arg}; + const { interaction, iEvent, starting, ending, deltaSource } = {arg}; const pointers = interaction.pointers; iEvent.touches = [pointers[0], pointers[1]]; - if (arg.starting) { - iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); + if (starting) { + iEvent.distance = utils.touchDistance(pointers, deltaSource); iEvent.box = utils.touchBBox(pointers); iEvent.scale = 1; iEvent.ds = 0; - iEvent.angle = utils.touchAngle(pointers, undefined, arg.deltaSource); + iEvent.angle = utils.touchAngle(pointers, undefined, deltaSource); iEvent.da = 0; } - else if (arg.ending || event instanceof InteractEvent) { + else if (ending || event instanceof InteractEvent) { iEvent.distance = interaction.prevEvent.distance; iEvent.box = interaction.prevEvent.box; iEvent.scale = interaction.prevEvent.scale; @@ -147,10 +147,10 @@ signals.on('interactevent-delta', function (arg) { iEvent.da = iEvent.angle - interaction.gesture.startAngle; } else { - iEvent.distance = utils.touchDistance(pointers, arg.deltaSource); + iEvent.distance = utils.touchDistance(pointers, deltaSource); iEvent.box = utils.touchBBox(pointers); iEvent.scale = iEvent.distance / interaction.gesture.startDistance; - iEvent.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, arg.deltaSource); + iEvent.angle = utils.touchAngle(pointers, interaction.gesture.prevAngle, deltaSource); iEvent.ds = iEvent.scale - interaction.gesture.prevScale; iEvent.da = iEvent.angle - interaction.gesture.prevAngle; diff --git a/src/actions/resize.js b/src/actions/resize.js index e05e59257..1c835f5df 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -9,18 +9,18 @@ const defaultOptions = require('../defaultOptions'); const resize = { defaults: { - enabled: false, - manualStart: false, - max: Infinity, + enabled : false, + manualStart : false, + max : Infinity, maxPerElement: 1, - snap: null, - restrict: null, - inertia: null, + snap : null, + restrict : null, + inertia : null, autoScroll: null, square: false, - axis: 'xy', + axis : 'xy', // use default margin margin: NaN, @@ -53,11 +53,11 @@ const resize = { for (const edge in resizeEdges) { resizeEdges[edge] = checkResizeEdge(edge, resizeOptions.edges[edge], - page, - interaction._eventTarget, - element, - rect, - resizeOptions.margin || scope.margin); + page, + interaction._eventTarget, + element, + rect, + resizeOptions.margin || scope.margin); } resizeEdges.left = resizeEdges.left && !resizeEdges.right; @@ -338,8 +338,8 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, // true value, use pointer coords and element rect if (value === true) { // if dimensions are negative, "switch" edges - const width = utils.isNumber(rect.width)? rect.width : rect.right - rect.left; - const height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top; + const width = utils.isNumber(rect.width )? rect.width : rect.right - rect.left; + const height = utils.isNumber(rect.height)? rect.height : rect.bottom - rect.top ; if (width < 0) { if (name === 'left' ) { name = 'right'; } @@ -364,15 +364,13 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, // the value is an element to use as a resize handle ? value === element // otherwise check if element matches value as selector - : utils.matchesUpTo(element, value, interactableElement); + : utils.matchesUpTo(element, value, interactableElement); } -signals.on('interact-event-set-delta', function (arg) { - const interaction = arg.interaction; - const iEvent = arg.interactEvent; - const options = interaction.target.options; +signals.on('interactevent-set-delta', function ({ interaction, iEvent, action }) { + if (action !== 'resize' || !interaction.resizeAxes) { return; } - if (arg.action !== 'resize' || !interaction.resizeAxes) { return; } + const options = interaction.target.options; if (options.resize.square) { if (interaction.resizeAxes === 'y') { diff --git a/src/autoScroll.js b/src/autoScroll.js index 55a1168be..3fa2077ce 100644 --- a/src/autoScroll.js +++ b/src/autoScroll.js @@ -7,10 +7,10 @@ const defaultOptions = require('./defaultOptions'); const autoScroll = { defaults: { - enabled : false, - container : null, // the item that is scrolled (Window or HTMLElement) - margin : 60, - speed : 300, // the scroll speed in pixels per second + enabled : false, + container: null, // the item that is scrolled (Window or HTMLElement) + margin : 60, + speed : 300, // the scroll speed in pixels per second }, interaction: null, @@ -64,12 +64,9 @@ const autoScroll = { check: function (interactable, actionName) { const options = interactable.options; - return options[actionName].autoScroll && options[actionName].autoScroll.enabled; + return options[actionName].autoScroll && options[actionName].autoScroll.enabled; }, - onInteractionMove: function (arg) { - const interaction = arg.interaction; - const pointer = arg.pointer; - + onInteractionMove: function ({ interaction, pointer }) { if (!(interaction.interacting() && autoScroll.check(interaction.target, interaction.prepared.name))) { return; diff --git a/src/defaultOptions.js b/src/defaultOptions.js index 75bcd9aa5..1ef145b4f 100644 --- a/src/defaultOptions.js +++ b/src/defaultOptions.js @@ -8,7 +8,6 @@ module.exports = { deltaSource : 'page', allowFrom : null, ignoreFrom : null, - _context : require('./utils/domObjects').document, checker : null, }, diff --git a/src/interact.js b/src/interact.js index 6938c7d42..61553ceb6 100644 --- a/src/interact.js +++ b/src/interact.js @@ -6,10 +6,10 @@ * https://raw.github.com/taye/interact.js/master/LICENSE */ -const scope = require('./scope'); -const utils = require('./utils'); -const browser = utils.browser; -const events = require('./utils/events'); +const scope = require('./scope'); +const utils = require('./utils'); +const browser = utils.browser; +const events = require('./utils/events'); const Interactable = require('./Interactable'); scope.dynamicDrop = false; diff --git a/src/modifiers/index.js b/src/modifiers/index.js index 840e4951d..170b50b24 100644 --- a/src/modifiers/index.js +++ b/src/modifiers/index.js @@ -14,7 +14,7 @@ const modifiers = { } }, - setAll: function (interaction, coords, statuses, preEnd, requireEndOnly) { + setAll: function (interaction, coordsArg, statuses, preEnd, requireEndOnly) { const result = { dx: 0, dy: 0, @@ -23,12 +23,11 @@ const modifiers = { shouldMove: true, }; const target = interaction.target; - let currentStatus; + const coords = utils.extend({}, coordsArg); - coords = utils.extend({}, coords); + let currentStatus; - for (let i = 0; i < modifiers.names.length; i++) { - const modifierName = modifiers.names[i]; + for (const modifierName of modifiers.names) { const modifier = modifiers[modifierName]; if (!modifier.shouldDo(target, interaction.prepared.name, preEnd, requireEndOnly)) { continue; } @@ -54,9 +53,7 @@ const modifiers = { }, resetStatuses: function (statuses) { - for (let i = 0; i < modifiers.names.length; i++) { - const modifierName = modifiers.names[i]; - + for (const modifierName of modifiers.names) { statuses[modifierName] = modifiers[modifierName].reset(statuses[modifierName] || {}); } diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index ea78849b7..996aef9ab 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -11,9 +11,11 @@ const restrict = { }, shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { - const restrict = interactable.options[actionName].restrict; + const restrictOptions = interactable.options[actionName].restrict; - return restrict && restrict.enabled && (preEnd || !restrict.endOnly) && (!requireEndOnly || restrict.endOnly); + return (restrictOptions && restrictOptions.enabled + && (preEnd || !restrictOptions.endOnly) + && (!requireEndOnly || restrictOptions.endOnly)); }, setOffset: function (interaction, interactable, element, rect, startOffset) { @@ -35,9 +37,9 @@ const restrict = { }, set: function (pageCoords, interaction, status) { - const target = interaction.target; - const restrict = target && target.options[interaction.prepared.name].restrict; - let restriction = restrict && restrict.restriction; + const target = interaction.target; + const restrictOptions = target && target.options[interaction.prepared.name].restrict; + let restriction = restrictOptions && restrictOptions.restriction; if (!restriction) { return status; diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index 33294f4ea..a7acfdde5 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -15,26 +15,29 @@ const snap = { }, shouldDo: function (interactable, actionName, preEnd, requireEndOnly) { - const snap = interactable.options[actionName].snap; + const snapOptions = interactable.options[actionName].snap; - return snap && snap.enabled && (preEnd || !snap.endOnly) && (!requireEndOnly || snap.endOnly); + return (snapOptions && snapOptions.enabled + && (preEnd || !snapOptions.endOnly) + && (!requireEndOnly || snapOptions.endOnly)); }, setOffset: function (interaction, interactable, element, rect, startOffset) { const offsets = []; const origin = utils.getOriginXY(interactable, element); - const snapOffset = (snap && snap.offset === 'startCoords' + const snapOptions = interactable.options[interaction.prepared.name].snap; + const snapOffset = (snapOptions && snapOptions.offset === 'startCoords' ? { x: interaction.startCoords.page.x - origin.x, y: interaction.startCoords.page.y - origin.y, } - : snap && snap.offset || { x: 0, y: 0 }); + : snapOptions && snapOptions.offset || { x: 0, y: 0 }); - if (rect && snap && snap.relativePoints && snap.relativePoints.length) { - for (let i = 0; i < snap.relativePoints.length; i++) { + if (rect && snapOptions && snapOptions.relativePoints && snapOptions.relativePoints.length) { + for (const { x: relativeX, y: relativeY } of snapOptions.relativePoints) { offsets.push({ - x: startOffset.left - (rect.width * snap.relativePoints[i].x) + snapOffset.x, - y: startOffset.top - (rect.height * snap.relativePoints[i].y) + snapOffset.y, + x: startOffset.left - (rect.width * relativeX) + snapOffset.x, + y: startOffset.top - (rect.height * relativeY) + snapOffset.y, }); } } @@ -46,7 +49,7 @@ const snap = { }, set: function (pageCoords, interaction, status) { - const snap = interaction.target.options[interaction.prepared.name].snap; + const snapOptions = interaction.target.options[interaction.prepared.name].snap; const targets = []; let target; let page; @@ -71,29 +74,27 @@ const snap = { page.y -= interaction.inertiaStatus.resumeDy; const offsets = interaction.modifierOffsets.snap; - let len = snap.targets? snap.targets.length : 0; + let len = snapOptions.targets? snapOptions.targets.length : 0; - for (let relIndex = 0; relIndex < offsets.length; relIndex++) { - const relative = { - x: page.x - offsets[relIndex].x, - y: page.y - offsets[relIndex].y, - }; + for (const { x: offsetX, y: offsetY } of offsets) { + const relativeX = page.x - offsetX; + const relativeY = page.y - offsetY; - for (i = 0; i < len; i++) { - if (utils.isFunction(snap.targets[i])) { - target = snap.targets[i](relative.x, relative.y, interaction); + for (const snapTarget of snapOptions.targets) { + if (utils.isFunction(snapTarget)) { + target = snapTarget(relativeX, relativeY, interaction); } else { - target = snap.targets[i]; + target = snapTarget; } if (!target) { continue; } targets.push({ - x: utils.isNumber(target.x) ? (target.x + offsets[relIndex].x) : relative.x, - y: utils.isNumber(target.y) ? (target.y + offsets[relIndex].y) : relative.y, + x: utils.isNumber(target.x) ? (target.x + offsetX) : relativeX, + y: utils.isNumber(target.y) ? (target.y + offsetY) : relativeY, - range: utils.isNumber(target.range)? target.range: snap.range, + range: utils.isNumber(target.range)? target.range: snapOptions.range, }); } } @@ -179,10 +180,10 @@ const snap = { }, modifyCoords: function (page, client, interactable, status, actionName, phase) { - const snap = interactable.options[actionName].snap; - const relativePoints = snap && snap.relativePoints; + const snapOptions = interactable.options[actionName].snap; + const relativePoints = snapOptions && snapOptions.relativePoints; - if (snap && snap.enabled + if (snapOptions && snapOptions.enabled && !(phase === 'start' && relativePoints && relativePoints.length)) { if (status.locked) { diff --git a/src/pointerEvents.js b/src/pointerEvents.js index 51871ac25..d8465c3ef 100644 --- a/src/pointerEvents.js +++ b/src/pointerEvents.js @@ -149,32 +149,30 @@ function collectEventTargets (interaction, pointer, event, eventTarget, eventTyp } } -signals.on('interaction-move', function (arg) { - const interaction = arg.interaction; +signals.on('interaction-move', function ({ interaction, pointer, event, eventTarget, duplicateMove }) { const pointerIndex = (interaction.mouse ? 0 - : utils.indexOf(interaction.pointerIds, utils.getPointerId(arg.pointer))); + : utils.indexOf(interaction.pointerIds, utils.getPointerId(pointer))); - if (!arg.duplicateMove && (!interaction.pointerIsDown || interaction.pointerWasMoved)) { + if (!duplicateMove && (!interaction.pointerIsDown || interaction.pointerWasMoved)) { if (interaction.pointerIsDown) { clearTimeout(interaction.holdTimers[pointerIndex]); } - collectEventTargets(interaction, arg.pointer, arg.event, arg.eventTarget, 'move'); + collectEventTargets(interaction, pointer, event, eventTarget, 'move'); } }); -signals.on('interaction-down', function (arg) { - const interaction = arg.interaction; +signals.on('interaction-down', function ({ interaction, pointer, event, eventTarget, pointerIndex }) { // copy event to be used in timeout for IE8 - const eventCopy = browser.isIE8? utils.extend({}, arg.event) : arg.event; + const eventCopy = browser.isIE8? utils.extend({}, event) : event; - interaction.holdTimers[arg.pointerIndex] = setTimeout(function () { + interaction.holdTimers[pointerIndex] = setTimeout(function () { collectEventTargets(interaction, - browser.isIE8? eventCopy : arg.pointer, + browser.isIE8? eventCopy : pointer, eventCopy, - arg.eventTarget, + eventTarget, 'hold'); }, scope.defaultOptions._holdDuration); @@ -218,8 +216,8 @@ if (browser.ie8) { } }; - signals.on('listen-to-document', function (arg) { - events.add(arg.doc, 'dblclick', onIE8Dblclick); + signals.on('listen-to-document', function ({ doc }) { + events.add(doc, 'dblclick', onIE8Dblclick); }); } diff --git a/src/scope.js b/src/scope.js index 95fe903f6..e774ce262 100644 --- a/src/scope.js +++ b/src/scope.js @@ -1,22 +1,18 @@ -const scope = {}; -const utils = require('./utils'); +const scope = {}; +const utils = require('./utils'); const signals = require('./utils/signals'); -const extend = utils.extend; - -scope.documents = []; // all documents being listened to - -scope.interactables = []; // all set interactables -scope.interactions = []; // all interactions scope.defaultOptions = require('./defaultOptions'); +scope.events = require('./utils/events'); +scope.signals = require('./utils/signals'); -scope.events = require('./utils/events'); -scope.signals = require('./utils/signals'); - -extend(scope, require('./utils/window')); -extend(scope, require('./utils/domObjects')); +utils.extend(scope, require('./utils/window')); +utils.extend(scope, require('./utils/domObjects')); -scope.eventTypes = []; +scope.documents = []; // all documents being listened to +scope.interactables = []; // all set interactables +scope.interactions = []; // all interactions +scope.eventTypes = []; // all event types specific to interact.js scope.withinInteractionLimit = function (interactable, element, action) { const options = interactable.options; @@ -64,9 +60,9 @@ scope.endAllInteractions = function (event) { } }; -signals.on('listen-to-document', function (arg) { +signals.on('listen-to-document', function ({ doc }) { // if document is already known - if (utils.contains(scope.documents, arg.doc)) { + if (utils.contains(scope.documents, doc)) { // don't call any further signal listeners return false; } diff --git a/src/utils/browser.js b/src/utils/browser.js index 184200934..01dbeaf16 100644 --- a/src/utils/browser.js +++ b/src/utils/browser.js @@ -1,5 +1,5 @@ -const win = require('./window'); -const isType = require('./isType'); +const win = require('./window'); +const isType = require('./isType'); const domObjects = require('./domObjects'); const browser = { @@ -34,13 +34,11 @@ const browser = { pEventTypes: (domObjects.PointerEvent ? (domObjects.PointerEvent === win.window.MSPointerEvent - ? { - up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', - out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' } - : { - up: 'pointerup', down: 'pointerdown', over: 'pointerover', + ? { up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover', + out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' } + : { up: 'pointerup', down: 'pointerdown', over: 'pointerover', out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' }) - : null), + : null), }; browser.useMatchesSelectorPolyfill = !isType.isFunction(Element.prototype[browser.prefixedMatchesSelector]); diff --git a/src/utils/domUtils.js b/src/utils/domUtils.js index b105e62c1..958c715dc 100644 --- a/src/utils/domUtils.js +++ b/src/utils/domUtils.js @@ -1,6 +1,6 @@ -const win = require('./window'); -const browser = require('./browser'); -const isType = require('./isType'); +const win = require('./window'); +const browser = require('./browser'); +const isType = require('./isType'); const domObjects = require('./domObjects'); const domUtils = { @@ -44,17 +44,19 @@ const domUtils = { }, // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified - matchesSelectorPolyfill: (browser.useMatchesSelectorPolyfill? function (element, selector, elems) { - elems = elems || element.parentNode.querySelectorAll(selector); + matchesSelectorPolyfill: browser.useMatchesSelectorPolyfill + ? function (element, selector, elems) { + elems = elems || element.parentNode.querySelectorAll(selector); - for (let i = 0, len = elems.length; i < len; i++) { - if (elems[i] === element) { - return true; + for (let i = 0, len = elems.length; i < len; i++) { + if (elems[i] === element) { + return true; + } } - } - return false; - } : null), + return false; + } + : null, matchesSelector: function (element, selector, nodeList) { if (browser.useMatchesSelectorPolyfill) { diff --git a/src/utils/events.js b/src/utils/events.js index 1daf78642..3fe3a9e98 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -54,16 +54,16 @@ function add (element, type, listener, useCapture) { let ret; if (useAttachEvent) { - const listeners = attachedListeners[elementIndex]; - const listenerIndex = indexOf(listeners.supplied, listener); + const { supplied, wrapped, useCount } = [ attachedListeners[elementIndex] ]; + const listenerIndex = indexOf(supplied, listener); - const wrapped = listeners.wrapped[listenerIndex] || function (event) { + const wrappedListener = wrapped[listenerIndex] || function (event) { if (!event.immediatePropagationStopped) { event.target = event.srcElement; event.currentTarget = element; - event.preventDefault = event.preventDefault || preventDef; - event.stopPropagation = event.stopPropagation || stopProp; + event.preventDefault = event.preventDefault || preventDef; + event.stopPropagation = event.stopPropagation || stopProp; event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp; if (/mouse|click/.test(event.type)) { @@ -75,15 +75,15 @@ function add (element, type, listener, useCapture) { } }; - ret = element[addEvent](on + type, wrapped, !!useCapture); + ret = element[addEvent](on + type, wrappedListener, !!useCapture); if (listenerIndex === -1) { - listeners.supplied.push(listener); - listeners.wrapped.push(wrapped); - listeners.useCount.push(1); + supplied.push(listener); + wrapped.push(wrappedListener); + useCount.push(1); } else { - listeners.useCount[listenerIndex]++; + useCount[listenerIndex]++; } } else { @@ -98,19 +98,19 @@ function add (element, type, listener, useCapture) { function remove (element, type, listener, useCapture) { const elementIndex = indexOf(elements, element); const target = targets[elementIndex]; - let wrapped = listener; - let listeners; - let listenerIndex; - let i; if (!target || !target.events) { return; } + let wrappedListener = listener; + let listeners; + let listenerIndex; + if (useAttachEvent) { listeners = attachedListeners[elementIndex]; listenerIndex = indexOf(listeners.supplied, listener); - wrapped = listeners.wrapped[listenerIndex]; + wrappedListener = listeners.wrapped[listenerIndex]; } if (type === 'all') { @@ -126,14 +126,15 @@ function remove (element, type, listener, useCapture) { const len = target.events[type].length; if (listener === 'all') { - for (i = 0; i < len; i++) { + for (let i = 0; i < len; i++) { remove(element, type, target.events[type][i], !!useCapture); } return; - } else { - for (i = 0; i < len; i++) { + } + else { + for (let i = 0; i < len; i++) { if (target.events[type][i] === listener) { - element[removeEvent](on + type, wrapped, !!useCapture); + element[removeEvent](on + type, wrappedListener, !!useCapture); target.events[type].splice(i, 1); if (useAttachEvent && listeners) { @@ -321,18 +322,18 @@ function stopImmProp () { } module.exports = { - add: add, - remove: remove, + add, + remove, - addDelegate: addDelegate, - removeDelegate: removeDelegate, + addDelegate, + removeDelegate, - delegateListener: delegateListener, - delegateUseCapture: delegateUseCapture, - delegatedEvents: delegatedEvents, - documents: documents, + delegateListener, + delegateUseCapture, + delegatedEvents, + documents, - useAttachEvent: useAttachEvent, + useAttachEvent, _elements: elements, _targets: targets, diff --git a/src/utils/isType.js b/src/utils/isType.js index ce2f80387..7b43f7a0d 100644 --- a/src/utils/isType.js +++ b/src/utils/isType.js @@ -1,8 +1,8 @@ -const win = require('./window'); +const win = require('./window'); const domObjects = require('./domObjects'); const isType = { - isElement : function (o) { + isElement : function (o) { if (!o || (typeof o !== 'object')) { return false; } const _window = win.getWindow(o) || win.window; diff --git a/src/utils/isWindow.js b/src/utils/isWindow.js index 4ade6a81a..09b23006b 100644 --- a/src/utils/isWindow.js +++ b/src/utils/isWindow.js @@ -1,3 +1 @@ -module.exports = function isWindow (thing) { - return !!(thing && thing.Window) && (thing instanceof thing.Window); -}; +module.exports = (thing) => !!(thing && thing.Window) && (thing instanceof thing.Window); diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 9e0665b22..9610f0dff 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -1,10 +1,10 @@ const pointerUtils = {}; - // reduce object creation in getXY() -const win = require('./window'); -const hypot = require('./hypot'); -const extend = require('./extend'); -const browser = require('./browser'); -const isType = require('./isType'); + +const win = require('./window'); +const hypot = require('./hypot'); +const extend = require('./extend'); +const browser = require('./browser'); +const isType = require('./isType'); const InteractEvent = require('../InteractEvent'); pointerUtils.copyCoords = function (dest, src) { @@ -20,11 +20,13 @@ pointerUtils.copyCoords = function (dest, src) { }; pointerUtils.setEventDeltas = function (targetObj, prev, cur) { - targetObj.page.x = cur.page.x - prev.page.x; - targetObj.page.y = cur.page.y - prev.page.y; - targetObj.client.x = cur.client.x - prev.client.x; - targetObj.client.y = cur.client.y - prev.client.y; - targetObj.timeStamp = new Date().getTime() - prev.timeStamp; + const now = new Date().getTime(); + + targetObj.page.x = cur.page.x - prev.page.x; + targetObj.page.y = cur.page.y - prev.page.y; + targetObj.client.x = cur.client.x - prev.client.x; + targetObj.client.y = cur.client.y - prev.client.y; + targetObj.timeStamp = now - prev.timeStamp; // set pointer velocity const dt = Math.max(targetObj.timeStamp / 1000, 0.001); diff --git a/src/utils/raf.js b/src/utils/raf.js index 096ef9840..e3fc17cda 100644 --- a/src/utils/raf.js +++ b/src/utils/raf.js @@ -1,15 +1,15 @@ const vendors = ['ms', 'moz', 'webkit', 'o']; let lastTime = 0; -let reqFrame; -let cancelFrame; +let request; +let cancel; for (let x = 0; x < vendors.length && !window.requestAnimationFrame; x++) { - reqFrame = window[vendors[x] + 'RequestAnimationFrame']; - cancelFrame = window[vendors[x] +'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; + request = window[vendors[x] + 'RequestAnimationFrame']; + cancel = window[vendors[x] +'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame']; } -if (!reqFrame) { - reqFrame = function (callback) { +if (!request) { + request = function (callback) { const currTime = new Date().getTime(); const timeToCall = Math.max(0, 16 - (currTime - lastTime)); const id = setTimeout(function () { callback(currTime + timeToCall); }, @@ -20,13 +20,13 @@ if (!reqFrame) { }; } -if (!cancelFrame) { - cancelFrame = function (id) { +if (!cancel) { + cancel = function (id) { clearTimeout(id); }; } module.exports = { - request: reqFrame, - cancel: cancelFrame, + request, + cancel, }; diff --git a/src/utils/signals.js b/src/utils/signals.js index 17cbe5d72..82a2c075e 100644 --- a/src/utils/signals.js +++ b/src/utils/signals.js @@ -1,9 +1,9 @@ +const arr = require('./arr'); + const listeners = { // signalName: [listeners], }; -const arr = require('./arr'); - const signals = { on: function (name, listener) { if (!listeners[name]) { From 7738216ab12b2ec393f0753f0b7d8c003c8ffee3 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 04:38:22 +0100 Subject: [PATCH 105/131] Improve babel and eslint config files --- .babelrc | 3 --- .eslintrc | 10 ++-------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/.babelrc b/.babelrc index a2162ee4f..ab272ee27 100644 --- a/.babelrc +++ b/.babelrc @@ -1,5 +1,4 @@ { - "stage": 1, "loose": ["all"], "whitelist": [ "es3.memberExpressionLiterals", @@ -12,10 +11,8 @@ "es6.forOf", "es6.literals", "es6.objectSuper", - "es6.parameters", "es6.properties.computed", "es6.properties.shorthand", - "es6.spread", "es6.tailCall", "es6.templateLiterals" ] diff --git a/.eslintrc b/.eslintrc index e0e6f6891..b9d63f5a0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,8 +4,7 @@ extends: 'eslint:recommended' env: browser: true - es6: true - node: true + commonjs: true rules: comma-dangle: [2, always-multiline] @@ -21,6 +20,7 @@ rules: no-extra-bind: 2 no-self-compare: 2 no-sequences: 2 + no-shadow: 2 no-shadow-restricted-names: 2 no-trailing-spaces: 2 no-unused-expressions: 2 @@ -32,21 +32,15 @@ rules: space-after-keywords: [2, always] space-before-function-paren: [2, always] strict: [2, never] - use-isnan: 2 ecma-features: arrow-functions: true block-bindings: true classes: true for-of: true - default-params: true destructuring: true - generators: false object-literal-computed-properties: true - object-literal-duplicate-properties: false object-literal-shorthand-methods: true oobject-literal-shorthand-properties: true - spread: true super-in-functions: true template-strings: true - jsx: false From a7d69d017454d889c815dd78a323ba015b17202d Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 05:08:50 +0100 Subject: [PATCH 106/131] codeclimate.yml: use eslint engine --- .codeclimate.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.codeclimate.yml b/.codeclimate.yml index 624c15216..0735a95d9 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,5 +1,10 @@ -languages: - JavaScript: true +engines: + eslint: + enabled: true + +ratings: + paths: + - "src/**" exclude_paths: - "demo/*" From c33ddfaa8f4ed5438ebce60d420f64d940bd5f05 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 17:50:10 +0100 Subject: [PATCH 107/131] gulp/*: fix source map generation 1. Always use browserify with `debug: true` to generate inline sourcemaps 2. Use exorcist to extract the sourcemap to itneract.js.map 3. Use gulp-souremap to load the original sourcemap and use it when making a souremap for the uglify output --- gulp/config.js | 24 ++++++++++++++---------- gulp/tasks/browserify.js | 11 ++++++++--- package.json | 1 + 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/gulp/config.js b/gulp/config.js index 3af39bbc0..583f421ee 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -16,16 +16,20 @@ module.exports = { // A separate bundle will be generated for each // bundle config in the list below bundleConfigs: [ - { - entries: src + '/index.js', - dest: dest, - outputName: 'interact.js', - outputNameMin: 'interact.min.js', - standalone: 'interact', - // Additional file extentions to make optional - extensions: [] - } - ] + { + entries: src + '/index.js', + dest: dest, + debug: true, + outputName: 'interact.js', + outputNameMin: 'interact.min.js', + souremapComment: true, + + standalone: 'interact', + transform: [[ 'babelify', {} ]], + // Additional file extentions to make optional + extensions: [], + }, + ], }, jshint: { src: src + "/**/*.js", diff --git a/gulp/tasks/browserify.js b/gulp/tasks/browserify.js index 2c7f173d6..2ca815b1c 100644 --- a/gulp/tasks/browserify.js +++ b/gulp/tasks/browserify.js @@ -24,7 +24,9 @@ var _ = require('lodash'); var uglify = require('gulp-uglify'); var buffer = require('vinyl-buffer'); var sourcemaps = require('gulp-sourcemaps'); +var exorcist = require('exorcist'); var rename = require('gulp-rename'); +var path = require('path'); var browserifyTask = function (devMode) { @@ -32,7 +34,7 @@ var browserifyTask = function (devMode) { if (devMode) { // Add watchify args and debug (sourcemaps) option - _.extend(bundleConfig, watchify.args, {debug: true}); + _.extend(bundleConfig, watchify.args); // A watchify require/external bug that prevents proper recompiling, // so (for now) we'll ignore these options during development. Running // `gulp browserify` directly will properly require and externalize. @@ -46,21 +48,24 @@ var browserifyTask = function (devMode) { bundleLogger.start(bundleConfig.outputName); return b - .transform(require('babelify')) .bundle() // Report compile errors .on('error', handleErrors) // Use vinyl-source-stream to make the // stream gulp compatible. Specify the // desired output filename here. + .pipe(exorcist(path.join(bundleConfig.dest, bundleConfig.outputName + '.map'), + undefined, + '', + './')) .pipe(source(bundleConfig.outputName)) .pipe(gulp.dest(bundleConfig.dest)) .pipe(buffer()) .pipe(sourcemaps.init({loadMaps: true})) .pipe(uglify()) .on('error', gulpUtil.log) + .pipe(rename(bundleConfig.outputNameMin)) .pipe(sourcemaps.write('./')) - .pipe(rename(bundleConfig.outputNameMin)) .pipe(gulp.dest(bundleConfig.dest)) .pipe(browserSync.reload({ stream: true diff --git a/package.json b/package.json index f95f3b702..bac5c273e 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "browserify": "^11.0.1", "chai": "^3.2.0", "eslint": "^1.3.1", + "exorcist": "^0.4.0", "gulp": "^3.9.0", "gulp-changed": "^1.3.0", "gulp-filesize": "0.0.6", From be40b335c46a301733119704a569d039f36166c8 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 18:20:38 +0100 Subject: [PATCH 108/131] gulp/tasks: add lint task; runs eslint on ./src --- gulp/tasks/lint.js | 15 +++++++++++++++ package.json | 1 + 2 files changed, 16 insertions(+) create mode 100644 gulp/tasks/lint.js diff --git a/gulp/tasks/lint.js b/gulp/tasks/lint.js new file mode 100644 index 000000000..1aa55e00d --- /dev/null +++ b/gulp/tasks/lint.js @@ -0,0 +1,15 @@ +var gulp = require('gulp'); +var eslint = require('gulp-eslint'); + +gulp.task('lint', module.exports = function () { + return gulp.src(['src/**/*.js']) + // eslint() attaches the lint output to the eslint property + // of the file object so it can be used by other modules. + .pipe(eslint()) + // eslint.format() outputs the lint results to the console. + // Alternatively use eslint.formatEach() (see Docs). + .pipe(eslint.format()) + // To have the process exit with an error code (1) on + // lint error, return the stream and pipe to failOnError last. + // .pipe(eslint.failOnError()); +}); diff --git a/package.json b/package.json index bac5c273e..9a92d0962 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "exorcist": "^0.4.0", "gulp": "^3.9.0", "gulp-changed": "^1.3.0", + "gulp-eslint": "^1.0.0", "gulp-filesize": "0.0.6", "gulp-jshint": "^1.11.2", "gulp-notify": "^2.2.0", From e7c54635cb5f900696739b6dd785070f121422a4 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 20:03:30 +0100 Subject: [PATCH 109/131] gulp/tasks: tidy up tasks and fix watch, default --- gulp/config.js | 9 --------- gulp/tasks/default.js | 5 ++--- gulp/tasks/jshint.js | 17 ----------------- gulp/tasks/uglifyJs.js | 11 ----------- gulp/tasks/watch.js | 7 ++----- package.json | 4 ++++ 6 files changed, 8 insertions(+), 45 deletions(-) delete mode 100644 gulp/tasks/jshint.js delete mode 100644 gulp/tasks/uglifyJs.js diff --git a/gulp/config.js b/gulp/config.js index 583f421ee..2ff1eec2f 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -30,14 +30,5 @@ module.exports = { extensions: [], }, ], - }, - jshint: { - src: src + "/**/*.js", - settings: '.jshintrc' - }, - production: { - cssSrc: dest + '/*.css', - jsSrc: dest + '/*.js', - dest: dest } }; diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js index 8ab2d30d7..93fc34204 100644 --- a/gulp/tasks/default.js +++ b/gulp/tasks/default.js @@ -1,5 +1,4 @@ var gulp = require('gulp'); -//gulp.task('default', ['sass', 'images', 'markup', 'watch']); - -gulp.task('default', ['watch']); +gulp.task('build', ['lint', 'browserify']); +gulp.task('default', ['build']); diff --git a/gulp/tasks/jshint.js b/gulp/tasks/jshint.js deleted file mode 100644 index b28dabaf0..000000000 --- a/gulp/tasks/jshint.js +++ /dev/null @@ -1,17 +0,0 @@ -var gulp = require('gulp'); -var jshint = require('gulp-jshint'); -var stylish = require('jshint-stylish'); -var config = require('../config').jshint; - -var jshintTask = function() { - - gulp.src(config.src) - .pipe(jshint(config.settings)) - .pipe(jshint.reporter(stylish)) - .pipe(jshint.reporter('fail')); - -}; - -gulp.task('jshint', jshintTask); - -module.exports = jshintTask; diff --git a/gulp/tasks/uglifyJs.js b/gulp/tasks/uglifyJs.js deleted file mode 100644 index e5024d34f..000000000 --- a/gulp/tasks/uglifyJs.js +++ /dev/null @@ -1,11 +0,0 @@ -var gulp = require('gulp'); -var config = require('../config').production; -var size = require('gulp-filesize'); -var uglify = require('gulp-uglify'); - -gulp.task('uglifyJs', ['browserify'], function() { - return gulp.src(config.jsSrc) - .pipe(uglify()) - .pipe(gulp.dest(config.dest)) - .pipe(size()); -}); diff --git a/gulp/tasks/watch.js b/gulp/tasks/watch.js index 922d0e11b..dedcda316 100644 --- a/gulp/tasks/watch.js +++ b/gulp/tasks/watch.js @@ -6,9 +6,6 @@ var gulp = require('gulp'); var config = require('../config'); -gulp.task('watch', ['watchify', 'karma'], function() { - //gulp.watch(config.sass.src, ['sass']); - //gulp.watch(config.images.src, ['images']); - //gulp.watch(config.markup.src, ['markup']); - // Watchify will watch and recompile our JS, so no need to gulp.watch it +gulp.task('watch', ['watchify', /* 'karma' */], function() { + gulp.watch('./src/**/*.js', ['lint']); }); diff --git a/package.json b/package.json index 9a92d0962..b9a139ad5 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,10 @@ "url": "https://github.com/taye/interact.js.git" }, "main": "./index.js", + "scripts": { + "gulp": "./node_modules/.bin/gulp", + "build": "./node_modules/.bin/gulp build" + }, "browser": "./src/index.js", "description": "Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)", "homepage": "http://interactjs.io", From 77a4bb05b8e0df86ad17e1c6b0257ef33bdf9f9c Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 9 Sep 2015 20:04:37 +0100 Subject: [PATCH 110/131] utils/events: fix events.on IE8 workaround --- src/utils/events.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/events.js b/src/utils/events.js index 3fe3a9e98..2efa32e0e 100644 --- a/src/utils/events.js +++ b/src/utils/events.js @@ -54,7 +54,7 @@ function add (element, type, listener, useCapture) { let ret; if (useAttachEvent) { - const { supplied, wrapped, useCount } = [ attachedListeners[elementIndex] ]; + const { supplied, wrapped, useCount } = attachedListeners[elementIndex]; const listenerIndex = indexOf(supplied, listener); const wrappedListener = wrapped[listenerIndex] || function (event) { From fd03e94f9d6ff5ffd86c7a9a45c7a479abb88248 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 12 Sep 2015 19:28:00 +0100 Subject: [PATCH 111/131] utils/pointerUtils: lose InteractEvent dependency --- src/utils/pointerUtils.js | 64 ++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 9610f0dff..1ec17f109 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -5,7 +5,6 @@ const hypot = require('./hypot'); const extend = require('./extend'); const browser = require('./browser'); const isType = require('./isType'); -const InteractEvent = require('../InteractEvent'); pointerUtils.copyCoords = function (dest, src) { dest.page = dest.page || {}; @@ -40,66 +39,61 @@ pointerUtils.setEventDeltas = function (targetObj, prev, cur) { targetObj.client.vy = targetObj.client.y / dt; }; +pointerUtils.isInertiaPointer = function (pointer) { + return (!!pointer.interaction && /inertiastart/.test(pointer.type)); +}; + // Get specified X/Y coords for mouse or event.touches[0] -pointerUtils.getXY = function (type, pointer, xy) { - xy = xy || {}; +pointerUtils.getXY = function (type, pointer, xy, inertia) { + xy = xy || {}; type = type || 'page'; - xy.x = pointer[type + 'X']; - xy.y = pointer[type + 'Y']; + if (inertia) { + var interaction = pointer.interaction; + + extend(xy, interaction.inertiaStatus.upCoords[type]); + + xy.x += interaction.inertiaStatus.sx; + xy.y += interaction.inertiaStatus.sy; + } + else { + xy.x = pointer[type + 'X']; + xy.y = pointer[type + 'Y']; + } return xy; }; -pointerUtils.getPageXY = function (pointer, page, interaction) { +pointerUtils.getPageXY = function (pointer, page) { page = page || {}; - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - interaction = interaction || pointer.interaction; + const inertia = pointerUtils.isInertiaPointer(pointer); - extend(page, interaction.inertiaStatus.upCoords.page); - - page.x += interaction.inertiaStatus.sx; - page.y += interaction.inertiaStatus.sy; - } - else { - page.x = pointer.pageX; - page.y = pointer.pageY; - } - } // Opera Mobile handles the viewport and scrolling oddly - else if (browser.isOperaMobile) { - pointerUtils.getXY('screen', pointer, page); + if (browser.isOperaMobile && !inertia) { + pointerUtils.getXY('screen', pointer, page, inertia); page.x += win.window.scrollX; page.y += win.window.scrollY; } else { - pointerUtils.getXY('page', pointer, page); + pointerUtils.getXY('page', pointer, page, inertia); } return page; }; -pointerUtils.getClientXY = function (pointer, client, interaction) { +pointerUtils.getClientXY = function (pointer, client) { client = client || {}; - if (pointer instanceof InteractEvent) { - if (/inertiastart/.test(pointer.type)) { - extend(client, interaction.inertiaStatus.upCoords.client); + const inertia = pointerUtils.isInertiaPointer(pointer); - client.x += interaction.inertiaStatus.sx; - client.y += interaction.inertiaStatus.sy; - } - else { - client.x = pointer.clientX; - client.y = pointer.clientY; - } + // Opera Mobile handles the viewport and scrolling oddly + if (browser.isOperaMobile && !inertia) { + client = pointerUtils.getXY('screen', pointer, client, inertia); } else { - // Opera Mobile handles the viewport and scrolling oddly - pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client); + client = pointerUtils.getXY('client', pointer, client, inertia); } return client; From e781299551d6b4f35539a82fea43733a99f6a9b4 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 12 Sep 2015 19:46:21 +0100 Subject: [PATCH 112/131] utils/pointerUtils: add methods in object literal --- src/utils/pointerUtils.js | 199 +++++++++++++++++++------------------- 1 file changed, 99 insertions(+), 100 deletions(-) diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 1ec17f109..820e568e7 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -1,116 +1,115 @@ -const pointerUtils = {}; - -const win = require('./window'); -const hypot = require('./hypot'); -const extend = require('./extend'); -const browser = require('./browser'); -const isType = require('./isType'); - -pointerUtils.copyCoords = function (dest, src) { - dest.page = dest.page || {}; - dest.page.x = src.page.x; - dest.page.y = src.page.y; - - dest.client = dest.client || {}; - dest.client.x = src.client.x; - dest.client.y = src.client.y; - - dest.timeStamp = src.timeStamp; -}; - -pointerUtils.setEventDeltas = function (targetObj, prev, cur) { - const now = new Date().getTime(); - - targetObj.page.x = cur.page.x - prev.page.x; - targetObj.page.y = cur.page.y - prev.page.y; - targetObj.client.x = cur.client.x - prev.client.x; - targetObj.client.y = cur.client.y - prev.client.y; - targetObj.timeStamp = now - prev.timeStamp; - - // set pointer velocity - const dt = Math.max(targetObj.timeStamp / 1000, 0.001); - - targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; - targetObj.page.vx = targetObj.page.x / dt; - targetObj.page.vy = targetObj.page.y / dt; - - targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; - targetObj.client.vx = targetObj.client.x / dt; - targetObj.client.vy = targetObj.client.y / dt; -}; - -pointerUtils.isInertiaPointer = function (pointer) { - return (!!pointer.interaction && /inertiastart/.test(pointer.type)); -}; - -// Get specified X/Y coords for mouse or event.touches[0] -pointerUtils.getXY = function (type, pointer, xy, inertia) { - xy = xy || {}; - type = type || 'page'; - - if (inertia) { - var interaction = pointer.interaction; - - extend(xy, interaction.inertiaStatus.upCoords[type]); - - xy.x += interaction.inertiaStatus.sx; - xy.y += interaction.inertiaStatus.sy; - } - else { - xy.x = pointer[type + 'X']; - xy.y = pointer[type + 'Y']; - } +const win = require('./window'); +const hypot = require('./hypot'); +const extend = require('./extend'); +const browser = require('./browser'); +const isType = require('./isType'); + +const pointerUtils = { + copyCoords: function (dest, src) { + dest.page = dest.page || {}; + dest.page.x = src.page.x; + dest.page.y = src.page.y; + + dest.client = dest.client || {}; + dest.client.x = src.client.x; + dest.client.y = src.client.y; + + dest.timeStamp = src.timeStamp; + }, + + setEventDeltas: function (targetObj, prev, cur) { + const now = new Date().getTime(); + + targetObj.page.x = cur.page.x - prev.page.x; + targetObj.page.y = cur.page.y - prev.page.y; + targetObj.client.x = cur.client.x - prev.client.x; + targetObj.client.y = cur.client.y - prev.client.y; + targetObj.timeStamp = now - prev.timeStamp; + + // set pointer velocity + const dt = Math.max(targetObj.timeStamp / 1000, 0.001); + + targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt; + targetObj.page.vx = targetObj.page.x / dt; + targetObj.page.vy = targetObj.page.y / dt; + + targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt; + targetObj.client.vx = targetObj.client.x / dt; + targetObj.client.vy = targetObj.client.y / dt; + }, + + isInertiaPointer: function (pointer) { + return (!!pointer.interaction && /inertiastart/.test(pointer.type)); + }, + + // Get specified X/Y coords for mouse or event.touches[0] + getXY: function (type, pointer, xy, inertia) { + xy = xy || {}; + type = type || 'page'; + + if (inertia) { + const interaction = pointer.interaction; + + extend(xy, interaction.inertiaStatus.upCoords[type]); + + xy.x += interaction.inertiaStatus.sx; + xy.y += interaction.inertiaStatus.sy; + } + else { + xy.x = pointer[type + 'X']; + xy.y = pointer[type + 'Y']; + } - return xy; -}; + return xy; + }, -pointerUtils.getPageXY = function (pointer, page) { - page = page || {}; + getPageXY: function (pointer, page) { + page = page || {}; - const inertia = pointerUtils.isInertiaPointer(pointer); + const inertia = pointerUtils.isInertiaPointer(pointer); - // Opera Mobile handles the viewport and scrolling oddly - if (browser.isOperaMobile && !inertia) { - pointerUtils.getXY('screen', pointer, page, inertia); + // Opera Mobile handles the viewport and scrolling oddly + if (browser.isOperaMobile && !inertia) { + pointerUtils.getXY('screen', pointer, page, inertia); - page.x += win.window.scrollX; - page.y += win.window.scrollY; - } - else { - pointerUtils.getXY('page', pointer, page, inertia); - } + page.x += win.window.scrollX; + page.y += win.window.scrollY; + } + else { + pointerUtils.getXY('page', pointer, page, inertia); + } - return page; -}; + return page; + }, -pointerUtils.getClientXY = function (pointer, client) { - client = client || {}; + getClientXY: function (pointer, client) { + client = client || {}; - const inertia = pointerUtils.isInertiaPointer(pointer); + const inertia = pointerUtils.isInertiaPointer(pointer); - // Opera Mobile handles the viewport and scrolling oddly - if (browser.isOperaMobile && !inertia) { - client = pointerUtils.getXY('screen', pointer, client, inertia); - } - else { - client = pointerUtils.getXY('client', pointer, client, inertia); - } + // Opera Mobile handles the viewport and scrolling oddly + if (browser.isOperaMobile && !inertia) { + client = pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client, inertia); + } + else { + client = pointerUtils.getXY('client', pointer, client, inertia); + } - return client; -}; + return client; + }, -pointerUtils.getPointerId = function (pointer) { - return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; -}; + getPointerId: function (pointer) { + return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; + }, -pointerUtils.pointerExtend = function (dest, source) { - for (const prop in source) { - if (prop !== 'webkitMovementX' && prop !== 'webkitMovementY') { - dest[prop] = source[prop]; + pointerExtend: function (dest, source) { + for (const prop in source) { + if (prop !== 'webkitMovementX' && prop !== 'webkitMovementY') { + dest[prop] = source[prop]; + } } - } - return dest; + return dest; + }, }; - module.exports = pointerUtils; From 5af67e22d30122436b67ff4dcc743210500acd9a Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 12 Sep 2015 19:48:13 +0100 Subject: [PATCH 113/131] utils/pointerUtils: add missing methds getTouchPair, touchAngle, touchAverage, touchBBox, touchDistance --- src/utils/pointerUtils.js | 106 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 820e568e7..28c7a151e 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -110,6 +110,112 @@ const pointerUtils = { } return dest; }, + + getTouchPair: function (event) { + const touches = []; + + // array of touches is supplied + if (isType.isArray(event)) { + touches[0] = event[0]; + touches[1] = event[1]; + } + // an event + else { + if (event.type === 'touchend') { + if (event.touches.length === 1) { + touches[0] = event.touches[0]; + touches[1] = event.changedTouches[0]; + } + else if (event.touches.length === 0) { + touches[0] = event.changedTouches[0]; + touches[1] = event.changedTouches[1]; + } + } + else { + touches[0] = event.touches[0]; + touches[1] = event.touches[1]; + } + } + + return touches; + }, + + touchAverage: function (event) { + const touches = pointerUtils.getTouchPair(event); + + return { + pageX: (touches[0].pageX + touches[1].pageX) / 2, + pageY: (touches[0].pageY + touches[1].pageY) / 2, + clientX: (touches[0].clientX + touches[1].clientX) / 2, + clientY: (touches[0].clientY + touches[1].clientY) / 2, + }; + }, + + touchBBox: function (event) { + if (!event.length && !(event.touches && event.touches.length > 1)) { + return; + } + + const touches = pointerUtils.getTouchPair(event); + const minX = Math.min(touches[0].pageX, touches[1].pageX); + const minY = Math.min(touches[0].pageY, touches[1].pageY); + const maxX = Math.max(touches[0].pageX, touches[1].pageX); + const maxY = Math.max(touches[0].pageY, touches[1].pageY); + + return { + x: minX, + y: minY, + left: minX, + top: minY, + width: maxX - minX, + height: maxY - minY, + }; + }, + + touchDistance: function (event, deltaSource) { + deltaSource = deltaSource; + + const sourceX = deltaSource + 'X'; + const sourceY = deltaSource + 'Y'; + const touches = pointerUtils.getTouchPair(event); + + + const dx = touches[0][sourceX] - touches[1][sourceX]; + const dy = touches[0][sourceY] - touches[1][sourceY]; + + return hypot(dx, dy); + }, + + touchAngle: function (event, prevAngle, deltaSource) { + deltaSource = deltaSource; + + const sourceX = deltaSource + 'X'; + const sourceY = deltaSource + 'Y'; + const touches = pointerUtils.getTouchPair(event); + const dx = touches[0][sourceX] - touches[1][sourceX]; + const dy = touches[0][sourceY] - touches[1][sourceY]; + let angle = 180 * Math.atan(dy / dx) / Math.PI; + + if (isType.isNumber(prevAngle)) { + const dr = angle - prevAngle; + const drClamped = dr % 360; + + if (drClamped > 315) { + angle -= 360 + (angle / 360)|0 * 360; + } + else if (drClamped > 135) { + angle -= 180 + (angle / 360)|0 * 360; + } + else if (drClamped < -315) { + angle += 360 + (angle / 360)|0 * 360; + } + else if (drClamped < -135) { + angle += 180 + (angle / 360)|0 * 360; + } + } + + return angle; + }, }; module.exports = pointerUtils; From 3c0bf3ee236fe503c3c844ef64b8eabc9df195f0 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 13 Sep 2015 16:47:25 +0100 Subject: [PATCH 114/131] legacyBrowsers: extract more ie8-specific logic Requires changes from the next commit. --- src/Interaction.js | 11 -------- src/ie8.js | 9 ------- src/index.js | 6 +++-- src/legacyBrowsers.js | 60 +++++++++++++++++++++++++++++++++++++++++++ src/pointerEvents.js | 40 ++++------------------------- 5 files changed, 69 insertions(+), 57 deletions(-) delete mode 100644 src/ie8.js create mode 100644 src/legacyBrowsers.js diff --git a/src/Interaction.js b/src/Interaction.js index ee7cd8a9b..35a9fca9e 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -1255,17 +1255,6 @@ signals.on('listen-to-document', function ({ doc, win }) { } }); - if (browser.isIE8) { - // For IE's lack of Event#preventDefault - events.add(doc, 'selectstart', function (event) { - const interaction = scope.interactions[0]; - - if (interaction.currentAction()) { - interaction.checkAndPreventDefault(event); - } - }); - } - scope.documents.push(doc); events.documents.push(doc); }); diff --git a/src/ie8.js b/src/ie8.js deleted file mode 100644 index 0d9a8021e..000000000 --- a/src/ie8.js +++ /dev/null @@ -1,9 +0,0 @@ -const toString = Object.prototype.toString; - -if (!window.Array.isArray) { - window.Array.isArray = function (obj) { - return toString.call(obj) === '[object Array]'; - }; -} - -module.exports = null; diff --git a/src/index.js b/src/index.js index 1f8c2fd35..57cda3071 100644 --- a/src/index.js +++ b/src/index.js @@ -3,8 +3,6 @@ // Legacy browser support require('./ie8'); -module.exports = require('./interact'); - // actions require('./actions/resize'); require('./actions/drag'); @@ -19,3 +17,7 @@ require('./pointerEvents'); // modifiers require('./modifiers/snap'); require('./modifiers/restrict'); + +require('./Interaction.js'); + +module.exports = require('./interact'); diff --git a/src/legacyBrowsers.js b/src/legacyBrowsers.js new file mode 100644 index 000000000..f58bb7614 --- /dev/null +++ b/src/legacyBrowsers.js @@ -0,0 +1,60 @@ +const scope = require('./scope'); +const events = require('./utils/events'); +const signals = require('./utils/signals'); +const browser = require('./utils/browser'); +const iFinder = require('./utils/interactionFinder'); + +const toString = Object.prototype.toString; + +if (!window.Array.isArray) { + window.Array.isArray = function (obj) { + return toString.call(obj) === '[object Array]'; + }; +} + +if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); + }; +} + +// http://www.quirksmode.org/dom/events/click.html +// >Events leading to dblclick +// +// IE8 doesn't fire down event before dblclick. +// This workaround tries to fire a tap and doubletap after dblclick +function onIE8Dblclick (event) { + const interaction = iFinder.search(event, event.type, event.target); + + if (!interaction) { return; } + + if (interaction.prevTap + && event.clientX === interaction.prevTap.clientX + && event.clientY === interaction.prevTap.clientY + && event.target === interaction.prevTap.target) { + + interaction.downTargets[0] = event.target; + interaction.downTimes [0] = new Date().getTime(); + + scope.pointerEvents.collectEventTargets(interaction, event, event, event.target, 'tap'); + } +} + +if (browser.isIE8) { + signals.on('listen-to-document', function ({ doc }) { + // For IE's lack of Event#preventDefault + events.add(doc, 'selectstart', function (event) { + const interaction = scope.interactions[0]; + + if (interaction.currentAction()) { + interaction.checkAndPreventDefault(event); + } + }); + + if (scope.pointerEvents) { + events.add(doc, 'dblclick', onIE8Dblclick); + } + }); +} + +module.exports = null; diff --git a/src/pointerEvents.js b/src/pointerEvents.js index d8465c3ef..2e2d8cf6d 100644 --- a/src/pointerEvents.js +++ b/src/pointerEvents.js @@ -1,10 +1,9 @@ const scope = require('./scope'); -const Interaction = require('./Interaction'); const InteractEvent = require('./InteractEvent'); const utils = require('./utils'); const browser = require('./utils/browser'); -const events = require('./utils/events'); const signals = require('./utils/signals'); + const simpleSignals = [ 'interaction-down', 'interaction-up', @@ -192,35 +191,6 @@ for (let i = 0; i < simpleSignals.length; i++) { signals.on(simpleSignals[i], createSignalListener(simpleEvents[i])); } -if (browser.ie8) { - // http://www.quirksmode.org/dom/events/click.html - // >Events leading to dblclick - // - // IE8 doesn't fire down event before dblclick. - // This workaround tries to fire a tap and doubletap after dblclick - const onIE8Dblclick = function (event) { - const target = Interaction.getInteractionFromPointer(event); - - if (!target) { return; } - - const interaction = target.interaction; - - if (interaction.prevTap - && event.clientX === interaction.prevTap.clientX - && event.clientY === interaction.prevTap.clientY - && target.eventTarget === interaction.prevTap.target) { - - interaction.downTargets[0] = target.eventTarget; - interaction.downTimes[0] = new Date().getTime(); - collectEventTargets(interaction, target.pointer, target.event, target.eventTarget, 'tap'); - } - }; - - signals.on('listen-to-document', function ({ doc }) { - events.add(doc, 'dblclick', onIE8Dblclick); - }); -} - utils.merge(scope.eventTypes, [ 'down', 'move', @@ -231,8 +201,8 @@ utils.merge(scope.eventTypes, [ 'hold', ]); -module.exports = { - firePointers: firePointers, - collectEventTargets: collectEventTargets, - preventOriginalDefault: preventOriginalDefault, +module.exports = scope.pointerEvents = { + firePointers, + collectEventTargets, + preventOriginalDefault, }; From 0227d75da339bceb0951497f257a081b9eeeca2b Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sun, 13 Sep 2015 16:52:09 +0100 Subject: [PATCH 115/131] *: merge master and improve Interactions Replace getInteractionFromPointer with utils/interactionFinder module: interaction = finder.search(pointer, eventType, eventTarget); --- src/Interaction.js | 214 ++++++++++----------------------- src/actions/drop.js | 32 ++--- src/index.js | 2 +- src/interact.js | 8 +- src/utils/domObjects.js | 2 + src/utils/interactionFinder.js | 97 +++++++++++++++ src/utils/pointerUtils.js | 74 ++++++------ 7 files changed, 222 insertions(+), 207 deletions(-) create mode 100644 src/utils/interactionFinder.js diff --git a/src/Interaction.js b/src/Interaction.js index 35a9fca9e..17cfae896 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -4,6 +4,7 @@ const InteractEvent = require('./InteractEvent'); const events = require('./utils/events'); const signals = require('./utils/signals'); const browser = require('./utils/browser'); +const finder = require('./utils/interactionFinder'); const actions = require('./actions/base'); const modifiers = require('./modifiers/'); const animationFrame = utils.raf; @@ -36,6 +37,7 @@ class Interaction { this.inertiaStatus = { active : false, smoothEnd: false, + ending : false, startEvent: null, upCoords : {}, @@ -135,15 +137,10 @@ class Interaction { scope.interactions.push(this); } - setEventXY (targetObj, pointer) { - if (!pointer) { - if (this.pointerIds.length > 1) { - pointer = utils.touchAverage(this.pointers); - } - else { - pointer = this.pointers[0]; - } - } + setEventXY (targetObj, pointers) { + const pointer = (pointers.length > 1 + ? utils.pointerAverage(pointers) + : pointers[0]); const tmpXY = {}; @@ -247,7 +244,7 @@ class Interaction { let action; // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); + this.setEventXY(this.curCoords, [pointer]); if (matches) { action = this.validateSelector(pointer, event, matches, matchElements); @@ -341,7 +338,7 @@ class Interaction { }; // update pointer coords for defaultActionChecker to use - this.setEventXY(this.curCoords, pointer); + this.setEventXY(this.curCoords, [pointer]); this.downEvent = event; while (utils.isElement(element) && !action) { @@ -410,7 +407,7 @@ class Interaction { if (target && (forceAction || !this.prepared.name)) { action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element); - this.setEventXY(this.startCoords); + this.setEventXY(this.startCoords, this.pointers); if (!action) { return; } @@ -434,7 +431,7 @@ class Interaction { this.downTargets[pointerIndex] = eventTarget; utils.pointerExtend(this.downPointer, pointer); - this.setEventXY(this.prevCoords); + utils.copyCoords(this.prevCoords, this.startCoords); this.pointerWasMoved = false; this.checkAndPreventDefault(event, target, this.element); @@ -522,7 +519,7 @@ class Interaction { this.target = interactable; this.element = element; - this.setEventXY(this.startCoords); + this.setEventXY(this.startCoords, this.pointers); this.setStartOffsets(action.name, interactable, element, this.modifierOffsets); modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); @@ -531,11 +528,21 @@ class Interaction { } pointerMove (pointer, event, eventTarget, curEventTarget, preEnd) { - this.recordPointer(pointer); - - this.setEventXY(this.curCoords, (pointer instanceof InteractEvent) - ? this.inertiaStatus.startEvent - : undefined); + if (this.inertiaStatus.active) { + const pageUp = this.inertiaStatus.upCoords.page; + const clientUp = this.inertiaStatus.upCoords.client; + + this.setEventXY(this.curCoords, [ { + pageX : pageUp.x + this.inertiaStatus.sx, + pageY : pageUp.y + this.inertiaStatus.sy, + clientX: clientUp.x + this.inertiaStatus.sx, + clientY: clientUp.y + this.inertiaStatus.sy, + } ]); + } + else { + this.recordPointer(pointer); + this.setEventXY(this.curCoords, this.pointers); + } const duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x && this.curCoords.page.y === this.prevCoords.page.y @@ -665,7 +672,7 @@ class Interaction { if (this.interacting()) { - if (inertiaStatus.active) { return; } + if (inertiaStatus.active && !inertiaStatus.ending) { return; } const now = new Date().getTime(); const statuses = {}; @@ -818,6 +825,7 @@ class Interaction { // remove this interaction if it's not the only one of it's type if (scope.interactions[i] !== this && scope.interactions[i].mouse === this.mouse) { scope.interactions.splice(utils.indexOf(scope.interactions, this), 1); + break; } } } @@ -853,13 +861,15 @@ class Interaction { inertiaStatus.i = animationFrame.request(this.boundInertiaFrame); } else { + inertiaStatus.ending = true; + inertiaStatus.sx = inertiaStatus.modifiedXe; inertiaStatus.sy = inertiaStatus.modifiedYe; this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - inertiaStatus.active = false; this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + inertiaStatus.active = inertiaStatus.ending = false; } } @@ -877,15 +887,16 @@ class Interaction { inertiaStatus.i = animationFrame.request(this.boundSmoothEndFrame); } else { + inertiaStatus.ending = true; + inertiaStatus.sx = inertiaStatus.xe; inertiaStatus.sy = inertiaStatus.ye; this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent); - - inertiaStatus.active = false; - inertiaStatus.smoothEnd = false; - this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent); + + inertiaStatus.smoothEnd = + inertiaStatus.active = inertiaStatus.ending = false; } } @@ -909,10 +920,7 @@ class Interaction { if (index === -1) { return; } - if (!this.interacting()) { - this.pointers.splice(index, 1); - } - + this.pointers .splice(index, 1); this.pointerIds .splice(index, 1); this.downTargets.splice(index, 1); this.downTimes .splice(index, 1); @@ -920,10 +928,6 @@ class Interaction { } recordPointer (pointer) { - // Do not update pointers while inertia is active. - // The inertia start event should be this.pointers[0] - if (this.inertiaStatus.active) { return; } - const index = this.mouse? 0: utils.indexOf(this.pointerIds, utils.getPointerId(pointer)); if (index === -1) { return; } @@ -1018,142 +1022,57 @@ for (let i = 0, len = methodNames.length; i < len; i++) { listeners[method] = doOnInteractions(method); } -function getInteractionFromPointer (pointer, eventType, eventTarget) { - const mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) - // MSPointerEvent.MSPOINTER_TYPE_MOUSE - || pointer.pointerType === 4); - let i = 0; - const len = scope.interactions.length; - let interaction; - - const id = utils.getPointerId(pointer); - - // try to resume inertia with a new pointer - if (/down|start/i.test(eventType)) { - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; - - let element = eventTarget; - - if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume - && (interaction.mouse === mouseEvent)) { - while (element) { - // if the element is the interaction element - if (element === interaction.element) { - // update the interaction's pointer - if (interaction.pointers[0]) { - interaction.removePointer(interaction.pointers[0]); - } - interaction.addPointer(pointer); - - return interaction; - } - element = utils.parentElement(element); - } - } - } - } - - // if it's a mouse interaction - if (mouseEvent || !(browser.supportsTouch || browser.supportsPointerEvent)) { - - // find a mouse interaction that's not in inertia phase - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !scope.interactions[i].inertiaStatus.active) { - return scope.interactions[i]; - } - } - - // find any interaction specifically for mouse. - // if the eventType is a mousedown, and inertia is active - // ignore the interaction - for (i = 0; i < len; i++) { - if (scope.interactions[i].mouse && !(/down/.test(eventType) && scope.interactions[i].inertiaStatus.active)) { - return interaction; - } - } - - // create a new interaction for mouse - interaction = new Interaction(); - interaction.mouse = true; - - return interaction; - } - - // get interaction that has this pointer - for (i = 0; i < len; i++) { - if (utils.contains(scope.interactions[i].pointerIds, id)) { - return scope.interactions[i]; - } - } - - // at this stage, a pointerUp should not return an interaction - if (/up|end|out/i.test(eventType)) { - return null; - } - - // get first idle interaction - for (i = 0; i < len; i++) { - interaction = scope.interactions[i]; - - if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) - && !interaction.interacting() - && !(!mouseEvent && interaction.mouse)) { - - interaction.addPointer(pointer); - - return interaction; - } - } - - return new Interaction(); -} - function doOnInteractions (method) { return (function (event) { - let interaction; const eventTarget = utils.getActualElement(event.path ? event.path[0] : event.target); const curEventTarget = utils.getActualElement(event.currentTarget); - let i; + const matches = []; // [ [pointer, interaction], ...] if (browser.supportsTouch && /touch/.test(event.type)) { scope.prevTouchTime = new Date().getTime(); - for (i = 0; i < event.changedTouches.length; i++) { - const pointer = event.changedTouches[i]; - - interaction = getInteractionFromPointer(pointer, event.type, eventTarget); - - if (!interaction) { continue; } + for (const pointer of event.changedTouches) { + const interaction = finder.search(pointer, event.type, eventTarget, true); - interaction._updateEventTargets(eventTarget, curEventTarget); - - interaction[method](pointer, event, eventTarget, curEventTarget); + if (interaction) { + matches.push([pointer, interaction]); + } } } else { + let invalidPointer = false; + if (!browser.supportsPointerEvent && /mouse/.test(event.type)) { // ignore mouse events while touch interactions are active - for (i = 0; i < scope.interactions.length; i++) { - if (!scope.interactions[i].mouse && scope.interactions[i].pointerIsDown) { - return; - } + for (let i = 0; i < scope.interactions.length && !invalidPointer; i++) { + invalidPointer = !scope.interactions[i].mouse && scope.interactions[i].pointerIsDown; } // try to ignore mouse events that are simulated by the browser // after a touch event - if (new Date().getTime() - scope.prevTouchTime < 500) { - return; - } - } + invalidPointer = invalidPointer || (new Date().getTime() - scope.prevTouchTime < 500); - interaction = getInteractionFromPointer(event, event.type, eventTarget); + if (!invalidPointer) { + let interaction = finder.search(event, event.type, eventTarget, true); - if (!interaction) { return; } + if (!interaction && (/mouse/i.test(event.pointerType || event.type) + // MSPointerEvent.MSPOINTER_TYPE_MOUSE + || event.pointerType === 4)) { - interaction._updateEventTargets(eventTarget, curEventTarget); + interaction = new Interaction(); + interaction.mouse = true; + } - interaction[method](event, event, eventTarget, curEventTarget); + if (interaction) { + matches.push([event, interaction]); + } + } + } + } + + for (const [pointer, interaction] of matches) { + interaction._updateEventTargets(eventTarget, curEventTarget); + interaction[method](pointer, event, eventTarget, curEventTarget); } }); } @@ -1264,7 +1183,6 @@ signals.fire('listen-to-document', { doc: scope.document, }); -Interaction.getInteractionFromPointer = getInteractionFromPointer; Interaction.doOnInteractions = doOnInteractions; Interaction.withinLimit = scope.withinInteractionLimit; diff --git a/src/actions/drop.js b/src/actions/drop.js index a83a72ded..65a9bc723 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -31,7 +31,7 @@ const drop = { move: function (interaction, event, dragEvent) { const draggableElement = interaction.element; - const dropOptions = getDrop(interaction, event, draggableElement); + const dropOptions = getDrop(dragEvent, event, draggableElement); interaction.dropTarget = dropOptions.dropzone; interaction.dropElement = dropOptions.element; @@ -50,7 +50,7 @@ const drop = { end: function (interaction, event, endEvent) { const draggableElement = interaction.element; - const dropResult = getDrop(interaction, event, draggableElement); + const dropResult = getDrop(endEvent, event, draggableElement); interaction.dropTarget = dropResult.dropzone; interaction.dropElement = dropResult.element; @@ -148,7 +148,8 @@ function setActiveDrops (interaction, dragElement) { } } -function getDrop (interaction, event, dragElement) { +function getDrop (dragEvent, event, dragElement) { + const interaction = dragEvent.interaction; const validDrops = []; if (scope.dynamicDrop) { @@ -161,7 +162,7 @@ function getDrop (interaction, event, dragElement) { const currentElement = interaction.activeDrops.elements [j]; const rect = interaction.activeDrops.rects [j]; - validDrops.push(current.dropCheck(interaction.pointers[0], event, interaction.target, dragElement, currentElement, rect) + validDrops.push(current.dropCheck(dragEvent, event, interaction.target, dragElement, currentElement, rect) ? currentElement : null); } @@ -316,14 +317,15 @@ Interactable.prototype.dropzone = function (options) { if (utils.isFunction(options.ondragleave) ) { this.ondragleave = options.ondragleave ; } if (utils.isFunction(options.ondropmove) ) { this.ondropmove = options.ondropmove ; } - this.accept(options.accept); - if (/^(pointer|center)$/.test(options.overlap)) { this.options.drop.overlap = options.overlap; } else if (utils.isNumber(options.overlap)) { this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0); } + if ('accept' in options) { + this.options.drop.accept = options.accept; + } if ('checker' in options) { this.options.drop.checker = options.checker; } @@ -341,14 +343,14 @@ Interactable.prototype.dropzone = function (options) { return this.options.drop; }; -Interactable.prototype.dropCheck = function (pointer, event, draggable, draggableElement, dropElement, rect) { +Interactable.prototype.dropCheck = function (dragEvent, event, draggable, draggableElement, dropElement, rect) { let dropped = false; // if the dropzone has no rect (eg. display: none) // call the custom dropChecker or just return false if (!(rect = rect || this.getRect(dropElement))) { return (this.options.drop.checker - ? this.options.drop.checker(pointer, event, dropped, this, dropElement, draggable, draggableElement) + ? this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement) : false); } @@ -356,7 +358,7 @@ Interactable.prototype.dropCheck = function (pointer, event, draggable, draggabl if (dropOverlap === 'pointer') { const origin = utils.getOriginXY(draggable, draggableElement); - const page = utils.getPageXY(pointer); + const page = utils.getPageXY(dragEvent); let horizontal; let vertical; @@ -388,7 +390,7 @@ Interactable.prototype.dropCheck = function (pointer, event, draggable, draggabl } if (this.options.drop.checker) { - dropped = this.options.drop.checker(pointer, event, dropped, this, dropElement, draggable, draggableElement); + dropped = this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement); } return dropped; @@ -408,9 +410,9 @@ Interactable.prototype.dropCheck = function (pointer, event, draggable, draggabl * * The checker function takes the following arguments: * - - pointer (Touch | PointerEvent | MouseEvent) The pointer/event that ends a drag - - event (TouchEvent | PointerEvent | MouseEvent) The event related to the pointer - - dropped (boolean) The value from the default drop check + - dragEvent (InteractEvent) The related dragmove or dragend event + - event (TouchEvent | PointerEvent | MouseEvent) The user move/up/end Event related to the dragEvent + - dropped (boolean) The value from the default drop checker - dropzone (Interactable) The dropzone interactable - dropElement (Element) The dropzone element - draggable (Interactable) The Interactable being dragged @@ -418,9 +420,9 @@ Interactable.prototype.dropCheck = function (pointer, event, draggable, draggabl * > Usage: | interact(target) - | .dropChecker(function (pointer, // Touch/PointerEvent/MouseEvent + | .dropChecker(function(dragEvent, // related dragmove or dragend event | event, // TouchEvent/PointerEvent/MouseEvent - | dropped, // result of the default checker + | dropped, // bool result of the default checker | dropzone, // dropzone Interactable | dropElement, // dropzone elemnt | draggable, // draggable Interactable diff --git a/src/index.js b/src/index.js index 57cda3071..f1c6f094d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ // browser entry point // Legacy browser support -require('./ie8'); +require('./legacyBrowsers'); // actions require('./actions/resize'); diff --git a/src/interact.js b/src/interact.js index 61553ceb6..bac950180 100644 --- a/src/interact.js +++ b/src/interact.js @@ -254,10 +254,10 @@ interact.debug = function () { }; // expose the functions used to calculate multi-touch properties -interact.getTouchAverage = utils.touchAverage; -interact.getTouchBBox = utils.touchBBox; -interact.getTouchDistance = utils.touchDistance; -interact.getTouchAngle = utils.touchAngle; +interact.getPointerAverage = utils.pointerAverage; +interact.getTouchBBox = utils.touchBBox; +interact.getTouchDistance = utils.touchDistance; +interact.getTouchAngle = utils.touchAngle; interact.getElementRect = utils.getElementRect; interact.getElementClientRect = utils.getElementClientRect; diff --git a/src/utils/domObjects.js b/src/utils/domObjects.js index bfbc6810a..f0e86eecb 100644 --- a/src/utils/domObjects.js +++ b/src/utils/domObjects.js @@ -10,6 +10,8 @@ domObjects.SVGSVGElement = win.SVGSVGElement || blank; domObjects.SVGElementInstance = win.SVGElementInstance || blank; domObjects.HTMLElement = win.HTMLElement || win.Element; +domObjects.Event = win.Event; +domObjects.Touch = win.Touch || blank; domObjects.PointerEvent = (win.PointerEvent || win.MSPointerEvent); module.exports = domObjects; diff --git a/src/utils/interactionFinder.js b/src/utils/interactionFinder.js new file mode 100644 index 000000000..553596775 --- /dev/null +++ b/src/utils/interactionFinder.js @@ -0,0 +1,97 @@ +const scope = require('../scope'); +const utils = require('./index'); +const browser = require('./browser'); + +const finder = { + methodOrder: [ 'inertiaResume', 'mouse', 'hasPointer', 'idle' ], + + search: function (pointer, eventType, eventTarget) { + const mouseEvent = (/mouse/i.test(pointer.pointerType || eventType) + // MSPointerEvent.MSPOINTER_TYPE_MOUSE + || pointer.pointerType === 4); + const pointerId = utils.getPointerId(pointer); + const details = { pointer, pointerId, mouseEvent, eventType, eventTarget }; + + for (const method of finder.methodOrder) { + const interaction = finder[method](details); + + if (interaction) { + return interaction; + } + } + }, + + // try to resume inertia with a new pointer + inertiaResume: function ({ mouseEvent, eventType, eventTarget }) { + if (!/down|start/i.test(eventType)) { + return null; + } + + for (const interaction of scope.interactions) { + let element = eventTarget; + + if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume + && (interaction.mouse === mouseEvent)) { + while (element) { + // if the element is the interaction element + if (element === interaction.element) { + return interaction; + } + element = utils.parentElement(element); + } + } + } + + return null; + }, + + // if it's a mouse interaction + mouse: function ({ mouseEvent, eventType }) { + if (!mouseEvent && (browser.supportsTouch || browser.supportsPointerEvent)) { + return null; + } + + // Find a mouse interaction that's not in inertia phase + for (const interaction of scope.interactions) { + if (interaction.mouse && !interaction.inertiaStatus.active) { + return interaction; + } + } + + // Find any interaction specifically for mouse. + // If the eventType is a mousedown, and inertia is active + // ignore the interaction + for (const interaction of scope.interactions) { + if (interaction.mouse && !(/down/.test(eventType) && interaction.inertiaStatus.active)) { + return interaction; + } + } + + return null; + }, + + // get interaction that has this pointer + hasPointer: function ({ pointerId }) { + for (const interaction of scope.interactions) { + if (utils.contains(interaction.pointerIds, pointerId)) { + return interaction; + } + } + }, + + // get first idle interaction + idle: function ({ mouseEvent }) { + for (const interaction of scope.interactions) { + if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled)) + && !interaction.interacting() + && !(!mouseEvent && interaction.mouse)) { + + return interaction; + } + } + + return null; + }, +}; + +module.exports = finder; diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 28c7a151e..187e9604a 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -1,7 +1,6 @@ -const win = require('./window'); const hypot = require('./hypot'); -const extend = require('./extend'); const browser = require('./browser'); +const dom = require('./domObjects'); const isType = require('./isType'); const pointerUtils = { @@ -38,27 +37,17 @@ const pointerUtils = { targetObj.client.vy = targetObj.client.y / dt; }, - isInertiaPointer: function (pointer) { - return (!!pointer.interaction && /inertiastart/.test(pointer.type)); + isNativePointer: function (pointer) { + return (pointer instanceof dom.Event || pointer instanceof dom.Touch); }, // Get specified X/Y coords for mouse or event.touches[0] - getXY: function (type, pointer, xy, inertia) { - xy = xy || {}; + getXY: function (type, pointer, xy) { + xy = xy || {}; type = type || 'page'; - if (inertia) { - const interaction = pointer.interaction; - - extend(xy, interaction.inertiaStatus.upCoords[type]); - - xy.x += interaction.inertiaStatus.sx; - xy.y += interaction.inertiaStatus.sy; - } - else { - xy.x = pointer[type + 'X']; - xy.y = pointer[type + 'Y']; - } + xy.x = pointer[type + 'X']; + xy.y = pointer[type + 'Y']; return xy; }, @@ -66,17 +55,15 @@ const pointerUtils = { getPageXY: function (pointer, page) { page = page || {}; - const inertia = pointerUtils.isInertiaPointer(pointer); - // Opera Mobile handles the viewport and scrolling oddly - if (browser.isOperaMobile && !inertia) { - pointerUtils.getXY('screen', pointer, page, inertia); + if (browser.isOperaMobile && pointerUtils.isNativePointer(pointer)) { + pointerUtils.getXY('screen', pointer, page); - page.x += win.window.scrollX; - page.y += win.window.scrollY; + page.x += window.scrollX; + page.y += window.scrollY; } else { - pointerUtils.getXY('page', pointer, page, inertia); + pointerUtils.getXY('page', pointer, page); } return page; @@ -85,14 +72,12 @@ const pointerUtils = { getClientXY: function (pointer, client) { client = client || {}; - const inertia = pointerUtils.isInertiaPointer(pointer); - - // Opera Mobile handles the viewport and scrolling oddly - if (browser.isOperaMobile && !inertia) { - client = pointerUtils.getXY(browser.isOperaMobile? 'screen': 'client', pointer, client, inertia); + if (browser.isOperaMobile && pointerUtils.isNativePointer(pointer)) { + // Opera Mobile handles the viewport and scrolling oddly + pointerUtils.getXY('screen', pointer, client); } else { - client = pointerUtils.getXY('client', pointer, client, inertia); + pointerUtils.getXY('client', pointer, client); } return client; @@ -140,15 +125,26 @@ const pointerUtils = { return touches; }, - touchAverage: function (event) { - const touches = pointerUtils.getTouchPair(event); - - return { - pageX: (touches[0].pageX + touches[1].pageX) / 2, - pageY: (touches[0].pageY + touches[1].pageY) / 2, - clientX: (touches[0].clientX + touches[1].clientX) / 2, - clientY: (touches[0].clientY + touches[1].clientY) / 2, + pointerAverage: function (pointers) { + const average = { + pageX : 0, + pageY : 0, + clientX: 0, + clientY: 0, + screenX: 0, + screenY: 0, }; + + for (const pointer of pointers) { + for (const prop in average) { + average[prop] += pointer[prop]; + } + } + for (const prop in average) { + average[prop] /= pointers.length; + } + + return average; }, touchBBox: function (event) { From f61fe8c532135760767e980a147222066d19913e Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 14 Sep 2015 21:59:10 +0100 Subject: [PATCH 116/131] *: merge recent changes from master --- src/Interaction.js | 6 +++++- src/scope.js | 2 ++ src/utils/pointerUtils.js | 17 ++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 17cfae896..02ef32c68 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -513,13 +513,17 @@ class Interaction { scope.interactions.push(this); } + // set the startCoords if there was no prepared action + if (!this.prepared.name) { + this.setEventXY(this.startCoords, this.pointers); + } + this.prepared.name = action.name; this.prepared.axis = action.axis; this.prepared.edges = action.edges; this.target = interactable; this.element = element; - this.setEventXY(this.startCoords, this.pointers); this.setStartOffsets(action.name, interactable, element, this.modifierOffsets); modifiers.setAll(this, this.startCoords.page, this.modifierStatuses); diff --git a/src/scope.js b/src/scope.js index e774ce262..893caeaaf 100644 --- a/src/scope.js +++ b/src/scope.js @@ -60,6 +60,8 @@ scope.endAllInteractions = function (event) { } }; +scope.prefixedPropREs = utils.prefixedPropREs; + signals.on('listen-to-document', function ({ doc }) { // if document is already known if (utils.contains(scope.documents, doc)) { diff --git a/src/utils/pointerUtils.js b/src/utils/pointerUtils.js index 187e9604a..4da182a21 100644 --- a/src/utils/pointerUtils.js +++ b/src/utils/pointerUtils.js @@ -87,9 +87,24 @@ const pointerUtils = { return isType.isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier; }, + prefixedPropREs: { + webkit: /(Movement[XY]|Radius[XY]|RotationAngle|Force)$/, + }, + pointerExtend: function (dest, source) { for (const prop in source) { - if (prop !== 'webkitMovementX' && prop !== 'webkitMovementY') { + const prefixedPropREs = pointerUtils.prefixedPropREs; + let deprecated = false; + + // skip deprecated prefixed properties + for (const vendor in prefixedPropREs) { + if (prop.indexOf(vendor) === 0 && prefixedPropREs[vendor].test(prop)) { + deprecated = true; + break; + } + } + + if (!deprecated) { dest[prop] = source[prop]; } } From b1ed8fc2ada1d764ebdbce0ad3b608631b0975fe Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 14 Sep 2015 22:22:14 +0100 Subject: [PATCH 117/131] scope: move defs of interact{ion,able}s arrays ... to their respective modules --- src/Interactable.js | 3 +++ src/Interaction.js | 3 +++ src/scope.js | 6 ++---- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Interactable.js b/src/Interactable.js index 97770023e..958cad634 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -4,6 +4,9 @@ const events = require('./utils/events'); const signals = require('./utils/signals'); const actions = require('./actions/base'); +// all set interactables +scope.interactables = []; + /*\ * Interactable [ property ] diff --git a/src/Interaction.js b/src/Interaction.js index 02ef32c68..26edbf966 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -16,6 +16,9 @@ const methodNames = [ 'addPointer', 'removePointer', 'recordPointer', ]; +// all active and idle interactions +scope.interactions = []; + class Interaction { constructor () { this.target = null; // current interactable being interacted with diff --git a/src/scope.js b/src/scope.js index 893caeaaf..05f8f72a6 100644 --- a/src/scope.js +++ b/src/scope.js @@ -9,10 +9,8 @@ scope.signals = require('./utils/signals'); utils.extend(scope, require('./utils/window')); utils.extend(scope, require('./utils/domObjects')); -scope.documents = []; // all documents being listened to -scope.interactables = []; // all set interactables -scope.interactions = []; // all interactions -scope.eventTypes = []; // all event types specific to interact.js +scope.documents = []; // all documents being listened to +scope.eventTypes = []; // all event types specific to interact.js scope.withinInteractionLimit = function (interactable, element, action) { const options = interactable.options; From b9b05f2ad5fa38626b14bf446f9427f4021fc5e6 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 14 Sep 2015 23:06:35 +0100 Subject: [PATCH 118/131] modifiers/base: rename from modifiers/index --- src/InteractEvent.js | 2 +- src/Interaction.js | 2 +- src/modifiers/{index.js => base.js} | 0 src/modifiers/restrict.js | 2 +- src/modifiers/snap.js | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/modifiers/{index.js => base.js} (100%) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index af4e42d7f..1aead2f97 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -1,7 +1,7 @@ const scope = require('./scope'); const utils = require('./utils'); const signals = require('./utils/signals'); -const modifiers = require('./modifiers'); +const modifiers = require('./modifiers/base'); class InteractEvent { constructor (interaction, event, action, phase, element, related) { diff --git a/src/Interaction.js b/src/Interaction.js index 26edbf966..1954e0a64 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -6,7 +6,7 @@ const signals = require('./utils/signals'); const browser = require('./utils/browser'); const finder = require('./utils/interactionFinder'); const actions = require('./actions/base'); -const modifiers = require('./modifiers/'); +const modifiers = require('./modifiers/base'); const animationFrame = utils.raf; const listeners = {}; diff --git a/src/modifiers/index.js b/src/modifiers/base.js similarity index 100% rename from src/modifiers/index.js rename to src/modifiers/base.js diff --git a/src/modifiers/restrict.js b/src/modifiers/restrict.js index 996aef9ab..ce7772e80 100644 --- a/src/modifiers/restrict.js +++ b/src/modifiers/restrict.js @@ -1,4 +1,4 @@ -const modifiers = require('./index'); +const modifiers = require('./base'); const utils = require('../utils'); const defaultOptions = require('../defaultOptions'); diff --git a/src/modifiers/snap.js b/src/modifiers/snap.js index a7acfdde5..67237a29f 100644 --- a/src/modifiers/snap.js +++ b/src/modifiers/snap.js @@ -1,4 +1,4 @@ -const modifiers = require('./index'); +const modifiers = require('./base'); const interact = require('../interact'); const utils = require('../utils'); const defaultOptions = require('../defaultOptions'); From 05e837c661e17d72cec666f297c7b16607acca3a Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Mon, 14 Sep 2015 23:49:37 +0100 Subject: [PATCH 119/131] utils: move getOriginXY to separate module --- src/utils/getOriginXY.js | 29 +++++++++++++++++++++++++++++ src/utils/index.js | 40 +++++----------------------------------- 2 files changed, 34 insertions(+), 35 deletions(-) create mode 100644 src/utils/getOriginXY.js diff --git a/src/utils/getOriginXY.js b/src/utils/getOriginXY.js new file mode 100644 index 000000000..5d7b04b03 --- /dev/null +++ b/src/utils/getOriginXY.js @@ -0,0 +1,29 @@ +const { closest, parentElement, getElementRect } = require('./domUtils'); +const { isElement, isFunction, trySelector } = require('./isType'); + +module.exports = function (interactable, element) { + let origin = interactable.options.origin; + + if (origin === 'parent') { + origin = parentElement(element); + } + else if (origin === 'self') { + origin = interactable.getRect(element); + } + else if (trySelector(origin)) { + origin = closest(element, origin) || { x: 0, y: 0 }; + } + + if (isFunction(origin)) { + origin = origin(interactable && element); + } + + if (isElement(origin)) { + origin = getElementRect(origin); + } + + origin.x = ('x' in origin)? origin.x : origin.left; + origin.y = ('y' in origin)? origin.y : origin.top; + + return origin; +}; diff --git a/src/utils/index.js b/src/utils/index.js index acce303ca..06449154e 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -1,6 +1,5 @@ const utils = module.exports; const extend = require('./extend'); -const defaultOptions = require('../defaultOptions'); const win = require('./window'); utils.blank = function () {}; @@ -37,40 +36,11 @@ utils.easeOutQuad = function (t, b, c, d) { return -c * t*(t-2) + b; }; -utils.getOriginXY = function (interactable, element) { - let origin = interactable - ? interactable.options.origin - : defaultOptions.origin; - - if (origin === 'parent') { - origin = utils.parentElement(element); - } - else if (origin === 'self') { - origin = interactable.getRect(element); - } - else if (utils.trySelector(origin)) { - origin = utils.closest(element, origin) || { x: 0, y: 0 }; - } - - if (utils.isFunction(origin)) { - origin = origin(interactable && element); - } - - if (utils.isElement(origin)) { - origin = utils.getElementRect(origin); - } - - origin.x = ('x' in origin)? origin.x : origin.left; - origin.y = ('y' in origin)? origin.y : origin.top; - - return origin; -}; - - -utils.extend = extend; -utils.hypot = require('./hypot'); -utils.raf = require('./raf'); -utils.browser = require('./browser'); +utils.extend = extend; +utils.hypot = require('./hypot'); +utils.raf = require('./raf'); +utils.browser = require('./browser'); +utils.getOriginXY = require('./getOriginXY'); extend(utils, require('./arr')); extend(utils, require('./isType')); From 8d22a8b8c5c9065d6881835e3793b4ebd5230186 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 15 Sep 2015 10:16:37 +0100 Subject: [PATCH 120/131] *: try to require specific modules of ./utils/ ... instead of require('./utils') --- src/InteractEvent.js | 18 +++++---- src/Interactable.js | 86 ++++++++++++++++++++++-------------------- src/actions/base.js | 4 -- src/actions/drag.js | 4 +- src/actions/gesture.js | 2 +- src/interact.js | 6 +-- src/modifiers/base.js | 4 +- src/utils/signals.js | 4 +- 8 files changed, 65 insertions(+), 63 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 1aead2f97..e8e0690ea 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -1,7 +1,9 @@ -const scope = require('./scope'); -const utils = require('./utils'); -const signals = require('./utils/signals'); -const modifiers = require('./modifiers/base'); +const hypot = require('./utils/hypot'); +const extend = require('./utils/extend'); +const getOriginXY = require('./utils/getOriginXY'); +const signals = require('./utils/signals'); +const modifiers = require('./modifiers/base'); +const scope = require('./scope'); class InteractEvent { constructor (interaction, event, action, phase, element, related) { @@ -10,15 +12,15 @@ class InteractEvent { const sourceX = deltaSource + 'X'; const sourceY = deltaSource + 'Y'; const options = target? target.options: scope.defaultOptions; - const origin = utils.getOriginXY(target, element); + const origin = getOriginXY(target, element); const starting = phase === 'start'; const ending = phase === 'end'; const coords = starting? interaction.startCoords : interaction.curCoords; element = element || interaction.element; - const page = utils.extend({}, coords.page); - const client = utils.extend({}, coords.client); + const page = extend({}, coords.page); + const client = extend({}, coords.client); page.x -= origin.x; page.y -= origin.y; @@ -149,7 +151,7 @@ class InteractEvent { const dy = this[sourceY] - interaction.prevEvent[sourceY]; const dt = this.dt / 1000; - this.speed = utils.hypot(dx, dy) / dt; + this.speed = hypot(dx, dy) / dt; this.velocityX = dx / dt; this.velocityY = dy / dt; } diff --git a/src/Interactable.js b/src/Interactable.js index 958cad634..2ab9367cf 100644 --- a/src/Interactable.js +++ b/src/Interactable.js @@ -1,8 +1,12 @@ -const scope = require('./scope'); -const utils = require('./utils'); +const isType = require('./utils/isType'); const events = require('./utils/events'); const signals = require('./utils/signals'); +const extend = require('./utils/extend'); const actions = require('./actions/base'); +const scope = require('./scope'); + +const { getElementRect } = require('./utils/domUtils'); +const { indexOf, contains } = require('./utils/arr'); // all set interactables scope.interactables = []; @@ -21,7 +25,7 @@ class Interactable { let _window; - if (utils.trySelector(element)) { + if (isType.trySelector(element)) { this.selector = element; const context = options && options.context; @@ -30,7 +34,7 @@ class Interactable { if (context && (_window.Node ? context instanceof _window.Node - : (utils.isElement(context) || context === _window.document))) { + : (isType.isElement(context) || context === _window.document))) { this._context = context; } @@ -63,10 +67,10 @@ class Interactable { setOnEvents (action, phases) { const onAction = 'on' + action; - if (utils.isFunction(phases.onstart) ) { this[onAction + 'start' ] = phases.onstart ; } - if (utils.isFunction(phases.onmove) ) { this[onAction + 'move' ] = phases.onmove ; } - if (utils.isFunction(phases.onend) ) { this[onAction + 'end' ] = phases.onend ; } - if (utils.isFunction(phases.oninertiastart)) { this[onAction + 'inertiastart' ] = phases.oninertiastart ; } + if (isType.isFunction(phases.onstart) ) { this[onAction + 'start' ] = phases.onstart ; } + if (isType.isFunction(phases.onmove) ) { this[onAction + 'move' ] = phases.onmove ; } + if (isType.isFunction(phases.onend) ) { this[onAction + 'end' ] = phases.onend ; } + if (isType.isFunction(phases.oninertiastart)) { this[onAction + 'inertiastart' ] = phases.oninertiastart ; } return this; } @@ -77,15 +81,15 @@ class Interactable { // if this option exists for this action if (option in scope.defaultOptions[action]) { // if the option in the options arg is an object value - if (utils.isObject(options[option])) { + if (isType.isObject(options[option])) { // duplicate the object - this.options[action][option] = utils.extend(this.options[action][option] || {}, options[option]); + this.options[action][option] = extend(this.options[action][option] || {}, options[option]); - if (utils.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { + if (isType.isObject(scope.defaultOptions.perAction[option]) && 'enabled' in scope.defaultOptions.perAction[option]) { this.options[action][option].enabled = options[option].enabled === false? false : true; } } - else if (utils.isBool(options[option]) && utils.isObject(scope.defaultOptions.perAction[option])) { + else if (isType.isBool(options[option]) && isType.isObject(scope.defaultOptions.perAction[option])) { this.options[action][option].enabled = options[option]; } else if (options[option] !== undefined) { @@ -135,7 +139,7 @@ class Interactable { | }); \*/ actionChecker (checker) { - if (utils.isFunction(checker)) { + if (isType.isFunction(checker)) { this.options.actionChecker = checker; return this; @@ -171,11 +175,11 @@ class Interactable { getRect (element) { element = element || this._element; - if (this.selector && !(utils.isElement(element))) { + if (this.selector && !(isType.isElement(element))) { element = this._context.querySelector(this.selector); } - return utils.getElementRect(element); + return getElementRect(element); } /*\ @@ -189,7 +193,7 @@ class Interactable { = (function | object) The checker function or this Interactable \*/ rectChecker (checker) { - if (utils.isFunction(checker)) { + if (isType.isFunction(checker)) { this.getRect = checker; return this; @@ -216,7 +220,7 @@ class Interactable { = (boolean | Interactable) The current setting or this Interactable \*/ styleCursor (newValue) { - if (utils.isBool(newValue)) { + if (isType.isBool(newValue)) { this.options.styleCursor = newValue; return this; @@ -250,7 +254,7 @@ class Interactable { return this; } - if (utils.isBool(newValue)) { + if (isType.isBool(newValue)) { this.options.preventDefault = newValue? 'always' : 'never'; return this; } @@ -272,11 +276,11 @@ class Interactable { = (object) The current origin or this Interactable \*/ origin (newValue) { - if (utils.trySelector(newValue)) { + if (isType.trySelector(newValue)) { this.options.origin = newValue; return this; } - else if (utils.isObject(newValue)) { + else if (isType.isObject(newValue)) { this.options.origin = newValue; return this; } @@ -333,12 +337,12 @@ class Interactable { | interact(element).ignoreFrom('input, textarea, a'); \*/ ignoreFrom (newValue) { - if (utils.trySelector(newValue)) { // CSS selector to match event.target + if (isType.trySelector(newValue)) { // CSS selector to match event.target this.options.ignoreFrom = newValue; return this; } - if (utils.isElement(newValue)) { // specific element + if (isType.isElement(newValue)) { // specific element this.options.ignoreFrom = newValue; return this; } @@ -362,12 +366,12 @@ class Interactable { | interact(element).allowFrom('.handle'); \*/ allowFrom (newValue) { - if (utils.trySelector(newValue)) { // CSS selector to match event.target + if (isType.trySelector(newValue)) { // CSS selector to match event.target this.options.allowFrom = newValue; return this; } - if (utils.isElement(newValue)) { // specific element + if (isType.isElement(newValue)) { // specific element this.options.allowFrom = newValue; return this; } @@ -399,7 +403,7 @@ class Interactable { = (Interactable) this Interactable \*/ fire (iEvent) { - if (!(iEvent && iEvent.type) || !utils.contains(scope.eventTypes, iEvent.type)) { + if (!(iEvent && iEvent.type) || !contains(scope.eventTypes, iEvent.type)) { return this; } @@ -416,7 +420,7 @@ class Interactable { } // interactable.onevent listener - if (utils.isFunction(this[onEvent])) { + if (isType.isFunction(this[onEvent])) { this[onEvent](iEvent); } @@ -443,11 +447,11 @@ class Interactable { = (object) This Interactable \*/ on (eventType, listener, useCapture) { - if (utils.isString(eventType) && eventType.search(' ') !== -1) { + if (isType.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } - if (utils.isArray(eventType)) { + if (isType.isArray(eventType)) { for (let i = 0; i < eventType.length; i++) { this.on(eventType[i], listener, useCapture); } @@ -455,7 +459,7 @@ class Interactable { return this; } - if (utils.isObject(eventType)) { + if (isType.isObject(eventType)) { for (const prop in eventType) { this.on(prop, eventType[prop], listener); } @@ -470,7 +474,7 @@ class Interactable { // convert to boolean useCapture = useCapture? true: false; - if (utils.contains(scope.eventTypes, eventType)) { + if (contains(scope.eventTypes, eventType)) { // if this type of event was never bound to this Interactable if (!(eventType in this._iEvents)) { this._iEvents[eventType] = [listener]; @@ -502,11 +506,11 @@ class Interactable { = (object) This Interactable \*/ off (eventType, listener, useCapture) { - if (utils.isString(eventType) && eventType.search(' ') !== -1) { + if (isType.isString(eventType) && eventType.search(' ') !== -1) { eventType = eventType.trim().split(/ +/); } - if (utils.isArray(eventType)) { + if (isType.isArray(eventType)) { for (let i = 0; i < eventType.length; i++) { this.off(eventType[i], listener, useCapture); } @@ -514,7 +518,7 @@ class Interactable { return this; } - if (utils.isObject(eventType)) { + if (isType.isObject(eventType)) { for (const prop in eventType) { this.off(prop, eventType[prop], listener); } @@ -531,9 +535,9 @@ class Interactable { } // if it is an action event type - if (utils.contains(scope.eventTypes, eventType)) { + if (contains(scope.eventTypes, eventType)) { const eventList = this._iEvents[eventType]; - const index = eventList? utils.indexOf(eventList, listener) : -1; + const index = eventList? indexOf(eventList, listener) : -1; if (index !== -1) { this._iEvents[eventType].splice(index, 1); @@ -560,18 +564,18 @@ class Interactable { = (object) This Interactable \*/ set (options) { - if (!utils.isObject(options)) { + if (!isType.isObject(options)) { options = {}; } - this.options = utils.extend({}, scope.defaultOptions.base); + this.options = extend({}, scope.defaultOptions.base); - const perActions = utils.extend({}, scope.defaultOptions.perAction); + const perActions = extend({}, scope.defaultOptions.perAction); for (const actionName in actions.methodDict) { const methodName = actions.methodDict[actionName]; - this.options[actionName] = utils.extend({}, scope.defaultOptions[actionName]); + this.options[actionName] = extend({}, scope.defaultOptions[actionName]); this.setPerAction(actionName, perActions); @@ -609,7 +613,7 @@ class Interactable { unset () { events.remove(this._element, 'all'); - if (!utils.isString(this.selector)) { + if (!isType.isString(this.selector)) { events.remove(this, 'all'); if (this.options.styleCursor) { this._element.style.cursor = ''; @@ -644,7 +648,7 @@ class Interactable { signals.fire('interactable-unset', { interactable: this }); - scope.interactables.splice(utils.indexOf(scope.interactables, this), 1); + scope.interactables.splice(isType.indexOf(scope.interactables, this), 1); return scope.interact; } diff --git a/src/actions/base.js b/src/actions/base.js index 56c172327..09c0fcc4d 100644 --- a/src/actions/base.js +++ b/src/actions/base.js @@ -1,8 +1,4 @@ -const scope = require('../scope'); - const actions = { - scope, - defaultChecker: function (pointer, event, interaction, element) { const rect = this.getRect(element); let action = null; diff --git a/src/actions/drag.js b/src/actions/drag.js index 4edeca215..41f608f44 100644 --- a/src/actions/drag.js +++ b/src/actions/drag.js @@ -1,8 +1,8 @@ const base = require('./base'); const drop = require('./drop'); -const scope = base.scope; +const scope = require('../scope'); const utils = require('../utils'); -const browser = utils.browser; +const browser = require('../utils/browser'); const InteractEvent = require('../InteractEvent'); const Interactable = require('../Interactable'); const defaultOptions = require('../defaultOptions'); diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 9e03a756f..e67771661 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -2,7 +2,7 @@ const base = require('./base'); const utils = require('../utils'); const InteractEvent = require('../InteractEvent'); const Interactable = require('../Interactable'); -const scope = base.scope; +const scope = require('../scope'); const signals = require('../utils/signals'); const defaultOptions = require('../defaultOptions'); diff --git a/src/interact.js b/src/interact.js index bac950180..a0c8dffcd 100644 --- a/src/interact.js +++ b/src/interact.js @@ -6,10 +6,10 @@ * https://raw.github.com/taye/interact.js/master/LICENSE */ -const scope = require('./scope'); -const utils = require('./utils'); -const browser = utils.browser; +const browser = require('./utils/browser'); const events = require('./utils/events'); +const utils = require('./utils'); +const scope = require('./scope'); const Interactable = require('./Interactable'); scope.dynamicDrop = false; diff --git a/src/modifiers/base.js b/src/modifiers/base.js index 170b50b24..1c82a29cf 100644 --- a/src/modifiers/base.js +++ b/src/modifiers/base.js @@ -1,4 +1,4 @@ -const utils = require('../utils'); +const extend = require('../utils/extend'); const modifiers = { names: [], @@ -23,7 +23,7 @@ const modifiers = { shouldMove: true, }; const target = interaction.target; - const coords = utils.extend({}, coordsArg); + const coords = extend({}, coordsArg); let currentStatus; diff --git a/src/utils/signals.js b/src/utils/signals.js index 82a2c075e..c215e7353 100644 --- a/src/utils/signals.js +++ b/src/utils/signals.js @@ -1,4 +1,4 @@ -const arr = require('./arr'); +const { indexOf } = require('./arr'); const listeners = { // signalName: [listeners], @@ -16,7 +16,7 @@ const signals = { off: function (name, listener) { if (!listeners[name]) { return; } - const index = arr.indexOf(listeners[name], listener); + const index = indexOf(listeners[name], listener); if (index !== -1) { listeners[name].splice(index, 1); From 62243909da99ad4adb56fc0f0e8ae5fc22464f16 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 16 Sep 2015 19:40:15 +0100 Subject: [PATCH 121/131] Interaction: move prevTouchTime out of scope --- src/Interaction.js | 7 +++++-- src/interact.js | 3 --- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index 1954e0a64..e85040042 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -16,6 +16,9 @@ const methodNames = [ 'addPointer', 'removePointer', 'recordPointer', ]; +// for ignoring browser's simulated mouse events +let prevTouchTime = 0; + // all active and idle interactions scope.interactions = []; @@ -1036,7 +1039,7 @@ function doOnInteractions (method) { const matches = []; // [ [pointer, interaction], ...] if (browser.supportsTouch && /touch/.test(event.type)) { - scope.prevTouchTime = new Date().getTime(); + prevTouchTime = new Date().getTime(); for (const pointer of event.changedTouches) { const interaction = finder.search(pointer, event.type, eventTarget, true); @@ -1057,7 +1060,7 @@ function doOnInteractions (method) { // try to ignore mouse events that are simulated by the browser // after a touch event - invalidPointer = invalidPointer || (new Date().getTime() - scope.prevTouchTime < 500); + invalidPointer = invalidPointer || (new Date().getTime() - prevTouchTime < 500); if (!invalidPointer) { let interaction = finder.search(event, event.type, eventTarget, true); diff --git a/src/interact.js b/src/interact.js index a0c8dffcd..187b0ac26 100644 --- a/src/interact.js +++ b/src/interact.js @@ -19,9 +19,6 @@ scope.margin = browser.supportsTouch || browser.supportsPointerEvent? 20: 10; scope.pointerMoveTolerance = 1; -// for ignoring browser's simulated mouse events -scope.prevTouchTime = 0; - // Allow this many interactions to happen simultaneously scope.maxInteractions = Infinity; From 5fa9034abc299302e5e50c5ecb11fc109ee7a4d8 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 19 Sep 2015 18:20:45 +0100 Subject: [PATCH 122/131] Interaction: add and use 'interaction-new' signal --- src/Interaction.js | 42 +++++++++--------------------------------- src/actions/drop.js | 14 ++++++++++++++ src/actions/gesture.js | 15 +++++++++++++++ src/actions/resize.js | 4 ++++ src/pointerEvents.js | 5 +++++ 5 files changed, 47 insertions(+), 33 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index e85040042..6e75cf9db 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -24,22 +24,18 @@ scope.interactions = []; class Interaction { constructor () { - this.target = null; // current interactable being interacted with - this.element = null; // the target element of the interactable - this.dropTarget = null; // the dropzone a drag target might be dropped into - this.dropElement = null; // the element at the time of checking - this.prevDropTarget = null; // the dropzone that was recently dragged away from - this.prevDropElement = null; // the element at the time of checking - - this.prepared = { // action that's ready to be fired on next move event + this.target = null; // current interactable being interacted with + this.element = null; // the target element of the interactable + + this.matches = []; // all selectors that are matched by target element + this.matchElements = []; // corresponding elements + + this.prepared = { // action that's ready to be fired on next move event name : null, axis : null, edges: null, }; - this.matches = []; // all selectors that are matched by target element - this.matchElements = []; // corresponding elements - this.inertiaStatus = { active : false, smoothEnd: false, @@ -66,12 +62,6 @@ class Interaction { this.boundInertiaFrame = () => this.inertiaFrame (); this.boundSmoothEndFrame = () => this.smoothEndFrame(); - this.activeDrops = { - dropzones: [], // the dropzones that are mentioned below - elements : [], // elements of dropzones that accept the target draggable - rects : [], // the rects of the elements mentioned above - }; - // keep track of added pointers this.pointers = []; this.pointerIds = []; @@ -113,33 +103,19 @@ class Interaction { this._curEventTarget = null; this.prevEvent = null; // previous action event - this.tapTime = 0; // time of the most recent tap event - this.prevTap = null; this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 }; this.modifierOffsets = {}; this.modifierStatuses = modifiers.resetStatuses({}); - this.gesture = { - start: { x: 0, y: 0 }, - - startDistance: 0, // distance between two touches of touchStart - prevDistance : 0, - distance : 0, - - scale: 1, // gesture.distance / gesture.startDistance - - startAngle: 0, // angle of line joining two touches - prevAngle : 0, // angle of the previous gesture event - }; - this.pointerIsDown = false; this.pointerWasMoved = false; this._interacting = false; - this.resizeAxes = 'xy'; this.mouse = false; + signals.fire('interaction-new', this); + scope.interactions.push(this); } diff --git a/src/actions/drop.js b/src/actions/drop.js index 65a9bc723..cad7e95f6 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -490,6 +490,20 @@ signals.on('interactable-unset', function ({ interactable }) { interactable.dropzone(false); }); +signals.on('interaction-new', function (interaction) { + interaction.dropTarget = null; // the dropzone a drag target might be dropped into + interaction.dropElement = null; // the element at the time of checking + interaction.prevDropTarget = null; // the dropzone that was recently dragged away from + interaction.prevDropElement = null; // the element at the time of checking + + interaction.activeDrops = { + dropzones: [], // the dropzones that are mentioned below + elements : [], // elements of dropzones that accept the target draggable + rects : [], // the rects of the elements mentioned above + }; + +}); + signals.on('interaction-stop', function ({ interaction }) { interaction.dropTarget = interaction.dropElement = interaction.prevDropTarget = interaction.prevDropElement = null; diff --git a/src/actions/gesture.js b/src/actions/gesture.js index e67771661..82f19e25e 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -157,6 +157,21 @@ signals.on('interactevent-delta', function (arg) { } }); +signals.on('interaction-new', function (interaction) { + interaction.gesture = { + start: { x: 0, y: 0 }, + + startDistance: 0, // distance between two touches of touchStart + prevDistance : 0, + distance : 0, + + scale: 1, // gesture.distance / gesture.startDistance + + startAngle: 0, // angle of line joining two touches + prevAngle : 0, // angle of the previous gesture event + }; +}); + base.gesture = gesture; base.names.push('gesture'); utils.merge(scope.eventTypes, [ diff --git a/src/actions/resize.js b/src/actions/resize.js index 1c835f5df..4c41a405f 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -367,6 +367,10 @@ function checkResizeEdge (name, value, page, element, interactableElement, rect, : utils.matchesUpTo(element, value, interactableElement); } +signals.on('interaction-new', function (interaction) { + interaction.resizeAxes = 'xy'; +}); + signals.on('interactevent-set-delta', function ({ interaction, iEvent, action }) { if (action !== 'resize' || !interaction.resizeAxes) { return; } diff --git a/src/pointerEvents.js b/src/pointerEvents.js index 2e2d8cf6d..89decdb9c 100644 --- a/src/pointerEvents.js +++ b/src/pointerEvents.js @@ -191,6 +191,11 @@ for (let i = 0; i < simpleSignals.length; i++) { signals.on(simpleSignals[i], createSignalListener(simpleEvents[i])); } +signals.on('interaction-new', function (interaction) { + interaction.prevTap = null; // the most recent tap event on this interaction + interaction.tapTime = 0; // time of the most recent tap event +}); + utils.merge(scope.eventTypes, [ 'down', 'move', From e340646b88991a474cc819120c9b3d238efb7256 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 22 Sep 2015 09:19:19 +0100 Subject: [PATCH 123/131] InteractEvent: improve signals --- src/InteractEvent.js | 121 ++++++++++++++++++++++-------------------- src/actions/resize.js | 4 +- 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index e8e0690ea..32ec3370f 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -11,7 +11,6 @@ class InteractEvent { const deltaSource = (target && target.options || scope.defaultOptions).deltaSource; const sourceX = deltaSource + 'X'; const sourceY = deltaSource + 'Y'; - const options = target? target.options: scope.defaultOptions; const origin = getOriginXY(target, element); const starting = phase === 'start'; const ending = phase === 'end'; @@ -28,18 +27,18 @@ class InteractEvent { client.x -= origin.x; client.y -= origin.y; - this.ctrlKey = event.ctrlKey; - this.altKey = event.altKey; - this.shiftKey = event.shiftKey; - this.metaKey = event.metaKey; - this.button = event.button; - this.buttons = event.buttons; - this.target = element; - this.t0 = interaction.downTimes[0]; - this.type = action + (phase || ''); - - this.interaction = interaction; - this.interactable = target; + this.ctrlKey = event.ctrlKey; + this.altKey = event.altKey; + this.shiftKey = event.shiftKey; + this.metaKey = event.metaKey; + this.button = event.button; + this.buttons = event.buttons; + this.target = element; + this.relatedTarget = related || null; + this.t0 = interaction.downTimes[interaction.downTimes.length - 1]; + this.type = action + (phase || ''); + this.interaction = interaction; + this.interactable = target; for (let i = 0; i < modifiers.names.length; i++) { const modifierName = modifiers.names[i]; @@ -58,7 +57,6 @@ class InteractEvent { this.clientX0 = interaction.startCoords.client.x - origin.x; this.clientY0 = interaction.startCoords.client.y - origin.y; - const inertiaStatus = interaction.inertiaStatus; const signalArg = { interaction, event, @@ -75,55 +73,14 @@ class InteractEvent { iEvent: this, }; + const inertiaStatus = interaction.inertiaStatus; + if (inertiaStatus.active) { this.detail = 'inertia'; } - if (related) { - this.relatedTarget = related; - } - - // end event dx, dy is difference between start and end points - if (ending) { - if (deltaSource === 'client') { - this.dx = client.x - interaction.startCoords.client.x; - this.dy = client.y - interaction.startCoords.client.y; - } - else { - this.dx = page.x - interaction.startCoords.page.x; - this.dy = page.y - interaction.startCoords.page.y; - } - } - else if (starting) { - this.dx = 0; - this.dy = 0; - } - // copy properties from previousmove if starting inertia - else if (phase === 'inertiastart') { - this.dx = interaction.prevEvent.dx; - this.dy = interaction.prevEvent.dy; - } - else { - if (deltaSource === 'client') { - this.dx = client.x - interaction.prevEvent.clientX; - this.dy = client.y - interaction.prevEvent.clientY; - } - else { - this.dx = page.x - interaction.prevEvent.pageX; - this.dy = page.y - interaction.prevEvent.pageY; - } - } - if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' - && !inertiaStatus.active - && options[action].inertia && options[action].inertia.zeroResumeDelta) { - - inertiaStatus.resumeDx += this.dx; - inertiaStatus.resumeDy += this.dy; - - this.dx = this.dy = 0; - } - - signals.fire('interactevent-set-delta', signalArg); + signals.fire('interactevent-new', signalArg); + signals.fire('interactevent-' + action, signalArg); if (starting) { this.timeStamp = interaction.downTimes[0]; @@ -207,4 +164,50 @@ class InteractEvent { } } +signals.on('interactevent-new', function ({ iEvent, interaction, action, phase, ending, starting, + page, client, deltaSource, inertiaStatus }) { + // end event dx, dy is difference between start and end points + if (ending) { + if (deltaSource === 'client') { + iEvent.dx = client.x - interaction.startCoords.client.x; + iEvent.dy = client.y - interaction.startCoords.client.y; + } + else { + iEvent.dx = page.x - interaction.startCoords.page.x; + iEvent.dy = page.y - interaction.startCoords.page.y; + } + } + else if (starting) { + iEvent.dx = 0; + iEvent.dy = 0; + } + // copy properties from previousmove if starting inertia + else if (phase === 'inertiastart') { + iEvent.dx = interaction.prevEvent.dx; + iEvent.dy = interaction.prevEvent.dy; + } + else { + if (deltaSource === 'client') { + iEvent.dx = client.x - interaction.prevEvent.clientX; + iEvent.dy = client.y - interaction.prevEvent.clientY; + } + else { + iEvent.dx = page.x - interaction.prevEvent.pageX; + iEvent.dy = page.y - interaction.prevEvent.pageY; + } + } + + const options = interaction.target.options; + + if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' + && !inertiaStatus.active + && options[action].inertia && options[action].inertia.zeroResumeDelta) { + + inertiaStatus.resumeDx += iEvent.dx; + inertiaStatus.resumeDy += iEvent.dy; + + iEvent.dx = iEvent.dy = 0; + } +}); + module.exports = InteractEvent; diff --git a/src/actions/resize.js b/src/actions/resize.js index 4c41a405f..82d1f8b74 100644 --- a/src/actions/resize.js +++ b/src/actions/resize.js @@ -371,8 +371,8 @@ signals.on('interaction-new', function (interaction) { interaction.resizeAxes = 'xy'; }); -signals.on('interactevent-set-delta', function ({ interaction, iEvent, action }) { - if (action !== 'resize' || !interaction.resizeAxes) { return; } +signals.on('interactevent-resize', function ({ interaction, iEvent }) { + if (!interaction.resizeAxes) { return; } const options = interaction.target.options; From 4a4be17cd4b7625fb4659db583a359a409619f33 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 3 Oct 2015 21:32:47 +0100 Subject: [PATCH 124/131] InteractEvent: fix use of interaction-new arg inertiaStatus isn't given as an arg property. Get it from interaction.inertiaStatus --- src/InteractEvent.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/InteractEvent.js b/src/InteractEvent.js index 32ec3370f..b7cb9e18d 100644 --- a/src/InteractEvent.js +++ b/src/InteractEvent.js @@ -165,7 +165,7 @@ class InteractEvent { } signals.on('interactevent-new', function ({ iEvent, interaction, action, phase, ending, starting, - page, client, deltaSource, inertiaStatus }) { + page, client, deltaSource }) { // end event dx, dy is difference between start and end points if (ending) { if (deltaSource === 'client') { @@ -198,6 +198,7 @@ signals.on('interactevent-new', function ({ iEvent, interaction, action, phase, } const options = interaction.target.options; + const inertiaStatus = interaction.inertiaStatus; if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia' && !inertiaStatus.active From 17d7c7c73eecf75fd7e6a8aade857ad483ebfafc Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 3 Oct 2015 21:37:27 +0100 Subject: [PATCH 125/131] Interaction: fix doOnInteraction with PointerEvent --- src/Interaction.js | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/src/Interaction.js b/src/Interaction.js index b750a56e1..05fc5aa3d 100644 --- a/src/Interaction.js +++ b/src/Interaction.js @@ -1010,11 +1010,9 @@ function doOnInteractions (method) { prevTouchTime = new Date().getTime(); for (const pointer of event.changedTouches) { - const interaction = finder.search(pointer, event.type, eventTarget, true); + const interaction = finder.search(pointer, event.type, eventTarget); - if (interaction) { - matches.push([pointer, interaction]); - } + matches.push([pointer, interaction || new Interaction]); } } else { @@ -1029,22 +1027,20 @@ function doOnInteractions (method) { // try to ignore mouse events that are simulated by the browser // after a touch event invalidPointer = invalidPointer || (new Date().getTime() - prevTouchTime < 500); + } - if (!invalidPointer) { - let interaction = finder.search(event, event.type, eventTarget, true); - - if (!interaction && (/mouse/i.test(event.pointerType || event.type) - // MSPointerEvent.MSPOINTER_TYPE_MOUSE - || event.pointerType === 4)) { + if (!invalidPointer) { + let interaction = finder.search(event, event.type, eventTarget); - interaction = new Interaction(); - interaction.mouse = true; - } + if (!interaction) { - if (interaction) { - matches.push([event, interaction]); - } + interaction = new Interaction(); + interaction.mouse = (/mouse/i.test(event.pointerType || event.type) + // MSPointerEvent.MSPOINTER_TYPE_MOUSE + || event.pointerType === 4); } + + matches.push([event, interaction]); } } From af85914ae3958e4efdc2927a3a121e362c900c77 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Sat, 3 Oct 2015 21:46:08 +0100 Subject: [PATCH 126/131] actions/gesture: fix interactevent-gesture signal --- src/actions/gesture.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/actions/gesture.js b/src/actions/gesture.js index 82f19e25e..d125b1632 100644 --- a/src/actions/gesture.js +++ b/src/actions/gesture.js @@ -122,10 +122,10 @@ Interactable.prototype.gesturable = function (options) { return this.options.gesture; }; -signals.on('interactevent-delta', function (arg) { +signals.on('interactevent-gesture', function (arg) { if (arg.action !== 'gesture') { return; } - const { interaction, iEvent, starting, ending, deltaSource } = {arg}; + const { interaction, iEvent, starting, ending, deltaSource } = arg; const pointers = interaction.pointers; iEvent.touches = [pointers[0], pointers[1]]; From ff1eee875168c0faf577678da26e4df250839a13 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 6 Oct 2015 08:45:35 +0100 Subject: [PATCH 127/131] dist: rename from build --- .gitignore | 2 +- gulp/config.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index dd87e2d73..f06235c46 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ node_modules -build +dist diff --git a/gulp/config.js b/gulp/config.js index 2ff1eec2f..8b39a9c9b 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -1,4 +1,4 @@ -var dest = "./build"; +var dest = "./dist"; var src = './src'; module.exports = { From e12150e59a91c2030ed350ae580e0806f6c4c7e0 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 6 Oct 2015 14:13:47 +0100 Subject: [PATCH 128/131] gulp/tasks/default: create dist/ before bundling Without doing so dist/interact.js.map is not created correctly. --- gulp/config.js | 1 + gulp/tasks/default.js | 9 ++++++++- package.json | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/gulp/config.js b/gulp/config.js index 8b39a9c9b..80e8e3550 100644 --- a/gulp/config.js +++ b/gulp/config.js @@ -2,6 +2,7 @@ var dest = "./dist"; var src = './src'; module.exports = { + dest: dest, browserSync: { server: { // Serve up our build folder diff --git a/gulp/tasks/default.js b/gulp/tasks/default.js index 93fc34204..bfa677c77 100644 --- a/gulp/tasks/default.js +++ b/gulp/tasks/default.js @@ -1,4 +1,11 @@ var gulp = require('gulp'); +var mkdirp = require('mkdirp'); +var config = require('../config'); + +gulp.task('mkdest', function () { + mkdirp.sync(config.dest, 0755); +}); + +gulp.task('build', ['lint', 'mkdest', 'browserify']); -gulp.task('build', ['lint', 'browserify']); gulp.task('default', ['build']); diff --git a/package.json b/package.json index 4e1fa9eb2..9173fa301 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "karma-nyan-reporter": "^0.2.2", "lodash": "^3.10.1", "merge-stream": "^1.0.0", + "mkdirp": "^0.5.1", "mocha": "^2.3.0", "pretty-hrtime": "^1.0.0", "require-dir": "^0.3.0", From fa2cba8e63bfe534c936dd895153932ff6d9035c Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Tue, 6 Oct 2015 15:34:17 +0100 Subject: [PATCH 129/131] *: remove deprecated methods Interactable.{accept,dropzone} interact.margin --- src/actions/drop.js | 121 ++++++++++---------------------------------- src/interact.js | 22 -------- 2 files changed, 27 insertions(+), 116 deletions(-) diff --git a/src/actions/drop.js b/src/actions/drop.js index cad7e95f6..dfd378728 100644 --- a/src/actions/drop.js +++ b/src/actions/drop.js @@ -290,14 +290,37 @@ function getDropEvents (interaction, pointerEvent, dragEvent) { * - `dragmove` when a draggable that has entered the dropzone is moved * - `drop` when a draggable is dropped into this dropzone * - * Use the `accept` option to allow only elements that match the given CSS selector or element. + * Use the `accept` option to allow only elements that match the given CSS + * selector or element. The value can be: + * + * - **an Element** - only that element can be dropped into this dropzone. + * - **a string**, - the element being dragged must match it as a CSS selector. + * - **`null`** - accept options is cleared - it accepts any element. + * + * Use the `overlap` option to set how drops are checked for. The allowed + * values are: * - * Use the `overlap` option to set how drops are checked for. The allowed values are: * - `'pointer'`, the pointer must be over the dropzone (default) * - `'center'`, the draggable element's center must be over the dropzone * - a number from 0-1 which is the `(intersection area) / (draggable area)`. - * e.g. `0.5` for drop to happen when half of the area of the - * draggable is over the dropzone + * e.g. `0.5` for drop to happen when half of the area of the draggable is + * over the dropzone + * + * Use the `checker` option to specify a function to check if a dragged + * element is over this Interactable. + * + | interact(target) + | .dropChecker(function(dragEvent, // related dragmove or dragend event + | event, // TouchEvent/PointerEvent/MouseEvent + | dropped, // bool result of the default checker + | dropzone, // dropzone Interactable + | dropElement, // dropzone elemnt + | draggable, // draggable Interactable + | draggableElement) {// draggable element + | + | return dropped && event.target.hasAttribute('allow-drop'); + | } + * * - options (boolean | object | null) #optional The new value to be set. | interact('.drop').dropzone({ @@ -396,96 +419,6 @@ Interactable.prototype.dropCheck = function (dragEvent, event, draggable, dragga return dropped; }; -/*\ - * Interactable.dropChecker - [ method ] - * - * DEPRECATED. Use interactable.dropzone({ checker: function... }) instead. - * - * Gets or sets the function used to check if a dragged element is - * over this Interactable. - * - - checker (function) #optional The function that will be called when checking for a drop - = (Function | Interactable) The checker function or this Interactable - * - * The checker function takes the following arguments: - * - - dragEvent (InteractEvent) The related dragmove or dragend event - - event (TouchEvent | PointerEvent | MouseEvent) The user move/up/end Event related to the dragEvent - - dropped (boolean) The value from the default drop checker - - dropzone (Interactable) The dropzone interactable - - dropElement (Element) The dropzone element - - draggable (Interactable) The Interactable being dragged - - draggableElement (Element) The actual element that's being dragged - * - > Usage: - | interact(target) - | .dropChecker(function(dragEvent, // related dragmove or dragend event - | event, // TouchEvent/PointerEvent/MouseEvent - | dropped, // bool result of the default checker - | dropzone, // dropzone Interactable - | dropElement, // dropzone elemnt - | draggable, // draggable Interactable - | draggableElement) {// draggable element - | - | return dropped && event.target.hasAttribute('allow-drop'); - | } -\*/ -Interactable.prototype.dropChecker = utils.warnOnce(function (checker) { - if (utils.isFunction(checker)) { - this.options.drop.checker = checker; - - return this; - } - if (checker === null) { - delete this.options.getRect; - - return this; - } - - return this.options.drop.checker; -}, 'Interactable#dropChecker is deprecated. use Interactable#dropzone({ dropChecker: checkerFunction }) instead'); - -/*\ - * Interactable.accept - [ method ] - * - * Deprecated. add an `accept` property to the options object passed to - * @Interactable.dropzone instead. - * - * Gets or sets the Element or CSS selector match that this - * Interactable accepts if it is a dropzone. - * - - newValue (Element | string | null) #optional - * If it is an Element, then only that element can be dropped into this dropzone. - * If it is a string, the element being dragged must match it as a selector. - * If it is null, the accept options is cleared - it accepts any element. - * - = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable -\*/ -Interactable.prototype.accept = utils.warnOnce(function (newValue) { - if (utils.isElement(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - // test if it is a valid CSS selector - if (utils.trySelector(newValue)) { - this.options.drop.accept = newValue; - - return this; - } - - if (newValue === null) { - delete this.options.drop.accept; - - return this; - } - - return this.options.drop.accept; -}, 'Interactable#accept is deprecated. use Interactable#dropzone({ accept: target }) instead'); - signals.on('interactable-unset', function ({ interactable }) { interactable.dropzone(false); }); diff --git a/src/interact.js b/src/interact.js index 7f38269b1..046c20523 100644 --- a/src/interact.js +++ b/src/interact.js @@ -261,28 +261,6 @@ interact.getElementClientRect = utils.getElementClientRect; interact.matchesSelector = utils.matchesSelector; interact.closest = utils.closest; -/*\ - * interact.margin - [ method ] - * - * Deprecated. Use `interact(target).resizable({ margin: number });` instead. - * Returns or sets the margin for autocheck resizing used in - * @Interactable.getAction. That is the distance from the bottom and right - * edges of an element clicking in which will start resizing - * - - newValue (number) #optional - = (number | interact) The current margin value or interact -\*/ -interact.margin = utils.warnOnce(function (newvalue) { - if (utils.isNumber(newvalue)) { - scope.margin = newvalue; - - return interact; - } - return scope.margin; -}, -'interact.margin is deprecated. Use interact(target).resizable({ margin: number }); instead.') ; - /*\ * interact.supportsTouch [ method ] From 1fa8bd64cbc3f5ab313d44563628ae3d41bebf35 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 7 Oct 2015 09:48:20 +0100 Subject: [PATCH 130/131] docs: add dr.js dependency and docs gulp task --- docs/dr.json | 40 +++++++++++++++++++++++++++++++++------- docs/template.dot | 2 +- gulp/tasks/docs.js | 5 +++++ package.json | 1 + 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 gulp/tasks/docs.js diff --git a/docs/dr.json b/docs/dr.json index 86a92cd8c..1130a054e 100644 --- a/docs/dr.json +++ b/docs/dr.json @@ -1,9 +1,35 @@ { - "title": "interact.js", - "output": "index.html", - "template": "template.dot", - "files": [{ - "url": "../interact.js", - "link": "https://github.com/taye/interact.js/blob/3ddcd9f/interact.js" - }] + "title": "interact.js", + "output": "index.html", + "template": "template.dot", + "files": [ + { + "url": "../src/actions/drag.js", + "link": "https://github.com/taye/interact.js/blob/modularization/src/actions/drag.js" + }, + { + "url": "../src/actions/drop.js", + "link": "https://github.com/taye/interact.js/blob/modularization/src/actions/drop.js" + }, + { + "url": "../src/actions/resize.js", + "link": "https://github.com/taye/interact.js/blob/modularization/src/actions/resize.js" + }, + { + "url": "../src/actions/gesture.js", + "link": "https://github.com/taye/interact.js/blob/modularization/src/actions/gesture.js" + }, + { + "url": "../src/Interactable.js", + "link": "https://github.com/taye/interact.js/blob/modularization/src/Interactable.js" + }, + { + "url": "../src/interact.js", + "link": "https://github.com/taye/interact.js/blob/modularization/src/interact.js" + }, + { + "url": "../src/Interaction.js", + "link": "https://github.com/taye/interact.js/blob/modularization/src/Interaction.js" + } + ] } diff --git a/docs/template.dot b/docs/template.dot index 14b93d492..69b541034 100644 --- a/docs/template.dot +++ b/docs/template.dot @@ -45,7 +45,7 @@ {{~it.out :item:index}}
-

{{=item[0].name}}

+

{{=item[0].name}}

diff --git a/gulp/tasks/docs.js b/gulp/tasks/docs.js new file mode 100644 index 000000000..5445128f2 --- /dev/null +++ b/gulp/tasks/docs.js @@ -0,0 +1,5 @@ +var gulp = require('gulp'); + +gulp.task('docs', module.exports = function () { + require('child_process').execSync('./node_modules/.bin/dr.js docs/dr.json'); +}); diff --git a/package.json b/package.json index 9173fa301..8753c3527 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "browser-sync": "^2.9.1", "browserify": "^11.0.1", "chai": "^3.2.0", + "dr.js": "taye/dr.js", "eslint": "^1.3.1", "exorcist": "^0.4.0", "gulp": "^3.9.0", From 24d31ea3d2a51dff5a238d3d3b68472cdf5e8fb0 Mon Sep 17 00:00:00 2001 From: Taye Adeyemi Date: Wed, 7 Oct 2015 22:56:18 +0100 Subject: [PATCH 131/131] demo: reference dist/interact.js instead of build/ --- demo/dropzones.html | 4 ++-- demo/events.html | 2 +- demo/gallery.html | 2 +- demo/html_svg.html | 2 +- demo/iframes-middle.html | 2 +- demo/snap.html | 2 +- demo/star.svg | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/demo/dropzones.html b/demo/dropzones.html index f7e1594d7..8666a6e86 100644 --- a/demo/dropzones.html +++ b/demo/dropzones.html @@ -3,7 +3,7 @@ Highlight dropzones with interact.js - + @@ -21,4 +21,4 @@ - \ No newline at end of file + diff --git a/demo/events.html b/demo/events.html index d94ed625c..d7639f1f5 100644 --- a/demo/events.html +++ b/demo/events.html @@ -9,7 +9,7 @@ - + diff --git a/demo/gallery.html b/demo/gallery.html index a37e2bd81..2face745f 100644 --- a/demo/gallery.html +++ b/demo/gallery.html @@ -88,7 +88,7 @@ } - + diff --git a/demo/html_svg.html b/demo/html_svg.html index 118cb004f..f44bff37f 100644 --- a/demo/html_svg.html +++ b/demo/html_svg.html @@ -5,7 +5,7 @@ interact.js demo - + diff --git a/demo/iframes-middle.html b/demo/iframes-middle.html index 96e84ea6a..acb69fcdc 100644 --- a/demo/iframes-middle.html +++ b/demo/iframes-middle.html @@ -1,7 +1,7 @@ - + diff --git a/demo/snap.html b/demo/snap.html index f0da5b464..6e638e7e5 100644 --- a/demo/snap.html +++ b/demo/snap.html @@ -5,7 +5,7 @@ interact.js drag snapping - + diff --git a/demo/star.svg b/demo/star.svg index 901be5f92..a000502b7 100644 --- a/demo/star.svg +++ b/demo/star.svg @@ -1,7 +1,7 @@ - +